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>
Related
i am having some trouble with a d3.js v4 stacked bar chart trying to create a hover effect
the goal is to have a the entire stack highlight and a tooltip appear on hover. i think i am looking for away to get the location coordinates and size for the rect to draw over the entirety of the stack
i'm using this modified d3-tip library for v4 for tooltips
https://github.com/VACLab/d3-tip
A picture of the issue speaks a thousand words:
http://nicholasmahoney.com/gj/ex.jpg
--here, i can get pretty close to the x position and i can get the width by using x(d.fellow) and x.bandwith but can't figure out the y component. i'd like the black bars in this example to be the same place and size as the stacked bars
EDIt: adding jsfiddle with code and data,
here is the exact code in action:
http://jsfiddle.net/nickmahoney/ddjbumrx/6/
<svg width="500" height="500">
</svg>
<script>
var svg = d3.select("svg"),
margin = {
top: 40,
right: 20,
bottom: 30,
left: 40
},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom,
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var x = d3.scaleBand()
.rangeRound([0, width])
.paddingInner(0.05)
.align(0.1);
var y = d3.scaleLinear()
.rangeRound([height, 0]);
var z = d3.scaleOrdinal()
.range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]);
var data = [ { "fellow": "demo", "primary": 1, "assistant": 1, "observer": 0, "instructor": 0 }, { "fellow": "alpha", "primary": 22, "assistant": 8, "observer": 0, "instructor": 0 }, { "fellow": "betta", "primary": 0, "assistant": 4, "observer": 0, "instructor": 0 }, { "fellow": "gamma", "primary": 4, "assistant": 2, "observer": 0, "instructor": 0 }, { "fellow": "donkey", "primary": 44, "assistant": 149, "observer": 20, "instructor": 0 },{ "fellow": "donkey", "primary": 44, "assistant": 149, "observer": 20, "instructor": 0 } , { "fellow": "eeee", "primary": 22, "assistant": 8, "observer": 0, "instructor": 0 }, { "fellow": "ffff", "primary": 0, "assistant": 4, "observer": 0, "instructor": 0 }, { "fellow": "gaggggmma", "primary": 4, "assistant": 2, "observer": 0, "instructor": 0 }, { "fellow": "aaaa", "primary": 44, "assistant": 149, "observer": 20, "instructor": 0 },{ "fellow": "ddddddefef", "primary": 44, "assistant": 149, "observer": 20, "instructor": 0 }];
// fix pre-processing
var keys = [];
for (key in data[0]){
if (key != "fellow")
keys.push(key);}
data.forEach(function(d){
d.total = 0;
keys.forEach(function(k){
d.total += d[k];
})
});
var tip = d3.tip()
.attr('class', 'd3-tip')
.offset([-10, 0])
.html(function(d) {
return "<strong>Name:</strong> <span style='color:red'>" + d.fellow + "<br><strong>Primary:</strong>" + d.primary + "</span>";
})
svg.call(tip);
//data.sort(function(a, b) { return b.total - a.total; });
x.domain(data.map(function(d) { return d.fellow; }));
y.domain([0, d3.max(data, function(d) { return d.total; })]).nice();
z.domain(keys);
g.append("g")
.selectAll("g")
.data(d3.stack().keys(keys)(data))
.enter().append("g")
.attr("fill", function(d) { return z(d.key); })
.selectAll("rect")
.data(function(d) { return d; })
.enter().append("rect")
.attr("x", function(d) { return x(d.data.fellow); })
.attr("y", function(d) { return y(d[1]); })
.attr("height", function(d) { return y(d[0]) - y(d[1]); })
.attr("width", x.bandwidth())
;
//tooltip bars
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(d) { return 40+ x(d.fellow) ; })
.attr("width", x.bandwidth())
.attr("y", function(d) { return y(d.primary) ; })
.attr("height", function(d) { return y(d.primary) ; })
.on('mouseover', tip.show)
.on('mouseout', tip.hide)
g.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x))
;
g.append("g")
.attr("class", "axis")
.call(d3.axisLeft(y).ticks(null, "s"))
.append("text")
.attr("x", 2)
.attr("y", y(y.ticks().pop()) + 0.5)
.attr("dy", "0.32em")
.attr("fill", "#000")
.attr("font-weight", "bold")
.attr("text-anchor", "start")
;
var legend = g.append("g")
.attr("font-family", "sans-serif")
.attr("font-size", 10)
.attr("text-anchor", "end")
.selectAll("g")
.data(keys.slice().reverse())
.enter().append("g")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
legend.append("rect")
.attr("x", width - 19)
.attr("width", 19)
.attr("height", 19)
.attr("fill", z);
legend.append("text")
.attr("x", width - 24)
.attr("y", 9.5)
.attr("dy", "0.32em")
.text(function(d) {return d;});
</script>
Here's a fork of your fiddle: http://jsfiddle.net/s54tbyxb/
You're already computing the total value based on the desired keys. I'm just using that value to determine the y component and the height.
Relevant changes in the code:
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.attr("x", function(d) { return x(d.fellow) ; })
.attr("width", x.bandwidth())
.attr("y", function(d) {
return y(d.total) ;
})
.attr("height", function(d) { return height-y(d.total) ; })
Hope this helps.
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>
I have a bar chart with three bars overlaying each other. First the grey bar chart is rendered, then the salmon one and the the blue one. But when they are sorted this order seems to change randomly, so that sometimes a gray bar is drawn over the other two and then they can't seen.
Solution: jsFiddle
Here is a jsfiddle for this problem.
<!DOCTYPE html>
<body>
<div style="text-align:left;">
<form id="form">
<strong>Sort by: </strong><span class="clocktime-radio"><input type="radio" name="stack" checked value="clock">Clock time and place </span>
<span class="racetime-radio"><input class="racetime-radio" type="radio" name="stack" value="race">Race time </span>
<span class="handicap-radio"><input type="radio" name="stack" value="hand">Handicap </span>
</form>
</div>
<div id="race_graph">
</div>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
// set the dimensions and margins of the graph
var margin = {
top: 50,
right: 50,
bottom: 100,
left: 80
},
width = 500 - margin.left - margin.right,
height = 600 - margin.top - margin.bottom;
// Get the data
var data = [{
"name": "RT",
"clocktime": "21:33",
"handicap": "02:30",
"racetime": "19:03"
},
{
"name": "KM",
"clocktime": "22:13",
"handicap": "00:45",
"racetime": "21:28"
},
{
"name": "SD",
"clocktime": "22:15",
"handicap": "01:45",
"racetime": "20:30"
},
{
"name": "DK",
"clocktime": "22:20",
"handicap": "02:45",
"racetime": "19:35"
},
{
"name": "BD",
"clocktime": "22:21",
"handicap": "02:15",
"racetime": "20:06"
},
{
"name": "KC",
"clocktime": "22:21",
"handicap": "02:00",
"racetime": "20:21"
},
{
"name": "PM",
"clocktime": "22:22",
"handicap": "00:45",
"racetime": "21:37"
},
{
"name": "NR",
"clocktime": "22:23",
"handicap": "01:45",
"racetime": "20:38"
},
{
"name": "LM",
"clocktime": "22:25",
"handicap": "02:15",
"racetime": "20:10"
},
{
"name": "SL",
"clocktime": "22:26",
"handicap": "00:15",
"racetime": "22:11"
}
]
var parseTime = d3.timeParse("%M:%S");
var timeformat = d3.timeFormat("%M:%S")
// format the data
data.forEach(function(d) {
d.racetime = parseTime(d.racetime);
d.handicap = parseTime(d.handicap);
d.clocktime = parseTime(d.clocktime);
d.place = +d.place;
d.points = +d.points;
d.raceplace = +d.raceplace;
d.timeplace = +d.timeplace;
});
// set the domains and ranges
var x = d3.scaleBand()
.domain(data.map(function(d) {
return d.name
}))
.range([0, width]);
// temporal y-scale
var y = d3.scaleTime()
.domain([parseTime('00:00'), d3.max(data, function(d) {
return d.clocktime
// return d.handicap
})])
.range([height, 0]); //time must increase from 0 to height else racetime and handicap are inverted!!!
// spacial y-scale (race distance)
var y1 = d3.scaleLinear()
.domain([0, 1200]) //race distance
.range([height, 0]);
// points y-scale
var y2 = d3.scaleLinear()
.domain([0, 10]) //points awarded
.range([height, 0]);
//****************************
//***** Main Graph setup *****
//****************************
var svg = d3.select("#race_graph")
.data(data)
.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 + ")");
// Add the X Axis
var xAxis = d3.axisBottom(x)
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.style("font", "7px times")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("transform", "rotate(-65)");
// Add the left Y Axis
svg.append("g")
.attr("class", "axis")
.call(d3.axisLeft(y)
.ticks(7)
.tickFormat(d3.timeFormat("%M:%S")));
// text label for the y axis on left
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 0 - margin.left)
.attr("x", 0 - (height / 2))
.attr("dy", "1em")
.style("text-anchor", "middle")
.text("Time (minutes:seconds)");
//************************************************************
//******* BarChart by clocktime *******************************
//************************************************************
var rects0 = svg.selectAll(".bar")
.data(data);
var newRects0 = rects0.enter();
// Note: y2(d.points) is the y coordinate of rect
newRects0.append('rect')
.attr('x', function(d, i) {
return x(d.name) + 2;
})
.attr('width', 20)
.attr("transform", "translate(5,0)")
.style('fill', 'gray')
.attr("class", "bar")
.attr('y', function(d, i) {
return y(d.clocktime);
})
.attr('height', function(d, i) {
return height - y(d.clocktime)
});
//************************************************************
//******* BarChart by racetime *******************************
//************************************************************
var newRects1 = rects0.enter();
// Note: y2(d.points) is the y coordinate of rect
newRects1.append('rect')
.attr('class', 'bar')
.attr('x', function(d, i) {
return x(d.name) + 2;
})
.attr('width', 20)
.attr("transform", "translate(5,0)")
.style('fill', 'salmon')
.attr('y', function(d, i) {
return y(d.racetime);
})
.attr('height', function(d, i) {
return height - y(d.racetime)
});
//************************************************************
//******* BarCharts by handicap ******************************
//************************************************************
var newRects2 = rects0.enter();
newRects2.append('rect')
.attr('x', function(d, i) {
return x(d.name) + 2;
})
.attr('width', 20)
.attr("transform", "translate(5,0)")
.style('fill', 'blue')
.attr('y', function(d, i) {
return y(d.handicap);
})
.attr('height', function(d, i) {
return height - y(d.handicap)
})
.attr('class', 'bar');
d3.selectAll("input[name='stack']").on("change", change);
function change() {
var x0 = x.domain(data.sort(this.value == "clock" ?
(function(a, b) {
return (new Date(b.clocktime) - new Date(a.clocktime)) * -1;
}) : (this.value == "race") ?
(function(a, b) {
return (new Date(b.racetime) - new Date(a.racetime)) * -1;
}) : (function(a, b) {
return (new Date(b.handicap) - new Date(a.handicap)) * -1;
})).map(function(d) {
return d.name;
}))
.copy();
svg.selectAll(".bar")
.sort(function(a, b) {
return x0(a.name) - x0(b.name);
});
var transition = svg.transition().duration(750),
delay = function(d, i) {
return i * 5;
};
transition.selectAll(".bar")
.delay(delay)
.attr("x", function(d) {
return x0(d.name);
});
transition.select(".x.axis") //selects the x-axis
.call(xAxis)
.selectAll("g")
.delay(delay);
}
</script>
</body>
Maybe when I draw multiple bar charts I should not repeat the code for each as I do and that there is some way to more efficiently draw them which will get rid of the render issue I am having.
Thanks
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.
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);