UNPKG

zz-chart

Version:

Alauda Chart components by Alauda Frontend Team

263 lines 10.4 kB
import { StyleSheet, css } from 'aphrodite/no-important.js'; import { get, merge, set, uniqBy } from 'lodash-es'; import { BaseComponent } from './base.js'; import { addAreas, addLines } from '../strategy/gradient-fills.js'; const TEXT_SPACE = 4; const styles = StyleSheet.create({ 'mark-x': { position: 'absolute', display: 'inline-block', height: '100%', }, 'mark-x-label': { position: 'absolute', transform: 'translateX(-50%)', padding: '0 8px', fontSize: '12px', whiteSpace: 'nowrap', // background: '#fff', }, }); export class Annotation extends BaseComponent { constructor() { super(...arguments); this.annotationXFn = []; this.annotationYFn = []; } get name() { return 'annotation'; } render() { // .. const opt = this.ctrl.getOption(); this.option = get(opt, this.name, {}); const x = get(this.option, 'lineX'); const yList = get(this.option, 'lineY', []); const yAreaList = get(this.option, 'areaY', []); this.lineX(x); yList?.forEach(y => this.lineY(y)); this.areaY(yAreaList || []); } update() { // .. this.option = get(this.ctrl.getOption(), this.name); const x = get(this.option, 'lineX'); const yList = get(this.option, 'lineY', []); this.lineX(x); yList?.forEach(y => this.lineY(y)); } // eslint-disable-next-line sonarjs/cognitive-complexity lineX(options) { if (this.annotationXFn.length) { const opt = merge(get(this.option, 'lineX'), options); set(this.option, 'lineX', opt); this.ctrl.setOption([this.name, 'lineX'], opt); this.ctrl.redraw(); return this; } this.ctrl.setOption([this.name, 'lineX'], options); const fn = (u) => { const { data, text, style = { lineDash: [5, 5], width: 2, stroke: this.ctrl.getTheme().colorVar['n-6'], }, } = get(this.option, 'lineX', {}); if (!data) { return; } const ctx = u.ctx; ctx.save(); const xData = u.data[0]; const isTransposed = u.scales.y.ori === 0 && u.axes[1].side === 2; const posValue = u.scales.x.distr === 2 ? xData.indexOf(data) : data; const x0 = isTransposed ? u.valToPos(u.scales.y.min, 'y', true) : u.valToPos(posValue, 'x', true); const x1 = isTransposed ? u.bbox.width : x0; const y0 = isTransposed ? u.valToPos(posValue, 'x', true) : u.bbox.top; const y1 = isTransposed ? u.valToPos(posValue, 'x', true) : u.valToPos(u.scales.y.min, 'y', true); ctx.beginPath(); ctx.setLineDash(style?.lineDash || [5, 5]); ctx.lineWidth = style?.width || 2; ctx.moveTo(x0, y0); ctx.lineTo(x1, y1); ctx.strokeStyle = style?.stroke || 'red'; ctx.stroke(); if (x0 && text?.content) { const markE = u.over.querySelector('.u-mark-x'); const markLabelE = u.over.querySelector('.u-mark-x-label'); const markEl = (markE || document.createElement('div')); markEl.className = `u-mark-x ${css(styles['mark-x'])}`; const labelEl = (markLabelE || document.createElement('div')); labelEl.className = `u-mark-x-label ${css(styles['mark-x-label'])}`; if (text?.border) { const { padding, style } = text?.border || {}; labelEl.style.padding = padding ? `${padding[0]}px ${padding[1]}px` : '0 8px'; labelEl.style.border = style; } labelEl.textContent = String(text.content); Object.assign(labelEl.style, text.style || {}); const labelStyle = getComputedStyle(labelEl); requestAnimationFrame(() => { const x = isTransposed ? u.valToPos(u.scales.y.max, 'y') : Math.round(u.valToPos(posValue, 'x')); const markElWidth = parseInt(labelStyle.width, 10) / 2; const y = isTransposed ? u.valToPos(posValue, 'x') - parseInt(labelStyle.height, 10) / 2 : -parseInt(labelStyle.height, 10) + TEXT_SPACE; let left = x; if (x + markElWidth > u.over.clientWidth) { left = x - markElWidth; } if (x <= 0) { left = x + markElWidth; } markEl.style.left = `${left}px`; markEl.style.top = `${y}px`; }); !markLabelE && markEl.append(labelEl); !markE && u.over.append(markEl); } ctx.restore(); }; this.annotationXFn.push(fn); return this; } // eslint-disable-next-line sonarjs/cognitive-complexity lineY(options, empty) { this.setOptions('lineY', options, empty); if (this.annotationYFn.length) { this.ctrl.redraw(); return this; } const fn = (u) => { const values = get(this.option, 'lineY', []); if (!values.length) { return; } const ctx = u.ctx; values.forEach(item => { const { data, text, style = { lineDash: [8, 5], width: 2, stroke: 'red' }, } = item; if (+data < u.scales.y.min || +data > u.scales.y.max) { return; } ctx.save(); const isTransposed = u.scales.y.ori === 0 && u.axes[1].side === 2; const [i0, i1] = u.series[0].idxs; const d = u.data[0]; const x0 = isTransposed ? u.valToPos(data, 'y', true) : u.bbox.left || u.valToPos(d[i0], 'x', true); // x 开始位置 const x1 = isTransposed ? u.valToPos(data, 'y', true) : u.bbox.width + u.bbox.left || u.valToPos(d[i1], 'x', true); // x 结束为止 const y1 = isTransposed ? u.bbox.height : u.valToPos(data, 'y', true); // y 位置 ctx.beginPath(); ctx.setLineDash(style?.lineDash || [8, 5]); ctx.lineWidth = style?.width || 2; ctx.moveTo(x1, isTransposed ? 0 : y1); ctx.lineTo(x0, y1); // ctx.moveTo(x1, y1); // ctx.lineTo(x0, y1); ctx.strokeStyle = style?.stroke || 'red'; ctx.stroke(); if (text?.content) { const { width, actualBoundingBoxDescent } = ctx.measureText(String(text.content)); ctx.fillStyle = style?.stroke || 'red'; ctx.fillText(String(text.content), this.getTextPosition(text.position, x0, width, u.bbox.width), isTransposed ? 0 : y1 - (actualBoundingBoxDescent + TEXT_SPACE)); } }); ctx.restore(); }; this.annotationYFn.push(fn); } areaY(options, empty) { this.setOptions('areaY', options, empty); const fn = (u) => { const ctx = u.ctx; const { min: xMin, max: xMax } = u.scales.x; const { min: yMin, max: yMax } = u.scales.y; if (xMin == null || xMax == null || yMin == null || yMax == null || !options?.length) { return; } // if (mode === ThresholdsMode.Percentage) { // let [min, max] = getGradientRange( // u, // scaleKey, // hardMin, // hardMax, // softMin, // softMax, // ); // let range = max - min; // steps = steps.map(step => ({ // ...step, // value: min + range * (step.value / 100), // })); // } ctx.save(); addAreas(u, 'y', options, this); addLines(u, 'y', options, this); // switch (config.mode) { // case GraphTresholdsStyleMode.Line: // case GraphTresholdsStyleMode.Dashed: // addLines(u, scaleKey, steps, theme); // break; // case GraphTresholdsStyleMode.Area: // addAreas(u, scaleKey, steps, theme); // break; // case GraphTresholdsStyleMode.LineAndArea: // case GraphTresholdsStyleMode.DashedAndArea: // addAreas(u, scaleKey, steps, theme); // addLines(u, scaleKey, steps, theme); // } ctx.restore(); }; this.annotationYFn.push(fn); } setOptions(type, options, empty) { const value = Array.isArray(options) ? options : [options]; if (empty) { this.ctrl.setOption([this.name, type], value); } const option = get(this.ctrl.getOption(), [this.name, type]) || []; const data = uniqBy([...option, ...value], 'data'); this.ctrl.setOption([this.name, type], data); } getTextPosition(position = 'left', start, textWidth, width) { let x = start; if (position === 'left') { x = start + textWidth + TEXT_SPACE; } if (position === 'right') { x = start + width; } return x; } getOptions() { return { hooks: { draw: [ (u) => { [...this.annotationXFn, ...this.annotationYFn].forEach(fn => fn(u)); }, ], }, }; } } //# sourceMappingURL=annotation.js.map