Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
students
survey
Commits
133b2de1
Commit
133b2de1
authored
Jan 03, 2023
by
Yoon, Daeki
😅
Browse files
Merge branch 'chart'
parents
05ae28b3
622eecb4
Changes
29
Show whitespace changes
Inline
Side-by-side
frontend/package.json
View file @
133b2de1
...
...
@@ -12,6 +12,7 @@
"author"
:
"Daeki Yoon"
,
"license"
:
"ISC"
,
"devDependencies"
:
{
"@types/d3"
:
"^7.4.0"
,
"@types/react"
:
"^18.0.14"
,
"@types/react-dom"
:
"^18.0.5"
,
"@types/react-router-dom"
:
"^5.3.3"
,
...
...
@@ -29,7 +30,12 @@
"webpack-dev-server"
:
"^4.9.2"
},
"dependencies"
:
{
"@juggle/resize-observer"
:
"^3.4.0"
,
"axios"
:
"^0.27.2"
,
"d3"
:
"^7.8.0"
,
"d3-axis"
:
"^3.0.0"
,
"d3-scale"
:
"^4.0.2"
,
"d3-shape"
:
"^3.2.0"
,
"react"
:
"^18.2.0"
,
"react-dom"
:
"^18.2.0"
,
"react-router-dom"
:
"^6.3.0"
...
...
frontend/src/charts/axes/Axis.tsx
0 → 100644
View file @
133b2de1
import
React
from
"
react
"
;
import
type
{
AxisScale
,
AxisDomain
}
from
"
d3-axis
"
;
function
identity
(
x
:
any
)
{
return
x
;
}
/**
* Instead of a component for each orientation (like AxisLeft, AxisRight),
* we provide a value from this Orient object. Provide a value, like
* Orient.left, to the `orient` prop of the Axis component
* to place the axis on the left.
*/
export
enum
Orient
{
top
=
1
,
right
=
2
,
bottom
=
3
,
left
=
4
,
}
function
translateX
(
x
:
number
)
{
return
"
translate(
"
+
x
+
"
,0)
"
;
}
function
translateY
(
y
:
number
)
{
return
"
translate(0,
"
+
y
+
"
)
"
;
}
/**
* The axis component. This renders an axis, within a
* `g` element, for use in a chart.
*/
export
const
Axis
=
<
Domain
extends
AxisDomain
>
(
{
scale
,
ticks
,
tickArguments
=
[],
tickValues
=
null
,
tickFormat
=
null
,
tickSize
,
tickSizeInner
=
6
,
tickSizeOuter
=
6
,
tickPadding
=
3
,
tickTextProps
=
{},
tickLineProps
=
{},
domainPathProps
=
{},
orient
=
Orient
.
bottom
,
offset
=
typeof
window
!==
"
undefined
"
&&
window
.
devicePixelRatio
>
1
?
0
:
0.5
,
}
:
{
/** An initialized d3 scale object, like a d3.linearScale */
scale
:
AxisScale
<
Domain
>
;
ticks
?:
any
[];
tickArguments
?:
any
[];
tickValues
?:
any
[]
|
null
;
tickFormat
?:
any
;
tickSize
?:
number
;
tickSizeInner
?:
number
;
tickSizeOuter
?:
number
;
tickPadding
?:
number
;
/** Additional attributes to add to tick text elements, or null to omit */
tickTextProps
?:
React
.
SVGProps
<
SVGTextElement
>
|
null
;
/** Additional attributes to add to tick line elements, or null to omit */
tickLineProps
?:
React
.
SVGProps
<
SVGLineElement
>
|
null
;
/** Additional attributes to the domain path, or null to omit */
domainPathProps
?:
React
.
SVGProps
<
SVGPathElement
>
|
null
;
offset
?:
number
;
orient
?:
Orient
;
}
) =>
{
if
(
tickSize
)
{
tickSizeInner
=
tickSize
;
tickSizeOuter
=
tickSize
;
}
if
(
ticks
)
{
tickArguments
=
ticks
;
}
function
number
(
scale
:
AxisScale
<
Domain
>
)
{
return
(
d
:
any
)
=>
{
const
value
=
scale
(
d
);
return
value
===
undefined
?
0
:
+
value
;
};
}
function
center
(
scale
:
AxisScale
<
Domain
>
,
offset
:
number
)
{
if
(
scale
.
bandwidth
)
{
offset
=
Math
.
max
(
0
,
scale
.
bandwidth
()
-
offset
*
2
)
/
2
;
}
if
((
scale
as
any
).
round
())
offset
=
Math
.
round
(
offset
);
return
(
d
:
Domain
)
=>
{
const
value
=
scale
(
d
);
return
value
===
undefined
?
0
:
value
+
offset
;
};
}
const k = orient === Orient.top || orient === Orient.left ? -1 : 1,
x = orient === Orient.left || orient === Orient.right ? "x" : "y",
transform =
orient === Orient.top || orient === Orient.bottom
? translateX
: translateY;
// Rendering
const values =
tickValues == null
? (scale as any).ticks
? (scale as any).ticks.apply(scale, tickArguments)
: scale.domain()
: tickValues,
format =
tickFormat == null
? "tickFormat" in scale
? (scale as any).tickFormat.apply(scale, tickArguments)
: identity
: tickFormat,
spacing = Math.max(tickSizeInner, 0) + tickPadding,
range = scale.range(),
range0 = +range[0] + offset,
range1 = +range[range.length - 1] + offset,
position = (scale.bandwidth ? center : number)(scale.copy(), offset);
const domainPath =
orient === Orient.left || orient === Orient.right
? tickSizeOuter
? "M" +
k * tickSizeOuter +
"," +
range0 +
"H" +
offset +
"V" +
range1 +
"H" +
k * tickSizeOuter
: "M" + offset + "," + range0 + "V" + range1
: tickSizeOuter
? "M" +
range0 +
"," +
k * tickSizeOuter +
"V" +
offset +
"H" +
range1 +
"V" +
k * tickSizeOuter
: "M" + range0 + "," + offset + "H" + range1;
const lineProps =
{
[
x
+
"
2
"
]:
k
*
tickSizeInner
,
}
;
const textProps =
{
[
x
]:
k
*
spacing
,
}
;
return (
<
g
>
{
values
.
map
((
tick
:
any
,
i
:
number
)
=>
(
<
g
className
=
"tick"
key
=
{
i
}
transform
=
{
transform
(
position
(
tick
)
+
offset
)
}
>
{
tickLineProps
&&
(
<
line
stroke
=
"currentColor"
{
...
lineProps
}
{
...
tickLineProps
}
/>
)
}
{
tickTextProps
&&
(
<
text
fill
=
"currentColor"
dy
=
{
orient
===
Orient
.
top
?
"
0em
"
:
orient
===
Orient
.
bottom
?
"
0.71em
"
:
"
0.32em
"
}
fontSize
=
"10"
fontFamily
=
"sans-serif"
textAnchor
=
{
orient
===
Orient
.
right
?
"
start
"
:
orient
===
Orient
.
left
?
"
end
"
:
"
middle
"
}
{
...
textProps
}
{
...
tickTextProps
}
>
{
format
(
tick
)
}
</
text
>
)
}
</
g
>
))
}
{
domainPathProps
&&
(
<
path
className
=
"domain"
stroke
=
"currentColor"
fill
=
"transparent"
d
=
{
domainPath
}
{
...
domainPathProps
}
/>
)
}
</
g
>
);
};
frontend/src/charts/axes/AxisScaleLinear.tsx
0 → 100644
View file @
133b2de1
import
React
,
{
useMemo
}
from
"
react
"
;
import
{
ScaleLinear
,
scaleLinear
}
from
"
d3-scale
"
;
type
Props
=
{
x
:
number
;
y
:
number
;
scale
:
ScaleLinear
<
number
,
number
>
;
};
export
const
AxisScaleLinear
=
({
x
=
0
,
y
=
0
,
scale
}:
Props
)
=>
{
const
orient
=
"
bottom
"
;
const
ticks
=
useMemo
(
()
=>
scale
.
ticks
().
map
((
value
)
=>
({
value
,
xOffset
:
scale
(
value
),
})),
[]
);
// console.log("ticks:", ticks);
const
range
=
scale
.
range
();
return
(
<
g
fontSize
=
"10px"
textAnchor
=
"middle"
transform
=
{
`translate(
${
x
}
,
${
y
}
)`
}
>
<
path
d
=
{
`M
${
range
[
0
]}
0.5 H
${
range
[
1
]}
`
}
stroke
=
"currentColor"
/>
{
ticks
.
map
(({
value
,
xOffset
})
=>
(
<
g
key
=
{
value
}
transform
=
{
`translate(
${
xOffset
}
, 0)`
}
>
<
line
y2
=
{
6
}
stroke
=
"currentColor"
/>
<
text
key
=
{
value
}
dy
=
"1.5em"
>
{
value
}
</
text
>
</
g
>
))
}
</
g
>
);
};
frontend/src/charts/axes/AxisV0.tsx
0 → 100644
View file @
133b2de1
import
React
,
{
useMemo
}
from
"
react
"
;
import
{
scaleLinear
}
from
"
d3-scale
"
;
export
const
AxisV0
=
()
=>
{
const
ticks
=
useMemo
(()
=>
{
const
xScale
=
scaleLinear
().
domain
([
0
,
100
]).
range
([
10
,
290
]);
return
xScale
.
ticks
().
map
((
value
)
=>
({
value
,
xOffset
:
xScale
(
value
),
}));
},
[]);
return
(
<
svg
>
<
path
d
=
"M 9.5 0.5 H 290.5"
stroke
=
"currentColor"
/>
{
ticks
.
map
(({
value
,
xOffset
})
=>
(
<
g
key
=
{
value
}
transform
=
{
`translate(
${
xOffset
}
, 0)`
}
>
<
line
y2
=
{
6
}
stroke
=
"currentColor"
/>
<
text
key
=
{
value
}
style
=
{
{
fontSize
:
"
10px
"
,
textAnchor
:
"
middle
"
,
transform
:
"
translateY(20px)
"
,
}
}
>
{
value
}
</
text
>
</
g
>
))
}
</
svg
>
);
};
frontend/src/charts/axes/AxisVerticalScaleLinear.tsx
0 → 100644
View file @
133b2de1
import
React
,
{
useMemo
}
from
"
react
"
;
import
{
ScaleLinear
,
scaleLinear
}
from
"
d3-scale
"
;
type
Props
=
{
x
:
number
;
y
:
number
;
scale
:
ScaleLinear
<
number
,
number
>
;
};
export
const
AxisVerticalScaleLinear
=
({
x
=
0
,
y
=
0
,
scale
}:
Props
)
=>
{
const
orient
=
"
left
"
;
const
ticks
=
useMemo
(
()
=>
scale
.
ticks
().
map
((
value
)
=>
({
value
,
yOffset
:
scale
(
value
),
})),
[]
);
// console.log("ticks:", ticks);
const
range
=
scale
.
range
();
return
(
<
g
fontSize
=
"10px"
textAnchor
=
"middle"
transform
=
{
`translate(
${
x
}
,
${
y
}
)`
}
>
<
path
d
=
{
`M 0.5
${
range
[
0
]}
V
${
range
[
1
]}
`
}
stroke
=
"currentColor"
/>
{
ticks
.
map
(({
value
,
yOffset
})
=>
(
<
g
key
=
{
value
}
transform
=
{
`translate(0,
${
yOffset
}
)`
}
>
<
line
x2
=
{
-
6
}
stroke
=
"currentColor"
/>
<
text
key
=
{
value
}
dx
=
"-1.5em"
dy
=
{
"
0.3em
"
}
>
{
value
}
</
text
>
</
g
>
))
}
</
g
>
);
};
frontend/src/charts/axes/BottomAxis.tsx
0 → 100644
View file @
133b2de1
import
React
from
"
react
"
;
import
{
axisBottom
}
from
"
d3-axis
"
;
import
{
scaleLinear
}
from
"
d3-scale
"
;
export
const
BottomAxis
=
()
=>
{
let
scale
=
scaleLinear
().
domain
([
0
,
100
]).
range
([
0
,
500
]);
let
axis
=
axisBottom
(
scale
);
console
.
log
(
"
axis:
"
,
axis
);
return
(
<
div
>
<
h1
>
BottomAxis
</
h1
>
<
svg
width
=
"600"
height
=
"100"
>
<
g
transform
=
"translate(20, 50)"
></
g
>
</
svg
>
</
div
>
);
};
frontend/src/charts/axes/index.tsx
0 → 100644
View file @
133b2de1
export
{
AxisScaleLinear
}
from
"
./AxisScaleLinear
"
;
export
{
AxisVerticalScaleLinear
}
from
"
./AxisVerticalScaleLinear
"
;
export
{
Axis
,
Orient
}
from
"
./Axis
"
;
frontend/src/charts/bars/Bar.tsx
0 → 100644
View file @
133b2de1
import
{
ScaleBand
,
ScaleLinear
}
from
"
d3-scale
"
;
import
React
from
"
react
"
;
type
Props
=
{
dataset
:
number
[];
height
:
number
;
xScale
:
ScaleBand
<
number
>
;
yScale
:
ScaleLinear
<
number
,
number
>
;
};
export
const
Bar
=
({
dataset
,
height
,
xScale
,
yScale
}:
Props
)
=>
{
// console.log("dataset:", dataset);
return
(
<
g
>
{
dataset
.
map
((
d
,
i
)
=>
{
console
.
log
(
"
d
"
,
d
,
"
i
"
,
i
,
"
x:
"
,
xScale
(
i
),
"
y
"
,
yScale
(
d
));
return
(
<
rect
key
=
{
i
}
x
=
{
xScale
(
i
)
}
y
=
{
height
-
yScale
(
d
)
}
width
=
{
xScale
.
bandwidth
()
}
height
=
{
yScale
(
d
)
}
fill
=
{
`rgb(0, 0,
${
Math
.
round
(
d
*
10
)}
)`
}
></
rect
>
);
})
}
</
g
>
);
};
frontend/src/charts/bars/BarChart.tsx
0 → 100644
View file @
133b2de1
import
React
from
"
react
"
;
import
{
max
,
min
,
range
}
from
"
d3
"
;
import
{
scaleLinear
,
scaleBand
}
from
"
d3-scale
"
;
import
{
BarText
}
from
"
../texts
"
;
import
{
Bar
}
from
"
./Bar
"
;
type
Props
=
{
dataset
:
number
[];
dimensions
:
DOMRect
|
undefined
;
};
export
const
BarChart
=
({
dataset
,
dimensions
}:
Props
)
=>
{
const
margin
=
{
top
:
0
,
right
:
0
,
bottom
:
0
,
left
:
0
};
const
width
=
dimensions
?.
width
||
600
;
const
height
=
dimensions
?.
height
||
300
;
const
innerWidth
=
width
-
margin
.
left
-
margin
.
right
;
const
innerHeight
=
height
-
margin
.
bottom
-
margin
.
top
;
console
.
log
(
"
width:
"
,
width
,
"
height:
"
,
height
);
const
xScale
=
scaleBand
<
number
>
()
.
domain
(
range
(
dataset
.
length
))
.
rangeRound
([
0
,
innerWidth
])
.
paddingInner
(
0.05
);
const
yScale
=
scaleLinear
()
.
domain
([
0
,
max
(
dataset
)
||
0
])
.
rangeRound
([
0
,
innerHeight
]);
return
(
<
div
>
<
svg
width
=
{
width
}
height
=
{
height
}
// style={{ borderWidth: "2px", borderColor: "black" }}
>
<
g
transform
=
{
`translate(
${
margin
.
left
}
,
${
margin
.
top
}
)`
}
>
<
Bar
dataset
=
{
dataset
}
xScale
=
{
xScale
}
yScale
=
{
yScale
}
height
=
{
innerHeight
}
/>
<
BarText
dataset
=
{
dataset
}
xScale
=
{
xScale
}
yScale
=
{
yScale
}
height
=
{
innerHeight
}
/>
</
g
>
</
svg
>
</
div
>
);
};
frontend/src/charts/bars/BarChartWithAnimation.tsx
0 → 100644
View file @
133b2de1
import
React
from
"
react
"
;
import
{
max
,
min
,
range
}
from
"
d3
"
;
import
{
scaleLinear
,
scaleBand
}
from
"
d3-scale
"
;
import
{
BarText
}
from
"
../texts
"
;
import
{
BarWithAnimation
}
from
"
./BarWithAnimation
"
;
type
Props
=
{
dataset
:
number
[];
};
export
const
BarChartWithAnimation
=
({
dataset
}:
Props
)
=>
{
const
margin
=
{
top
:
20
,
right
:
30
,
bottom
:
20
,
left
:
30
};
const
width
=
window
.
innerWidth
-
margin
.
left
-
margin
.
right
;
const
height
=
300
-
margin
.
bottom
-
margin
.
top
;
const
xScale
=
scaleBand
<
number
>
()
.
domain
(
range
(
dataset
.
length
))
.
rangeRound
([
0
,
width
])
.
paddingInner
(
0.05
);
const
yScale
=
scaleLinear
()
.
domain
([
0
,
max
(
dataset
)
||
0
])
.
rangeRound
([
0
,
height
]);
return
(
<
svg
width
=
{
width
+
margin
.
left
+
margin
.
right
}
height
=
{
height
+
margin
.
bottom
+
margin
.
top
}
style
=
{
{
borderWidth
:
"
2px
"
,
borderColor
:
"
black
"
}
}
>
<
BarWithAnimation
dataset
=
{
dataset
}
xScale
=
{
xScale
}
yScale
=
{
yScale
}
height
=
{
height
}
/>
<
BarText
dataset
=
{
dataset
}
xScale
=
{
xScale
}
yScale
=
{
yScale
}
height
=
{
height
}
/>
</
svg
>
);
};
frontend/src/charts/bars/BarWithAnimation.tsx
0 → 100644
View file @
133b2de1
import
{
ScaleBand
,
ScaleLinear
}
from
"
d3-scale
"
;
import
React
,
{
Fragment
,
useEffect
,
useRef
}
from
"
react
"
;
type
Props
=
{
dataset
:
number
[];
height
:
number
;
xScale
:
ScaleBand
<
number
>
;
yScale
:
ScaleLinear
<
number
,
number
>
;
};
export
const
BarWithAnimation
=
({
dataset
,
height
,
xScale
,
yScale
,
}:
Props
)
=>
{
// console.log("dataset:", dataset);
const
preDataset
=
useRef
(
dataset
);
useEffect
(()
=>
{
preDataset
.
current
=
dataset
;
},
[
dataset
]);
const
getStyle
=
(
i
:
number
)
=>
{
const
style
=
{
// fill: "red",
animationName
:
`inmoveleftright-
${
i
}
`
,
animationDuration
:
"
1s
"
,
// animationDirection: "alternate",
animationIterationCount
:
"
1
"
,
// transformOrigin: "bottom",
};
return
style
;
};
return
(
<
g
transform
=
{
`scale(1, -1) translate(0,
${
-
height
}
)`
}
>
{
dataset
.
map
((
d
,
i
)
=>
{
// console.log("d", d, "i", i, "x:", xScale(i), "y", yScale(d));
return
(
<
Fragment
key
=
{
Math
.
random
()
}
>
<
style
>
{
`
@keyframes inmoveleftright-
${
i
}
{
0% {
height:
${
yScale
(
preDataset
.
current
[
i
])}
px;
}
100% {
height:
${
yScale
(
d
)}
px;
}
}
`
}
</
style
>
<
rect
x
=
{
xScale
(
i
)
}
y
=
{
0
}
width
=
{
xScale
.
bandwidth
()
}
height
=
{
yScale
(
d
)
}
fill
=
{
`rgb(0, 0,
${
Math
.
round
(
d
*
10
)}
)`
}
style
=
{
getStyle
(
i
)
}
></
rect
>
</
Fragment
>
);
})
}
</
g
>
);
};
frontend/src/charts/bars/index.tsx
0 → 100644
View file @
133b2de1
export
{
Bar
}
from
"
./Bar
"
;
export
{
BarChart
}
from
"
./BarChart
"
;
frontend/src/charts/hooks/index.ts
0 → 100644
View file @
133b2de1
export
{
useSize
}
from
"
./useSize
"
;
frontend/src/charts/hooks/useChartDimensions.tsx
0 → 100644
View file @
133b2de1
/**
* 출처: https://github.com/73nko/advanced-d3/blob/master/13-using-d3-with-react-js/src/completed/Chart/utils.js
*/
import
{
useEffect
,
useState
,
useRef
}
from
"
react
"
;
export
const
combineChartDimensions
=
(
dimensions
:
any
)
=>
{
let
parsedDimensions
=
{
marginTop
:
40
,
marginRight
:
30
,
marginBottom
:
40
,
marginLeft
:
75
,
...
dimensions
,
};
return
{
...
parsedDimensions
,
boundedHeight
:
Math
.
max
(
parsedDimensions
.
height
-
parsedDimensions
.
marginTop
-
parsedDimensions
.
marginBottom
,
0
),
boundedWidth
:
Math
.
max
(
parsedDimensions
.
width
-
parsedDimensions
.
marginLeft
-
parsedDimensions
.
marginRight
,
0
),
};
};
export
const
useChartDimensions
=
(
passedSettings
:
any
)
=>
{
const
ref
=
useRef
<
HTMLElement
>
();
const
dimensions
=
combineChartDimensions
(
passedSettings
);
const
[
width
,
changeWidth
]
=
useState
(
0
);
const
[
height
,
changeHeight
]
=
useState
(
0
);
useEffect
(()
=>
{
if
(
dimensions
.
width
&&
dimensions
.
height
)
return
;
const
element
=
ref
.
current
;
const
resizeObserver
=
new
ResizeObserver
((
entries
)
=>
{
if
(
!
Array
.
isArray
(
entries
))
return
;
if
(
!
entries
.
length
)
return
;
const
entry
=
entries
[
0
];
if
(
width
!==
entry
.
contentRect
.
width
)
changeWidth
(
entry
.
contentRect
.
width
);
if
(
height
!==
entry
.
contentRect
.
height
)
changeHeight
(
entry
.
contentRect
.
height
);
});
element
&&
resizeObserver
.
observe
(
element
);
return
()
=>
element
&&
resizeObserver
.
unobserve
(
element
);
},
[
passedSettings
,
height
,
width
,
dimensions
]);
const
newSettings
=
combineChartDimensions
({
...
dimensions
,
width
:
dimensions
.
width
||
width
,
height
:
dimensions
.
height
||
height
,
});
return
[
ref
,
newSettings
];
};
frontend/src/charts/hooks/useLatest.tsx
0 → 100644
View file @
133b2de1
/**
* 출처: https://github.com/jaredLunde/react-hook
*/
import
{
useRef
,
useEffect
}
from
"
react
"
;
const
useLatest
=
<
T
extends
any
>
(current: T) =>
{
const
storedValue
=
useRef
(
current
);
useEffect
(()
=>
{
storedValue
.
current
=
current
;
});
return
storedValue
;
}
;
export default useLatest;
frontend/src/charts/hooks/usePassiveLayoutEffect.tsx
0 → 100644
View file @
133b2de1
/**
* 출처: https://github.com/jaredLunde/react-hook
*/
import
React
from
"
react
"
;
const
usePassiveLayoutEffect
=
React
[
typeof
document
!==
"
undefined
"
&&
document
.
createElement
!==
void
0
?
"
useLayoutEffect
"
:
"
useEffect
"
];
export
default
usePassiveLayoutEffect
;
frontend/src/charts/hooks/useResizeObserver.tsx
0 → 100644
View file @
133b2de1
/**
* 출처: https://github.com/jaredLunde/react-hook
*/
import
{
ResizeObserver
as
Polyfill
,
ResizeObserverEntry
,
}
from
"
@juggle/resize-observer
"
;
import
useLayoutEffect
from
"
./usePassiveLayoutEffect
"
;
import
useLatest
from
"
./useLatest
"
;
// 브라우저 ResizeObserver를 사용할 건지 폴리필을 사용할 건지 결정
const
ResizeObserver
=
typeof
window
!==
"
undefined
"
&&
"
ResizeObserver
"
in
window
?
// @ts-ignore
window
.
ResizeObserver
:
Polyfill
;
/**
* A React hook that fires a callback whenever ResizeObserver detects a change to its size
*
* @param target A React ref created by `useRef()` or an HTML element
* @param callback Invoked with a single `ResizeObserverEntry` any time
* the `target` resizes
*/
function
useResizeObserver
<
T
extends
HTMLElement
>
(
target
:
React
.
RefObject
<
T
>
|
T
|
null
,
callback
:
UseResizeObserverCallback
):
Polyfill
{
const
resizeObserver
=
getResizeObserver
();
const
storedCallback
=
useLatest
(
callback
);
useLayoutEffect
(()
=>
{
let
didUnsubscribe
=
false
;
const
targetEl
=
target
&&
"
current
"
in
target
?
target
.
current
:
target
;
if
(
!
targetEl
)
return
()
=>
{};
function
cb
(
entry
:
ResizeObserverEntry
,
observer
:
Polyfill
)
{
if
(
didUnsubscribe
)
return
;
storedCallback
.
current
(
entry
,
observer
);
}
resizeObserver
.
subscribe
(
targetEl
as
HTMLElement
,
cb
);
return
()
=>
{
didUnsubscribe
=
true
;
resizeObserver
.
unsubscribe
(
targetEl
as
HTMLElement
,
cb
);
};
},
[
target
,
resizeObserver
,
storedCallback
]);
return
resizeObserver
.
observer
;
}
function
createResizeObserver
()
{
let
ticking
=
false
;
let
allEntries
:
ResizeObserverEntry
[]
=
[];
const
callbacks
:
Map
<
any
,
Array
<
UseResizeObserverCallback
>>
=
new
Map
();
const
observer
=
new
ResizeObserver
(
(
entries
:
ResizeObserverEntry
[],
obs
:
Polyfill
)
=>
{
allEntries
=
allEntries
.
concat
(
entries
);
if
(
!
ticking
)
{
window
.
requestAnimationFrame
(()
=>
{
const
triggered
=
new
Set
<
Element
>
();
for
(
let
i
=
0
;
i
<
allEntries
.
length
;
i
++
)
{
if
(
triggered
.
has
(
allEntries
[
i
].
target
))
continue
;
triggered
.
add
(
allEntries
[
i
].
target
);
const
cbs
=
callbacks
.
get
(
allEntries
[
i
].
target
);
cbs
?.
forEach
((
cb
)
=>
cb
(
allEntries
[
i
],
obs
));
}
allEntries
=
[];
ticking
=
false
;
});
}
ticking
=
true
;
}
);
return
{
observer
,
subscribe
(
target
:
HTMLElement
,
callback
:
UseResizeObserverCallback
)
{
observer
.
observe
(
target
);
const
cbs
=
callbacks
.
get
(
target
)
??
[];
cbs
.
push
(
callback
);
callbacks
.
set
(
target
,
cbs
);
},
unsubscribe
(
target
:
HTMLElement
,
callback
:
UseResizeObserverCallback
)
{
const
cbs
=
callbacks
.
get
(
target
)
??
[];
if
(
cbs
.
length
===
1
)
{
observer
.
unobserve
(
target
);
callbacks
.
delete
(
target
);
return
;
}
const
cbIndex
=
cbs
.
indexOf
(
callback
);
if
(
cbIndex
!==
-
1
)
cbs
.
splice
(
cbIndex
,
1
);
callbacks
.
set
(
target
,
cbs
);
},
};
}
let
_resizeObserver
:
ReturnType
<
typeof
createResizeObserver
>
;
const
getResizeObserver
=
()
=>
!
_resizeObserver
?
(
_resizeObserver
=
createResizeObserver
())
:
_resizeObserver
;
export
type
UseResizeObserverCallback
=
(
entry
:
ResizeObserverEntry
,
observer
:
Polyfill
)
=>
any
;
export
default
useResizeObserver
;
frontend/src/charts/hooks/useSize.tsx
0 → 100644
View file @
133b2de1
import
React
from
"
react
"
;
import
useResizeObserver
from
"
./useResizeObserver
"
;
export
const
useSize
=
(
target
:
React
.
RefObject
<
HTMLElement
>
)
=>
{
const
[
size
,
setSize
]
=
React
.
useState
<
DOMRectReadOnly
>
();
React
.
useLayoutEffect
(()
=>
{
setSize
(
target
.
current
?.
getBoundingClientRect
());
},
[
target
]);
// Where the magic happens
useResizeObserver
(
target
,
(
entry
)
=>
setSize
(
entry
.
contentRect
));
return
size
;
};
frontend/src/charts/pies/Arc.tsx
0 → 100644
View file @
133b2de1
import
React
from
"
react
"
;
import
{
arc
,
DefaultArcObject
,
PieArcDatum
}
from
"
d3-shape
"
;
// import { DefaultArcObject } from "d3";
interface
Props
{
arcData
:
DefaultArcObject
&
PieArcDatum
<
any
>
;
label
?:
string
;
[
rest
:
string
]:
any
;
}
export
const
Arc
=
({
arcData
,
label
,
...
rest
}:
Props
)
=>
{
console
.
log
(
"
rest:
"
,
rest
);
const
arcGenerator
=
arc
();
arcGenerator
.
cornerRadius
(
5.2
);
const
centroid
=
arcGenerator
.
centroid
({
...
arcData
,
// startAngle: arcData.startAngle,
// endAngle: arcData.endAngle,
});
const
pathData
=
arcGenerator
(
arcData
);
// console.log("arcdata:", arcData, "path data:", pathData);
return
(
<>
<
path
d
=
{
pathData
??
""
}
{
...
rest
}
></
path
>
{
label
&&
(
<
text
x
=
{
centroid
[
0
]
}
y
=
{
centroid
[
1
]
}
dy
=
{
"
0.33em
"
}
>
{
label
}
</
text
>
)
}
</>
);
};
frontend/src/charts/pies/Pie.tsx
0 → 100644
View file @
133b2de1
import
{
scaleLinear
}
from
"
d3-scale
"
;
import
{
arc
,
pie
}
from
"
d3-shape
"
;
import
React
from
"
react
"
;
import
{
Arc
}
from
"
./Arc
"
;
type
Props
=
{
data
:
any
;
};
export
const
Pie
=
({
data
}:
Props
)
=>
{
const
width
=
300
;
const
height
=
300
;
const
pieGenerator
=
pie
();
const
arcs
=
pieGenerator
.
padAngle
(
0.1
)(
data
);
console
.
log
(
arcs
);
return
(
<
div
>
<
h1
>
Pie
</
h1
>
<
svg
width
=
{
width
}
height
=
{
height
}
>
<
g
transform
=
{
`translate(
${
width
/
2
}
,
${
height
/
2
}
)`
}
textAnchor
=
"middle"
stroke
=
"white"
>
{
arcs
.
map
((
arc
,
i
)
=>
(
<
Arc
key
=
{
i
}
arcData
=
{
{
...
arc
,
innerRadius
:
0
,
outerRadius
:
100
}
}
label
=
{
String
(
arc
.
value
)
}
fill
=
{
"
blue
"
}
stroke
=
{
"
red
"
}
strokeWidth
=
{
2
}
/>
))
}
</
g
>
</
svg
>
</
div
>
);
};
Prev
1
2
Next
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