in D3.js how to add/remove datapoints at the same time? - d3.js

In the example below, if my new dataset has new entry and also some record removed (added burrito, removed apple), how can i reflect the changes at the same time? So far the code pushes in a new bar, but not removing the first bar, nor does the axis updates even though I tried to re-set the x axis domain.
Blockquote
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://d3js.org/d3.v4.min.js"></script>
<meta charset="utf-8">
<title>D3: Loading data from a CSV file</title>
</head>
<body>
<p>Click on this text to update the chart with new data values (once).</p>
<script type="text/javascript">
var margin = {top: 20, right: 20, bottom: 30, left: 40},
w = 600 - margin.left - margin.right,
h = 300 - margin.top - margin.bottom;
var padding = 40;
var data = [
{ "Food": "Apples", "Deliciousness": 9, "new":4 },
{ "Food": "Green Beans", "Deliciousness": 5, "new":4 },
{ "Food": "Egg Salad Sandwich", "Deliciousness": 4, "new":4 },
{ "Food": "Cookies", "Deliciousness": 10, "new":4 },
{ "Food": "Liver", "Deliciousness": 2, "new":4 },
];
data.forEach(function(d) {
d.Deliciousness = +d.Deliciousness;
});
//define key
var key = function(d) {
return d.Food;
}
var svg = d3.select("body")
.append("svg")
.attr("width", w + margin.left + margin.right + padding)
.attr("height", h + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left+"," +
margin.top+")");
//initial state
//scale and axis
var xScale = d3.scaleBand()
.domain(d=>d.Food)
.range([0,w])
.paddingInner(0.2);
xScale.domain(data.map(function(d) { return d.Food; }));
var yScale = d3.scaleLinear()
.domain([0, d3.max(data, d=>d.Deliciousness)])
.rangeRound([h,0]);
var xAxis = d3.axisBottom()
.scale(xScale)
.ticks(5);
var yAxis = d3.axisLeft()
.scale(yScale)
.ticks(5);
//draw rect
svg.selectAll('rect')
.data(data, key)
.enter()
.append('rect')
.attr('x',(d,i) => margin.left + i * ((w + 20 ) / data.length))
.attr('y',d=>yScale(d.Deliciousness))
.attr('width', xScale.bandwidth())
.attr('height',d =>h-yScale(d.Deliciousness))
.attr('fill',function(d){
if (d===30) return "red";
return "rgb(0,0,"+d.Deliciousness*10+")" ;});
//text label
svg.selectAll("text")
.data(data)
.enter()
.append("text")
.text(d=>d.Deliciousness)
.attr('x',(d,i) => margin.left + i * ((w + 20 ) / data.length) + 0.4*w/ data.length)
.attr("y", d=>yScale(d.Deliciousness)+15)
.attr("fill","white")
.attr("text-anchor", "middle");
//draw axis
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(" + padding + ",0)")
.call(yAxis);
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(" + margin.left + "," + h + ")")
.call(xAxis);
//transition
d3.select("p")
.on("click", function() {
//New values for dataset
data = [
{ "Food": "Green Beans", "Deliciousness": 5, "new":4 },
{ "Food": "Egg Salad Sandwich", "Deliciousness": 4, "new":4 },
{ "Food": "Cookies", "Deliciousness": 10, "new":4 },
{ "Food": "Liver", "Deliciousness": 2, "new":4 },
{ "Food": "Burrito", "Deliciousness": 7, "new":4 }];
xScale.domain(data.map(function(d) { return d.Food; }));
//Update all rects
var bars = svg.selectAll("rect")
.data(data, key);
bars.enter()
.append("rect")
// <-- This makes it a smooth transition!
.attr("x", w)
.attr('y',d=>yScale(d.Deliciousness))
.attr("width", xScale.bandwidth())
.attr('height',d =>h-yScale(d.Deliciousness))
.merge(bars) //Merges the enter selection with the update selection
.transition() //Initiate a transition on all elements in the update selection (all rects)
.duration(500)
.attr('x',(d,i) => margin.left + i * ((w + 20 ) / data.length))
.attr('y',d=>yScale(d.Deliciousness))
.attr('width', xScale.bandwidth())
.attr('height',d =>h-yScale(d.Deliciousness))
});
</script>
</body>
</html>
And the current wrong output is attached.
Thanks!

