UNPKG

apexcharts

Version:

A JavaScript Chart Library

366 lines (310 loc) 9.78 kB
import Utils from '../utils/Utils' export default class Range { constructor (ctx) { this.ctx = ctx this.w = ctx.w } // http://stackoverflow.com/questions/326679/choosing-an-attractive-linear-scale-for-a-graphs-y-axiss // This routine creates the Y axis values for a graph. niceScale (yMin, yMax, ticks = 10) { if ( (yMin === Number.MIN_VALUE && yMax === 0) || (!Utils.isNumber(yMin) && !Utils.isNumber(yMax)) ) { // when all values are 0 yMin = 0 yMax = 1 ticks = 1 let linearScale = this.linearScale(yMin, yMax, ticks) return linearScale } if (yMin > yMax) { // if somehow due to some wrong config, user sent max less than min, // adjust the min/max again console.warn('yaxis.min cannot be greater than yaxis.max') yMax = yMin + 0.1 } else if (yMin === yMax) { // If yMin and yMax are identical, then // adjust the yMin and yMax values to actually // make a graph. Also avoids division by zero errors. yMin = yMin === 0 ? 0 : yMin - 0.1 // some small value yMax = yMax === 0 ? 2 : yMax + 0.1 // some small value } // Calculate Min amd Max graphical labels and graph // increments. The number of ticks defaults to // 10 which is the SUGGESTED value. Any tick value // entered is used as a suggested value which is // adjusted to be a 'pretty' value. // // Output will be an array of the Y axis values that // encompass the Y values. let result = [] // Determine Range let range = yMax - yMin let tiks = ticks + 1 // Adjust ticks if needed if (tiks < 2) { tiks = 2 } else if (tiks > 2) { tiks -= 2 } // Get raw step value let tempStep = range / tiks // Calculate pretty step value let mag = Math.floor(Utils.log10(tempStep)) let magPow = Math.pow(10, mag) let magMsd = parseInt(tempStep / magPow) let stepSize = magMsd * magPow // build Y label array. // Lower and upper bounds calculations let lb = stepSize * Math.floor(yMin / stepSize) let ub = stepSize * Math.ceil((yMax / stepSize)) // Build array let val = lb while (1) { result.push(val) val += stepSize if (val > ub) { break } } // TODO: need to remove this condition below which makes this function tightly coupled with w. if (this.w.config.yaxis[0].max === undefined && this.w.config.yaxis[0].min === undefined) { return { result, niceMin: result[0], niceMax: result[result.length - 1] } } else { result = [] let v = yMin result.push(v) let valuesDivider = Math.abs(yMax - yMin) / ticks for (let i = 0; i <= ticks - 1; i++) { v = v + valuesDivider result.push(v) } return { result, niceMin: result[0], niceMax: result[result.length - 1] } } } linearScale (yMin, yMax, ticks = 10) { let range = Math.abs(yMax - yMin) let step = range / ticks if (ticks === Number.MAX_VALUE) { ticks = 10 step = 1 } let result = [] let v = yMin while (ticks >= 0) { result.push(v) v = v + step ticks -= 1 } return { result, niceMin: result[0], niceMax: result[result.length - 1] } } logarithmicScale (index, yMin, yMax, ticks) { const w = this.w if (yMin < 0 || yMin === Number.MIN_VALUE) yMin = 0.01 const base = w.config.yaxis[index].logBase let min = Math.log(yMin) / Math.log(base) let max = Math.log(yMax) / Math.log(base) let range = Math.abs(yMax - yMin) let step = range / ticks let result = [] let v = yMin while (ticks >= 0) { result.push(v) v = v + step ticks -= 1 } const logs = result.map((niceNumber, i) => { if (niceNumber <= 0) { niceNumber = 0.01 } // calculate adjustment factor var scale = (max - min) / (yMax - yMin) const logVal = Math.pow(base, min + scale * (niceNumber - min)) return Math.round(logVal / Utils.roundToBase(logVal, base)) * Utils.roundToBase(logVal, base) }) // Math.floor may have rounded the value to 0, revert back to 1 if (logs[0] === 0) logs[0] = 1 return { result: logs, niceMin: logs[0], niceMax: logs[logs.length - 1] } } setYScaleForIndex (index, minY, maxY) { const gl = this.w.globals const cnf = this.w.config let y = cnf.yaxis[index] if (typeof gl.yAxisScale[index] === 'undefined') { gl.yAxisScale[index] = [] } if (cnf.yaxis[index].logarithmic) { gl.allSeriesCollapsed = false gl.yAxisScale[index] = this.logarithmicScale( index, minY, maxY, y.tickAmount ? y.tickAmount : Math.floor(Math.log10(maxY)) ) } else { if (maxY === -Number.MAX_VALUE || !Utils.isNumber(maxY)) { // no data in the chart. Either all series collapsed or user passed a blank array gl.yAxisScale[index] = this.linearScale( 0, 5, 5 ) } else { // there is some data. Turn off the allSeriesCollapsed flag gl.allSeriesCollapsed = false gl.yAxisScale[index] = this.niceScale( minY, maxY, y.tickAmount ? y.tickAmount : 6 ) } } } setMultipleYScales () { const gl = this.w.globals const cnf = this.w.config const minYArr = gl.minYArr.concat([]) const maxYArr = gl.maxYArr.concat([]) let scalesIndices = [] // here, we loop through the yaxis array and find the item which has "seriesName" property cnf.yaxis.forEach((yaxe, i) => { let index = i cnf.series.forEach((s, si) => { // if seriesName matches and that series is not collapsed, we use that scale if (s.name === yaxe.seriesName && gl.collapsedSeriesIndices.indexOf(si) === -1) { index = si if (i !== si) { scalesIndices.push({ index: si, similarIndex: i, alreadyExists: true }) } else { scalesIndices.push({ index: si }) } } }) let minY = minYArr[index] let maxY = maxYArr[index] this.setYScaleForIndex(i, minY, maxY) }) this.sameScaleInMultipleAxes(minYArr, maxYArr, scalesIndices) } sameScaleInMultipleAxes (minYArr, maxYArr, scalesIndices) { const cnf = this.w.config // we got the scalesIndices array in the above code, but we need to filter out the items which doesn't have same scales let similarIndices = [] scalesIndices.forEach((scale) => { if (scale.alreadyExists) { if (typeof similarIndices[scale.index] === 'undefined') { similarIndices[scale.index] = [] } similarIndices[scale.index].push(scale.index) similarIndices[scale.index].push(scale.similarIndex) } }) function intersect (a, b) { return a.filter(value => b.indexOf(value) !== -1) } similarIndices.forEach((si, i) => { similarIndices.forEach((sj, j) => { if (i !== j) { if (intersect(si, sj).length > 0) { similarIndices[i] = similarIndices[i].concat(similarIndices[j]) } } }) }) // then, we remove duplicates from the similarScale array let uniqueSimilarIndices = similarIndices.map(function (item) { return item.filter((i, pos) => { return item.indexOf(i) === pos }) }) // sort further to remove whole duplicate arrays later let sortedIndices = uniqueSimilarIndices.map((s) => { return s.sort() }) // remove undefined items similarIndices = similarIndices.filter((s) => { return !!s }) let indices = sortedIndices.slice() let stringIndices = indices.map((ind) => { return JSON.stringify(ind) }) indices = indices.filter((ind, p) => { return stringIndices.indexOf(JSON.stringify(ind)) === p }) let sameScaleMinYArr = [] let sameScaleMaxYArr = [] minYArr.forEach((minYValue, yi) => { indices.forEach((scale, i) => { // we compare only the yIndex which exists in the indices array if (scale.indexOf(yi) > -1) { if (typeof sameScaleMinYArr[i] === 'undefined') { sameScaleMinYArr[i] = [] sameScaleMaxYArr[i] = [] } sameScaleMinYArr[i].push({ key: yi, value: minYValue }) sameScaleMaxYArr[i].push({ key: yi, value: maxYArr[yi] }) } }) }) let sameScaleMin = Array(indices.length).fill().map((e, i) => Number.MAX_SAFE_INTEGER) let sameScaleMax = Array(indices.length).fill().map((e, i) => Number.MIN_SAFE_INTEGER) sameScaleMinYArr.forEach((s, i) => { s.forEach((sc, j) => { sameScaleMin[i] = Math.min(sc.value, sameScaleMin[i]) }) }) sameScaleMaxYArr.forEach((s, i) => { s.forEach((sc, j) => { sameScaleMax[i] = Math.max(sc.value, sameScaleMax[i]) }) }) minYArr.forEach((min, i) => { sameScaleMaxYArr.forEach((s, si) => { let minY = sameScaleMin[si] let maxY = sameScaleMax[si] s.forEach((ind, k) => { if (s[k].key === i) { if (cnf.yaxis[i].min !== undefined) { minY = cnf.yaxis[i].min } if (cnf.yaxis[i].max !== undefined) { maxY = cnf.yaxis[i].max } this.setYScaleForIndex(i, minY, maxY) } }) }) }) } }