Skip to content

Commit 2505a18

Browse files
Merge pull request #17 from VNguyenCode/onHover
onHover functionality added and compatible with Recoil and Non-Recoil apps
2 parents ed44f24 + 43260cf commit 2505a18

File tree

13 files changed

+366
-61
lines changed

13 files changed

+366
-61
lines changed

src/app/actions/actions.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,13 @@ export const resetSlider = () => ({
7878
type: types.SLIDER_ZERO,
7979
});
8080

81-
export const onHover = () => ({
81+
export const onHover = (rtid) => ({
8282
type: types.ON_HOVER,
8383
//the payload should be something to relate the component we're hovering and highlight that component on the DOM
84-
payload: 'PAYLOAD FROM onHover inside of action.ts'
84+
payload: rtid
85+
})
86+
87+
export const onHoverExit = (rtid) => ({
88+
type: types.ON_HOVER_EXIT,
89+
payload: rtid
8590
})

src/app/components/AtomsRelationship.tsx

Lines changed: 87 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,20 @@ import { Cluster, hierarchy } from '@visx/hierarchy';
55
import { LinkVertical } from '@visx/shape';
66
import { LinearGradient } from '@visx/gradient';
77
import { StateRouteProps} from './StateRoute'
8+
import { onHover, onHoverExit } from '../actions/actions'
9+
import { useStoreContext } from '../store'
10+
import Legend from './AtomsRelationshipLegend'
811

9-
const blue = '#acdbdf';
10-
const selectWhite = '#f0ece2';
12+
export const blue = '#acdbdf';
13+
export const selectWhite = '#f0ece2';
1114

1215
export const lightgreen = '#0BAB64';
13-
const green = '#3BB78F'
14-
const orange = '#FED8B1';
16+
export const green = '#3BB78F'
17+
export const orange = '#FED8B1';
1518

16-
const merlinsbeard = '#f7f7f3';
19+
export const merlinsbeard = '#f7f7f3';
1720
export const background = '#242529';
18-
const root = '#d2f5e3';
21+
export const root = '#d2f5e3';
1922

2023
interface clusterShape {
2124
name?:string;
@@ -38,8 +41,10 @@ interface selectorsCache {
3841

3942

4043
const clusterData : clusterShape = {};
41-
const selectorsCache :selectorsCache = {}
42-
44+
const selectorsCache :selectorsCache = {};
45+
const bothObj = {};
46+
47+
4348
let initialFire = false
4449
function clusterDataPopulate(props:StateRouteProps) {
4550
let atomCompObj = reorganizedCompObj(props);
@@ -57,10 +62,14 @@ function clusterDataPopulate(props:StateRouteProps) {
5762
let outerobj:outerObjShape = {}
5863
outerobj.name = key
5964
selectorsCache[key] = true
65+
66+
if(!bothObj[key]){
67+
bothObj[key] = []
68+
}
69+
6070

6171
if(props[0].atomSelectors[key].length){
6272
for(let i=0; i<props[0].atomSelectors[key].length;i++){
63-
6473
if(!outerobj.children) outerobj.children = []
6574
let innerobj:innerObjShape = {}
6675
innerobj.name = props[0].atomSelectors[key][i]
@@ -69,8 +78,15 @@ function clusterDataPopulate(props:StateRouteProps) {
6978
//if atoms contain components
7079
if(atomCompObj[props[0].atomSelectors[key][i]]){
7180
for(let j=0; j<atomCompObj[props[0].atomSelectors[key][i]].length;j++){
81+
if(!bothObj[props[0].atomSelectors[key][i]]){
82+
bothObj[props[0].atomSelectors[key][i]] = []
83+
}
84+
bothObj[props[0].atomSelectors[key][i]].push(atomCompObj[props[0].atomSelectors[key][i]][0])
85+
7286
if(!innerobj.children) innerobj.children = []
7387
innerobj.children.push({name:atomCompObj[props[0].atomSelectors[key][i]]})
88+
bothObj[key].push(atomCompObj[props[0].atomSelectors[key][i]][0])
89+
7490
}
7591
}
7692
outerobj.children.push(innerobj)
@@ -81,6 +97,11 @@ function clusterDataPopulate(props:StateRouteProps) {
8197
if(atomCompObj[key] && atomCompObj[key].length){
8298
for (let i=0; i<atomCompObj[key].length;i++){
8399
outerobj.children.push({name:atomCompObj[key][i]})
100+
101+
if(!bothObj[key]){
102+
bothObj[key] = []
103+
}
104+
bothObj[key].push(atomCompObj[key][i])
84105
}
85106
}
86107

@@ -92,9 +113,11 @@ function clusterDataPopulate(props:StateRouteProps) {
92113
let outObj:outerObjShape = {};
93114
if(!selectorsCache[key]){
94115
outObj.name = key
116+
if(!bothObj[key]) bothObj[key] = []
95117
for (let i=0; i<atomCompObj[key].length;i++){
96118
if(!outObj.children) outObj.children = []
97119
outObj.children.push({name:atomCompObj[key][i]})
120+
bothObj[key].push(atomCompObj[key][i])
98121
}
99122
clusterData.children.push(outObj)
100123
}
@@ -120,13 +143,14 @@ function reorganizedCompObj(props) {
120143
return reorganizedCompObj;
121144
}
122145

123-
function Node({ node }) {
146+
function Node({ node, snapshots, dispatch, bothObj}) {
147+
// const [dispatch] = useStoreContext();
124148
const selector = node.depth === 1 && node.height === 2
125149
const isRoot = node.depth === 0;
126150
const isParent = !!node.children;
127-
151+
128152
if (isRoot) return <RootNode node={node} />;
129-
if (selector) return <SelectorNode node = {node}/>;
153+
if (selector) return <SelectorNode node = {node} snapshots = {snapshots} bothObj = {bothObj} dispatch = {dispatch}/>;
130154

131155
return (
132156
<Group top={node.y} left={node.x}>
@@ -135,9 +159,16 @@ function Node({ node }) {
135159
r={12}
136160
fill={isParent ? orange : blue}
137161
stroke={isParent ? orange : blue}
138-
// onClick={() => {
139-
// alert(`clicked: ${JSON.stringify(node.data.name)}`);
140-
// }}
162+
onMouseLeave={()=> {
163+
for (let i=0; i<bothObj[node.data.name].length; i++){
164+
dispatch(onHoverExit(snapshots[0].recoilDomNode[bothObj[node.data.name][i]]))
165+
}
166+
}}
167+
onMouseEnter={()=> {
168+
for (let i=0; i<bothObj[node.data.name].length; i++){
169+
dispatch(onHover(snapshots[0].recoilDomNode[bothObj[node.data.name][i]]))
170+
}
171+
}}
141172
/>
142173
)}
143174
<text
@@ -156,6 +187,7 @@ function Node({ node }) {
156187
}
157188

158189
function RootNode({ node }) {
190+
159191
const width = 40;
160192
const height = 20;
161193
const centerX = -width / 2;
@@ -189,18 +221,24 @@ function RootNode({ node }) {
189221
);
190222
}
191223

192-
function SelectorNode({ node }) {
193-
224+
function SelectorNode({ node, snapshots, dispatch, bothObj}) {
194225
return (
195226
<Group top={node.y} left={node.x}>
196227
{node.depth !== 0 && (
197228
<circle
198229
r={12}
199230
fill={selectWhite}
200231
stroke={selectWhite}
201-
// onClick={() => {
202-
// alert(`clicked: ${JSON.stringify(node.data.name)}`);
203-
// }}
232+
onMouseLeave={()=> {
233+
for (let i=0; i<bothObj[node.data.name].length; i++){
234+
dispatch(onHoverExit(snapshots[0].recoilDomNode[bothObj[node.data.name][i]]))
235+
}
236+
}}
237+
onMouseEnter={()=> {
238+
for (let i=0; i<bothObj[node.data.name].length; i++){
239+
dispatch(onHover(snapshots[0].recoilDomNode[bothObj[node.data.name][i]]))
240+
}
241+
}}
204242
/>
205243
)}
206244
<text
@@ -218,6 +256,15 @@ function SelectorNode({ node }) {
218256
);
219257
}
220258

259+
function removeDup(bothObj){
260+
let filteredObj = {}
261+
for (let key in bothObj){
262+
let array = bothObj[key].filter((a,b) => bothObj[key].indexOf(a) === b)
263+
filteredObj[key] = array
264+
}
265+
return filteredObj
266+
}
267+
221268
const defaultMargin = { top: 40, left: 0, right: 0, bottom: 40 };
222269

223270
// export type DendrogramProps = {
@@ -226,13 +273,18 @@ const defaultMargin = { top: 40, left: 0, right: 0, bottom: 40 };
226273
// margin?: { top: number; right: number; bottom: number; left: number };
227274
// };
228275

229-
export default function Example({
276+
export default function AtomsRelationship({
230277
width,
231278
height,
232279
margin = defaultMargin,
233280
snapshots,
234281
}) {
235282

283+
284+
let filtered = removeDup(bothObj)
285+
286+
const [{ tabs, currentTab }, dispatch] = useStoreContext();
287+
236288
if(!initialFire){
237289
clusterDataPopulate(snapshots);
238290
}
@@ -242,11 +294,16 @@ export default function Example({
242294
const yMax = height - margin.top - margin.bottom;
243295

244296
return width < 10 ? null : (
297+
<>
298+
<div>
299+
<Legend
300+
hierarchy = {hierarchy} />
301+
</div>
245302
<svg width={width} height={height}>
303+
246304
<LinearGradient id="top" from={lightgreen} to={green} />
247305

248306
<rect width={width} height={height} rx={14} fill={background} />
249-
250307
<Cluster root={data} size={[xMax, yMax]}>
251308
{(cluster) => (
252309
<Group top={margin.top} left={margin.left}>
@@ -257,15 +314,20 @@ export default function Example({
257314
stroke={merlinsbeard}
258315
strokeWidth="1"
259316
strokeOpacity={0.2}
260-
fill="none"
317+
fill="none"
261318
/>
262319
))}
263320
{cluster.descendants().map((node, i) => (
264-
<Node key={`cluster-node-${i}`} node={node} />
321+
<Node key={`cluster-node-${i}`}
322+
node={node}
323+
bothObj = {filtered}
324+
snapshots = {snapshots}
325+
dispatch = {dispatch} />
265326
))}
266327
</Group>
267328
)}
268329
</Cluster>
269330
</svg>
331+
</>
270332
);
271-
}
333+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import React from 'react';
2+
import { scaleOrdinal } from '@visx/scale';
3+
import {
4+
LegendOrdinal,
5+
LegendItem,
6+
LegendLabel,
7+
} from '@visx/legend';
8+
9+
10+
const ordinalColorScale = scaleOrdinal({
11+
domain: ['Root', 'Selectors', 'Atoms', 'Components'],
12+
range: [ '#3BB78F', '#f0ece2', '#FED8B1', '#acdbdf'],
13+
});
14+
15+
const legendGlyphSize = 15;
16+
17+
export default function Legend({ events = false }: { events?: boolean }) {
18+
return (
19+
<div className="legends">
20+
<LegendDemo title="Recoil Relationships">
21+
<LegendOrdinal scale={ordinalColorScale} labelFormat={label => `${label}`}>
22+
{labels => (
23+
<div
24+
style={{ display: 'flex', flexDirection: 'column' }}
25+
>
26+
{labels.map((label, i) => (
27+
<LegendItem
28+
key={`legend-quantile-${i}`}
29+
margin="0 5px"
30+
onClick={() => {
31+
if (events) alert(`clicked: ${JSON.stringify(label)}`);
32+
}}
33+
>
34+
<svg width={legendGlyphSize} height={legendGlyphSize}>
35+
<circle
36+
fill={label.value}
37+
r={legendGlyphSize / 2}
38+
cx={legendGlyphSize / 2}
39+
cy={legendGlyphSize / 2}
40+
/>
41+
</svg>
42+
<LegendLabel align="left" margin="4px 0px 4px 4px">
43+
{label.text}
44+
</LegendLabel>
45+
</LegendItem>
46+
))}
47+
</div>
48+
)}
49+
</LegendOrdinal>
50+
</LegendDemo>
51+
<style jsx>
52+
{`
53+
.legends {
54+
width: 25%;
55+
font-family: arial;
56+
font-weight: 900;;
57+
border-radius: 14px;
58+
padding: 2px 2px 2px 2px;
59+
overflow-y: auto;
60+
flex-grow: 1;
61+
}
62+
`}
63+
</style>
64+
</div>
65+
);
66+
}
67+
68+
function LegendDemo({ title, children }: { title: string; children: React.ReactNode }) {
69+
return (
70+
<div className="legend">
71+
<div className="title">{title}</div>
72+
{children}
73+
<style jsx>{`
74+
.legend {
75+
position: absolute;
76+
top: 50;
77+
left: 50;
78+
line-height: 0.9em;
79+
color: #efefef;
80+
font-size: 9px;
81+
font-family: arial;
82+
padding: 10px 10px;
83+
float: left;
84+
margin: 5px 5px;
85+
}
86+
.title {
87+
font-size: 12px;
88+
margin-bottom: 10px;
89+
font-weight: 100;
90+
}
91+
`}</style>
92+
</div>
93+
);
94+
}

0 commit comments

Comments
 (0)