1- import { useEffect , useRef , useState } from "react" ;
1+ import { useEffect , useMemo , useRef , useState } from "react" ;
22import * as d3 from "d3" ;
33import { TideExtreme } from "../hooks/useTideData" ;
44import { useContainerDimensions } from "../hooks/useContainerDimensions" ;
@@ -23,14 +23,10 @@ export function TideChart({
2323 units = "m" ,
2424} : TideChartProps ) {
2525 const container = useRef < HTMLDivElement > ( null ) ;
26- const now = useRef < SVGLineElement > ( null ) ;
26+ const nowLine = useRef < SVGLineElement > ( null ) ;
2727 const { height = 0 } = useContainerDimensions ( container ) || { } ;
2828 const [ width , setWidth ] = useState ( 0 )
2929 const gx = useRef < SVGGElement > ( null ) ;
30- const x = useRef < d3 . ScaleTime < number , number , never > > ( null ) ;
31- const y = useRef < d3 . ScaleLinear < number , number , never > > ( null ) ;
32- const area = useRef < d3 . Area < TideExtreme > > ( null ) ;
33- const line = useRef < d3 . Line < TideExtreme > > ( null ) ;
3430 const textPadding = 25 ;
3531 const yPadding = 0.3 ; // meters
3632
@@ -49,39 +45,46 @@ export function TideChart({
4945 } ) . format ( new Date ( value ) ) ;
5046 }
5147
52- useEffect ( ( ) => {
48+ const scales = useMemo ( ( ) => {
49+ if ( ! data . length || ! width || ! height ) return null ;
50+
5351 const [ min = 0 , max = 0 ] = d3 . extent ( data , d => new Date ( d . time ) ) ;
5452
55- x . current = d3 . scaleTime ( )
53+ const xScale = d3 . scaleTime ( )
5654 . domain ( [ min , max ] )
5755 . range ( [ marginLeft , width - marginRight ] ) ;
5856
5957 const [ yMin = 0 , yMax = 0 ] = d3 . extent ( data , d => d . level )
6058 const yPad = ( yMax - yMin ) * .3 ;
6159
62- // Declare the y (vertical position) scale.
63- y . current = d3 . scaleLinear ( )
60+ const yScale = d3 . scaleLinear ( )
6461 . domain ( [ yMin - yPad , yMax + yPad ] )
6562 . range ( [ height - marginBottom , marginTop ] ) ;
6663
67- // Declare the area generator.
68- area . current = d3 . area < TideExtreme > ( )
64+ const areaGen = d3 . area < TideExtreme > ( )
6965 . curve ( d3 . curveMonotoneX )
70- . x ( d => x . current ! ( new Date ( d . time ) ) )
71- . y0 ( y . current ! ( d3 . min ( data , d => d . level - yPadding ) ?? 0 ) )
72- . y1 ( d => y . current ! ( d . level ) ) ;
66+ . x ( d => xScale ( new Date ( d . time ) ) )
67+ . y0 ( yScale ( d3 . min ( data , d => d . level - yPadding ) ?? 0 ) )
68+ . y1 ( d => yScale ( d . level ) ) ;
7369
74- line . current = d3 . line < TideExtreme > ( )
70+ const lineGen = d3 . line < TideExtreme > ( )
7571 . curve ( d3 . curveMonotoneX )
76- . x ( d => x . current ! ( new Date ( d . time ) ) )
77- . y ( d => y . current ! ( d . level ) )
72+ . x ( d => xScale ( new Date ( d . time ) ) )
73+ . y ( d => yScale ( d . level ) )
7874
79- if ( gx . current ) d3 . select ( gx . current ) . call ( d3 . axisBottom ( x . current ) ) ;
75+ return { x : xScale , y : yScale , area : areaGen , line : lineGen } ;
8076 } , [ data , height , width , marginBottom , marginLeft , marginRight , marginTop ] )
8177
78+ // Imperatively render the x-axis via d3
79+ useEffect ( ( ) => {
80+ if ( scales && gx . current ) {
81+ d3 . select ( gx . current ) . call ( d3 . axisBottom ( scales . x ) ) ;
82+ }
83+ } , [ scales ] )
84+
8285 useEffect ( ( ) => {
83- now . current ?. scrollIntoView ( { block : 'center' , inline : 'center' } )
84- } , [ data , now , width ] )
86+ nowLine . current ?. scrollIntoView ( { block : 'center' , inline : 'center' } )
87+ } , [ data , width ] )
8588
8689 return (
8790 < div className = "TideChart" ref = { container } >
@@ -98,18 +101,18 @@ export function TideChart({
98101 className = "TideChart__LowWater"
99102 x1 = { marginLeft }
100103 x2 = { width - marginRight }
101- y1 = { y . current ?. ( 0 ) }
102- y2 = { y . current ?. ( 0 ) }
104+ y1 = { scales ?. y ( 0 ) }
105+ y2 = { scales ?. y ( 0 ) }
103106 />
104107
105- < path fill = "url(#gradient)" d = { area . current ?. ( data ) || '' } />
106- < path className = "TideChart__Line" d = { line . current ?. ( data ) || '' } />
108+ < path fill = "url(#gradient)" d = { scales ?. area ( data ) || '' } />
109+ < path className = "TideChart__Line" d = { scales ?. line ( data ) || '' } />
107110
108111 < line
109- ref = { now }
112+ ref = { nowLine }
110113 className = "TideChart__Now"
111- x1 = { x . current ?. ( new Date ( ) ) }
112- x2 = { x . current ?. ( new Date ( ) ) }
114+ x1 = { scales ?. x ( new Date ( ) ) }
115+ x2 = { scales ?. x ( new Date ( ) ) }
113116 y1 = { marginTop }
114117 y2 = { height - marginTop }
115118 />
@@ -120,8 +123,8 @@ export function TideChart({
120123 < g key = { i } >
121124 < circle
122125 className = "TideChart__DataPoint"
123- cx = { x . current ?. ( new Date ( d . time ) ) }
124- cy = { y . current ?. ( d . level ) }
126+ cx = { scales ?. x ( new Date ( d . time ) ) }
127+ cy = { scales ?. y ( d . level ) }
125128 r = { 5 }
126129 />
127130 {
@@ -130,10 +133,10 @@ export function TideChart({
130133 className = { [ "TideChart__Text" , `TideChart__Text--${ d . label } ` ] . join ( " " ) }
131134 y = { d . label === "High" ? marginTop + textPadding : height - marginBottom - textPadding }
132135 >
133- < tspan className = "TideChart__Depth" x = { x . current ?. ( new Date ( d . time ) ) } dy = { d . label === "High" ? "1.5em" : "-1.5em" } >
136+ < tspan className = "TideChart__Depth" x = { scales ?. x ( new Date ( d . time ) ) } dy = { d . label === "High" ? "1.5em" : "-1.5em" } >
134137 { displayDepth ( d . level ) }
135138 </ tspan >
136- < tspan className = "TideChart__Time" x = { x . current ?. ( new Date ( d . time ) ) } dy = { d . label === "High" ? "-1.5em" : "1.5em" } >
139+ < tspan className = "TideChart__Time" x = { scales ?. x ( new Date ( d . time ) ) } dy = { d . label === "High" ? "-1.5em" : "1.5em" } >
137140 { displayTime ( d . time ) }
138141 </ tspan >
139142 </ text >
0 commit comments