Closed kkxxkk2019 closed 1 month ago
lastest
none
import { AbstractComponent } from '@visactor/vrender-components'; import type { Dict } from '@visactor/vutils'; import { isEmpty, last } from '@visactor/vutils'; import { Factory } from '@visactor/vgrammar-core'; import type { IGraphic } from '@visactor/vrender-core'; import { createText } from '@visactor/vrender-core'; export class SeriesLabelComponent extends AbstractComponent<Required<any>> { name = 'seriesLabel'; protected render() { this.removeAllChild(); const { data, layout, label, line = {} } = this.attribute as any; if (isEmpty(data)) { return; } const labelStyleMap = label?.styleMap ?? {}; const adjustedPoints: Dict<any> = {}; const filteredData = (data as any[]).filter(datum => labelStyleMap[datum.id]?.visible !== false); const lineHeight = (label?.style?.fontSize ?? 12) * 1.5; // 进行扰动,将重叠的文本抖开 const createAndAddTextGraphic = (datum: any, textPoint: any, labelStyleMap: any) => { const { label: text, color, textAlign, textBaseline, id } = datum; const formatMethod = labelStyleMap[id]?.formatMethod ?? this.attribute.label?.formatMethod; const textGraphic = createText({ text: formatMethod ? formatMethod(text, datum.datum, { series: datum.series }) : text, ...this.attribute.label?.style, ...textPoint, textAlign, textBaseline, fill: this.attribute.label?.style?.fill ?? color, ...labelStyleMap[id]?.style }); textGraphic.name = 'series-label-text'; textGraphic.id = id; this.add(textGraphic); return textGraphic; }; ['start', 'end'].forEach(position => { const posData = filteredData.filter(datum => datum.position === position); posData.forEach((datum, index) => { const textPoint = { x: datum.point.x + (label?.space ?? 8) * (datum.textAlign === 'start' ? 1 : -1), y: datum.point.y }; createAndAddTextGraphic(datum, textPoint, labelStyleMap); adjustedPoints[datum.id] = textPoint; }); }); } } Factory.registerGraphicComponent( 'seriesLabel', (attrs: Required<any>) => new SeriesLabelComponent(attrs) as unknown as IGraphic ); const spec1 = { direction: 'vertical', type: 'common', color: ['#00295C', '#2568BD', '#9F9F9F', '#C5C5C5', '#00B0F0', '#4BCFFF', '#C2C2C2', '#D7D7D7'], series: [ { type: 'bar', stack: true, direction: 'vertical', bar: { style: { stroke: '', lineWidth: 1 }, state: { hover: { stroke: '#000', lineWidth: 1 } } }, barBackground: { style: { stroke: '', lineWidth: 1 } }, label: { visible: true, position: 'inside', style: { lineHeight: '100%', fontSize: 16, fontWeight: 'bold' }, overlap: { strategy: [] }, smartInvert: true, interactive: true }, totalLabel: { visible: true, position: 'top', overlap: false, clampForce: false, formatConfig: { fixed: 0, content: 'value' }, style: { lineHeight: '100%', lineWidth: 1, fill: '#1F2329', stroke: '#ffffff', fontSize: 16, fontWeight: 'bold' }, interactive: true }, seriesLabel: { visible: true, position: 'end', label: { style: { lineHeight: '100%', lineWidth: 1, stroke: '#ffffff', fontSize: 16, fontWeight: 'bold' }, space: 10 } }, xField: '_editor_dimension_field', yField: '_editor_value_field', dataId: '0', id: 'series-0', EDITOR_SERIES_DATA_KEY: 'a', seriesField: '_editor_type_field' }, { type: 'bar', stack: true, direction: 'vertical', bar: { style: { stroke: '', lineWidth: 1 }, state: { hover: { stroke: '#000', lineWidth: 1 } } }, barBackground: { style: { stroke: '', lineWidth: 1 } }, label: { visible: true, position: 'inside', style: { lineHeight: '100%', fontSize: 16, fontWeight: 'bold' }, overlap: { strategy: [] }, smartInvert: true, interactive: true }, totalLabel: { visible: true, position: 'top', overlap: false, clampForce: false, formatConfig: { fixed: 0, content: 'value' }, style: { lineHeight: '100%', lineWidth: 1, fill: '#1F2329', stroke: '#ffffff', fontSize: 16, fontWeight: 'bold' }, interactive: true }, seriesLabel: { visible: true, position: 'end', label: { style: { lineHeight: '100%', lineWidth: 1, stroke: '#ffffff', fontSize: 16, fontWeight: 'bold' }, space: 10 } }, xField: '_editor_dimension_field', yField: '_editor_value_field', dataId: '1', id: 'series-1', EDITOR_SERIES_DATA_KEY: 'b', seriesField: '_editor_type_field' } ], legends: { id: 'legend-discrete', visible: false, autoPage: false, position: 'start', interactive: false, item: { label: { style: { fill: '#1F2329', fontSize: 16 } } } }, region: [ { id: 'region-0' } ], tooltip: { visible: true }, axes: [ { orient: 'left', id: 'axis-left', type: 'linear', label: { autoLimit: false, style: { fill: '#1F2329', fontSize: 16 }, formatMethod: null }, domainLine: { visible: true, style: { stroke: '#000000' } }, tick: { visible: true, style: { stroke: '#000000' } }, grid: { visible: false, style: { stroke: '#bbbfc4', pickStrokeBuffer: 2 } }, autoIndent: false, maxWidth: null, maxHeight: null }, { orient: 'bottom', id: 'axis-bottom', type: 'band', label: { autoLimit: false, style: { fill: '#1F2329', fontSize: 16 } }, domainLine: { visible: true, style: { stroke: '#000000' }, onZero: true }, tick: { visible: true, style: { stroke: '#000000' } }, grid: { visible: false, style: { stroke: '#bbbfc4', pickStrokeBuffer: 2 } }, autoIndent: false, maxWidth: null, maxHeight: null, trimPadding: false, paddingInner: [0.2, 0], paddingOuter: [0.2, 0] } ], data: [ { id: '0', sourceKey: 'a', values: [ { _editor_dimension_field: 'x1', _editor_value_field: 20, _editor_type_field: 'a' }, { _editor_dimension_field: 'x2', _editor_value_field: 23, _editor_type_field: 'a' }, { _editor_dimension_field: 'x3', _editor_value_field: 26, _editor_type_field: 'a' } ] }, { id: '1', sourceKey: 'b', values: [ { _editor_dimension_field: 'x1', _editor_value_field: 20, _editor_type_field: 'b' }, { _editor_dimension_field: 'x2', _editor_value_field: 24, _editor_type_field: 'b' }, { _editor_dimension_field: 'x3', _editor_value_field: 29, _editor_type_field: 'b' } ] } ], markLine: [ { id: '3062c9c7-cfff-4b16-910d-e338a4477179', interactive: true, name: 'total-diff-line', type: 'type-step', coordinates: [ { _editor_dimension_field: 'x1', _editor_value_field: 40, _editor_type_field: 'b', refRelativeSeriesId: 'series-1' }, { _editor_dimension_field: 'x2', _editor_value_field: 47, _editor_type_field: 'b', refRelativeSeriesId: 'series-1' } ], connectDirection: 'top', expandDistance: '10.273972602739725%', line: { style: { stroke: '#000', lineWidth: 1, pickStrokeBuffer: 10, lineDash: [0], cornerRadius: 4 } }, label: { position: 'middle', text: '18%', labelBackground: { style: { fill: '#fff', fillOpacity: 1, stroke: '#000', lineWidth: 1, cornerRadius: 4 } }, style: { fill: '#1F2329', fontSize: 16, fontWeight: 'bold' }, pickable: true, childrenPickable: false }, endSymbol: { size: 12, refX: -4, symbolType: 'M -0.0625 -0.3167 C -0.0625 -0.2821 -0.0346 -0.2542 0 -0.2542 C 0.0346 -0.2542 0.0625 -0.2821 0.0625 -0.3167 C 0.0625 -0.3167 -0.0625 -0.3167 -0.0625 -0.3167 Z M 0.0442 -0.4025 C 0.0196 -0.4271 -0.0196 -0.4271 -0.0442 -0.4025 C -0.0442 -0.4025 -0.4421 -0.0046 -0.4421 -0.0046 C -0.4662 0.0196 -0.4662 0.0592 -0.4421 0.0838 C -0.4175 0.1079 -0.3779 0.1079 -0.3538 0.0838 C -0.3538 0.0838 0 -0.27 0 -0.27 C 0 -0.27 0.3538 0.0838 0.3538 0.0838 C 0.3779 0.1079 0.4175 0.1079 0.4421 0.0838 C 0.4662 0.0592 0.4662 0.0196 0.4421 -0.0046 C 0.4421 -0.0046 0.0442 -0.4025 0.0442 -0.4025 Z M 0.0625 -0.3167 C 0.0625 -0.3167 0.0625 -0.3583 0.0625 -0.3583 C 0.0625 -0.3583 -0.0625 -0.3583 -0.0625 -0.3583 C -0.0625 -0.3583 -0.0625 -0.3167 -0.0625 -0.3167 C -0.0625 -0.3167 0.0625 -0.3167 0.0625 -0.3167 Z', style: { fill: '#000', lineWidth: 0, stroke: null } }, _originValue_: [40, 47], zIndex: 510, coordinatesOffset: [ { x: 0, y: -26.000000000000014 }, { x: 0, y: -26.000000000000007 } ] }, { id: 'be65e8ee-26c8-4c99-aa0f-33171ba5458e', interactive: true, name: 'growth-line', coordinates: [ { _editor_dimension_field: 'x2', _editor_value_field: 47, _editor_type_field: 'b', refRelativeSeriesId: 'series-1' }, { _editor_dimension_field: 'x3', _editor_value_field: 55, _editor_type_field: 'b', refRelativeSeriesId: 'series-1' } ], line: { style: { stroke: '#000', lineWidth: 1, pickStrokeBuffer: 10, lineDash: [0] } }, label: { position: 'middle', text: '17%', labelBackground: { style: { fill: '#fff', fillOpacity: 1, stroke: '#000', lineWidth: 1, cornerRadius: 4 }, padding: { top: 2, bottom: 2, right: 4, left: 4 } }, style: { fill: '#1F2329', fontSize: 16, fontWeight: 'bold', fontStyle: 'normal' }, pickable: true, childrenPickable: false }, endSymbol: { size: 12, refX: -4, symbolType: 'M -0.0625 -0.3167 C -0.0625 -0.2821 -0.0346 -0.2542 0 -0.2542 C 0.0346 -0.2542 0.0625 -0.2821 0.0625 -0.3167 C 0.0625 -0.3167 -0.0625 -0.3167 -0.0625 -0.3167 Z M 0.0442 -0.4025 C 0.0196 -0.4271 -0.0196 -0.4271 -0.0442 -0.4025 C -0.0442 -0.4025 -0.4421 -0.0046 -0.4421 -0.0046 C -0.4662 0.0196 -0.4662 0.0592 -0.4421 0.0838 C -0.4175 0.1079 -0.3779 0.1079 -0.3538 0.0838 C -0.3538 0.0838 0 -0.27 0 -0.27 C 0 -0.27 0.3538 0.0838 0.3538 0.0838 C 0.3779 0.1079 0.4175 0.1079 0.4421 0.0838 C 0.4662 0.0592 0.4662 0.0196 0.4421 -0.0046 C 0.4421 -0.0046 0.0442 -0.4025 0.0442 -0.4025 Z M 0.0625 -0.3167 C 0.0625 -0.3167 0.0625 -0.3583 0.0625 -0.3583 C 0.0625 -0.3583 -0.0625 -0.3583 -0.0625 -0.3583 C -0.0625 -0.3583 -0.0625 -0.3167 -0.0625 -0.3167 C -0.0625 -0.3167 0.0625 -0.3167 0.0625 -0.3167 Z', style: { fill: '#000', lineWidth: 0, stroke: null }, visible: true }, coordinatesOffset: [ { x: 0, y: '-30%' }, { x: 0, y: '-30%' } ], _originValue_: [47, 55], zIndex: 510, startSymbol: { visible: false, symbolType: 'triangle', size: 10, style: { fill: '#606773', stroke: null, lineWidth: 0 } }, expression: null } ], markArea: [], labelLayout: 'region', customMark: [ { type: 'component', componentType: 'seriesLabel', interactive: false, style: { // id: '8124d358-3d47-4187-b301-a81853f09b56', position: 'end', label: { space: 10, style: { lineHeight: '100%', lineWidth: 1, stroke: '#ffffff', fontSize: 16, fontWeight: 'bold' } }, data: (datum: any, ctx: any) => { const vchart = ctx.vchart; const chart = vchart.getChart(); const series = chart.getAllSeries()[0] as ICartesianSeries; const isTransposed = series.direction === 'horizontal'; const bandAxisHelper = (isTransposed ? series.getYAxisHelper() : series.getXAxisHelper()) as any; const bandAxisScale = bandAxisHelper.getScale?.(0) as any; const isBandAxisInverse = bandAxisHelper.isInverse(); const dimensionField = series.getDimensionField()[0]; const seriesField = series.getSeriesField() as string; const seriesTypes = chart .getAllSeries() .map((s: ICartesianSeries) => s.type) .filter((value: string, index: number, self: string[]) => { return self.indexOf(value) === index; }); const labelData: any[] = []; // 系列标签目前不支持不同系列的组合图 const pos = 'end'; const targetValue = last(bandAxisScale.domain()); const region = series.getRegion(); const { x: regionStartX, y: regionStartY } = region.getLayoutStartPoint(); let i = 0; chart.getAllSeries().forEach((s: ICartesianSeries) => { const mark = s.getMarkInName(seriesTypes[0] === 'waterfall' ? 'bar' : seriesTypes[0]); const vgrammarElements = mark.getProduct().elements; vgrammarElements.forEach((element: any) => { const data = element.data.find((datum: any) => datum[dimensionField] === targetValue); if (data) { const graphItem = element.getGraphicItem(); const graphBounds = graphItem.AABBBounds; const point = { x: isTransposed ? (graphBounds.x1 + graphBounds.x2) / 2 : isBandAxisInverse ? graphBounds.x1 : graphBounds.x2, y: isTransposed ? isBandAxisInverse ? graphBounds.y2 : graphBounds.y1 : (graphBounds.y1 + graphBounds.y2) / 2 }; const color = graphItem.attribute.fill; const textAlign = isTransposed ? 'center' : isBandAxisInverse ? 'end' : 'start'; const textBaseline = isTransposed ? (isBandAxisInverse ? 'top' : 'bottom') : 'middle'; const labelValue = data[seriesField] ?? data[dimensionField]; labelData.push({ point: { x: point.x + regionStartX, y: point.y + regionStartY }, label: labelValue, color, textAlign, textBaseline, series: s, datum: data, id: `${pos}-${i}`, position: pos }); i++; } }); }); return labelData; } } } ], width: 640, height: 360, background: 'transparent' }; const vchart = new VChart(spec1, { dom: CONTAINER_ID, animation: false }); vchart.renderSync(); vchart.updateSpecSync(spec1, false, false);
fix it
- OS: - Browser: - Framework:
No response
label的 updateSpec 导致更新类型检测为 remake: true remake的时候,rect 图元进入到reuse逻辑,但是图形被删掉了 建议实现的时候,增加数据和图形的判断
remake: true
reuse
if (data && element.getGraphicItem()) { }
Version
lastest
Link to Minimal Reproduction
none
Steps to Reproduce
Current Behavior
Expected Behavior
fix it
Environment
Any additional comments?
No response