Skip to content

Commit 9baebcd

Browse files
committed
use quickcharts
1 parent 65a1d14 commit 9baebcd

File tree

11 files changed

+451
-563
lines changed

11 files changed

+451
-563
lines changed

β€ŽREADME.mdβ€Ž

Lines changed: 199 additions & 497 deletions
Large diffs are not rendered by default.

β€Žpackages/bench/benchmark-all/benchmark-artifacts.tsβ€Ž

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
import _task, { type Task } from 'tasuku';
22
import type { ArtifactLoaded } from '@minification-benchmarks/artifacts';
3-
import byteSize from 'byte-size';
43
import { benchmarkMinifiers } from './benchmark-minifiers.js';
54
import type { MinifierInstance } from './types.js';
65

6+
const byteFormatter = new Intl.NumberFormat('en', {
7+
notation: 'compact',
8+
compactDisplay: 'short',
9+
minimumFractionDigits: 2,
10+
maximumFractionDigits: 2,
11+
});
12+
713
export const benchmarkArtifacts = async (
814
artifacts: ArtifactLoaded[],
915
minifiers: MinifierInstance[],
@@ -15,7 +21,7 @@ export const benchmarkArtifacts = async (
1521
artifact => task(
1622
artifact.name,
1723
async ({ task, setOutput }) => {
18-
setOutput(byteSize(artifact.size).toString());
24+
setOutput(byteFormatter.format(artifact.size).toString());
1925

2026
const benchmarkResults = await benchmarkMinifiers(
2127
artifact,

β€Žpackages/bench/package.jsonβ€Ž

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
"@minification-benchmarks/data": "*",
88
"@minification-benchmarks/minifiers": "*",
99
"@minification-benchmarks/utils": "*",
10-
"byte-size": "^8.1.1",
1110
"cleye": "^1.3.2",
1211
"execa": "^6.1.0",
1312
"fs-require": "^1.6.0",

β€Žpackages/data/package.jsonβ€Ž

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,14 @@
77
"@minification-benchmarks/bench": "*",
88
"@minification-benchmarks/minifiers": "*",
99
"@minification-benchmarks/utils": "*",
10-
"byte-size": "^8.1.1",
10+
"chartjs-plugin-datalabels": "^2.2.0",
1111
"comment-mark": "^2.0.1",
1212
"date-fns": "^4.1.0",
1313
"lodash-es": "^4.17.21",
1414
"markdown-table": "^3.0.3",
1515
"openai": "^4.87.3",
16-
"outdent": "^0.8.0"
16+
"outdent": "^0.8.0",
17+
"quickchart-js": "^3.1.3"
1718
},
1819
"devDependencies": {
1920
"@types/byte-size": "^8.1.0",

β€Žpackages/data/update-readme/analyzed-data.tsβ€Ž

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { minBy } from 'lodash-es';
2-
import type { BenchmarkResultSuccessWithRuns } from '@minification-benchmarks/bench/types.js';
2+
import type { BenchmarkResultSuccessWithRuns, BenchmarkResultWithRuns } from '@minification-benchmarks/bench/types.js';
33
import { data } from '../data/index.js';
44
import type { Artifact, Minifier } from '../types.js';
55

@@ -8,12 +8,14 @@ export type BestMinifier = [
88
Minifier<BenchmarkResultSuccessWithRuns>
99
];
1010

11-
export type MinifierWithScore = {
11+
export type MinifierWithScore<T = BenchmarkResultWithRuns> = {
1212
minifierName: string;
13-
minifier: Minifier;
13+
minifier: Minifier<T>;
1414
score: number;
1515
};
1616

17+
export const isSuccessful = (m: MinifierWithScore): m is MinifierWithScore<BenchmarkResultSuccessWithRuns> => 'data' in m.minifier.result;
18+
1719
export type ScoredMinifiers = {
1820
minifiedWithScores: MinifierWithScore[];
1921
bestMinified?: BestMinifier;
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
import QuickChart from 'quickchart-js';
2+
import type { Context } from 'chartjs-plugin-datalabels';
3+
import type { Artifact } from '../types.js';
4+
import { type MinifierWithScore, isSuccessful } from './analyzed-data.js';
5+
6+
/* eslint-disable no-bitwise */
7+
const hash32 = (string_: string) => {
8+
let h = 2_166_136_261 >>> 0;
9+
for (let i = 0; i < string_.length;) {
10+
const cp = string_.codePointAt(i)!;
11+
h ^= cp;
12+
h = Math.imul(h, 16_777_619);
13+
i += cp > 0xFF_FF ? 2 : 1;
14+
}
15+
return h >>> 0;
16+
};
17+
18+
const getColorFromName = (name: string, opacity = 1) => {
19+
const GOLDEN_ANGLE = 137.507_764_050_037_85;
20+
const hv = hash32(name);
21+
22+
const h = (hv * GOLDEN_ANGLE) % 360;
23+
24+
const sJitter = ((hv >>> 8) & 0xFF) / 255;
25+
const s = 75 + (sJitter - 0.5) * 15; // ~55–70%
26+
27+
const lJitter = ((hv >>> 16) & 0xFF) / 255;
28+
const l = 50 + (lJitter - 0.5) * 15; // ~60–75%
29+
30+
return `hsla(${h.toFixed(0)}, ${s.toFixed(0)}%, ${l.toFixed(0)}%, ${opacity})`;
31+
};
32+
/* eslint-enable no-bitwise */
33+
34+
const byteFormatter = (n: number | string) => `${Intl.NumberFormat('en', {
35+
notation: 'compact',
36+
compactDisplay: 'short',
37+
}).format(Number(n))}B`;
38+
39+
const percentFormatter = (
40+
value: number,
41+
context: Context,
42+
) => {
43+
const base = Number(context.dataset.data[0] ?? 0);
44+
if (!base || value === base) { return ''; }
45+
return `${Math.round((value / base) * 100)}%`;
46+
};
47+
48+
const lightMode = {
49+
text: '#333',
50+
grid: '#33333340',
51+
} as const;
52+
53+
const darkMode = {
54+
text: '#f0f6fc',
55+
grid: '#f0f6fc1a',
56+
} as const;
57+
58+
export const getBarChartUrl = (
59+
name: string,
60+
artifact: Artifact,
61+
minifiedWithScores: MinifierWithScore[],
62+
isDarkMode?: boolean,
63+
) => {
64+
const colors = isDarkMode ? darkMode : lightMode;
65+
const successfulMinifiers = minifiedWithScores.filter(isSuccessful);
66+
const labels = successfulMinifiers.map(m => m.minifierName);
67+
const minzippedData = successfulMinifiers.map(m => m.minifier.result.data.minzippedBytes);
68+
69+
const myChart = new QuickChart();
70+
myChart.setFormat('svg');
71+
myChart.setWidth(720);
72+
myChart.setHeight(400);
73+
myChart.setBackgroundColor('transparent');
74+
myChart.setVersion('4');
75+
myChart.setConfig({
76+
type: 'bar',
77+
data: {
78+
labels: ['Original', ...labels],
79+
datasets: [
80+
{
81+
label: 'Gzipped Size',
82+
data: [artifact.gzipSize, ...minzippedData],
83+
backgroundColor: [
84+
'rgba(150, 150, 150, 0.7)',
85+
...labels.map(lbl => getColorFromName(lbl, 0.7)),
86+
],
87+
borderColor: [
88+
'rgb(150, 150, 150)',
89+
...labels.map(lbl => getColorFromName(lbl)),
90+
],
91+
borderWidth: 1,
92+
},
93+
],
94+
},
95+
options: {
96+
scales: {
97+
x: {
98+
ticks: {
99+
color: colors.text,
100+
maxRotation: 45,
101+
minRotation: 45,
102+
},
103+
grid: {
104+
display: false,
105+
},
106+
},
107+
y: {
108+
min: 0,
109+
ticks: {
110+
color: colors.text,
111+
callback: byteFormatter,
112+
font: {
113+
size: 10,
114+
},
115+
},
116+
title: {
117+
display: true,
118+
text: 'Gzipped Size',
119+
color: colors.text,
120+
font: {
121+
size: 14,
122+
},
123+
},
124+
grid: {
125+
color: colors.grid,
126+
},
127+
},
128+
},
129+
plugins: {
130+
title: {
131+
display: true,
132+
text: `${name} v${artifact.version}`,
133+
font: {
134+
size: 20,
135+
},
136+
color: colors.text,
137+
},
138+
legend: {
139+
display: false,
140+
},
141+
datalabels: {
142+
labels: {
143+
bytes: {
144+
anchor: 'end',
145+
align: 'top',
146+
formatter: byteFormatter,
147+
color: colors.text,
148+
font: { size: 11 },
149+
},
150+
percent: {
151+
anchor: 'center',
152+
align: 'center',
153+
formatter: percentFormatter,
154+
color: '#fff',
155+
font: {
156+
size: 11,
157+
weight: 'bold',
158+
},
159+
clamp: true,
160+
},
161+
},
162+
},
163+
},
164+
},
165+
});
166+
167+
return myChart.getUrl();
168+
};

β€Žpackages/data/update-readme/formatting.tsβ€Ž

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,14 @@ export const formatMs = (
22
ms: number,
33
) => `${ms.toLocaleString(undefined, { maximumFractionDigits: 0 })} ms`;
44

5+
const roundTo = (value: number, place = 0) => {
6+
const factor = 10 ** place;
7+
return Math.round(value * factor) / factor;
8+
};
9+
510
export const percent = (
611
from: number,
712
to: number,
8-
) => ((to - from) / from).toLocaleString(undefined, {
13+
) => roundTo((to - from) / from, 2).toLocaleString(undefined, {
914
style: 'percent',
1015
});

β€Žpackages/data/update-readme/index.tsβ€Ž

Lines changed: 9 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
11
import fs from 'fs/promises';
22
import path from 'path';
3-
import { commentMark } from 'comment-mark';
43
import outdent from 'outdent';
4+
import { commentMark } from 'comment-mark';
55
import { format } from 'date-fns';
66
import { markdownTable } from 'markdown-table';
77
import { capitalize } from 'lodash-es';
88
import type { BenchmarkResultSuccessWithRuns } from '@minification-benchmarks/bench/types.js';
99
import { minifiersDirectory } from '@minification-benchmarks/minifiers/utils/minifiers-directory.js';
1010
import { getMinifiers } from '@minification-benchmarks/minifiers';
1111
import { byteSize } from '../utils/byte-size.js';
12-
import type { Artifact } from '../types.js';
1312
import { percent, formatMs } from './formatting.js';
1413
import * as mdu from './mdu.js';
1514
import { getAiAnalysis } from './ai-analysis/index.js';
1615
import {
17-
getAnalyzedData, type AnalyzedData, type AnalyzedArtifact, type MinifierWithScore,
16+
getAnalyzedData, type AnalyzedData, type AnalyzedArtifact,
1817
} from './analyzed-data.js';
18+
import { getBarChartUrl } from './bar-chart.js';
1919

2020
const displayColumn = (
2121
text: string,
@@ -116,50 +116,17 @@ const generateBenchmarkTable = (
116116
},
117117
);
118118

119-
const generateMermaidGraph = (
120-
name: string,
121-
artifact: Artifact,
122-
minifiedWithScores: MinifierWithScore[],
123-
) => {
124-
const minifiers = minifiedWithScores
125-
.map(({ minifier }) => {
126-
const { result } = minifier;
127-
if ('error' in result) {
128-
return;
129-
}
130-
return result.data.minzippedBytes;
131-
})
132-
.filter(Boolean);
133-
134-
return mdu.mermaid(outdent`
135-
---
136-
config:
137-
xyChart:
138-
width: 720
139-
height: 360
140-
xAxis:
141-
labelPadding: 20
142-
yAxis:
143-
labelPadding: 10
144-
---
145-
xychart-beta
146-
title ${JSON.stringify(`${name} v${artifact.version}`)}
147-
x-axis ${
148-
JSON.stringify(['Original', ...minifiers.map((_, index) => index + 1)])
149-
}
150-
y-axis "Gzip size" 0 --> ${artifact.gzipSize}
151-
bar ${
152-
JSON.stringify([artifact.gzipSize, ...minifiers])
153-
}
154-
`);
155-
};
156-
157119
const generateBenchmarks = (
158120
analyzedData: AnalyzedData,
159121
) => analyzedData
160122
.map(
161123
([name, artifact]) => [
162-
generateMermaidGraph(name, artifact, artifact.minifiedWithScores),
124+
outdent`
125+
<picture>
126+
<source media="(prefers-color-scheme: dark)" srcset="${getBarChartUrl(name, artifact, artifact.minifiedWithScores, true)}">
127+
<img src="${getBarChartUrl(name, artifact, artifact.minifiedWithScores)}">
128+
</picture>
129+
`,
163130
mdu.div(
164131
generateBenchmarkTable(name, artifact),
165132
{ align: 'center' },

β€Žpackages/data/update-readme/mdu.tsβ€Ž

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,4 @@ export const sub = (content: string, attributes?: Attributes) => `<sub${stringif
2020
export const strong = (content: string) => `**${content}**`;
2121
export const emphasize = (content: string) => `*${content}*`;
2222
export const link = (label: string, href: string) => `[${label}](${href})`;
23-
export const mermaid = (code: string) => `\`\`\`mermaid\n${code}\n\`\`\``;
2423
export const div = (content: string, attributes?: Attributes) => `<div${stringifyAttributes(attributes)}>\n\n${content}\n</div>`;
Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1-
import byteSize from 'byte-size';
2-
3-
byteSize.defaultOptions({ precision: 2 });
1+
const formatter = new Intl.NumberFormat('en', {
2+
notation: 'compact',
3+
compactDisplay: 'short',
4+
minimumFractionDigits: 2,
5+
maximumFractionDigits: 2,
6+
});
7+
const byteSize = (bytes: number) => `${formatter.format(bytes).replace(/(\D+)$/, ' $1')}B`;
48

59
export { byteSize };

0 commit comments

Comments
Β (0)