UNPKG

ikizmet-apexcharts

Version:

iKizmet fork of Apex Charts library

752 lines (636 loc) 20.1 kB
import Graphics from './Graphics' import Formatters from '../modules/Formatters' import Utils from './../utils/Utils' import YAxis from './axes/YAxis' /** * ApexCharts Dimensions Class for calculating rects of all elements that are drawn and will be drawn. * * @module Dimensions **/ export default class Dimensions { constructor(ctx) { this.ctx = ctx this.w = ctx.w this.lgRect = {} this.yAxisWidth = 0 this.xAxisHeight = 0 this.isSparkline = this.w.config.chart.sparkline.enabled this.xPadRight = 0 this.xPadLeft = 0 } /** * @memberof Dimensions * @param {object} w - chart context **/ plotCoords() { let w = this.w let gl = w.globals let lgRect = this.getLegendsRect() if (gl.axisCharts) { // for line / area / scatter / column this.setGridCoordsForAxisCharts(lgRect) } else { // for pie / donuts / circle this.setGridCoordsForNonAxisCharts(lgRect) } this.titleSubtitleOffset() // after calculating everything, apply padding set by user gl.gridHeight = gl.gridHeight - w.config.grid.padding.top - w.config.grid.padding.bottom gl.gridWidth = gl.gridWidth - w.config.grid.padding.left - w.config.grid.padding.right - this.xPadRight - this.xPadLeft gl.translateX = gl.translateX + w.config.grid.padding.left + this.xPadLeft gl.translateY = gl.translateY + w.config.grid.padding.top } conditionalChecksForAxisCoords(xaxisLabelCoords, xtitleCoords) { const w = this.w this.xAxisHeight = (xaxisLabelCoords.height + xtitleCoords.height) * w.globals.LINE_HEIGHT_RATIO + 15 this.xAxisWidth = xaxisLabelCoords.width if ( this.xAxisHeight - xtitleCoords.height > w.config.xaxis.labels.maxHeight ) { this.xAxisHeight = w.config.xaxis.labels.maxHeight } if ( w.config.xaxis.labels.minHeight && this.xAxisHeight < w.config.xaxis.labels.minHeight ) { this.xAxisHeight = w.config.xaxis.labels.minHeight } if (w.config.xaxis.floating) { this.xAxisHeight = 0 } if (!w.globals.isBarHorizontal) { this.yAxisWidth = this.getTotalYAxisWidth() } else { this.yAxisWidth = w.globals.yLabelsCoords[0].width + w.globals.yTitleCoords[0].width + 15 } let minYAxisWidth = 0 let maxYAxisWidth = 0 w.config.yaxis.forEach((y) => { minYAxisWidth += y.labels.minWidth maxYAxisWidth += y.labels.maxWidth }) if (this.yAxisWidth < minYAxisWidth) { this.yAxisWidth = minYAxisWidth } if (this.yAxisWidth > maxYAxisWidth) { this.yAxisWidth = maxYAxisWidth } } setGridCoordsForAxisCharts(lgRect) { let w = this.w let gl = w.globals let yaxisLabelCoords = this.getyAxisLabelsCoords() let xaxisLabelCoords = this.getxAxisLabelsCoords() let yTitleCoords = this.getyAxisTitleCoords() let xtitleCoords = this.getxAxisTitleCoords() w.globals.yLabelsCoords = [] w.globals.yTitleCoords = [] w.config.yaxis.map((yaxe, index) => { // store the labels and titles coords in global vars w.globals.yLabelsCoords.push({ width: yaxisLabelCoords[index].width, index }) w.globals.yTitleCoords.push({ width: yTitleCoords[index].width, index }) }) this.conditionalChecksForAxisCoords(xaxisLabelCoords, xtitleCoords) gl.translateXAxisY = w.globals.rotateXLabels ? this.xAxisHeight / 8 : -4 gl.translateXAxisX = w.globals.rotateXLabels && w.globals.isXNumeric && w.config.xaxis.labels.rotate <= -45 ? -this.xAxisWidth / 4 : 0 if (w.globals.isBarHorizontal) { gl.rotateXLabels = false gl.translateXAxisY = -1 * (parseInt(w.config.xaxis.labels.style.fontSize) / 1.5) } gl.translateXAxisY = gl.translateXAxisY + w.config.xaxis.labels.offsetY gl.translateXAxisX = gl.translateXAxisX + w.config.xaxis.labels.offsetX let yAxisWidth = this.yAxisWidth let xAxisHeight = this.xAxisHeight gl.xAxisLabelsHeight = this.xAxisHeight gl.xAxisHeight = this.xAxisHeight let translateY = 10 if (!w.config.grid.show || w.config.chart.type === 'radar') { yAxisWidth = 0 xAxisHeight = 35 } if (this.isSparkline) { lgRect = { height: 0, width: 0 } xAxisHeight = 0 yAxisWidth = 0 translateY = 0 } this.additionalPaddingXLabels(xaxisLabelCoords) switch (w.config.legend.position) { case 'bottom': gl.translateY = translateY gl.translateX = yAxisWidth gl.gridHeight = gl.svgHeight - lgRect.height - xAxisHeight - (!this.isSparkline ? (w.globals.rotateXLabels ? 10 : 15) : 0) gl.gridWidth = gl.svgWidth - yAxisWidth break case 'top': gl.translateY = lgRect.height + translateY gl.translateX = yAxisWidth gl.gridHeight = gl.svgHeight - lgRect.height - xAxisHeight - (!this.isSparkline ? (w.globals.rotateXLabels ? 10 : 15) : 0) gl.gridWidth = gl.svgWidth - yAxisWidth break case 'left': gl.translateY = translateY gl.translateX = lgRect.width + yAxisWidth gl.gridHeight = gl.svgHeight - xAxisHeight - 12 gl.gridWidth = gl.svgWidth - lgRect.width - yAxisWidth break case 'right': gl.translateY = translateY gl.translateX = yAxisWidth gl.gridHeight = gl.svgHeight - xAxisHeight - 12 gl.gridWidth = gl.svgWidth - lgRect.width - yAxisWidth - 5 break default: throw new Error('Legend position not supported') } this.setGridXPosForDualYAxis(yTitleCoords, yaxisLabelCoords) // after drawing everything, set the Y axis positions let objyAxis = new YAxis(this.ctx) objyAxis.setYAxisXPosition(yaxisLabelCoords, yTitleCoords) } setGridCoordsForNonAxisCharts(lgRect) { let w = this.w let gl = w.globals let xPad = 0 if (w.config.legend.show && !w.config.legend.floating) { xPad = 20 } let offY = 10 let offX = 0 if (w.config.chart.type === 'pie' || w.config.chart.type === 'donut') { offY = offY + w.config.plotOptions.pie.offsetY offX = offX + w.config.plotOptions.pie.offsetX } else if (w.config.chart.type === 'radialBar') { offY = offY + w.config.plotOptions.radialBar.offsetY offX = offX + w.config.plotOptions.radialBar.offsetX } if (!w.config.legend.show) { gl.gridHeight = gl.svgHeight - 35 gl.gridWidth = gl.gridHeight gl.translateY = offY - 10 gl.translateX = offX + (gl.svgWidth - gl.gridWidth) / 2 return } switch (w.config.legend.position) { case 'bottom': gl.gridHeight = gl.svgHeight - lgRect.height - 35 gl.gridWidth = gl.gridHeight gl.translateY = offY - 20 gl.translateX = offX + (gl.svgWidth - gl.gridWidth) / 2 break case 'top': gl.gridHeight = gl.svgHeight - lgRect.height - 35 gl.gridWidth = gl.gridHeight gl.translateY = lgRect.height + offY gl.translateX = offX + (gl.svgWidth - gl.gridWidth) / 2 break case 'left': gl.gridWidth = gl.svgWidth - lgRect.width - xPad gl.gridHeight = gl.gridWidth gl.translateY = offY gl.translateX = offX + lgRect.width + xPad break case 'right': gl.gridWidth = gl.svgWidth - lgRect.width - xPad - 5 gl.gridHeight = gl.gridWidth gl.translateY = offY gl.translateX = offX + 10 break default: throw new Error('Legend position not supported') } } setGridXPosForDualYAxis(yTitleCoords, yaxisLabelCoords) { let w = this.w w.config.yaxis.map((yaxe, index) => { if ( w.globals.ignoreYAxisIndexes.indexOf(index) === -1 && !w.config.yaxis[index].floating && w.config.yaxis[index].show ) { if (yaxe.opposite) { w.globals.translateX = w.globals.translateX - (yaxisLabelCoords[index].width + yTitleCoords[index].width) - parseInt(w.config.yaxis[index].labels.style.fontSize) / 1.2 - 12 } } }) } // Sometimes, the last labels gets cropped in category/numeric xaxis. // Hence, we add some additional padding based on the label length to avoid the last label being cropped. // NOTE: datetime x-axis won't have any effect with this as we don't know the label length there due to many constraints. additionalPaddingXLabels(xaxisLabelCoords) { const w = this.w if ( (w.config.xaxis.type === 'category' && w.globals.isBarHorizontal) || w.config.xaxis.type === 'numeric' ) { const rightPad = (labels) => { if (w.config.grid.padding.right < labels.width) { this.xPadRight = labels.width / 2 + 1 } } const leftPad = (labels) => { if (w.config.grid.padding.left < labels.width) { this.xPadLeft = labels.width / 2 + 1 } } const lineArea = w.config.chart.type === 'line' || w.config.chart.type === 'area' w.config.yaxis.forEach((yaxe, i) => { let shouldPad = !yaxe.show || yaxe.floating || w.globals.collapsedSeriesIndices.indexOf(i) !== -1 || lineArea || (yaxe.opposite && w.globals.isBarHorizontal) if (shouldPad) { if ( (lineArea && w.globals.isMultipleYAxis && w.globals.collapsedSeriesIndices.indexOf(i) !== -1) || (w.globals.isBarHorizontal && yaxe.opposite) ) { leftPad(xaxisLabelCoords) } if ( (!w.globals.isBarHorizontal && yaxe.opposite && w.globals.collapsedSeriesIndices.indexOf(i) !== -1) || (lineArea && !w.globals.isMultipleYAxis) ) { rightPad(xaxisLabelCoords) } } }) } } titleSubtitleOffset() { const w = this.w const gl = w.globals let gridShrinkOffset = this.isSparkline ? 0 : 10 if (w.config.title.text !== undefined) { gridShrinkOffset += w.config.title.margin } else { gridShrinkOffset += this.isSparkline ? 0 : 5 } if (w.config.subtitle.text !== undefined) { gridShrinkOffset += w.config.subtitle.margin } else { gridShrinkOffset += this.isSparkline ? 0 : 5 } if ( w.config.legend.show && w.config.legend.position === 'bottom' && !w.config.legend.floating && w.config.series.length > 1 ) { gridShrinkOffset += 10 } let titleCoords = this.getTitleSubtitleCoords('title') let subtitleCoords = this.getTitleSubtitleCoords('subtitle') gl.gridHeight = gl.gridHeight - titleCoords.height - subtitleCoords.height - gridShrinkOffset gl.translateY = gl.translateY + titleCoords.height + subtitleCoords.height + gridShrinkOffset } getTotalYAxisWidth() { let w = this.w let yAxisWidth = 0 let padding = 10 const isHiddenYAxis = function(index) { return w.globals.ignoreYAxisIndexes.indexOf(index) > -1 } w.globals.yLabelsCoords.map((yLabelCoord, index) => { let floating = w.config.yaxis[index].floating if (yLabelCoord.width > 0 && !floating) { yAxisWidth = yAxisWidth + yLabelCoord.width + padding if (isHiddenYAxis(index)) { yAxisWidth = yAxisWidth - yLabelCoord.width - padding } } else { yAxisWidth = yAxisWidth + (floating || !w.config.yaxis[index].show ? 0 : 5) } }) w.globals.yTitleCoords.map((yTitleCoord, index) => { let floating = w.config.yaxis[index].floating padding = parseInt(w.config.yaxis[index].title.style.fontSize) if (yTitleCoord.width > 0 && !floating) { yAxisWidth = yAxisWidth + yTitleCoord.width + padding if (isHiddenYAxis(index)) { yAxisWidth = yAxisWidth - yTitleCoord.width - padding } } else { yAxisWidth = yAxisWidth + (floating || !w.config.yaxis[index].show ? 0 : 5) } }) return yAxisWidth } getxAxisTimeScaleLabelsCoords() { let w = this.w let rect let timescaleLabels = w.globals.timelineLabels.slice() if (w.globals.isBarHorizontal && w.config.xaxis.type === 'datetime') { timescaleLabels = w.globals.invertedTimelineLabels.slice() } let labels = timescaleLabels.map((label) => { return label.value }) // get the longest string from the labels array and also apply label formatter to it let val = labels.reduce(function(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.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 } /** * Get X Axis Dimensions * @memberof Dimensions * @return {{width, height}} **/ getxAxisLabelsCoords() { let w = this.w let xaxisLabels = w.globals.labels.slice() let rect if (w.globals.timelineLabels.length > 0) { const coords = this.getxAxisTimeScaleLabelsCoords() rect = { width: coords.width, height: coords.height } } else { let lgWidthForSideLegends = w.config.legend.position === 'left' && w.config.legend.position === 'right' && !w.config.legend.floating ? this.lgRect.width : 0 // get the longest string from the labels array and also apply label formatter to it let labels = [] let xlbFormatter = w.globals.xLabelFormatter xaxisLabels.forEach((xl) => { let xFormat = new Formatters(this.ctx) let label = xFormat.xLabelFormat(xlbFormatter, xl) labels.push(label) }) let val = labels.reduce(function(a, b) { return a.length > b.length ? a : b }, 0) // the labels gets changed for bar charts if (w.globals.isBarHorizontal) { val = w.globals.yAxisScale[0].result.reduce(function(a, b) { return a.length > b.length ? a : b }, 0) } let xFormat = new Formatters(this.ctx) val = xFormat.xLabelFormat(xlbFormatter, val) let graphics = new Graphics(this.ctx) let xLabelrect = graphics.getTextRects( val, w.config.xaxis.labels.style.fontSize ) rect = { width: xLabelrect.width, height: xLabelrect.height } if ( rect.width * xaxisLabels.length > w.globals.svgWidth - lgWidthForSideLegends - this.yAxisWidth && w.config.xaxis.labels.rotate !== 0 ) { if (!w.globals.isBarHorizontal) { w.globals.rotateXLabels = true xLabelrect = graphics.getTextRects( val, w.config.xaxis.labels.style.fontSize, w.config.xaxis.labels.style.fontFamily, `rotate(${w.config.xaxis.labels.rotate} 0 0)`, false ) rect.height = xLabelrect.height / 1.66 } } else { w.globals.rotateXLabels = false } } if (!w.config.xaxis.labels.show) { rect = { width: 0, height: 0 } } return { width: rect.width, height: rect.height } } /** * Get Y Axis Dimensions * @memberof Dimensions * @return {{width, height}} **/ getyAxisLabelsCoords() { let w = this.w let width = 0 let height = 0 let ret = [] let labelPad = 10 w.config.yaxis.map((yaxe, index) => { if ( yaxe.show && yaxe.labels.show && w.globals.yAxisScale[index].result.length ) { let lbFormatter = w.globals.yLabelFormatters[index] // the second parameter -1 is the index of tick which user can use in the formatter let val = lbFormatter(w.globals.yAxisScale[index].niceMax, -1) // if user has specified a custom formatter, and the result is null or empty, we need to discard the formatter and take the value as it is. if (typeof val === 'undefined' || val.length === 0) { val = w.globals.yAxisScale[index].niceMax } if (w.globals.isBarHorizontal) { labelPad = 0 let barYaxisLabels = w.globals.labels.slice() // get the longest string from the labels array and also apply label formatter to it val = barYaxisLabels.reduce(function(a, b) { return a.length > b.length ? a : b }, 0) val = lbFormatter(val, -1) } let graphics = new Graphics(this.ctx) let rect = graphics.getTextRects(val, yaxe.labels.style.fontSize) ret.push({ width: rect.width + labelPad, height: rect.height }) } else { ret.push({ width, height }) } }) return ret } /** * 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.ctx) let rect = graphics.getTextRects( w.config.xaxis.title.text, w.config.xaxis.title.style.fontSize ) width = rect.width height = rect.height } return { width: width, height: height } } /** * Get Y Axis Dimensions * @memberof Dimensions * @return {{width, height}} **/ getyAxisTitleCoords() { let w = this.w let ret = [] w.config.yaxis.map((yaxe, index) => { if (yaxe.show && yaxe.title.text !== undefined) { let graphics = new Graphics(this.ctx) let rect = graphics.getTextRects( yaxe.title.text, yaxe.title.style.fontSize, yaxe.title.style.fontFamily, 'rotate(-90 0 0)', false ) ret.push({ width: rect.width, height: rect.height }) } else { ret.push({ width: 0, height: 0 }) } }) return ret } /** * Get Chart Title/Subtitle Dimensions * @memberof Dimensions * @return {{width, height}} **/ getTitleSubtitleCoords(type) { let w = this.w let width = 0 let height = 0 const floating = type === 'title' ? w.config.title.floating : w.config.subtitle.floating let el = w.globals.dom.baseEl.querySelector(`.apexcharts-${type}-text`) if (el !== null && !floating) { let coord = el.getBoundingClientRect() width = coord.width height = w.globals.axisCharts ? coord.height + 5 : coord.height } return { width, height } } getLegendsRect() { let w = this.w let elLegendWrap = w.globals.dom.baseEl.querySelector('.apexcharts-legend') let lgRect = Object.assign({}, Utils.getBoundingClientRect(elLegendWrap)) if ( elLegendWrap !== null && !w.config.legend.floating && w.config.legend.show ) { this.lgRect = { x: lgRect.x, y: lgRect.y, height: lgRect.height, width: lgRect.height === 0 ? 0 : lgRect.width } } else { this.lgRect = { x: 0, y: 0, height: 0, width: 0 } } return this.lgRect } }