@ixfx/components 0.1.5 → 0.2.1

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/bundle/index.js CHANGED
@@ -1,9 +1,10 @@
1
- import { Circles, degreeToRadian, radianArc, radianToDegree, radiansSum } from "ixfx/geometry.js";
1
+ import { Circles, Rects, degreeToRadian, radianArc, radianToDegree, radiansSum } from "ixfx/geometry.js";
2
2
  import * as Numbers from "ixfx/numbers.js";
3
3
  import { clamp } from "ixfx/numbers.js";
4
4
  import { getCssVariable, getCssVariablesWithFallback } from "ixfx/dom.js";
5
5
  import { Trees } from "ixfx/collections.js";
6
6
  import { CanvasHelper, Colour, Drawing } from "ixfx/visual.js";
7
+ import * as Arrays from "ixfx/arrays.js";
7
8
  import { Pathed } from "ixfx";
8
9
 
9
10
  //#region node_modules/@lit/reactive-element/css-tag.js
@@ -2227,31 +2228,139 @@ SplitLayoutElement = __decorate([t("ixfx-split-layout")], SplitLayoutElement);
2227
2228
 
2228
2229
  //#endregion
2229
2230
  //#region src/charts/plot.ts
2231
+ const rangeIsEqual = (a$2, b$2) => {
2232
+ if (a$2 === void 0 || b$2 === void 0) return false;
2233
+ return a$2.max === b$2.max && a$2.min === b$2.min;
2234
+ };
2235
+ var PlotAxis = class {
2236
+ bounds = `auto`;
2237
+ #currentScalerRange;
2238
+ #persistentScale = Numbers.rangeInit();
2239
+ #currentScaler = (v$2) => v$2;
2240
+ constructor(name) {
2241
+ this.name = name;
2242
+ }
2243
+ humanFormatValue(value) {
2244
+ if (value.length === 0) return ``;
2245
+ const v$2 = value[0];
2246
+ if (v$2 < 2) return v$2.toFixed(2);
2247
+ return v$2.toString();
2248
+ }
2249
+ getRange(series) {
2250
+ if (this.bounds === `auto`) return this.computeActualRange(series);
2251
+ if (this.bounds === `auto-persistent`) {
2252
+ const r$6 = this.computeActualRange(series);
2253
+ this.#persistentScale = Numbers.rangeMergeRange(r$6, this.#persistentScale);
2254
+ return this.#persistentScale;
2255
+ }
2256
+ return this.bounds;
2257
+ }
2258
+ getCurrentRange() {
2259
+ return this.#currentScalerRange;
2260
+ }
2261
+ getValueScaler(series) {
2262
+ const r$6 = this.getRange(series);
2263
+ if (!rangeIsEqual(r$6, this.#currentScalerRange)) {
2264
+ this.#currentScalerRange = r$6;
2265
+ this.#currentScaler = Numbers.scaler(r$6.min, r$6.max, 0, 1);
2266
+ }
2267
+ return this.#currentScaler;
2268
+ }
2269
+ computeActualRange(seriesOnAxis, column = 0) {
2270
+ let min = Number.MAX_SAFE_INTEGER;
2271
+ let max = Number.MIN_SAFE_INTEGER;
2272
+ let count = 0;
2273
+ for (const s$5 of seriesOnAxis) {
2274
+ for (const [v$2, _index] of s$5.getValuesForColumn(column)) {
2275
+ if (typeof v$2 !== `number`) continue;
2276
+ if (Number.isNaN(v$2)) continue;
2277
+ if (v$2 < min) min = v$2;
2278
+ if (v$2 > max) max = v$2;
2279
+ }
2280
+ count++;
2281
+ }
2282
+ return {
2283
+ min,
2284
+ max
2285
+ };
2286
+ }
2287
+ };
2230
2288
  let PlotElement = class PlotElement$1 extends i {
2231
- streaming = true;
2232
2289
  hideLegend = false;
2233
- maxLength = 500;
2234
- dataWidth = 5;
2235
- fixedMax = NaN;
2236
- fixedMin = NaN;
2237
- lineWidth = 2;
2238
2290
  renderStyle = `dot`;
2239
- manualDraw = false;
2240
2291
  padding = 5;
2292
+ primaryAxis = new PlotAxis(`primary`);
2293
+ secondaryAxis = new PlotAxis(`secondary`);
2241
2294
  paused = false;
2295
+ plotAreaFormat = {
2296
+ fill: { type: `none` },
2297
+ outline: { type: `none` }
2298
+ };
2299
+ /**
2300
+ * Settings used for automatically generating series colour
2301
+ */
2302
+ automaticSeriesColour = {
2303
+ saturation: 1,
2304
+ lightness: .5,
2305
+ alpha: 1
2306
+ };
2307
+ seriesDefault = {};
2242
2308
  #series = /* @__PURE__ */ new Map();
2243
2309
  #canvas;
2244
2310
  #drawing;
2245
2311
  #legendColour = ``;
2246
- #hue = 0;
2312
+ #autoSeriesColour = 0;
2313
+ #hitboxes = [];
2314
+ #seriesFormatting = /* @__PURE__ */ new Map();
2315
+ #swatchSize = 10;
2247
2316
  canvasEl = e$1();
2248
- seriesRanges = /* @__PURE__ */ new Map();
2317
+ tooltipEl = e$1();
2318
+ seriesColourGenerate = (_seriesName) => {
2319
+ const c$6 = Colour.goldenAngleColour(this.#autoSeriesColour, this.automaticSeriesColour.saturation, this.automaticSeriesColour.lightness, this.automaticSeriesColour.alpha);
2320
+ this.#autoSeriesColour++;
2321
+ return c$6;
2322
+ };
2249
2323
  get series() {
2250
2324
  return [...this.#series.values()];
2251
2325
  }
2252
2326
  get seriesCount() {
2253
2327
  return this.#series.size;
2254
2328
  }
2329
+ getSeriesFormatting(seriesName, createIfNeeded) {
2330
+ let f$4 = this.#seriesFormatting.get(seriesName);
2331
+ if (!f$4 && createIfNeeded) {
2332
+ f$4 = {
2333
+ axis: `primary`,
2334
+ fill: {
2335
+ colour: this.seriesColourGenerate(seriesName),
2336
+ type: `solid`
2337
+ },
2338
+ outline: { type: `none` },
2339
+ bar: { gapWidth: 10 },
2340
+ dot: {
2341
+ gapWidth: 0,
2342
+ radius: `automatic`
2343
+ },
2344
+ line: {
2345
+ width: 2,
2346
+ cap: `round`,
2347
+ join: `round`
2348
+ }
2349
+ };
2350
+ this.#seriesFormatting.set(seriesName, f$4);
2351
+ }
2352
+ return f$4;
2353
+ }
2354
+ setSeriesFormatting(seriesName, formatting) {
2355
+ let existing = this.getSeriesFormatting(seriesName, true);
2356
+ const combined = {
2357
+ ...existing,
2358
+ ...formatting
2359
+ };
2360
+ combined.fill = resolveFillDrawStyle(combined.fill);
2361
+ combined.outline = resolveOutlineDrawStyle(combined.outline);
2362
+ this.#seriesFormatting.set(seriesName, combined);
2363
+ }
2255
2364
  /**
2256
2365
  * Returns a `PlotElement` instance based on a query
2257
2366
  * ```js
@@ -2298,6 +2407,7 @@ let PlotElement = class PlotElement$1 extends i {
2298
2407
  */
2299
2408
  clear() {
2300
2409
  this.#series.clear();
2410
+ this.#autoSeriesColour = 0;
2301
2411
  }
2302
2412
  /**
2303
2413
  * Keeps all series, but deletes their data
@@ -2305,8 +2415,45 @@ let PlotElement = class PlotElement$1 extends i {
2305
2415
  clearData() {
2306
2416
  for (const s$5 of this.#series.values()) s$5.clear();
2307
2417
  }
2308
- render() {
2309
- return x`<canvas ${n$1(this.canvasEl)}></canvas>`;
2418
+ #lastPointerPosition;
2419
+ #onPointerLeave() {
2420
+ this.#lastPointerPosition = void 0;
2421
+ this.#hideTooltip();
2422
+ }
2423
+ #onPointerMove(event) {
2424
+ this.#lastPointerPosition = {
2425
+ x: event.offsetX,
2426
+ screenX: event.x,
2427
+ y: event.offsetY,
2428
+ screenY: event.y
2429
+ };
2430
+ this.#updateToolTip();
2431
+ }
2432
+ #updateToolTip() {
2433
+ if (!this.#lastPointerPosition) return;
2434
+ for (const hb of this.#hitboxes) if (Rects.intersectsPoint(hb, this.#lastPointerPosition)) {
2435
+ if (hb.value) this.#setTooltip(hb.series.humanFormatValue(hb.value));
2436
+ else this.#setTooltip(``);
2437
+ return;
2438
+ }
2439
+ this.#hideTooltip();
2440
+ }
2441
+ #hideTooltip() {
2442
+ const v$2 = this.tooltipEl.value;
2443
+ if (!v$2) return;
2444
+ v$2.classList.add(`hidden`);
2445
+ }
2446
+ #setTooltip(innerHtml) {
2447
+ const v$2 = this.tooltipEl.value;
2448
+ if (!v$2) return;
2449
+ const pos = this.#lastPointerPosition;
2450
+ v$2.classList.remove(`hidden`);
2451
+ const offset = 10;
2452
+ v$2.innerHTML = innerHtml;
2453
+ if (pos) {
2454
+ v$2.style.left = `${pos.screenX + offset}px`;
2455
+ v$2.style.top = `${pos.screenY + offset}px`;
2456
+ }
2310
2457
  }
2311
2458
  #setupCanvas() {
2312
2459
  if (this.#canvas !== void 0) return this.#canvas;
@@ -2326,48 +2473,46 @@ let PlotElement = class PlotElement$1 extends i {
2326
2473
  if (!c$6) return;
2327
2474
  const entry = event[0];
2328
2475
  c$6.setLogicalSize(entry.contentRect);
2476
+ if (this.#hasDirty()) this.draw();
2329
2477
  });
2330
2478
  ro.observe(this);
2331
2479
  this.updateColours();
2332
2480
  }
2481
+ #hasDirty() {
2482
+ for (const s$5 of this.series.values()) if (s$5.isDirty) return true;
2483
+ return false;
2484
+ }
2333
2485
  updateColours() {
2334
2486
  this.#legendColour = getCssVariable(`legend-fg`, `black`);
2335
2487
  }
2336
- plot(value, seriesName = ``, skipDrawing = false) {
2337
- if (typeof value !== "number") throw new TypeError(`Can only add numbers. Got: ${typeof value}`);
2338
- let s$5 = this.#series.get(seriesName.toLowerCase());
2339
- if (s$5 === void 0) {
2340
- s$5 = new PlotSeries(seriesName, this.colourGenerator(seriesName), this);
2341
- this.#series.set(seriesName.toLowerCase(), s$5);
2342
- }
2343
- s$5.push(value);
2344
- if (!this.manualDraw && !skipDrawing) this.draw();
2345
- return s$5;
2346
- }
2347
- plotSeries(values, seriesName = ``, skipDrawing = false) {
2488
+ setRawValues(values, seriesName, automaticallyDraw) {
2348
2489
  if (!Array.isArray(values)) throw new TypeError(`Param 'values' is not an array`);
2349
- let s$5 = this.#series.get(seriesName.toLowerCase());
2350
- if (s$5 === void 0) {
2351
- s$5 = new PlotSeries(seriesName, this.colourGenerator(seriesName), this);
2352
- this.#series.set(seriesName.toLowerCase(), s$5);
2353
- }
2354
- s$5.setValues(values);
2355
- if (!this.manualDraw && !skipDrawing) this.draw();
2490
+ let s$5 = this.getOrCreateSeries(seriesName);
2491
+ s$5.setRawValues(values, automaticallyDraw);
2356
2492
  return s$5;
2357
2493
  }
2358
2494
  /**
2359
- * Draw a set of key-value pairs as a batch.
2495
+ * Treats each property of an object as the series name
2360
2496
  * @param value
2497
+ * @param automaticallyDraw
2361
2498
  */
2362
- plotObject(value) {
2363
- for (const p$3 of Pathed.getPathsAndData(value, true)) this.plot(p$3.value, p$3.path, true);
2364
- this.draw();
2499
+ appendObjectBySeries(value, automaticallyDraw) {
2500
+ for (const p$3 of Pathed.getPathsAndData(value, true)) {
2501
+ const v$2 = p$3.value;
2502
+ if (typeof v$2 === `number`) this.appendRawValues(v$2, p$3.path, false);
2503
+ }
2504
+ if (automaticallyDraw) this.draw();
2365
2505
  }
2366
- colourGenerator(_series) {
2367
- const c$6 = Colour.HslSpace.scalar(this.#hue, .9, .4);
2368
- this.#hue = Numbers.wrap(this.#hue + .1);
2369
- return c$6;
2506
+ appendRawValues(values, seriesName, automaticallyDraw) {
2507
+ if (!Array.isArray(values)) values = [values];
2508
+ let s$5 = this.getOrCreateSeries(seriesName);
2509
+ for (const v$2 of values) s$5.pushValue(v$2, false);
2510
+ if (automaticallyDraw) this.draw();
2370
2511
  }
2512
+ /**
2513
+ * Draw a set of key-value pairs as a batch.
2514
+ * @param value
2515
+ */
2371
2516
  draw() {
2372
2517
  if (this.paused) return;
2373
2518
  const c$6 = this.#canvas;
@@ -2384,14 +2529,12 @@ let PlotElement = class PlotElement$1 extends i {
2384
2529
  width: axisYwidth,
2385
2530
  height: plotHeight
2386
2531
  };
2387
- const cp = this.computePlot(c$6, plotHeight, cy.width, padding);
2532
+ const plotDataArea = this.calculatePlotDataArea(c$6, plotHeight, cy.width, padding);
2388
2533
  const cl = {
2389
2534
  ...clLegend.bounds,
2390
2535
  x: cy.width,
2391
- y: cp.y + cp.height + padding
2536
+ y: plotDataArea.y + plotDataArea.height + padding
2392
2537
  };
2393
- let globalScaler;
2394
- if (!Number.isNaN(this.fixedMax) && !Number.isNaN(this.fixedMin)) globalScaler = Numbers.scaler(this.fixedMin, this.fixedMax);
2395
2538
  if (!this.hideLegend) {
2396
2539
  ctx.save();
2397
2540
  ctx.translate(cl.x + padding, cl.y + padding);
@@ -2399,19 +2542,41 @@ let PlotElement = class PlotElement$1 extends i {
2399
2542
  ctx.restore();
2400
2543
  }
2401
2544
  ctx.save();
2402
- ctx.translate(cp.x + padding, cp.y + padding);
2545
+ ctx.translate(plotDataArea.x + padding, plotDataArea.y + padding);
2546
+ if (this.plotAreaFormat.fill.type === `solid`) {
2547
+ ctx.fillStyle = Colour.resolveCss(this.plotAreaFormat.fill.colour);
2548
+ ctx.fillRect(0, 0, plotDataArea.width, plotDataArea.height);
2549
+ }
2550
+ if (this.plotAreaFormat.outline.type === `solid`) {
2551
+ ctx.strokeStyle = Colour.resolveCss(this.plotAreaFormat.outline.colour);
2552
+ ctx.strokeRect(0, 0, plotDataArea.width, plotDataArea.height);
2553
+ }
2554
+ const primaryAxisS = this.primaryAxis.getValueScaler(this.#series.values().filter((s$5) => s$5.onPrimaryAxis));
2555
+ const secondaryAxisAxisS = this.secondaryAxis.getValueScaler(this.#series.values().filter((s$5) => !s$5.onPrimaryAxis));
2556
+ this.#hitboxes = [];
2403
2557
  for (const series of this.#series.values()) {
2404
- const seriesScale = this.seriesRanges.get(series.name);
2405
- const data = seriesScale === void 0 ? globalScaler === void 0 ? series.getScaled() : series.getScaledBy(globalScaler) : series.getScaledBy(Numbers.scaler(seriesScale[0], seriesScale[1]));
2406
- const colour = Colour.toCssColour(series.colour);
2407
2558
  switch (this.renderStyle) {
2408
2559
  case `line`:
2409
- this.drawLineSeries(data, cp, d$3, colour);
2560
+ this.#drawLineSeries(series, d$3, series.onPrimaryAxis ? primaryAxisS : secondaryAxisAxisS, plotDataArea, {
2561
+ x: padding,
2562
+ y: padding
2563
+ });
2410
2564
  break;
2411
- default: this.drawDotSeries(data, cp, d$3, colour);
2565
+ case `bar`:
2566
+ this.#drawBarSeries(series, d$3, series.onPrimaryAxis ? primaryAxisS : secondaryAxisAxisS, plotDataArea, {
2567
+ x: padding,
2568
+ y: padding
2569
+ });
2570
+ break;
2571
+ default: this.#drawDotSeries(series, d$3, series.onPrimaryAxis ? primaryAxisS : secondaryAxisAxisS, plotDataArea, {
2572
+ x: padding,
2573
+ y: padding
2574
+ });
2412
2575
  }
2576
+ series.isDirty = false;
2413
2577
  }
2414
2578
  ctx.restore();
2579
+ this.#updateToolTip();
2415
2580
  }
2416
2581
  drawLegend(cl, d$3) {
2417
2582
  const textColour = this.#legendColour;
@@ -2421,7 +2586,9 @@ let PlotElement = class PlotElement$1 extends i {
2421
2586
  const swatchSize = 10;
2422
2587
  const ctx = d$3.ctx;
2423
2588
  for (const series of this.#series.values()) {
2424
- ctx.fillStyle = Colour.toCssColour(series.colour);
2589
+ const f$4 = series.getFormatting();
2590
+ const colour = f$4.fill.type === `solid` ? f$4.fill.colour : `transparent`;
2591
+ ctx.fillStyle = Colour.toCssColour(colour);
2425
2592
  ctx.fillRect(x$1, y$2, swatchSize, swatchSize);
2426
2593
  ctx.fillStyle = textColour;
2427
2594
  x$1 += swatchSize + padding;
@@ -2435,60 +2602,161 @@ let PlotElement = class PlotElement$1 extends i {
2435
2602
  }
2436
2603
  }
2437
2604
  }
2438
- drawLineSeries(data, cp, d$3, colour) {
2439
- const pointWidth = this.streaming ? this.dataWidth : cp.width / data.length;
2605
+ #drawLineSeries(series, d$3, scaler, plotDataArea, plotDataAreaOffset) {
2606
+ const formatting = series.getFormatting();
2607
+ const lineFormatting = formatting.line;
2608
+ const widthPerValue = plotDataArea.width / series.length;
2609
+ const xOffset = widthPerValue / 2;
2610
+ const lineThickness = lineFormatting.width;
2611
+ const lineThicknessHalf = lineThickness / 2;
2612
+ const h$5 = plotDataArea.height;
2440
2613
  let x$1 = 0;
2441
- if (this.streaming) x$1 = cp.width - pointWidth * data.length;
2442
- const pos = data.map((d$4) => {
2443
- x$1 += pointWidth;
2614
+ const dotsToDraw = [...series.getValuesForColumn(0)].map((d$4) => {
2615
+ const [value, index] = d$4;
2616
+ x$1 += widthPerValue;
2617
+ const scaledValue = scaler(value);
2618
+ if (Number.isNaN(value)) return void 0;
2444
2619
  return {
2445
- x: x$1,
2446
- y: (1 - d$4) * cp.height,
2447
- radius: pointWidth
2620
+ x: x$1 - xOffset,
2621
+ y: (1 - scaledValue) * h$5,
2622
+ value,
2623
+ index
2448
2624
  };
2449
2625
  });
2450
- const trimmed = pos.filter((p$3) => {
2451
- if (p$3.x < 0) return false;
2452
- return true;
2453
- });
2454
- d$3.connectedPoints(trimmed, {
2455
- strokeStyle: colour,
2456
- lineWidth: this.lineWidth
2457
- });
2458
- }
2459
- drawDotSeries(data, cp, d$3, colour) {
2460
- const pointWidth = this.streaming ? this.dataWidth : cp.width / data.length;
2626
+ const filtered = dotsToDraw.filter((f$4) => f$4 !== void 0);
2627
+ const hitboxes = filtered.map((d$4) => ({
2628
+ height: lineThickness,
2629
+ width: widthPerValue,
2630
+ x: plotDataAreaOffset.x + d$4.x - lineThicknessHalf,
2631
+ y: plotDataAreaOffset.y + d$4.y - lineThicknessHalf,
2632
+ series,
2633
+ index: d$4.index,
2634
+ value: series.getValue(d$4.index)
2635
+ }));
2636
+ this.#hitboxes.push(...hitboxes);
2637
+ const drawOpts = {
2638
+ filled: false,
2639
+ fillStyle: `transparent`,
2640
+ stroked: true,
2641
+ strokeStyle: `black`,
2642
+ strokeWidth: lineFormatting.width
2643
+ };
2644
+ if (formatting.outline.type === `solid`) drawOpts.strokeStyle = formatting.outline.colour;
2645
+ else if (formatting.fill.type === `solid`) drawOpts.strokeStyle = formatting.fill.colour;
2646
+ d$3.ctx.lineWidth = lineFormatting.width;
2647
+ d$3.ctx.lineCap = lineFormatting.cap;
2648
+ d$3.ctx.lineJoin = lineFormatting.join;
2649
+ d$3.connectedPoints(filtered, drawOpts);
2650
+ }
2651
+ #drawDotSeries(series, d$3, scaler, plotDataArea, plotDataAreaOffset) {
2652
+ const formatting = series.getFormatting();
2653
+ const dotFormatting = formatting.dot;
2654
+ const widthPerValue = plotDataArea.width / series.length;
2655
+ const radius = dotFormatting.radius === `automatic` ? widthPerValue * .3 : dotFormatting.radius;
2656
+ const xOffset = widthPerValue / 2;
2657
+ const h$5 = plotDataArea.height;
2461
2658
  let x$1 = 0;
2462
- if (this.streaming) x$1 = cp.width - pointWidth * data.length;
2463
- const pos = data.map((d$4) => {
2464
- x$1 += pointWidth;
2659
+ const dotsToDraw = [...series.getValuesForColumn(0)].map((d$4) => {
2660
+ const [value, index] = d$4;
2661
+ x$1 += widthPerValue;
2662
+ const scaledValue = scaler(value);
2663
+ if (Number.isNaN(value)) return void 0;
2465
2664
  return {
2466
- x: x$1,
2467
- y: (1 - d$4) * cp.height,
2468
- radius: pointWidth
2665
+ x: x$1 - xOffset,
2666
+ y: (1 - scaledValue) * h$5,
2667
+ radius,
2668
+ value,
2669
+ index
2469
2670
  };
2470
2671
  });
2471
- const trimmed = pos.filter((p$3) => {
2472
- if (p$3.x < 0) return false;
2473
- return true;
2474
- });
2475
- d$3.dot(trimmed, {
2476
- filled: true,
2477
- fillStyle: colour
2672
+ const filtered = dotsToDraw.filter((f$4) => f$4 !== void 0);
2673
+ const hitboxes = filtered.map((d$4) => ({
2674
+ height: d$4.radius * 2,
2675
+ width: d$4.radius * 2,
2676
+ x: plotDataAreaOffset.x + d$4.x - d$4.radius,
2677
+ y: plotDataAreaOffset.y + d$4.y - d$4.radius,
2678
+ series,
2679
+ index: d$4.index,
2680
+ value: series.getValue(d$4.index)
2681
+ }));
2682
+ this.#hitboxes.push(...hitboxes);
2683
+ const drawOpts = {
2684
+ filled: false,
2685
+ fillStyle: `transparent`,
2686
+ stroked: false,
2687
+ strokeStyle: `transparent`
2688
+ };
2689
+ if (formatting.fill.type === `solid`) {
2690
+ drawOpts.filled = true;
2691
+ drawOpts.fillStyle = formatting.fill.colour;
2692
+ }
2693
+ if (formatting.outline.type === `solid`) {
2694
+ drawOpts.stroked = true;
2695
+ drawOpts.strokeStyle = formatting.outline.colour;
2696
+ }
2697
+ d$3.dot(filtered, drawOpts);
2698
+ }
2699
+ #drawBarSeries(series, d$3, scaler, plotDataArea, plotDataAreaOffset) {
2700
+ const formatting = series.getFormatting();
2701
+ const barFormatting = formatting.bar;
2702
+ const widthPerValue = plotDataArea.width / series.length;
2703
+ const rectWidth = widthPerValue - barFormatting.gapWidth / 2;
2704
+ const xOffset = widthPerValue / 2;
2705
+ const h$5 = plotDataArea.height;
2706
+ let x$1 = 0;
2707
+ const rectsToDraw = [...series.getValuesForColumn(0)].map((d$4) => {
2708
+ const [value, index] = d$4;
2709
+ x$1 += widthPerValue;
2710
+ const scaledValue = scaler(value);
2711
+ if (Number.isNaN(value)) return void 0;
2712
+ const barHeight = scaledValue * h$5;
2713
+ return {
2714
+ x: x$1 - xOffset,
2715
+ y: h$5 - barHeight,
2716
+ width: rectWidth,
2717
+ height: barHeight,
2718
+ value,
2719
+ index
2720
+ };
2478
2721
  });
2722
+ const filtered = rectsToDraw.filter((f$4) => f$4 !== void 0);
2723
+ const hitboxes = filtered.map((d$4) => ({
2724
+ height: Math.max(5, d$4.height),
2725
+ width: d$4.width,
2726
+ x: plotDataAreaOffset.x + d$4.x,
2727
+ y: plotDataAreaOffset.y + d$4.y,
2728
+ series,
2729
+ index: d$4.index,
2730
+ value: series.getValue(d$4.index)
2731
+ }));
2732
+ this.#hitboxes.push(...hitboxes);
2733
+ const drawOpts = {
2734
+ filled: false,
2735
+ fillStyle: `transparent`,
2736
+ stroked: false,
2737
+ strokeStyle: `transparent`
2738
+ };
2739
+ if (formatting.fill.type === `solid`) {
2740
+ drawOpts.filled = true;
2741
+ drawOpts.fillStyle = formatting.fill.colour;
2742
+ }
2743
+ if (formatting.outline.type === `solid`) {
2744
+ drawOpts.stroked = true;
2745
+ drawOpts.strokeStyle = formatting.outline.colour;
2746
+ }
2747
+ d$3.rect(filtered, drawOpts);
2479
2748
  }
2480
- computePlot(c$6, plotHeight, axisYwidth, padding) {
2749
+ calculatePlotDataArea(c$6, plotHeight, axisYwidth, padding) {
2481
2750
  return {
2482
2751
  x: axisYwidth,
2483
2752
  y: 0,
2484
- width: c$6.width - axisYwidth - padding,
2485
- height: plotHeight - padding - padding
2753
+ width: c$6.width - axisYwidth - padding - padding,
2754
+ height: plotHeight - padding
2486
2755
  };
2487
2756
  }
2488
2757
  computeAxisYWidth(_c) {
2489
2758
  return 0;
2490
2759
  }
2491
- #swatchSize = 10;
2492
2760
  computeLegend(c$6, maxWidth, padding) {
2493
2761
  if (this.hideLegend) return {
2494
2762
  bounds: {
@@ -2537,105 +2805,158 @@ let PlotElement = class PlotElement$1 extends i {
2537
2805
  parts
2538
2806
  };
2539
2807
  }
2808
+ getOrCreateSeries(name) {
2809
+ let s$5 = this.#series.get(name.toLowerCase());
2810
+ if (s$5) return s$5;
2811
+ s$5 = new PlotSeries(name, this);
2812
+ const seriesDefault = {
2813
+ limit: NaN,
2814
+ ...this.seriesDefault
2815
+ };
2816
+ if (!Number.isNaN(seriesDefault.limit)) s$5.setCapacityLimit(seriesDefault.limit);
2817
+ this.#series.set(name.toLowerCase(), s$5);
2818
+ return s$5;
2819
+ }
2540
2820
  getSeries(name) {
2541
2821
  return this.#series.get(name);
2542
2822
  }
2823
+ render() {
2824
+ return x`<canvas @pointerleave="${this.#onPointerLeave}" @pointermove="${this.#onPointerMove}" ${n$1(this.canvasEl)}></canvas><div class="hidden" ${n$1(this.tooltipEl)} id="tooltip">Tooltip!</div>`;
2825
+ }
2543
2826
  static styles = i$1`
2544
2827
  :host {
2545
2828
  width: 100%;
2546
2829
  height: 100%;
2547
2830
  display: block;
2548
2831
  }
2832
+
2833
+ #tooltip {
2834
+ border: 1px solid black;
2835
+ background: white;
2836
+ color: black;
2837
+ padding: 0.3em;
2838
+ font-size: 10px;
2839
+ display: inline-block;
2840
+ position: absolute;
2841
+ left: 0px;
2842
+ top: 0px;
2843
+ opacity:1;
2844
+ pointer-events: none;
2845
+ }
2846
+
2847
+ .hidden {
2848
+ opacity:0 !important;
2849
+ }
2549
2850
  `;
2550
2851
  };
2551
- __decorate([n({
2552
- attribute: `streaming`,
2553
- type: Boolean
2554
- })], PlotElement.prototype, "streaming", void 0);
2555
2852
  __decorate([n({
2556
2853
  attribute: `hide-legend`,
2557
2854
  type: Boolean
2558
2855
  })], PlotElement.prototype, "hideLegend", void 0);
2559
- __decorate([n({
2560
- attribute: `max-length`,
2561
- type: Number
2562
- })], PlotElement.prototype, "maxLength", void 0);
2563
- __decorate([n({
2564
- attribute: `data-width`,
2565
- type: Number
2566
- })], PlotElement.prototype, "dataWidth", void 0);
2567
- __decorate([n({
2568
- attribute: `fixed-max`,
2569
- type: Number
2570
- })], PlotElement.prototype, "fixedMax", void 0);
2571
- __decorate([n({
2572
- attribute: `fixed-min`,
2573
- type: Number
2574
- })], PlotElement.prototype, "fixedMin", void 0);
2575
- __decorate([n({
2576
- attribute: `line-width`,
2577
- type: Number
2578
- })], PlotElement.prototype, "lineWidth", void 0);
2579
2856
  __decorate([n({
2580
2857
  attribute: `render`,
2581
2858
  type: String
2582
2859
  })], PlotElement.prototype, "renderStyle", void 0);
2583
2860
  __decorate([n({
2584
- attribute: `manual-draw`,
2585
- type: Boolean
2586
- })], PlotElement.prototype, "manualDraw", void 0);
2861
+ attribute: `padding`,
2862
+ type: Number
2863
+ })], PlotElement.prototype, "padding", void 0);
2587
2864
  PlotElement = __decorate([t(`ixfx-plot-element`)], PlotElement);
2865
+ const resolveFillDrawStyle = (s$5) => {
2866
+ if (s$5.type === `none`) return s$5;
2867
+ const c$6 = Colour.resolveCss(s$5.colour);
2868
+ return {
2869
+ type: `solid`,
2870
+ colour: c$6
2871
+ };
2872
+ };
2873
+ const resolveOutlineDrawStyle = (s$5) => {
2874
+ if (s$5.type === `none`) return s$5;
2875
+ const c$6 = Colour.resolveCss(s$5.colour);
2876
+ return {
2877
+ ...s$5,
2878
+ colour: c$6
2879
+ };
2880
+ };
2588
2881
  var PlotSeries = class {
2589
- data = [];
2590
- minSeen = Number.MAX_SAFE_INTEGER;
2591
- maxSeen = Number.MIN_SAFE_INTEGER;
2592
- constructor(name, colour, plot) {
2882
+ #data = [];
2883
+ #capacityLimit = NaN;
2884
+ #dirty = false;
2885
+ #onPrimaryAxis = true;
2886
+ constructor(name, plot) {
2593
2887
  this.name = name;
2594
- this.colour = colour;
2595
2888
  this.plot = plot;
2596
2889
  }
2597
- clear() {
2598
- this.data = [];
2599
- this.resetScale();
2890
+ get length() {
2891
+ return this.#data.length;
2600
2892
  }
2601
- /**
2602
- * Returns a copy of the data scaled by the current
2603
- * range of the data
2604
- * @returns
2605
- */
2606
- getScaled() {
2607
- let min = this.minSeen;
2608
- let max = this.maxSeen;
2609
- if (Number.isNaN(min)) min = 0;
2610
- if (Number.isNaN(max)) max = 1;
2611
- const s$5 = Numbers.scaler(min, max);
2612
- return this.getScaledBy(s$5);
2613
- }
2614
- getScaledBy(scaler) {
2615
- return this.data.map((v$2) => {
2616
- if (typeof v$2 !== `number`) throw new Error(`Data should just be numbers. Got: ${typeof v$2}`);
2617
- if (Number.isNaN(v$2)) throw new Error(`data contains NaN`);
2618
- const scaled = scaler(v$2);
2619
- if (Number.isNaN(scaled)) throw new Error(`NaN. v: ${v$2} scaled: ${scaled}`);
2620
- return Numbers.clamp(scaled);
2621
- });
2893
+ *getValuesForColumn(column) {
2894
+ let index = 0;
2895
+ for (const dv of this.#data) {
2896
+ if (dv && column in dv) yield [dv[column], index];
2897
+ else yield [NaN, index];
2898
+ index++;
2899
+ }
2622
2900
  }
2623
- push(value) {
2624
- if (typeof value !== "number") throw new Error(`Can only add numbers. Got: ${typeof value}`);
2625
- this.data.push(value);
2626
- if (this.data.length > this.plot.maxLength && this.plot.streaming) this.data = this.data.slice(1);
2627
- this.minSeen = Math.min(this.minSeen, value);
2628
- this.maxSeen = Math.max(this.maxSeen, value);
2901
+ getValue(index) {
2902
+ return this.#data[index];
2629
2903
  }
2630
- setValues(values) {
2904
+ pushValue(value, automaticallyDraw) {
2905
+ if (!Array.isArray(value)) value = [value];
2906
+ if (value.length === 0) throw new Error(`Param 'value' is empty. Expected a value or array of value components`);
2907
+ this.#data.push(value);
2908
+ if (!Number.isNaN(this.#capacityLimit)) this.#data = Arrays.ensureLength(this.#data, this.#capacityLimit, `undefined`, `from-start`);
2909
+ this.#dirty = true;
2910
+ if (automaticallyDraw) this.plot.draw();
2911
+ }
2912
+ setValues(values, automaticallyDraw) {
2631
2913
  if (!Array.isArray(values)) throw new TypeError(`Param 'values' is not an array`);
2632
- this.data = values;
2633
- this.minSeen = Math.min(...values);
2634
- this.maxSeen = Math.max(...values);
2914
+ this.#data = Arrays.ensureLength(values, this.#capacityLimit, `undefined`);
2915
+ this.#dirty = true;
2916
+ if (automaticallyDraw) this.plot.draw();
2917
+ }
2918
+ humanFormatValue(value) {
2919
+ let v$2 = `${this.name} `;
2920
+ if (this.onPrimaryAxis) v$2 += this.plot.primaryAxis.humanFormatValue(value);
2921
+ else v$2 += this.plot.secondaryAxis.humanFormatValue(value);
2922
+ return v$2;
2923
+ }
2924
+ setRawValues(values, automaticallyDraw) {
2925
+ this.setValues(values.map((v$2) => [v$2]), automaticallyDraw);
2926
+ }
2927
+ setCapacityLimit(v$2) {
2928
+ if (typeof v$2 !== `number`) throw new TypeError(`Expect type number. Got: ${typeof v$2}`);
2929
+ if (!Number.isFinite(v$2)) throw new TypeError(`Param 'v' is not finite`);
2930
+ if (Number.isNaN(v$2)) throw new TypeError(`Param 'v' is NaN`);
2931
+ this.#capacityLimit = v$2;
2932
+ this.#data = Arrays.ensureLength(this.#data, this.#capacityLimit, `undefined`);
2933
+ }
2934
+ unsetCapacityLimit() {
2935
+ this.#capacityLimit = NaN;
2635
2936
  }
2636
- resetScale() {
2637
- this.minSeen = Number.MAX_SAFE_INTEGER;
2638
- this.maxSeen = Number.MIN_SAFE_INTEGER;
2937
+ setFormatting(formatting) {
2938
+ this.plot.setSeriesFormatting(this.name, formatting);
2939
+ }
2940
+ getFormatting() {
2941
+ return this.plot.getSeriesFormatting(this.name, true);
2942
+ }
2943
+ get isDirty() {
2944
+ return this.#dirty;
2945
+ }
2946
+ set isDirty(value) {
2947
+ this.#dirty = value;
2948
+ }
2949
+ get onPrimaryAxis() {
2950
+ return this.#onPrimaryAxis;
2951
+ }
2952
+ set onPrimaryAxis(value) {
2953
+ if (value === this.#onPrimaryAxis) return;
2954
+ this.#onPrimaryAxis = value;
2955
+ this.#dirty = true;
2956
+ }
2957
+ clear() {
2958
+ this.#data = [];
2959
+ this.#dirty = true;
2639
2960
  }
2640
2961
  };
2641
2962
 
@@ -3013,5 +3334,5 @@ const init = () => {
3013
3334
  };
3014
3335
 
3015
3336
  //#endregion
3016
- export { CrumbNavElement, CrumbNavigationElement, DialElement, FrequencyHistogramPlot, HistogramVis, PlotElement, PlotSeries, SelectHorizontalElement, SplitLayoutElement, TabListElement, TabListItemElement, TabPanelElement, TabPanelsElement, init };
3337
+ export { CrumbNavElement, CrumbNavigationElement, DialElement, FrequencyHistogramPlot, HistogramVis, PlotAxis, PlotElement, PlotSeries, SelectHorizontalElement, SplitLayoutElement, TabListElement, TabListItemElement, TabPanelElement, TabPanelsElement, init };
3017
3338
  //# sourceMappingURL=index.js.map