Skip to content

Commit 98995f8

Browse files
committed
create map.tsx start D3 visualization
1 parent fa3bf55 commit 98995f8

File tree

2 files changed

+252
-13
lines changed

2 files changed

+252
-13
lines changed

src/app/components/Map.tsx

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
/* eslint-disable arrow-body-style */
2+
/* eslint-disable max-len */
3+
/* eslint-disable @typescript-eslint/no-explicit-any */
4+
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
5+
/* eslint-disable @typescript-eslint/ban-types */
6+
7+
import React, { useRef, useState, useEffect } from 'react';
8+
import * as d3 from 'd3';
9+
10+
const Map = (props) => {
11+
const { snapshot } = props;
12+
console.log('MAP SNAPSHOT', snapshot);
13+
14+
// set the heights and width of the tree to be passed into treeMap function
15+
const width: number = 600;
16+
const height: number = 1100;
17+
18+
// this state allows the canvas to stay at the zoom level on multiple re-renders
19+
const [{ x, y, k }, setZoomState]: any = useState({ x: 0, y: 0, k: 0 });
20+
21+
useEffect(() => {
22+
setZoomState(d3.zoomTransform(d3.select('#canvas').node()));
23+
}, [snapshot]);
24+
25+
// this only clears the canvas if Visualizer is already rendered on the extension
26+
useEffect(() => {
27+
document.getElementById('canvas').innerHTML = '';
28+
29+
// creating the main svg container for d3 elements
30+
const svgContainer: any = d3
31+
.select('#canvas')
32+
.attr('width', width)
33+
.attr('height', height);
34+
35+
// creating a pseudo-class for reusability
36+
const g: any = svgContainer
37+
.append('g')
38+
.attr('transform', `translate(${x}, ${y}), scale(${k})`); // sets the canvas to the saved zoomState
39+
40+
// atomState is the object that is passed into d3.hierarchy
41+
const childrenArr = [];
42+
if (snapshot.children[0].state.hooksState) {
43+
snapshot.children[0].state.hooksState[0].undefined.forEach((el) =>
44+
childrenArr.push(el)
45+
);
46+
}
47+
console.log(childrenArr);
48+
49+
const atomState: any = {
50+
name: ' Root',
51+
// pass in parsed data here
52+
// call the helper function passing in the most recent snapshot
53+
children: childrenArr,
54+
};
55+
56+
console.log('STATE', atomState);
57+
// creating the tree map
58+
const treeMap: any = d3.tree().nodeSize([width, height]);
59+
60+
// creating the nodes of the tree
61+
// pass
62+
const hierarchyNodes: any = d3.hierarchy(atomState);
63+
64+
// calling the tree function with nodes created from data
65+
const finalMap: any = treeMap(hierarchyNodes);
66+
67+
// renders a flat array of objects containing all parent-child links
68+
// renders the paths onto the component
69+
let paths: any = finalMap.links();
70+
console.log('PATHS', paths);
71+
72+
// this creates the paths to each atom and its contents in the tree
73+
g.append('g')
74+
.attr('fill', 'none')
75+
.attr('stroke', '#646464')
76+
.attr('stroke-width', 5)
77+
.selectAll('path')
78+
.data(paths)
79+
.enter()
80+
.append('path')
81+
.attr(
82+
'd',
83+
d3
84+
.linkHorizontal()
85+
.x((d: any) => d.y)
86+
.y((d: any) => d.x)
87+
);
88+
89+
// returns a flat array of objects containing all the nodes and their information
90+
// renders nodes onto the canvas
91+
let nodes: any = hierarchyNodes.descendants();
92+
93+
// const node is used to create all the nodes
94+
// this segment places all the nodes on the canvas
95+
const node: any = g
96+
.append('g')
97+
.attr('stroke-linejoin', 'round') // no clue what this does
98+
.attr('stroke-width', 1)
99+
.selectAll('g')
100+
.data(nodes)
101+
.enter()
102+
.append('g')
103+
.attr('transform', (d: any) => `translate(${d.y}, ${d.x})`)
104+
.attr('class', 'atomNodes');
105+
106+
// for each node that got created, append a circle element
107+
node.append('circle').attr('fill', '#c300ff').attr('r', 50);
108+
109+
// for each node that got created, append a text element that displays the name of the node
110+
node
111+
.append('text')
112+
.attr('dy', '.31em')
113+
.attr('x', (d: any) => (d.children ? -75 : 75))
114+
.attr('text-anchor', (d: any) => (d.children ? 'end' : 'start'))
115+
.text((d: any) => d.data.name)
116+
.style('font-size', `2rem`)
117+
.style('fill', 'white')
118+
.clone(true)
119+
.lower()
120+
.attr('stroke', '#646464')
121+
.attr('stroke-width', 2);
122+
123+
console.log('142');
124+
// adding a mouseOver event handler to each node
125+
// only add popup text on nodes with no children
126+
// display the data in the node on hover
127+
node.on('mouseover', function (d: any, i: number): any {
128+
if (!d.children) {
129+
d3.select(this)
130+
.append('text')
131+
.text(JSON.stringify(d.data, undefined, 2))
132+
.style('fill', 'white')
133+
.attr('x', 75)
134+
.attr('y', 60)
135+
.style('font-size', '3rem')
136+
.attr('stroke', '#646464')
137+
.attr('id', `popup${i}`);
138+
}
139+
});
140+
141+
// add mouseOut event handler that removes the popup text
142+
node.on('mouseout', function (d: any, i: number): any {
143+
d3.select(`#popup${i}`).remove();
144+
});
145+
146+
// allows the canvas to be draggable
147+
node.call(
148+
d3
149+
.drag()
150+
.on('start', dragStarted)
151+
.on('drag', dragged)
152+
.on('end', dragEnded)
153+
);
154+
155+
// allows the canvas to be zoom-able
156+
svgContainer.call(
157+
d3
158+
.zoom()
159+
.extent([
160+
[0, 0],
161+
[width, height],
162+
])
163+
.scaleExtent([0, 8])
164+
.on('zoom', zoomed)
165+
);
166+
167+
// helper functions that help with dragging functionality
168+
function dragStarted(): any {
169+
d3.select(this).raise();
170+
g.attr('cursor', 'grabbing');
171+
}
172+
173+
function dragged(d: any): any {
174+
d3.select(this)
175+
.attr('dx', (d.x = d3.event.x))
176+
.attr('dy', (d.y = d3.event.y));
177+
}
178+
179+
function dragEnded(): any {
180+
g.attr('cursor', 'grab');
181+
}
182+
183+
// helper function that allows for zooming
184+
function zoomed(): any {
185+
g.attr('transform', d3.event.transform);
186+
}
187+
});
188+
189+
console.log('208');
190+
return (
191+
<div data-testid="canvas">
192+
<div className="Visualizer">
193+
<svg id="canvas"></svg>
194+
</div>
195+
</div>
196+
);
197+
};
198+
199+
export default Map;

