Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
F
frontend
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
vicotor
frontend
Commits
8116525c
Commit
8116525c
authored
Oct 26, 2022
by
tom
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
multiline chart
parent
51b62da5
Changes
11
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
213 additions
and
105 deletions
+213
-105
.gitattributes
.gitattributes
+1
-0
charts_eth_token_transfer.json
data/charts_eth_token_transfer.json
+3
-0
EthereumChart.tsx
ui/charts/EthereumChart.tsx
+80
-43
Graph.tsx
ui/pages/Graph.tsx
+5
-4
ChartArea.tsx
ui/shared/chart/ChartArea.tsx
+2
-2
ChartAxis.tsx
ui/shared/chart/ChartAxis.tsx
+7
-1
ChartLegend.tsx
ui/shared/chart/ChartLegend.tsx
+48
-0
ChartSelectionX.tsx
ui/shared/chart/ChartSelectionX.tsx
+9
-6
ChartTooltip.tsx
ui/shared/chart/ChartTooltip.tsx
+43
-40
types.tsx
ui/shared/chart/types.tsx
+8
-0
useTimeChartController.tsx
ui/shared/chart/useTimeChartController.tsx
+7
-9
No files found.
.gitattributes
View file @
8116525c
__snapshots__/** filter=lfs diff=lfs merge=lfs -text
__snapshots__/** filter=lfs diff=lfs merge=lfs -text
data/charts_eth_txs.json filter=lfs diff=lfs merge=lfs -text
data/charts_eth_txs.json filter=lfs diff=lfs merge=lfs -text
data/charts_eth_token_transfer.json filter=lfs diff=lfs merge=lfs -text
data/charts_eth_token_transfer.json
0 → 100644
LFS
View file @
8116525c
This source diff could not be displayed because it is stored in LFS. You can
view the blob
instead.
ui/charts/Ethereum
DailyTxs
Chart.tsx
→
ui/charts/EthereumChart.tsx
View file @
8116525c
import
{
useToken
,
Button
,
Box
}
from
'
@chakra-ui/react
'
;
import
{
useToken
,
Button
,
Box
}
from
'
@chakra-ui/react
'
;
import
_range
from
'
lodash/range
'
;
import
React
from
'
react
'
;
import
React
from
'
react
'
;
import
json
from
'
data/charts_eth_txs.json
'
;
import
type
{
TimeChartData
}
from
'
ui/shared/chart/types
'
;
import
ethTokenTransferData
from
'
data/charts_eth_token_transfer.json
'
;
import
ethTxsData
from
'
data/charts_eth_txs.json
'
;
import
ChartArea
from
'
ui/shared/chart/ChartArea
'
;
import
ChartArea
from
'
ui/shared/chart/ChartArea
'
;
import
ChartAxis
from
'
ui/shared/chart/ChartAxis
'
;
import
ChartAxis
from
'
ui/shared/chart/ChartAxis
'
;
import
ChartGridLine
from
'
ui/shared/chart/ChartGridLine
'
;
import
ChartGridLine
from
'
ui/shared/chart/ChartGridLine
'
;
import
ChartLegend
from
'
ui/shared/chart/ChartLegend
'
;
import
ChartLine
from
'
ui/shared/chart/ChartLine
'
;
import
ChartLine
from
'
ui/shared/chart/ChartLine
'
;
import
ChartOverlay
from
'
ui/shared/chart/ChartOverlay
'
;
import
ChartOverlay
from
'
ui/shared/chart/ChartOverlay
'
;
import
ChartSelectionX
from
'
ui/shared/chart/ChartSelectionX
'
;
import
ChartSelectionX
from
'
ui/shared/chart/ChartSelectionX
'
;
import
ChartTooltip
from
'
ui/shared/chart/ChartTooltip
'
;
import
ChartTooltip
from
'
ui/shared/chart/ChartTooltip
'
;
import
useBrushX
from
'
ui/shared/chart/useBrushX
'
;
//
import useBrushX from 'ui/shared/chart/useBrushX';
import
useChartSize
from
'
ui/shared/chart/useChartSize
'
;
import
useChartSize
from
'
ui/shared/chart/useChartSize
'
;
import
useTimeChartController
from
'
ui/shared/chart/useTimeChartController
'
;
import
useTimeChartController
from
'
ui/shared/chart/useTimeChartController
'
;
const
CHART_MARGIN
=
{
bottom
:
20
,
left
:
65
,
right
:
30
,
top
:
10
};
const
CHART_MARGIN
=
{
bottom
:
20
,
left
:
65
,
right
:
30
,
top
:
10
};
const
Ethereum
DailyTxs
Chart
=
()
=>
{
const
EthereumChart
=
()
=>
{
const
ref
=
React
.
useRef
<
SVGSVGElement
>
(
null
);
const
ref
=
React
.
useRef
<
SVGSVGElement
>
(
null
);
const
overlayRef
=
React
.
useRef
<
SVGRectElement
>
(
null
);
const
overlayRef
=
React
.
useRef
<
SVGRectElement
>
(
null
);
const
{
width
,
height
,
innerWidth
,
innerHeight
}
=
useChartSize
(
ref
.
current
,
CHART_MARGIN
);
const
{
width
,
height
,
innerWidth
,
innerHeight
}
=
useChartSize
(
ref
.
current
,
CHART_MARGIN
);
const
[
range
,
setRange
]
=
React
.
useState
<
[
number
,
number
]
>
([
0
,
Infinity
]);
const
[
range
,
setRange
]
=
React
.
useState
<
[
number
,
number
]
>
([
0
,
Infinity
]);
const
brushLimits
=
React
.
useMemo
(()
=>
(
const
data
:
TimeChartData
=
[
[
[
0
,
innerHeight
],
[
innerWidth
,
height
]
]
as
[[
number
,
number
],
[
number
,
number
]]
{
),
[
height
,
innerHeight
,
innerWidth
]);
name
:
'
Daily Transactions
'
,
useBrushX
({
anchor
:
ref
.
current
,
limits
:
brushLimits
,
setRange
});
color
:
useToken
(
'
colors
'
,
'
blue.500
'
),
items
:
ethTxsData
.
slice
(
range
[
0
],
range
[
1
]).
map
((
d
)
=>
({
...
d
,
date
:
new
Date
(
d
.
date
)
})),
},
{
name
:
'
ERC-20 Token Transfers
'
,
color
:
useToken
(
'
colors
'
,
'
green.500
'
),
items
:
ethTokenTransferData
.
slice
(
range
[
0
],
range
[
1
]).
map
((
d
)
=>
({
...
d
,
date
:
new
Date
(
d
.
date
)
})),
},
];
const
data
=
{
const
[
selectedLines
,
setSelectedLines
]
=
React
.
useState
<
Array
<
number
>>
(
_range
(
data
.
length
));
items
:
json
.
slice
(
range
[
0
],
range
[
1
]).
map
((
d
)
=>
({
...
d
,
date
:
new
Date
(
d
.
date
)
})),
const
filteredData
=
data
.
filter
((
_
,
index
)
=>
selectedLines
.
includes
(
index
));
};
const
{
yTickFormat
,
xScale
,
yScale
}
=
useTimeChartController
({
data
,
width
:
innerWidth
,
height
:
innerHeight
});
const
lineColor
=
useToken
(
'
colors
'
,
'
blue.500
'
);
const
{
yTickFormat
,
xScale
,
yScale
}
=
useTimeChartController
({
data
:
filteredData
.
length
===
0
?
data
:
filteredData
,
width
:
innerWidth
,
height
:
innerHeight
,
});
const
handleRangeSelect
=
React
.
useCallback
((
nextRange
:
[
number
,
number
])
=>
{
const
handleRangeSelect
=
React
.
useCallback
((
nextRange
:
[
number
,
number
])
=>
{
setRange
([
range
[
0
]
+
nextRange
[
0
],
range
[
0
]
+
nextRange
[
1
]
]);
setRange
([
range
[
0
]
+
nextRange
[
0
],
range
[
0
]
+
nextRange
[
1
]
]);
...
@@ -42,6 +57,17 @@ const EthereumDailyTxsChart = () => {
...
@@ -42,6 +57,17 @@ const EthereumDailyTxsChart = () => {
setRange
([
0
,
Infinity
]);
setRange
([
0
,
Infinity
]);
},
[
]);
},
[
]);
const
handleLegendItemClick
=
React
.
useCallback
((
index
:
number
)
=>
{
const
nextSelectedLines
=
selectedLines
.
includes
(
index
)
?
selectedLines
.
filter
((
item
)
=>
item
!==
index
)
:
[
...
selectedLines
,
index
];
setSelectedLines
(
nextSelectedLines
);
},
[
selectedLines
]);
// uncomment if we need brush the chart
// const brushLimits = React.useMemo(() => (
// [ [ 0, innerHeight ], [ innerWidth, height ] ] as [[number, number], [number, number]]
// ), [ height, innerHeight, innerWidth ]);
// useBrushX({ anchor: ref.current, limits: brushLimits, setRange });
return
(
return
(
<
Box
display=
"inline-block"
position=
"relative"
width=
"100%"
height=
"100%"
>
<
Box
display=
"inline-block"
position=
"relative"
width=
"100%"
height=
"100%"
>
<
svg
width=
{
width
||
'
100%
'
}
height=
{
height
||
'
100%
'
}
ref=
{
ref
}
>
<
svg
width=
{
width
||
'
100%
'
}
height=
{
height
||
'
100%
'
}
ref=
{
ref
}
>
...
@@ -73,19 +99,25 @@ const EthereumDailyTxsChart = () => {
...
@@ -73,19 +99,25 @@ const EthereumDailyTxsChart = () => {
/>
/>
{
/* GRAPH */
}
{
/* GRAPH */
}
<
ChartLine
{
filteredData
.
map
((
d
)
=>
(
data=
{
data
.
items
}
<
ChartLine
xScale=
{
xScale
}
key=
{
d
.
name
}
yScale=
{
yScale
}
data=
{
d
.
items
}
stroke=
{
lineColor
}
xScale=
{
xScale
}
animation=
"left"
yScale=
{
yScale
}
/>
stroke=
{
d
.
color
}
<
ChartArea
animation=
"left"
data=
{
data
.
items
}
/>
color=
{
lineColor
}
))
}
xScale=
{
xScale
}
{
filteredData
.
map
((
d
)
=>
(
yScale=
{
yScale
}
<
ChartArea
/>
key=
{
d
.
name
}
data=
{
d
.
items
}
color=
{
d
.
color
}
xScale=
{
xScale
}
yScale=
{
yScale
}
/>
))
}
{
/* AXISES */
}
{
/* AXISES */
}
<
ChartAxis
<
ChartAxis
...
@@ -104,22 +136,26 @@ const EthereumDailyTxsChart = () => {
...
@@ -104,22 +136,26 @@ const EthereumDailyTxsChart = () => {
anchorEl=
{
overlayRef
.
current
}
anchorEl=
{
overlayRef
.
current
}
disableAnimation
disableAnimation
/>
/>
<
ChartTooltip
{
filteredData
.
length
>
0
&&
(
anchorEl=
{
overlayRef
.
current
}
<
ChartTooltip
width=
{
innerWidth
}
anchorEl=
{
overlayRef
.
current
}
height=
{
innerHeight
}
width=
{
innerWidth
}
margin=
{
CHART_MARGIN
}
height=
{
innerHeight
}
xScale=
{
xScale
}
margin=
{
CHART_MARGIN
}
yScale=
{
yScale
}
xScale=
{
xScale
}
data=
{
data
}
yScale=
{
yScale
}
/>
data=
{
filteredData
}
<
ChartSelectionX
/>
anchorEl=
{
overlayRef
.
current
}
)
}
height=
{
innerHeight
}
{
filteredData
.
length
>
0
&&
(
scale=
{
xScale
}
<
ChartSelectionX
data=
{
data
}
anchorEl=
{
overlayRef
.
current
}
onSelect=
{
handleRangeSelect
}
height=
{
innerHeight
}
/>
scale=
{
xScale
}
data=
{
filteredData
}
onSelect=
{
handleRangeSelect
}
/>
)
}
</
ChartOverlay
>
</
ChartOverlay
>
</
g
>
</
g
>
</
svg
>
</
svg
>
...
@@ -132,11 +168,12 @@ const EthereumDailyTxsChart = () => {
...
@@ -132,11 +168,12 @@ const EthereumDailyTxsChart = () => {
right=
{
`${ CHART_MARGIN?.right || 0 }px`
}
right=
{
`${ CHART_MARGIN?.right || 0 }px`
}
onClick=
{
handleZoomReset
}
onClick=
{
handleZoomReset
}
>
>
Reset zoom
Reset zoom
</
Button
>
</
Button
>
)
}
)
}
<
ChartLegend
data=
{
data
}
selectedIndexes=
{
selectedLines
}
onClick=
{
handleLegendItemClick
}
/>
</
Box
>
</
Box
>
);
);
};
};
export
default
React
.
memo
(
Ethereum
DailyTxs
Chart
);
export
default
React
.
memo
(
EthereumChart
);
ui/pages/Graph.tsx
View file @
8116525c
import
{
Box
}
from
'
@chakra-ui/react
'
;
import
{
Box
,
Heading
}
from
'
@chakra-ui/react
'
;
import
React
from
'
react
'
;
import
React
from
'
react
'
;
import
Ethereum
DailyTxsChart
from
'
ui/charts/EthereumDailyTxs
Chart
'
;
import
Ethereum
Chart
from
'
ui/charts/Ethereum
Chart
'
;
import
Page
from
'
ui/shared/Page/Page
'
;
import
Page
from
'
ui/shared/Page/Page
'
;
import
PageTitle
from
'
ui/shared/Page/PageTitle
'
;
import
PageTitle
from
'
ui/shared/Page/PageTitle
'
;
const
Graph
=
()
=>
{
const
Graph
=
()
=>
{
return
(
return
(
<
Page
>
<
Page
>
<
PageTitle
text=
"Ethereum Daily Transactions Chart"
/>
<
PageTitle
text=
"Charts"
/>
<
Heading
as=
"h2"
size=
"sm"
fontWeight=
"500"
mb=
{
3
}
>
Ethereum Daily Transactions
&
ERC-20 Token Transfer Chart
</
Heading
>
<
Box
w=
"100%"
h=
"400px"
>
<
Box
w=
"100%"
h=
"400px"
>
<
Ethereum
DailyTxs
Chart
/>
<
EthereumChart
/>
</
Box
>
</
Box
>
</
Page
>
</
Page
>
);
);
...
...
ui/shared/chart/ChartArea.tsx
View file @
8116525c
...
@@ -37,8 +37,8 @@ const ChartArea = ({ xScale, yScale, color, data, disableAnimation, ...props }:
...
@@ -37,8 +37,8 @@ const ChartArea = ({ xScale, yScale, color, data, disableAnimation, ...props }:
<
path
ref=
{
ref
}
d=
{
d
}
fill=
{
`url(#gradient-${ color })`
}
opacity=
{
0
}
{
...
props
}
/>
<
path
ref=
{
ref
}
d=
{
d
}
fill=
{
`url(#gradient-${ color })`
}
opacity=
{
0
}
{
...
props
}
/>
<
defs
>
<
defs
>
<
linearGradient
id=
{
`gradient-${ color }`
}
x1=
"0%"
x2=
"0%"
y1=
"0%"
y2=
"100%"
>
<
linearGradient
id=
{
`gradient-${ color }`
}
x1=
"0%"
x2=
"0%"
y1=
"0%"
y2=
"100%"
>
<
stop
offset=
"0%"
stopColor=
{
color
}
stopOpacity=
{
0.9
}
/>
<
stop
offset=
"0%"
stopColor=
{
color
}
stopOpacity=
{
1
}
/>
<
stop
offset=
"100%"
stopColor=
{
color
}
stopOpacity=
{
0.1
}
/>
<
stop
offset=
"100%"
stopColor=
{
color
}
stopOpacity=
{
0.1
5
}
/>
</
linearGradient
>
</
linearGradient
>
</
defs
>
</
defs
>
</>
</>
...
...
ui/shared/chart/ChartAxis.tsx
View file @
8116525c
...
@@ -45,7 +45,9 @@ const ChartAxis = ({ type, scale, ticks, tickFormat, disableAnimation, anchorEl,
...
@@ -45,7 +45,9 @@ const ChartAxis = ({ type, scale, ticks, tickFormat, disableAnimation, anchorEl,
return
;
return
;
}
}
d3
.
select
(
anchorEl
)
const
anchorD3
=
d3
.
select
(
anchorEl
);
anchorD3
.
on
(
'
mouseout.axisX
'
,
()
=>
{
.
on
(
'
mouseout.axisX
'
,
()
=>
{
d3
.
select
(
ref
.
current
)
d3
.
select
(
ref
.
current
)
.
selectAll
(
'
text
'
)
.
selectAll
(
'
text
'
)
...
@@ -60,6 +62,10 @@ const ChartAxis = ({ type, scale, ticks, tickFormat, disableAnimation, anchorEl,
...
@@ -60,6 +62,10 @@ const ChartAxis = ({ type, scale, ticks, tickFormat, disableAnimation, anchorEl,
textElements
textElements
.
style
(
'
font-weight
'
,
(
d
,
i
)
=>
i
===
index
-
1
?
'
bold
'
:
'
normal
'
);
.
style
(
'
font-weight
'
,
(
d
,
i
)
=>
i
===
index
-
1
?
'
bold
'
:
'
normal
'
);
});
});
return
()
=>
{
anchorD3
.
on
(
'
mouseout.axisX mousemove.axisX
'
,
null
);
};
},
[
anchorEl
,
scale
]);
},
[
anchorEl
,
scale
]);
return
<
g
ref=
{
ref
}
{
...
props
}
/>;
return
<
g
ref=
{
ref
}
{
...
props
}
/>;
...
...
ui/shared/chart/ChartLegend.tsx
0 → 100644
View file @
8116525c
import
{
Box
,
Circle
,
Text
}
from
'
@chakra-ui/react
'
;
import
React
from
'
react
'
;
import
type
{
TimeChartData
}
from
'
ui/shared/chart/types
'
;
interface
Props
{
data
:
TimeChartData
;
selectedIndexes
:
Array
<
number
>
;
onClick
:
(
index
:
number
)
=>
void
;
}
const
ChartLegend
=
({
data
,
selectedIndexes
,
onClick
}:
Props
)
=>
{
const
handleItemClick
=
React
.
useCallback
((
event
:
React
.
MouseEvent
<
HTMLDivElement
>
)
=>
{
const
itemIndex
=
(
event
.
currentTarget
as
HTMLDivElement
).
getAttribute
(
'
data-index
'
);
onClick
(
Number
(
itemIndex
));
},
[
onClick
]);
return
(
<
Box
display=
"flex"
columnGap=
{
3
}
mt=
{
2
}
>
{
data
.
map
(({
name
,
color
},
index
)
=>
{
const
isSelected
=
selectedIndexes
.
includes
(
index
);
return
(
<
Box
key=
{
name
}
data
-
index=
{
index
}
display=
"flex"
columnGap=
{
1
}
alignItems=
"center"
onClick=
{
handleItemClick
}
cursor=
"pointer"
>
<
Circle
size=
{
2
}
bgColor=
{
isSelected
?
color
:
'
transparent
'
}
borderWidth=
{
2
}
borderColor=
{
color
}
/>
<
Text
fontSize=
"xs"
>
{
name
}
</
Text
>
</
Box
>
);
})
}
</
Box
>
);
};
export
default
React
.
memo
(
ChartLegend
);
ui/shared/chart/ChartSelectionX.tsx
View file @
8116525c
...
@@ -2,7 +2,7 @@ import { useToken } from '@chakra-ui/react';
...
@@ -2,7 +2,7 @@ import { useToken } from '@chakra-ui/react';
import
*
as
d3
from
'
d3
'
;
import
*
as
d3
from
'
d3
'
;
import
React
from
'
react
'
;
import
React
from
'
react
'
;
import
type
{
TimeChartItem
}
from
'
ui/shared/chart/types
'
;
import
type
{
TimeChart
Data
,
TimeChart
Item
}
from
'
ui/shared/chart/types
'
;
const
SELECTION_THRESHOLD
=
1
;
const
SELECTION_THRESHOLD
=
1
;
...
@@ -10,9 +10,7 @@ interface Props {
...
@@ -10,9 +10,7 @@ interface Props {
height
:
number
;
height
:
number
;
anchorEl
?:
SVGRectElement
|
null
;
anchorEl
?:
SVGRectElement
|
null
;
scale
:
d3
.
ScaleTime
<
number
,
number
>
;
scale
:
d3
.
ScaleTime
<
number
,
number
>
;
data
:
{
data
:
TimeChartData
;
items
:
Array
<
TimeChartItem
>
;
};
onSelect
:
(
range
:
[
number
,
number
])
=>
void
;
onSelect
:
(
range
:
[
number
,
number
])
=>
void
;
}
}
...
@@ -28,8 +26,8 @@ const ChartSelectionX = ({ anchorEl, height, scale, data, onSelect }: Props) =>
...
@@ -28,8 +26,8 @@ const ChartSelectionX = ({ anchorEl, height, scale, data, onSelect }: Props) =>
const
getIndexByX
=
React
.
useCallback
((
x
:
number
)
=>
{
const
getIndexByX
=
React
.
useCallback
((
x
:
number
)
=>
{
const
xDate
=
scale
.
invert
(
x
);
const
xDate
=
scale
.
invert
(
x
);
const
bisectDate
=
d3
.
bisector
<
TimeChartItem
,
unknown
>
((
d
)
=>
d
.
date
).
left
;
const
bisectDate
=
d3
.
bisector
<
TimeChartItem
,
unknown
>
((
d
)
=>
d
.
date
).
left
;
return
bisectDate
(
data
.
items
,
xDate
,
1
);
return
bisectDate
(
data
[
0
]
.
items
,
xDate
,
1
);
},
[
data
.
items
,
scale
]);
},
[
data
,
scale
]);
const
drawSelection
=
React
.
useCallback
((
x0
:
number
,
x1
:
number
)
=>
{
const
drawSelection
=
React
.
useCallback
((
x0
:
number
,
x1
:
number
)
=>
{
const
diffX
=
x1
-
x0
;
const
diffX
=
x1
-
x0
;
...
@@ -99,6 +97,11 @@ const ChartSelectionX = ({ anchorEl, height, scale, data, onSelect }: Props) =>
...
@@ -99,6 +97,11 @@ const ChartSelectionX = ({ anchorEl, height, scale, data, onSelect }: Props) =>
handelMouseUp
();
handelMouseUp
();
}
}
});
});
return
()
=>
{
anchorD3
.
on
(
'
mousedown.selectionX mouseup.selectionX mousemove.selectionX
'
,
null
);
d3
.
select
(
'
body
'
).
on
(
'
mouseup.selectionX
'
,
null
);
};
},
[
anchorEl
,
drawSelection
,
getIndexByX
,
handelMouseUp
]);
},
[
anchorEl
,
drawSelection
,
getIndexByX
,
handelMouseUp
]);
return
(
return
(
...
...
ui/shared/chart/ChartTooltip.tsx
View file @
8116525c
...
@@ -2,15 +2,13 @@ import { useToken, useColorModeValue } from '@chakra-ui/react';
...
@@ -2,15 +2,13 @@ import { useToken, useColorModeValue } from '@chakra-ui/react';
import
*
as
d3
from
'
d3
'
;
import
*
as
d3
from
'
d3
'
;
import
React
from
'
react
'
;
import
React
from
'
react
'
;
import
type
{
TimeChartItem
,
ChartMargin
}
from
'
ui/shared/chart/types
'
;
import
type
{
TimeChartItem
,
ChartMargin
,
TimeChartData
}
from
'
ui/shared/chart/types
'
;
interface
Props
{
interface
Props
{
width
?:
number
;
width
?:
number
;
height
?:
number
;
height
?:
number
;
margin
?:
ChartMargin
;
margin
?:
ChartMargin
;
data
:
{
data
:
TimeChartData
;
items
:
Array
<
TimeChartItem
>
;
};
xScale
:
d3
.
ScaleTime
<
number
,
number
>
;
xScale
:
d3
.
ScaleTime
<
number
,
number
>
;
yScale
:
d3
.
ScaleLinear
<
number
,
number
>
;
yScale
:
d3
.
ScaleLinear
<
number
,
number
>
;
anchorEl
:
SVGRectElement
|
null
;
anchorEl
:
SVGRectElement
|
null
;
...
@@ -60,24 +58,24 @@ const ChartTooltip = ({ xScale, yScale, width, height, data, margin: _margin, an
...
@@ -60,24 +58,24 @@ const ChartTooltip = ({ xScale, yScale, width, height, data, margin: _margin, an
[
xScale
,
margin
,
width
],
[
xScale
,
margin
,
width
],
);
);
const
onChangePosition
=
React
.
useCallback
((
d
:
TimeChartItem
,
isVisible
:
boolean
)
=>
{
const
updateDisplayedValue
=
React
.
useCallback
((
d
:
TimeChartItem
,
i
:
number
)
=>
{
d3
.
select
(
'
.ChartTooltip__value
'
)
d3
.
selectAll
(
'
.ChartTooltip__value
'
)
.
text
(
isVisible
?
d
.
value
.
toLocaleString
()
:
''
);
.
filter
((
td
,
tIndex
)
=>
tIndex
===
i
)
.
text
(
d
.
value
.
toLocaleString
());
},
[]);
},
[]);
const
followPoint
s
=
React
.
useCallback
((
event
:
MouseEvent
)
=>
{
const
drawCircle
s
=
React
.
useCallback
((
event
:
MouseEvent
)
=>
{
const
[
x
]
=
d3
.
pointer
(
event
,
anchorEl
);
const
[
x
]
=
d3
.
pointer
(
event
,
anchorEl
);
const
xDate
=
xScale
.
invert
(
x
);
const
xDate
=
xScale
.
invert
(
x
);
const
bisectDate
=
d3
.
bisector
<
TimeChartItem
,
unknown
>
((
d
)
=>
d
.
date
).
left
;
const
bisectDate
=
d3
.
bisector
<
TimeChartItem
,
unknown
>
((
d
)
=>
d
.
date
).
left
;
let
baseXPos
=
0
;
let
baseXPos
=
0
;
// draw circles on line
d3
.
select
(
ref
.
current
)
d3
.
select
(
ref
.
current
)
.
select
(
'
.ChartTooltip__linePoint
'
)
.
select
All
(
'
.ChartTooltip__linePoint
'
)
.
attr
(
'
transform
'
,
(
cur
,
i
)
=>
{
.
attr
(
'
transform
'
,
(
cur
,
i
)
=>
{
const
index
=
bisectDate
(
data
.
items
,
xDate
,
1
);
const
index
=
bisectDate
(
data
[
i
]
.
items
,
xDate
,
1
);
const
d0
=
data
.
items
[
index
-
1
];
const
d0
=
data
[
i
]
.
items
[
index
-
1
];
const
d1
=
data
.
items
[
index
];
const
d1
=
data
[
i
]
.
items
[
index
];
const
d
=
xDate
.
getTime
()
-
d0
?.
date
.
getTime
()
>
d1
?.
date
.
getTime
()
-
xDate
.
getTime
()
?
d1
:
d0
;
const
d
=
xDate
.
getTime
()
-
d0
?.
date
.
getTime
()
>
d1
?.
date
.
getTime
()
-
xDate
.
getTime
()
?
d1
:
d0
;
if
(
d
.
date
===
undefined
&&
d
.
value
===
undefined
)
{
if
(
d
.
date
===
undefined
&&
d
.
value
===
undefined
)
{
...
@@ -90,30 +88,21 @@ const ChartTooltip = ({ xScale, yScale, width, height, data, margin: _margin, an
...
@@ -90,30 +88,21 @@ const ChartTooltip = ({ xScale, yScale, width, height, data, margin: _margin, an
baseXPos
=
xPos
;
baseXPos
=
xPos
;
}
}
let
isVisible
=
true
;
if
(
xPos
!==
baseXPos
)
{
isVisible
=
false
;
}
const
yPos
=
yScale
(
d
.
value
);
const
yPos
=
yScale
(
d
.
value
);
onChangePosition
(
d
,
isVisible
);
updateDisplayedValue
(
d
,
i
);
return
isVisible
?
return
`translate(
${
xPos
}
,
${
yPos
}
)`
;
`translate(
${
xPos
}
,
${
yPos
}
)`
:
'
translate(-100,-100)
'
;
});
});
return
baseXPos
;
},
[
anchorEl
,
data
,
updateDisplayedValue
,
xScale
,
yScale
]);
const
followPoints
=
React
.
useCallback
((
event
:
MouseEvent
)
=>
{
const
baseXPos
=
drawCircles
(
event
);
drawLine
(
baseXPos
);
drawLine
(
baseXPos
);
drawContent
(
baseXPos
);
drawContent
(
baseXPos
);
},
[
},
[
drawCircles
,
drawLine
,
drawContent
]);
anchorEl
,
drawLine
,
drawContent
,
xScale
,
yScale
,
data
,
onChangePosition
,
]);
React
.
useEffect
(()
=>
{
React
.
useEffect
(()
=>
{
const
anchorD3
=
d3
.
select
(
anchorEl
);
const
anchorD3
=
d3
.
select
(
anchorEl
);
...
@@ -136,7 +125,7 @@ const ChartTooltip = ({ xScale, yScale, width, height, data, margin: _margin, an
...
@@ -136,7 +125,7 @@ const ChartTooltip = ({ xScale, yScale, width, height, data, margin: _margin, an
if
(
!
isPressed
.
current
)
{
if
(
!
isPressed
.
current
)
{
d3
.
select
(
ref
.
current
).
attr
(
'
opacity
'
,
1
);
d3
.
select
(
ref
.
current
).
attr
(
'
opacity
'
,
1
);
d3
.
select
(
ref
.
current
)
d3
.
select
(
ref
.
current
)
.
select
(
'
.ChartTooltip__linePoint
'
)
.
select
All
(
'
.ChartTooltip__linePoint
'
)
.
attr
(
'
opacity
'
,
1
);
.
attr
(
'
opacity
'
,
1
);
followPoints
(
event
);
followPoints
(
event
);
}
}
...
@@ -148,13 +137,18 @@ const ChartTooltip = ({ xScale, yScale, width, height, data, margin: _margin, an
...
@@ -148,13 +137,18 @@ const ChartTooltip = ({ xScale, yScale, width, height, data, margin: _margin, an
isPressed
.
current
=
false
;
isPressed
.
current
=
false
;
}
}
});
});
return
()
=>
{
anchorD3
.
on
(
'
mousedown.tooltip mouseup.tooltip mouseout.tooltip mouseover.tooltip mousemove.tooltip
'
,
null
);
d3
.
select
(
'
body
'
).
on
(
'
mouseup.tooltip
'
,
null
);
};
},
[
anchorEl
,
followPoints
]);
},
[
anchorEl
,
followPoints
]);
return
(
return
(
<
g
ref=
{
ref
}
opacity=
{
0
}
{
...
props
}
>
<
g
ref=
{
ref
}
opacity=
{
0
}
{
...
props
}
>
<
line
className=
"ChartTooltip__line"
stroke=
{
lineColor
}
/>
<
line
className=
"ChartTooltip__line"
stroke=
{
lineColor
}
/>
<
g
className=
"ChartTooltip__content"
>
<
g
className=
"ChartTooltip__content"
>
<
rect
className=
"ChartTooltip__contentBg"
rx=
{
8
}
ry=
{
8
}
fill=
{
bgColor
}
width=
{
125
}
height=
{
52
}
/>
<
rect
className=
"ChartTooltip__contentBg"
rx=
{
8
}
ry=
{
8
}
fill=
{
bgColor
}
width=
{
125
}
height=
{
data
.
length
*
22
+
34
}
/>
<
text
<
text
className=
"ChartTooltip__contentTitle"
className=
"ChartTooltip__contentTitle"
transform=
"translate(8,20)"
transform=
"translate(8,20)"
...
@@ -163,15 +157,24 @@ const ChartTooltip = ({ xScale, yScale, width, height, data, margin: _margin, an
...
@@ -163,15 +157,24 @@ const ChartTooltip = ({ xScale, yScale, width, height, data, margin: _margin, an
fill=
{
textColor
}
fill=
{
textColor
}
pointerEvents=
"none"
pointerEvents=
"none"
/>
/>
<
text
<
g
>
transform=
"translate(8,40)"
{
data
.
map
(({
name
,
color
},
index
)
=>
(
className=
"ChartTooltip__value"
<
g
key=
{
name
}
className=
"ChartTooltip__contentLine"
transform=
{
`translate(12,${ 40 + index * 20 })`
}
>
fontSize=
"12px"
<
circle
r=
{
4
}
fill=
{
color
}
/>
fill=
{
textColor
}
<
text
pointerEvents=
"none"
transform=
"translate(10,4)"
/>
className=
"ChartTooltip__value"
fontSize=
"12px"
fill=
{
textColor
}
pointerEvents=
"none"
/>
</
g
>
))
}
</
g
>
</
g
>
</
g
>
<
circle
className=
"ChartTooltip__linePoint"
r=
{
3
}
opacity=
{
0
}
fill=
{
lineColor
}
/>
{
data
.
map
(({
name
,
color
})
=>
(
<
circle
key=
{
name
}
className=
"ChartTooltip__linePoint"
r=
{
4
}
opacity=
{
0
}
fill=
{
color
}
stroke=
"#FFF"
strokeWidth=
{
1
}
/>
))
}
</
g
>
</
g
>
);
);
};
};
...
...
ui/shared/chart/types.tsx
View file @
8116525c
...
@@ -9,3 +9,11 @@ export interface ChartMargin {
...
@@ -9,3 +9,11 @@ export interface ChartMargin {
bottom
?:
number
;
bottom
?:
number
;
left
?:
number
;
left
?:
number
;
}
}
export
interface
TimeChartDataItem
{
items
:
Array
<
TimeChartItem
>
;
name
:
string
;
color
:
string
;
}
export
type
TimeChartData
=
Array
<
TimeChartDataItem
>
;
ui/shared/chart/useTimeChartController.tsx
View file @
8116525c
import
*
as
d3
from
'
d3
'
;
import
*
as
d3
from
'
d3
'
;
import
{
useMemo
}
from
'
react
'
;
import
{
useMemo
}
from
'
react
'
;
import
type
{
TimeChart
Item
}
from
'
ui/shared/chart/types
'
;
import
type
{
TimeChart
Data
}
from
'
ui/shared/chart/types
'
;
interface
Props
{
interface
Props
{
data
:
{
data
:
TimeChartData
;
items
:
Array
<
TimeChartItem
>
;
};
width
:
number
;
width
:
number
;
height
:
number
;
height
:
number
;
}
}
...
@@ -14,12 +12,12 @@ interface Props {
...
@@ -14,12 +12,12 @@ interface Props {
export
default
function
useTimeChartController
({
data
,
width
,
height
}:
Props
)
{
export
default
function
useTimeChartController
({
data
,
width
,
height
}:
Props
)
{
const
xMin
=
useMemo
(
const
xMin
=
useMemo
(
()
=>
d3
.
min
(
data
.
items
,
({
date
})
=>
date
)
||
new
Date
(),
()
=>
d3
.
min
(
data
,
({
items
})
=>
d3
.
min
(
items
,
({
date
})
=>
date
)
)
||
new
Date
(),
[
data
],
[
data
],
);
);
const
xMax
=
useMemo
(
const
xMax
=
useMemo
(
()
=>
d3
.
max
(
data
.
items
,
({
date
})
=>
date
)
||
new
Date
(),
()
=>
d3
.
max
(
data
,
({
items
})
=>
d3
.
max
(
items
,
({
date
})
=>
date
)
)
||
new
Date
(),
[
data
],
[
data
],
);
);
...
@@ -29,17 +27,17 @@ export default function useTimeChartController({ data, width, height }: Props) {
...
@@ -29,17 +27,17 @@ export default function useTimeChartController({ data, width, height }: Props) {
);
);
const
yMin
=
useMemo
(
const
yMin
=
useMemo
(
()
=>
d3
.
min
(
data
.
items
,
({
value
})
=>
value
)
||
0
,
()
=>
d3
.
min
(
data
,
({
items
})
=>
d3
.
min
(
items
,
({
value
})
=>
value
)
)
||
0
,
[
data
],
[
data
],
);
);
const
yMax
=
useMemo
(
const
yMax
=
useMemo
(
()
=>
d3
.
max
(
data
.
items
,
({
value
})
=>
value
)
||
0
,
()
=>
d3
.
max
(
data
,
({
items
})
=>
d3
.
max
(
items
,
({
value
})
=>
value
)
)
||
0
,
[
data
],
[
data
],
);
);
const
yScale
=
useMemo
(()
=>
{
const
yScale
=
useMemo
(()
=>
{
const
indention
=
(
yMax
-
yMin
)
*
0.
5
;
const
indention
=
(
yMax
-
yMin
)
*
0.
3
;
return
d3
.
scaleLinear
()
return
d3
.
scaleLinear
()
.
domain
([
yMin
>=
0
&&
yMin
-
indention
<=
0
?
0
:
yMin
-
indention
,
yMax
+
indention
])
.
domain
([
yMin
>=
0
&&
yMin
-
indention
<=
0
?
0
:
yMin
-
indention
,
yMax
+
indention
])
.
range
([
height
,
0
]);
.
range
([
height
,
0
]);
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment