Wednesday, September 23, 2020

D3 Chart - Rendering issue with Angular Material Table

I encountered this behavior while rendering a D3 chart in material table.

I use dynamic indexing to avoid collision with the D3 selectors.

<table mat-table [dataSource]="dataSource" matSort>

<ng-container matColumnDef="{{col.key}}" *ngFor="let col of tableDef">
<th mat-header-cell *matHeaderCellDef mat-sort-header> {{col.header}} </th>
<td mat-cell [ngSwitch]="col.key" *matCellDef="let element; let i = index">
<span *ngSwitchCase="'time_vec'" (click)="getRecord(element)">
<izoa-lib-timeline index={{i}} pageIndex={{pageIndex}} [data]=element[col.key]></izoa-lib-timeline>
</span>
<span *ngSwitchDefault [innerHTML]="element[col.key]">
</span>
</td>
</ng-container>

the D3 chart (omitting some logic for the sake of brevity)

export class TimelineComponent implements OnInit, AfterViewInit {

@Input()
index: number;

@Input()
pageIndex: number;

@Input()
data: String[];

@ViewChild('timeline')
private chartContainer: ElementRef;

private svg;
private margin = 50;
private width = 200;
private height = 30;
private datasource: TimelineModel[] = [];

constructor() { }

ngOnInit(): void {
// this.data = ['14', '56', '57', '64', '119'];
}

ngAfterViewInit(): void {
this.createDatasource();
this.createSvg();
}

private createSvg(): void {
// this.tooltip = d3.select('mat-drawer-container').append('div').attr('class', 'chart-tooltip').text('tooltip');
const selector = `#timeline-${this.index}-${this.pageIndex}`;
console.log('selector: ' + selector);
const element = this.chartContainer.nativeElement;

this.svg = d3.select(selector)
.append('svg')
.attr('width', this.width)
.attr('height', this.height)
.attr('class', 'timeline')
.append('g');
this.drawBars(this.datasource);
}
....

the chart renders fine the first time:


However, once the table updates based on user input (new time range, etc...) and the chart redraws , it omits the charts because Material start rendering the chart on top of the existing one then remove the previous one. D3 will use the selector in the previous table and render the charts on top of the exising one, after the refresh, they are no longer visible.


The trick is to remove the previous table before rendering the chart, so there are no more duplicated selectors.



d3.selectAll('.mat-row.cdk-row.heatmap-element-row.ng-star-inserted').remove();


No comments:

Post a Comment