Skip to content

Commit

Permalink
added tooltipElement option to tooltip config. Attempted fix for mult…
Browse files Browse the repository at this point in the history
…iple instances with multiple tooltips (issues/130)
  • Loading branch information
joewdavies committed Dec 2, 2024
1 parent a7fc123 commit 75f835b
Show file tree
Hide file tree
Showing 6 changed files with 286 additions and 157 deletions.
162 changes: 84 additions & 78 deletions dist/gridviz.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/gridviz.min.js

Large diffs are not rendered by default.

117 changes: 117 additions & 0 deletions examples/test/test-tooltips.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
<!DOCTYPE html>
<html lang="en" style="height: 100%">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Gridviz tooltips test</title>
<style>
.container {
position: relative;
margin: 17px 0;
min-height: 1.5rem;
}
.filler {
background-color: bisque;
height: 50px;
width: 50%;
}
</style>
</head>

<body>
<h1>Test multiple instances / tooltips</h1>

<div class="container">
<div class="filler"></div>
<div id="map" style="height: 300px; width: 50%"></div>
</div>

<div class="container">
<div class="filler"></div>
<div id="map2" style="height: 300px; width: 50%"></div>
</div>

<script src="../../../dist/gridviz.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]"></script>
<script src="https://cdn.jsdelivr.net/npm/d3-color@3"></script>
<script src="https://cdn.jsdelivr.net/npm/d3-interpolate@3"></script>
<script src="https://cdn.jsdelivr.net/npm/d3-scale-chromatic@3"></script>
<script>
const container = document.getElementById('map')
const container2 = document.getElementById('map2')
// define map

const map = new gridviz.Map(container, {
x: 4500000,
y: 2900000,
z: 2000,
}).addZoomButtons()
const map2 = new gridviz.Map(container2, {
x: 4500000,
y: 2900000,
z: 2000,
}).addZoomButtons()

//define multi resolution dataset
const dataset = new gridviz.MultiResolutionDataset(
//the resolutions
[1000, 2000, 5000, 10000, 20000, 50000, 100000],
//the function returning each dataset from the resolution
(resolution) =>
new gridviz.TiledGrid(
map,
'https://raw.githubusercontent.com/jgaffuri/tiledgrids/main/data/europe/population2/' +
resolution +
'm/',
{
onlyDrawWhenAllTilesReady: true,
}
)
)

const dataset2 = new gridviz.MultiResolutionDataset(
//the resolutions
[1000, 2000, 5000, 10000, 20000, 50000, 100000],
//the function returning each dataset from the resolution
(resolution) =>
new gridviz.TiledGrid(
map2,
'https://raw.githubusercontent.com/jgaffuri/tiledgrids/main/data/europe/population2/' +
resolution +
'm/',
{
onlyDrawWhenAllTilesReady: true,
}
)
)

//define color for each cell c
const colorFunction = (cell, resolution) => {
const density = (1000000 * cell.TOT_P_2021) / (resolution * resolution)
if (density > 1500) return '#993404'
else if (density > 600) return '#d95f0e'
else if (density > 200) return '#fe9929'
else if (density > 60) return '#fec44f'
else if (density > 15) return '#fee391'
else return '#ffffd4'
}

//define style
const style = new gridviz.ShapeColorSizeStyle({
color: colorFunction,
})

//add layer to map
map.layers = [
new gridviz.GridLayer(dataset, [style], {
minPixelsPerCell: 1,
}),
]

