Current File : /home/tradevaly/www/node_modules/apexcharts/src/modules/Data.js |
import CoreUtils from './CoreUtils'
import DateTime from './../utils/DateTime'
import Series from './Series'
import Utils from '../utils/Utils'
import Defaults from './settings/Defaults'
export default class Data {
constructor(ctx) {
this.ctx = ctx
this.w = ctx.w
this.twoDSeries = []
this.threeDSeries = []
this.twoDSeriesX = []
this.seriesGoals = []
this.coreUtils = new CoreUtils(this.ctx)
}
isMultiFormat() {
return this.isFormatXY() || this.isFormat2DArray()
}
// given format is [{x, y}, {x, y}]
isFormatXY() {
const series = this.w.config.series.slice()
const sr = new Series(this.ctx)
this.activeSeriesIndex = sr.getActiveConfigSeriesIndex()
if (
typeof series[this.activeSeriesIndex].data !== 'undefined' &&
series[this.activeSeriesIndex].data.length > 0 &&
series[this.activeSeriesIndex].data[0] !== null &&
typeof series[this.activeSeriesIndex].data[0].x !== 'undefined' &&
series[this.activeSeriesIndex].data[0] !== null
) {
return true
}
}
// given format is [[x, y], [x, y]]
isFormat2DArray() {
const series = this.w.config.series.slice()
const sr = new Series(this.ctx)
this.activeSeriesIndex = sr.getActiveConfigSeriesIndex()
if (
typeof series[this.activeSeriesIndex].data !== 'undefined' &&
series[this.activeSeriesIndex].data.length > 0 &&
typeof series[this.activeSeriesIndex].data[0] !== 'undefined' &&
series[this.activeSeriesIndex].data[0] !== null &&
series[this.activeSeriesIndex].data[0].constructor === Array
) {
return true
}
}
handleFormat2DArray(ser, i) {
const cnf = this.w.config
const gl = this.w.globals
const isBoxPlot =
cnf.chart.type === 'boxPlot' || cnf.series[i].type === 'boxPlot'
for (let j = 0; j < ser[i].data.length; j++) {
if (typeof ser[i].data[j][1] !== 'undefined') {
if (
Array.isArray(ser[i].data[j][1]) &&
ser[i].data[j][1].length === 4 &&
!isBoxPlot
) {
// candlestick nested ohlc format
this.twoDSeries.push(Utils.parseNumber(ser[i].data[j][1][3]))
} else if (ser[i].data[j].length >= 5) {
// candlestick non-nested ohlc format
this.twoDSeries.push(Utils.parseNumber(ser[i].data[j][4]))
} else {
this.twoDSeries.push(Utils.parseNumber(ser[i].data[j][1]))
}
gl.dataFormatXNumeric = true
}
if (cnf.xaxis.type === 'datetime') {
// if timestamps are provided and xaxis type is datetime,
let ts = new Date(ser[i].data[j][0])
ts = new Date(ts).getTime()
this.twoDSeriesX.push(ts)
} else {
this.twoDSeriesX.push(ser[i].data[j][0])
}
}
for (let j = 0; j < ser[i].data.length; j++) {
if (typeof ser[i].data[j][2] !== 'undefined') {
this.threeDSeries.push(ser[i].data[j][2])
gl.isDataXYZ = true
}
}
}
handleFormatXY(ser, i) {
const cnf = this.w.config
const gl = this.w.globals
const dt = new DateTime(this.ctx)
let activeI = i
if (gl.collapsedSeriesIndices.indexOf(i) > -1) {
// fix #368
activeI = this.activeSeriesIndex
}
// get series
for (let j = 0; j < ser[i].data.length; j++) {
if (typeof ser[i].data[j].y !== 'undefined') {
if (Array.isArray(ser[i].data[j].y)) {
this.twoDSeries.push(
Utils.parseNumber(ser[i].data[j].y[ser[i].data[j].y.length - 1])
)
} else {
this.twoDSeries.push(Utils.parseNumber(ser[i].data[j].y))
}
}
if (
typeof ser[i].data[j].goals !== 'undefined' &&
Array.isArray(ser[i].data[j].goals)
) {
if (typeof this.seriesGoals[i] === 'undefined') {
this.seriesGoals[i] = []
}
this.seriesGoals[i].push(ser[i].data[j].goals)
} else {
if (typeof this.seriesGoals[i] === 'undefined') {
this.seriesGoals[i] = []
}
this.seriesGoals[i].push(null)
}
}
// get seriesX
for (let j = 0; j < ser[activeI].data.length; j++) {
const isXString = typeof ser[activeI].data[j].x === 'string'
const isXArr = Array.isArray(ser[activeI].data[j].x)
const isXDate =
!isXArr && !!dt.isValidDate(ser[activeI].data[j].x.toString())
if (isXString || isXDate) {
// user supplied '01/01/2017' or a date string (a JS date object is not supported)
if (isXString || cnf.xaxis.convertedCatToNumeric) {
const isRangeColumn = gl.isBarHorizontal && gl.isRangeData
if (cnf.xaxis.type === 'datetime' && !isRangeColumn) {
this.twoDSeriesX.push(dt.parseDate(ser[activeI].data[j].x))
} else {
// a category and not a numeric x value
this.fallbackToCategory = true
this.twoDSeriesX.push(ser[activeI].data[j].x)
}
} else {
if (cnf.xaxis.type === 'datetime') {
this.twoDSeriesX.push(
dt.parseDate(ser[activeI].data[j].x.toString())
)
} else {
gl.dataFormatXNumeric = true
gl.isXNumeric = true
this.twoDSeriesX.push(parseFloat(ser[activeI].data[j].x))
}
}
} else if (isXArr) {
// a multiline label described in array format
this.fallbackToCategory = true
this.twoDSeriesX.push(ser[activeI].data[j].x)
} else {
// a numeric value in x property
gl.isXNumeric = true
gl.dataFormatXNumeric = true
this.twoDSeriesX.push(ser[activeI].data[j].x)
}
}
if (ser[i].data[0] && typeof ser[i].data[0].z !== 'undefined') {
for (let t = 0; t < ser[i].data.length; t++) {
this.threeDSeries.push(ser[i].data[t].z)
}
gl.isDataXYZ = true
}
}
handleRangeData(ser, i) {
const cnf = this.w.config
const gl = this.w.globals
let range = {}
if (this.isFormat2DArray()) {
range = this.handleRangeDataFormat('array', ser, i)
} else if (this.isFormatXY()) {
range = this.handleRangeDataFormat('xy', ser, i)
}
gl.seriesRangeStart.push(range.start)
gl.seriesRangeEnd.push(range.end)
if (cnf.xaxis.type === 'datetime') {
gl.seriesRangeBarTimeline.push(range.rangeUniques)
}
// check for overlaps to avoid clashes in a timeline chart
gl.seriesRangeBarTimeline.forEach((sr, si) => {
if (sr) {
sr.forEach((sarr, sarri) => {
sarr.y.forEach((arr, arri) => {
for (let sri = 0; sri < sarr.y.length; sri++) {
if (arri !== sri) {
const range1y1 = arr.y1
const range1y2 = arr.y2
const range2y1 = sarr.y[sri].y1
const range2y2 = sarr.y[sri].y2
if (range1y1 <= range2y2 && range2y1 <= range1y2) {
if (sarr.overlaps.indexOf(arr.rangeName) < 0) {
sarr.overlaps.push(arr.rangeName)
}
if (sarr.overlaps.indexOf(sarr.y[sri].rangeName) < 0) {
sarr.overlaps.push(sarr.y[sri].rangeName)
}
}
}
}
})
})
}
})
return range
}
handleCandleStickBoxData(ser, i) {
const gl = this.w.globals
let ohlc = {}
if (this.isFormat2DArray()) {
ohlc = this.handleCandleStickBoxDataFormat('array', ser, i)
} else if (this.isFormatXY()) {
ohlc = this.handleCandleStickBoxDataFormat('xy', ser, i)
}
gl.seriesCandleO[i] = ohlc.o
gl.seriesCandleH[i] = ohlc.h
gl.seriesCandleM[i] = ohlc.m
gl.seriesCandleL[i] = ohlc.l
gl.seriesCandleC[i] = ohlc.c
return ohlc
}
handleRangeDataFormat(format, ser, i) {
const rangeStart = []
const rangeEnd = []
const uniqueKeys = ser[i].data
.filter(
(thing, index, self) => index === self.findIndex((t) => t.x === thing.x)
)
.map((r, index) => {
return {
x: r.x,
overlaps: [],
y: []
}
})
const err =
'Please provide [Start, End] values in valid format. Read more https://apexcharts.com/docs/series/#rangecharts'
const serObj = new Series(this.ctx)
const activeIndex = serObj.getActiveConfigSeriesIndex()
if (format === 'array') {
if (ser[activeIndex].data[0][1].length !== 2) {
throw new Error(err)
}
for (let j = 0; j < ser[i].data.length; j++) {
rangeStart.push(ser[i].data[j][1][0])
rangeEnd.push(ser[i].data[j][1][1])
}
} else if (format === 'xy') {
if (ser[activeIndex].data[0].y.length !== 2) {
throw new Error(err)
}
for (let j = 0; j < ser[i].data.length; j++) {
const id = Utils.randomId()
const x = ser[i].data[j].x
const y = {
y1: ser[i].data[j].y[0],
y2: ser[i].data[j].y[1],
rangeName: id
}
// mutating config object by adding a new property
// TODO: As this is specifically for timeline rangebar charts, update the docs mentioning the series only supports xy format
ser[i].data[j].rangeName = id
const uI = uniqueKeys.findIndex((t) => t.x === x)
uniqueKeys[uI].y.push(y)
rangeStart.push(y.y1)
rangeEnd.push(y.y2)
}
}
return {
start: rangeStart,
end: rangeEnd,
rangeUniques: uniqueKeys
}
}
handleCandleStickBoxDataFormat(format, ser, i) {
const w = this.w
const isBoxPlot =
w.config.chart.type === 'boxPlot' || w.config.series[i].type === 'boxPlot'
const serO = []
const serH = []
const serM = []
const serL = []
const serC = []
if (format === 'array') {
if (
(isBoxPlot && ser[i].data[0].length === 6) ||
(!isBoxPlot && ser[i].data[0].length === 5)
) {
for (let j = 0; j < ser[i].data.length; j++) {
serO.push(ser[i].data[j][1])
serH.push(ser[i].data[j][2])
if (isBoxPlot) {
serM.push(ser[i].data[j][3])
serL.push(ser[i].data[j][4])
serC.push(ser[i].data[j][5])
} else {
serL.push(ser[i].data[j][3])
serC.push(ser[i].data[j][4])
}
}
} else {
for (let j = 0; j < ser[i].data.length; j++) {
if (Array.isArray(ser[i].data[j][1])) {
serO.push(ser[i].data[j][1][0])
serH.push(ser[i].data[j][1][1])
if (isBoxPlot) {
serM.push(ser[i].data[j][1][2])
serL.push(ser[i].data[j][1][3])
serC.push(ser[i].data[j][1][4])
} else {
serL.push(ser[i].data[j][1][2])
serC.push(ser[i].data[j][1][3])
}
}
}
}
} else if (format === 'xy') {
for (let j = 0; j < ser[i].data.length; j++) {
if (Array.isArray(ser[i].data[j].y)) {
serO.push(ser[i].data[j].y[0])
serH.push(ser[i].data[j].y[1])
if (isBoxPlot) {
serM.push(ser[i].data[j].y[2])
serL.push(ser[i].data[j].y[3])
serC.push(ser[i].data[j].y[4])
} else {
serL.push(ser[i].data[j].y[2])
serC.push(ser[i].data[j].y[3])
}
}
}
}
return {
o: serO,
h: serH,
m: serM,
l: serL,
c: serC
}
}
parseDataAxisCharts(ser, ctx = this.ctx) {
const cnf = this.w.config
const gl = this.w.globals
const dt = new DateTime(ctx)
const xlabels =
cnf.labels.length > 0 ? cnf.labels.slice() : cnf.xaxis.categories.slice()
gl.isTimelineBar =
cnf.chart.type === 'rangeBar' && cnf.xaxis.type === 'datetime'
const handleDates = () => {
for (let j = 0; j < xlabels.length; j++) {
if (typeof xlabels[j] === 'string') {
// user provided date strings
let isDate = dt.isValidDate(xlabels[j])
if (isDate) {
this.twoDSeriesX.push(dt.parseDate(xlabels[j]))
} else {
throw new Error(
'You have provided invalid Date format. Please provide a valid JavaScript Date'
)
}
} else {
// user provided timestamps
this.twoDSeriesX.push(xlabels[j])
}
}
}
for (let i = 0; i < ser.length; i++) {
this.twoDSeries = []
this.twoDSeriesX = []
this.threeDSeries = []
if (typeof ser[i].data === 'undefined') {
console.error(
"It is a possibility that you may have not included 'data' property in series."
)
return
}
if (
cnf.chart.type === 'rangeBar' ||
cnf.chart.type === 'rangeArea' ||
ser[i].type === 'rangeBar' ||
ser[i].type === 'rangeArea'
) {
gl.isRangeData = true
this.handleRangeData(ser, i)
}
if (this.isMultiFormat()) {
if (this.isFormat2DArray()) {
this.handleFormat2DArray(ser, i)
} else if (this.isFormatXY()) {
this.handleFormatXY(ser, i)
}
if (
cnf.chart.type === 'candlestick' ||
ser[i].type === 'candlestick' ||
cnf.chart.type === 'boxPlot' ||
ser[i].type === 'boxPlot'
) {
this.handleCandleStickBoxData(ser, i)
}
gl.series.push(this.twoDSeries)
gl.labels.push(this.twoDSeriesX)
gl.seriesX.push(this.twoDSeriesX)
gl.seriesGoals = this.seriesGoals
if (i === this.activeSeriesIndex && !this.fallbackToCategory) {
gl.isXNumeric = true
}
} else {
if (cnf.xaxis.type === 'datetime') {
// user didn't supplied [{x,y}] or [[x,y]], but single array in data.
// Also labels/categories were supplied differently
gl.isXNumeric = true
handleDates()
gl.seriesX.push(this.twoDSeriesX)
} else if (cnf.xaxis.type === 'numeric') {
gl.isXNumeric = true
if (xlabels.length > 0) {
this.twoDSeriesX = xlabels
gl.seriesX.push(this.twoDSeriesX)
}
}
gl.labels.push(this.twoDSeriesX)
const singleArray = ser[i].data.map((d) => Utils.parseNumber(d))
gl.series.push(singleArray)
}
gl.seriesZ.push(this.threeDSeries)
if (ser[i].name !== undefined) {
gl.seriesNames.push(ser[i].name)
} else {
gl.seriesNames.push('series-' + parseInt(i + 1, 10))
}
// overrided default color if user inputs color with series data
if (ser[i].color !== undefined) {
gl.seriesColors.push(ser[i].color)
} else {
gl.seriesColors.push(undefined)
}
}
return this.w
}
parseDataNonAxisCharts(ser) {
const gl = this.w.globals
const cnf = this.w.config
gl.series = ser.slice()
gl.seriesNames = cnf.labels.slice()
for (let i = 0; i < gl.series.length; i++) {
if (gl.seriesNames[i] === undefined) {
gl.seriesNames.push('series-' + (i + 1))
}
}
return this.w
}
/** User possibly set string categories in xaxis.categories or labels prop
* Or didn't set xaxis labels at all - in which case we manually do it.
* If user passed series data as [[3, 2], [4, 5]] or [{ x: 3, y: 55 }],
* this shouldn't be called
* @param {array} ser - the series which user passed to the config
*/
handleExternalLabelsData(ser) {
const cnf = this.w.config
const gl = this.w.globals
if (cnf.xaxis.categories.length > 0) {
// user provided labels in xaxis.category prop
gl.labels = cnf.xaxis.categories
} else if (cnf.labels.length > 0) {
// user provided labels in labels props
gl.labels = cnf.labels.slice()
} else if (this.fallbackToCategory) {
// user provided labels in x prop in [{ x: 3, y: 55 }] data, and those labels are already stored in gl.labels[0], so just re-arrange the gl.labels array
gl.labels = gl.labels[0]
if (gl.seriesRangeBarTimeline.length) {
gl.seriesRangeBarTimeline.map((srt) => {
srt.forEach((sr) => {
if (gl.labels.indexOf(sr.x) < 0 && sr.x) {
gl.labels.push(sr.x)
}
})
})
gl.labels = gl.labels.filter(
(elem, pos, arr) => arr.indexOf(elem) === pos
)
}
if (cnf.xaxis.convertedCatToNumeric) {
const defaults = new Defaults(cnf)
defaults.convertCatToNumericXaxis(cnf, this.ctx, gl.seriesX[0])
this._generateExternalLabels(ser)
}
} else {
this._generateExternalLabels(ser)
}
}
_generateExternalLabels(ser) {
const gl = this.w.globals
const cnf = this.w.config
// user didn't provided any labels, fallback to 1-2-3-4-5
let labelArr = []
if (gl.axisCharts) {
if (gl.series.length > 0) {
for (let i = 0; i < gl.series[gl.maxValsInArrayIndex].length; i++) {
labelArr.push(i + 1)
}
}
gl.seriesX = []
// create gl.seriesX as it will be used in calculations of x positions
for (let i = 0; i < ser.length; i++) {
gl.seriesX.push(labelArr)
}
// turn on the isXNumeric flag to allow minX and maxX to function properly
gl.isXNumeric = true
}
// no series to pull labels from, put a 0-10 series
// possibly, user collapsed all series. Hence we can't work with above calc
if (labelArr.length === 0) {
labelArr = gl.axisCharts
? []
: gl.series.map((gls, glsi) => {
return glsi + 1
})
for (let i = 0; i < ser.length; i++) {
gl.seriesX.push(labelArr)
}
}
// Finally, pass the labelArr in gl.labels which will be printed on x-axis
gl.labels = labelArr
if (cnf.xaxis.convertedCatToNumeric) {
gl.categoryLabels = labelArr.map((l) => {
return cnf.xaxis.labels.formatter(l)
})
}
// Turn on this global flag to indicate no labels were provided by user
gl.noLabelsProvided = true
}
// Segregate user provided data into appropriate vars
parseData(ser) {
let w = this.w
let cnf = w.config
let gl = w.globals
this.excludeCollapsedSeriesInYAxis()
// If we detected string in X prop of series, we fallback to category x-axis
this.fallbackToCategory = false
this.ctx.core.resetGlobals()
this.ctx.core.isMultipleY()
if (gl.axisCharts) {
// axisCharts includes line / area / column / scatter
this.parseDataAxisCharts(ser)
} else {
// non-axis charts are pie / donut
this.parseDataNonAxisCharts(ser)
}
this.coreUtils.getLargestSeries()
// set Null values to 0 in all series when user hides/shows some series
if (cnf.chart.type === 'bar' && cnf.chart.stacked) {
const series = new Series(this.ctx)
gl.series = series.setNullSeriesToZeroValues(gl.series)
}
this.coreUtils.getSeriesTotals()
if (gl.axisCharts) {
this.coreUtils.getStackedSeriesTotals()
}
this.coreUtils.getPercentSeries()
if (
!gl.dataFormatXNumeric &&
(!gl.isXNumeric ||
(cnf.xaxis.type === 'numeric' &&
cnf.labels.length === 0 &&
cnf.xaxis.categories.length === 0))
) {
// x-axis labels couldn't be detected; hence try searching every option in config
this.handleExternalLabelsData(ser)
}
// check for multiline xaxis
const catLabels = this.coreUtils.getCategoryLabels(gl.labels)
for (let l = 0; l < catLabels.length; l++) {
if (Array.isArray(catLabels[l])) {
gl.isMultiLineX = true
break
}
}
}
excludeCollapsedSeriesInYAxis() {
const w = this.w
w.globals.ignoreYAxisIndexes = w.globals.collapsedSeries.map(
(collapsed, i) => {
// fix issue #1215
// if stacked, not returning collapsed.index to preserve yaxis
if (this.w.globals.isMultipleYAxis && !w.config.chart.stacked) {
return collapsed.index
}
}
)
}
}