Skip to main content Skip to footer

Adding Dashed Lines to a Bar Chart in Angular

Adding Dashed Lines to Bar Chart

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:

  1. Install Wijmo + styles
  2. Render a Column chart with two series
  3. Get the FlexChart instance
  4. Select only the top series' bar
  5. Dash the bars
  6. 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!

Andrew Peterson

Technical Engagement Engineer