@internetstiftelsen/charts 0.19.0 → 0.19.2

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.
@@ -39,6 +39,10 @@ export declare abstract class RadialChartBase extends BaseChart {
39
39
  protected showTooltipAtElement(target: Element, content: string): void;
40
40
  protected hideTooltip(): void;
41
41
  private applyTooltipPosition;
42
+ private getTooltipViewportLeft;
43
+ private getTooltipViewportRight;
44
+ private getTooltipViewportTop;
45
+ private getTooltipViewportBottom;
42
46
  private resolveRadialLabelLayout;
43
47
  private resolveRadialLabelOverflowContext;
44
48
  private createVisibleRadialLabelLayout;
@@ -159,7 +159,7 @@ export class RadialChartBase extends BaseChart {
159
159
  }
160
160
  let x = event.pageX + TOOLTIP_OFFSET_PX;
161
161
  const y = event.pageY - rect.height / 2;
162
- if (x + rect.width > window.innerWidth - EDGE_MARGIN_PX) {
162
+ if (x + rect.width > this.getTooltipViewportRight()) {
163
163
  x = event.pageX - rect.width - TOOLTIP_OFFSET_PX;
164
164
  }
165
165
  this.applyTooltipPosition(x, y, rect.height, rect.width);
@@ -182,7 +182,7 @@ export class RadialChartBase extends BaseChart {
182
182
  window.scrollY +
183
183
  targetRect.height / 2 -
184
184
  tooltipRect.height / 2;
185
- if (x + tooltipRect.width > window.innerWidth - EDGE_MARGIN_PX) {
185
+ if (x + tooltipRect.width > this.getTooltipViewportRight()) {
186
186
  x =
187
187
  targetRect.left +
188
188
  window.scrollX -
@@ -195,10 +195,22 @@ export class RadialChartBase extends BaseChart {
195
195
  this.tooltip?.hide();
196
196
  }
197
197
  applyTooltipPosition(rawX, rawY, height, width) {
198
- const x = Math.max(EDGE_MARGIN_PX, Math.min(rawX, window.innerWidth + window.scrollX - width - EDGE_MARGIN_PX));
199
- const y = Math.max(EDGE_MARGIN_PX, Math.min(rawY, window.innerHeight + window.scrollY - height - EDGE_MARGIN_PX));
198
+ const x = Math.max(this.getTooltipViewportLeft(), Math.min(rawX, this.getTooltipViewportRight() - width));
199
+ const y = Math.max(this.getTooltipViewportTop(), Math.min(rawY, this.getTooltipViewportBottom() - height));
200
200
  this.tooltip?.showAt(x, y);
201
201
  }
202
+ getTooltipViewportLeft() {
203
+ return window.scrollX + EDGE_MARGIN_PX;
204
+ }
205
+ getTooltipViewportRight() {
206
+ return window.scrollX + window.innerWidth - EDGE_MARGIN_PX;
207
+ }
208
+ getTooltipViewportTop() {
209
+ return window.scrollY + EDGE_MARGIN_PX;
210
+ }
211
+ getTooltipViewportBottom() {
212
+ return window.scrollY + window.innerHeight - EDGE_MARGIN_PX;
213
+ }
202
214
  resolveRadialLabelLayout(text, options) {
203
215
  const overflowContext = this.resolveRadialLabelOverflowContext(options.maxLabelWidth);
204
216
  if (!overflowContext) {
@@ -17,6 +17,7 @@ export declare class TooltipDom {
17
17
  private readonly transition;
18
18
  private readonly tooltipStyleKeys;
19
19
  private readonly tooltipTransitionFrameIds;
20
+ private readonly tooltipPositionResetTimeoutIds;
20
21
  private readonly tooltipStyles;
21
22
  private tooltipDiv;
22
23
  constructor(config: TooltipDomConfig);
@@ -45,6 +46,9 @@ export declare class TooltipDom {
45
46
  private showTooltipAt;
46
47
  private showTooltipSelection;
47
48
  private hideTooltipElement;
49
+ private resetTooltipPosition;
50
+ private scheduleTooltipPositionReset;
51
+ private cancelTooltipPositionReset;
48
52
  private getTooltipTransitionStyle;
49
53
  private isTooltipVisible;
50
54
  private getTooltipPosition;
@@ -1,6 +1,7 @@
1
1
  import { select } from 'd3';
2
2
  import { resolveTooltipArrowPosition, resolveTooltipConnectorLayout, } from './geometry.js';
3
3
  import { TOOLTIP_ARROW_BORDER_Z_INDEX, TOOLTIP_ARROW_FILL_Z_INDEX, TOOLTIP_BODY_Z_INDEX, TOOLTIP_BORDER_WIDTH_PX, TOOLTIP_BOX_ARROW_HALF_HEIGHT_PX, TOOLTIP_BOX_ARROW_LENGTH_PX, TOOLTIP_CONNECTOR_Z_INDEX, TOOLTIP_HIDDEN_TRANSFORM, TOOLTIP_ROOT_Z_INDEX, TOOLTIP_VISIBLE_TRANSFORM, } from './types.js';
4
+ const HIDDEN_TOOLTIP_POSITION_PX = '0px';
4
5
  export class TooltipDom {
5
6
  constructor(config) {
6
7
  Object.defineProperty(this, "id", {
@@ -39,6 +40,12 @@ export class TooltipDom {
39
40
  writable: true,
40
41
  value: new WeakMap()
41
42
  });
43
+ Object.defineProperty(this, "tooltipPositionResetTimeoutIds", {
44
+ enumerable: true,
45
+ configurable: true,
46
+ writable: true,
47
+ value: new WeakMap()
48
+ });
42
49
  Object.defineProperty(this, "tooltipStyles", {
43
50
  enumerable: true,
44
51
  configurable: true,
@@ -284,15 +291,16 @@ export class TooltipDom {
284
291
  : null);
285
292
  }
286
293
  showTooltipSelection(tooltip, slideOffset = null) {
294
+ const node = tooltip.node();
295
+ if (!node) {
296
+ return;
297
+ }
298
+ this.cancelTooltipPositionReset(node);
287
299
  tooltip.style('visibility', 'visible');
288
300
  if (!this.transition.show) {
289
301
  return;
290
302
  }
291
303
  tooltip.style('opacity', '1');
292
- const node = tooltip.node();
293
- if (!node) {
294
- return;
295
- }
296
304
  if (!slideOffset || !this.hasVisibleSlideOffset(slideOffset)) {
297
305
  this.cancelTooltipTransitionFrame(node);
298
306
  tooltip.style('transform', TOOLTIP_VISIBLE_TRANSFORM);
@@ -301,14 +309,43 @@ export class TooltipDom {
301
309
  this.slideTooltipFromOffset(node, slideOffset);
302
310
  }
303
311
  hideTooltipElement(node) {
312
+ const wasVisible = this.isTooltipVisible(node);
313
+ this.cancelTooltipPositionReset(node);
304
314
  this.cancelTooltipTransitionFrame(node);
305
315
  if (!this.transition.show) {
306
316
  node.style.visibility = 'hidden';
317
+ this.resetTooltipPosition(node);
307
318
  return;
308
319
  }
309
320
  node.style.visibility = 'visible';
310
321
  node.style.opacity = '0';
311
322
  node.style.transform = TOOLTIP_HIDDEN_TRANSFORM;
323
+ if (!wasVisible) {
324
+ this.resetTooltipPosition(node);
325
+ return;
326
+ }
327
+ this.scheduleTooltipPositionReset(node);
328
+ }
329
+ resetTooltipPosition(node) {
330
+ node.style.left = HIDDEN_TOOLTIP_POSITION_PX;
331
+ node.style.top = HIDDEN_TOOLTIP_POSITION_PX;
332
+ }
333
+ scheduleTooltipPositionReset(node) {
334
+ const timeoutId = window.setTimeout(() => {
335
+ this.tooltipPositionResetTimeoutIds.delete(node);
336
+ if (!this.isTooltipVisible(node)) {
337
+ this.resetTooltipPosition(node);
338
+ }
339
+ }, this.transition.duration);
340
+ this.tooltipPositionResetTimeoutIds.set(node, timeoutId);
341
+ }
342
+ cancelTooltipPositionReset(node) {
343
+ const timeoutId = this.tooltipPositionResetTimeoutIds.get(node);
344
+ if (timeoutId === undefined) {
345
+ return;
346
+ }
347
+ window.clearTimeout(timeoutId);
348
+ this.tooltipPositionResetTimeoutIds.delete(node);
312
349
  }
313
350
  getTooltipTransitionStyle() {
314
351
  return `opacity ${this.transition.duration}ms ${this.transition.easing}, transform ${this.transition.duration}ms ${this.transition.easing}`;
@@ -525,8 +525,97 @@ function packHorizontalAboveBelowRow(layouts, arrowEdge, bounds) {
525
525
  top: getHorizontalAboveBelowTop(layout, arrowEdge),
526
526
  arrowEdge,
527
527
  }));
528
+ if (shouldWrapHorizontalAboveBelowRow(placements, arrowEdge, bounds)) {
529
+ return packHorizontalAboveBelowRows(placements, arrowEdge, bounds);
530
+ }
528
531
  return packHorizontalTooltipRow(placements, bounds);
529
532
  }
533
+ function packHorizontalAboveBelowRows(placements, arrowEdge, bounds) {
534
+ const rows = getHorizontalTooltipRows(placements, bounds);
535
+ if (arrowEdge === 'bottom') {
536
+ positionHorizontalTooltipRowsAbove(rows);
537
+ }
538
+ else {
539
+ positionHorizontalTooltipRowsBelow(rows);
540
+ }
541
+ return rows.flatMap((row) => packHorizontalTooltipRow(row, bounds));
542
+ }
543
+ function shouldWrapHorizontalAboveBelowRow(placements, arrowEdge, bounds) {
544
+ if (placements.length < 2) {
545
+ return false;
546
+ }
547
+ const availableWidth = Math.max(0, bounds.maxRight - bounds.minLeft);
548
+ const totalWidth = placements.reduce((sum, placement, index) => {
549
+ const gap = index === 0 ? 0 : SPLIT_TOOLTIP_GAP_PX;
550
+ return sum + gap + placement.layout.width;
551
+ }, 0);
552
+ if (totalWidth <= availableWidth) {
553
+ return false;
554
+ }
555
+ return !hasRoomOnOppositeHorizontalTooltipSide(placements, arrowEdge, bounds);
556
+ }
557
+ function hasRoomOnOppositeHorizontalTooltipSide(placements, arrowEdge, bounds) {
558
+ const rowHeight = getHorizontalTooltipRowHeight(placements);
559
+ if (arrowEdge === 'top') {
560
+ const oppositeTop = Math.min(...placements.map((placement) => placement.layout.anchor.top)) -
561
+ TOOLTIP_OFFSET_PX -
562
+ rowHeight;
563
+ return oppositeTop >= bounds.minTop;
564
+ }
565
+ const oppositeBottom = Math.max(...placements.map((placement) => placement.layout.anchor.bottom)) +
566
+ TOOLTIP_OFFSET_PX +
567
+ rowHeight;
568
+ return oppositeBottom <= bounds.maxBottom;
569
+ }
570
+ function getHorizontalTooltipRows(placements, bounds) {
571
+ const availableWidth = Math.max(0, bounds.maxRight - bounds.minLeft);
572
+ const rows = [];
573
+ let currentRow = [];
574
+ let currentRowWidth = 0;
575
+ placements.forEach((placement) => {
576
+ const nextRowWidth = currentRow.length === 0
577
+ ? placement.layout.width
578
+ : currentRowWidth +
579
+ SPLIT_TOOLTIP_GAP_PX +
580
+ placement.layout.width;
581
+ if (currentRow.length > 0 && nextRowWidth > availableWidth) {
582
+ rows.push(currentRow);
583
+ currentRow = [placement];
584
+ currentRowWidth = placement.layout.width;
585
+ return;
586
+ }
587
+ currentRow.push(placement);
588
+ currentRowWidth = nextRowWidth;
589
+ });
590
+ if (currentRow.length > 0) {
591
+ rows.push(currentRow);
592
+ }
593
+ return rows;
594
+ }
595
+ function positionHorizontalTooltipRowsAbove(rows) {
596
+ let nextRowBottom = Math.min(...rows.flatMap((row) => row.map((placement) => placement.top + placement.layout.height)));
597
+ rows.forEach((row) => {
598
+ const rowHeight = getHorizontalTooltipRowHeight(row);
599
+ const rowTop = nextRowBottom - rowHeight;
600
+ row.forEach((placement) => {
601
+ placement.top = rowTop;
602
+ });
603
+ nextRowBottom = rowTop - SPLIT_TOOLTIP_GAP_PX;
604
+ });
605
+ }
606
+ function positionHorizontalTooltipRowsBelow(rows) {
607
+ let nextRowTop = Math.max(...rows.flatMap((row) => row.map((placement) => placement.top)));
608
+ rows.forEach((row) => {
609
+ const rowHeight = getHorizontalTooltipRowHeight(row);
610
+ row.forEach((placement) => {
611
+ placement.top = nextRowTop;
612
+ });
613
+ nextRowTop += rowHeight + SPLIT_TOOLTIP_GAP_PX;
614
+ });
615
+ }
616
+ function getHorizontalTooltipRowHeight(row) {
617
+ return Math.max(...row.map((placement) => placement.layout.height));
618
+ }
530
619
  function packHorizontalTooltipRow(placements, bounds) {
531
620
  if (placements.length === 0) {
532
621
  return placements;
@@ -809,7 +809,7 @@ function attachTooltipScrollListeners(svgNode, onScroll) {
809
809
  };
810
810
  }
811
811
  function getTooltipScrollTargets(svgNode) {
812
- const scrollTargets = new Set([window]);
812
+ const scrollTargets = new Set();
813
813
  let currentElement = svgNode.parentElement;
814
814
  while (currentElement) {
815
815
  if (isScrollableElement(currentElement)) {
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.19.0",
2
+ "version": "0.19.2",
3
3
  "name": "@internetstiftelsen/charts",
4
4
  "type": "module",
5
5
  "sideEffects": false,