Skip to content

Commit

Permalink
feat: olympic line chart better zoom control
Browse files Browse the repository at this point in the history
  • Loading branch information
Kwaadpepper committed Oct 4, 2024
1 parent 63352ed commit 98097a0
Show file tree
Hide file tree
Showing 12 changed files with 147 additions and 30 deletions.
Original file line number Diff line number Diff line change
@@ -1,3 +1,22 @@
<div class="center-toolbar">
<sl-button-group>
<sl-button class="teal" (click)="onPanLeft();$event.stopPropagation();">
<sl-icon name="arrow-left-circle" [title]="panLeftLabel"></sl-icon>
</sl-button>
<sl-button class="teal" (click)="onPanRight();$event.stopPropagation();">
<sl-icon name="arrow-right-circle" [title]="panRightLabel"></sl-icon>
</sl-button>
<sl-button class="teal" (click)="onZoomIn();$event.stopPropagation();">
<sl-icon name="zoom-in" [title]="zoomInLabel"></sl-icon>
</sl-button>
<sl-button class="teal" (click)="onZoomOut();$event.stopPropagation();">
<sl-icon name="zoom-out" [title]="zoomOutLabel"></sl-icon>
</sl-button>
<sl-button class="teal" (click)="onResetZoom();$event.stopPropagation();">
<sl-icon name="arrow-repeat" [title]="zoomResetLabel"></sl-icon>
</sl-button>
</sl-button-group>
</div>
<!--noformat-->
<apx-chart
ngModel #chartComponent
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
color: #f00 !important;
}

.center-toolbar {
display: flex;
justify-content: center;
}

