@@ -23,7 +23,13 @@ const getChartData = () => {
2323 const trip = gameState .value .trip
2424 if (! trip || trip .length === 0 ) return { blocks: [], totalTravel: 0 , totalWait: 0 }
2525
26- const blocks: Array <{ type: string , start: number , end: number , duration: number , label: string }> = []
26+ const blocks: Array <{
27+ type: string
28+ start: number
29+ end: number
30+ duration: number
31+ label: string
32+ }> = []
2733 let totalTravel = 0
2834 let totalWait = 0
2935
@@ -34,55 +40,50 @@ const getChartData = () => {
3440 lastAbsSec = rawSec
3541 return rawSec
3642 }
37-
43+
3844 let candidate = rawSec
39- while (candidate < lastAbsSec - 1800 ) {
40- candidate += 86400
45+ while (candidate < lastAbsSec - 1800 ) {
46+ candidate += 86400
4147 }
42-
48+
4349 lastAbsSec = candidate
4450 return candidate
4551 }
4652
4753 let previousArrivalSec = - 1
4854
4955 for (let i = 0 ; i < trip .length ; i ++ ) {
50- const segment = trip [i ]
51- const startStop = segment !! .details .stops .find (s => s .stop_id === segment !! .start_station .stop_id )
52- const endStop = segment !! .details .stops .find (s => s .stop_id === segment !! .end_station .stop_id )
53-
54- if (startStop && endStop ) {
55- const depSec = getAbsoluteSec (startStop .departure_time )
56- const arrSec = getAbsoluteSec (endStop .arrival_time )
57-
58- if (previousArrivalSec !== - 1 ) {
59- const waitDuration = depSec - previousArrivalSec
60- if (waitDuration > 0 ) {
61- totalWait += waitDuration
62- blocks .push ({
63- type: ' wait' ,
64- start: previousArrivalSec ,
65- end: depSec ,
66- duration: waitDuration ,
67- label: ` Waiting at ${segment !! .start_station .stop_name } `
68- })
69- }
70- }
71-
72- const travelDuration = arrSec - depSec
73- if (travelDuration > 0 ) {
74- totalTravel += travelDuration
56+ const segment = trip [i ]!!
57+ const depSec = getAbsoluteSec (segment .departure_time )
58+ const arrSec = getAbsoluteSec (segment .arrival_time )
59+
60+ if (previousArrivalSec !== - 1 ) {
61+ const waitDuration = depSec - previousArrivalSec
62+ if (waitDuration > 0 ) {
63+ totalWait += waitDuration
7564 blocks .push ({
76- type: ' travel ' ,
77- start: depSec ,
78- end: arrSec ,
79- duration: travelDuration ,
80- label: ` ${ segment !! . details . route_short_name } to ${segment !! . end_station .stop_name }`
65+ type: ' wait ' ,
66+ start: previousArrivalSec ,
67+ end: depSec ,
68+ duration: waitDuration ,
69+ label: ` Waiting at ${segment . start_station .stop_name }` ,
8170 })
8271 }
83-
84- previousArrivalSec = arrSec
8572 }
73+
74+ const travelDuration = arrSec - depSec
75+ if (travelDuration > 0 ) {
76+ totalTravel += travelDuration
77+ blocks .push ({
78+ type: ' travel' ,
79+ start: depSec ,
80+ end: arrSec ,
81+ duration: travelDuration ,
82+ label: ` ${segment .details .route_short_name } to ${segment .end_station .stop_name } ` ,
83+ })
84+ }
85+
86+ previousArrivalSec = arrSec
8687 }
8788
8889 return { blocks , totalTravel , totalWait }
@@ -96,112 +97,123 @@ const drawChart = () => {
9697 const height = 80
9798 const margin = { top: 10 , right: 20 , bottom: 30 , left: 20 }
9899
99- const svg = d3 .select (svgRef .value )
100- .attr (" viewBox" , ` 0 0 ${width } ${height } ` )
101- .style (" width" , " 100%" )
102- .style (" height" , " auto" )
100+ const svg = d3
101+ .select (svgRef .value )
102+ .attr (' viewBox' , ` 0 0 ${width } ${height } ` )
103+ .style (' width' , ' 100%' )
104+ .style (' height' , ' auto' )
103105
104- svg .selectAll (" * " ).remove ()
106+ svg .selectAll (' * ' ).remove ()
105107 const tooltip = d3 .select (tooltipRef .value )
106108
107109 const minTime = blocks [0 ]!! .start
108110 const maxTime = blocks [blocks .length - 1 ]!! .end
109111
110- const x = d3 .scaleLinear ()
112+ const x = d3
113+ .scaleLinear ()
111114 .domain ([minTime , maxTime ])
112115 .range ([margin .left , width - margin .right ])
113116
114- svg .selectAll (" rect" )
117+ svg
118+ .selectAll (' rect' )
115119 .data (blocks )
116120 .enter ()
117- .append (" rect" )
118- .attr (" x " , d => x (d .start ))
119- .attr (" y " , margin .top )
120- .attr (" width" , d => Math .max (2 , x (d .end ) - x (d .start )))
121- .attr (" height" , height - margin .top - margin .bottom )
122- .attr (" rx " , 4 )
123- .attr (" fill" , d => d .type === ' travel' ? " var(--color-primary)" : " var(--color-accent)" )
124- .attr (" stroke" , " var(--color-base-100)" )
125- .attr (" stroke-width" , 2 )
126- .style (" cursor" , " pointer" )
127- .on (" mouseover" , function (event , d ) {
128- d3 .select (this ).style (" opacity" , 0.8 )
129-
130- const typeText = d . type === ' travel '
131- ? ` <span class="text-primary font-bold">Moving</span> `
132- : ` <span class="text-accent font-bold">Waiting </span> `
133-
134- tooltip . style ( " opacity " , 1 )
135- .html (`
121+ .append (' rect' )
122+ .attr (' x ' , ( d ) => x (d .start ))
123+ .attr (' y ' , margin .top )
124+ .attr (' width' , ( d ) => Math .max (2 , x (d .end ) - x (d .start )))
125+ .attr (' height' , height - margin .top - margin .bottom )
126+ .attr (' rx ' , 4 )
127+ .attr (' fill' , ( d ) => ( d .type === ' travel' ? ' var(--color-primary)' : ' var(--color-accent)' ) )
128+ .attr (' stroke' , ' var(--color-base-100)' )
129+ .attr (' stroke-width' , 2 )
130+ .style (' cursor' , ' pointer' )
131+ .on (' mouseover' , function (event , d ) {
132+ d3 .select (this ).style (' opacity' , 0.8 )
133+
134+ const typeText =
135+ d . type === ' travel '
136+ ? ` <span class="text-primary font-bold">Moving </span> `
137+ : ` <span class="text-accent font-bold">Waiting</span> `
138+
139+ tooltip . style ( ' opacity ' , 1 ) .html (`
136140 <div class="mb-1">${typeText }</div>
137141 <strong>${d .label }</strong><br/>
138142 Duration: ${formatDuration (d .duration )}
139143 ` )
140144 })
141- .on (" mousemove" , function (event ) {
145+ .on (' mousemove' , function (event ) {
142146 const [xPos, yPos] = d3 .pointer (event , svgRef .value )
143147 const isRightSide = xPos > width / 2
144- tooltip .style (" left" , isRightSide ? (xPos - 150 ) + " px" : (xPos + 15 ) + " px" )
145- .style (" top" , " 10px" )
148+ tooltip .style (' left' , isRightSide ? xPos - 150 + ' px' : xPos + 15 + ' px' ).style (' top' , ' 10px' )
146149 })
147- .on (" mouseout" , function () {
148- d3 .select (this ).style (" opacity" , 1 )
149- tooltip .style (" opacity" , 0 )
150+ .on (' mouseout' , function () {
151+ d3 .select (this ).style (' opacity' , 1 )
152+ tooltip .style (' opacity' , 0 )
150153 })
151154
152- svg .append (" g" )
153- .attr (" transform" , ` translate(0,${height - margin .bottom + 5 }) ` )
154- .append (" text" )
155- .attr (" x" , margin .left )
156- .attr (" y" , 10 )
157- .attr (" fill" , " currentColor" )
158- .style (" font-size" , " 12px" )
159- .text (" Start" )
160-
161- svg .append (" g" )
162- .attr (" transform" , ` translate(0,${height - margin .bottom + 5 }) ` )
163- .append (" text" )
164- .attr (" x" , width - margin .right )
165- .attr (" y" , 10 )
166- .attr (" fill" , " currentColor" )
167- .attr (" text-anchor" , " end" )
168- .style (" font-size" , " 12px" )
169- .text (" Finish" )
155+ svg
156+ .append (' g' )
157+ .attr (' transform' , ` translate(0,${height - margin .bottom + 5 }) ` )
158+ .append (' text' )
159+ .attr (' x' , margin .left )
160+ .attr (' y' , 10 )
161+ .attr (' fill' , ' currentColor' )
162+ .style (' font-size' , ' 12px' )
163+ .text (' Start' )
164+
165+ svg
166+ .append (' g' )
167+ .attr (' transform' , ` translate(0,${height - margin .bottom + 5 }) ` )
168+ .append (' text' )
169+ .attr (' x' , width - margin .right )
170+ .attr (' y' , 10 )
171+ .attr (' fill' , ' currentColor' )
172+ .attr (' text-anchor' , ' end' )
173+ .style (' font-size' , ' 12px' )
174+ .text (' Finish' )
170175}
171176
172177watch (() => gameState .value .trip , drawChart , { deep: true })
173178onMounted (drawChart )
174179
175- const stats = ref ({ travel: " 0m" , wait: " 0m" })
176- watch (() => gameState .value .trip , () => {
177- const data = getChartData ()
178- stats .value = {
179- travel: formatDuration (data .totalTravel ),
180- wait: formatDuration (data .totalWait )
181- }
182- }, { deep: true , immediate: true })
183-
180+ const stats = ref ({ travel: ' 0m' , wait: ' 0m' })
181+ watch (
182+ () => gameState .value .trip ,
183+ () => {
184+ const data = getChartData ()
185+ stats .value = {
186+ travel: formatDuration (data .totalTravel ),
187+ wait: formatDuration (data .totalWait ),
188+ }
189+ },
190+ { deep: true , immediate: true },
191+ )
184192 </script >
185193
186194<template >
187195 <div class =" w-full flex flex-col" >
188196 <div class =" flex justify-between items-center mb-2 px-4 text-sm" >
189197 <div class =" flex items-center gap-2" >
190198 <div class =" w-3 h-3 rounded-full bg-primary shadow-sm" ></div >
191- <span class =" font-semibold" >Time in Trains: <span class =" text-base-content" >{{ stats.travel }}</span ></span >
199+ <span class =" font-semibold"
200+ >Time in Trains: <span class =" text-base-content" >{{ stats.travel }}</span ></span
201+ >
192202 </div >
193203 <div class =" flex items-center gap-2" >
194204 <div class =" w-3 h-3 rounded-full bg-accent shadow-sm" ></div >
195- <span class =" font-semibold" >Time Waiting: <span class =" text-base-content" >{{ stats.wait }}</span ></span >
205+ <span class =" font-semibold"
206+ >Time Waiting: <span class =" text-base-content" >{{ stats.wait }}</span ></span
207+ >
196208 </div >
197209 </div >
198-
210+
199211 <div class =" relative w-full flex justify-center" >
200212 <svg ref =" svgRef" class =" text-base-content" ></svg >
201- <div
202- ref =" tooltipRef"
213+ <div
214+ ref =" tooltipRef"
203215 class =" absolute opacity-0 bg-neutral text-neutral-content px-4 py-2 rounded-box shadow-lg pointer-events-none text-sm transition-opacity duration-200 z-50 min-w-[150px] text-left"
204216 ></div >
205217 </div >
206218 </div >
207- </template >
219+ </template >
0 commit comments