@funnest/charts 0.6.25 → 0.6.27

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/chart.d.ts CHANGED
@@ -2,7 +2,7 @@ import { type Locale } from './model/time-format';
2
2
  import { TimeScale, type IndexRange, type TimeScaleOptions } from './model/time-scale';
3
3
  import type { IndicatorDefinition, IndicatorParams, IndicatorType } from './indicators/definitions';
4
4
  import { type IndicatorConfig, type IndicatorPatch } from './indicators/manager';
5
- import { type Drawing, type DrawingDefinition, type DrawingType } from './objects/drawings';
5
+ import { type Drawing, type DrawingDefaults, type DrawingDefinition, type DrawingType } from './objects/drawings';
6
6
  import { type AlertLineOptions, type ExecutionMarker, type LineHandle, type OrderLineOptions, type PositionLineOptions, type TradingLabelOptions } from './objects/trading';
7
7
  import { type ChartSettings, type ChartState } from './settings';
8
8
  import { type TradingAdapter } from './objects/trading-sync';
@@ -77,8 +77,14 @@ export interface ChartEvents {
77
77
  loadingChange: boolean;
78
78
  /** Drawings were created, edited or removed (not fired during a live drag). */
79
79
  drawingsChange: Drawing[];
80
- /** The armed drawing tool changed (null = cursor). Tools disarm after one drawing. */
80
+ /** The armed drawing tool changed (null = cursor). Tools disarm after one drawing (unless `keepDrawingTool`). */
81
81
  drawingToolChange: DrawingType | null;
82
+ /** All drawings hidden (true) or shown again. */
83
+ drawingsHiddenChange: boolean;
84
+ /** 연속 그리기 turned on or off. */
85
+ keepDrawingToolChange: boolean;
86
+ /** A tool's last color / style changed (save it to restore with `setDrawingDefaults`). */
87
+ drawingDefaultsChange: DrawingDefaults;
82
88
  selectionChange: Drawing | null;
83
89
  magnetChange: boolean;
84
90
  chartTypeChange: ChartType;
@@ -358,6 +364,16 @@ export declare class Chart {
358
364
  /** Arms a drawing tool (null = cursor). It disarms itself after one drawing is placed. */
359
365
  setDrawingTool(tool: DrawingType | null): void;
360
366
  get drawingTool(): DrawingType | null;
367
+ /** Hides every drawing (not drawn, not clickable) or shows them again. Arming a tool shows them. */
368
+ setDrawingsHidden(hidden: boolean): void;
369
+ get drawingsHidden(): boolean;
370
+ /** 연속 그리기: the armed tool stays armed after each drawing (Esc or the cursor drops it). */
371
+ setKeepDrawingTool(on: boolean): void;
372
+ get keepDrawingTool(): boolean;
373
+ /** Each tool's last color / line / text style; new drawings of a tool start with it. */
374
+ getDrawingDefaults(): DrawingDefaults;
375
+ /** Restores them (e.g. from storage). Doesn't fire drawingDefaultsChange. */
376
+ setDrawingDefaults(defaults: DrawingDefaults): void;
361
377
  /** Magnet mode: drawing points snap to the nearest open/high/low/close of the bar. */
362
378
  setMagnet(on: boolean): void;
363
379
  get magnetEnabled(): boolean;
package/dist/index.d.ts CHANGED
@@ -14,7 +14,7 @@ export type { FormatPriceOptions } from './price';
14
14
  export { decimalsOf, formatCompact, formatPrice, krxTickSize, resolveTickSize, roundToTick } from './price';
15
15
  export { isIntradayResolution, resolutionLabel, resolutionToMs } from './resolution';
16
16
  export type { ChartType } from './render/renderer';
17
- export type { BuiltinDrawingType, Drawing, DrawingDefinition, DrawingGeometry, DrawingPoint, DrawingRenderContext, DrawingStyle, DrawingColorSlot, DrawingType, } from './objects/drawings';
17
+ export type { BuiltinDrawingType, Drawing, DrawingDefaults, DrawingDefinition, DrawingGeometry, DrawingPoint, DrawingRenderContext, DrawingStyle, DrawingColorSlot, DrawingType, } from './objects/drawings';
18
18
  export { BUILTIN_DRAWINGS, distToSegment, FIB_LEVELS } from './objects/drawings';
19
19
  export type { ChartOverlay, ObjectRenderContext, PaneCoords } from './objects/types';
20
20
  export type { Painter } from './render/painter';
package/dist/index.js CHANGED
@@ -1312,6 +1312,7 @@ function drawingRegistry(extra = []) {
1312
1312
  for (const def of extra) registry.set(def.type, def);
1313
1313
  return registry;
1314
1314
  }
1315
+ var CLOSE_PX = 7;
1315
1316
  var DrawingManager = class {
1316
1317
  constructor(onChange, registry = drawingRegistry()) {
1317
1318
  this.onChange = onChange;
@@ -1322,6 +1323,11 @@ var DrawingManager = class {
1322
1323
  drawings = [];
1323
1324
  tool = null;
1324
1325
  selectedId = null;
1326
+ /** Every drawing hidden at once (arming a tool shows them again). */
1327
+ hiddenAll = false;
1328
+ /** The tool stays armed after a drawing is placed (연속 그리기). */
1329
+ keepTool = false;
1330
+ defaults = {};
1325
1331
  draft = null;
1326
1332
  freehandLast = null;
1327
1333
  drag = null;
@@ -1333,8 +1339,15 @@ var DrawingManager = class {
1333
1339
  this.tool = tool;
1334
1340
  this.draft = null;
1335
1341
  if (tool) this.selectedId = null;
1342
+ if (tool && this.hiddenAll) this.setHiddenAll(false);
1336
1343
  this.onChange("tool");
1337
1344
  }
1345
+ setHiddenAll(hidden) {
1346
+ if (hidden === this.hiddenAll) return;
1347
+ this.hiddenAll = hidden;
1348
+ if (hidden) this.selectedId = null;
1349
+ this.onChange("hidden");
1350
+ }
1338
1351
  select(id) {
1339
1352
  if (id === this.selectedId) return;
1340
1353
  this.selectedId = id;
@@ -1350,6 +1363,12 @@ var DrawingManager = class {
1350
1363
  const d = this.drawings.find((x) => x.id === id);
1351
1364
  if (!d) return;
1352
1365
  Object.assign(d, patch);
1366
+ if ("color" in patch || patch.style) {
1367
+ const { width, dash, fontSize } = d.style ?? {};
1368
+ const style = Object.fromEntries(Object.entries({ width, dash, fontSize }).filter(([, v]) => v !== void 0));
1369
+ this.defaults = { ...this.defaults, [d.type]: { ...d.color ? { color: d.color } : {}, ...Object.keys(style).length ? { style } : {} } };
1370
+ this.onChange("defaults");
1371
+ }
1353
1372
  this.onChange("commit");
1354
1373
  }
1355
1374
  remove(id) {
@@ -1364,13 +1383,24 @@ var DrawingManager = class {
1364
1383
  this.draft = null;
1365
1384
  this.onChange("commit");
1366
1385
  }
1367
- /** Esc: abort an in-progress drawing, else drop the tool, else deselect. */
1386
+ /** Esc: finish a path that has enough points, else abort an in-progress drawing, else drop the tool, else deselect. */
1368
1387
  cancel() {
1388
+ if (this.finishOpen()) return;
1369
1389
  if (this.draft || this.tool) this.setTool(null);
1370
1390
  else this.select(null);
1371
1391
  }
1392
+ /** Finishes an `open` tool's drawing in progress (Enter); false when there is none or too few points. */
1393
+ finishOpen() {
1394
+ const draft = this.draft;
1395
+ const def = draft && this.registry.get(draft.drawing.type);
1396
+ if (!draft || !def?.open || draft.placed.length < Math.max(2, def.clicks)) return false;
1397
+ draft.drawing.points = draft.placed.map((p) => ({ ...p }));
1398
+ this.finish();
1399
+ return true;
1400
+ }
1372
1401
  // ── Creation ──────────────────────────────────────────────────────
1373
1402
  expand(def, placed, c) {
1403
+ if (def.open) return placed.map((p) => ({ ...p }));
1374
1404
  const full = Array.from({ length: def.clicks }, (_, i) => ({ ...placed[i] ?? placed[placed.length - 1] }));
1375
1405
  return def.create ? def.create(full, c) : full;
1376
1406
  }
@@ -1387,11 +1417,28 @@ var DrawingManager = class {
1387
1417
  }
1388
1418
  if (!this.draft) {
1389
1419
  const placed = [point];
1390
- this.draft = { drawing: { id: newId("d"), type: def.type, points: this.expand(def, placed, c) }, placed };
1391
- if (def.clicks <= 1) this.finish();
1420
+ const last = this.defaults[def.type];
1421
+ this.draft = {
1422
+ drawing: { id: newId("d"), type: def.type, points: this.expand(def, placed, c), ...last?.color ? { color: last.color } : {}, ...last?.style ? { style: { ...last.style } } : {} },
1423
+ placed
1424
+ };
1425
+ if (def.clicks <= 1 && !def.open) this.finish();
1392
1426
  else this.onChange("preview");
1393
1427
  return;
1394
1428
  }
1429
+ if (def.open) {
1430
+ const near = (p) => Math.hypot(c.timeToX(p.time) - x, c.priceToY(p.price) - y) <= CLOSE_PX;
1431
+ const placed = this.draft.placed;
1432
+ if (placed.length >= Math.max(2, def.clicks) && (near(placed[placed.length - 1]) || near(placed[0]))) {
1433
+ this.finishOpen();
1434
+ return;
1435
+ }
1436
+ if (near(placed[placed.length - 1])) return;
1437
+ placed.push(point);
1438
+ this.draft.drawing.points = [...placed.map((p) => ({ ...p })), { ...point }];
1439
+ this.onChange("preview");
1440
+ return;
1441
+ }
1395
1442
  this.draft.placed.push(point);
1396
1443
  this.draft.drawing.points = this.expand(def, this.draft.placed, c);
1397
1444
  if (this.draft.placed.length >= def.clicks) this.finish();
@@ -1410,7 +1457,7 @@ var DrawingManager = class {
1410
1457
  this.onChange("preview");
1411
1458
  return;
1412
1459
  }
1413
- draft.drawing.points = this.expand(def, [...draft.placed, c.pointAt(x, y)], c);
1460
+ draft.drawing.points = def.open ? [...draft.placed.map((p) => ({ ...p })), c.pointAt(x, y)] : this.expand(def, [...draft.placed, c.pointAt(x, y)], c);
1414
1461
  this.onChange("preview");
1415
1462
  }
1416
1463
  /** Pointer released: a drag since the first point places the next one (drag-to-draw). */
@@ -1426,6 +1473,10 @@ var DrawingManager = class {
1426
1473
  const drawing = this.draft.drawing;
1427
1474
  this.draft = null;
1428
1475
  this.drawings.push(drawing);
1476
+ if (this.keepTool) {
1477
+ this.onChange("commit");
1478
+ return;
1479
+ }
1429
1480
  this.selectedId = drawing.id;
1430
1481
  this.tool = null;
1431
1482
  this.onChange("tool");
@@ -1446,8 +1497,10 @@ var DrawingManager = class {
1446
1497
  const handle = this.handles(c, selected).findIndex(([px, py]) => Math.hypot(px - x, py - y) <= HANDLE_RADIUS + tolerance);
1447
1498
  if (handle >= 0) return { kind: "drawing", id: selected.id, part: handle };
1448
1499
  }
1500
+ if (this.hiddenAll) return null;
1449
1501
  for (let i = this.drawings.length - 1; i >= 0; i--) {
1450
1502
  const d = this.drawings[i];
1503
+ if (d.style?.hidden) continue;
1451
1504
  const def = this.registry.get(d.type);
1452
1505
  if (def && def.distance(this.geometry(c, d), x, y) <= tolerance) return { kind: "drawing", id: d.id, part: "body" };
1453
1506
  }
@@ -1481,7 +1534,8 @@ var DrawingManager = class {
1481
1534
  }
1482
1535
  // ── Rendering ─────────────────────────────────────────────────────
1483
1536
  render(rc, badges, bars = [], barIndexAt = () => -1) {
1484
- const all = this.draft ? [...this.drawings, this.draft.drawing] : this.drawings;
1537
+ const shown = this.hiddenAll ? [] : this.drawings.filter((d) => !d.style?.hidden);
1538
+ const all = this.draft ? [...shown, this.draft.drawing] : shown;
1485
1539
  const c = rc.coords;
1486
1540
  for (const d of all) {
1487
1541
  const def = this.registry.get(d.type);
@@ -1919,6 +1973,12 @@ var CHART_THEMES = [
1919
1973
  name: { ko: "\uB525 \uB124\uC774\uBE44", en: "Deep navy" },
1920
1974
  colors: { upColor: "#FF5A5F", downColor: "#4C8DFF", background: "#0F1A2E", gridColor: "#1B2A44", textColor: "#C8D3E6" }
1921
1975
  },
1976
+ {
1977
+ // True black (OLED): the darkest, with a barely-there grid.
1978
+ id: "deep-dark",
1979
+ name: { ko: "\uB525 \uB2E4\uD06C", en: "Deep dark" },
1980
+ colors: { upColor: "#F04452", downColor: "#3182F6", background: "#000000", gridColor: "#121212", textColor: "#B8BCC4" }
1981
+ },
1922
1982
  {
1923
1983
  id: "mono",
1924
1984
  name: { ko: "\uBAA8\uB178", en: "Mono" },
@@ -3024,6 +3084,9 @@ var Chart = class {
3024
3084
  this.recordHistory();
3025
3085
  }
3026
3086
  if (change === "tool") this.emitter.emit("drawingToolChange", this.drawings.tool);
3087
+ if (change === "hidden") this.emitter.emit("drawingsHiddenChange", this.drawings.hiddenAll);
3088
+ if (change === "defaults") this.emitter.emit("drawingDefaultsChange", { ...this.drawings.defaults });
3089
+ if (change === "defaults") return;
3027
3090
  if (change !== "preview") {
3028
3091
  const d = this.drawings.selected;
3029
3092
  this.emitter.emit("selectionChange", d ? { ...d, points: d.points.map((p) => ({ ...p })) } : null);
@@ -3481,6 +3544,30 @@ var Chart = class {
3481
3544
  get drawingTool() {
3482
3545
  return this.drawings.tool;
3483
3546
  }
3547
+ /** Hides every drawing (not drawn, not clickable) or shows them again. Arming a tool shows them. */
3548
+ setDrawingsHidden(hidden) {
3549
+ this.drawings.setHiddenAll(hidden);
3550
+ }
3551
+ get drawingsHidden() {
3552
+ return this.drawings.hiddenAll;
3553
+ }
3554
+ /** 연속 그리기: the armed tool stays armed after each drawing (Esc or the cursor drops it). */
3555
+ setKeepDrawingTool(on) {
3556
+ if (on === this.drawings.keepTool) return;
3557
+ this.drawings.keepTool = on;
3558
+ this.emitter.emit("keepDrawingToolChange", on);
3559
+ }
3560
+ get keepDrawingTool() {
3561
+ return this.drawings.keepTool;
3562
+ }
3563
+ /** Each tool's last color / line / text style; new drawings of a tool start with it. */
3564
+ getDrawingDefaults() {
3565
+ return JSON.parse(JSON.stringify(this.drawings.defaults));
3566
+ }
3567
+ /** Restores them (e.g. from storage). Doesn't fire drawingDefaultsChange. */
3568
+ setDrawingDefaults(defaults) {
3569
+ this.drawings.defaults = JSON.parse(JSON.stringify(defaults ?? {}));
3570
+ }
3484
3571
  /** Magnet mode: drawing points snap to the nearest open/high/low/close of the bar. */
3485
3572
  setMagnet(on) {
3486
3573
  if (on === this.magnet) return;
@@ -4667,6 +4754,8 @@ var Chart = class {
4667
4754
  this.setReplaySelecting(false);
4668
4755
  } else if (e.key === "Escape" && (this.drawings.tool || this.drawings.selectedId)) {
4669
4756
  this.drawings.cancel();
4757
+ } else if (e.key === "Enter" && this.drawings.finishOpen()) {
4758
+ e.preventDefault();
4670
4759
  } else if ((e.key === "Delete" || e.key === "Backspace") && this.drawings.selectedId) {
4671
4760
  e.preventDefault();
4672
4761
  this.drawings.remove(this.drawings.selectedId);
@@ -24,6 +24,8 @@ export interface DrawingStyle {
24
24
  fontSize?: number;
25
25
  /** Can't be dragged or edited until unlocked (still selectable). */
26
26
  locked?: boolean;
27
+ /** Not drawn and can't be hit (a drawings list can show it again). */
28
+ hidden?: boolean;
27
29
  /** Colors of the tool's parts (`DrawingDefinition.colors` keys), where set apart from the rest. */
28
30
  colors?: Record<string, string>;
29
31
  }
@@ -89,8 +91,13 @@ export interface DrawingDefinition {
89
91
  * are listed under it, with a divider between sections. Default: no heading.
90
92
  */
91
93
  section?: Record<Locale, string>;
92
- /** Clicks (or drag ends) needed to place it. */
94
+ /** Clicks (or drag ends) needed to place it; with `open`, the fewest it takes. */
93
95
  clicks: number;
96
+ /**
97
+ * Placed point by point (a path, a polygon): every click adds one, until a double-click (a
98
+ * click on the last point), a click on the first point, Enter or Esc finishes it.
99
+ */
100
+ open?: boolean;
94
101
  /**
95
102
  * Turns the placed click points into the stored points, e.g. a position tool that creates
96
103
  * entry/target/stop from one click. Called for the live preview too. Default: as placed.
@@ -119,7 +126,12 @@ export declare function distToSegment(px: number, py: number, x1: number, y1: nu
119
126
  export declare const BUILTIN_DRAWINGS: readonly DrawingDefinition[];
120
127
  /** Built-ins plus definitions passed to the chart; later ones replace earlier ones of the same type. */
121
128
  export declare function drawingRegistry(extra?: readonly DrawingDefinition[]): Map<string, DrawingDefinition>;
122
- export type DrawingsChange = 'preview' | 'commit' | 'tool' | 'selection';
129
+ export type DrawingsChange = 'preview' | 'commit' | 'tool' | 'selection' | 'defaults' | 'hidden';
130
+ /** The last color and line / text style used per tool type: new drawings of that type start with it. */
131
+ export type DrawingDefaults = Record<string, {
132
+ color?: string;
133
+ style?: Pick<DrawingStyle, 'width' | 'dash' | 'fontSize'>;
134
+ }>;
123
135
  /**
124
136
  * Owns drawings: creation (click-click or drag), selection, handle/body dragging,
125
137
  * hit testing and rendering. Pure state + canvas; pointer routing lives in Interaction.
@@ -131,20 +143,28 @@ export declare class DrawingManager {
131
143
  drawings: Drawing[];
132
144
  tool: DrawingType | null;
133
145
  selectedId: string | null;
146
+ /** Every drawing hidden at once (arming a tool shows them again). */
147
+ hiddenAll: boolean;
148
+ /** The tool stays armed after a drawing is placed (연속 그리기). */
149
+ keepTool: boolean;
150
+ defaults: DrawingDefaults;
134
151
  private draft;
135
152
  private freehandLast;
136
153
  private drag;
137
154
  constructor(onChange: (change: DrawingsChange) => void, registry?: Map<string, DrawingDefinition>);
138
155
  get selected(): Drawing | null;
139
156
  setTool(tool: DrawingType | null): void;
157
+ setHiddenAll(hidden: boolean): void;
140
158
  select(id: string | null): void;
141
159
  /** Drawings of unknown types are kept (and saved) but not shown. */
142
160
  set(drawings: readonly Drawing[]): void;
143
161
  update(id: string, patch: Partial<Omit<Drawing, 'id'>>): void;
144
162
  remove(id: string): void;
145
163
  clear(): void;
146
- /** Esc: abort an in-progress drawing, else drop the tool, else deselect. */
164
+ /** Esc: finish a path that has enough points, else abort an in-progress drawing, else drop the tool, else deselect. */
147
165
  cancel(): void;
166
+ /** Finishes an `open` tool's drawing in progress (Enter); false when there is none or too few points. */
167
+ finishOpen(): boolean;
148
168
  private expand;
149
169
  createAt(c: PaneCoords, x: number, y: number): void;
150
170
  /** Moves the not-yet-placed points (live preview while hovering or dragging). */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@funnest/charts",
3
- "version": "0.6.25",
3
+ "version": "0.6.27",
4
4
  "description": "Canvas charting engine for financial charts",
5
5
  "keywords": [
6
6
  "chart",