d3.js stacked bar chart - modify stack order logic - javascript

I would like to create a stacked bar chart whereby the order of the rects is determined by data values (i.e. largest to smallest, tallest to shortest, richest to poorest, ect). To the best of my knowledge, after stacking data, the initial order seems to be preserved. This can be seen in my snippet, hardcoded data lets us see what's happening before and after d3.stack(). Note that the third rect fmc3 goes from being the third largest in t1 to the largest of all rects in t3 despite its position in the stack remaining the same:
var margins = {top:100, bottom:300, left:100, right:100};
var height = 600;
var width = 900;
var totalWidth = width+margins.left+margins.right;
var totalHeight = height+margins.top+margins.bottom;
var svg = d3.select('body')
.append('svg')
.attr('width', totalWidth)
.attr('height', totalHeight);
var graphGroup = svg.append('g')
.attr('transform', "translate("+margins.left+","+margins.top+")");
var data = [
{period:'t1', fmc1:2, fmc2:5, fmc3:6, fmc4:9, fmc5:10},
{period:'t2', fmc1:3, fmc2:4, fmc3:9, fmc4:8, fmc5:11},
{period:'t3', fmc1:3, fmc2:5, fmc3:15, fmc4:12, fmc5:10},
];
var groups = d3.map(data, function(d){return(d.period)}).keys();
var subgroups = Object.keys(data[0]).slice(1);
var stackedData = d3.stack()
.keys(subgroups)
(data);
//console.log(stackedData);
var yScale = d3.scaleLinear()
.domain([0,80])
.range([height,0]);
var xScale = d3.scaleBand()
.domain(['t1','t2','t3'])
.range([0,width])
.padding([.5]);
var colorScale = d3.scaleOrdinal()
.domain(subgroups)
.range(["#003366","#366092","#4f81b9","#95b3d7","#b8cce4","#e7eef8","#a6a6a6","#d9d9d9","#ffffcc","#f6d18b","#e4a733","#b29866","#a6a6a6","#d9d9d9","#e7eef8","#b8cce4","#95b3d7","#4f81b9","#366092","#003366"].reverse());
graphGroup.append("g")
.selectAll("g")
.data(stackedData)
.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) { return xScale(d.data.period); })
.attr("y", function(d) { return yScale(d[1]); })
.attr("height", function(d) { return yScale(d[0]) - yScale(d[1]); })
.attr("width",xScale.bandwidth());
<script src="https://d3js.org/d3.v5.min.js"></script>
I suspect preserving the initial order may be somewhat necessary to calculate adjacent rects in the stack. However, on the other hand, ordering data before visualizing it is a very common, even preferred practice in the field of visualization and I would be surprised if no one has found a solution to this issue yet.
Question
Given there are no built-in features to specify the ordering of the rects in a stack, how should I approach the sort logic to achieve largest to smallest ordering?

Well, there is a built-in feature to specify the order, which is stack.order(). However, it specifies the order computing the entire series, not every single value the stack (which I believe is what you want... in that case, you'll have to create your own function).
So, for instance, using stack.order(d3.stackOrderDescending):
var margins = {
top: 0,
bottom: 0,
left: 0,
right: 0
};
var height = 300;
var width = 500;
var totalWidth = width + margins.left + margins.right;
var totalHeight = height + margins.top + margins.bottom;
var svg = d3.select('body')
.append('svg')
.attr('width', totalWidth)
.attr('height', totalHeight);
var graphGroup = svg.append('g')
.attr('transform', "translate(" + margins.left + "," + margins.top + ")");
var data = [{
period: 't1',
fmc1: 2,
fmc2: 5,
fmc3: 6,
fmc4: 9,
fmc5: 10
},
{
period: 't2',
fmc1: 3,
fmc2: 4,
fmc3: 9,
fmc4: 8,
fmc5: 11
},
{
period: 't3',
fmc1: 3,
fmc2: 5,
fmc3: 15,
fmc4: 12,
fmc5: 10
},
];
var groups = d3.map(data, function(d) {
return (d.period)
}).keys();
var subgroups = Object.keys(data[0]).slice(1);
var stackedData = d3.stack()
.keys(subgroups)
.order(d3.stackOrderDescending)
(data);
//console.log(stackedData);
var yScale = d3.scaleLinear()
.domain([0, 60])
.range([height, 0]);
var xScale = d3.scaleBand()
.domain(['t1', 't2', 't3'])
.range([0, width])
.padding([.5]);
var colorScale = d3.scaleOrdinal()
.domain(subgroups)
.range(["#003366", "#366092", "#4f81b9", "#95b3d7", "#b8cce4", "#e7eef8", "#a6a6a6", "#d9d9d9", "#ffffcc", "#f6d18b", "#e4a733", "#b29866", "#a6a6a6", "#d9d9d9", "#e7eef8", "#b8cce4", "#95b3d7", "#4f81b9", "#366092", "#003366"].reverse());
graphGroup.append("g")
.selectAll("g")
.data(stackedData)
.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) {
return xScale(d.data.period);
})
.attr("y", function(d) {
return yScale(d[1]);
})
.attr("height", function(d) {
return yScale(d[0]) - yScale(d[1]);
})
.attr("width", xScale.bandwidth());
<script src="https://d3js.org/d3.v5.min.js"></script>

