Skip to content

Commit

Permalink
added support for albums and tracks to race-chart (closes #40)
Browse files Browse the repository at this point in the history
  • Loading branch information
felhag committed Feb 23, 2024
1 parent a5f6ae1 commit 8610974
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 18 deletions.
16 changes: 11 additions & 5 deletions projects/shared/src/lib/charts/charts.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,17 @@
</div>
}
<div id="race-chart-toolbar" class="toolbar">
<div>
<button class="play" mat-icon-button><mat-icon>play_arrow</mat-icon></button>
<button class="rewind" mat-icon-button><mat-icon class="material-icons-outlined">keyboard_double_arrow_down</mat-icon></button>
<button class="current" mat-icon-button disabled>1</button>
<button class="forward" mat-icon-button><mat-icon class="material-icons-outlined">keyboard_double_arrow_up</mat-icon></button>
<button class="open" mat-icon-button><mat-icon>settings</mat-icon></button>
<button class="play" mat-icon-button><mat-icon>play_arrow</mat-icon></button>
<span class="tooltip"></span>
<div class="toolbar-menu mat-elevation-z8">
<h3>Speed:</h3>
<div>
<button class="rewind" mat-icon-button><mat-icon class="material-icons-outlined">keyboard_double_arrow_down</mat-icon></button>
<button class="current" mat-icon-button disabled>1</button>
<button class="forward" mat-icon-button><mat-icon class="material-icons-outlined">keyboard_double_arrow_up</mat-icon></button>
</div>
<h3>Type:</h3>
</div>
<input type="range">
</div>
Expand Down
19 changes: 19 additions & 0 deletions projects/shared/src/lib/charts/charts.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,22 @@ mat-card-content .toolbar {
right: 16px;
top: 10px;
}

.tooltip {
line-height: 48px;
vertical-align: top;
}

.toolbar-menu {
position: absolute;
background-color: var(--mat-menu-container-color);

&:not(.open) {
display: none;
}

h3 {
padding: 8px 16px 0 16px;
margin: 0;
}
}
5 changes: 3 additions & 2 deletions projects/shared/src/lib/charts/charts.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { MatTooltipModule } from '@angular/material/tooltip';
import { MatButtonModule } from '@angular/material/button';
import { ChartLoaderDirective } from '../directive/chart-loader.directive';
import { MatCardModule } from '@angular/material/card';
import { MatMenuModule } from '@angular/material/menu';


