gridviz 3.0.17 → 3.0.18

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gridviz",
3
- "version": "3.0.17",
3
+ "version": "3.0.18",
4
4
  "description": "Visualization tool for gridded statistics",
5
5
  "keywords": [
6
6
  "statistics",
@@ -46,7 +46,7 @@ export class Button {
46
46
  this.style('display', 'flex')
47
47
  this.style('justify-content', 'center')
48
48
  this.style('align-items', 'center')
49
- this.style('width', '30px')
49
+ this.style('width', '35px')
50
50
  this.style('height', '30px')
51
51
  // this.style(padding , '4px'
52
52
 
@@ -87,24 +87,29 @@ export class GeoCanvas {
87
87
  .on('zoom', (e) => {
88
88
  const t = e.transform
89
89
  const f = tP.k / t.k
90
- if (f == 1) {
91
- //pan
90
+
91
+ // Get container's bounding rect to adjust for offsets
92
+ const containerRect = this.canvas.getBoundingClientRect()
93
+
94
+ // Adjust for container's offset and zoom center
95
+ const offsetX = e.sourceEvent.offsetX - containerRect.left
96
+ const offsetY = e.sourceEvent.offsetY - containerRect.top
97
+
98
+ if (f === 1) {
99
+ // Pan logic
92
100
  const dx = tP.x - t.x
93
101
  const dy = tP.y - t.y
94
102
  this.pan(dx * this.view.z, -dy * this.view.z)
95
103
  } else {
96
104
  const se = e.sourceEvent
105
+
97
106
  if (se instanceof WheelEvent) {
98
- //zoom at the mouse position
99
- this.zoom(
100
- f,
101
- this.pixToGeoX(e.sourceEvent.offsetX),
102
- this.pixToGeoY(e.sourceEvent.offsetY)
103
- )
107
+ // Zoom at mouse position, adjusted by container offset
108
+ this.zoom(f, this.pixToGeoX(offsetX), this.pixToGeoY(offsetY))
104
109
  } else if (se instanceof TouchEvent) {
105
110
  if (!se.targetTouches.length) return
106
- //pinch zoom
107
- //compute average position of the touches
111
+
112
+ // Compute average position of the touches
108
113
  let tx = 0,
109
114
  ty = 0
110
115
  for (let tt of se.targetTouches) {
@@ -113,7 +118,12 @@ export class GeoCanvas {
113
118
  }
114
119
  tx /= se.targetTouches.length
115
120
  ty /= se.targetTouches.length
116
- //zoom at this average position
121
+
122
+ // Adjust for container's offset
123
+ tx -= containerRect.left
124
+ ty -= containerRect.top
125
+
126
+ // Zoom at the average touch position
117
127
  this.zoom(f, this.pixToGeoX(tx), this.pixToGeoY(ty))
118
128
  }
119
129
  }
package/src/core/Map.js CHANGED
@@ -60,9 +60,10 @@ export class Map {
60
60
  }
61
61
 
62
62
  // legend div
63
- this.legendDivId = opts.legendDivId || 'gvizLegend'
64
- this.legend = select('#' + this.legendDivId)
65
- if (this.legend.empty()) this.initialiseLegend()
63
+ this.legend = opts.legendContainer
64
+ ? select(opts.legendContainer) // Wrap the provided HTML node in a D3 selection
65
+ : null
66
+ if (!this.legend) this.initialiseLegend()
66
67
 
67
68
  //tooltip
68
69
 
@@ -122,9 +123,9 @@ export class Map {
122
123
  }
123
124
 
124
125
  initialiseLegend() {
125
- this.legend = select(this.container.id && this.container.id != '' ? '#' + this.container.id : 'body')
126
- .append('div')
127
- .attr('id', this.legendDivId)
126
+ this.legend = select(this.container)
127
+ .append('div') // Create a new container
128
+ .attr('id', 'gridviz-legend')
128
129
  .style('position', 'absolute')
129
130
  .style('width', 'auto')
130
131
  .style('height', 'auto')
@@ -432,35 +433,43 @@ export class Map {
432
433
  * @memberof App
433
434
  */
434
435
  defineResizeObserver() {
435
- // listen to resize events
436
+ // Track whether the observer is currently processing a resize event
437
+ let resizePending = false
438
+
436
439
  const resizeObserver = new ResizeObserver((entries) => {
440
+ if (!Array.isArray(entries) || !entries.length) return
441
+
437
442
  let container = this.container
438
- // make sure canvas has been built
443
+
444
+ // Ensure the container has valid dimensions
439
445
  if (container.clientWidth > 0 && container.clientHeight > 0) {
440
- // make sure we dont exceed loop limit first
441
- // see: https://stackoverflow.com/questions/49384120/resizeobserver-loop-limit-exceeded
442
- window.requestAnimationFrame(() => {
443
- if (!Array.isArray(entries) || !entries.length) {
444
- return
445
- }
446
- // update the map and canvas size
447
- if (this.h !== container.clientHeight || this.w !== container.clientWidth) {
448
- this.h = container.clientHeight
449
- this.w = container.clientWidth
450
- this.geoCanvas.h = container.clientHeight
451
- this.geoCanvas.w = container.clientWidth
452
- this.geoCanvas.canvas.setAttribute('width', '' + this.w)
453
- this.geoCanvas.canvas.setAttribute('height', '' + this.h)
454
- // offscreen canvas
455
- this.geoCanvas.offscreenCanvas.setAttribute('width', '' + this.w)
456
- this.geoCanvas.offscreenCanvas.setAttribute('height', '' + this.h)
457
- this.redraw()
458
-
459
- //update button positions
460
- // if (this.zoomButtons) this.zoomButtons.node.style.left = this.w - 50 + 'px'
461
- // if (this.fullscreenButton) this.fullscreenButton.node.style.left = this.w - 50 + 'px'
462
- }
463
- })
446
+ if (!resizePending) {
447
+ resizePending = true // Prevent overlapping resize triggers
448
+
449
+ window.requestAnimationFrame(() => {
450
+ resizePending = false // Reset the flag after processing
451
+
452
+ // Check for size changes
453
+ if (this.h !== container.clientHeight || this.w !== container.clientWidth) {
454
+ this.h = container.clientHeight
455
+ this.w = container.clientWidth
456
+
457
+ // Update geoCanvas sizes
458
+ this.geoCanvas.h = this.h
459
+ this.geoCanvas.w = this.w
460
+ this.geoCanvas.canvas.setAttribute('width', String(this.w))
461
+ this.geoCanvas.canvas.setAttribute('height', String(this.h))
462
+ this.geoCanvas.offscreenCanvas.setAttribute('width', String(this.w))
463
+ this.geoCanvas.offscreenCanvas.setAttribute('height', String(this.h))
464
+
465
+ this.redraw()
466
+
467
+ // Optionally reposition UI elements
468
+ // if (this.zoomButtons) this.zoomButtons.node.style.left = this.w - 50 + 'px';
469
+ // if (this.fullscreenButton) this.fullscreenButton.node.style.left = this.w - 50 + 'px';
470
+ }
471
+ })
472
+ }
464
473
  }
465
474
  })
466
475
 
@@ -16,8 +16,6 @@ export class Tooltip {
16
16
  constructor(opts) {
17
17
  opts = opts || {}
18
18
 
19
- /** @type {string} */
20
- this.div = opts.div || 'tooltip_eurostat'
21
19
  /** @type {string} */
22
20
  this.maxWidth = opts.maxWidth || '20em'
23
21
  /** @type {string} */
@@ -47,21 +45,15 @@ export class Tooltip {
47
45
  this.xMouseOffset = opts.xMouseOffset || 0
48
46
  /** @type {HTMLElement} */
49
47
  this.parentElement = opts.parentElement || document.body
48
+ /** @type {HTMLElement} */
49
+ this.tooltipElement = opts.tooltipElement || null
50
50
 
51
51
  /**
52
52
  * @public
53
53
  * @type {import("d3-selection").Selection} */
54
- this.tooltip = select('#' + this.div)
55
-
56
- if (this.tooltip.empty()) {
57
- //create tooltip DOM node
58
- // this.tooltip = select(
59
- // '#' + this.parentElement.id && this.parentElement.id != ''
60
- // ? '#' + this.parentElement.id
61
- // : 'body'
62
- // )
63
- this.tooltip = select('body').append('div').attr('id', this.div)
64
- }
54
+ this.tooltip = opts.tooltipElement
55
+ ? select(opts.tooltipElement) // Wrap the provided HTML node in a D3 selection
56
+ : select(this.parentElement).append('div').attr('id', 'gridviz-tooltip').attr('class', 'gridviz-tooltip') // create default element
65
57
 
66
58
  //initialise
67
59
  this.tooltip.style('max-width', this.maxWidth)
@@ -78,6 +70,10 @@ export class Tooltip {
78
70
  this.tooltip.style('opacity', '0')
79
71
  this.tooltip.style('text-wrap', 'nowrap')
80
72
 
73
+ // these placeholders are needed to prevent an infinite DOM resizeObserver loop:
74
+ this.tooltip.style('left', '0')
75
+ this.tooltip.style('top', '0')
76
+
81
77
  // aria-labels (thanks to wahlatlas)
82
78
  this.tooltip.attr('role', 'tooltip').attr('aria-live', 'polite')
83
79
  }
@@ -107,14 +103,51 @@ export class Tooltip {
107
103
  * @param {MouseEvent} event
108
104
  */
109
105
  setPosition(event) {
106
+ // Get the bounding rect of the parent container (map2)
110
107
  let parentRect = this.parentElement.getBoundingClientRect()
111
108
 
112
- let x = event.pageX + this.xOffset
113
- let y = event.pageY - this.yOffset
109
+ // Get the mouse position (relative to the parent container)
110
+ let x = event.clientX - parentRect.left + this.xOffset // Relative to parent
111
+ let y = event.clientY - parentRect.top - this.yOffset // Relative to parent
114
112
 
113
+ // Now, apply the position to the tooltip
115
114
  this.tooltip.style('left', x + 'px').style('top', y + 'px')
116
115
 
117
- this.ensureTooltipInsideContainer(event, parentRect)
116
+ // Ensure the tooltip stays inside the parent container
117
+ this.ensureTooltipInsideContainer(event, parentRect, this.tooltip.node())
118
+ }
119
+ /**
120
+ * @function ensureTooltipInsideContainer
121
+ * @description Prevents the tooltip from overflowing out of the App container (ensures that the tooltip is inside the gridviz container)
122
+ * @param {MouseEvent} event
123
+ * @param {DOMRect} parentRect
124
+ * @param {HTMLElement} tooltipNode
125
+ */
126
+ ensureTooltipInsideContainer(event, parentRect, tooltipNode) {
127
+ let node = tooltipNode
128
+ let parentWidth = parentRect.width
129
+ let parentHeight = parentRect.height
130
+
131
+ // Ensure tooltip doesn't go beyond the right edge
132
+ if (node.offsetLeft + node.clientWidth > parentWidth) {
133
+ let left = event.clientX - node.clientWidth - this.xOffset
134
+ node.style.left = left + 'px'
135
+ }
136
+
137
+ // Ensure tooltip doesn't go beyond the bottom edge
138
+ if (node.offsetTop + node.clientHeight > parentHeight) {
139
+ node.style.top = parentHeight - node.clientHeight + 'px'
140
+ }
141
+
142
+ // Ensure tooltip doesn't go above the top edge
143
+ if (node.offsetTop < 0) {
144
+ node.style.top = 0 + 'px'
145
+ }
146
+
147
+ // Ensure tooltip doesn't go beyond the left edge
148
+ if (node.offsetLeft < 0) {
149
+ node.style.left = 0 + 'px'
150
+ }
118
151
  }
119
152
 
120
153
  /*
@@ -145,39 +178,4 @@ export class Tooltip {
145
178
  this.tooltip.attr(k, v)
146
179
  return this
147
180
  }
148
-
149
- /**
150
- * @function ensureTooltipInsideContainer
151
- * @description Prevents the tooltip from overflowing out of the App container (ensures that the tooltip is inside the gridviz container)
152
- * @param {MouseEvent} event
153
- * @param {DOMRect} parentRect
154
- */
155
- ensureTooltipInsideContainer = function (event, parentRect) {
156
- let node = this.tooltip.node()
157
- let parentWidth = parentRect.width
158
- let parentHeight = parentRect.height
159
-
160
- //too far right
161
- if (node.offsetLeft > parentRect.left + parentWidth - node.clientWidth) {
162
- let left = event.x - node.clientWidth - this.xOffset
163
- node.style.left = left + 'px'
164
- // check if mouse covers tooltip
165
- if (node.offsetLeft + node.clientWidth > event.x) {
166
- //move tooltip left so it doesnt cover mouse
167
- let left2 = event.x - node.clientWidth - this.xOffset
168
- node.style.left = left2 + 'px'
169
- }
170
- // node.style.top = node.offsetTop + config.yOffset + "px";
171
- }
172
-
173
- //too far down
174
- if (node.offsetTop + node.clientHeight > parentRect.top + parentHeight) {
175
- node.style.top = node.offsetTop - node.clientHeight + 'px'
176
- }
177
-
178
- //too far up
179
- if (node.offsetTop < parentRect.top) {
180
- node.style.top = parentRect.top + this.yOffset + 'px'
181
- }
182
- }
183
181
  }