map2.layers = [
new gridviz.GridLayer(dataset2, [style], {
minPixelsPerCell: 1,
}),
]
</script>
</body>
</html>
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "gridviz",
"version": "3.0.17",
"version": "3.0.18",
"description": "Visualization tool for gridded statistics",
"keywords": [
"statistics",
Expand Down
60 changes: 34 additions & 26 deletions src/core/Map.js
Original file line number Diff line number Diff line change
Expand Up @@ -433,35 +433,43 @@ export class Map {
* @memberof App
*/
defineResizeObserver() {
// listen to resize events
// Track whether the observer is currently processing a resize event
let resizePending = false

const resizeObserver = new ResizeObserver((entries) => {
if (!Array.isArray(entries) || !entries.length) return

let container = this.container
// make sure canvas has been built

// Ensure the container has valid dimensions
if (container.clientWidth > 0 && container.clientHeight > 0) {
// make sure we dont exceed loop limit first
// see: https://stackoverflow.com/questions/49384120/resizeobserver-loop-limit-exceeded
window.requestAnimationFrame(() => {
if (!Array.isArray(entries) || !entries.length) {
return
}
// update the map and canvas size
if (this.h !== container.clientHeight || this.w !== container.clientWidth) {
this.h = container.clientHeight
this.w = container.clientWidth
this.geoCanvas.h = container.clientHeight
this.geoCanvas.w = container.clientWidth
this.geoCanvas.canvas.setAttribute('width', '' + this.w)
this.geoCanvas.canvas.setAttribute('height', '' + this.h)
// offscreen canvas
this.geoCanvas.offscreenCanvas.setAttribute('width', '' + this.w)
this.geoCanvas.offscreenCanvas.setAttribute('height', '' + this.h)
this.redraw()

//update button positions
// if (this.zoomButtons) this.zoomButtons.node.style.left = this.w - 50 + 'px'
// if (this.fullscreenButton) this.fullscreenButton.node.style.left = this.w - 50 + 'px'
}
})
if (!resizePending) {
resizePending = true // Prevent overlapping resize triggers

window.requestAnimationFrame(() => {
resizePending = false // Reset the flag after processing

// Check for size changes
if (this.h !== container.clientHeight || this.w !== container.clientWidth) {
this.h = container.clientHeight
this.w = container.clientWidth

// Update geoCanvas sizes
this.geoCanvas.h = this.h
this.geoCanvas.w = this.w
this.geoCanvas.canvas.setAttribute('width', String(this.w))
this.geoCanvas.canvas.setAttribute('height', String(this.h))
this.geoCanvas.offscreenCanvas.setAttribute('width', String(this.w))
this.geoCanvas.offscreenCanvas.setAttribute('height', String(this.h))

this.redraw()

// Optionally reposition UI elements
// if (this.zoomButtons) this.zoomButtons.node.style.left = this.w - 50 + 'px';
// if (this.fullscreenButton) this.fullscreenButton.node.style.left = this.w - 50 + 'px';
}
})
}
}
})

Expand Down
100 changes: 49 additions & 51 deletions src/core/Tooltip.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@ export class Tooltip {
constructor(opts) {
opts = opts || {}

/** @type {string} */
this.div = opts.div || 'tooltip_eurostat'
/** @type {string} */
this.maxWidth = opts.maxWidth || '20em'
/** @type {string} */
Expand Down Expand Up @@ -47,21 +45,15 @@ export class Tooltip {
this.xMouseOffset = opts.xMouseOffset || 0
/** @type {HTMLElement} */
this.parentElement = opts.parentElement || document.body
/** @type {HTMLElement} */
this.tooltipElement = opts.tooltipElement || null

/**
* @public
* @type {import("d3-selection").Selection} */
this.tooltip = select('#' + this.div)

if (this.tooltip.empty()) {
//create tooltip DOM node
// this.tooltip = select(
// '#' + this.parentElement.id && this.parentElement.id != ''
// ? '#' + this.parentElement.id
// : 'body'
// )
this.tooltip = select('body').append('div').attr('id', this.div)
}
this.tooltip = opts.tooltipElement
? select(opts.tooltipElement) // Wrap the provided HTML node in a D3 selection
: select(this.parentElement).append('div').attr('id', 'gridviz-tooltip').attr('class', 'gridviz-tooltip') // create default element

//initialise
this.tooltip.style('max-width', this.maxWidth)
Expand All @@ -78,6 +70,10 @@ export class Tooltip {
this.tooltip.style('opacity', '0')
this.tooltip.style('text-wrap', 'nowrap')

// these placeholders are needed to prevent an infinite DOM resizeObserver loop:
this.tooltip.style('left', '0')
this.tooltip.style('top', '0')

// aria-labels (thanks to wahlatlas)
this.tooltip.attr('role', 'tooltip').attr('aria-live', 'polite')
}
Expand Down Expand Up @@ -107,14 +103,51 @@ export class Tooltip {
* @param {MouseEvent} event
*/
setPosition(event) {
// Get the bounding rect of the parent container (map2)
let parentRect = this.parentElement.getBoundingClientRect()

let x = event.pageX + this.xOffset
let y = event.pageY - this.yOffset
// Get the mouse position (relative to the parent container)
let x = event.clientX - parentRect.left + this.xOffset // Relative to parent
let y = event.clientY - parentRect.top - this.yOffset // Relative to parent

// Now, apply the position to the tooltip
this.tooltip.style('left', x + 'px').style('top', y + 'px')

this.ensureTooltipInsideContainer(event, parentRect)
// Ensure the tooltip stays inside the parent container
this.ensureTooltipInsideContainer(event, parentRect, this.tooltip.node())
}
/**
* @function ensureTooltipInsideContainer
* @description Prevents the tooltip from overflowing out of the App container (ensures that the tooltip is inside the gridviz container)
* @param {MouseEvent} event
* @param {DOMRect} parentRect
* @param {HTMLElement} tooltipNode
*/
ensureTooltipInsideContainer(event, parentRect, tooltipNode) {
let node = tooltipNode
let parentWidth = parentRect.width
let parentHeight = parentRect.height

// Ensure tooltip doesn't go beyond the right edge
if (node.offsetLeft + node.clientWidth > parentWidth) {
let left = event.clientX - node.clientWidth - this.xOffset
node.style.left = left + 'px'
}

// Ensure tooltip doesn't go beyond the bottom edge
if (node.offsetTop + node.clientHeight > parentHeight) {
node.style.top = parentHeight - node.clientHeight + 'px'
}

// Ensure tooltip doesn't go above the top edge
if (node.offsetTop < 0) {
node.style.top = 0 + 'px'
}

// Ensure tooltip doesn't go beyond the left edge
if (node.offsetLeft < 0) {
node.style.left = 0 + 'px'
}
}

/*
Expand Down Expand Up @@ -145,39 +178,4 @@ export class Tooltip {
this.tooltip.attr(k, v)
return this
}

/**
* @function ensureTooltipInsideContainer
* @description Prevents the tooltip from overflowing out of the App container (ensures that the tooltip is inside the gridviz container)
* @param {MouseEvent} event
* @param {DOMRect} parentRect
*/
ensureTooltipInsideContainer = function (event, parentRect) {
let node = this.tooltip.node()
let parentWidth = parentRect.width
let parentHeight = parentRect.height

//too far right
if (node.offsetLeft > parentRect.left + parentWidth - node.clientWidth) {
let left = event.x - node.clientWidth - this.xOffset
node.style.left = left + 'px'
// check if mouse covers tooltip
if (node.offsetLeft + node.clientWidth > event.x) {
//move tooltip left so it doesnt cover mouse
let left2 = event.x - node.clientWidth - this.xOffset
node.style.left = left2 + 'px'
}
// node.style.top = node.offsetTop + config.yOffset + "px";
}

//too far down
if (node.offsetTop + node.clientHeight > parentRect.top + parentHeight) {
node.style.top = node.offsetTop - node.clientHeight + 'px'
}

//too far up
if (node.offsetTop < parentRect.top) {
node.style.top = parentRect.top + this.yOffset + 'px'
}
}
}

0 comments on commit 75f835b

Please sign in to comment.