Skip to content

Commit 5389ca8

Browse files
committed
last improvements
1 parent 06b09e6 commit 5389ca8

5 files changed

Lines changed: 180 additions & 128 deletions

File tree

via-ui/src/App.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ onMounted(() => {
152152
<TimeChart />
153153
</div>
154154

155-
<div class="grid grid-cols-1 gap-6">
155+
<div class="grid grid-cols-1 gap-6 lg:grid-cols-2">
156156
<!--Distance vs stop viz-->
157157
<div class="bg-base-100 p-6 border border-base-300 rounded-box shadow-sm">
158158
<h4 class="font-bold text-xl mb-1 text-base-content">Distance Evolution</h4>

via-ui/src/api/game/manager.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ export interface UserTrip {
1111
distance_before: number
1212
distance_after: number
1313
shape: ShapePoint[]
14+
departure_time: string
15+
arrival_time: string
1416
}
1517

1618
export interface GameState {
@@ -24,7 +26,8 @@ export interface GameState {
2426
currentDistance: number | null
2527
shapePreview: ShapePoint[]
2628
stopPreview: Stop | null
27-
seed: String | null
29+
seed: String | null,
30+
chosenDepartureTime: string | null
2831
}
2932

3033
const state = ref<GameState>({
@@ -39,6 +42,7 @@ const state = ref<GameState>({
3942
shapePreview: [],
4043
stopPreview: null,
4144
seed: null,
45+
chosenDepartureTime: null,
4246
})
4347

4448
const service = new Service('/via/data')
@@ -100,6 +104,7 @@ export function useGameManager() {
100104
}
101105

102106
async function chooseTrain(departure: Departure) {
107+
state.value.chosenDepartureTime = departure.departure_time
103108
state.value.chosenTrain = await service.getTripDetails(
104109
departure.trip_id,
105110
state.value.currentStop?.stop_id,
@@ -125,7 +130,10 @@ export function useGameManager() {
125130
distance_before: distanceBefore,
126131
distance_after: distanceAfter,
127132
shape: shape,
133+
departure_time: state.value.chosenDepartureTime!!,
134+
arrival_time: stop.arrival_time,
128135
}
136+
state.value.chosenDepartureTime = null
129137

130138
state.value.chosenTrain = null
131139
state.value.currentStop = endStation
@@ -154,6 +162,19 @@ export function useGameManager() {
154162
state.value.stopPreview = null
155163
}
156164

165+
async function resetProgression(){
166+
state.value.currentTime = START_DATE
167+
state.value.currentStop = state.value.startStation
168+
state.value.departures = await service.getUpcomingDepartures(
169+
state.value.startStation!!.stop_id,
170+
START_DATE,
171+
)
172+
state.value.currentDistance = computeDistanceToTarget(state.value.startStation!!)
173+
state.value.trip = []
174+
state.value.stopPreview = null
175+
state.value.shapePreview = []
176+
}
177+
157178
return {
158179
gameState,
159180
startGame,
@@ -163,5 +184,6 @@ export function useGameManager() {
163184
clearTripPreview,
164185
showStopPreview,
165186
clearStopPreview,
187+
resetProgression,
166188
}
167189
}

via-ui/src/components/TimeChart.vue

Lines changed: 114 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -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
172177
watch(() => gameState.value.trip, drawChart, { deep: true })
173178
onMounted(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

Comments
 (0)