Related
I have implemented one scatter chart using d3.js. I want to convert this chart to line chart, but i am not able to do so. I have tried to follow ( http://embed.plnkr.co/wJDcZmkEzXaLVhuLZmcQ/ ) but it didn't helped me.
This is the code for scatter chart.
var data = [{"buildName":"otfa_R5-10_a1","build":"Build 1","value":"19628"},{"buildName":"otfa_R5-91_a1","build":"Build 2","value":"19628"},{"buildName":"otfa_R5-9_a1","build":"Build 3","value":"19628"}]
var yValues = [], responseData = [];
data.map(function(key) {
var test = [];
test[0] = key.build;
test[1] = key.value;
responseData.push(test);
yValues = key.value;
})
var margin = {
top: 20,
right: 15,
bottom: 60,
left: 60
},
width = 300 - margin.left - margin.right,
height = 200 - margin.top - margin.bottom;
var x = d3.scale.ordinal()
.domain(responseData.map(function(d) {
return d[0];
}))
.rangePoints([0, width], 0.5)
var y = d3.scale.linear()
.domain([5000,20000])
.range([height, 0]);
var chart = d3.select(divId)
.append('svg:svg')
.attr('width', width + margin.right + margin.left)
.attr('height', height + margin.top + margin.bottom)
.attr('class', 'chart')
var colors = d3.scale.linear()
.domain([5, 20])
.range(['#4577bc', '#4577bc'])
var main = chart.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
.attr('width', width)
.attr('height', height)
.attr('class', 'main')
// draw the x axis
var xAxis = d3.svg.axis()
.scale(x)
.orient('bottom');
main.append('g')
.attr('transform', 'translate(0,' + height + ')')
.attr('class', 'main axis date')
.call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("transform", "rotate(-45)" );
// draw the y axis
var yAxis = d3.svg.axis()
.scale(y)
.orient('left');
main.append('g')
.attr('transform', 'translate(0,0)')
.attr('class', 'main axis date')
.call(yAxis);
var div = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
var g = main.append("svg:g");
g.selectAll("scatter-dots")
.data(responseData)
.enter().append("svg:circle")
.attr("cx", function(d, i) {
return x(d[0]);
})
.attr("cy", function(d) {
return y(d[1]);
})
.attr("r", 6)
.style('stroke', function(d, i) {
return colors(i);
})
.style('fill', function(d, i) {
return colors(i);
})
.on("mouseover", function(d) {
d3.select(this).attr("r", 10).style("fill", "#fff8ee");
div.transition()
.duration(200)
.style("opacity", 2.9);
div .html((d[1]))
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 18) + "px");
})
.on("mouseout", function(d) {
d3.select(this).attr("r", 5.5).style("fill", "#4577bc");
div.transition()
.duration(500)
.style("opacity", 0);
});
How we can add a line connecting these points ?
Please help me !!
To add a line to your existing chart, just add it using path generators.
Line generator:
var line = d3.svg.line()
.x(function (d) { return x(d[0]); })
.y(function (d) { return y(d[1]); });
Append the line to the svg:
g.append('path').classed('line', true)
.style( { fill: 'none', 'stroke': 'steelblue'} )
.attr('d', line(responseData));
Snippet with the above code included and a few CSS styles to make it look better:
var data = [{"buildName":"otfa_R5-10_a1","build":"Build 1","value":"19628"},{"buildName":"otfa_R5-91_a1","build":"Build 2","value":"10628"},{"buildName":"otfa_R5-9_a1","build":"Build 3","value":"17628"}]
var yValues = [], responseData = [];
data.map(function(key) {
var test = [];
test[0] = key.build;
test[1] = key.value;
responseData.push(test);
yValues = key.value;
})
var margin = {
top: 20,
right: 15,
bottom: 60,
left: 60
},
width = 300 - margin.left - margin.right,
height = 200 - margin.top - margin.bottom;
var x = d3.scale.ordinal()
.domain(responseData.map(function(d) {
return d[0];
}))
.rangePoints([0, width], 0.5)
var y = d3.scale.linear()
.domain([5000,20000])
.range([height, 0]);
var chart = d3.select('body')
.append('svg:svg')
.attr('width', width + margin.right + margin.left)
.attr('height', height + margin.top + margin.bottom)
.attr('class', 'chart')
var colors = d3.scale.linear()
.domain([5, 20])
.range(['#4577bc', '#4577bc'])
var main = chart.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
.attr('width', width)
.attr('height', height)
.attr('class', 'main')
// draw the x axis
var xAxis = d3.svg.axis()
.scale(x)
.orient('bottom');
main.append('g')
.attr('transform', 'translate(0,' + height + ')')
.attr('class', 'main axis date')
.call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("transform", "rotate(-45)" );
// draw the y axis
var yAxis = d3.svg.axis()
.scale(y)
.orient('left');
main.append('g')
.attr('transform', 'translate(0,0)')
.attr('class', 'main axis date')
.call(yAxis);
var div = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
var g = main.append("svg:g");
g.selectAll("scatter-dots")
.data(responseData)
.enter().append("svg:circle")
.attr("cx", function(d, i) {
return x(d[0]);
})
.attr("cy", function(d) {
return y(d[1]);
})
.attr("r", 6)
.style('stroke', function(d, i) {
return colors(i);
})
.style('fill', function(d, i) {
return colors(i);
})
.on("mouseover", function(d) {
d3.select(this).attr("r", 10).style("fill", "#fff8ee");
div.transition()
.duration(200)
.style("opacity", 2.9);
div .html((d[1]))
.style("left", (d3.event.pageX+4) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout", function(d) {
d3.select(this).attr("r", 5.5).style("fill", "#4577bc");
div.transition()
.duration(500)
.style("opacity", 0);
});
var line = d3.svg.line()
.x(function (d) { return x(d[0]); })
.y(function (d) { return y(d[1]); });
g.append('path').classed('line', true)
.style( { fill: 'none', 'stroke': 'steelblue'} )
.attr('d', line(responseData));
path.domain {
fill: none;
stroke: #000;
}
.axis text {
font-size: 12px;
}
div.tooltip {
position: absolute;
background: #FFF;
padding: 5px;
border: 1px solid #DDD;
pointer-events: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.0/d3.min.js"></script>
I have the following segment in one of my d3 graph.
What I want is to display some text in a rectangle.
var values = $('#<%=hdnDtArray.ClientID%>').val();
var data = JSON.parse(values);
margin = {
top: 20,
right: 60,
bottom: 20,
left: 100
};
var width = 900,
height = 350;
var vis = d3.select("#line_chart").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
var parseTime = d3.time.format("%Y.%m.%d").parse;
max_y = 0;
min_y = data[0].close;
var extent = d3.extent(data.map(function(d) {
return d.date
}));
max_x = extent[1];
min = extent[0];
for (i = 0; i < data.length; i++) {
max_y = Math.max(max_y, data[i].close);
min_y = Math.min(min_y, data[i].close);
}
var x = d3.time.scale()
.rangeRound([margin.left, width]);
xScale = x.domain(d3.extent(data, function(d) {
return parseTime(d.date);
}));
yScale = d3.scale.linear().range([height - margin.top, margin.bottom]).domain([0, max_y]),
xAxis = d3.svg.axis()
.scale(xScale),
yAxis = d3.svg.axis()
.scale(yScale)
.orient("left")
.innerTickSize(-width + margin.left)
.outerTickSize(0)
.tickPadding(10);
vis.append("svg:g")
.attr("class", "x axis")
.style({
'stroke': 'Black',
'fill': 'none',
"stroke-width": 1,
"font-size": "13px"
})
.attr("transform", "translate(0," + (height - margin.bottom) + ")")
.call(xAxis)
.selectAll("text")
.attr("transform", "translate(-10,0) rotate(-40)")
.style("text-anchor", "end");
vis.append("text")
.attr("class", "x label")
.attr("text-anchor", "end")
.attr("x", width + 120)
.attr("y", height - 10)
.attr("font-weight", "bold");
vis.append("svg:g")
.attr("class", "y axis")
.style({
'stroke': 'Black',
'fill': 'none',
'stroke-width': 1,
"font-size": "13px"
})
.attr("transform", "translate(" + (margin.left) + ",0)")
.call(yAxis);
vis.append("text")
.attr("class", "y label")
.attr("text-anchor", "end")
.attr("x", margin.left + 5)
.attr("y", margin.top - 2)
.attr("font-weight", "bold");
var line = d3.svg.line()
.x(function(d) {
return xScale(parseTime(d.date));
})
.y(function(d) {
return yScale(d.close);
})
.interpolate("basis");
vis.append('svg:path')
.datum(data)
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-linejoin", "round")
.attr("stroke-linecap", "round")
.attr("stroke-width", 1.5)
.attr("d", line);
var hoverLineGroup = vis.append("g")
.attr("class", "hover-line");
var hoverLine = hoverLineGroup.append("line")
.attr("stroke", "#000000")
.attr("stroke-width", "1px")
.attr("x1", 10).attr("x2", 10)
.attr("y1", 20).attr("y2", height - 20);
var hoverTT = hoverLineGroup.append('text')
.attr("class", "hover-tex capo")
.attr('dy', "0.35em");
var cle = hoverLineGroup.append("circle")
.attr("r", 4.5);
var hoverTT2 = hoverLineGroup.append('text')
.attr("class", "hover-text capo")
.attr('dy', "0.55em");
hoverLineGroup.style("opacity", 1e-6);
var rectHover = vis.append("rect")
.data(data)
.attr("fill", "none")
.attr("width", width)
.attr("height", height);
var hoverCircle = hoverLineGroup.append("circle");
var hoverRect = hoverLineGroup
.append("rect");
vis.on("mouseout", hoverMouseOff)
.on("mousemove", hoverMouseOn);
var bisectDate = d3.bisector(function(d) {
return parseTime(d.date);
}).left;
function hoverMouseOn() {
var mouse_x = d3.mouse(this)[0];
var mouse_y = d3.mouse(this)[1];
var graph_y = yScale.invert(mouse_y);
var graph_x = xScale.invert(mouse_x);
var mouseDate = xScale.invert(mouse_x);
var i = bisectDate(data, mouseDate);
var d0 = data[i - 1]
var d1 = data[i];
var d = mouseDate - d0[0] > d1[0] - mouseDate ? d1 : d0;
hoverRect.attr("class", "y")
.style("fill", "none")
.style("stroke", "black")
.attr('x', mouse_x + 8)
.attr('y', yScale(d.close) - 20)
.attr("width", 200)
.attr("height", 50);
hoverTT.text("Test Text")
.attr("opacity", "1");
hoverTT.attr('x', mouse_x + 23);
hoverTT.attr('y', yScale(d.close));
/*
hoverTT.text("Date: " + d.date);
hoverTT.attr('x', mouse_x + 23);
hoverTT.attr('y', yScale(d.close));
hoverTT2.text("Portfolio Value: " + Math.round(d.close * 100) / 100)
.attr('x', mouse_x + 23)
.attr('y', yScale(d.close) + 10);
*/
hoverLine.attr("x1", mouse_x).attr("x2", mouse_x)
hoverLineGroup.style({
'font-weight': 'bold',
'opacity': 1
});
hoverCircle.attr("class", "y")
.style("fill", "blue")
.style("stroke", "blue")
.attr("r", 4)
.attr('cx', mouse_x)
.attr('cy', yScale(d.close));
}
function hoverMouseOff() {
hoverLineGroup.style("opacity", 1e-6);
}
The text is not visible now. But if I set the "fill" property to "none", then the text becomes visible.
What I want is the background to be non transparent, that's why I made it white.
Still I want the text to be visible.
The problem with your code is the order of the selections.
In an SVG, just like a real painter using ink in a real canvas, what is painted later remains on top. So, if you want the text to be on top of the rectangle (with any fill you want), set the text's selection after the rectangle's selection.
Therefore, in your case, this...
var hoverTT = hoverLineGroup.append('text')
.attr("class", "hover-tex capo")
.attr('dy', "0.35em");
... has to be after this:
var hoverRect = hoverLineGroup
.append("rect");
Here is a demo, the rectangle has a solid white fill. Have a look at the order of the selections:
var svg = d3.select("svg");
var hoverRect = svg.append("rect")
.attr("fill", "white")
.attr("stroke", "firebrick")
.attr("width", 40)
.attr("height", 30)
.attr("opacity", 0);
var hoverText = svg.append("text")
.text("foo")
svg.on("mousemove", function() {
var coords = d3.mouse(this);
hoverRect.attr("x", coords[0] + 15)
.attr("y", coords[1])
.attr("opacity", 1)
hoverText.attr("x", coords[0] + 25)
.attr("y", coords[1] + 20)
})
svg {
border: 1px solid gray;
background-color: gainsboro;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg></svg>
This question is pretty such similar to this D3JS question but I am using latest D3 version(//d3js.org/d3.v4.min.js).
I am trying make this Normalized Stacked Bar Chart
chart horizontal. Is there any optimized way in latest version to achieve this?
I have swapped the x axis and y axis as below
var svg = d3.select("svg"),
margin = {top: 20, right: 60, bottom: 30, left: 40},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom,
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var y = d3.scaleBand()
.rangeRound([0, width])
.padding(0.1)
.align(0.1);
var x = d3.scaleLinear()
.rangeRound([height, 0]);
var z = d3.scaleOrdinal()
.range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]);
var stack = d3.stack()
.offset(d3.stackOffsetExpand);
d3.csv("data.csv", type, function(error, data) {
if (error) throw error;
data.sort(function(a, b) { return b[data.columns[1]] / b.total - a[data.columns[1]] / a.total; });
y.domain(data.map(function(d) { return d.State; }));
z.domain(data.columns.slice(1));
var serie = g.selectAll(".serie")
.data(stack.keys(data.columns.slice(1))(data))
.enter().append("g")
.attr("class", "serie")
.attr("fill", function(d) { return z(d.key); });
serie.selectAll("rect")
.data(function(d) { return d; })
.enter().append("rect")
.attr("y", function(d) { return y(d.data.State); })
.attr("x", function(d) { return x(d[1]); })
.attr("height", function(d) { return x(d[0]) - x(d[1]); })
.attr("width", y.bandwidth());
g.append("g")
.attr("class", "axis axis--y")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(y));
g.append("g")
.attr("class", "axis axis--x")
.call(d3.axisLeft(x).ticks(10, "%"));
var legend = serie.append("g")
.attr("class", "legend")
.attr("transform", function(d) { var d = d[d.length - 1]; return "translate(" + (y(d.data.State) + y.bandwidth()) + "," + ((x(d[0]) + x(d[1])) / 2) + ")"; });
legend.append("line")
.attr("x1", -6)
.attr("x2", 6)
.attr("stroke", "#000");
legend.append("text")
.attr("y", 9)
.attr("dy", "0.35em")
.attr("fill", "#000")
.style("font", "10px sans-serif")
.text(function(d) { return d.key; });
});
function type(d, i, columns) {
for (i = 1, t = 0; i < columns.length; ++i) t += d[columns[i]] = +d[columns[i]];
d.total = t;
return d;
}
Referring the example
You will need to reverse the domains:
var y = d3.scaleBand()
.rangeRound([0, width])
.padding(0.1)
.align(0.1);
var x = d3.scaleLinear()
.rangeRound([height, 0]);
Swap the x to y as the domains are reverse so x will become y and y will become x, when you create the rectangles.
serie.selectAll("rect")
.data(function(d) {
return d;
})
.enter().append("rect")
.attr("y", function(d) {
return y(d.data.State);
})
.attr("x", function(d) {
return x(d[1]);
})
.attr("width", function(d) {
return x(d[0]) - x(d[1]);
})
.attr("height", y.bandwidth());
Change the legend position accordingly to position it on the top bar.
var legend = serie.append("g")
.attr("class", "legend")
.attr("transform", function(d) {
var d = d[0];//get the top data for placing legends on that.
return "translate(" + ((x(d[0]) + x(d[1])) / 2) + ", " +(y(d.data.State) - y.bandwidth())+ ")";
});
Finally position the legend lines:
legend.append("line")
.attr("y1", 5)
.attr("x1", 15)
.attr("x2", 15)
.attr("y2", 12)
.attr("stroke", "#000");
working code here
Below example also will help you
var initStackedBarChart = {
draw: function(config) {
me = this,
domEle = config.element,
stackKey = config.key,
data = config.data,
margin = {top: 20, right: 20, bottom: 30, left: 50},
parseDate = d3.timeParse("%m/%Y"),
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom,
xScale = d3.scaleLinear().rangeRound([0, width]),
yScale = d3.scaleBand().rangeRound([height, 0]).padding(0.1),
color = d3.scaleOrdinal(d3.schemeCategory20),
xAxis = d3.axisBottom(xScale),
yAxis = d3.axisLeft(yScale).tickFormat(d3.timeFormat("%b")),
svg = d3.select("#"+domEle).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 stack = d3.stack()
.keys(stackKey)
/*.order(d3.stackOrder)*/
.offset(d3.stackOffsetNone);
var layers= stack(data);
data.sort(function(a, b) { return b.total - a.total; });
yScale.domain(data.map(function(d) { return parseDate(d.date); }));
xScale.domain([0, d3.max(layers[layers.length - 1], function(d) { return d[0] + d[1]; }) ]).nice();
var layer = svg.selectAll(".layer")
.data(layers)
.enter().append("g")
.attr("class", "layer")
.style("fill", function(d, i) { return color(i); });
layer.selectAll("rect")
.data(function(d) { return d; })
.enter().append("rect")
.attr("y", function(d) { return yScale(parseDate(d.data.date)); })
.attr("x", function(d) { return xScale(d[0]); })
.attr("height", yScale.bandwidth())
.attr("width", function(d) { return xScale(d[1]) - xScale(d[0]) });
svg.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + (height+5) + ")")
.call(xAxis);
svg.append("g")
.attr("class", "axis axis--y")
.attr("transform", "translate(0,0)")
.call(yAxis);
}
}
var data = [{"date":"4/1854","total":8571,"disease":1,"wounds":0,"other":5},{"date":"5/1854","total":23333,"disease":12,"wounds":0,"other":9},{"date":"6/1854","total":28333,"disease":11,"wounds":0,"other":6},{"date":"7/1854","total":28772,"disease":359,"wounds":0,"other":23},{"date":"8/1854","total":30246,"disease":828,"wounds":1,"other":30},{"date":"9/1854","total":30290,"disease":788,"wounds":81,"other":70},{"date":"10/1854","total":30643,"disease":503,"wounds":132,"other":128},{"date":"11/1854","total":29736,"disease":844,"wounds":287,"other":106},{"date":"12/1854","total":32779,"disease":1725,"wounds":114,"other":131},{"date":"1/1855","total":32393,"disease":2761,"wounds":83,"other":324},{"date":"2/1855","total":30919,"disease":2120,"wounds":42,"other":361},{"date":"3/1855","total":30107,"disease":1205,"wounds":32,"other":172},{"date":"4/1855","total":32252,"disease":477,"wounds":48,"other":57},{"date":"5/1855","total":35473,"disease":508,"wounds":49,"other":37},{"date":"6/1855","total":38863,"disease":802,"wounds":209,"other":31},{"date":"7/1855","total":42647,"disease":382,"wounds":134,"other":33},{"date":"8/1855","total":44614,"disease":483,"wounds":164,"other":25},{"date":"9/1855","total":47751,"disease":189,"wounds":276,"other":20},{"date":"10/1855","total":46852,"disease":128,"wounds":53,"other":18},{"date":"11/1855","total":37853,"disease":178,"wounds":33,"other":32},{"date":"12/1855","total":43217,"disease":91,"wounds":18,"other":28},{"date":"1/1856","total":44212,"disease":42,"wounds":2,"other":48},{"date":"2/1856","total":43485,"disease":24,"wounds":0,"other":19},{"date":"3/1856","total":46140,"disease":15,"wounds":0,"other":35}];
var key = ["wounds", "other", "disease"];
initStackedBarChart.draw({
data: data,
key: key,
element: 'stacked-bar'
});
.axis text {
font: 10px sans-serif;
}
.axis line,
.axis path {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.path-line {
fill: none;
stroke: yellow;
stroke-width: 1.5px;
}
svg {
background: #f0f0f0;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<div id='stacked-bar'></div>
im trying to update http://bl.ocks.org/d3noob/6eb506b129f585ce5c8a and add brushing into it(brushing displayed under the line graph) to make it look like https://www.google.com.hk/#q=s%26p+500
added coded to the first link:
var brush = d3.svg.brush()
.x(x)
.on("brush", brushmove)
.on("brushend", brushend);
svg.append("g")
.attr("class", "brush")
.call(brush)
.selectAll('rect')
.attr('height', height);
function brushmove() {
var extent = brush.extent();
}
function brushend() {
x.domain(brush.extent())
console.log(brush.extent());
}
The problem is that once i add brushing into it, there's a background formed behind the graph and i can't perform mouse events(mousemove) anymore.
Is there a way to fix it to make it look like google?
1) brushing and mouse event coexist
2) brushing area under the curve
var csv = date,close1, close2
26-Mar-12,606.98,58.13
27-Mar-12,614.48,53.98
28-Mar-12,617.62,67.00
29-Mar-12,609.86,89.70
30-Mar-12,599.55,99.00
2-Apr-12,618.63,130.28
3-Apr-12,629.32,166.70
4-Apr-12,624.31,234.98
5-Apr-12,633.68,345.44
9-Apr-12,636.23,443.34
10-Apr-12,628.44,543.70
11-Apr-12,626.20,580.13
12-Apr-12,622.77,605.23
13-Apr-12,605.23,626.20
16-Apr-12,580.13,628.44
17-Apr-12,543.70,636.23
18-Apr-12,443.34,633.68
19-Apr-12,345.44,624.31
20-Apr-12,234.98,629.32
23-Apr-12,166.70,618.63
24-Apr-12,130.28,599.55
25-Apr-12,99.00,609.86
26-Apr-12,89.70,617.62
27-Apr-12,67.00,614.48
30-Apr-12,53.98,606.98
1-May-12,58.13,503.15
Both the example you link to and the brush add a rect on top of the plot to capture mouse events. The key to making them coexist is to add the brush (and allow it to create its rect) and then use that rect to add the tooltip events. This way you only end up with one point-events rect:
// add a g for the brush
var context = svg.append("g");
// add the brush
context.call(brush);
// grab the brush's rect and add the tooltip events
context.select(".background")
.on("mouseover", function() {
focus.style("display", null);
})
.on("mouseout", function() {
focus.style("display", "none");
})
.on("mousemove", mousemove);
Full code:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
/* set the CSS */
body {
font: 12px Arial;
}
path {
stroke: steelblue;
stroke-width: 2;
fill: none;
}
.axis path,
.axis line {
fill: none;
stroke: grey;
stroke-width: 1;
shape-rendering: crispEdges;
}
.extent {
stroke: #fff;
fill-opacity: .125;
shape-rendering: crispEdges;
}
</style>
<body>
<!-- load the d3.js library -->
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
// Set the dimensions of the canvas / graph
var margin = {
top: 30,
right: 20,
bottom: 30,
left: 50
},
width = 600 - margin.left - margin.right,
height = 270 - margin.top - margin.bottom;
// Parse the date / time
var parseDate = d3.time.format("%d-%b-%y").parse,
formatDate = d3.time.format("%d-%b"),
bisectDate = d3.bisector(function(d) {
return d.date;
}).left;
// Set the ranges
var x = d3.time.scale().range([0, width]);
var y = d3.scale.linear().range([height, 0]);
// Define the axes
var xAxis = d3.svg.axis().scale(x)
.orient("bottom").ticks(5);
var yAxis = d3.svg.axis().scale(y)
.orient("left").ticks(5);
// Define the line
var valueline = d3.svg.line()
.x(function(d) {
return x(d.date);
})
.y(function(d) {
return y(d.close);
});
var area = d3.svg.area()
.x(function(d) {
return x(d.date);
})
.y0(height)
.y1(function(d) {
return y(d.close);
});
// Adds the svg canvas
var svg = d3.select("body")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
var defs = svg.append("defs");
var areaClip = defs.append("clipPath")
.attr("id", "areaClip")
.append("rect")
.attr("x", width)
.attr("y", 0)
.attr("width", width)
.attr("height", height);
var lineSvg = svg.append("g");
var focus = svg.append("g")
.style("display", "none");
var brush = d3.svg.brush()
.x(x)
.on("brush", function() {
var s = brush.extent(),
x1 = x(s[0]),
x2 = x(s[1]);
areaClip.attr('x', x1);
areaClip.attr('width', x2 - x1);
})
var csv = `date,close
26-Mar-12,606.98
27-Mar-12,614.48
28-Mar-12,617.62
29-Mar-12,609.86
30-Mar-12,599.55
2-Apr-12,618.63
3-Apr-12,629.32
4-Apr-12,624.31
5-Apr-12,633.68
9-Apr-12,636.23
10-Apr-12,628.44
11-Apr-12,626.20
12-Apr-12,622.77
13-Apr-12,605.23
16-Apr-12,580.13
17-Apr-12,543.70
18-Apr-12,443.34
19-Apr-12,345.44
20-Apr-12,234.98
23-Apr-12,166.70
24-Apr-12,130.28
25-Apr-12,99.00
26-Apr-12,89.70
27-Apr-12,67.00
30-Apr-12,53.98
1-May-12,58.13`;
var data = d3.csv.parse(csv);
data.forEach(function(d) {
d.date = parseDate(d.date);
d.close = +d.close;
});
// Scale the range of the data
x.domain(d3.extent(data, function(d) {
return d.date;
}));
y.domain([0, d3.max(data, function(d) {
return d.close+20;
})]);
// Add the valueline path.
lineSvg.append("path")
.attr("class", "line")
.attr("d", valueline(data));
lineSvg.append("path")
.attr("d", area(data))
.style("fill", "steelblue")
.style("stroke", "none")
.style("opacity", "0.5")
.attr("clip-path", "url(#areaClip)")
// Add the X Axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Add the Y Axis
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
// append the x line
focus.append("line")
.attr("class", "x")
.style("stroke", "blue")
.style("stroke-dasharray", "3,3")
.style("opacity", 0.5)
.attr("y1", 0)
.attr("y2", height);
// append the y line
focus.append("line")
.attr("class", "y")
.style("stroke", "blue")
.style("stroke-dasharray", "3,3")
.style("opacity", 0.5)
.attr("x1", width)
.attr("x2", width);
// append the circle at the intersection
focus.append("circle")
.attr("class", "y")
.style("fill", "none")
.style("stroke", "blue")
.attr("r", 4);
// place the value at the intersection
focus.append("text")
.attr("class", "y1")
.style("stroke", "white")
.style("stroke-width", "3.5px")
.style("opacity", 0.8)
.attr("dx", 8)
.attr("dy", "-.3em");
focus.append("text")
.attr("class", "y2")
.attr("dx", 8)
.attr("dy", "-.3em");
// place the date at the intersection
focus.append("text")
.attr("class", "y3")
.style("stroke", "white")
.style("stroke-width", "3.5px")
.style("opacity", 0.8)
.attr("dx", 8)
.attr("dy", "1em");
focus.append("text")
.attr("class", "y4")
.attr("dx", 8)
.attr("dy", "1em");
// append the rectangle to capture mouse
var context = svg.append("g");
context.call(brush);
context.selectAll(".resize").append("path")
.attr("d", "M0,2V" + (height - 2))
.style("stroke", "black")
context.select(".extent")
.attr("height", height - 2)
.attr("fill", "none");
context.select(".background")
.attr("height", height)
.on("mouseover.tooltip", function() {
focus.style("display", null);
})
.on("mouseout.tooltip", function() {
focus.style("display", "none");
})
.on("mousemove.tooltip", mousemove);
function mousemove() {
var x0 = x.invert(d3.mouse(this)[0]),
i = bisectDate(data, x0, 1),
d0 = data[i - 1],
d1 = data[i],
d = x0 - d0.date > d1.date - x0 ? d1 : d0;
focus.select("circle.y")
.attr("transform",
"translate(" + x(d.date) + "," +
y(d.close) + ")");
focus.select("text.y1")
.attr("transform",
"translate(" + x(d.date) + "," +
y(d.close) + ")")
.text(d.close);
focus.select("text.y2")
.attr("transform",
"translate(" + x(d.date) + "," +
y(d.close) + ")")
.text(d.close);
focus.select("text.y3")
.attr("transform",
"translate(" + x(d.date) + "," +
y(d.close) + ")")
.text(formatDate(d.date));
focus.select("text.y4")
.attr("transform",
"translate(" + x(d.date) + "," +
y(d.close) + ")")
.text(formatDate(d.date));
focus.select(".x")
.attr("transform",
"translate(" + x(d.date) + "," +
y(d.close) + ")")
.attr("y2", height - y(d.close));
focus.select(".y")
.attr("transform",
"translate(" + width * -1 + "," +
y(d.close) + ")")
.attr("x2", width + width);
}
</script>
</body>
I am trying to make a bar chart out of some grouped data. This is dummy data, but the structure is basically the same. The data: election results includes a bunch of candidates, organized into the districts they were running in, and the total vote count:
district,candidate,votes
Dist 1,Leticia Putte,3580
Dist 2,David Barron,1620
Dist 2,John Higginson,339
Dist 2,Walter Bannister,2866
[...]
I'd like to create a bar or column chart (either, honestly, though my end goal is horizontal) that groups the candidates by district.
Mike Bostock has an excellent demo but I'm having trouble translating it intelligently for my purposes. I started to tease it out at https://jsfiddle.net/97ur6cwt/6/ but my data is organized somewhat differently -- instead of rows, by group, I have a column that sets the category. And there might be just one candidate or there might be a few candidates.
Can I group items if the groups aren't the same size?
My answer is similar to #GerardoFurtado but instead I use a d3.nest to build a domain per district. This removes the need for hardcoding values and cleans it up a bit:
y0.domain(data.map(function(d) { return d.district; }));
var districtD = d3.nest()
.key(function(d) { return d.district; })
.rollup(function(d){
return d3.scale.ordinal()
.domain(d.map(function(c){return c.candidate}))
.rangeRoundBands([0, y0.rangeBand()], pad);
}).map(data);
districtD becomes a map of domains for your y-axis which you use when placing the rects:
svg.selectAll("bar")
.data(data)
.enter().append("rect")
.style("fill", function(d,i) {
return color(d.district);
})
.attr("x", 0)
.attr("y", function(d) { return y0(d.district) + districtD[d.district](d.candidate); })
.attr("height", function(d){
return districtD[d.district].rangeBand();
})
.attr("width", function(d) {
return x(d.votes);
});
I'm off to a meeting but the next step is to clean up the axis and get the candidate names on there.
Full running code:
var url = "https://gist.githubusercontent.com/amandabee/edf73bc0bbe131435c952f5ed47524a6/raw/99febb9971f76e36af06f1b99913fcaa645ecb3e/election.csv"
var m = {top: 10, right: 10, bottom: 50, left: 110},
w = 800 - m.left - m.right,
h = 500 - m.top - m.bottom,
pad = .1;
var x = d3.scale.linear().range([0, w]);
y0 = d3.scale.ordinal().rangeRoundBands([0, h], pad);
var color = d3.scale.category20c();
var yAxis = d3.svg.axis()
.scale(y0)
.orient("left");
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(5)
.tickFormat(d3.format("$,.0f"));
var svg = d3.select("#chart").append("svg")
.attr("width", w + m.right + m.left + 100)
.attr("height", h + m.top + m.bottom)
.append("g")
.attr("transform",
"translate(" + m.left + "," + m.top + ")");
// This moves the SVG over by m.left(110)
// and down by m.top (10)
d3.csv(url, function(error, data) {
data.forEach(function(d) {
d.votes = +d.votes;
});
y0.domain(data.map(function(d) { return d.district; }));
districtD = d3.nest()
.key(function(d) { return d.district; })
.rollup(function(d){
console.log(d);
return d3.scale.ordinal()
.domain(d.map(function(c){return c.candidate}))
.rangeRoundBands([0, y0.rangeBand()], pad);
})
.map(data);
x.domain([0, d3.max(data, function(d) {
return d.votes;
})]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + h + ")")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "middle");
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text");
svg.selectAll("bar")
.data(data)
.enter().append("rect")
.style("fill", function(d,i) {
return color(d.district);
})
.attr("x", 0)
.attr("y", function(d) { return y0(d.district) + districtD[d.district](d.candidate); })
.attr("height", function(d){
return districtD[d.district].rangeBand();
})
.attr("width", function(d) {
return x(d.votes);
});
svg.selectAll(".label")
.data(data)
.enter().append("text")
.text(function(d) {
return (d.votes);
})
.attr("text-anchor", "start")
.attr("x", function(d) { return x(d.votes)})
.attr("y", function(d) { return y0(d.district) + districtD[d.district](d.candidate) + districtD[d.district].rangeBand()/2;})
.attr("class", "axis");
});
.axis {
font: 10px sans-serif;
}
.axis path, .axis line {
fill: none;
stroke: black;
shape-rendering: crispEdges;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="chart"></div>
An alternate version which sizes the bars the same and scales the outer domain appropriately:
<!DOCTYPE html>
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<style>
.label {
font: 10px sans-serif;
}
.axis {
font: 11px sans-serif;
font-weight: bold;
}
.axis path,
.axis line {
fill: none;
stroke: black;
shape-rendering: crispEdges;
}
</style>
</head>
<body>
<div id="chart"></div>
<script>
var url = "https://gist.githubusercontent.com/amandabee/edf73bc0bbe131435c952f5ed47524a6/raw/99febb9971f76e36af06f1b99913fcaa645ecb3e/election.csv"
var m = {
top: 10,
right: 10,
bottom: 50,
left: 110
},
w = 800 - m.left - m.right,
h = 500 - m.top - m.bottom,
pad = .1, padPixel = 5;
var x = d3.scale.linear().range([0, w]);
var y0 = d3.scale.ordinal();
var color = d3.scale.category20c();
var yAxis = d3.svg.axis()
.scale(y0)
.orient("left");
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(5)
.tickFormat(d3.format("$,.0f"));
var svg = d3.select("#chart").append("svg")
.attr("width", w + m.right + m.left + 100)
.attr("height", h + m.top + m.bottom)
.append("g")
.attr("transform",
"translate(" + m.left + "," + m.top + ")");
// This moves the SVG over by m.left(110)
// and down by m.top (10)
d3.csv(url, function(error, data) {
data.forEach(function(d) {
d.votes = +d.votes;
});
var barHeight = h / data.length;
y0.domain(data.map(function(d) {
return d.district;
}));
var y0Range = [0];
districtD = d3.nest()
.key(function(d) {
return d.district;
})
.rollup(function(d) {
var barSpace = (barHeight * d.length);
y0Range.push(y0Range[y0Range.length - 1] + barSpace);
return d3.scale.ordinal()
.domain(d.map(function(c) {
return c.candidate
}))
.rangeRoundBands([0, barSpace], pad);
})
.map(data);
y0.range(y0Range);
x.domain([0, d3.max(data, function(d) {
return d.votes;
})]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + h + ")")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "middle");
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text");
svg.selectAll("bar")
.data(data)
.enter().append("rect")
.style("fill", function(d, i) {
return color(d.district);
})
.attr("x", 0)
.attr("y", function(d) {
return y0(d.district) + districtD[d.district](d.candidate);
})
.attr("height", function(d) {
return districtD[d.district].rangeBand();
})
.attr("width", function(d) {
return x(d.votes);
});
var ls = svg.selectAll(".labels")
.data(data)
.enter().append("g");
ls.append("text")
.text(function(d) {
return (d.votes);
})
.attr("text-anchor", "start")
.attr("x", function(d) {
return x(d.votes)
})
.attr("y", function(d) {
return y0(d.district) + districtD[d.district](d.candidate) + districtD[d.district].rangeBand() / 2;
})
.attr("class", "label");
ls.append("text")
.text(function(d) {
return (d.candidate);
})
.attr("text-anchor", "end")
.attr("x", -2)
.attr("y", function(d) {
return y0(d.district) + districtD[d.district](d.candidate) + districtD[d.district].rangeBand() / 2;
})
.style("alignment-baseline", "middle")
.attr("class", "label");
});
</script>
</body>
</html>
This is a partial solution: https://jsfiddle.net/hb13oe4v/
The main problem here is creating a scale for each group with a variable domain. Unlike Bostock's example, you don't have the same amount of bars(candidates) for each group(districts).
So, I had to do a workaround. First, I nested the data in the most trivial way:
var nested = d3.nest()
.key(function(d) { return d.district; })
.entries(data);
And then created the groups accordingly:
var district = svg.selectAll(".district")
.data(nested)
.enter()
.append("g")
.attr("transform", function(d) { return "translate(0," + y(d.key) + ")"; });
As I couldn't create an y1 (x1 in Bostock's example) scale, I had to hardcode the height of the bars (which is inherently bad). Also, for centring the bars in each group, I created this crazy math, that puts one bar in the center, the next under, the next above, the next under and so on:
.attr("y", function(d, i) {
if( i % 2 == 0){ return (y.rangeBand()/2 - 10) + (i/2 + 0.5) * 10}
else { return (y.rangeBand()/2 - 10) - (i/2) * 10}
})
Of course, all this can be avoided and coded way more elegantly if we could set a variable scale for each group.