I'm working on a D3 (V5) chart from a dataset that includes both a JobCode (unique) and JobTitle (not unique). I need to use the JobCode for the axis, but would like to display the JobTitle as the label.
Relevant code:
const xScale = d3.scaleLinear().domain([0,d3.max(grouped, d => d.jobCodeMax)-d3.min(grouped, d => d.jobCodeMax)]).range([0,width-margin.left-margin.right]);
const yScale = d3.scaleBand().domain(grouped.map(d => d.all[0].jobCode)).range([0,height - margin.top - margin.bottom]).padding(0.3);
const yAxis = d3.axisLeft(yScale);
const xAxis = d3.axisTop(xScale);
const radius = yScale.bandwidth()/2.75
const g = svg.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`)
yAxis(g.append("g"));
xAxis(g.append("g"));
I can access the jobTitles with d.jobTitle.
My tentative plan is to abandon the d3.axisLeft() function and draw my own. Is there a more straightforward method? Something like a parameter to d3.axisLeft() where I specify an alternate field?
Thank you for any insights.
The domain of d3.scaleBand:
... Domain values are stored internally in an
InternMapfrom primitive value to index; the resulting index is then used to determine the band. Thus, a band scale’s values must be coercible to a primitive value, and the primitive domain value uniquely identifies the corresponding band. ...
InternMap extends a standard Map object which - so we can see that values in the domain of a scaleBand need to be unique.
To get the result you want you can switch the text values to jobTitle after the axis is rendered e.g.:
// replace text values on y axis
yLabels = d3.selectAll(".yaxis g text");
yLabels.each(function(d, i) { d3.select(this).text(data[i].alias) });
Using arrow functions, the equivalent is:
// arrow function way
yLabels = d3.selectAll(".yaxis g text");
yLabels.each((d, i, n) => d3.select(n[i]).text(data[i].alias))
Working example below:
const margin = {top: 10, bottom: 30, left: 60, right: 10}
const width = 500 - margin.left - margin.right;
const height = 180 - margin.top - margin.bottom;
const 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})`);
const data = [
{group: "A", value: 3, alias: "foo"},
{group: "B", value: 1, alias: "foo"},
{group: "C", value: 2, alias: "bar"},
{group: "D", value: 5, alias: "bar"},
{group: "E", value: 4, alias: "bar"}
];
const xScale = d3.scaleLinear()
.domain([0, d3.max(data, d => d.value)])
.range([0, width]);
const yScale = d3.scaleBand()
.domain(data.map(d => d.group))
.range([height, 0])
.padding(.2);
const xAxis = d3.axisBottom(xScale);
const yAxis = d3.axisLeft(yScale);
const gXAxis = svg.append("g")
.attr("transform", `translate(0, ${height})`)
.call(xAxis);
const gYAxis = svg.append("g")
.attr("class", "yaxis")
.call(yAxis);
svg.selectAll(".bar")
.data(data)
.enter()
.append("rect")
.attr("x", xScale(0))
.attr("y", d => yScale(d.group))
.attr("width", d => xScale(d.value))
.attr("height", yScale.bandwidth())
.attr("fill", "steelblue");
// replace text values on y axis
yLabels = d3.selectAll(".yaxis g text");
yLabels.each(function(d, i) { d3.select(this).text(data[i].alias) });
// arrow function way
//yLabels.each((d, i, n) => d3.select(n[i]).text(data[i].alias))
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>