Changing the Position of the Text in the FlexChart Legend | Angular
Background:
It’s rare, but sometimes you need to change where the legend text sits in a Wijmo FlexChart. The approach to take is to hook the rendered event, grab the legend SVG, then lay out your own legend items. This article shows how to do this in Angular.
Steps to Complete:
-
Create your chart and wire up the (rendered) event
-
Get the legend rect attributes
-
Create legend items
-
Position the legend items
-
Set the legend sizing
Getting Started:
Use the chart’s rendered event to reposition the rect and text elements inside the legend. Rects are SVG rectangles.
Create your chart and wire up the (rendered) event
<!-- app.component.html -->
<wj-flex-chart
#chart
[itemsSource]="data"
bindingName="name"
binding="value"
chartType="Pie"
(rendered)="onRendered(chart, $event)">
<wj-flex-chart-legend position="Right"></wj-flex-chart-legend>
</wj-flex-chart>
Get the legend rect attributes
const legendRectAttrs = ['x', 'y', 'width', 'height'] as const;
legendRectAttrs.forEach(attr => rects[0]?.setAttribute(attr, legend.getAttribute(attr) || '0'));
Create legend items
const itemsSourceMap = (s.itemsSource || []).reduce((acc: Record<string, any>, item: any) => {
acc[item[s.bindingName]] = item;
return acc;
}, {});
const legendItems = texts
.map((legendTextLabel, i) => {
const item = itemsSourceMap[legendTextLabel.textContent || ''];
if (!item) return null;
const val = `${wjcCore.Globalize.format((+item[s.binding] || 0) / (sum || 1), 'p1')}`;
const valueTextLabel = legendTextLabel.cloneNode() as SVGTextElement;
valueTextLabel.classList.add('wj-value-label');
valueTextLabel.textContent = val;
const legendItem = document.createElementNS('http://www.w3.org/2000/svg', 'g');
legendItem.classList.add('wj-legendItem');
legendItem.setAttribute('fill', 'transparent');
// IMPORTANT: keep order: label, color rect, value label
legendItem.appendChild(legendTextLabel);
if (rects[i + 1]) legendItem.appendChild(rects[i + 1]);
legendItem.appendChild(valueTextLabel);
legend.appendChild(legendItem);
return legendItem;
})
.filter(Boolean) as SVGGElement[];
Position the legend items
const legendX = parseFloat(legend.getAttribute('x') || '0');
const legendY = parseFloat(legend.getAttribute('y') || '0');
const legendWidth = parseFloat(legend.getAttribute('width') || '0');
const legendHeight = parseFloat(legend.getAttribute('height') || '0');
const columnWidth =
(legendWidth - this.padding.paddingLeft - this.padding.paddingRight) / this.legendColumnCount;
const rows = Math.ceil((legendItems.length || 1) / this.legendColumnCount);
const rowHeight = rows ? legendHeight / rows : legendHeight;
legendItems.forEach((item, i) => {
const col = i % this.legendColumnCount;
const row = Math.floor(i / this.legendColumnCount);
const x = legendX + this.padding.paddingLeft + col * columnWidth;
const y = legendY + row * rowHeight - this.padding.paddingTop - this.padding.paddingBottom;
const textLabel = item.querySelector('text.wj-label') as SVGTextElement || item.querySelector('text') as SVGTextElement;
const valueLabel = item.querySelector('text.wj-value-label') as SVGTextElement;
const colorRect = item.querySelector('rect') as SVGRectElement;
item.setAttribute('x', String(x));
item.setAttribute('y', String(y));
if (colorRect) {
colorRect.setAttribute('x', String(x));
colorRect.setAttribute('y', String(y));
}
if (textLabel && colorRect) {
const cw = parseFloat(colorRect.getAttribute('width') || '0');
const ch = parseFloat(colorRect.getAttribute('height') || '0');
textLabel.setAttribute('x', String(x + 10 + cw)); // 10px spacing
textLabel.setAttribute('y', String(y + ch));
}
if (valueLabel) {
// valueText right-justified to the column end
const valueLabelWidth = valueLabel.getBoundingClientRect().width || 0;
valueLabel.setAttribute('x', String(legendX + (col + 1) * columnWidth - valueLabelWidth));
if (colorRect) {
const ch = parseFloat(colorRect.getAttribute('height') || '0');
valueLabel.setAttribute('y', String(y + ch));
}
}
});
}
Set the legend sizing
private setLegendMaximumSize(host: HTMLElement, padding: { paddingTop: number; paddingRight: number; paddingBottom: number; paddingLeft: number; }) {
const hostEleRect = host.getBoundingClientRect();
const hostRect = (host.querySelector('svg') as SVGElement)?.getBoundingClientRect();
const footerRect = (host.querySelector('.wj-footer') as HTMLElement)?.getBoundingClientRect();
// For pie/donut rendering group:
const plotRect = (host.querySelector('.wj-slice-group') as SVGGElement)?.getBoundingClientRect();
if (!hostRect || !plotRect) return;
const availableWidth = hostRect.width - padding.paddingLeft - padding.paddingRight;
const availableHeight = Math.max(
0,
((footerRect?.top ?? hostRect.bottom) - plotRect.bottom) - padding.paddingTop - padding.paddingBottom
);
const legend = host.querySelector('.wj-legend') as SVGGElement;
if (!legend) return;
legend.setAttribute('x', String(padding.paddingLeft));
legend.setAttribute('y', String(plotRect.bottom - hostEleRect.top));
legend.setAttribute('width', String(availableWidth));
legend.setAttribute('height', String(availableHeight));
}
private getMaxLabelWidth(chart: any, engine: any, labelElements: Element[], sum: number) {
if (!engine || !labelElements?.length) return 0;
return Math.max(
...Array.from(labelElements).map((label, i) => {
const name = (chart.itemsSource?.[i]?.[chart.bindingName]) ?? '';
const frac = (chart.itemsSource?.[i]?.[chart.binding] ?? 0) / (sum || 1);
const val = `${name} ${wjcCore.Globalize.format(frac, 'p1')}`;
return engine.measureString(val).width;
})
);
}
}
If followed correctly, your FlexChart legend should look something like this:

