D3 chart missing data - javascript

I have a d3 line graph which works perfectly. It maps the number of telephone calls and emails received in the current month or current year. However, the only issue is that if phone calls were only received on one day of the month, then only a dot appears at that value. What i would like to happen is that for every day there is not a value then the value is treated as 0. I have had a look around google but i can only find examples of going the other way. Is anybody able to help?
My code as it stands is:
function buildCommunicationLineChart(data, placeholder, callback, type) {
var margin = { top: 20, right: 30, bottom: 40, left: 50 },
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom,
tooltipTextColour = "white";
var color = ["#FF9797", "#86BCFF", "#33FDC0", "#EFA9FE", "#7BCAE1", "#8C8CFF", "#80B584", "#C88E8E", "#DD597D", "#D8F0F8", "#DD597D", "#D6C485", "#990099", "#5B5BFF", "#1FCB4A", "#000000", "#00BFFF", "#BE81F7", "#BDBDBD", "#F79F81"];
if (type == "month") {
var x = d3.scale.linear()
.domain([1, 31])
.range([0, width]);
var tip = d3.tip()
.attr('class', 'd3-tip')
.offset([-10, 0])
.html(function (d) {
return "<strong>Value:</strong> <span style='color:" + tooltipTextColour + "'>" + d.Value + "</span><br /><strong>Day of Month:</strong><span style='color:white'>" + d.xValue + "</span>";
});
}
else if (type == "year")
{
var x = d3.scale.linear()
.domain([1, 12])
.range([0, width]);
var tip = d3.tip()
.attr('class', 'd3-tip')
.offset([-10, 0])
.html(function (d) {
return "<strong>Value:</strong> <span style='color:" + tooltipTextColour + "'>" + d.Value + "</span><br /><strong>Month of Year:</strong><span style='color:white'>" + d.xValue + "</span>";
});
}
var y = d3.scale.linear()
.domain([0, 60])
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.tickSize(-height)
.tickPadding(10)
.tickSubdivide(true)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.tickPadding(10)
.tickSize(-width)
.tickSubdivide(true)
.orient("left");
var line = d3.svg.line()
.x(function (d) { var xTest = x(d.xValue); return x(d.xValue); })
.y(function (d) { var yTest = y(d.Value); return y(d.Value); });
var svg = placeholder.append("svg")
.attr("width", width + margin.left + margin.right + 50)
.attr("height", height + margin.top + margin.bottom)
.attr("class", "chart")
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
if (type == "year") {
svg.append("g")
.attr("class", "x axis")
.append("text")
.attr("class", "axis-label")
.attr("transform", "none")
.attr("y", (-margin.left) + 530)
.attr("x", -height + 860)
.text('Month');
}
else if (type == "month") {
svg.append("g")
.attr("class", "x axis")
.append("text")
.attr("class", "axis-label")
.attr("transform", "none")
.attr("y", (-margin.left) + 525)
.attr("x", -height + 860)
.text('Day');
}
var methods = d3.entries(data);
y.domain([
d3.min(methods, function (c) { return d3.min(c.value.DataPoints, function (v) { return v.Value; }); }) -1,
d3.max(methods, function (c) { return d3.max(c.value.DataPoints, function (v) { return v.Value; }); }) +1
]);
svg.call(tip);
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
svg.append("g")
.attr("class", "y axis")
.append("text")
.attr("class", "axis-label")
.attr("transform", "rotate(-90)")
.attr("y", (-margin.left) + 15)
.attr("x", -height / 2)
.text('Communications');
var method = svg.selectAll('.method')
.data(methods)
.enter().append('g')
.attr('class', 'method')
.style('fill',function(d,i){
return color[i];
})
.style('stroke', function (d, i) {
return color[i];
});
var m = method.append('path')
.attr('class', function (d, i) { return 'line line-' + i; })
.attr('d', function (d) { return line(d.value.DataPoints); });
method.selectAll('circle')
.data(function (d) { return d.value.DataPoints; })
.enter().append("circle")
.attr('class', function (d, i) { return 'circle circle-' + i; })
.attr("cx", function (dd) { return x(dd.xValue); })
.attr("cy", function (dd) { return y(dd.Value); })
.attr("r", 3.5)
.on('mouseover', tip.show)
.on('mouseout', tip.hide);
method.append('text')
.datum(function (d) { return { commType: d.value.Type, value: d.value.DataPoints[d.value.DataPoints.length - 1] }; })
.attr("transform", function (d) { return "translate(" + x(d.value.xValue) + "," + y(d.value.Value) + ")"; })
.attr('x', 5)
.attr('class',function(d,i){return 'text text-'+i;})
.attr('dy', '.15em')
.style('stroke','none')
.text(function (d) { return d.commType; });
if (callback) {
callback();
}
}
Many Thanks :)
EDIT
xVal is the day number of month number:
[{
"Type": "Email",
"DataPoints": [{
"xValue": 1,
"Value": 17
},
{
"xValue": 2,
"Value": 59
}]
},
{
"Type": "Phone",
"DataPoints": [{
"xValue": 1,
"Value": 1
}]
}]

