Skip to content

Commit b9af266

Browse files
committed
Merge branch 'develop' into jesse/enableAxTreeButtons
2 parents 041bbdd + f1c9f4c commit b9af266

File tree

11 files changed

+362
-55
lines changed

11 files changed

+362
-55
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: 166 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,20 @@
1-
import React, { useState } from 'react';
1+
import React, { useState, useRef } from 'react';
2+
import { useDispatch, useSelector } from 'react-redux';
23
import { Group } from '@visx/group';
34
import { hierarchy, Tree } from '@visx/hierarchy';
45
import { LinearGradient } from '@visx/gradient';
56
import { pointRadial } from 'd3-shape';
6-
import { useTooltipInPortal } from '@visx/tooltip';
77
import LinkControls from './axLinkControls';
88
import getLinkComponent from './getAxLinkComponents';
9-
9+
import { useTooltip, useTooltipInPortal, defaultStyles } from '@visx/tooltip';
10+
import ToolTipDataDisplay from './ToolTipDataDisplay';
11+
import { ToolTipStyles } from '../../../FrontendTypes';
12+
import { localPoint } from '@visx/event';
13+
import AxLegend from './axLegend';
14+
import { renderAxLegend } from '../../../slices/AxSlices/axLegendSlice';
15+
import type { RootState } from '../../../store';
16+
17+
//still using below themes?
1018
const theme = {
1119
scheme: 'monokai',
1220
author: 'wimer hazenberg (http://www.monokai.nl)',
@@ -162,6 +170,50 @@ export default function AxTree(props) {
162170
let totalWidth = width;
163171
let totalHeight = height;
164172

173+
174+
175+
const toolTipTimeoutID = useRef(null); //useRef stores stateful data that’s not needed for rendering.
176+
const {
177+
tooltipData, // value/data that tooltip may need to render
178+
tooltipLeft, // number used for tooltip positioning
179+
tooltipTop, // number used for tooltip positioning
180+
tooltipOpen, // boolean whether the tooltip state is open or closed
181+
showTooltip, // function to set tooltip state
182+
hideTooltip, // function to close a tooltip
183+
} = useTooltip(); // returns an object with several properties that you can use to manage the tooltip state of your component
184+
console.log('tool tip data: ', tooltipData);
185+
// let nameVal = JSON.stringify(tooltipData)
186+
// console.log('nameVal', nameVal);
187+
const {
188+
containerRef, // Access to the container's bounding box. This will be empty on first render.
189+
TooltipInPortal, // TooltipWithBounds in a Portal, outside of your component DOM tree
190+
} = useTooltipInPortal({
191+
// Visx hook
192+
detectBounds: true, // use TooltipWithBounds
193+
scroll: true, // when tooltip containers are scrolled, this will correctly update the Tooltip position
194+
});
195+
196+
const tooltipStyles: ToolTipStyles = {
197+
...defaultStyles,
198+
minWidth: 60,
199+
maxWidth: 300,
200+
backgroundColor: 'rgb(15,15,15)',
201+
color: 'white',
202+
fontSize: '16px',
203+
lineHeight: '18px',
204+
fontFamily: 'Roboto',
205+
zIndex: 100,
206+
pointerEvents: 'all !important',
207+
};
208+
209+
// const formatRenderTime = (time: number): string => {
210+
// if (!time) return 'No time information';
211+
// const renderTime = time.toFixed(3);
212+
// return `${renderTime} ms `;
213+
// };
214+
215+
216+
165217
const [layout, setLayout] = useState('cartesian');
166218
const [orientation, setOrientation] = useState('horizontal');
167219
const [linkType, setLinkType] = useState('diagonal');
@@ -266,45 +318,49 @@ export default function AxTree(props) {
266318
populateNodeAxArr(rootAxNode);
267319
console.log('nodeAxArr: ', nodeAxArr);
268320

269-
const {
270-
containerRef, // Access to the container's bounding box. This will be empty on first render.
271-
} = useTooltipInPortal({
272-
// Visx hook
273-
detectBounds: true, // use TooltipWithBounds
274-
scroll: true, // when tooltip containers are scrolled, this will correctly update the Tooltip position
275-
});
321+
// ax Legend
322+
const { axLegendButtonClicked } = useSelector((state: RootState) => state.axLegend);
323+
const dispatch = useDispatch();
276324

277325
return totalWidth < 10 ? null : (
278326
<div>
279-
<LinkControls
280-
layout={layout}
281-
orientation={orientation}
282-
linkType={linkType}
283-
stepPercent={stepPercent}
284-
setLayout={setLayout}
285-
setOrientation={setOrientation}
286-
setLinkType={setLinkType}
287-
setStepPercent={setStepPercent}
288-
/>
327+
<div id='axControls'>
328+
<LinkControls
329+
layout={layout}
330+
orientation={orientation}
331+
linkType={linkType}
332+
stepPercent={stepPercent}
333+
setLayout={setLayout}
334+
setOrientation={setOrientation}
335+
setLinkType={setLinkType}
336+
setStepPercent={setStepPercent}
337+
/>
338+
339+
<button id='axLegendButton' onClick={() => dispatch(renderAxLegend())}>
340+
Generate Ax Tree Legend
341+
</button>
342+
</div>
289343

290344
{/* svg references purple background */}
291-
<svg ref={containerRef} width={totalWidth} height={totalHeight + 0}>
345+
<svg ref={containerRef} width={totalWidth + 0.2*totalWidth} height={totalHeight}>
292346
<LinearGradient id='root-gradient' from='#488689' to='#3c6e71' />
293347
<LinearGradient id='parent-gradient' from='#488689' to='#3c6e71' />
294348
<rect
295349
className='componentMapContainer'
296350
width={sizeWidth / aspect}
297351
height={sizeHeight / aspect + 0}
298352
rx={14}
299-
/>
353+
onClick={() => {
354+
hideTooltip();
355+
}}/>
300356
<Group transform={`scale(${aspect})`} top={margin.top} left={margin.left}>
301357
<Tree
302358
root={hierarchy(nodeAxArr[0], (d) => (d.isExpanded ? null : d.children))}
303359
size={[sizeWidth / aspect, sizeHeight / aspect]}
304360
separation={(a, b) => (a.parent === b.parent ? 0.5 : 0.5) / a.depth}
305361
>
306362
{(tree) => (
307-
<Group top={origin.y + 35} left={origin.x + 50 / aspect}>
363+
<Group top={origin.y + 35} left={origin.x + 110}>
308364
{tree.links().map((link, i) => (
309365
<LinkComponent
310366
key={i}
@@ -409,6 +465,19 @@ export default function AxTree(props) {
409465
} else {
410466
aspect = Math.max(aspect, 0.2);
411467
}
468+
const handleMouseAndClickOver = (event): void => {
469+
const coords = localPoint(event.target.ownerSVGElement, event);
470+
const tooltipObj = { ...node.data };
471+
console.log("tooltipobj: ", tooltipObj);
472+
473+
showTooltip({
474+
tooltipLeft: coords.x,
475+
tooltipTop: coords.y,
476+
tooltipData: tooltipObj,
477+
// this is where the data for state and render time is displayed
478+
// but does not show props functions and etc
479+
});
480+
};
412481

413482
return (
414483
<Group top={top} left={left} key={key} className='rect'>
@@ -425,6 +494,7 @@ export default function AxTree(props) {
425494
rx={node.children ? 4 : 10}
426495
onClick={() => {
427496
node.data.isExpanded = !node.data.isExpanded;
497+
hideTooltip();
428498
}}
429499
/>
430500
)}
@@ -448,6 +518,40 @@ export default function AxTree(props) {
448518
rx={node.children ? 4 : 10}
449519
onClick={() => {
450520
node.data.isExpanded = !node.data.isExpanded;
521+
hideTooltip();
522+
}}
523+
// Mouse Enter Rect (Component Node) -----------------------------------------------------------------------
524+
/** This onMouseEnter event fires when the mouse first moves/hovers over a component node.
525+
* The supplied event listener callback produces a Tooltip element for the current node. */
526+
527+
onMouseEnter={(event) => {
528+
/** This 'if' statement block checks to see if you've just left another component node
529+
* by seeing if there's a current setTimeout waiting to close that component node's
530+
* tooltip (see onMouseLeave immediately below). If so it clears the tooltip generated
531+
* from that component node so a new tooltip for the node you've just entered can render. */
532+
if (toolTipTimeoutID.current !== null) {
533+
clearTimeout(toolTipTimeoutID.current);
534+
hideTooltip();
535+
}
536+
// Removes the previous timeoutID to avoid errors
537+
toolTipTimeoutID.current = null;
538+
//This generates a tooltip for the component node the mouse has entered.
539+
handleMouseAndClickOver(event);
540+
}}
541+
// Mouse Leave Rect (Component Node) --------------------------------------------------------------------------
542+
/** This onMouseLeave event fires when the mouse leaves a component node.
543+
* The supplied event listener callback generates a setTimeout call which gives the
544+
* mouse a certain amount of time between leaving the current component node and
545+
* closing the tooltip for that node.
546+
* If the mouse enters the tooltip before the timeout delay has passed, the
547+
* setTimeout event will be canceled. */
548+
onMouseLeave={() => {
549+
// Store setTimeout ID so timeout can be cleared if necessary
550+
toolTipTimeoutID.current = setTimeout(() => {
551+
// hideTooltip unmounts the tooltip
552+
hideTooltip();
553+
toolTipTimeoutID.current = null;
554+
}, 300);
451555
}}
452556
/>
453557
)}
@@ -476,6 +580,45 @@ export default function AxTree(props) {
476580
</Tree>
477581
</Group>
478582
</svg>
583+
{tooltipOpen && tooltipData && (
584+
<TooltipInPortal
585+
// set this to random so it correctly updates with parent bounds
586+
key={Math.random()}
587+
top={tooltipTop}
588+
left={tooltipLeft}
589+
style={tooltipStyles}
590+
//------------- Mouse Over TooltipInPortal--------------------------------------------------------------------
591+
/** After the mouse enters the tooltip, it's able to persist by clearing the setTimeout
592+
* that would've unmounted it */
593+
onMouseEnter={() => {
594+
clearTimeout(toolTipTimeoutID.current);
595+
toolTipTimeoutID.current = null;
596+
}}
597+
//------------- Mouse Leave TooltipInPortal -----------------------------------------------------------------
598+
/** When the mouse leaves the tooltip, the tooltip unmounts */
599+
onMouseLeave={() => {
600+
hideTooltip();
601+
}}
602+
>
603+
<div>
604+
<div>
605+
<strong>{JSON.stringify(tooltipData['name'].value)}</strong>
606+
</div>
607+
<div>
608+
<ToolTipDataDisplay containerName='Ax Node Info' dataObj={tooltipData} />
609+
{/* <ToolTipDataDisplay containerName='State'dataObj={tooltipData}/> */}
610+
</div>
611+
</div>
612+
</TooltipInPortal>
613+
)}
614+
615+
{/* ax Legend */}
616+
<div>
617+
{ axLegendButtonClicked ?
618+
<AxLegend /> : ''
619+
}
620+
</div>
621+
479622
</div>
480623
);
481624
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
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+
//ignored false vs true
44+
//ignored reasons here
45+
//&& key = name? / order?
46+
for (const key in dataObj) {
47+
console.log('keys in dataObj in tooltiptotalDisplay: ', key);
48+
if(key === 'properties' || key === 'ignored' || key === 'ignoredReasons'){// loop through properties, adding them to the data object
49+
50+
if (typeof dataObj[key] === 'string') {
51+
//if 'key' is ignored, put the ignored key and its value on the data object
52+
//if ignoredReasons has length it should loop through adding the reasons names to the data object
53+
//actually might only need to give it the properties and ignored and ignored reasons and it'll take care of the rest
54+
try {
55+
data[key] = JSON.parse(dataObj[key]);
56+
} catch {
57+
data[key] = dataObj[key];
58+
}
59+
} else {
60+
data[key] = dataObj[key];
61+
}
62+
}
63+
}
64+
/*
65+
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.
66+
*/
67+
printableObject[containerName] = data;
68+
}
69+
70+
return (
71+
<div className='tooltipData' key={`${containerName}-data-container`}>
72+
<JSONTree
73+
data={printableObject} // data to be rendered, a snapshot object
74+
theme={{ extend: colors, tree: () => ({ className: `tooltipData-JSONTree` }) }} // theme set to a base16 theme that has been extended to include "className: 'json-tree'"
75+
shouldExpandNodeInitially={() => true} // determines if node should be expanded when it first renders (root is expanded by default)
76+
hideRoot={true} // hides the root node
77+
/>
78+
</div>
79+
);
80+
};
81+
82+
export default ToolTipDataDisplay;
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import React from 'react';
2+
3+
const AxLegend = () => {
4+
return (
5+
<div style={{borderStyle: 'solid'}}>
6+
Nodes from the accessibility tree have either a role <strong>role</strong> or <strong>internalRole</strong>
7+
<ul>
8+
<li>
9+
<i><b>Role</b></i> refers to <strong> ARIA </strong> roles, which indicate the purpose of the element to assistive technologies, like screen readers.
10+
All of the nodes rendered in this tree have a role of 'role'
11+
</li>
12+
<li>
13+
<i><b>internalRole</b></i> refers to browser-specific roles <strong> Chrome </strong> for its own accessibility processing
14+
</li>
15+
</ul>
16+
17+
<p> Each node is given a property labeled <strong>ignored</strong>. </p>
18+
<p> Nodes read by the screen reader have their ignored property evaluate to <strong>false</strong>.
19+
Nodes not read by the screen reader evaluate to <strong>true</strong>.</p>
20+
<p> Nodes labeled <strong>"visible node with no name"</strong> have the ignored property set to false, but are not given a name</p>
21+
</div>
22+
);
23+
}
24+
25+
export default AxLegend;

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ export default function ComponentMap({
109109
hideTooltip, // function to close a tooltip
110110
} = useTooltip(); // returns an object with several properties that you can use to manage the tooltip state of your component
111111

112+
console.log('tooltipData component map: ', tooltipData);
112113
const {
113114
containerRef, // Access to the container's bounding box. This will be empty on first render.
114115
TooltipInPortal, // TooltipWithBounds in a Portal, outside of your component DOM tree

0 commit comments

Comments
 (0)