@thi.ng/text-canvas 2.6.37 → 3.0.0

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/CHANGELOG.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Change Log
2
2
 
3
- - **Last updated**: 2024-02-10T08:59:57Z
3
+ - **Last updated**: 2024-02-19T15:50:26Z
4
4
  - **Generator**: [thi.ng/monopub](https://thi.ng/monopub)
5
5
 
6
6
  All notable changes to this project will be documented in this file.
@@ -9,6 +9,28 @@ See [Conventional Commits](https://conventionalcommits.org/) for commit guidelin
9
9
  **Note:** Unlisted _patch_ versions only involve non-code or otherwise excluded changes
10
10
  and/or version bumps of transitive dependencies.
11
11
 
12
+ # [3.0.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/text-canvas@3.0.0) (2024-02-19)
13
+
14
+ #### 🛑 Breaking changes
15
+
16
+ - add plotting, additive blending/blitting, refactor bar chart fns ([7cd6d41](https://github.com/thi-ng/umbrella/commit/7cd6d41))
17
+ - BREAKING CHANGE: swap naming of barChartH/V fns, update args for blit()/blitMask()
18
+ - swap naming of barChartH/V fns:
19
+ - barChartHLines/Str() <=> barChartVLines/Str()
20
+ - add plotBarsV() multi-plot function
21
+ - add blitBarsV() fn w/ support for custom blending fns
22
+ - add blendBarsVAdd() additive blending fn
23
+ - add BLEND_ADD lookup table for additive blending using ANSI16 colors
24
+ - update arg order of blit()/blitMask() fns
25
+ - add Canvas.empty(), Canvas.clear() fns
26
+
27
+ #### ♻️ Refactoring
28
+
29
+ - unify plotting function naming ([cb275ae](https://github.com/thi-ng/umbrella/commit/cb275ae))
30
+ - plotBarsV() => plotBarChartV()
31
+ - lineChart() => plotLineChart()
32
+ - migrate line chart fns to plot.ts
33
+
12
34
  ### [2.6.17](https://github.com/thi-ng/umbrella/tree/@thi.ng/text-canvas@2.6.17) (2023-11-09)
13
35
 
14
36
  #### ♻️ Refactoring
package/README.md CHANGED
@@ -46,12 +46,15 @@
46
46
  - [Bars & bar charts](#bars--bar-charts)
47
47
  - [Tables](#tables)
48
48
  - [3D wireframe cube example](#3d-wireframe-cube-example)
49
+ - [Multiple bar plots with additive blending](#multiple-bar-plots-with-additive-blending)
49
50
  - [Authors](#authors)
50
51
  - [License](#license)
51
52
 
52
53
  ## About
53
54
 
54
- Text based canvas, drawing, tables with arbitrary formatting (incl. ANSI/HTML).
55
+ Text based canvas, drawing, plotting, tables with arbitrary formatting (incl. ANSI/HTML).
56
+
57
+ ![Terminal based textmode bar plots](https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/text-canvas/multi-barplot.png)
55
58
 
56
59
  ## Status
57
60
 
@@ -83,7 +86,7 @@ For Node.js REPL:
83
86
  const textCanvas = await import("@thi.ng/text-canvas");
84
87
  ```
85
88
 
86
- Package sizes (brotli'd, pre-treeshake): ESM: 5.56 KB
89
+ Package sizes (brotli'd, pre-treeshake): ESM: 6.27 KB
87
90
 
88
91
  ## Dependencies
89
92
 
@@ -205,9 +208,9 @@ package):
205
208
 
206
209
  ```ts
207
210
  // Terminal
208
- console.log(formatCanvas(canvas, FMT_ANSI16));
211
+ process.stdout.write(formatCanvas(canvas, FMT_ANSI16));
209
212
  // or
210
- console.log(formatCanvas(canvas, FMT_ANSI256));
213
+ console.log(formatCanvas(canvas, FMT_ANSI16));
211
214
 
212
215
  // Browser
213
216
  const el = document.createElement("pre");
@@ -253,23 +256,22 @@ each newly pushed one being intersected with the previous top-of-stack rect:
253
256
 
254
257
  ### Drawing functions
255
258
 
256
- - `line`
257
- - `hline`
258
- - `vline`
259
- - `circle`
259
+ - `line()`
260
+ - `hline()`
261
+ - `vline()`
262
+ - `circle()`
260
263
 
261
- - `clear`
262
- - `fillRect`
263
- - `strokeRect`
264
+ - `clear()`
265
+ - `fillRect()`
266
+ - `strokeRect()`
264
267
 
265
268
  ### Image functions
266
269
 
267
- - `blit`
268
- - `resize`
269
- - `extract`
270
- - `scrollV`
271
- - `image` / `imageRaw` / `imageCanvas565` / `imageString565`
272
- - `imageBraille` / `imageCanvasBraille` / `imageStringBraille`
270
+ - `blit()` / `blitMask()` / `blitBarsV()`
271
+ - `image()` / `imageRaw()` / `imageCanvas565()` / `imageString565()`
272
+ - `imageBraille()` / `imageCanvasBraille()` / `imageStringBraille()`
273
+ - `resize()` / `extract()`
274
+ - `scrollV()`
273
275
 
274
276
  ```ts
275
277
  import { RGB565 } from "@thi.ng/pixel";
@@ -289,19 +291,19 @@ console.log(imageString565(img));
289
291
 
290
292
  ### Text functions
291
293
 
292
- - `textLine`
293
- - `textLines`
294
- - `textColumn` (word wrapped)
295
- - `textBox` (word wrapped)
294
+ - `textLine()`
295
+ - `textLines()`
296
+ - `textColumn()` (word wrapped)
297
+ - `textBox()` (word wrapped)
296
298
 
297
299
  ### Bars & bar charts
298
300
 
299
- The following are string builders only, draw result via text functions:
301
+ The following are string builders only, draw result via [text functions](#text-functions):
300
302
 
301
- - `barHorizontal`
302
- - `barVertical`
303
- - `barChartHStr`
304
- - `barChartVStr`
303
+ - `barHorizontal()`
304
+ - `barVertical()`
305
+ - `barChartHStr()`
306
+ - `barChartVStr()`
305
307
 
306
308
  ### Tables
307
309
 
@@ -323,7 +325,7 @@ Table cell contents will be word-wrapped. By default, individual words longer
323
325
  than the configured cell width will be truncated, but can be forced to wrap by
324
326
  enabling the `hard` option (see example below).
325
327
 
326
- ```ts
328
+ ```ts tangle:export/readme-table.ts
327
329
  import { repeatedly } from "@thi.ng/transducers";
328
330
  import * as tc from "@thi.ng/text-canvas";
329
331
  import * as tf from "@thi.ng/text-format";
@@ -331,7 +333,7 @@ import * as tf from "@thi.ng/text-format";
331
333
  // generate 20 random values
332
334
  const data = repeatedly(() => Math.random(), 20)
333
335
  // format as bar chart string
334
- const chart = tc.barChartHStr(4, data, 0, 1);
336
+ const chart = tc.barChartVStr(4, data, 0, 1);
335
337
 
336
338
  // create text canvas
337
339
  const canvas = new tc.Canvas(64, 20);
@@ -390,44 +392,11 @@ as content.
390
392
 
391
393
  ### 3D wireframe cube example
392
394
 
393
- ```text
394
- ┌───┐
395
- ┌──────────────────────┐
396
- │ @thi.ng/text-canvas │
397
- │ wireframe cube │++++++++++
398
- │ │ +++++++++++ ┌───┐
399
- │ x: 0.42 │ ++++│ 6 │
400
- │ y: 0.30 │ ┌───┐ ++++++++ └───┘
401
- └──────────────────────┘++++++++│ 7 │+ +
402
- + └───┘ └───┘ +
403
- + + + +
404
- + + + +
405
- + + + +
406
- + + + +
407
- + + + +
408
- + + + +
409
- + + + +
410
- + + ┌───┐ +
411
- + + +│ 3 │ +
412
- + ┌───┐+++ └───┘ +
413
- + │ 0 │ + +
414
- + └───┘ + +
415
- + + + +
416
- + + + +
417
- + + + +
418
- + + + +
419
- + + ┌───┐
420
- + + │ 2 │
421
- + + ++└───┘
422
- + + +++
423
- + + ++
424
- + + +++
425
- ++ ++
426
- ````
395
+ ![3D wireframe cube](https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/text-canvas/3dcube.png)
427
396
 
428
397
  Code for this above example output (CLI version):
429
398
 
430
- ```ts
399
+ ```ts tangle:export/readme-cube.ts
431
400
  import * as geom from "@thi.ng/geom";
432
401
  import * as mat from "@thi.ng/matrices";
433
402
  import * as tc from "@thi.ng/text-canvas";
@@ -491,13 +460,64 @@ setInterval(() => {
491
460
  padding: [1, 0]
492
461
  }
493
462
  );
494
- // draw canvas
495
- console.clear();
496
463
  // output as ANSI formatted string
497
- console.log(tc.formatCanvas(canvas, tf.FMT_ANSI16));
498
- // output as plain text
464
+ process.stdout.write(
465
+ tf.ANSI_SYNC_START +
466
+ tf.ANSI_CLEAR_SCREEN +
467
+ tf.ANSI_HOME +
468
+ tc.formatCanvas(canvas, tf.FMT_ANSI16) +
469
+ tf.ANSI_SYNC_END
470
+ );
471
+ // ...our output as plain text
499
472
  // console.log(tc.formatCanvas(canvas));
500
- }, 15);
473
+ }, 16);
474
+ ```
475
+
476
+ ### Multiple bar plots with additive blending
477
+
478
+ ```ts tangle:export/readme-barplot.ts
479
+ import { HERMITE_V, VEC4, ramp } from "@thi.ng/ramp";
480
+ import { canvas, formatCanvas, plotBarChartV } from "@thi.ng/text-canvas";
481
+ import { FG_BLUE, FG_GRAY, FG_GREEN, FG_RED, FMT_ANSI16 } from "@thi.ng/text-format";
482
+
483
+ // define curves for 4 params which will be computed via
484
+ // cubic hermite interpolation
485
+ const curves = ramp(
486
+ // use VEC4 interpolation preset
487
+ HERMITE_V(VEC4),
488
+ // keyframes
489
+ [
490
+ [0.0, [1, 0, 0.33, 0]],
491
+ [0.5, [0, 1, 0.06, -0.3]],
492
+ [1.0, [0, 0, 1, 0.5]],
493
+ ]
494
+ );
495
+
496
+ const W = 100;
497
+ const H = 24;
498
+ const samples: number[][] = [];
499
+
500
+ // sample curves
501
+ for (let i = 0; i < W; i++) {
502
+ samples.push(<number[]>curves.at(i / (W - 1)));
503
+ }
504
+
505
+ // create empty canvas
506
+ const plot = canvas(W, H);
507
+
508
+ // create all 4 bar plots in the same canvas, by default uses additive blending
509
+ // to composite each plot layer
510
+ plotBarChartV(
511
+ plot,
512
+ { min: 0, max: 1 },
513
+ { data: samples.map((x) => x[0]), color: FG_RED },
514
+ { data: samples.map((x) => x[1]), color: FG_GREEN },
515
+ { data: samples.map((x) => x[2]), color: FG_BLUE },
516
+ { data: samples.map((x) => x[3]), color: FG_GRAY }
517
+ );
518
+
519
+ // format & print canvas using ANSI colors
520
+ console.log(formatCanvas(plot, FMT_ANSI16));
501
521
  ```
502
522
 
503
523
  ## Authors
package/api.d.ts CHANGED
@@ -13,6 +13,7 @@ export declare enum Border {
13
13
  FRAME_H = 5,
14
14
  FRAME_V = 6
15
15
  }
16
+ export type BlendFn = (src: number, dest: number, x: number, y: number) => number;
16
17
  export interface TableOpts {
17
18
  cols: {
18
19
  width: number;
package/bars.d.ts CHANGED
@@ -1,7 +1,41 @@
1
- export declare const barChartHLines: (height: number, vals: Iterable<number>, min?: number, max?: number) => string[];
2
- export declare const barChartHStr: (height: number, vals: Iterable<number>, min?: number, max?: number) => string;
3
- export declare const barChartVLines: (width: number, vals: Iterable<number>, min?: number, max?: number) => string[];
4
- export declare const barChartVStr: (width: number, vals: Iterable<number>, min?: number, max?: number) => string;
1
+ /**
2
+ * Visualizes given values (in `[min..max]`interval) as vertical bar chart.
3
+ * Returns array of line strings.
4
+ *
5
+ * @param height
6
+ * @param vals
7
+ * @param min
8
+ * @param max
9
+ */
10
+ export declare const barChartVLines: (height: number, vals: Iterable<number>, min?: number, max?: number) => string[];
11
+ /**
12
+ * Same as {@link barChartVLines}, but returns result as single string.
13
+ *
14
+ * @param height
15
+ * @param vals
16
+ * @param min
17
+ * @param max
18
+ */
19
+ export declare const barChartVStr: (height: number, vals: Iterable<number>, min?: number, max?: number) => string;
20
+ /**
21
+ * Visualizes given values (in `[min..max]`interval) as horizontal bar chart.
22
+ * Returns array of line strings.
23
+ *
24
+ * @param height
25
+ * @param vals
26
+ * @param min
27
+ * @param max
28
+ */
29
+ export declare const barChartHLines: (width: number, vals: Iterable<number>, min?: number, max?: number) => string[];
30
+ /**
31
+ * Same as {@link barChartVLines}, but returns result as single string.
32
+ *
33
+ * @param height
34
+ * @param vals
35
+ * @param min
36
+ * @param max
37
+ */
38
+ export declare const barChartHStr: (width: number, vals: Iterable<number>, min?: number, max?: number) => string;
5
39
  export declare const barHorizontal: (width: number, x: number, min?: number, max?: number) => string;
6
40
  export declare const barVertical: (height: number, x: number, min?: number, max?: number, delim?: string) => string;
7
41
  //# sourceMappingURL=bars.d.ts.map
package/bars.js CHANGED
@@ -8,7 +8,7 @@ import { map } from "@thi.ng/transducers/map";
8
8
  import { max as $max } from "@thi.ng/transducers/max";
9
9
  import { min as $min } from "@thi.ng/transducers/min";
10
10
  import { BARS_H, BARS_V } from "./api.js";
11
- const barChartHLines = (height, vals, min, max) => {
11
+ const barChartVLines = (height, vals, min, max) => {
12
12
  const $vals = ensureArray(vals);
13
13
  min = min !== void 0 ? min : $min($vals);
14
14
  max = max !== void 0 ? max : $max($vals);
@@ -24,14 +24,14 @@ const barChartHLines = (height, vals, min, max) => {
24
24
  }
25
25
  return res;
26
26
  };
27
- const barChartHStr = (height, vals, min, max) => barChartHLines(height, vals, min, max).join("\n");
28
- const barChartVLines = (width, vals, min, max) => {
27
+ const barChartVStr = (height, vals, min, max) => barChartVLines(height, vals, min, max).join("\n");
28
+ const barChartHLines = (width, vals, min, max) => {
29
29
  const $vals = ensureArray(vals);
30
30
  min = min !== void 0 ? min : $min($vals);
31
31
  max = max !== void 0 ? max : $max($vals);
32
32
  return [...map((x) => barHorizontal(width, x, min, max), $vals)];
33
33
  };
34
- const barChartVStr = (width, vals, min, max) => barChartVLines(width, vals, min, max).join("\n");
34
+ const barChartHStr = (width, vals, min, max) => barChartHLines(width, vals, min, max).join("\n");
35
35
  const barHorizontal = (width, x, min = 0, max = 1) => bar(BARS_H, width, false, x, min, max, "");
36
36
  const barVertical = (height, x, min = 0, max = 1, delim = "\n") => bar(BARS_V, height, true, x, min, max, delim);
37
37
  const bar = (chars, size, left, x, min, max, delim) => {
package/canvas.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { type Fn0, type ICopy, type IGrid2D, type NumOrString } from "@thi.ng/api";
1
+ import { type Fn0, type ICopy, type IGrid2D, type NumOrString, type IEmpty, type IClear } from "@thi.ng/api";
2
2
  import { type ClipRect, type StrokeStyle } from "./api.js";
3
- export declare class Canvas implements ICopy<Canvas>, IGrid2D<Uint32Array, number> {
3
+ export declare class Canvas implements IClear, ICopy<Canvas>, IEmpty<Canvas>, IGrid2D<Uint32Array, number> {
4
4
  data: Uint32Array;
5
5
  size: [number, number];
6
6
  stride: [number, number];
@@ -14,6 +14,8 @@ export declare class Canvas implements ICopy<Canvas>, IGrid2D<Uint32Array, numbe
14
14
  get offset(): number;
15
15
  get dim(): 2;
16
16
  copy(): Canvas;
17
+ empty(): Canvas;
18
+ clear(): void;
17
19
  order(): number[];
18
20
  includes(d0: number, d1: number): boolean;
19
21
  indexAt(d0: number, d1: number): number;
package/canvas.js CHANGED
@@ -62,6 +62,12 @@ let Canvas = class {
62
62
  res.clipRects = this.clipRects.slice();
63
63
  return res;
64
64
  }
65
+ empty() {
66
+ return new Canvas(this.width, this.height);
67
+ }
68
+ clear() {
69
+ this.data.fill(32);
70
+ }
65
71
  // @ts-ignore mixin
66
72
  order() {
67
73
  }
package/image.d.ts CHANGED
@@ -1,11 +1,18 @@
1
- import type { NumOrString, UIntArray } from "@thi.ng/api";
2
- import { type ImageOpts } from "./api.js";
1
+ import type { FnN4, NumOrString, UIntArray } from "@thi.ng/api";
2
+ import { type BlendFn, type ImageOpts } from "./api.js";
3
3
  import { Canvas } from "./canvas.js";
4
- export declare const blit: (dest: Canvas, x: number, y: number, src: Canvas) => void;
4
+ /**
5
+ *
6
+ * @param dest
7
+ * @param src
8
+ * @param x
9
+ * @param y
10
+ */
11
+ export declare const blit: (dest: Canvas, src: Canvas, x?: number, y?: number) => void;
5
12
  /**
6
13
  * Similar to {@link blit}. Pastes `src` {@link Canvas} into `dest` at given
7
14
  * position and uses `mask` to exclude pixels from being copied (and therefore
8
- * achieve a form of 1bit transparency, similar to GIFs), i.e. only non-`mask`
15
+ * achieve a form of on/off transparency, similar to GIFs), i.e. only non-`mask`
9
16
  * pixels/chars will be copied. Supports region clipping.
10
17
  *
11
18
  * @example
@@ -40,12 +47,30 @@ export declare const blit: (dest: Canvas, x: number, y: number, src: Canvas) =>
40
47
  * ```
41
48
  *
42
49
  * @param dest
50
+ * @param src
43
51
  * @param x
44
52
  * @param y
45
- * @param src
46
53
  * @param mask
47
54
  */
48
- export declare const blitMask: (dest: Canvas, x: number, y: number, src: Canvas, mask?: NumOrString) => void;
55
+ export declare const blitMask: (dest: Canvas, src: Canvas, x?: number, y?: number, mask?: NumOrString) => void;
56
+ export declare const blitBarsV: (dest: Canvas, src: Canvas, x?: number, y?: number, blend?: BlendFn) => void;
57
+ /**
58
+ * Blending function for {@link blendBarsVAdd}. Additive blending for vertical
59
+ * box drawing characters.
60
+ *
61
+ * @param a
62
+ * @param b
63
+ */
64
+ export declare const blendBarsVAdd: FnN4;
65
+ /**
66
+ * @remarks
67
+ * Lookups in this index are symmetrical (see {@link __blend}). Grays are
68
+ * handled via the last group of entries.
69
+ *
70
+ * The order of entries in each group should be B,G,R,C,M,Y, followed by light
71
+ * versions (in same order)
72
+ */
73
+ export declare const BLEND_ADD: Record<number, Record<number, number>>;
49
74
  export declare const resize: (canvas: Canvas, newWidth: number, newHeight: number) => void;
50
75
  export declare const extract: (canvas: Canvas, x: number, y: number, w: number, h: number) => Canvas;
51
76
  /**
package/image.js CHANGED
@@ -1,13 +1,32 @@
1
- import { blit1d } from "@thi.ng/arrays/blit";
1
+ import { blit1d, blitPred1d } from "@thi.ng/arrays/blit";
2
2
  import { peek } from "@thi.ng/arrays/peek";
3
3
  import { isNumber } from "@thi.ng/checks/is-number";
4
4
  import { clamp0 } from "@thi.ng/math/interval";
5
+ import {
6
+ FG_BLUE,
7
+ FG_CYAN,
8
+ FG_GRAY,
9
+ FG_GREEN,
10
+ FG_LIGHT_BLUE,
11
+ FG_LIGHT_CYAN,
12
+ FG_LIGHT_GRAY,
13
+ FG_LIGHT_GREEN,
14
+ FG_LIGHT_MAGENTA,
15
+ FG_LIGHT_RED,
16
+ FG_LIGHT_YELLOW,
17
+ FG_MAGENTA,
18
+ FG_RED,
19
+ FG_YELLOW,
20
+ FG_WHITE
21
+ } from "@thi.ng/text-format";
5
22
  import { FMT_ANSI565 } from "@thi.ng/text-format/ansi";
6
- import { SHADES_BLOCK } from "./api.js";
23
+ import {
24
+ SHADES_BLOCK
25
+ } from "./api.js";
7
26
  import { Canvas, canvas } from "./canvas.js";
8
27
  import { formatCanvas } from "./format.js";
9
28
  import { charCode, intersectRect } from "./utils.js";
10
- const blit = (dest, x, y, src) => {
29
+ const __initBlit = (dest, x, y, src) => {
11
30
  x |= 0;
12
31
  y |= 0;
13
32
  const { data: sbuf, width: sw, height: sh } = src;
@@ -26,31 +45,24 @@ const blit = (dest, x, y, src) => {
26
45
  return;
27
46
  const sx = clamp0(x1 - x);
28
47
  const sy = clamp0(y1 - y);
48
+ return { sbuf, dbuf, sw, dw, x, y, x1, y1, y2, iw, ih, sx, sy };
49
+ };
50
+ const blit = (dest, src, x = 0, y = 0) => {
51
+ const state = __initBlit(dest, x, y, src);
52
+ if (!state)
53
+ return;
54
+ const { sbuf, dbuf, x1, y1, y2, sx, sy, iw, sw, dw } = state;
29
55
  for (let yy = sy, dy = y1; dy < y2; yy++, dy++) {
30
56
  let sidx = sx + yy * sw;
31
57
  let didx = x1 + dy * dw;
32
58
  dbuf.set(sbuf.subarray(sidx, sidx + iw), didx);
33
59
  }
34
60
  };
35
- const blitMask = (dest, x, y, src, mask = 32) => {
36
- x |= 0;
37
- y |= 0;
38
- const { data: sbuf, width: sw, height: sh } = src;
39
- const { data: dbuf, width: dw } = dest;
40
- const {
41
- x1,
42
- y1,
43
- y2,
44
- w: iw,
45
- h: ih
46
- } = intersectRect(
47
- { x1: x, y1: y, x2: x + sw, y2: y + sh, w: sw, h: sh },
48
- peek(dest.clipRects)
49
- );
50
- if (!iw || !ih)
61
+ const blitMask = (dest, src, x = 0, y = 0, mask = 32) => {
62
+ const state = __initBlit(dest, x, y, src);
63
+ if (!state)
51
64
  return;
52
- const sx = clamp0(x1 - x);
53
- const sy = clamp0(y1 - y);
65
+ const { sbuf, dbuf, x1, y1, y2, sx, sy, iw, sw, dw } = state;
54
66
  mask = charCode(mask, 0);
55
67
  for (let yy = sy, dy = y1; dy < y2; yy++, dy++) {
56
68
  let sidx = sx + yy * sw;
@@ -58,12 +70,192 @@ const blitMask = (dest, x, y, src, mask = 32) => {
58
70
  blit1d(dbuf, didx, sbuf.subarray(sidx, sidx + iw), mask);
59
71
  }
60
72
  };
73
+ const blitBarsV = (dest, src, x = 0, y = 0, blend = blendBarsVAdd) => {
74
+ const state = __initBlit(dest, x, y, src);
75
+ if (!state)
76
+ return;
77
+ const { sbuf, dbuf, x1, y1, y2, sx, sy, iw, sw, dw } = state;
78
+ for (let yy = sy, dy = y1; dy < y2; yy++, dy++) {
79
+ let sidx = sx + yy * sw;
80
+ let didx = x1 + dy * dw;
81
+ blitPred1d(dbuf, didx, sbuf.subarray(sidx, sidx + iw), (a, b, x2) => {
82
+ const ac = a & 65535;
83
+ return ac === 32 ? void 0 : ac > 9600 && ac < 9609 ? blend(a, b, x2, yy) : a;
84
+ });
85
+ }
86
+ };
87
+ const blendBarsVAdd = (a, b) => {
88
+ const ac = a & 65535;
89
+ const fmtA = a >> 16 & 31;
90
+ const fgA = fmtA & 31;
91
+ const bc = b & 65535;
92
+ const fmtB = b >> 16;
93
+ const bgB = fmtB >> 5;
94
+ const fgB = fmtB & 31;
95
+ let col;
96
+ let col2;
97
+ if (bc === 32) {
98
+ col = __blend(fgA, bgB) || fgA;
99
+ return ac == 9608 ? col << 21 | 32 : bgB << 21 | col << 16 | ac;
100
+ }
101
+ if (ac <= bc) {
102
+ col2 = bc > 9604 ? fgB : bgB;
103
+ col = __blend(fgA, col2);
104
+ return col2 << 21 | col << 16 | ac + bc >> 1;
105
+ } else {
106
+ col = __blend(fgA, fgB) || fgA;
107
+ col2 = __blend(fgA, bgB) || fgA;
108
+ return col2 << 21 | col << 16 | bc;
109
+ }
110
+ };
111
+ const __blend = (a, b) => BLEND_ADD[b]?.[a] || BLEND_ADD[a]?.[b] || 0;
112
+ const BLEND_ADD = {
113
+ // primary
114
+ [FG_BLUE]: {
115
+ [FG_BLUE]: FG_LIGHT_BLUE,
116
+ [FG_GREEN]: FG_CYAN,
117
+ [FG_RED]: FG_MAGENTA,
118
+ [FG_CYAN]: FG_LIGHT_CYAN,
119
+ [FG_MAGENTA]: FG_LIGHT_MAGENTA,
120
+ [FG_YELLOW]: FG_LIGHT_GRAY,
121
+ [FG_LIGHT_BLUE]: FG_LIGHT_CYAN,
122
+ [FG_LIGHT_GREEN]: FG_LIGHT_CYAN,
123
+ [FG_LIGHT_RED]: FG_LIGHT_MAGENTA,
124
+ [FG_LIGHT_CYAN]: FG_LIGHT_GRAY,
125
+ [FG_LIGHT_MAGENTA]: FG_LIGHT_GRAY,
126
+ [FG_LIGHT_YELLOW]: FG_WHITE
127
+ },
128
+ [FG_GREEN]: {
129
+ [FG_GREEN]: FG_LIGHT_GREEN,
130
+ [FG_RED]: FG_YELLOW,
131
+ [FG_CYAN]: FG_LIGHT_CYAN,
132
+ [FG_MAGENTA]: FG_LIGHT_GRAY,
133
+ [FG_YELLOW]: FG_LIGHT_YELLOW,
134
+ [FG_LIGHT_BLUE]: FG_LIGHT_CYAN,
135
+ [FG_LIGHT_GREEN]: FG_LIGHT_GRAY,
136
+ [FG_LIGHT_RED]: FG_LIGHT_YELLOW,
137
+ [FG_LIGHT_CYAN]: FG_LIGHT_GRAY,
138
+ [FG_LIGHT_MAGENTA]: FG_LIGHT_GRAY,
139
+ [FG_LIGHT_YELLOW]: FG_WHITE
140
+ },
141
+ [FG_RED]: {
142
+ [FG_RED]: FG_LIGHT_RED,
143
+ [FG_CYAN]: FG_LIGHT_GRAY,
144
+ [FG_MAGENTA]: FG_LIGHT_MAGENTA,
145
+ [FG_YELLOW]: FG_LIGHT_YELLOW,
146
+ [FG_LIGHT_BLUE]: FG_LIGHT_MAGENTA,
147
+ [FG_LIGHT_GREEN]: FG_LIGHT_YELLOW,
148
+ [FG_LIGHT_RED]: FG_LIGHT_GRAY,
149
+ [FG_LIGHT_CYAN]: FG_LIGHT_GRAY,
150
+ [FG_LIGHT_MAGENTA]: FG_LIGHT_GRAY,
151
+ [FG_LIGHT_YELLOW]: FG_WHITE
152
+ },
153
+ // secondary
154
+ [FG_CYAN]: {
155
+ [FG_CYAN]: FG_LIGHT_CYAN,
156
+ [FG_MAGENTA]: FG_LIGHT_GRAY,
157
+ [FG_YELLOW]: FG_LIGHT_GRAY,
158
+ [FG_LIGHT_BLUE]: FG_LIGHT_CYAN,
159
+ [FG_LIGHT_GREEN]: FG_LIGHT_CYAN,
160
+ [FG_LIGHT_RED]: FG_LIGHT_GRAY,
161
+ [FG_LIGHT_CYAN]: FG_LIGHT_GRAY,
162
+ [FG_LIGHT_MAGENTA]: FG_LIGHT_GRAY,
163
+ [FG_LIGHT_YELLOW]: FG_WHITE
164
+ },
165
+ [FG_MAGENTA]: {
166
+ [FG_MAGENTA]: FG_LIGHT_MAGENTA,
167
+ [FG_YELLOW]: FG_LIGHT_GRAY,
168
+ [FG_LIGHT_BLUE]: FG_LIGHT_MAGENTA,
169
+ [FG_LIGHT_GREEN]: FG_LIGHT_GRAY,
170
+ [FG_LIGHT_RED]: FG_LIGHT_GRAY,
171
+ [FG_LIGHT_CYAN]: FG_LIGHT_GRAY,
172
+ [FG_LIGHT_MAGENTA]: FG_LIGHT_GRAY,
173
+ [FG_LIGHT_YELLOW]: FG_WHITE
174
+ },
175
+ [FG_YELLOW]: {
176
+ [FG_YELLOW]: FG_LIGHT_YELLOW,
177
+ [FG_LIGHT_BLUE]: FG_LIGHT_GRAY,
178
+ [FG_LIGHT_GREEN]: FG_LIGHT_GRAY,
179
+ [FG_LIGHT_RED]: FG_LIGHT_GRAY,
180
+ [FG_LIGHT_CYAN]: FG_LIGHT_GRAY,
181
+ [FG_LIGHT_MAGENTA]: FG_LIGHT_GRAY,
182
+ [FG_LIGHT_YELLOW]: FG_WHITE
183
+ },
184
+ // light primary
185
+ [FG_LIGHT_BLUE]: {
186
+ [FG_LIGHT_BLUE]: FG_LIGHT_GRAY
187
+ },
188
+ [FG_LIGHT_GREEN]: {
189
+ [FG_LIGHT_GREEN]: FG_LIGHT_GRAY
190
+ },
191
+ [FG_LIGHT_RED]: {
192
+ [FG_LIGHT_RED]: FG_LIGHT_GRAY
193
+ },
194
+ // light secondary
195
+ [FG_LIGHT_CYAN]: {
196
+ [FG_LIGHT_CYAN]: FG_LIGHT_GRAY
197
+ },
198
+ [FG_LIGHT_MAGENTA]: {
199
+ [FG_LIGHT_MAGENTA]: FG_LIGHT_GRAY
200
+ },
201
+ [FG_LIGHT_YELLOW]: {
202
+ [FG_LIGHT_YELLOW]: FG_WHITE
203
+ },
204
+ // grays
205
+ [FG_GRAY]: {
206
+ [FG_BLUE]: FG_LIGHT_BLUE,
207
+ [FG_GREEN]: FG_LIGHT_GREEN,
208
+ [FG_RED]: FG_LIGHT_RED,
209
+ [FG_CYAN]: FG_LIGHT_CYAN,
210
+ [FG_MAGENTA]: FG_LIGHT_MAGENTA,
211
+ [FG_YELLOW]: FG_LIGHT_YELLOW,
212
+ [FG_GRAY]: FG_LIGHT_GRAY,
213
+ [FG_LIGHT_BLUE]: FG_LIGHT_GRAY,
214
+ [FG_LIGHT_GREEN]: FG_LIGHT_GRAY,
215
+ [FG_LIGHT_RED]: FG_LIGHT_GRAY,
216
+ [FG_LIGHT_CYAN]: FG_LIGHT_GRAY,
217
+ [FG_LIGHT_MAGENTA]: FG_LIGHT_GRAY,
218
+ [FG_LIGHT_YELLOW]: FG_LIGHT_GRAY
219
+ },
220
+ [FG_LIGHT_GRAY]: {
221
+ [FG_BLUE]: FG_WHITE,
222
+ [FG_GREEN]: FG_WHITE,
223
+ [FG_RED]: FG_WHITE,
224
+ [FG_CYAN]: FG_WHITE,
225
+ [FG_MAGENTA]: FG_WHITE,
226
+ [FG_YELLOW]: FG_WHITE,
227
+ [FG_GRAY]: FG_WHITE,
228
+ [FG_LIGHT_BLUE]: FG_WHITE,
229
+ [FG_LIGHT_GREEN]: FG_WHITE,
230
+ [FG_LIGHT_RED]: FG_WHITE,
231
+ [FG_LIGHT_CYAN]: FG_WHITE,
232
+ [FG_LIGHT_MAGENTA]: FG_WHITE,
233
+ [FG_LIGHT_YELLOW]: FG_WHITE,
234
+ [FG_LIGHT_GRAY]: FG_WHITE
235
+ },
236
+ [FG_WHITE]: {
237
+ [FG_BLUE]: FG_WHITE,
238
+ [FG_CYAN]: FG_WHITE,
239
+ [FG_GRAY]: FG_WHITE,
240
+ [FG_GREEN]: FG_WHITE,
241
+ [FG_RED]: FG_WHITE,
242
+ [FG_MAGENTA]: FG_WHITE,
243
+ [FG_YELLOW]: FG_WHITE,
244
+ [FG_LIGHT_BLUE]: FG_WHITE,
245
+ [FG_LIGHT_CYAN]: FG_WHITE,
246
+ [FG_LIGHT_GRAY]: FG_WHITE,
247
+ [FG_LIGHT_GREEN]: FG_WHITE,
248
+ [FG_LIGHT_MAGENTA]: FG_WHITE,
249
+ [FG_LIGHT_RED]: FG_WHITE,
250
+ [FG_LIGHT_YELLOW]: FG_WHITE
251
+ }
252
+ };
61
253
  const resize = (canvas2, newWidth, newHeight) => {
62
254
  if (canvas2.width === newWidth && canvas2.height === newHeight)
63
255
  return;
64
256
  const dest = new Canvas(newWidth, newHeight);
65
257
  dest.data.fill(charCode(32, canvas2.format));
66
- blit(dest, 0, 0, canvas2);
258
+ blit(dest, canvas2);
67
259
  canvas2.data = dest.data;
68
260
  canvas2.size[0] = newWidth;
69
261
  canvas2.size[1] = newHeight;
@@ -80,7 +272,7 @@ const resize = (canvas2, newWidth, newHeight) => {
80
272
  };
81
273
  const extract = (canvas2, x, y, w, h) => {
82
274
  const dest = new Canvas(w, h, canvas2.format, peek(canvas2.styles));
83
- blit(dest, -x, -y, canvas2);
275
+ blit(dest, canvas2, -x, -y);
84
276
  return dest;
85
277
  };
86
278
  const scrollV = (canvas2, dy, clear = 32) => {
@@ -241,7 +433,10 @@ const imgRect = (canvas2, x, y, w, h) => {
241
433
  return rect;
242
434
  };
243
435
  export {
436
+ BLEND_ADD,
437
+ blendBarsVAdd,
244
438
  blit,
439
+ blitBarsV,
245
440
  blitMask,
246
441
  extract,
247
442
  image,
package/index.d.ts CHANGED
@@ -6,6 +6,7 @@ export * from "./format.js";
6
6
  export * from "./hvline.js";
7
7
  export * from "./image.js";
8
8
  export * from "./line.js";
9
+ export * from "./plot.js";
9
10
  export * from "./rect.js";
10
11
  export * from "./table.js";
11
12
  export * from "./text.js";
package/index.js CHANGED
@@ -6,6 +6,7 @@ export * from "./format.js";
6
6
  export * from "./hvline.js";
7
7
  export * from "./image.js";
8
8
  export * from "./line.js";
9
+ export * from "./plot.js";
9
10
  export * from "./rect.js";
10
11
  export * from "./table.js";
11
12
  export * from "./text.js";
package/line.d.ts CHANGED
@@ -13,6 +13,4 @@ import { Canvas } from "./canvas.js";
13
13
  * @param char -
14
14
  */
15
15
  export declare const line: (canvas: Canvas, ax: number, ay: number, bx: number, by: number, char?: NumOrString, format?: number) => void;
16
- export declare const lineChart: (canvas: Canvas, x: number, y: number, height: number, vals: Iterable<number>, min?: number, max?: number, format?: number) => void;
17
- export declare const lineChartStr: (height: number, vals: Iterable<number>, min?: number, max?: number) => string;
18
16
  //# sourceMappingURL=line.d.ts.map
package/line.js CHANGED
@@ -1,12 +1,6 @@
1
- import { ensureArray } from "@thi.ng/arrays/ensure-array";
2
1
  import { peek } from "@thi.ng/arrays/peek";
3
2
  import { liangBarsky2Raw } from "@thi.ng/geom-clip-line/liang-barsky";
4
- import { fitClamped } from "@thi.ng/math/fit";
5
- import { minMax } from "@thi.ng/math/interval";
6
- import { max as $max } from "@thi.ng/transducers/max";
7
- import { min as $min } from "@thi.ng/transducers/min";
8
3
  import { Canvas } from "./canvas.js";
9
- import { formatCanvas } from "./format.js";
10
4
  import { charCode } from "./utils.js";
11
5
  const line = (canvas, ax, ay, bx, by, char, format = canvas.format) => {
12
6
  const { x1, y1, x2, y2 } = peek(canvas.clipRects);
@@ -42,40 +36,6 @@ const line = (canvas, ax, ay, bx, by, char, format = canvas.format) => {
42
36
  }
43
37
  }
44
38
  };
45
- const lineChart = (canvas, x, y, height, vals, min, max, format = canvas.format) => {
46
- const $vals = ensureArray(vals);
47
- min = min !== void 0 ? min : $min($vals);
48
- max = max !== void 0 ? max : $max($vals);
49
- height--;
50
- format <<= 16;
51
- const { x1, x2 } = peek(canvas.clipRects);
52
- for (let i = 0, n = $vals.length - 1; i < n; i++) {
53
- const xx = x + i;
54
- if (xx < x1)
55
- continue;
56
- if (xx > x2)
57
- break;
58
- const ya = Math.round(fitClamped($vals[i], min, max, height, 0)) + y;
59
- const yb = Math.round(fitClamped($vals[i + 1], min, max, height, 0)) + y;
60
- if (ya === yb) {
61
- canvas.setAt(xx, ya, 9472 | format);
62
- } else {
63
- let [y1, y2] = minMax(ya, yb);
64
- while (++y1 < y2)
65
- canvas.setAt(xx, y1, 9474 | format);
66
- canvas.setAt(xx, ya, (ya < yb ? 9582 : 9583) | format);
67
- canvas.setAt(xx, yb, (ya < yb ? 9584 : 9581) | format);
68
- }
69
- }
70
- };
71
- const lineChartStr = (height, vals, min, max) => {
72
- const $vals = ensureArray(vals);
73
- const surf = new Canvas($vals.length, height);
74
- lineChart(surf, 0, 0, height, $vals, min, max);
75
- return formatCanvas(surf);
76
- };
77
39
  export {
78
- line,
79
- lineChart,
80
- lineChartStr
40
+ line
81
41
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@thi.ng/text-canvas",
3
- "version": "2.6.37",
4
- "description": "Text based canvas, drawing, tables with arbitrary formatting (incl. ANSI/HTML)",
3
+ "version": "3.0.0",
4
+ "description": "Text based canvas, drawing, plotting, tables with arbitrary formatting (incl. ANSI/HTML)",
5
5
  "type": "module",
6
6
  "module": "./index.js",
7
7
  "typings": "./index.d.ts",
@@ -36,14 +36,14 @@
36
36
  },
37
37
  "dependencies": {
38
38
  "@thi.ng/api": "^8.9.23",
39
- "@thi.ng/arrays": "^2.7.20",
39
+ "@thi.ng/arrays": "^2.8.0",
40
40
  "@thi.ng/checks": "^3.4.23",
41
41
  "@thi.ng/errors": "^2.4.16",
42
- "@thi.ng/geom-clip-line": "^2.3.64",
43
- "@thi.ng/math": "^5.9.1",
42
+ "@thi.ng/geom-clip-line": "^2.3.66",
43
+ "@thi.ng/math": "^5.10.0",
44
44
  "@thi.ng/strings": "^3.7.14",
45
- "@thi.ng/text-format": "^2.0.10",
46
- "@thi.ng/transducers": "^8.9.1"
45
+ "@thi.ng/text-format": "^2.1.0",
46
+ "@thi.ng/transducers": "^8.9.3"
47
47
  },
48
48
  "devDependencies": {
49
49
  "@microsoft/api-extractor": "^7.40.1",
@@ -68,18 +68,23 @@
68
68
  "braille",
69
69
  "canvas",
70
70
  "circle",
71
+ "clipping",
71
72
  "color",
72
73
  "datastructure",
74
+ "dataviz",
73
75
  "drawing",
74
76
  "format",
75
77
  "image",
76
78
  "line",
79
+ "plot",
77
80
  "rect",
78
81
  "rgb",
82
+ "shape",
79
83
  "table",
80
84
  "text",
81
85
  "theme",
82
86
  "typescript",
87
+ "visualization",
83
88
  "wordwrap"
84
89
  ],
85
90
  "publishConfig": {
@@ -120,6 +125,9 @@
120
125
  "./line": {
121
126
  "default": "./line.js"
122
127
  },
128
+ "./plot": {
129
+ "default": "./plot.js"
130
+ },
123
131
  "./rect": {
124
132
  "default": "./rect.js"
125
133
  },
@@ -139,5 +147,5 @@
139
147
  ],
140
148
  "year": 2020
141
149
  },
142
- "gitHead": "e5e7d5c6ed2eadee7a91d59cbd0c86ce880ab1c5\n"
150
+ "gitHead": "ea2ec2e4f14c572bbfac00c43953a6c4033da09e\n"
143
151
  }
package/plot.d.ts ADDED
@@ -0,0 +1,17 @@
1
+ import type { NumericArray } from "@thi.ng/api";
2
+ import type { BlendFn } from "./api.js";
3
+ import { Canvas } from "./canvas.js";
4
+ export interface PlotBarsVOpts {
5
+ min: number;
6
+ max: number;
7
+ blend?: BlendFn;
8
+ }
9
+ interface PlotSpec {
10
+ data: NumericArray;
11
+ color: number;
12
+ }
13
+ export declare const plotBarChartV: (canv: Canvas, opts: PlotBarsVOpts, ...plots: PlotSpec[]) => Canvas;
14
+ export declare const plotLineChart: (canvas: Canvas, x: number, y: number, height: number, vals: Iterable<number>, min?: number, max?: number, format?: number) => void;
15
+ export declare const lineChartStr: (height: number, vals: Iterable<number>, min?: number, max?: number) => string;
16
+ export {};
17
+ //# sourceMappingURL=plot.d.ts.map
package/plot.js ADDED
@@ -0,0 +1,64 @@
1
+ import { ensureArray } from "@thi.ng/arrays/ensure-array";
2
+ import { peek } from "@thi.ng/arrays/peek";
3
+ import { fitClamped } from "@thi.ng/math/fit";
4
+ import { minMax } from "@thi.ng/math/interval";
5
+ import { max as $max } from "@thi.ng/transducers/max";
6
+ import { min as $min } from "@thi.ng/transducers/min";
7
+ import { barChartVLines } from "./bars.js";
8
+ import { Canvas } from "./canvas.js";
9
+ import { formatCanvas } from "./format.js";
10
+ import { blendBarsVAdd, blitBarsV } from "./image.js";
11
+ import { textLines } from "./text.js";
12
+ const plotBarChartV = (canv, opts, ...plots) => {
13
+ const channel = canv.empty();
14
+ const blend = opts.blend || blendBarsVAdd;
15
+ for (let plot of plots) {
16
+ channel.clear();
17
+ textLines(
18
+ channel,
19
+ 0,
20
+ 0,
21
+ barChartVLines(channel.height, plot.data, opts.min, opts.max),
22
+ plot.color
23
+ );
24
+ blitBarsV(canv, channel, 0, 0, blend);
25
+ }
26
+ return canv;
27
+ };
28
+ const plotLineChart = (canvas, x, y, height, vals, min, max, format = canvas.format) => {
29
+ const $vals = ensureArray(vals);
30
+ min = min !== void 0 ? min : $min($vals);
31
+ max = max !== void 0 ? max : $max($vals);
32
+ height--;
33
+ format <<= 16;
34
+ const { x1, x2 } = peek(canvas.clipRects);
35
+ for (let i = 0, n = $vals.length - 1; i < n; i++) {
36
+ const xx = x + i;
37
+ if (xx < x1)
38
+ continue;
39
+ if (xx > x2)
40
+ break;
41
+ const ya = Math.round(fitClamped($vals[i], min, max, height, 0)) + y;
42
+ const yb = Math.round(fitClamped($vals[i + 1], min, max, height, 0)) + y;
43
+ if (ya === yb) {
44
+ canvas.setAt(xx, ya, 9472 | format);
45
+ } else {
46
+ let [y1, y2] = minMax(ya, yb);
47
+ while (++y1 < y2)
48
+ canvas.setAt(xx, y1, 9474 | format);
49
+ canvas.setAt(xx, ya, (ya < yb ? 9582 : 9583) | format);
50
+ canvas.setAt(xx, yb, (ya < yb ? 9584 : 9581) | format);
51
+ }
52
+ }
53
+ };
54
+ const lineChartStr = (height, vals, min, max) => {
55
+ const $vals = ensureArray(vals);
56
+ const surf = new Canvas($vals.length, height);
57
+ plotLineChart(surf, 0, 0, height, $vals, min, max);
58
+ return formatCanvas(surf);
59
+ };
60
+ export {
61
+ lineChartStr,
62
+ plotBarChartV,
63
+ plotLineChart
64
+ };