diff --git a/src/utils.js b/src/utils.js new file mode 100644 index 0000000..c1dba05 --- /dev/null +++ b/src/utils.js @@ -0,0 +1,471 @@ +/** + * @file + * Code shared between multiple axes + */ + +import * as d3 from 'd3'; + +export const getDecimalFormat = (span) => { + if (span >= 0.5) { + return d3.format('.1f'); + } else if (span < 0.5) { + return d3.format('.2f'); + } else if (span <= 0.011) { + return d3.format('.3f'); + } else if (span < 0.0011) { + return d3.format('.4f'); + } else if (span < 0.00011) { + return d3.format('.5f'); + } else if (span < 0.000011) { + return d3.format('.6f'); + } else if (isNaN(span)) { + throw new Error('Span value is not a number'); + } + + return null; +}; + +export const getAxis = (alignment) => { + try { + return { + top: d3.axisTop(), + bottom: d3.axisBottom(), + left: d3.axisLeft(), + right: d3.axisRight(), + }[alignment]; + } catch (e) { + throw new Error('Invalid axis specified.'); + } +}; + +export const convertToPointScale = scale => + d3 + .scalePoint() + .domain(scale.domain()) + .range(scale.range()); + +export const getTimeTicks = intvl => + ({ + century: d3.timeYear.every(100), + jubilee: d3.timeYear.every(50), + decade: d3.timeYear.every(10), + lustrum: d3.timeYear.every(5), + years: d3.timeYear.every(1), + fiscal: d3.timeYear.every(1), + quarters: d3.timeYear.every(1), + months: d3.timeMonth.every(1), + weeks: d3.timeWeek.every(1), + daily: d3.timeDay.every(1), + days: d3.timeDay.every(1), + hours: d3.timeHour.every(1), + }[intvl]); + +export const getTimeTicksMinor = intvl => + ({ + century: d3.timeYear.every(10), + jubilee: d3.timeYear.every(10), + decade: d3.timeYear.every(1), + lustrum: d3.timeYear.every(1), + years: d3.timeMonth.every(1), + fiscal: d3.timeMonth.every(1), + quarters: d3.timeMonth.every(3), + months: d3.timeDay.every(1), + weeks: d3.timeDay.every(1), + daily: d3.timeHour.every(1), + days: d3.timeHour.every(1), + hours: d3.timeMinute.every(1), + }[intvl]); + +export const getTimeTickFormat = (intvl, { fullYear, scale }) => { + const formatFullYear = d3.timeFormat('%Y'); + const formatYear = d3.timeFormat('%y'); + const formatMonth = d3.timeFormat('%b'); + const formatWeek = d3.timeFormat('%W'); + const formatDay = d3.timeFormat('%d'); + const formatHour = d3.timeFormat('%H:%M'); + + return { + century: d3.timeFormat('%Y'), + jubilee(d, i) { + const format = checkCentury(d, i); + return format; + }, + decade(d, i) { + const format = checkCentury(d, i); + return format; + }, + lustrum(d, i) { + const format = checkCentury(d, i); + return format; + }, + years(d, i) { + const format = checkCentury(d, i); + return format; + }, + fiscal(d, i) { + const format = getFiscal(d, i); + return format; + }, + quarters(d, i) { + const format = getQuarters(d, i); + return format; + }, + months(d, i) { + const format = checkMonth(d, i); + return format; + }, + weeks(d, i) { + const format = getWeek(d, i); + return format; + }, + days(d, i) { + const format = getDays(d, i); + return format; + }, + daily(d, i) { + const format = getDaily(d, i); + return format; + }, + hours(d, i) { + const format = getHours(d, i); + return format; + }, + }[intvl]; + + function getHours(d, i) { + if (d.getHours() === 1 || i === 0) { + return `${formatHour(d)} ${formatDay(d)}`; + } + return formatHour(d); + } + + function getDays(d, i) { + if (d.getDate() === 1 || i === 0) { + return `${formatDay(d)} ${formatMonth(d)}`; + } + return formatDay(d); + } + + function getDaily(d, i) { + const last = scale.domain().length - 1; + if (i === 0) { + return `${formatDay(d)} ${formatMonth(d)}`; + } + if (d.getDate() === 1) { + return `${formatMonth(d)}`; + } + if (d.getDay() === 5) { + return `${formatDay(d)}`; + } + if (i === last) { + return formatDay(d); + } + return ''; + } + + function getWeek(d) { + if (d.getDate() < 9) { + return `${formatWeek(d)} ${formatMonth(d)}`; + } + return formatWeek(d); + } + + function getQuarters(d, i) { + if (d.getMonth() < 3 && i < 4) { + return `Q1 ${formatFullYear(d)}`; + } + if (d.getMonth() < 3) { + return 'Q1'; + } + if (d.getMonth() >= 3 && d.getMonth() < 6) { + return 'Q2'; + } + if (d.getMonth() >= 6 && d.getMonth() < 9) { + return 'Q3'; + } + if (d.getMonth() >= 9 && d.getMonth() < 12) { + return 'Q4'; + } + throw new Error('Invalid quarter'); + } + + function checkMonth(d, i) { + if (d.getMonth() === 0 || i === 0) { + const newYear = d3.timeFormat('%b %Y'); + return newYear(d); + } + return formatMonth(d); + } + + function checkCentury(d, i) { + if (fullYear || +formatFullYear(d) % 100 === 0 || i === 0) { + return formatFullYear(d); + } + return formatYear(d); + } + function getFiscal(d, i) { + if (fullYear || +formatFullYear(d) % 100 === 0 || i === 0) { + return `${formatFullYear(d)}/${Number(formatYear(d)) + 1}`; + } + return `${formatYear(d)}/${Number(formatYear(d)) + 1}`; + } +}; + +export const setLabelIds = ({ selection, frameName, axis }) => { + selection + .selectAll(`.axis.${axis.toLowerCase()}Axis text`) + .attr('id', `${frameName}${axis.toLowerCase()}Label`); + + selection + .selectAll(`.axis.${axis.toLowerCase()}Axis line`) + .attr('id', `${frameName}${axis.toLowerCase()}Tick`); +}; + +export const getDefaultXAxisLabel = label => ({ + tag: label.tag, + hori: label.hori || 'middle', + vert: label.vert || 'bottom', + anchor: label.anchor || 'middle', + rotate: label.rotate || 0, +}); + +export const getDefaultYAxisLabel = label => ({ + tag: label.tag, + hori: label.hori || 'left', + vert: label.vert || 'middle', + anchor: label.anchor || 'middle', + rotate: label.rotate || -90, +}); + +export const getXVertical = ({ align, vert, plotHeight, rem, tickSize }) => { + const calcOffset = () => { + if (tickSize > 0 && tickSize < rem) { + return tickSize + (rem * 0.8); // prettier-ignore + } + return (rem * 0.9); // prettier-ignore + }; + return { + toptop: 0 - rem, + topmiddle: 0, + topbottom: 0 + rem, + bottomtop: plotHeight, + bottommiddle: plotHeight + calcOffset(), + bottombottom: plotHeight + calcOffset() + (rem * 1.1), // prettier-ignore + }[align + vert]; +}; + +export const getXHorizontal = ({ hori, plotWidth }) => + ({ + left: plotWidth - plotWidth, + middle: plotWidth / 2, + right: plotWidth, + }[hori]); + +export const getYVertical = ({ vert, plotHeight }) => + ({ + top: plotHeight - plotHeight, + middle: plotHeight / 2, + bottom: plotHeight, + }[vert]); + +export const getYHorizontal = ({ + align, + hori, + plotWidth, + rem, + tickSize, + labelWidth, +}) => { + const calcOffset = () => { + if (tickSize > 0 && tickSize < rem) { + return tickSize / 2; + } + return 0; + }; + + // prettier-ignore + return ({ + leftleft: 0 - (labelWidth + (rem * 0.6)), + leftmiddle: 0 - (labelWidth / 2) - calcOffset(), + leftright: rem * 0.7, + rightleft: plotWidth - labelWidth, + rightmiddle: plotWidth + (labelWidth / 2) + (rem * 0.5) + calcOffset(), + rightright: plotWidth + (rem) + calcOffset(), + }[align + hori]); +}; + +export const getBandWidth = ({ index, bands, plotWidth, scale }) => { + if (index === bands.length - 1) { + return plotWidth - scale(bands[index]); + } + return scale(bands[index + 1]) - scale(bands[index]); +}; + +export const formatNumber = ( + d, + { divisor, numberFormat, deciFormat, deciCheck, logScale }, +) => { + const checkDecimal = Number.isInteger(d / divisor); + if (checkDecimal === false) { + deciCheck = true; + } + if (d / divisor === 0) { + return numberFormat(d / divisor); + } + if (logScale) { + return numberFormat(d / divisor); + } + if (deciCheck) { + return deciFormat(d / divisor); + } + return numberFormat(d / divisor); +}; + +export const generateDateTickValues = ({ + intraday, + scale, + interval, + endTicks, +}) => { + if (intraday) { + // prettier-ignore + return scale + .domain() + .filter( + (d, i) => + (i > 0 + ? d.getDay() !== + new Date(scale.domain()[i - 1]).getDay() + : d.getDay()), + ); + } + + let newTicks = scale.ticks(getTimeTicks(interval)); + const dayCheck = scale.domain()[0].getDate(); + const monthCheck = scale.domain()[0].getMonth(); + + if (dayCheck !== 1 && monthCheck !== 0) { + newTicks.unshift(scale.domain()[0]); + } + + if ( + interval === 'lustrum' || + interval === 'decade' || + interval === 'jubilee' || + interval === 'century' + ) { + newTicks.push(d3.timeYear(scale.domain()[1])); + } + + if (endTicks) { + newTicks = scale.domain(); + } + + return newTicks; +}; + +export const generateLabels = ( + axis, + { label, plotHeight, parent, align, plotWidth, rem, tickSize, labelWidth }, +) => { + const axisLabel = parent.append('g'); + const defaultLabel = + axis === 'x' + ? getDefaultXAxisLabel(label) + : getDefaultYAxisLabel(label); + + switch (axis) { + case 'x': + axisLabel + .attr('class', 'axis xAxis') + .append('text') + .attr( + 'y', + getXVertical({ + align, + vert: defaultLabel.vert, + plotHeight, + rem, + tickSize, + }), + ) + .attr( + 'x', + getXHorizontal({ hori: defaultLabel.hori, plotWidth }), + ) + .text(defaultLabel.tag); + break; + case 'y': + axisLabel.attr('class', 'axis xAxis'); + + axisLabel + .append('text') + .attr( + 'y', + getYVertical({ vert: defaultLabel.vert, plotHeight }), + ) + .attr( + 'x', + getYHorizontal({ + align, + hori: defaultLabel.hori, + plotWidth, + rem, + tickSize, + labelWidth, + }), + ) + .text(defaultLabel.tag); + break; + default: + throw new Error('No axis direction specified'); + } + + const text = axisLabel.selectAll('text'); + const width = text.node().getBBox().width / 2; + const height = text.node().getBBox().height / 2; + const textX = text.node().getBBox().x + width; + const textY = text.node().getBBox().y + height; + text.attr( + 'transform', + `rotate(${defaultLabel.rotate}, ${textX}, ${textY})`, + ).style('text-anchor', defaultLabel.anchor); +}; + +export const generateBanding = ( + direction, + { parent, bands, scale, plotHeight, plotWidth, labelWidth, align, rem }, +) => { + const selection = parent + .append('g') + .attr('class', 'highlights') + .selectAll('rect') + .data(bands) + .enter() + .append('rect') + .attr(direction, 0); + + const yOffset = scale.step + ? ((scale.step() / 100) * (scale.paddingInner() * 100)) / 2 + : 0; + + switch (direction) { + case 'x': + selection + .attr('height', plotHeight) + .attr('x', d => scale(d.pos) - yOffset) + .attr('width', scale.step ? scale.step() : d => d.width); + break; + case 'y': + // prettier-ignore + selection + .attr('width', () => (align === 'left' || !rem + ? plotWidth - labelWidth + : plotWidth - labelWidth - rem)) + .attr('y', d => scale(d.pos) - yOffset) + .attr('height', scale.step ? scale.step() : d => d.height); + break; + default: + throw new Error('No axis direction specified for banding'); + } +}; diff --git a/src/xDate.js b/src/xDate.js index 790e07b..2784a9c 100644 --- a/src/xDate.js +++ b/src/xDate.js @@ -1,4 +1,20 @@ +/** + * @file + * Date x-axes + */ + import * as d3 from 'd3'; +import { + convertToPointScale, + generateBanding, + generateDateTickValues, + generateLabels, + getAxis, + getBandWidth, + getTimeTickFormat, + getTimeTicksMinor, + setLabelIds, +} from './utils'; export default function xaxisDate() { let banding; @@ -28,82 +44,32 @@ export default function xaxisDate() { function axis(parent) { const plotWidth = plotDim[0]; const plotHeight = plotDim[1]; - - function getAxis(alignment) { - if (intraday) { - const newDomain = scale.domain(); - const newRange = scale.range(); - scale = d3 - .scalePoint() - .domain(newDomain) - .range(newRange); - return { - top: d3.axisTop(), - bottom: d3.axisBottom(), - }[alignment]; - } - return { - top: d3.axisTop(), - bottom: d3.axisBottom(), - }[alignment]; - } - const xAxis = getAxis(align); + if (intraday) { - xAxis - .tickSize(tickSize) - .tickFormat(tickFormat(interval)) - .scale(scale); - xAxis.tickValues( - scale.domain().filter((d, i) => { - let checkDate; - if (i === 0) { - return d.getDay(); - } - if (i > 0) { - checkDate = new Date(scale.domain()[i - 1]); - } - return d.getDay() !== checkDate.getDay(); + scale = convertToPointScale(scale); + } + + xAxis + .tickSize(tickSize) + .tickFormat(getTimeTickFormat(interval, { fullYear, scale })) + .scale(scale) + .tickValues( + generateDateTickValues({ + intraday, + scale, + interval, + endTicks, }), ); - } else { - xAxis - .tickSize(tickSize) - // .ticks(getTicks(interval)) - .tickFormat(tickFormat(interval)) - .scale(scale); - let newTicks = scale.ticks(getTicks(interval)); - const dayCheck = scale.domain()[0].getDate(); - const monthCheck = scale.domain()[0].getMonth(); - if (dayCheck !== 1 && monthCheck !== 0) { - newTicks.unshift(scale.domain()[0]); - } - if ( - interval === 'lustrum' || - interval === 'decade' || - interval === 'jubilee' || - interval === 'century' - ) { - newTicks.push(d3.timeYear(scale.domain()[1])); - } - if (endTicks) { - newTicks = scale.domain(); - } - xAxis.tickValues(newTicks); - } - const xMinor = getAxis(align); - if (intraday) { - xMinor - .tickSize(minorTickSize) - .tickFormat('') - .scale(scale); - } else { - xMinor - .tickSize(minorTickSize) - .ticks(getTicksMinor(interval)) - .tickFormat('') - .scale(scale); + const xMinor = getAxis(align) + .tickSize(minorTickSize) + .tickFormat('') + .scale(scale); + + if (!intraday) { + xMinor.ticks(getTimeTicksMinor(interval)); } if (tickValues) { @@ -114,8 +80,6 @@ export default function xaxisDate() { xAxis.tickFormat(customFormat); } - const bandHolder = parent.append('g').attr('class', 'highlights'); - xLabel = parent .append('g') .attr('class', 'axis xAxis axis baseline') @@ -134,12 +98,7 @@ export default function xaxisDate() { } if (frameName) { - xLabel - .selectAll('.axis.xAxis text') - .attr('id', `${frameName}xLabel`); - xLabel - .selectAll('.axis.xAxis line') - .attr('id', `${frameName}xTick`); + setLabelIds({ selection: xLabel, axis: 'x', frameName }); if (minorAxis) { xLabelMinor .selectAll('.axis.xAxis line') @@ -148,251 +107,36 @@ export default function xaxisDate() { } if (label) { - const defaultLabel = { - tag: label.tag, - hori: label.hori || 'middle', - vert: label.vert || 'bottom', - anchor: label.anchor || 'middle', - rotate: label.rotate || 0, - }; - - const axisLabel = parent.append('g').attr('class', 'axis xAxis'); - const calcOffset = () => { - if (tickSize > 0 && tickSize < rem) { - return tickSize + (rem * 0.8); // prettier-ignore - } - return rem * 0.9; - }; - const getVertical = (axisAlign, vertAlign) => - ({ - toptop: 0 - rem, - topmiddle: 0, - topbottom: 0 + rem, - bottomtop: plotHeight, - bottommiddle: plotHeight + calcOffset(), - bottombottom: plotHeight + calcOffset() + (rem * 1.1), // prettier-ignore - }[axisAlign + vertAlign]); - - const getHorizontal = hori => - ({ - left: plotWidth - plotWidth, - middle: plotWidth / 2, - right: plotWidth, - }[hori]); - - axisLabel - .append('text') - .attr('y', getVertical(align, defaultLabel.vert)) - .attr('x', getHorizontal(defaultLabel.hori)) - .text(defaultLabel.tag); - - const text = axisLabel.selectAll('text'); - const width = text.node().getBBox().width / 2; - const height = text.node().getBBox().height / 2; - const textX = text.node().getBBox().x + width; - const textY = text.node().getBBox().y + height; - text.attr( - 'transform', - `rotate(${defaultLabel.rotate}, ${textX}, ${textY})`, - ).style('text-anchor', defaultLabel.anchor); + generateLabels('x', { + align, + label, + parent, + plotHeight, + plotWidth, + rem, + tickSize, + }); } if (banding) { - let bands = xAxis.tickValues(); - const getBandWidth = (index) => { - if (index === bands.length - 1) { - return plotWidth - scale(bands[index]); - } - return scale(bands[index + 1]) - scale(bands[index]); - }; - bands = bands - .map((d, i) => ({ - date: d, - width: getBandWidth(i), + const bands = xAxis + .tickValues() + .map((d, i, a) => ({ + pos: d, + width: getBandWidth({ + index: i, + bands: a, + plotWidth, + scale, + }), })) .filter((d, i) => i % 2 === 0); - console.log('bands', bands); - - bandHolder - .selectAll('rect') - .data(bands) - .enter() - .append('rect') - .attr('y', 0) - .attr('height', plotHeight) - .attr('x', d => scale(d.date)) - .attr('width', d => d.width); + generateBanding('x', { parent, bands, plotHeight, scale }); } xLabel.selectAll('.domain').remove(); } - function getTicks(intvl) { - return { - century: d3.timeYear.every(100), - jubilee: d3.timeYear.every(50), - decade: d3.timeYear.every(10), - lustrum: d3.timeYear.every(5), - years: d3.timeYear.every(1), - fiscal: d3.timeYear.every(1), - quarters: d3.timeYear.every(1), - months: d3.timeMonth.every(1), - weeks: d3.timeWeek.every(1), - daily: d3.timeDay.every(1), - days: d3.timeDay.every(1), - hours: d3.timeHour.every(1), - }[intvl]; - } - function getTicksMinor(intvl) { - return { - century: d3.timeYear.every(10), - jubilee: d3.timeYear.every(10), - decade: d3.timeYear.every(1), - lustrum: d3.timeYear.every(1), - years: d3.timeMonth.every(1), - fiscal: d3.timeMonth.every(1), - quarters: d3.timeMonth.every(3), - months: d3.timeDay.every(1), - weeks: d3.timeDay.every(1), - daily: d3.timeHour.every(1), - days: d3.timeHour.every(1), - hours: d3.timeMinute.every(1), - }[intvl]; - } - - function tickFormat(intvl) { - const formatFullYear = d3.timeFormat('%Y'); - const formatYear = d3.timeFormat('%y'); - const formatMonth = d3.timeFormat('%b'); - const formatWeek = d3.timeFormat('%W'); - const formatDay = d3.timeFormat('%d'); - const formatHour = d3.timeFormat('%H:%M'); - return { - century: d3.timeFormat('%Y'), - jubilee(d, i) { - const format = checkCentury(d, i); - return format; - }, - decade(d, i) { - const format = checkCentury(d, i); - return format; - }, - lustrum(d, i) { - const format = checkCentury(d, i); - return format; - }, - years(d, i) { - const format = checkCentury(d, i); - return format; - }, - fiscal(d, i) { - const format = getFiscal(d, i); - return format; - }, - quarters(d, i) { - const format = getQuarters(d, i); - return format; - }, - months(d, i) { - const format = checkMonth(d, i); - return format; - }, - weeks(d, i) { - const format = getWeek(d, i); - return format; - }, - days(d, i) { - const format = getDays(d, i); - return format; - }, - daily(d, i) { - const format = getDaily(d, i); - return format; - }, - hours(d, i) { - const format = getHours(d, i); - return format; - }, - }[intvl]; - - function getHours(d, i) { - if (d.getHours() === 1 || i === 0) { - return `${formatHour(d)} ${formatDay(d)}`; - } - return formatHour(d); - } - - function getDays(d, i) { - if (d.getDate() === 1 || i === 0) { - return `${formatDay(d)} ${formatMonth(d)}`; - } - return formatDay(d); - } - - function getDaily(d, i) { - const last = scale.domain().length - 1; - if (i === 0) { - return `${formatDay(d)} ${formatMonth(d)}`; - } - if (d.getDate() === 1) { - return `${formatMonth(d)}`; - } - if (d.getDay() === 5) { - return `${formatDay(d)}`; - } - if (i === last) { - return formatDay(d); - } - return ''; - } - - function getWeek(d) { - if (d.getDate() < 9) { - return `${formatWeek(d)} ${formatMonth(d)}`; - } - return formatWeek(d); - } - - function getQuarters(d, i) { - if (d.getMonth() < 3 && i < 4) { - return `Q1 ${formatFullYear(d)}`; - } - if (d.getMonth() < 3) { - return 'Q1'; - } - if (d.getMonth() >= 3 && d.getMonth() < 6) { - return 'Q2'; - } - if (d.getMonth() >= 6 && d.getMonth() < 9) { - return 'Q3'; - } - if (d.getMonth() >= 9 && d.getMonth() < 12) { - return 'Q4'; - } - throw new Error('Invalid quarter'); - } - - function checkMonth(d, i) { - if (d.getMonth() === 0 || i === 0) { - const newYear = d3.timeFormat('%b %Y'); - return newYear(d); - } - return formatMonth(d); - } - - function checkCentury(d, i) { - if (fullYear || +formatFullYear(d) % 100 === 0 || i === 0) { - return formatFullYear(d); - } - return formatYear(d); - } - function getFiscal(d, i) { - if (fullYear || +formatFullYear(d) % 100 === 0 || i === 0) { - return `${formatFullYear(d)}/${Number(formatYear(d)) + 1}`; - } - return `${formatYear(d)}/${Number(formatYear(d)) + 1}`; - } - } axis.align = (d) => { align = d; return axis; diff --git a/src/xLinear.js b/src/xLinear.js index a57b4d4..a2517d1 100644 --- a/src/xLinear.js +++ b/src/xLinear.js @@ -1,4 +1,18 @@ +/** + * @file + * Linear x-axes + */ + import * as d3 from 'd3'; +import { + formatNumber, + generateBanding, + generateLabels, + getAxis, + getBandWidth, + getDecimalFormat, + setLabelIds, +} from './utils'; export default function () { let banding; @@ -21,15 +35,8 @@ export default function () { let tickValues; let customFormat = false; - function getAxis(alignment) { - return { - top: d3.axisTop(), - bottom: d3.axisBottom(), - }[alignment]; - } - function axis(parent) { - let deciCheck = false; + const deciCheck = false; const span = scale.domain()[1] - scale.domain()[0]; const plotWidth = plotDim[0]; const plotHeight = plotDim[1]; @@ -46,49 +53,22 @@ export default function () { scale = newScale; } - let deciFormat; - if (span >= 0.5) { - deciFormat = d3.format('.1f'); - } - if (span < 0.5) { - deciFormat = d3.format('.2f'); - } - if (span <= 0.011) { - deciFormat = d3.format('.3f'); - } - if (span < 0.0011) { - deciFormat = d3.format('.4f'); - } - if (span < 0.00011) { - deciFormat = d3.format('.5f'); - } - if (span < 0.000011) { - deciFormat = d3.format('.6f'); - } + const deciFormat = getDecimalFormat(span); const numberFormat = d3.format(','); const xAxis = getAxis(align) .tickSize(tickSize) .ticks(numTicks) .scale(scale) - .tickFormat(formatNumber); - - function formatNumber(d) { - const checkDecimal = Number.isInteger(d / divisor); - if (checkDecimal === false) { - deciCheck = true; - } - if (d / divisor === 0) { - return numberFormat(d / divisor); - } - if (logScale) { - return numberFormat(d / divisor); - } - if (deciCheck) { - return deciFormat(d / divisor); - } - return numberFormat(d / divisor); - } + .tickFormat(d => + formatNumber(d, { + divisor, + numberFormat, + deciFormat, + deciCheck, + logScale, + }), + ); if (tickValues) { xAxis.tickValues(tickValues); @@ -98,8 +78,6 @@ export default function () { xAxis.tickFormat(customFormat); } - const bandHolder = parent.append('g').attr('class', 'highlights'); - xLabel = parent .append('g') .attr('class', 'axis xAxis') @@ -111,93 +89,38 @@ export default function () { .classed('baseline', true); if (frameName) { - xLabel - .selectAll('.axis.xAxis text') - .attr('id', `${frameName}xLabel`); - xLabel - .selectAll('.axis.xAxis line') - .attr('id', `${frameName}xTick`); + setLabelIds({ selection: xLabel, axis: 'x', frameName }); } if (label) { - const defaultLabel = { - tag: label.tag, - hori: label.hori || 'middle', - vert: label.vert || 'bottom', - anchor: label.anchor || 'middle', - rotate: label.rotate || 0, - }; - - const axisLabel = parent.append('g').attr('class', 'axis xAxis'); - - const calcOffset = () => { - if (tickSize > 0 && tickSize < rem) { - return tickSize + (rem * 0.8); // prettier-ignore - } - return (rem * 0.9); // prettier-ignore - }; - - const getVertical = (axisAlign, vertAlign) => - ({ - toptop: 0 - rem, - topmiddle: 0, - topbottom: 0 + rem, - bottomtop: plotHeight, - bottommiddle: plotHeight + calcOffset(), - bottombottom: plotHeight + calcOffset() + (rem * 1.1), // prettier-ignore - }[axisAlign + vertAlign]); - - const getHorizontal = hori => - ({ - left: plotWidth - plotWidth, - middle: plotWidth / 2, - right: plotWidth, - }[hori]); - - axisLabel - .append('text') - .attr('y', getVertical(align, defaultLabel.vert)) - .attr('x', getHorizontal(defaultLabel.hori)) - .text(defaultLabel.tag); - - const text = axisLabel.selectAll('text'); - const width = text.node().getBBox().width / 2; - const height = text.node().getBBox().height / 2; - const textX = text.node().getBBox().x + width; - const textY = text.node().getBBox().y + height; - text.attr( - 'transform', - `rotate(${defaultLabel.rotate}, ${textX}, ${textY})`, - ).style('text-anchor', defaultLabel.anchor); + generateLabels('x', { + align, + label, + parent, + plotHeight, + plotWidth, + rem, + tickSize, + }); } if (banding) { - const getBandWidth = (index, bands) => { - if (index === bands.length - 1) { - return plotWidth - scale(bands[index]); - } - return scale(bands[index + 1]) - scale(bands[index]); - }; - const bands = (tickValues ? xAxis.tickValues() : scale.ticks(numTicks) ) .map((d, i, a) => ({ pos: d, - width: getBandWidth(i, a), + width: getBandWidth({ + index: i, + bands: a, + plotWidth, + scale, + }), })) .filter((d, i) => i % 2 === 0); - bandHolder - .selectAll('rect') - .data(bands) - .enter() - .append('rect') - .attr('y', 0) - .attr('height', plotHeight) - .attr('x', d => scale(d.pos)) - .attr('width', d => d.width); + generateBanding('x', { parent, bands, plotHeight, scale }); } xLabel diff --git a/src/xOrdinal.js b/src/xOrdinal.js index 82cf1ae..de36635 100644 --- a/src/xOrdinal.js +++ b/src/xOrdinal.js @@ -1,4 +1,10 @@ +/** + * @file + * Ordinal x-axes + */ + import * as d3 from 'd3'; +import { generateBanding, generateLabels, getAxis, setLabelIds } from './utils'; export default function xAxisOrdinal() { let banding; @@ -36,72 +42,25 @@ export default function xAxisOrdinal() { scale.paddingInner(0.2); } - const bandHolder = parent.append('g').attr('class', 'highlights'); - xLabel = parent .append('g') .attr('class', 'axis xAxis') .call(xAxis); if (frameName) { - xLabel - .selectAll('.axis.xAxis text') - .attr('id', `${frameName}xLabel`); - xLabel - .selectAll('.axis.xAxis line') - .attr('id', `${frameName}xTick`); + setLabelIds({ selection: xLabel, axis: 'x', frameName }); } if (label) { - const calcOffset = () => { - if (tickSize > 0 && tickSize < rem) { - return tickSize + (rem * 0.8); // prettier-ignore - } - return (rem * 0.9); // prettier-ignore - }; - - const getVertical = (axisAlign, vertAlign) => - ({ - toptop: 0 - rem, - topmiddle: 0, - topbottom: 0 + rem, - bottomtop: plotHeight, - bottommiddle: plotHeight + calcOffset(), - bottombottom: plotHeight + calcOffset() + (rem * 1.1), // prettier-ignore - }[axisAlign + vertAlign]); - - const getHorizontal = hori => - ({ - left: plotWidth - plotWidth, - middle: plotWidth / 2, - right: plotWidth, - }[hori]); - - const defaultLabel = { - tag: label.tag, - hori: label.hori || 'middle', - vert: label.vert || 'bottom', - anchor: label.anchor || 'middle', - rotate: label.rotate || 0, - }; - - const axisLabel = parent.append('g').attr('class', 'axis xAxis'); - - axisLabel - .append('text') - .attr('y', getVertical(align, defaultLabel.vert)) - .attr('x', getHorizontal(defaultLabel.hori)) - .text(defaultLabel.tag); - - const text = axisLabel.selectAll('text'); - const width = text.node().getBBox().width / 2; - const height = text.node().getBBox().height / 2; - const textX = text.node().getBBox().x + width; - const textY = text.node().getBBox().y + height; - text.attr( - 'transform', - `rotate(${defaultLabel.rotate}, ${textX}, ${textY})`, - ).style('text-anchor', defaultLabel.anchor); + generateLabels('x', { + align, + label, + parent, + plotHeight, + plotWidth, + rem, + tickSize, + }); } if (banding) { @@ -112,17 +71,7 @@ export default function xAxisOrdinal() { })) .filter((d, i) => i % 2 === 1); - const yOffset = (scale.step() / 100) * (scale.paddingInner() * 100); // prettier-ignore - - // prettier-ignore - bandHolder.selectAll('rect') - .data(bands) - .enter() - .append('rect') - .attr('y', 0) - .attr('height', plotHeight) - .attr('x', d => scale(d.pos) - (yOffset / 2)) - .attr('width', scale.step()); + generateBanding('x', { parent, bands, plotHeight, scale }); } xLabel.selectAll('.domain').remove(); @@ -200,11 +149,6 @@ export default function xAxisOrdinal() { xLabel = d; return axis; }; - function getAxis(alignment) { - return { - top: d3.axisTop(), - bottom: d3.axisBottom(), - }[alignment]; - } + return axis; } diff --git a/src/yDate.js b/src/yDate.js index f316e0d..a519ecf 100644 --- a/src/yDate.js +++ b/src/yDate.js @@ -1,4 +1,19 @@ +/** + * @file + * Date y-axes + */ + import * as d3 from 'd3'; +import { + convertToPointScale, + generateBanding, + generateDateTickValues, + generateLabels, + getAxis, + getTimeTickFormat, + getTimeTicksMinor, + setLabelIds, +} from './utils'; export default function () { let banding; @@ -29,83 +44,30 @@ export default function () { function axis(parent) { const plotWidth = plotDim[0]; const plotHeight = plotDim[1]; - - function getAxis(alignment) { - if (intraday) { - console.log('intraday axis'); // eslint-disable-line - const newDomain = scale.domain(); - const newRange = scale.range(); - scale = d3 - .scalePoint() - .domain(newDomain) - .range(newRange); - return { - left: d3.axisLeft(), - right: d3.axisRight(), - }[alignment]; - } - return { - left: d3.axisLeft(), - right: d3.axisRight(), - }[alignment]; - } - const yAxis = getAxis(align); if (intraday) { - yAxis - .tickSize(tickSize) - .tickFormat(tickFormat(interval)) - .scale(scale); - yAxis.tickValues( - scale.domain().filter((d, i) => { - let checkDate; - if (i === 0) { - return d.getDay(); - } - if (i > 0) { - checkDate = new Date(scale.domain()[i - 1]); - } - return d.getDay() !== checkDate.getDay(); + scale = convertToPointScale(scale); + } + yAxis + .tickSize(tickSize) + .tickFormat(getTimeTickFormat(interval, { fullYear, scale })) + .scale(scale) + .tickValues( + generateDateTickValues({ + intraday, + scale, + interval, + endTicks, }), ); - } else { - yAxis - .tickSize(tickSize) - // .ticks(getTicks(interval)) - .tickFormat(tickFormat(interval)) - .scale(scale); - let newTicks = scale.ticks(getTicks(interval)); - const dayCheck = scale.domain()[0].getDate(); - const monthCheck = scale.domain()[0].getMonth(); - if (dayCheck !== 1 && monthCheck !== 0) { - newTicks.unshift(scale.domain()[0]); - } - if ( - interval === 'lustrum' || - interval === 'decade' || - interval === 'jubilee' || - interval === 'century' - ) { - newTicks.push(d3.timeYear(scale.domain()[1])); - } - if (endTicks) { - newTicks = scale.domain(); - } - yAxis.tickValues(newTicks); - } - const yMinor = getAxis(align); - if (intraday) { - yMinor - .tickSize(minorTickSize) - .tickFormat('') - .scale(scale); - } else { - yMinor - .tickSize(minorTickSize) - .ticks(getTicksMinor(interval)) - .tickFormat('') - .scale(scale); + const yMinor = getAxis(align) + .tickSize(minorTickSize) + .tickFormat('') + .scale(scale); + + if (!intraday) { + yMinor.ticks(getTimeTicksMinor(interval)); } if (tickValues) { @@ -116,8 +78,6 @@ export default function () { yAxis.tickFormat(customFormat); } - const bandHolder = parent.append('g').attr('class', 'highlights'); - yLabel = parent .append('g') .attr('class', 'axis yAxis axis baseline') @@ -161,12 +121,7 @@ export default function () { } if (frameName) { - yLabel - .selectAll('.axis.yAxis text') - .attr('id', `${frameName}yLabel`); - yLabel - .selectAll('.axis.yAxis line') - .attr('id', `${frameName}xTick`); + setLabelIds({ selection: yLabel, axis: 'y', frameName }); if (minorAxis) { yLabelMinor .selectAll('.axis.yAxis line') @@ -174,55 +129,16 @@ export default function () { } } if (label) { - const defaultLabel = { - tag: label.tag, - hori: label.hori || 'left', - vert: label.vert || 'middle', - anchor: label.anchor || 'middle', - rotate: label.rotate || -90, - }; - - const axisLabel = parent.append('g').attr('class', 'axis xAxis'); - - const getVertical = vert => - ({ - top: plotHeight - plotHeight, - middle: plotHeight / 2, - bottom: plotHeight, - }[vert]); - - const calcOffset = () => { - if (tickSize > 0 && tickSize < rem) { - return tickSize / 2; - } - return 0; - }; - - // prettier-ignore - const getHorizontal = (axisAlign, horiAlign) => ({ - leftleft: 0 - (labelWidth + (rem * 0.6)), - leftmiddle: 0 - (labelWidth / 2) - calcOffset(), - leftright: rem * 0.7, - rightleft: plotWidth - labelWidth, - rightmiddle: plotWidth + (labelWidth / 2) + (rem * 0.5) + calcOffset(), - rightright: plotWidth + (rem) + calcOffset(), - }[axisAlign + horiAlign]); - - axisLabel - .append('text') - .attr('y', getVertical(defaultLabel.vert)) - .attr('x', getHorizontal(align, defaultLabel.hori)) - .text(defaultLabel.tag); - - // The following doesn't appear to be used anywhere: - - // const text = axisLabel.selectAll('text'); - // const width = (text.node().getBBox().width) / 2; - // const height = (text.node().getBBox().height) / 2; - // const textX = text.node().getBBox().x + width; - // const textY = text.node().getBBox().y + height; - // text.attr('transform', 'rotate(' + (defaultLabel.rotate) + ', ' + textX + ', ' + textY + ')') - // .style('text-anchor', defaultLabel.anchor); + generateLabels('y', { + align, + label, + labelWidth, + parent, + plotHeight, + plotWidth, + rem, + tickSize, + }); } if (banding) { @@ -236,173 +152,23 @@ export default function () { const bands = yAxis .tickValues() .map((d, i, a) => ({ - date: d, + pos: d, height: getBandWidth(i, a), })) .filter((d, i) => i % 2 === 0); - bandHolder - .selectAll('rect') - .data(bands) - .enter() - .append('rect') - .attr('x', 0) - .attr('width', () => { - if (align === 'left ') { - return plotWidth - labelWidth; - } - return plotWidth - labelWidth - rem; - }) - .attr('y', d => scale(d.date)) - .attr('height', d => d.height); + generateBanding('y', { + parent, + bands, + scale, + plotWidth, + labelWidth, + }); } yLabel.selectAll('.domain').remove(); } - function getTicks(intvl) { - return { - century: d3.timeYear.every(100), - jubilee: d3.timeYear.every(50), - decade: d3.timeYear.every(10), - lustrum: d3.timeYear.every(5), - years: d3.timeYear.every(1), - fiscal: d3.timeYear.every(1), - quarters: d3.timeYear.every(1), - months: d3.timeMonth.every(1), - weeks: d3.timeWeek.every(1), - days: d3.timeDay.every(1), - hours: d3.timeHour.every(1), - }[intvl]; - } - function getTicksMinor(intvl) { - return { - century: d3.timeYear.every(10), - jubilee: d3.timeYear.every(10), - decade: d3.timeYear.every(1), - lustrum: d3.timeYear.every(1), - years: d3.timeMonth.every(1), - fiscal: d3.timeMonth.every(1), - quarters: d3.timeMonth.every(3), - months: d3.timeDay.every(1), - weeks: d3.timeDay.every(1), - days: d3.timeHour.every(1), - hours: d3.timeMinute.every(1), - }[intvl]; - } - - function tickFormat(intvl) { - const formatFullYear = d3.timeFormat('%Y'); - const formatYear = d3.timeFormat('%y'); - const formatMonth = d3.timeFormat('%b'); - const formatWeek = d3.timeFormat('%W'); - const formatDay = d3.timeFormat('%d'); - const formatHour = d3.timeFormat('%H:%M'); - return { - century: d3.timeFormat('%Y'), - jubilee(d, i) { - const format = checkCentury(d, i); - return format; - }, - decade(d, i) { - const format = checkCentury(d, i); - return format; - }, - lustrum(d, i) { - const format = checkCentury(d, i); - return format; - }, - years(d, i) { - const format = checkCentury(d, i); - return format; - }, - fiscal(d, i) { - const format = getFiscal(d, i); - return format; - }, - quarters(d, i) { - const format = getQuarters(d, i); - return format; - }, - months(d, i) { - const format = checkMonth(d, i); - return format; - }, - weeks(d, i) { - const format = getWeek(d, i); - return format; - }, - days(d, i) { - const format = getDays(d, i); - return format; - }, - hours(d, i) { - const format = getHours(d, i); - return format; - }, - }[intvl]; - - function getHours(d, i) { - if (d.getHours() === 1 || i === 0) { - return `${formatHour(d)} ${formatDay(d)}`; - } - return formatHour(d); - } - - function getDays(d, i) { - if (d.getDate() === 1 || i === 0) { - return `${formatDay(d)} ${formatMonth(d)}`; - } - return formatDay(d); - } - - function getWeek(d) { - if (d.getDate() < 9) { - return `${formatWeek(d)} ${formatMonth(d)}`; - } - return formatWeek(d); - } - - function getQuarters(d, i) { - if (d.getMonth() < 3 && i < 4) { - return `Q1 ${formatFullYear(d)}`; - } - if (d.getMonth() < 3) { - return 'Q1'; - } - if (d.getMonth() >= 3 && d.getMonth() < 6) { - return 'Q2'; - } - if (d.getMonth() >= 6 && d.getMonth() < 9) { - return 'Q3'; - } - if (d.getMonth() >= 9 && d.getMonth() < 12) { - return 'Q4'; - } - throw new Error('Invalid quarter'); - } - - function checkMonth(d, i) { - if (d.getMonth() === 0 || i === 0) { - const newYear = d3.timeFormat('%b %Y'); - return newYear(d); - } - return formatMonth(d); - } - - function checkCentury(d, i) { - if (fullYear || +formatFullYear(d) % 100 === 0 || i === 0) { - return formatFullYear(d); - } - return formatYear(d); - } - function getFiscal(d, i) { - if (fullYear || +formatFullYear(d) % 100 === 0 || i === 0) { - return `${formatFullYear(d)}/${Number(formatYear(d)) + 1}`; - } - return `${formatYear(d)}/${Number(formatYear(d)) + 1}`; - } - } axis.align = (d) => { align = d; return axis; diff --git a/src/yLinear.js b/src/yLinear.js index 7181bf5..5ab4d40 100644 --- a/src/yLinear.js +++ b/src/yLinear.js @@ -1,4 +1,17 @@ +/** + * @file + * Linear y-axes + */ + import * as d3 from 'd3'; +import { + formatNumber, + generateBanding, + generateLabels, + getAxis, + getDecimalFormat, + setLabelIds, +} from './utils'; export default function () { let banding; @@ -23,7 +36,7 @@ export default function () { let customFormat = false; function axis(parent) { - let deciCheck = false; + const deciCheck = false; const span = scale.domain()[1] - scale.domain()[0]; const plotWidth = plotDim[0]; const plotHeight = plotDim[1]; @@ -40,48 +53,21 @@ export default function () { scale.range(newRange); } - let deciFormat; - if (span >= 0.5) { - deciFormat = d3.format('.1f'); - } - if (span < 0.5) { - deciFormat = d3.format('.2f'); - } - if (span <= 0.011) { - deciFormat = d3.format('.3f'); - } - if (span < 0.0011) { - deciFormat = d3.format('.4f'); - } - if (span < 0.00011) { - deciFormat = d3.format('.5f'); - } - if (span < 0.000011) { - deciFormat = d3.format('.6f'); - } + const deciFormat = getDecimalFormat(span); const numberFormat = d3.format(','); const yAxis = getAxis(align) .ticks(numTicks) .scale(scale) - .tickFormat(formatNumber); - - function formatNumber(d) { - const checkDecimal = Number.isInteger(d / divisor); - if (checkDecimal === false) { - deciCheck = true; - } - if (d / divisor === 0) { - return numberFormat(d / divisor); - } - if (logScale) { - return numberFormat(d / divisor); - } - if (deciCheck) { - return deciFormat(d / divisor); - } - return numberFormat(d / divisor); - } + .tickFormat(d => + formatNumber(d, { + divisor, + numberFormat, + deciFormat, + deciCheck, + logScale, + }), + ); if (tickValues) { yAxis.tickValues(tickValues); @@ -91,8 +77,6 @@ export default function () { yAxis.tickFormat(customFormat); } - const bandHolder = parent.append('g').attr('class', 'highlights'); - yLabel = parent .append('g') .attr('class', 'axis yAxis') @@ -117,64 +101,20 @@ export default function () { } if (frameName) { - yLabel - .selectAll('.axis.yAxis text') - .attr('id', `${frameName}yLabel`); - yLabel - .selectAll('.axis.yAxis line') - .attr('id', `${frameName}yTick`); + setLabelIds({ selection: yLabel, axis: 'y', frameName }); } if (label) { - const defaultLabel = { - tag: label.tag, - hori: label.hori || 'left', - vert: label.vert || 'middle', - anchor: label.anchor || 'middle', - rotate: label.rotate || -90, - }; - - const axisLabel = parent.append('g').attr('class', 'axis xAxis'); - - const getVertical = vert => - ({ - top: plotHeight - plotHeight, - middle: plotHeight / 2, - bottom: plotHeight, - }[vert]); - - const calcOffset = () => { - if (tickSize > 0 && tickSize < rem) { - return tickSize / 2; - } - return 0; - }; - - // prettier-ignore - const getHorizontal = (axisAlign, horiAlign) => ({ - leftleft: 0 - (labelWidth + (rem * 0.6)), - leftmiddle: 0 - (labelWidth / 2) - calcOffset(), - leftright: rem * 0.7, - rightleft: plotWidth - labelWidth, - rightmiddle: plotWidth + (labelWidth / 2) + (rem * 0.5) + calcOffset(), - rightright: plotWidth + (rem) + calcOffset(), - }[axisAlign + horiAlign]); - - axisLabel - .append('text') - .attr('y', getVertical(defaultLabel.vert)) - .attr('x', getHorizontal(align, defaultLabel.hori)) - .text(defaultLabel.tag); - - const text = axisLabel.selectAll('text'); - const width = text.node().getBBox().width / 2; - const height = text.node().getBBox().height / 2; - const textX = text.node().getBBox().x + width; - const textY = text.node().getBBox().y + height; - text.attr( - 'transform', - `rotate(${defaultLabel.rotate}, ${textX}, ${textY})`, - ).style('text-anchor', defaultLabel.anchor); + generateLabels('y', { + align, + label, + labelWidth, + parent, + plotHeight, + plotWidth, + rem, + tickSize, + }); } if (banding) { @@ -195,15 +135,13 @@ export default function () { })) .filter((d, i) => i % 2 === 0); - bandHolder - .selectAll('rect') - .data(bands) - .enter() - .append('rect') - .attr('x', 0) - .attr('width', plotWidth - labelWidth) - .attr('y', d => scale(d.pos)) - .attr('height', d => d.height); + generateBanding('y', { + parent, + bands, + scale, + plotWidth, + labelWidth, + }); } yLabel @@ -214,13 +152,6 @@ export default function () { yLabel.selectAll('.domain').remove(); } - function getAxis(alignment) { - return { - left: d3.axisLeft(), - right: d3.axisRight(), - }[alignment]; - } - axis.align = (d) => { if (!d) return align; align = d; diff --git a/src/yOrdinal.js b/src/yOrdinal.js index 1f9be5d..a897342 100644 --- a/src/yOrdinal.js +++ b/src/yOrdinal.js @@ -1,4 +1,10 @@ +/** + * @file + * Ordinal y-axes + */ + import * as d3 from 'd3'; +import { generateBanding, generateLabels, getAxis } from './utils'; export default function () { let banding; @@ -20,13 +26,6 @@ export default function () { let frameName; let invert = false; - function getAxis(alignment) { - return { - left: d3.axisLeft(), - right: d3.axisRight(), - }[alignment]; - } - function axis(parent) { const plotWidth = plotDim[0]; const plotHeight = plotDim[1]; @@ -46,8 +45,6 @@ export default function () { scale.paddingInner(0.2); } - const bandHolder = parent.append('g').attr('class', 'highlights'); - yLabel = parent .append('g') .attr('class', 'axis yAxis') @@ -68,55 +65,16 @@ export default function () { } if (label) { - const defaultLabel = { - tag: label.tag, - hori: label.hori || 'left', - vert: label.vert || 'middle', - anchor: label.anchor || 'middle', - rotate: label.rotate || -90, - }; - - const axisLabel = parent.append('g').attr('class', 'axis xAxis'); - - const getVertical = vert => - ({ - top: plotHeight - plotHeight, - middle: plotHeight / 2, - bottom: plotHeight, - }[vert]); - - const calcOffset = () => { - if (tickSize > 0 && tickSize < rem) { - return tickSize / 2; - } - return 0; - }; - - // prettier-ignore - const getHorizontal = (axisAlign, horiAlign) => ({ - leftleft: 0 - (labelWidth + (rem * 0.6)), - leftmiddle: 0 - (labelWidth / 2) - calcOffset(), - leftright: rem * 0.7, - rightleft: plotWidth - labelWidth, - rightmiddle: plotWidth + (labelWidth / 2) + (rem * 0.5) + calcOffset(), - rightright: plotWidth + (rem) + calcOffset(), - }[axisAlign + horiAlign]); - - axisLabel - .append('text') - .attr('y', getVertical(defaultLabel.vert)) - .attr('x', getHorizontal(align, defaultLabel.hori)) - .text(defaultLabel.tag); - - const text = axisLabel.selectAll('text'); - const width = text.node().getBBox().width / 2; - const height = text.node().getBBox().height / 2; - const textX = text.node().getBBox().x + width; - const textY = text.node().getBBox().y + height; - text.attr( - 'transform', - `rotate(${defaultLabel.rotate}, ${textX}, ${textY})`, - ).style('text-anchor', defaultLabel.anchor); + generateLabels('y', { + align, + label, + labelWidth, + parent, + plotHeight, + plotWidth, + rem, + tickSize, + }); } if (banding) { @@ -127,17 +85,14 @@ export default function () { })) .filter((d, i) => i % 2 === 0); - const yOffset = (scale.step() / 100) * (scale.paddingInner() * 100); // prettier-ignore - - // prettier-ignore - bandHolder.selectAll('rect') - .data(bands) - .enter() - .append('rect') - .attr('x', 0) - .attr('width', plotWidth - labelWidth) - .attr('y', d => scale(d.pos) - (yOffset / 2)) - .attr('height', scale.step()); + generateBanding('y', { + parent, + bands, + scale, + plotWidth, + labelWidth, + align, + }); } yLabel.selectAll('.domain').remove();