Skip to content

Commit

Permalink
feat(examples): add package-stats charting example
Browse files Browse the repository at this point in the history
  • Loading branch information
postspectacular committed Jan 10, 2019
1 parent 27438f9 commit bac59db
Show file tree
Hide file tree
Showing 8 changed files with 358 additions and 0 deletions.
6 changes: 6 additions & 0 deletions examples/package-stats/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.cache
out
node_modules
yarn.lock
*.js
*.svg
26 changes: 26 additions & 0 deletions examples/package-stats/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# package-stats

This non-browser example generates several SVG charts of various package
stats in this mono-repo. The charts will be saved in this example's
directory.

```bash
git clone https://github.com/thi-ng/umbrella.git

# first need to build the entire mono-repo
# to produce necessary meta data
yarn install
yarn build

# then run example
cd umbrella/examples/module-stats
yarn build
```

## Authors

- Karsten Schmidt

## License

© 2018 Karsten Schmidt // Apache Software License 2.0
32 changes: 32 additions & 0 deletions examples/package-stats/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"name": "package-stats",
"version": "0.0.1",
"repository": "https://github.com/thi-ng/umbrella",
"author": "Karsten Schmidt <k+npm@thi.ng>",
"license": "Apache-2.0",
"scripts": {
"clean": "rm -rf *.js *.svg lib",
"build": "yarn clean && tsc && node lib/index.js"
},
"devDependencies": {
"parcel-bundler": "^1.10.3",
"terser": "^3.11.0",
"typescript": "^3.2.2"
},
"dependencies": {
"@thi.ng/checks": "latest",
"@thi.ng/dgraph": "latest",
"@thi.ng/hiccup": "latest",
"@thi.ng/hiccup-svg": "latest",
"@thi.ng/math": "latest",
"@thi.ng/path": "latest",
"@thi.ng/strings": "latest",
"@thi.ng/transducers": "latest"
},
"browserslist": [
"last 3 Chrome versions"
],
"browser": {
"process": false
}
}
91 changes: 91 additions & 0 deletions examples/package-stats/src/dep-chart.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { exists } from "@thi.ng/checks";
import { DGraph } from "@thi.ng/dgraph";
import { serialize } from "@thi.ng/hiccup";
import { group, text } from "@thi.ng/hiccup-svg";
import {
comp,
filter,
map,
mapcat,
mapIndexed,
max,
pluck,
push,
reducer,
repeat,
transduce,
tuples
} from "@thi.ng/transducers";
import * as fs from "fs";
import {
barChart,
labeledTickY,
labeledTickX,
} from "./viz";

const BASE_DIR = "../../packages/";

const packages = transduce(
comp(
map((f) => BASE_DIR + f),
filter((f) => fs.statSync(f).isDirectory()),
map((f) => { try { return fs.readFileSync(f + "/package.json"); } catch (_) { } }),
filter(exists),
map((p) => JSON.parse(p.toString())),
map((p) => ({
id: p.name,
v: p.version,
deps: p.dependencies ? Object.keys(p.dependencies) : []
}))
),
push(),
fs.readdirSync(BASE_DIR)
);

const graph = transduce(
mapcat((p: any) => tuples(repeat(p.id), p.deps)),
reducer(() => new DGraph(), (g, [p, d]) => g.addDependency(p, d)),
packages
);

const packageDeps = packages
.map((p) => [p.id, graph.transitiveDependents(p.id).size])
.sort((a, b) => b[1] - a[1]);

const maxDeps = transduce(pluck(1), max(), packageDeps);

fs.writeFileSync(
`package-deps.svg`,
serialize(
[barChart,
{
attribs: {
width: 1024,
height: 260,
"font-size": "10px",
"font-family": "Iosevka-Term-Light, Menlo, sans-serif"
},
x: {
axis: [80, 1010, 170],
domain: [0, packageDeps.length, 1],
range: [80, 1020],
ticks: [...map((x) => x[0].substr(8), packageDeps)],
label: labeledTickX
},
y: {
axis: [170, 10, 65],
domain: [0, maxDeps, 10],
range: [160, 20],
label: labeledTickY(1010)
},
axis: "#666",
fill: "#0cc"
},
mapIndexed((i, m) => [i, m[1]], packageDeps),
group({ "font-size": "20px", "text-anchor": "middle" },
text([592, 28], "@thi.ng/umbrella internal re-use"),
text([592, 56], "(transitive dependents)"),
)
]
)
);
2 changes: 2 additions & 0 deletions examples/package-stats/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import "./dep-chart";
import "./size-chart";
81 changes: 81 additions & 0 deletions examples/package-stats/src/size-chart.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { serialize } from "@thi.ng/hiccup";
import { group, text } from "@thi.ng/hiccup-svg";
import { getter } from "@thi.ng/paths";
import { bytes } from "@thi.ng/strings";
import {
comp,
filter,
map,
mapcat,
mapIndexed,
max,
push,
transduce
} from "@thi.ng/transducers";
import * as fs from "fs";
import { barChart, labeledTickX, labeledTickY } from "./viz";

const BASE_DIR = "../../packages/";

const meta = transduce(
comp(
map((m: string) => [m, BASE_DIR + m + "/.meta/size.json"]),
filter(([_, path]) => fs.existsSync(path)),
map(([m, path]) => [m, JSON.parse(fs.readFileSync(path).toString())])
),
push(),
fs.readdirSync(BASE_DIR)
);

