d3 stack bar chart issue - javascript

i am creating a stacked bar chart using d3. The graph axis ares getting rendered but not the bar chart and JS console is also not throwing any errors.
Here is the code:
var margin = {
top: 20,
right: 30,
bottom: 30,
left: 40
},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var data = [{
data: [
["2016-01-20T10:36:00.000Z", 95.6981132075472],
["2016-01-20T11:10:00.000Z", 95.8882352941176],
["2016-01-20T11:44:00.000Z", 95.8470588235294],
["2016-01-20T12:18:00.000Z", 95.7941176470588],
["2016-01-20T12:52:00.000Z", 95.675],
["2016-01-20T13:26:00.000Z", 95.7573529411765],
["2016-01-20T14:00:00.000Z", 95.8294117647059],
["2016-01-20T14:34:00.000Z", 95.7736842105263]
],
label: "a"
}, {
data: [
["2016-01-20T10:36:00.000Z", 3.18867924528302],
["2016-01-20T11:10:00.000Z", 3.15441176470588],
["2016-01-20T11:44:00.000Z", 3.15],
["2016-01-20T12:18:00.000Z", 3.16323529411765],
["2016-01-20T12:52:00.000Z", 3.13970588235294],
["2016-01-20T13:26:00.000Z", 3.17794117647059],
["2016-01-20T14:00:00.000Z", 3.16617647058824],
["2016-01-20T14:34:00.000Z", 3.18888888888889]
],
label: "b"
}];
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var x = d3.scale.ordinal()
.rangeRoundBands([0, width]);
var y = d3.scale.linear()
.rangeRound([height, 0]);
var z = d3.scale.category10();
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var stack = d3.layout.stack()
.values(function(d) {
return d.data;
})
.x(function(d) {
return new Date(d[0]);
})
.y(function(d) {
return d[1];
});
var layers = stack(data);
var ary = [];
layers.forEach(function(d) {
ary.push(d.data);
});
x.domain(d3.extent(d3.merge(ary), function(d) {
return new Date(d[0]);
}));
y.domain([0, d3.max(d3.merge(ary), function(d) {
return d.y0 + d.y;
})]);
var layer = svg.selectAll(".layer")
.data(layers)
.enter().append("g")
.attr("class", "layer")
.style("fill", function(d, i) {
return z(i);
});
layer.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.y + d.y0);
})
.attr("height", function(d) {
return y(d.y0) - y(d.y + d.y0);
})
.attr("width", x.rangeBand() - 1);
svg.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "axis y")
.call(yAxis);
body {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
Please let me know what i am doing wrong in the above posted code and help me fix this code.

I have fixed the issues with my axis in the above code. And able to display the stack bar due to the error pointed by #adilapapaya.
var margin = {
top: 20,
right: 30,
bottom: 30,
left: 40
},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var data = [{
data: [
["2016-01-20T10:36:00.000Z", 95.6981132075472],
["2016-01-20T11:10:00.000Z", 95.8882352941176],
["2016-01-20T11:44:00.000Z", 95.8470588235294],
["2016-01-20T12:18:00.000Z", 95.7941176470588],
["2016-01-20T12:52:00.000Z", 95.675],
["2016-01-20T13:26:00.000Z", 95.7573529411765],
["2016-01-20T14:00:00.000Z", 95.8294117647059],
["2016-01-20T14:34:00.000Z", 95.7736842105263]
],
label: "a"
}, {
data: [
["2016-01-20T10:36:00.000Z", 3.18867924528302],
["2016-01-20T11:10:00.000Z", 3.15441176470588],
["2016-01-20T11:44:00.000Z", 3.15],
["2016-01-20T12:18:00.000Z", 3.16323529411765],
["2016-01-20T12:52:00.000Z", 3.13970588235294],
["2016-01-20T13:26:00.000Z", 3.17794117647059],
["2016-01-20T14:00:00.000Z", 3.16617647058824],
["2016-01-20T14:34:00.000Z", 3.18888888888889]
],
label: "b"
}];
var parseDate = d3.time.format("%Y-%m-%dT%H:%M:%S.%LZ").parse;
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], 0.7);
var y = d3.scale.linear()
.range([height, 0]);
var z = d3.scale.category10();
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom").ticks(d3.time.hour, 1)
.tickSize(0).innerTickSize(-height).outerTickSize(0).tickFormat(d3.time.format("%H:%M"));
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var stack = d3.layout.stack()
.values(function(d) {
return d.data;
})
.x(function(d) {
return parseDate(d[0]);
})
.y(function(d) {
return d[1];
});
var layers = stack(data);
var ary = [];
layers.forEach(function(d) {
ary.push(d.data);
});
console.log("d = ", ary)
x.domain(ary[0].map(function(d) {
return parseDate(d[0]);
}));
y.domain([0, d3.max(d3.merge(ary), function(d) {
return d.y0 + d.y;
})]);
var layer = svg.selectAll(".layer")
.data(layers)
.enter().append("g")
.attr("class", "layer")
.style("fill", function(d, i) {
return z(i);
});
layer.selectAll("rect")
.data(function(d) {
return d.data;
})
.enter().append("rect")
.attr("x", function(d) {
return x(parseDate(d[0]));
})
.attr("y", function(d) {
return y(d.y + d.y0);
})
.attr("height", function(d) {
return y(d.y0) - y(d.y + d.y0);
})
.attr("width", x.rangeBand() - 1);
svg.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "axis y")
.call(yAxis);
body {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

The issue is in this section:
layer.selectAll("rect")
.data(function(d) {
return d;
})
.attr("x", function(d) {
console.log("d = ", d) // this doesn't get called since there is no data
return x(d.x);
})
d is an object but d3 wants an array so it's not storing the data you pass it.
I think this is what you want (though the output graph looks way off so I'm not 100% sure...). Either way, just remember that you want to return an array in the data function.
layer.selectAll("rect")
.data(function(d) {
return d.data;
})

Related

Grouping bar and line charts

I am trying to group bar chart and line chart in d3 js and I followed one Link for that purpose,
Here is what my Ajax is returning in response:
[
{
"date_created": "2017-12-27",
"jobs_fail": 19,
"jobs_resub": 31,
"jobs_success": 50
},
{
"date_created": "2017-12-29",
"jobs_fail": 18,
"jobs_resub": 25,
"jobs_success": 44
},
{
"date_created": "2017-12-28",
"jobs_fail": 8,
"jobs_resub": 24,
"jobs_success": 44
},
{
"date_created": "2018-01-02",
"jobs_fail": 2,
"jobs_resub": 0,
"jobs_success": 0
}
]
And what I am trying to show is displaying the jobs_fail and jobs_resub as a bar and jobs_sucess as line chart in same graphs with respect to date_created,
Here is my code for that purpose.
<script>
function get_data() {
console.log("create post is working!") // sanity check
return $.ajax({
url : "/group/guest/query/", // the endpoint
type : "GET", // http method
});
};
var margin = {
top: 20,
right: 20,
bottom: 30,
left: 40
},
width = 960 - 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 colorRange = d3.scale.category20();
var color = d3.scale.ordinal()
.range(colorRange.range());
var divTooltip = d3.select("body").append("div").attr("class", "toolTip");
var xAxis = d3.svg.axis()
.scale(x0)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(10, "");
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 ajdata = get_data();
var k = [];
ajdata.success(function (data) {
var obj = jQuery.parseJSON(data);
alert(data);
var options = d3.keys(obj[0]).filter(function(key) { if (key != "date_created" & key != "jobs_success" ) { return key }}); // & key != "date_created"){return key} });
var line_option = d3.keys(obj[0]).filter(function(key) { if (key == "jobs_success" & key == "date_created"){return key} });
alert(options);
obj.forEach(function(d) {
d.valores = options.map(function(name) {return { name: name, value: +d[name]}; });});
x0.domain(obj.map(function(d) { return d.date_created; }));
x1.domain(options).rangeRoundBands([0, x0.rangeBand()]);
y.domain([0, d3.max(obj, function(d) { return d3.max(d.valores, function(d) { return d.value; }); })]);
var line = d3.svg.line()
.x(function(d) { return x1(d.date_created); })
.y(function(d) { return y(d.jobs_success); });
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 jobs");
var bar = svg.selectAll(".bar")
.data(obj)
.enter().append("g")
.attr("class", "rect")
.attr("transform", function(d) { return "translate(" + x0(d.date_created) + ",0)"; });
bar.selectAll("rect")
.data(function(d) { return d.valores; })
.enter().append("rect")
.attr("width", x1.rangeBand())
.attr("x", function(d) { return x1(d.name); })
.attr("y", function(d) { return y(d.value); })
.attr("value", function(d){return d.name;})
.attr("height", function(d) { return height - y(d.value); })
.style("fill", function(d) { return color(d.name); });
bar
.on("mousemove", function(d){
divTooltip.style("left", d3.event.pageX+10+"px");
divTooltip.style("top", d3.event.pageY-25+"px");
divTooltip.style("display", "inline-block");
var x = d3.event.pageX, y = d3.event.pageY
var elements = document.querySelectorAll(':hover');
l = elements.length
l = l-1
elementData = elements[l].__data__
divTooltip.html((d.date_created)+"<br>"+elementData.name+"<br>"+elementData.value);
});
bar
.on("mouseout", function(d){
divTooltip.style("display", "none");
});
var legend = svg.selectAll(".legend")
.data(options.slice())
.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; });
svg.append("path")
//.data(obj)
.attr("class", "line")
.attr("d", line(obj));
UPDATE
What problem I am facing is I am able to render bar but not the line chart on bars.
I am trying to debug but not able to do so.
Please let me know what might I am doing wrong here.
You have some minor problems and a big problem.
The minor problems are:
Your y scale should take into account the maximum value in your dataset:
y.domain([0, d3.max(obj, function(d) {
return d.jobs_success
})]);
Your line generator should use x0. Besides that, you'll have to move the line by half rangeBand:
.x(function(d) {
return x0(d.date_created) + x0.rangeBand() / 2;
})
By default, a <path> has a black fill. Change it:
.style("fill", "none")
Those, however, are minor problems. The biggest problem lies here, in the data() method:
svg.append("path")
.data(obj)
.attr("class", "line")
.attr("d", line);
Let's see in detail what's happening here. You're passing the obj array to the data(). However, if you do this, each element of that array will be passed, individually, to the line generator.
So, supposing that this is your array...
["foo", "bar", "baz"]
...what you're passing to the line generator is just:
"foo".
You have some different solutions here. First, you can pass the array to the line generator directly, as you did in your edit. Second, you can wrap the array in an outer array:
svg.append("path")
.data([obj])
.attr("class", "line")
.attr("d", line);
That way, the whole obj array will be passed to the line generator.
Or, third, you can use datum:
svg.append("path")
.datum(obj)
.attr("class", "line")
.attr("d", line);
Here is your code with those changes and using datum to draw the path:
var obj = [{
"date_created": "2017-12-27",
"jobs_fail": 19,
"jobs_resub": 31,
"jobs_success": 50
}, {
"date_created": "2017-12-29",
"jobs_fail": 18,
"jobs_resub": 25,
"jobs_success": 44
}, {
"date_created": "2017-12-28",
"jobs_fail": 8,
"jobs_resub": 24,
"jobs_success": 44
}, {
"date_created": "2018-01-02",
"jobs_fail": 2,
"jobs_resub": 0,
"jobs_success": 0
}];
var margin = {
top: 20,
right: 20,
bottom: 30,
left: 40
},
width = 960 - 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 colorRange = d3.scale.category20();
var color = d3.scale.ordinal()
.range(colorRange.range());
var divTooltip = d3.select("body").append("div").attr("class", "toolTip");
var xAxis = d3.svg.axis()
.scale(x0)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(10, "");
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var options = d3.keys(obj[0]).filter(function(key) {
if (key != "date_created" & key != "jobs_success") {
return key
}
}); // & key != "date_created"){return key} });
var line_option = d3.keys(obj[0]).filter(function(key) {
if (key == "jobs_success" & key == "date_created") {
return key
}
});
obj.forEach(function(d) {
d.valores = options.map(function(name) {
return {
name: name,
value: +d[name]
};
});
});
x0.domain(obj.map(function(d) {
return d.date_created;
}));
x1.domain(options).rangeRoundBands([0, x0.rangeBand()]);
y.domain([0, d3.max(obj, function(d) {
return d.jobs_success
})]);
var line = d3.svg.line()
.x(function(d) {
return x0(d.date_created) + x0.rangeBand() / 2;
})
.y(function(d) {
return y(d.jobs_success);
});
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 jobs");
var bar = svg.selectAll(".bar")
.data(obj)
.enter().append("g")
.attr("class", "rect")
.attr("transform", function(d) {
return "translate(" + x0(d.date_created) + ",0)";
});
bar.selectAll("rect")
.data(function(d) {
return d.valores;
})
.enter().append("rect")
.attr("width", x1.rangeBand())
.attr("x", function(d) {
return x1(d.name);
})
.attr("y", function(d) {
return y(d.value);
})
.attr("value", function(d) {
return d.name;
})
.attr("height", function(d) {
return height - y(d.value);
})
.style("fill", function(d) {
return color(d.name);
});
bar
.on("mousemove", function(d) {
divTooltip.style("left", d3.event.pageX + 10 + "px");
divTooltip.style("top", d3.event.pageY - 25 + "px");
divTooltip.style("display", "inline-block");
var x = d3.event.pageX,
y = d3.event.pageY
var elements = document.querySelectorAll(':hover');
l = elements.length
l = l - 1
elementData = elements[l].__data__
divTooltip.html((d.date_created) + "<br>" + elementData.name + "<br>" + elementData.value);
});
bar
.on("mouseout", function(d) {
divTooltip.style("display", "none");
});
var legend = svg.selectAll(".legend")
.data(options.slice())
.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;
});
svg.append("path")
.datum(obj)
.attr("class", "line")
.attr("d", line)
.style("fill", "none")
.style("stroke", "red")
.style("stroke-width", 2);
.axis line,
.axis path {
fill: none;
stroke: black;
shape-rendering: crispEdges;
}
<script src="https://d3js.org/d3.v3.min.js"></script>
Ok a couple of things. Your line's x function should be relying on x0 not x1:
var line = d3.svg.line()
.x(function(d) { return x0(d.date_created); })
.y(function(d) { return y(d.jobs_success); });
And your path needs to be called like this:
svg.append("path")
.attr("class", "line")
.attr("d", line(obj));
That should get you most of the way there - you might want to tweak the maximum y-value, and shift the x co-ordinate of the line by x0.rangeBand()/2 as well to make it line up properly with the centre of the bars.

