import Formatters from '../Formatters' import Graphics from '../Graphics' import Utils from '../../utils/Utils' import DateTime from '../../utils/DateTime' export default class DimXAxis { constructor(dCtx) { this.w = dCtx.w this.dCtx = dCtx } /** * Get X Axis Dimensions * @memberof Dimensions * @return {{width, height}} **/ getxAxisLabelsCoords() { let w = this.w let xaxisLabels = w.globals.labels.slice() if (w.config.xaxis.convertedCatToNumeric && xaxisLabels.length === 0) { xaxisLabels = w.globals.categoryLabels } let rect if (w.globals.timescaleLabels.length > 0) { const coords = this.getxAxisTimeScaleLabelsCoords() rect = { width: coords.width, height: coords.height } w.globals.rotateXLabels = false } else { this.dCtx.lgWidthForSideLegends = (w.config.legend.position === 'left' || w.config.legend.position === 'right') && !w.config.legend.floating ? this.dCtx.lgRect.width : 0 // get the longest string from the labels array and also apply label formatter let xlbFormatter = w.globals.xLabelFormatter // prevent changing xaxisLabels to avoid issues in multi-yaxes - fix #522 let val = Utils.getLargestStringFromArr(xaxisLabels) let valArr = this.dCtx.dimHelpers.getLargestStringFromMultiArr( val, xaxisLabels ) // the labels gets changed for bar charts if (w.globals.isBarHorizontal) { val = w.globals.yAxisScale[0].result.reduce( (a, b) => (a.length > b.length ? a : b), 0 ) valArr = val } let xFormat = new Formatters(this.dCtx.ctx) let timestamp = val val = xFormat.xLabelFormat(xlbFormatter, val, timestamp, { i: undefined, dateFormatter: new DateTime(this.dCtx.ctx).formatDate, w }) valArr = xFormat.xLabelFormat(xlbFormatter, valArr, timestamp, { i: undefined, dateFormatter: new DateTime(this.dCtx.ctx).formatDate, w }) if ( (w.config.xaxis.convertedCatToNumeric && typeof val === 'undefined') || String(val).trim() === '' ) { val = '1' valArr = val } let graphics = new Graphics(this.dCtx.ctx) let xLabelrect = graphics.getTextRects( val, w.config.xaxis.labels.style.fontSize ) let xArrLabelrect = xLabelrect if (val !== valArr) { xArrLabelrect = graphics.getTextRects( valArr, w.config.xaxis.labels.style.fontSize ) } rect = { width: xLabelrect.width >= xArrLabelrect.width ? xLabelrect.width : xArrLabelrect.width, height: xLabelrect.height >= xArrLabelrect.height ? xLabelrect.height : xArrLabelrect.height } if ( (rect.width * xaxisLabels.length > w.globals.svgWidth - this.dCtx.lgWidthForSideLegends - this.dCtx.yAxisWidth - this.dCtx.gridPad.left - this.dCtx.gridPad.right && w.config.xaxis.labels.rotate !== 0) || w.config.xaxis.labels.rotateAlways ) { if (!w.globals.isBarHorizontal) { w.globals.rotateXLabels = true const getRotatedTextRects = (text) => { return graphics.getTextRects( text, w.config.xaxis.labels.style.fontSize, w.config.xaxis.labels.style.fontFamily, `rotate(${w.config.xaxis.labels.rotate} 0 0)`, false ) } xLabelrect = getRotatedTextRects(val) if (val !== valArr) { xArrLabelrect = getRotatedTextRects(valArr) } rect.height = (xLabelrect.height > xArrLabelrect.height ? xLabelrect.height : xArrLabelrect.height) / 1.5 rect.width = xLabelrect.width > xArrLabelrect.width ? xLabelrect.width : xArrLabelrect.width } } else { w.globals.rotateXLabels = false } } if (!w.config.xaxis.labels.show) { rect = { width: 0, height: 0 } } return { width: rect.width, height: rect.height } } /** * Get X Axis Label Group height * @memberof Dimensions * @return {{width, height}} */ getxAxisGroupLabelsCoords() { let w = this.w if (!w.globals.hasGroups) { return { width: 0, height: 0 } } const fontSize = w.config.xaxis.group.style?.fontSize || w.config.xaxis.labels.style.fontSize let xaxisLabels = w.globals.groups.map((g) => g.title) let rect // prevent changing xaxisLabels to avoid issues in multi-yaxes - fix #522 let val = Utils.getLargestStringFromArr(xaxisLabels) let valArr = this.dCtx.dimHelpers.getLargestStringFromMultiArr( val, xaxisLabels ) let graphics = new Graphics(this.dCtx.ctx) let xLabelrect = graphics.getTextRects(val, fontSize) let xArrLabelrect = xLabelrect if (val !== valArr) { xArrLabelrect = graphics.getTextRects(valArr, fontSize) } rect = { width: xLabelrect.width >= xArrLabelrect.width ? xLabelrect.width : xArrLabelrect.width, height: xLabelrect.height >= xArrLabelrect.height ? xLabelrect.height : xArrLabelrect.height } if (!w.config.xaxis.labels.show) { rect = { width: 0, height: 0 } } return { width: rect.width, height: rect.height } } /** * Get X Axis Title Dimensions * @memberof Dimensions * @return {{width, height}} **/ getxAxisTitleCoords() { let w = this.w let width = 0 let height = 0 if (w.config.xaxis.title.text !== undefined) { let graphics = new Graphics(this.dCtx.ctx) let rect = graphics.getTextRects( w.config.xaxis.title.text, w.config.xaxis.title.style.fontSize ) width = rect.width height = rect.height } return { width, height } } getxAxisTimeScaleLabelsCoords() { let w = this.w let rect this.dCtx.timescaleLabels = w.globals.timescaleLabels.slice() let labels = this.dCtx.timescaleLabels.map((label) => label.value) // get the longest string from the labels array and also apply label formatter to it let val = labels.reduce((a, b) => { // if undefined, maybe user didn't pass the datetime(x) values if (typeof a === 'undefined') { console.error( 'You have possibly supplied invalid Date format. Please supply a valid JavaScript Date' ) return 0 } else { return a.length > b.length ? a : b } }, 0) let graphics = new Graphics(this.dCtx.ctx) rect = graphics.getTextRects(val, w.config.xaxis.labels.style.fontSize) let totalWidthRotated = rect.width * 1.05 * labels.length if ( totalWidthRotated > w.globals.gridWidth && w.config.xaxis.labels.rotate !== 0 ) { w.globals.overlappingXLabels = true } return rect } // In certain cases, the last labels gets cropped in xaxis. // Hence, we add some additional padding based on the label length to avoid the last label being cropped or we don't draw it at all additionalPaddingXLabels(xaxisLabelCoords) { const w = this.w const gl = w.globals const cnf = w.config const xtype = cnf.xaxis.type let lbWidth = xaxisLabelCoords.width gl.skipLastTimelinelabel = false gl.skipFirstTimelinelabel = false const isBarOpposite = w.config.yaxis[0].opposite && w.globals.isBarHorizontal const isCollapsed = (i) => gl.collapsedSeriesIndices.indexOf(i) !== -1 const rightPad = (yaxe) => { if (this.dCtx.timescaleLabels && this.dCtx.timescaleLabels.length) { // for timeline labels, we take the last label and check if it exceeds gridWidth const firstimescaleLabel = this.dCtx.timescaleLabels[0] const lastTimescaleLabel = this.dCtx.timescaleLabels[ this.dCtx.timescaleLabels.length - 1 ] const lastLabelPosition = lastTimescaleLabel.position + lbWidth / 1.75 - this.dCtx.yAxisWidthRight const firstLabelPosition = firstimescaleLabel.position - lbWidth / 1.75 + this.dCtx.yAxisWidthLeft let lgRightRectWidth = w.config.legend.position === 'right' && this.dCtx.lgRect.width > 0 ? this.dCtx.lgRect.width : 0 if ( lastLabelPosition > gl.svgWidth - gl.translateX - lgRightRectWidth ) { gl.skipLastTimelinelabel = true } if ( firstLabelPosition < -((!yaxe.show || yaxe.floating) && (cnf.chart.type === 'bar' || cnf.chart.type === 'candlestick' || cnf.chart.type === 'rangeBar' || cnf.chart.type === 'boxPlot') ? lbWidth / 1.75 : 10) ) { gl.skipFirstTimelinelabel = true } } else if (xtype === 'datetime') { // If user has enabled DateTime, but uses own's formatter if (this.dCtx.gridPad.right < lbWidth && !gl.rotateXLabels) { gl.skipLastTimelinelabel = true } } else if (xtype !== 'datetime') { if ( this.dCtx.gridPad.right < lbWidth / 2 - this.dCtx.yAxisWidthRight && !gl.rotateXLabels && !w.config.xaxis.labels.trim && (w.config.xaxis.tickPlacement !== 'between' || w.globals.isBarHorizontal) ) { this.dCtx.xPadRight = lbWidth / 2 + 1 } } } const padYAxe = (yaxe, i) => { if (isCollapsed(i)) return // the code below causes issue apexcharts.js#1989 // after testing with other use-cases, this has no actual value, hence commented // if (xtype !== 'datetime') { // if ( // this.dCtx.gridPad.left < lbWidth / 2 - this.dCtx.yAxisWidthLeft && // !gl.rotateXLabels && // !cnf.xaxis.labels.trim // ) { // this.dCtx.xPadLeft = lbWidth / 2 + 1 // } // } rightPad(yaxe) } cnf.yaxis.forEach((yaxe, i) => { if (isBarOpposite) { if (this.dCtx.gridPad.left < lbWidth) { this.dCtx.xPadLeft = lbWidth / 2 + 1 } this.dCtx.xPadRight = lbWidth / 2 + 1 } else { padYAxe(yaxe, i) } }) } }