Creating a Text Labeled x-Axis with an Ordinal Scale in D3.js
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.)
arete
Updated on July 09, 2022Comments
-
arete almost 2 years
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; });
-
arete almost 11 yearsSo what is it about incorporating the value-label pairs into a single object in d3 instead of linking it labels to an object and values to an object. I appreciate your response, but I want to go the extra mile and know exactly what's going on behind the scenes so I'm not just programming by coincidence.
-
nsonnad almost 11 yearsIt's because you are defining the domain explicitly with one set of data then passing another set to it when you do
.attr("x", function(d, i) { return xScale(i); })
. The relevant line from the API reference says: "If the domain is set explicitly, values passed to the scale that were not explicitly part of the domain will be added." To this end, you might notice that in your original code calling the x axis before the rects will result in only your explicit array being called.