@nanahoshi/mona-ui 0.17.0 → 0.17.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.
@@ -36060,23 +36060,79 @@ class ChartLayoutEngine {
36060
36060
  * Small overflow allowance so marks whose stroke or radius sits exactly at a
36061
36061
  * domain extreme (a peak touching the max, a hovered point marker at the
36062
36062
  * first/last category) aren't visually chopped in half by the plot clip rect.
36063
+ * Only applied on sides that don't already have a drawn axis boundary line
36064
+ * (see {@link resolvePlotEdgeAxisLines}) — a side with a visible axis line
36065
+ * clips flush against it instead, so series strokes don't visibly poke past it.
36063
36066
  */
36064
36067
  const PLOT_CLIP_OVERFLOW = 8;
36068
+ /**
36069
+ * Resolves which of the plot rect's four edges have a visible, boundary-hugging
36070
+ * (sideOffset 0) axis line drawn on them. Stacked secondary axes (sideOffset > 0)
36071
+ * don't count: their line is drawn further out, not at the plot boundary itself.
36072
+ */
36073
+ function resolvePlotEdgeAxisLines(axes) {
36074
+ let top = false;
36075
+ let bottom = false;
36076
+ let left = false;
36077
+ let right = false;
36078
+ for (const axis of axes) {
36079
+ if (!axis.visible || !axis.axisLine || (axis.sideOffset ?? 0) !== 0) {
36080
+ continue;
36081
+ }
36082
+ switch (axis.position) {
36083
+ case "top":
36084
+ top = true;
36085
+ break;
36086
+ case "bottom":
36087
+ bottom = true;
36088
+ break;
36089
+ case "left":
36090
+ left = true;
36091
+ break;
36092
+ case "right":
36093
+ right = true;
36094
+ break;
36095
+ }
36096
+ }
36097
+ return { bottom, left, right, top };
36098
+ }
36099
+ /**
36100
+ * Computes the clip rect for series/overlay content: `plotRect` inflated by
36101
+ * {@link PLOT_CLIP_OVERFLOW} on each side, except sides with a visible boundary
36102
+ * axis line, which stay flush so strokes don't visibly cross the axis line.
36103
+ */
36104
+ function computeSeriesClipRect(plotRect, axes) {
36105
+ const edges = resolvePlotEdgeAxisLines(axes);
36106
+ const left = edges.left ? 0 : PLOT_CLIP_OVERFLOW;
36107
+ const right = edges.right ? 0 : PLOT_CLIP_OVERFLOW;
36108
+ const top = edges.top ? 0 : PLOT_CLIP_OVERFLOW;
36109
+ const bottom = edges.bottom ? 0 : PLOT_CLIP_OVERFLOW;
36110
+ return {
36111
+ height: plotRect.height + top + bottom,
36112
+ width: plotRect.width + left + right,
36113
+ x: plotRect.x - left,
36114
+ y: plotRect.y - top
36115
+ };
36116
+ }
36065
36117
  function crispPixel(pixel, lineWidth = 1) {
36066
36118
  if (lineWidth % 2 === 1) {
36067
36119
  return Math.floor(pixel) + 0.5;
36068
36120
  }
36069
36121
  return Math.round(pixel);
36070
36122
  }
36071
- /**
36072
- * Clips the canvas context to `plotRect`, inflated on all four sides by
36073
- * {@link PLOT_CLIP_OVERFLOW} so marks touching a domain extreme aren't cut in half.
36074
- */
36075
- function clipToPlotRect(context, plotRect) {
36123
+ /** Clips the canvas context to `rect` as-is, with no further inflation. */
36124
+ function clipToRect(context, rect) {
36076
36125
  context.beginPath();
36077
- context.rect(plotRect.x - PLOT_CLIP_OVERFLOW, plotRect.y - PLOT_CLIP_OVERFLOW, plotRect.width + PLOT_CLIP_OVERFLOW * 2, plotRect.height + PLOT_CLIP_OVERFLOW * 2);
36126
+ context.rect(rect.x, rect.y, rect.width, rect.height);
36078
36127
  context.clip();
36079
36128
  }
36129
+ /**
36130
+ * Clips the canvas context to `plotRect`, inflated per {@link computeSeriesClipRect}.
36131
+ * Pass the scene's `axes` so sides with a visible boundary line clip flush against it.
36132
+ */
36133
+ function clipToPlotRect(context, plotRect, axes = []) {
36134
+ clipToRect(context, computeSeriesClipRect(plotRect, axes));
36135
+ }
36080
36136
  function drawRoundedRectCorners(context, x, y, width, height, radii) {
36081
36137
  if (width <= 0 || height <= 0) {
36082
36138
  return;
@@ -36915,12 +36971,12 @@ class CartesianOverlayRenderer {
36915
36971
  context.setLineDash([]);
36916
36972
  context.restore();
36917
36973
  }
36918
- static renderOverlays(context, overlayScene, plotRect, annotationBadgeAnchors) {
36974
+ static renderOverlays(context, overlayScene, plotRect, axes = [], annotationBadgeAnchors) {
36919
36975
  if (!overlayScene) {
36920
36976
  return;
36921
36977
  }
36922
36978
  context.save();
36923
- clipToPlotRect(context, plotRect);
36979
+ clipToPlotRect(context, plotRect, axes);
36924
36980
  // 1. Overlay Bands
36925
36981
  for (const band of overlayScene.referenceBands) {
36926
36982
  if (band.layer === "overlay") {
@@ -36940,12 +36996,12 @@ class CartesianOverlayRenderer {
36940
36996
  }
36941
36997
  context.restore();
36942
36998
  }
36943
- static renderUnderlays(context, overlayScene, plotRect) {
36999
+ static renderUnderlays(context, overlayScene, plotRect, axes = []) {
36944
37000
  if (!overlayScene || (overlayScene.referenceBands.length === 0 && overlayScene.referenceLines.length === 0)) {
36945
37001
  return;
36946
37002
  }
36947
37003
  context.save();
36948
- clipToPlotRect(context, plotRect);
37004
+ clipToPlotRect(context, plotRect, axes);
36949
37005
  // 1. Underlay Bands
36950
37006
  for (const band of overlayScene.referenceBands) {
36951
37007
  if (band.layer === "underlay") {
@@ -36974,7 +37030,7 @@ function getCrosshairDash$1(style) {
36974
37030
  }
36975
37031
  }
36976
37032
  class CartesianCrosshairRenderer {
36977
- static render(context, crosshairState, registration, plotRect, styleResolver) {
37033
+ static render(context, crosshairState, registration, plotRect, styleResolver, axes = []) {
36978
37034
  if (!crosshairState || !registration || registration.enabled() === false) {
36979
37035
  return;
36980
37036
  }
@@ -36990,7 +37046,7 @@ class CartesianCrosshairRenderer {
36990
37046
  }
36991
37047
  const dash = getCrosshairDash$1(registration.lineStyle());
36992
37048
  context.save();
36993
- clipToPlotRect(context, plotRect);
37049
+ clipToPlotRect(context, plotRect, axes);
36994
37050
  context.strokeStyle = style.color;
36995
37051
  context.lineWidth = style.width;
36996
37052
  context.globalAlpha = style.opacity;
@@ -37027,7 +37083,7 @@ class CartesianInteractionOverlayRenderer {
37027
37083
  (!interactionState.activeHits || interactionState.activeHits.length === 0))) {
37028
37084
  return;
37029
37085
  }
37030
- const { plotRect, series } = scene;
37086
+ const { axes, plotRect, series } = scene;
37031
37087
  if (plotRect.width <= 0 || plotRect.height <= 0) {
37032
37088
  return;
37033
37089
  }
@@ -37040,7 +37096,7 @@ class CartesianInteractionOverlayRenderer {
37040
37096
  return;
37041
37097
  }
37042
37098
  context.save();
37043
- clipToPlotRect(context, plotRect);
37099
+ clipToPlotRect(context, plotRect, axes);
37044
37100
  const markerStrokeColor = styleResolver.resolveCssVariable("--mona-chart-marker-stroke-color") ||
37045
37101
  styleResolver.resolveCssVariable("--color-surface") ||
37046
37102
  "#ffffff";
@@ -37178,9 +37234,9 @@ class CartesianSelectionOverlayRenderer {
37178
37234
  if (scene.hits.length === 0) {
37179
37235
  return;
37180
37236
  }
37181
- const { color = "#3b82f6", fillOpacity = 0.12, plotRect, strokeWidth = 2 } = options;
37237
+ const { axes = [], color = "#3b82f6", fillOpacity = 0.12, plotRect, strokeWidth = 2 } = options;
37182
37238
  ctx.save();
37183
- clipToPlotRect(ctx, plotRect);
37239
+ clipToPlotRect(ctx, plotRect, axes);
37184
37240
  ctx.strokeStyle = color;
37185
37241
  ctx.lineWidth = strokeWidth;
37186
37242
  for (const hit of scene.hits) {
@@ -37293,7 +37349,7 @@ class CartesianBrushRenderer {
37293
37349
 
37294
37350
  class CartesianChartRenderer {
37295
37351
  static render(context, scene, overlayState, styleResolver) {
37296
- const { plotRect } = scene;
37352
+ const { axes, plotRect } = scene;
37297
37353
  if (plotRect.width <= 0 || plotRect.height <= 0) {
37298
37354
  return;
37299
37355
  }
@@ -37310,15 +37366,15 @@ class CartesianChartRenderer {
37310
37366
  // 1. Grid
37311
37367
  this.renderGridLayer(context, scene, styleResolver);
37312
37368
  // 2. Static Underlays
37313
- this.renderStaticUnderlayLayer(context, cartesianOverlay, plotRect);
37369
+ this.renderStaticUnderlayLayer(context, cartesianOverlay, plotRect, axes);
37314
37370
  // 3. Series
37315
37371
  this.renderSeriesLayer(context, scene);
37316
37372
  // 4. Persistent Selection Highlights
37317
- this.renderSelectionLayer(context, selectionScene, selectionOptions, plotRect, styleResolver);
37373
+ this.renderSelectionLayer(context, selectionScene, selectionOptions, plotRect, styleResolver, axes);
37318
37374
  // 5. Data Labels (Canvas default labels)
37319
37375
  this.renderDataLabelLayer(context, cartesianDataLabels, plotRect);
37320
37376
  // 6. Static Overlays & Annotations
37321
- this.renderStaticOverlayLayer(context, cartesianOverlay, plotRect, annotationBadgeAnchors);
37377
+ this.renderStaticOverlayLayer(context, cartesianOverlay, plotRect, annotationBadgeAnchors, axes);
37322
37378
  // 7. Axes
37323
37379
  this.renderAxisLayer(context, scene, styleResolver);
37324
37380
  // 8. Transient (Crosshair + Highlights)
@@ -37395,7 +37451,7 @@ class CartesianChartRenderer {
37395
37451
  }
37396
37452
  }
37397
37453
  static renderCrossfade(context, fromScene, toScene, progress, overlayState, styleResolver) {
37398
- const { plotRect } = toScene;
37454
+ const { axes, plotRect } = toScene;
37399
37455
  if (plotRect.width <= 0 || plotRect.height <= 0) {
37400
37456
  return;
37401
37457
  }
@@ -37420,7 +37476,7 @@ class CartesianChartRenderer {
37420
37476
  context.restore();
37421
37477
  }
37422
37478
  // 2. Target static underlay once (at full own opacity)
37423
- this.renderStaticUnderlayLayer(context, cartesianOverlay, plotRect);
37479
+ this.renderStaticUnderlayLayer(context, cartesianOverlay, plotRect, axes);
37424
37480
  // 3. Series crossfade
37425
37481
  if (fromScene && progress < 1) {
37426
37482
  context.save();
@@ -37435,7 +37491,7 @@ class CartesianChartRenderer {
37435
37491
  context.restore();
37436
37492
  }
37437
37493
  // 4. Static overlay (at full own opacity)
37438
- this.renderStaticOverlayLayer(context, cartesianOverlay, plotRect, annotationBadgeAnchors);
37494
+ this.renderStaticOverlayLayer(context, cartesianOverlay, plotRect, annotationBadgeAnchors, axes);
37439
37495
  // 7. Axes crossfade
37440
37496
  if (fromScene && progress < 1) {
37441
37497
  context.save();
@@ -37491,10 +37547,11 @@ class CartesianChartRenderer {
37491
37547
  }
37492
37548
  context.restore();
37493
37549
  }
37494
- static renderSelectionLayer(context, selectionScene, options, plotRect, styleResolver) {
37550
+ static renderSelectionLayer(context, selectionScene, options, plotRect, styleResolver, axes = []) {
37495
37551
  if (selectionScene && selectionScene.hits.length > 0) {
37496
37552
  const fallback = styleResolver?.resolveSelectionStyle(null);
37497
37553
  CartesianSelectionOverlayRenderer.render(context, selectionScene, {
37554
+ axes,
37498
37555
  color: options?.color ?? fallback?.color,
37499
37556
  fillOpacity: options?.fillOpacity ?? fallback?.fillOpacity,
37500
37557
  plotRect,
@@ -37503,9 +37560,9 @@ class CartesianChartRenderer {
37503
37560
  }
37504
37561
  }
37505
37562
  static renderSeriesLayer(context, scene) {
37506
- const { plotRect, series } = scene;
37563
+ const { axes, plotRect, series } = scene;
37507
37564
  context.save();
37508
- clipToPlotRect(context, plotRect);
37565
+ clipToPlotRect(context, plotRect, axes);
37509
37566
  for (const s of series) {
37510
37567
  switch (s.type) {
37511
37568
  case "area":
@@ -37537,14 +37594,14 @@ class CartesianChartRenderer {
37537
37594
  }
37538
37595
  context.restore();
37539
37596
  }
37540
- static renderStaticOverlayLayer(context, cartesianOverlay, plotRect, annotationBadgeAnchors) {
37541
- CartesianOverlayRenderer.renderOverlays(context, cartesianOverlay, plotRect, annotationBadgeAnchors ?? undefined);
37597
+ static renderStaticOverlayLayer(context, cartesianOverlay, plotRect, annotationBadgeAnchors, axes = []) {
37598
+ CartesianOverlayRenderer.renderOverlays(context, cartesianOverlay, plotRect, axes, annotationBadgeAnchors ?? undefined);
37542
37599
  }
37543
- static renderStaticUnderlayLayer(context, cartesianOverlay, plotRect) {
37544
- CartesianOverlayRenderer.renderUnderlays(context, cartesianOverlay, plotRect);
37600
+ static renderStaticUnderlayLayer(context, cartesianOverlay, plotRect, axes = []) {
37601
+ CartesianOverlayRenderer.renderUnderlays(context, cartesianOverlay, plotRect, axes);
37545
37602
  }
37546
37603
  static renderTransientLayer(context, scene, overlayState, styleResolver) {
37547
- const { plotRect } = scene;
37604
+ const { axes, plotRect } = scene;
37548
37605
  if (!overlayState) {
37549
37606
  return;
37550
37607
  }
@@ -37556,7 +37613,7 @@ class CartesianChartRenderer {
37556
37613
  context.save();
37557
37614
  // Crosshair Lines
37558
37615
  if (crosshairState && crosshairRegistration) {
37559
- CartesianCrosshairRenderer.render(context, crosshairState, crosshairRegistration, plotRect, styleResolver);
37616
+ CartesianCrosshairRenderer.render(context, crosshairState, crosshairRegistration, plotRect, styleResolver, axes);
37560
37617
  }
37561
37618
  // Active Highlights
37562
37619
  if (interactionState) {
@@ -41311,12 +41368,6 @@ class SvgRangeBarSeriesRenderer {
41311
41368
  }
41312
41369
  }
41313
41370
 
41314
- /**
41315
- * Small overflow allowance so marks whose stroke or radius sits exactly at a
41316
- * domain extreme (a peak touching the max, a point at the first/last category)
41317
- * aren't visually chopped in half by the plot clip rectangle.
41318
- */
41319
- const SERIES_CLIP_OVERFLOW$1 = 8;
41320
41371
  class SvgCartesianChartRenderer {
41321
41372
  #axisRenderer;
41322
41373
  #brushRenderer;
@@ -41511,7 +41562,8 @@ class SvgCartesianChartRenderer {
41511
41562
  return;
41512
41563
  }
41513
41564
  this.#clearCrossfadeScopes();
41514
- const plotClipUrl = defs.useClipRect("plot-clip", plotRect.x - SERIES_CLIP_OVERFLOW$1, plotRect.y - SERIES_CLIP_OVERFLOW$1, plotRect.width + SERIES_CLIP_OVERFLOW$1 * 2, plotRect.height + SERIES_CLIP_OVERFLOW$1 * 2);
41565
+ const seriesClipRect = computeSeriesClipRect(plotRect, scene.axes);
41566
+ const plotClipUrl = defs.useClipRect("plot-clip", seriesClipRect.x, seriesClipRect.y, seriesClipRect.width, seriesClipRect.height);
41515
41567
  // 1. Grid
41516
41568
  this.#gridRenderer.render(scene, styleResolver);
41517
41569
  // 2. Static Underlays
@@ -41570,7 +41622,8 @@ class SvgCartesianChartRenderer {
41570
41622
  const fromOpacity = Math.max(0, Math.min(1, 1 - p));
41571
41623
  const toOpacity = Math.max(0, Math.min(1, p));
41572
41624
  const toDefs = defs.withScope("cf-to");
41573
- const toPlotClipUrl = toDefs.useClipRect("plot-clip", toScene.plotRect.x - SERIES_CLIP_OVERFLOW$1, toScene.plotRect.y - SERIES_CLIP_OVERFLOW$1, toScene.plotRect.width + SERIES_CLIP_OVERFLOW$1 * 2, toScene.plotRect.height + SERIES_CLIP_OVERFLOW$1 * 2);
41625
+ const toSeriesClipRect = computeSeriesClipRect(toScene.plotRect, toScene.axes);
41626
+ const toPlotClipUrl = toDefs.useClipRect("plot-clip", toSeriesClipRect.x, toSeriesClipRect.y, toSeriesClipRect.width, toSeriesClipRect.height);
41574
41627
  // Persistent selection and data labels are suppressed during animation
41575
41628
  this.#selectionRenderer.clear();
41576
41629
  this.#dataLabelRenderer.clear();
@@ -41615,7 +41668,8 @@ class SvgCartesianChartRenderer {
41615
41668
  this.#layers.series.removeAttribute("clip-path");
41616
41669
  if (fromScene) {
41617
41670
  const fromDefs = defs.withScope("cf-from");
41618
- const fromPlotClipUrl = fromDefs.useClipRect("plot-clip", fromScene.plotRect.x - SERIES_CLIP_OVERFLOW$1, fromScene.plotRect.y - SERIES_CLIP_OVERFLOW$1, fromScene.plotRect.width + SERIES_CLIP_OVERFLOW$1 * 2, fromScene.plotRect.height + SERIES_CLIP_OVERFLOW$1 * 2);
41671
+ const fromSeriesClipRect = computeSeriesClipRect(fromScene.plotRect, fromScene.axes);
41672
+ const fromPlotClipUrl = fromDefs.useClipRect("plot-clip", fromSeriesClipRect.x, fromSeriesClipRect.y, fromSeriesClipRect.width, fromSeriesClipRect.height);
41619
41673
  if (!this.#crossfadeFromScope) {
41620
41674
  this.#crossfadeFromScope = createSvgElement("g");
41621
41675
  this.#crossfadeFromScope.setAttribute("data-crossfade-scope", "from");
@@ -41684,12 +41738,6 @@ class SvgCartesianChartRenderer {
41684
41738
  }
41685
41739
  }
41686
41740
 
41687
- /**
41688
- * Small overflow allowance so marks whose stroke or radius sits exactly at a
41689
- * domain extreme (a peak touching the max, a point at the first/last category)
41690
- * aren't visually chopped in half by the plot clip rectangle.
41691
- */
41692
- const SERIES_CLIP_OVERFLOW = 8;
41693
41741
  class SvgCartesianContentRenderer {
41694
41742
  #axesGroup;
41695
41743
  #axisRenderer;
@@ -41827,7 +41875,8 @@ class SvgCartesianContentRenderer {
41827
41875
  this.clear();
41828
41876
  return;
41829
41877
  }
41830
- const plotClipUrl = defs.useClipRect("plot-clip", plotRect.x - SERIES_CLIP_OVERFLOW, plotRect.y - SERIES_CLIP_OVERFLOW, plotRect.width + SERIES_CLIP_OVERFLOW * 2, plotRect.height + SERIES_CLIP_OVERFLOW * 2);
41878
+ const seriesClipRect = computeSeriesClipRect(plotRect, scene.axes);
41879
+ const plotClipUrl = defs.useClipRect("plot-clip", seriesClipRect.x, seriesClipRect.y, seriesClipRect.width, seriesClipRect.height);
41831
41880
  // 1. Grid
41832
41881
  this.#gridRenderer.render(scene, styleResolver);
41833
41882
  // 2. Underlays
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nanahoshi/mona-ui",
3
- "version": "0.17.0",
3
+ "version": "0.17.1",
4
4
  "description": "A very light Angular UI framework",
5
5
  "author": "Nanahoshi",
6
6
  "repository": {