Skip to content

Commit 1c882fc

Browse files
committed
styled d3 tree in history need to style tool tip hover
1 parent f86eb5c commit 1c882fc

File tree

2 files changed

+50
-21
lines changed

2 files changed

+50
-21
lines changed

src/app/components/StateRoute/History.tsx

Lines changed: 47 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ const defaultMargin: DefaultMargin = {
2020
bottom: 70,
2121
};
2222

23+
// Fixed node separation distances
24+
const FIXED_NODE_HEIGHT = 100; // Vertical distance between nodes
25+
const FIXED_NODE_WIDTH = 180; // Horizontal distance between nodes
26+
2327
// main function exported to StateRoute
2428
// below we destructure the props
2529
function History(props: Record<string, unknown>): JSX.Element {
@@ -163,23 +167,43 @@ function History(props: Record<string, unknown>): JSX.Element {
163167
*/
164168

165169
const makeD3Tree = () => {
166-
const svg = d3.select(svgRef.current); // d3.select Selects the first element/node that matches svgRef.current. If no element/node match returns an empty selection. If multiple elements/nodes match the selector, only the first matching element/node (in document order) will be selected.
167-
svg.selectAll('*').remove(); // Selects all elements. The elements will be selected in document order (top-to-bottom). We then remove the selected elements/nodes from the DOM. This is important as to ensure that the SVG is empty before rendering the D3 based visualization to avoid interference/overlap with any previously rendered content.
168-
const tree = (data) => {
169-
// function that takes in data and turns it into a d3 tree.
170-
const treeRoot = d3.hierarchy(data); // 'd3.hierarchy' constructs a root node from the specified hierarchical data.
171-
return d3.tree().size([innerWidth, innerHeight])(treeRoot); // d3.tree creates a new tree layout with a size option of innerWidth (~line 41) and innerHeight (~line 42). We specify our new tree layout's root as 'treeRoot' which assigns an x and y property to each node to represent an [x, y] coordinate system.
172-
};
170+
const svg = d3.select(svgRef.current);
171+
svg.selectAll('*').remove();
172+
173+
// Create tree layout with fixed node separation
174+
const treeLayout = d3.tree().nodeSize([FIXED_NODE_WIDTH, FIXED_NODE_HEIGHT]); // Set fixed sizes between nodes
175+
176+
// Calculate the tree structure
177+
const d3root = treeLayout(d3.hierarchy(root));
178+
179+
// Calculate total required height and width
180+
let minX = Infinity;
181+
let maxX = -Infinity;
182+
let minY = Infinity;
183+
let maxY = -Infinity;
184+
185+
d3root.each((d) => {
186+
minX = Math.min(minX, d.x);
187+
maxX = Math.max(maxX, d.x);
188+
minY = Math.min(minY, d.y);
189+
maxY = Math.max(maxY, d.y);
190+
});
173191

174-
const d3root = tree(root); // create a d3. tree from our root
175-
const currNode = labelCurrentNode(d3root); // iterate through our nodes and apply a color property
192+
const actualWidth = maxX - minX + FIXED_NODE_WIDTH;
193+
const actualHeight = maxY - minY + FIXED_NODE_HEIGHT;
176194

177-
const g = svg //serves as a container for the nodes and links within the D3 Visualization of the tree
178-
.append('g') // create an element 'g' on svg
179-
.attr(
180-
'transform',
181-
`translate(${margin.left},${d3root.height === 0 ? totalHeight / 2 : margin.top})`, //Set the position of the group 'g' by translating it horizontally by 'margin.left' pixels and vertically based on the conditional expression.
182-
);
195+
// Set SVG size to accommodate the entire tree
196+
svg
197+
.attr('width', Math.max(actualWidth + margin.left + margin.right, totalWidth))
198+
.attr('height', Math.max(actualHeight + margin.top + margin.bottom, totalHeight));
199+
200+
// Center the root node horizontally
201+
const centerOffset = totalWidth / 2 - (maxX - minX) / 2;
202+
203+
const g = svg.append('g').attr('transform', `translate(${centerOffset},${margin.top})`);
204+
205+
// Label current node
206+
const currNode = labelCurrentNode(d3root);
183207

184208
const link = g
185209
.selectAll('.link')
@@ -241,18 +265,21 @@ function History(props: Record<string, unknown>): JSX.Element {
241265
}
242266
})
243267
.on('mouseenter', function (event, d) {
268+
d3.selectAll('.tooltip').remove();
244269
const [x, y] = d3.pointer(event);
245270
if (d3.selectAll('.tooltip')._groups['0'].length === 0) {
246271
const div = d3
247272
.select('.display:first-child')
248273
.append('div')
249274
.attr('class', `tooltip`)
250275
.attr('id', `tt-${d.data.index}`)
251-
.style('left', `${event.clientX + 0}px`)
252-
.style('top', `${event.clientY + 0}px`)
276+
.style('left', `${event.clientX + 30}px`)
277+
.style('top', `${event.clientY - 75}px`)
253278
.style('max-height', `25%`)
254279
.style('overflow', `auto`)
255-
.on('mouseenter', function (event, d) {})
280+
.on('mouseenter', function (event, d) {
281+
d3.select(this).interrupt(); // Interrupt any ongoing transitions
282+
})
256283
.on('mouseleave', function (event, d) {
257284
d3.selectAll('.tooltip').remove().style('display', 'hidden');
258285
});
@@ -262,7 +289,7 @@ function History(props: Record<string, unknown>): JSX.Element {
262289
})
263290
.on('mouseleave', function (event, d) {
264291
if (event.relatedTarget.id !== `tt-${d.data.index}`) {
265-
d3.selectAll('.tooltip').transition().delay(100).remove();
292+
d3.selectAll('.tooltip').transition().delay(0).remove();
266293
}
267294
});
268295

@@ -275,7 +302,7 @@ function History(props: Record<string, unknown>): JSX.Element {
275302
d3.selectAll('.tooltip').remove();
276303
});
277304

278-
node.append('circle').attr('r', 18);
305+
node.append('circle').attr('r', 20);
279306

280307
node
281308
.append('text')

src/app/styles/components/d3graph.css

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ div.tooltip {
9090
}
9191

9292
/* Container styling */
93-
.history-d3-container svg {
93+
.display {
9494
background-color: #f9fafb;
95+
height: 100%;
96+
overflow: auto;
9597
}

0 commit comments

Comments
 (0)