81fee6445bf3caf3098041a8244b80c4e269f5f00146e263ca707b78d5e4f9c7aec27b5d61373930975dd5ee4d9604600fd6f6c02ab75225981db8858b92b7 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480
  1. import { doUpdate } from '../class/updater.class'
  2. import { lineConfig } from '../config'
  3. import bezierCurve from '@jiaminghi/bezier-curve'
  4. import { deepMerge, initNeedSeries, mergeSameStackData, getPolylineLength } from '../util'
  5. const { polylineToBezierCurve, getBezierCurveLength } = bezierCurve
  6. export function line (chart, option = {}) {
  7. const { xAxis, yAxis, series } = option
  8. let lines = []
  9. if (xAxis && yAxis && series) {
  10. lines = initNeedSeries(series, lineConfig, 'line')
  11. lines = calcLinesPosition(lines, chart)
  12. }
  13. doUpdate({
  14. chart,
  15. series: lines,
  16. key: 'lineArea',
  17. getGraphConfig: getLineAreaConfig,
  18. getStartGraphConfig: getStartLineAreaConfig,
  19. beforeUpdate: beforeUpdateLineAndArea,
  20. beforeChange: beforeChangeLineAndArea
  21. })
  22. doUpdate({
  23. chart,
  24. series: lines,
  25. key: 'line',
  26. getGraphConfig: getLineConfig,
  27. getStartGraphConfig: getStartLineConfig,
  28. beforeUpdate: beforeUpdateLineAndArea,
  29. beforeChange: beforeChangeLineAndArea
  30. })
  31. doUpdate({
  32. chart,
  33. series: lines,
  34. key: 'linePoint',
  35. getGraphConfig: getPointConfig,
  36. getStartGraphConfig: getStartPointConfig
  37. })
  38. doUpdate({
  39. chart,
  40. series: lines,
  41. key: 'lineLabel',
  42. getGraphConfig: getLabelConfig
  43. })
  44. }
  45. function calcLinesPosition (lines, chart) {
  46. const { axisData } = chart
  47. return lines.map(lineItem => {
  48. let lineData = mergeSameStackData(lineItem, lines)
  49. lineData = mergeNonNumber(lineItem, lineData)
  50. const lineAxis = getLineAxis(lineItem, axisData)
  51. const linePosition = getLinePosition(lineData, lineAxis)
  52. const lineFillBottomPos = getLineFillBottomPos(lineAxis)
  53. return {
  54. ...lineItem,
  55. linePosition: linePosition.filter(p => p),
  56. lineFillBottomPos
  57. }
  58. })
  59. }
  60. function mergeNonNumber (lineItem, lineData) {
  61. const { data } = lineItem
  62. return lineData.map((v, i) => typeof data[i] === 'number' ? v : null)
  63. }
  64. function getLineAxis (line, axisData) {
  65. const { xAxisIndex, yAxisIndex } = line
  66. const xAxis = axisData.find(({ axis, index }) => axis === 'x' && index === xAxisIndex)
  67. const yAxis = axisData.find(({ axis, index }) => axis === 'y' && index === yAxisIndex)
  68. return [xAxis, yAxis]
  69. }
  70. function getLinePosition (lineData, lineAxis) {
  71. const valueAxisIndex = lineAxis.findIndex(({ data }) => data === 'value')
  72. const valueAxis = lineAxis[valueAxisIndex]
  73. const labelAxis = lineAxis[1 - valueAxisIndex]
  74. const { linePosition, axis } = valueAxis
  75. const { tickPosition } = labelAxis
  76. const tickNum = tickPosition.length
  77. const valueAxisPosIndex = axis === 'x' ? 0 : 1
  78. const valueAxisStartPos = linePosition[0][valueAxisPosIndex]
  79. const valueAxisEndPos = linePosition[1][valueAxisPosIndex]
  80. const valueAxisPosMinus = valueAxisEndPos - valueAxisStartPos
  81. const { maxValue, minValue } = valueAxis
  82. const valueMinus = maxValue - minValue
  83. const position = new Array(tickNum).fill(0)
  84. .map((foo, i) => {
  85. const v = lineData[i]
  86. if (typeof v !== 'number') return null
  87. let valuePercent = (v - minValue) / valueMinus
  88. if (valueMinus === 0) valuePercent = 0
  89. return valuePercent * valueAxisPosMinus + valueAxisStartPos
  90. })
  91. return position.map((vPos, i) => {
  92. if (i >= tickNum || typeof vPos !== 'number') return null
  93. const pos = [vPos, tickPosition[i][1 - valueAxisPosIndex]]
  94. if (valueAxisPosIndex === 0) return pos
  95. pos.reverse()
  96. return pos
  97. })
  98. }
  99. function getLineFillBottomPos (lineAxis) {
  100. const valueAxis = lineAxis.find(({ data }) => data === 'value')
  101. const { axis, linePosition, minValue, maxValue } = valueAxis
  102. const changeIndex = axis === 'x' ? 0 : 1
  103. let changeValue = linePosition[0][changeIndex]
  104. if (minValue < 0 && maxValue > 0) {
  105. const valueMinus = maxValue - minValue
  106. const posMinus = Math.abs(linePosition[0][changeIndex] - linePosition[1][changeIndex])
  107. let offset = Math.abs(minValue) / valueMinus * posMinus
  108. if (axis === 'y') offset *= -1
  109. changeValue += offset
  110. }
  111. return {
  112. changeIndex,
  113. changeValue
  114. }
  115. }
  116. function getLineAreaConfig (lineItem) {
  117. const { animationCurve, animationFrame, lineFillBottomPos, rLevel } = lineItem
  118. return [{
  119. name: getLineGraphName(lineItem),
  120. index: rLevel,
  121. animationCurve,
  122. animationFrame,
  123. visible: lineItem.lineArea.show,
  124. lineFillBottomPos,
  125. shape: getLineAndAreaShape(lineItem),
  126. style: getLineAreaStyle(lineItem),
  127. drawed: lineAreaDrawed
  128. }]
  129. }
  130. function getLineAndAreaShape (lineItem) {
  131. const { linePosition } = lineItem
  132. return {
  133. points: linePosition
  134. }
  135. }
  136. function getLineAreaStyle (lineItem) {
  137. const { lineArea, color } = lineItem
  138. let { gradient, style } = lineArea
  139. const fillColor = [style.fill || color]
  140. const gradientColor = deepMerge(fillColor, gradient)
  141. if (gradientColor.length === 1) gradientColor.push(gradientColor[0])
  142. const gradientParams = getGradientParams(lineItem)
  143. style = { ...style, stroke: 'rgba(0, 0, 0, 0)' }
  144. return deepMerge({
  145. gradientColor,
  146. gradientParams,
  147. gradientType: 'linear',
  148. gradientWith: 'fill',
  149. }, style)
  150. }
  151. function getGradientParams (lineItem) {
  152. const { lineFillBottomPos, linePosition } = lineItem
  153. const { changeIndex, changeValue } = lineFillBottomPos
  154. const mainPos = linePosition.map(p => p[changeIndex])
  155. const maxPos = Math.max(...mainPos)
  156. const minPos = Math.min(...mainPos)
  157. let beginPos = maxPos
  158. if (changeIndex === 1) beginPos = minPos
  159. if (changeIndex === 1) {
  160. return [0, beginPos, 0, changeValue]
  161. } else {
  162. return [beginPos, 0, changeValue, 0]
  163. }
  164. }
  165. function lineAreaDrawed ({ lineFillBottomPos, shape }, { ctx }) {
  166. const { points } = shape
  167. const { changeIndex, changeValue } = lineFillBottomPos
  168. const linePoint1 = [...points[points.length - 1]]
  169. const linePoint2 = [...points[0]]
  170. linePoint1[changeIndex] = changeValue
  171. linePoint2[changeIndex] = changeValue
  172. ctx.lineTo(...linePoint1)
  173. ctx.lineTo(...linePoint2)
  174. ctx.closePath()
  175. ctx.fill()
  176. }
  177. function getStartLineAreaConfig (lineItem) {
  178. const config = getLineAreaConfig(lineItem)[0]
  179. const style = { ...config.style }
  180. style.opacity = 0
  181. config.style = style
  182. return [config]
  183. }
  184. function beforeUpdateLineAndArea (graphs, lineItem, i, updater) {
  185. const cache = graphs[i]
  186. if (!cache) return
  187. const currentName = getLineGraphName(lineItem)
  188. const { render } = updater.chart
  189. const { name } = cache[0]
  190. const delAll = currentName !== name
  191. if (!delAll) return
  192. cache.forEach(g => render.delGraph(g))
  193. graphs[i] = null
  194. }
  195. function beforeChangeLineAndArea (graph, config) {
  196. const { points } = config.shape
  197. const graphPoints = graph.shape.points
  198. const graphPointsNum = graphPoints.length
  199. const pointsNum = points.length
  200. if (pointsNum > graphPointsNum) {
  201. const lastPoint = graphPoints.slice(-1)[0]
  202. const newAddPoints = new Array(pointsNum - graphPointsNum)
  203. .fill(0).map(foo => ([...lastPoint]))
  204. graphPoints.push(...newAddPoints)
  205. } else if (pointsNum < graphPointsNum) {
  206. graphPoints.splice(pointsNum)
  207. }
  208. }
  209. function getLineConfig (lineItem) {
  210. const { animationCurve, animationFrame, rLevel } = lineItem
  211. return [{
  212. name: getLineGraphName(lineItem),
  213. index: rLevel + 1,
  214. animationCurve,
  215. animationFrame,
  216. shape: getLineAndAreaShape(lineItem),
  217. style: getLineStyle(lineItem)
  218. }]
  219. }
  220. function getLineGraphName (lineItem) {
  221. const { smooth } = lineItem
  222. return smooth ? 'smoothline' : 'polyline'
  223. }
  224. function getLineStyle (lineItem) {
  225. const { lineStyle, color, smooth, linePosition } = lineItem
  226. const lineLength = getLineLength(linePosition, smooth)
  227. return deepMerge({
  228. stroke: color,
  229. lineDash: [lineLength, 0]
  230. }, lineStyle)
  231. }
  232. function getLineLength (points, smooth = false) {
  233. if (!smooth) return getPolylineLength(points)
  234. const curve = polylineToBezierCurve(points)
  235. return getBezierCurveLength(curve)
  236. }
  237. function getStartLineConfig (lineItem) {
  238. const { lineDash } = lineItem.lineStyle
  239. const config = getLineConfig(lineItem)[0]
  240. let realLineDash = config.style.lineDash
  241. if (lineDash) {
  242. realLineDash = [0, 0]
  243. } else {
  244. realLineDash = [...realLineDash].reverse()
  245. }
  246. config.style.lineDash = realLineDash
  247. return [config]
  248. }
  249. function getPointConfig (lineItem) {
  250. const { animationCurve, animationFrame, rLevel } = lineItem
  251. const shapes = getPointShapes(lineItem)
  252. const style = getPointStyle(lineItem)
  253. return shapes.map(shape => ({
  254. name: 'circle',
  255. index: rLevel + 2,
  256. visible: lineItem.linePoint.show,
  257. animationCurve,
  258. animationFrame,
  259. shape,
  260. style
  261. }))
  262. }
  263. function getPointShapes (lineItem) {
  264. const { linePosition, linePoint: { radius } } = lineItem
  265. return linePosition.map(([rx, ry]) => ({
  266. r: radius,
  267. rx,
  268. ry
  269. }))
  270. }
  271. function getPointStyle (lineItem) {
  272. let { color, linePoint: { style } } = lineItem
  273. return deepMerge({ stroke: color }, style)
  274. }
  275. function getStartPointConfig (lineItem) {
  276. const configs = getPointConfig(lineItem)
  277. configs.forEach(config => {
  278. config.shape.r = 0.1
  279. })
  280. return configs
  281. }
  282. function getLabelConfig (lineItem) {
  283. const { animationCurve, animationFrame, rLevel } = lineItem
  284. const shapes = getLabelShapes(lineItem)
  285. const style = getLabelStyle(lineItem)
  286. return shapes.map((shape, i) => ({
  287. name: 'text',
  288. index: rLevel + 3,
  289. visible: lineItem.label.show,
  290. animationCurve,
  291. animationFrame,
  292. shape,
  293. style
  294. }))
  295. }
  296. function getLabelShapes (lineItem) {
  297. const contents = formatterLabel(lineItem)
  298. const position = getLabelPosition(lineItem)
  299. return contents.map((content, i) => ({
  300. content,
  301. position: position[i]
  302. }))
  303. }
  304. function getLabelPosition (lineItem) {
  305. const { linePosition, lineFillBottomPos, label } = lineItem
  306. let { position, offset } = label
  307. let { changeIndex, changeValue } = lineFillBottomPos
  308. return linePosition.map(pos => {
  309. if (position === 'bottom') {
  310. pos = [...pos]
  311. pos[changeIndex] = changeValue
  312. }
  313. if (position === 'center') {
  314. const bottom = [...pos]
  315. bottom[changeIndex] = changeValue
  316. pos = getCenterLabelPoint(pos, bottom)
  317. }
  318. return getOffsetedPoint(pos, offset)
  319. })
  320. }
  321. function getOffsetedPoint ([x, y], [ox, oy]) {
  322. return [x + ox, y + oy]
  323. }
  324. function getCenterLabelPoint([ax, ay], [bx, by]) {
  325. return [
  326. (ax + bx) / 2,
  327. (ay + by) / 2
  328. ]
  329. }
  330. function formatterLabel (lineItem) {
  331. let { data, label: { formatter } } = lineItem
  332. data = data.filter(d => typeof d === 'number').map(d => d.toString())
  333. if (!formatter) return data
  334. const type = typeof formatter
  335. if (type === 'string') return data.map(d => formatter.replace('{value}', d))
  336. if (type === 'function') return data.map((value, index) => formatter({ value, index }))
  337. return data
  338. }
  339. function getLabelStyle (lineItem) {
  340. const { color, label: { style } } = lineItem
  341. return deepMerge({ fill: color }, style)
  342. }