d3 force not applying x scale properly - d3.js

I want to apply a scale to circles generated with d3 force from an array.
This code produces the right x axis, but there are loads of circles produced with the class 'goalamount' and they are all offscreen by tens of thousands of pixels. There should only be six circles in the goalamount class and they should all scale to the xaxis - what am I doing wrong?
const data = [{
x: 2020,
cx: 0,
colour: "#69306D",
scY: 0,
y2: 50,
rad: 10,
amt: 5000
},
{
x: 2020,
cx: 0,
colour: "#247BA0",
scY: 0,
y2: 50,
rad: 10,
amt: 5000
},
{
x: 2020,
cx: 0,
colour: "#3F762C",
y1: 0,
y2: 50,
rad: 10,
amt: 5000
},
{
x: 2020,
cx: 0,
colour: "#F25F5C",
y1: 0,
y2: 50,
rad: 10,
amt: 5000
},
{
x: 2022,
cx: 0,
colour: "#0C3957",
y1: 0,
y2: 170,
rad: 10,
amt: 5000
},
{
x: 2055,
cx: 0,
colour: "#BF802F",
y1: 0,
y2: 50,
rad: 10,
amt: 15000
}
];
const maxYear = Math.max.apply(Math, data.map(function(o) {
return o.x;
}));
const svg = d3.select("svg");
const pxX = svg.attr("width");
const pxY = svg.attr("height");
let tickLabelOffset = 170;
let minDotX = Math.min.apply(Math, data.map(function(o) {
return o.y1;
}))
if (minDotX < -20) {
tickLabelOffset += minDotX + 20;
}
const makeScale = (arr, accessor, range) => {
return d3.scaleLinear()
.domain(d3.extent(arr, accessor))
.range(range)
.nice()
}
const thisYear = new Date().getFullYear()
let tickTens = [];
for (let i = thisYear; i < maxYear; i++) {
if (i % 10 === 0) {
tickTens.push(i)
}
}
const scX = makeScale(data, d => d.x, [0, pxX - 200]);
const scX1 = makeScale(data, d => d.x, [0, pxX - 2020]);
const scY = d3.scaleLinear().domain([0, 100]).range([0, 100]);
const g = d3.axisBottom(scX).tickValues(
tickTens.map((tickVal) => {
return tickVal
})
)
const rad = d3.scaleLinear()
.domain(d3.extent(data, d => d.rad))
.range([3, 10]);
const amt = d3.scaleLinear()
.domain(d3.extent(data, d => d.amt))
.range([20, 50]);
for (let dotindex = 0; dotindex < data.length; dotindex++) {
if (data[dotindex - 1]) {
if (data[dotindex - 1].x === data[dotindex].x) {
data[dotindex].scY = data[dotindex - 1].scY - 20
}
}
}
const ticked = () => {
var u = d3.select('svg')
.append("g")
.attr("class", "goalAmounts")
.selectAll('goalAmounts')
.data(data)
u.enter()
.append("circle")
// .attr( "transform", "translate(" + 2000 + "," + 50 + ")")
.attr("r", d => amt(d.amt))
.merge(u)
.attr("fill", d => d.colour)
.attr("cx", d => scX(d.x))
.attr("cy", d => scY(d.y2))
u.exit().remove()
}
svg.append("g")
.attr("transform", "translate(" + 50 + "," + (pxY - 200) + ")")
.call(g)
.selectAll(".tick text")
.attr("fill", "#7A7A7A")
svg.selectAll("circle")
.data(data)
.enter()
.append("g")
.attr("class", "circles")
.append("circle")
.attr("transform", "translate(" + 100 + "," + 650 + ")")
.attr("fill", "white")
.attr("stroke", d => d.colour)
.attr("stroke-width", "2px")
.attr("cx", d => scX(d.x))
.attr("cy", d => scY(d.y2))
.attr("r", d => rad(d.rad));
svg.selectAll(".domain")
.attr("stroke", "#BDBDBD")
.attr("stroke-width", "2px")
.attr("transform", "translate(" + 50 + "," + 150 + ")")
svg.selectAll(".tick line")
.attr("stroke", "#BDBDBD")
.attr("stroke-width", "4px")
.attr("transform", "translate(" + 50 + "," + 150 + ")")
svg.selectAll(".tick text")
.attr("font-size", 20)
.attr("transform", "translate(" + 50 + "," + tickLabelOffset + ")")
.attr("font-weight", "bold")
.attr("dy", "0.5em")
d3.forceSimulation(data)
.force('charge', d3.forceManyBody())
.force('center', d3.forceCenter(pxX / 2, pxY / 2))
.force('collision', d3.forceCollide().radius(function(d) {
return d.amt
}))
.on('tick', ticked);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div class="App">
<svg id="demo1" width="1200" height="700">
</svg>
</div>

