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