| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742 |
- import { doUpdate } from '../class/updater.class'
- import { xAxisConfig, yAxisConfig } from '../config'
- import { filterNonNumber, deepMerge, mergeSameStackData } from '../util'
- import { deepClone } from '@jiaminghi/c-render/lib/plugin/util'
- const axisConfig = { xAxisConfig, yAxisConfig }
- const { min, max, abs, pow } = Math
- export function axis (chart, option = {}) {
- let { xAxis, yAxis, series } = option
- let allAxis = []
- if (xAxis && yAxis && series) {
- allAxis = getAllAxis(xAxis, yAxis)
- allAxis = mergeDefaultAxisConfig(allAxis)
- allAxis = allAxis.filter(({ show }) => show)
-
- allAxis = mergeDefaultBoundaryGap(allAxis)
-
- allAxis = calcAxisLabelData(allAxis, series)
-
- allAxis = setAxisPosition(allAxis)
-
- allAxis = calcAxisLinePosition(allAxis, chart)
-
- allAxis = calcAxisTickPosition(allAxis, chart)
-
- allAxis = calcAxisNamePosition(allAxis, chart)
-
- allAxis = calcSplitLinePosition(allAxis, chart)
- }
- doUpdate({
- chart,
- series: allAxis,
- key: 'axisLine',
- getGraphConfig: getLineConfig
- })
- doUpdate({
- chart,
- series: allAxis,
- key: 'axisTick',
- getGraphConfig: getTickConfig
- })
- doUpdate({
- chart,
- series: allAxis,
- key: 'axisLabel',
- getGraphConfig: getLabelConfig
- })
- doUpdate({
- chart,
- series: allAxis,
- key: 'axisName',
- getGraphConfig: getNameConfig
- })
- doUpdate({
- chart,
- series: allAxis,
- key: 'splitLine',
- getGraphConfig: getSplitLineConfig
- })
- chart.axisData = allAxis
- }
- function getAllAxis (xAxis, yAxis) {
- let [allXAxis, allYAxis] = [[], []]
- if (xAxis instanceof Array) {
- allXAxis.push(...xAxis)
- } else {
- allXAxis.push(xAxis)
- }
- if (yAxis instanceof Array) {
- allYAxis.push(...yAxis)
- } else {
- allYAxis.push(yAxis)
- }
- allXAxis.splice(2)
- allYAxis.splice(2)
- allXAxis = allXAxis.map((axis, i) => ({ ...axis, index: i, axis: 'x' }))
- allYAxis = allYAxis.map((axis, i) => ({ ...axis, index: i, axis: 'y' }))
- return [...allXAxis, ...allYAxis]
- }
- function mergeDefaultAxisConfig (allAxis) {
- let xAxis = allAxis.filter(({ axis }) => axis === 'x')
- let yAxis = allAxis.filter(({ axis }) => axis === 'y')
- xAxis = xAxis.map(axis => deepMerge(deepClone(xAxisConfig), axis))
- yAxis = yAxis.map(axis => deepMerge(deepClone(yAxisConfig), axis))
- return [...xAxis, ...yAxis]
- }
- function mergeDefaultBoundaryGap (allAxis) {
- const valueAxis = allAxis.filter(({ data }) => data === 'value')
- const labelAxis = allAxis.filter(({ data }) => data !== 'value')
- valueAxis.forEach(axis => {
- if (typeof axis.boundaryGap === 'boolean') return
- axis.boundaryGap = false
- })
- labelAxis.forEach(axis => {
- if (typeof axis.boundaryGap === 'boolean') return
- axis.boundaryGap = true
- })
- return [...valueAxis, ...labelAxis]
- }
- function calcAxisLabelData (allAxis, series) {
- let valueAxis = allAxis.filter(({ data }) => data === 'value')
- let labelAxis = allAxis.filter(({ data }) => data instanceof Array)
- valueAxis = calcValueAxisLabelData(valueAxis, series)
- labelAxis = calcLabelAxisLabelData(labelAxis)
- return [...valueAxis, ...labelAxis]
- }
- function calcValueAxisLabelData (valueAxis, series) {
- return valueAxis.map(axis => {
- const minMaxValue = getValueAxisMaxMinValue(axis, series)
- const [min, max] = getTrueMinMax(axis, minMaxValue)
- const interval = getValueInterval(min, max, axis)
- const { axisLabel: { formatter } } = axis
- let label = []
- if (min < 0 && max > 0) {
- label = getValueAxisLabelFromZero(min, max, interval)
- } else {
- label = getValueAxisLabelFromMin(min, max, interval)
- }
- label = label.map(l => parseFloat(l.toFixed(2)))
- return {
- ...axis,
- maxValue: label.slice(-1)[0],
- minValue: label[0],
- label: getAfterFormatterLabel(label, formatter)
- }
- })
- }
- function getValueAxisMaxMinValue (axis, series) {
- series = series.filter(({ show, type }) => {
- if (show === false) return false
- if (type === 'pie') return false
- return true
- })
- if (series.length === 0) return [0, 0]
-
- const { index, axis: axisType } = axis
- series = mergeStackData(series)
- const axisName = axisType + 'Axis'
- let valueSeries = series.filter(s => s[axisName] === index)
- if (!valueSeries.length) valueSeries = series
- return getSeriesMinMaxValue(valueSeries)
- }
- function getSeriesMinMaxValue (series) {
- if (!series) return
- const minValue = Math.min(
- ...series
- .map(({ data }) => Math.min(...filterNonNumber(data)))
- )
- const maxValue = Math.max(
- ...series
- .map(({ data }) => Math.max(...filterNonNumber(data)))
- )
- return [minValue, maxValue]
- }
- function mergeStackData (series) {
- const seriesCloned = deepClone(series, true)
- series.forEach((item, i) => {
- const data = mergeSameStackData(item, series)
- seriesCloned[i].data = data
- })
- return seriesCloned
- }
- function getTrueMinMax ({ min, max, axis }, [minValue, maxValue]) {
- let [minType, maxType] = [typeof min, typeof max]
- if (!testMinMaxType(min)) {
- min = axisConfig[axis + 'AxisConfig'].min
- minType = 'string'
- }
- if (!testMinMaxType(max)) {
- max = axisConfig[axis + 'AxisConfig'].max
- maxType = 'string'
- }
- if (minType === 'string') {
- min = parseInt(minValue - abs(minValue * parseFloat(min) / 100))
- const lever = getValueLever(min)
- min = parseFloat((min / lever - 0.1).toFixed(1)) * lever
- }
- if (maxType === 'string') {
- max = parseInt(maxValue + abs(maxValue * parseFloat(max) / 100))
- const lever = getValueLever(max)
- max = parseFloat((max / lever + 0.1).toFixed(1)) * lever
- }
- return [min, max]
- }
- function getValueLever (value) {
- const valueString = abs(value).toString()
- const valueLength = valueString.length
- const firstZeroIndex = valueString.replace(/0*$/g, '').indexOf('0')
- let pow10Num = valueLength - 1
- if (firstZeroIndex !== -1) pow10Num -= firstZeroIndex
- return pow(10, pow10Num)
- }
- function testMinMaxType (val) {
- const valType = typeof val
- const isValidString = (valType === 'string' && /^\d+%$/.test(val))
- const isValidNumber = valType === 'number'
- return isValidString || isValidNumber
- }
- function getValueAxisLabelFromZero (min, max, interval) {
- let [negative, positive] = [[], []]
- let [currentNegative, currentPositive] = [0, 0]
- do {
- negative.push(currentNegative -= interval)
- } while (currentNegative > min)
- do {
- positive.push(currentPositive += interval)
- } while (currentPositive < max)
- return [...negative.reverse(), 0, ...positive]
- }
- function getValueAxisLabelFromMin (min, max, interval) {
- let [label, currentValue] = [[min], min]
- do {
- label.push(currentValue += interval)
- } while (currentValue < max)
- return label
- }
- function getAfterFormatterLabel (label, formatter) {
- if (!formatter) return label
- if (typeof formatter === 'string') label = label.map(l => formatter.replace('{value}', l))
- if (typeof formatter === 'function') label = label.map((value, index) => formatter({ value, index }))
- return label
- }
- function calcLabelAxisLabelData (labelAxis) {
- return labelAxis.map(axis => {
- const { data, axisLabel: { formatter } } = axis
- return { ...axis, label: getAfterFormatterLabel(data, formatter) }
- })
- }
- function getValueInterval (min, max, axis) {
- let { interval, minInterval, maxInterval, splitNumber, axis: axisType } = axis
- const config = axisConfig[axisType + 'AxisConfig']
- if (typeof interval !== 'number') interval = config.interval
- if (typeof minInterval !== 'number') minInterval = config.minInterval
- if (typeof maxInterval !== 'number') maxInterval = config.maxInterval
- if (typeof splitNumber !== 'number') splitNumber = config.splitNumber
- if (typeof interval === 'number') return interval
- let valueInterval = parseInt((max - min) / (splitNumber - 1))
- if (valueInterval.toString().length > 1) valueInterval = parseInt(valueInterval.toString().replace(/\d$/, '0'))
- if (valueInterval === 0) valueInterval = 1
- if (typeof minInterval === 'number' && valueInterval < minInterval) return minInterval
- if (typeof maxInterval === 'number' && valueInterval > maxInterval) return maxInterval
- return valueInterval
- }
- function setAxisPosition (allAxis) {
- const xAxis = allAxis.filter(({ axis }) => axis === 'x')
- const yAxis = allAxis.filter(({ axis }) => axis === 'y')
- if (xAxis[0] && !xAxis[0].position) xAxis[0].position = xAxisConfig.position
- if (xAxis[1] && !xAxis[1].position) {
- xAxis[1].position = xAxis[0].position === 'bottom' ? 'top' : 'bottom'
- }
- if (yAxis[0] && !yAxis[0].position) yAxis[0].position = yAxisConfig.position
- if (yAxis[1] && !yAxis[1].position) {
- yAxis[1].position = yAxis[0].position === 'left' ? 'right' : 'left'
- }
- return [...xAxis, ...yAxis]
- }
- function calcAxisLinePosition (allAxis, chart) {
- const { x, y, w, h } = chart.gridArea
- allAxis = allAxis.map(axis => {
- const { position } = axis
- let linePosition = []
- if (position === 'left') {
- linePosition = [[x, y], [x, y + h]].reverse()
- } else if (position === 'right') {
- linePosition = [[x + w, y], [x + w, y + h]].reverse()
- } else if (position === 'top') {
- linePosition = [[x, y], [x + w, y]]
- } else if (position === 'bottom') {
- linePosition = [[x, y + h], [x + w, y + h]]
- }
- return {
- ...axis,
- linePosition
- }
- })
- return allAxis
- }
- function calcAxisTickPosition (allAxis, chart) {
- return allAxis.map(axisItem => {
- let { axis, linePosition, position, label, boundaryGap } = axisItem
- if (typeof boundaryGap !== 'boolean') boundaryGap = axisConfig[axis + 'AxisConfig'].boundaryGap
- const labelNum = label.length
- const [[startX, startY], [endX, endY]] = linePosition
- const gapLength = axis === 'x' ? endX - startX : endY - startY
- const gap = gapLength / (boundaryGap ? labelNum : labelNum - 1)
- const tickPosition = new Array(labelNum)
- .fill(0)
- .map((foo, i) => {
- if (axis === 'x') {
- return [startX + gap * (boundaryGap ? (i + 0.5) : i), startY]
- }
- return [startX, startY + gap * (boundaryGap ? (i + 0.5) : i)]
- })
- const tickLinePosition = getTickLinePosition(axis, boundaryGap, position, tickPosition, gap)
- return {
- ...axisItem,
- tickPosition,
- tickLinePosition,
- tickGap: gap
- }
- })
- }
- function getTickLinePosition (axisType, boundaryGap, position, tickPosition, gap) {
- let index = axisType === 'x' ? 1 : 0
- let plus = 5
- if (axisType === 'x' && position === 'top') plus = -5
- if (axisType === 'y' && position === 'left') plus = -5
- let tickLinePosition = tickPosition.map(lineStart => {
- const lineEnd = deepClone(lineStart)
- lineEnd[index] += plus
- return [deepClone(lineStart), lineEnd]
- })
- if (!boundaryGap) return tickLinePosition
- index = axisType === 'x' ? 0 : 1
- plus = gap / 2
- tickLinePosition.forEach(([lineStart, lineEnd]) => {
- lineStart[index] += plus
- lineEnd[index] += plus
- })
- return tickLinePosition
- }
- function calcAxisNamePosition (allAxis, chart) {
- return allAxis.map(axisItem => {
- let { nameGap, nameLocation, position, linePosition } = axisItem
- const [lineStart, lineEnd] = linePosition
- let namePosition = [...lineStart]
- if (nameLocation === 'end') namePosition = [...lineEnd]
- if (nameLocation === 'center') {
- namePosition[0] = (lineStart[0] + lineEnd[0]) / 2
- namePosition[1] = (lineStart[1] + lineEnd[1]) / 2
- }
- let index = 0
- if (position === 'top' && nameLocation === 'center') index = 1
- if (position === 'bottom' && nameLocation === 'center') index = 1
- if (position === 'left' && nameLocation !== 'center') index = 1
- if (position === 'right' && nameLocation !== 'center') index = 1
- let plus = nameGap
- if (position === 'top' && nameLocation !== 'end') plus *= -1
- if (position === 'left' && nameLocation !== 'start') plus *= -1
- if (position === 'bottom' && nameLocation === 'start') plus *= -1
- if (position === 'right' && nameLocation === 'end') plus *= -1
- namePosition[index] += plus
- return {
- ...axisItem,
- namePosition
- }
- })
- }
- function calcSplitLinePosition (allAxis, chart) {
- const { w, h } = chart.gridArea
- return allAxis.map(axisItem => {
- const { tickLinePosition, position, boundaryGap } = axisItem
- let [index, plus] = [0, w]
- if (position === 'top' || position === 'bottom') index = 1
- if (position === 'top' || position === 'bottom') plus = h
- if (position === 'right' || position === 'bottom') plus *= -1
- const splitLinePosition = tickLinePosition.map(([startPoint]) => {
- const endPoint = [...startPoint]
- endPoint[index] += plus
- return [[...startPoint], endPoint]
- })
- if (!boundaryGap) splitLinePosition.shift()
- return {
- ...axisItem,
- splitLinePosition
- }
- })
- }
- function getLineConfig (axisItem) {
- const { animationCurve, animationFrame, rLevel } = axisItem
- return [{
- name: 'polyline',
- index: rLevel,
- visible: axisItem.axisLine.show,
- animationCurve,
- animationFrame,
- shape: getLineShape(axisItem),
- style: getLineStyle(axisItem)
- }]
- }
- function getLineShape (axisItem) {
- const { linePosition } = axisItem
- return {
- points: linePosition
- }
- }
- function getLineStyle (axisItem) {
- return axisItem.axisLine.style
- }
- function getTickConfig (axisItem) {
- const { animationCurve, animationFrame, rLevel } = axisItem
- const shapes = getTickShapes(axisItem)
- const style = getTickStyle(axisItem)
- return shapes.map(shape => ({
- name: 'polyline',
- index: rLevel,
- visible: axisItem.axisTick.show,
- animationCurve,
- animationFrame,
- shape,
- style
- }))
- }
- function getTickShapes (axisItem) {
- const { tickLinePosition } = axisItem
- return tickLinePosition.map(points => ({ points }))
- }
- function getTickStyle (axisItem) {
- return axisItem.axisTick.style
- }
- function getLabelConfig (axisItem) {
- const { animationCurve, animationFrame, rLevel } = axisItem
- const shapes = getLabelShapes(axisItem)
- const styles = getLabelStyle(axisItem, shapes)
- return shapes.map((shape, i) => ({
- name: 'text',
- index: rLevel,
- visible: axisItem.axisLabel.show,
- animationCurve,
- animationFrame,
- shape,
- style: styles[i],
- setGraphCenter: () => (void 0)
- }))
- }
- function getLabelShapes (axisItem) {
- const { label, tickPosition, position } = axisItem
- return tickPosition.map((point, i) => ({
- position: getLabelRealPosition(point, position),
- content: label[i].toString(),
- }))
- }
- function getLabelRealPosition (points, position) {
- let [index, plus] = [0, 10]
- if (position === 'top' || position === 'bottom') index = 1
- if (position === 'top' || position === 'left') plus = -10
- points = deepClone(points)
- points[index] += plus
- return points
- }
- function getLabelStyle (axisItem, shapes) {
- const { position } = axisItem
- let { style } = axisItem.axisLabel
- const align = getAxisLabelRealAlign(position)
- style = deepMerge(align, style)
- const styles = shapes.map(({ position }) => ({
- ...style,
- graphCenter: position
- }))
- return styles
- }
- function getAxisLabelRealAlign (position) {
- if (position === 'left') return {
- textAlign: 'right',
- textBaseline: 'middle'
- }
- if (position === 'right') return {
- textAlign: 'left',
- textBaseline: 'middle'
- }
- if (position === 'top') return {
- textAlign: 'center',
- textBaseline: 'bottom'
- }
- if (position === 'bottom') return {
- textAlign: 'center',
- textBaseline: 'top'
- }
- }
- function getNameConfig (axisItem) {
- const { animationCurve, animationFrame, rLevel } = axisItem
- return [{
- name: 'text',
- index: rLevel,
- animationCurve,
- animationFrame,
- shape: getNameShape(axisItem),
- style: getNameStyle(axisItem)
- }]
- }
- function getNameShape (axisItem) {
- const { name, namePosition } = axisItem
- return {
- content: name,
- position: namePosition
- }
- }
- function getNameStyle (axisItem) {
- const { nameLocation, position, nameTextStyle: style } = axisItem
- const align = getNameRealAlign(position, nameLocation)
- return deepMerge(align, style)
- }
- function getNameRealAlign (position, location) {
- if (
- (position === 'top' && location === 'start') ||
- (position === 'bottom' && location === 'start') ||
- (position === 'left' && location === 'center')
- ) return {
- textAlign: 'right',
- textBaseline: 'middle'
- }
- if (
- (position === 'top' && location === 'end') ||
- (position === 'bottom' && location === 'end') ||
- (position === 'right' && location === 'center')
- ) return {
- textAlign: 'left',
- textBaseline: 'middle'
- }
- if (
- (position === 'top' && location === 'center') ||
- (position === 'left' && location === 'end') ||
- (position === 'right' && location === 'end')
- ) return {
- textAlign: 'center',
- textBaseline: 'bottom'
- }
- if (
- (position === 'bottom' && location === 'center') ||
- (position === 'left' && location === 'start') ||
- (position === 'right' && location === 'start')
- ) return {
- textAlign: 'center',
- textBaseline: 'top'
- }
- }
- function getSplitLineConfig (axisItem) {
- const { animationCurve, animationFrame, rLevel } = axisItem
- const shapes = getSplitLineShapes(axisItem)
- const style = getSplitLineStyle(axisItem)
- return shapes.map(shape => ({
- name: 'polyline',
- index: rLevel,
- visible: axisItem.splitLine.show,
- animationCurve,
- animationFrame,
- shape,
- style
- }))
- }
- function getSplitLineShapes (axisItem) {
- const { splitLinePosition } = axisItem
- return splitLinePosition.map(points => ({ points }))
- }
- function getSplitLineStyle (axisItem) {
- return axisItem.splitLine.style
- }
|