apexcharts
Version:
A JavaScript Chart Library
425 lines (375 loc) • 11.3 kB
JavaScript
// @ts-check
import Formatters from '../Formatters'
import Graphics from '../Graphics'
import Utils from '../../utils/Utils'
import DateTime from '../../utils/DateTime'
export default class DimXAxis {
/**
* @param {import('./Dimensions').default} dCtx
*/
constructor(dCtx) {
this.w = dCtx.w
this.dCtx = dCtx
}
/**
* Get X Axis Dimensions
* @memberof Dimensions
* @return {{width: number, height: number}}
**/
getxAxisLabelsCoords() {
const w = this.w
let xaxisLabels = w.labelData.labels.slice()
if (w.config.xaxis.convertedCatToNumeric && xaxisLabels.length === 0) {
xaxisLabels = w.labelData.categoryLabels
}
let rect
if (w.labelData.timescaleLabels.length > 0) {
const coords = this.getxAxisTimeScaleLabelsCoords()
rect = {
width: coords.width,
height: coords.height,
}
w.layout.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
const xlbFormatter = w.formatters.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) {
/**
* @param {any} a
* @param {any} b
*/
val = w.globals.yAxisScale[0].result.reduce(
(/** @type {any} */ a, /** @type {any} */ b) =>
a.length > b.length ? a : b,
0,
)
valArr = val
}
const xFormat = new Formatters(this.w)
const timestamp = val
val = xFormat.xLabelFormat(
/** @type {Function} */ (xlbFormatter),
val,
timestamp,
{
i: undefined,
dateFormatter: new DateTime(this.w).formatDate,
w,
},
)
valArr = xFormat.xLabelFormat(
/** @type {Function} */ (xlbFormatter),
valArr,
timestamp,
{
i: undefined,
dateFormatter: new DateTime(this.w).formatDate,
w,
},
)
if (
(w.config.xaxis.convertedCatToNumeric && typeof val === 'undefined') ||
String(val).trim() === ''
) {
val = '1'
valArr = val
}
const graphics = new Graphics(this.w)
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.layout.rotateXLabels = true
/**
* @param {string} text
*/
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.layout.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: number, height: number}}
*/
getxAxisGroupLabelsCoords() {
const w = this.w
if (!w.labelData.hasXaxisGroups) {
return { width: 0, height: 0 }
}
const fontSize =
w.config.xaxis.group.style?.fontSize ||
w.config.xaxis.labels.style.fontSize
/**
* @param {Record<string, any>} g
*/
const xaxisLabels = w.labelData.groups.map(
(/** @type {any} */ g) => g.title,
)
let rect
// prevent changing xaxisLabels to avoid issues in multi-yaxes - fix #522
const val = Utils.getLargestStringFromArr(xaxisLabels)
const valArr = this.dCtx.dimHelpers.getLargestStringFromMultiArr(
val,
xaxisLabels,
)
const graphics = new Graphics(this.w)
const 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: number, height: number}}
**/
getxAxisTitleCoords() {
const w = this.w
let width = 0
let height = 0
if (w.config.xaxis.title.text !== undefined) {
const graphics = new Graphics(this.w)
const rect = graphics.getTextRects(
w.config.xaxis.title.text,
w.config.xaxis.title.style.fontSize,
)
width = rect.width
height = rect.height
}
return {
width,
height,
}
}
getxAxisTimeScaleLabelsCoords() {
const w = this.w
this.dCtx.timescaleLabels = w.labelData.timescaleLabels.slice()
/**
* @param {string} label
*/
const labels = this.dCtx.timescaleLabels.map(
(/** @type {any} */ label) => label.value,
)
// get the longest string from the labels array and also apply label formatter to it
/**
* @param {any} a
* @param {any} b
*/
const val = labels.reduce((/** @type {any} */ a, /** @type {any} */ 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)
const graphics = new Graphics(this.w)
const rect = graphics.getTextRects(
val,
w.config.xaxis.labels.style.fontSize,
)
const totalWidthRotated = rect.width * 1.05 * labels.length
if (
totalWidthRotated > w.layout.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
/**
* @param {Record<string, any>} xaxisLabelCoords
*/
additionalPaddingXLabels(xaxisLabelCoords) {
const w = this.w
const gl = w.globals
const cnf = w.config
const xtype = cnf.xaxis.type
const lbWidth = xaxisLabelCoords.width
gl.skipLastTimelinelabel = false
gl.skipFirstTimelinelabel = false
const isBarOpposite =
w.config.yaxis[0].opposite && w.globals.isBarHorizontal
/**
* @param {number} i
*/
const isCollapsed = (i) => gl.collapsedSeriesIndices.indexOf(i) !== -1
/**
* @param {ApexYAxis} yaxe
*/
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
const lgRightRectWidth =
w.config.legend.position === 'right' && this.dCtx.lgRect.width > 0
? this.dCtx.lgRect.width
: 0
if (
lastLabelPosition >
gl.svgWidth - w.layout.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 && !w.layout.rotateXLabels) {
gl.skipLastTimelinelabel = true
}
} else if (xtype !== 'datetime') {
if (
this.dCtx.gridPad.right < lbWidth / 2 - this.dCtx.yAxisWidthRight &&
!w.layout.rotateXLabels &&
!w.config.xaxis.labels.trim
) {
this.dCtx.xPadRight = lbWidth / 2 + 1
}
}
}
/**
* @param {ApexYAxis} yaxe
* @param {number} i
*/
const padYAxe = (yaxe, i) => {
if (cnf.yaxis.length > 1 && isCollapsed(i)) return
rightPad(yaxe)
}
/**
* @param {ApexYAxis} yaxe
* @param {number} i
*/
cnf.yaxis.forEach((/** @type {any} */ yaxe, /** @type {any} */ 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)
}
})
}
}