Related

D3.JS yScale returning "undefined" for only one single value

I'm using D3.js for the first time, i used this code to draw a bar chart.
var dataset = [20, 55, 30, 40, 50, 35, 97, 75, 100, 45];
var w = 500;
var h =100;
var xScale = d3.scaleBand()
.domain(d3.range(dataset.length))
.rangeRound([0,w])
.paddingInner(0.05);
var yScale = d3.scaleBand()
.domain(d3.range(d3.max(dataset)))
.rangeRound([0,h]);
var svg = d3.select("#chart")
.append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", function(d,i) {
return xScale(i);
})
.attr("y",function(d){console.log(yScale(d)); return h - yScale(d);})
.attr("width", xScale.bandwidth())
.attr("fill", function(d) {if(d<50) { return "blue" }
else if (d>=50) { return "red" }; })
.attr("height", function(d){return yScale(d);
});
But i'm getting this result:
I can't undersand why i'm getting "undefined" for one specific value.
The issue is with the yScale domain, and using d3.range to set the domain.
d3.range(100) creates an array with 100 elements, with values from 0 to 99. So when you try to get yScale(100), "100" is not in the domain.
For your data, you would recommend using a linear scale, for example
var yScale = d3.scaleLinear()
.domain([0, d3.max(dataset)])
.rangeRound([0,h]);
Or if you need to use d3.range, set the number of elements to 1 + the max value, for example:
var yScale = d3.scaleRange()
.domain(d3.range(d3.max(dataset) + 1))
.rangeRound([0,h]);

How to specify a data column as the property used in a line generator?

