@swimlane/ngx-charts
Version:
Declarative Charting Framework for Angular2 and beyond!
201 lines (173 loc) • 6.18 kB
text/typescript
import {
Component,
ContentChild,
ElementRef,
HostListener,
Input,
TemplateRef,
ViewChild,
Output,
EventEmitter,
ChangeDetectionStrategy
} from '@angular/core';
import { ChartComponent } from '../common/charts/chart.component';
import { BaseChartComponent } from '../common/base-chart.component';
import { calculateViewDimensions, ViewDimensions } from '../common/view-dimensions.helper';
import d3 from '../d3';
import { ColorHelper } from '../common/color.helper';
export class ForceDirectedGraphComponent extends BaseChartComponent {
force = d3.forceSimulation()
.force('charge', d3.forceManyBody())
.force('collide', d3.forceCollide(5))
.force('x', d3.forceX())
.force('y', d3.forceY());
forceLink = d3.forceLink().id(node => node.value);
legend: boolean;
nodes: any[] = [];
links: Array<{ source: any, target: any }> = [];
activeEntries: any[] = [];
activate: EventEmitter<any> = new EventEmitter();
deactivate: EventEmitter<any> = new EventEmitter();
linkTemplate: TemplateRef<any>;
nodeTemplate: TemplateRef<any>;
chart: ElementRef;
colors: ColorHelper;
dims: ViewDimensions;
draggingNode: any;
draggingStart: { x: number, y: number };
margin = [0, 0, 0, 0];
results = [];
seriesDomain: any;
transform: string;
legendOptions: any;
groupResultsBy: (node: any) => string = node => node.value;
update(): void {
super.update();
this.zone.run(() => {
// center graph
this.dims = calculateViewDimensions({
width: this.width,
height: this.height,
margins: this.margin,
showLegend: this.legend,
});
this.seriesDomain = this.getSeriesDomain();
this.setColors();
this.legendOptions = this.getLegendOptions();
this.transform = `
translate(${ this.dims.xOffset + this.dims.width / 2 }, ${ this.margin[0] + this.dims.height / 2 })
`;
if(this.force) {
this.force.nodes(this.nodes)
.force('link', this.forceLink.links(this.links))
.alpha(0.5).restart();
}
});
}
onClick(data, node): void {
this.select.emit(data);
}
onActivate(event): void {
if(this.activeEntries.indexOf(event) > -1) return;
this.activeEntries = [ event, ...this.activeEntries ];
this.activate.emit({ value: event, entries: this.activeEntries });
}
onDeactivate(event): void {
const idx = this.activeEntries.indexOf(event);
this.activeEntries.splice(idx, 1);
this.activeEntries = [...this.activeEntries];
this.deactivate.emit({ value: event, entries: this.activeEntries });
}
getSeriesDomain(): any[] {
return this.nodes.map(d => this.groupResultsBy(d))
.reduce((nodes: any[], node): any[] => nodes.includes(node) ? nodes : nodes.concat([node]), [])
.sort();
}
trackLinkBy(index, link): any {
return link.index;
}
trackNodeBy(index, node): any {
return node.value;
}
setColors(): void {
this.colors = new ColorHelper(this.scheme, 'ordinal', this.seriesDomain, this.customColors);
}
getLegendOptions() {
return {
scaleType: 'ordinal',
domain: this.seriesDomain,
colors: this.colors
};
}
// Easier to use Angular2 event management than use d3.drag
onDragStart(node, $event: MouseEvent): void {
this.force.alphaTarget(0.3).restart();
this.draggingNode = node;
this.draggingStart = { x: $event.x - node.x, y: $event.y - node.y };
this.draggingNode.fx = $event.x - this.draggingStart.x;
this.draggingNode.fy = $event.y - this.draggingStart.y;
}
onDrag($event: MouseEvent): void {
if (!this.draggingNode) return;
this.draggingNode.fx = $event.x - this.draggingStart.x;
this.draggingNode.fy = $event.y - this.draggingStart.y;
}
onDragEnd(node, $event: MouseEvent): void {
if (!this.draggingNode) return;
this.force.alphaTarget(0);
this.draggingNode.fx = undefined;
this.draggingNode.fy = undefined;
this.draggingNode = undefined;
}
}