Skip to content

Commit 37e14c3

Browse files
committed
[results] show currently highlighted transition on the ToolsExperienceTransitionsChart
1 parent 60d2969 commit 37e14c3

File tree

11 files changed

+252
-110
lines changed

11 files changed

+252
-110
lines changed

results/src/core/blocks/tools/ToolsExperienceTransitionsBlock/Grid.tsx

Lines changed: 0 additions & 9 deletions
This file was deleted.

results/src/core/blocks/tools/ToolsExperienceTransitionsBlock/ToolsExperienceTransitionsBlock.tsx

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
import React, { useMemo, useState } from 'react'
1+
import React, { useCallback, useMemo, useState } from 'react'
2+
import styled from 'styled-components'
23
import { ToolExperienceId } from 'core/bucket_keys'
4+
import { useLegends } from 'core/helpers/useBucketKeys'
35
import Block from 'core/blocks/block/BlockVariant'
46
import {
57
ToolsExperienceTransitionsBlockData,
68
ApiToolExperienceTransitions,
79
} from './types'
8-
import { Grid } from './Grid'
910
import { ToolsExperienceTransitionsChart } from './ToolsExperienceTransitionsChart'
10-
import { useLegends } from 'core/helpers/useBucketKeys'
1111

1212
export const ToolsExperienceTransitionsBlock = ({
1313
block,
@@ -16,14 +16,36 @@ export const ToolsExperienceTransitionsBlock = ({
1616
block: ToolsExperienceTransitionsBlockData
1717
data: ApiToolExperienceTransitions[]
1818
}) => {
19-
console.log(data)
2019
const filteredData = useMemo(() =>
2120
data.filter(toolData => toolData.experienceTransitions.nodes.length > 0),
2221
[data]
2322
)
2423

25-
const [currentExperience, setCurrentExperience] = useState<ToolExperienceId>('interested')
26-
const [currentTransition, setCurrentTransition] = useState<[ToolExperienceId, ToolExperienceId] | null>(null)
24+
const [currentExperience, _setCurrentExperience] = useState<ToolExperienceId>('interested')
25+
const [currentTransition, _setCurrentTransition] = useState<[ToolExperienceId, ToolExperienceId] | null>([
26+
'interested',
27+
'would_use'
28+
])
29+
30+
// avoid creating a new transition array if the values don't change
31+
const setCurrentTransition = useCallback((transition: [ToolExperienceId, ToolExperienceId] | null) => {
32+
_setCurrentTransition((previous) => {
33+
if (transition === null) return null
34+
if (previous !== null && previous[0] === transition[0] && previous[1] === transition[1]) {
35+
return previous
36+
}
37+
38+
return transition
39+
})
40+
}, [_setCurrentTransition])
41+
42+
// reset the current transition when a new source experience is selected
43+
const setCurrentExperience = useCallback((experience: ToolExperienceId) => {
44+
if (experience === currentExperience) return
45+
46+
_setCurrentExperience(experience)
47+
_setCurrentTransition(null)
48+
}, [currentExperience, _setCurrentTransition])
2749

2850
const keys = data[0].experienceTransitions.keys
2951
const legends = useLegends(block, keys, 'tools')
@@ -55,4 +77,12 @@ export const ToolsExperienceTransitionsBlock = ({
5577
</Grid>
5678
</Block>
5779
)
58-
}
80+
}
81+
82+
const Grid = styled.div`
83+
display: grid;
84+
width: 100%;
85+
grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr));
86+
column-gap: 24px;
87+
row-gap: 16px;
88+
`

results/src/core/blocks/tools/ToolsExperienceTransitionsBlock/ToolsExperienceTransitionsChart/CustomSankey.tsx

Lines changed: 12 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@ import React, { useMemo } from 'react'
22
import { ToolExperienceId } from 'core/bucket_keys'
33
import { SankeyNodeDatum, SankeyLinkDatum, SankeyYear } from '../types'
44
import { staticProps } from './config'
5+
import { useChartContext } from './state'
56
import { YearsLegend } from './YearsLegend'
67
import { LinksBackground } from './LinksBackground'
78
import { LinkPercentages } from './LinkPercentages'
89
import { Nodes } from './Nodes'
910
import { ExperienceLinks } from './ExperienceLinks'
10-
import { useChartContext } from './state'
11+
import { TransitionLegend } from './TransitionLegend'
1112

1213
/**
1314
* Used to entirely replace the default nivo Sankey component,
@@ -23,12 +24,10 @@ export const CustomSankey = ({ nodes, links }: {
2324
const {
2425
currentExperience,
2526
setCurrentExperience,
27+
currentTransition,
2628
} = useChartContext()
2729

28-
const {
29-
years,
30-
linksByExperience,
31-
} = useMemo(() => {
30+
const { years, linksByExperience } = useMemo(() => {
3231
const _years: SankeyYear[] = []
3332

3433
const _nodesByExperience: Partial<Record<ToolExperienceId, SankeyNodeDatum[]>> = {}
@@ -66,24 +65,9 @@ export const CustomSankey = ({ nodes, links }: {
6665

6766
_years.sort((a, b) => a.year - b.year)
6867

69-
const _startNodes: SankeyNodeDatum[] = []
70-
const _endNodes: SankeyNodeDatum[] = []
71-
const firstYear = _years[0]
72-
const lastYear = _years[_years.length - 1]
73-
nodes.forEach(node => {
74-
if (node.year === firstYear.year) {
75-
_startNodes.push(node)
76-
}
77-
if (node.year === lastYear.year) {
78-
_endNodes.push(node)
79-
}
80-
})
81-
8268
return {
8369
years: _years,
8470
nodesByChoice: _nodesByExperience,
85-
startNodes: _startNodes,
86-
endNodes: _endNodes,
8771
linksByExperience: Object.values(_linksByExperience),
8872
}
8973
}, [nodes, links])
@@ -93,6 +77,13 @@ export const CustomSankey = ({ nodes, links }: {
9377
[linksByExperience, currentExperience]
9478
)
9579

80+
let currentTransitionLink: SankeyLinkDatum | undefined = undefined
81+
if (currentTransition !== null) {
82+
currentTransitionLink = currentLinks!.links.find(link => {
83+
return link.source.choice === currentTransition[0] && link.target.choice === currentTransition[1]
84+
})
85+
}
86+
9687
return (
9788
<>
9889
<YearsLegend years={years} />
@@ -113,6 +104,7 @@ export const CustomSankey = ({ nodes, links }: {
113104
/>
114105
))}
115106
<LinkPercentages links={currentLinks!.links} />
107+
<TransitionLegend link={currentTransitionLink} />
116108
</>
117109
)
118110
}

results/src/core/blocks/tools/ToolsExperienceTransitionsBlock/ToolsExperienceTransitionsChart/ExperienceLinks.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ export const NonMemoizedExperienceLinks = ({
4040
<LinkWithGradient
4141
key={`${link.source.id}.${link.target.id}`}
4242
link={link}
43+
isActive={isActive}
4344
/>
4445
))}
4546
</animated.g>
Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,36 @@
1-
import React, { memo } from 'react'
1+
import React, { memo, useCallback } from 'react'
22
import { SankeyLinkDatum } from '../types'
33
import { getLinkPath } from './getLinkPath'
4+
import { useChartContext } from './state'
5+
import styled from 'styled-components'
46

5-
const NonMemoizedLinkWithGradient = ({ link }: {
7+
const NonMemoizedLinkWithGradient = ({ link, isActive }: {
68
link: SankeyLinkDatum
9+
isActive: boolean
710
}) => {
811
const linkId = `${link.source.id}.${link.target.id}`
912
const gradientId = `${linkId}Gradient`
1013

14+
const { currentTransition, setCurrentTransition } = useChartContext()
15+
16+
const sourceExperience = link.source.choice
17+
const targetExperience = link.target.choice
18+
19+
const handleClick = useCallback(() => {
20+
setCurrentTransition([sourceExperience, targetExperience])
21+
}, [
22+
setCurrentTransition,
23+
sourceExperience,
24+
targetExperience,
25+
])
26+
27+
let opacity = 1
28+
// change the opacity of the inactive links in case
29+
// users selected a specific transition.
30+
if (currentTransition !== null && (link.source.choice !== currentTransition[0] || link.target.choice !== currentTransition[1])) {
31+
opacity = .3
32+
}
33+
1134
return (
1235
<>
1336
<defs>
@@ -16,12 +39,22 @@ const NonMemoizedLinkWithGradient = ({ link }: {
1639
<stop offset="100%" stopColor={link.target.color} />
1740
</linearGradient>
1841
</defs>
19-
<path
20-
fill={`url(#${gradientId})`}
42+
<Path
43+
$isActive={isActive}
44+
onClick={handleClick}
2145
d={getLinkPath(link, 1)}
46+
fill={`url(#${gradientId})`}
47+
opacity={opacity}
2248
/>
2349
</>
2450
)
2551
}
2652

53+
const Path = styled.path<{
54+
$isActive: boolean
55+
}>`
56+
pointer-events: ${({ $isActive }) => $isActive ? 'auto' : 'none'};
57+
cursor: ${({ $isActive }) => $isActive ? 'pointer' : 'auto'};
58+
`
59+
2760
export const LinkWithGradient = memo(NonMemoizedLinkWithGradient)
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,29 @@
1+
import React from 'react'
12
import styled from 'styled-components'
2-
import { staticProps } from './config'
3+
import { Entity } from 'core/types'
34

4-
export const ChartContainer = styled.div`
5-
display: flex;
6-
align-items: stretch;
7-
height: 180px;
8-
`
5+
export const ToolLegend = ({ tool }: {
6+
tool: Entity
7+
}) => {
8+
return (
9+
<Container>
10+
<Label>
11+
{tool.name}
12+
</Label>
13+
</Container>
14+
)
15+
}
916

10-
export const ToolLegendContainer = styled.div`
17+
const Container = styled.div`
1118
flex-grow: 0;
1219
flex-shrink: 0;
1320
width: 20px;
14-
margin-top: ${staticProps.margin.top}px;
15-
margin-bottom: ${staticProps.margin.bottom}px;
1621
display: flex;
1722
align-items: center;
1823
justify-content: center;
1924
`
2025

21-
export const ToolLegend = styled.div`
26+
export const Label = styled.div`
2227
height: 20px;
2328
display: flex;
2429
align-items: center;
@@ -27,4 +32,4 @@ export const ToolLegend = styled.div`
2732
transform: rotate(-90deg);
2833
font-size: ${({ theme }) => theme.typography.size.smaller};
2934
font-weight: ${({ theme }) => theme.typography.weight.bold};
30-
`
35+
`
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import React, { useCallback, useMemo } from 'react'
2-
import { useTheme } from 'styled-components'
2+
import styled, { useTheme } from 'styled-components'
33
import { ResponsiveSankey } from '@nivo/sankey'
44
import { ToolExperienceId } from 'core/bucket_keys'
55
import { ApiToolExperienceTransitions } from '../types'
66
import { staticProps } from './config'
7-
import { ChartContainer, ToolLegendContainer, ToolLegend } from './ChartContainer'
87
import { ChartContextProvider } from './state'
8+
import { ToolLegend } from './ToolLegend'
99

1010
export const ToolsExperienceTransitionsChart = ({
1111
data,
@@ -22,7 +22,7 @@ export const ToolsExperienceTransitionsChart = ({
2222
}) => {
2323
const theme = useTheme()
2424

25-
const chartData = {
25+
const chartData = useMemo(() => ({
2626
nodes: data.experienceTransitions.nodes,
2727
links: data.experienceTransitions.transitions.map(transition => {
2828
return {
@@ -32,7 +32,7 @@ export const ToolsExperienceTransitionsChart = ({
3232
percentage: transition.percentage,
3333
}
3434
}),
35-
}
35+
}), [data])
3636

3737
const context = useMemo(() => ({
3838
toolId: data.id,
@@ -54,27 +54,36 @@ export const ToolsExperienceTransitionsChart = ({
5454

5555
return (
5656
<ChartContextProvider value={context}>
57-
<ChartContainer>
58-
<ToolLegendContainer>
59-
<ToolLegend>
60-
{data.entity.name}
61-
</ToolLegend>
62-
</ToolLegendContainer>
63-
<ResponsiveSankey
64-
margin={staticProps.margin}
65-
data={chartData}
66-
sort="input"
67-
align="justify"
68-
colors={getColor}
69-
nodeThickness={18}
70-
nodeInnerPadding={1}
71-
nodeSpacing={2}
72-
linkContract={1}
73-
animate={false}
74-
// @ts-ignore
75-
layers={staticProps.layers}
76-
/>
77-
</ChartContainer>
57+
<Container>
58+
<ToolLegend tool={data.entity} />
59+
<ChartContainer>
60+
<ResponsiveSankey
61+
margin={staticProps.margin}
62+
data={chartData}
63+
sort="input"
64+
align="justify"
65+
colors={getColor}
66+
nodeThickness={18}
67+
nodeInnerPadding={1}
68+
nodeSpacing={2}
69+
linkContract={1}
70+
animate={false}
71+
// @ts-ignore
72+
layers={staticProps.layers}
73+
/>
74+
</ChartContainer>
75+
</Container>
7876
</ChartContextProvider>
7977
)
80-
}
78+
}
79+
80+
const Container = styled.div`
81+
display: flex;
82+
overflow: hidden;
83+
align-items: center;
84+
`
85+
86+
const ChartContainer = styled.div`
87+
width: calc(100% - 20px);
88+
height: ${staticProps.margin.top + staticProps.chartHeight + staticProps.margin.bottom}px;
89+
`

0 commit comments

Comments
 (0)