I will assume some points:
You are receiving from your data source year's array of month's arrays (if you are receiving month by month you will have to make a Get request for each month, it's pretty easy, but I hope you have a route giving you data on year, would not be cool to do 12 http get for this result) data like:
[
{
month: "January",
data: [{
"Type": "Email",
"DataPoints":
[{
"xValue": 1,
"Value": 17
},
{
"xValue": 2,
"Value": 59
}]
},
{
"Type": "Phone",
"DataPoints": [{
"xValue": 1,
"Value": 1
}]
}]
}
},{
month: "February",
data: [...]
},
...
]
I will do that client side in Js:
Assuming each month is 31 days, i'm too lazy to think about doing it exactly but it shoudn't be that difficult, and xValue is the day of the month.
function completeMonths(data){
_.each(data, function(month){
_.each(month.data, function(typeSource){
for(var i = 1; i < 32; i++){
var dayOnList = _.some(typeSource.DataPoints, function(day){
return day.xValue == i;
});
if(!dayOnList)
typeSource.DataPoints.push({xValue: i, Value: 0});
}
typeSource.DataPoints = _.sortBy(typeSource.DataPoints, function(ite){
return ite.xValue;
});
});
});
return data;
}
Probably not the more optimized code, but I've tried it and it's working.
Actually I think it would be better to map the month's days present, and reject them from and 1 to 31 array.
Btw your Json isn't well formatted, you're missing a closing "]".

With JSON structured like:
[{
"Type": "Email",
"DataPoints": [{
"xValue": 1,
"Value": 17
},
{
"xValue": 2,
"Value": 59
}]
},
{
"Type": "Phone",
"DataPoints": [{
"xValue": 1,
"Value": 1
}]
}]
And assuming your DataPoints array is sorted by xValue and 31 days in the month:
var maxDaysInMonth = 31;
// for each dataset
dataset.forEach(function(d){
// loop our month
for (var i = 1; i <= maxDaysInMonth; i++){
// if there's no xValue at that location
if (!d.DataPoints.some(function(v){ return (v.xValue === i) })){
// add a zero in place
d.values.splice((i - 1), 0, {xValue: i, Value: 0});
}
}
});

Related

Text on top of d3 vertical stacked bar chart

