I make a scatterplot using d3.js where the points are scattered according to their name and an accompanying number. The x axis displays the group_1 once per group.
The data that is plotted would look like this:
name, group_1, group_2, number
"a", "A", "1", 0.5
"b", "A", "1", 10.0
"c", "A", "1", 5.0
"d", "A", "2", 1.0
"e", "A", "2", 3.0
"f", "A", "2", 10.0
"g", "B", "1", 3.0
Starting point.
The current code (based on this SO answer/example on bl.ocks.org) ...
var margin = {
top: 10,
right: 10,
bottom: 30,
left: 30
},
width = 500 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom,
offset = 7;
var chartData = [{
name: "a",
group_1: "A",
group_2: "1",
number: 0.5
}, {
name: "b",
group_1: "A",
group_2: "1",
number: 10
}, {
name: "c",
group_1: "A",
group_2: "1",
number: 5.0
}, {
name: "d",
group_1: "A",
group_2: "2",
number: 1.0
}, {
name: "e",
group_1: "A",
group_2: "2",
number: 3.0
}, {
name: "f",
group_1: "A",
group_2: "2",
number: 10.0
}, {
name: "g",
group_1: "B",
group_2: "1",
number: 3.0
}];
var x = d3.scale.ordinal()
.domain(chartData.map(function(d) {
return d.name;
}))
.rangePoints([0, width]);
var chartGroups = [];
chartData.forEach(function(d, i, array) {
if (i === 0) d.first = true;
else if (d.group_1 !== array[i - 1].group_1) d.first = true;
if (i === array.length - 1) d.last = true;
else if (d.group_1 !== array[i + 1].group_1) d.last = true;
if (d.first) chartGroups.push({
group: d.group_1,
start: i === 0 ? x(d.name) : ((x(d.name) + x(array[i - 1].name)) / 2)
});
if (d.last) chartGroups[chartGroups.length - 1].end = i === array.length - 1 ? x(d.name) : ((x(d.name) + x(array[i + 1].name)) / 2);
});
var y = d3.scale.linear()
.domain([0, d3.max(chartData, function(d) {
return d.number;
})])
.range([height, 0]),
xAxis = d3.svg.axis()
.scale(x)
.tickValues([])
.outerTickSize(offset)
.orient("bottom"),
yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var svg = d3.select("body")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.selectAll("circle.point")
.data(chartData)
.enter().append("circle")
.attr({
"class": "point",
cx: function(d) {
return x(d.name);
},
cy: function(d) {
return y(d.number);
},
r: 5
});
var groups = svg.selectAll("g.chartGroup")
.data(chartGroups)
.enter().append("g")
.attr("class", "chartGroup")
.attr("transform", "translate(" + 0 + "," + (height + offset) + ")");
groups.append("text")
.attr({
x: function(d) {
return (d.start + d.end) / 2;
},
dy: "1em",
"text-anchor": "middle"
})
.text(function(d) {
return d.group;
});
groups.append("path")
.attr("d", function(d) {
var t = d3.select(this.parentNode).select("text").node().getBBox(),
ttop = [t.x + t.width / 2, t.y];
console.log(d, t, ttop);
return "M" + d.start + ",0" + "V" + -offset;
});
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
text {
font: 12px sans-serif;
}
.axis path,
.axis line,
g.chartGroup path {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
circle.point {
fill: steelblue;
stroke: black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
... generates this plot:
What I want to achieve.
1. Zoom Level:
I want zoom in to only display all elements of A (from group_1) when clicking on the label A on the x axis. Besides the zooming, also the labeling of the x axis should change to incorporate data from group_2.
2. Zoom Level:
On the 2. zoom level, this should be repeated. By clicking on 1 (from group_2), I want a zoom to all elements of this group. The x axis should change to incorporate data from name
What I tried.
I tried adding basic zooming function (from this example on bl.ocks.org) through these code parts, but even this didn't work out:
var zoom = d3.behavior.zoom()
.x(x)
.y(y)
.scaleExtent([1, 10])
.on("zoom", zoomed);
var svg = d3.select("body")
// ...
.call(zoom);
function zoomed() {
svg.select(".x.axis").call(xAxis);
svg.select(".y.axis").call(yAxis);
}
The array chartGroups, which is based on group_1 is an array of calculated start and end coordinates for displaying the groups on the x axis. But I can't figure out, how to use them for zooming.
var margin = {
top: 10,
right: 10,
bottom: 30,
left: 30
},
width = 500 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom,
offset = 7;
var chartData = [{
name: "a",
group_1: "A",
group_2: "1",
number: 0.5
}, {
name: "b",
group_1: "A",
group_2: "1",
number: 10
}, {
name: "c",
group_1: "A",
group_2: "1",
number: 5.0
}, {
name: "d",
group_1: "A",
group_2: "2",
number: 1.0
}, {
name: "e",
group_1: "A",
group_2: "2",
number: 3.0
}, {
name: "f",
group_1: "A",
group_2: "2",
number: 10.0
}, {
name: "g",
group_1: "B",
group_2: "1",
number: 3.0
}];
var x = d3.scale.ordinal()
.domain(chartData.map(function(d) {
return d.name;
}))
.rangePoints([0, width]);
var chartGroups = [];
chartData.forEach(function(d, i, array) {
if (i === 0) d.first = true;
else if (d.group_1 !== array[i - 1].group_1) d.first = true;
if (i === array.length - 1) d.last = true;
else if (d.group_1 !== array[i + 1].group_1) d.last = true;
if (d.first) chartGroups.push({
group: d.group_1,
start: i === 0 ? x(d.name) : ((x(d.name) + x(array[i - 1].name)) / 2)
});
if (d.last) chartGroups[chartGroups.length - 1].end = i === array.length - 1 ? x(d.name) : ((x(d.name) + x(array[i + 1].name)) / 2);
});
var y = d3.scale.linear()
.domain([0, d3.max(chartData, function(d) {
return d.number;
})])
.range([height, 0]),
xAxis = d3.svg.axis()
.scale(x)
.tickValues([])
.outerTickSize(offset)
.orient("bottom"),
yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var zoom = d3.behavior.zoom()
.x(x)
.y(y)
.scaleExtent([1, 10])
.on("zoom", zoomed);
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 + ")")
.call(zoom);
svg.selectAll("circle.point")
.data(chartData)
.enter().append("circle")
.attr({
"class": "point",
cx: function(d) {
return x(d.name);
},
cy: function(d) {
return y(d.number);
},
r: 5
});
var groups = svg.selectAll("g.chartGroup")
.data(chartGroups)
.enter().append("g")
.attr("class", "chartGroup")
.attr("transform", "translate(" + 0 + "," + (height + offset) + ")");
groups.append("text")
.attr({
x: function(d) {
return (d.start + d.end) / 2;
},
dy: "1em",
"text-anchor": "middle"
})
.text(function(d) {
return d.group;
});
groups.append("path")
.attr("d", function(d) {
var t = d3.select(this.parentNode).select("text").node().getBBox(),
ttop = [t.x + t.width / 2, t.y];
console.log(d, t, ttop);
return "M" + d.start + ",0" + "V" + -offset;
});
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
function zoomed() {
svg.select(".x.axis").call(xAxis);
svg.select(".y.axis").call(yAxis);
}
text {
font: 12px sans-serif;
}
.axis path,
.axis line,
g.chartGroup path {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
circle.point {
fill: steelblue;
stroke: black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
Question.
How can I zoom to specficic areas just by clicking on a text element?
Edit.
As suggested in the comments, I tried to adjust the domain and redraw the circles. I did this the following way:
function zoom_update(group) {
//adjust domain
x.domain(chartData.map(function (d) {
if (d.group_1 == group) {
return d.name;
}
}));
console.log(x.domain());
svg.selectAll(".xaxis")
.transition(750)
.call(xAxis);
// update circles
svg.selectAll("circle")
.transition(750)
.attr({
cx: function (d) {return x(d.name);}
});
}
var margin = {
top: 10,
right: 10,
bottom: 30,
left: 30
},
width = 500 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom,
offset = 7;
var chartData = [{
name: "a",
group_1: "A",
group_2: "1",
number: 0.5
}, {
name: "b",
group_1: "A",
group_2: "1",
number: 10
}, {
name: "c",
group_1: "A",
group_2: "1",
number: 5.0
}, {
name: "d",
group_1: "A",
group_2: "2",
number: 1.0
}, {
name: "e",
group_1: "A",
group_2: "2",
number: 3.0
}, {
name: "f",
group_1: "A",
group_2: "2",
number: 10.0
}, {
name: "g",
group_1: "B",
group_2: "1",
number: 3.0
}];
var x = d3.scale.ordinal()
.rangePoints([0, width]);
x.domain(chartData.map(function(d) {
return d.name;
}));
var chartGroups = [];
chartData.forEach(function(d, i, array) {
if (i === 0) d.first = true;
else if (d.group_1 !== array[i - 1].group_1) d.first = true;
if (i === array.length - 1) d.last = true;
else if (d.group_1 !== array[i + 1].group_1) d.last = true;
if (d.first) chartGroups.push({
group: d.group_1,
start: i === 0 ? x(d.name) : ((x(d.name) + x(array[i - 1].name)) / 2)
});
if (d.last) chartGroups[chartGroups.length - 1].end = i === array.length - 1 ? x(d.name) : ((x(d.name) + x(array[i + 1].name)) / 2);
});
var y = d3.scale.linear()
.domain([0, d3.max(chartData, function(d) {
return d.number;
})])
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.tickValues([])
.outerTickSize(offset)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var svg = d3.select("body")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.selectAll("circle.point")
.data(chartData)
.enter().append("circle")
.attr({
"class": "point",
cx: function(d) {
return x(d.name);
},
cy: function(d) {
return y(d.number);
},
r: 5
});
var groups = svg.selectAll("g.chartGroup")
.data(chartGroups)
.enter().append("g")
.attr("class", "chartGroup")
.attr("transform", "translate(" + 0 + "," + (height + offset) + ")");
groups.append("text")
.attr({
x: function(d) {
return (d.start + d.end) / 2;
},
dy: "1em",
"text-anchor": "middle"
})
.on("click", function(d) {
zoom_update(d.group);
})
.text(function(d) {
return d.group;
});
groups.append("path")
.attr("d", function(d) {
var t = d3.select(this.parentNode).select("text").node().getBBox(),
ttop = [t.x + t.width / 2, t.y];
console.log(d, t, ttop);
return "M" + d.start + ",0" + "V" + -offset;
});
svg.append("g")
.attr("class", "xaxis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "yaxis")
.call(yAxis);
function zoom_update(group) {
x.domain(chartData.map(function(d) {
if (d.group_1 == group) {
return d.name;
}
}));
alert(x.domain());
svg.selectAll(".xaxis")
.transition(750)
.call(xAxis);
// update circles
svg.selectAll("circle")
.transition(750)
.attr({
cx: function(d) {
return x(d.name);
}
});
}
text {
font: 12px sans-serif;
}
.xaxis path,
.xaxis line,
.yaxis path,
.yaxis line,
.axis path,
.axis line,
g.chartGroup path {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
circle.point {
fill: steelblue;
stroke: black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
The log shows that the domain is set depending on which label is clicked. In addition, the circles that are outside the new domain are moved to x=0, but the remaining circles are not spread over the whole domain.
Related
I would like to show labels on the circles of the scatter plot when they are not colliding/overlapping. A simple example of scatter plot with labels is as follows:
var width = 500;
var height = 500;
var margin = {
top: 40,
right: 40,
bottom: 40,
left: 40
};
var x = d3.scale.linear().range([0, width]);
var y = d3.scale.linear().range([height, 0]);
var minX = _(data).orderBy('x').first().x;
var maxX = _(data).orderBy('x').last().x;
x.domain([minX - 500, maxX + 500]);
y.domain([0, 100]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var svg = d3
.select("#d3")
.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 + ")");
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(" + 0 + "," + height / 2 + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + width / 2 + "," + 0 + ")")
.call(yAxis)
.append("text");
var gdots = svg.selectAll("g.dot")
.data(data)
.enter().append('g');
gdots.append("circle")
.attr("class", "dot")
.attr("r", function (d) {
return d.r;
})
.attr("cx", function (d) {
return x(d.x);
})
.attr("cy", function (d) {
return y(d.y);
})
.style("fill", function (d) {
return d.c;
});
gdots.append("text").text(function(d){
return d.name;
})
.attr("x", function (d) {
return x(d.x);
})
.attr("y", function (d) {
return y(d.y);
});
The example is available on fiddle:
https://jsfiddle.net/8e7qmzw8/1/
How can I apply collision detection in the given example to show the labels for non-collided circles?
Here's a brute force search approach:
gdots
// filter out those in a colliding state
.filter(function(d0, i){
var isCollide = false,
x0 = x(d0.x),
y0 = y(d0.y);
gdots.data().forEach(function(d1,j){
// if it has a collision with another, stop looking
if (!isCollide){
// if it's not itself
if (d0.name != d1.name){
var x1 = x(d1.x),
y1 = y(d1.y);
// if they overlap
isCollide = Math.pow((x1-x0),2) + Math.pow((y1-y0),2) <= Math.pow((d0.r+d1.r), 2);
}
}
});
return !isCollide;
})
.append("text").text(function(d){
return d.name;
});
Running code:
var data = [
{"x": -123, "y": 63, "r": 37, "c": "#50C2E3", "name": "A"},
{"x": 71, "y": 0, "r": 15, "c": "#50C2E3", "name": "B"},
{"x": 3845, "y": 77, "r": 15, "c": "#50C2E3", "name": "C"},
{"x": 3176, "y": 90, "r": 15, "c": "#50C2E3", "name": "D"},
{"x": -17, "y": 56, "r": 15, "c": "#50C2E3", "name": "D"},
{"x": 1357, "y": 58, "r": 15, "c": "#50C2E3", "name": "E"},
{"x": 7684, "y": 75, "r": 15, "c": "#50C2E3", "name": "F"}
];
var width = 500;
var height = 500;
var margin = {
top: 40,
right: 40,
bottom: 40,
left: 40
};
var x = d3.scale.linear().range([0, width]);
var y = d3.scale.linear().range([height, 0]);
var minX = _(data).orderBy('x').first().x;
var maxX = _(data).orderBy('x').last().x;
x.domain([minX - 500, maxX + 500]);
y.domain([0, 100]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var svg = d3
.select("#d3")
.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 + ")");
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(" + 0 + "," + height / 2 + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + width / 2 + "," + 0 + ")")
.call(yAxis)
.append("text");
var gdots = svg.selectAll("g.dot")
.data(data)
.enter().append('g');
gdots.append("circle")
.attr("class", "dot")
.attr("r", function (d) {
return d.r;
})
.attr("cx", function (d) {
return x(d.x);
})
.attr("cy", function (d) {
return y(d.y);
})
.style("fill", function (d) {
return d.c;
});
gdots
.filter(function(d0, i){
var isCollide = false,
x0 = x(d0.x),
y0 = y(d0.y);
gdots.data().forEach(function(d1,j){
if (!isCollide){
if (d0.name != d1.name){
var x1 = x(d1.x),
y1 = y(d1.y);
isCollide = Math.pow((x1-x0),2) + Math.pow((y1-y0),2) <= Math.pow((d0.r+d1.r), 2);
}
}
});
return !isCollide;
})
.append("text").text(function(d){
return d.name;
})
.attr("x", function (d) {
return x(d.x);
})
.attr("y", function (d) {
return y(d.y);
});
.node {
cursor: pointer;
}
.dot {
opacity: .7;
cursor: pointer;
}
.axis path,
.axis line {
fill: none;
stroke: rgb(31, 119, 180);
shape-rendering: crispEdges;
}
text {
stroke: none;
fill: #666666;
font-size: .6em;
font-family: "Helvetica Neue"
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.11.2/lodash.min.js"></script>
<div id="d3"></div>
I am trying to follow this https://bl.ocks.org/mbostock/34f08d5e11952a80609169b7917d4172
Here is my fiddle: https://jsfiddle.net/q0xece4k/17/'
<!DOCTYPE html>
<meta charset="utf-8">
<title>Plotting a Trendline with D3.js</title>
<style>
.line {
stroke: blue;
fill:none;
stroke-width: 3;
}
.axis path,
.axis line {
fill: none;
stroke: black;
shape-rendering: crispEdges;
}
.axis text {
font-size: 10px;
font-family: sans-serif;
}
.text-label {
font-size: 10px;
font-family: sans-serif;
}
.zoom {
cursor: move;
fill: none;
pointer-events: all;
}
</style>
<div>
<div id="container"></div>
</div>
<body>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment.min.js"></script>
<script>
var height = 300;
var width = 600;
var margin = {top: 10, right:20, bottom: 50, left: 40};
// formatters for axis and labels
var rateFormat = d3.format("0.1f");
var trendDataArr = [
{
date: "2017-01-29",
value: 87.1
},
{
date: "2017-02-06",
value: 88
},
{
date: "2017-02-13",
value: 86.8
},
{
date: "2017-02-20",
value: 86.8
},
{
date: "2017-02-27",
value: 87.1
},
{
date: "2017-03-05",
value: 85.8
},
{
date: "2017-03-12",
value: 85.5
},
{
date: "2017-03-19",
value: 87.1
},
{
date: "2017-03-26",
value: 88
},
{
date: "2017-04-02",
value: 87.4
},
{
date: "2017-04-09",
value: 86.8
},
{
date: "2017-04-16",
value: 87.1
},
{
date: "2017-04-23",
value: 87.4
},
{
date: "2017-04-30",
value: 85.8
},
{
date: "2017-05-07",
value: 86.5
},
{
date: "2017-05-14",
value: 87.1
},
{
date: "2017-05-21",
value: 86.9
},
{
date: "2017-05-28",
value: 87.7
},
{
date: "2017-06-04",
value: 87.8
}
];
var zoom = d3.zoom()
.scaleExtent([1, Infinity])
.translateExtent([[0, 0], [width, height]])
.extent([[0, 0], [width, height]])
.on("zoom", zoomed);
var svg = d3.select('#container')
.append("svg")
.attr("pointer-events", "all")
.attr("width", 1000 + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.append("g");
svg.append("rect")
.attr("class", "zoom")
.attr("width", width)
.attr("height", height)
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(zoom);
svg.append("g")
.attr("class", "y axis");
svg.append("g")
.attr("class", "x axis");
function getDate(d) {
return moment(d["date"],"YYYY-MM-DD").toDate();
}
function removeDuplicates(xLabels){
var result = [];
xLabels.forEach(function(item) {
if(result.indexOf(item) < 0) {
result.push(item);
}
return result;
});
}
var minDate = getDate(trendDataArr[0]),
maxDate = getDate(trendDataArr[trendDataArr.length-1]);
var xScale = d3.scaleTime()
// .rangeBands([margin.left, width-100], .1);
.domain([minDate, maxDate])
.range([margin.left, width])
// .nice(trendDataArr.length);
var yScale = d3.scaleLinear()
.range([height, 0]);
var xAxis = d3.axisBottom(xScale)
.tickFormat(d3.timeFormat('%d %b %y'));
var yAxis = d3.axisLeft(yScale);
var xLabels = trendDataArr.map(function (d) { return getDate(d); });
xScale.domain(d3.extent(xLabels));
yScale.domain([Math.round(d3.min(trendDataArr, function(d) { return parseFloat(d['value']); }))-5, Math.round(d3.max(trendDataArr, function(d) { return parseFloat(d['value']); }))+5]);
var line = d3.line()
.x(function(d) { return xScale(getDate(d)); })
.y(function(d) { return yScale(d['value']); })
.curve(d3.curveCardinal);
svg.append("path")
.datum(trendDataArr)
.attr('fill','none')
.attr("d", line(trendDataArr.filter(function(d) {
return d;
}
)
)
)
.attr('stroke', "steelblue")
.attr('stroke-width', 2);
svg.select(".x.axis")
.attr("transform", "translate(0," + (height+5) + ")")
.call(xAxis.tickFormat(d3.timeFormat("%d %b %y")))
.selectAll("text")
.style("text-anchor","end")
.attr("transform", function(d) {
return "rotate(-45) ";
});
svg.select(".y.axis")
.attr("transform", "translate(" + (margin.left) + ",0)")
.call(yAxis.tickFormat(rateFormat));
// chart title
svg.append("text")
.attr("x", (width + (margin.left + margin.right) )/ 2 -200)
.attr("y", 0 + margin.top -20)
.attr("text-anchor", "middle")
.style("font-size", "16px")
.style("font-family", "sans-serif")
.text("Zoomable chart");
//y axis label
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("y", -20)
.attr("x",0 - (height / 2))
.attr("dy", "1em")
.style("text-anchor", "middle")
.text("Rate");
function zoomed() {
var t = d3.event.transform;
xScale.domain(t.rescaleX(xScale).domain());
}
</script>
</body>
Could someone help me with the zooming in/out on the axis so that I can drill into each day or zoom out to years?
I got it to work:
here is the updated fiddle:
https://jsfiddle.net/q0xece4k/17/
var height = 300;
var width = 600;
var margin = {top: 10, right:20, bottom: 50, left: 40};
// formatters for axis and labels
var rateFormat = d3.format("0.1f");
var trendDataArr = [
{
date: "2017-01-29",
value: 87.1
},
{
date: "2017-02-06",
value: 88
},
{
date: "2017-02-13",
value: 86.8
},
{
date: "2017-02-20",
value: 86.8
},
{
date: "2017-02-27",
value: 87.1
},
{
date: "2017-03-05",
value: 85.8
},
{
date: "2017-03-12",
value: 85.5
},
{
date: "2017-03-19",
value: 87.1
},
{
date: "2017-03-26",
value: 88
},
{
date: "2017-04-02",
value: 87.4
},
{
date: "2017-04-09",
value: 86.8
},
{
date: "2017-04-16",
value: 87.1
},
{
date: "2017-04-23",
value: 87.4
},
{
date: "2017-04-30",
value: 85.8
},
{
date: "2017-05-07",
value: 86.5
},
{
date: "2017-05-14",
value: 87.1
},
{
date: "2017-05-21",
value: 86.9
},
{
date: "2017-05-28",
value: 87.7
},
{
date: "2017-06-04",
value: 87.8
}
];
var zoom = d3.zoom()
.scaleExtent([1, Infinity])
.translateExtent([[0, 0], [width, height]])
.extent([[0, 0], [width, height]])
.on("zoom", zoomed);
var svg = d3.select('#container')
.append("svg")
.attr("pointer-events", "all")
.attr("width", 1000 + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.append("g");
svg.append("rect")
.attr("class", "zoom")
.attr("width", width)
.attr("height", height)
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(zoom);
svg.append("g")
.attr("class", "y axis");
svg.append("g")
.attr("class", "x axis");
function getDate(d) {
return moment(d["date"],"YYYY-MM-DD").toDate();
}
function removeDuplicates(xLabels){
var result = [];
xLabels.forEach(function(item) {
if(result.indexOf(item) < 0) {
result.push(item);
}
return result;
});
}
var minDate = getDate(trendDataArr[0]),
maxDate = getDate(trendDataArr[trendDataArr.length-1]);
var xScale = d3.scaleTime()
// .rangeBands([margin.left, width-100], .1);
.domain([minDate, maxDate])
.range([margin.left, width])
// .nice(trendDataArr.length);
var yScale = d3.scaleLinear()
.range([height, 0]);
var xAxis = d3.axisBottom(xScale)
.tickFormat(d3.timeFormat('%d %b %y'));
var yAxis = d3.axisLeft(yScale);
var xLabels = trendDataArr.map(function (d) { return getDate(d); });
xScale.domain(d3.extent(xLabels));
yScale.domain([Math.round(d3.min(trendDataArr, function(d) { return parseFloat(d['value']); }))-5, Math.round(d3.max(trendDataArr, function(d) { return parseFloat(d['value']); }))+5]);
var line = d3.line()
.x(function(d) { return xScale(getDate(d)); })
.y(function(d) { return yScale(d['value']); })
.curve(d3.curveCardinal);
svg.append("path")
.datum(trendDataArr)
.attr('fill','none')
.attr("d", line(trendDataArr.filter(function(d) {
return d;
}
)
)
)
.attr('stroke', "steelblue")
.attr('stroke-width', 2);
svg.select(".x.axis")
.attr("transform", "translate(0," + (height+5) + ")")
.call(xAxis.tickFormat(d3.timeFormat("%d %b %y")))
.selectAll("text")
.style("text-anchor","end")
.attr("transform", function(d) {
return "rotate(-45) ";
});
svg.select(".y.axis")
.attr("transform", "translate(" + (margin.left) + ",0)")
.call(yAxis.tickFormat(rateFormat));
// chart title
svg.append("text")
.attr("x", (width + (margin.left + margin.right) )/ 2 -200)
.attr("y", 0 + margin.top -20)
.attr("text-anchor", "middle")
.style("font-size", "16px")
.style("font-family", "sans-serif")
.text("Zoomable chart");
//y axis label
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("y", -20)
.attr("x",0 - (height / 2))
.attr("dy", "1em")
.style("text-anchor", "middle")
.text("Rate");
function zoomed() {
var t = d3.event.transform;
xScale.domain(t.rescaleX(xScale).domain());
}
You need to call xAxis again.
function zoomed() {
console.info('zoom called');
var t = d3.event.transform;
xScale.domain(t.rescaleX(xScale).domain());
svg.select(".x.axis")
.call(xAxis.tickFormat(d3.timeFormat("%d %b %y")));
}
This is not the complete solution as it just zooms xAxis and not the plot but it might help you in zooming the plot too.
Or you might want to refer: https://bl.ocks.org/deristnochda/1ffe16ccf8bed2035ea5091ab9bb53fb
My requirement is to generate Bar Chart With Panning And Zooming Via Brushing to overcome the x axis label overlapping id data is more.I implemented bar chart panning and zooming via brushing based on below link example
http://bl.ocks.org/MartynJones87/7db0d637e178e7204c0a
I am able to get panning and zooming but x axis labels are not displayed on axis.I don't know where i did mistake.
here is the plunker link for code.
On data load the core code called is:
var xBrush = d3.svg.brush().x(min_x).on("brush", xBrushed);
// Called to re-draw the bars on the main chart when the brush on the x axis
// has been altered.
function xBrushed() {
var originalRange = main_xZoom.range();
main_xZoom.domain(xBrush.empty() ? originalRange : xBrush.extent());
x.rangeRoundBands([main_xZoom(originalRange[0]), main_xZoom(originalRange[1])], .2);
//main_x1.rangeRoundBands([0, x.rangeBand()], 0);
main.selectAll("rect")
.data(data)
.attr("width", function (d) {
return x.rangeBand();
})
.attr("x", function (d) {
// alert("d is"+JSON.stringify(d));
return x(d.letter);
});
main.select("g.x.axis").call(xAxis).selectAll(".tick text").call(wrap, x.rangeBand());
};
I was able to find two issues in your code.
The clipPath rectangle used in svg defs is also getting updated within xBrushed function. The problem is with the selector you used.
You should have used
main.selectAll(".rect") //which selects all elements with class rect in main selection.
instead of
main.selectAll("rect") //which selects all rect elements in main selection which includes clipPath rect also.
within the xBrushed function.
You will need to apply the clip-path attribute to the group element which holds the rectangle bar elements.
Working code snippet:
var margin = {
top: 20,
right: 20,
bottom: 30,
left: 40
},
width = 860 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var min_margin = {
top: height,
right: margin.right + 10,
bottom: margin.bottom,
left: margin.left + 10
},
min_height = 10,
min_width = 860 - min_margin.left - min_margin.right;
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .2);
var min_x = d3.scale.ordinal().rangeRoundBands([0, width], .2);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var min_xAxis = d3.svg.axis()
.scale(min_x)
.orient("bottom");
var main_xZoom = d3.scale.linear()
.range([0, width])
.domain([0, width]);
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(10, "%");
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
var main = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
main.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height + min_height + margin.bottom);
var mini_x_append = svg.append("g")
.attr("transform", "translate(" + min_margin.left + "," + (margin.top + height) + ")")
.attr("width", min_width);
var data = [{
"letter": "A~q112",
"frequency": 0.08167
}, {
"letter": "B~12",
"frequency": 0.01492
}, {
"letter": "C~c",
"frequency": 0.02782
}, {
"letter": "D~1a",
"frequency": 0.04253
}, {
"letter": "E~d",
"frequency": 0.12702
}, {
"letter": "F~x",
"frequency": 0.02288
}, {
"letter": "G~v",
"frequency": 0.02015
}, {
"letter": "H~xs",
"frequency": 0.06094
}, {
"letter": "I~za",
"frequency": 0.06966
}, {
"letter": "J~mj",
"frequency": 0.00153
}, {
"letter": "K~th",
"frequency": 0.00772
}, {
"letter": "L~ws",
"frequency": 0.04025
}, {
"letter": "M~qjl",
"frequency": 0.02406
}, {
"letter": "N~i",
"frequency": 0.06749
}, {
"letter": "O~p",
"frequency": 0.07507
}, {
"letter": "P~zs",
"frequency": 0.01929
}, {
"letter": "Q~rip",
"frequency": 0.00095
}, {
"letter": "R~hi",
"frequency": 0.05987
}, {
"letter": "S~eop",
"frequency": 0.06327
}, {
"letter": "T~tl",
"frequency": 0.09056
}, {
"letter": "U~se",
"frequency": 0.02758
}, {
"letter": "V~wh",
"frequency": 0.00978
}, {
"letter": "W~jl",
"frequency": 0.0236
}, {
"letter": "X~y",
"frequency": 0.0015
}, {
"letter": "Y~ty",
"frequency": 0.01974
}, {
"letter": "Z~o",
"frequency": 0.00074
}, {
"letter": "A~q12",
"frequency": 0.08167
}, {
"letter": "B~122",
"frequency": 0.01492
}, {
"letter": "C~c2",
"frequency": 0.02782
}, {
"letter": "D~1a2",
"frequency": 0.04253
}, {
"letter": "E~d2",
"frequency": 0.12702
}, {
"letter": "F~x2",
"frequency": 0.02288
}, {
"letter": "G~v2",
"frequency": 0.02015
}, {
"letter": "H~xs2",
"frequency": 0.06094
}, {
"letter": "I~za2",
"frequency": 0.06966
}, {
"letter": "J~mj2",
"frequency": 0.00153
}, {
"letter": "K~th2",
"frequency": 0.00772
}, {
"letter": "L~ws2",
"frequency": 0.04025
}, {
"letter": "M~qjl2",
"frequency": 0.02406
}, {
"letter": "N~i2",
"frequency": 0.06749
}, {
"letter": "O~p2",
"frequency": 0.07507
}, {
"letter": "P~zs2",
"frequency": 0.01929
}, {
"letter": "Q~rip2",
"frequency": 0.00095
}, {
"letter": "R~hi2",
"frequency": 0.05987
}, {
"letter": "S~eo2p",
"frequency": 0.06327
}, {
"letter": "T~tl2",
"frequency": 0.09056
}, {
"letter": "U~se2",
"frequency": 0.02758
}, {
"letter": "V~wh2",
"frequency": 0.00978
}, {
"letter": "W~jl2",
"frequency": 0.0236
}, {
"letter": "X~y2",
"frequency": 0.0015
}, {
"letter": "Y~ty2",
"frequency": 0.01974
}, {
"letter": "Z~o2",
"frequency": 0.00074
}];
x.domain(data.map(function(d) {
return d.letter;
}));
min_x.domain(data.map(function(d) {
return d.letter;
}));
y.domain([0, d3.max(data, function(d) {
return d.frequency;
})]);
var xBrush = d3.svg.brush().x(min_x).on("brush", xBrushed);
// Add the x axis
main.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + (height + min_height) + ")")
.attr("clip-path", "url(#clip)")
.call(xAxis)
.selectAll(".tick text")
.call(wrap, x.rangeBand());
var x_arc = d3.svg.arc()
.outerRadius(min_height / 2)
.startAngle(0)
.endAngle(function(d, i) {
return i ? -Math.PI : Math.PI;
});
var brush_x_grab = mini_x_append.append("g")
.attr("class", "x brush")
.call(xBrush);
brush_x_grab.selectAll(".resize").append("path")
.attr("transform", "translate(0," + min_height / 2 + ")")
.attr("d", x_arc);
brush_x_grab.selectAll("rect").attr("height", min_height);
main.append("g")
.attr("class", "y axis")
.call(yAxis);
var bar = main.append("g")
.attr("clip-path", "url(#clip)")
.selectAll(".rect")
.data(data)
.enter().append("rect")
.attr("class", "rect")
.attr("x", function(d) {
return x(d.letter);
})
.attr("width", x.rangeBand())
.attr("y", function(d) {
return y(d.frequency);
})
.attr("height", function(d) {
return height - y(d.frequency);
});
// Called to re-draw the bars on the main chart when the brush on the x axis
// has been altered.
function xBrushed() {
var originalRange = main_xZoom.range();
main_xZoom.domain(xBrush.empty() ? originalRange : xBrush.extent());
x.rangeRoundBands([main_xZoom(originalRange[0]), main_xZoom(originalRange[1])], .2);
// min_x.rangeRoundBands([0, x.rangeBand()], 0);
main.selectAll(".rect")
.data(data)
.attr("width", function(d) {
return x.rangeBand();
})
.attr("x", function(d) {
// alert("d is"+JSON.stringify(d));
return x(d.letter);
});
main.select("g.x.axis").call(xAxis).selectAll(".tick text").call(wrap, x.rangeBand());
};
// This comes from the example at http://bl.ocks.org/mbostock/7555321
// for wrapping long axis tick labels
function wrap(text, width) {
text.each(function() {
var text = d3.select(this),
words = text.text().split(/\s+/).reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 1.1, // ems
y = text.attr("y"),
dy = parseFloat(text.attr("dy")),
tspan = text.text(null).append("tspan").attr("x", 0).attr("y", y).attr("dy", dy + "em");
while (word = words.pop()) {
// console.log("wrap is"+JSON.stringify(word));
line.push(word);
tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(" "));
line = [word];
tspan = text.append("tspan").attr("x", 0).attr("y", y).attr("dy", ++lineNumber * lineHeight + dy + "em").text(word);
}
}
});
};
// Set the initial brush selections.
// svg.select(".x.brush").call(xBrush.extent(main_xZoom.domain()));
svg.select(".x.brush").call(xBrush.extent([0, 110]));
//svg.select(".y.brush").call(yBrush.extent(mini_y0.domain()));
// Forces a refresh of the brushes and main chart based
// on the selected extents.
xBrushed();
//yBrushed();
function type(d) {
d.frequency = +d.frequency;
return d;
}
g.axis path,
g.axis line {
fill: none;
stroke: black;
shape-rendering: crispEdges;
}
g.brush rect.extent {
fill-opacity: 0.2;
}
.resize path {
fill-opacity: 0.2;
}
.bar {
fill: steelblue;
}
.axis {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.x.axis path {
display: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
EDIT:
I found it is more easy to implement this functionality by converting the json data you have to the same format used at this link.
var nestedData;
var main_margin = {
top: 25,
right: 80,
bottom: 60,
left: 70
},
width = 900 - main_margin.left - main_margin.right,
mini_x_height = 10;
main_height = 525 - main_margin.top - main_margin.bottom,
mini_x_margin = {
top: main_height,
right: main_margin.right + 10,
bottom: main_margin.bottom,
left: main_margin.left + 10
},
mini_x_width = 900 - mini_x_margin.left - mini_x_margin.right,
mini_y_margin = {
top: main_margin.top + 10,
right: 0,
bottom: main_margin.bottom + 10,
left: 0
},
mini_y_width = 10,
mini_y_height = 525 - mini_y_margin.top - mini_y_margin.bottom;
var color = d3.scale.category10();
// x0 is the groups scale on the x axis.
var main_x0 = d3.scale.ordinal().rangeRoundBands([0, width], 0.2);
var mini_x0 = d3.scale.ordinal().rangeRoundBands([0, width], 0.2);
var main_xZoom = d3.scale.linear()
.range([0, width])
.domain([0, width]);
// x1 is the series scale on the x axis.
var main_x1 = d3.scale.ordinal();
var mini_x1 = d3.scale.ordinal();
// y is the value scale on the y axis.
var main_y0 = d3.scale.linear().range([main_height, 0]);
var mini_y0 = d3.scale.linear().range([mini_y_height, 0]);
var main_xAxis = d3.svg.axis()
.scale(main_x0)
.orient("bottom");
var mini_xAxis = d3.svg.axis()
.scale(mini_x0)
.orient("bottom");
var main_yAxis = d3.svg.axis()
.scale(main_y0)
.orient("left");
var mini_yAxis = d3.svg.axis()
.scale(mini_y0)
.orient("left");
var svg = d3.select("#chart").append("svg")
.attr("width", width + main_margin.left + main_margin.right)
.attr("height", main_height + main_margin.top + main_margin.bottom);
var main = svg.append("g")
.attr("transform", "translate(" + main_margin.left + "," + main_margin.top + ")");
main.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", main_height + mini_x_height + main_margin.bottom);
var mini_x = svg.append("g")
.attr("transform", "translate(" + mini_x_margin.left + "," + (main_margin.top + main_height) + ")")
.attr("width", mini_x_width);
var mini_y = svg.append("g")
.attr("width", mini_y_width)
.attr("transform", "translate(" + (main_margin.left - mini_y_width) + ", " + mini_y_margin.top + ")")
.attr("height", mini_y_height);
var data = [{
key: 'Mechanical',
values: [{
key: 'Gear',
value: 11
}, {
key: 'Bearing',
value: 8
}, {
key: 'Motor',
value: 3
}, {
key: 'Bearing',
value: 8
}, {
key: 'Motor',
value: 3
}, {
key: 'Bearing',
value: 8
}, {
key: 'Motor',
value: 3
}]
}, {
key: 'Electrical',
values: [{
key: 'Switch',
value: 19
}, {
key: 'Plug',
value: 12
}, {
key: 'Cord',
value: 11
}, {
key: 'Fuse',
value: 3
}, {
key: 'Bulb',
value: 2
}]
}, {
key: 'Hydraulic',
values: [{
key: 'Pump',
value: 4
}, {
key: 'Leak',
value: 3
}, {
key: 'Seals',
value: 1
},{
key: 'Switch',
value: 19
}, {
key: 'Plug',
value: 12
}, {
key: 'Cord',
value: 11
}, {
key: 'Fuse',
value: 3
}, {
key: 'Bulb',
value: 2
}]
}];
var res = [];
data.forEach(function(d){
res.push(d.values.map(function(o){
o.item=o.key;
o.subject = d.key; return o; }));
});
data = [].concat.apply([], res);
res.sort(function(a,b){ return a.subject<b.subject });
//console.log(data);
var seriesValues = d3.set(data.map(function(x) {
return x.item;
})).values().sort(d3.ascending);
nestedData = d3.nest()
.key(function(d) {
return d.subject;
})
.sortKeys(d3.ascending)
.sortValues(function(a, b) {
return a.item - b.item;
})
.entries(data);
var groupValues = d3.set(data.map(function(x) {
return x.subject;
})).values();
// Define the axis domains
main_x0.domain(groupValues);
mini_x0.domain(groupValues);
main_x1.domain(seriesValues).rangeRoundBands([0, main_x0.rangeBand()], 0);
mini_x1.domain(seriesValues).rangeRoundBands([0, main_x0.rangeBand()], 0);
main_y0.domain([0, d3.max(nestedData, function(d) {
return d3.max(d.values, function(d) {
return d.value;
});
})]);
mini_y0.domain(main_y0.domain());
var xBrush = d3.svg.brush().x(mini_x0).on("brush", xBrushed);
var yBrush = d3.svg.brush().y(mini_y0).on("brush", yBrushed);
// Add the x axis
main.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + (main_height + mini_x_height) + ")")
.attr("clip-path", "url(#clip)")
.call(main_xAxis)
.selectAll(".tick text")
.call(wrap, main_x0.rangeBand());
// Add the y axis
main.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + (-mini_y_width) + ", 0)")
.call(main_yAxis)
.append("text")
.attr("transform", "rotate(-90), translate(" + -(main_height / 2) + ", " + -(mini_y_width + main_margin.left - 20) + ")")
.attr("dy", ".71em")
.style("text-anchor", "middle")
.text("value");
var x_arc = d3.svg.arc()
.outerRadius(mini_x_height / 2)
.startAngle(0)
.endAngle(function(d, i) {
return i ? -Math.PI : Math.PI;
});
var brush_x_grab = mini_x.append("g")
.attr("class", "x brush")
.call(xBrush);
brush_x_grab.selectAll(".resize").append("path")
.attr("transform", "translate(0," + mini_x_height / 2 + ")")
.attr("d", x_arc);
brush_x_grab.selectAll("rect").attr("height", mini_x_height);
var y_arc = d3.svg.arc()
.outerRadius(mini_y_width / 2)
.startAngle(-(Math.PI / 2))
.endAngle(function(d, i) {
return i ? -((3 * Math.PI) / 2) : ((Math.PI) / 2);
});
var brush_y_grab = mini_y.append("g")
.attr("class", "y brush")
.call(yBrush);
brush_y_grab.selectAll(".resize").append("path")
.attr("transform", "translate(" + (mini_y_width / 2) + ", 0)")
.attr("d", y_arc);
brush_y_grab.selectAll("rect").attr("width", mini_y_width);
// Create the main bars
var bar = main.selectAll(".bars")
.data(nestedData)
.enter().append("g")
.attr("clip-path", "url(#clip)")
.attr("class", function(d) {
return d.key + "-group bar";
});
bar.selectAll("rect")
.data(function(d) {
return d.values;
})
.enter().append("rect")
.attr("class", function(d) {
return d.item;
})
.attr("transform", function(d) {
return "translate(" + main_x0(d.subject) + ",0)";
})
.attr("width", function(d) {
return main_x1.rangeBand();
})
.attr("x", function(d) {
return main_x1(d.item);
})
.attr("y", function(d) {
return main_y0(d.value);
})
.attr("height", function(d) {
return main_height - main_y0(d.value);
})
.style("fill", function(d) {
return color(d.item);
});
// Draws the series items onto a legend
var legend = svg.selectAll(".legend")
.data(seriesValues.slice())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) {
return "translate(50," + (main_margin.top + (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;
});
// Called to re-draw the bars on the main chart when the brush on the x axis
// has been altered.
function xBrushed() {
var originalRange = main_xZoom.range();
main_xZoom.domain(xBrush.empty() ? originalRange : xBrush.extent());
main_x0.rangeRoundBands([main_xZoom(originalRange[0]), main_xZoom(originalRange[1])], .1);
main_x1.rangeRoundBands([0, main_x0.rangeBand()], 0);
bar.selectAll("rect")
.attr("transform", function(d) {
return "translate(" + main_x0(d.subject) + ",0)";
})
.attr("width", function(d) {
return main_x1.rangeBand();
})
.attr("x", function(d) {
return main_x1(d.item);
});
main.select("g.x.axis").call(main_xAxis).selectAll(".tick text").call(wrap, main_x0.rangeBand());
};
// Called to re-draw the bars on the main chart when the
// brush on the y axis has been altered.
function yBrushed() {
main_y0.domain(yBrush.empty() ? mini_y0.domain() : yBrush.extent());
bar.selectAll("rect")
.attr("y", function(d) {
return main_y0(d.value);
})
.attr("height", function(d) {
return main_height - main_y0(d.value);
});
main.select("g.y.axis").call(main_yAxis);
};
// This comes from the example at http://bl.ocks.org/mbostock/7555321
// for wrapping long axis tick labels
function wrap(text, width) {
text.each(function() {
var text = d3.select(this),
words = text.text().split(/\s+/).reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 1.1, // ems
y = text.attr("y"),
dy = parseFloat(text.attr("dy")),
tspan = text.text(null).append("tspan").attr("x", 0).attr("y", y).attr("dy", dy + "em");
while (word = words.pop()) {
line.push(word);
tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(" "));
line = [word];
tspan = text.append("tspan").attr("x", 0).attr("y", y).attr("dy", ++lineNumber * lineHeight + dy + "em").text(word);
}
}
});
};
// Set the initial brush selections.
// svg.select(".x.brush").call(xBrush.extent(main_xZoom.domain()));
svg.select(".x.brush").call(xBrush.extent([0, 610]));
svg.select(".y.brush").call(yBrush.extent(mini_y0.domain()));
// Forces a refresh of the brushes and main chart based
// on the selected extents.
xBrushed();
yBrushed();
//});
g.axis path,
g.axis line {
fill: none;
stroke: black;
shape-rendering: crispEdges;
}
g.brush rect.extent {
fill-opacity: 0.2;
}
.resize path {
fill-opacity: 0.2;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="chart"></div>
I wanted to add data labels on each boxes in bars but couldn't figure out how to do it.
All examples I found on the net was getting data from simple arrays, not an external JSON file like I did.
Here is 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="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.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 x = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
var y = d3.scale.linear()
.rangeRound([height, 0]);
var color = d3.scale.ordinal()
.range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]);
var xAxis = d3.svg.axis()
.scale(x)
.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.json("data.json", function(error, data) {
if (error) throw error;
color.domain(d3.keys(data[0]).filter(function(key) { return key !== "Brand"; }));
data.forEach(function(d) {
var y0 = 0;
d.stores = color.domain().map(function(name) { return {name: name, y0: y0, y1: y0 += +d[name]}; });
d.total = d.stores[d.stores.length - 1].y1;
});
data.sort(function(a, b) { return b.total - a.total; });
x.domain(data.map(function(d) { return d.Brand; }));
y.domain([0, d3.max(data, function(d) { return d.total; })]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
var brand = svg.selectAll(".brand")
.data(data)
.enter().append("g")
.attr("class", "g")
.attr("transform", function(d) { return "translate(" + x(d.Brand) + ",0)"; });
brand.selectAll("rect")
.data(function(d) { return d.stores; })
.enter().append("rect")
.attr("width", x.rangeBand())
.attr("y", function(d) { return y(d.y1); })
.attr("height", function(d) { return y(d.y0) - y(d.y1); })
.style("fill", function(d) { return color(d.name); });
var legend = svg.selectAll(".legend")
.data(color.domain().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>
data.json
[
{
"Brand": "A",
"LAST 3 MONTHS": "22",
"LAST MONTH": "15",
"THIS YEAR": "36",
"ALL STORES": "72"
},
{
"Brand": "B",
"LAST 3 MONTHS": "10",
"LAST MONTH": "24",
"THIS YEAR": "15",
"ALL STORES": "61"
},
{
"Brand": "C",
"LAST 3 MONTHS": "10",
"LAST MONTH": "11",
"THIS YEAR": "23",
"ALL STORES": "67"
},
{
"Brand": "D",
"LAST 3 MONTHS": "10",
"LAST MONTH": "17",
"THIS YEAR": "21",
"ALL STORES": "81"
},
{
"Brand": "E",
"LAST 3 MONTHS": "10",
"LAST MONTH": "31",
"THIS YEAR": "51",
"ALL STORES": "92"
},
{
"Brand": "F",
"LAST 3 MONTHS": "10",
"LAST MONTH": "27",
"THIS YEAR": "35",
"ALL STORES": "76"
},
{
"Brand": "G",
"LAST 3 MONTHS": "10",
"LAST MONTH": "23",
"THIS YEAR": "19",
"ALL STORES": "59"
},
{
"Brand": "H",
"LAST 3 MONTHS": "32",
"LAST MONTH": "27",
"THIS YEAR": "15",
"ALL STORES": "45"
}
]
How can I show data labels on each boxes in the bars? (like: 22, 15, 36, 72 on the first bar etc.)
I want a final view on all bars like the first bar on this picture:
https://dl.dropboxusercontent.com/u/58490833/Yollanan%20Dosyalar/stackedbar.jpg
Do the same code as you did for all rectangle .. but you have to either pass original object with it to read value or you can get it from parent node like this
brand.selectAll("text")
.data(function(d) { return d.stores; })
.enter().append("text")
.attr("y", function(d) { return y(d.y1)+10; })
.attr("x", x.rangeBand()/2)
.attr("text-anchor","middle")
.style("fill",'#fff')
.text(function(d) { return d3.select(this)[0][0].parentNode.__data__[d.name]; });
here is fiddle with solution hope this is what you needed ..
I want to create a Stacked bar chart like http://bl.ocks.org/mbostock/3886208 . But I don't want to use CSV file.
How can I create Stacked chart using array or JSON data?
In csv we are using like this :
State,Post,Comment
AL,310504,552339
AK,52083,85640
How can I define data in array or json like
var data = []
do it like this
<!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 x = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
var y = d3.scale.linear()
.rangeRound([height, 0]);
var color = d3.scale.ordinal()
.range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]);
var xAxis = d3.svg.axis()
.scale(x)
.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 + ")");
var data = [
{
"State": "AL",
"Under 5 Years": 10,
"5 to 13 Years": 20,
"14 to 17 Years": 30,
"18 to 24 Years": 40,
"25 to 44 Years": 50,
"45 to 64 Years": 60,
"65 Years and Over": 70
},{
"State": "AK",
"Under 5 Years": 15,
"5 to 13 Years": 25,
"14 to 17 Years": 35,
"18 to 24 Years": 45,
"25 to 44 Years": 55,
"45 to 64 Years": 65,
"65 Years and Over": 75
}];
color.domain(d3.keys(data[0]).filter(function(key) { return key !== "State"; }));
data.forEach(function(d) {
var y0 = 0;
d.ages = color.domain().map(function(name) { return {name: name, y0: y0, y1: y0 += +d[name]}; });
d.total = d.ages[d.ages.length - 1].y1;
});
data.sort(function(a, b) { return b.total - a.total; });
x.domain(data.map(function(d) { return d.State; }));
y.domain([0, d3.max(data, function(d) { return d.total; })]);
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("Population");
var state = svg.selectAll(".state")
.data(data)
.enter().append("g")
.attr("class", "g")
.attr("transform", function(d) { return "translate(" + x(d.State) + ",0)"; });
state.selectAll("rect")
.data(function(d) { return d.ages; })
.enter().append("rect")
.attr("width", x.rangeBand())
.attr("y", function(d) { return y(d.y1); })
.attr("height", function(d) { return y(d.y0) - y(d.y1); })
.style("fill", function(d) { return color(d.name); });
var legend = svg.selectAll(".legend")
.data(color.domain().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 know late for replying to this one. I modified #heshjse's code for D3 version 4. When I tried the above code with d3 v3, it's working fine. But we had limitation to use version 4, I was getting problem bcoz of some changes in d3 v4. So adding the code which worked for me. I hope it helps.
This should work fine for Json format in d3 v4.
<!DOCTYPE html>
<html>
<head>
<title></title>
<meta charset="utf-8" />
</head>
<body>
<div id="Dash"></div>
</body>
</html>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script>
$(document).ready(function () {
drawStackChart();
});
//Draw Stack Chart
var marginStackChart = { top: 20, right: 20, bottom: 30, left: 40 },
widthStackChart = 500 - marginStackChart.left - marginStackChart.right,
heightStackChart = 300 - marginStackChart.top - marginStackChart.bottom;
var xStackChart = d3.scaleBand()
.range([0, widthStackChart])
.padding(0.1);
var yStackChart = d3.scaleLinear()
.range([heightStackChart, 0]);
var colorStackChart = d3.scaleOrdinal(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"])
var canvasStackChart = d3.select("#Dash").append("svg")
.attr("width", widthStackChart + marginStackChart.left + marginStackChart.right)
.attr("height", heightStackChart + marginStackChart.top + marginStackChart.bottom)
.append("g")
.attr("transform", "translate(" + marginStackChart.left + "," + marginStackChart.top + ")");
function drawStackChart() {
var data = [
{
"Year": "2012",
"Category1": "20",
"Category2": "5",
"Category3": "5",
"Category4": "5",
"Category5": "5",
"Category6": "5",
"Category7": "5",
"Category8": "5",
"Category9": "5"
},
{
"Year": "2013",
"Category1": "30",
"Category2": "10",
"Category3": "10",
"Category4": "10",
"Category5": "10",
"Category6": "10",
"Category7": "10",
"Category8": "10",
"Category9": "10"
},
{
"Year": "2014",
"Category1": "35",
"Category2": "15",
"Category3": "15",
"Category4": "15",
"Category5": "15",
"Category6": "15",
"Category7": "15",
"Category8": "15",
"Category9": "15"
},
{
"Year": "2015",
"Category1": "60",
"Category2": "20",
"Category3": "20",
"Category4": "20",
"Category5": "20",
"Category6": "20",
"Category7": "20",
"Category8": "20",
"Category9": "20"
},
{
"Year": "2016",
"Category1": "70",
"Category2": "40",
"Category3": "40",
"Category4": "40",
"Category5": "40",
"Category6": "40",
"Category7": "40",
"Category8": "40",
"Category9": "40"
}
];
colorStackChart.domain(d3.keys(data[0]).filter(function (key) { return key !== "Year"; }));
data.forEach(function (d) {
var y0 = 0;
d.ages = colorStackChart.domain().map(function (name) { return { name: name, y0: y0, y1: y0 += +d[name] }; });
d.total = d.ages[d.ages.length - 1].y1;
});
data.sort(function (a, b) { return b.total - a.total; });
xStackChart.domain(data.map(function (d) { return d.Year; }));
yStackChart.domain([0, d3.max(data, function (d) { return d.total; })]);
canvasStackChart.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + heightStackChart + ")")
.call(d3.axisBottom(xStackChart));
canvasStackChart.append("g")
.attr("class", "y axis")
.call(d3.axisLeft(yStackChart))
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("No Of Buildings");
var state = canvasStackChart.selectAll(".Year")
.data(data)
.enter().append("g")
.attr("class", "g")
.attr("transform", function (d) { return "translate(" + xStackChart(d.Year) + ",0)"; });
state.selectAll("rect")
.data(function (d) { return d.ages; })
.enter().append("rect")
.attr("width", xStackChart.bandwidth())
.attr("y", function (d) { return yStackChart(d.y1); })
.attr("height", function (d) { return yStackChart(d.y0) - yStackChart(d.y1); })
.style("fill", function (d) { return colorStackChart(d.name); });
var legend = canvasStackChart.selectAll(".legend")
.data(colorStackChart.domain().slice().reverse())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function (d, i) { return "translate(0," + i * 20 + ")"; });
legend.append("rect")
.attr("x", widthStackChart - 18)
.attr("width", 18)
.attr("height", 18)
.style("fill", colorStackChart);
legend.append("text")
.attr("x", widthStackChart - 24)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function (d) { return d; });
}
</script>
If you have an array, data, you can use that just like the parameter data in the csv function in the example you linked. The code within that function will work as expected, provided that your data is in the same format.
If you can set breakpoints with your browser, you can have a look at what that format is fairly easily, set one just inside the csv function call in the js and look at the data variable.