d3.v3.min.js:2 Uncaught TypeError: Cannot read property 'length' of undefined

I have a mistake and I can not find it, thanks for your help.
My data is called consumer_complaints.csv:
date_received,product,sub_product,issue,sub_issue,consumer_complaint_narrative,company_public_response,company,state,zipcode,tags,consumer_consent_provided,submitted_via,date_sent_to_company,company_response_to_consumer,timely_response, consumer_disputed,complaint_id
08/30/2013,Mortgage,Other mortgage,Loan modification,collection,foreclosure,U.S. Bancorp,CA,95993,Referral,09/03/2013,Closed with explanation,Yes,Yes,511074
My code:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.bar {
fill: steelblue;
}
.x.axis path {
display: none;
}
</style>
<body>
<script src="//d3js.org/d3.v3.min.js"></script>
<script>
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var 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.ordinal()
.range(["#ff0000", "#00ff00"]);
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.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
d3.csv("consumer_complaints.csv", function(error, data) {
if (error) throw error;
var nested_data = d3.nest()
.key(function(d) { return d.submitted_via; })
.entries(data);
var subVia = [];
nested_data.forEach(function(d,i){
var count = 0;
subVia[i] = d.key;
d.values.forEach(function(v){
if(v.consumer_disputed == "Yes")
count++
});
d.dispu = [{name: "Yes",value: count/d.values.length},{name: "No",value:
(d.values.length-count)/d.values.length}];
});
x0.domain(subVia.forEach(function(d,i){ return subVia[i]; }));
x1.domain(subVia).rangeRoundBands([0, x0.rangeBand()]);
y.domain([0, d3.max(nested_data, function(d) { return d3.max(d.dispu, 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("Satisfaction");
var state = svg.selectAll(".state")
.data(data)
.enter().append("g")
.attr("class", "state")
.attr("transform", subVia.forEach(
function(d,i) {console.log(subVia[i]); return "translate(" + x0(subVia[i]) + ",0)"; }));
state.selectAll("rect")
.data(function(d) { return d.dispu; })
.enter().append("rect")
.attr("width", x1.rangeBand())
.attr("x", function(d) { return x1(d.key); })
.attr("y", function(d) { return y(d.value); })
.attr("height", function(d) { return height - y(d.value); })
.style("fill", function(d) { return color(d.key); });
var legend = svg.selectAll(".legend")
.data(subVia.slice().reverse())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
legend.append("rect")
.attr("x", width - 18)
.attr("width", 18)
.attr("height", 18)
.style("fill", color);
legend.append("text")
.attr("x", width - 24)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) { return d; });
});
</script>
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.bar {
fill: steelblue;
}
.x.axis path {
display: none;
}
</style>
<body>
<script src="//d3js.org/d3.v3.min.js"></script>
<script>
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var 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.ordinal()
.range(["#ff0000", "#00ff00"]);
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.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
d3.csv("consumer_complaints.csv", function(error, data) {
if (error) throw error;
var nested_data = d3.nest()
.key(function(d) { return d.submitted_via; })
.entries(data);
var subVia = [];
nested_data.forEach(function(d,i){
var count = 0;
subVia[i] = d.key;
d.values.forEach(function(v){
if(v.consumer_disputed == "Yes")
count++
});
d.dispu = [{name: "Yes",value: count/d.values.length},{name: "No",value:
(d.values.length-count)/d.values.length}];
});
x0.domain(subVia.forEach(function(d,i){ return subVia[i]; }));
x1.domain(subVia).rangeRoundBands([0, x0.rangeBand()]);
y.domain([0, d3.max(nested_data, function(d) { return d3.max(d.dispu, 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("Satisfaction");
var state = svg.selectAll(".state")
.data(data)
.enter().append("g")
.attr("class", "state")
.attr("transform", subVia.forEach(
function(d,i) {console.log(subVia[i]); return "translate(" + x0(subVia[i]) + ",0)"; }));
state.selectAll("rect")
.data(function(d) { return d.dispu; })
.enter().append("rect")
.attr("width", x1.rangeBand())
.attr("x", function(d) { return x1(d.key); })
.attr("y", function(d) { return y(d.value); })
.attr("height", function(d) { return height - y(d.value); })
.style("fill", function(d) { return color(d.key); });
var legend = svg.selectAll(".legend")
.data(subVia.slice().reverse())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
legend.append("rect")
.attr("x", width - 18)
.attr("width", 18)
.attr("height", 18)
.style("fill", color);
legend.append("text")
.attr("x", width - 24)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) { return d; });
});
</script>

How to add tool-tips in d3 js vertically grouped bar chart

I was creating an animated grouped bar chart. I was able to create a simple vertical grouped bar chart by the code provided below. Now I like to add some tool-tips which will show some specific data about the bar. For example, "Letter:A,Frequency:0.05". Can anyone help me out in this case.Thanks in advance :)
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 960 - 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.ordinal()
.range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]);
var xAxis = d3.svg.axis()
.scale(x0)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickFormat(d3.format(".2s"));
//console.log(margin.left);
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var data = [
{letter: "A", frequency: .08167,depth:.32},
{letter: "B", frequency: .01492,depth:.69}
];
var groupNames=d3.keys(data[0]).filter(function(key){return key!="letter";})
data.forEach(function(d){
d.groups=groupNames.map(function(name){return {name:name,value:+d[name]};})
});
x0.domain(data.map(function(d){ alert(d.letter);return d.letter;}));
x1.domain(groupNames).rangeRoundBands([0, x0.rangeBand()]);
y.domain([0,d3.max(data,function(d){
return d3.max(d.groups,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("Letter Fun");
var letter = svg.selectAll(".letter")
.data(data)
.enter().append("g")
.attr("class", "g")
.attr("transform", function(d) { return "translate(" + x0(d.letter) + ",0)"; });
letter.selectAll("rect")
.data(function(d) { return d.groups; })
.enter().append("rect")
.attr("width", x1.rangeBand())
.attr("x", function(d) {alert(d.name); 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); });
letter.selectAll("text")
.data(function(d) { return d.groups; })
.enter().append("text")
.attr("class","barstext")
.attr("x", function(d) { return x1(d.name); })
.attr("y",function(d) { return y(d.value); })
.text(function(d){return d.value;})
var legend = svg.selectAll(".legend")
.data(groupNames.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; });
body {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.bar {
fill: steelblue;
}
.x.axis path {
display: none;
}
<body></body>
Step 1: Add letter information to the groups data.
data.forEach(function(d) {
d.groups = groupNames.map(function(name) {
return {
name: name,
letter: d.letter,
value: +d[name]
};
})
});
Step 2: Append title to bars.
.append("title")
.text(function(d,i){
var name = d.name.replace(/^./,d.name[0].toUpperCase());
return "Letter : "+d.letter+" \n"+name+" : "+d.value;
});
var margin = {
top: 20,
right: 20,
bottom: 30,
left: 40
},
width = 960 - 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.ordinal()
.range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]);
var xAxis = d3.svg.axis()
.scale(x0)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickFormat(d3.format(".2s"));
//console.log(margin.left);
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var data = [{
letter: "A",
frequency: .08167,
depth: .32
}, {
letter: "B",
frequency: .01492,
depth: .69
}];
var groupNames = d3.keys(data[0]).filter(function(key) {
return key != "letter";
})
data.forEach(function(d) {
d.groups = groupNames.map(function(name) {
return {
name: name,
letter: d.letter,
value: +d[name]
};
})
});
x0.domain(data.map(function(d) {
return d.letter;
}));
x1.domain(groupNames).rangeRoundBands([0, x0.rangeBand()]);
y.domain([0, d3.max(data, function(d) {
return d3.max(d.groups, 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("Letter Fun");
var letter = svg.selectAll(".letter")
.data(data)
.enter().append("g")
.attr("class", "g")
.attr("transform", function(d) {
return "translate(" + x0(d.letter) + ",0)";
});
letter.selectAll("rect")
.data(function(d) {
return d.groups;
})
.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);
})
.append("title")
.text(function(d,i){
var name = d.name.replace(/^./,d.name[0].toUpperCase());
return "Letter : "+d.letter+" \n"+name+" : "+d.value;
});
letter.selectAll("text")
.data(function(d) {
return d.groups;
})
.enter().append("text")
.attr("class", "barstext")
.attr("x", function(d) {
return x1(d.name);
})
.attr("y", function(d) {
return y(d.value);
})
.text(function(d) {
return d.value;
})
var legend = svg.selectAll(".legend")
.data(groupNames.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;
});
body {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.bar {
fill: steelblue;
}
.x.axis path {
display: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<body></body>

d3 - multiple overlaying charts with custom DOM element

I'm new to d3, but pretty familiar with the HighCharts api.
I've seen lots of examples of multiple d3 charts on the same page; but can't seem to find examples of one chart overlaying/sitting directly on top of another chart. Is this possible?
With HighCharts, you can define multiple chart types in the plotOptions config object. Is there something similar with d3? Or, how could you do this with d3?
I would effectively like to have a line graph on top of a bar chart. There will be different 'stages' according to the data, so some of the bar's could be inactive/empty.
Additionally, I need to display an indicator to show where the 'stage' is currently; and ensure that this is all responsive.
Example (rough mockup):
After researching d3 and looking for similar examples, I am thinking that maybe d3 isn't the best choice for this; maybe a custom CSS/JS/HTML solution (inside an angular app) would be better.
Any recommendations or pointers would be very appreciated.
Here's a quick mock-up started from this excellent bar chart example:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.point rect {
fill: steelblue;
}
.point circle {
fill: orange;
}
.point rect:hover {
fill: brown;
}
.axis {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.x.axis path {
display: none;
}
.line {
fill: none;
stroke: orange
}
</style>
<body>
<script src="//d3js.org/d3.v3.min.js"></script>
<script>
var margin = {top: 75, right: 20, bottom: 30, left: 40},
width = 600 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var data = "abcdefghijklmnopqrstuvwxyz".split("").map(function(d){
return {
letter: d,
bar: Math.random() * 10,
line: Math.random() * 10
};
})
x.domain(data.map(function(d) { return d.letter; }));
y.domain([0, d3.max(data, function(d) { return d3.max([d.bar, d.line]); })]);
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");
var points = svg.selectAll(".point")
.data(data)
.enter().append("g")
.attr("class", "point");
points.append('rect')
.attr("x", function(d) { return x(d.letter); })
.attr("width", x.rangeBand())
.attr("y", function(d) { return y(d.bar); })
.attr("height", function(d) { return height - y(d.bar); });
points.append('circle')
.attr("r", 5)
.attr("cx", function(d){ return x(d.letter) + x.rangeBand() / 2; })
.attr("cy", function(d){ return y(d.line)});
var line = d3.svg.line()
.x(function(d) { return x(d.letter) + x.rangeBand() / 2; })
.y(function(d) { return y(d.line); });
svg.append("path")
.attr("class", "line")
.datum(data)
.attr("d", line);
var indicator = svg.append("g")
.attr("r", 5)
.attr("transform", "translate(" + (x("q") + x.rangeBand() / 2) + "," + -20 + ")");
indicator.append("circle")
.attr("r", 40)
.style("fill", "red");
indicator.append("text")
.text("!")
.style("fill", "white")
.style("text-anchor", "middle")
.style("alignment-baseline", "middle")
.style("font-size", 70);
indicator.append("line")
.attr("y1", 20)
.attr("y2", height + 20)
.attr("x1", 0)
.attr("x2", 0)
.style("stroke", "red")
.style("stroke-width", "4px");
</script>
New Solution Based on Comments
Given your input data, here's a new example. I went a bit overboard here, so please ask question on any confusing bits. I tried to comment it out:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
rect {
fill: steelblue;
}
circle {
fill: orange;
}
rect:hover {
fill: brown;
}
.axis {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.x.axis path {
display: none;
}
.line {
fill: none;
stroke: orange
}
</style>
<body>
<script src="//d3js.org/d3.v3.min.js"></script>
<script>
var margin = {
top: 75,
right: 20,
bottom: 30,
left: 40
},
width = 600 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var 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 + ")");
// here's your data
var data =
{
'point1': [{
'value': 50
}, {
'value': 100
}, {
'value': 100
}, {
'value': 150
}],
'point2': [{
'value': 25
}, {
'value': 40
}, {
'value': 60
}],
'point3': [{
'value': 25
}]
};
// d3ify your data
// d3 likes arrays of objects, you have an object of objects
// so first make it an array
var barData = d3.entries(data);
// set x domain
x.domain(barData.map(function(d){ return d.key }));
// create lineData
var lineData = [];
barData.forEach(function(d0, i){
d0.mean = d3.mean(d0.value, function(d1){ return d1.value });
d0.max = d3.max(d0.value, function(d1){ return d1.value});
var N = d0.value.length,
// this is an inner scale
// that represents each bar
s = d3.scale.linear().range([
x(d0.key) + (x.rangeBand() / N) / 2,
x(d0.key) + x.rangeBand()
]).domain([
0, N
])
d0.value.forEach(function(d1, j){
lineData.push({
x: s(j), // this is the pixel position of x, it's jittered on the bar
y: d1.value // this is the user position of y
})
});
});
// set y domain
y.domain([0, d3.max(barData, function(d) {
return d.max;
})]);
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");
// draw bars
var bars = svg.selectAll(".bar")
.data(barData)
.enter()
.append('rect')
.attr('class', 'bar')
.attr("x", function(d) {
return x(d.key);
})
.attr("width", x.rangeBand())
.attr("y", function(d) {
return y(d.mean);
})
.attr("height", function(d) {
return height - y(d.mean);
});
// add points
var points = svg.selectAll('point')
.data(lineData)
.enter()
.append('circle')
.attr('class', 'point')
.attr("r", 5)
.attr("cx", function(d) {
return d.x; // already pixel position
})
.attr("cy", function(d) {
return y(d.y)
});
var line = d3.svg.line()
.x(function(d) {
return d.x; // already pixel position
})
.y(function(d) {
return y(d.y);
});
svg.append("path")
.attr("class", "line")
.datum(lineData)
.attr("d", line);
var indicator = svg.append("g")
.attr("transform", "translate(" + (x("point2") + x.rangeBand() / 2) + "," + -20 + ")");
indicator.append("circle")
.attr("r", 40)
.style("fill", "red");
indicator.append("text")
.text("!")
.style("fill", "white")
.style("text-anchor", "middle")
.style("alignment-baseline", "middle")
.style("font-size", 70);
indicator.append("line")
.attr("y1", 20)
.attr("y2", height + 20)
.attr("x1", 0)
.attr("x2", 0)
.style("stroke", "red")
.style("stroke-width", "4px");
</script>
Happy d3ing!

Dynamic number of lines on chart

I currently have a d3 multiseries line chart which displays how many emails and phone calls have been received.
My data retrieval and data structure is as follows:
var allCommunications = _uow.CommunicationRepository.Get()
.Where(c => c.DateOpened.Year == year)
.GroupBy(c => new { c.Method, c.DateOpened.Month })
.Select(g => new
{
Type = g.Key.Method,
xVal = g.Key.Month,
Value = g.Count()
});
This is then converted to the following structure:
public class LineChartData
{
public int xValue { get; set; }
public int EmailValue { get; set; }
public int PhoneValue { get; set; }
}
The graph is created using the following javascript:
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,
emailLineColour = "#779ECB", phoneLineColour = "#FF6961", tooltipTextColour = "white";
var x;
if (type == "month") {
var x = d3.scale.linear()
.domain([1, 31])
.range([0, width]);
} else if (type == "year")
{
var x = d3.scale.linear()
.domain([1, 12])
.range([0, width]);
}
var minPhone = Math.min.apply(Math, data.map(function (o) { return o.PhoneValue }));
var maxPhone = Math.max.apply(Math, data.map(function (o) { return o.PhoneValue }));
var minEmail = Math.min.apply(Math, data.map(function (o) { return o.EmailValue }));
var maxEmail = Math.max.apply(Math, data.map(function (o) { return o.EmailValue }));
var minY = Math.min(minPhone, minEmail);
var maxY = Math.max(maxPhone, maxEmail);
var y = d3.scale.linear()
.domain([minY, maxY + 5])
.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");
if (type == "month") {
var emailTip = d3.tip()
.attr('class', 'd3-tip')
.offset([-10, 0])
.html(function (d) {
return "<strong>Emails:</strong> <span style='color:"+tooltipTextColour+"'>" + d.EmailValue + "</span><br /><strong>Day of Month:</strong><span style='color:white'>" + d.xValue + "</span>";
});
var phoneTip = d3.tip()
.attr('class', 'd3-tip')
.offset([-10, 0])
.html(function (d) {
return "<strong>Calls:</strong> <span style='color:" + tooltipTextColour + "'>" + d.PhoneValue + "</span><br /><strong>Day of Month:</strong><span style='color:white'>" + d.xValue + "</span>";
});
}
else if (type == "year") {
var emailTip = d3.tip()
.attr('class', 'd3-tip')
.offset([-10, 0])
.html(function (d) {
return "<strong>Emails:</strong> <span style='color:" + tooltipTextColour + "'>" + d.EmailValue + "</span><br /><strong>Month of Year:</strong><span style='color:white'>" + d.xValue + "</span>";
});
var phoneTip = d3.tip()
.attr('class', 'd3-tip')
.offset([-10, 0])
.html(function (d) {
return "<strong>Calls:</strong> <span style='color:" + tooltipTextColour + "'>" + d.PhoneValue + "</span><br /><strong>Month of Year:</strong><span style='color:white'>" + d.xValue + "</span>";
});
}
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);
svg.call(emailTip);
svg.call(phoneTip);
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');
}
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');
svg.append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
var emailLine = d3.svg.line()
.interpolate("linear")
.x(function (d) { return x(d.xValue); })
.y(function (d) { return y(d.EmailValue); });
var phoneLine = d3.svg.line()
.interpolate("linear")
.x(function (d) { return x(d.xValue); })
.y(function (d) { return y(d.PhoneValue); });
svg.selectAll('.emailLine')
.data(data)
.enter()
.append("path")
.attr("class", "line")
.attr('stroke', emailLineColour)
.attr("d", emailLine(data));
svg.selectAll("circle.emailLine")
.data(data)
.enter().append("svg:circle")
.attr("class", "emailLine")
.style("fill", emailLineColour)
.attr("cx", emailLine.x())
.attr("cy", emailLine.y())
.attr("r", 5)
.on('mouseover', emailTip.show)
.on('mouseout', emailTip.hide);
svg.selectAll('.phoneLine')
.data(data)
.enter()
.append("path")
.attr("class", "line")
.attr('stroke', phoneLineColour)
.attr("d", phoneLine(data));
svg.selectAll("circle.phoneLine")
.data(data)
.enter().append("svg:circle")
.attr("class", "phoneLine")
.style("fill", phoneLineColour)
.attr("cx", phoneLine.x())
.attr("cy", phoneLine.y())
.attr("r", 5)
.on('mouseover', phoneTip.show)
.on('mouseout', phoneTip.hide);
svg.append("text")
.attr("transform", "translate(" + (x(data[data.length - 1].xValue) + 5) + "," + y(data[data.length - 1].EmailValue) + ")")
.attr("dy", ".35em")
.style("fill", emailLineColour)
.text("Email");
svg.append("text")
.attr("transform", "translate(" + (x(data[data.length - 1].xValue) + 5) + "," + y(data[data.length - 1].PhoneValue) + ")")
.attr("dy", ".35em")
.style("fill", phoneLineColour)
.text("Phone");
if (callback) {
callback();
}
}
Obviously this is very long and very limited due to each series for the chart being hardcoded. Therefore, it would be quite a bit of work if another method of communication is added. My idea behind resolving this is to have a dynamic number of series and create a line for each series. Therefore i guess my data structure would have to be something like:
public class LineChartData
{
public string Type {get;set;} //for the label
public Data Data{get;set;}
}
public class Data
{
public int xValue { get; set; }
public int Value { get; set; }
}
Or something similar?
So i guess my question is, would this be the correct approach to structuring my data, any suggestions to change my query to do this, and how would i edit my javascript in order to account for this.
Apologies for the long winded question and thanks in advance for any help.
If any more info is required, please ask and i will provide anything i can.
Thanks,
EDIT:
Here is my updated code after attempting the suggestion by Mark below:
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,
emailLineColour = "#779ECB", phoneLineColour = "#FF6961", tooltipTextColour = "white";
var color = d3.scale.category10();
var nest = d3.nest()
.key(function (d) { return d.Type; })
.entries(data);
var x;
if (type == "month") {
var x = d3.scale.linear()
.domain([1, 31])
.range([0, width]);
} else if (type == "year")
{
var x = d3.scale.linear()
.domain([1, 12])
.range([0, width]);
}
var y = d3.scale.linear()
.domain([0, 100])
.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()
.interpolate("linear")
.x(function (d) { return x(d.xValue); })
.y(function (d) { 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');
}
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');
svg.append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
color.domain(d3.keys(nest[0]).filter(function (key) { return key === nest[0].key; }));
var methods = color.domain().map(function (commType) {
return {
commType: commType,
values: nest.map(function (d) {
return { xValue: d.xVal, Value: d.Value };
})
};
});
x.domain(d3.extent(nest, function (d) { return d.xVal; }));
y.domain([
d3.min(methods, function (m) { return d3.min(m.values, function (v) { return v.Value; }); }),
d3.max(methods, function (m) { return d3.max(m.values, function (v) { return v.Value; }); })
]);
var method = svg.selectAll('.method')
.data(methods)
.enter().append('g')
.attr('class', 'method');
method.append('path')
.attr('class', 'line')
.attr('d', function (d) { return line(d.values); })
.attr('stroke', function (d) { return color(d.commType); });
method.append('text')
.datum(function (d) { return { commType: d.commType, value: d.values[d.values.length - 1] }; })
.attr("transform", function (d) { return "translate(" + x(d.value.xVal) + "," + y(d.value.Value) + ")"; })
.attr('x', 3)
.attr('dy', '.35em')
.text(function (d) { return d.commType; });
if (callback) {
callback();
}
}
Your question might be a little too broad for StackOverflow, but I'll try to help. The way I always approach the question of how should my API output data, is to ask how is my data going to be consumed on the front-end? In this case, you are trying to create a d3 multi-line chart and d3 will want an array of objects containing an array of data points (here's a great example). Something like this in JSON:
[
{
key: 'Email', //<-- identifies the line
values: [ //<-- points for the line
{
xVal: '20160101',
Value: 10
}, {
xVal: '20160102',
Value: 20
}, ...
]
}, {
key: 'Phone',
values: [
{
xVal: 'Jan',
Value: 30
}, {
xVal: '20160102',
Value: 25
}, ...
]
},
...
]
Now the question becomes how to get your data into a structure like that. Given many hours, you could probably write a linq statement that'll do but, I kinda like returning a flat JSON object (after all if we are writing a re-useable restful interface, flat is the most useful). So, how then would we make that final jump for our easy to use d3 structure. Given your:
.Select(g => new
{
Type = g.Key.Method,
xVal = g.Key.Month,
Value = g.Count()
});
would produce a JSON object like:
[{"Type":"Phone","xVal":"Feb","Value":1},{"Type":"Email","xVal":"Jan","Value":3},{"Type":"Phone","xVal":"Jan","Value":1}]
d3 could then get to our "easy to work with" format as easy as:
var nest = d3.nest()
.key(function(d) { return d.Type; })
.entries(data);
Which produces:
[
{
"key":"Phone",
"values":[
{
"Type":"Phone",
"xVal":"Feb",
"Value":1
},
{
"Type":"Phone",
"xVal":"Jan",
"Value":1
}
]
},
{
"key":"Email",
"values":[
{
"Type":"Email",
"xVal":"Jan",
"Value":3
}
]
}
]
From this structure, your multi-line chart becomes a breeze....
EDITS FOR COMMENTS
I really didn't understand what you were attempting to do with some of your code (in particular with your methods variable - the data was already in a great format for d3). So I refactored a bit:
<!DOCTYPE html>
<html>
<head>
<script data-require="d3#3.5.3" data-semver="3.5.3" src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.3/d3.js"></script>
<style>
body {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.x.axis path {
display: none;
}
.line {
fill: none;
stroke: steelblue;
stroke-width: 1.5px;
}
</style>
</head>
<body>
<script>
// 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;
var colors = {
"Phone": "#FF6961",
"Email": "#779ECB"
}
var color = d3.scale.category10();
var data = [{
"Type": "Phone",
"xValue": 1,
"Value": 5
}, {
"Type": "Email",
"xValue": 1,
"Value": 7
}, {
"Type": "Email",
"xValue": 2,
"Value": 1
}, {
"Type": "Phone",
"xValue": 2,
"Value": 4
}, {
"Type": "Phone",
"xValue": 4,
"Value": 2
}];
var nest = d3.nest()
.key(function(d) {
return d.Type;
})
.entries(data);
var x;
var type = "month";
if (type == "month") {
var x = d3.scale.linear()
.domain([1, 31])
.range([0, width]);
} else if (type == "year") {
var x = d3.scale.linear()
.domain([1, 12])
.range([0, width]);
}
var y = d3.scale.linear()
.domain([0, 100])
.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()
.interpolate("linear")
.x(function(d) {
return x(d.xValue);
})
.y(function(d) {
return y(d.Value);
});
var svg = d3.select('body').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 + ")");
y.domain([
0,
d3.max(nest, function(t) { return d3.max(t.values, function(v) { return v.Value; }); })
]);
x.domain([
d3.min(nest, function(t) { return d3.min(t.values, function(v) { return v.xValue; }); }),
d3.max(nest, function(t) { return d3.max(t.values, function(v) { return v.xValue; }); })
]);
nest.forEach(function(d){
for (var i = x.domain()[0]; i <= x.domain()[1]; i++){
if (!d.values.some(function(v){ return (v.xValue === i) })){
d.values.splice((i - 1), 0, {xValue: i, Value: 0});
}
}
});
var xAxis = svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
if (type == "year") {
xAxis
.append("text")
.attr("class", "axis-label")
.attr("transform", "none")
.attr("y", margin.top + 15)
.attr("x", width / 2)
.text('Month');
} else if (type == "month") {
xAxis
.append("text")
.attr("class", "axis-label")
.attr("y", margin.top + 15)
.attr("x", width / 2)
.text('Day')
.style('text-anchor', 'middle');
}
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("class", "axis-label")
.attr("transform", "rotate(-90)")
.attr("y", (-margin.left) + 15)
.attr("x", -height / 2)
.text('Communications')
.style('text-anchor', 'middle');
svg.append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
/*
color.domain(d3.keys(nest[0]).filter(function(key) {
return key === nest[0].key;
}));
var methods = color.domain().map(function(commType) {
return {
commType: commType,
values: nest.map(function(d) {
return {
xValue: d.xVal,
Value: d.Value
};
})
};
});
*/
var method = svg.selectAll('.method')
.data(nest)
.enter().append('g')
.attr('class', 'method');
method.append('path')
.attr('class', 'line')
.attr('d', function(d) {
return line(d.values);
})
.style('stroke', function(d) {
return color(d.key);
// OR if you want to use you defined ones
//return colors[d.key];
});
method.append('text')
.attr("transform", function(d) {
var len = d.values.length - 1;
return "translate(" + x(d.values[len].xValue) + "," + y(d.values[len].Value) + ")";
})
.attr('x', 3)
.attr('dy', '.35em')
.text(function(d) {
return d.key;
});
//if (callback) {
// callback();
//}
// }
</script>
</body>
</html>
EDIT FOR COMMENTS 2
That's actually a tricky question. How about:
// for each dataset
nest.forEach(function(d){
// loop our domain
for (var i = x.domain()[0]; i <= x.domain()[1]; i++){
// if there's no xValue at that location
if (!d.values.some(function(v){ return (v.xValue === i) })){
// add a zero in place
d.values.splice((i - 1), 0, {xValue: i, Value: 0});
}
}
});
Code sample above is edited also.

Categories

Resources