I'm trying to build a combo chart, i.e. vertical stack bar and a line chart together. I have built the graph but i want the value of each bar on top of the bar. I found certain code for text on top of single bar but not a clear answer for stacked bar. I have written down some code which is available below and I have commented it as // code i tried for text on top of each stack//. But that doesnt seem to work.
d3GroupBarChart(datas){
this.showData = datas
let textArray = [];
datas.forEach(element => {
element.stack.forEach(stack => {
textArray.push(stack)
});
});
if (datas === null || datas.length == 0) {
$(".sieir-chart").empty()
$('.sieir-chart').append(`<div class="no-card-data" >
<h5>No Data Available </h5>
</div>`)
return
}
$('.sieir-chart').html('')
var margin = { top: 20, right: 80, bottom: 100, left: 80 },
width = $('.group-bar-chart').width() - margin.left - margin.right,
height = 410 - margin.top - margin.bottom;
var svg: any = d3.select(".sieir-chart")
.append("svg")
.attr("viewBox", `0 0 ${$('.group-bar-chart').width()} 410`)
.attr("preserveAspectRatio", "xMinYMin meet")
var g = svg.append("g")
.attr("height", height)
.attr("transform",
"translate(" + (margin.left) + "," + (20) + ")");
var x: any = d3.scaleBand()
.range([0, width])
.domain(datas.map(function (d) { return d.group; }))
.padding(0.2);
var yMax = Math.max.apply(Math, datas.map(function (o) { return o.maxBarValue; }))
// Add Y axis
var y = d3.scaleLinear()
.domain([0, yMax])
.range([height, 0])
.nice();
var self = this;
var formatyAxis = d3.format('.0f');
g.append("g")
.style('font-weight', 'bold')
.call(d3.axisLeft(y).tickFormat(function (d: any) {
if (d % 1 === 0) {
return d.toLocaleString()
}
else {
return ''
}
}).ticks(5));
var y1Max = Math.max.apply(Math, datas.map(function (o) { return o.percentage; }))
var y1: any = d3.scaleLinear().range([height, 0]).domain([0, y1Max]);
var yAxisRight: any = d3.axisRight(y1).ticks(5)
// //this will make the y axis to the right
g.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + (width) + " ,0)")
.style('font-weight', 'bold')
.call(yAxisRight);
// // text label for the y axis
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 0 - (margin.left - 100))
.attr("x", 0 - (height / 2))
.attr("dy", "1em")
.style("text-anchor", "middle")
.style("font-family", "poppins_regular")
.text("Logged User Count");
// text label for the y1 axis
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("y1", 0 - (margin.right - 50))
.attr("x", 0 - (height / 2))
.attr("dy", width + 130)
.style("text-anchor", "middle")
.style("font-family", "poppins_regular")
.text("Duration in min");
g.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x))
.selectAll(".tick text")
.attr("transform", "translate(-5,7)rotate(-15)")
.style("text-anchor", "middle")
.style("font-size", "11px")
.style('font-weight', 'bold')
.call(this.wrap, x.bandwidth())
var subgroups = ["Total Headcount","Onboarded resource count"];
var groups = d3.map(datas, function (d) { return (d['group']) }).keys();
// Another scale for subgroup position?
var xSubgroup = d3.scaleBand()
.domain(subgroups)
.range([0, x.bandwidth()])
.padding(0.05)
// color palette = one color per subgroup
var color = d3.scaleOrdinal()
.domain(subgroups)
.range(['#006287', '#F68721'])
var self = this;
datas.forEach(data => {
// console.log("data",data);
g.selectAll("mybar")
// Enter in data = loop group per group
.data(datas)
.enter()
.append("g")
.attr("class","bars")
.attr("transform", function (d) { return "translate(" + x(d.group) + ",0)"; })
.selectAll("rect")
.data(function (d) { return subgroups.map(function (key) { return { key: key,
value: d[key] }; }); })
.enter().append("rect")
.attr("x", function (d) { return xSubgroup(d.key); })
.attr("y", function (d) { return y(d.value); })
.attr("width", xSubgroup.bandwidth())
.attr("height", function (d) { return height - y(d.value); })
.attr("fill", function (d) { return color(d.key); })
.append("svg:title")
.text(function (d) {
return `${d['key']}:` + d.value;
})
//code i tried for text on top of each stack
g.selectAll(".text")
.data(data.stack)
.enter().append("text")
.attr("class", "barstext")
.attr("x", function (d) { console.log("d", d); return x(d.name); })
.attr("y", function (d) { return y(d.value); })
.text(function (d) { console.log("text", d); return (d.value); })
// // line chart
var averageline = d3.line()
.x(function (d, i) { return x(d['group']) + x.bandwidth() / 2; })
.y(function (d) { return y1(d['percentage']); })
.curve(d3.curveMonotoneX);
var path = g.append("path")
.attr("class", "line")
.style("fill", "none")
.style("stroke", "#58D68D")
.style("stroke-width", 2)
.attr("d", averageline(datas));
g.selectAll("myCircles")
.data(datas)
.enter()
.append("circle")
.attr("class", "dot")
.style("fill", "white")
.style("stroke", "#58D68D")
.style("stroke-width", 2)
.style('cursor', 'pointer')
.attr("cx", function (d, i) { return x(d['group']) + x.bandwidth() / 2; })
.attr("cy", function (d) { return y1(d['percentage']); })
.attr("r", 3)
.append("svg:title")
.text(function (d) {
return "Percentage: " + d.percentage;
})
})
}
dummy data
[
{
"group": "Digital Process Industries",
"Total Headcount": 12,
"Onboarded resource count": 1,
"percentage": 13,
"maxBarValue": 12,
"stack": [
{
"name": "Total Headcount",
"value": 12
},
{
"name": "Onboarded resource count",
"value": 1
}
]
},
{
"group": "Digital Discrete Industries",
"Total Headcount": 6,
"Onboarded resource count": 6,
"percentage": 33,
"maxBarValue": 6,
"stack": [
{
"name": "Total Headcount",
"value": 6
},
{
"name": "Onboarded resource count",
"value": 6
}
]
}]
You are pretty close with your current solution. There are two main things you need to do to get this working correctly:
If you are looping over your data already (datas.forEeach) there is no need to rebind to it in your databinding for the group offset. You should be binding to the individual data element instead (so bind to [data] instead).
Set the group you create off the data element to a variable and append both the rectangles for the bars and the text for the labels to that group rather than the svg. The reason for this is that it is already offset for the group (via the transform call) so you just have to worry about the subgroup x scale.
See this jsfiddle for a working version of your code. I added comments prepended with EDITED -- to all the lines I changed along with an explanation of what I did.

