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";
}
});
Related
I'm pretty new to D3.js (I'm a R programmer).
I'm trying to create a scatter and put a legend in the right bottom. However, it's overlapping with chart area.
I've been trying to take some examples in internet with no lucky.
When I try to move it to the right, the names and/or symbols are not visible.
I want to place the legend outside the chart frame, so it won't overlap.
Any help is appreciated.
Here's what I tried so far:
d3.csv('https://gist.githubusercontent.com/netj/8836201/raw/6f9306ad21398ea43cba4f7d537619d0e07d5ae3/iris.csv', function(data) {
// CSV section
var body = d3.select('body')
var selectData = [{
"text": "sepal.length"
},
{
"text": "sepal.width"
},
{
"text": "petal.length"
},
{
"text": "petal.width"
},
]
// setup fill color
var colors = ['#575757', '#5FB1B9', '#C94257'];
var symbol = d3.svg.symbol()
.type('circle')
.size("160")
var cValue = function(d) {
return d.variety;
},
color = d3.scale.ordinal()
.range(colors);
// Select Y-axis Variable
var span = body.append('span')
.text('Select Y-Axis variable: ')
var yInput = body.append('select')
.attr('id', 'ySelect')
.on('change', yChange)
.selectAll('option')
.data(selectData)
.enter()
.append('option')
.attr('value', function(d) {
return d.text
})
.text(function(d) {
return d.text;
})
body.append('br')
// Select X-axis Variable
var span = body.append('span')
.text('Select X-Axis variable: ')
var yInput = body.append('select')
.attr('id', 'xSelect')
.on('change', xChange)
.selectAll('option')
.data(selectData)
.enter()
.append('option')
.attr('value', function(d) {
return d.text
})
.text(function(d) {
return d.text;
})
body.append('br')
// Variables
var body = d3.select('body')
var margin = {
top: 50,
right: 50,
bottom: 50,
left: 50
}
var h = 500 - margin.top - margin.bottom
var w = 500 - margin.left - margin.right
// var formatPercent = d3.format('.2%')
// Scales
// var colorScale = d3.scale.category20()
var xScale = d3.scale.linear()
.domain([
d3.min([0, d3.min(data, function(d) {
return d['sepal.length']
})]),
d3.max([0, d3.max(data, function(d) {
return d['sepal.length']
})])
])
.range([0, w])
var yScale = d3.scale.linear()
.domain([
d3.min([0, d3.min(data, function(d) {
return d['sepal.length']
})]),
d3.max([0, d3.max(data, function(d) {
return d['sepal.length']
})])
])
.range([h, 0])
// SVG
var svg = body.append('svg')
.attr('height', h + margin.top + margin.bottom)
.attr('width', w + margin.left + margin.right)
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
// X-axis
var xAxis = d3.svg.axis()
.scale(xScale)
// .tickFormat(formatPercent)
.ticks(6)
.outerTickSize(0)
.tickSize(0)
.orient('bottom')
// Y-axis
var yAxis = d3.svg.axis()
.scale(yScale)
// .tickFormat(formatPercent)
.ticks(6)
.tickSize(-w)
.outerTickSize(0)
.orient('left')
// Circles
var circles = svg.selectAll('circle')
.data(data)
.enter()
.append('circle')
.attr('cx', function(d) {
return xScale(d['sepal.length'])
})
.attr('cy', function(d) {
return yScale(d['sepal.length'])
})
.attr('r', '10')
// .attr('stroke', 'black')
.attr('stroke-width', 0.2)
.attr("fill", function(d) {
return color(cValue(d));
})
.attr('fill-opacity', 0.8)
// .attr('fill', function(d, i) {
// return colorScale(i)
// })
.on('mouseover', function() {
d3.select(this)
.transition()
.duration(300)
.ease('elastic')
.attr('r', 15)
.attr('stroke-width', 1)
.attr('fill-opacity', 1)
})
.on('mouseout', function() {
d3.select(this)
.transition()
.duration(100)
.attr('r', 10)
.attr('stroke-width', 0.5)
})
.append('title') // Tooltip
.text(function(d) {
return d.variety +
'\nSepal Length: ' + d['sepal.length'] +
'\nSepal Width: ' + d['sepal.width'] +
'\nPetal Length: ' + d['petal.length'] +
'\nPetal Width: ' + d['petal.width']
})
// X-axis
svg.append('g')
.attr('class', 'axis')
.attr('id', 'xAxis')
.attr('transform', 'translate(0,' + h + ')')
.call(xAxis)
.append('text') // X-axis Label
.attr('id', 'xAxisLabel')
.attr('y', -25)
.attr('x', w)
.attr('dy', '.71em')
.style('text-anchor', 'end')
.text('Sepal Length')
// labels distance from xaxis
svg.selectAll(".axis text")
.attr("dy", 15);
// Y-axis
svg.append('g')
.attr('class', 'axis')
.attr('id', 'yAxis')
.call(yAxis)
.append('text') // y-axis Label
.attr('id', 'yAxisLabel')
.attr('transform', 'rotate(-90)')
.attr('x', 0)
.attr('y', 5)
.attr('dy', '.71em')
.style('text-anchor', 'end')
.text('Sepal Length')
function yChange() {
var value = this.value // get the new y value
yScale // change the yScale
.domain([
d3.min([0, d3.min(data, function(d) {
return d[value]
})]),
d3.max([0, d3.max(data, function(d) {
return d[value]
})])
])
yAxis.scale(yScale) // change the yScale
d3.select('#yAxis') // redraw the yAxis
.transition().duration(500)
.call(yAxis)
d3.select('#yAxisLabel') // change the yAxisLabel
.text(value)
d3.selectAll('circle') // move the circles
.transition().duration(500)
.delay(function(d, i) {
return i * 10
})
.attr('cy', function(d) {
return yScale(d[value])
})
}
function xChange() {
var value = this.value // get the new x value
xScale // change the xScale
.domain([
d3.min([0, d3.min(data, function(d) {
return d[value]
})]),
d3.max([0, d3.max(data, function(d) {
return d[value]
})])
])
xAxis.scale(xScale) // change the xScale
d3.select('#xAxis') // redraw the xAxis
.transition().duration(500)
.call(xAxis)
d3.select('#xAxisLabel') // change the xAxisLabel
.transition().duration(500)
.text(value)
d3.selectAll('circle') // move the circles
.transition().duration(500)
.delay(function(d, i) {
return i * 10
})
.attr('cx', function(d) {
return xScale(d[value])
})
}
// create legend
var legend = svg.selectAll(".legend")
.data(color.domain())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) {
return "translate(0," + i * 25 + ")";
});
// draw legend colored rectangles
legend.append("path")
.attr('d', symbol)
.attr("transform", "translate(434, 313)") //much easier approach to position the symbols
// .attr("x", w + 34)
// .attr("y", h - 97)
// .attr("width", 18)
// .attr("height", 18)
.style("fill", color);
// draw legend text
legend.append("text")
.attr("transform", "translate(422, 311)")
// .attr("x", w + 24)
// .attr("y", h - 89)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) {
return d;
})
})
body {
font-size: 16px;
}
/*
circle {
fill: steelblue;
} */
circle:hover {
fill: orange;
}
.axis text {
font-size: 13px;
/* font-weight: bold; */
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
stroke-width: 0.02px;
}
/* .circle {
fill: orange;
} */
label {
position: absolute;
top: 10px;
right: 10px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.10/d3.min.js"></script>
<body></body>
Why not just render your legend as HTML and use CSS to help with layout?
d3.csv('https://gist.githubusercontent.com/netj/8836201/raw/6f9306ad21398ea43cba4f7d537619d0e07d5ae3/iris.csv', function(data) {
// CSV section
var body = d3.select('body')
var selectData = [{
"text": "sepal.length"
},
{
"text": "sepal.width"
},
{
"text": "petal.length"
},
{
"text": "petal.width"
},
]
// setup fill color
var colors = ['#575757', '#5FB1B9', '#C94257'];
var symbol = d3.svg.symbol()
.type('circle')
.size("160")
var cValue = function(d) {
return d.variety;
},
color = d3.scale.ordinal()
.range(colors);
var controls = body.append('div').attr('class', 'controls')
// Select Y-axis Variable
var yControls = controls.append('div')
var span = yControls.append('span')
.text('Select Y-Axis variable: ')
var yInput = yControls.append('select')
.attr('id', 'ySelect')
.on('change', yChange)
.selectAll('option')
.data(selectData)
.enter()
.append('option')
.attr('value', function(d) {
return d.text
})
.text(function(d) {
return d.text;
})
// Select X-axis Variable
var xControls = controls.append('div')
var span = xControls.append('span')
.text('Select X-Axis variable: ')
var yInput = xControls.append('select')
.attr('id', 'xSelect')
.on('change', xChange)
.selectAll('option')
.data(selectData)
.enter()
.append('option')
.attr('value', function(d) {
return d.text
})
.text(function(d) {
return d.text;
})
// Variables
var body = d3.select('body')
var margin = {
top: 50,
right: 50,
bottom: 50,
left: 50
}
var h = 500 - margin.top - margin.bottom
var w = 500 - margin.left - margin.right
var xScale = d3.scale.linear()
.domain([
d3.min([0, d3.min(data, function(d) {
return d['sepal.length']
})]),
d3.max([0, d3.max(data, function(d) {
return d['sepal.length']
})])
])
.range([0, w])
var yScale = d3.scale.linear()
.domain([
d3.min([0, d3.min(data, function(d) {
return d['sepal.length']
})]),
d3.max([0, d3.max(data, function(d) {
return d['sepal.length']
})])
])
.range([h, 0])
// SVG
var svgContainer = body.append('div').attr('class', 'svg-container')
var svg = svgContainer.append('svg')
.attr('height', h + margin.top + margin.bottom)
.attr('width', w + margin.left + margin.right)
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
// X-axis
var xAxis = d3.svg.axis()
.scale(xScale)
// .tickFormat(formatPercent)
.ticks(6)
.outerTickSize(0)
.tickSize(0)
.orient('bottom')
// Y-axis
var yAxis = d3.svg.axis()
.scale(yScale)
// .tickFormat(formatPercent)
.ticks(6)
.tickSize(-w)
.outerTickSize(0)
.orient('left')
// Circles
var circles = svg.selectAll('circle')
.data(data)
.enter()
.append('circle')
.attr('cx', function(d) {
return xScale(d['sepal.length'])
})
.attr('cy', function(d) {
return yScale(d['sepal.length'])
})
.attr('r', '10')
// .attr('stroke', 'black')
.attr('stroke-width', 0.2)
.attr("fill", function(d) {
return color(cValue(d));
})
.attr('fill-opacity', 0.8)
// .attr('fill', function(d, i) {
// return colorScale(i)
// })
.on('mouseover', function() {
d3.select(this)
.transition()
.duration(300)
.ease('elastic')
.attr('r', 15)
.attr('stroke-width', 1)
.attr('fill-opacity', 1)
})
.on('mouseout', function() {
d3.select(this)
.transition()
.duration(100)
.attr('r', 10)
.attr('stroke-width', 0.5)
})
.append('title') // Tooltip
.text(function(d) {
return d.variety +
'\nSepal Length: ' + d['sepal.length'] +
'\nSepal Width: ' + d['sepal.width'] +
'\nPetal Length: ' + d['petal.length'] +
'\nPetal Width: ' + d['petal.width']
})
// X-axis
svg.append('g')
.attr('class', 'axis')
.attr('id', 'xAxis')
.attr('transform', 'translate(0,' + h + ')')
.call(xAxis)
.append('text') // X-axis Label
.attr('id', 'xAxisLabel')
.attr('y', -25)
.attr('x', w)
.attr('dy', '.71em')
.style('text-anchor', 'end')
.text('Sepal Length')
// labels distance from xaxis
svg.selectAll(".axis text")
.attr("dy", 15);
// Y-axis
svg.append('g')
.attr('class', 'axis')
.attr('id', 'yAxis')
.call(yAxis)
.append('text') // y-axis Label
.attr('id', 'yAxisLabel')
.attr('transform', 'rotate(-90)')
.attr('x', 0)
.attr('y', 5)
.attr('dy', '.71em')
.style('text-anchor', 'end')
.text('Sepal Length')
function yChange() {
var value = this.value // get the new y value
yScale // change the yScale
.domain([
d3.min([0, d3.min(data, function(d) {
return d[value]
})]),
d3.max([0, d3.max(data, function(d) {
return d[value]
})])
])
yAxis.scale(yScale) // change the yScale
d3.select('#yAxis') // redraw the yAxis
.transition().duration(500)
.call(yAxis)
d3.select('#yAxisLabel') // change the yAxisLabel
.text(value)
d3.selectAll('circle') // move the circles
.transition().duration(500)
.delay(function(d, i) {
return i * 10
})
.attr('cy', function(d) {
return yScale(d[value])
})
}
function xChange() {
var value = this.value // get the new x value
xScale // change the xScale
.domain([
d3.min([0, d3.min(data, function(d) {
return d[value]
})]),
d3.max([0, d3.max(data, function(d) {
return d[value]
})])
])
xAxis.scale(xScale) // change the xScale
d3.select('#xAxis') // redraw the xAxis
.transition().duration(500)
.call(xAxis)
d3.select('#xAxisLabel') // change the xAxisLabel
.transition().duration(500)
.text(value)
d3.selectAll('circle') // move the circles
.transition().duration(500)
.delay(function(d, i) {
return i * 10
})
.attr('cx', function(d) {
return xScale(d[value])
})
}
// create legend
var legendContainer = body.append('div')
.attr('class', 'legend-container')
var legend = legendContainer.selectAll(".legend")
.data(color.domain())
.enter().append("div")
.attr("class", "legend")
// draw legend colored rectangles
legend.append("span")
.attr("class", "legend-color")
.style("background-color", color);
// draw legend text
legend.append("span")
.text(function(d) {
return d;
})
})
body {
font-size: 16px;
display: flex;
flex-wrap: wrap;
}
.controls {
flex: 1 1 100%;
}
.legend-container {
align-items: center;
flex: 0 1 auto;
align-self: center;
margin: 0 auto;
}
circle:hover {
fill: orange;
}
.axis text {
font-size: 13px;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
stroke-width: 0.02px;
}
label {
position: absolute;
top: 10px;
right: 10px;
}
.legend {
margin-bottom: 0.5em;
}
.legend-color {
width: 20px;
height: 20px;
display: inline-block;
border-radius: 50%;
vertical-align: middle;
margin-right: 1em;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.10/d3.min.js"></script>
<body></body>
With this approach you end up with a structure like this:
body
.controls
.svg-container
svg
.legend-container
which can make responsive layouts especially easy with CSS.
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
Im trying to append text to a Grouped Bar Chart in d3js v4, more specifically, the values corresponding to each bar. I want the numbers to be displayed inside the bars and I can't get it to work. (Like this: http://bl.ocks.org/ctiml/541d7cc770108ccff79a)
But I want it to work in d3js v4 instead.
Here's my code, I've commented out the part of the code that is supposed to append the text
<!DOCTYPE html>
<html>
<meta charset="utf-8">
<head>
<style>
body {
margin:auto;
width:1100px;
}
.axis .domain {
display: none;
}
.bar1 {
opacity:.9;
}
.yaxis {
stroke-dasharray: 1 1;
opacity:.8;
font-family:arial;
font-size:10px;
}
path {
display:none;
}
.baseline {
stroke:#000;
stroke-width:1px;
shape-rendering: crispEdges;
}
</style>
<script src="https://d3js.org/d3.v4.min.js"></script>
</head>
<body><br>
<div id="chart1"></div>
<script>
var margin = {top: 20, right: 110, bottom: 30, left: 40},
width = 350 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom,
formatCount = d3.format("s");
var x0 = d3.scaleBand()
.rangeRound([0, width])
.paddingInner(0.1);
var x1 = d3.scaleBand();
//.padding(0.05);
var y = d3.scaleLinear()
.rangeRound([height, 0]);
var z = d3.scaleOrdinal()
.range(["steelblue", "lightblue", "darkorange"]);
var g = d3.select("#chart1").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
d3.csv("data.csv", function(d, i, columns) {
for (var i = 1, n = columns.length; i < n; ++i) d[columns[i]] = +d[columns[i]];
return d;
}, function(error, data) {
if (error) throw error;
var keys = data.columns.slice(1);
x0.domain(data.map(function(d) { return d.State; }));
x1.domain(keys).rangeRound([0, x0.bandwidth()]);
y.domain([0, d3.max(data, function(d) { return d3.max(keys, function(key) { return d[key]; }); })]).nice();
g.append("g")
.attr("class", "yaxis")
.call(d3.axisLeft(y).ticks(8,"s").tickSize(-width));
g.append("line")
.attr("class", "baseline")
.attr("x1",0)
.attr("x2",width)
.attr("y1",y(0))
.attr("y2",y(0));
g.append("g")
.selectAll("g")
.data(data)
.enter().append("g")
.attr("transform", function(d) { return "translate(" + x0(d.State) + ",0)"; })
.selectAll("rect")
.data(function(d) { return keys.map(function(key) { return {key: key, value: d[key]}; }); })
.enter().append("rect")
.attr("class", "bar1")
.attr("x", function(d) { return x1(d.key); })
.attr("y", function(d) { return y(d.value); })
.attr("width", x1.bandwidth())
.attr("height", function(d) { return height - y(d.value); })
.attr("fill", function(d) { return z(d.key); });
g.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x0));
//g.selectAll(".bar-text")
// .data(data)
//.enter().append("text")
// .attr("class",function(d) { return "bar-text " + d.value; })
// .attr("x", function(d) { return x1(d.key)+20; })
// .attr("y", function(d) { return y(d.value)+10; })
//.attr("fill","#000")
// .text(function(d) { return formatCount(d.value)});
var legend = g.append("g")
.attr("font-family", "sans-serif")
.attr("font-size", 10)
.attr("text-anchor", "start")
.selectAll("g")
.data(keys.slice()) //.reverse
.enter().append("g")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
legend.append("rect")
.attr("x", width + 15)
.attr("y", 4)
.attr("width", 12)
.attr("height", 12)
.attr("fill", z);
legend.append("text")
.attr("x", width + 35)
.attr("y", 9.5)
.attr("dy", "0.32em")
.text(function(d) { return d; });
});
</script>
</body>
</html>
And here's the csv format:
State,Team 1,Team 2,Team 3
2015,2704659,4499890,2159981
2016,2027307,3277946,1420518
Lots of ways to do this; here's how I would do it.
First, keep a reference to the groups g element so that we can append our text with the bars:
var gE = g.append("g")
.selectAll("g")
.data(data)
.enter().append("g")
.attr("transform", function(d) {
return "translate(" + x0(d.State) + ",0)";
});
gE.selectAll("rect")
.data(function(d) {
...
Then use a sub-selection to add the text:
gE.selectAll("text")
.data(function(d) {
return [d['Team 1'], d['Team 2'], d['Team 3']];
})
.enter()
.append("text")
...
Running code is here.
I have a stacked bar chart to Grouped bar chart function with transition that works fine with one chart, but as soon as I add a second it breaks. The first chart will not transition and the second chart works fine. I think this has something to do with the transition being in a function so it only runs for the last chart made.
Any help on this would be great!?
I put together a jsFiddle for this here
My function is as follows:
function createChartDate(inputdata, chartname, inputtop, inputbottom, inputwidth, inputheight, inputleft, inputright, bargap, yaxisShift) {
var stack = d3.layout.stack(),
layers = inputdata,
m = layers[0].length, // number of samples per layer
n = layers.length, // number of layers
data = stack(d3.range(n).map(function(d) {
return layers[d];
}));
var yGroupMax = d3.max(data, function(layer) {
return d3.max(layer, function(d) {
return d.y;
});
}),
yStackMax = d3.max(data, function(layer) {
return d3.max(layer, function(d) {
return d.y0 + d.y;
});
});
var margin = {
top: inputtop,
right: inputright,
bottom: inputbottom,
left: inputleft
},
width = inputwidth - margin.left - margin.right,
height = inputheight - margin.top - margin.bottom;
var x = d3.scale.ordinal()
.domain(d3.range(m))
.rangeRoundBands([0, width], (Number(bargap)/100));
var xTime = d3.time.scale()
.domain([new Date(inputdata[0][0].x), d3.time.day.offset(new Date(inputdata[0][inputdata[0].length - 1].x), 1)])
.rangeRound([0, width - margin.left - margin.right]);
var xAxisTime = d3.svg.axis()
.scale(xTime)
.orient('bottom')
.ticks(d3.time.day, 1)
.tickFormat(d3.time.format('%x'))
.tickSize(0)
.tickPadding(8);
var y = d3.scale.linear()
.domain([0, yStackMax])
.range([height, 0]);
var color = d3.scale.linear()
.domain([0, n - 1])
.range(["#aad", "#556"]);
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickSize(2)
.tickPadding(6)
.outerTickSize(0);
var svg = d3.select(chartname).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 layer = svg.selectAll(".layer")
.data(data)
.enter().append("g")
.attr("class", "layer")
.style("fill", function(d, i) {
return color(i);
});
var rect = layer.selectAll("rect")
.data(function(d) {
return d;
})
.enter().append("rect")
.attr("x", function(d) {
return xTime(d.x);
})
.attr("y", height)
.attr("width", x.rangeBand())
.attr("height", 0)
var allrect = layer.selectAll('rect')
.style("cursor","pointer")
.append("svg:title")
.text(function(d){return d.y;})
rect.transition()
.delay(function(d, i) {
return i * 10;
})
.attr("y", function(d) {
return y(d.y0 + d.y);
})
.attr("height", function(d) {
return y(d.y0) - y(d.y0 + d.y);
});
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxisTime)
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("transform", function(d) {
return "rotate(-65)"
});
svg.append("g")
.attr("class", "yaxis")
.attr("transform", "translate(" + (Number(margin.left) - yaxisShift) + ",0)")
.call(yAxis);
svg.select("g.yaxis").selectAll(".tick")
.each(function(d) {
if (d === 0) {
this.remove();
}
});
var allchart = d3.select(chartname).selectAll(".layer").selectAll("rect")
allchart.on('mouseover', function(d){
d3.select(this).style("opacity","0.8")
})
.on('mouseout', function(d){
d3.select(this).style("opacity","1")
});
d3.selectAll("input").on("change", change);
/* var timeout = setTimeout(function() {
d3.select("input[value=\"grouped\"]").property("checked", true).each(change);
d3.select("input[value=\"0\"]").property("checked", true).each(change);
}, 2000); */
function change() {
//clearTimeout(timeout);
if (this.value === "grouped") transitionGrouped();
if (this.value === "stacked") transitionStacked();
//else transitionStacked();
}
function transitionGrouped() {
y.domain([0, yGroupMax]);
var allchart = d3.select(chartname).selectAll(".layer").selectAll("rect"),
axistran = d3.selectAll("svg");
allchart.transition()
.ease("linear")
.duration(300)
.delay(function(d, i) {
return i * 10;
})
.attr("x", function(d, i, j) {
return xTime(d.x) + x.rangeBand() / n * j;
})
.attr("width", x.rangeBand() / n)
.transition()
.duration(200)
.ease("linear")
.attr("y", function(d) {
return y(d.y);
})
.attr("height", function(d) {
return height - y(d.y);
});
axistran.select("g.yaxis").transition()
.duration(600)
.call(yAxis);
axistran.select("g.yaxis").selectAll(".tick")
.each(function(d) {
if (d === 0) {
this.remove();
}
});
};
function transitionStacked() {
y.domain([0, yStackMax]);
var allchart = d3.select(chartname).selectAll(".layer").selectAll("rect"),
axistran = d3.selectAll("svg");
allchart.transition()
.ease("linear")
.duration(300)
.delay(function(d, i) {
return i * 10;
})
.attr("y", function(d) {
return y(d.y0 + d.y);
})
.attr("height", function(d) {
return y(d.y0) - y(d.y0 + d.y);
})
.transition()
.duration(200)
.ease("linear")
.attr("x", function(d) {
return xTime(d.x);
})
.attr("width", x.rangeBand());
axistran.select("g.yaxis").transition()
.duration(600)
.call(yAxis);
axistran.select("g.yaxis").selectAll(".tick")
.each(function(d) {
if (d === 0) {
this.remove();
}
});
};
};
As I said in my comment:
Since your createChartDate function wraps multiple variables (and
other functions), your charts aren't independent of each other. Only
the last one transitions because var allchart =
d3.select(chartname)..., chartname holds the value "#chart2"
Lots of ways to fix this but a quick refactor would be to have your createChartDate return the transition functions (in an object), so you can call them later (and you create a closure).
<!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-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
margin: auto;
position: relative;
width: 960px;
}
text {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
form {
position: absolute;
right: 10px;
top: 10px;
}
</style>
</head>
<body>
<form>
<label>
<input type="radio" name="mode" value="grouped" /> Grouped
</label>
<label>
<input type="radio" name="mode" value="stacked" checked="" /> Stacked
</label>
</form>
<chart id="chart1"></chart>
<chart id="chart2"></chart>
<script>
var layers = [{
"x": "2016-01-01",
"y": 4,
"z": 5
}, {
"x": "2016-01-02",
"y": 5,
"z": 6
}, {
"x": "2016-01-03",
"y": 6,
"z": 3
}, {
"x": "2016-01-04",
"y": 7,
"z": 1
}];
var converted = convertjson(layers, "x", ["y", "z"]);
var trans1 = createChartDate(converted, "#chart1", 40, 60, 960, 550, 30, 30, 30, 30),
trans2 = createChartDate(converted, "#chart2", 40, 60, 960, 550, 30, 30, 30, 30);
function createChartDate(inputdata, chartname, inputtop, inputbottom, inputwidth, inputheight, inputleft, inputright, bargap, yaxisShift) {
var stack = d3.layout.stack(),
layers = inputdata,
m = layers[0].length, // number of samples per layer
n = layers.length, // number of layers
data = stack(d3.range(n).map(function(d) {
return layers[d];
}));
var yGroupMax = d3.max(data, function(layer) {
return d3.max(layer, function(d) {
return d.y;
});
}),
yStackMax = d3.max(data, function(layer) {
return d3.max(layer, function(d) {
return d.y0 + d.y;
});
});
var margin = {
top: inputtop,
right: inputright,
bottom: inputbottom,
left: inputleft
},
width = inputwidth - margin.left - margin.right,
height = inputheight - margin.top - margin.bottom;
var x = d3.scale.ordinal()
.domain(d3.range(m))
.rangeRoundBands([0, width], (Number(bargap) / 100));
var xTime = d3.time.scale()
.domain([new Date(inputdata[0][0].x), d3.time.day.offset(new Date(inputdata[0][inputdata[0].length - 1].x), 1)])
.rangeRound([0, width - margin.left - margin.right]);
var xAxisTime = d3.svg.axis()
.scale(xTime)
.orient('bottom')
.ticks(d3.time.day, 1)
.tickFormat(d3.time.format('%x'))
.tickSize(0)
.tickPadding(8);
var y = d3.scale.linear()
.domain([0, yStackMax])
.range([height, 0]);
var color = d3.scale.linear()
.domain([0, n - 1])
.range(["#aad", "#556"]);
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickSize(2)
.tickPadding(6)
.outerTickSize(0);
var svg = d3.select(chartname).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 layer = svg.selectAll(".layer")
.data(data)
.enter().append("g")
.attr("class", "layer")
.style("fill", function(d, i) {
return color(i);
});
var rect = layer.selectAll("rect")
.data(function(d) {
return d;
})
.enter().append("rect")
.attr("x", function(d) {
return xTime(d.x);
})
.attr("y", height)
.attr("width", x.rangeBand())
.attr("height", 0)
var allrect = layer.selectAll('rect')
.style("cursor", "pointer")
.append("svg:title")
.text(function(d) {
return d.y;
})
rect.transition()
.delay(function(d, i) {
return i * 10;
})
.attr("y", function(d) {
return y(d.y0 + d.y);
})
.attr("height", function(d) {
return y(d.y0) - y(d.y0 + d.y);
});
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxisTime)
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("transform", function(d) {
return "rotate(-65)"
});
svg.append("g")
.attr("class", "yaxis")
.attr("transform", "translate(" + (Number(margin.left) - yaxisShift) + ",0)")
.call(yAxis);
svg.select("g.yaxis").selectAll(".tick")
.each(function(d) {
if (d === 0) {
this.remove();
}
});
var allchart = d3.select(chartname).selectAll(".layer").selectAll("rect")
allchart.on('mouseover', function(d) {
d3.select(this).style("opacity", "0.8")
})
.on('mouseout', function(d) {
d3.select(this).style("opacity", "1")
});
/* var timeout = setTimeout(function() {
d3.select("input[value=\"grouped\"]").property("checked", true).each(change);
d3.select("input[value=\"0\"]").property("checked", true).each(change);
}, 2000); */
return {
group: function transitionGrouped() {
y.domain([0, yGroupMax]);
var allchart = d3.select(chartname).selectAll(".layer").selectAll("rect"),
axistran = d3.selectAll("svg");
allchart.transition()
.ease("linear")
.duration(300)
.delay(function(d, i) {
return i * 10;
})
.attr("x", function(d, i, j) {
return xTime(d.x) + x.rangeBand() / n * j;
})
.attr("width", x.rangeBand() / n)
.transition()
.duration(200)
.ease("linear")
.attr("y", function(d) {
return y(d.y);
})
.attr("height", function(d) {
return height - y(d.y);
});
axistran.select("g.yaxis").transition()
.duration(600)
.call(yAxis);
axistran.select("g.yaxis").selectAll(".tick")
.each(function(d) {
if (d === 0) {
this.remove();
}
});
},
stack: function transitionStacked() {
y.domain([0, yStackMax]);
var allchart = d3.select(chartname).selectAll(".layer").selectAll("rect"),
axistran = d3.selectAll("svg");
allchart.transition()
.ease("linear")
.duration(300)
.delay(function(d, i) {
return i * 10;
})
.attr("y", function(d) {
return y(d.y0 + d.y);
})
.attr("height", function(d) {
return y(d.y0) - y(d.y0 + d.y);
})
.transition()
.duration(200)
.ease("linear")
.attr("x", function(d) {
return xTime(d.x);
})
.attr("width", x.rangeBand());
axistran.select("g.yaxis").transition()
.duration(600)
.call(yAxis);
axistran.select("g.yaxis").selectAll(".tick")
.each(function(d) {
if (d === 0) {
this.remove();
}
});
}
}
};
d3.selectAll("input").on("change", change);
function change() {
//clearTimeout(timeout);
if (this.value === "grouped") {
trans1.group();
trans2.group();
}
if (this.value === "stacked") {
trans1.stack();
trans2.stack();
}
}
function convertjson(data, xValue, yArray) {
var arrayconvertedjson = [];
var convertedjson = [];
for (var j = 0; j < yArray.length; j++) {
for (var i = 0; i < data.length; i++) {
convertedjson.push({
"x": new Date(data[i][xValue]),
"y": data[i][yArray[j]]
});
};
arrayconvertedjson.push(convertedjson)
convertedjson = [];
};
return arrayconvertedjson;
};
</script>
</body>
</html>