const fileSizeChart =
(stats, modType, type) => {

const get = getter([1, modType, type]);
stats = [...stats].sort((a, b) => get(b) - get(a));

const maxSize = transduce(
mapcat(([_, m]) => [m.esm[type], m.cjs[type], m.umd[type]]),
max(),
stats
);

fs.writeFileSync(
`package-sizes-${modType}.svg`,
serialize(
[barChart,
{
attribs: {
width: 1024,
height: 260,
"font-size": "10px",
"font-family": "Iosevka-Term-Light, Menlo, sans-serif"
},
x: {
axis: [80, 1010, 170],
domain: [0, stats.length, 1],
range: [80, 1020],
ticks: [...map((x) => x[0], stats)],
label: labeledTickX
},
y: {
axis: [170, 10, 65],
domain: [0, maxSize, 5 * 1024],
range: [160, 20],
label: labeledTickY(1010, bytes)
},
axis: "#666",
fill: "#0cc"
},
mapIndexed((i, m) => [i, get(m)], stats),
group({ "font-size": "20px", "text-anchor": "middle" },
text([592, 28], `@thi.ng/umbrella package sizes (${modType.toUpperCase()})`),
text([592, 56], `(minified + gzipped)`),
)
]
)
);
};

fileSizeChart(meta, "esm", "gzip");
fileSizeChart(meta, "cjs", "gzip");
fileSizeChart(meta, "umd", "gzip");
107 changes: 107 additions & 0 deletions examples/package-stats/src/viz.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { group, line, svg, text, rect } from "@thi.ng/hiccup-svg";
import { fit, fit01 } from "@thi.ng/math";
import { map, mapcat, range, normRange, mapIndexed } from "@thi.ng/transducers";

// iterator of range mapped tuples: `[mapped, orig]`
const mappedRange =
(from: number, to: number, step: number, start: number, end: number) =>
map(
(n) => [fit(n, from, to, start, end), n],
range(from, to, step)
);

const mappedTicks =
(start: number, end: number, ticks: any[]) =>
mapIndexed(
(i, x) => [fit01(i / ticks.length, start, end), x],
0,
ticks
);

// reusuable axis tick & label combo
export const tick =
(x1: number, y1: number, x2: number, y2: number, tx: number, ty: number, label: any) => [
line([x1, y1], [x2, y2]),
text([tx, ty], label, { stroke: "none" })
];

// mapping fn for x-axis ticks
const tickX =
(y: number) =>
([x, n]: [number, any]) =>
tick(x, y, x, y + 10, x, y + 20, n);

// mapping fn for y-axis ticks
const tickY =
(x: number) =>
([y, n]: [number, any]) =>
tick(x - 10, y, x, y, x - 15, y, n);

export const labeledTickX =
(y) =>
([x, n]: any[]) => [
line([x, y], [x, y + 5]),
text([x, y + 15], n, {
stroke: "none",
"text-anchor": "end",
transform: `rotate(-45 ${x} ${y + 15})`
})
];

export const labeledTickY =
(width, fmt = (x) => String(x)) =>
(x) =>
([y, n]: [number, any]) => [
...tick(x - 5, y, x, y, x - 10, y + 4, n > 0 ? fmt(n) : 0),
n > 0 ?
line([x + 20, y], [width, y], { "stroke-dasharray": "1 3" }) :
null
];

// x-axis with ticks as SVG group
const axisX =
({ axis: a, domain: d, range: r, label, ticks }) =>
["g", { "text-anchor": "middle" },
line([a[0], a[2]], [a[1], a[2]]),
mapcat(
(label || tickX)(a[2]),
ticks ?
mappedTicks(r[0], r[1], ticks) :
mappedRange(d[0], d[1], d[2], r[0], r[1])
)];

// y-axis with ticks as SVG group
const axisY =
({ axis: a, domain: d, range: r, label, ticks }) =>
["g", { "text-anchor": "end" },
line([a[2], a[0]], [a[2], a[1]]),
mapcat(
(label || tickY)(a[2]),
ticks ?
mappedTicks(r[0], r[1], ticks) :
mappedRange(d[0], d[1], d[2], r[0], r[1])
)];

// mapping fn to create a single bar from `[domainPos, value]`
const bar =
({ domain: xd, range: xr }, { domain: yd, range: yr }) =>
([xx, yy]) => {
const y = fit(yy, yd[0], yd[1], yr[0], yr[1]);
return rect(
[fit(xx, xd[0], xd[1], xr[0], xr[1]) - 5, y],
10, yr[0] - y
);
};

// complete bar chart component
export const barChart =
(_, opts, values, ...xs) =>
svg(
opts.attribs,
group({ stroke: opts.axis, fill: opts.axis },
axisX(opts.x),
axisY(opts.y)),
group({ fill: opts.fill },
map(bar(opts.x, opts.y), values)),
...xs
);
13 changes: 13 additions & 0 deletions examples/package-stats/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "lib",
"module": "commonjs",
"target": "es6",
"noUnusedLocals": false,
"noUnusedParameters": false,
},
"include": [
"./src/**/*.ts"
]
}

0 comments on commit bac59db

Please sign in to comment.