Related
Assuming I have data like this:
const data = [
{
month: 1
apples: ...,
bananas: ...,
cherries: ...,
dates: ...,
},
{
month: 2
apples: ...,
bananas: ...,
cherries: ...,
dates: ...,
},
{
month: 3
apples: ...,
bananas: ...,
cherries: ...,
dates: ...,
}
]
Going for 12 months, using keys of ['apples','bananas','cherries','dates']. The d3.stack() will produce an array for 4 bars with 12 sets of values. This makes sense. However, what if I wanted to create 12 bars with the keys broken up so it's sets of 4 values.
Is it possible to flip things on their heads in this fashion?
You just need to convert the data into the required format and flip the axis and data configurations in the chart as shown below.
Existing :
var margin = {
top: 20,
right: 160,
bottom: 35,
left: 30
};
var width = 500 - margin.left - margin.right,
height = 250 - 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 = [{
year: "2006",
redDelicious: "10",
mcintosh: "15",
oranges: "9",
pears: "6"
},
{
year: "2007",
redDelicious: "12",
mcintosh: "18",
oranges: "9",
pears: "4"
},
{
year: "2008",
redDelicious: "05",
mcintosh: "20",
oranges: "8",
pears: "2"
},
{
year: "2009",
redDelicious: "01",
mcintosh: "15",
oranges: "5",
pears: "4"
}
];
var parse = d3.time.format("%Y").parse;
// Transpose the data into layers
var dataset = d3.layout.stack()(["redDelicious", "mcintosh", "oranges", "pears"].map(function(fruit) {
return data.map(function(d) {
return {
x: parse(d.year),
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.02);
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 = ["b33040", "#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("%Y"));
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
svg.append("g")
.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 rect = groups.selectAll("rect")
.data(function(d) {
return d;
})
.enter()
.append("rect")
.attr("x", function(d) {
return x(d.x);
})
.attr("y", function(d) {
return y(d.y0 + d.y);
})
.attr("height", function(d) {
return y(d.y0) - y(d.y0 + d.y);
})
.attr("width", x.rangeBand())
.on("mouseover", function() {
tooltip.style("display", null);
})
.on("mouseout", function() {
tooltip.style("display", "none");
})
.on("mousemove", function(d) {
var xPosition = d3.mouse(this)[0] - 15;
var yPosition = d3.mouse(this)[1] - 25;
tooltip.attr("transform", "translate(" + xPosition + "," + yPosition + ")");
tooltip.select("text").text(d.y);
});
// Draw legend
var legend = svg.selectAll(".legend")
.data(colors)
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) {
return "translate(30," + i * 19 + ")";
});
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 "Anjou pears";
case 1:
return "Naval oranges";
case 2:
return "McIntosh apples";
case 3:
return "Red Delicious apples";
}
});
// Prep the tooltip bits, initial display is hidden
var tooltip = svg.append("g")
.attr("class", "tooltip")
.style("display", "none");
tooltip.append("rect")
.attr("width", 30)
.attr("height", 20)
.attr("fill", "white")
.style("opacity", 0.5);
tooltip.append("text")
.attr("x", 15)
.attr("dy", "1.2em")
.style("text-anchor", "middle")
.attr("font-size", "12px")
.attr("font-weight", "bold");
svg {
font: 10px sans-serif;
shape-rendering: crispEdges;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
}
path.domain {
stroke: none;
}
.y .tick line {
stroke: #ddd;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
New :
var margin = {
top: 20,
right: 160,
bottom: 35,
left: 30
};
var width = 500 - margin.left - margin.right,
height = 250 - 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 = [{
fruit: "redDelicious",
2006: "10",
2007: "12",
2008: "05",
2009: "01",
2010: "02"
},
{
fruit: "mcintosh",
2006: "15",
2007: "18",
2008: "20",
2009: "15",
2010: "10"
},
{
fruit: "oranges",
2006: "9",
2007: "9",
2008: "8",
2009: "5",
2010: "4"
},
{
fruit: "pears",
2006: "6",
2007: "4",
2008: "2",
2009: "4",
2010: "2"
}
];
var legends = Object.keys(data[0]);
legends.splice(legends.indexOf('fruit'), 1);
// Transpose the data into layers
var dataset = d3.layout.stack()(legends.map(function(year) {
return data.map(function(d) {
return {
x: d.fruit,
y: +d[year]
};
});
}));
// Set x, y and colors
var x = d3.scale.ordinal()
.domain(dataset[0].map(function(d) {
return d.x;
}))
.rangeRoundBands([10, width - 10], 0.02);
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 = ["b33040", "#d25c4d", "#f2b447", "#d9d574","#6aa8e0"];
// 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("%Y"));
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
svg.append("g")
.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 rect = groups.selectAll("rect")
.data(function(d) {
return d;
})
.enter()
.append("rect")
.attr("x", function(d) {
return x(d.x);
})
.attr("y", function(d) {
return y(d.y0 + d.y);
})
.attr("height", function(d) {
return y(d.y0) - y(d.y0 + d.y);
})
.attr("width", x.rangeBand())
.on("mouseover", function() {
tooltip.style("display", null);
})
.on("mouseout", function() {
tooltip.style("display", "none");
})
.on("mousemove", function(d) {
var xPosition = d3.mouse(this)[0] - 15;
var yPosition = d3.mouse(this)[1] - 25;
tooltip.attr("transform", "translate(" + xPosition + "," + yPosition + ")");
tooltip.select("text").text(d.y);
});
// Draw legend
var legend = svg.selectAll(".legend")
.data(colors)
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) {
return "translate(30," + i * 19 + ")";
});
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) {
return legends.reverse()[i];
});
// Prep the tooltip bits, initial display is hidden
var tooltip = svg.append("g")
.attr("class", "tooltip")
.style("display", "none");
tooltip.append("rect")
.attr("width", 30)
.attr("height", 20)
.attr("fill", "white")
.style("opacity", 0.5);
tooltip.append("text")
.attr("x", 15)
.attr("dy", "1.2em")
.style("text-anchor", "middle")
.attr("font-size", "12px")
.attr("font-weight", "bold");
svg {
font: 10px sans-serif;
shape-rendering: crispEdges;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
}
path.domain {
stroke: none;
}
.y .tick line {
stroke: #ddd;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
I have a bar chart with three bars overlaying each other. First the grey bar chart is rendered, then the salmon one and the the blue one. But when they are sorted this order seems to change randomly, so that sometimes a gray bar is drawn over the other two and then they can't seen.
Solution: jsFiddle
Here is a jsfiddle for this problem.
<!DOCTYPE html>
<body>
<div style="text-align:left;">
<form id="form">
<strong>Sort by: </strong><span class="clocktime-radio"><input type="radio" name="stack" checked value="clock">Clock time and place </span>
<span class="racetime-radio"><input class="racetime-radio" type="radio" name="stack" value="race">Race time </span>
<span class="handicap-radio"><input type="radio" name="stack" value="hand">Handicap </span>
</form>
</div>
<div id="race_graph">
</div>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
// set the dimensions and margins of the graph
var margin = {
top: 50,
right: 50,
bottom: 100,
left: 80
},
width = 500 - margin.left - margin.right,
height = 600 - margin.top - margin.bottom;
// Get the data
var data = [{
"name": "RT",
"clocktime": "21:33",
"handicap": "02:30",
"racetime": "19:03"
},
{
"name": "KM",
"clocktime": "22:13",
"handicap": "00:45",
"racetime": "21:28"
},
{
"name": "SD",
"clocktime": "22:15",
"handicap": "01:45",
"racetime": "20:30"
},
{
"name": "DK",
"clocktime": "22:20",
"handicap": "02:45",
"racetime": "19:35"
},
{
"name": "BD",
"clocktime": "22:21",
"handicap": "02:15",
"racetime": "20:06"
},
{
"name": "KC",
"clocktime": "22:21",
"handicap": "02:00",
"racetime": "20:21"
},
{
"name": "PM",
"clocktime": "22:22",
"handicap": "00:45",
"racetime": "21:37"
},
{
"name": "NR",
"clocktime": "22:23",
"handicap": "01:45",
"racetime": "20:38"
},
{
"name": "LM",
"clocktime": "22:25",
"handicap": "02:15",
"racetime": "20:10"
},
{
"name": "SL",
"clocktime": "22:26",
"handicap": "00:15",
"racetime": "22:11"
}
]
var parseTime = d3.timeParse("%M:%S");
var timeformat = d3.timeFormat("%M:%S")
// format the data
data.forEach(function(d) {
d.racetime = parseTime(d.racetime);
d.handicap = parseTime(d.handicap);
d.clocktime = parseTime(d.clocktime);
d.place = +d.place;
d.points = +d.points;
d.raceplace = +d.raceplace;
d.timeplace = +d.timeplace;
});
// set the domains and ranges
var x = d3.scaleBand()
.domain(data.map(function(d) {
return d.name
}))
.range([0, width]);
// temporal y-scale
var y = d3.scaleTime()
.domain([parseTime('00:00'), d3.max(data, function(d) {
return d.clocktime
// return d.handicap
})])
.range([height, 0]); //time must increase from 0 to height else racetime and handicap are inverted!!!
// spacial y-scale (race distance)
var y1 = d3.scaleLinear()
.domain([0, 1200]) //race distance
.range([height, 0]);
// points y-scale
var y2 = d3.scaleLinear()
.domain([0, 10]) //points awarded
.range([height, 0]);
//****************************
//***** Main Graph setup *****
//****************************
var svg = d3.select("#race_graph")
.data(data)
.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 + ")");
// Add the X Axis
var xAxis = d3.axisBottom(x)
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.style("font", "7px times")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("transform", "rotate(-65)");
// Add the left Y Axis
svg.append("g")
.attr("class", "axis")
.call(d3.axisLeft(y)
.ticks(7)
.tickFormat(d3.timeFormat("%M:%S")));
// text label for the y axis on left
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 0 - margin.left)
.attr("x", 0 - (height / 2))
.attr("dy", "1em")
.style("text-anchor", "middle")
.text("Time (minutes:seconds)");
//************************************************************
//******* BarChart by clocktime *******************************
//************************************************************
var rects0 = svg.selectAll(".bar")
.data(data);
var newRects0 = rects0.enter();
// Note: y2(d.points) is the y coordinate of rect
newRects0.append('rect')
.attr('x', function(d, i) {
return x(d.name) + 2;
})
.attr('width', 20)
.attr("transform", "translate(5,0)")
.style('fill', 'gray')
.attr("class", "bar")
.attr('y', function(d, i) {
return y(d.clocktime);
})
.attr('height', function(d, i) {
return height - y(d.clocktime)
});
//************************************************************
//******* BarChart by racetime *******************************
//************************************************************
var newRects1 = rects0.enter();
// Note: y2(d.points) is the y coordinate of rect
newRects1.append('rect')
.attr('class', 'bar')
.attr('x', function(d, i) {
return x(d.name) + 2;
})
.attr('width', 20)
.attr("transform", "translate(5,0)")
.style('fill', 'salmon')
.attr('y', function(d, i) {
return y(d.racetime);
})
.attr('height', function(d, i) {
return height - y(d.racetime)
});
//************************************************************
//******* BarCharts by handicap ******************************
//************************************************************
var newRects2 = rects0.enter();
newRects2.append('rect')
.attr('x', function(d, i) {
return x(d.name) + 2;
})
.attr('width', 20)
.attr("transform", "translate(5,0)")
.style('fill', 'blue')
.attr('y', function(d, i) {
return y(d.handicap);
})
.attr('height', function(d, i) {
return height - y(d.handicap)
})
.attr('class', 'bar');
d3.selectAll("input[name='stack']").on("change", change);
function change() {
var x0 = x.domain(data.sort(this.value == "clock" ?
(function(a, b) {
return (new Date(b.clocktime) - new Date(a.clocktime)) * -1;
}) : (this.value == "race") ?
(function(a, b) {
return (new Date(b.racetime) - new Date(a.racetime)) * -1;
}) : (function(a, b) {
return (new Date(b.handicap) - new Date(a.handicap)) * -1;
})).map(function(d) {
return d.name;
}))
.copy();
svg.selectAll(".bar")
.sort(function(a, b) {
return x0(a.name) - x0(b.name);
});
var transition = svg.transition().duration(750),
delay = function(d, i) {
return i * 5;
};
transition.selectAll(".bar")
.delay(delay)
.attr("x", function(d) {
return x0(d.name);
});
transition.select(".x.axis") //selects the x-axis
.call(xAxis)
.selectAll("g")
.delay(delay);
}
</script>
</body>
Maybe when I draw multiple bar charts I should not repeat the code for each as I do and that there is some way to more efficiently draw them which will get rid of the render issue I am having.
Thanks
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 grouped category bar chart. I am able to generate that graph but the problem is if x axis labels names are bigger than bar range band then label names are overlapping.because of this unable to get good look and feel as expected.I found some where that we can wrap text labels but I don't know how to achieve that.
here js and html code related to generated grouped category chart
var margin = {
top: 20,
right: 20,
bottom: 130,
left: 40
},
width = 2360 - margin.left - margin.right,
height = 560 - margin.top - margin.bottom;
var color = {
Mechanical: '#4A7B9D',
Electrical: '#54577C',
Hydraulic: '#ED6A5A'
};
var barPadding = 40;
var data = [
{
"key" : "portFolio1",
"values" :[
{
"key" : "project1",
"values" :[
{
"key" : "config1",
"values" :[
{
"key" :"date1",
"value": 12
},
{
"key" : "date2",
"value": 10
},
{
"key" : "date3",
"value": 2
}
],
},
{
"key" : "configurationwithvalue",
"values" :[
{
"key" :"date1asa",
"value": 10
},
{
"key" : "date2",
"value": 10
},
{
"key" : "date3",
"value": 2
}
],
},
{
"key" : "config2",
"values" :[
{
"key" : "date1",
"value": 1
},
{
"key" : "date3",
"value": 14
}
],
}
],
},
{
"key" : "projct2",
"values" :[
{
"key" : "congi1",
"values" :[
{
"key" : "date2",
"value" :1
},
{
"key" : "date3",
"value": 10
}
],
}
],
}
] ,
},
{
"key" : "portfolio2",
"values" :[
{
"key" : "portfolio2_project1",
"values" :[
{
"key" : "congie1",
"values" : [
{
"key" : "date12",
"value": 23
},
{
"key" : "date1",
"value" : 1
}
],
}
],
}
],
},
{
"key" : "portFolio3",
"values" :[
{
"key" : "project_portfolio3",
"values" :[
{
"key" : "congi1",
"values" :[
{
"key" :"date1",
"value": 12
}
],
},
{
"key" : "congi2",
"values" :[
{
"key" : "date1",
"value": 1
},
{
"key" : "date3",
"value": 14
}
],
}
],
},
{
"key" : "projct2_prortfolio_23",
"values" :[
{
"key" : "congi1",
"values" :[
{
"key" : "date2",
"value" :1
},
{
"key" : "date3",
"value": 10
}
],
}
],
}
] ,
}
]
var rangeBands = [];
var cummulative = 0; // to get transform position of categories 'portfolio1,portfolio2,portfolio3 on x axis scale
data.forEach(function(val, i) {
var valLength = 0; // to get position of x axis for categories 'portfolio1,portfolo2,portfolio3 on x axis scale
val.cummulative = cummulative;
var cum = 0; // to get trnsfrom position of categories 'projct1,project2,project3' on x axis scale
val.values.forEach(function(values) {
var valLe = 0; // to get position of x axis for categories 'projct1,project2,project3' on x axis scale
values.cummulative2 = cum;
values.parentKey = val.key;
var cum3 = 0; // to get trnsfrom position of categories 'config1,config2...etc on x axis scale
values.values.forEach(function(values) { // config level
values.cummulative3 = cum3;
values.parentKey = values.key;
values.values.forEach(function(values) {
cum3 = cum3 +1;
valLe = valLe + 1;
cum = cum+1;
valLength = valLength +1 ;
cummulative = cummulative+1;
values.parentKey = values.key;
rangeBands.push(i);
})
})
values.valueLength2 = valLe;
})
val.valueLength = valLength;
});
// set x axis domain adn range
var x_category = d3.scale.linear()
.range([0, width]);
var x_defect = d3.scale.ordinal().domain(rangeBands).rangeRoundBands([0, width], .1);
var x_category_domain = x_defect.rangeBand() * rangeBands.length*3;
x_category.domain([0, x_category_domain]);
// y axis domain and range
var y = d3.scale.linear()
.range([height, 0]);
y.domain([0, d3.max(data, function(cat) {
return d3.max(cat.values, function(def) {
return d3.max(def.values, function(def) {
return d3.max(def.values, function(def) {
return def.value;
});
});
});
})]);
// x axis scale
var category_axis = d3.svg.axis()
.scale(x_category)
.orient("bottom");
// y axis scale
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)
.style('background-color', 'EFEFEF')
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
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("Value");
//add all category groups of 'profolio1,portfolio2,portfolio3 ..etc for svg
var category_g = svg.selectAll(".category")
.data(data)
.enter().append("g")
.attr("class", function(d) {
return 'category category-' + d.key;
})
.attr("transform", function(d) {
return "translate(" + x_category((d.cummulative * x_defect.rangeBand())) + ",0)";
});
//add all category groups of projct1,project2 etc for every category group of category_g
var category_g1 = category_g.selectAll(".category1")
.data(function(d) {
return d.values;
})
.enter().append("g")
.attr("class", function(d) {
return 'category category1-' + d.key;
})
.attr("transform", function(d) {
return "translate(" + x_category((d.cummulative2 * x_defect.rangeBand())) + ",0)";
})
//add all category groups of 'congif1,config2' etc for every category group of category_g1
var category_g2 = category_g1.selectAll(".category2")
.data(function(d) {
return d.values;
})
.enter().append("g")
.attr("class", function(d) {
return 'category category2-' + d.key;
})
.attr("transform", function(d) {
return "translate(" + x_category((d.cummulative3 * x_defect.rangeBand())) + ",0)";
})
// lables for category_g
var category_label = category_g.selectAll(".category-label")
.data(function(d) {
return [d];
})
.enter().append("text")
.attr("class", function(d) {
return 'category-label category-label-' + d.key;
})
.attr("transform", function(d) {
var x_label = x_category((d.valueLength * x_defect.rangeBand() + barPadding) / 2);
var y_label = height + 120;
return "translate(" + x_label + "," + y_label + ")";
})
.text(function(d) {
return d.key;
})
.attr('text-anchor', 'middle');
// lines to separate each category_g labels
category_g.append("line")
.attr("x1", function(d) {
return x_category((d.valueLength * x_defect.rangeBand()+ barPadding));
})
.attr("y1", 0)
.attr("x2", function(d) {
return x_category((d.valueLength * x_defect.rangeBand()+ barPadding));
})
.attr("y2", 120)
.attr("transform", function(d) {
var x_label = 0;
var y_label = height;
return "translate(" + x_label + "," + y_label + ")";
})
.style("stroke-width", 1)
.style("stroke", "red")
.style("fill", "none");
// lables for category_g1
var category_label1 = category_g1.selectAll(".category-label1")
.data(function(d) {
return [d];
})
.enter().append("text")
.attr("class", function(d) {
return 'category-label category-label-' + d.key;
})
.attr("transform", function(d) {
var x_label = x_category((d.values.length * x_defect.rangeBand()+ barPadding) / 2);
var y_label = height + 80;
return "translate(" + x_label + "," + y_label + ")";
})
.text(function(d) {
return d.key;
})
.attr('text-anchor', 'middle');
// lines to separate each category_g1 labels
category_g1.append("line")
.attr("x1", function(d) {
return x_category((d.valueLength2* x_defect.rangeBand()+ barPadding));
})
.attr("y1", 0)
.attr("x2", function(d) {
return x_category((d.valueLength2 * x_defect.rangeBand()+ barPadding));
})
.attr("y2", 80)
.attr("transform", function(d) {
var x_label = 0;
var y_label = height;
return "translate(" + x_label + "," + y_label + ")";
})
.style("stroke-width", 1)
.style("stroke", "blue")
.style("fill", "none");
// lables for category_g2
var category_label2 = category_g2.selectAll(".category-label2")
.data(function(d) {
return [d];
})
.enter().append("text")
.attr("class", function(d) {
return 'category-label category-label2-' + d.key;
})
.attr("transform", function(d) {
var x_label = x_category((d.values.length * x_defect.rangeBand()) / 2);
var y_label = height + 50;
return "translate(" + x_label + "," + y_label + ")";
})
.text(function(d) {
return d.key;
})
.attr('text-anchor', 'middle');
// lines to separate each category_g2 labels
category_g2.append("line")
.attr("x1", function(d) {
return x_category((d.values.length* x_defect.rangeBand()+ barPadding));
})
.attr("y1", 0)
.attr("x2", function(d) {
return x_category((d.values.length * x_defect.rangeBand()+ barPadding));
})
.attr("y2", 60)
.attr("transform", function(d) {
var x_label = 0;
var y_label = height;
return "translate(" + x_label + "," + y_label + ")";
})
.style("stroke-width", 1)
.style("stroke", "black")
.style("fill", "none");
// lables for defect_g
var defect_g = category_g2.selectAll(".defect")
.data(function(d) {
return d.values;
})
.enter().append("g")
.attr("class", function(d) {
return 'defect defect-' + d.key;
})
.attr("transform", function(d, i) {
return "translate(" + x_category((i * x_defect.rangeBand())) + ",0)";
});
var defect_label = defect_g.selectAll(".defect-label")
.data(function(d) {
return [d];
})
.enter().append("text")
.attr("class", function(d) {
return 'defect-label defect-label-' + d.key;
})
.attr("transform", function(d) {
var x_label = x_category((x_defect.rangeBand() + barPadding) / 2);
var y_label = height + 10;
return "translate(" + x_label + "," + y_label + "),rotate(-90)";
})
.text(function(d) {
return d.key;
})
.attr('text-anchor', 'middle');
var rects = defect_g.selectAll('.rect')
.data(function(d) {
return [d];
})
.enter().append("rect")
.attr("class", "rect")
.attr("width", x_category(x_defect.rangeBand() - barPadding))
.attr("x", function(d) {
return x_category(barPadding);
})
.attr("y", function(d) {
return y(d.value);
})
.attr("height", function(d) {
return height - y(d.value);
});
// to display values on top of bar chart
defect_g.selectAll("text.bar")
.data(function(d) {
return [d];
})
.enter().append("text")
.attr("class", "rect1")
.attr("text-anchor", "middle")
.attr("x", function(d) { return x_category(x_defect.rangeBand()-barPadding)/2; })
.attr("y", function(d) { return y(d.value); })
.text(function(d) { return d.value+"%"; });
<!DOCTYPE html>
<html>
<head>
<script data-require="d3#3.5.3" data-semver="3.5.3" src="https://d3js.org/d3.v3.min.js"></script>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<script src="script2.js"></script>
</body>
</html>
I think rotating labels will do good with area without overlapping.
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text")
.attr("y", 0)
.attr("x", 9)
.attr("dy", ".35em")
.attr("transform", "rotate(90)")
.style("text-anchor", "start");
or in order to wrap text:
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll(".tick text")
.call(wrap, x.rangeBand());
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);
}
}
});
}
Here are examples of :
Rotating axis labels : http://bl.ocks.org/mbostock/4403522
Wrapping axis labels : https://bl.ocks.org/mbostock/7555321
I have a grouped Bar Chart and I am rotating the x axis text. But the x axis text is not properly aligned under the bar. How can i make the x axis text aligned closely to the particular bar?
I think something is going wrong in the x axis text rotation transform attribute.
Here is the code:
the html
<!DOCTYPE html>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://labratrevenge.com/d3-tip/javascripts/d3.tip.v0.6.3.js">
</script>
<body>
<div id="chartdiv" style="height: 500px; width: 600px;"> </div>
</body>
and here is the javascript
var data = [
{
"PAG": "FACT",
"Projects": "3",
"High Level Design": "1",
"Business Requirements": "1",
"Change Impact Document": "2",
"MAC Reviews": "3"
}
,
{
"PAG": "Advisory Platforms & Solutions",
"Projects": "1", "MAC Reviews": "1"
},
{
"PAG": "Capital Markets",
"Projects": "2" },
{
"PAG": "Field Management Home Office",
"Projects": "3"
},
{
"PAG": "Institutional Wealth Services",
"Projects": "1",
"Business Requirements": "1",
"Change Impact Document": "1"
},
{
"PAG": "Traditional Invest Products",
"Projects": "2"
},
{
"PAG": "WM Operations",
"Projects": "2",
"High Level Design": "2",
"Low Level Design": "1"
}
];
function keysLen(obj) {
count = 0;
index = 0;
obj.map(function (d, i) {
if (d3.keys(d).length > count) {
count = d3.keys(d).length;
index = i
}
})
return obj[index];
}
var margin = { top: 20, right: 20, bottom: 30, left: 40 },
width = parseInt(d3.select("#chartdiv").style("width")) - margin.left - margin.right,
height = parseInt(d3.select("#chartdiv").style("height")) - margin.top - margin.bottom,
padding = 100;
//var border = 1; var bordercolor = 'black';
var x0 = d3.scale.ordinal().domain(data.map(function (d) { return d.PAG; }))
.rangeRoundBands([0, width - padding], .1);
//var x0 = d3.scale.ordinal()
// .rangeRoundBands([0, width], .1);
var x1 = d3.scale.ordinal();
var y = d3.scale.linear().range([height, 0]);
var ageNames = d3.keys(keysLen(data)).filter(function (d) {
return d != "PAG"
});
var p = d3.scale.category20();
var r = p.range(); // ["#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd",
var color = d3.scale.ordinal().range(r);
var xAxis = d3.svg.axis()
.scale(x0)
.tickSize(0)
//.tickPadding(8)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickFormat(d3.format(".0d"));
//add tooltip------------------------------------------------
var tooltip = d3.select("#chartdiv")
.append("div")
.attr("class", "d3-tip")
.style("position", "absolute")
.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 + ")");
data.forEach(function (d) {
d.ages = ageNames.map(function (name) {
return { name: name, value: +d[name] };
});
});
x0.domain(data.map(function (d) { return d.PAG; }));
//x0.domain(data.map(function (d) { return d.PAG; })).rangeRoundBands([0, width - padding], .1);
x1.domain(ageNames).rangeRoundBands([0, x0.rangeBand()]);
y.domain([0, d3.max(data, function (d) { return d3.max(d.ages, function (d) { return d.value; }); })]).nice().range([height - padding, 0]);
var tip = d3.tip()
.attr('class', 'd3-tip')
.offset([-10, 0])
.html(data, function (d) {
return "<strong>Total Count:</strong> <span style='color:#1F497D'>" + d.ages.value + "</span>";
})
//svg.call(tip);
var gXAxis = svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + (height - padding) + ")")
.call(xAxis);
gXAxis.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("transform", "rotate(-30)");
// xAxis label
svg.append("text")
.attr("transform", "translate(" + (width / 2) + " ," + (height + margin.bottom - 5) + ")")
.style("text-anchor", "middle")
.attr("class", "subtitle")
.text("Process Area Group");
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("x", -(height - padding) / 2)
.attr("y", 0 - margin.left)
//.attr("y", -margin.bottom)
.attr("dy", "1em")
.style("text-anchor", "middle")
.text("Artifacts")
.attr("class", "subtitle");
var state = svg.selectAll(".PAG")
.data(data)
.enter().append("g")
.attr("class", "PAG")
.attr("transform", function (d) { return "translate(" + x0(d.PAG) + ",0)"; });
state.selectAll("rect")
.data(function (d) { return d.ages; })
.enter().append("rect")
.attr("width", x1.rangeBand())
.attr("x", function (d) { return x1(d.name); })
.attr("y", function (d) { return y(d.value); })
.attr("height", function (d) { return height - padding - y(d.value); })
.style("fill", function (d) { return color(d.name); })
.on("mouseover", function (d) {
var pos = d3.mouse(this);
tooltip
.transition()
.duration(500)
.style("opacity", 1);
tooltip.html('<strong>' + d.name + ':' + '</strong>' + '<span style=color:#1F497D>' + d.value + '</span>')
.style("left", d3.event.x + "px")
.style("top", d3.event.y + "px")
//.text(d.name + ":" + d.value);
})
.on("mouseout", function () {
tooltip.style("opacity", 0);
});
//Adding Legend for the Grouped Bar Chart
var legend = svg.selectAll(".legend")
.data(ageNames.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", 15)
.attr("height", 15)
.style("fill", color);
legend.append("text")
.attr("x", width - 24)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function (d) { return d; });
svg.append("text")
.text("Project Artifacts By PAG")
.attr("x", width / 2)
.attr("y", 0 - (margin.top / 2))
.attr("text-anchor", "middle")
.style("text-decoration", "underline")
.attr("class", "title");
Please find the below mentioned working jsfiddle for that http://jsfiddle.net/qAHC2/1246/