Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Progress bar + cleanup of query caching logic #134

Merged
merged 11 commits into from
Jul 12, 2024
55 changes: 55 additions & 0 deletions frontend/src/Sidebar/ResultsContainer.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { useContext, useState, useEffect } from 'react'
import { DataContext } from '../Layout'
import BigButton from './BigButton'

export default function ResultsContainer(){
const [ isFetchingData, setIsFetchingData ] = useState(false)
const [ progress, setProgress ] = useState(-1)
const { data } = useContext(DataContext)
useEffect(()=>{
data.queue.on('active',()=>{
setProgress( 100 * data.queryCountFinished / data.queryCount )
})
},[])
return (
<div>
{!isFetchingData && <>
{data.queryCount} travel time{data.queryCount == 1 ? '' : 's'} to be queried
</>}
{data.queryCount > 0 && !isFetchingData && !data.allQueriesHaveData &&
<BigButton onClick={()=>{
setIsFetchingData(true)
data.fetchAllResults().then( () => {
setIsFetchingData(false)
} )
}}>Submit Query</BigButton>
}
{isFetchingData && <>
<p>Finished fetching {data.queryCountFinished}/{data.queryCount} results</p>
<ProgressBar percentDone={progress}/>
</>}
{data.allQueriesHaveData && <>
<a download='results.json'
href={`data:text/plain;charset=utf-8,${encodeURIComponent(JSON.stringify(data.travelTimeQueries.map(r=>r.resultsRecord('json'))))}`}
>
<BigButton>Download results as JSON</BigButton>
</a>
<a download='results.csv'
href={`data:text/plain;charset=utf-8,${encodeURIComponent([...data.travelTimeQueries[0].resultsRecord('').keys()].join(',') + '\n' + data.travelTimeQueries.map(r=>r.resultsRecord('csv')).join('\n'))}`}
>
<BigButton>Download results as CSV </BigButton>
</a>
</>
}
</div>
)
}

function ProgressBar({percentDone}){
return (
<svg viewBox='0 0 100 7'>
<rect height='100%' width='100%' fill='white' stroke='black' strokeWidth='1'/>
<rect height='100%' width={percentDone} fill='darkgreen' strokeWidth='1'/>
</svg>
)
}
49 changes: 3 additions & 46 deletions frontend/src/Sidebar/index.jsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { useContext, useState } from 'react'
import { useContext } from 'react'
import { DataContext } from '../Layout'

import FactorContainer from './FactorContainer'
import BigButton from './BigButton'
import ResultsContainer from './ResultsContainer'
import FactorList from './FactorList'
import { restoreStateFromFile } from './restoreStateFromFile.js'
import './sidebar.css'
Expand All @@ -29,51 +30,7 @@ export default function SidebarContent(){
<div className='big-math-symbol'>&#xd7;</div>
<HolidaysContainer/>
<div className='big-math-symbol'>=</div>
<Results/>
</div>
)
}