src/app/components/StateRoute.tsx

Lines changed: 53 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,27 +5,49 @@
55
/* eslint-disable max-len */
66
/* eslint-disable object-curly-newline */
77
import React, { useState } from 'react';
8-
import { MemoryRouter as Router, Route, NavLink, Switch } from 'react-router-dom';
8+
import {
9+
MemoryRouter as Router,
10+
Route,
11+
NavLink,
12+
Switch,
13+
} from 'react-router-dom';
914
import Tree from './Tree';
15+
import Map from './Map';
1016
import PerfView from './PerfView';
1117

1218
const Chart = require('./Chart').default;
19+
1320
const ErrorHandler = require('./ErrorHandler').default;
1421

15-
const NO_STATE_MSG = 'No state change detected. Trigger an event to change state';
22+
const NO_STATE_MSG =
23+
'No state change detected. Trigger an event to change state';
1624
// eslint-disable-next-line react/prop-types
1725

1826
interface StateRouteProps {
19-
snapshot: { name?: string; componentData?: object; state?: string | object; stateSnaphot?: object; children?: any[]; };
27+
snapshot: {
28+
name?: string;
29+
componentData?: object;
30+
state?: string | object;
31+
stateSnaphot?: object;
32+
children?: any[];
33+
};
2034
hierarchy: object;
2135
snapshots: [];
2236
viewIndex: number;
2337
}
2438

25-
const StateRoute = (props:StateRouteProps) => {
39+
const StateRoute = (props: StateRouteProps) => {
2640
const { snapshot, hierarchy, snapshots, viewIndex } = props;
2741
const [noRenderData, setNoRenderData] = useState(false);
2842

43+
//Test Map
44+
const renderMap = () => {
45+
if (hierarchy) {
46+
return <Map snapshot={snapshot} />;
47+
}
48+
return <div className="noState">{NO_STATE_MSG}</div>;
49+
};
50+
2951
// the hierarchy gets set on the first click in the page
3052
// when the page is refreshed we may not have a hierarchy, so we need to check if hierarchy was initialized
3153
// if true involk render chart with hierarchy
@@ -58,29 +80,47 @@ const StateRoute = (props:StateRouteProps) => {
5880
/>
5981
);
6082
} else {
61-
perfChart = <div className="no-data-message">Application must be running in development mode in order to view performance data</div>;
83+
perfChart = (
84+
<div className="no-data-message">
85+
Application must be running in development mode in order to view
86+
performance data
87+
</div>
88+
);
6289
}
6390

64-
const renderPerfView = () => (
65-
<ErrorHandler>
66-
{perfChart}
67-
</ErrorHandler>
68-
);
91+
const renderPerfView = () => <ErrorHandler>{perfChart}</ErrorHandler>;
6992

7093
return (
7194
<Router>
7295
<div className="navbar">
73-
<NavLink className="router-link" activeClassName="is-active" exact to="/">
96+
<NavLink
97+
className="router-link"
98+
activeClassName="is-active"
99+
exact
100+
to="/"
101+
>
74102
Tree
75103
</NavLink>
76-
<NavLink className="router-link" activeClassName="is-active" to="/chart">
104+
<NavLink
105+
className="router-link"
106+
activeClassName="is-active"
107+
to="/chart"
108+
>
77109
History
78110
</NavLink>
79-
<NavLink className="router-link" activeClassName="is-active" to="/performance">
111+
<NavLink className="router-link" activeClassName="is-active" to="/map">
112+
Map
113+
</NavLink>
114+
<NavLink
115+
className="router-link"
116+
activeClassName="is-active"
117+
to="/performance"
118+
>
80119
Performance
81120
</NavLink>
82121
</div>
83122
<Switch>
123+
<Route path="/map" render={renderMap} />
84124
<Route path="/chart" render={renderChart} />
85125
<Route path="/performance" render={renderPerfView} />
86126
<Route path="/" render={renderTree} />

0 commit comments

Comments
 (0)