@ggterm/core 0.3.1 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli-plot.js CHANGED
@@ -14602,7 +14602,6 @@ __export(exports_serve, {
14602
14602
  import { watch, writeFileSync as writeFileSync3, unlinkSync } from "fs";
14603
14603
  import { join as join3 } from "path";
14604
14604
  import { createServer } from "http";
14605
- import { createHash } from "crypto";
14606
14605
  import { spawn } from "child_process";
14607
14606
  function plotToVegaLite(plot) {
14608
14607
  const geomTypes = plot._provenance.geomTypes;
@@ -14620,27 +14619,6 @@ function getLatestPayload() {
14620
14619
  const { spec, provenance } = plotToVegaLite(plot);
14621
14620
  return JSON.stringify({ type: "plot", spec, provenance });
14622
14621
  }
14623
- function encodeWebSocketFrame(data) {
14624
- const payload = Buffer.from(data, "utf-8");
14625
- const len = payload.length;
14626
- let header;
14627
- if (len < 126) {
14628
- header = Buffer.alloc(2);
14629
- header[0] = 129;
14630
- header[1] = len;
14631
- } else if (len < 65536) {
14632
- header = Buffer.alloc(4);
14633
- header[0] = 129;
14634
- header[1] = 126;
14635
- header.writeUInt16BE(len, 2);
14636
- } else {
14637
- header = Buffer.alloc(10);
14638
- header[0] = 129;
14639
- header[1] = 127;
14640
- header.writeBigUInt64BE(BigInt(len), 2);
14641
- }
14642
- return Buffer.concat([header, payload]);
14643
- }
14644
14622
  function jsonResponse(res, data, status = 200) {
14645
14623
  const body = JSON.stringify(data);
14646
14624
  res.writeHead(status, { "content-type": "application/json" });
@@ -14661,10 +14639,12 @@ function handleServe(port) {
14661
14639
  const payload = getLatestPayload();
14662
14640
  if (!payload)
14663
14641
  return;
14664
- const frame = encodeWebSocketFrame(payload);
14642
+ const sseData = `data: ${payload}
14643
+
14644
+ `;
14665
14645
  for (const client of clients) {
14666
14646
  try {
14667
- client.write(frame);
14647
+ client.write(sseData);
14668
14648
  } catch {
14669
14649
  clients.delete(client);
14670
14650
  }
@@ -14673,6 +14653,21 @@ function handleServe(port) {
14673
14653
  });
14674
14654
  const server = createServer((req, res) => {
14675
14655
  const url = new URL(req.url || "/", `http://localhost:${p}`);
14656
+ if (url.pathname === "/events") {
14657
+ res.writeHead(200, {
14658
+ "content-type": "text/event-stream",
14659
+ "cache-control": "no-cache",
14660
+ connection: "keep-alive"
14661
+ });
14662
+ clients.add(res);
14663
+ const payload = getLatestPayload();
14664
+ if (payload)
14665
+ res.write(`data: ${payload}
14666
+
14667
+ `);
14668
+ req.on("close", () => clients.delete(res));
14669
+ return;
14670
+ }
14676
14671
  if (url.pathname === "/api/latest") {
14677
14672
  const payload = getLatestPayload();
14678
14673
  if (!payload)
@@ -14698,53 +14693,14 @@ function handleServe(port) {
14698
14693
  res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
14699
14694
  res.end(CLIENT_HTML);
14700
14695
  });
14701
- server.on("upgrade", (req, socket, _head) => {
14702
- const url = new URL(req.url || "/", `http://localhost:${p}`);
14703
- if (url.pathname !== "/ws") {
14704
- socket.destroy();
14705
- return;
14706
- }
14707
- const key = req.headers["sec-websocket-key"];
14708
- if (!key) {
14709
- socket.destroy();
14710
- return;
14696
+ server.on("error", (err) => {
14697
+ if (err.code === "EADDRINUSE") {
14698
+ console.error(`Port ${p} is already in use.`);
14699
+ console.error(`Kill the existing server: lsof -ti:${p} | xargs kill`);
14700
+ console.error(`Or use a different port: npx ggterm-plot serve ${p + 1}`);
14701
+ process.exit(1);
14711
14702
  }
14712
- const accept = createHash("sha1").update(key + "258EAFA5-E914-47DA-95CA-5AB5DC85B7A8").digest("base64");
14713
- socket.write(`HTTP/1.1 101 Switching Protocols\r
14714
- ` + `Upgrade: websocket\r
14715
- ` + `Connection: Upgrade\r
14716
- ` + `Sec-WebSocket-Accept: ${accept}\r
14717
- ` + `\r
14718
- `);
14719
- clients.add(socket);
14720
- const payload = getLatestPayload();
14721
- if (payload)
14722
- socket.write(encodeWebSocketFrame(payload));
14723
- socket.on("close", () => clients.delete(socket));
14724
- socket.on("error", () => clients.delete(socket));
14725
- socket.on("data", (data) => {
14726
- if (data.length < 2)
14727
- return;
14728
- const opcode = data[0] & 15;
14729
- if (opcode === 8) {
14730
- const closeFrame = Buffer.alloc(2);
14731
- closeFrame[0] = 136;
14732
- closeFrame[1] = 0;
14733
- try {
14734
- socket.write(closeFrame);
14735
- } catch {}
14736
- socket.end();
14737
- clients.delete(socket);
14738
- return;
14739
- }
14740
- if (opcode === 9) {
14741
- const pong = Buffer.from(data);
14742
- pong[0] = pong[0] & 240 | 10;
14743
- try {
14744
- socket.write(pong);
14745
- } catch {}
14746
- }
14747
- });
14703
+ throw err;
14748
14704
  });
14749
14705
  server.listen(p, () => {
14750
14706
  const url = `http://localhost:${p}`;
@@ -15034,7 +14990,6 @@ let history = [];
15034
14990
  let historyIndex = {};
15035
14991
  let currentIdx = -1;
15036
14992
  let view = null;
15037
- let ws = null;
15038
14993
 
15039
14994
  function updateMeta(prov) {
15040
14995
  if (!prov) return;
@@ -15192,17 +15147,12 @@ function downloadPNG() {
15192
15147
  }
15193
15148
 
15194
15149
  function connect() {
15195
- const proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
15196
- ws = new WebSocket(proto + '//' + location.host + '/ws');
15150
+ const es = new EventSource('/events');
15197
15151
 
15198
- ws.onopen = () => { statusEl.classList.add('connected'); };
15199
- ws.onclose = () => {
15200
- statusEl.classList.remove('connected');
15201
- setTimeout(connect, 2000);
15202
- };
15203
- ws.onerror = () => ws.close();
15152
+ es.onopen = () => { statusEl.classList.add('connected'); };
15153
+ es.onerror = () => { statusEl.classList.remove('connected'); };
15204
15154
 
15205
- ws.onmessage = (e) => {
15155
+ es.onmessage = (e) => {
15206
15156
  const data = JSON.parse(e.data);
15207
15157
  if (data.type === 'plot') {
15208
15158
  history.push(data);
@@ -15242,7 +15192,7 @@ import { join as join4 } from "path";
15242
15192
  // src/init.ts
15243
15193
  import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2, readFileSync as readFileSync2, existsSync as existsSync2, readdirSync as readdirSync2, statSync } from "fs";
15244
15194
  import { join as join2, extname } from "path";
15245
- var SKILLS_VERSION = "0.2.7";
15195
+ var SKILLS_VERSION = "0.3.2";
15246
15196
  var SKILLS = {
15247
15197
  "data-load": {
15248
15198
  files: {
@@ -15256,73 +15206,41 @@ allowed-tools: Bash(npx:*), Read, Write
15256
15206
 
15257
15207
  Load data into arrays of records for use with ggterm plotting and analysis.
15258
15208
 
15259
- ## Quick Patterns by Format
15260
-
15261
- ### CSV
15262
-
15263
- \`\`\`typescript
15264
- import { parse } from 'csv-parse/sync'
15265
- import { readFileSync } from 'fs'
15209
+ ## Built-in Datasets (No files needed!)
15266
15210
 
15267
- const text = readFileSync('data.csv', 'utf-8')
15268
- const data = parse(text, {
15269
- columns: true, // First row as headers
15270
- cast: true, // Auto-convert numbers
15271
- skip_empty_lines: true
15272
- })
15273
- \`\`\`
15211
+ ggterm includes built-in datasets that can be used directly by name:
15274
15212
 
15275
- **Alternative with d3-dsv** (lighter weight):
15213
+ | Dataset | Rows | Columns |
15214
+ |---------|------|---------|
15215
+ | \`iris\` | 150 | sepal_length, sepal_width, petal_length, petal_width, species |
15216
+ | \`mtcars\` | 16 | mpg, cyl, hp, wt, name |
15276
15217
 
15277
- \`\`\`typescript
15278
- import { csvParse, autoType } from 'd3-dsv'
15218
+ Use them directly in plot commands:
15279
15219
 
15280
- const data = csvParse(readFileSync('data.csv', 'utf-8'), autoType)
15220
+ \`\`\`bash
15221
+ npx ggterm-plot iris sepal_length sepal_width species "Iris Dataset" point
15222
+ npx ggterm-plot mtcars mpg hp cyl "Motor Trend Cars" point
15281
15223
  \`\`\`
15282
15224
 
15283
- ### JSON
15225
+ **IMPORTANT**: When the user asks about iris, mtcars, or bundled/built-in datasets, use these names directly with \`npx ggterm-plot\`. Do NOT try to generate CSV files or install Python packages.
15284
15226
 
15285
- \`\`\`typescript
15286
- import { readFileSync } from 'fs'
15287
-
15288
- // JSON array
15289
- const data = JSON.parse(readFileSync('data.json', 'utf-8'))
15290
- \`\`\`
15227
+ ## External Files
15291
15228
 
15292
- ### JSONL (Newline-delimited JSON)
15293
-
15294
- \`\`\`typescript
15295
- const data = readFileSync('data.jsonl', 'utf-8')
15296
- .trim()
15297
- .split('\\n')
15298
- .map(line => JSON.parse(line))
15229
+ \`\`\`bash
15230
+ npx ggterm-plot data.csv x_column y_column color_column "Title" point
15231
+ npx ggterm-plot data.json x_column y_column
15232
+ npx ggterm-plot data.jsonl x_column y_column
15299
15233
  \`\`\`
15300
15234
 
15301
15235
  ## Verification
15302
15236
 
15303
- After loading, always verify the data structure:
15237
+ Inspect any data file to see columns and types:
15304
15238
 
15305
- \`\`\`typescript
15306
- console.log(\`Loaded \${data.length} rows\`)
15307
- console.log('Columns:', Object.keys(data[0]))
15308
- console.log('Sample row:', data[0])
15239
+ \`\`\`bash
15240
+ npx ggterm-plot inspect data.csv
15309
15241
  \`\`\`
15310
15242
 
15311
- ## Integration with ggterm
15312
-
15313
- Once data is loaded, pass directly to ggterm:
15314
-
15315
- \`\`\`typescript
15316
- import { gg, geom_point } from '@ggterm/core'
15317
-
15318
- const data = loadData('measurements.csv')
15319
-
15320
- const plot = gg(data)
15321
- .aes({ x: 'time', y: 'value' })
15322
- .geom(geom_point())
15323
-
15324
- console.log(plot.render({ width: 80, height: 24 }))
15325
- \`\`\`
15243
+ $ARGUMENTS
15326
15244
  `
15327
15245
  }
15328
15246
  },
@@ -15336,62 +15254,77 @@ allowed-tools: Bash(npx:ggterm-plot*), Read
15336
15254
 
15337
15255
  # Terminal Plotting with ggterm
15338
15256
 
15339
- Create plots using the CLI tool. Start by inspecting the data, then plot.
15257
+ Create plots using the CLI tool. Supports both built-in datasets and external files.
15340
15258
 
15341
- ## Step 1: Inspect Data (Recommended)
15259
+ ## Built-in Datasets
15260
+
15261
+ ggterm includes datasets that work by name — no CSV files needed:
15262
+
15263
+ | Dataset | Rows | Columns |
15264
+ |---------|------|---------|
15265
+ | \`iris\` | 150 | sepal_length, sepal_width, petal_length, petal_width, species |
15266
+ | \`mtcars\` | 16 | mpg, cyl, hp, wt, name |
15342
15267
 
15343
15268
  \`\`\`bash
15344
- npx ggterm-plot inspect <data.csv>
15269
+ npx ggterm-plot iris sepal_length sepal_width species "Iris" point
15270
+ npx ggterm-plot mtcars mpg hp cyl "Cars" point
15345
15271
  \`\`\`
15346
15272
 
15347
- Shows column names, types (numeric/categorical/date), unique counts, and sample values.
15273
+ **IMPORTANT**: When the user mentions iris, mtcars, or asks for demo/sample data, use these built-in names directly. Do NOT look for CSV files or generate data.
15274
+
15275
+ ## Live Plot Viewer
15348
15276
 
15349
- ## Step 2: Get Suggestions (Optional)
15277
+ Start the companion viewer for high-resolution interactive plots:
15350
15278
 
15351
15279
  \`\`\`bash
15352
- npx ggterm-plot suggest <data.csv>
15280
+ npx ggterm-plot serve # default port 4242
15281
+ npx ggterm-plot serve 8080 # custom port
15353
15282
  \`\`\`
15354
15283
 
15355
- Returns ready-to-run plot commands based on column types.
15284
+ When serve is running, plots auto-display in the browser/Wave panel instead of ASCII art.
15356
15285
 
15357
- ## Step 3: Create Plot
15286
+ ## CLI Command
15358
15287
 
15359
15288
  \`\`\`bash
15360
- npx ggterm-plot <data.csv> <x> <y> [color] [title] [geom]
15289
+ npx ggterm-plot <data> <x> <y> [color] [title] [geom]
15361
15290
  \`\`\`
15362
15291
 
15363
15292
  Arguments:
15364
- - \`data.csv\` - Path to CSV file
15293
+ - \`data\` - Built-in dataset name (\`iris\`, \`mtcars\`) OR path to CSV/JSON/JSONL file
15365
15294
  - \`x\` - Column name for x-axis
15366
15295
  - \`y\` - Column name for y-axis (use \`-\` for histogram)
15367
15296
  - \`color\` - Column name for color (optional, use \`-\` to skip)
15368
15297
  - \`title\` - Plot title (optional, use \`-\` to skip)
15369
- - \`geom\` - Geometry type: \`point\` (default), \`line\`, \`histogram\`, \`boxplot\`, \`bar\`, \`violin\`, \`area\`, etc.
15298
+ - \`geom\` - Geometry type: \`point\` (default), \`line\`, \`histogram\`, \`boxplot\`, \`bar\`, \`violin\`, \`density\`, \`area\`, etc.
15370
15299
 
15371
- ## Examples
15300
+ ## Inspect & Suggest (for external files)
15372
15301
 
15373
- Scatter plot:
15374
15302
  \`\`\`bash
15375
- npx ggterm-plot data.csv sepal_length sepal_width species "Iris Dataset" point
15303
+ npx ggterm-plot inspect <data.csv>
15304
+ npx ggterm-plot suggest <data.csv>
15376
15305
  \`\`\`
15377
15306
 
15378
- Histogram:
15307
+ ## Examples
15308
+
15309
+ Built-in data:
15379
15310
  \`\`\`bash
15380
- npx ggterm-plot data.csv sepal_width - - "Sepal Width Distribution" histogram
15311
+ npx ggterm-plot iris sepal_length sepal_width species "Iris Dataset" point
15312
+ npx ggterm-plot iris petal_length - species "Petal Length" histogram
15313
+ npx ggterm-plot mtcars mpg hp cyl "MPG vs HP" point
15381
15314
  \`\`\`
15382
15315
 
15383
- Box plot:
15316
+ External files:
15384
15317
  \`\`\`bash
15385
- npx ggterm-plot data.csv treatment response_time - "Response by Treatment" boxplot
15318
+ npx ggterm-plot data.csv x y color "Title" point
15319
+ npx ggterm-plot data.json date value - "Time Series" line
15386
15320
  \`\`\`
15387
15321
 
15388
15322
  ## Workflow
15389
15323
 
15390
- 1. Identify the data file from $ARGUMENTS or ask user
15391
- 2. Run \`inspect\` to see column names and types
15392
- 3. Run \`suggest\` to get recommended visualizations (or choose based on user request)
15393
- 4. Run the plot command
15394
- 5. Briefly describe what the plot shows
15324
+ 1. If user asks for iris/mtcars/demo data, use built-in dataset names directly
15325
+ 2. For external files: run \`inspect\` to see columns, then \`suggest\` for recommendations
15326
+ 3. Run the plot command
15327
+ 4. Briefly describe what the plot shows
15395
15328
 
15396
15329
  $ARGUMENTS
15397
15330
 
@@ -15404,6 +15337,11 @@ $ARGUMENTS
15404
15337
  | Distribution of 1 variable | \`histogram\` | Frequency distribution |
15405
15338
  | Distribution by group | \`boxplot\` | Compare medians |
15406
15339
  | Category comparison | \`bar\` | Counts per category |
15340
+ | Smoothed distribution | \`density\` | Kernel density estimate |
15341
+ | Density shape | \`violin\` | Distribution shape |
15342
+ | Stacked distributions | \`ridgeline\` | Joy plot |
15343
+ | Filled region | \`area\` | Cumulative or stacked |
15344
+ | Cumulative distribution | \`ecdf\` | Empirical CDF |
15407
15345
  `
15408
15346
  }
15409
15347
  },
@@ -15524,7 +15462,7 @@ $ARGUMENTS
15524
15462
  "SKILL.md": `---
15525
15463
  name: ggterm-customize
15526
15464
  description: Customize plot aesthetics using natural language. Use when the user wants to change colors, fonts, titles, labels, themes, or any visual aspect of a plot before publication.
15527
- allowed-tools: Read, Write
15465
+ allowed-tools: Read, Write, Bash(npx:*)
15528
15466
  ---
15529
15467
 
15530
15468
  # Natural Language Plot Customization
@@ -15581,7 +15519,7 @@ $ARGUMENTS
15581
15519
  "SKILL.md": `---
15582
15520
  name: ggterm-style
15583
15521
  description: Apply publication-quality style presets to plots. Use when the user wants to style a plot like Wilke, Tufte, Nature, The Economist, or apply minimal/publication styling.
15584
- allowed-tools: Read, Write
15522
+ allowed-tools: Read, Write, Bash(npx:*)
15585
15523
  ---
15586
15524
 
15587
15525
  # Plot Style Presets
@@ -15606,47 +15544,6 @@ Apply expert-curated style presets to Vega-Lite specifications for publication-q
15606
15544
  3. Write the updated spec
15607
15545
  4. Inform user they can export with \`/ggterm-publish\`
15608
15546
 
15609
- ## Style Configurations
15610
-
15611
- ### Wilke Style (Recommended Default)
15612
-
15613
- \`\`\`javascript
15614
- const wilkeStyle = {
15615
- config: {
15616
- font: "Helvetica Neue, Helvetica, Arial, sans-serif",
15617
- background: "white",
15618
- view: { stroke: null },
15619
- title: { fontSize: 14, fontWeight: "normal", anchor: "start" },
15620
- axis: { grid: false, labelFontSize: 11, titleFontSize: 12 },
15621
- axisY: { grid: true, gridColor: "#ebebeb" }
15622
- }
15623
- }
15624
- \`\`\`
15625
-
15626
- ### Tufte Style
15627
-
15628
- \`\`\`javascript
15629
- const tufteStyle = {
15630
- config: {
15631
- font: "Georgia, serif",
15632
- view: { stroke: null },
15633
- axis: { domain: false, grid: false, ticks: false }
15634
- }
15635
- }
15636
- \`\`\`
15637
-
15638
- ### Economist Style
15639
-
15640
- \`\`\`javascript
15641
- const economistStyle = {
15642
- config: {
15643
- background: "#d5e4eb",
15644
- axis: { grid: true, gridColor: "#ffffff" },
15645
- axisX: { grid: false, domain: true }
15646
- }
15647
- }
15648
- \`\`\`
15649
-
15650
15547
  ## Response Format
15651
15548
 
15652
15549
  After applying a style:
@@ -15683,18 +15580,6 @@ Generate analysis reports with embedded terminal visualizations.
15683
15580
  3. **Visualizations** - Embedded plots with interpretations
15684
15581
  4. **Findings** - Key insights from the analysis
15685
15582
 
15686
- ## Embedding Plots
15687
-
15688
- \`\`\`markdown
15689
- ## Visualization
15690
-
15691
- \\\`\\\`\\\`
15692
- [terminal plot output here]
15693
- \\\`\\\`\\\`
15694
-
15695
- **Interpretation**: The scatter plot shows [describe the relationship observed].
15696
- \`\`\`
15697
-
15698
15583
  ## Width Guidelines
15699
15584
 
15700
15585
  | Context | Width | Height |
@@ -15824,6 +15709,63 @@ function handleInit() {
15824
15709
  console.log(`ggterm v${SKILLS_VERSION} ready`);
15825
15710
  console.log("");
15826
15711
  }
15712
+ const claudeMdPath = join2(cwd, "CLAUDE.md");
15713
+ const claudeMdContent = `# ggterm Data Analysis Project
15714
+
15715
+ This project uses ggterm (@ggterm/core) for data visualization.
15716
+
15717
+ ## IMPORTANT: Built-in Datasets
15718
+
15719
+ ggterm has built-in datasets that work by name — NO CSV files or Python packages needed:
15720
+
15721
+ \`\`\`bash
15722
+ npx ggterm-plot iris sepal_length sepal_width species "Iris" point
15723
+ npx ggterm-plot mtcars mpg hp cyl "Cars" point
15724
+ \`\`\`
15725
+
15726
+ | Dataset | Rows | Columns |
15727
+ |---------|------|---------|
15728
+ | \`iris\` | 150 | sepal_length, sepal_width, petal_length, petal_width, species |
15729
+ | \`mtcars\` | 16 | mpg, cyl, hp, wt, name |
15730
+
15731
+ When asked about iris, mtcars, or sample data, use these names directly as the first argument to \`npx ggterm-plot\`. Do NOT search for CSV files or generate data.
15732
+
15733
+ ## Plotting Commands
15734
+
15735
+ \`\`\`bash
15736
+ npx ggterm-plot <data> <x> <y> [color] [title] [geom]
15737
+ npx ggterm-plot serve # Start live viewer (port 4242)
15738
+ npx ggterm-plot inspect <file> # Show column types
15739
+ npx ggterm-plot suggest <file> # Suggest visualizations
15740
+ npx ggterm-plot history # List previous plots
15741
+ npx ggterm-plot export <id> out.html # Export plot
15742
+ \`\`\`
15743
+
15744
+ ## Geom Types
15745
+
15746
+ point, line, histogram, boxplot, bar, violin, density, area, ridgeline, heatmap, scatter, ecdf, smooth, and 50+ more.
15747
+
15748
+ ## Live Viewer
15749
+
15750
+ When \`npx ggterm-plot serve\` is running, plots auto-display in the browser/Wave panel as high-resolution interactive Vega-Lite visualizations instead of ASCII art.
15751
+ `;
15752
+ const GGTERM_MARKER = "# ggterm Data Analysis Project";
15753
+ let shouldWriteClaudeMd = !existsSync2(claudeMdPath);
15754
+ if (!shouldWriteClaudeMd) {
15755
+ try {
15756
+ const existing = readFileSync2(claudeMdPath, "utf-8");
15757
+ shouldWriteClaudeMd = existing.startsWith(GGTERM_MARKER);
15758
+ } catch {}
15759
+ }
15760
+ if (shouldWriteClaudeMd) {
15761
+ writeFileSync2(claudeMdPath, claudeMdContent);
15762
+ console.log(" ✓ CLAUDE.md (project instructions for Claude Code)");
15763
+ console.log("");
15764
+ }
15765
+ console.log("Built-in datasets:");
15766
+ console.log(" • iris (150 rows: sepal_length, sepal_width, petal_length, petal_width, species)");
15767
+ console.log(" • mtcars (16 rows: mpg, cyl, hp, wt, name)");
15768
+ console.log("");
15827
15769
  const dataFiles = findDataFiles(cwd);
15828
15770
  if (dataFiles.length > 0) {
15829
15771
  console.log("Data files:");
@@ -15851,9 +15793,9 @@ function handleInit() {
15851
15793
  }
15852
15794
  if (needsInstall) {
15853
15795
  console.log("Try:");
15854
- console.log(' "Show me a scatter plot of x vs y from data.csv"');
15855
- console.log(' "Create a histogram of the age column"');
15856
- console.log(' "Style this like Tufte and export as PNG"');
15796
+ console.log(' npx ggterm-plot iris sepal_length sepal_width species "Iris" point');
15797
+ console.log(" npx ggterm-plot serve # Start live viewer");
15798
+ console.log(' "Plot the iris dataset" # Ask Claude Code');
15857
15799
  console.log("");
15858
15800
  }
15859
15801
  }
@@ -16389,19 +16331,68 @@ Examples:`);
16389
16331
  Tip: Use "inspect <file>" to see available columns`);
16390
16332
  process.exit(1);
16391
16333
  }
16392
- if (!args[0].includes(".") && !fileExists(args[0])) {
16334
+ const BUILTIN_DATASETS = {
16335
+ iris: () => {
16336
+ const species = ["setosa", "versicolor", "virginica"];
16337
+ const data2 = Array.from({ length: 150 }, (_, i) => {
16338
+ const sp = species[Math.floor(i / 50)];
16339
+ const base = sp === "setosa" ? 0 : sp === "versicolor" ? 1 : 2;
16340
+ return {
16341
+ sepal_length: +(5 + base * 0.5 + Math.random()).toFixed(1),
16342
+ sepal_width: +(3 + Math.random() * 0.5).toFixed(1),
16343
+ petal_length: +(1.5 + base * 2 + Math.random()).toFixed(1),
16344
+ petal_width: +(0.2 + base * 0.8 + Math.random() * 0.3).toFixed(1),
16345
+ species: sp
16346
+ };
16347
+ });
16348
+ return { headers: ["sepal_length", "sepal_width", "petal_length", "petal_width", "species"], data: data2 };
16349
+ },
16350
+ mtcars: () => {
16351
+ const data2 = [
16352
+ { name: "Mazda RX4", mpg: 21, cyl: 6, hp: 110, wt: 2.62 },
16353
+ { name: "Mazda RX4 Wag", mpg: 21, cyl: 6, hp: 110, wt: 2.875 },
16354
+ { name: "Datsun 710", mpg: 22.8, cyl: 4, hp: 93, wt: 2.32 },
16355
+ { name: "Hornet 4 Drive", mpg: 21.4, cyl: 6, hp: 110, wt: 3.215 },
16356
+ { name: "Hornet Sportabout", mpg: 18.7, cyl: 8, hp: 175, wt: 3.44 },
16357
+ { name: "Valiant", mpg: 18.1, cyl: 6, hp: 105, wt: 3.46 },
16358
+ { name: "Duster 360", mpg: 14.3, cyl: 8, hp: 245, wt: 3.57 },
16359
+ { name: "Merc 240D", mpg: 24.4, cyl: 4, hp: 62, wt: 3.19 },
16360
+ { name: "Merc 230", mpg: 22.8, cyl: 4, hp: 95, wt: 3.15 },
16361
+ { name: "Merc 280", mpg: 19.2, cyl: 6, hp: 123, wt: 3.44 },
16362
+ { name: "Merc 280C", mpg: 17.8, cyl: 6, hp: 123, wt: 3.44 },
16363
+ { name: "Merc 450SE", mpg: 16.4, cyl: 8, hp: 180, wt: 4.07 },
16364
+ { name: "Merc 450SL", mpg: 17.3, cyl: 8, hp: 180, wt: 3.73 },
16365
+ { name: "Merc 450SLC", mpg: 15.2, cyl: 8, hp: 180, wt: 3.78 },
16366
+ { name: "Cadillac Fleetwood", mpg: 10.4, cyl: 8, hp: 205, wt: 5.25 },
16367
+ { name: "Lincoln Continental", mpg: 10.4, cyl: 8, hp: 215, wt: 5.424 }
16368
+ ];
16369
+ return { headers: ["name", "mpg", "cyl", "hp", "wt"], data: data2 };
16370
+ }
16371
+ };
16372
+ const [dataFile, x, y, color, title, geomSpec = "point", facetVar] = args;
16373
+ let headers;
16374
+ let data;
16375
+ if (BUILTIN_DATASETS[dataFile]) {
16376
+ const result = BUILTIN_DATASETS[dataFile]();
16377
+ headers = result.headers;
16378
+ data = result.data;
16379
+ } else if (!args[0].includes(".") && !fileExists(args[0])) {
16393
16380
  console.error(`
16394
16381
  Error: "${args[0]}" doesn't look like a file path`);
16395
16382
  console.error(`
16383
+ Built-in datasets: iris, mtcars`);
16384
+ console.error(`
16396
16385
  Did you mean one of these commands?`);
16397
16386
  console.error(` inspect <file> - Show column types`);
16398
16387
  console.error(` suggest <file> - Get plot suggestions`);
16399
16388
  console.error(` history - List saved plots`);
16400
16389
  console.error(` help - Show full usage`);
16401
16390
  process.exit(1);
16391
+ } else {
16392
+ const loaded = loadData(dataFile);
16393
+ headers = loaded.headers;
16394
+ data = loaded.data;
16402
16395
  }
16403
- const [dataFile, x, y, color, title, geomSpec = "point", facetVar] = args;
16404
- let { headers, data } = loadData(dataFile);
16405
16396
  const { baseGeom: geomType, refLines } = parseGeomSpec(geomSpec);
16406
16397
  if (geomType === "manhattan" && y && y !== "-") {
16407
16398
  data = data.map((row) => {
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAmkBH,wBAAgB,UAAU,IAAI,IAAI,CAyFjC"}
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAkgBH,wBAAgB,UAAU,IAAI,IAAI,CAyJjC"}
@@ -1 +1 @@
1
- {"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../src/serve.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AA6gBH,wBAAgB,WAAW,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CA2I/C"}
1
+ {"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../src/serve.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AA0eH,wBAAgB,WAAW,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CA6G/C"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ggterm/core",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "description": "Grammar of Graphics engine for terminals",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",