Skip to content

Commit

Permalink
feat: added data export to stats tables
Browse files Browse the repository at this point in the history
  • Loading branch information
Quacken8 committed Aug 11, 2024
1 parent 4cbbdab commit 5c1d331
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 29 deletions.
22 changes: 20 additions & 2 deletions src/App.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
faArrowLeft,
faChartLine,
} from "@fortawesome/free-solid-svg-icons";
import { IEC } from "unitlib/systems";
import { IEC, SI } from "unitlib/systems";
import {
ChartComponent as Chart,
NumericDateRepresentation,
StatsTable,
TraceList,
} from "./lib/mod.js";
Expand Down Expand Up @@ -106,7 +107,24 @@
let wrapDiv: HTMLDivElement;
let commonXRuler$ = mut();
const table = StatsTable.mergeByVariant(
StatsTable.fromSingleStat({
statTitle: "Megabytes imagery",
dataUnit: IEC.parseUnit("MiB"),
data: [1000, 2000, 3000],
ids: ["a", "b", "c"],
displayUnit: IEC.parseUnit("GiB"),
}),
StatsTable.fromSingleStat({
statTitle: "Centimeters of flesh",
dataUnit: SI.parseUnit("cm"),
data: [22, 33, 44],
ids: ["d", "b", "c"],
displayUnit: SI.parseUnit("m"),
}),
);
$: (window as any).table = table;
</script>

<main>
Expand Down
145 changes: 123 additions & 22 deletions src/lib/data/trace-export.ts → src/lib/data/data-export.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import type { TraceList } from "../mod.js";
import type { DataUnit, StatsTable, TraceList } from "../mod.js";
import { type ChartRange, type VariantHandle } from "../types.js";
import { toNumeric } from "../units/mod.js";
import { toNumeric, unitOf } from "../units/mod.js";
import { variantIds } from "./variant-ids.js";
import { PARAMS } from "./trace-list.js";
import type { Bundle } from "./bundle.js";
import { Queue, assertNever } from "@typek/typek";
import { Queue, assertNever, concat } from "@typek/typek";

export type ExportRow = {
export type TraceExportRow = {
x: number;
y: { [traceId: string]: number };
};
Expand All @@ -28,11 +28,11 @@ export class TraceListExport {
private readonly options: TraceListExportOptions,
) {}

[Symbol.iterator](): IterableIterator<ExportRow> {
[Symbol.iterator](): IterableIterator<TraceExportRow> {
return this.rows();
}

*rows(this: TraceListExport): IterableIterator<ExportRow> {
*rows(this: TraceListExport): IterableIterator<TraceExportRow> {
const { traces } = this;
const range = this.options.range ?? traces.range;
const linesPerBuffer = 1000;
Expand All @@ -49,8 +49,8 @@ export class TraceListExport {
new Map(),
);
// fill up queues for all buffers
const queues: Map<Bundle, Queue<ExportRow>> = new Map(
unfinishedBundles.map((bundle) => [bundle, new Queue<ExportRow>()]),
const queues: Map<Bundle, Queue<TraceExportRow>> = new Map(
unfinishedBundles.map((bundle) => [bundle, new Queue<TraceExportRow>()]),
);
const currentBufferLength: Map<Bundle, number> = new Map();
const fillUpQueue = (bundle: Bundle, from: number) => {
Expand All @@ -63,7 +63,7 @@ export class TraceListExport {
currentBufferLength.set(bundle, length);

const queue = queues.get(bundle)!;
let row: ExportRow = { x: buffer[0], y: {} };
let row: TraceExportRow = { x: buffer[0], y: {} };
for (const [i, el] of buffer.entries()) {
if (i >= length) break;

Expand Down Expand Up @@ -91,23 +91,23 @@ export class TraceListExport {
(b) => queues.get(b)!.size !== 0,
);

const lastLines: Map<Bundle, ExportRow> = new Map(
const lastLines: Map<Bundle, TraceExportRow> = new Map(
unfinishedBundles
.map((b) => [b, queues.get(b)!.peek()] as const)
.filter((_, q) => q !== undefined) as [Bundle, ExportRow][],
.filter((_, q) => q !== undefined) as [Bundle, TraceExportRow][],
);

// TODO implement different interpolation strategies
const getOrInterpolate = (
x: number,
bundle: Bundle,
lastLine: ExportRow,
lastLine: TraceExportRow,
) => {
const thisQueue = queues.get(bundle)!;
const nextLine = thisQueue.peek()!;
if (x === nextLine.x) return thisQueue.dequeue()!;
const fraction = (x - lastLine.x) / (nextLine.x - lastLine.x);
const toReturn: ExportRow = { x: x, y: {} };
const toReturn: TraceExportRow = { x: x, y: {} };
for (const id of Object.keys(lastLine)) {
toReturn.y[id] =
fraction * (nextLine.y[id] - lastLine.y[id]) + lastLine.y[id];
Expand All @@ -117,7 +117,7 @@ export class TraceListExport {

let lastX = Number.NEGATIVE_INFINITY;
while (unfinishedBundles.length > 0) {
const toWrite: ExportRow[] = [];
const toWrite: TraceExportRow[] = [];
const xs = Array.from(
new Set(
unfinishedBundles.map((b) =>
Expand All @@ -139,7 +139,7 @@ export class TraceListExport {
);
if (xs[0] >= rangeToInLatest || xs.length === 0) break;
for (const x of xs) {
let row: ExportRow = { x: x, y: {} };
let row: TraceExportRow = { x: x, y: {} };
for (const bundle of unfinishedBundles) {
if (queues.get(bundle)!.peek() === undefined) {
continue;
Expand Down Expand Up @@ -168,11 +168,11 @@ export class TraceListExport {
csv(
this: TraceListExport,
{ valueOnMissingData }: { valueOnMissingData?: string } = {},
): TraceListExportTextFile {
): ExportTextFile {
valueOnMissingData ??= "";
const self = this;

return new TraceListExportTextFile("export.csv", function* () {
return new ExportTextFile("export.csv", function* () {
const ids = Array.from(self.traces.traces());
yield `timestamp,${ids.join(",")}\n`;

Expand All @@ -183,20 +183,122 @@ export class TraceListExport {
}
}

type TableExportRow = {
rowKey: "variantId" | "statTitle";
rowTitle: string;
values: { [columnTitle: string]: number | undefined };
};

export interface StatsTableExportOptions {
valueOnMissingData?: string;
orientation?: "oneStatPerRow" | "oneVariantPerRow";
}

export class StatsTableExport {
constructor(
public readonly statsTable: StatsTable,
private readonly options: StatsTableExportOptions,
) {}

[Symbol.iterator](): IterableIterator<TableExportRow> {
return this.rows();
}
#commonUnitsOfStat: Map<string, DataUnit> = new Map();

*rows(this: StatsTableExport): IterableIterator<TableExportRow> {
const { statsTable } = this;
const orientation = this.options.orientation ?? "oneVariantPerRow";

switch (orientation) {
case "oneStatPerRow":
for (const { statTitle, variants } of statsTable.statEntries()) {
const unit = this.#commonUnitsOfStat.get(statTitle);

yield {
rowKey: "statTitle",
rowTitle: `${statTitle}${unit ? ` [${unit}]` : ""}`,
values: Object.fromEntries(
variants.map(({ variantId, value }) => [
variantId,
value ? toNumeric(value, unit) : undefined,
]),
),
};
}
break;
case "oneVariantPerRow":
for (const { variantId, stats } of statsTable.variantEntries()) {
yield {
rowKey: "variantId",
rowTitle: variantId,
values: Object.fromEntries(
stats.map(({ statTitle, value }) => {
const unit = this.#commonUnitsOfStat.get(statTitle);
return [statTitle, value ? toNumeric(value, unit) : undefined];
}),
),
};
}
break;
default:
assertNever(orientation);
}
}

csv(this: StatsTableExport): ExportTextFile {
const self = this;
return new ExportTextFile("export.csv", function* () {
for (const stat of self.statsTable.statEntries()) {
self.#commonUnitsOfStat.set(
stat.statTitle,
unitOf(stat.variants.find((v) => v.value !== undefined)?.value ?? 0),
);
}
const unitString = (id: string) => {
return `${self.#commonUnitsOfStat.has(id) ? ` [${self.#commonUnitsOfStat.get(id)}]` : ""}`;
};
const rows = self.rows();
const firstRow = rows.next();
if (firstRow.done) return;
const ids = Object.keys(firstRow.value.values);

if (firstRow.value.rowKey === "statTitle")
yield `${firstRow.value.rowKey},${ids.join(",")}\n`;
else
yield `${firstRow.value.rowKey},${ids.map((id) => `${id}${unitString(id)}`).join(",")}\n`;

for (const row of concat(
(function* () {
yield firstRow.value;
})(),
rows,
)) {
const rowUnits =
row.rowKey === "statTitle" ? unitString(row.rowTitle) : "";
yield `${row.rowTitle}${rowUnits},${ids.map((id) => row.values[id] ?? self.options.valueOnMissingData ?? "").join(",")}\n`;
}
});
}
}

export interface ExportDownloadOptions {
fileName?: string;
method?: "anchor" | "fs-api";
}

export class TraceListExportTextFile {
export class ExportTextFile {
constructor(
public readonly defaultFileName: string,
public readonly lines: () => Iterable<string>,
public readonly lines: () => IterableIterator<string>,
) {}

[Symbol.iterator](): IterableIterator<string> {
return this.lines();
}

async download(
this: TraceListExportTextFile,
{ fileName, method }: ExportDownloadOptions,
this: ExportTextFile,
{ fileName, method }: ExportDownloadOptions = {},
): Promise<void> {
fileName ??= this.defaultFileName;
method ??= "anchor";
Expand Down Expand Up @@ -232,7 +334,6 @@ async function downloadViaFileSystemApi(
const writer = (await fileHandle.createWritable()).getWriter();

for (const line of lines) {
console.log(line);
await writer.ready;
await writer.write(line);
}
Expand Down
2 changes: 1 addition & 1 deletion src/lib/data/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@ export type {
TracePointsStyle,
PaletteName,
} from "./trace-styles.js";
export type * from "./trace-export.js";
export type * from "./data-export.js";
11 changes: 11 additions & 0 deletions src/lib/data/stats-table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ import {
yeet,
zip,
} from "@typek/typek";
import {
StatsTableExport,
type StatsTableExportOptions,
} from "./data-export.js";

export interface Stat {
title: string;
Expand Down Expand Up @@ -400,4 +404,11 @@ export class StatsTable {
}),
});
}

/**
* Creates an iterator that goes over all the available stat data.
*/
exportData(opts: StatsTableExportOptions = {}): StatsTableExport {
return new StatsTableExport(this, opts);
}
}
5 changes: 1 addition & 4 deletions src/lib/data/trace-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,7 @@ import {
unitConversionFactor,
unitOf,
} from "../units/mod.js";
import {
TraceListExport,
type TraceListExportOptions,
} from "./trace-export.js";
import { TraceListExport, type TraceListExportOptions } from "./data-export.js";
import { Bundle } from "./bundle.js";
import { resolvedColorToHex } from "../utils/color.js";
import { hashAny } from "../utils/hash.js";
Expand Down

0 comments on commit 5c1d331

Please sign in to comment.