In d3.js it is possible to access parts of a dataset by using the syntax d.measure with d accessing the data property and "measure" accessing a specific field in our dataset. Based on the code I found on bl.ocks.org I created a line chart. I however wanted to alter the function dsLineChart() in such a way that I can pass the name of the column that I want to use for visualising the values on the y-axis, i.e. how to specify an argument dsLineChart(argument) that determines the column to use e.g. d.measure2 instead of d.measure.
See below for the script. The dataset I have contains the columns "measure", "measure2", "measure3" and "measure4" of which "measure" is visualised by d.measure, but I want to call e.g. dsLineChart("measure2") to use the same function but for another column.
e.g.
Dataset
var data = [
{group:"All",category:2011,measure:28107,measure2:53301,measure3:89015.40,measure4:138394},
{group:"All",category:2012,measure:39400,measure2:7001, measure3:55550.50,measure4:18004},
{group:"All",category:2013,measure:33894,measure2:690597,measure3:68289.50,measure4:17455},
{group:"All",category:2014,measure:55261,measure2:7172,measure3:73380.93,measure:418143} ];
Script
I have created a minimal working script that can be found on the following link Fiddle D3js line chart
Thanks to the of feedback #GerardoFurtado the resulting script is provided in below snippet and allows for calling the function dsLineChart() with different arguments resulting in linecharts using different measures e.g. dsLineChart("measure2") vs. dsLineChart("measure").
// dataset
var lineChartData = [{
category: 2011,
measure: 28107,
measure2: 53301,
measure3: 89015.40,
measure4: 138394
},
{
category: 2012,
measure: 39400,
measure2: 7001,
measure3: 55550.50,
measure4: 18004
},
{
category: 2013,
measure: 33894,
measure2: 690597,
measure3: 68289.50,
measure4: 17455
},
{
category: 2014,
measure: 55261,
measure2: 7172,
measure3: 73380.93,
measure: 418143
}
];
// layout
var margin = {
top: 20,
right: 10,
bottom: 0,
left: 50
},
width = 350 - margin.left - margin.right,
height = 250 - margin.top - margin.bottom;
// function to draw linechart
function dsLineChart(selMeasure) {
//convert object to array
var data = d3.values(lineChartData);
var property;
var measures = [selMeasure];
var xScale = d3.scaleLinear()
.domain([0, data.length - 1])
.range([0, width]);
var yScale = d3.scaleLinear()
.domain([0, d3.max(data, function(d) {
return d[selMeasure];
})])
.range([height, 0])
.range([height, 0]);
var line = d3.line()
.x(function(d, i) {
return xScale(i);
})
.y(function(d) {
return yScale(d[property]);
});
var svg = d3.select("#lineChart").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.attr("position", "absolute")
.attr("top", "10px")
.attr("left", "410px")
var plot = svg
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.attr("id", "lineChartPlot");
var paths = plot.selectAll(null)
.data(measures)
.enter()
.append("path")
.attr("class", "line")
.attr("d", function(d) {
property = d;
return line(data)
})
.attr("stroke", "lightgrey")
.attr("fill", "none")
.attr("stroke-width", "4px");
}
dsLineChart("measure2");
<script src="https://d3js.org/d3.v4.min.js"></script>
<div id="lineChart"></div>
The most "elegant" solution here, and probably the most idiomatic one, is nesting your data, in such a way that the y value property can have the same name for all lines.
However, this doesn't mean that what you're asking is not possible: it certainly is. You can specify what scale you pass to the line generator (for instance, have a look at this answer), and what property you use for each method.
For this to work, we'll first declare a variable:
var property;
That's the variable we'll use in the line generator:
var line = d3.line()
.x(function(d, i) {
return xScale(i);
})
.y(function(d) {
return yScale(d[property]);
});
Now, let's get the real properties. Here I'm hardcoding them, but you can easily extract them from the data:
var measures = ["measure", "measure2", "measure3", "measure4"];
Then, we bind that array as data:
var paths = plot.selectAll(null)
.data(measures)
.enter()
.append("path")
Now comes the important part: in the callback, you simply assign the value of property, which is used by the line generator:
.attr("d", function(d) {
property = d;
return line(data)
})
All together, here is your code with those changes:
// dataset
var data = [{
group: "All",
category: 2011,
measure: 28107,
measure2: 53301,
measure3: 89015.40,
measure4: 138394
},
{
group: "All",
category: 2012,
measure: 39400,
measure2: 7001,
measure3: 55550.50,
measure4: 18004
},
{
group: "All",
category: 2013,
measure: 33894,
measure2: 690597,
measure3: 68289.50,
measure4: 17455
},
{
group: "All",
category: 2014,
measure: 55261,
measure2: 7172,
measure3: 73380.93,
measure: 418143
}
];
var property;
var measures = ["measure", "measure2", "measure3", "measure4"];
// layout
var margin = {
top: 20,
right: 10,
bottom: 0,
left: 50
},
width = 350 - margin.left - margin.right,
height = 250 - margin.top - margin.bottom;
// function to draw linechart
function dsLineChart() {
var firstDatasetLineChart = data
var xScale = d3.scaleLinear()
.domain([0, firstDatasetLineChart.length - 1])
.range([0, width]);
var yScale = d3.scaleLinear()
.domain([0, d3.max(firstDatasetLineChart, function(d) {
return d.measure;
})])
.range([height, 0])
.range([height, 0]);
var line = d3.line()
.x(function(d, i) {
return xScale(i);
})
.y(function(d) {
return yScale(d[property]);
});
var svg = d3.select("#lineChart").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.attr("position", "absolute")
.attr("top", "10px")
.attr("left", "410px")
var plot = svg
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.attr("id", "lineChartPlot");
var paths = plot.selectAll(null)
.data(measures)
.enter()
.append("path")
.attr("class", "line")
.attr("d", function(d) {
property = d;
return line(data)
})
.attr("stroke", "lightgrey")
.attr("fill", "none")
.attr("stroke-width", "4px");
}
dsLineChart();
<script src="https://d3js.org/d3.v4.min.js"></script>
<div id="lineChart"></div>

