Commit 20834bf1 authored by Léonard Treille's avatar Léonard Treille
Browse files

Add occupancy chart

parent 17b1c9bf
......@@ -3094,8 +3094,7 @@
"commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"dev": true
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
},
"commondir": {
"version": "1.0.1",
......@@ -3823,6 +3822,270 @@
"integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=",
"dev": true
},
"d3": {
"version": "5.16.0",
"resolved": "https://registry.npmjs.org/d3/-/d3-5.16.0.tgz",
"integrity": "sha512-4PL5hHaHwX4m7Zr1UapXW23apo6pexCgdetdJ5kTmADpG/7T9Gkxw0M0tf/pjoB63ezCCm0u5UaFYy2aMt0Mcw==",
"requires": {
"d3-array": "1",
"d3-axis": "1",
"d3-brush": "1",
"d3-chord": "1",
"d3-collection": "1",
"d3-color": "1",
"d3-contour": "1",
"d3-dispatch": "1",
"d3-drag": "1",
"d3-dsv": "1",
"d3-ease": "1",
"d3-fetch": "1",
"d3-force": "1",
"d3-format": "1",
"d3-geo": "1",
"d3-hierarchy": "1",
"d3-interpolate": "1",
"d3-path": "1",
"d3-polygon": "1",
"d3-quadtree": "1",
"d3-random": "1",
"d3-scale": "2",
"d3-scale-chromatic": "1",
"d3-selection": "1",
"d3-shape": "1",
"d3-time": "1",
"d3-time-format": "2",
"d3-timer": "1",
"d3-transition": "1",
"d3-voronoi": "1",
"d3-zoom": "1"
}
},
"d3-array": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz",
"integrity": "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw=="
},
"d3-axis": {
"version": "1.0.12",
"resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-1.0.12.tgz",
"integrity": "sha512-ejINPfPSNdGFKEOAtnBtdkpr24c4d4jsei6Lg98mxf424ivoDP2956/5HDpIAtmHo85lqT4pruy+zEgvRUBqaQ=="
},
"d3-brush": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-1.1.5.tgz",
"integrity": "sha512-rEaJ5gHlgLxXugWjIkolTA0OyMvw8UWU1imYXy1v642XyyswmI1ybKOv05Ft+ewq+TFmdliD3VuK0pRp1VT/5A==",
"requires": {
"d3-dispatch": "1",
"d3-drag": "1",
"d3-interpolate": "1",
"d3-selection": "1",
"d3-transition": "1"
}
},
"d3-chord": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-1.0.6.tgz",
"integrity": "sha512-JXA2Dro1Fxw9rJe33Uv+Ckr5IrAa74TlfDEhE/jfLOaXegMQFQTAgAw9WnZL8+HxVBRXaRGCkrNU7pJeylRIuA==",
"requires": {
"d3-array": "1",
"d3-path": "1"
}
},
"d3-collection": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/d3-collection/-/d3-collection-1.0.7.tgz",
"integrity": "sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A=="
},
"d3-color": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.4.1.tgz",
"integrity": "sha512-p2sTHSLCJI2QKunbGb7ocOh7DgTAn8IrLx21QRc/BSnodXM4sv6aLQlnfpvehFMLZEfBc6g9pH9SWQccFYfJ9Q=="
},
"d3-contour": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-1.3.2.tgz",
"integrity": "sha512-hoPp4K/rJCu0ladiH6zmJUEz6+u3lgR+GSm/QdM2BBvDraU39Vr7YdDCicJcxP1z8i9B/2dJLgDC1NcvlF8WCg==",
"requires": {
"d3-array": "^1.1.1"
}
},
"d3-dispatch": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-1.0.6.tgz",
"integrity": "sha512-fVjoElzjhCEy+Hbn8KygnmMS7Or0a9sI2UzGwoB7cCtvI1XpVN9GpoYlnb3xt2YV66oXYb1fLJ8GMvP4hdU1RA=="
},
"d3-drag": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-1.2.5.tgz",
"integrity": "sha512-rD1ohlkKQwMZYkQlYVCrSFxsWPzI97+W+PaEIBNTMxRuxz9RF0Hi5nJWHGVJ3Om9d2fRTe1yOBINJyy/ahV95w==",
"requires": {
"d3-dispatch": "1",
"d3-selection": "1"
}
},
"d3-dsv": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-1.2.0.tgz",
"integrity": "sha512-9yVlqvZcSOMhCYzniHE7EVUws7Fa1zgw+/EAV2BxJoG3ME19V6BQFBwI855XQDsxyOuG7NibqRMTtiF/Qup46g==",
"requires": {
"commander": "2",
"iconv-lite": "0.4",
"rw": "1"
}
},
"d3-ease": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-1.0.6.tgz",
"integrity": "sha512-SZ/lVU7LRXafqp7XtIcBdxnWl8yyLpgOmzAk0mWBI9gXNzLDx5ybZgnRbH9dN/yY5tzVBqCQ9avltSnqVwessQ=="
},
"d3-fetch": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-1.1.2.tgz",
"integrity": "sha512-S2loaQCV/ZeyTyIF2oP8D1K9Z4QizUzW7cWeAOAS4U88qOt3Ucf6GsmgthuYSdyB2HyEm4CeGvkQxWsmInsIVA==",
"requires": {
"d3-dsv": "1"
}
},
"d3-force": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/d3-force/-/d3-force-1.2.1.tgz",
"integrity": "sha512-HHvehyaiUlVo5CxBJ0yF/xny4xoaxFxDnBXNvNcfW9adORGZfyNF1dj6DGLKyk4Yh3brP/1h3rnDzdIAwL08zg==",
"requires": {
"d3-collection": "1",
"d3-dispatch": "1",
"d3-quadtree": "1",
"d3-timer": "1"
}
},
"d3-format": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.4.4.tgz",
"integrity": "sha512-TWks25e7t8/cqctxCmxpUuzZN11QxIA7YrMbram94zMQ0PXjE4LVIMe/f6a4+xxL8HQ3OsAFULOINQi1pE62Aw=="
},
"d3-geo": {
"version": "1.12.0",
"resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-1.12.0.tgz",
"integrity": "sha512-NalZVW+6/SpbKcnl+BCO67m8gX+nGeJdo6oGL9H6BRUGUL1e+AtPcP4vE4TwCQ/gl8y5KE7QvBzrLn+HsKIl+w==",
"requires": {
"d3-array": "1"
}
},
"d3-hierarchy": {
"version": "1.1.9",
"resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-1.1.9.tgz",
"integrity": "sha512-j8tPxlqh1srJHAtxfvOUwKNYJkQuBFdM1+JAUfq6xqH5eAqf93L7oG1NVqDa4CpFZNvnNKtCYEUC8KY9yEn9lQ=="
},
"d3-interpolate": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-1.4.0.tgz",
"integrity": "sha512-V9znK0zc3jOPV4VD2zZn0sDhZU3WAE2bmlxdIwwQPPzPjvyLkd8B3JUVdS1IDUFDkWZ72c9qnv1GK2ZagTZ8EA==",
"requires": {
"d3-color": "1"
}
},
"d3-path": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz",
"integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg=="
},
"d3-polygon": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-1.0.6.tgz",
"integrity": "sha512-k+RF7WvI08PC8reEoXa/w2nSg5AUMTi+peBD9cmFc+0ixHfbs4QmxxkarVal1IkVkgxVuk9JSHhJURHiyHKAuQ=="
},
"d3-quadtree": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-1.0.7.tgz",
"integrity": "sha512-RKPAeXnkC59IDGD0Wu5mANy0Q2V28L+fNe65pOCXVdVuTJS3WPKaJlFHer32Rbh9gIo9qMuJXio8ra4+YmIymA=="
},
"d3-random": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/d3-random/-/d3-random-1.1.2.tgz",
"integrity": "sha512-6AK5BNpIFqP+cx/sreKzNjWbwZQCSUatxq+pPRmFIQaWuoD+NrbVWw7YWpHiXpCQ/NanKdtGDuB+VQcZDaEmYQ=="
},
"d3-scale": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-2.2.2.tgz",
"integrity": "sha512-LbeEvGgIb8UMcAa0EATLNX0lelKWGYDQiPdHj+gLblGVhGLyNbaCn3EvrJf0A3Y/uOOU5aD6MTh5ZFCdEwGiCw==",
"requires": {
"d3-array": "^1.2.0",
"d3-collection": "1",
"d3-format": "1",
"d3-interpolate": "1",
"d3-time": "1",
"d3-time-format": "2"
}
},
"d3-scale-chromatic": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-1.5.0.tgz",
"integrity": "sha512-ACcL46DYImpRFMBcpk9HhtIyC7bTBR4fNOPxwVSl0LfulDAwyiHyPOTqcDG1+t5d4P9W7t/2NAuWu59aKko/cg==",
"requires": {
"d3-color": "1",
"d3-interpolate": "1"
}
},
"d3-selection": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.4.1.tgz",
"integrity": "sha512-BTIbRjv/m5rcVTfBs4AMBLKs4x8XaaLkwm28KWu9S2vKNqXkXt2AH2Qf0sdPZHjFxcWg/YL53zcqAz+3g4/7PA=="
},
"d3-shape": {
"version": "1.3.7",
"resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz",
"integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==",
"requires": {
"d3-path": "1"
}
},
"d3-time": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.1.0.tgz",
"integrity": "sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA=="
},
"d3-time-format": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.2.3.tgz",
"integrity": "sha512-RAHNnD8+XvC4Zc4d2A56Uw0yJoM7bsvOlJR33bclxq399Rak/b9bhvu/InjxdWhPtkgU53JJcleJTGkNRnN6IA==",
"requires": {
"d3-time": "1"
}
},
"d3-timer": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-1.0.10.tgz",
"integrity": "sha512-B1JDm0XDaQC+uvo4DT79H0XmBskgS3l6Ve+1SBCfxgmtIb1AVrPIoqd+nPSv+loMX8szQ0sVUhGngL7D5QPiXw=="
},
"d3-transition": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-1.3.2.tgz",
"integrity": "sha512-sc0gRU4PFqZ47lPVHloMn9tlPcv8jxgOQg+0zjhfZXMQuvppjG6YuwdMBE0TuqCZjeJkLecku/l9R0JPcRhaDA==",
"requires": {
"d3-color": "1",
"d3-dispatch": "1",
"d3-ease": "1",
"d3-interpolate": "1",
"d3-selection": "^1.1.0",
"d3-timer": "1"
}
},
"d3-voronoi": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/d3-voronoi/-/d3-voronoi-1.1.4.tgz",
"integrity": "sha512-dArJ32hchFsrQ8uMiTBLq256MpnZjeuBtdHpaDlYuQyjU0CVzCJl/BVW+SkszaAeH95D/8gxqAhgx0ouAWAfRg=="
},
"d3-zoom": {
"version": "1.8.3",
"resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-1.8.3.tgz",
"integrity": "sha512-VoLXTK4wvy1a0JpH2Il+F2CiOhVu7VRXWF5M/LroMIh3/zBAC3WAt7QoIvPibOavVo20hN6/37vwAsdBejLyKQ==",
"requires": {
"d3-dispatch": "1",
"d3-drag": "1",
"d3-interpolate": "1",
"d3-selection": "1",
"d3-transition": "1"
}
},
"damerau-levenshtein": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.6.tgz",
......@@ -5641,7 +5904,6 @@
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
"dev": true,
"requires": {
"safer-buffer": ">= 2.1.2 < 3"
}
......@@ -10143,6 +10405,11 @@
"aproba": "^1.1.1"
}
},
"rw": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz",
"integrity": "sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q="
},
"rxjs": {
"version": "6.5.5",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.5.tgz",
......@@ -10169,8 +10436,7 @@
"safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"dev": true
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"sass": {
"version": "1.26.3",
......
......@@ -35,6 +35,11 @@ import { CommunePipe } from '@pipes/commune.pipe';
import { LibellePipe } from '@pipes/libelle.pipe';
import { FilterPipe } from '@pipes/filter.pipe';
import { TimePipe, TimeUnitPipe, TimeSchedulePipe } from '@pipes/time.pipe';
import { ChartComponent } from '@components/chart/chart.component';
import { OccupancyDatasetPipe } from '@features/occupancy/occupancy-dataset.pipe';
import { VerticalBarChartType } from '@components/chart/types/vertical-bar.chart-type';
import { OccupancyChartType } from '@components/chart/types/occupancy.chart-type';
import { OccupancyChartDialogComponent } from '@features/occupancy/occupancy-chart-dialog/occupancy-chart-dialog.component';
@NgModule({
declarations: [
......@@ -56,6 +61,9 @@ import { TimePipe, TimeUnitPipe, TimeSchedulePipe } from '@pipes/time.pipe';
TimePipe,
TimeUnitPipe,
TimeSchedulePipe,
ChartComponent,
OccupancyDatasetPipe,
OccupancyChartDialogComponent
],
imports: [
BrowserModule,
......@@ -80,7 +88,14 @@ import { TimePipe, TimeUnitPipe, TimeSchedulePipe } from '@pipes/time.pipe';
entryComponents: [
DialogRecherchePointComponent
],
providers: [WINDOW_PROVIDERS],
providers: [
WINDOW_PROVIDERS,
// ChartTypes
{ provide: 'ChartType', useClass: OccupancyChartType, multi: true },
// { provide: 'ChartType', useClass: ScatterGraphChartType, multi: true },
// { provide: 'ChartType', useClass: LineChartType, multi: true },
{ provide: 'ChartType', useClass: VerticalBarChartType, multi: true },
],
bootstrap: [AppComponent]
})
export class AppModule { }
import { Component, OnInit, ElementRef, Input, OnChanges, SimpleChanges, Optional, Inject, OnDestroy, HostListener, ViewChild, AfterViewInit, HostBinding } from '@angular/core';
import * as d3 from 'd3';
import { Subject, fromEvent, merge } from 'rxjs';
import { debounceTime, takeUntil, tap } from 'rxjs/operators';
import { Dataset, ChartType, ChartContext, Point } from './chart.model';
@Component({
selector: 'app-chart',
template: `
<div class="chart-tooltip" #tooltip *ngIf="tooltipVisible && !tooltipDisabled" [ngStyle]="{'top.px': tooltipTopPosition, 'left.px': tooltipLeftPosition}" [class.fixed]="tooltipFixed">
<span class="x">{{ tooltipXValue }}</span><br>
<span class="y">{{ tooltipYValue }}</span>
</div>
`,
styles: [
':host {display: block; position: relative;}'
]
})
export class ChartComponent implements OnInit, OnChanges, OnDestroy, AfterViewInit {
@Input() dataset: Dataset;
@Input() type = 'scatter-graph';
@Input() tooltipDisabled = false;
@Input() tooltipOnClick = false;
@Input() tooltipOnHover = false;
tooltipXValue: string;
tooltipYValue: string;
tooltipTopPosition: number;
tooltipLeftPosition: number;
tooltipVisible = false;
tooltipFixed = false;
@ViewChild('tooltip') tooltip: ElementRef<HTMLElement>;
private chart: d3.Selection;
private chartTypes: ChartType[];
private unsubscriber = new Subject<void>();
private changes$ = new Subject<void>();
private _context: ChartContext;
constructor(
private host: ElementRef,
@Optional() @Inject('ChartType') chartTypes: ChartType[]
) {
this.chartTypes = chartTypes || [];
merge(
fromEvent(window, 'resize').pipe(
debounceTime(300),
tap(() => {
this._context = this.context;
})
),
this.changes$
).pipe(takeUntil(this.unsubscriber)).subscribe(() => {
if (this.chart) {
this.update();
}
});
}
ngAfterViewInit(): void {
if (!this.chart) {
this.initChart();
}
// For unknown reasons, the width and height may be changed, so we avoid this behavior by freezing the context.
this._context = this.context;
this.changes$.next();
}
ngOnInit(): void { }
ngOnDestroy(): void {
this.unsubscriber.next();
this.unsubscriber.complete();
}
ngOnChanges(changes: SimpleChanges) {
if (changes.type && changes.type.previousValue) {
const previousType = this.chartTypes.find(t => t.type === changes.type.previousValue);
const width = this.width;
const height = this.height;
previousType.destroy({ height, width, chart: this.chart });
}
if (changes.dataset) {
this.changes$.next();
}
}
update() {
this.chartType.udpate(this.dataset, this.context);
}
private get context(): ChartContext {
if (this._context) {
return this._context;
}
return { height: this.height, width: this.width, chart: this.chart };
}
private get width(): number {
return (this.host.nativeElement as HTMLElement).getBoundingClientRect().width;
}
private get height(): number {
return (this.host.nativeElement as HTMLElement).getBoundingClientRect().height;
}
private initChart() {
this.chart = d3.select(this.host.nativeElement)
.append('svg')
.attr('width', '100%')
.attr('height', '100%');
}
private get chartType(): ChartType {
return this.chartTypes.find(t => t.type === this.type);
}
@HostListener('click', ['$event'])
onClick(event: MouseEvent) {
if (this.tooltipOnClick && (event.target as SVGElement).classList.contains('clickable')) {
this.tooltipFixed = true;
this.tooltipVisible = true;
const rect = (event.target as SVGElement).getBoundingClientRect();
this.tooltipTopPosition = rect.top;
this.tooltipLeftPosition = rect.left + rect.width / 2;
const point: Point = JSON.parse((event.target as SVGElement).getAttribute('data-point'));
const xFormatter = this.dataset.axis && this.dataset.axis.x && this.dataset.axis.x.tooltipFormatter || ((value, _point, dataset) => value);
const yFormatter = this.dataset.axis && this.dataset.axis.y && this.dataset.axis.y.tooltipFormatter || ((value, _point, dataset) => value);
this.tooltipXValue = xFormatter(point.x, point, this.dataset);
this.tooltipYValue = yFormatter(point.y, point, this.dataset);
}
else {
this.tooltipVisible = false;
}
}
@HostListener('mousemove', ['$event'])
onMouseMove(event: MouseEvent) {
if (!this.tooltipOnHover) {
return;
}
if (this.chart) {
this.chartType.globalMouseUpdate(this.dataset, this.context, event);
}
if (!this.tooltipDisabled && this.tooltip && this.chartType.scaleX && this.chartType.scaleY) {
const rect = this.tooltip.nativeElement.getBoundingClientRect();
const padding = this.chartType.padding;
let x = event.offsetX + padding;
let y = event.offsetY + padding;
if (event.offsetX + rect.width + padding * 2 > this.width) {
x = event.offsetX - rect.width - padding;
}
if (event.offsetY + rect.height + padding * 2 > this.height) {
y = event.offsetY - rect.height - padding;
}
this.tooltipTopPosition = y;
this.tooltipLeftPosition = x;
const xFormatter = this.dataset.axis && this.dataset.axis.x && this.dataset.axis.x.tooltipFormatter || ((value) => value);
const yFormatter = this.dataset.axis && this.dataset.axis.y && this.dataset.axis.y.tooltipFormatter || ((value) => value);
this.tooltipXValue = xFormatter(this.chartType.scaleX.invert(event.offsetX));
this.tooltipYValue = yFormatter(this.chartType.scaleY.invert(event.offsetY));
}
}
@HostListener('mouseenter')
onMouseEnter() {
if (this.tooltipOnHover) {
this.tooltipVisible = true;
}
}
@HostListener('mouseleave')
onMouseLeave() {
if (this.tooltipOnHover) {
this.tooltipVisible = false;
}
}
}
import * as d3 from 'd3';
export enum ChartPadding {
TOP = 0,
RIGHT = 1,
BOTTOM = 2,
LEFT = 3,
}
export interface Point {
x: number | string;
y: number | string;
}
export interface Axis {
grouped?: boolean;
isDate?: boolean;
tooltipFormatter?: (value: any, point?: Point, dataset?: Dataset) => string;
}
export interface Dataset {
points: Point[][];
axis?: {
x?: Axis;
y?: Axis;
};
options?: {
padding?: number | [number, number, number, number]
}
}
export interface ChartContext {
height: number;
width: number;
chart: d3.Selection;
}
export interface ChartType {
readonly type: string;
padding: number;
scaleX: d3.Scale;
scaleY: d3.Scale;
udpate(dataset: Dataset, context: ChartContext): void;
destroy(context: ChartContext): void;
globalMouseUpdate(dataset: Dataset, context: ChartContext, event: MouseEvent): void;
}
import * as d3 from 'd3';
import { Dataset, ChartContext, Point, ChartPadding } from '../chart.model';
export abstract class BaseChartType {
padding = 16;
protected transitionDuration = 250;
protected axisLineStaggerTime = 25;
protected displayUserLines = false;
protected displayAxis = false;
scaleX: d3.Scale;
scaleY: d3.Scale;
getPadding(padding: number | ChartPadding = ChartPadding.TOP, dataset?: Dataset) {
if (dataset?.options?.padding) {
return Array.isArray(dataset.options.padding) ? dataset.options.padding[padding] : dataset.options.padding;
}
return Array.isArray(this.padding) ? this.padding[padding] : this.padding;
}
update(dataset: Dataset, context: ChartContext) {
this.scaleX = this.getXScale(dataset, context);
const xAxis = this.getXAxis(dataset, this.scaleX);
this.scaleY = this.getYScale(dataset, context);
const yAxis = this.getYAxis(dataset, this.scaleY);
// Create axis: