UNPKG

@swimlane/ngx-charts

Version:

Declarative Charting Framework for Angular2 and beyond!

430 lines (375 loc) 11.2 kB
import { Component, Input, Output, EventEmitter, HostListener, ChangeDetectionStrategy } from '@angular/core'; import { calculateViewDimensions, ViewDimensions } from '../common/view-dimensions.helper'; import { ColorHelper } from '../common/color.helper'; import { BaseChartComponent } from '../common/base-chart.component'; import * as moment from 'moment'; import { id } from '../utils/id'; import d3 from '../d3'; @Component({ selector: 'ngx-charts-area-chart', template: ` <ngx-charts-chart [view]="[width, height]" [showLegend]="legend" [legendOptions]="legendOptions" [activeEntries]="activeEntries" (legendLabelClick)="onClick($event)" (legendLabelActivate)="onActivate($event)" (legendLabelDeactivate)="onDeactivate($event)"> <svg:defs> <svg:clipPath [attr.id]="clipPathId"> <svg:rect [attr.width]="dims.width + 10" [attr.height]="dims.height + 10" [attr.transform]="'translate(-5, -5)'"/> </svg:clipPath> </svg:defs> <svg:g [attr.transform]="transform" class="area-chart chart"> <svg:g ngx-charts-x-axis *ngIf="xAxis" [xScale]="xScale" [dims]="dims" [showGridLines]="showGridLines" [showLabel]="showXAxisLabel" [labelText]="xAxisLabel" (dimensionsChanged)="updateXAxisHeight($event)"> </svg:g> <svg:g ngx-charts-y-axis *ngIf="yAxis" [yScale]="yScale" [dims]="dims" [showGridLines]="showGridLines" [showLabel]="showYAxisLabel" [labelText]="yAxisLabel" (dimensionsChanged)="updateYAxisWidth($event)"> </svg:g> <svg:g [attr.clip-path]="clipPath"> <svg:g *ngFor="let series of results; trackBy:trackBy"> <svg:g ngx-charts-area-series [xScale]="xScale" [yScale]="yScale" [colors]="colors" [data]="series" [activeEntries]="activeEntries" [scaleType]="scaleType" [gradient]="gradient" [curve]="curve" /> </svg:g> <svg:g ngx-charts-area-tooltip [xSet]="xSet" [xScale]="xScale" [yScale]="yScale" [results]="results" [height]="dims.height" [colors]="colors" (hover)="updateHoveredVertical($event)" /> <svg:g *ngFor="let series of results"> <svg:g ngx-charts-circle-ceries [xScale]="xScale" [yScale]="yScale" [colors]="colors" [activeEntries]="activeEntries" [data]="series" [scaleType]="scaleType" [visibleValue]="hoveredVertical" (select)="onClick($event, series)" (activate)="onActivate($event)" (deactivate)="onDeactivate($event)" /> </svg:g> </svg:g> </svg:g> <svg:g ngx-charts-timeline *ngIf="timeline && scaleType === 'time'" [attr.transform]="timelineTransform" [results]="results" [view]="[timelineWidth, height]" [height]="timelineHeight" [scheme]="scheme" [customColors]="customColors" [legend]="legend" [scaleType]="scaleType" (onDomainChange)="updateDomain($event)"> <svg:g *ngFor="let series of results; trackBy:trackBy"> <svg:g ngx-charts-area-series [xScale]="timelineXScale" [yScale]="timelineYScale" [colors]="colors" [data]="series" [scaleType]="scaleType" [gradient]="gradient" [curve]="curve" /> </svg:g> </svg:g> </ngx-charts-chart> `, changeDetection: ChangeDetectionStrategy.OnPush }) export class AreaChartComponent extends BaseChartComponent { @Input() legend; @Input() state; @Input() xAxis; @Input() yAxis; @Input() autoScale; @Input() showXAxisLabel; @Input() showYAxisLabel; @Input() xAxisLabel; @Input() yAxisLabel; @Input() timeline; @Input() gradient: boolean; @Input() showGridLines: boolean = true; @Input() curve = d3.shape.curveLinear; @Input() activeEntries: any[] = []; @Input() schemeType: string; @Output() activate: EventEmitter<any> = new EventEmitter(); @Output() deactivate: EventEmitter<any> = new EventEmitter(); dims: ViewDimensions; xSet: any; xDomain: any; yDomain: any; seriesDomain: any; xScale: any; yScale: any; transform: string; colors: ColorHelper; clipPathId: string; clipPath: string; scaleType: string; series: any; margin = [10, 20, 10, 20]; hoveredVertical: any; // the value of the x axis that is hovered over xAxisHeight: number = 0; yAxisWidth: number = 0; filteredDomain: any; legendOptions: any; timelineWidth: any; timelineHeight: number = 50; timelineXScale: any; timelineYScale: any; timelineXDomain: any; timelineTransform: any; timelinePadding: number = 10; update(): void { super.update(); this.zone.run(() => { this.dims = calculateViewDimensions({ width: this.width, height: this.height, margins: this.margin, showXAxis: this.xAxis, showYAxis: this.yAxis, xAxisHeight: this.xAxisHeight, yAxisWidth: this.yAxisWidth, showXLabel: this.showXAxisLabel, showYLabel: this.showYAxisLabel, showLegend: this.legend, legendType: this.schemeType }); if (this.timeline) { this.dims.height -= (this.timelineHeight + this.margin[2] + this.timelinePadding); } this.xDomain = this.getXDomain(); if (this.filteredDomain) { this.xDomain = this.filteredDomain; } this.yDomain = this.getYDomain(); this.seriesDomain = this.getSeriesDomain(); this.xScale = this.getXScale(this.xDomain, this.dims.width); this.yScale = this.getYScale(this.yDomain, this.dims.height); this.updateTimeline(); this.setColors(); this.legendOptions = this.getLegendOptions(); this.transform = `translate(${ this.dims.xOffset }, ${ this.margin[0] })`; let pageUrl = window.location.href; this.clipPathId = 'clip' + id().toString(); this.clipPath = `url(${pageUrl}#${this.clipPathId})`; }); } updateTimeline(): void { if (this.timeline) { this.timelineWidth = this.width; if (this.legend) { this.timelineWidth = this.dims.width; } this.timelineXDomain = this.getXDomain(); this.timelineXScale = this.getXScale(this.timelineXDomain, this.timelineWidth); this.timelineYScale = this.getYScale(this.yDomain, this.timelineHeight); this.timelineTransform = `translate(${ this.dims.xOffset }, ${ -this.margin[2] })`; } } getXDomain(): any[] { let values = []; for (let results of this.results) { for (let d of results.series){ if (!values.includes(d.name)) { values.push(d.name); } } } this.scaleType = this.getScaleType(values); let domain = []; if (this.scaleType === 'time') { values = values.map(v => moment(v).toDate()); let min = Math.min(...values); let max = Math.max(...values); domain = [min, max]; } else if (this.scaleType === 'linear') { values = values.map(v => Number(v)); let min = Math.min(...values); let max = Math.max(...values); domain = [min, max]; } else { domain = values; } this.xSet = values; return domain; } getYDomain(): any[] { let domain = []; for (let results of this.results) { for (let d of results.series){ if (!domain.includes(d.value)) { domain.push(d.value); } } } let min = Math.min(...domain); let max = Math.max(...domain); if (!this.autoScale) { min = Math.min(0, min); } return [min, max]; } getSeriesDomain(): any[] { return this.results.map(d => d.name); } getXScale(domain, width) { let scale; if (this.scaleType === 'time') { scale = d3.scaleTime() .range([0, width]) .domain(domain); } else if (this.scaleType === 'linear') { scale = d3.scaleLinear() .range([0, width]) .domain(domain); } else if (this.scaleType === 'ordinal') { scale = d3.scalePoint() .range([0, width]) .padding(0.1) .domain(domain); } return scale; } getYScale(domain, height) { return d3.scaleLinear() .range([height, 0]) .domain(domain); } getScaleType(values): string { let date = true; let num = true; for (let value of values) { if (!this.isDate(value)) { date = false; } if (typeof value !== 'number') { num = false; } } if (date) { return 'time'; } if (num) { return 'linear'; } return 'ordinal'; } isDate(value): boolean { if (value instanceof Date) { return true; } return false; } updateDomain(domain): void { this.filteredDomain = domain; this.xDomain = this.filteredDomain; this.xScale = this.getXScale(this.xDomain, this.dims.width); } updateHoveredVertical(item): void { this.hoveredVertical = item.value; } @HostListener('mouseleave') hideCircles(): void { this.hoveredVertical = null; } onClick(data, series): void { if (series) { data.series = series.name; } this.select.emit(data); } trackBy(index, item): string { return item.name; } setColors(): void { let domain; if (this.schemeType === 'ordinal') { domain = this.seriesDomain; } else { domain = this.yDomain; } this.colors = new ColorHelper(this.scheme, this.schemeType, domain, this.customColors); } getLegendOptions() { let opts = { scaleType: this.schemeType, colors: undefined, domain: [] }; if (opts.scaleType === 'ordinal') { opts.domain = this.seriesDomain; opts.colors = this.colors; } else { opts.domain = this.yDomain; opts.colors = this.colors.scale; } return opts; } updateYAxisWidth({ width }): void { this.yAxisWidth = width; this.update(); } updateXAxisHeight({ height }): void { this.xAxisHeight = height; this.update(); } onActivate(item) { const idx = this.activeEntries.findIndex(d => { return d.name === item.name && d.value === item.value; }); if (idx > -1) { return; } this.activeEntries = [ item, ...this.activeEntries ]; this.activate.emit({ value: item, entries: this.activeEntries }); } onDeactivate(item) { const idx = this.activeEntries.findIndex(d => { return d.name === item.name && d.value === item.value; }); this.activeEntries.splice(idx, 1); this.activeEntries = [...this.activeEntries]; this.deactivate.emit({ value: event, entries: this.activeEntries }); } }