Use d3.forceX and d3.forceY instead, so you draw the nodes towards their intended position. Also, d3-force populates x and y properties of the nodes, so you need to use d.x1 or something instead. scX(d.x) caused the huge values of the nodes.
const data = [{
x1: 2020,
cx: 0,
colour: "#69306D",
scY: 0,
y2: 50,
rad: 10,
amt: 5000
},
{
x1: 2020,
cx: 0,
colour: "#247BA0",
scY: 0,
y2: 50,
rad: 10,
amt: 5000
},
{
x1: 2020,
cx: 0,
colour: "#3F762C",
y1: 0,
y2: 50,
rad: 10,
amt: 5000
},
{
x1: 2020,
cx: 0,
colour: "#F25F5C",
y1: 0,
y2: 50,
rad: 10,
amt: 5000
},
{
x1: 2022,
cx: 0,
colour: "#0C3957",
y1: 0,
y2: 170,
rad: 10,
amt: 5000
},
{
x1: 2055,
cx: 0,
colour: "#BF802F",
y1: 0,
y2: 50,
rad: 10,
amt: 15000
}
];
const maxYear = Math.max.apply(Math, data.map(function(o) {
return o.x1;
}));
const svg = d3.select("svg");
const pxX = svg.attr("width");
const pxY = svg.attr("height");
let tickLabelOffset = 170;
let minDotX = Math.min.apply(Math, data.map(function(o) {
return o.y1;
}))
if (minDotX < -20) {
tickLabelOffset += minDotX + 20;
}
const makeScale = (arr, accessor, range) => {
return d3.scaleLinear()
.domain(d3.extent(arr, accessor))
.range(range)
.nice()
}
const thisYear = new Date().getFullYear()
let tickTens = [];
for (let i = thisYear; i < maxYear; i++) {
if (i % 10 === 0) {
tickTens.push(i)
}
}
const scX = makeScale(data, d => d.x1, [0, pxX - 200]);
const scX1 = makeScale(data, d => d.x1, [0, pxX - 2020]);
const scY = d3.scaleLinear().domain([0, 100]).range([0, 100]);
const g = d3.axisBottom(scX).tickValues(
tickTens.map((tickVal) => {
return tickVal
})
)
const rad = d3.scaleLinear()
.domain(d3.extent(data, d => d.rad))
.range([3, 10]);
const amt = d3.scaleLinear()
.domain(d3.extent(data, d => d.amt))
.range([20, 50]);
for (let dotindex = 0; dotindex < data.length; dotindex++) {
if (data[dotindex - 1]) {
if (data[dotindex - 1].x1 === data[dotindex].x1) {
data[dotindex].scY = data[dotindex - 1].scY - 20
}
}
}
const ticked = () => {
circles
.attr("cx", d => d.x)
.attr("cy", d => d.y);
}
svg.append("g")
.attr("transform", "translate(" + 50 + "," + (pxY - 200) + ")")
.call(g)
.selectAll(".tick text")
.attr("fill", "#7A7A7A")
const circles = svg.append("g")
.attr("class", "circles")
.attr( "transform", "translate(" + 100 + "," + 100 + ")")
.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("fill", "white")
.attr("stroke", d => d.colour)
.attr("stroke-width", "2px")
.attr("cx", d => scX(d.x1))
.attr("cy", d => scY(d.y2))
.attr("r", d => rad(d.rad));
svg.selectAll(".domain")
.attr("stroke", "#BDBDBD")
.attr("stroke-width", "2px")
.attr("transform", "translate(" + 50 + "," + 150 + ")")
svg.selectAll(".tick line")
.attr("stroke", "#BDBDBD")
.attr("stroke-width", "4px")
.attr("transform", "translate(" + 50 + "," + 150 + ")")
svg.selectAll(".tick text")
.attr("font-size", 20)
.attr("transform", "translate(" + 50 + "," + tickLabelOffset + ")")
.attr("font-weight", "bold")
.attr("dy", "0.5em")
d3.forceSimulation(data)
.force("x", d3.forceX(d => scX(d.x1)))
.force("y", d3.forceY(d => scY(d.y2)))
.force('collision', d3.forceCollide().radius(d => rad(d.rad)))
.on("tick", ticked);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div class="App">
<svg id="demo1" width="1200" height="700">
</svg>
</div>

Related

D3 multi line chart

