Skip to content

Commit

Permalink
Implement MissingOneImplementation LineChart (#955)
Browse files Browse the repository at this point in the history
* Missing one implementation draft

* fix

* fix

* fix

* fix

* fix

* another screenshot fix
  • Loading branch information
KyleJu authored Dec 16, 2024
1 parent 2901887 commit 3f84f0f
Show file tree
Hide file tree
Showing 7 changed files with 137 additions and 39 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 13 additions & 4 deletions frontend/src/static/js/components/webstatus-gchart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ export class WebstatusGChart extends LitElement {
})
dataObj: WebStatusDataObj | undefined;

@property({
type: Boolean,
attribute: 'hasMax',
})
hasMax = true;

@property({state: true, type: Object})
dataTable:
| google.visualization.DataTable
Expand Down Expand Up @@ -142,10 +148,13 @@ export class WebstatusGChart extends LitElement {
augmentOptions(
options: google.visualization.ComboChartOptions,
): google.visualization.ComboChartOptions {
options = {
...options,
tooltip: {trigger: 'selection'},
};
if (!this.hasMax) {
options = {
...options,
tooltip: {trigger: 'selection'},
};
return options;
}

const numColumns = this.dataTable!.getNumberOfColumns();
// The number of series is the number of columns with role 'data'.
Expand Down
159 changes: 124 additions & 35 deletions frontend/src/static/js/components/webstatus-stats-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ import {apiClientContext} from '../contexts/api-client-context.js';
import './webstatus-gchart';
import {WebStatusDataObj} from './webstatus-gchart.js';

/** Generate a key for globalFeatureSupport. */
function globalFeatureSupportKey(browser: BrowsersParameter): string {
/** Generate a key for globalFeatureSupport and missingOneImplementationMap. */
function statsDataKey(browser: BrowsersParameter): string {
return browser;
}

Expand All @@ -47,7 +47,7 @@ export class StatsPage extends LitElement {
apiClient!: APIClient;

@state()
globalFeatureSupportBrowsers: BrowsersParameter[] = ALL_BROWSERS;
supportedBrowsers: BrowsersParameter[] = ALL_BROWSERS;

@state()
// Default: Date.now() - 1 year.
Expand All @@ -57,7 +57,7 @@ export class StatsPage extends LitElement {
endDate: Date = new Date(); // Today

// Map from browser-channel to global feature support.
// The key is generated by globalFeatureSupportKey().
// The key is generated by statsDataKey().
@state()
globalFeatureSupport = new Map<string, Array<BrowserReleaseFeatureMetric>>();

Expand All @@ -67,6 +67,15 @@ export class StatsPage extends LitElement {
@state()
globalFeatureSupportChartDataObj: WebStatusDataObj | undefined;

@state()
missingOneImplementationMap = new Map<
string,
Array<BrowserReleaseFeatureMetric>
>();

@state()
missingOneImplementationChartDataObj: WebStatusDataObj | undefined;

static get styles(): CSSResultGroup {
return [
SHARED_STYLES,
Expand Down Expand Up @@ -101,7 +110,7 @@ export class StatsPage extends LitElement {
) as Array<SlMenuItem>;

// Build the list of values of checked menu-items.
this.globalFeatureSupportBrowsers = menuItemsArray
this.supportedBrowsers = menuItemsArray
.filter(menuItem => menuItem.checked)
.map(menuItem => menuItem.value) as BrowsersParameter[];
// Regenerate data and redraw. We should instead just filter it.
Expand All @@ -121,6 +130,8 @@ export class StatsPage extends LitElement {

globalFeatureSupportResizeObserver: ResizeObserver | null = null;

missingOneImplementationResizeObserver: ResizeObserver | null = null;

setupResizeObserver() {
// Set up ResizeObserver one time to redraw chart when container resizes.
if (!this.globalFeatureSupportResizeObserver) {
Expand All @@ -133,6 +144,17 @@ export class StatsPage extends LitElement {
});
this.globalFeatureSupportResizeObserver.observe(gfsChartElement);
}

if (!this.missingOneImplementationResizeObserver) {
const gfsChartElement = this.shadowRoot!.getElementById(
'missing-one-implementation-chart',
);
if (!gfsChartElement) return;
this.missingOneImplementationResizeObserver = new ResizeObserver(() => {
// TODO: trigger update based on resize.
});
this.missingOneImplementationResizeObserver.observe(gfsChartElement);
}
}

async _fetchGlobalFeatureSupportData(
Expand All @@ -149,18 +171,46 @@ export class StatsPage extends LitElement {
)) {
// Append the new data to existing data
const existingData =
this.globalFeatureSupport.get(globalFeatureSupportKey(browser)) || [];
this.globalFeatureSupport.set(globalFeatureSupportKey(browser), [
this.globalFeatureSupport.get(statsDataKey(browser)) || [];
this.globalFeatureSupport.set(statsDataKey(browser), [
...existingData,
...page,
]);
}
this.globalFeatureSupportChartDataObj =
this.createGlobalFeatureSupportDataFromMap();
this.globalFeatureSupportChartDataObj = this.createDisplayDataFromMap(
this.globalFeatureSupport,
true,
);
});
await Promise.all(promises); // Wait for all browsers to finish
}

// TODO: Finish this method with real data.
async _fetchMissingOneImplemenationCounts() {
const browserReleaseFeatureMetric: BrowserReleaseFeatureMetric = {
count: 8,
timestamp: new Date(Date.now() - 200 * 24 * 60 * 60 * 1000).toISOString(),
};
const browserReleaseFeatureMetric1: BrowserReleaseFeatureMetric = {
count: 5,
timestamp: new Date(Date.now() - 150 * 24 * 60 * 60 * 1000).toISOString(),
};
let count: number = 0;
ALL_BROWSERS.map(browser => {
browserReleaseFeatureMetric.count = 8 + count;
browserReleaseFeatureMetric1.count = 5 + count;
count++;
this.missingOneImplementationMap.set(statsDataKey(browser), [
{...browserReleaseFeatureMetric},
{...browserReleaseFeatureMetric1},
]);
});
this.missingOneImplementationChartDataObj = this.createDisplayDataFromMap(
this.missingOneImplementationMap,
false,
);
}

constructor() {
super();

Expand All @@ -181,31 +231,37 @@ export class StatsPage extends LitElement {
startDate,
endDate,
);
await this._fetchMissingOneImplemenationCounts();
return this.globalFeatureSupport;
},
});
}

// Make a DataTable from the data in globalFeatureSupport
createGlobalFeatureSupportDataFromMap(): WebStatusDataObj {
// Get the list of browsers from globalFeatureSupport
const browsers = this.globalFeatureSupportBrowsers;
// Make a DataTable from the target data map.
// TODO(kyleju): refactor this method acorss feature detail page
// and stats page, https://github.com/GoogleChrome/webstatus.dev/issues/964.
createDisplayDataFromMap(
targetMap: Map<string, Array<BrowserReleaseFeatureMetric>>,
addMax: boolean,
): WebStatusDataObj {
// Get the list of supported browsers.
const browsers = this.supportedBrowsers;

const dataObj: WebStatusDataObj = {cols: [], rows: []};
dataObj.cols.push({type: 'date', label: 'Date', role: 'domain'});
for (const browser of browsers) {
dataObj.cols.push({type: 'number', label: browser, role: 'data'});
}
dataObj.cols.push({type: 'number', label: 'Max features', role: 'data'});
if (addMax) {
dataObj.cols.push({type: 'number', label: 'Max features', role: 'data'});
}

// Map from date to an object with counts for each browser
const dateToBrowserDataMap = new Map<number, {[key: string]: number}>();

// Merge data across all browsers into one array of rows.
for (const browser of browsers) {
const data = this.globalFeatureSupport.get(
globalFeatureSupportKey(browser),
);
const data = targetMap.get(statsDataKey(browser));
if (!data) continue;
for (const row of data) {
if (!row) continue;
Expand All @@ -218,36 +274,42 @@ export class StatsPage extends LitElement {
browserCounts[browser] = featureCount;
}
}

// Create array of dateToBrowserDataMap entries and sort by dateSeconds
const data = Array.from(dateToBrowserDataMap.entries()).sort(
([d1], [d2]) => d1 - d2,
);

// For each date, add a row to the dataTable
// Accumulate the max count across all browsers.
let max = 0;
for (const datum of data) {
const dateSeconds = datum[0];
const date = new Date(dateSeconds);
const browserCounts = datum[1];
// Make an array of browser counts, in the order of browsers.
// If the browser is not in the browserCounts, add null.
const browserCountArray = browsers.map(browser => {
if (browserCounts[browser]) {
max = Math.max(max, browserCounts[browser]);
return browserCounts[browser];
const browserCountArray = browsers.map(
browser => browserCounts[browser] || null,
);

if (addMax) {
let maxCount = 0;
for (const count of browserCountArray) {
if (count) {
maxCount = Math.max(maxCount, count);
}
}
return null;
});
dataObj.rows.push([date, ...browserCountArray, max]);
dataObj.rows.push([date, ...browserCountArray, maxCount]);
} else {
dataObj.rows.push([date, ...browserCountArray]);
}
}
return dataObj;
}

generateGlobalFeatureSupportChartOptions(): google.visualization.LineChartOptions {
generatedisplayDataChartOptions(
vAxisTitle: string,
): google.visualization.LineChartOptions {
// Compute seriesColors from selected browsers and BROWSER_ID_TO_COLOR
const selectedBrowsers = this.globalFeatureSupportBrowsers;
const selectedBrowsers = this.supportedBrowsers;
const seriesColors = [...selectedBrowsers, 'total'].map(browser => {
const browserKey = browser as keyof typeof BROWSER_ID_TO_COLOR;
return BROWSER_ID_TO_COLOR[browserKey];
Expand All @@ -264,7 +326,7 @@ export class StatsPage extends LitElement {
},
vAxis: {
minValue: 0,
title: 'Number of features supported',
title: vAxisTitle,
format: '#,###',
},
legend: {position: 'top'},
Expand Down Expand Up @@ -331,10 +393,13 @@ export class StatsPage extends LitElement {
return html`
<webstatus-gchart
id="global-feature-support-chart"
.hasMax=${true}
.containerId="${'global-feature-support-chart-container'}"
.chartType="${'LineChart'}"
.dataObj="${this.globalFeatureSupportChartDataObj}"
.options="${this.generateGlobalFeatureSupportChartOptions()}"
.options="${this.generatedisplayDataChartOptions(
'Number of features supported',
)}"
>
Loading chart...
</webstatus-gchart>
Expand All @@ -350,6 +415,32 @@ export class StatsPage extends LitElement {
});
}

renderMissingOneImplementationChartWhenComplete(): TemplateResult {
return html`
<webstatus-gchart
id="missing-one-implementation-chart"
.hasMax=${false}
.containerId="${'missing-one-implementation-chart-container'}"
.chartType="${'LineChart'}"
.dataObj="${this.missingOneImplementationChartDataObj}"
.options="${this.generatedisplayDataChartOptions(
'Number of features missing',
)}"
>
Loading chart...
</webstatus-gchart>
`;
}

renderMissingOneImplementationChart(): TemplateResult | undefined {
return this._loadingGFSTask.render({
complete: () => this.renderMissingOneImplementationChartWhenComplete(),
error: () => this.renderChartWhenError(),
initial: () => this.renderChartWhenInitial(),
pending: () => this.renderChartWhenPending(),
});
}

renderGlobalFeatureSupport(): TemplateResult {
return html`
<sl-card id="global-feature-support">
Expand All @@ -364,7 +455,7 @@ export class StatsPage extends LitElement {
id="global-feature-support-browser-selector"
multiple
stay-open-on-select
.value="${this.globalFeatureSupportBrowsers.join(' ')}"
.value="${this.supportedBrowsers.join(' ')}"
>
<sl-button slot="trigger">
<sl-icon slot="suffix" name="chevron-down"></sl-icon>
Expand Down Expand Up @@ -402,9 +493,7 @@ export class StatsPage extends LitElement {
<sl-option>Safari</sl-option>
</sl-dropdown>
</div>
<div class="under-construction" id="features-lagging-chart">
Chart goes here...
</div>
<div>${this.renderMissingOneImplementationChart()}</div>
</sl-card>
`;
}
Expand Down

0 comments on commit 3f84f0f

Please sign in to comment.