/* stylelint-disable-next-line selector-pseudo-element-no-unknown */
::ng-deep .apexcharts-tooltip {
overflow: initial !important;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { Component, CUSTOM_ELEMENTS_SCHEMA, Input, OnDestroy, OnInit, ViewChild } from '@angular/core'
import { ApexAxisChartSeries, ChartComponent, NgApexchartsModule } from 'ng-apexcharts'
import { Component, CUSTOM_ELEMENTS_SCHEMA, Input, OnChanges, OnDestroy, OnInit, SimpleChange, SimpleChanges, ViewChild } from '@angular/core'
import { ChartComponent, NgApexchartsModule } from 'ng-apexcharts'
import Participation from 'src/app/core/models/participation.interface'
import OlympicConfig from 'src/app/core/OlympicConfig'
import { CustomTooltip } from 'src/app/core/types/apex-charts/apex-tooltip-custom.type'
import { PickApexOptions } from 'src/app/core/types/apex-charts/pick-apex-options.type'

/** Zoom step in years */
const ZOOM_STEP = 4

@Component({
selector: 'app-olympic-country-line-chart',
standalone: true,
Expand All @@ -13,7 +16,7 @@ import { PickApexOptions } from 'src/app/core/types/apex-charts/pick-apex-option
styleUrl: './olympic-country-line-chart.component.scss',
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class OlympicCountryLineChartComponent implements OnInit, OnDestroy {
export class OlympicCountryLineChartComponent implements OnInit, OnChanges, OnDestroy {
@ViewChild('chartComponent', { static: false }) chartComponent?: ChartComponent

@Input({ required: true }) participations: Participation[] = []
Expand All @@ -32,30 +35,25 @@ export class OlympicCountryLineChartComponent implements OnInit, OnDestroy {

public responsive: PickApexOptions<'responsive'>

public panLeftLabel: string = $localize`Pan left`
public panRightLabel: string = $localize`Pan right`
public zoomOutLabel: string = $localize`Zoom out`
public zoomInLabel: string = $localize`Zoom in`
public zoomResetLabel: string = $localize`Zoom reset`

private startDate: Date
private endDate: Date
private minDate: Date
private maxDate: Date

constructor() {
this.chart = {
type: 'line',
stacked: false,
height: 350,
zoom: {
type: 'x',
enabled: true,
autoScaleYaxis: false,
allowMouseWheelZoom: true,
},
zoom: { enabled: false },
selection: { enabled: false },
toolbar: {
autoSelected: 'pan',
tools: {
zoom: false,
zoomin: '<img src="/assets/images/zoom-in.png" width="28">',
zoomout: '<img src="/assets/images/zoom-out.png" width="28">',
reset: '<img src="/assets/images/zoom-reset.png" width="25">',
selection: false,
download: false,
pan: true,
},
},
toolbar: { show: false },
}
this.colors = OlympicConfig.getColors()
this.dataLabels = { enabled: false }
Expand Down Expand Up @@ -89,13 +87,46 @@ export class OlympicCountryLineChartComponent implements OnInit, OnDestroy {
},
},
}
this.xaxis = { categories: [] }

this.xaxis = {
type: 'datetime',
labels: {
show: true,
},
}

this.startDate = new Date()
this.endDate = new Date()
this.minDate = new Date()
this.maxDate = new Date()
}

ngOnInit(): void {
this.updateChartData()
}

ngOnChanges(changes: SimpleChanges): void {
const participationsChanges: SimpleChange | undefined = changes['participations']
if (participationsChanges) {
const participations: Participation[] = participationsChanges.currentValue
if (participationsChanges.firstChange) {
this.participations = participations
}

this.startDate = new Date(`${this.participations.at(0)?.year}-01-01`)
this.endDate = new Date(`${this.participations.at(this.participations.length - 1)?.year}-01-01`)
this.minDate = new Date(this.startDate.getTime())
this.maxDate = new Date(this.endDate.getTime())

this.xaxis = {
min: this.startDate.getTime(),
max: this.endDate.getTime(),
type: 'datetime',
labels: { show: true },
}
}
}

ngOnDestroy(): void {
this.chartComponent?.resetSeries()
this.chartComponent?.destroy()
Expand All @@ -104,36 +135,78 @@ export class OlympicCountryLineChartComponent implements OnInit, OnDestroy {
delete this.chartComponent
}

onResetZoom(): void {
this.minDate.setFullYear(this.startDate.getFullYear())
this.maxDate.setFullYear(this.endDate.getFullYear())
this.updateChartOptions()
}

onZoomIn(): void {
this.minDate.setFullYear(this.minDate.getFullYear() + ZOOM_STEP)
this.maxDate.setFullYear(this.maxDate.getFullYear() - ZOOM_STEP)
this.updateChartOptions()
}

onZoomOut(): void {
this.minDate.setFullYear(this.minDate.getFullYear() - ZOOM_STEP)
this.maxDate.setFullYear(this.maxDate.getFullYear() + ZOOM_STEP)
this.updateChartOptions()
}

onPanLeft(): void {
this.minDate.setFullYear(this.minDate.getFullYear() - ZOOM_STEP)
this.maxDate.setFullYear(this.maxDate.getFullYear() - ZOOM_STEP)
this.updateChartOptions()
}

onPanRight(): void {
this.minDate.setFullYear(this.minDate.getFullYear() + ZOOM_STEP)
this.maxDate.setFullYear(this.maxDate.getFullYear() + ZOOM_STEP)
this.updateChartOptions()
}

private updateChartOptions(): void {
this.chartComponent?.updateOptions({
xaxis: {
min: this.minDate.getTime(),
max: this.maxDate.getTime(),
type: 'datetime',
labels: { show: true },
},
})
}

/** Update chart labels and series */
private updateChartData(): void {
const participations: Participation[] = this.participations
this.xaxis!.categories = participations
.map((participation: Participation) => participation.year)
this.series = [
{
name: $localize`Medals this year`,
type: 'line',
data: participations.map((participation: Participation) => participation.medalsCount),
data: participations.map((participation: Participation) => [
(new Date(`${participation.year}-01-01`)), participation.medalsCount,
]),
} as never,
]
}

/** Format tooltips text */
private tooltipFormatter(
{ series, seriesIndex, dataPointIndex, w }:
Omit<CustomTooltip, 'series'> & { series: ApexAxisChartSeries },
{ series, seriesIndex, dataPointIndex }:
Omit<CustomTooltip, 'series'> & { series: number[][] },
): string {
const serie = series[seriesIndex]
const dataString = serie instanceof Array ? serie[dataPointIndex] : 0
const dataString: number = serie instanceof Array ? serie[dataPointIndex] : 0
const dataLabel = $localize`${dataString} \u{1F3C5}\u{00A0} Medal${dataString ? 's' : ''} won `
const yean = this.participations.at(dataPointIndex)?.year

const div = document.createElement('div')
div.classList.add('olympic-tooltip')
const spanCountry = document.createElement('span')
spanCountry.innerText = dataLabel
const spanMedals = document.createElement('span')
// U+1F3C5 = 🏅 U+00A0 = &nbsp;
spanMedals.innerText = `in ${w.globals.categoryLabels[dataPointIndex]}`
spanMedals.innerText = `in ${yean}`

div.appendChild(spanCountry)
div.appendChild(spanMedals)
Expand Down
2 changes: 1 addition & 1 deletion src/app/core/types/apex-charts/apex-tooltip-custom.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export interface CustomTooltip {
w: {
config: ApexOptions
globals: object & {
labels: string[] | string[][]
labels: string[] | string[][] | number[][]
categoryLabels: string[]
}
}
Expand Down
3 changes: 3 additions & 0 deletions src/assets/icons/arrow-left-circle.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/assets/icons/arrow-repeat.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/assets/icons/arrow-right-circle.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions src/assets/icons/zoom-in.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions src/assets/icons/zoom-out.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed src/assets/images/zoom-in.png
Binary file not shown.
Binary file removed src/assets/images/zoom-out.png
Binary file not shown.
Binary file removed src/assets/images/zoom-reset.png
Binary file not shown.

0 comments on commit 98097a0

Please sign in to comment.