I am trying to create a transition in my bar chart code, that would allow the user to click on a particular and be shown a different set of data related to that bar.
This is a sample dataset:
module_category,component_category,date_repair,actual,predicted
M1,P06,2009/01,39,63
M1,P06,2009/10,3,4
M1,P06,2009/11,4,3
M1,P06,2009/12,4,2
M1,P06,2009/02,29,45
M1,P06,2009/03,29,32
M1,P06,2009/04,10,22
M1,P06,2009/05,13,15
M1,P06,2009/06,9,16
M1,P06,2009/07,7,12
The full dataset can be found here: full dataset
So based on my current code I can create this bar chart:
but now I want to add interactivity that will allow the user after clicking on the bar for e.g "M2", they graph then updates to show the components from the "component_category" related to that module with the respective "actual" and "predicted" values shown as bar charts also.
This is my current code:
var margin = {top: 20, right: 90, bottom: 30, left: 60},
width = 980 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x0 = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
var x1 = d3.scale.ordinal();
var y = d3.scale.linear()
.range([height, 0]);
var color = d3.scale.category10();
var xAxis = d3.svg.axis()
.scale(x0)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickFormat(d3.format(".2s"));
var svg = d3.select("#maincontent").append("svg")
.attr('id','chart')
.attr('viewBox', '0 0 980 500')
.attr('perserveAspectRatio', 'xMinYMid')
.attr('width', width + margin.right + margin.left)
.attr('height', height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var tip=d3.tip()
.attr("class","d3-tip")
.offset([-10, 0])
.html(function(d) { return "No. of repairs: " + d.value; });
d3.csv("data/Consolidated_result.csv", function(error, data) {
if (error) throw error;
data = d3.nest()
.key(function(d) { return d.module_category;}).sortKeys(d3.ascending)
.rollup(function(values){
var counts = {}, keys = ['actual', 'predicted']
keys.forEach(function(key){
counts[key] = d3.sum(values, function(d){ return d[key]})
})
return counts
})
.entries(data);
console.log(data);
x0.domain(data.map(function(d) { return d.key; }));
x1.domain(['actual','predicted']).rangeRoundBands([0, x0.rangeBand()]);
// store all the values in an array
var yval = [];
data.forEach(function(d){
yval.push(d.values.actual);
yval.push(d.values.predicted);
});
y.domain([0, d3.max(yval)]);
svg.call(tip);
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", 0 - margin.left)
.attr("x", 0 - (height / 2))
.attr("dy", "1em")
.style("text-anchor", "middle")
.text("Number of Repairs");
var module = svg.selectAll(".module")
.data(data)
.enter().append("g")
.attr("class", "g")
.attr("transform", function(d) { return "translate(" + x0(d.key) + ",0)"; });
module.selectAll("rect")
.data(function(d){
var ary = [];
ary.push({name:"actual", value:d.values.actual});
ary.push({name:'predicted', value: d.values.predicted});
return ary;
})
.enter().append("rect")
.on('mouseover', tip.show)
.on('mouseout', tip.hide)
.on("click", function(d){
d3.select("svg")
.style("opacity",0)
.remove()
tip.hide()
setTimeout(componentgroupedchart, 1000);
})
/*
.on("click", function(d){
d3.select(this)
setTimeout(updateChart(name), 500);
})*/
.attr("width", x1.rangeBand())
.attr("x", function(d) { return x1(d.name); })
.attr("y", function(d) { return y(d.value); })
.attr("height", function(d) { return height - y(d.value); })
.style("fill", function(d) { return color(d.name); });
var legend = svg.selectAll(".legend")
.data(['actual','predicted'])
.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", function(d){
return color(d)
});
legend.append("text")
.attr("x", width - 24)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) { return d; });
});
What I want to implement will be to create a function where i update the chart and reload the data and nest it to this:
data = d3.nest()
.key(function(d) { return d.module_category;}).sortKeys(d3.ascending)
.key(function(d) { return d.component_category;}).sortKeys(d3.ascending)
.rollup(function(values){
var counts = {}, keys = ['actual', 'predicted']
keys.forEach(function(key){
counts[key] = d3.sum(values, function(d){ return d[key]})
})
return counts
})
.entries(data);
This is so that I can access for each module:
the number of component related to that module &
the actual and predicted repair values
The resulting data then becomes:
var data = [{
key: "M1"
values: {
key: "P06"
values: {
actual: 156 ,
predicted: 228
},
key: "P09"
values: {
actual: 31,
predicted: 20
},
key: "P12"
values: {
actual: 140,
predicted: 176
},
key: "P15"
values: {
actual: 38,
predicted: 40
},
key: "P16"
values: {
actual: 112,
predicted:113
},
key: "P17"
values: {
actual: 20 ,
predicted: 7
},
key: "P20"
values: {
actual: 98,
predicted: 127
},
key: "P28"
values: {
actual: 143 ,
predicted: 149
},
key: "P30"
values: {
actual: 16,
predicted: 38
}
},
key: "M5"
values: {
key: "P06"
values: {
actual: 61 ,
predicted: 65
},
key: "P09"
values: {
actual: 83,
predicted: 82
},
key: "P12"
values: {
actual: 45,
predicted: 58
},
key: "P15"
values: {
actual: 26,
predicted: 31
},
key: "P16"
values: {
actual: 152,
predicted:174
},
key: "P21"
values: {
actual: 74 ,
predicted: 120
}
}
}]
From this new data, the chart then transitions to a new bar chart display that shows the components and their repair values based on the selected module. I hope the question is much clearer now.
This can be achieved by making one function for making module graph, another for making the drill down category graph. Define the domain with in the functions, since the y axis domain x axis domain will change with with the module/category graph.
I have added comments in the code; in case you have any issues, feel free to ask.
var margin = {
top: 20,
right: 90,
bottom: 30,
left: 60
},
width = 980 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x0 = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
var x1 = d3.scale.ordinal();
var y = d3.scale.linear()
.range([height, 0]);
var color = d3.scale.category10();
var xAxis = d3.svg.axis()
.scale(x0)
.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.right + margin.left)
.attr('height', height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var tip = d3.tip()
.attr("class", "d3-tip")
.offset([-10, 0])
.html(function(d) {
return "No. of repairs: " + d.value;
});
d3.csv("my.csv", function(error, data) {
if (error) throw error;
fullData = data;
data = d3.nest()
.key(function(d) {
return d.module_category;
})
.rollup(function(values) {
var counts = {},
keys = ['actual', 'predicted']
keys.forEach(function(key) {
counts[key] = d3.sum(values, function(d) {
return d[key];
})
})
return counts
})
.entries(data);
//make the x axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
//make the y axis
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 0 - margin.left)
.attr("x", 0 - (height / 2))
.attr("dy", "1em")
.style("text-anchor", "middle")
.text("Number of Repairs");
makeModuleGraph(data)
var legend = svg.selectAll(".legend")
.data(['actual', 'predicted'])
.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", function(d) {
return color(d);
});
legend.append("text")
.attr("x", width - 24)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) {
return d;
});
});
function makeModuleGraph(data) {
var yval = [];
data.forEach(function(d) {
yval.push(d.values.actual);
yval.push(d.values.predicted);
});
x0.domain(data.map(function(d) {
return d.key;
}));
x1.domain(['actual', 'predicted']).rangeRoundBands([0, x0.rangeBand()]);
y.domain([0, d3.max(yval)]);
svg.call(tip);
svg.selectAll("g .x")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.selectAll("g .y")
.attr("class", "y axis")
.call(yAxis);
var module = svg.selectAll(".module")
.data(data)
.enter().append("g")
.attr("class", "module")
.attr("transform", function(d) {
return "translate(" + x0(d.key) + ",0)";
});
module.selectAll("rect")
.data(function(d) {
var ary = [];
ary.push({
name: "actual",
value: d.values.actual,
key: d.key
});
ary.push({
name: "predicted",
value: d.values.predicted,
key: d.key
});
return ary;
})
.enter().append("rect")
.attr("width", x1.rangeBand())
.attr("x", function(d) {
return x1(d.name);
})
.attr("y", function(d) {
return y(d.value);
})
.attr("height", function(d) {
return height - y(d.value);
})
.style("fill", function(d) {
return color(d.name);
}).on("click", function(d) {
makeComponentCategoryGraph(d);//make the graph for category
});
}
function makeComponentCategoryGraph(d){
var filtered = fullData.filter(function(k){ if(d.key == k.module_category){return true;}else {return false;}})
var data = d3.nest()
.key(function(d) {
return d.component_category;
})
.rollup(function(values) {
var counts = {},
keys = ['actual', 'predicted']
keys.forEach(function(key) {
counts[key] = d3.sum(values, function(d) {
return d[key];
})
})
return counts
})
.entries(filtered);
var yval = [];
data.forEach(function(d) {
yval.push(d.values.actual);
yval.push(d.values.predicted);
});
x0.domain(data.map(function(d) {
return d.key;
}));
x1.domain(['actual', 'predicted']).rangeRoundBands([0, x0.rangeBand()]);
y.domain([0, d3.max(yval)]);
svg.call(tip);
svg.selectAll("g .x")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.selectAll("g .y")
.attr("class", "y axis")
.call(yAxis);
svg.selectAll(".module").remove();//remove alll the bar graphs
var module = svg.selectAll(".module")
.data(data)
.enter().append("g")
.attr("class", "module")
.attr("transform", function(d) {
return "translate(" + x0(d.key) + ",0)";
});
module.selectAll("rect")
.data(function(d) {
var ary = [];
ary.push({
name: "actual",
value: d.values.actual,
key: d.key
});
ary.push({
name: "predicted",
value: d.values.predicted,
key: d.key
});
return ary;
})
.enter().append("rect")
.attr("width", x1.rangeBand())
.attr("x", function(d) {
return x1(d.name);
})
.attr("y", function(d) {
return y(d.value);
})
.attr("height", function(d) {
return height - y(d.value);
})
.style("fill", function(d) {
return color(d.name);
})
}
Working code here.
Related
I'm trying to fix the margins for grouped bars in my D3.js chart.
Right now, it has ugly gaps like this:
But I need it to look more like this for each group, centered on the x-axis labels (Month and Year):
I think this line of code is the culprit, but I don't know how to modify it:
.attr("x", function(d) { return x1(d.grpName); })
Please see the code snippet below as a full page.
const groupData = [
{ key: "Jan. 2020", values:
[
{grpName:'Team1', grpValue:26},
{grpName:'Team2', grpValue:15},
{grpName:'Team3', grpValue:48}
]
},
{ key: "Feb.2020", values:
[
{grpName:'Team1', grpValue:14},
{grpName:'Team2', grpValue:23},
{grpName:'Team3', grpValue:5}
]
},
{ key: "March 2020", values:
[
{grpName:'Team1', grpValue:32},
{grpName:'Team2', grpValue:9},
{grpName:'Team3', grpValue:25}
]
},
{ key: "April 2020", values:
[
{grpName:'Team1', grpValue:41},
{grpName:'Team2', grpValue:55},
{grpName:'Team3', grpValue:26}
]
}
];
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 1200 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
var x0 = d3.scaleBand().rangeRound([0, width], .5);
var x1 = d3.scaleBand();
var y = d3.scaleLinear().rangeRound([height, 0]);
var xAxis = d3.axisBottom().scale(x0)
var yAxis = d3.axisLeft().scale(y);
const color = d3.scaleOrdinal(d3.schemeCategory10);
var svg = d3.select('#barChart')
.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 categoriesNames = groupData.map(function(d) { return d.key; });
var rateNames = groupData[0].values.map(function(d) { return d.grpName; });
x0.domain(categoriesNames);
x1.domain(rateNames).rangeRound([0, x0.bandwidth()]);
y.domain([0, d3.max(groupData, function(key) { return d3.max(key.values, function(d) { return d.grpValue; }); })]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.style('opacity','0')
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.style('font-weight','bold')
.text("Value");
svg.select('.y').transition().duration(500).delay(1300).style('opacity','1');
var slice = svg.selectAll(".slice")
.data(groupData)
.enter().append("g")
.attr("class", "g")
.attr("transform",function(d) { return "translate(" + x0(d.key) + ",0)"; });
slice.selectAll("rect")
.data(function(d) { return d.values; })
.enter().append("rect")
.attr("width", "35")
.attr("x", function(d) { return x1(d.grpName); })
.style("fill", function(d) { return color(d.grpName) })
.attr("y", function(d) { return y(0); })
.attr("height", function(d) { return height - y(0); })
.on("mouseover", function(d) {
d3.select(this).style("fill", d3.rgb(color(d.grpName)).darker(2));
})
.on("mouseout", function(d) {
d3.select(this).style("fill", color(d.grpName));
});
slice.selectAll("rect")
.transition()
.delay(function (d) {return Math.random()*1000;})
.duration(1000)
.attr("y", function(d) { return y(d.grpValue); })
.attr("height", function(d) { return height - y(d.grpValue); });
//Legend
var legend = svg.selectAll(".legend")
.data(groupData[0].values.map(function(d) { return d.grpName; }).reverse())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d,i) { return "translate(0," + i * 20 + ")"; })
.style("opacity","0");
legend.append("rect")
.attr("x", width - 18)
.attr("width", 18)
.attr("height", 18)
.style("fill", function(d) { return color(d); });
legend.append("text")
.attr("x", width - 24)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) {return d; });
legend.transition().duration(500).delay(function(d,i){ return 1300 + 100 * i; }).style("opacity","1");
.axis path,
.axis line {
fill: none;
stroke: #000;\
shape-rendering: crispEdges;
}
<script src="https://d3js.org/d3.v5.min.js"></script>
<div id="barChart"></div>
You have two x scales, x0 and x1, the first placing the groups, the second placing the bars in each group. However, while you use x0.bandwidth() to define the width of each group, you use 35 to define the width of each bar.
If x0.bandwidth() is smaller than, or much greater than, 35 times the number of bars we get some awkward positioning.
Instead, you should use x1.bandwidth() to set the bar width, just as you used x0.bandwidth to set the group width (the range of x1).
If you use x1.bandwidth() to set bar width, you get bars that touch adjacent bars, but now you can use scaleBand.padding() to set margin between group and bar:
x0.padding(0.1); // space the groups: bandwidth 90% of original, now with 10% margin
x1.padding(0.1); // same for spacing the bars
Taken together:
const groupData = [
{ key: "Jan. 2020", values:
[
{grpName:'Team1', grpValue:26},
{grpName:'Team2', grpValue:15},
{grpName:'Team3', grpValue:48}
]
},
{ key: "Feb.2020", values:
[
{grpName:'Team1', grpValue:14},
{grpName:'Team2', grpValue:23},
{grpName:'Team3', grpValue:5}
]
},
{ key: "March 2020", values:
[
{grpName:'Team1', grpValue:32},
{grpName:'Team2', grpValue:9},
{grpName:'Team3', grpValue:25}
]
},
{ key: "April 2020", values:
[
{grpName:'Team1', grpValue:41},
{grpName:'Team2', grpValue:55},
{grpName:'Team3', grpValue:26}
]
}
];
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 1200 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
var x0 = d3.scaleBand().rangeRound([0, width], .5).padding(0.1);
var x1 = d3.scaleBand().padding(0.1);
var y = d3.scaleLinear().rangeRound([height, 0]);
var xAxis = d3.axisBottom().scale(x0)
var yAxis = d3.axisLeft().scale(y);
const color = d3.scaleOrdinal(d3.schemeCategory10);
var svg = d3.select('#barChart')
.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 categoriesNames = groupData.map(function(d) { return d.key; });
var rateNames = groupData[0].values.map(function(d) { return d.grpName; });
x0.domain(categoriesNames);
x1.domain(rateNames).rangeRound([0, x0.bandwidth()]);
y.domain([0, d3.max(groupData, function(key) { return d3.max(key.values, function(d) { return d.grpValue; }); })]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.style('opacity','0')
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.style('font-weight','bold')
.text("Value");
svg.select('.y').transition().duration(500).delay(1300).style('opacity','1');
var slice = svg.selectAll(".slice")
.data(groupData)
.enter().append("g")
.attr("class", "g")
.attr("transform",function(d) { return "translate(" + x0(d.key) + ",0)"; });
slice.selectAll("rect")
.data(function(d) { return d.values; })
.enter().append("rect")
.attr("width", x1.bandwidth())
.attr("x", function(d) { return x1(d.grpName); })
.style("fill", function(d) { return color(d.grpName) })
.attr("y", function(d) { return y(0); })
.attr("height", function(d) { return height - y(0); })
.on("mouseover", function(d) {
d3.select(this).style("fill", d3.rgb(color(d.grpName)).darker(2));
})
.on("mouseout", function(d) {
d3.select(this).style("fill", color(d.grpName));
});
slice.selectAll("rect")
.transition()
.delay(function (d) {return Math.random()*1000;})
.duration(1000)
.attr("y", function(d) { return y(d.grpValue); })
.attr("height", function(d) { return height - y(d.grpValue); });
//Legend
var legend = svg.selectAll(".legend")
.data(groupData[0].values.map(function(d) { return d.grpName; }).reverse())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d,i) { return "translate(0," + i * 20 + ")"; })
.style("opacity","0");
legend.append("rect")
.attr("x", width - 18)
.attr("width", 18)
.attr("height", 18)
.style("fill", function(d) { return color(d); });
legend.append("text")
.attr("x", width - 24)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) {return d; });
legend.transition().duration(500).delay(function(d,i){ return 1300 + 100 * i; }).style("opacity","1");
.axis path,
.axis line {
fill: none;
stroke: #000;\
shape-rendering: crispEdges;
}
<script src="https://d3js.org/d3.v5.min.js"></script>
<div id="barChart"></div>
I faced with a problem when some values in a bar very small when at the same time most of the other values are big enough. As the result these chunks with low values are almost not visible. I did not find any solution hot to correctly round chunks(not manually because I now that I can round them to more higher values via scale + invert(in order to determine what values I needed to show them more or less visible)). As an example below: as you see the last bar with low values is almost not visible. So can you suggest how to fix it? It would be great to have an opportunity to be able to specify the min size of stacked bar chart chunk. Thank you in advance.
http://jsfiddle.net/vhcdt13x/
// Setup svg using Bostock's margin convention
var margin = {top: 20, right: 160, bottom: 35, left: 30};
var width = 960 - margin.left - margin.right,
height = 500 - 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: "100", mcintosh: "150", oranges: "90", pears: "60" },
{ year: "2012", redDelicious: "1", mcintosh: "1", oranges: "1", pears: "1" }
];
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");
I have a set of nested json data:
var data = [{"time":"2016-03-01","values":[{"specimen_count":1,"trap":"S0024", "species":1},{"specimen_count":2,"trap":"S0025", "species":2},{"specimen_count":2,"trap":"S0026", "species":2}]},{"time":"2016-03-15","values":[{"specimen_count":6,"trap":"S0024", "species":6},{"specimen_count":5,"trap":"S0025", "species":4},{"specimen_count":7,"trap":"S0026", "species":6}]}];
And I want to draw a set of grouped bar charts each group representing a time interval and each group with 3 bars, each representing a trap, and the height of the bar is the specimen_count field.
Now I want to add a scatterplot, one dot for each bar and the height of the dot is the species field, using the same scales. But I am having trouble successfully placing the dots on top the the grouped bar chart. I did manage to add a line with the species data, but I can't add the dots using the same logic.
Here is my code:
var margin = {top: 100, right: 20, bottom: 30, left: 40},
width = 600 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x0 = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
var x1 = d3.scale.ordinal();
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x0)
.tickSize(0)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var color = d3.scale.ordinal()
.range(["#ca0020","#f4a582","#92c5de"]);
var svg = d3.select('#chart').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 categoriesNames = data.map(function(d) { return d.time; }); // the 5 time periods
var trapNames = data[0].values.map(function(d) { return d.trap; }); // the name of the traps
console.log(trapNames);
x0.domain(categoriesNames);
x1.domain(trapNames).rangeRoundBands([0, x0.rangeBand()]);
y.domain([0, d3.max(data, function(category) { return d3.max(category.values, function(d) { return d.specimen_count; }); })]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.style('opacity','0')
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.style('font-weight','bold')
.text("Value");
svg.select('.y').transition().duration(500).delay(1300).style('opacity','1');
var slice = svg.selectAll(".slice")
.data(data)
.enter().append("g")
.attr("class", "slice")
.attr("transform",function(d) { return "translate(" + x0(d.time) + ",0)"; });
slice.selectAll("rect")
.data(function(d) { return d.values; })
.enter().append("rect")
.attr("width", x1.rangeBand())
.attr("x", function(d) { return x1(d.trap); })
.style("fill", function(d) { return color(d.trap) })
.attr("y", function(d) { return y(0); })
.attr("height", function(d) { return height - y(0); })
.on("mouseover", function(d) {
d3.select(this).style("fill", d3.rgb(color(d.trap)).darker(2));
})
.on("mouseout", function(d) {
d3.select(this).style("fill", color(d.trap));
});
slice.selectAll("rect")
.transition()
.delay(function (d) {return Math.random()*1000;})
.duration(1000)
.attr("y", function(d) { return y(d.specimen_count); })
.attr("height", function(d) { return height - y(d.specimen_count); });
var valueline = d3.svg.line()
.x(function (d) { return x1(d.trap) + x1.rangeBand()/2; })
.y(function (d) { return y(d.species); });
slice.enter()
.append('path')
.attr('class','line')
.style('stroke', "#0571b0")
.style('stroke-width', "3px")
.attr('fill', 'none')
.attr('d', function(d) { return valueline(d.values); });
slice.selectAll('.dot').data(data,function(d){return d.time;})
.enter()
.append("circle")
.attr("class", "dot")
.attr("r",5)
.attr("cx", function(d){
return x1(d.trap) + x1.rangeBand()/2;
})
.attr("cy",function(d){
return y(d.species);
})
.attr("fill","#0571b0");
There error I'm getting from the circle-related code is: d3.min.js:1 Error: attribute cx: Expected length, "NaN".
I think the nested data and the ordinal scale for the bar chart is throwing me off a bit, so it could be that I am not understanding fulling data access in these cases.
Also here is the screenshot of the current graph
If you need the dots on every bar chart, then the data() callback must return a list of bars not a single item. Did you try replacing it with:
slice.selectAll('.dot')
.data(function(d) {
return d.values;
})
.enter()
.append("circle") //... and so on
Doing this will use the existing data object (with 5 bar groups), but render a dot for each bar.
Here it is running:
<!DOCTYPE html>
<html>
<head>
<script data-require="d3#3.5.17" data-semver="3.5.17" src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.js"></script>
</head>
<body>
<div id="chart"></div>
<script>
var data = [{
"time": "2016-03-01",
"values": [{
"specimen_count": 1,
"trap": "S0024",
"species": 1
}, {
"specimen_count": 2,
"trap": "S0025",
"species": 2
}, {
"specimen_count": 2,
"trap": "S0026",
"species": 2
}]
}, {
"time": "2016-03-15",
"values": [{
"specimen_count": 6,
"trap": "S0024",
"species": 6
}, {
"specimen_count": 5,
"trap": "S0025",
"species": 4
}, {
"specimen_count": 7,
"trap": "S0026",
"species": 6
}]
}];
var margin = {
top: 100,
right: 20,
bottom: 30,
left: 40
},
width = 600 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x0 = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
var x1 = d3.scale.ordinal();
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x0)
.tickSize(0)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var color = d3.scale.ordinal()
.range(["#ca0020", "#f4a582", "#92c5de"]);
var svg = d3.select('#chart').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 categoriesNames = data.map(function(d) {
return d.time;
}); // the 5 time periods
var trapNames = data[0].values.map(function(d) {
return d.trap;
}); // the name of the traps
console.log(trapNames);
x0.domain(categoriesNames);
x1.domain(trapNames).rangeRoundBands([0, x0.rangeBand()]);
y.domain([0, d3.max(data, function(category) {
return d3.max(category.values, function(d) {
return d.specimen_count;
});
})]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.style('opacity', '0')
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.style('font-weight', 'bold')
.text("Value");
svg.select('.y').transition().duration(500).delay(1300).style('opacity', '1');
var slice = svg.selectAll(".slice")
.data(data)
.enter().append("g")
.attr("class", "slice")
.attr("transform", function(d) {
return "translate(" + x0(d.time) + ",0)";
});
slice.selectAll("rect")
.data(function(d) {
return d.values;
})
.enter().append("rect")
.attr("width", x1.rangeBand())
.attr("x", function(d) {
return x1(d.trap);
})
.style("fill", function(d) {
return color(d.trap)
})
.attr("y", function(d) {
return y(0);
})
.attr("height", function(d) {
return height - y(0);
})
.on("mouseover", function(d) {
d3.select(this).style("fill", d3.rgb(color(d.trap)).darker(2));
})
.on("mouseout", function(d) {
d3.select(this).style("fill", color(d.trap));
});
slice.selectAll("rect")
.transition()
.delay(function(d) {
return Math.random() * 1000;
})
.duration(1000)
.attr("y", function(d) {
return y(d.specimen_count);
})
.attr("height", function(d) {
return height - y(d.specimen_count);
});
var valueline = d3.svg.line()
.x(function(d) {
return x1(d.trap) + x1.rangeBand() / 2;
})
.y(function(d) {
return y(d.species);
});
slice
.append('path')
.attr('class', 'line')
.style('stroke', "#0571b0")
.style('stroke-width', "3px")
.attr('fill', 'none')
.attr('d', function(d) {
return valueline(d.values);
});
slice.selectAll('.dot').data(function(d) {
return d.values;
})
.enter()
.append("circle")
.attr("class", "dot")
.attr("r", 5)
.attr("cx", function(d) {
return x1(d.trap) + x1.rangeBand() / 2;
})
.attr("cy", function(d) {
return y(d.species);
})
.attr("fill", "#0571b0");
</script>
</body>
</html>
I'm trying to build a multi series bar chart using d3 but running into problems due to the sparse nature of the dataset.
I want to force the x-axis to have a tick for every day, even if there is no data. The test data I have can have data points that are weeks apart so I'm expecting wide areas with no bars - which is fine.
I thought I could force the xAxis to use a set of predefined ticks using the tickValues array, but these leads to very strange display of overlaying the text for each day on top of days that do have some data.
I've included a screenshot of what I mean.
I get the feeling I'm supposed to do something when calculating the width of the bars but can't figure out what that might be.
Code:
var data = [];
var tickValues = [];
var max = _.max(chartData.tabular, function(assessment) { return assessment.dateUTC; });
var min = _.min(chartData.tabular, function(assessment) { return assessment.dateUTC; });
var iter = moment.twix(min.dateUTC, max.dateUTC).iterate("days");
while(iter.hasNext()){
var momentObj = iter.next();
var assessment = _.find(chartData.tabular, {'date': momentObj.format('DD/MM/YYYY')});
tickValues.push(momentObj.valueOf());
if(assessment != null){
if(assessment.type == 'calculated'){
data.push({date: momentObj.valueOf(), calculated: assessment.score, manual: null});
}
if(assessment.type == 'manual'){
data.push({date: momentObj.valueOf(), calculated: null, manual: assessment.score});
}
}
}
log(data);
var margin = {top: 20, right: 55, bottom: 30, left: 40},
width = $('#cahai-chart').width() - 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 xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickValues(tickValues)
.tickFormat(function(d){return d3.time.format('%d/%m/%y')(new Date(d))});
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var color = d3.scale.ordinal()
.range(["#001c9c","#101b4d","#475003","#9c8305","#d3c47c"]);
var svg = d3.select("#cahai-chart 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 labelVar = 'date';
var varNames = d3.keys(data[0]).filter(function (key) { return key !== labelVar;});
color.domain(varNames);
data.forEach(function (d) {
var y0 = 0;
d.mapping = varNames.map(function (name) {
return {
name: name,
label: d[labelVar],
y0: y0,
y1: y0 += +d[name]
};
});
d.total = d.mapping[d.mapping.length - 1].y1;
});
x.domain(data.map(function (d) { return d.date; }));
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("Score");
var selection = svg.selectAll(".series")
.data(data)
.enter().append("g")
.attr("class", "series")
.attr("transform", function (d) { return "translate(" + x(d.date) + ",0)"; });
selection.selectAll("rect")
.data(function (d) { return d.mapping; })
.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); })
.style("stroke", "grey")
.on("mouseover", function (d) { showPopover.call(this, d); })
.on("mouseout", function (d) { removePopovers(); })
var legend = svg.selectAll(".legend")
.data(varNames.slice().reverse())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function (d, i) { return "translate(55," + i * 20 + ")"; });
legend.append("rect")
.attr("x", width - 10)
.attr("width", 10)
.attr("height", 10)
.style("fill", color)
.style("stroke", "grey");
legend.append("text")
.attr("x", width - 12)
.attr("y", 6)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function (d) { return d; });
function removePopovers () {
$('.popover').each(function() {
$(this).remove();
});
}
function showPopover (d) {
$(this).popover({
title: d.name,
placement: 'auto top',
container: 'body',
trigger: 'manual',
html : true,
content: function() {
return "Date: " + d3.time.format('%d/%m/%y')(new Date(d.label)) +
"<br/>Score: " + d3.format(",")(d.value ? d.value: d.y1 - d.y0); }
});
$(this).popover('show')
}
An ordinal scale will always show as many ticks are there are values in the domain. You just need to pass the full array of dates as the domain.
Replace this line
x.domain(data.map(function (d) { return d.date; }));
with this
x.domain(tickValues);
It looks like you have everything else set up correctly, so this will space the bars out along the axis and make them slimmer.
i'm using the following code for generating multi line graph. I'm getting the result. But the graph looks like:
var margin = {top: 20, right: 20, bottom: 30, left: 50},
width = 1025 - margin.left - margin.right,
height = 339 - margin.top - margin.bottom;
var x = d3.scale.linear()
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
var color = d3.scale.category10();
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var line = d3.svg.line()
.interpolate("basis")
.x(function(d) { return x(d.days); })
.y(function(d) { return y(d.testRuns); });
var svg = d3.select("#application_status_graph").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 + ")");
d3.json("js/lineChartData.json", function(error, data) {
color.domain(d3.keys(data[0]).filter(function(key) {return key !== "days"; }));
data.forEach(function(d) {
d.days = parseInt(d.days);
});
var status = color.domain().map(function(name){
return{
name: name,
values: data.map(function(d){
return {days: d.days, testRuns: +d[name]};
})
}
});
x.domain(d3.extent(data, function(d) { return d.days; }));
y.domain([
d3.min(status, function(c) { return d3.min(c.values, function(v) { return v.testRuns; }); }),
d3.max(status, function(c) { return d3.max(c.values, function(v) { return v.testRuns; }); })
]);
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("Number of Test Runs");
var city = svg.selectAll(".city")
.data(status)
.enter().append("g")
.attr("class", "city");
city.append("path")
.attr("class", "line")
.attr("d", function(d) { return line(d.values); })
.style("stroke", function(d) { return color(d.name); });
city.append("text")
.datum(function(d) { return {name: d.name, value: d.values[d.values.length - 1]}; })
.attr("transform", function(d) { return "translate(" + x(d.value.days) + "," + y(d.value.testRuns) + ")"; })
.attr("x", 3)
.attr("dy", ".35em")
.text(function(d) { return d.name; });
});
But i need it in this form:
I'm new to d3. The attribute which i'm using is line only for both. But it is coming as curves. Any help is appreciated. Thanks
the problem is in var line = d3.svg.line()
.interpolate("basis")
.x(function(d) { return x(d.days); })
.y(function(d) { return y(d.testRuns); });
it should be changed to .interpolate("linear") to get the desired effect.
the wiki link explanes all types of line you can obtain:
https://github.com/mbostock/d3/wiki/SVG-Shapes#wiki-line_interpolate
hope this helps!