Reputation: 61
I need to left align the month ticks. And I can't use a hard-coded value to shift them because the number of weeks per month is not constant.
There's so many questions like this here but none have working answers. Seems like this is such a common problem maybe there's an in-built function out there I'm not aware of that can handle this for me.
Also notice there are a variable number of weeks per month, yet the spacing of the months is constant. How can I fix that?
EDIT: let x2 = d3.scalePoint().rangeRound([0, width]).padding(0)
left aligns the entire set of month ticks. But this means only the first tick is properly positioned.
<!DOCTYPE html>
<svg width='960' height='500'></svg>
<style>
.bar {
fill: steelblue
}
.bar:hover {
fill: brown
}
.line {
fill: none;
stroke: MidnightBlue;
stroke-width: .5px;
}
</style>
<script src='https://d3js.org/d3.v5.min.js'></script>
<script>
`use strict`;
(async o => {
let svg = d3.select(`svg`)
let margin = { top: 20, right: 20, bottom: 30, left: 50 }
let width = +svg.attr(`width`) - margin.left - margin.right
let height = +svg.attr(`height`) - margin.top - margin.bottom
height -= 100
let g = svg.append(`g`).attr(`transform`, `translate(${margin.left},${margin.top})`)
let x = d3.scaleBand()
.rangeRound([0, width])
.padding(0.1)
let x2 = d3.scaleBand()
.rangeRound([0, width])
.padding(0.1)
let y = d3.scaleLinear()
.rangeRound([height, 0])
let data = [{"week":"July_1_2019","profit":"100"},{"week":"July_8_2019","profit":"394"},{"week":"August_3_2019","profit":"395"},{"week":"August_14_2019","profit":"112"},{"week":"August_24_2019","profit":"488"},{"week":"September_7_2019","profit":"123"},{"week":"September_14_2019","profit":"534"},{"week":"September_21_2019","profit":"600"},{"week":"September_22_2019","profit":"604"},{"week":"September_23_2019","profit":"788"},{"week":"October_10_2019","profit":"433"},{"week":"October_11_2019","profit":"100"}]
function formatWeek(week) {
let datum = week.split(`_`)
return { month: datum[0], day: +datum[1], year: +datum[2] }
}
x.domain(data.map(o => o.week))
x2.domain(data.map(o => formatWeek(o.week).month))
y.domain([0, d3.max(data, o => Number(o.profit))])
g.append(`g`)
.attr(`transform`, `translate(0,${height})`)
.call(d3.axisBottom(x).tickFormat(o => `${formatWeek(o).month} ${formatWeek(o).day}, ${formatWeek(o).year}`))
.selectAll(`text`)
.style(`text-anchor`, `end`)
.attr(`dx`, `-1em`)
.attr(`dy`, `-0.5em`)
.attr(`transform`, d => `rotate(-90)`)
g.append(`g`)
.call(d3.axisTop(x2))
.selectAll(`text`)
.style(`text-anchor`, `end`)
g.append(`g`)
.call(d3.axisLeft(y))
.append(`text`)
.attr(`fill`, `#000`)
.attr(`transform`, `rotate(-90)`)
.attr(`y`, 6)
.attr(`dy`, `0.71em`)
.attr(`text-anchor`, `end`)
.text(`profit`)
g.selectAll(`.bar`)
.data(data)
.enter().append(`rect`)
.attr(`class`, `bar`)
.attr(`x`, o => x(o.week))
.attr(`y`, o => y(Number(o.profit)))
.attr(`width`, x.bandwidth())
.attr(`height`, o => height - y(Number(o.profit)))
function make_y_gridlines() { return d3.axisTop(x2) }
g.append(`g`)
.attr(`class`, `line`)
.call(make_y_gridlines().tickSize(-width).tickFormat(``))
})()
</script>
Upvotes: 0
Views: 782
Reputation: 3412
You could adjust the ticks based on the x position of the first bar in each month. Without changing your code too much, under x.domain(data.map(o => o.week))
add:
// get month names and first bar in month
let monthNames = d3.set(data.map(o => formatWeek(o.week).month)).values(),
monthVals = monthNames.map(function(d) { return data.filter(function(e) { return e.week.match(d) != null })[0].week});
x2.domain(monthVals)
Then change your calls to your top axis and gridlines to :
g.append(`g`)
.call(d3.axisTop(x2).tickValues(monthNames))
.call(function(context) {
// bind the first months to the ticks and adjust
var ticks = context.selectAll('.tick')
.data(monthVals)
.attr('transform', function(d) { return 'translate(' + x(d) + ',0)' })
})
.selectAll(`text`)
.style(`text-anchor`, `end`)
And:
g.append(`g`)
.attr(`class`, `line`)
.call(make_y_gridlines().tickSize(-width).tickFormat(``))
.call(function(context) {
// bind the first months to the lines and adjust
context.selectAll('.tick')
.data(monthVals)
.attr('transform', function(d) { return 'translate(' + x(d) + ',0)' })
})
Upvotes: 2