Adding Dashed Lines to a Bar Chart in Angular
Background:
Dashed outlines are great for showing forecasted values, uncertainty, or emphasis. Wijmo’s FlexChart doesn’t natively support dashed bars, but because it renders as SVG, you can hook into the chart’s rendered lifecycle and tweak the generated <rect> elements to get a dashed outline with a transparent fill, plus update the legend to match.
Steps to Complete:
- Install Wijmo + styles
- Render a Column chart with two series
- Get the FlexChart instance
- Select only the top series' bar
- Dash the bars
- Match the legend swatch
Getting Started:
Install Wijmo + styles
npm i @mescius/wijmo.angular2.all @mescius/wijmo.styles
Add styles (choose one):
- src/styles.css @import '@mescius/wijmo.styles/wijmo.css';
- or angular.json add node_modules/@mescius/wijmo.styles/wijmo.css to styles.
Render a Column chart with two series
Template (note the #chart ref + (rendered) hook):
<wj-flex-chart
#chart
[itemsSource]="data"
[bindingX]="'month'"
[chartType]="'Column'"
(rendered)="onRendered()"
>
<wj-flex-chart-series [name]="'Actuals'" [binding]="'actual'"></wj-flex-chart-series>
<wj-flex-chart-series [name]="'Forecast'" [binding]="'forecast'"></wj-flex-chart-series>
</wj-flex-chart>
Get the FlexChart instance
Use @ViewChild('chart') to access the directive, then directive.control:
@ViewChild('chart') chartCmp?: WjFlexChart;
const chart = this.chartCmp?.control as FlexChart | undefined;
Select only the top series' bar
The top series is the last <g> inside .wj-series-group:
const rects = host.querySelectorAll('.wj-series-group g:last-child rect') as NodeListOf<SVGRectElement>;
Dash the bars
let srcFill: string | null = null;
rects.forEach(r => {
r.setAttribute('stroke-dasharray', '5,5');
srcFill = srcFill ?? r.getAttribute('fill');
r.setAttribute('fill', 'transparent');
r.setAttribute('stroke', 'black');
r.setAttribute('shape-rendering', 'crispEdges'); // avoid blurry dashes
});
Match the legend swatch
Find the legend <rect> with the original fill and style it:
if (srcFill) {
const legendRects = host.querySelectorAll('.wj-legend rect') as NodeListOf<SVGRectElement>;
legendRects.forEach(lr => {
if (lr.getAttribute('fill') === srcFill) {
lr.setAttribute('fill', 'transparent');
lr.setAttribute('stroke', 'black');
lr.setAttribute('stroke-dasharray', '5,5');
}
});
}
Full code example:
// package.json (excerpt)
{
"dependencies": {
"@angular/animations": "*",
"@angular/common": "*",
"@angular/compiler": "*",
"@angular/core": "*",
"@angular/platform-browser": "*",
"@angular/platform-browser-dynamic": "*",
"@mescius/wijmo.angular2.all": "*",
"@mescius/wijmo.styles": "*",
"rxjs": "*",
"zone.js": "*"
}
}
/* src/styles.css */
@import '@mescius/wijmo.styles/wijmo.css';
/* src/main.ts */
import { bootstrapApplication } from '@angular/platform-browser';
import { provideAnimations } from '@angular/platform-browser/animations';
import { AppComponent } from './app/app.component';
bootstrapApplication(AppComponent, {
providers: [provideAnimations()]
});
/* src/app/app.component.ts */
import { Component, ViewChild } from '@angular/core';
import { CommonModule } from '@angular/common';
import { WjChartModule, WjFlexChart } from '@mescius/wijmo.angular2.chart';
import { FlexChart } from '@mescius/wijmo.chart';
interface Point { month: string; actual: number; forecast: number; }
@Component({
standalone: true,
selector: 'app-root',
imports: [CommonModule, WjChartModule],
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
@ViewChild('chart') chartCmp?: WjFlexChart; // Why: access underlying FlexChart control
readonly data: Point[] = [
{ month: 'Jan', actual: 42, forecast: 45 },
{ month: 'Feb', actual: 38, forecast: 44 },
{ month: 'Mar', actual: 51, forecast: 53 },
{ month: 'Apr', actual: 47, forecast: 50 },
{ month: 'May', actual: 58, forecast: 60 },
{ month: 'Jun', actual: 62, forecast: 66 }
];
onRendered(): void {
const chart = this.chartCmp?.control as FlexChart | undefined;
if (!chart) return;
const host = chart.hostElement as HTMLElement;
const topSeriesRects = host.querySelectorAll(
'.wj-series-group g:last-child rect'
) as NodeListOf<SVGRectElement>;
if (!topSeriesRects.length) return;
let sourceFill: string | null = null;
topSeriesRects.forEach((rect) => {
rect.setAttribute('stroke-dasharray', '5,5');
if (sourceFill === null) sourceFill = rect.getAttribute('fill');
rect.setAttribute('fill', 'transparent');
rect.setAttribute('stroke', 'black');
rect.setAttribute('shape-rendering', 'crispEdges'); // Why: crisper dash rendering
});
if (sourceFill) {
const legendRects = host.querySelectorAll(
'.wj-legend rect'
) as NodeListOf<SVGRectElement>;
legendRects.forEach((rect) => {
if (rect.getAttribute('fill') === sourceFill) {
rect.setAttribute('fill', 'transparent');
rect.setAttribute('stroke', 'black');
rect.setAttribute('stroke-dasharray', '5,5');
}
});
}
}
}
/* src/app/app.component.html */
<div class="wrap">
<wj-flex-chart
#chart
[itemsSource]="data"
[bindingX]="'month'"
[chartType]="'Column'"
(rendered)="onRendered()"
>
<wj-flex-chart-series [name]="'Actuals'" [binding]="'actual'"></wj-flex-chart-series>
<wj-flex-chart-series [name]="'Forecast'" [binding]="'forecast'"></wj-flex-chart-series>
</wj-flex-chart>
</div>
/* src/app/app.component.css */
.wrap { max-width: 860px; margin: 2rem auto; }
If implemented correctly, you should have dashed lines on your FlexChart. Happy coding!