zz-chart
Version:
Alauda Chart components by Alauda Frontend Team
263 lines • 10.4 kB
JavaScript
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