function Results(){
const [ results, setResults ] = useState(undefined)
const [ isFetchingData, setIsFetchingData ] = useState(false)
const { data } = useContext(DataContext)
const numResults = data.travelTimeQueries.length
return (
<div>
{numResults} travel time{numResults == 1 ? '' : 's'} to estimate currently
{numResults > 0 && ! isFetchingData &&
<BigButton onClick={()=>{
setResults(undefined)
setIsFetchingData(true)
data.fetchAllResults().then( () => {
setIsFetchingData(false)
setResults(data.travelTimeQueries)
} )
}}>
Submit Query
</BigButton>
}
{isFetchingData &&
<p>Please wait while your request is being processed</p>
}
{results && <>
<a download='results.json'
href={`data:text/plain;charset=utf-8,${encodeURIComponent(JSON.stringify(results.map(r=>r.resultsRecord('json'))))}`}
>
<BigButton>
Download results as JSON
</BigButton>
</a>
<a download='results.csv'
href={`data:text/plain;charset=utf-8,${encodeURIComponent([...results[0].resultsRecord('').keys()].join(',') + '\n' + results.map(r=>r.resultsRecord('csv')).join('\n'))}`}
>
<BigButton>
Download results as CSV
</BigButton>
</a>
</>
}
<ResultsContainer/>
</div>
)
}
Expand Down
1 change: 1 addition & 0 deletions frontend/src/corridor.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export class Corridor extends Factor {
.then( () => {
// notify the layout that the path is ready to be rendered
logActivity('shortest path returned')
this.hasUpdated()
} )
}
get intersections(){ return [...this.#intersections.values()] }
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/dateRange.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,12 @@ export class DateRange extends Factor {
}
setStartDate(inputDate){
this.#startDate = inputDate
this.hasUpdated()
return this.#startDate
}
setEndDate(inputDate){
this.#endDate = inputDate
this.hasUpdated()
return this.#endDate
}
static dateFormatted(datetime){
Expand Down
7 changes: 6 additions & 1 deletion frontend/src/days.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,15 @@ export class Days extends Factor {
addDay(number){
if( daylist.map(d=>d.iso).includes(parseInt(number)) ){
this.#days.add(parseInt(number))
this.hasUpdated()
}
}
removeDay(number){
this.#days.delete(parseInt(number))
let dayNum = parseInt(number)
if(this.#days.has(dayNum)){
this.#days.delete(dayNum)
this.hasUpdated()
}
}
hasDay(number){
return this.#days.has(parseInt(number))
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/factor.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,8 @@ export class Factor {
render(){ // this will be overwritten but must be implemented
return <></>
}
hasUpdated(){
// should be called when a factor has been updated
this.#dataContext.updateQueries()
}
}
2 changes: 1 addition & 1 deletion frontend/src/holidayOption.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Factor } from './factor.js'
export class HolidayOption extends Factor {
#includeHolidays
#dataContext
constructor(dataContext,includeHolidays){
constructor(dataContext, includeHolidays){
super(dataContext)
// store this here too to actually access the holiday data
this.#dataContext = dataContext
Expand Down
54 changes: 43 additions & 11 deletions frontend/src/spatialData.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export class SpatialData {
let days = new Days(this)
this.#factors.push(days)
days.activate()
this.updateQueries() // this is the only factor that starts out complete
return days
}
get segments(){
Expand All @@ -63,27 +64,45 @@ export class SpatialData {
}
dropFactor(factor){
this.#factors = this.#factors.filter(f => f != factor)
this.updateQueries()
}
deactivateOtherFactors(factor){
this.#factors.forEach( f => {
if(f != factor) f.deactivate()
} )
}
includeHolidays(){
this.holidayOptions.forEach(f => this.dropFactor(f))
this.#factors.push(new HolidayOption(this,true))
this.holidayOptions.forEach( factor => {
if(!factor.holidaysIncluded) this.dropFactor(factor)
} )
if(this.holidayOptions.length == 0){
this.#factors.push(new HolidayOption(this,true))
}
this.updateQueries()
}
excludeHolidays(){
this.holidayOptions.forEach(f => this.dropFactor(f))
this.#factors.push(new HolidayOption(this,false))
this.holidayOptions.forEach( factor => {
if(factor.holidaysIncluded) this.dropFactor(factor)
} )
if(this.holidayOptions.length == 0){
this.#factors.push(new HolidayOption(this,false))
}
this.updateQueries()
}
includeAndExcludeHolidays(){
this.holidayOptions.forEach(f => this.dropFactor(f))
this.#factors.push(new HolidayOption(this,true))
this.#factors.push(new HolidayOption(this,false))
console.assert(this.holidayOptions.length == 1)
// add a factor for whatever the opposite of the existing one is
this.#factors.push(
new HolidayOption(
this,
! this.holidayOptions[0].holidaysIncluded
)
)
this.updateQueries()
}
get travelTimeQueries(){
// is the crossproduct of all complete/valid factors
updateQueries(){
// this should be run any time the inputs change to keep the list fresh
// queries are the crossproduct of all complete/valid factors
const crossProduct = []
this.corridors.filter(c=>c.isComplete).forEach( corridor => {
this.timeRanges.filter(tr=>tr.isComplete).forEach( timeRange => {
Expand All @@ -104,17 +123,19 @@ export class SpatialData {
} )
} )
})
// add new travelTimeRequests
// add any new travelTimeRequests
crossProduct.forEach( TTQ => {
if( ! this.#queries.has(TTQ.URI) ){
this.#queries.set(TTQ.URI,TTQ)
}
} )
// remove old/modified travelTimeRequests
// remove any old/modified travelTimeRequests
let currentURIs = new Set(crossProduct.map(TTI=>TTI.URI))
let currentKeys = [...this.#queries.keys()]
currentKeys.filter( key => ! currentURIs.has(key) )
.forEach( key => this.#queries.delete(key) )
}
get travelTimeQueries(){
return [...this.#queries.values()].sort((a,b)=> a.URI < b.URI ? -1 : 1)
}
fetchAllResults(){
Expand All @@ -124,4 +145,15 @@ export class SpatialData {
.map( TTQ => () => TTQ.fetchData() )
)
}
get queue(){ return this.#queue }
get allQueriesHaveData(){
return ( // some queries, all with data
this.queryCount > 0
&& this.queryCount == this.queryCountFinished
)
}
get queryCount(){ return this.#queries.size }
get queryCountFinished(){
return [...this.#queries.values()].filter(q=>q.hasData).length
}
}
2 changes: 2 additions & 0 deletions frontend/src/timeRange.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,12 @@ export class TimeRange extends Factor {
}
setStartTime(input){
this.#startTime = TimeRange.parseTime(input)
this.hasUpdated()
return this.#startTime
}
setEndTime(input){
this.#endTime = TimeRange.parseTime(input)
this.hasUpdated()
return this.#endTime
}
static timeFormatted(datetime){
Expand Down
Loading