Related
I have a simple bar chart showing data by year. Over top, I am making a line chart representing another data point over the same time. However, I don't have data for the line chart from the final year.
[Here is an image of the graph so far.][1]
As you'll see, this graph makes it look like unemployment numbers in the final year suddenly dropped.
For this line chart, where would I change the range of data I want the line to be drawn over.
Here is my d3.js for the line chart:
var margin = {top: 20, right: 35, bottom: 30, left: 40},
width = 600,
height = 400;
var xScale = d3.scaleBand()
.rangeRound([0, width])
.padding(0.1)
.domain(dataset.map(function(d) {
return d.Year;
}));
yScale = d3.scaleLinear()
.rangeRound([height, 0])
.domain([0, d3.max(dataset, (function (d) {
return d.SYEP_Enrollment;
}))]);
yLineScale = d3.scaleLinear()
.rangeRound([height, 0])
.domain([0, d3.max(dataset, (function (d) {
return d.Teen_Unemployment;
}))]);
var svg = d3.select(".bar-chart-wrapper svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
var g = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// axis-x
g.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(xScale));
// axis-y
g.append("g")
.attr("class", "axis axis--y")
.call(d3.axisLeft(yScale));
// axis-y for the line
g.append("g")
.attr("class", "axis axis--y")
.attr('transform',`translate(${width},0)`)
.call(d3.axisRight(yLineScale));
var bar = g.selectAll("rect")
.data(dataset)
.enter().append("g");
// bar chart
bar.append("rect")
.attr("x", function(d) { return xScale(d.Year); })
.attr("y", function(d) { return yScale(d.SYEP_Enrollment); })
.attr("width", xScale.bandwidth())
.attr("height", function(d) { return height - yScale(d.SYEP_Enrollment);})
.attr('class','bar')
// labels on the bar chart
bar.append("text")
.attr("dy", "1.8em")
.attr("x", function(d) { return xScale(d.Year) + xScale.bandwidth() / 2; })
.attr("y", function(d) { return yScale(d.SYEP_Enrollment); })
.attr("text-anchor", "middle")
.text(function(d) {
return d.SYEP_Enrollment;
});
// line chart
// axis-y
g.append("g")
.call(d3.axisRight(yLineScale));
var line = d3.line()
.x(function(d,i) { return xScale(d.Year) + xScale.bandwidth() / 2})
.y(function(d) { return yLineScale(d.Teen_Unemployment)})
.curve(d3.curveMonotoneX);
bar.append("path")
.attr("class", "line") // Assign a class for styling
.attr("d", line(dataset)); // 11. Calls the line generator
bar.append("circle")
.attr("class", "dot")
.attr("cx", function(d, i) { return xScale(d.Year)+ xScale.bandwidth() / 2})
.attr("cy", function(d) { return yLineScale(d.Teen_Unemployment); })
.attr("r", 5);
// labels on the line chart
bar.append("text")
.attr("x", function(d) { return xScale(d.Year) + xScale.bandwidth() / 2; })
.attr("y", function(d) { return yLineScale(d.Teen_Unemployment) - 10; })
.attr("text-anchor", "middle")
.text(function(d) {
return parseInt(d.Teen_Unemployment * 100) + '%';
});
[Here is a link to the JSON file.][2]
[1]: https://drive.google.com/open?id=1-6SW_wRVaBA70rItvuhecXSxCTgUwH2x
[2]: https://docs.google.com/document/d/1g2h934hhEA0VsXZZLcDL9oHnRLnwBCJYBCD_NdZGGXw/edit?usp=sharing
As mentioned in the comments above the issue lies in the dataset where the final data point does not have the Teen_Unemployment data point. As a result the last point is counted as 0 or null and the point drops to the bottom of the y-scale.
To fix this, we create a separate dataset for the line chart by eliminating any data points which have empty Teen_Unemployment field like:
var lineData = dataset.filter(f => f.Teen_Unemployment != "");
Then we use the lineData to map the domain of for the yLineScale like:
var yLineScale = d3.scaleLinear()
.rangeRound([height, 0])
.domain([0, d3.max(lineData, (function (d) {
return d.Teen_Unemployment;
}))]);
Finally we create the lineChart with the lineData like so:
g.append("g")
.call(d3.axisRight(yLineScale));
var line = d3.line()
.x(function(d,i) { return xScale(d.Year) + xScale.bandwidth() / 2})
.y(function(d) { return yLineScale(d.Teen_Unemployment)})
.curve(d3.curveMonotoneX);
let lineChart = g.append('g')
.attr('class', 'lineChart')
;
lineChart.append("path")
.attr("class", "line") // Assign a class for styling
.attr("d", line(lineData)); // 11. Calls the line generator
lineChart.selectAll('circle').data(lineData).enter().append("circle")
.attr("class", "dot")
.attr("cx", function(d, i) { return xScale(d.Year)+ xScale.bandwidth() / 2})
.attr("cy", function(d) { return yLineScale(d.Teen_Unemployment); })
.attr("r", 5);
// labels on the line chart
lineChart.selectAll('text').data(lineData).enter().append("text")
.attr("x", function(d) { return xScale(d.Year) + xScale.bandwidth() / 2; })
.attr("y", function(d) { return yLineScale(d.Teen_Unemployment) - 10; })
.attr("text-anchor", "middle")
.text(function(d) {
return parseInt(d.Teen_Unemployment * 100) + '%';
});
Working example:
var dataset = [
{
"Year": 2007,
"SYEP_Enrollment": 41650,
"Teen_Unemployment": .330
},
{
"Year": 2008,
"SYEP_Enrollment": 41804,
"Teen_Unemployment": .302
},
{
"Year": 2009,
"SYEP_Enrollment": 43113,
"Teen_Unemployment": .308
},
{
"Year": 2010,
"SYEP_Enrollment": 52255,
"Teen_Unemployment": .325
},
{
"Year": 2011,
"SYEP_Enrollment": 35725,
"Teen_Unemployment": .383
},
{
"Year": 2012,
"SYEP_Enrollment": 30628,
"Teen_Unemployment": .399
},
{
"Year": 2013,
"SYEP_Enrollment": 29416,
"Teen_Unemployment": .408
},
{
"Year": 2014,
"SYEP_Enrollment": 35957,
"Teen_Unemployment": .369
},
{
"Year": 2015,
"SYEP_Enrollment": 47126,
"Teen_Unemployment": .357
},
{
"Year": 2016,
"SYEP_Enrollment": 54263,
"Teen_Unemployment": .334
},
{
"Year": 2017,
"SYEP_Enrollment": 60113,
"Teen_Unemployment": .316
},
{
"Year": 2018,
"SYEP_Enrollment": 69716,
"Teen_Unemployment": .291
},
{
"Year": 2019,
"SYEP_Enrollment": 74354,
"Teen_Unemployment":""
}
];
//make a copy of the dataset for the lineChart
var lineData = dataset.filter(f => f.Teen_Unemployment != "");
var margin = {top: 20, right: 35, bottom: 30, left: 40},
width = 600,
height = 400;
var xScale = d3.scaleBand()
.rangeRound([0, width])
.padding(0.1)
.domain(dataset.map(function(d) {
return d.Year;
}));
var yScale = d3.scaleLinear()
.rangeRound([height, 0])
.domain([0, d3.max(dataset, (function (d) {
return d.SYEP_Enrollment;
}))]);
var yLineScale = d3.scaleLinear()
.rangeRound([height, 0])
.domain([0, d3.max(lineData, (function (d) {
return d.Teen_Unemployment;
}))]);
var svg = d3.select(".bar-chart-wrapper svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
var g = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top +")");
// axis-x
g.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(xScale));
// axis-y
g.append("g")
.attr("class", "axis axis--y")
.call(d3.axisLeft(yScale));
// axis-y for the line
g.append("g")
.attr("class", "axis axis--y")
.attr('transform',`translate(${width},0)`)
.call(d3.axisRight(yLineScale));
var bar = g.selectAll("rect")
.data(dataset)
.enter().append("g");
// bar chart
bar.append("rect")
.attr("x", function(d) { return xScale(d.Year); })
.attr("y", function(d) { return yScale(d.SYEP_Enrollment); })
.attr("width", xScale.bandwidth())
.attr("height", function(d) { return height - yScale(d.SYEP_Enrollment);})
.attr('class','bar')
// labels on the bar chart
bar.append("text")
.attr("dy", "1.8em")
.attr("x", function(d) { return xScale(d.Year) + xScale.bandwidth() / 2; })
.attr("y", function(d) { return yScale(d.SYEP_Enrollment); })
.attr("text-anchor", "middle")
.text(function(d) {
return d.SYEP_Enrollment;
});
// line chart
// data filtered above
// axis-y
g.append("g")
.call(d3.axisRight(yLineScale));
var line = d3.line()
.x(function(d,i) { return xScale(d.Year) + xScale.bandwidth() / 2})
.y(function(d) { return yLineScale(d.Teen_Unemployment)})
.curve(d3.curveMonotoneX);
let lineChart = g.append('g')
.attr('class', 'lineChart')
;
lineChart.append("path")
.attr("class", "line") // Assign a class for styling
.attr("d", line(lineData)); // 11. Calls the line generator
lineChart.selectAll('circle').data(lineData).enter().append("circle")
.attr("class", "dot")
.attr("cx", function(d, i) { return xScale(d.Year)+ xScale.bandwidth() / 2})
.attr("cy", function(d) { return yLineScale(d.Teen_Unemployment); })
.attr("r", 5);
// labels on the line chart
lineChart.selectAll('text').data(lineData).enter().append("text")
.attr("x", function(d) { return xScale(d.Year) + xScale.bandwidth() / 2; })
.attr("y", function(d) { return yLineScale(d.Teen_Unemployment) - 10; })
.attr("text-anchor", "middle")
.text(function(d) {
return parseInt(d.Teen_Unemployment * 100) + '%';
});
.line {
fill-opacity: 0;
stroke: blue
}
.dot {
fill: white;
stroke: blue;
}
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v4.min.js"></script>
<style>
body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; }
</style>
</head>
<body>
<div class="bar-chart-wrapper">
<svg></svg>
</div>
</body>
I am trying to draw a multi line chart with D3.js. The chart is drawn properly but the x-axis ticks are not aligned correctly with the data points circle.
Find the example code here: https://jsfiddle.net/gopal31795/qsLd7pc5/9/
I assume that there is some error in the code for creating the dots.
// Creating Dots on line
segment.selectAll("dot")
.data(function(d) {
return d.linedata;
})
.enter().append("circle")
.attr("r", 5)
.attr("cx", function(d) {
//return x(parseDate(new Date(d.mins))) + x.rangeBand() / 2;
return x(d.mins);
})
.attr("cy", function(d) {
return y(d.value);
})
.style("stroke", "white")
.style("fill", function(d) {
return color(this.parentNode.__data__.name);
})
.on("mouseenter", function(d) {
d3.select(this).transition().style("opacity", "0.25");
tooltip.html("<span style='color:" + color(this.parentNode.__data__.name) + ";'>" + this.parentNode.__data__.name + "</span>: " + (d.value + "s")).style("visibility", "visible").style("top", (event.pageY + 10) + "px").style("left", (event.pageX) + "px");
})
.on("mouseleave", function(d) {
d3.select(this).transition().style("opacity", "1");
tooltip.style("visibility", "hidden");
});
The problem in your code (which uses the old v3) is that you're using rangeRoundBands. That would be the correct choice if you had a bar chart, for instance.
However, since you're dealing with data points, you should use rangePoints or rangeRoundPoints, which:
Sets the output range from the specified continuous interval. The array interval contains two elements representing the minimum and maximum numeric value. This interval is subdivided into n evenly-spaced points, where n is the number of (unique) values in the input domain.
So, it should be:
var x = d3.scale.ordinal()
.rangeRoundPoints([0, width]);
Here is your code with that change:
var Data = [{
"name": "R",
"linedata": [{
"mins": 0,
"value": 1120
},
{
"mins": 2,
"value": 1040
},
{
"mins": 4,
"value": 1400
},
{
"mins": 6,
"value": 1500
}
]
},
{
"name": "E",
"linedata": [{
"mins": 0,
"value": 1220
},
{
"mins": 2,
"value": 1500
},
{
"mins": 4,
"value": 1610
},
{
"mins": 6,
"value": 1700
}
]
}
];
var margin = {
top: 20,
right: 90,
bottom: 35,
left: 90
},
width = $("#lineChart").width() - margin.left - margin.right,
height = $("#lineChart").width() * 0.3745 - margin.top - margin.bottom;
//var parseDate = d3.time.format("%d-%b");
var x = d3.scale.ordinal()
.rangeRoundPoints([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")
.ticks(5);
var xData = Data[0].linedata.map(function(d) {
return d.mins;
});
var line = d3.svg.line()
.interpolate("linear")
.x(function(d) {
return x(d.mins);
})
.y(function(d) {
return y(d.value);
});
function transition(path) {
path.transition()
.duration(4000)
.attrTween("stroke-dasharray", tweenDash);
}
function tweenDash() {
var l = this.getTotalLength(),
i = d3.interpolateString("0," + l, l + "," + l);
return function(t) {
return i(t);
};
}
var svg = d3.select("#lineChart").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 + ")");
color.domain(Data.map(function(d) {
return d.name;
}));
x.domain(xData);
var valueMax = d3.max(Data, function(r) {
return d3.max(r.linedata, function(d) {
return d.value;
})
});
var valueMin = d3.min(Data, function(r) {
return d3.min(r.linedata, function(d) {
return d.value;
})
});
y.domain([valueMin, valueMax]);
//Drawing X Axis
svg.append("g")
.attr("class", "x-axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.append("text")
.attr("class", "xAxisText")
.attr("x", width)
.attr("dy", "-.41em")
.style("text-anchor", "end")
.style("fill", "white")
.text("Time(m)");
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")
.style("fill", "none")
.style("stroke", "white")
.style("stroke-width", 0.8)
.text("Duration(s)");
// Drawing Lines for each segments
var segment = svg.selectAll(".segment")
.data(Data)
.enter().append("g")
.attr("class", "segment");
segment.append("path")
.attr("class", "line")
.attr("id", function(d) {
return d.name;
})
.attr("visible", 1)
.call(transition)
//.delay(750)
//.duration(1000)
//.ease('linear')
.attr("d", function(d) {
return line(d.linedata);
})
.style("stroke", function(d) {
return color(d.name);
});
// Creating Dots on line
segment.selectAll("dot")
.data(function(d) {
return d.linedata;
})
.enter().append("circle")
.attr("r", 5)
.attr("cx", function(d) {
//return x(parseDate(new Date(d.mins))) + x.rangeBand() / 2;
return x(d.mins);
})
.attr("cy", function(d) {
return y(d.value);
})
.style("stroke", "white")
.style("fill", function(d) {
return color(this.parentNode.__data__.name);
})
.on("mouseenter", function(d) {
d3.select(this).transition().style("opacity", "0.25");
tooltip.html("<span style='color:" + color(this.parentNode.__data__.name) + ";'>" + this.parentNode.__data__.name + "</span>: " + (d.value + "s")).style("visibility", "visible").style("top", (event.pageY + 10) + "px").style("left", (event.pageX) + "px");
})
.on("mouseleave", function(d) {
d3.select(this).transition().style("opacity", "1");
tooltip.style("visibility", "hidden");
});
path {
fill: none;
stroke: black;
}
line {
stroke: black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<div class="card-body" id="lineChart">
</div>
Finally, as a tip: don't mix D3 and jQuery.
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 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.
Full disclosure: I'm not new to programming, but I'm pretty new to d3 and javascript.
I am trying to combine the Grouped Bar Chart Example and the Sortable Bar Chart Example. I have a total of 51 groups of 3 variables. Here is a truncated form of my dataset you can use to run the code if you want:
State,Response,Predicted,Difference
1,0.0526,0.0983,0.0456
2,0.1161,0.1093,0.0068
5,0.0967,0.1035,0.0067
4,0.0998,0.0942,0.0055
6,0.0888,0.0957,0.0069
I want to be able to order the data by the Response variable by checking a box. Right now I can get the x-axis labels to move accordingly, but I can't get the bars to move with them. To get to this point I renamed the variables in the change() function according to my data. I tried saving the transition.selectAll(".state") function as state2 and then using state2.selectAll(".rect") to modify the x-coordinates of the rectangles, but I realized that wasn't going to get me anywhere.
Here is my code right now (mostly copied from the examples linked above). The relevant function is at the end.
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 1000 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom,
code = "";
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(".0%"));
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 + ")");
d3.csv("data.csv", function(error, data) {
var ageNames = d3.keys(data[0]).filter(function(key) { return key !== "State"; });
data.forEach(function(d) {
d.ages = ageNames.map(function(name) { return {name: name, value: +d[name]}; });
});
x0.domain(data.map(function(d) { return d.State; }));
x1.domain(ageNames).rangeRoundBands([0, x0.rangeBand()]);
y.domain([0, d3.max(data, function(d) { return d3.max(d.ages, function(d) { return d.value; }); })]);
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("Prevalence");
var state = svg.selectAll(".state")
.data(data)
.enter().append("g")
.attr("class", "g")
.attr("transform", function(d) { return "translate(" + x0(d.State) + ",0)"; });
state.selectAll("rect")
.data(function(d) { return d.ages; })
.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); });
d3.select("input").on("change", change);
var sortTimeout = setTimeout(function() {
d3.select("input").property("checked", true).each(change);
}, 2000);
var legend = svg.selectAll(".legend")
.data(ageNames.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; });
function change() {
clearTimeout(sortTimeout);
// Copy-on-write since tweens are evaluated after a delay.
var x2 = x0.domain(data.sort(this.checked
? function(a, b) { return b.Response - a.Response; }
: function(a, b) { return d3.ascending(a.State, b.State); })
.map(function(d) { return d.State; }))
.copy();
var transition = svg.transition().duration(750),
delay = function(d, i) { return i * 50; };
var state2 = transition.selectAll(".state")
.delay(delay)
.attr("x", function(d) { return x2(d.State); });
transition.select(".x.axis")
.call(xAxis)
.selectAll("g")
.delay(delay);
}
})
Any help would be greatly appreciated. I've found nothing so far searching SO and Google.
I assume that you want to keep the grouping when sorting. Your groups are contained in g elements, so all you need to do is adjust the coordinates of the groups. That is, the code to move the groups would look something like
svg.selectAll("g.g")
.transition().duration(750)
.delay(delay)
.attr("transform", function(d) { return "translate(" + x2(d.State) + ",0)"; });
Am tried with the stacked bar chart. To sort the stacked chart, please find the
Stacked Bar Chart
function change() {
// Copy-on-write since tweens are evaluated after a delay.
var x0 = x.domain(data.sort(this.checked
? function(a, b) { return b.noncomplete - a.noncomplete; }
: function(a, b) { return d3.ascending(a.moduleName, b.moduleName); })
.map(function(d) { return d.moduleName; }))
.copy();
var transition = svg.transition().duration(750),
delay = function(d, i) { return i * 60; };
transition.selectAll(".moduleName")
.delay(delay)
.attr("transform",function(d, i) { return "translate(" + (x0(d.moduleName)) + ",0)"; } );
transition.select(".x.axis")
.call(xAxis)
.selectAll("g")
.delay(delay);
}