Skip to content

Commit daf99c3

Browse files
committed
initial tooltip rendered for ax
1 parent ac435cd commit daf99c3

File tree

4 files changed

+211
-8
lines changed

4 files changed

+211
-8
lines changed

src/app/FrontendTypes.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ export interface TooltipData {
5454
x: number;
5555
y: number;
5656
color: string;
57+
name?: string;
5758
}
5859

5960
export interface snapshot {

src/app/components/StateRoute/AxMap/Ax.tsx

Lines changed: 137 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
1-
import React, { useState } from 'react';
1+
import React, { useState, useRef } from 'react';
22
import { Group } from '@visx/group';
33
import { hierarchy, Tree } from '@visx/hierarchy';
44
import { LinearGradient } from '@visx/gradient';
55
import { pointRadial } from 'd3-shape';
66
import LinkControls from './axLinkControls';
77
import getLinkComponent from './getAxLinkComponents';
8+
import { useTooltip, useTooltipInPortal, defaultStyles } from '@visx/tooltip';
9+
import ToolTipDataDisplay from '../ComponentMap/ToolTipDataDisplay';
10+
import { ToolTipStyles } from '../../../FrontendTypes';
11+
import { localPoint } from '@visx/event';
812

13+
//still using below themes?
914
const theme = {
1015
scheme: 'monokai',
1116
author: 'wimer hazenberg (http://www.monokai.nl)',
@@ -153,6 +158,50 @@ export default function AxTree(props, {
153158
height: totalHeight,
154159
margin = defaultMargin,
155160
}: LinkTypesProps) {
161+
162+
163+
const toolTipTimeoutID = useRef(null); //useRef stores stateful data that’s not needed for rendering.
164+
const {
165+
tooltipData, // value/data that tooltip may need to render
166+
tooltipLeft, // number used for tooltip positioning
167+
tooltipTop, // number used for tooltip positioning
168+
tooltipOpen, // boolean whether the tooltip state is open or closed
169+
showTooltip, // function to set tooltip state
170+
hideTooltip, // function to close a tooltip
171+
} = useTooltip(); // returns an object with several properties that you can use to manage the tooltip state of your component
172+
console.log('tool tip data: ', tooltipData);
173+
// let nameVal = JSON.stringify(tooltipData)
174+
// console.log('nameVal', nameVal);
175+
const {
176+
containerRef, // Access to the container's bounding box. This will be empty on first render.
177+
TooltipInPortal, // TooltipWithBounds in a Portal, outside of your component DOM tree
178+
} = useTooltipInPortal({
179+
// Visx hook
180+
detectBounds: true, // use TooltipWithBounds
181+
scroll: true, // when tooltip containers are scrolled, this will correctly update the Tooltip position
182+
});
183+
184+
const tooltipStyles: ToolTipStyles = {
185+
...defaultStyles,
186+
minWidth: 60,
187+
maxWidth: 300,
188+
backgroundColor: 'rgb(15,15,15)',
189+
color: 'white',
190+
fontSize: '16px',
191+
lineHeight: '18px',
192+
fontFamily: 'Roboto',
193+
zIndex: 100,
194+
pointerEvents: 'all !important',
195+
};
196+
197+
const formatRenderTime = (time: number): string => {
198+
if (!time) return 'No time information';
199+
const renderTime = time.toFixed(3);
200+
return `${renderTime} ms `;
201+
};
202+
203+
204+
156205
const [layout, setLayout] = useState('cartesian');
157206
const [orientation, setOrientation] = useState('horizontal');
158207
const [linkType, setLinkType] = useState('diagonal');
@@ -284,7 +333,9 @@ export default function AxTree(props, {
284333
/>
285334
<svg width={totalWidth} height={totalHeight + 0}>
286335
<LinearGradient id="links-gradient" from="#fd9b93" to="#fe6e9e" />
287-
<rect width={totalWidth} height={totalHeight} rx={14} fill="#272b4d" />
336+
<rect width={totalWidth} height={totalHeight} rx={14} fill="#272b4d" onClick={() => {
337+
hideTooltip();
338+
}}/>
288339
<Group top={margin.top} left={margin.left}>
289340
<Tree
290341
root={hierarchy(data, (d) => (d.isExpanded ? null : d.children))}
@@ -398,6 +449,19 @@ export default function AxTree(props, {
398449
} else {
399450
aspect = Math.max(aspect, 0.2);
400451
}
452+
const handleMouseAndClickOver = (event): void => {
453+
const coords = localPoint(event.target.ownerSVGElement, event);
454+
const tooltipObj = { ...node.data };
455+
console.log("tooltipobj: ", tooltipObj);
456+
457+
showTooltip({
458+
tooltipLeft: coords.x,
459+
tooltipTop: coords.y,
460+
tooltipData: tooltipObj,
461+
// this is where the data for state and render time is displayed
462+
// but does not show props functions and etc
463+
});
464+
};
401465

402466
return (
403467
<Group top={top} left={left} key={key} className='rect'>
@@ -408,6 +472,7 @@ export default function AxTree(props, {
408472
fill="url('#root-gradient')"
409473
onClick={() => {
410474
node.data.isExpanded = !node.data.isExpanded;
475+
hideTooltip();
411476
}}
412477
/>
413478
)}
@@ -431,6 +496,40 @@ export default function AxTree(props, {
431496
rx={node.children ? 4 : 10}
432497
onClick={() => {
433498
node.data.isExpanded = !node.data.isExpanded;
499+
hideTooltip();
500+
}}
501+
// Mouse Enter Rect (Component Node) -----------------------------------------------------------------------
502+
/** This onMouseEnter event fires when the mouse first moves/hovers over a component node.
503+
* The supplied event listener callback produces a Tooltip element for the current node. */
504+
505+
onMouseEnter={(event) => {
506+
/** This 'if' statement block checks to see if you've just left another component node
507+
* by seeing if there's a current setTimeout waiting to close that component node's
508+
* tooltip (see onMouseLeave immediately below). If so it clears the tooltip generated
509+
* from that component node so a new tooltip for the node you've just entered can render. */
510+
if (toolTipTimeoutID.current !== null) {
511+
clearTimeout(toolTipTimeoutID.current);
512+
hideTooltip();
513+
}
514+
// Removes the previous timeoutID to avoid errors
515+
toolTipTimeoutID.current = null;
516+
//This generates a tooltip for the component node the mouse has entered.
517+
handleMouseAndClickOver(event);
518+
}}
519+
// Mouse Leave Rect (Component Node) --------------------------------------------------------------------------
520+
/** This onMouseLeave event fires when the mouse leaves a component node.
521+
* The supplied event listener callback generates a setTimeout call which gives the
522+
* mouse a certain amount of time between leaving the current component node and
523+
* closing the tooltip for that node.
524+
* If the mouse enters the tooltip before the timeout delay has passed, the
525+
* setTimeout event will be canceled. */
526+
onMouseLeave={() => {
527+
// Store setTimeout ID so timeout can be cleared if necessary
528+
toolTipTimeoutID.current = setTimeout(() => {
529+
// hideTooltip unmounts the tooltip
530+
hideTooltip();
531+
toolTipTimeoutID.current = null;
532+
}, 300);
434533
}}
435534
/>
436535
)}
@@ -459,6 +558,42 @@ export default function AxTree(props, {
459558
</Tree>
460559
</Group>
461560
</svg>
561+
{tooltipOpen && tooltipData && (
562+
<TooltipInPortal
563+
// set this to random so it correctly updates with parent bounds
564+
key={Math.random()}
565+
top={tooltipTop}
566+
left={tooltipLeft}
567+
style={tooltipStyles}
568+
//------------- Mouse Over TooltipInPortal--------------------------------------------------------------------
569+
/** After the mouse enters the tooltip, it's able to persist by clearing the setTimeout
570+
* that would've unmounted it */
571+
onMouseEnter={() => {
572+
clearTimeout(toolTipTimeoutID.current);
573+
toolTipTimeoutID.current = null;
574+
}}
575+
//------------- Mouse Leave TooltipInPortal -----------------------------------------------------------------
576+
/** When the mouse leaves the tooltip, the tooltip unmounts */
577+
onMouseLeave={() => {
578+
hideTooltip();
579+
}}
580+
>
581+
<div>
582+
<div>
583+
<strong>{JSON.stringify(tooltipData['name'].value)}</strong>
584+
</div>
585+
<div>
586+
<ToolTipDataDisplay containerName='Props' dataObj={tooltipData} />
587+
<ToolTipDataDisplay
588+
containerName='State'
589+
dataObj={
590+
tooltipData
591+
}
592+
/>
593+
</div>
594+
</div>
595+
</TooltipInPortal>
596+
)}
462597
</div>
463598
);
464599
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import React from 'react';
2+
import { JSONTree } from 'react-json-tree';
3+
4+
/*
5+
Code that show's the tooltip of our JSON tree
6+
*/
7+
8+
const colors = {
9+
scheme: 'paraiso',
10+
author: 'jan t. sott',
11+
base00: '#2f1e2e',
12+
base01: '#41323f',
13+
base02: '#4f424c',
14+
base03: '#776e71',
15+
base04: '#8d8687',
16+
base05: '#a39e9b',
17+
base06: '#b9b6b0',
18+
base07: '#e7e9db',
19+
base08: '#ef6155',
20+
base09: '#824508', //base09 is orange for booleans and numbers. This base in particular fails to match the entered color.
21+
// base09: '#592bad', // alternative purple
22+
base0A: '#fec418',
23+
base0B: '#48b685',
24+
base0C: '#5bc4bf',
25+
base0D: '#06b6ef',
26+
base0E: '#815ba4',
27+
base0F: '#e96ba8',
28+
};
29+
30+
const ToolTipDataDisplay = ({ containerName, dataObj }) => {
31+
const printableObject = {}; // The key:value properties of printableObject will be rendered in the JSON Tree
32+
33+
if (!dataObj) {
34+
// If state is null rather than an object, print "State: null" in tooltip
35+
printableObject[containerName] = dataObj;
36+
} else {
37+
/*
38+
Props often contain circular references.
39+
Messages from the backend must be sent as JSON objects (strings).
40+
JSON objects can't contain circular ref's, so the backend filters out problematic values by stringifying the values of object properties and ignoring any values that fail the conversion due to a circular ref. The following logic converts these values back to JS so they display clearly and are collapsible.
41+
*/
42+
const data = {};
43+
for (const key in dataObj) {
44+
if (typeof dataObj[key] === 'string') {
45+
try {
46+
data[key] = JSON.parse(dataObj[key]);
47+
} catch {
48+
data[key] = dataObj[key];
49+
}
50+
} else {
51+
data[key] = dataObj[key];
52+
}
53+
}
54+
/*
55+
Adds container name (State, Props, future different names for hooks) at top of object so everything nested in it will collapse when you click on it.
56+
*/
57+
printableObject[containerName] = data;
58+
}
59+
60+
return (
61+
<div className='tooltipData' key={`${containerName}-data-container`}>
62+
<JSONTree
63+
data={printableObject} // data to be rendered, a snapshot object
64+
theme={{ extend: colors, tree: () => ({ className: `tooltipData-JSONTree` }) }} // theme set to a base16 theme that has been extended to include "className: 'json-tree'"
65+
shouldExpandNodeInitially={() => true} // determines if node should be expanded when it first renders (root is expanded by default)
66+
hideRoot={true} // hides the root node
67+
/>
68+
</div>
69+
);
70+
};
71+
72+
export default ToolTipDataDisplay;

src/app/components/StateRoute/ComponentMap/ComponentMap.tsx

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,6 @@ export default function ComponentMap({
5757
const [selectedNode, setSelectedNode] = useState('root'); // We create a local state "selectedNode" and set it to a string 'root'.
5858
const dispatch = useDispatch();
5959

60-
console.log('totalHeight: ', totalHeight);
61-
6260
const toolTipTimeoutID = useRef(null); //useRef stores stateful data that’s not needed for rendering.
6361

6462
useEffect(() => {
@@ -111,6 +109,7 @@ export default function ComponentMap({
111109
hideTooltip, // function to close a tooltip
112110
} = useTooltip(); // returns an object with several properties that you can use to manage the tooltip state of your component
113111

112+
console.log('tooltipData component map: ', tooltipData);
114113
const {
115114
containerRef, // Access to the container's bounding box. This will be empty on first render.
116115
TooltipInPortal, // TooltipWithBounds in a Portal, outside of your component DOM tree
@@ -184,10 +183,6 @@ export default function ComponentMap({
184183

185184
findSelectedNode(); // locates the rootNode... do we really need this? This function is only used once... it's here.
186185

187-
console.log('startNode:', startNode);
188-
console.log('nodelist:', nodeList);
189-
console.log('nodelist length:', nodeList.length);
190-
191186
// controls for the map
192187
const LinkComponent: React.ComponentType<unknown> = getLinkComponent({
193188
layout,

0 commit comments

Comments
 (0)