const darkMode = window.matchMedia('(prefers-color-scheme: dark)');
Expand Down Expand Up @@ -62,7 +63,7 @@ if (darkMode.matches) {
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [TranslatePipe],
standalone: true,
imports: [MatCardModule, ChartLoaderDirective, MatButtonModule, MatTooltipModule, MatIconModule]
imports: [MatCardModule, ChartLoaderDirective, MatButtonModule, MatTooltipModule, MatIconModule, MatMenuModule]
})
export class ChartsComponent {
Highcharts: typeof Highcharts = Highcharts;
Expand All @@ -83,7 +84,7 @@ export class ChartsComponent {
new PunchcardChart(translate, url),
new ScrobbleScatterChart(translate),
new ScrobblePerDayChart(translate),
new RaceChart(url),
new RaceChart(url, mapper),
new ScrobbleMomentChart(translate, 'hours', Array.from(Array(24).keys()).map(k => `${k}h`), s => s.hours),
new ScrobbleMomentChart(translate, 'days', Constants.DAYS,
stats => stats.days,
Expand Down
57 changes: 48 additions & 9 deletions projects/shared/src/lib/charts/race-chart.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,29 @@
import * as Highcharts from 'highcharts';
import { PointOptionsObject, SeriesOptionsType } from 'highcharts';
import { TempStats, Month } from 'projects/shared/src/lib/app/model';
import { TempStats, Month, ItemType, Artist, MonthItem, StreakItem } from 'projects/shared/src/lib/app/model';
import { AbstractChart } from 'projects/shared/src/lib/charts/abstract-chart';
import { AbstractUrlService } from '../service/abstract-url.service';
import { MapperService } from '../service/mapper.service';

export class RaceChart extends AbstractChart {
private readonly defaultSpeed = 2000;

protected type: ItemType = 'artist';
protected stats?: TempStats;

colors: {[key: string]: string} = {};
months: Month[] = [];
artists: string[] = [];
items: string[] = [];
current = -1;
timer?: number;
toolbar?: HTMLElement;
button?: HTMLElement;
tooltip?: HTMLElement;
input?: HTMLInputElement;
speedText?: HTMLElement;
speed = this.defaultSpeed;

constructor(url: AbstractUrlService) {
constructor(url: AbstractUrlService, private mapper: MapperService) {
super();
this.options = {
chart: {
Expand All @@ -32,11 +38,27 @@ export class RaceChart extends AbstractChart {
this.toolbar = document.getElementById('race-chart-toolbar')!;
this.speedText = this.toolbar.querySelector('.current') as HTMLElement;
this.button = this.toolbar.querySelector('.play mat-icon') as HTMLElement;
this.tooltip = this.toolbar.querySelector('.tooltip') as HTMLElement;
this.input = this.toolbar.querySelector('input') as HTMLInputElement;
this.input.onchange = (ev: any) => this.tick(parseInt(ev.target.value));
this.input.onchange = (ev: any) => this.changeRange(parseInt(ev.target.value));
this.input.oninput = (ev: any) => this.showTooltip(parseInt(ev.target.value));

const menu = (this.toolbar!.querySelector('.toolbar-menu') as HTMLButtonElement);
(this.toolbar.querySelector('.play') as HTMLButtonElement).onclick = () => this.toggle();
(this.toolbar.querySelector('.rewind') as HTMLButtonElement).onclick = () => this.changeSpeed(() => this.speed * 2);
(this.toolbar.querySelector('.forward') as HTMLButtonElement).onclick = () => this.changeSpeed(() => this.speed / 2);
(this.toolbar.querySelector('.open') as HTMLButtonElement).onclick = () => menu.classList.toggle('open');

const toggleToolbar = document.getElementById('toggleable-scrobbles-toolbar')!.cloneNode(true) as HTMLElement;
const toggles = toggleToolbar.querySelectorAll('.toggle') as NodeListOf<HTMLButtonElement>;
const toggleTypes: ItemType[] = ['artist', 'album', 'track']
toggles.forEach((button, idx) => button.onclick = () => {
toggles?.forEach(t => t.classList.remove('mat-primary'));
button.classList.add('mat-primary');
this.changeType(toggleTypes[idx]);
});
toggleToolbar.classList.remove('toolbar');
menu.appendChild(toggleToolbar);

const chart = event.target as any as Highcharts.Chart;
chart.container.parentNode!.appendChild(this.toolbar);
Expand All @@ -53,7 +75,7 @@ export class RaceChart extends AbstractChart {
borderWidth: 0
} as any
},
title: {text: 'Artists race chart'},
title: {text: 'Race chart'},
xAxis: {type: 'category'},
yAxis: [{
opposite: true,
Expand Down Expand Up @@ -114,8 +136,9 @@ export class RaceChart extends AbstractChart {
}

update(stats: TempStats): void {
this.months = Object.values(stats.monthList);
this.artists = Object.keys(stats.seenArtists);
this.stats = stats;
this.months = Object.values(this.stats!.monthList);
this.items = Object.keys(this.mapper.seen(this.type, this.stats!));
this.updateSlider();
}

Expand All @@ -126,7 +149,7 @@ export class RaceChart extends AbstractChart {
}

private getData(month: Month): PointOptionsObject[] {
return this.artists
return this.items
.map(a => ({name: a, count: this.cumulativeUntil(month, a)}))
.sort((a, b) => b.count - a.count)
.slice(0, 25)
Expand All @@ -148,7 +171,7 @@ export class RaceChart extends AbstractChart {
}

private cumulativeUntil(until: Month, artist: string): number {
return this.months.slice(0, this.months.indexOf(until) + 1).reduce((acc, cur) => acc + (cur.artists.get(artist)?.count || 0), 0);
return this.months.slice(0, this.months.indexOf(until) + 1).reduce((acc, cur) => acc + (this.mapper.monthItem(this.type, cur, {name: artist} as StreakItem)?.count || 0), 0);
}

/**
Expand Down Expand Up @@ -198,6 +221,22 @@ export class RaceChart extends AbstractChart {
}
}

changeType(type: ItemType): void {
this.type = type;
this.update(this.stats!);
}

changeRange(value: number): void {
this.tooltip!.style.display = 'none';
this.tick(value);
}

showTooltip(value: number): void {
const next = Math.min(value, this.months.length - 1);
this.tooltip!.style.display = 'inline';
this.tooltip!.innerText = this.months[next].alias;
}

protected load(container: HTMLElement): void {
super.load(container);
this.tick(0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ <h2 mat-dialog-title>Retrieve data from Spotify</h2>
retrieve this data programmatically so you'll have to request this data manually. You can
do this on the <a href="https://www.spotify.com/us/account/privacy/" target="_blank">spotify privacy page</a>.<br><br>

<strong>Note:</strong> When you use the "Download your data" tool it will only provide streaming history for the <u>past year</u>.
If you send an email to <a href="mailto:[email protected]">privacy&#64;spotify.com</a> it includes <u>all</u> your streaming history!<br><br>
<strong>Note:</strong> Default it will only provide streaming history for the <u>past year</u>.
If you select Extended streaming history it includes <u>all</u> your streaming history!<br><br>

After you received an email (takes about a week) you can download the zip and upload my_spotify_data.zip here.
</div>
Expand Down

0 comments on commit 8610974

Please sign in to comment.