@swimlane/ngx-charts
Version:
Declarative Charting Framework for Angular2 and beyond!
430 lines (375 loc) • 11.2 kB
text/typescript
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';
export class AreaChartComponent extends BaseChartComponent {
legend;
state;
xAxis;
yAxis;
autoScale;
showXAxisLabel;
showYAxisLabel;
xAxisLabel;
yAxisLabel;
timeline;
gradient: boolean;
showGridLines: boolean = true;
curve = d3.shape.curveLinear;
activeEntries: any[] = [];
schemeType: string;
activate: EventEmitter<any> = new EventEmitter();
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;
}
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 });
}
}