src/bar-chart/bar-vertical.component.ts
import {
Component,
Input,
ViewEncapsulation,
Output,
EventEmitter,
ChangeDetectionStrategy,
ContentChild,
TemplateRef
} from '@angular/core';
import { scaleBand, scaleLinear } from 'd3-scale';
import { calculateViewDimensions, ViewDimensions } from '../common/view-dimensions.helper';
import { ColorHelper } from '../common/color.helper';
import { BaseChartComponent } from '../common/base-chart.component';
import { DataItem } from '../models/chart-data.model';
import { ScaleType } from '../utils/scale-type.enum';
import { LegendPosition } from '../common/legend/legend-position.enum';
import { LegendOptions } from '../common/legend/legend-options';
@Component({
selector: 'ngx-charts-bar-vertical',
template: `
<ngx-charts-chart
[view]="[width, height]"
[showLegend]="legend"
[legendOptions]="legendOptions"
[activeEntries]="activeEntries"
[animations]="animations"
(legendLabelClick)="onClick($event)"
(legendLabelActivate)="onActivate($event, true)"
(legendLabelDeactivate)="onDeactivate($event, true)"
>
<svg:g [attr.transform]="transform" class="bar-chart chart">
<svg:g
ngx-charts-x-axis
*ngIf="xAxis"
[xScale]="xScale"
[dims]="dims"
[showLabel]="showXAxisLabel"
[labelText]="xAxisLabel"
[showXAxisLineTop]="showXAxisLineTop"
[trimTicks]="trimXAxisTicks"
[rotateTicks]="rotateXAxisTicks"
[maxTickLength]="maxXAxisTickLength"
[tickFormatting]="xAxisTickFormatting"
[ticks]="xAxisTicks"
[xAxisOffset]="dataLabelMaxHeight.negative"
(dimensionsChanged)="updateXAxisHeight($event)"
></svg:g>
<svg:g
ngx-charts-y-axis
*ngIf="yAxis"
[yScale]="yScale"
[dims]="dims"
[showGridLines]="showGridLines"
[showLabel]="showYAxisLabel"
[showYAxisLineLeft]="showYAxisLineLeft"
[showYAxisLineRight]="showYAxisLineRight"
[labelText]="yAxisLabel"
[trimTicks]="trimYAxisTicks"
[maxTickLength]="maxYAxisTickLength"
[tickFormatting]="yAxisTickFormatting"
[ticks]="yAxisTicks"
(dimensionsChanged)="updateYAxisWidth($event)"
></svg:g>
<svg:g
ngx-charts-series-vertical
[xScale]="xScale"
[yScale]="yScale"
[colors]="colors"
[series]="results"
[dims]="dims"
[gradient]="gradient"
[tooltipDisabled]="tooltipDisabled"
[tooltipTemplate]="tooltipTemplate"
[showDataLabel]="showDataLabel"
[dataLabelFormatting]="dataLabelFormatting"
[activeEntries]="activeEntries"
[roundEdges]="roundEdges"
[animations]="animations"
[barWidth]="barWidth"
[noBarWhenZero]="noBarWhenZero"
(activate)="onActivate($event)"
(deactivate)="onDeactivate($event)"
(select)="onClick($event)"
(dataLabelHeightChanged)="onDataLabelMaxHeightChanged($event)"
></svg:g>
</svg:g>
</ngx-charts-chart>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
styleUrls: ['../common/base-chart.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class BarVerticalComponent extends BaseChartComponent {
@Input() legend = false;
@Input() legendTitle: string = 'Legend';
@Input() legendPosition = LegendPosition.right;
@Input() xAxis;
@Input() yAxis;
@Input() showXAxisLabel;
@Input() showYAxisLabel;
@Input() showXAxisLineTop;
@Input() showYAxisLineRight;
@Input() showYAxisLineLeft;
@Input() xAxisLabel;
@Input() yAxisLabel;
@Input() tooltipDisabled: boolean = false;
@Input() gradient: boolean;
@Input() showGridLines: boolean = true;
@Input() activeEntries: any[] = [];
@Input() schemeType: ScaleType;
@Input() trimXAxisTicks: boolean = true;
@Input() trimYAxisTicks: boolean = true;
@Input() rotateXAxisTicks: boolean = true;
@Input() maxXAxisTickLength: number = 16;
@Input() maxYAxisTickLength: number = 16;
@Input() xAxisTickFormatting: any;
@Input() yAxisTickFormatting: any;
@Input() xAxisTicks: any[];
@Input() yAxisTicks: any[];
@Input() barPadding = 8;
@Input() barWidth: number;
@Input() roundDomains: boolean = false;
@Input() roundEdges: boolean = true;
@Input() yScaleMax: number;
@Input() yScaleMin: number;
@Input() showDataLabel: boolean = false;
@Input() dataLabelFormatting: any;
@Input() noBarWhenZero: boolean = true;
@Output() activate: EventEmitter<any> = new EventEmitter();
@Output() deactivate: EventEmitter<any> = new EventEmitter();
@ContentChild('tooltipTemplate', { static: false }) tooltipTemplate: TemplateRef<any>;
dims: ViewDimensions;
xScale: any;
yScale: any;
xDomain: any;
yDomain: any;
transform: string;
colors: ColorHelper;
margin: any[] = [10, 20, 10, 20];
xAxisHeight: number = 0;
yAxisWidth: number = 0;
legendOptions: any;
dataLabelMaxHeight: any = { negative: 0, positive: 0 };
update(): void {
super.update();
if (!this.showDataLabel) {
this.dataLabelMaxHeight = { negative: 0, positive: 0 };
}
this.margin = [10 + this.dataLabelMaxHeight.positive, 20, 10 + this.dataLabelMaxHeight.negative, 20];
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,
legendPosition: this.legendPosition
});
this.formatDates();
if (this.showDataLabel) {
this.dims.height -= this.dataLabelMaxHeight.negative;
}
this.xScale = this.getXScale();
this.yScale = this.getYScale();
this.setColors();
this.legendOptions = this.getLegendOptions();
this.transform = `translate(${this.dims.xOffset} , ${this.margin[0] + this.dataLabelMaxHeight.negative})`;
}
getXScale(): any {
this.xDomain = this.getXDomain();
const spacing = this.xDomain.length / (this.dims.width / this.barPadding + 1);
return scaleBand()
.rangeRound([0, this.dims.width])
.paddingInner(spacing)
.domain(this.xDomain);
}
getYScale(): any {
this.yDomain = this.getYDomain();
const scale = scaleLinear()
.range([this.dims.height, 0])
.domain(this.yDomain);
return this.roundDomains ? scale.nice() : scale;
}
getXDomain(): any[] {
return this.results.map(d => d.label);
}
getYDomain(): [number, number] {
const values = this.results.map(d => d.value);
let min = this.yScaleMin ? Math.min(this.yScaleMin, ...values) : Math.min(0, ...values);
if (this.yAxisTicks && !this.yAxisTicks.some(isNaN)) {
min = Math.min(min, ...this.yAxisTicks);
}
let max = this.yScaleMax ? Math.max(this.yScaleMax, ...values) : Math.max(0, ...values);
if (this.yAxisTicks && !this.yAxisTicks.some(isNaN)) {
max = Math.max(max, ...this.yAxisTicks);
}
return [min, max];
}
onClick(data: DataItem) {
this.select.emit(data);
}
setColors(): void {
let domain;
if (this.schemeType === 'ordinal') {
domain = this.xDomain;
} else {
domain = this.yDomain;
}
this.colors = new ColorHelper(this.scheme, this.schemeType, domain, this.customColors);
}
getLegendOptions(): LegendOptions {
const opts = {
scaleType: this.schemeType,
colors: undefined,
domain: [],
title: undefined,
position: this.legendPosition
};
if (opts.scaleType === 'ordinal') {
opts.domain = this.xDomain;
opts.colors = this.colors;
opts.title = this.legendTitle;
} 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();
}
onDataLabelMaxHeightChanged(event) {
if (event.size.negative) {
this.dataLabelMaxHeight.negative = Math.max(this.dataLabelMaxHeight.negative, event.size.height);
} else {
this.dataLabelMaxHeight.positive = Math.max(this.dataLabelMaxHeight.positive, event.size.height);
}
if (event.index === this.results.length - 1) {
setTimeout(() => this.update());
}
}
onActivate(item, fromLegend = false) {
item = this.results.find(d => {
if (fromLegend) {
return d.label === item.name;
} else {
return d.name === item.name;
}
});
const idx = this.activeEntries.findIndex(d => {
return d.name === item.name && d.value === item.value && d.series === item.series;
});
if (idx > -1) {
return;
}
this.activeEntries = [item, ...this.activeEntries];
this.activate.emit({ value: item, entries: this.activeEntries });
}
onDeactivate(item, fromLegend = false) {
item = this.results.find(d => {
if (fromLegend) {
return d.label === item.name;
} else {
return d.name === item.name;
}
});
const idx = this.activeEntries.findIndex(d => {
return d.name === item.name && d.value === item.value && d.series === item.series;
});
this.activeEntries.splice(idx, 1);
this.activeEntries = [...this.activeEntries];
this.deactivate.emit({ value: item, entries: this.activeEntries });
}
}