Adjustments will be needed depending on your applications’ needs, but this example should be a good starting point. I hope you find this article helpful.
Happy coding!
Combined file for reference:
// app.component.ts
import { Component } from '@angular/core';
import * as wjcCore from '@mescius/wijmo';
@Component({
selector: 'app-root',
templateUrl: './app.component.html'
})
export class AppComponent {
// demo data
data = [
{ name: 'A', value: 10 },
{ name: 'B', value: 25 },
{ name: 'C', value: 15 },
{ name: 'D', value: 50 }
];
// tweak as needed
legendColumnCount = 2;
padding = { paddingTop: 5, paddingRight: 5, paddingBottom: 5, paddingLeft: 5 };
onRendered(s: any, e: any) {
const host: HTMLElement = s.hostElement as HTMLElement;
const legend: SVGGElement | null = host.querySelector('.wj-legend');
if (!legend) return;
const texts = Array.from(legend.querySelectorAll('text'));
const rects = Array.from(legend.querySelectorAll('rect'));
// compute total once
const sum = (s.itemsSource || []).reduce((acc: number, it: any) => acc + (+it[s.binding] || 0), 0);
// Get max width (optional but useful for sizing)
const maxWidth = this.getMaxLabelWidth(s, e?.engine, texts, sum);
// Set legend max size based on plot/footer space
this.setLegendMaximumSize(host, this.padding);
// 2) Get the legend rect attributes (sync outer rect with legend group)
const legendRectAttrs = ['x', 'y', 'width', 'height'] as const;
legendRectAttrs.forEach(attr => rects[0]?.setAttribute(attr, legend.getAttribute(attr) || '0'));
// 3) Create legend items
const itemsSourceMap = (s.itemsSource || []).reduce((acc: Record<string, any>, item: any) => {
acc[item[s.bindingName]] = item;
return acc;
}, {});
const legendItems = texts
.map((legendTextLabel, i) => {
const item = itemsSourceMap[legendTextLabel.textContent || ''];
if (!item) return null;
const val = `${wjcCore.Globalize.format((+item[s.binding] || 0) / (sum || 1), 'p1')}`;
const valueTextLabel = legendTextLabel.cloneNode() as SVGTextElement;
valueTextLabel.classList.add('wj-value-label');
valueTextLabel.textContent = val;
const legendItem = document.createElementNS('http://www.w3.org/2000/svg', 'g');
legendItem.classList.add('wj-legendItem');
legendItem.setAttribute('fill', 'transparent');
// IMPORTANT: keep order: label, color rect, value label
legendItem.appendChild(legendTextLabel);
if (rects[i + 1]) legendItem.appendChild(rects[i + 1]);
legendItem.appendChild(valueTextLabel);
legend.appendChild(legendItem);
return legendItem;
})
.filter(Boolean) as SVGGElement[];
// 4) Position legend items
const legendX = parseFloat(legend.getAttribute('x') || '0');
const legendY = parseFloat(legend.getAttribute('y') || '0');
const legendWidth = parseFloat(legend.getAttribute('width') || '0');
const legendHeight = parseFloat(legend.getAttribute('height') || '0');
const columnWidth =
(legendWidth - this.padding.paddingLeft - this.padding.paddingRight) / this.legendColumnCount;
const rows = Math.ceil((legendItems.length || 1) / this.legendColumnCount);
const rowHeight = rows ? legendHeight / rows : legendHeight;
legendItems.forEach((item, i) => {
const col = i % this.legendColumnCount;
const row = Math.floor(i / this.legendColumnCount);
const x = legendX + this.padding.paddingLeft + col * columnWidth;
const y = legendY + row * rowHeight - this.padding.paddingTop - this.padding.paddingBottom;
const textLabel = item.querySelector('text.wj-label') as SVGTextElement || item.querySelector('text') as SVGTextElement;
const valueLabel = item.querySelector('text.wj-value-label') as SVGTextElement;
const colorRect = item.querySelector('rect') as SVGRectElement;
item.setAttribute('x', String(x));
item.setAttribute('y', String(y));
if (colorRect) {
colorRect.setAttribute('x', String(x));
colorRect.setAttribute('y', String(y));
}
if (textLabel && colorRect) {
const cw = parseFloat(colorRect.getAttribute('width') || '0');
const ch = parseFloat(colorRect.getAttribute('height') || '0');
textLabel.setAttribute('x', String(x + 10 + cw)); // 10px spacing
textLabel.setAttribute('y', String(y + ch));
}
if (valueLabel) {
// valueText right-justified to the column end
const valueLabelWidth = valueLabel.getBoundingClientRect().width || 0;
valueLabel.setAttribute('x', String(legendX + (col + 1) * columnWidth - valueLabelWidth));
if (colorRect) {
const ch = parseFloat(colorRect.getAttribute('height') || '0');
valueLabel.setAttribute('y', String(y + ch));
}
}
});
}
// 5) Set the legend sizing
private setLegendMaximumSize(host: HTMLElement, padding: { paddingTop: number; paddingRight: number; paddingBottom: number; paddingLeft: number; }) {
const hostEleRect = host.getBoundingClientRect();
const hostRect = (host.querySelector('svg') as SVGElement)?.getBoundingClientRect();
const footerRect = (host.querySelector('.wj-footer') as HTMLElement)?.getBoundingClientRect();
// For pie/donut rendering group:
const plotRect = (host.querySelector('.wj-slice-group') as SVGGElement)?.getBoundingClientRect();
if (!hostRect || !plotRect) return;
const availableWidth = hostRect.width - padding.paddingLeft - padding.paddingRight;
const availableHeight = Math.max(
0,
((footerRect?.top ?? hostRect.bottom) - plotRect.bottom) - padding.paddingTop - padding.paddingBottom
);
const legend = host.querySelector('.wj-legend') as SVGGElement;
if (!legend) return;
legend.setAttribute('x', String(padding.paddingLeft));
legend.setAttribute('y', String(plotRect.bottom - hostEleRect.top));
legend.setAttribute('width', String(availableWidth));
legend.setAttribute('height', String(availableHeight));
}
private getMaxLabelWidth(chart: any, engine: any, labelElements: Element[], sum: number) {
if (!engine || !labelElements?.length) return 0;
return Math.max(
...Array.from(labelElements).map((label, i) => {
const name = (chart.itemsSource?.[i]?.[chart.bindingName]) ?? '';
const frac = (chart.itemsSource?.[i]?.[chart.binding] ?? 0) / (sum || 1);
const val = `${name} ${wjcCore.Globalize.format(frac, 'p1')}`;
return engine.measureString(val).width;
})
);
}
}
