Angular7搭配D3v7在表格单元格渲染面积图出现循环重复渲染问题
问题根因
- 核心原因:你将D3渲染方法直接写在了模板插值
{{}}中,Angular变更检测每次触发都会执行该方法,而方法内每次都会调用.append('svg')新增SVG元素,最终导致重复渲染。 - 代码笔误:TS方法的第二个入参是
code,但DOM选择时错误使用了未定义的fleet变量;X轴比例尺定义域引用了未定义的data变量,应为处理后的temp1;第二个图表容器调用方法时错误传了A参数,导致两个图表数据一致。
修复方案
1. 调整模板代码
移除插值中的方法调用,仅标记图表容器,同时为*ngFor添加trackBy减少不必要的重渲染:
<tr [id]="'row' + '-' + fleet" *ngFor="let fleet of fleetcolumn; trackBy: trackByFleet"> <td>{{ fleet }}</td> <ng-container *ngFor="let station of formattedBaseStations | slice: 1; trackBy: trackByStation"> <td style="width: 28em;"> <mat-grid-list cols="2" rowHeight="90px"> <mat-grid-tile class="captainChart"> <div [id]="'demandChartInTable1-' + station + '-' + fleet + '-A'"></div> </mat-grid-tile> <mat-grid-tile class="FOChart"> <div [id]="'demandChartInTable1-' + station + '-' + fleet + '-B'"></div> </mat-grid-tile> </mat-grid-list> </td> </ng-container> </tr>
2. 调整TS逻辑
将渲染逻辑移到ngAfterViewInit生命周期中执行,每次渲染前先清空容器避免重复生成元素:
// 新增trackBy方法优化ngFor性能 trackByFleet(index: number, item: string) { return item; } trackByStation(index: number, item: string) { return item; } ngAfterViewInit() { // 视图加载完成后批量渲染所有图表 this.fleetcolumn.forEach(fleet => { (this.formattedBaseStations.slice(1) as string[]).forEach(station => { this.renderChart(station, fleet, 'A'); this.renderChart(station, fleet, 'B'); }); }); } // 改造后的渲染方法 renderChart(station: string, code: string, position: string) { const containerSelector = `#demandChartInTable1-${station}-${code}-${position}`; const container = d3.select(containerSelector); // 清空容器已有内容,防止重复渲染 container.html(''); // 处理数据 const temp1 = this.data .filter(d => d.baseCode == station && d.code == code && d.position == position) .map(d => ({ date: d3.timeParse('%Y-%m-%d')(d.date), hours: d.hours })); const chartContainer = document.querySelector('.captainChart'); if (!chartContainer) return; const margin = { top: 0, right: 0, bottom: 1, left: 1 }; const width = chartContainer.clientWidth - margin.left - margin.right; const height = 90 - margin.top - margin.bottom; // 生成SVG const svg = container .append('svg') .attr('width', chartContainer.clientWidth + margin.left + margin.right) .attr('height', chartContainer.clientHeight + margin.bottom) .append('g') .attr('transform', `translate(${margin.left},${margin.top})`); // 定义比例尺 const x = d3.scaleTime() .domain(d3.extent(temp1, d => d.date)) .range([0, chartContainer.clientWidth]); const y = d3.scaleLinear() .domain([0, 500]) .range([chartContainer.clientHeight + margin.bottom, 0]); // 绘制面积图 svg.append('path') .datum(temp1) .attr('fill', '#cce5df') .attr('stroke', '#69b3a2') .attr('stroke-width', 1.5) .attr('d', d3.area() .x(d => x(d.date)) .y0(y(0)) .y1(d => y(d.hours)) ); }
如果后续数据有更新,在数据更新的回调中重新调用renderChart方法即可,不要在模板中直接调用渲染逻辑。
内容的提问来源于stack exchange,提问作者user10368959
相关产品推荐
相关产品推荐

