apexcharts
Version:
A JavaScript Chart Library
313 lines (271 loc) • 9.91 kB
JavaScript
import Utils from '../utils/Utils'
import Scales from './Scales'
/**
* Range is used to generates values between min and max.
*
* @module Range
**/
class Range {
constructor (ctx) {
this.ctx = ctx
this.w = ctx.w
this.scales = new Scales(ctx)
}
init () {
this.setYRange()
this.setXRange()
this.setZRange()
}
getMinYMaxY (startingIndex, lowestY = Number.MAX_VALUE, highestY = Number.MIN_SAFE_INTEGER, len = null) {
const gl = this.w.globals
let maxY = -Number.MAX_VALUE
let minY = Number.MIN_VALUE
if (len === null) {
len = startingIndex + 1
}
const series = gl.series
let seriesMin = series
let seriesMax = series
if (this.w.config.chart.type === 'candlestick') {
seriesMin = gl.seriesCandleL
seriesMax = gl.seriesCandleH
}
for (let i = startingIndex; i < len; i++) {
gl.dataPoints = Math.max(gl.dataPoints, series[i].length)
if (Utils.isIE11()) {
minY = Math.min(...seriesMin[i], 0)
}
for (let j = 0; j < gl.series[i].length; j++) {
if (series[i][j] !== null && Utils.isNumber(series[i][j])) {
maxY = Math.max(maxY, seriesMax[i][j])
lowestY = Math.min(lowestY, seriesMin[i][j])
highestY = Math.max(highestY, seriesMin[i][j])
if (Utils.isFloat(series[i][j])) {
gl.yValueDecimal = Math.max(gl.yValueDecimal, series[i][j].toString().split('.')[1].length)
}
if (minY > seriesMin[i][j] && seriesMin[i][j] < 0) {
minY = seriesMin[i][j]
}
} else {
gl.hasNullValues = true
}
}
}
return {
minY,
maxY,
lowestY,
highestY
}
}
setYRange () {
let gl = this.w.globals
let cnf = this.w.config
gl.maxY = -Number.MAX_VALUE
gl.minY = Number.MIN_VALUE
const yaxis = cnf.yaxis
let lowestYInAllSeries = Number.MAX_VALUE
if (gl.isMultipleYAxis) {
// we need to get minY and maxY for multiple y axis
for (let i = 0; i < gl.series.length; i++) {
const minYMaxYArr = this.getMinYMaxY(i, lowestYInAllSeries, null, i + 1)
gl.minYArr.push(minYMaxYArr.minY)
gl.maxYArr.push(minYMaxYArr.maxY)
lowestYInAllSeries = minYMaxYArr.lowestY
}
}
// and then, get the minY and maxY from all series
const minYMaxY = this.getMinYMaxY(0, lowestYInAllSeries, null, gl.series.length)
gl.minY = minYMaxY.minY
gl.maxY = minYMaxY.maxY
lowestYInAllSeries = minYMaxY.lowestY
if (cnf.chart.stacked) {
// for stacked charts, we calculate each series's parallel values. i.e, series[0][j] + series[1][j] .... [series[i.length][j]] and get the max out of it
let stackedPoss = []
let stackedNegs = []
for (let j = 0; j < gl.series[gl.maxValsInArrayIndex].length; j++) {
let poss = 0
let negs = 0
for (let i = 0; i < gl.series.length; i++) {
if (gl.series[i][j] !== null && Utils.isNumber(gl.series[i][j])) {
if (gl.series[i][j] > 0) {
// 0.0001 fixes #185 when values are very small
poss = poss + parseFloat(gl.series[i][j]) + 0.0001
} else {
negs = negs + parseFloat(gl.series[i][j])
}
}
if (i === gl.series.length - 1) {
// push all the totals to the array for future use
stackedPoss.push(poss)
stackedNegs.push(negs)
}
}
}
// get the max/min out of the added parallel values
for (let z = 0; z < stackedPoss.length; z++) {
gl.maxY = Math.max(gl.maxY, stackedPoss[z])
gl.minY = Math.min(gl.minY, stackedNegs[z])
}
}
// if the numbers are too big, reduce the range
// for eg, if number is between 100000-110000, putting 0 as the lowest value is not so good idea. So change the gl.minY for line/area/candlesticks
if (cnf.chart.type === 'line' || cnf.chart.type === 'area' || cnf.chart.type === 'candlestick') {
if (gl.minY === Number.MIN_VALUE && lowestYInAllSeries !== Number.MAX_SAFE_INTEGER) {
let diff = gl.maxY - lowestYInAllSeries
if (lowestYInAllSeries >= 0 && lowestYInAllSeries <= 10) {
// if minY is already 0/low value, we don't want to go negatives here - so this check is essential.
diff = 0
}
gl.minY = (lowestYInAllSeries - (diff * 5) / 100)
// no negatives present and values are small.
if ((lowestYInAllSeries > 0 && gl.maxY < 50) || (lowestYInAllSeries > 0 && gl.minY < 0)) {
gl.minY = 0
}
if (gl.maxY > 10) {
gl.maxY = (gl.maxY + (diff * 5) / 100) + 0.6
}
}
}
cnf.yaxis.map((yaxe, index) => {
// override all min/max values by user defined values (y axis)
if (
yaxe.max !== undefined &&
typeof yaxe.max === 'number'
) {
gl.maxYArr[index] = yaxe.max
// gl.maxY is for single y-axis chart, it will be ignored in multi-yaxis
gl.maxY = yaxis[0].max
}
if (
yaxe.min !== undefined &&
typeof yaxe.min === 'number'
) {
gl.minYArr[index] = yaxe.min
// gl.minY is for single y-axis chart, it will be ignored in multi-yaxis
gl.minY = yaxis[0].min
}
})
// for multi y-axis we need different scales for each
if (gl.isMultipleYAxis) {
this.scales.setMultipleYScales()
gl.yAxisScale.forEach((scale, i) => {
gl.minYArr[i] = scale.niceMin
gl.maxYArr[i] = scale.niceMax
})
} else {
this.scales.setYScaleForIndex(0, gl.minY, gl.maxY)
gl.minY = gl.yAxisScale[0].niceMin
gl.maxY = gl.yAxisScale[0].niceMax
gl.minYArr[0] = gl.yAxisScale[0].niceMin
gl.maxYArr[0] = gl.yAxisScale[0].niceMax
}
}
setXRange () {
let gl = this.w.globals
let cnf = this.w.config
// minX maxX starts here
if (gl.isXNumeric) {
for (let i = 0; i < gl.series.length; i++) {
if (gl.labels[i]) {
for (let j = 0; j < gl.labels[i].length; j++) {
if (gl.labels[i][j] !== null && Utils.isNumber(gl.labels[i][j])) {
gl.maxX = Math.max(gl.maxX, gl.labels[i][j])
gl.initialmaxX = Math.max(gl.maxX, gl.labels[i][j])
gl.minX = Math.min(gl.minX, gl.labels[i][j])
gl.initialminX = Math.min(gl.minX, gl.labels[i][j])
}
}
}
}
}
if (gl.noLabelsProvided) {
if (cnf.xaxis.categories.length === 0) {
gl.maxX = gl.labels[gl.labels.length - 1]
gl.initialmaxX = gl.labels[gl.labels.length - 1]
gl.minX = 1
gl.initialminX = 1
}
}
// for numeric xaxis, we need to adjust some padding left and right for bar charts
if (gl.comboChartsHasBars || (cnf.chart.type === 'bar' && cnf.xaxis.type !== 'category')) {
if (cnf.xaxis.type !== 'category') {
const minX = gl.minX - (gl.svgWidth / gl.dataPoints) * (Math.abs(gl.maxX - gl.minX) / gl.svgWidth) / 3
gl.minX = minX
gl.initialminX = minX
const maxX = gl.maxX + (gl.svgWidth / gl.dataPoints) * (Math.abs(gl.maxX - gl.minX) / gl.svgWidth) / 3
gl.maxX = maxX
gl.initialmaxX = maxX
}
}
if (gl.isXNumeric || gl.noLabelsProvided) {
let ticks
if (cnf.xaxis.tickAmount === undefined) {
ticks = Math.round(gl.svgWidth / 150)
// no labels provided and total number of dataPoints is less than 20
if (cnf.xaxis.type === 'numeric' && gl.dataPoints < 20) {
ticks = gl.dataPoints - 1
}
// this check is for when ticks exceeds total datapoints and that would result in duplicate labels
if (ticks > gl.dataPoints && gl.dataPoints !== 0) {
ticks = gl.dataPoints - 1
}
} else if (cnf.xaxis.tickAmount === 'dataPoints') {
ticks = gl.series[gl.maxValsInArrayIndex].length - 1
} else {
ticks = cnf.xaxis.tickAmount
}
// override all min/max values by user defined values (x axis)
if (
cnf.xaxis.max !== undefined &&
typeof cnf.xaxis.max === 'number'
) {
gl.maxX = cnf.xaxis.max
}
if (
cnf.xaxis.min !== undefined &&
typeof cnf.xaxis.min === 'number'
) {
gl.minX = cnf.xaxis.min
}
// if range is provided, adjust the new minX
if (cnf.xaxis.range !== undefined) {
gl.minX = gl.maxX - cnf.xaxis.range
}
if (gl.minX !== Number.MAX_VALUE && gl.maxX !== -Number.MAX_VALUE) {
gl.xAxisScale = this.scales.linearScale(
gl.minX,
gl.maxX,
ticks
)
} else {
gl.xAxisScale = this.scales.linearScale(1, ticks, ticks)
if (gl.noLabelsProvided && gl.labels.length > 0) {
gl.xAxisScale = this.scales.linearScale(1, gl.labels.length, ticks - 1)
gl.seriesX = gl.labels.slice()
}
}
// we will still store these labels as the count for this will be different (to draw grid and labels placement)
if (cnf.xaxis.type === 'numeric' || cnf.xaxis.type === 'datetime' || (cnf.xaxis.type === 'category' && !gl.noLabelsProvided)) {
gl.labels = gl.xAxisScale.result.slice()
}
}
}
setZRange () {
let gl = this.w.globals
// minZ, maxZ starts here
if (gl.isDataXYZ) {
for (let i = 0; i < gl.series.length; i++) {
if (typeof gl.seriesZ[i] !== 'undefined') {
for (let j = 0; j < gl.seriesZ[i].length; j++) {
if (gl.seriesZ[i][j] !== null && Utils.isNumber(gl.seriesZ[i][j])) {
gl.maxZ = Math.max(gl.maxZ, gl.seriesZ[i][j])
gl.minZ = Math.min(gl.minZ, gl.seriesZ[i][j])
}
}
}
}
}
}
}
export default Range