You are missing two things. One, .remove the exit selection and two, update your x-axis.
xScale.domain(data.map(function(d) {
return d.Food;
}));
// redraw x-axis
svg.select('.xaxis')
.call(xAxis);
//Update all rects
var bars = svg.selectAll("rect")
.data(data, key);
// remove those things exitiing
bars.exit().remove();
Running code:
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://d3js.org/d3.v4.min.js"></script>
<meta charset="utf-8">
<title>D3: Loading data from a CSV file</title>
</head>
<body>
<p>Click on this text to update the chart with new data values (once).</p>
<script type="text/javascript">
var margin = {
top: 20,
right: 20,
bottom: 30,
left: 40
},
w = 600 - margin.left - margin.right,
h = 300 - margin.top - margin.bottom;
var padding = 40;
var data = [{
"Food": "Apples",
"Deliciousness": 9,
"new": 4
}, {
"Food": "Green Beans",
"Deliciousness": 5,
"new": 4
}, {
"Food": "Egg Salad Sandwich",
"Deliciousness": 4,
"new": 4
}, {
"Food": "Cookies",
"Deliciousness": 10,
"new": 4
}, {
"Food": "Liver",
"Deliciousness": 2,
"new": 4
}, ];
data.forEach(function(d) {
d.Deliciousness = +d.Deliciousness;
});
//define key
var key = function(d) {
return d.Food;
}
var svg = d3.select("body")
.append("svg")
.attr("width", w + margin.left + margin.right + padding)
.attr("height", h + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," +
margin.top + ")");
//initial state
//scale and axis
var xScale = d3.scaleBand()
.domain(d => d.Food)
.range([0, w])
.paddingInner(0.2);
xScale.domain(data.map(function(d) {
return d.Food;
}));
var yScale = d3.scaleLinear()
.domain([0, d3.max(data, d => d.Deliciousness)])
.rangeRound([h, 0]);
var xAxis = d3.axisBottom()
.scale(xScale)
.ticks(5);
var yAxis = d3.axisLeft()
.scale(yScale)
.ticks(5);
//draw rect
svg.selectAll('rect')
.data(data, key)
.enter()
.append('rect')
.attr('x', (d, i) => margin.left + i * ((w + 20) / data.length))
.attr('y', d => yScale(d.Deliciousness))
.attr('width', xScale.bandwidth())
.attr('height', d => h - yScale(d.Deliciousness))
.attr('fill', function(d) {
if (d === 30) return "red";
return "rgb(0,0," + d.Deliciousness * 10 + ")";
});
//text label
svg.selectAll("text")
.data(data)
.enter()
.append("text")
.text(d => d.Deliciousness)
.attr('x', (d, i) => margin.left + i * ((w + 20) / data.length) + 0.4 * w / data.length)
.attr("y", d => yScale(d.Deliciousness) + 15)
.attr("fill", "white")
.attr("text-anchor", "middle");
//draw axis
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(" + padding + ",0)")
.call(yAxis);
svg.append("g")
.attr("class", "xaxis")
.attr("transform", "translate(" + margin.left + "," + h + ")")
.call(xAxis);
//transition
d3.select("p")
.on("click", function() {
//New values for dataset
data = [{
"Food": "Green Beans",
"Deliciousness": 5,
"new": 4
}, {
"Food": "Egg Salad Sandwich",
"Deliciousness": 4,
"new": 4
}, {
"Food": "Cookies",
"Deliciousness": 10,
"new": 4
}, {
"Food": "Liver",
"Deliciousness": 2,
"new": 4
}, {
"Food": "Burrito",
"Deliciousness": 7,
"new": 4
}];
xScale.domain(data.map(function(d) {
return d.Food;
}));
svg.select('.xaxis')
.call(xAxis);
//Update all rects
var bars = svg.selectAll("rect")
.data(data, key);
bars.exit().remove();
bars.enter()
.append("rect")
// <-- This makes it a smooth transition!
.attr("x", w)
.attr('y', d => yScale(d.Deliciousness))
.attr("width", xScale.bandwidth())
.attr('height', d => h - yScale(d.Deliciousness))
.merge(bars) //Merges the enter selection with the update selection
.transition() //Initiate a transition on all elements in the update selection (all rects)
.duration(500)
.attr('x', (d, i) => margin.left + i * ((w + 20) / data.length))
.attr('y', d => yScale(d.Deliciousness))
.attr('width', xScale.bandwidth())
.attr('height', d => h - yScale(d.Deliciousness));
});
</script>
</body>
</html>

