UNPKG

apexcharts

Version:

A JavaScript Chart Library

602 lines (497 loc) 17.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.isBarHorizontal = !!(this.w.config.chart.type === 'bar' && this.w.config.plotOptions.bar.horizontal) } /** * @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 gl.translateX = gl.translateX + w.config.grid.padding.left gl.translateY = gl.translateY + w.config.grid.padding.top } conditionalChecksForAxisCoords (xaxisLabelCoords, xtitleCoords) { const w = this.w this.xAxisHeight = (xaxisLabelCoords.height + xtitleCoords.height) * w.globals.lineHeightRatio + 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 (!this.isBarHorizontal) { this.yAxisWidth = this.getTotalYAxisWidth() } else { this.yAxisWidth = w.globals.yLabelsCoords[0].width + w.globals.yTitleCoords[0].width + 15 } if (!w.globals.isMultipleYAxis) { if (this.yAxisWidth < w.config.yaxis[0].labels.minWidth) { this.yAxisWidth = w.config.yaxis[0].labels.minWidth } if (this.yAxisWidth > w.config.yaxis[0].labels.maxWidth) { this.yAxisWidth = w.config.yaxis[0].labels.maxWidth } } } 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 ( this.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 } 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') } if (!this.isBarHorizontal) { 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 } } }) } 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() 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 = { width: 0, height: 0 } 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 val = xaxisLabels.reduce(function (a, b) { return a.length > b.length ? a : b }, 0) let xlbFormatter = w.globals.xLabelFormatter 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 (!this.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 (this.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 } }