JS - D3 Multiline time series chart

I need to make a time series style graphic, with D3, multiple. Taking this example as a basis: example
The code is the following:
<script type="text/javascript">
var data = [{fecha: "2019-03-16", partidos: "1", goles: "0", tarjetas: "0"},
{fecha: "2019-03-23", partidos: "1", goles: "1", tarjetas: "0"},
{fecha: "2019-03-30", partidos: "1", goles: "0", tarjetas: "1"},
{fecha: "2019-04-06", partidos: "0", goles: "0", tarjetas: "0"},
{fecha: "2019-04-13", partidos: "1", goles: "2", tarjetas: "0"},
];
// Draw a line chart
var svg = d3.select('#graf_act_tiempo'),
margin = { top: 20, right: 50, bottom: 30, left: 50 },
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 + ')');
// Function to convert a string into a time
var parseTime = d3.time.format('%Y-%m-%d').parse;
// Set the X scale
var x = d3.time.scale().range([0, width], 0.5);
// Set the Y scale
var y = d3.scale.linear().range([height, 0]);
// Set the color scale
var color = d3.scale.category10();
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickSize(6, 0)
.tickFormat(d3.time.format('%d/%m/%y'));
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickSize(1, 0)
.tickFormat(d3.format("d"));
var line = d3.svg.line()
// .interpolate("basis")
.x(function(d) {
return x(d.fecha);
})
.y(function(d) {
return y(d.worth);
});
// load the data
// Select the important columns
color.domain(d3.keys(data[0]).filter(function(key) {
return key !== "fecha";
}));
// Correct the types
data.forEach(function(d) {
d.fecha = parseTime(d.fecha);
});
//console.log(data);
var currencies = color.domain().map(function(name) {
return {
name: name,
values: data.map(function(d) {
return {
fecha: d.fecha,
worth: +d[name]
};
})
};
});
//console.log(currencies)
// Set the X domain
x.domain(d3.extent(data, function(d) {
return d.fecha;
}));
// Set the Y domain
y.domain([
d3.min(currencies, function(c) {
return d3.min(c.values, function(v) {
return v.worth;
});
}),
d3.max(currencies, function(c) {
return d3.max(c.values, function(v) {
return v.worth;
});
})
]);
// Set the X axis
g.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("transform", "rotate(-65)");
// Set the Y axis
g.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
// .attr("transform", "rotate(-90)")
.attr("y", 0)
.attr("x", 60)
.attr("dy", "4px")
.style("text-anchor", "end")
.text("Cantidad");
// Draw the lines
var currency = g.selectAll(".currency")
.data(currencies)
.enter().append("g")
.attr("class", "currency");
currency.append("path")
.attr("class", "line")
.attr("d", function(d) {
return line(d.values);
})
.style("stroke", function(d) {
return color(d.name);
});
// Add the circles
currency.append("g").selectAll("circle")
.data(function(d){return d.values})
.enter()
.append("circle")
.attr("r", 2)
.attr("cx", function(dd){return x(dd.fecha)})
.attr("cy", function(dd){return y(dd.worth)})
.attr("fill", "none")
.attr("stroke", function(d){return color(this.parentNode.__data__.name)});
// Add label to the end of the line
currency.append("text")
.attr("class", "label")
.datum(function (d) {
return {
name: d.name,
value: d.values[d.values.length - 1]
};
})
.attr("transform", function (d) {
return "translate(" + x(d.value.fecha) + "," + y(d.value.worth) + ")";
})
.attr("x", 6)
.attr("dy", ".35em")
.text(function (d) {
return d.name;
});
</script>
The following result is obtained:
I need help to make these changes:
1) The legend "cantidad" of the "y" axis located above the maximum value of the axis (top) or left of axis.
2) The values of the "x" axis that are not cut, that can be read well
Thanks for the tips to improve it.
1) Add more value left to svg and .attr("x", -20) to y axis
2) Add more valur bottom to svg