This is the data I have to draw on a D3 line graph, where the lines are Addon, Loss, New, Repeat. Same should be shown in the legend:
dataf: [
{
drugs: "BENEPALI",
Addon: 328,
Loss: 560,
New: 250,
Repeat: 321,
},
{
drugs: "CIMZIA",
Addon: 328,
Loss: 460,
New: 250,
Repeat: 321,
},
{
drugs: "COSENTYX",
Addon: 265,
Loss: 360,
New: 250,
Repeat: 421,
},
{
drugs: "ENBREL",
Addon: 281,
Loss: 260,
New: 150,
Repeat: 321,
},
{
drugs: "TALTZ",
Addon: 290,
Loss: 560,
New: 150,
Repeat: 321,
},
],
I have some success with drawing the legend and plotting axis but not with line. I think there is a problem with the data.
Here is the code:
<template>
<div>
<b-button #click="esCall(dataf, stacklist, 1460, 350)">es call</b-button>
<div id="kmCurveLegend" style="margin-left: -150px; margin-top: 30px"></div>
<div id="kmCurve" style="margin-top: 10px"></div>
</div>
</template>
<script>
import * as d3 from "d3";
import d3Tip from "d3-tip";
export default {
data() {
return {
dataf: [
{
drugs: "BENEPALI",
Addon: 328,
Loss: 560,
New: 250,
Repeat: 321,
},
{
drugs: "CIMZIA",
Addon: 328,
Loss: 460,
New: 250,
Repeat: 321,
},
{
drugs: "COSENTYX",
Addon: 265,
Loss: 360,
New: 250,
Repeat: 421,
},
{
drugs: "ENBREL",
Addon: 281,
Loss: 260,
New: 150,
Repeat: 321,
},
{
drugs: "TALTZ",
Addon: 290,
Loss: 560,
New: 150,
Repeat: 321,
},
],
stacklist: ["Addon", "Loss", "New", "Repeat"],
xlabel: [],
sumstat: null,
mktSegName: "",
current_stacks: null,
height: 0,
width: 0,
svg: null,
res: null,
x1: null,
minWidth: null,
minHeight: null,
stacks: null,
totalStacks: null,
data: null,
};
},
mounted() {
// this.kmLayout();
},
methods: {
esCall(dataf, stacklist, w, h) {
this.stacks = stacklist;
this.data = dataf;
this.minWidth = w;
this.minHeight = h;
this.kmLayout(this.minWidth, this.minHeight, this.data, this.stacks);
},
kmLayout(w, h, data, stacklist) {
d3.selectAll("#KMGraph").remove();
// set the dimensions and margins of the graph
var margin = { top: 10, right: 30, bottom: 30, left: 60 };
this.width = w - margin.left - margin.right;
this.height = h - margin.top - margin.bottom;
// append the svg object to the body of the page
this.svg = d3
.select("#kmCurve")
.append("svg")
.attr("id", "KMGraph")
.attr("width", this.width + margin.left + margin.right)
.attr("height", this.height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
//Grouping according to multiple MS
var updatedData = [];
for (var i in data) {
updatedData.push(data[i]);
}
this.sumstat = d3.group(
updatedData,
(d) => d.Addon && d.Loss && d.New && d.Repeat
);
console.log("this.sumstat", this.sumstat);
var x = d3
.scaleBand()
.domain(
data.map(function (d) {
return d.drugs;
})
)
.range([0, this.width]); // This controls the Horizontal position of the Axis
// Draw the axis
this.svg
.append("g")
.attr("transform", "translate(0," + this.height + ")") // This controls the vertical position of the Axis
.call(d3.axisBottom(x));
// Add Y axis
var y = d3
.scaleLinear()
.domain([
0,
d3.max(data, function (d) {
return +d.Loss + d.Addon + d.New + d.Repeat;
}),
])
.range([this.height, 0]);
this.svg.append("g").call(d3.axisLeft(y));
//tooltip
this.tip = d3Tip()
.attr("class", "d3-tip")
.direction("e")
.html(function (d, i) {
return (
"<div style='margin-top:20%;background-color:whitesmoke;border-radius:4px;color:black;padding:5px'><strong>" +
i[0]
// +
// "</strong><br><strong>( " +
// i[1][parseInt(r / 10)] +
// "," +
// i[1][parseInt(r / 10)].sum +
// ")</strong></div>"
);
});
this.svg.call(this.tip);
var dataArray = Array.from(this.sumstat, ([key, value]) => ({
key,
value,
}));
var color = d3
.scaleOrdinal()
.domain(
stacklist.map(function (d) {
return d;
})
)
.range([
"#e20000",
"#377eb8",
"#4daf4a",
"#250056 ",
"#ff9300",
"#df216d",
"#a65628",
"#0768fd",
"#00c221",
"#9e54b0",
"#ffff33",
"#10558a",
"#595959",
]);
this.svg
.selectAll(".line")
.data(this.sumstat)
.enter()
.append("path")
.attr("class", "lineKM")
.attr("id", (d) => "kmCurve" + d[1])
.attr("fill", "none")
.attr("stroke", function (d) {
console.log(d);
console.log("color", d[0], color(d[0]));
return color(d[0]);
})
.attr("stroke-width", 3.5)
.attr("d", function (d) {
return d3
.line()
.x((d) => {
console.log("5555555555555555555555", x(d.drugs));
return x(d.drugs);
})
.y((d) => {
console.log("66666666666666666666666", y(d.Addon));
return y(d.Addon);
})(
d[1].sort(function (a, b) {
console.log("bbbbbbbbbbbbbbbbbbb", b.Addon - a.Addon);
return b.Addon - a.Addon;
})
);
})
.on("mouseover", function (d) {
d3.selectAll("#kmCurve" + d.replace(/[^a-zA-Z0-9]/g, "_")).style(
"opacity",
0.1
);
d3.select(this).style("opacity", 1);
})
.call(this.tip)
.on("mouseover", this.tip.show)
.on("mouseout", this.tip.hide);
this.svg
.selectAll(".line")
.data(this.sumstat)
.enter()
.append("path")
.attr("class", "lineKM")
.attr("id", (d) => "kmCurve" + d[1])
.attr("fill", "none")
.attr("stroke", function (d) {
console.log(d);
console.log("color", d[0], color(d[0]));
return color(d[0]);
})
.attr("stroke-width", 3.5)
.attr("d", function (d) {
return d3
.line()
.x((d) => {
console.log("5555555555555555555555", x(d.drugs));
return x(d.drugs);
})
.y((d) => {
console.log("66666666666666666666666", y(d.Loss));
return y(d.Loss);
})(
d[1].sort(function (a, b) {
console.log("bbbbbbbbbbbbbbbbbbb", b.Loss - a.Loss);
return b.Loss - a.Loss;
})
);
})
.on("mouseover", function (d) {
d3.selectAll("#kmCurve" + d.replace(/[^a-zA-Z0-9]/g, "_")).style(
"opacity",
0.1
);
d3.select(this).style("opacity", 1);
})
.call(this.tip)
.on("mouseover", this.tip.show)
.on("mouseout", this.tip.hide);
this.svg
.selectAll(".line")
.data(this.sumstat)
.enter()
.append("path")
.attr("class", "lineKM")
.attr("id", (d) => "kmCurve" + d[1])
.attr("fill", "none")
.attr("stroke", function (d) {
console.log(d);
console.log("color", d[0], color(d[0]));
return color(d[0]);
})
.attr("stroke-width", 3.5)
.attr("d", function (d) {
return d3
.line()
.x((d) => {
console.log("5555555555555555555555", x(d.drugs));
return x(d.drugs);
})
.y((d) => {
console.log("66666666666666666666666", y(d.New));
return y(d.New);
})(
d[1].sort(function (a, b) {
console.log("bbbbbbbbbbbbbbbbbbb", b.New - a.New);
return b.New - a.New;
})
);
})
.on("mouseover", function (d) {
d3.selectAll("#kmCurve" + d.replace(/[^a-zA-Z0-9]/g, "_")).style(
"opacity",
0.1
);
d3.select(this).style("opacity", 1);
})
.call(this.tip)
.on("mouseover", this.tip.show)
.on("mouseout", this.tip.hide);
this.svg
.selectAll(".line")
.data(this.sumstat)
.enter()
.append("path")
.attr("class", "lineKM")
.attr("id", (d) => "kmCurve" + d[1])
.attr("fill", "none")
.attr("stroke", function (d) {
console.log(d);
console.log("color", d[0], color(d[0]));
return color(d[0]);
})
.attr("stroke-width", 3.5)
.attr("d", function (d) {
return d3
.line()
.x((d) => {
console.log("5555555555555555555555", x(d.drugs));
return x(d.drugs);
})
.y((d) => {
console.log("66666666666666666666666", y(d.Repeat));
return y(d.Repeat);
})(
d[1].sort(function (a, b) {
console.log("bbbbbbbbbbbbbbbbbbb", b.Repeat - a.Repeat);
return b.Repeat - a.Repeat;
})
);
})
.on("mouseover", function (d) {
d3.selectAll("#kmCurve" + d.replace(/[^a-zA-Z0-9]/g, "_")).style(
"opacity",
0.1
);
d3.select(this).style("opacity", 1);
})
.call(this.tip)
.on("mouseover", this.tip.show)
.on("mouseout", this.tip.hide);
this.createLegend(stacklist);
},
createLegend(stacklist) {
d3.selectAll("#KmLegends").remove();
var stacks = stacklist;
this.current_stacks = stacks;
console.log("stacks", stacks);
this.stackedZ = d3
.scaleOrdinal()
.range([
"#e20000",
"#377eb8",
"#4daf4a",
"#250056 ",
"#ff9300",
"#df216d",
"#a65628",
"#0768fd",
"#00c221",
"#9e54b0",
"#ffff33",
"#10558a",
"#595959",
])
.domain(stacks);
this.legendsvg = d3
.select("#kmCurveLegend")
.append("svg")
.attr("class", "legendKmCurve")
.attr("id", "KmLegends")
.attr("width", 1000)
.attr("height", 50);
this.legendg = this.legendsvg.append("g");
stacks.forEach((d, i) => {
var position = i;
this.legendg
.append("circle")
.attr("class", "legendCircle")
.attr("id", "legendCircle" + d.replace(/[^a-zA-Z0-9]/g, "_"))
.attr("r", 10)
.attr("cy", 20)
.attr("cx", position * 165 + 225)
.attr("fill", this.stackedZ(d))
.on("click", () => {
this.addOrDelete(d);
})
.on("mouseover", () => {
console.log("circle d", d);
d3.selectAll(".lineKM").style("opacity", 0.2);
d3.select("#kmCurve" + d.replace(/[^a-zA-Z0-9]/g, "_")).style(
"opacity",
2
);
d3.selectAll(".legendCircle").style("opacity", 0.1);
d3.select("#legendCircle" + d.replace(/[^a-zA-Z0-9]/g, "_")).style(
"opacity",
2
);
})
.on("mouseout", () => {
d3.selectAll(".lineKM").style("opacity", 1);
d3.selectAll(".legendCircle").style("opacity", 1);
})
.append("svg:title")
.text(d);
this.legendg
.append("text")
.attr("class", "legendKM" + d.replace(/[^a-zA-Z0-9]/g, "_"))
.on("click", (d, i, key) => {
console.log("key of text", key);
if (
document.getElementById(
"kmCurve" + d[0].replace(/[^a-zA-Z0-9]/g, "_").innerHTML
).style.opacity == 1
) {
d3.selectAll(
"#kmCurve" + d[0].replace(/[^a-zA-Z0-9]/g, "_").innerHTML
).style("opacity", 0);
d3.selectAll(
"." + "legendKM" + d[0].replace(/[^a-zA-Z0-9]/g, "_").innerHTML
)
.style("text-decoration", "line-through")
.style("opacity", 0.2);
d3.selectAll(
"#legendCircle" + d[0].replace(/[^a-zA-Z0-9]/g, "_").innerHTML
)
.style("text-decoration", "line-through")
.style("opacity", 0.2);
this.$forceUpdate();
} else {
d3.selectAll(
"#kmCurve" + d[0].replace(/[^a-zA-Z0-9]/g, "_").innerHTML
).style("opacity", 1);
d3.selectAll(
"." + "legendKM" + d[0].replace(/[^a-zA-Z0-9]/g, "_").innerHTML
)
.style("text-decoration", "none")
.style("opacity", 1)
.style("white-space", "nowrap")
.style("text-overflow", "ellipsis");
d3.selectAll(
"#legendCircle" + d[0].replace(/[^a-zA-Z0-9]/g, "_").innerHTML
)
.style("text-decoration", "line-through")
.style("opacity", 1);
this.$forceUpdate();
}
this.$forceUpdate();
this.addOrDelete(d[0].innerHTML);
})
.text(d)
.attr("y", 20)
.attr("x", position * 165 + 240)
.style("font-weight", "bold")
.style("font-size", "18px")
.attr("transform", "translate(0,6)")
.append("svg:title");
this.$forceUpdate();
});
},
addOrDelete(name) {
this.current_stacks.includes(name)
? this.filterData(name)
: this.addData(name);
},
filterData(name) {
console.log("this.current_stacks.length", this.current_stacks.length);
if (this.current_stacks.length == 1) {
return;
}
this.current_stacks = this.current_stacks.filter((obj) => {
return obj != name;
});
this.kmUpdatedLayout(
this.minWidth,
this.minHeight,
this.data,
this.current_stacks
);
this.legendUpdate(name, 0);
},
addData(name) {
if (this.current_stacks.includes(name)) {
return;
}
this.current_stacks.push(name);
this.kmUpdatedLayout(
this.minWidth,
this.minHeight,
this.data,
this.current_stacks
);
this.legendUpdate(name, 1);
},
legendUpdate(name, status) {
status
? d3
.selectAll("." + "legendKM" + name.replace(/[^a-zA-Z0-9]/g, "_"))
.style("text-decoration", "none")
.style("opacity", 1)
: d3
.selectAll("." + "legendKM" + name.replace(/[^a-zA-Z0-9]/g, "_"))
.style("text-decoration", "line-through")
.style("opacity", 0.4);
},
},
};
</script>
<style scoped>
</style>

d3 shrinking radar chart shape using attrTween

Hi I'm trying to use attrTween for my radar chart.
I made it happen for arc chart so I wanted to apply the same logic to my radar chart but it didn't work out.
can anyone let me know which part I missed?
I would like to shrink the radar chart on 'click'.
the code is as below.
d3.selectAll('.btn').on('click', function(d) {
let selectedone = d3.select(this).attr('class').split(' ')[1]
console.log(selectedone);
d3.select(`.radar${selectedone}`)
.transition()
.duration(1000)
.attrTween('d', shrinkRadar(0))
function shrinkRadar(target) {
return function(d) {
console.log(d)
let interpolate = d3.interpolate(rscale(d.value), target)
return function(t) {
console.log(d.radius);
d.radius = interpolate(t)
return radarLine(d)
}
}
}
// .attrTween('d',shrinkRadar(finalvalue))
})
Full code in the following link
https://codepen.io/jotnajoa/pen/dypJzmZ
Your radar chart's line functions take an array of data to output a path (line and area fill). You are attempting, though, to operate the attrTween like it's manipulating a single piece of data and not an array. Take a look at this quick re-write:
function shrinkRadar(target) {
return function(d) {
// create an array of interpolators
// one for each point in the line
// that run the value from starting to 0
var interps = d.map( da => d3.interpolate(da.value, target));
return function(t) {
// for each point call it's interpolator
// and re-assign the value
d.forEach( (da,i) => {
da.value = interps[i](t);
});
// regenerate path with new value
return radarLine(d)
}
}
}
Running code:
let margin = {
top: 100,
bottom: 100,
left: 100,
right: 100
}
let width = Math.min(700, window.innerWidth - 10) - margin.left - margin.right;
let height = Math.min(width, window.innerHeight - margin.top - margin.bottom - 20);
var data = [
[ //iPhone
{
axis: "Battery Life",
value: 0.22
},
{
axis: "Brand",
value: 0.28
},
{
axis: "Contract Cost",
value: 0.29
},
{
axis: "Design And Quality",
value: 0.17
},
{
axis: "Have Internet Connectivity",
value: 0.22
},
{
axis: "Large Screen",
value: 0.02
},
{
axis: "Price Of Device",
value: 0.21
},
{
axis: "To Be A Smartphone",
value: 0.50
}
],
[ //Samsung
{
axis: "Battery Life",
value: 0.27
},
{
axis: "Brand",
value: 0.16
},
{
axis: "Contract Cost",
value: 0.35
},
{
axis: "Design And Quality",
value: 0.13
},
{
axis: "Have Internet Connectivity",
value: 0.20
},
{
axis: "Large Screen",
value: 0.13
},
{
axis: "Price Of Device",
value: 0.35
},
{
axis: "To Be A Smartphone",
value: 0.38
}
],
[ //Nokia Smartphone
{
axis: "Battery Life",
value: 0.26
},
{
axis: "Brand",
value: 0.10
},
{
axis: "Contract Cost",
value: 0.30
},
{
axis: "Design And Quality",
value: 0.14
},
{
axis: "Have Internet Connectivity",
value: 0.22
},
{
axis: "Large Screen",
value: 0.04
},
{
axis: "Price Of Device",
value: 0.41
},
{
axis: "To Be A Smartphone",
value: 0.30
}
]
];
var color = d3.scaleOrdinal()
.range(['#6678D9', '#61F06C', '#FF36AA'])
var radarChartOptions = {
w: width,
h: height,
margin: margin,
maxValue: 0.5,
levels: 5,
roundStrokes: true,
color: color
};
// blue, green pink inorder
RadarChart('.radarChart', data, radarChartOptions)
function RadarChart(id, data, options) {
let cfg = {
w: 600,
h: 600,
margin: {
top: 20,
right: 20,
bottom: 20,
left: 20
},
levels: 3,
maxValue: 0,
labelFactor: 1.25,
wrapWidth: 60,
opacityArea: 0.35,
dotRadius: 4,
opacityCircles: 0.1,
strokeWidth: 2,
roundStroke: false,
color: ['#6678D9', '#61F06C', '#FF36AA']
}
if ('undefined' !== typeof options) {
for (var i in options) {
if ('undefined' !== typeof options[i]) {
cfg[i] = options[i];
}
} //for i
} //if
// 결론적으로 options에 들어가있는 녀석과 함수에있는 녀석을 매치시키는것
var maxValue =
// Math.max(cfg.maxValue,
d3.max(data,
(i) => d3.max(
i.map((o) => o.value)
// 벨류로 이루어진 어레이를 만들고, 그 어레이에서 최대값을 출력해낸다.
)
)
// )
var allAxis = data[0].map((d) => {
return d.axis
})
var total = allAxis.length,
radius = Math.min(cfg.w / 2, cfg.h / 2),
Format = d3.format('%'),
angleSlice = Math.PI * 2 / total
var rscale = d3.scaleLinear()
.range([0, radius])
.domain([0, maxValue])
d3.select(id).select('svg').remove()
var svg = d3.select(id).append('svg')
.attr('width', cfg.w + cfg.margin.left + cfg.margin.right)
.attr('height', cfg.h + cfg.margin.top + cfg.margin.bottom)
.attr('class',
'radar' + id);
var g = svg.append('g').attr('transform', `translate(${cfg.w/2+cfg.margin.left},${cfg.h/2+cfg.margin.top})`)
var filter = g.append('defs').append('filter').attr('id', 'glow')
var feGaussianBlur = filter.append('feGaussianBlur').attr('stdDeviation', '2.5')
.attr('result', 'coloredBlur')
var feMerge = filter.append('feMerge')
var feMergeNode1 = feMerge.append('feMergeNode').attr('in', 'coloredBlur')
var feMergeNode2 = feMerge.append('feMergeNode').attr('in', 'SourceGraphic')
var axisGrid = g.append('g').attr('class', 'axisWrapper')
axisGrid.selectAll('.levels').data(
d3.range(1, (cfg.levels + 1))
)
.join('circle')
.attr('class', 'gridCircle')
.attr('r', (d) => {
return radius / cfg.levels * d
})
.style('fill', '#CDCDCD')
.style('stroke', '#CDCDCD')
.style('fill-opacity', cfg.opacityCircles)
.style('filter', 'url(#glow)')
axisGrid.selectAll('.axisLabel')
.data(d3.range(1, cfg.levels + 1))
.join('text')
.attr('class', 'axisLabel')
.attr('x', 4)
.attr('y', (d) => {
return -d * radius / cfg.levels
})
.attr('dy', '0.4em')
.style('font-size', '10px')
.attr('fill', '#737373')
.text((d, i) => {
return Format(maxValue * d / cfg.levels)
})
var axis = axisGrid.selectAll('.axis')
.data(allAxis)
.join('g')
.attr('class', 'axis')
axis.append('line')
.attr('x1', 0)
.attr('y1', 0)
.attr("x2", function(d, i) {
return rscale(maxValue * 1.1) * Math.cos(angleSlice * i - Math.PI / 2);
})
.attr("y2", function(d, i) {
return rscale(maxValue * 1.1) * Math.sin(angleSlice * i - Math.PI / 2);
})
.attr("class", "line")
.style("stroke", "white")
.style("stroke-width", "2px");
axis.append("text")
.attr("class", "legend")
.style("font-size", "11px")
.attr("text-anchor", "middle")
.attr("dy", "0.35em")
.attr("x", function(d, i) {
return rscale(maxValue * cfg.labelFactor) * Math.cos(angleSlice * i - Math.PI / 2);
})
.attr("y", function(d, i) {
return rscale(maxValue * cfg.labelFactor) * Math.sin(angleSlice * i - Math.PI / 2);
})
.text(function(d) {
return d
})
.call(wrap, cfg.wrapWidth)
// .call(testfunc, 'testString')
var radarLine = d3.radialLine()
.radius(function(d) {
return rscale(d.value)
})
.angle(function(d, i, t) {
return i * angleSlice
})
.curve(d3.curveCardinalClosed)
// if (cfg.roundStrokes) {
// radarLine.interpolate("cardinal-closed");
// }
const blobWrapper = g.selectAll(".radarWrapper")
.data(data)
.join('g')
.attr("class", "radarWrapper");
blobWrapper
.append("path")
.attr("class", "radarArea")
.attr("d", function(d) {
return radarLine(d)
})
.attr('class', function(d, i) {
return `radar${i}`
})
.style("fill", (d, i) => cfg.color(i))
.style("fill-opacity", cfg.opacityArea)
.on('mouseover', function(d, i) {
d3.selectAll('.radarArea').transition().duration(200)
.style('fill-opacity', 0.1)
d3.select(this).transition().duration(200).style('fill-opacity', cfg.opacityArea);
})
.on('mouseout', function() {
//Bring back all blobs
d3.selectAll(".radarArea")
.transition().duration(200)
.style("fill-opacity", cfg.opacityArea);
});
blobWrapper.append('path')
.attr('class', 'radarStroke')
.attr('d', radarLine)
.attr('class', function(d, i) {
return `radarStroke${i}`
})
.style('stroke', (d, i) => cfg.color(i))
.style('stroke-width', cfg.strokeWidth + 'px')
.style('fill', 'none')
.style('filter', 'url(#glow)')
let round = 0;
blobWrapper.selectAll(".radarCircle")
.data(data)
.data(function(d) {
return d
})
.join('circle')
.attr("class", "radarCircle")
.attr("r", cfg.dotRadius)
.attr("cx", function(d, i) {
return rscale(d.value) * Math.cos(angleSlice * i - Math.PI / 2);
})
.attr("cy", function(d, i) {
return rscale(d.value) * Math.sin(angleSlice * i - Math.PI / 2);
})
.style("fill", function(d, i, j) {
if (i == 0) {
round = round + 1;;
}
return cfg.color(round - 1);
})
.style("fill-opacity", 0.8);
var blobCircleWrapper = g.selectAll('.radarCircleWrapper')
.data(data)
.join('g')
.attr('class', 'radarCircleWrapper')
blobCircleWrapper.selectAll('circles').data(function(d) {
return d
})
.join('circle').attr('class', 'invisibleCircles')
.attr('r', cfg.dotRadius * 1.5)
.attr("cx", function(d, i) {
return rscale(d.value) * Math.cos(angleSlice * i - Math.PI / 2);
})
.attr("cy", function(d, i) {
return rscale(d.value) * Math.sin(angleSlice * i - Math.PI / 2);
})
.style('fill', 'none')
.style('pointer-events', 'all')
.on('mouseover', function(d, i) {
newX = parseFloat(d3.select(this).attr('cx')) - 10;
newY = parseFloat(d3.select(this).attr('cy')) - 10;
tooltip
.attr('x', newX)
.attr('y', newY)
.text(Format(d.value))
.transition().duration(200)
.style('opacity', 1);
})
.on("mouseout", function() {
tooltip.transition().duration(200)
.style("opacity", 0);
});
var tooltip = g.append("text")
.attr("class", "tooltip")
.style("opacity", 0);
d3.selectAll('.btn').on('click', function(d) {
let selectedone = d3.select(this).attr('class').split(' ')[1]
console.log(selectedone);
d3.select(`.radar${selectedone}`)
.transition()
.duration(1000)
.attrTween('d', shrinkRadar(0))
function shrinkRadar(target) {
return function(d) {
var interps = d.map(da => d3.interpolate(da.value, target));
return function(t) {
d.forEach((da, i) => {
da.value = interps[i](t);
});
return radarLine(d)
}
}
}
// .attrTween('d',shrinkRadar(finalvalue))
})
}
function wrap(text, width) {
text.each(function() {
var text = d3.select(this),
words = text.text().split(/\s+/).reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 1.4, // ems
y = text.attr("y"),
x = text.attr("x"),
dy = parseFloat(text.attr("dy")),
tspan = text.text(null).append("tspan").attr("x", x).attr("y", y).attr("dy", dy + "em");
while (word = words.pop()) {
line.push(word);
tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(" "));
line = [word];
tspan = text.append("tspan").attr("x", x).attr("y", y).attr("dy", ++lineNumber * lineHeight + dy + "em").text(word);
}
}
});
}
.radarChart {
width: 1000px;
height: 500px;
}
.buttonwrap {
margin-left: 200px;
text-align: left;
/* border: dashed grey 0.5px; */
}
.btn {
text-align: center;
display: inline-block;
width: 100px;
height: 20px;
border: solid 1px navy;
border-radius: 5px;
margin: auto;
cursor: pointer;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.3.1/d3.min.js"></script>
<svg class="radarChart"></svg>
<div class="buttonwrap">
<div class="btn 0">Show 1</div>
<div class="btn 1">Show 2</div>
<div class="btn 2">show 3</div>
</div>

d3 labelled horizontal chart with labels and animation

I am working on a d3js horizontal chart - the designers are specific in having the labels this way.
I've built the following - but would like to model it more on older code that had animation properties.
//current chart
https://codepen.io/anon/pen/ZmJzXZ
//static vertical chart http://jsfiddle.net/pg886/201/
//animated vertical chart http://jsfiddle.net/Qh9X5/12073/
-- d3js code
var data = [{
"name": "Apples",
"value": 20,
},
{
"name": "Bananas",
"value": 12,
},
{
"name": "Grapes",
"value": 19,
},
{
"name": "Lemons",
"value": 5,
},
{
"name": "Limes",
"value": 16,
},
{
"name": "Oranges",
"value": 26,
},
{
"name": "Pears",
"value": 30,
}];
//sort bars based on value
data = data.sort(function (a, b) {
return d3.ascending(a.value, b.value);
})
//set up svg using margin conventions - we'll need plenty of room on the left for labels
var margin = {
top: 15,
right: 25,
bottom: 15,
left: 60
};
var width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var svg = d3.select("#graphic").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var x = d3.scale.linear()
.range([0, width])
.domain([0, d3.max(data, function (d) {
return d.value;
})]);
var y = d3.scale.ordinal()
.rangeRoundBands([height, 0], .3)
.domain(data.map(function (d) {
return d.name;
}));
//make y axis to show bar names
var yAxis = d3.svg.axis()
.scale(y)
//no tick marks
.tickSize(0)
.orient("right");
var gy = svg.append("g")
.attr("class", "y axis")
.call(yAxis)
var bars = svg.selectAll(".bar")
.data(data)
.enter()
.append("g")
.attr("class", "bars")
//append rects
bars.append("rect")
.attr("class", "bar")
.attr("y", function (d) {
return y(d.name);
})
.attr("height", y.rangeBand())
.attr("x", 0)
.attr("width", function (d) {
return x(d.value);
});
//add a value label to the right of each bar
bars.append("text")
.attr("class", "label")
//y position of the label is halfway down the bar
.attr("y", function (d) {
return y(d.name) + y.rangeBand() / 2 + 4;
})
//x position is 3 pixels to the right of the bar
.attr("x", function (d) {
return x(d.value) + 3;
})
.text(function (d) {
return d.value;
});
var labels =
bars.append("text")
.attr("class", "labels")
.attr("y", function (d) {
return y(d.name) + y.rangeBand() / 2 - 30;
})
.attr("x", 0)
.text(function (d) {
return d.name;
});
I had a similar chart that I've made a few modifications to that might fill your requierments, so I'll be basing my answer of my own code.
I'll just go through the most relevant part of the question and you can just have a look at the code and hopefully figure out how it works yourself.
The inital animation works exactly the same way as in the third link you posted:
.transition().duration(speed)
.delay((_, i) => delay * i)
we set a delay so that each bar appear one at a time.
I've also set it up so that you can change the data using the d3js update pattern.
var bar = svg.selectAll(".bar")
.data(data, d => d.name)
bar.exit().remove();
bar.enter().insert("g", ".y-axis").append("rect")
.attr("class", "bar")
.attr("fill", "#ccc")
.attr("x", x(0))
.attr("y", d => y(d.name))
.attr("height", y.bandwidth())
.merge(bar)
.transition().duration(speed)
.delay((_, i) => delay * i)
.attr("y", d => y(d.name))
.attr("width", d => x(d.value) - x(0));
Since you didn't specify how you want to update the new data it's just a year filter for now.
Here's all the code:
var init = [
{"year": "2017", "name": "Apples", "value": 20},
{"year": "2017", "name": "Bananas","value": 12},
{"year": "2017", "name": "Grapes", "value": 19},
{"year": "2017", "name": "Lemons", "value": 5},
{"year": "2017", "name": "Limes", "value": 16},
{"year": "2017", "name": "Oranges", "value": 26},
{"year": "2017", "name": "Pears","value": 30},
{"year": "2018", "name": "Apples", "value": 10},
{"year": "2018", "name": "Bananas","value": 42},
{"year": "2018", "name": "Grapes", "value": 69},
{"year": "2018", "name": "Lemons", "value": 15},
{"year": "2018", "name": "Limes", "value": 26},
{"year": "2018", "name": "Oranges", "value": 36},
{"year": "2018", "name": "Pears","value": 20}
];
chart(init)
function chart(result) {
var format = d3.format(",.0f")
var years = [...new Set(result.map(d => d.year))]
var fruit = [...new Set(result.map(d => d.name))]
var options = d3.select("#year").selectAll("option")
.data(years)
.enter().append("option")
.text(d => d)
var svg = d3.select("#graphic"),
margin = {top: 25, bottom: 10, left: 50, right: 45},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom;
var x = d3.scaleLinear()
.range([margin.left, width - margin.right])
var y = d3.scaleBand()
.range([margin.top, height - margin.bottom])
.padding(0.1)
.paddingOuter(0.5)
.paddingInner(0.5)
var xAxis = svg.append("g")
.attr("class", "x-axis")
.attr("transform", `translate(0,${margin.top})`)
var yAxis = svg.append("g")
.attr("class", "y-axis")
.attr("transform", `translate(${margin.left},0)`)
update(d3.select("#year").property("value"), 750, 250)
function update(input, speed, delay) {
var data = result.filter(f => f.year == input)
var sum = d3.sum(data, d => d.value)
x.domain([0, d3.max(data, d => d.value)]).nice()
svg.selectAll(".x-axis").transition().duration(speed)
.call(d3.axisTop(x).tickSizeOuter(0));
data.sort((a, b) => b.value - a.value)
y.domain(data.map(d => d.name))
svg.selectAll(".y-axis").transition().duration(speed)
.call(d3.axisLeft(y));
yAxis.selectAll("text").remove()
yAxis.selectAll("line").remove()
var bar = svg.selectAll(".bar")
.data(data, d => d.name)
bar.exit().remove();
bar.enter().insert("g", ".y-axis").append("rect")
.attr("class", "bar")
.attr("fill", "#ccc")
.attr("x", x(0))
.attr("y", d => y(d.name))
.attr("height", y.bandwidth())
.merge(bar)
.transition().duration(speed)
.delay((_, i) => delay * i)
.attr("y", d => y(d.name))
.attr("width", d => x(d.value) - x(0));
var value = svg.selectAll(".value")
.data(data, d => d.name)
value.exit().remove();
value.enter().append("text")
.attr("class", "value")
.attr("opacity", 0)
.attr("dy", 4)
.attr("y", d => y(d.name) + y.bandwidth() / 2)
.merge(value)
.transition().duration(speed)
.delay((_, i) => delay * i)
.attr("opacity", 1)
.attr("y", d => y(d.name) + y.bandwidth() / 2)
.attr("x", d => x(d.value) + 5)
.text(d => format((d.value / sum) * 100) + " %")
var name = svg.selectAll(".name")
.data(data, d => d.name)
name.exit().remove();
name.enter().append("text")
.attr("class", "name")
.attr("opacity", 0)
.attr("dy", -5)
.attr("y", d => y(d.name))
.merge(name)
.transition().duration(speed)
.delay((_, i) => delay * i)
.attr("opacity", 1)
.attr("y", d => y(d.name))
.attr("x", d => x(0) + 5)
.text(d => d.name)
}
var select = d3.select("#year")
.style("border-radius", "5px")
.on("change", function() {
update(this.value, 750, 0)
})
}
body {
margin: auto;
width: 650px;
font: 12px arial;
}
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg id="graphic" width="600" height="380"></svg><br>
Choose year:
<select id="year"></select>

Show labels based on collision detection scatter plot in d3js

I would like to show labels on the circles of the scatter plot when they are not colliding/overlapping. A simple example of scatter plot with labels is as follows:
var width = 500;
var height = 500;
var margin = {
top: 40,
right: 40,
bottom: 40,
left: 40
};
var x = d3.scale.linear().range([0, width]);
var y = d3.scale.linear().range([height, 0]);
var minX = _(data).orderBy('x').first().x;
var maxX = _(data).orderBy('x').last().x;
x.domain([minX - 500, maxX + 500]);
y.domain([0, 100]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var svg = d3
.select("#d3")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(" + 0 + "," + height / 2 + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + width / 2 + "," + 0 + ")")
.call(yAxis)
.append("text");
var gdots = svg.selectAll("g.dot")
.data(data)
.enter().append('g');
gdots.append("circle")
.attr("class", "dot")
.attr("r", function (d) {
return d.r;
})
.attr("cx", function (d) {
return x(d.x);
})
.attr("cy", function (d) {
return y(d.y);
})
.style("fill", function (d) {
return d.c;
});
gdots.append("text").text(function(d){
return d.name;
})
.attr("x", function (d) {
return x(d.x);
})
.attr("y", function (d) {
return y(d.y);
});
The example is available on fiddle:
https://jsfiddle.net/8e7qmzw8/1/
How can I apply collision detection in the given example to show the labels for non-collided circles?
Here's a brute force search approach:
gdots
// filter out those in a colliding state
.filter(function(d0, i){
var isCollide = false,
x0 = x(d0.x),
y0 = y(d0.y);
gdots.data().forEach(function(d1,j){
// if it has a collision with another, stop looking
if (!isCollide){
// if it's not itself
if (d0.name != d1.name){
var x1 = x(d1.x),
y1 = y(d1.y);
// if they overlap
isCollide = Math.pow((x1-x0),2) + Math.pow((y1-y0),2) <= Math.pow((d0.r+d1.r), 2);
}
}
});
return !isCollide;
})
.append("text").text(function(d){
return d.name;
});
Running code:
var data = [
{"x": -123, "y": 63, "r": 37, "c": "#50C2E3", "name": "A"},
{"x": 71, "y": 0, "r": 15, "c": "#50C2E3", "name": "B"},
{"x": 3845, "y": 77, "r": 15, "c": "#50C2E3", "name": "C"},
{"x": 3176, "y": 90, "r": 15, "c": "#50C2E3", "name": "D"},
{"x": -17, "y": 56, "r": 15, "c": "#50C2E3", "name": "D"},
{"x": 1357, "y": 58, "r": 15, "c": "#50C2E3", "name": "E"},
{"x": 7684, "y": 75, "r": 15, "c": "#50C2E3", "name": "F"}
];
var width = 500;
var height = 500;
var margin = {
top: 40,
right: 40,
bottom: 40,
left: 40
};
var x = d3.scale.linear().range([0, width]);
var y = d3.scale.linear().range([height, 0]);
var minX = _(data).orderBy('x').first().x;
var maxX = _(data).orderBy('x').last().x;
x.domain([minX - 500, maxX + 500]);
y.domain([0, 100]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var svg = d3
.select("#d3")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(" + 0 + "," + height / 2 + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + width / 2 + "," + 0 + ")")
.call(yAxis)
.append("text");
var gdots = svg.selectAll("g.dot")
.data(data)
.enter().append('g');
gdots.append("circle")
.attr("class", "dot")
.attr("r", function (d) {
return d.r;
})
.attr("cx", function (d) {
return x(d.x);
})
.attr("cy", function (d) {
return y(d.y);
})
.style("fill", function (d) {
return d.c;
});
gdots
.filter(function(d0, i){
var isCollide = false,
x0 = x(d0.x),
y0 = y(d0.y);
gdots.data().forEach(function(d1,j){
if (!isCollide){
if (d0.name != d1.name){
var x1 = x(d1.x),
y1 = y(d1.y);
isCollide = Math.pow((x1-x0),2) + Math.pow((y1-y0),2) <= Math.pow((d0.r+d1.r), 2);
}
}
});
return !isCollide;
})
.append("text").text(function(d){
return d.name;
})
.attr("x", function (d) {
return x(d.x);
})
.attr("y", function (d) {
return y(d.y);
});
.node {
cursor: pointer;
}
.dot {
opacity: .7;
cursor: pointer;
}
.axis path,
.axis line {
fill: none;
stroke: rgb(31, 119, 180);
shape-rendering: crispEdges;
}
text {
stroke: none;
fill: #666666;
font-size: .6em;
font-family: "Helvetica Neue"
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.11.2/lodash.min.js"></script>
<div id="d3"></div>

Stacked bar chart, color bars

I have a codepen here - https://codepen.io/anon/pen/xpBqxG?editors=1010
I'm doing some simple changes to my starter data and then creating a stacked bar chart.
I'm currently coloring each bar the same color but there should be two colors, one for each key in the stacked chart. Each bar should be spilt into two colors.
How do I color each section of the bar the same color.
.style('fill', (d, i) => {
return colors[i];
});
Apply the style to the groups, not to the rectangles:
let layers = layersArea.selectAll('.layer')
.data(stackedSeries)
.enter()
.append('g')
.attr('class', 'layer')
.style('fill', (d, i) => {
return colors[i];
});
Here is your code with that change only:
let keys = [];
let dataToStack = [];
let totalData = [];
let legendKeys = ['usedInf', 'newInf'];
let w = 800;
let h = 450;
let margin = {
top: 30,
bottom: 40,
left: 50,
right: 20,
};
let width = w - margin.left - margin.right;
let height = h - margin.top - margin.bottom;
let colors = ['#FF9A00', '#FFEBB6', '#FFC400', '#B4EDA0', '#FF4436'];
let data = [{
"one": 10,
"two": 12,
"three": 18,
"four": 22,
"five": 30,
"six": 44,
"seven": 25,
"date": "2015-05-31T00:00:00"
}, {
"one": 30,
"two": 42,
"three": 38,
"four": 62,
"five": 90,
"six": 144,
"seven": 95,
"date": "2015-06-30T00:00:00"
}, {
"one": 30,
"two": 92,
"three": 18,
"four": 100,
"five": 120,
"six": 10,
"seven": 110,
"date": "2015-07-31T00:00:00"
}, ];
for (let i = 0; i < data.length; i++) {
dataToStack.push({
date: data[i]['date'].toString(),
usedInf: data[i]['one'] + data[i]['two'] + data[i]['three'],
newInf: data[i]['four'] + data[i]['five'] + data[i]['six']
});
totalData.push({
date: data[i]['date'].toString(),
total: data[i]['seven']
});
}
let stack = d3.stack()
.keys(legendKeys);
let stackedSeries = stack(dataToStack);
let x = d3.scaleBand()
.domain(dataToStack.map(function(d) {
return d.date;
}))
.rangeRound([0, width])
.padding(0.05);
let y = d3.scaleLinear()
.domain([0, d3.max(stackedSeries, function(d) {
return d3.max(d, (d) => {
return d[1];
})
})])
.range([height, 0]);
let svg = d3.select('.chart').append('svg')
.attr('class', 'chart')
.attr('width', w)
.attr('height', h);
let chart = svg.append('g')
.classed('graph', true)
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
let layersArea = chart.append('g')
.attr('class', 'layers');
let layers = layersArea.selectAll('.layer').data(stackedSeries)
.enter()
.append('g')
.attr('class', 'layer')
.style('fill', (d, i) => {
return colors[i];
});
layers.selectAll('rect')
.data((d) => {
return d
})
.enter()
.append('rect')
.attr('height', (d, i) => {
return y(d[0]) - y(d[1]);
})
.attr('y', (d) => {
return y(d[1]);
})
.attr('x', (d, i) => {
return x(d.data.date)
})
.attr('width', x.bandwidth());
chart.append('g')
.classed('x axis', true)
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
chart.append('g')
.classed('y axis', true)
.call(d3.axisLeft(y)
.ticks(10));
<script src="https://d3js.org/d3.v4.min.js"></script>
<div class="chart"></div>

Resources