Related
I'm trying to write a generic multiline chart function based on Mike Bostock's example (https://bl.ocks.org/mbostock/3884955).
I'm facing an issue wherein, the last tick label on my Monthly graph x-axis does not show up. The last tick appears fine on the Weekly graph x-axis.
JS Fiddle Link:
http://jsfiddle.net/Q5Jag/11879/
I'm suspecting the issue here could be due to the range specified for the x-axis which for some reason ignores the last value. But I'm not exactly sure what is going on here. Could anyone help me debug ?
Here is my code:
function renderMultiLineChart(datafile, chartDiv, xAxisLabel, yAxisLabel, graphCadence){
var margin = {top: 20, right: 60, bottom: 80, left: 60},
width = 760 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
var parseDate = d3.timeParse("%m/%d/%y");
var x = d3.scaleUtc()
.range([0, width]);
var y = d3.scaleLinear()
.range([height, 0]);
var color = d3.scaleOrdinal(d3.schemeCategory10);
//var xAxis = d3.axisBottom(x).tickFormat(function(d){ return d.x;});
switch(graphCadence) {
case "Daily":
var xAxis = d3.axisBottom(x).ticks(d3.timeDay.every(1)).tickFormat(d3.timeFormat("%m/%d"))
break;
case "Weekly":
var xAxis = d3.axisBottom(x).ticks(d3.timeSaturday.every(1)).tickFormat(d3.timeFormat("%m/%d"))
break;
case "Monthly":
//var xAxis = d3.axisBottom(x).ticks(d3.timeDay.every(1))
var xAxis = d3.axisBottom(x).ticks(d3.timeMonth.every(1)).tickFormat(d3.utcFormat("%m/%d"))
break;
}
var yAxis = d3.axisLeft(y);
var line = d3.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.count); })
var div = d3.select(chartDiv).append("div")
.attr("class", "tooltip")
.style("opacity", 0);
var svg = d3.select(chartDiv).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 + ")");
//https://pharos-rest-service-iad.iad.proxy.amazon.com/s3/tool.csv
d3.csv(datafile, function(error, data) {
color.domain(d3.keys(data[0]).filter(function(key) { return key !== "date"; }));
data.forEach(function(d) {
//console.log(d);
d.date = parseDate(d.date);
});
var datapoints = color.domain().map(function(name) {
return {
name: name,
values: data.map(function(d) {
return {date: d.date, count: +d[name]};
})
};
});
console.log(data);
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([
d3.min(datapoints, function(c) { return d3.min(c.values, function(v) { return v.count; }); }),
d3.max(datapoints, function(c) { return d3.max(c.values, function(v) { return v.count; }); })
]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.style("text-anchor", "end")
.attr("transform", "rotate(-45)");
// text label for the x axis
svg.append("text")
.attr("transform",
"translate(" + (width/2) + " ," +
(height + margin.top + 30) + ")")
.style("text-anchor", "middle")
.text(xAxisLabel);
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", "black") // set the line colour
.text(yAxisLabel);
var datapoint = svg.selectAll(".datapoint")
.data(datapoints)
.enter().append("g")
.attr("class", "datapoint");
datapoint.append("path")
.attr("class", "line")
.attr("d", function(d) { return line(d.values); })
.style("stroke", function(d) { return color(d.name); });
j = -1;
datapoint.selectAll("circle")
.data(function(d){return d.values})
.enter()
.append("circle")
.attr("r", 3)
.attr("cx", function(d,i) { return x(d.date); })
.attr("cy", function(d) { return y(d.count); })
.on("mouseover", function(d) {
div.transition()
.duration(200)
.style("opacity", 1);
div.html("<b>"+d.count+"</b>")
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout", function(d) {
div.transition()
.duration(500)
.style("opacity", 0);
})
.style("fill", function(d,i) { if (i == 0) { j++ }; return color(datapoints[j].name); });
var legendRectSize = 8;
var legendSpacing = 80;
var legendHolder = svg.append('g')
// translate the holder to the right side of the graph
.attr('transform', "translate(" + (100+margin.left+margin.right) + ","+(height+margin.bottom-20)+")")
var legend = legendHolder.selectAll('.legend')
.data(color.domain())
.enter()
.append('g')
.attr('class', 'legend')
.attr("transform", function (d, i) {
if (i === 0) {
dataL = legendRectSize + legendSpacing
return "translate(0,0)"
} else {
var newdataL = dataL
dataL += legendRectSize + legendSpacing
return "translate(" + (newdataL) + ",0)"
}
});
legend.append('rect')
.attr('width', legendRectSize)
.attr('height', legendRectSize)
.style('fill', color)
.style('stroke', color);
legend.append('text')
.attr('x', legendRectSize + 5)
.attr('y', legendRectSize)
.text(function(d) { return d; });
});
}
renderMultiLineChart("https://gist.githubusercontent.com/techyogii/323024b01c1eb4d0c07637e183e1e6d7/raw/422ed207cc2c38426fa726795ecd963f153135dd/app_usage","div#multiChartMonthly","Snapshot Date","Metric Count","Monthly")
renderMultiLineChart("https://gist.githubusercontent.com/techyogii/8ed38bdb3b8e44194ee8570ef9cc5b75/raw/d0c85aaf9eaa7e8819fd6e6e210885b0cfa6f47d/app_usage_weekly","div#multiChartWeekly","Snapshot Date","Metric Count","Weekly")
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;
}
div.tooltip {
position: absolute;
text-align: center;
/*width: 60px;
height: 28px;*/
padding: 4px;
font: 14px sans-serif;
background: lightsteelblue;
border: 0px;
border-radius: 10px;
pointer-events: none;
}
.legend {
font-size: 12px;
}
rect {
stroke-width: 2;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<div id="multiChartWeekly">
<div id="multiChartMonthly">
Thanks,
Yogesh
I believe the problem lies in a misalignment between how d3.timeMonth works and how your data is formatted. You see that d3.timeMonth is labeling the first of every month, whereas the data is grouped on the last day of every month. So when you call x.domain(d3.extent(data, function(d) { return d.date; }));, the last data point's date is less than the next label would have been.
One potential solution is to change your parse function to bump all your month dates forward by one day to make them line up with the first of the month. See below.
To remove any dates that end mid-month, you can filter the data set after parsing dates to clean it up.
function renderMultiLineChart(datafile, chartDiv, xAxisLabel, yAxisLabel, graphCadence){
var margin = {top: 20, right: 60, bottom: 80, left: 60},
width = 760 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
var parseDate = function(dateString){
if(graphCadence == "Monthly"){
var date = new Date(dateString);
date.setDate(date.getDate()+1);
return date;
}
return d3.timeParse("%m/%d/%y")(dateString);
}
var x = d3.scaleUtc()
.range([0, width]);
var y = d3.scaleLinear()
.range([height, 0]);
var color = d3.scaleOrdinal(d3.schemeCategory10);
//var xAxis = d3.axisBottom(x).tickFormat(function(d){ return d.x;});
switch(graphCadence) {
case "Daily":
var xAxis = d3.axisBottom(x).ticks(d3.timeDay.every(1)).tickFormat(d3.timeFormat("%m/%d"))
break;
case "Weekly":
var xAxis = d3.axisBottom(x).ticks(d3.timeSaturday.every(1)).tickFormat(d3.timeFormat("%m/%d"))
break;
case "Monthly":
//var xAxis = d3.axisBottom(x).ticks(d3.timeDay.every(1))
var xAxis = d3.axisBottom(x).ticks(d3.timeMonth.every(1)).tickFormat(d3.utcFormat("%m/%d"))
break;
}
var yAxis = d3.axisLeft(y);
var line = d3.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.count); })
var div = d3.select(chartDiv).append("div")
.attr("class", "tooltip")
.style("opacity", 0);
var svg = d3.select(chartDiv).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 + ")");
//https://pharos-rest-service-iad.iad.proxy.amazon.com/s3/tool.csv
d3.csv(datafile, function(error, data) {
color.domain(d3.keys(data[0]).filter(function(key) { return key !== "date"; }));
data.forEach(function(d) {
d.date = parseDate(d.date);
});
if(graphCadence == "Monthly"){
data = data.filter(function(d){
return d.date.getDate() == 1
});
}
var datapoints = color.domain().map(function(name) {
return {
name: name,
values: data.map(function(d) {
return {date: d.date, count: +d[name]};
})
};
});
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([
d3.min(datapoints, function(c) { return d3.min(c.values, function(v) { return v.count; }); }),
d3.max(datapoints, function(c) { return d3.max(c.values, function(v) { return v.count; }); })
]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.style("text-anchor", "end")
.attr("transform", "rotate(-45)");
// text label for the x axis
svg.append("text")
.attr("transform",
"translate(" + (width/2) + " ," +
(height + margin.top + 30) + ")")
.style("text-anchor", "middle")
.text(xAxisLabel);
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", "black") // set the line colour
.text(yAxisLabel);
var datapoint = svg.selectAll(".datapoint")
.data(datapoints)
.enter().append("g")
.attr("class", "datapoint");
datapoint.append("path")
.attr("class", "line")
.attr("d", function(d) { return line(d.values); })
.style("stroke", function(d) { return color(d.name); });
j = -1;
datapoint.selectAll("circle")
.data(function(d){return d.values})
.enter()
.append("circle")
.attr("r", 3)
.attr("cx", function(d,i) { return x(d.date); })
.attr("cy", function(d) { return y(d.count); })
.on("mouseover", function(d) {
div.transition()
.duration(200)
.style("opacity", 1);
div.html("<b>"+d.count+"</b>")
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout", function(d) {
div.transition()
.duration(500)
.style("opacity", 0);
})
.style("fill", function(d,i) { if (i == 0) { j++ }; return color(datapoints[j].name); });
var legendRectSize = 8;
var legendSpacing = 80;
var legendHolder = svg.append('g')
// translate the holder to the right side of the graph
.attr('transform', "translate(" + (100+margin.left+margin.right) + ","+(height+margin.bottom-20)+")")
var legend = legendHolder.selectAll('.legend')
.data(color.domain())
.enter()
.append('g')
.attr('class', 'legend')
.attr("transform", function (d, i) {
if (i === 0) {
dataL = legendRectSize + legendSpacing
return "translate(0,0)"
} else {
var newdataL = dataL
dataL += legendRectSize + legendSpacing
return "translate(" + (newdataL) + ",0)"
}
});
legend.append('rect')
.attr('width', legendRectSize)
.attr('height', legendRectSize)
.style('fill', color)
.style('stroke', color);
legend.append('text')
.attr('x', legendRectSize + 5)
.attr('y', legendRectSize)
.text(function(d) { return d; });
});
}
renderMultiLineChart("https://gist.githubusercontent.com/JstnPwll/5a24137a36c9246cf065c58d7f5bb5a5/raw/ff986ee88338e99d10ab93035ffacd3ffe92fd4e/gistfile1.txt","div#multiChartMonthly","Snapshot Date","Metric Count","Monthly")
renderMultiLineChart("https://gist.githubusercontent.com/techyogii/8ed38bdb3b8e44194ee8570ef9cc5b75/raw/d0c85aaf9eaa7e8819fd6e6e210885b0cfa6f47d/app_usage_weekly","div#multiChartWeekly","Snapshot Date","Metric Count","Weekly")
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;
}
div.tooltip {
position: absolute;
text-align: center;
/*width: 60px;
height: 28px;*/
padding: 4px;
font: 14px sans-serif;
background: lightsteelblue;
border: 0px;
border-radius: 10px;
pointer-events: none;
}
.legend {
font-size: 12px;
}
rect {
stroke-width: 2;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<div id="multiChartWeekly">
<div id="multiChartMonthly">
[EDIT] Outputted Graph: https://imgur.com/a/D0wtP
I am trying to shade in the area between 2 lines in a graph. The plots shows commits made by software engineers and shows their tt100 lines of raw and productive code, and i am trying to shade the delta region between the 2 lines. Some of the solutions I have found do not seem to match my approach. I appreciate any help. My code is the following:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.axis--x path {
display: none;
}
.axis--y path {
display: none;
}
.line {
fill: none;
stroke-width: 1.5px;
}
.grid line {
stroke: lightgrey;
stroke-opacity: 0.7;
shape-rendering: crispEdges;
}
.grid path {
stroke-width: 0;
}
</style>
<svg width="1080" height="500"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var svg = d3.select("svg"),
margin = {top: 20, right: 80, bottom: 30, left: 50},
width = svg.attr("width") - margin.left - margin.right - 50,
height = svg.attr("height") - margin.top - margin.bottom;
svg.append("rect")
.attr("width", "100%")
.attr("height", "100%")
.attr("fill", "#f5f5f1")
var parseTime = d3.timeParse("%Y%m");
var x = d3.scaleTime().range([0, width]),
y = d3.scaleLinear().range([height, 0]),
z = d3.scaleOrdinal(d3.schemeCategory10);
var line = d3.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.time); });
var g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// gridlines in y axis function
function make_y_gridlines() {
return d3.axisLeft(y)
.ticks(5)
}
d3.csv("data.csv", type, function(error, data) {
if (error) throw error;
var employees1 = data.columns.slice(1,3).map(function(id) {
return {
id: id,
values: data.map(function(d) {
return {date: d.date, time: d[id]};
})
};
});
var employees2 = data.columns.slice(3).map(function(id) {
return {
id: id,
values: data.map(function(d) {
return {date: d.date, time: d[id]};
})
};
});
var employees = data.columns.slice(1).map(function(id) {
return {
id: id,
values: data.map(function(d) {
return {date: d.date, time: d[id]};
})
};
});
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([
0,
d3.max(employees, function(c) { return d3.max(c.values, function(d) { return d.time; }); })
]);
z.domain(employees.map(function(c) { return c.id; }));
// add the Y gridlines
g.append("g")
.attr("class", "grid")
.call(make_y_gridlines()
.tickSize(-width)
.tickFormat("")
)
g.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
g.append("g")
.attr("class", "axis axis--y")
.call(d3.axisLeft(y))
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", -45)
.attr("dy", "0.71em")
.attr("fill", "#000")
.text("HOURS TO PRODUCE 100 LINES OF CODE (PRODUCTIVE VS RAW)");
g.append("text")
.attr("x", (width / 4))
.attr("y", 0 - (margin.top / 8))
.attr("text-anchor", "middle")
.style("font-size", "24px")
.text("Churn Over Time");
var employee1 = g.selectAll(".employee1")
.data(employees1)
.enter().append("g")
.attr("class", "employee1");
var employee2 = g.selectAll(".employee2")
.data(employees2)
.enter().append("g")
.attr("class", "employee2");
employee1.append("path")
.attr("class", "line")
.attr("d", function(d) { return line(d.values); })
.style("stroke", "A057AE");
employee1.append("text")
.datum(function(d) { return {id: d.id, value: d.values[d.values.length - 1]}; })
.attr("transform", function(d) { return "translate(" + x(d.value.date) + "," + y(d.value.time) + ")"; })
.attr("x", 3)
.attr("dy", "0.35em")
.style("font", "10px sans-serif")
.text(function(d) { return d.id; });
employee2.append("path")
.attr("class", "line")
.attr("d", function(d) { return line(d.values); })
.style("stroke", "91BF50");
employee2.append("text")
.datum(function(d) { return {id: d.id, value: d.values[d.values.length - 1]}; })
.attr("transform", function(d) { return "translate(" + x(d.value.date) + "," + y(d.value.time) + ")"; })
.attr("x", 3)
.attr("dy", "0.35em")
.style("font", "10px sans-serif")
.text(function(d) { return d.id; });
});
function type(d, _, columns) {
d.date = parseTime(d.date);
for (var i = 1, n = columns.length, c; i < n; ++i) d[c = columns[i]] = +d[c];
return d;
}
</script>
And the data.csv file is the following:
date,Paul Productive Code,Paul Raw Code,Michelle Productive Code,Michelle Raw Code
201801,4.1,3.2,2.2,1.9
201802,4.2,3.5,3.4,1.9
201803,4.1,3.1,3.1,1.9
201804,4.5,3.8,3.2,2.3
201805,6.4,4.7,3.7,2.7
201806,8.6,5.5,3.2,2.2
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>
I'm working on a a visualisation project using d3js. I have some distributed dataset that I need to visualise like that :
this visualisation was done using seaborn.jointplot in python, but I need this to be implemented using d3js, is there any alternative for that in d3js? how can this be done in d3js ?
seaborn.jointplot API Documentation
Cool looking plot and I had some time this morning, so here's a d3 v4 minimal implementation:
<!DOCTYPE html>
<meta charset="utf-8">
<body>
<script src="//d3js.org/d3.v4.min.js"></script>
<script>
// set the dimensions and margins of the graph
var margin = {
top: 80,
right: 80,
bottom: 20,
left: 20
},
width = 400 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
var g = svg.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
var x = d3.scaleLinear().range([0, width]).domain([0, 10]),
y = d3.scaleLinear().range([height, 0]).domain([0, 10]);
// Add the X Axis
g.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// Add the Y Axis
g.append("g")
.call(d3.axisLeft(y));
var random = d3.randomNormal(0, 1.2),
data = d3.range(100).map(function() {
return [random() + 5, random() + 5];
});
g.selectAll(".point")
.data(data)
.enter()
.append("circle")
.attr("cx", function(d) {
return x(d[0]);
})
.attr("cy", function(d) {
return y(d[1]);
})
.attr("r", 7)
.style("fill", "steelblue")
.style("stroke", "lightgray");
// top histogram
var gTop = svg.append("g")
.attr("transform",
"translate(" + margin.left + "," + 0 + ")");
var xBins = d3.histogram()
.domain(x.domain())
.thresholds(x.ticks(10))
.value(function(d) {
return d[0];
})(data);
var xy = d3.scaleLinear()
.domain([0, d3.max(xBins, function(d) {
return d.length;
})])
.range([margin.top, 0]);
var xBar = gTop.selectAll(".bar")
.data(xBins)
.enter().append("g")
.attr("class", "bar")
.attr("transform", function(d) {
return "translate(" + x(d.x0) + "," + xy(d.length) + ")";
});
var bWidth = x(xBins[0].x1) - x(xBins[0].x0) - 1;
xBar.append("rect")
.attr("x", 1)
.attr("width", bWidth)
.attr("height", function(d) {
return margin.top - xy(d.length);
})
.style("fill", "steelblue");
xBar.append("text")
.attr("dy", ".75em")
.attr("y", 2)
.attr("x", bWidth / 2)
.attr("text-anchor", "middle")
.text(function(d) {
return d.length < 4 ? "" : d.length;
})
.style("fill", "white")
.style("font", "9px sans-serif");
// right histogram
var gRight = svg.append("g")
.attr("transform",
"translate(" + (margin.left + width) + "," + margin.top + ")");
var yBins = d3.histogram()
.domain(y.domain())
.thresholds(y.ticks(10))
.value(function(d) {
return d[1];
})(data);
var yx = d3.scaleLinear()
.domain([0, d3.max(yBins, function(d) {
return d.length;
})])
.range([0, margin.right]);
var yBar = gRight.selectAll(".bar")
.data(yBins)
.enter().append("g")
.attr("class", "bar")
.attr("transform", function(d) {
return "translate(" + 0 + "," + y(d.x1) + ")";
});
var bWidth = y(yBins[0].x0) - y(yBins[0].x1) - 1;
yBar.append("rect")
.attr("y", 1)
.attr("width", function(d) {
return yx(d.length);
})
.attr("height", bWidth)
.style("fill", "steelblue");
yBar.append("text")
.attr("dx", "-.75em")
.attr("y", bWidth / 2 + 1)
.attr("x", function(d) {
return yx(d.length);
})
.attr("text-anchor", "middle")
.text(function(d) {
return d.length < 4 ? "" : d.length;
})
.style("fill", "white")
.style("font", "9px sans-serif");
</script>
</body>
</html>
d3.js version 3 sample:
<!DOCTYPE html>
<meta charset="utf-8">
<head>
<style>
.bar {
fill: steelblue;
}
.bar:hover {
fill: brown;
}
.axis {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.x.axis path {
display: none;
}
</style>
</head>
<body>
<script src="//d3js.org/d3.v3.min.js"></script>
<script>
// set the dimensions and margins of the graph
var margin = {
top: 80,
right: 80,
bottom: 20,
left: 20
},
width = 400 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
var g = svg.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
var x = d3.scale.linear().range([0, width]).domain([0, 10]),
y = d3.scale.linear().range([height, 0]).domain([0, 10]);
// Add the X Axis
g.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.svg.axis().orient("bottom").scale(x));
// Add the Y Axis
g.append("g")
.attr("class", "axis")
.call(d3.svg.axis().orient("left").scale(y));
var random = d3.random.normal(0, 1.2),
data = d3.range(100).map(function() {
return [random() + 5, random() + 5];
});
g.selectAll(".point")
.data(data)
.enter()
.append("circle")
.attr("cx", function(d) {
return x(d[0]);
})
.attr("cy", function(d) {
return y(d[1]);
})
.attr("r", 7)
.style("fill", "steelblue")
.style("stroke", "lightgray");
// top histogram
var gTop = svg.append("g")
.attr("transform",
"translate(" + margin.left + "," + 0 + ")");
var xBins = d3.layout.histogram()
.range(x.domain())
.bins(x.ticks(10))
.value(function(d) {
return d[0];
})(data);
var xy = d3.scale.linear()
.domain([0, d3.max(xBins, function(d) {
return d.length;
})])
.range([margin.top, 0]);
var xBar = gTop.selectAll(".bar")
.data(xBins)
.enter().append("g")
.attr("class", "bar")
.attr("transform", function(d) {
return "translate(" + x(d.x) + "," + xy(d.length) + ")";
});
var bWidth = x(xBins[0].dx) - 1;
xBar.append("rect")
.attr("x", 1)
.attr("width", bWidth)
.attr("height", function(d) {
return margin.top - xy(d.length);
})
.style("fill", "steelblue");
xBar.append("text")
.attr("dy", ".75em")
.attr("y", 2)
.attr("x", bWidth / 2)
.attr("text-anchor", "middle")
.text(function(d) {
return d.length < 4 ? "" : d.length;
})
.style("fill", "white")
.style("font", "9px sans-serif");
// right histogram
var gRight = svg.append("g")
.attr("transform",
"translate(" + (margin.left + width) + "," + margin.top + ")");
var yBins = d3.layout.histogram()
.range(y.domain())
.bins(y.ticks(10))
.value(function(d) {
return d[1];
})(data);
console.log(yBins);
var yx = d3.scale.linear()
.domain([0, d3.max(yBins, function(d) {
return d.length;
})])
.range([0, margin.right]);
var yBar = gRight.selectAll(".bar")
.data(yBins)
.enter().append("g")
.attr("class", "bar")
.attr("transform", function(d) {
return "translate(" + 0 + "," + y(d.x + d.dx) + ")";
});
yBar.append("rect")
.attr("y", 1)
.attr("width", function(d) {
return yx(d.length);
})
.attr("height", bWidth)
.style("fill", "steelblue");
yBar.append("text")
.attr("dx", "-.75em")
.attr("y", bWidth / 2 + 1)
.attr("x", function(d) {
return yx(d.length);
})
.attr("text-anchor", "middle")
.text(function(d) {
return d.length < 4 ? "" : d.length;
})
.style("fill", "white")
.style("font", "9px sans-serif");
</script>
</body>
</html>
I have a stacked bar chart, which has labels in the every stack. Now I wanted to have another label at top of each bar in the graph. I am not able to place a label at the top of the bar, and also I am getting sum of all the values in the chart, where as I want sum for each bar.
Here is my code,
var groups = svg.selectAll("g.cost")
.data(dataset.reverse())
.enter().append("g")
.attr("class", "cost")
.style("fill", function (d, i) { return colors[i]; });
var sum = [0];
var svg = d3.select("svg");
var bar = groups.selectAll("g")
.data(function (d) { return d; })
.enter().append("g")
.attr("transform", function (d, i) { return "translate(0," + i * y(d.y0) - y(d.y0 + d.y) + ")"; });
bar.append("rect")
.attr("x", function (d) { return x(d.x); })
.attr("y", function (d) { return y(d.y0 + d.y); })
.attr("height", function (d) { return y(d.y0) - y(d.y0 + d.y); })
.attr("width", x.rangeBand())
bar.append("text")
.attr("x", function (d) { return x(d.x); })
.attr("y", function (d) { return y(d.y0 + d.y); })
.attr("dy", ".35em")
.attr('style', 'font-size:13px')
.text(function (d) { if (d.y != 0) { sum += d.y; return "$" + d.y; } })
.style('fill', 'black');
bar.append("text")
.attr("x", function (d) { return x(d.x); })
.attr("y", function (d) { return y(d.y0 + d.y); })
.attr("dy", ".35em")
.attr('style', 'font-size:13px')
.text( sum)
.style('fill', 'black');
jsfiddle: http://fiddle.jshell.net/1fsm8cst/3/
Summarising vertically is not natural with the stack layout since it groups in layers, but d3 has plenty of functionality to help. In this case, d3.nest, d3.sum and d3.values (the really nice thing about d3.sum is that it ignores NaN values). With these you can summarise the data by month and then you can use the same coordinate system and scales to position the summarised elements.
var margin = {top: 20, right: 300, bottom: 35, left: 50};
var width = 760 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
var svg = d3.select("body")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
/* Data in strings like it would be if imported from a csv */
var data = [
{ month: "Jan", MobileCoupon: "430000", Bonus: "240000", Promotions: "200000", Merchandise: "150000" },
{ month: "Feb", MobileCoupon: "250000", Bonus: "440000", Promotions: "200000", Merchandise: "150000" },
{ month: "Mar", MobileCoupon: "350000", Bonus: "180000", Promotions: "200000", Merchandise: "150000" },
];
var parse = d3.time.format("%b").parse;
// Transpose the data into layers
var dataset = d3.layout.stack()(["MobileCoupon", "Bonus", "Promotions", "Merchandise"].map(function(fruit) {
return data.map(function(d) {
return {x: parse(d.month), y: +d[fruit]};
});
}));
var months = d3.nest()
.key(function(d){return parse(d.month)})
.rollup(function(leaves){
return d3.sum(leaves, function(d) {return d3.sum(d3.values(d))});
})
.entries(data);
// Set x, y and colors
var x = d3.scale.ordinal()
.domain(dataset[0].map(function(d) { return d.x; }))
.rangeRoundBands([10, width-10], 0.35);
var y = d3.scale.linear()
.domain([0, d3.max(dataset, function(d) { return d3.max(d, function(d) { return d.y0 + d.y; }); })])
.range([height, 0]);
var colors = ["#3D0000", "#d25c4d", "#f2b447", "#d9d574"];
// Define and draw axes
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(5)
.tickSize(-width, 0, 0)
.tickFormat( d3.format("$,s") );
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickFormat(d3.time.format("%b"));
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(0,0)")
.call(yAxis);
svg.append("g")
.call(xAxis)
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Create groups for each series, rects for each segment
var groups = svg.selectAll("g.cost")
.data(dataset)
.enter().append("g")
.attr("class", "cost")
.style("fill", function(d, i) { return colors[i]; });
// var svg = d3.select("svg");
var bar = groups.selectAll("g")
.data(function(d) { return d; })
.enter().append("g")
.attr("transform", function(d, i) {
return "translate(" + x(d.x) + ", 0)";
});
var sum=0;
bar.append("rect")
.attr("y", function(d) { return y(d.y0 + d.y); })
.attr("height", function(d) { return y(d.y0) - y(d.y0 + d.y); })
.attr("width", x.rangeBand())
bar.append("text")
.attr("x", -6)
.attr("y", function(d) { return y(d.y0 + d.y); })
.attr("dy", ".35em")
.text(function(d) {sum+=d.y; return d3.format("$,s")(d.y); });
columns = svg.append("g")
.selectAll("text").data(months)
.enter().append("text")
.attr("x", function(d){
return x(d.key) + x.rangeBand()/2
})
.attr("y", function (d) {
return y(d.values);
})
.attr("dy", "1.35em")
.attr('style', 'font-size:13px')
.text( function (d){
return d3.format("$,s")(d.values);
})
.style({fill: 'black', "text-anchor": "middle"});
// svg.call(tip);
// Draw legend
var legend = svg.selectAll(".legend")
.data(colors)
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, m) { return "translate(90," + (m+5) * 20 + ")"; });
legend.append("rect")
.attr("x", width - 18)
.attr("width", 18)
.attr("height", 18)
.style("fill", function(d, i) {return colors.slice().reverse()[i];});
legend.append("text")
.attr("x", width + 5)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "start")
.text(function(d, i) {
switch (i) {
case 0: return "Mobile Coupon";
case 1: return "Bonus";
case 2: return "Promotions";
case 3: return "Merchandise";
}
});
svg {
font: 10px sans-serif;
shape-rendering: crispEdges;
}
.axis path,
.axis line {
fill: none;
stroke: black;
shape-rendering: crispEdges;
}
path.domain {
stroke: none;
}
.y .tick line {
stroke: #ddd;}
text {
font: 10px sans-serif;
text-anchor: end;
}
.d3-tip {
line-height: 1;
font-weight: bold;
padding: 12px;
background: rgba(0, 0, 0, 0.8);
color: #fff;
border-radius: 2px;
}
/* Creates a small triangle extender for the tooltip */
.d3-tip:after {
box-sizing: border-box;
display: inline;
font-size: 10px;
width: 100%;
line-height: 1;
color: rgba(0, 0, 0, 0.8);
content: "\25BC";
position: absolute;
text-align: center;
}
/* Style northward tooltips differently */
.d3-tip.n:after {
margin: -1px 0 0 0;
top: 100%;
left: 0;
}
.legend
{
position: relative;
top: -401px;
left: 380px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
I am currently a beginner in Javascript and D3.
Hope my solution serves the purpose :
http://jsfiddle.net/sandeepedara/n3ew5sqq/
var margin = {top: 20, right: 300, bottom: 35, left: 50};
var width = 760 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
var svg = d3.select("body")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
/* Data in strings like it would be if imported from a csv */
var data = [
{ month: "Jan", MobileCoupon: "430000", Bonus: "240000", Promotions: "200000", Merchandise: "150000" },
{ month: "Feb", MobileCoupon: "250000", Bonus: "440000", Promotions: "200000", Merchandise: "150000" },
{ month: "Mar", MobileCoupon: "350000", Bonus: "180000", Promotions: "200000", Merchandise: "150000" },
];
for (var key in data) {
var sum=0;
if (data.hasOwnProperty(key)) {
var obj = data[key];
for (var prop in obj) {
// important check that this is objects own property
// not from prototype prop inherited
if(obj.hasOwnProperty(prop)){
if(prop=="month"){console.log("month");}
else{
sum = sum + parseInt(obj[prop]);
obj.sum = sum;
}
}
}
}
}
var parse = d3.time.format("%b").parse;
// Transpose the data into layers
var dataset = d3.layout.stack()(["MobileCoupon", "Bonus", "Promotions", "Merchandise","sum"].map(function(fruit) {
return data.map(function(d) {
return {x: parse(d.month), y: +d[fruit]};
});
}));
// Set x, y and colors
var x = d3.scale.ordinal()
.domain(dataset[0].map(function(d) { return d.x; }))
.rangeRoundBands([10, width-10], 0.35);
var y = d3.scale.linear()
.domain([0, d3.max(dataset, function(d) { return d3.max(d, function(d) { return d.y0 + d.y; }); })])
.range([height, 0]);
var colors = ["#3D0000", "#d25c4d", "#f2b447", "#d9d574"];
// Define and draw axes
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(5)
.tickSize(-width, 0, 0)
.tickFormat( function(d) { return "$" + d } );
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickFormat(d3.time.format("%b"));
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(0,0)")
.call(yAxis);
svg.append("g")
.call(xAxis)
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")");
//.call(xAxis);
// Create groups for each series, rects for each segment
var groups = svg.selectAll("g.cost")
.data(dataset)
.enter().append("g")
.attr("class", "cost")
.style("fill", function(d, i) { return colors[i]; });
var svg = d3.select("svg");
var bar = groups.selectAll("g")
.data(function(d) { return d; })
.enter().append("g")
.attr("transform", function(d, i) { return "translate(0," + i * y(d.y0) - y(d.y0 + d.y) + ")"; });
bar.append("rect")
.attr("x", function(d) { return x(d.x); })
.attr("y", function(d) { return y(d.y0 + d.y); })
.attr("height", function(d) { return y(d.y0) - y(d.y0 + d.y); })
.attr("width", x.rangeBand())
bar.append("text")
.attr("x", function(d) { return x(d.x); })
.attr("y", function(d) { return y(d.y0 + d.y); })
.attr("dy", ".35em")
.text(function(d) { return d.y; });
svg.call(tip);
// Draw legend
var legend = svg.selectAll(".legend")
.data(colors)
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, m) { return "translate(90," + (m+5) * 20 + ")"; });
legend.append("rect")
.attr("x", width - 18)
.attr("width", 18)
.attr("height", 18)
.style("fill", function(d, i) {return colors.slice().reverse()[i];});
legend.append("text")
.attr("x", width + 5)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "start")
.text(function(d, i) {
switch (i) {
case 0: return "Mobile Coupon";
case 1: return "Bonus";
case 2: return "Promotions";
case 3: return "Merchandise";
}
});