From f77a3389b03a36c487960feb2ada7c2c1067f22a Mon Sep 17 00:00:00 2001 From: pearmini Date: Fri, 22 Aug 2025 08:53:56 -0400 Subject: [PATCH 1/2] Add require example --- package.json | 1 + pnpm-lock.yaml | 8 ++++++++ src/editor.js | 24 +++++++++++++----------- src/examples.js | 38 +++++++++++++++++++++++++++++++++++--- 4 files changed, 57 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index cc2cb96..f1141cb 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "acorn": "^8.15.0", "codemirror": "^6.0.2", "d3-array": "^3.2.4", + "d3-require": "^1.3.0", "object-inspect": "^1.13.4", "react": "^19.1.1", "react-dom": "^19.1.1" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a590141..a23ee13 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -41,6 +41,9 @@ importers: d3-array: specifier: ^3.2.4 version: 3.2.4 + d3-require: + specifier: ^1.3.0 + version: 1.3.0 object-inspect: specifier: ^1.13.4 version: 1.13.4 @@ -693,6 +696,9 @@ packages: resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} engines: {node: '>=12'} + d3-require@1.3.0: + resolution: {integrity: sha512-XaNc2azaAwXhGjmCMtxlD+AowpMfLimVsAoTMpqrvb8CWoA4QqyV12mc4Ue6KSoDvfuS831tsumfhDYxGd4FGA==} + data-urls@5.0.0: resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} engines: {node: '>=18'} @@ -1805,6 +1811,8 @@ snapshots: dependencies: internmap: 2.0.3 + d3-require@1.3.0: {} + data-urls@5.0.0: dependencies: whatwg-mimetype: 4.0.0 diff --git a/src/editor.js b/src/editor.js index 8ac667e..6204aaf 100644 --- a/src/editor.js +++ b/src/editor.js @@ -6,11 +6,16 @@ import {Runtime} from "@observablehq/runtime"; import inspector from "object-inspect"; import {parse} from "acorn"; import {group} from "d3-array"; +import {require} from "d3-require"; const PREFIX = "//➜"; +const BUILTINS = { + require: () => require, +}; + function split(code) { - return parse(code, {ecmaVersion: "latest"}).body; + return parse(code, {ecmaVersion: "latest", sourceType: "module"}).body; } function uid() { @@ -31,11 +36,6 @@ function debounce(fn, delay = 0) { }; } -function formatMultiline(value) { - const lines = value.split("\n"); - return `\`${lines.map((line, i) => (i === 0 ? line : ` ${line}`)).join("\n")}\``; -} - function isMultiline(value) { const isString = typeof value === "string"; if (!isString) return false; @@ -43,9 +43,11 @@ function isMultiline(value) { return lines.length > 1; } -function inspect(value, options) { - if (isMultiline(value)) return formatMultiline(value); - return inspector(value, options); +function inspect(value, {limit = 200, ...rest} = {}) { + if (isMultiline(value)) return value; + const string = inspector(value, rest); + if (string.length > limit) return string.slice(0, limit) + "…"; + return string; } function format(value, options) { @@ -81,7 +83,7 @@ export function createEditor(container, options) { run(code); } - const runtime = new Runtime(); + const runtime = new Runtime(BUILTINS); const main = runtime.module(); const nodesByKey = new Map(); @@ -107,7 +109,7 @@ export function createEditor(container, options) { for (const node of nodes) { const start = node.start; const {values, error} = node.state; - const V = error ? {value: error} : values; + const V = error ? [{value: error}] : values; if (V.length) { const output = V.map(({value, options}) => format(value, options)).join("\n") + "\n"; dispatch.push({from: start, insert: output}); diff --git a/src/examples.js b/src/examples.js index 0cccac6..c3c5ed3 100644 --- a/src/examples.js +++ b/src/examples.js @@ -9,7 +9,7 @@ function add(a, b) { return a + b; }`; -const INSPECTORS = `doc("line1\\nline2"); +const INSPECTOR = `doc("line1\\nline2"); doc([1, 2, 3]); @@ -32,6 +32,34 @@ doc(new Map([[1, 2], [3, 4]]), {indent: 2}); doc(new Date());`; +const REQUIRE = `const d3 = await require("d3"); + +const count = 200; +const width = 50; + +const randomInt = doc(d3.randomInt(0, width)); + +const numbers = doc(d3.range(count).map(randomInt)); + +const groups = doc(d3.group(numbers, d => d)); + +const bins = doc(d3.range(width).map((_, i) => groups.has(i) ? groups.get(i).length : 0)); + +const height = doc(d3.max(bins)); + +{ + let output = ""; + for (let i = 0; i < height; i++) { + for (let j = 0; j < width; j++) { + const bin = bins[j]; + const h = bin ? bin * height / d3.max(bins) : 0; + output += h >= height - i ? "█" : " "; + } + output += i === height - 1 ? "" : "\\n"; + } + doc(output); +}`; + const MANDELBROT_SET = `const cols = 80; const rows = 30; const maxIter = 80; @@ -69,8 +97,12 @@ export const examples = [ code: ADDITION, }, { - name: "Inspectors", - code: INSPECTORS, + name: "Inspector", + code: INSPECTOR, + }, + { + name: "Require", + code: REQUIRE, }, { name: "Mandelbrot Set", From 9fd88e3e8998eef7b40b99a4ad5278bddb37d87f Mon Sep 17 00:00:00 2001 From: pearmini Date: Fri, 22 Aug 2025 08:59:56 -0400 Subject: [PATCH 2/2] Rename to random histogram --- src/App.jsx | 13 +++++++++++-- src/examples.js | 6 +++--- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index 1e1bca5..55efe3c 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -2,12 +2,21 @@ import {useMemo, useRef, useState, useEffect} from "react"; import {examples} from "./examples.js"; import {createEditor} from "./editor.js"; -export default function App() { +function getInitialValue() { const initialValue = new URL(location).searchParams.get("name"); + if (initialValue && examples.find(({name}) => name === initialValue)) { + return initialValue; + } + const name = examples[0].name; + history.pushState(null, "", `?name=${name}`); + return name; +} + +export default function App() { const containerRef = useRef(null); const editorRef = useRef(null); - const [selected, setSelected] = useState(initialValue || examples[0].name); + const [selected, setSelected] = useState(getInitialValue()); const code = useMemo(() => { return examples.find(({name}) => name === selected).code; diff --git a/src/examples.js b/src/examples.js index c3c5ed3..0df9f2e 100644 --- a/src/examples.js +++ b/src/examples.js @@ -32,7 +32,7 @@ doc(new Map([[1, 2], [3, 4]]), {indent: 2}); doc(new Date());`; -const REQUIRE = `const d3 = await require("d3"); +const RANDOM_HISTOGRAM = `const d3 = await require("d3"); const count = 200; const width = 50; @@ -101,8 +101,8 @@ export const examples = [ code: INSPECTOR, }, { - name: "Require", - code: REQUIRE, + name: "Random Histogram", + code: RANDOM_HISTOGRAM, }, { name: "Mandelbrot Set",