I have a plunker here https://plnkr.co/edit/hBWoIIyzcHELGyewOyZE?p=preview
I'm trying to create a simple stacked bar chart.
The bars go above the top of the chart which I think is a problem with the domain
I also need a scale on the y axis which I think is to do with the y domain.
Is it the y domain that controls the height of the bars and scales shown on the y axis
y.domain([0, d3.max(data, (d)=>{
return d
})])
This is a list of the issues so far:
First, your y domain is not correctly set. It should use the stacked data:
y.domain([0, d3.max(stackedSeries, function(d) {
return d3.max(d, function(d) {
return d[0] + d[1];
});
})])
Second, the math for the y and height of the rectangles is wrong. It should be:
.attr('height', (d) => {
return y(d[0]) - y(d[0] + d[1]);
})
.attr('y', (d) => {
return y(d[0] + d[1]);
})
Finally, use the x scale for the x position:
.attr('x', (d, i) => {
return x(d.data.day)
})
Here is the code with those changes:
var margin = {
top: 40,
right: 20,
bottom: 40,
left: 40
}
var width = 400 - margin.left - margin.right
var height = 500 - margin.top - margin.bottom
var data = [{
day: 'Mon',
apricots: 120,
blueberries: 180,
cherries: 100
},
{
day: 'Tue',
apricots: 60,
blueberries: 185,
cherries: 105
},
{
day: 'Wed',
apricots: 100,
blueberries: 215,
cherries: 110
},
{
day: 'Thu',
apricots: 150,
blueberries: 330,
cherries: 105
},
{
day: 'Fri',
apricots: 120,
blueberries: 240,
cherries: 105
}
];
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 colors = ['#FBB65B', '#513551', '#de3163'];
var stack = d3.stack()
.keys(['apricots', 'blueberries', 'cherries']);
var stackedSeries = stack(data);
// Create a g element for each series
var g = d3.select('g')
.selectAll('g.series')
.data(stackedSeries)
.enter()
.append('g')
.classed('series', true)
.style('fill', (d, i) => {
return colors[i];
});
var x = d3.scaleBand()
.rangeRound([0, width])
.padding(0.1)
var y = d3.scaleLinear()
.range([height, 0])
x.domain(data.map((d) => {
return d.day
}))
y.domain([0, d3.max(stackedSeries, function(d) {
return d3.max(d, function(d) {
return d[0] + d[1];
});
})])
svg.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0, ' + height + ')')
.call(d3.axisBottom(x))
svg.append('g')
.attr('class', 'y axis')
.call(d3.axisLeft(y))
// For each series create a rect element for each day
g.selectAll('rect')
.data((d) => {
return d;
})
.enter()
.append('rect')
.attr('height', (d) => {
return y(d[0]) - y(d[0] + d[1]);
})
.attr('y', (d) => {
return y(d[0] + d[1]);
})
.attr('x', (d, i) => {
return x(d.data.day)
})
.attr('width', x.bandwidth())
.style("stroke", "#ccc");
<script src="https://d3js.org/d3.v4.min.js"></script>
Related
d3.json("data2.json", function(error, data) {
data.forEach(function(d) {
d.Total = +d.Total;
});
var width = 200,
height = 50;
var margin = {
top: 10,
right: 10,
bottom: 30,
left: 10
};
var svg = d3.select('body')
.append('svg')
.attr('width', '20%')
.attr('height', '20%')
.attr('viewBox', '0 0 ' + width + ' ' + height)
.append('g');
width = width - margin.left - margin.right,
height = height - margin.top - margin.bottom;
svg.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
var xScale = d3.scaleLinear()
.range([0, width])
var yScale = d3.scaleBand()
.range([0, height])
.padding(0.1);
xScale.domain([0, d3.sum(data, function(d) {
return d.Total;
})]);
var x_axis = svg.append('g')
.attr('class', 'axis')
.attr('padding', 1)
.attr('transform', 'translate(' + 0 + ',' + height + ')');
var keys = data.map(function(d) {
return d.Type;
});
var newData = [{}];
data.forEach(function(d) {
newData[0][d.Type] = d.Total
});
var stack = d3.stack()
.keys(keys);
var series = stack(newData);
var colorScale = d3.scaleOrdinal()
.domain([0, 12])
.range(d3.schemeCategory10);
var bars = svg.selectAll()
.data(series)
.enter()
.append('g')
.attr('fill', function(d) {
return colorScale(d.key);
})
.selectAll('rect')
.data(function(d) {
return d;
})
.enter()
.append('rect')
.attr('x', function(d, i) {
return xScale(d[0]) ;
})
.attr('width', function(d, i) {
return xScale(d[1]) - xScale(d[0]);
})
.attr("height", yScale.bandwidth());
});
This is the code I tried. I tried adding padding to g as well as x. It does not seem to be working. I just need a horizontal single stacked bar chart along with a tooltip. I can add the tooltip later but I just need somebody to help me figure this out. I have been struggling for far too long on this. Below is the data I am using.
[
{
"Type": "Pending Review",
"Total": 3209,
"Percent": "23.90%"
},
{
"Type": "Audit Finding",
"Total": 2715,
"Percent": "20.22%"
},
{
"Type": "No Issues",
"Total": 1675,
"Percent": "12.50%"
}
]
The easiest way that I found is to subtract few pixels from the width of the bar obtained with the x-axis mapping object (xScale in your case). You can keep the positioning (x and y values) as they are. So you can just change this line:
.attr('width', function(d, i) {
return xScale(d[1]) - xScale(d[0]);
})
like this:
.attr('width', function(d, i) {
return xScale(d[1]) - xScale(d[0]) - 2;
})
The -2 do the trick.
https://codepen.io/bemipefe/pen/ExyWeYm
My data for a horizontal bar graph is an array of objects that look like this:
{value: -10, dataset:"Corvette", year: "1975"}. The "dataset" labels are on the y axis. I would like to append the "year" label to the "dataset" label, so the labels on the y axis would look like this:
Corvette 1975
So far I can add one or the other to the Y axis but not both. Here is the code I have:
var margin = {top: 30, right: 10, bottom: 50, left: 50},
width = 500,
height = 300;
var data = [{value: -10, dataset:"Corvette", year: "1975"},
{value: 40, dataset:"Lumina", year: "1975"},
{value: -10, dataset:"Gran Torino", year: "1971"},
{value: -50, dataset:"Pomtiac GTO", year: "1964"},
{value: 30, dataset:"Mustang", year: "19655"},
{value: -20, dataset:"Camaro", year: "1973"},
{value: -70, dataset:"Firebird", year: "1975"}];
// Add svg to
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 + ')');
// set the ranges
var y = d3.scaleBand()
.range([height, 0])
.padding(0.1);
var x = d3.scaleLinear()
.range([0, width]);
// Scale the range of the data in the domains
x.domain(d3.extent(data, function (d) {
return d.value;
}));
y.domain(data.map(function (d) {
return d.dataset;
}));
// append the rectangles for the bar chart
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", function (d) {
return "bar bar--" + (d.value < 0 ? "negative" : "positive");
})
.attr("x", function (d) {
return x(Math.min(0, d.value));
})
.attr("y", function (d) {
return y(d.dataset);
})
.attr("width", function (d) {
return Math.abs(x(d.value) - x(0));
})
.attr("height", y.bandwidth());
// add the x Axis
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// add the y Axis
let yAxisGroup = svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + x(0) + ",0)")
.call(d3.axisRight(y));
yAxisGroup.selectAll('.tick')
.data(data)
.select('text')
.attr('x', function(d,i){return d.value<0?9:-9})
.style('text-anchor', function(d,i){return d.value<0?'start':'end'})
Here is the fiddle:
https://jsfiddle.net/Kavitha_2817/2e1xLxLc/
You could map a concatenated string of your d.dataset and d.year to the y scale, and then use the same concatenated string when positioning your rects using that y scale.
The y axis will then use that concatenated string.
Example:
https://jsfiddle.net/2e1xLxLc/4/
Relevant code:
//create a reusable function to concatenate the values you want to use
function yValue(d) { return d.dataset + " " + d.year }
// Scale the range of the data in the domains
x.domain(d3.extent(data, function (d) {
return d.value;
}));
y.domain(data.map(function(d){ return yValue(d) }));
// append the rectangles for the bar chart
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", function (d) {
return "bar bar--" + (d.value < 0 ? "negative" : "positive");
})
.attr("x", function (d) {
return x(Math.min(0, d.value));
})
.attr("y", function (d) {
return y(yValue(d));
})
.attr("width", function (d) {
return Math.abs(x(d.value) - x(0));
})
.attr("height", y.bandwidth());
If you (for any reason) want to keep the same domain, get the year using tickFormat:
.call(d3.axisRight(y)
.tickFormat(function(d) {
//filter the data array according to 'd', which is 'dataset'
var filtered = data.filter(function(e) {
return e.dataset === d;
})[0];
//get the year in the 'filtered' object using 'filtered.year'
return d + " " + filtered.year
})
);
Here is your code with that change:
var margin = {
top: 30,
right: 10,
bottom: 50,
left: 50
},
width = 500,
height = 300;
var data = [{
value: -10,
dataset: "Corvette",
year: "1975"
}, {
value: 40,
dataset: "Lumina",
year: "1975"
}, {
value: -10,
dataset: "Gran Torino",
year: "1971"
}, {
value: -50,
dataset: "Pomtiac GTO",
year: "1964"
}, {
value: 30,
dataset: "Mustang",
year: "19655"
}, {
value: -20,
dataset: "Camaro",
year: "1973"
}, {
value: -70,
dataset: "Firebird",
year: "1975"
}];
// Add svg to
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 + ')');
// set the ranges
var y = d3.scaleBand()
.range([height, 0])
.padding(0.1);
var x = d3.scaleLinear()
.range([0, width]);
// Scale the range of the data in the domains
x.domain(d3.extent(data, function(d) {
return d.value;
}));
y.domain(data.map(function(d) {
return d.dataset;
}));
// append the rectangles for the bar chart
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", function(d) {
return "bar bar--" + (d.value < 0 ? "negative" : "positive");
})
.attr("x", function(d) {
return x(Math.min(0, d.value));
})
.attr("y", function(d) {
return y(d.dataset);
})
.attr("width", function(d) {
return Math.abs(x(d.value) - x(0));
})
.attr("height", y.bandwidth());
// add the x Axis
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// add the y Axis
let yAxisGroup = svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + x(0) + ",0)")
.call(d3.axisRight(y)
.tickFormat(function(d) {
var filtered = data.filter(function(e) {
return e.dataset === d;
})[0];
return d + " " + filtered.year
})
);
yAxisGroup.selectAll('.tick')
.data(data)
.select('text')
.attr('x', function(d, i) {
return d.value < 0 ? 9 : -9
})
.style('text-anchor', function(d, i) {
return d.value < 0 ? 'start' : 'end'
})
<style> .bar--positive {
fill: steelblue;
}
.bar--negative {
fill: darkorange;
}
</style>
<script src="https://d3js.org/d3.v4.min.js"></script>
I am new to D3 JS and looking for a customize solution which is not available out of the box in d3 JS.
Below code produced a bar chart which denotes no. of students against 3 different classes,
Question, Can I show Circle instead of bar? please suggest some code? Thanks!
//data
let data = [{ "noOfStudents": 30, "ClassName": "Class 1" }, { "noOfStudents": 42, "ClassName": "Class 2" }, { "noOfStudents": 38, "ClassName": "Class 3" }];
// set the dimensions and margins of the graph
var margin = { top: 20, right: 20, bottom: 30, left: 40 },
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
// set the ranges
var x = d3.scaleBand().range([0, width]).padding(0.1);
var y = d3.scaleLinear().range([height, 0]);
var svg = d3.select("#chart").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 + ")");
// get and format the data
data.forEach(function (d) {
d.noOfStudents = +d.noOfStudents;
});
// Scale the range of the data in the domains
x.domain(data.map(function (d) { return d.ClassName; }));
y.domain([0, d3.max(data, function (d) { return d.noOfStudents; })]);
// append the rectangles for the bar chart
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function (d) { return x(d.ClassName); })
.attr("width", x.bandwidth())
.attr("y", function (d) { return y(d.noOfStudents); })
.attr("height", function (d) { return height - y(d.noOfStudents); })
.text(function (d) { return d.noOfStudents; });
// add the x Axis
svg.append("g").attr("transform", "translate(0," + height + ")").call(d3.axisBottom(x));
// add the y Axis
svg.append("g").call(d3.axisLeft(y));
Instead of rectangles, just append circles:
svg.selectAll(".bar")
.data(data)
.enter().append("circle")
.attr("class", "bar")
.attr("cx", function (d) { return x(d.ClassName); })
.attr("cy", function (d) { return y(d.noOfStudents); })
.attr("r", 30)
.text(function (d) { return d.noOfStudents; });
And change your band scale for a point scale:
var x = d3.scalePoint()
.range([0, width])
.padding(0.4);
Here is a fiddle: https://jsfiddle.net/kks4gcL3/
I am trying to display stak chart using d3 js. I also tried with log scale but did not get to work.
Aug month staked bar not visible properly or there may be more cases like this.
How to make it visible properly using log scale?
var margins = {
top: 12,
left: 48,
right: 24,
bottom: 24
},
legendPanel = {
width: 180
},
width = 500 - margins.left - margins.right - legendPanel.width,
height = 100 - margins.top - margins.bottom,
dataset = [{
data: [{
month: 'Aug',
count: 1
}, {
month: 'feb',
count: 234
}, {
month: 'mar',
count: 345
}],
name: 'Series #1'
}, {
data: [{
month: 'Aug',
count: 3
}, {
month: 'feb',
count: 267
}, {
month: 'mar',
count: 573
}],
name: 'Series #2'
},
{
data: [{
month: 'Aug',
count: 20
}, {
month: 'feb',
count: 267
}, {
month: 'mar',
count: 573
}],
name: 'Series #3'
}
],
series = dataset.map(function (d) {
return d.name;
}),
dataset = dataset.map(function (d) {
return d.data.map(function (o, i) {
// Structure it so that your numeric
// axis (the stacked amount) is y
return {
y: o.count,
x: o.month
};
});
}),
stack = d3.layout.stack();
stack(dataset);
var dataset = dataset.map(function (group) {
return group.map(function (d) {
// Invert the x and y values, and y0 becomes x0
return {
x: d.y,
y: d.x,
x0: d.y0
};
});
}),
svg = d3.select('body')
.append('svg')
.attr('width', width + margins.left + margins.right + legendPanel.width)
.attr('height', height + margins.top + margins.bottom)
.append('g')
.attr('transform', 'translate(' + margins.left + ',' + margins.top + ')'),
xMax = d3.max(dataset, function (group) {
return d3.max(group, function (d) {
return d.x + d.x0;
});
}),
xScale = d3.scale.linear()
.domain([0, xMax])
.range([0, width]),
months = dataset[0].map(function (d) {
return d.y;
}),
_ = console.log(months),
yScale = d3.scale.ordinal()
.domain(months)
.rangeRoundBands([0, height], .1),
xAxis = d3.svg.axis()
.scale(xScale)
.orient('bottom'),
yAxis = d3.svg.axis()
.scale(yScale)
.orient('left'),
colours = d3.scale.category10(),
groups = svg.selectAll('g')
.data(dataset)
.enter()
.append('g')
.style('fill', function (d, i) {
return colours(i);
}),
rects = groups.selectAll('rect')
.data(function (d) {
return d;
})
.enter()
.append('rect')
.attr('x', function (d) {
return xScale(d.x0);
})
.attr('y', function (d, i) {
return yScale(d.y);
})
.attr('height', function (d) {
return yScale.rangeBand();
})
.attr('width', function (d) {
return xScale(d.x);
})
.on('mouseover', function (d) {
var xPos = parseFloat(d3.select(this).attr('x')) / 2 + width / 2;
var yPos = parseFloat(d3.select(this).attr('y')) + yScale.rangeBand() / 2;
d3.select('#tooltip')
.style('left', xPos + 'px')
.style('top', yPos + 'px')
.select('#value')
.text(d.x);
d3.select('#tooltip').classed('hidden', false);
})
.on('mouseout', function () {
d3.select('#tooltip').classed('hidden', true);
})
svg.append('g')
.attr('class', 'axis')
.attr('transform', 'translate(0,' + height + ')')
.call(xAxis);
svg.append('g')
.attr('class', 'axis')
.call(yAxis);
svg.append('rect')
.attr('fill', 'yellow')
.attr('width', 160)
.attr('height', 30 * dataset.length)
.attr('x', width + margins.left)
.attr('y', 0);
series.forEach(function (s, i) {
svg.append('text')
.attr('fill', 'black')
.attr('x', width + margins.left + 8)
.attr('y', i * 24 + 24)
.text(s);
svg.append('rect')
.attr('fill', colours(i))
.attr('width', 60)
.attr('height', 20)
.attr('x', width + margins.left + 90)
.attr('y', i * 24 + 6);
});
sample code jsfiddle
There is a built in log-scale in d3, but some more modifications are needed.
The first thing you need to do is change your xScale to be logarithmic:
xScale = d3.scale.log()
.domain([0.5, xMax])//The x-min can not be 0!
.range([0, width]),
Note that xmin is 0.5 instead of 0. Because 0 can not exist in a logarithmic scale. And putting anything larger than 1 would make the Series #1 of august not visable since it is 1.
The second thing you need is to calculate the width in a different way since your scale is not linear anymore, i.e. 10->20 is wider than 110->120:
.attr('width', function (d) {
return xScale(d.x0+d.x)- (d.x0==0 ? 0 : xScale(d.x0));
})
This simply takes the position of the end-point (d.x0+d.x) and subtract the position of the startpoint d.x0. Not that if d.x0 == 0, 0 is used as start-pos since 0 do not exist in xScale
And finaly, default is to show ticks as 1e+02 for example, so change the format for the tick by
xAxis = d3.svg.axis()
.scale(xScale)
.orient('bottom')
.ticks(0, '.1s'),
See jsfiddle
Saw this graph on JSFiddle. I modified it with my own data, but I'd like to have a vertical bar chart instead of a horizontal. Here's what it looks like now. I tried fiddling with the X and Y axes and can't seem to get it right. Any help would be greatly appreciated.
Here's my code:
var margins = {
top: 12,
left: 48,
right: 24,
bottom: 40
},
legendPanel = {
width: 180
},
width = 400 - margins.left - margins.right - legendPanel.width,
height = 200 - margins.top - margins.bottom,
dataset = [{
data: [{
year: '2009',
count: 80
}, {
year: '2010',
count: 79
}, {
year: '2011',
count: 65
},
{
year: '2012',
count: 70
},
{
year: '2013',
count: 72
} ,
{
year: '2014*',
count: 38
}
],
name: 'Male'
}, {
data: [{
year: '2009',
count: 15
}, {
year: '2010',
count: 17
}, {
year: '2011',
count: 18
}, {
year: '2012',
count: 20
}, {
year: '2013',
count: 17
},
{
year: '2014*',
count: 8
}],
name: 'Female'
}
],
series = dataset.map(function (d) {
return d.name;
}),
dataset = dataset.map(function (d) {
return d.data.map(function (o, i) {
// Structure it so that your numeric
// axis (the stacked amount) is y
return {
y: o.count,
x: o.year
};
});
}),
stack = d3.layout.stack();
stack(dataset);
var dataset = dataset.map(function (group) {
return group.map(function (d) {
// Invert the x and y values, and y0 becomes x0
return {
x: d.y,
y: d.x,
x0: d.y0
};
});
}),
svg = d3.select('#sex')
.append('svg')
.attr('width', width + margins.left + margins.right + legendPanel.width)
.attr('height', height + margins.top + margins.bottom)
.append('g')
.attr('transform', 'translate(' + margins.left + ',' + margins.top + ')'),
xMax = d3.max(dataset, function (group) {
return d3.max(group, function (d) {
return d.x + d.x0;
});
}),
xScale = d3.scale.linear()
.domain([0, xMax])
.range([0, 200]),
months = dataset[0].map(function (d) {
return d.y;
}),
_ = console.log(months),
yScale = d3.scale.ordinal()
.domain(months)
.rangeRoundBands([0, height], .1),
xAxis = d3.svg.axis()
.scale(xScale)
.orient('bottom'),
yAxis = d3.svg.axis()
.scale(yScale)
.orient('left'),
colours = d3.scale.category20c(),
groups = svg.selectAll('g')
.data(dataset)
.enter()
.append('g')
.style('fill', function (d, i) {
return colours(i);
}),
rects = groups.selectAll('rect')
.data(function (d) {
return d;
})
.enter()
.append('rect')
.attr('x', function (d) {
return xScale(d.x0);
})
.attr('y', function (d, i) {
return yScale(d.y);
})
.attr('height', function (d) {
return yScale.rangeBand();
})
.attr('width', function (d) {
return xScale(d.x);
})
.on('mouseover', function (d) {
var xPos = parseFloat(d3.select(this).attr('x')) / 2 + width / 2;
var yPos = parseFloat(d3.select(this).attr('y')) + yScale.rangeBand() / 2;
d3.select('#tooltip')
.style('left', xPos + 'px')
.style('top', yPos + 'px')
.select('#value')
.text(d.x);
d3.select('#tooltip').classed('hidden', false);
})
.on('mouseout', function () {
d3.select('#tooltip').classed('hidden', true);
})
svg.append('g')
.attr('class', 'axis')
.attr('transform', 'translate(0,' + height + ')')
.call(xAxis);
svg.append('g')
.attr('class', 'axis')
.call(yAxis);
svg.append('rect')
.attr('fill', 'white')
.attr('width', 160)
.attr('height', 30 * dataset.length)
.attr('x', width + margins.left)
.attr('y', 0);
//X AXIS LABEL
svg.append("text")
.attr("class", 'x label')
.attr("y", height + 35)
.attr("x", width / 2)
.text("Victims")
.style("font-size","13px")
.style("font-weight", "600");
/*
//FOOTNOTE
svg.append("text")
.attr("class", 'x label')
.attr("y", height + 35)
.attr("x", width + 65)
.text("*As of July 2014")
.style("font-size","11px")
.style("font-style", "italic");
*/
//LEGEND
series.forEach(function (s, i) {
svg.append('text')
.attr('fill', 'black')
.attr('x', width + margins.left + 18)
.attr('y', i * 24 + 24)
.text(s);
svg.append('rect')
.attr('fill', colours(i))
.attr('width', 20)
.attr('height', 20)
.attr('x', width + margins.left + 75)
.attr('y', i * 24 + 6);
});