How to use x and width in a bar chart with scaleTime?

I have a codepen here - https://codepen.io/anon/pen/xpaYYw?editors=0010
Its a simple test graph but the date will be formatted like this.
I have dates on the x axis and amounts on the y
How can I use the x scale to set the width and x position of the bars.
layers.selectAll('rect')
.data(data)
.enter()
.append('rect')
.attr('height', function(d, i) {
return height - y(d.one);
})
.attr('y', function(d, i) {
return y(d.one);
})
.attr('width', function(d, i) {
return 50;
})
.attr('x', function(d, i) {
return 80*i;
})
.style('fill', (d, i) => {
return colors[i];
});
The problem with your question has nothing to do with programming, or JavaScript, or D3... the problem is a basic dataviz concept (that's why I added the data-visualization tag in your question):
What you're trying to do is not correct! You should not use bars with a time scale. Time scales are for time series (in which we use dots, or dots connected by lines).
If you use bars with time in the x axis you'll face problems:
Positioning the bar: the left margin of the bar will be always at the date you set. The whole bar will lie after that date;
Setting the width of the bar: in a real bar chart, which uses categorical variables for the x axis, the width has no meaning. But in a time scale the width represents time.
However, just for the sake of explanation, let's create this bar chart with a time scale (despite the fact that this is a wrong choice)... Here is how to do it:
First, set the "width" of the bars in time. Let's say, each bar will have 10 days of width:
.attr("width", function(d){
return x(d3.timeDay.offset(d.date, 10)) - x(d.date)
})
Then, set the x position of the bar to the current date less half its width (that is, less 5 days in our example):
.attr('x', function(d, i) {
return x(d3.timeDay.offset(d.date, -5));
})
Finally, don't forget to create a "padding" in the time scale:
var x = d3.scaleTime()
.domain([d3.min(data, function(d) {
return d3.timeDay.offset(d.date, -10);
}), d3.max(data, function(d) {
return d3.timeDay.offset(d.date, 10);
})])
.range([0, width]);
Here is your code with those changes:
var keys = [];
var legendKeys = [];
var maxVal = [];
var w = 800;
var h = 450;
var margin = {
top: 30,
bottom: 40,
left: 50,
right: 20,
};
var width = w - margin.left - margin.right;
var height = h - margin.top - margin.bottom;
var colors = ['#FF9A00', '#FFEBB6', '#FFC400', '#B4EDA0', '#FF4436'];
var data = [{
"one": 4306,
"two": 2465,
"three": 2299,
"four": 988,
"five": 554,
"six": 1841,
"date": "2015-05-31T00:00:00"
}, {
"one": 4378,
"two": 2457,
"three": 2348,
"four": 1021,
"five": 498,
"six": 1921,
"date": "2015-06-30T00:00:00"
}, {
"one": 3404,
"two": 2348,
"three": 1655,
"four": 809,
"five": 473,
"six": 1056,
"date": "2015-07-31T00:00:00"
},
];
data.forEach(function(d) {
d.date = new Date(d.date)
})
for (var i = 0; i < data.length; i++) {
for (var key in data[i]) {
if (!data.hasOwnProperty(key) && key !== "date")
maxVal.push(data[i][key]);
}
}
var x = d3.scaleTime()
.domain([d3.min(data, function(d) {
return d3.timeDay.offset(d.date, -10);
}), d3.max(data, function(d) {
return d3.timeDay.offset(d.date, 10);
})])
.range([0, width]);
var y = d3.scaleLinear()
.domain([0, d3.max(maxVal, function(d) {
return d;
})])
.range([height, 0]);
var svg = d3.select('body').append('svg')
.attr('class', 'chart')
.attr('width', w)
.attr('height', h);
var chart = svg.append('g')
.classed('graph', true)
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
var layersArea = chart.append('g')
.attr('class', 'layers');
var layers = layersArea.append('g')
.attr('class', 'layer');
layers.selectAll('rect')
.data(data)
.enter()
.append('rect')
.attr('height', function(d, i) {
return height - y(d.one);
})
.attr('y', function(d, i) {
return y(d.one);
})
// .attr('width', function(d, i) {
// return 50;
// })
.attr("width", function(d) {
return x(d3.timeDay.offset(d.date, 10)) - x(d.date)
})
.attr('x', function(d, i) {
return x(d3.timeDay.offset(d.date, -5));
})
.style('fill', (d, i) => {
return colors[i];
});
chart.append('g')
.classed('x axis', true)
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x)
.tickFormat(d3.timeFormat("%Y-%m-%d")).tickValues(data.map(function(d) {
return new Date(d.date)
})));
chart.append('g')
.classed('y axis', true)
.call(d3.axisLeft(y)
.ticks(10));
<script src="https://d3js.org/d3.v4.min.js"></script>

