| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480 |
- import { doUpdate } from '../class/updater.class'
- import { lineConfig } from '../config'
- import bezierCurve from '@jiaminghi/bezier-curve'
- import { deepMerge, initNeedSeries, mergeSameStackData, getPolylineLength } from '../util'
- const { polylineToBezierCurve, getBezierCurveLength } = bezierCurve
- export function line (chart, option = {}) {
- const { xAxis, yAxis, series } = option
- let lines = []
- if (xAxis && yAxis && series) {
- lines = initNeedSeries(series, lineConfig, 'line')
- lines = calcLinesPosition(lines, chart)
- }
- doUpdate({
- chart,
- series: lines,
- key: 'lineArea',
- getGraphConfig: getLineAreaConfig,
- getStartGraphConfig: getStartLineAreaConfig,
- beforeUpdate: beforeUpdateLineAndArea,
- beforeChange: beforeChangeLineAndArea
- })
- doUpdate({
- chart,
- series: lines,
- key: 'line',
- getGraphConfig: getLineConfig,
- getStartGraphConfig: getStartLineConfig,
- beforeUpdate: beforeUpdateLineAndArea,
- beforeChange: beforeChangeLineAndArea
- })
- doUpdate({
- chart,
- series: lines,
- key: 'linePoint',
- getGraphConfig: getPointConfig,
- getStartGraphConfig: getStartPointConfig
- })
- doUpdate({
- chart,
- series: lines,
- key: 'lineLabel',
- getGraphConfig: getLabelConfig
- })
- }
- function calcLinesPosition (lines, chart) {
- const { axisData } = chart
- return lines.map(lineItem => {
- let lineData = mergeSameStackData(lineItem, lines)
- lineData = mergeNonNumber(lineItem, lineData)
- const lineAxis = getLineAxis(lineItem, axisData)
- const linePosition = getLinePosition(lineData, lineAxis)
- const lineFillBottomPos = getLineFillBottomPos(lineAxis)
- return {
- ...lineItem,
- linePosition: linePosition.filter(p => p),
- lineFillBottomPos
- }
- })
- }
- function mergeNonNumber (lineItem, lineData) {
- const { data } = lineItem
- return lineData.map((v, i) => typeof data[i] === 'number' ? v : null)
- }
- function getLineAxis (line, axisData) {
- const { xAxisIndex, yAxisIndex } = line
- const xAxis = axisData.find(({ axis, index }) => axis === 'x' && index === xAxisIndex)
- const yAxis = axisData.find(({ axis, index }) => axis === 'y' && index === yAxisIndex)
- return [xAxis, yAxis]
- }
- function getLinePosition (lineData, lineAxis) {
- const valueAxisIndex = lineAxis.findIndex(({ data }) => data === 'value')
- const valueAxis = lineAxis[valueAxisIndex]
- const labelAxis = lineAxis[1 - valueAxisIndex]
- const { linePosition, axis } = valueAxis
- const { tickPosition } = labelAxis
- const tickNum = tickPosition.length
- const valueAxisPosIndex = axis === 'x' ? 0 : 1
- const valueAxisStartPos = linePosition[0][valueAxisPosIndex]
- const valueAxisEndPos = linePosition[1][valueAxisPosIndex]
- const valueAxisPosMinus = valueAxisEndPos - valueAxisStartPos
- const { maxValue, minValue } = valueAxis
- const valueMinus = maxValue - minValue
- const position = new Array(tickNum).fill(0)
- .map((foo, i) => {
- const v = lineData[i]
- if (typeof v !== 'number') return null
- let valuePercent = (v - minValue) / valueMinus
- if (valueMinus === 0) valuePercent = 0
- return valuePercent * valueAxisPosMinus + valueAxisStartPos
- })
- return position.map((vPos, i) => {
- if (i >= tickNum || typeof vPos !== 'number') return null
- const pos = [vPos, tickPosition[i][1 - valueAxisPosIndex]]
- if (valueAxisPosIndex === 0) return pos
- pos.reverse()
- return pos
- })
- }
- function getLineFillBottomPos (lineAxis) {
- const valueAxis = lineAxis.find(({ data }) => data === 'value')
- const { axis, linePosition, minValue, maxValue } = valueAxis
- const changeIndex = axis === 'x' ? 0 : 1
- let changeValue = linePosition[0][changeIndex]
- if (minValue < 0 && maxValue > 0) {
- const valueMinus = maxValue - minValue
- const posMinus = Math.abs(linePosition[0][changeIndex] - linePosition[1][changeIndex])
- let offset = Math.abs(minValue) / valueMinus * posMinus
- if (axis === 'y') offset *= -1
- changeValue += offset
- }
- return {
- changeIndex,
- changeValue
- }
- }
- function getLineAreaConfig (lineItem) {
- const { animationCurve, animationFrame, lineFillBottomPos, rLevel } = lineItem
- return [{
- name: getLineGraphName(lineItem),
- index: rLevel,
- animationCurve,
- animationFrame,
- visible: lineItem.lineArea.show,
- lineFillBottomPos,
- shape: getLineAndAreaShape(lineItem),
- style: getLineAreaStyle(lineItem),
- drawed: lineAreaDrawed
- }]
- }
- function getLineAndAreaShape (lineItem) {
- const { linePosition } = lineItem
- return {
- points: linePosition
- }
- }
- function getLineAreaStyle (lineItem) {
- const { lineArea, color } = lineItem
- let { gradient, style } = lineArea
- const fillColor = [style.fill || color]
- const gradientColor = deepMerge(fillColor, gradient)
- if (gradientColor.length === 1) gradientColor.push(gradientColor[0])
- const gradientParams = getGradientParams(lineItem)
- style = { ...style, stroke: 'rgba(0, 0, 0, 0)' }
- return deepMerge({
- gradientColor,
- gradientParams,
- gradientType: 'linear',
- gradientWith: 'fill',
- }, style)
- }
- function getGradientParams (lineItem) {
- const { lineFillBottomPos, linePosition } = lineItem
- const { changeIndex, changeValue } = lineFillBottomPos
- const mainPos = linePosition.map(p => p[changeIndex])
- const maxPos = Math.max(...mainPos)
- const minPos = Math.min(...mainPos)
- let beginPos = maxPos
- if (changeIndex === 1) beginPos = minPos
- if (changeIndex === 1) {
- return [0, beginPos, 0, changeValue]
- } else {
- return [beginPos, 0, changeValue, 0]
- }
- }
- function lineAreaDrawed ({ lineFillBottomPos, shape }, { ctx }) {
- const { points } = shape
- const { changeIndex, changeValue } = lineFillBottomPos
- const linePoint1 = [...points[points.length - 1]]
- const linePoint2 = [...points[0]]
- linePoint1[changeIndex] = changeValue
- linePoint2[changeIndex] = changeValue
- ctx.lineTo(...linePoint1)
- ctx.lineTo(...linePoint2)
- ctx.closePath()
- ctx.fill()
- }
- function getStartLineAreaConfig (lineItem) {
- const config = getLineAreaConfig(lineItem)[0]
- const style = { ...config.style }
- style.opacity = 0
- config.style = style
- return [config]
- }
- function beforeUpdateLineAndArea (graphs, lineItem, i, updater) {
- const cache = graphs[i]
- if (!cache) return
- const currentName = getLineGraphName(lineItem)
- const { render } = updater.chart
- const { name } = cache[0]
- const delAll = currentName !== name
- if (!delAll) return
- cache.forEach(g => render.delGraph(g))
- graphs[i] = null
- }
- function beforeChangeLineAndArea (graph, config) {
- const { points } = config.shape
- const graphPoints = graph.shape.points
- const graphPointsNum = graphPoints.length
- const pointsNum = points.length
- if (pointsNum > graphPointsNum) {
- const lastPoint = graphPoints.slice(-1)[0]
- const newAddPoints = new Array(pointsNum - graphPointsNum)
- .fill(0).map(foo => ([...lastPoint]))
- graphPoints.push(...newAddPoints)
- } else if (pointsNum < graphPointsNum) {
- graphPoints.splice(pointsNum)
- }
- }
- function getLineConfig (lineItem) {
- const { animationCurve, animationFrame, rLevel } = lineItem
- return [{
- name: getLineGraphName(lineItem),
- index: rLevel + 1,
- animationCurve,
- animationFrame,
- shape: getLineAndAreaShape(lineItem),
- style: getLineStyle(lineItem)
- }]
- }
- function getLineGraphName (lineItem) {
- const { smooth } = lineItem
- return smooth ? 'smoothline' : 'polyline'
- }
- function getLineStyle (lineItem) {
- const { lineStyle, color, smooth, linePosition } = lineItem
- const lineLength = getLineLength(linePosition, smooth)
- return deepMerge({
- stroke: color,
- lineDash: [lineLength, 0]
- }, lineStyle)
- }
- function getLineLength (points, smooth = false) {
- if (!smooth) return getPolylineLength(points)
- const curve = polylineToBezierCurve(points)
-
- return getBezierCurveLength(curve)
- }
- function getStartLineConfig (lineItem) {
- const { lineDash } = lineItem.lineStyle
- const config = getLineConfig(lineItem)[0]
- let realLineDash = config.style.lineDash
- if (lineDash) {
- realLineDash = [0, 0]
- } else {
- realLineDash = [...realLineDash].reverse()
- }
- config.style.lineDash = realLineDash
- return [config]
- }
- function getPointConfig (lineItem) {
- const { animationCurve, animationFrame, rLevel } = lineItem
- const shapes = getPointShapes(lineItem)
- const style = getPointStyle(lineItem)
- return shapes.map(shape => ({
- name: 'circle',
- index: rLevel + 2,
- visible: lineItem.linePoint.show,
- animationCurve,
- animationFrame,
- shape,
- style
- }))
- }
- function getPointShapes (lineItem) {
- const { linePosition, linePoint: { radius } } = lineItem
- return linePosition.map(([rx, ry]) => ({
- r: radius,
- rx,
- ry
- }))
- }
- function getPointStyle (lineItem) {
- let { color, linePoint: { style } } = lineItem
- return deepMerge({ stroke: color }, style)
- }
- function getStartPointConfig (lineItem) {
- const configs = getPointConfig(lineItem)
- configs.forEach(config => {
- config.shape.r = 0.1
- })
- return configs
- }
- function getLabelConfig (lineItem) {
- const { animationCurve, animationFrame, rLevel } = lineItem
- const shapes = getLabelShapes(lineItem)
- const style = getLabelStyle(lineItem)
- return shapes.map((shape, i) => ({
- name: 'text',
- index: rLevel + 3,
- visible: lineItem.label.show,
- animationCurve,
- animationFrame,
- shape,
- style
- }))
- }
- function getLabelShapes (lineItem) {
- const contents = formatterLabel(lineItem)
- const position = getLabelPosition(lineItem)
- return contents.map((content, i) => ({
- content,
- position: position[i]
- }))
- }
- function getLabelPosition (lineItem) {
- const { linePosition, lineFillBottomPos, label } = lineItem
- let { position, offset } = label
- let { changeIndex, changeValue } = lineFillBottomPos
- return linePosition.map(pos => {
- if (position === 'bottom') {
- pos = [...pos]
- pos[changeIndex] = changeValue
- }
- if (position === 'center') {
- const bottom = [...pos]
- bottom[changeIndex] = changeValue
- pos = getCenterLabelPoint(pos, bottom)
- }
- return getOffsetedPoint(pos, offset)
- })
- }
- function getOffsetedPoint ([x, y], [ox, oy]) {
- return [x + ox, y + oy]
- }
- function getCenterLabelPoint([ax, ay], [bx, by]) {
- return [
- (ax + bx) / 2,
- (ay + by) / 2
- ]
- }
- function formatterLabel (lineItem) {
- let { data, label: { formatter } } = lineItem
- data = data.filter(d => typeof d === 'number').map(d => d.toString())
- if (!formatter) return data
- const type = typeof formatter
- if (type === 'string') return data.map(d => formatter.replace('{value}', d))
- if (type === 'function') return data.map((value, index) => formatter({ value, index }))
- return data
- }
- function getLabelStyle (lineItem) {
- const { color, label: { style } } = lineItem
- return deepMerge({ fill: color }, style)
- }
|