Multiline chart x-axis ticks not aligned with data points

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.

D3.js transitions between charts

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.

What sort my data in this graph?

I am about to go crazy. My data looks like this and in the following order :
[
{
"x": 0,
"y": 200,
"WeekNumber": 10
},
{
"x": 1,
"y": 300,
"WeekNumber": 11
},
{
"x": 2,
"y": 400,
"WeekNumber": 12
},
{
"x": 3,
"y": 200,
"WeekNumber": 10
}
]
Where x is identifier for an object in the array, and weeknumber can go from 1-54, which means i can have 3 weeknumber 10 if i take span over 3 years. What i expect on my x axis is weeknumbers in following order : 10, 11, 12, 10. But for some reason something in d3js are sorting my data and result is 10, 10, 11, 12.
What does actually sort my data? and how do i solve this?
public DrawGenericGraph(dataItems: Interfaces.IAreaChartItemList, parentId: string, id: string) {
var data = dataItems.Models;
var Xmax = d3.max(data, function (d: any) {
return d.x;
})
this.shared.EmptyElement(id);
var yMax = d3.max(data, function (d: any) {
return d.y;
})
var width: number = this.shared.getParentWidth(parentId) - this.margin.Right - this.margin.Left;
this.params = new GraphsHelper.GraphParameters(this.margin, this.height, width);
var ticksCount = function () {
if (data.length == 2) {
return 1;
}
else
return data.length;
};
var x = d3.scale.linear().range([0, width]);
var y = d3.scale.linear().range([this.params.height, 0]);
x.domain([0, Xmax]);
y.domain([0, yMax]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickFormat(function (d) {
return dataItems.Format(d);
}).ticks(ticksCount());
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var area = d3.svg.area()
.x(function (d: any) { return x(d.x); })
.y0(this.params.height)
.y1(function (d: any) { return y(d.y); });
var valueline = d3.svg.line()
.x(function (d: any) { return x(d.x); })
.y(function (d: any) { return y(d.y); });
var svg = d3.select("#" + id).append("svg")
.attr("width", this.params.OuterWidth)
.attr("height", this.params.OuterHeight)
.append("g")
.attr("transform", "translate(" + this.params.Margin.Left + "," + this.params.Margin.Top + ")");
y.nice()
y.ticks().forEach(function (element) {
svg.append("line")
.attr("x1", function () { return 0; })
.attr("y1", function () { return y(element); })
.attr("x2", function () { return x(Xmax); })
.attr("y2", function () { return y(element); })
.attr("stroke", "black")
.attr("stroke-width", "1px")
})
svg.append("g").append("path")
.datum(data)
.attr("class", "area")
.attr("d", area);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + this.params.height + ")")
.call(xAxis);
var gy = svg.append("g")
.attr("class", "yaxis")
.call(yAxis);
});

Categories