D3 bar chart to stacked bar chart

Hi i'm new to D3 and I'm trying to convert a normal bar chart to a stacked bar chart.
This is my code
var data = [4, 8, 15, 16, 23, 42, 200];
var height = 200;
var width = 200;
var barWidth = 35;
var barOffset = 5;
var myChart = d3.select(".chart").append('svg')
.attr('width', width)
.attr('height', height)
.style("background", "grey")
.selectAll('rect')
.data(data)
.enter().append('rect').
style("fill", "blue")
.attr("width", barWidth)
.attr("height", function(d){ return d;})
.attr('x', function(d, i)
{ return i *(barWidth + barOffset);})
.attr('y', function(d){
return height - d;
});
Any help or hint in to the right direction would be much appreciated.
just for the comment on above answer..
here is the code -
var data = [
[4, 8, 15, 16, 23, 42, 200],
[50, 60, 20, 100, 30, 50, 40]
];
//console.log(data)
//console.log("REMAP---------------------------");
var remapped =data[0].map(function(dat,i){
return data.map(function(d,ii){
return {x: ii, y: d[i] };
})
});
var w = 200,
h = 200
// create canvas
var svg = d3.select(".chart").append("svg:svg")
.attr("width", w)
.attr("height", h )
.append("svg:g")
.attr("transform", "translate(0,"+h+")");
var x = d3.scale.ordinal().rangeRoundBands([0, w], 0.5)
var y = d3.scale.linear().range([0, h])
var color = d3.scale.ordinal()
.range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]).domain(data[0].map(function(d,i){return i;}));
//console.log("LAYOUT---------------------------");
var stacked = d3.layout.stack()(remapped)
//console.log(stacked)
x.domain(stacked[0].map(function(d) { return d.x; }));
y.domain([0, d3.max(stacked[stacked.length - 1], function(d) { return d.y0 + d.y; })]);
// show the domains of the scales
console.log("x.domain(): " + x.domain())
console.log("y.domain(): " + y.domain())
// Add a group for each column.
var valgroup = svg.selectAll("g.valgroup")
.data(stacked)
.enter().append("svg:g")
.attr("class", "valgroup")
.style("fill", function(d, i) { return color(i); })
.style("stroke", function(d, i) { return d3.rgb(color(i)).darker(); });
// Add a rect for each date.
var rect = valgroup.selectAll("rect")
.data(function(d){return d;})
.enter().append("svg:rect")
.attr("x", function(d) { return x(d.x); })
.attr("y", function(d) { return -y(d.y0) - y(d.y); })
.attr("height", function(d) { return y(d.y); })
.attr("width", x.rangeBand());
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div class="chart"></div>
Here data plays an important role.
You need to modify the data, compatible with the chart type.
Here is the solution for your problem.
hope, this will surely help you. thanks :)
var data = [4, 8, 15, 16, 23, 42, 200];
data = data.map(function(d) { return [{x: 0, y: d}] });
var stack = d3.layout.stack();
var stackData = stack(data);
var color = d3.scale.ordinal()
.range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]).domain(data.map(function(d,i){return i;}));
var height = 200;
var width = 200;
var barWidth = 35;
var barOffset = 5;
var lastData = stackData[stackData.length-1][0]; //to get the max value
var y = d3.scale.linear()
.rangeRound([height, 0]).domain([0, lastData.y+lastData.y0]);
var myChart = d3.select(".chart").append('svg')
.attr('width', width)
.attr('height', height)
.style("fill", "grey")
.selectAll('rect')
.data(stack(data))
.enter().append('rect').
style("fill", function(d,i) {
return color(i);
})
.attr("width", barWidth)
.attr('x', 0)
.attr("y", function(d) {
return y(d[0].y0+d[0].y);
})
.attr("height", function(d) {
return height - y(d[0].y);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div class="chart"></div>
yes!! ideally stacked bar chart must require a 2D array
following are few scenarios that will makes your understanding clear
scenario 1 -
we need to visualise sales of MAC(only one product) for 5 years
then the data will be : sales: [$2000, $5555, $20177, $9999, $80805]
so here we need simple bar chart, where each bar will show the sales in respective year
senario 2 -
we need to visualise sales of 3 products(MAC, iPad, iPhone) for 5 years
then the data will be :
sales of MAC: [$2000, $5555, $20177, $9999, $80805]
sales of iPad: [$2000, $5555, $20177, $9999, $80805]
sales of iPhone: [$2000, $5555, $20177, $9999, $80805]
here we need stacked bar chat, where each stack will show the sales of 3 products in respective year
if we have multiple array then.. in addition to above code we need, x scale
you can follow the exapmle
hope this will help :) thank you :)

