I have a large SVG file representing a manufacturing process line, and I need to add callouts/tooltips to certain devices/assets/equipment to display associated data.
Could anyone suggest the best approach to implement these callouts/tooltips effectively?
Here’s an example image illustrating what I’m aiming to achieve:
example
Specific requirements:
- The callouts/tooltips should appear when necessary.
- The content of the tooltips should be dynamically generated based on the associated data.
- The tooltips should be styled appropriately to fit within the design of the manufacturing process line.
Any advice, examples, or resources would be greatly appreciated!
Thank you!
Currently, I’m using D3.js to render the SVG.
Each section of the process line is a distinct representation with its own datasets.
When a user clicks on a section, D3.js zooms into that section, and I want to display callouts/tooltips with relevant data.
Example code
const d3Svg = d3.select<SVGSVGElement, unknown>(d3SvgRef.current);
const g = d3Svg.append<SVGGElement>('g').attr('class', 'zoomable');
// Define the zoom behavior
zoom.current = d3
.zoom<SVGSVGElement, unknown>()
.scaleExtent([1, 10])
.on('zoom', (event) => {
g.attr('transform', event.transform);
setTransform(event.transform);
});
d3Svg.call(zoom.current);
// load svg file
d3.xml('/images/op-dashboard/GR-02-3331-ME-PFD-0001-REVC.svg').then((data) => {
const importedNode = document.importNode(data.documentElement, true);
g.node()?.appendChild(importedNode);
// draw section rect in the svg
sections.forEach((section) => {
const [x1, y1, x2, y2, ...rest] = section.svgShapeCoords;
const sectionShape = section.shape || 'rect';
if (sectionShape === 'rect') {
// draw rect
g.append('rect')
.attr('x', x1)
.attr('y', y1)
.attr('width', x2 - x1)
.attr('height', y2 - y1)
.attr('fill', 'rgba(0, 0, 0, 0)')
// .attr('stroke', 'rgba(255, 0, 0, 0.2)')
.style('cursor', 'pointer')
.on('click', () => {
// setActiveSection(section.sectionId);
console.log('Clicked', section.name);
handleZoomToSection(section, transform);
});
} else {
// draw polygon
g.append('polygon')
.attr('points', section.svgShapeCoords.join(','))
.attr('fill', 'rgba(0, 0, 0, 0)')
// .attr('stroke', 'rgba(255, 0, 0, 0.2)')
.style('cursor', 'pointer')
.on('click', () => {
// setActiveSection(section.sectionId);
console.log('Clicked', section.name);
handleZoomToSection(section, transform);
});
}
// draw a button for each section to section page
// dummy to measure the text width
const fontSize = 3;
const tempText = g
.append('text')
.attr('x', x1 + 5)
.attr('y', y1 + 5)
.attr('font-size', `${fontSize}px`)
.text(section.name)
.style('visibility', 'hidden');
const textWidth = tempText.node()!.getBBox().width;
tempText.remove();
g.append('rect')
.attr('x', x1)
.attr('y', y1 - 10)
.attr('width', textWidth + 10)
.attr('height', 10)
.attr('fill', 'rgba(0, 0, 0, 0.8)')
.style('cursor', 'pointer')
.style('z-index', '100')
.on('click', () => {
// do something here
handleClickSectionTitle(section.sectionId);
});
g.append('text')
.attr('x', x1 + 5)
.attr('y', y1 - 10 + 5)
.attr('fill', 'white')
.attr('text-anchor', 'left')
.attr('dominant-baseline', 'middle')
.attr('font-size', `${fontSize}px`)
.text(section.name)
.style('cursor', 'pointer')
.style('z-index', '100')
.on('click', () => {
// do something here
handleClickSectionTitle(section.sectionId);
});
});
});
Steven Peng is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.