|
|
@ -1,6 +1,5 @@ |
|
|
|
import { css, customElement, FASTElement, html, observable, ref } from '@microsoft/fast-element'; |
|
|
|
import type { Chart, DataItem, RegionOptions } from 'billboard.js'; |
|
|
|
import { bb, selection, spline, zoom } from 'billboard.js'; |
|
|
|
import { groupByMap } from '../../../../../system/array'; |
|
|
|
import { first, flatMap, map, some, union } from '../../../../../system/iterable'; |
|
|
|
import { pluralize } from '../../../../../system/string'; |
|
|
@ -387,7 +386,7 @@ export class GraphMinimap extends FASTElement { |
|
|
|
override connectedCallback(): void { |
|
|
|
super.connectedCallback(); |
|
|
|
|
|
|
|
this.loadChart(); |
|
|
|
this.dataChanged(undefined, undefined, false); |
|
|
|
} |
|
|
|
|
|
|
|
override disconnectedCallback(): void { |
|
|
@ -516,7 +515,14 @@ export class GraphMinimap extends FASTElement { |
|
|
|
} satisfies RegionOptions; |
|
|
|
} |
|
|
|
|
|
|
|
private _loading: Promise<void> | undefined; |
|
|
|
private loadChart() { |
|
|
|
if (this._loading == null) { |
|
|
|
this._loading = this.loadChartCore().then(() => (this._loading = undefined)); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
private async loadChartCore() { |
|
|
|
if (!this.data?.size) { |
|
|
|
this._chart?.destroy(); |
|
|
|
this._chart = undefined!; |
|
|
@ -582,148 +588,152 @@ export class GraphMinimap extends FASTElement { |
|
|
|
const yMax = p98 + Math.min(changesMax - p98, p98 * 0.02) + 100; |
|
|
|
|
|
|
|
if (this._chart == null) { |
|
|
|
this._chart = bb.generate({ |
|
|
|
bindto: this.chart, |
|
|
|
data: { |
|
|
|
x: 'date', |
|
|
|
xSort: false, |
|
|
|
axes: { |
|
|
|
activity: 'y', |
|
|
|
additions: 'y', |
|
|
|
deletions: 'y', |
|
|
|
}, |
|
|
|
columns: [ |
|
|
|
['date', ...dates], |
|
|
|
['activity', ...activity], |
|
|
|
// ['additions', ...additions],
|
|
|
|
// ['deletions', ...deletions],
|
|
|
|
], |
|
|
|
names: { |
|
|
|
activity: 'Activity', |
|
|
|
// additions: 'Additions',
|
|
|
|
// deletions: 'Deletions',
|
|
|
|
try { |
|
|
|
const { bb, selection, spline, zoom } = await import( |
|
|
|
/* webpackChunkName: "billboard" */ 'billboard.js' |
|
|
|
); |
|
|
|
this._chart = bb.generate({ |
|
|
|
bindto: this.chart, |
|
|
|
data: { |
|
|
|
x: 'date', |
|
|
|
xSort: false, |
|
|
|
axes: { |
|
|
|
activity: 'y', |
|
|
|
additions: 'y', |
|
|
|
deletions: 'y', |
|
|
|
}, |
|
|
|
columns: [ |
|
|
|
['date', ...dates], |
|
|
|
['activity', ...activity], |
|
|
|
// ['additions', ...additions],
|
|
|
|
// ['deletions', ...deletions],
|
|
|
|
], |
|
|
|
names: { |
|
|
|
activity: 'Activity', |
|
|
|
// additions: 'Additions',
|
|
|
|
// deletions: 'Deletions',
|
|
|
|
}, |
|
|
|
// hide: ['additions', 'deletions'],
|
|
|
|
onclick: d => { |
|
|
|
if (d.id !== 'activity') return; |
|
|
|
|
|
|
|
const date = new Date(d.x); |
|
|
|
const day = getDay(date); |
|
|
|
const sha = this.searchResults?.get(day)?.sha ?? this.data?.get(day)?.sha; |
|
|
|
|
|
|
|
queueMicrotask(() => { |
|
|
|
this.$emit('selected', { |
|
|
|
date: date, |
|
|
|
sha: sha, |
|
|
|
} satisfies GraphMinimapDaySelectedEventDetail); |
|
|
|
}); |
|
|
|
}, |
|
|
|
selection: { |
|
|
|
enabled: selection(), |
|
|
|
grouped: true, |
|
|
|
multiple: false, |
|
|
|
// isselectable: d => {
|
|
|
|
// if (d.id !== 'activity') return false;
|
|
|
|
|
|
|
|
// return (this.data?.get(getDay(new Date(d.x)))?.commits ?? 0) > 0;
|
|
|
|
// },
|
|
|
|
}, |
|
|
|
colors: { |
|
|
|
activity: 'var(--color-graph-minimap-line0)', |
|
|
|
// additions: 'rgba(73, 190, 71, 0.7)',
|
|
|
|
// deletions: 'rgba(195, 32, 45, 0.7)',
|
|
|
|
}, |
|
|
|
groups: [['additions', 'deletions']], |
|
|
|
types: { |
|
|
|
activity: spline(), |
|
|
|
// additions: bar(),
|
|
|
|
// deletions: bar(),
|
|
|
|
}, |
|
|
|
}, |
|
|
|
// hide: ['additions', 'deletions'],
|
|
|
|
onclick: d => { |
|
|
|
if (d.id !== 'activity') return; |
|
|
|
|
|
|
|
const date = new Date(d.x); |
|
|
|
const day = getDay(date); |
|
|
|
const sha = this.searchResults?.get(day)?.sha ?? this.data?.get(day)?.sha; |
|
|
|
|
|
|
|
queueMicrotask(() => { |
|
|
|
this.$emit('selected', { |
|
|
|
date: date, |
|
|
|
sha: sha, |
|
|
|
} satisfies GraphMinimapDaySelectedEventDetail); |
|
|
|
}); |
|
|
|
area: { |
|
|
|
linearGradient: true, |
|
|
|
front: true, |
|
|
|
below: true, |
|
|
|
zerobased: true, |
|
|
|
}, |
|
|
|
selection: { |
|
|
|
enabled: selection(), |
|
|
|
grouped: true, |
|
|
|
multiple: false, |
|
|
|
// isselectable: d => {
|
|
|
|
// if (d.id !== 'activity') return false;
|
|
|
|
|
|
|
|
// return (this.data?.get(getDay(new Date(d.x)))?.commits ?? 0) > 0;
|
|
|
|
axis: { |
|
|
|
x: { |
|
|
|
show: false, |
|
|
|
localtime: true, |
|
|
|
type: 'timeseries', |
|
|
|
}, |
|
|
|
y: { |
|
|
|
min: 0, |
|
|
|
max: yMax, |
|
|
|
show: true, |
|
|
|
padding: { |
|
|
|
// top: 10,
|
|
|
|
bottom: 8, |
|
|
|
}, |
|
|
|
}, |
|
|
|
// y2: {
|
|
|
|
// min: y2Min,
|
|
|
|
// max: yMax,
|
|
|
|
// show: true,
|
|
|
|
// // padding: {
|
|
|
|
// // top: 10,
|
|
|
|
// // bottom: 0,
|
|
|
|
// // },
|
|
|
|
// },
|
|
|
|
}, |
|
|
|
colors: { |
|
|
|
activity: 'var(--color-graph-minimap-line0)', |
|
|
|
// additions: 'rgba(73, 190, 71, 0.7)',
|
|
|
|
// deletions: 'rgba(195, 32, 45, 0.7)',
|
|
|
|
bar: { |
|
|
|
zerobased: false, |
|
|
|
width: { max: 3 }, |
|
|
|
}, |
|
|
|
groups: [['additions', 'deletions']], |
|
|
|
types: { |
|
|
|
activity: spline(), |
|
|
|
// additions: bar(),
|
|
|
|
// deletions: bar(),
|
|
|
|
clipPath: false, |
|
|
|
grid: { |
|
|
|
front: false, |
|
|
|
focus: { |
|
|
|
show: true, |
|
|
|
}, |
|
|
|
}, |
|
|
|
}, |
|
|
|
area: { |
|
|
|
linearGradient: true, |
|
|
|
front: true, |
|
|
|
below: true, |
|
|
|
zerobased: true, |
|
|
|
}, |
|
|
|
axis: { |
|
|
|
x: { |
|
|
|
legend: { |
|
|
|
show: false, |
|
|
|
localtime: true, |
|
|
|
type: 'timeseries', |
|
|
|
}, |
|
|
|
y: { |
|
|
|
min: 0, |
|
|
|
max: yMax, |
|
|
|
show: true, |
|
|
|
padding: { |
|
|
|
// top: 10,
|
|
|
|
bottom: 8, |
|
|
|
}, |
|
|
|
line: { |
|
|
|
point: true, |
|
|
|
zerobased: true, |
|
|
|
}, |
|
|
|
// y2: {
|
|
|
|
// min: y2Min,
|
|
|
|
// max: yMax,
|
|
|
|
// show: true,
|
|
|
|
// // padding: {
|
|
|
|
// // top: 10,
|
|
|
|
// // bottom: 0,
|
|
|
|
// // },
|
|
|
|
// },
|
|
|
|
}, |
|
|
|
bar: { |
|
|
|
zerobased: false, |
|
|
|
width: { max: 3 }, |
|
|
|
}, |
|
|
|
clipPath: false, |
|
|
|
grid: { |
|
|
|
front: false, |
|
|
|
focus: { |
|
|
|
point: { |
|
|
|
show: true, |
|
|
|
select: { |
|
|
|
r: 5, |
|
|
|
}, |
|
|
|
focus: { |
|
|
|
only: true, |
|
|
|
expand: { |
|
|
|
enabled: true, |
|
|
|
r: 3, |
|
|
|
}, |
|
|
|
}, |
|
|
|
sensitivity: 100, |
|
|
|
}, |
|
|
|
}, |
|
|
|
legend: { |
|
|
|
show: false, |
|
|
|
}, |
|
|
|
line: { |
|
|
|
point: true, |
|
|
|
zerobased: true, |
|
|
|
}, |
|
|
|
point: { |
|
|
|
show: true, |
|
|
|
select: { |
|
|
|
r: 5, |
|
|
|
regions: regions, |
|
|
|
resize: { |
|
|
|
auto: true, |
|
|
|
}, |
|
|
|
focus: { |
|
|
|
only: true, |
|
|
|
expand: { |
|
|
|
enabled: true, |
|
|
|
r: 3, |
|
|
|
spline: { |
|
|
|
interpolation: { |
|
|
|
type: 'catmull-rom', |
|
|
|
}, |
|
|
|
}, |
|
|
|
sensitivity: 100, |
|
|
|
}, |
|
|
|
regions: regions, |
|
|
|
resize: { |
|
|
|
auto: true, |
|
|
|
}, |
|
|
|
spline: { |
|
|
|
interpolation: { |
|
|
|
type: 'catmull-rom', |
|
|
|
}, |
|
|
|
}, |
|
|
|
tooltip: { |
|
|
|
contents: (data, _defaultTitleFormat, _defaultValueFormat, _color) => { |
|
|
|
const date = new Date(data[0].x); |
|
|
|
|
|
|
|
const stat = this.data?.get(getDay(date)); |
|
|
|
const markers = this.markers?.get(getDay(date)); |
|
|
|
let groups; |
|
|
|
if (markers?.length) { |
|
|
|
groups = groupByMap(markers, m => m.type); |
|
|
|
} |
|
|
|
|
|
|
|
return /*html*/ `<div class="bb-tooltip">
|
|
|
|
tooltip: { |
|
|
|
contents: (data, _defaultTitleFormat, _defaultValueFormat, _color) => { |
|
|
|
const date = new Date(data[0].x); |
|
|
|
|
|
|
|
const stat = this.data?.get(getDay(date)); |
|
|
|
const markers = this.markers?.get(getDay(date)); |
|
|
|
let groups; |
|
|
|
if (markers?.length) { |
|
|
|
groups = groupByMap(markers, m => m.type); |
|
|
|
} |
|
|
|
|
|
|
|
return /*html*/ `<div class="bb-tooltip">
|
|
|
|
<div class="header"> |
|
|
|
<span class="header--title">${formatDate(date, 'MMMM Do, YYYY')}</span> |
|
|
|
<span class="header--description">(${capitalize(fromNow(date))})</span> |
|
|
@ -791,48 +801,51 @@ export class GraphMinimap extends FASTElement { |
|
|
|
: '' |
|
|
|
} |
|
|
|
</div>`;
|
|
|
|
}, |
|
|
|
position: (_data, width, _height, element, pos) => { |
|
|
|
const { x } = pos; |
|
|
|
const rect = (element as HTMLElement).getBoundingClientRect(); |
|
|
|
let left = rect.right - x; |
|
|
|
if (left + width > rect.right) { |
|
|
|
left = rect.right - width; |
|
|
|
} |
|
|
|
return { top: 0, left: left }; |
|
|
|
}, |
|
|
|
}, |
|
|
|
position: (_data, width, _height, element, pos) => { |
|
|
|
const { x } = pos; |
|
|
|
const rect = (element as HTMLElement).getBoundingClientRect(); |
|
|
|
let left = rect.right - x; |
|
|
|
if (left + width > rect.right) { |
|
|
|
left = rect.right - width; |
|
|
|
} |
|
|
|
return { top: 0, left: left }; |
|
|
|
transition: { |
|
|
|
duration: 0, |
|
|
|
}, |
|
|
|
}, |
|
|
|
transition: { |
|
|
|
duration: 0, |
|
|
|
}, |
|
|
|
zoom: { |
|
|
|
enabled: zoom(), |
|
|
|
rescale: false, |
|
|
|
resetButton: { |
|
|
|
text: '', |
|
|
|
zoom: { |
|
|
|
enabled: zoom(), |
|
|
|
rescale: false, |
|
|
|
resetButton: { |
|
|
|
text: '', |
|
|
|
}, |
|
|
|
type: 'wheel', |
|
|
|
onzoom: () => { |
|
|
|
// Reset the active day when zooming because it fails to update properly
|
|
|
|
queueMicrotask(() => this.activeDayChanged()); |
|
|
|
}, |
|
|
|
}, |
|
|
|
type: 'wheel', |
|
|
|
onzoom: () => { |
|
|
|
// Reset the active day when zooming because it fails to update properly
|
|
|
|
queueMicrotask(() => this.activeDayChanged()); |
|
|
|
onafterinit: function () { |
|
|
|
const xAxis = this.$.main.selectAll<Element, any>('.bb-axis-x').node(); |
|
|
|
xAxis?.remove(); |
|
|
|
|
|
|
|
const yAxis = this.$.main.selectAll<Element, any>('.bb-axis-y').node(); |
|
|
|
yAxis?.remove(); |
|
|
|
|
|
|
|
const grid = this.$.main.selectAll<Element, any>('.bb-grid').node(); |
|
|
|
grid?.removeAttribute('clip-path'); |
|
|
|
|
|
|
|
// Move the regions to be on top of the bars
|
|
|
|
const bars = this.$.main.selectAll<Element, any>('.bb-chart-bars').node(); |
|
|
|
const regions = this.$.main.selectAll<Element, any>('.bb-regions').node(); |
|
|
|
bars?.insertAdjacentElement('afterend', regions!); |
|
|
|
}, |
|
|
|
}, |
|
|
|
onafterinit: function () { |
|
|
|
const xAxis = this.$.main.selectAll<Element, any>('.bb-axis-x').node(); |
|
|
|
xAxis?.remove(); |
|
|
|
|
|
|
|
const yAxis = this.$.main.selectAll<Element, any>('.bb-axis-y').node(); |
|
|
|
yAxis?.remove(); |
|
|
|
|
|
|
|
const grid = this.$.main.selectAll<Element, any>('.bb-grid').node(); |
|
|
|
grid?.removeAttribute('clip-path'); |
|
|
|
|
|
|
|
// Move the regions to be on top of the bars
|
|
|
|
const bars = this.$.main.selectAll<Element, any>('.bb-chart-bars').node(); |
|
|
|
const regions = this.$.main.selectAll<Element, any>('.bb-regions').node(); |
|
|
|
bars?.insertAdjacentElement('afterend', regions!); |
|
|
|
}, |
|
|
|
}); |
|
|
|
}); |
|
|
|
} catch (ex) { |
|
|
|
debugger; |
|
|
|
} |
|
|
|
} else { |
|
|
|
this._chart.load({ |
|
|
|
columns: [ |
|
|
|