Creating a Text Labeled x-Axis with an Ordinal Scale in D3.js

I'm building a bar chart in d3.js with an ordinal x-axis whose ticks should label the chart with text. Could anyone explain how the ordinal scale "maps" x ticks to the corresponding bar positions? Specifically, if I want to designate the x tick labels with an array of text values to the corresponding bars in a bar chart.
Currently I'm setting the domain as the following:
var labels = ["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t"];
var xScale = d3.scale.ordinal()
.domain(labels)
However, values of 1-19 are showing after the text labels.
As seen in this fiddle:
http://jsfiddle.net/chartguy/FbqjD/
Associated Fiddle Source Code:
//Width and height
var margin = {top: 20, right: 20, bottom: 30, left: 40};
var width = 600 - margin.left - margin.right;
var height= 500-margin.top -margin.bottom;
var w = width;
var h = height;
var dataset = [ 5, 10, 13, 19, 21, 25, 22, 18, 15, 13,
11, 12, 15, 20, 18, 17, 16, 18, 23, 25 ];
var labels = ["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t"];
var xScale = d3.scale.ordinal()
.domain(labels)
.rangeRoundBands([margin.left, width], 0.05);
var xAxis = d3.svg.axis().scale(xScale).orient("bottom");
var yScale = d3.scale.linear()
.domain([0, d3.max(dataset)])
.range([h,0]);
//Create SVG element
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
//Create bars
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", function(d, i) {
return xScale(i);
})
.attr("y", function(d) {
return yScale(d);
})
.attr("width", xScale.rangeBand())
.attr("height", function(d) {
return h - yScale(d);
})
.attr("fill", function(d) {
return "rgb(0, 0, 0)";
});
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + 0 + ")")
.call(xAxis);
//Create labels
svg.selectAll("text")
.data(dataset)
.enter()
.append("text")
.text(function(d) {
return d;
})
.attr("x", function(d, i) {
return xScale(i) + xScale.rangeBand() / 2;
})
.attr("y", function(d) {
return h - yScale(d) + 14;
});
You can set the tick values of an ordinal axis explicitly using d3.svg.axis().tickValues(*array*).
But this is an odd way to do it because it dangerously separates your keys and values, meaning you have to take care to manually align the scales and make sure that your data corresponds correctly. It helps to group the keys and values in a single object and then use the format:
axis.domain(array.map(function (d) { return d.value; }))
to map your axis domains.
I have reworked your data and fiddle to do it in what I see as the more d3 way. (Also note that I made some other changes just for fun, namely improved the margins and cleaned up the axis alignment, etc.)

Categories