Related

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>

Creating a reversed D3 stack bar chart

Assuming I have data like this:
const data = [
{
month: 1
apples: ...,
bananas: ...,
cherries: ...,
dates: ...,
},
{
month: 2
apples: ...,
bananas: ...,
cherries: ...,
dates: ...,
},
{
month: 3
apples: ...,
bananas: ...,
cherries: ...,
dates: ...,
}
]
Going for 12 months, using keys of ['apples','bananas','cherries','dates']. The d3.stack() will produce an array for 4 bars with 12 sets of values. This makes sense. However, what if I wanted to create 12 bars with the keys broken up so it's sets of 4 values.
Is it possible to flip things on their heads in this fashion?
You just need to convert the data into the required format and flip the axis and data configurations in the chart as shown below.
Existing :
var margin = {
top: 20,
right: 160,
bottom: 35,
left: 30
};
var width = 500 - margin.left - margin.right,
height = 250 - margin.top - margin.bottom;
var svg = d3.select("body")
.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 + ")");
/* Data in strings like it would be if imported from a csv */
var data = [{
year: "2006",
redDelicious: "10",
mcintosh: "15",
oranges: "9",
pears: "6"
},
{
year: "2007",
redDelicious: "12",
mcintosh: "18",
oranges: "9",
pears: "4"
},
{
year: "2008",
redDelicious: "05",
mcintosh: "20",
oranges: "8",
pears: "2"
},
{
year: "2009",
redDelicious: "01",
mcintosh: "15",
oranges: "5",
pears: "4"
}
];
var parse = d3.time.format("%Y").parse;
// Transpose the data into layers
var dataset = d3.layout.stack()(["redDelicious", "mcintosh", "oranges", "pears"].map(function(fruit) {
return data.map(function(d) {
return {
x: parse(d.year),
y: +d[fruit]
};
});
}));
// Set x, y and colors
var x = d3.scale.ordinal()
.domain(dataset[0].map(function(d) {
return d.x;
}))
.rangeRoundBands([10, width - 10], 0.02);
var y = d3.scale.linear()
.domain([0, d3.max(dataset, function(d) {
return d3.max(d, function(d) {
return d.y0 + d.y;
});
})])
.range([height, 0]);
var colors = ["b33040", "#d25c4d", "#f2b447", "#d9d574"];
// Define and draw axes
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(5)
.tickSize(-width, 0, 0)
.tickFormat(function(d) {
return d
});
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickFormat(d3.time.format("%Y"));
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Create groups for each series, rects for each segment
var groups = svg.selectAll("g.cost")
.data(dataset)
.enter().append("g")
.attr("class", "cost")
.style("fill", function(d, i) {
return colors[i];
});
var rect = groups.selectAll("rect")
.data(function(d) {
return d;
})
.enter()
.append("rect")
.attr("x", function(d) {
return x(d.x);
})
.attr("y", function(d) {
return y(d.y0 + d.y);
})
.attr("height", function(d) {
return y(d.y0) - y(d.y0 + d.y);
})
.attr("width", x.rangeBand())
.on("mouseover", function() {
tooltip.style("display", null);
})
.on("mouseout", function() {
tooltip.style("display", "none");
})
.on("mousemove", function(d) {
var xPosition = d3.mouse(this)[0] - 15;
var yPosition = d3.mouse(this)[1] - 25;
tooltip.attr("transform", "translate(" + xPosition + "," + yPosition + ")");
tooltip.select("text").text(d.y);
});
// Draw legend
var legend = svg.selectAll(".legend")
.data(colors)
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) {
return "translate(30," + i * 19 + ")";
});
legend.append("rect")
.attr("x", width - 18)
.attr("width", 18)
.attr("height", 18)
.style("fill", function(d, i) {
return colors.slice().reverse()[i];
});
legend.append("text")
.attr("x", width + 5)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "start")
.text(function(d, i) {
switch (i) {
case 0:
return "Anjou pears";
case 1:
return "Naval oranges";
case 2:
return "McIntosh apples";
case 3:
return "Red Delicious apples";
}
});
// Prep the tooltip bits, initial display is hidden
var tooltip = svg.append("g")
.attr("class", "tooltip")
.style("display", "none");
tooltip.append("rect")
.attr("width", 30)
.attr("height", 20)
.attr("fill", "white")
.style("opacity", 0.5);
tooltip.append("text")
.attr("x", 15)
.attr("dy", "1.2em")
.style("text-anchor", "middle")
.attr("font-size", "12px")
.attr("font-weight", "bold");
svg {
font: 10px sans-serif;
shape-rendering: crispEdges;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
}
path.domain {
stroke: none;
}
.y .tick line {
stroke: #ddd;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
New :
var margin = {
top: 20,
right: 160,
bottom: 35,
left: 30
};
var width = 500 - margin.left - margin.right,
height = 250 - margin.top - margin.bottom;
var svg = d3.select("body")
.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 + ")");
/* Data in strings like it would be if imported from a csv */
var data = [{
fruit: "redDelicious",
2006: "10",
2007: "12",
2008: "05",
2009: "01",
2010: "02"
},
{
fruit: "mcintosh",
2006: "15",
2007: "18",
2008: "20",
2009: "15",
2010: "10"
},
{
fruit: "oranges",
2006: "9",
2007: "9",
2008: "8",
2009: "5",
2010: "4"
},
{
fruit: "pears",
2006: "6",
2007: "4",
2008: "2",
2009: "4",
2010: "2"
}
];
var legends = Object.keys(data[0]);
legends.splice(legends.indexOf('fruit'), 1);
// Transpose the data into layers
var dataset = d3.layout.stack()(legends.map(function(year) {
return data.map(function(d) {
return {
x: d.fruit,
y: +d[year]
};
});
}));
// Set x, y and colors
var x = d3.scale.ordinal()
.domain(dataset[0].map(function(d) {
return d.x;
}))
.rangeRoundBands([10, width - 10], 0.02);
var y = d3.scale.linear()
.domain([0, d3.max(dataset, function(d) {
return d3.max(d, function(d) {
return d.y0 + d.y;
});
})])
.range([height, 0]);
var colors = ["b33040", "#d25c4d", "#f2b447", "#d9d574","#6aa8e0"];
// Define and draw axes
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(5)
.tickSize(-width, 0, 0)
.tickFormat(function(d) {
return d
});
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
//.tickFormat(d3.time.format("%Y"));
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Create groups for each series, rects for each segment
var groups = svg.selectAll("g.cost")
.data(dataset)
.enter().append("g")
.attr("class", "cost")
.style("fill", function(d, i) {
return colors[i];
});
var rect = groups.selectAll("rect")
.data(function(d) {
return d;
})
.enter()
.append("rect")
.attr("x", function(d) {
return x(d.x);
})
.attr("y", function(d) {
return y(d.y0 + d.y);
})
.attr("height", function(d) {
return y(d.y0) - y(d.y0 + d.y);
})
.attr("width", x.rangeBand())
.on("mouseover", function() {
tooltip.style("display", null);
})
.on("mouseout", function() {
tooltip.style("display", "none");
})
.on("mousemove", function(d) {
var xPosition = d3.mouse(this)[0] - 15;
var yPosition = d3.mouse(this)[1] - 25;
tooltip.attr("transform", "translate(" + xPosition + "," + yPosition + ")");
tooltip.select("text").text(d.y);
});
// Draw legend
var legend = svg.selectAll(".legend")
.data(colors)
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) {
return "translate(30," + i * 19 + ")";
});
legend.append("rect")
.attr("x", width - 18)
.attr("width", 18)
.attr("height", 18)
.style("fill", function(d, i) {
return colors.slice().reverse()[i];
});
legend.append("text")
.attr("x", width + 5)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "start")
.text(function(d, i) {
return legends.reverse()[i];
});
// Prep the tooltip bits, initial display is hidden
var tooltip = svg.append("g")
.attr("class", "tooltip")
.style("display", "none");
tooltip.append("rect")
.attr("width", 30)
.attr("height", 20)
.attr("fill", "white")
.style("opacity", 0.5);
tooltip.append("text")
.attr("x", 15)
.attr("dy", "1.2em")
.style("text-anchor", "middle")
.attr("font-size", "12px")
.attr("font-weight", "bold");
svg {
font: 10px sans-serif;
shape-rendering: crispEdges;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
}
path.domain {
stroke: none;
}
.y .tick line {
stroke: #ddd;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

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>

D3 Stacked Chart with array or JSON data

I want to create a Stacked bar chart like http://bl.ocks.org/mbostock/3886208 . But I don't want to use CSV file.
How can I create Stacked chart using array or JSON data?
In csv we are using like this :
State,Post,Comment
AL,310504,552339
AK,52083,85640
How can I define data in array or json like
var data = []
do it like this
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.bar {
fill: steelblue;
}
.x.axis path {
display: none;
}
</style>
<body>
<script src="//d3js.org/d3.v3.min.js"></script>
<script>
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
var y = d3.scale.linear()
.rangeRound([height, 0]);
var color = d3.scale.ordinal()
.range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickFormat(d3.format(".2s"));
var svg = d3.select("body").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 data = [
{
"State": "AL",
"Under 5 Years": 10,
"5 to 13 Years": 20,
"14 to 17 Years": 30,
"18 to 24 Years": 40,
"25 to 44 Years": 50,
"45 to 64 Years": 60,
"65 Years and Over": 70
},{
"State": "AK",
"Under 5 Years": 15,
"5 to 13 Years": 25,
"14 to 17 Years": 35,
"18 to 24 Years": 45,
"25 to 44 Years": 55,
"45 to 64 Years": 65,
"65 Years and Over": 75
}];
color.domain(d3.keys(data[0]).filter(function(key) { return key !== "State"; }));
data.forEach(function(d) {
var y0 = 0;
d.ages = color.domain().map(function(name) { return {name: name, y0: y0, y1: y0 += +d[name]}; });
d.total = d.ages[d.ages.length - 1].y1;
});
data.sort(function(a, b) { return b.total - a.total; });
x.domain(data.map(function(d) { return d.State; }));
y.domain([0, d3.max(data, function(d) { return d.total; })]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Population");
var state = svg.selectAll(".state")
.data(data)
.enter().append("g")
.attr("class", "g")
.attr("transform", function(d) { return "translate(" + x(d.State) + ",0)"; });
state.selectAll("rect")
.data(function(d) { return d.ages; })
.enter().append("rect")
.attr("width", x.rangeBand())
.attr("y", function(d) { return y(d.y1); })
.attr("height", function(d) { return y(d.y0) - y(d.y1); })
.style("fill", function(d) { return color(d.name); });
var legend = svg.selectAll(".legend")
.data(color.domain().slice().reverse())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
legend.append("rect")
.attr("x", width - 18)
.attr("width", 18)
.attr("height", 18)
.style("fill", color);
legend.append("text")
.attr("x", width - 24)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) { return d; });
</script>
I know late for replying to this one. I modified #heshjse's code for D3 version 4. When I tried the above code with d3 v3, it's working fine. But we had limitation to use version 4, I was getting problem bcoz of some changes in d3 v4. So adding the code which worked for me. I hope it helps.
This should work fine for Json format in d3 v4.
<!DOCTYPE html>
<html>
<head>
<title></title>
<meta charset="utf-8" />
</head>
<body>
<div id="Dash"></div>
</body>
</html>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script>
$(document).ready(function () {
drawStackChart();
});
//Draw Stack Chart
var marginStackChart = { top: 20, right: 20, bottom: 30, left: 40 },
widthStackChart = 500 - marginStackChart.left - marginStackChart.right,
heightStackChart = 300 - marginStackChart.top - marginStackChart.bottom;
var xStackChart = d3.scaleBand()
.range([0, widthStackChart])
.padding(0.1);
var yStackChart = d3.scaleLinear()
.range([heightStackChart, 0]);
var colorStackChart = d3.scaleOrdinal(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"])
var canvasStackChart = d3.select("#Dash").append("svg")
.attr("width", widthStackChart + marginStackChart.left + marginStackChart.right)
.attr("height", heightStackChart + marginStackChart.top + marginStackChart.bottom)
.append("g")
.attr("transform", "translate(" + marginStackChart.left + "," + marginStackChart.top + ")");
function drawStackChart() {
var data = [
{
"Year": "2012",
"Category1": "20",
"Category2": "5",
"Category3": "5",
"Category4": "5",
"Category5": "5",
"Category6": "5",
"Category7": "5",
"Category8": "5",
"Category9": "5"
},
{
"Year": "2013",
"Category1": "30",
"Category2": "10",
"Category3": "10",
"Category4": "10",
"Category5": "10",
"Category6": "10",
"Category7": "10",
"Category8": "10",
"Category9": "10"
},
{
"Year": "2014",
"Category1": "35",
"Category2": "15",
"Category3": "15",
"Category4": "15",
"Category5": "15",
"Category6": "15",
"Category7": "15",
"Category8": "15",
"Category9": "15"
},
{
"Year": "2015",
"Category1": "60",
"Category2": "20",
"Category3": "20",
"Category4": "20",
"Category5": "20",
"Category6": "20",
"Category7": "20",
"Category8": "20",
"Category9": "20"
},
{
"Year": "2016",
"Category1": "70",
"Category2": "40",
"Category3": "40",
"Category4": "40",
"Category5": "40",
"Category6": "40",
"Category7": "40",
"Category8": "40",
"Category9": "40"
}
];
colorStackChart.domain(d3.keys(data[0]).filter(function (key) { return key !== "Year"; }));
data.forEach(function (d) {
var y0 = 0;
d.ages = colorStackChart.domain().map(function (name) { return { name: name, y0: y0, y1: y0 += +d[name] }; });
d.total = d.ages[d.ages.length - 1].y1;
});
data.sort(function (a, b) { return b.total - a.total; });
xStackChart.domain(data.map(function (d) { return d.Year; }));
yStackChart.domain([0, d3.max(data, function (d) { return d.total; })]);
canvasStackChart.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + heightStackChart + ")")
.call(d3.axisBottom(xStackChart));
canvasStackChart.append("g")
.attr("class", "y axis")
.call(d3.axisLeft(yStackChart))
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("No Of Buildings");
var state = canvasStackChart.selectAll(".Year")
.data(data)
.enter().append("g")
.attr("class", "g")
.attr("transform", function (d) { return "translate(" + xStackChart(d.Year) + ",0)"; });
state.selectAll("rect")
.data(function (d) { return d.ages; })
.enter().append("rect")
.attr("width", xStackChart.bandwidth())
.attr("y", function (d) { return yStackChart(d.y1); })
.attr("height", function (d) { return yStackChart(d.y0) - yStackChart(d.y1); })
.style("fill", function (d) { return colorStackChart(d.name); });
var legend = canvasStackChart.selectAll(".legend")
.data(colorStackChart.domain().slice().reverse())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function (d, i) { return "translate(0," + i * 20 + ")"; });
legend.append("rect")
.attr("x", widthStackChart - 18)
.attr("width", 18)
.attr("height", 18)
.style("fill", colorStackChart);
legend.append("text")
.attr("x", widthStackChart - 24)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function (d) { return d; });
}
</script>
If you have an array, data, you can use that just like the parameter data in the csv function in the example you linked. The code within that function will work as expected, provided that your data is in the same format.
If you can set breakpoints with your browser, you can have a look at what that format is fairly easily, set one just inside the csv function call in the js and look at the data variable.

How to render D3 js graph using json in mvc3 razor

I am new in D3 js.most of the examples in gallery load data from TSV files.
but I wanted to render line graph using json in mvc3 razor. below is code where i hard code my data.please let me know how to retrive dynamic data using json.
var data = [
{ "date": 0, "close": 0.3372 },
{ "date": 1, "close": 1.7 },
{ "date": 2, "close": 1.8 },
{ "date": 3, "close": 2.014 },
{ "date": 4, "close": 10.995 },
{ "date": 5, "close": 16.227 },
{ "date": 6, "close": 16.643 },
{ "date": 7, "close": 20.644 },
{ "date": 8, "close": 22.478 }
];
var margin = { top: 20, right: 20, bottom: 30, left: 50 },
width = 600 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
var x = d3.time.scale()
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var line = d3.svg.line()
.x(function (d) { return x(d.date); })
.y(function (d) { return y(d.close); });
var svg = d3.select("body").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 + ")");
console.log("here");
data.forEach(function (d) {
d.date = parseInt(d.date);
d.close = +d.close;
});
console.log("here2");
x.domain(d3.extent(data, function (d) { return d.date; }));
y.domain(d3.extent(data, function (d) { return d.close; }));
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.text("Request")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Probability");
svg.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line);

Resources