Commit 162b4c08 authored by Léonard Treille's avatar Léonard Treille
Browse files

Improve tooltip and refactor vertical bar build

parent 205e34f3
......@@ -49,6 +49,7 @@ import { SearchInputComponent } from './features/search/search-input/search-inpu
import { SearchListComponent } from './features/search/search-list/search-list.component';
import { SearchItemDirective } from './features/search/search-item.directive';
import { IdPipe } from './pipes/id.pipe';
import { ChartTooltipDirective } from './components/chart/chart-tooltip.directive';
@NgModule({
declarations: [
......@@ -78,7 +79,8 @@ import { IdPipe } from './pipes/id.pipe';
SearchInputComponent,
SearchListComponent,
SearchItemDirective,
IdPipe
IdPipe,
ChartTooltipDirective
],
imports: [
BrowserModule,
......
import { Directive } from '@angular/core';
@Directive({
selector: '[appChartTooltip]'
})
export class ChartTooltipDirective {
constructor() { }
}
<div class="chart-tooltip" #tooltip *ngIf="tooltipVisible && !tooltipDisabled" [ngStyle]="{'top.px': tooltipTopPosition, 'left.px': tooltipLeftPosition}"
[class.fixed]="tooltipFixed">
<ng-container *ngTemplateOutlet="tooltipTemplate; context:{$implicit: {x: tooltipXValue, y: tooltipYValue}}"></ng-container>
</div>
<div class="loader-wrapper">
<mat-spinner mode="indeterminate" diameter="64" *ngIf="dataset.options.ghost && !dataset.options.unavailable"></mat-spinner>
</div>
<p class="empty-message" *ngIf="dataset?.options?.empty">Aucune donnée</p>
<p class="unavailable-message" *ngIf="dataset?.options?.unavailable">Données non disponibles</p>
:host {
display: block;
position: relative;
}
.loader-wrapper {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
import { Component, OnInit, ElementRef, Input, OnChanges, SimpleChanges, Optional, Inject, OnDestroy, HostListener, ViewChild, AfterViewInit, HostBinding } from '@angular/core';
import { Component, OnInit, ElementRef, Input, OnChanges, SimpleChanges, Optional, Inject, OnDestroy, HostListener, ViewChild, AfterViewInit, HostBinding, ContentChild, TemplateRef } 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';
import { ChartTooltipDirective } from './chart-tooltip.directive';
@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>
<div class="loader-wrapper">
<mat-spinner mode="indeterminate" diameter="64" *ngIf="dataset.options.ghost && !dataset.options.unavailable"></mat-spinner>
</div>
<p class="empty-message" *ngIf="dataset?.options?.empty">Aucune donnée</p>
<p class="unavailable-message" *ngIf="dataset?.options?.unavailable">Données non disponibles</p>
`,
styles: [
':host {display: block; position: relative;}',
'.loader-wrapper { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); }'
]
templateUrl: './chart.component.html',
styleUrls: ['./chart.component.scss']
})
export class ChartComponent implements OnInit, OnChanges, OnDestroy, AfterViewInit {
......@@ -39,6 +27,8 @@ export class ChartComponent implements OnInit, OnChanges, OnDestroy, AfterViewIn
@ViewChild('tooltip') tooltip: ElementRef<HTMLElement>;
@ContentChild(ChartTooltipDirective, { read: TemplateRef }) tooltipTemplate: TemplateRef<any>;
private chart: d3.Selection;
private chartTypes: ChartType[];
private unsubscriber = new Subject<void>();
......
......@@ -110,17 +110,8 @@ export abstract class BaseChartType {
.attr('transform', `translate(0, ${context.height - (paddingBottom * 2)})`)
.call(axisX);
const fakeData = [];
if (dataset.points.length) {
fakeData.push('y');
}
const yAxisSelection = context.chart.selectAll('.y-axis').data(fakeData);
yAxisSelection
.transition(this.transitionDuration)
.call(axisY)
.selectAll('.full-width-line')
.attr('x1', -6)
.attr('x2', context.width - (paddingRight * 2) + 6);
const yAxisSelection = context.chart.selectAll('.y-axis').data(['y']);
yAxisSelection
.enter()
.append('g')
......@@ -131,11 +122,16 @@ export abstract class BaseChartType {
.append('line')
.attr('class', 'full-width-line')
.attr('x1', -6)
.attr('x2', 0)
.style('opacity', 0)
.transition(this.transitionDuration)
.delay((p: Point, i: number) => i * this.axisLineStaggerTime)
.style('opacity', 1)
.attr('x2', context.width - (paddingRight * 3));
const select = yAxisSelection
.call(axisY);
const ticks = select.selectAll('.tick');
const customLines = ticks.selectAll('.full-width-line').data(['y'])
customLines
.enter()
.append('line')
.attr('class', 'full-width-line')
.attr('x1', -6)
.attr('x2', context.width - (paddingRight * 3));
this.displayAxis = true;
}
......
......@@ -64,7 +64,8 @@ export class VerticalBarChartType extends BaseChartType implements ChartType {
}
return classes;
})
.style('opacity', dataset.options.ghost ? 0.3 : 0.9);
.style('opacity', dataset.options.ghost ? 0.3 : 0.9)
.attr('data-point', p => JSON.stringify(p));
barsSelection.enter()
.append('path')
.attr('class', p => {
......
......@@ -57,7 +57,7 @@ export class DetailProchainspassagesComponent implements OnInit, OnDestroy {
constructor(
private realtimeDataService: RealtimeDataService,
private appVisibilityService: AppVisibilityService,
public dialog: MatDialog
private dialog: MatDialog
) { }
ngOnInit() {
......@@ -138,8 +138,7 @@ export class DetailProchainspassagesComponent implements OnInit, OnDestroy {
openChartDialog(obj: NextStop) {
this.dialog.open(OccupancyChartDialogComponent, {
data: obj,
minWidth: '300px',
maxWidth: '100vw'
panelClass: 'occupancy-chart-dialog',
});
}
......
<h1 mat-dialog-title>Fréquentation en temps normal</h1>
<div mat-dialog-content>
<app-chart type="vertical-bar" [dataset]="data | occupancyDataset:axis | async" [tooltipOnClick]="true"></app-chart>
<app-chart type="vertical-bar" [dataset]="data | occupancyDataset:axis | async" [tooltipOnClick]="true">
<p *appChartTooltip="let values">
<b>{{ values.y }} personnes</b><br> avaient été enregistrées à la montée entre {{ values.x }}.
</p>
</app-chart>
</div>
......@@ -11,3 +11,7 @@ app-chart {
margin-top: $spacing * 5;
box-sizing: border-box;
}
p {
margin: 0;
}
......@@ -15,17 +15,14 @@ export class OccupancyChartDialogComponent implements OnInit {
let end = '';
dataset.points.forEach(group => {
group.forEach((p, i, list) => {
if (p.x === point.x && p.y === point.y) {
if (p.x === point.x) {
end = list[i + 1] ? list[i + 1].x as string : '23:59';
}
});
});
return `Entre ${value.split(':').join('h')} et ${end.split(':').join('h')} :`;
return `${value.split(':').join('h')} et ${end.split(':').join('h')}`;
},
},
y: {
tooltipFormatter: (value, point: Point, dataset: Dataset) => `${value} personnes`,
}
};
constructor(@Inject(MAT_DIALOG_DATA) public data: NextStop) { }
......
......@@ -54,6 +54,8 @@ export class OccupancyDatasetPipe implements PipeTransform {
}
};
if (!isEmpty) {
// Add a fake value to display the last column correctly.
points.push({ x: '23:59', y: 0 });
dataset.points = [points];
}
} else {
......
......@@ -62,7 +62,7 @@
&::after {
content: "";
position: absolute;
bottom: 0;
bottom: 1px;
left: 50%;
transform: translate(-50%, 100%);
width: 0;
......@@ -71,10 +71,10 @@
border-right: 8px solid transparent;
body.dark-theme & {
border-top: 8px solid rgba(dark-color-overlay(16), 0.9);
border-top: 8px solid rgba(dark-color-overlay(16), 0.99);
}
body.light-theme & {
border-top: 8px solid rgba(light-color-overlay(16), 0.9);
border-top: 8px solid rgba(light-color-overlay(16), 0.99);
}
}
}
......@@ -85,7 +85,7 @@
}
body.dark-theme & {
background: rgba(dark-color-overlay(16), 0.9);
background: rgba(dark-color-overlay(16), 0.99);
span {
&.x {
......@@ -98,7 +98,7 @@
}
}
body.light-theme & {
background: rgba(light-color-overlay(1), 0.9);
background: rgba(light-color-overlay(1), 0.99);
span {
&.x {
......
......@@ -22,3 +22,11 @@
}
}
}
.cdk-overlay-pane.occupancy-chart-dialog {
width: 90%;
@media screen and (min-width: $mpwa-breakpoint + 1px) {
width: 50vw;
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment