Skip to content

Commit 7cf21a6

Browse files
committed
Adding methods writeChart and writeMap
1 parent bae8160 commit 7cf21a6

File tree

5 files changed

+191
-1
lines changed

5 files changed

+191
-1
lines changed

deno.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"imports": {
2424
"@duckdb/duckdb-wasm": "npm:@duckdb/[email protected]",
2525
"@nshiab/journalism": "jsr:@nshiab/journalism@^1.21.1",
26+
"@observablehq/plot": "npm:@observablehq/plot@^0.6.16",
2627
"@std/assert": "jsr:@std/[email protected]",
2728
"apache-arrow": "npm:[email protected]",
2829
"duckdb": "npm:[email protected]"
@@ -32,5 +33,11 @@
3233
".sda-cache",
3334
"test/output"
3435
]
36+
},
37+
"compilerOptions": {
38+
"lib": [
39+
"dom",
40+
"deno.ns"
41+
]
3542
}
3643
}

deno.lock

Lines changed: 3 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/class/SimpleTable.ts

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,14 @@ import {
3232
logDotChart,
3333
logLineChart,
3434
rewind,
35+
saveChart,
3536
} from "jsr:@nshiab/journalism@1";
3637
import writeDataAsArrays from "../helpers/writeDataAsArrays.ts";
3738
import logHistogram from "../methods/logHistogram.ts";
3839
import logData from "../helpers/logData.ts";
3940
import getProjectionParquet from "../helpers/getProjectionParquet.ts";
4041
import { readFileSync, writeFileSync } from "node:fs";
42+
import type { Data } from "npm:@observablehq/plot";
4143

4244
/**
4345
* SimpleTable is a class representing a table in a SimpleDB. It can handle tabular and geospatial data. To create one, it's best to instantiate a SimpleDB first.
@@ -1136,6 +1138,102 @@ export default class SimpleTable extends SimpleWebTable {
11361138
await logHistogram(this, values, options);
11371139
}
11381140

1141+
/**
1142+
* Creates an [Observable Plot](https://github.com/observablehq/plot) chart as an image file (.png or .jpeg) from the table data.
1143+
*
1144+
* To create maps, use the writeMap method.
1145+
*
1146+
* @example
1147+
* Basic usage:
1148+
* ```ts
1149+
* import { dot, plot } from "@observablehq/plot";
1150+
* import type { Data } from "@observablehq/plot";
1151+
*
1152+
* const sdb = new SimpleDB();
1153+
* const table = sdb.newTable();
1154+
*
1155+
* const data = [{ year: 2024, value: 10 }, { year: 2025, value: 15 }]
1156+
*
1157+
* await table.loadArray(data);
1158+
*
1159+
* const chart = (data: Data) =>
1160+
* plot({
1161+
* marks: [
1162+
* dot(data, { x: "year", y: "value" }),
1163+
* ],
1164+
* });
1165+
*
1166+
* const path = "output/chart.png";
1167+
*
1168+
* await table.writeChart(chart, path);
1169+
* ```
1170+
*
1171+
* @param chart - A function that takes data and returns an Observable Plot chart.
1172+
* @param path - The path where the chart image will be saved.
1173+
*/
1174+
async writeChart(
1175+
chart: (data: Data) => SVGSVGElement | HTMLElement,
1176+
path: string,
1177+
) {
1178+
await saveChart(await this.getData(), chart, path);
1179+
}
1180+
1181+
/**
1182+
* Creates an [Observable Plot](https://github.com/observablehq/plot) map as an image file (.png or .jpeg) from the table data.
1183+
*
1184+
* To create charts, use the writeChart method.
1185+
*
1186+
* @example
1187+
* Basic usage:
1188+
* ```ts
1189+
* import { geo, plot } from "@observablehq/plot";
1190+
* import type { Data } from "@observablehq/plot";
1191+
*
1192+
* const sdb = new SimpleDB();
1193+
* const table = sdb.newTable();
1194+
*
1195+
* await table.loadGeoData(
1196+
* "./CanadianProvincesAndTerritories.geojson",
1197+
* );
1198+
*
1199+
* const map = (data: Data) =>
1200+
* plot({
1201+
* projection: {
1202+
* type: "conic-conformal",
1203+
* rotate: [100, -60],
1204+
* domain: data,
1205+
* },
1206+
* marks: [
1207+
* geo(data, { stroke: "black", fill: "lightblue" }),
1208+
* ],
1209+
* });
1210+
*
1211+
* const path = "./output/map.png";
1212+
*
1213+
* // Note the option rewind, available if needed.
1214+
* await table.writeMap(map, path, { rewind: true });
1215+
* ```
1216+
*
1217+
* @param map - A function that takes data and returns an Observable Plot map.
1218+
* @param path - The path where the map image will be saved.
1219+
* @param options - An optional object with configuration options:
1220+
* @param options.column - The name of a column storing geometries. If there is just one, it will be used by default.
1221+
* @param options.rewind - If true, rewinds the winding order to be clockwise. Default is false.
1222+
*/
1223+
async writeMap(
1224+
map: (data: Data) => SVGSVGElement | HTMLElement,
1225+
path: string,
1226+
options: { column?: string; rewind?: true } = {},
1227+
) {
1228+
await saveChart(
1229+
await this.getGeoData(options.column, {
1230+
rewind: options.rewind,
1231+
}) as unknown as Data,
1232+
map,
1233+
path,
1234+
);
1235+
}
1236+
11391237
/**
11401238
* Logs a specified number of rows. Default is 10 rows.
11411239
*

test/unit/methods/writeChart.test.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { existsSync, mkdirSync } from "node:fs";
2+
import { assertEquals } from "jsr:@std/assert";
3+
import SimpleDB from "../../../src/class/SimpleDB.ts";
4+
import { dot, plot } from "@observablehq/plot";
5+
import type { Data } from "@observablehq/plot";
6+
const output = "./test/output/";
7+
if (!existsSync(output)) {
8+
mkdirSync(output);
9+
}
10+
11+
Deno.test("should write a chart", async () => {
12+
const sdb = new SimpleDB();
13+
const table = sdb.newTable();
14+
await table.loadData("test/data/files/dailyTemperatures.csv");
15+
await table.filter(`YEAR(time) === 2020`);
16+
await table.writeChart((data: Data) =>
17+
plot({
18+
title: "My chart",
19+
color: { legend: true, type: "diverging" },
20+
facet: { data: data, y: "id" },
21+
marginRight: 100,
22+
marks: [
23+
dot(data, { x: "time", y: "t", fill: "t", facet: "auto" }),
24+
],
25+
}), output + "temp.png");
26+
// How to assert?
27+
assertEquals(true, true);
28+
await sdb.done();
29+
});
30+
31+
Deno.test("should write a chart (example from docs)", async () => {
32+
const sdb = new SimpleDB();
33+
const table = sdb.newTable();
34+
await table.loadArray([{ year: 2024, value: 10 }, { year: 2025, value: 15 }]);
35+
36+
await table.writeChart((data: Data) =>
37+
plot({
38+
marks: [
39+
dot(data, { x: "year", y: "value" }),
40+
],
41+
}), output + "example.png");
42+
// How to assert?
43+
assertEquals(true, true);
44+
await sdb.done();
45+
});

test/unit/methods/writeMap.test.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { existsSync, mkdirSync } from "node:fs";
2+
import { assertEquals } from "jsr:@std/assert";
3+
import SimpleDB from "../../../src/class/SimpleDB.ts";
4+
import { geo, plot } from "@observablehq/plot";
5+
import type { Data } from "@observablehq/plot";
6+
const output = "./test/output/";
7+
if (!existsSync(output)) {
8+
mkdirSync(output);
9+
}
10+
11+
Deno.test("should write a map", async () => {
12+
const sdb = new SimpleDB();
13+
const table = sdb.newTable();
14+
15+
await table.loadGeoData(
16+
"test/geodata/files/CanadianProvincesAndTerritories.json",
17+
);
18+
19+
const map = (data: Data) =>
20+
plot({
21+
projection: {
22+
type: "conic-conformal",
23+
rotate: [100, -60],
24+
domain: data,
25+
},
26+
marks: [
27+
geo(data, { stroke: "black", fill: "lightblue" }),
28+
],
29+
});
30+
31+
const path = output + "map.png";
32+
33+
await table.writeMap(map, path, { rewind: true });
34+
35+
// How to assert?
36+
assertEquals(true, true);
37+
await sdb.done();
38+
});

0 commit comments

Comments
 (0)