From c06eed19a9b77af768c04e72b4c199986ba24654 Mon Sep 17 00:00:00 2001 From: Lorenzo Gasparini Date: Wed, 23 May 2018 14:26:59 +0200 Subject: [PATCH 01/28] Initial markers --- src/js/models/vehiclejourney.js | 3 ++- src/js/ptdataset.js | 34 +++++++++++++++++++++++++++ src/js/viz_components/mareydiagram.js | 20 +++++++++++++++- 3 files changed, 55 insertions(+), 2 deletions(-) diff --git a/src/js/models/vehiclejourney.js b/src/js/models/vehiclejourney.js index 63bc7fb..9053bbd 100644 --- a/src/js/models/vehiclejourney.js +++ b/src/js/models/vehiclejourney.js @@ -142,8 +142,9 @@ export default class VehicleJourney { // Extract array of static schedule distances at each stop const staticDistances = this.journeyPattern.distances; - return Object.values(this.rt).map(({ vehicleNumber, times, distances }) => ({ + return Object.values(this.rt).map(({ vehicleNumber, times, distances, markers }) => ({ vehicleNumber, + markers, // Enrich the vehicles position data with the distance since the last stop // and the index of that stop, as well as the status compared to the schedule positions: times.map((time, index) => { diff --git a/src/js/ptdataset.js b/src/js/ptdataset.js index 3169591..bf9678a 100644 --- a/src/js/ptdataset.js +++ b/src/js/ptdataset.js @@ -21,6 +21,22 @@ export default class PTDataset { this.vehicleJourneys = this.computeVehicleJourneys(inputData.vehicleJourneys); this.stopsLinks = this.computeLinks(); + const testMarkers = [ + { + id: 123, + reference: { + vehicleJourneyCode: 'HTM:1:10011:2018-05-16', + vehicleNumber: 3125, + }, + time: new Date(1526441459000), + message: 'Test Marker Message', + url: 'http://example.org/', + }, + ]; + + this.addMarkersToDataset(testMarkers); + + // Compute times of the first and last stop of any journey in the dataset this.earliestTime = Math.min(...Object.values(this.journeyPatterns).map(jp => jp.firstAndLastTimes.first)); @@ -28,6 +44,24 @@ export default class PTDataset { jp.firstAndLastTimes.last)); } + addMarkersToDataset(markers) { + for (const marker of markers) { + const { vehicleJourneyCode, vehicleNumber } = marker.reference; + if (Object.prototype.hasOwnProperty.call(this.vehicleJourneys, vehicleJourneyCode)) { + const { rt } = this.vehicleJourneys[vehicleJourneyCode]; + if (Object.prototype.hasOwnProperty.call(rt, vehicleNumber)) { + const vehicleData = rt[vehicleNumber]; + if (Object.prototype.hasOwnProperty.call(vehicleData, 'markers')) { + const markersData = vehicleData.markers; + markersData.push(marker); + } else { + vehicleData.markers = [marker]; + } + } + } + } + } + /** * Convert raw stops data into rich Stop and StopArea objects, * storing them in an object indexed by their code for fast lookup diff --git a/src/js/viz_components/mareydiagram.js b/src/js/viz_components/mareydiagram.js index ee3765d..789cde9 100644 --- a/src/js/viz_components/mareydiagram.js +++ b/src/js/viz_components/mareydiagram.js @@ -713,7 +713,7 @@ export default class MareyDiagram { const realtimeSequences = []; // Iterate over each of the real time vehicles - for (const { vehicleNumber, positions } of vehicleJourney.realTimeData) { + for (const { vehicleNumber, positions, markers } of vehicleJourney.realTimeData) { const vehicleSequences = []; // Iterate over the shared sequence @@ -747,6 +747,24 @@ export default class MareyDiagram { }; }); + // If there are markers to add, add them + if (markers) { + // Deep clone markers + const leftOverMarkers = markers.slice(); + for (const [indexP, { time }] of vehicleSequence.entries()) { + for (const [indexM, marker] of leftOverMarkers.entries()) { + if (indexP !== vehicleSequence.length - 1) { + const nextPosition = vehicleSequence[indexP + 1]; + if (time < marker.time && marker.time < nextPosition.time) { + console.log(`computing position for marker ${marker.id}`); + leftOverMarkers.splice(indexM, 1); + break; + } + } + } + } + } + // Filter out sequences with zero length (can happen that a vehicle belonging to a // journey pattern that shares >1 link(s) with the reference one does not have any // positions to be drawn because the positions are not part of the shared links) From 8a2435c8e97b4595f9dc40a839233979790c79f6 Mon Sep 17 00:00:00 2001 From: Lorenzo Gasparini Date: Fri, 1 Jun 2018 14:21:20 +0200 Subject: [PATCH 02/28] Progress on markers --- src/js/viz_components/mareydiagram.js | 43 +++++++++++++++++++++++++-- src/scss/main.scss | 6 +++- 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/src/js/viz_components/mareydiagram.js b/src/js/viz_components/mareydiagram.js index 789cde9..aee957f 100644 --- a/src/js/viz_components/mareydiagram.js +++ b/src/js/viz_components/mareydiagram.js @@ -681,6 +681,7 @@ export default class MareyDiagram { for (const otherJP of this.journeyPatternMix.otherJPs) { // Iterate over the trips of the journey pattern for (const vehicleJourney of otherJP.journeyPattern.vehicleJourneys) { + const tripMarkers = []; // Min and max time of every static/realtime position of the current journey, // only for the shared segments let minTime = null; @@ -751,12 +752,20 @@ export default class MareyDiagram { if (markers) { // Deep clone markers const leftOverMarkers = markers.slice(); - for (const [indexP, { time }] of vehicleSequence.entries()) { + for (const [indexP, { time, distance }] of vehicleSequence.entries()) { for (const [indexM, marker] of leftOverMarkers.entries()) { if (indexP !== vehicleSequence.length - 1) { const nextPosition = vehicleSequence[indexP + 1]; if (time < marker.time && marker.time < nextPosition.time) { - console.log(`computing position for marker ${marker.id}`); + const x1 = distance; + const y1 = time; + const x2 = nextPosition.distance; + const y2 = nextPosition.time; + const yM = marker.time; + const xM = (((x1 - x2) / (y1 - y2)) * yM) - + (((x1 * y2) - (x2 * y1)) / (y1 - y2)); + marker.distance = xM; + tripMarkers.push(marker); leftOverMarkers.splice(indexM, 1); break; } @@ -785,6 +794,7 @@ export default class MareyDiagram { code: vehicleJourney.code, staticSequences, realtimeSequences, + markers: tripMarkers, firstAndLastTimes: { first: minTime, last: maxTime }, }); } @@ -994,5 +1004,34 @@ export default class MareyDiagram { .attr('cx', ({ distance }) => this.xScale(distance)) // Trip enter + update > realtime vehicle sequences > realtime position enter .attr('cy', ({ time }) => this.yScale(time)); + + // Draw the markers at the end so that they are on top of everything else + // Trip enter + update > marker selection + const tripMarkersSel = tripsEnterUpdateSel + .selectAll('g.marker') + .data(({ markers }) => (typeof markers !== 'undefined' ? markers : [])); + + // Trip enter + update > marker exit + tripMarkersSel.exit().remove(); + + // Trip enter + update > marker enter + const tripMarkersGroup = tripMarkersSel.enter().append('g').attr('class', 'marker'); + tripMarkersGroup + .on('mouseover', function f() { + d3event.stopPropagation(); + d3.select(this).append('text').attr('class', 'message').text(({ message, url }) => `${message} | ${url}`); + }) + .on('mouseout', function f() { + d3event.stopPropagation(); + d3.select(this).select('text.message').remove(); + }) + .merge(tripMarkersSel) + .attr('transform', ({ distance, time }) => + `translate(${this.xScale(distance)},${this.yScale(time)})`); + + tripMarkersGroup.append('image') + .attr('width', 15) + .attr('height', 15) + .attr('xlink:href', 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABmJLR0QA/wD/AP+gvaeTAAADC0lEQVR4nO2aPWgUQRiGn1XjT4yQwiBGCAhaxDQSBEEJYqNC1NhEbCQWYikEC1vBaBkQCSKWYqFY2Kig8a8QIdGAiI0/KIiCQUW8i4IJWYvZJXfnTG5mb3ZnQuaBD3Kb2W/f99293dlJIBAIBAKBQCAQCATmiIBjwBgwBcQLtMrAE2C/aQDDHoi3XWd0ze/2QGxepXUl3PRAaF71uNZsJAngC7BeJ6kFSBlYU7lhiWTQ2mK0OOG/Ey4L4HsBQlzxoXaDLIDRAoS4QstbNzCL+xuW7ZoBOnWTGvFAsO0a0TUPsBJ46oFoW/UCaDYJAKANeOeB+EbrE9Buaj6lHfE+4NpE1noJdGQ1n9IMXPXAjGldB1ZnMXxYsf0I8MMDY/XqJ3DU0FsVM8CA4ncbgHsemFTVQ9SX/AAwrRNA2uwS4mlQSwScBH57YDitP8Ap5BO75cDFirHaAcTAa8TESEYnMO6B+VfAVoXGLYhHYOV4owBi4C9iMWGpZOwy4HQypmjjs8AFYIVEVwScQL6aZRxAWs+ATYp9tgNvCjT/Edil0NKBuBeo9s0cQAz8QiQrYxXijOT9HnEDaFVo6Kf+k6qhANK6g3rRZB9iUcW28UngkOKYrcA1zT5WAoiBr0CfokcbcMui+buop7N7gc8GvawFkNYVoEXR6zhQasB4KekhowW4jPlXznoAMfAe2Knot5lsj8vxZF8ZO4C3GbXmEkCMmEGeR0w8amkChhAzsXp9ppOxTYo+55JjZdWZWwBpTQBdit7dzP92OYZ64tWV9G5UX+4BxIip6SDyZfcI6AVuI+7sk8nPvfOMH0x62tBWSABpjSJeoLKyDhGOTU11sXmwGPiGmKCY0p/sa1tP4QFUXg09GsfvAR7kqKMK2XdOK6UGeI5YrbnP3B8qNgJ7EAsW23I+fqT8kJB3AK6p8ixbQFhUhABcC3BNCMC1ANeEACTbpgpXURyl2g2yACYKEOIKLW995DcNdV0HdJMa8kCs7Tqraz7lIOL/6soeiM9aZeARBmc+EAgEAoHA4uEfmPh3WpWTDh8AAAAASUVORK5CYII='); } } diff --git a/src/scss/main.scss b/src/scss/main.scss index 75a5662..2800cbc 100644 --- a/src/scss/main.scss +++ b/src/scss/main.scss @@ -48,6 +48,10 @@ svg { } #marey { + g.marker text{ + font-family: FontAwesome; + } + .marey-scroll { rect.selection { stroke: black; @@ -71,7 +75,7 @@ svg { display: none; } text { - transform: translate(4px, 0px); + transform: translate(8px, 0px); } } rect.selection { From ee3a04fa3e45fd3ab60699645c68b7c0d7065786 Mon Sep 17 00:00:00 2001 From: Stefan de Konink Date: Sun, 7 Oct 2018 20:40:53 +0200 Subject: [PATCH 03/28] Update d3-timer to 1.0.9 --- package.json | 2 +- yarn.lock | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 441efd1..ece0e4e 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ "d3-shape": "^1.2.0", "d3-time": "^1.0.8", "d3-time-format": "^2.1.1", - "d3-timer": "^1.0.7", + "d3-timer": "^1.0.9", "d3-zoom": "^1.7.1", "dat.gui": "^0.7.0", "jquery": "^3.3.1", diff --git a/yarn.lock b/yarn.lock index 11a7054..f53733e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1631,10 +1631,14 @@ d3-time@1, d3-time@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-1.0.8.tgz#dbd2d6007bf416fe67a76d17947b784bffea1e84" -d3-timer@1, d3-timer@^1.0.7: +d3-timer@1: version "1.0.7" resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-1.0.7.tgz#df9650ca587f6c96607ff4e60cc38229e8dd8531" +d3-timer@^1.0.9: + version "1.0.9" + resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-1.0.9.tgz#f7bb8c0d597d792ff7131e1c24a36dd471a471ba" + d3-transition@1: version "1.1.1" resolved "https://registry.yarnpkg.com/d3-transition/-/d3-transition-1.1.1.tgz#d8ef89c3b848735b060e54a39b32aaebaa421039" From 686bc74bdc7400f9f3ea20541d822f1cd275aef6 Mon Sep 17 00:00:00 2001 From: Stefan de Konink Date: Sun, 7 Oct 2018 20:41:43 +0200 Subject: [PATCH 04/28] Try out to update Marey in realtime --- src/js/ptdataset.js | 18 ++++++++++++++++++ src/js/ptds.js | 16 +++++++++++++++- src/js/viz_components/mareydiagram.js | 6 ++++++ 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/src/js/ptdataset.js b/src/js/ptdataset.js index 3169591..3ebb32f 100644 --- a/src/js/ptdataset.js +++ b/src/js/ptdataset.js @@ -15,6 +15,7 @@ import TimeUtils from './timeutils'; */ export default class PTDataset { constructor(inputData, referenceDate) { + this.updateUrl = inputData.updateUrl; this.referenceDate = referenceDate; Object.assign(this, PTDataset.computeStopsAndStopAreas(inputData.scheduledStopPoints)); Object.assign(this, this.computeLinesJourneyPatterns(inputData.journeyPatterns)); @@ -190,4 +191,21 @@ export default class PTDataset { vehicleJourney => vehicleJourney.code, ); } + + + updateVehicleJourneys(_vehicleJourneys) { + for (const [code, { realtime, cancelled }] of Object.entries(_vehicleJourneys)) { + // Convert time in seconds since noon minus 12h to Date object + for (const rtVehicle of Object.values(realtime)) { + rtVehicle.times = rtVehicle.times.map(time => + TimeUtils.secondsToDateObject(time, this.referenceDate)); + } + + const vehicleJourney = this.vehicleJourneys[code]; + if (vehicleJourney !== undefined) { + vehicleJourney.realtime = realtime; + vehicleJourney.cancelled = cancelled; + } + } + } } diff --git a/src/js/ptds.js b/src/js/ptds.js index a4444f7..40da34a 100644 --- a/src/js/ptds.js +++ b/src/js/ptds.js @@ -1,6 +1,6 @@ import { select } from 'd3-selection'; import { timeFormat } from 'd3-time-format'; -import { timer } from 'd3-timer'; +import { timer, interval } from 'd3-timer'; import dat from 'dat.gui'; import PTDataset from './ptdataset'; @@ -11,6 +11,7 @@ const d3 = Object.assign({}, { select, timeFormat, timer, + interval, }); /** @@ -18,7 +19,20 @@ const d3 = Object.assign({}, { */ export default class PTDS { constructor(inputData, options) { + this.marey = null; this.data = new PTDataset(inputData, options.selectedDate); + + this.dataUpdateTimer = d3.interval(() => { + if (this.marey !== null) { + fetch(this.data.updateUrl).then(r => r.json()).then((updateData) => { + this.data.updateVehicleJourneys(updateData.vehicleJourneys); + }); + /* This feels very redundant... */ + this.journeyPatternMix = this.computeJourneyPatternMix(); + this.marey.update(this.journeyPatternMix); + } + }, 15000, 15000); + this.options = options; if (['dual', 'marey'].includes(options.mode)) { diff --git a/src/js/viz_components/mareydiagram.js b/src/js/viz_components/mareydiagram.js index ee3765d..b56f455 100644 --- a/src/js/viz_components/mareydiagram.js +++ b/src/js/viz_components/mareydiagram.js @@ -51,6 +51,12 @@ export default class MareyDiagram { this.drawTrips(); } + update(journeyPatternMix) { + this.journeyPatternMix = journeyPatternMix; + this.trips = this.computeTrips(); + this.drawTrips(); + } + /** * Computes the time boundaries for the diagram, looking at the fist and last time of * all the trips of all the journey patterns displayed From e941e633d67dc6e2d810aa68c26ac156a12adc03 Mon Sep 17 00:00:00 2001 From: Stefan de Konink Date: Sun, 7 Oct 2018 21:27:49 +0200 Subject: [PATCH 05/28] Fix a bug, it should be rt, not realtime --- src/js/ptdataset.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/ptdataset.js b/src/js/ptdataset.js index 3ebb32f..0c84fc5 100644 --- a/src/js/ptdataset.js +++ b/src/js/ptdataset.js @@ -203,7 +203,7 @@ export default class PTDataset { const vehicleJourney = this.vehicleJourneys[code]; if (vehicleJourney !== undefined) { - vehicleJourney.realtime = realtime; + vehicleJourney.rt = realtime; vehicleJourney.cancelled = cancelled; } } From 88f752549fbcea97503a0ef1d4fbbee3b942db20 Mon Sep 17 00:00:00 2001 From: Stefan de Konink Date: Sun, 7 Oct 2018 21:28:18 +0200 Subject: [PATCH 06/28] Updating the Mix is not required. --- src/js/ptds.js | 4 +--- src/js/viz_components/mareydiagram.js | 3 +-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/js/ptds.js b/src/js/ptds.js index 40da34a..55b549b 100644 --- a/src/js/ptds.js +++ b/src/js/ptds.js @@ -27,9 +27,7 @@ export default class PTDS { fetch(this.data.updateUrl).then(r => r.json()).then((updateData) => { this.data.updateVehicleJourneys(updateData.vehicleJourneys); }); - /* This feels very redundant... */ - this.journeyPatternMix = this.computeJourneyPatternMix(); - this.marey.update(this.journeyPatternMix); + this.marey.update(); } }, 15000, 15000); diff --git a/src/js/viz_components/mareydiagram.js b/src/js/viz_components/mareydiagram.js index b56f455..52b8a78 100644 --- a/src/js/viz_components/mareydiagram.js +++ b/src/js/viz_components/mareydiagram.js @@ -51,8 +51,7 @@ export default class MareyDiagram { this.drawTrips(); } - update(journeyPatternMix) { - this.journeyPatternMix = journeyPatternMix; + update() { this.trips = this.computeTrips(); this.drawTrips(); } From 111058c0da0ebf40fea19fe49fc9781ba413ba1e Mon Sep 17 00:00:00 2001 From: Stefan de Konink Date: Sun, 7 Oct 2018 21:35:34 +0200 Subject: [PATCH 07/28] Add descriptive comments --- src/js/ptdataset.js | 6 +++++- src/js/viz_components/mareydiagram.js | 4 ++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/js/ptdataset.js b/src/js/ptdataset.js index 0c84fc5..d6fc461 100644 --- a/src/js/ptdataset.js +++ b/src/js/ptdataset.js @@ -192,7 +192,11 @@ export default class PTDataset { ); } - + /** + * Update raw vehicle journey realtime data into an existing VehicleJourney object, + * fetch each object indexed by their code for fast lookup and update that object + * @param {Object} _vehicleJourneys Raw vehicle realtime data + */ updateVehicleJourneys(_vehicleJourneys) { for (const [code, { realtime, cancelled }] of Object.entries(_vehicleJourneys)) { // Convert time in seconds since noon minus 12h to Date object diff --git a/src/js/viz_components/mareydiagram.js b/src/js/viz_components/mareydiagram.js index 52b8a78..290ce95 100644 --- a/src/js/viz_components/mareydiagram.js +++ b/src/js/viz_components/mareydiagram.js @@ -51,6 +51,10 @@ export default class MareyDiagram { this.drawTrips(); } + /** + * Updates the current dataset in the Marey diagram, and redraw. + * TODO: investigate if we could improve the update speed by only updating realtime. + */ update() { this.trips = this.computeTrips(); this.drawTrips(); From e7959391d6e8a730a4617e21f611eeee5a0e506e Mon Sep 17 00:00:00 2001 From: Stefan de Konink Date: Sun, 7 Oct 2018 22:15:03 +0200 Subject: [PATCH 08/28] Make sure only to start an update timer for a dataset that is expected to be updated. --- src/js/ptds.js | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/js/ptds.js b/src/js/ptds.js index 55b549b..514ae3d 100644 --- a/src/js/ptds.js +++ b/src/js/ptds.js @@ -22,14 +22,16 @@ export default class PTDS { this.marey = null; this.data = new PTDataset(inputData, options.selectedDate); - this.dataUpdateTimer = d3.interval(() => { - if (this.marey !== null) { - fetch(this.data.updateUrl).then(r => r.json()).then((updateData) => { - this.data.updateVehicleJourneys(updateData.vehicleJourneys); - }); - this.marey.update(); - } - }, 15000, 15000); + if (this.data.updateUrl !== undefined) { + this.dataUpdateTimer = d3.interval(() => { + if (this.marey !== null) { + fetch(this.data.updateUrl).then(r => r.json()).then((updateData) => { + this.data.updateVehicleJourneys(updateData.vehicleJourneys); + }); + this.marey.update(); + } + }, 15000, 15000); + } this.options = options; From 11c5a739ace538b348acb0059bc622ac8333bc23 Mon Sep 17 00:00:00 2001 From: Stefan de Konink Date: Mon, 8 Oct 2018 00:39:57 +0200 Subject: [PATCH 09/28] Fix issue #61 This adds the name of the first stop to StopArea. And replaces StopAreaRefs into StopNames. --- src/js/models/stoparea.js | 1 + src/js/viz_components/interactivemap.js | 5 +++-- src/js/viz_components/mareydiagram.js | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/js/models/stoparea.js b/src/js/models/stoparea.js index ed2b5c4..cf7b9e1 100644 --- a/src/js/models/stoparea.js +++ b/src/js/models/stoparea.js @@ -14,6 +14,7 @@ export default class StopArea { this.code = code; this.stops = stops; this.center = this.computeCenter(); + this.name = stops[0].name; /* TODO: This might need to be improved */ } /** diff --git a/src/js/viz_components/interactivemap.js b/src/js/viz_components/interactivemap.js index 90a216e..965e84d 100644 --- a/src/js/viz_components/interactivemap.js +++ b/src/js/viz_components/interactivemap.js @@ -219,7 +219,8 @@ export default class InteractiveMap { // Stoparea selection const stopAreasSel = this.stopAreasGroup.selectAll('g.stopArea') .data( - this.data.stopAreas.map(({ code, center }) => ({ code, center: this.mapToCanvas(center) })), + this.data.stopAreas.map(({ code, center, name }) => + ({ code, center: this.mapToCanvas(center), name })), ({ code }) => code, ); @@ -248,7 +249,7 @@ export default class InteractiveMap { .append('text') .attr('x', 0) .attr('y', -1.5) - .text(({ code }) => code); + .text(({ name }) => name); } /** diff --git a/src/js/viz_components/mareydiagram.js b/src/js/viz_components/mareydiagram.js index ee3765d..aef0c13 100644 --- a/src/js/viz_components/mareydiagram.js +++ b/src/js/viz_components/mareydiagram.js @@ -472,7 +472,7 @@ export default class MareyDiagram { // Truncate the tick label if longer than maxChars chars const maxChars = 25; const stop = this.journeyPatternMix.referenceJP.stops[index]; - let label = `${stop.code} ${stop.name}`; + let label = `${stop.name}`; if (label.length > maxChars) label = `${label.substr(0, maxChars - 3)}...`; return label; }); @@ -495,7 +495,7 @@ export default class MareyDiagram { this.xAxis = d3.axisTop(this.xScale) .tickSize(-this.dims.marey.innerHeight) .tickValues(this.journeyPatternMix.referenceJP.distances) - .tickFormat((_, index) => this.journeyPatternMix.referenceJP.stops[index].code); + .tickFormat((_, index) => this.journeyPatternMix.referenceJP.stops[index].name); } // Enhance vertical lines representing stops adding the stop code as attribute From 8e05344c1203aa242b699b57d96a7eb44e506ac2 Mon Sep 17 00:00:00 2001 From: Stefan de Konink Date: Mon, 8 Oct 2018 02:20:07 +0200 Subject: [PATCH 10/28] Start of implementation for #62 --- src/js/models/vehiclejourney.js | 13 ++++++++++++- src/js/viz_components/mareydiagram.js | 11 +++++++---- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/js/models/vehiclejourney.js b/src/js/models/vehiclejourney.js index 63bc7fb..9a72a29 100644 --- a/src/js/models/vehiclejourney.js +++ b/src/js/models/vehiclejourney.js @@ -72,6 +72,15 @@ export default class VehicleJourney { }; } + get tripLabel() { + if (this.isRealTime) { + /* TODO: Temporary workaround, we should have all blocks */ + const firstRt = this.rt[Object.keys(this.rt)]; + return `${this.journeyPattern.line.code} - ${(firstRt.blockNumber !== undefined ? firstRt.blockNumber : '?')} (${firstRt.vehicleNumber})`; + } + return this.journeyPattern.line.code; + } + /** * Check if given a specific time, the journey is active * @param {Date} time - Time @@ -132,6 +141,7 @@ export default class VehicleJourney { * Computes the realtime positions information of the vehicles belonging to this journey * @return {Array.<{ * vehicleNumber: number, + * blockNumber: number, * positions: {time: Date, distanceSinceLastStop: number, distanceFromStart: number, * status: string, prognosed: boolean} * }>} - List of enriched realtime position info @@ -142,8 +152,9 @@ export default class VehicleJourney { // Extract array of static schedule distances at each stop const staticDistances = this.journeyPattern.distances; - return Object.values(this.rt).map(({ vehicleNumber, times, distances }) => ({ + return Object.values(this.rt).map(({ vehicleNumber, blockNumber, times, distances }) => ({ vehicleNumber, + blockNumber, // Enrich the vehicles position data with the distance since the last stop // and the index of that stop, as well as the status compared to the schedule positions: times.map((time, index) => { diff --git a/src/js/viz_components/mareydiagram.js b/src/js/viz_components/mareydiagram.js index 9e9eeb1..3c40d22 100644 --- a/src/js/viz_components/mareydiagram.js +++ b/src/js/viz_components/mareydiagram.js @@ -668,12 +668,14 @@ export default class MareyDiagram { computeTrips() { // Compute drawing information for the trips of the reference journey pattern const trips = this.journeyPatternMix.referenceJP.vehicleJourneys - .map(({ code, staticSchedule, firstAndLastTimes, realTimeData }) => ({ + .map(({ code, tripLabel, staticSchedule, firstAndLastTimes, realTimeData }) => ({ code, + tripLabel, // For the reference journey pattern there is only one sequence staticSequences: [staticSchedule.map(({ time, distance }) => ({ time, distance }))], - realtimeSequences: realTimeData.map(({ vehicleNumber, positions }) => ({ + realtimeSequences: realTimeData.map(({ vehicleNumber, blockNumber, positions }) => ({ vehicleNumber, + blockNumber, // Again, only one sequence per vehicle for the reference journey pattern sequences: [positions.map(({ time, distanceFromStart, status, prognosed }) => ({ time, @@ -722,7 +724,7 @@ export default class MareyDiagram { const realtimeSequences = []; // Iterate over each of the real time vehicles - for (const { vehicleNumber, positions } of vehicleJourney.realTimeData) { + for (const { vehicleNumber, blockNumber, positions } of vehicleJourney.realTimeData) { const vehicleSequences = []; // Iterate over the shared sequence @@ -767,6 +769,7 @@ export default class MareyDiagram { if (vehicleSequences.length) { realtimeSequences.push({ vehicleNumber, + blockNumber, sequences: vehicleSequences, }); } @@ -837,7 +840,7 @@ export default class MareyDiagram { .attr('x', xPos) .attr('y', yPos) .attr('dy', -10) - .text(({ code }) => code); + .text(({ tripLabel }) => tripLabel); // Add 'selected' class to the trip SVG group tripSel.classed('selected', true); tripSel.selectAll('circle.static-stop').attr('r', selectedTripStaticStopRadius); From 171cd8b86ba028815443c70dcee453c7ec4c70bc Mon Sep 17 00:00:00 2001 From: Stefan de Konink Date: Mon, 8 Oct 2018 02:30:08 +0200 Subject: [PATCH 11/28] Fix tripLabel --- src/js/models/vehiclejourney.js | 2 +- src/js/viz_components/mareydiagram.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/js/models/vehiclejourney.js b/src/js/models/vehiclejourney.js index 9a72a29..3ef45b3 100644 --- a/src/js/models/vehiclejourney.js +++ b/src/js/models/vehiclejourney.js @@ -78,7 +78,7 @@ export default class VehicleJourney { const firstRt = this.rt[Object.keys(this.rt)]; return `${this.journeyPattern.line.code} - ${(firstRt.blockNumber !== undefined ? firstRt.blockNumber : '?')} (${firstRt.vehicleNumber})`; } - return this.journeyPattern.line.code; + return `${this.journeyPattern.line.code}`; } /** diff --git a/src/js/viz_components/mareydiagram.js b/src/js/viz_components/mareydiagram.js index 3c40d22..14908c0 100644 --- a/src/js/viz_components/mareydiagram.js +++ b/src/js/viz_components/mareydiagram.js @@ -777,6 +777,7 @@ export default class MareyDiagram { trips.push({ code: vehicleJourney.code, + tripLabel: vehicleJourney.tripLabel, staticSequences, realtimeSequences, firstAndLastTimes: { first: minTime, last: maxTime }, From b1734f09638df06e53e4408719bf5a7c9d4fe586 Mon Sep 17 00:00:00 2001 From: Stefan de Konink Date: Mon, 8 Oct 2018 02:40:30 +0200 Subject: [PATCH 12/28] Bugfixes --- src/js/models/vehiclejourney.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/models/vehiclejourney.js b/src/js/models/vehiclejourney.js index 3ef45b3..b064865 100644 --- a/src/js/models/vehiclejourney.js +++ b/src/js/models/vehiclejourney.js @@ -75,7 +75,7 @@ export default class VehicleJourney { get tripLabel() { if (this.isRealTime) { /* TODO: Temporary workaround, we should have all blocks */ - const firstRt = this.rt[Object.keys(this.rt)]; + const firstRt = this.rt[Object.keys(this.rt)[0]]; return `${this.journeyPattern.line.code} - ${(firstRt.blockNumber !== undefined ? firstRt.blockNumber : '?')} (${firstRt.vehicleNumber})`; } return `${this.journeyPattern.line.code}`; From 16bd67a84f4bcecc7299b8afa2cf9a65c6ebefd0 Mon Sep 17 00:00:00 2001 From: Stefan de Konink Date: Sat, 20 Oct 2018 14:45:38 +0200 Subject: [PATCH 13/28] Fix #72 --- src/js/viz_components/mareydiagram.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/js/viz_components/mareydiagram.js b/src/js/viz_components/mareydiagram.js index ee3765d..7e681c9 100644 --- a/src/js/viz_components/mareydiagram.js +++ b/src/js/viz_components/mareydiagram.js @@ -718,15 +718,18 @@ export default class MareyDiagram { // Iterate over the shared sequence for (let i = 0; i < referenceSequences.length; i += 1) { - // Filter out last stop of the sequence because it is not valid as "last stop" - const refSequence = referenceSequences[i].slice(0, -1); - const otherSequence = otherSequences[i].slice(0, -1); + const refSequence = referenceSequences[i]; + const otherSequence = otherSequences[i]; + const lastStopIndexTerm = otherSequence[otherSequence.length - 1]; // For each shared sequence, add the positions data of the current trip by mapping // the distance relative to the last stop of the trip to the absolute distance // in the reference journey pattern const vehicleSequence = positions .filter(({ lastStopIndex }) => otherSequence.includes(lastStopIndex)) + // Filter out last stop of the sequence beyond the arrival / departure + .filter(({ distanceSinceLastStop, lastStopIndex }) => + !(distanceSinceLastStop > 0 && lastStopIndex === lastStopIndexTerm)) .map(({ time, distanceSinceLastStop, lastStopIndex, status, prognosed }) => { // Find the index of the last stop before the current position // in the reference journey pattern From 9ca325f7868be9c65e6fc2d6b60eeaf32562992b Mon Sep 17 00:00:00 2001 From: Stefan de Konink Date: Sat, 20 Oct 2018 15:00:20 +0200 Subject: [PATCH 14/28] Apply suggestion of @joined --- src/js/ptdataset.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/ptdataset.js b/src/js/ptdataset.js index d6fc461..f0282f2 100644 --- a/src/js/ptdataset.js +++ b/src/js/ptdataset.js @@ -206,7 +206,7 @@ export default class PTDataset { } const vehicleJourney = this.vehicleJourneys[code]; - if (vehicleJourney !== undefined) { + if (typeof vehicleJourney !== 'undefined') { vehicleJourney.rt = realtime; vehicleJourney.cancelled = cancelled; } From 7984afac059d902726c7e4be6d49eede18f08e65 Mon Sep 17 00:00:00 2001 From: Stefan de Konink Date: Fri, 8 Mar 2019 14:35:17 +0100 Subject: [PATCH 15/28] eslint 5 fixes --- src/js/viz_components/mareydiagram.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/js/viz_components/mareydiagram.js b/src/js/viz_components/mareydiagram.js index fddc5e2..24afb9f 100644 --- a/src/js/viz_components/mareydiagram.js +++ b/src/js/viz_components/mareydiagram.js @@ -762,8 +762,8 @@ export default class MareyDiagram { const x2 = nextPosition.distance; const y2 = nextPosition.time; const yM = marker.time; - const xM = (((x1 - x2) / (y1 - y2)) * yM) - - (((x1 * y2) - (x2 * y1)) / (y1 - y2)); + const xM = (((x1 - x2) / (y1 - y2)) * yM) + - (((x1 * y2) - (x2 * y1)) / (y1 - y2)); marker.distance = xM; tripMarkers.push(marker); leftOverMarkers.splice(indexM, 1); @@ -1026,8 +1026,7 @@ export default class MareyDiagram { d3.select(this).select('text.message').remove(); }) .merge(tripMarkersSel) - .attr('transform', ({ distance, time }) => - `translate(${this.xScale(distance)},${this.yScale(time)})`); + .attr('transform', ({ distance, time }) => `translate(${this.xScale(distance)},${this.yScale(time)})`); tripMarkersGroup.append('image') .attr('width', 15) From d0566032bd49abc9688efcc32b289aa26a5a79ea Mon Sep 17 00:00:00 2001 From: Stefan de Konink Date: Sat, 9 Mar 2019 01:28:27 +0100 Subject: [PATCH 16/28] Implement markers visualisation on scheduled trips. --- src/js/ptdataset.js | 39 +++++++++++-- src/js/viz_components/mareydiagram.js | 82 ++++++++++++++++++++++++++- 2 files changed, 115 insertions(+), 6 deletions(-) diff --git a/src/js/ptdataset.js b/src/js/ptdataset.js index 57d60f1..98653a5 100644 --- a/src/js/ptdataset.js +++ b/src/js/ptdataset.js @@ -25,10 +25,30 @@ export default class PTDataset { { id: 123, reference: { - vehicleJourneyCode: 'HTM:1:10011:2018-05-16', - vehicleNumber: 3125, + vehicleJourneyCode: 'HTM:1:10003:2019-03-08', + vehicleNumber: 3124, }, - time: new Date(1526441459000), + time: new Date(1552020019000), + message: 'Test Marker Message', + url: 'http://example.org/', + }, + { + id: 124, + reference: { + vehicleJourneyCode: 'HTM:1:10239:2019-03-08', + vehicleNumber: null, + }, + time: new Date(1552076000000), + message: 'Test Marker Message', + url: 'http://example.org/', + }, + { + id: 125, + reference: { + vehicleJourneyCode: 'HTM:15:150006:2019-03-08', + vehicleNumber: null, + }, + time: new Date(1552019700000), message: 'Test Marker Message', url: 'http://example.org/', }, @@ -48,8 +68,10 @@ export default class PTDataset { for (const marker of markers) { const { vehicleJourneyCode, vehicleNumber } = marker.reference; if (Object.prototype.hasOwnProperty.call(this.vehicleJourneys, vehicleJourneyCode)) { - const { rt } = this.vehicleJourneys[vehicleJourneyCode]; - if (Object.prototype.hasOwnProperty.call(rt, vehicleNumber)) { + const vehicleJourneyData = this.vehicleJourneys[vehicleJourneyCode]; + const { rt } = vehicleJourneyData; + if (rt != null && vehicleNumber != null + && Object.prototype.hasOwnProperty.call(rt, vehicleNumber)) { const vehicleData = rt[vehicleNumber]; if (Object.prototype.hasOwnProperty.call(vehicleData, 'markers')) { const markersData = vehicleData.markers; @@ -57,6 +79,13 @@ export default class PTDataset { } else { vehicleData.markers = [marker]; } + } else if (vehicleNumber == null) { + if (Object.prototype.hasOwnProperty.call(vehicleJourneyData, 'markers')) { + const markersData = vehicleJourneyData.markers; + markersData.push(marker); + } else { + vehicleJourneyData.markers = [marker]; + } } } } diff --git a/src/js/viz_components/mareydiagram.js b/src/js/viz_components/mareydiagram.js index 24afb9f..cd57c6b 100644 --- a/src/js/viz_components/mareydiagram.js +++ b/src/js/viz_components/mareydiagram.js @@ -657,8 +657,9 @@ export default class MareyDiagram { * @return {Array.} - Trip drawing information */ computeTrips() { + /* // Compute drawing information for the trips of the reference journey pattern - const trips = this.journeyPatternMix.referenceJP.vehicleJourneys + const tripsOld = this.journeyPatternMix.referenceJP.vehicleJourneys .map(({ code, staticSchedule, firstAndLastTimes, realTimeData }) => ({ code, // For the reference journey pattern there is only one sequence @@ -674,7 +675,58 @@ export default class MareyDiagram { }))], })), firstAndLastTimes, + markers: null, })); + */ + + const trips = []; + // Compute drawing information for the trips of the reference journey pattern + for (const { code, staticSchedule, firstAndLastTimes, realTimeData, markers } of + this.journeyPatternMix.referenceJP.vehicleJourneys) { + const tripMarkers = []; + // If there are markers to add, add them + if (markers) { + // Deep clone markers + const leftOverMarkers = markers.slice(); + for (const [indexP, { time, distance }] of staticSchedule.entries()) { + for (const [indexM, marker] of leftOverMarkers.entries()) { + if (indexP !== staticSchedule.length - 1) { + const nextPosition = staticSchedule[indexP + 1]; + if (time < marker.time && marker.time < nextPosition.time) { + const x1 = distance; + const y1 = time; + const x2 = nextPosition.distance; + const y2 = nextPosition.time; + const yM = marker.time; + const xM = (((x1 - x2) / (y1 - y2)) * yM) + - (((x1 * y2) - (x2 * y1)) / (y1 - y2)); + marker.distance = xM; + tripMarkers.push(marker); + leftOverMarkers.splice(indexM, 1); + break; + } + } + } + } + } + trips.push({ + code, + // For the reference journey pattern there is only one sequence + staticSequences: [staticSchedule.map(({ time, distance }) => ({ time, distance }))], + realtimeSequences: realTimeData.map(({ vehicleNumber, positions }) => ({ + vehicleNumber, + // Again, only one sequence per vehicle for the reference journey pattern + sequences: [positions.map(({ time, distanceFromStart, status, prognosed }) => ({ + time, + distance: distanceFromStart, + status, + prognosed, + }))], + })), + markers: tripMarkers, + firstAndLastTimes, + }); + } // Then compute the trip drawing information for the other journey patterns that share // at least one link with the reference JP @@ -712,6 +764,34 @@ export default class MareyDiagram { })); } + // If there are markers to add, add them + if (vehicleJourney.markers) { + // Deep clone markers + const leftOverMarkers = vehicleJourney.markers.slice(); + for (const staticSequence of staticSequences) { + for (const [indexP, { time, distance }] of staticSequence.entries()) { + for (const [indexM, marker] of leftOverMarkers.entries()) { + if (indexP !== staticSequence.length - 1) { + const nextPosition = staticSequence[indexP + 1]; + if (time < marker.time && marker.time < nextPosition.time) { + const x1 = distance; + const y1 = time; + const x2 = nextPosition.distance; + const y2 = nextPosition.time; + const yM = marker.time; + const xM = (((x1 - x2) / (y1 - y2)) * yM) + - (((x1 * y2) - (x2 * y1)) / (y1 - y2)); + marker.distance = xM; + tripMarkers.push(marker); + leftOverMarkers.splice(indexM, 1); + break; + } + } + } + } + } + } + const realtimeSequences = []; // Iterate over each of the real time vehicles for (const { vehicleNumber, positions, markers } of vehicleJourney.realTimeData) { From 4dafc222af46ee7a38aaff08df45adbe12e98f1a Mon Sep 17 00:00:00 2001 From: Stefan de Konink Date: Sat, 9 Mar 2019 20:57:40 +0100 Subject: [PATCH 17/28] If a marker is placed at exactly the first stop. --- src/js/viz_components/mareydiagram.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/js/viz_components/mareydiagram.js b/src/js/viz_components/mareydiagram.js index cd57c6b..9b5be19 100644 --- a/src/js/viz_components/mareydiagram.js +++ b/src/js/viz_components/mareydiagram.js @@ -692,7 +692,7 @@ export default class MareyDiagram { for (const [indexM, marker] of leftOverMarkers.entries()) { if (indexP !== staticSchedule.length - 1) { const nextPosition = staticSchedule[indexP + 1]; - if (time < marker.time && marker.time < nextPosition.time) { + if (time <= marker.time && marker.time < nextPosition.time) { const x1 = distance; const y1 = time; const x2 = nextPosition.distance; @@ -773,7 +773,7 @@ export default class MareyDiagram { for (const [indexM, marker] of leftOverMarkers.entries()) { if (indexP !== staticSequence.length - 1) { const nextPosition = staticSequence[indexP + 1]; - if (time < marker.time && marker.time < nextPosition.time) { + if (time <= marker.time && marker.time < nextPosition.time) { const x1 = distance; const y1 = time; const x2 = nextPosition.distance; @@ -836,7 +836,7 @@ export default class MareyDiagram { for (const [indexM, marker] of leftOverMarkers.entries()) { if (indexP !== vehicleSequence.length - 1) { const nextPosition = vehicleSequence[indexP + 1]; - if (time < marker.time && marker.time < nextPosition.time) { + if (time <= marker.time && marker.time < nextPosition.time) { const x1 = distance; const y1 = time; const x2 = nextPosition.distance; From 1cc1a3955bbf8a47a84a653bcef57d9d3a95ad8e Mon Sep 17 00:00:00 2001 From: Stefan de Konink Date: Sat, 9 Mar 2019 20:58:22 +0100 Subject: [PATCH 18/28] Implement the option to load a generic markers.json per operational date. --- src/js/app.js | 18 +++++++++++++-- src/js/ptdataset.js | 53 ++++++++++++++------------------------------- src/js/ptds.js | 4 ++-- 3 files changed, 34 insertions(+), 41 deletions(-) diff --git a/src/js/app.js b/src/js/app.js index e399a16..0480916 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -43,6 +43,15 @@ const getSelectedDatasetURL = () => { return `${publicationInUse.url}${datasetInUse.filename}`; }; +const getSelectedDatasetMarkersURL = () => { + // Get the publication currently selected (date) + const publicationInUse = indexData.publications + .find(pub => pub.date === document.getElementById('day').value); + // Get the dataset currently selected (group of lines) within the publication selected + // Compute URL of dataset selected + return `${publicationInUse.url}markers.json`; +}; + // Load the available line-directions within this group of lines const loadAvailableLineDirections = () => { fetch(getSelectedDatasetURL()) @@ -110,7 +119,7 @@ const processIndex = () => { const defaultDatasetURL = `${publications[0].url}${publications[0].datasets[0].filename}`; Object.assign(options, { selectedDate: publications[0].date }); fetch(defaultDatasetURL).then(r => r.json()) - .then((defaultData) => { new PTDS(defaultData, options); }); + .then((defaultData) => { new PTDS(defaultData, options, null); }); }; // Form submission handler @@ -140,7 +149,12 @@ const formSubmit = (event) => { options.mode = 'spiralSimulation'; } Object.assign(options, { selectedDate: document.getElementById('day').value }); - new PTDS(data, options); + + const urlMarkersSelected = getSelectedDatasetMarkersURL(); + fetch(urlMarkersSelected).then(r => r.json()) + .then((markerdata) => { + new PTDS(data, options, markerdata); + }); }); }; diff --git a/src/js/ptdataset.js b/src/js/ptdataset.js index 98653a5..fa7f5ac 100644 --- a/src/js/ptdataset.js +++ b/src/js/ptdataset.js @@ -14,48 +14,17 @@ import TimeUtils from './timeutils'; * Class representing a public transport dataset */ export default class PTDataset { - constructor(inputData, referenceDate) { + constructor(inputData, referenceDate, markerData) { this.referenceDate = referenceDate; Object.assign(this, PTDataset.computeStopsAndStopAreas(inputData.scheduledStopPoints)); Object.assign(this, this.computeLinesJourneyPatterns(inputData.journeyPatterns)); this.vehicleJourneys = this.computeVehicleJourneys(inputData.vehicleJourneys); this.stopsLinks = this.computeLinks(); - - const testMarkers = [ - { - id: 123, - reference: { - vehicleJourneyCode: 'HTM:1:10003:2019-03-08', - vehicleNumber: 3124, - }, - time: new Date(1552020019000), - message: 'Test Marker Message', - url: 'http://example.org/', - }, - { - id: 124, - reference: { - vehicleJourneyCode: 'HTM:1:10239:2019-03-08', - vehicleNumber: null, - }, - time: new Date(1552076000000), - message: 'Test Marker Message', - url: 'http://example.org/', - }, - { - id: 125, - reference: { - vehicleJourneyCode: 'HTM:15:150006:2019-03-08', - vehicleNumber: null, - }, - time: new Date(1552019700000), - message: 'Test Marker Message', - url: 'http://example.org/', - }, - ]; - - this.addMarkersToDataset(testMarkers); - + this.markers = null; + if (markerData != null && markerData.markers != null) { + this.markers = this.computeMarkers(markerData.markers); + this.addMarkersToDataset(this.markers); + } // Compute times of the first and last stop of any journey in the dataset this.earliestTime = Math.min(...Object.values(this.journeyPatterns) @@ -253,4 +222,14 @@ export default class PTDataset { vehicleJourney => vehicleJourney.code, ); } + + computeMarkers(markers) { + return markers.map(({ id, reference, time, message, url }) => ({ + id, + reference, + time: TimeUtils.secondsToDateObject(time, this.referenceDate), + message, + url, + })); + } } diff --git a/src/js/ptds.js b/src/js/ptds.js index 8672bed..9b5d361 100644 --- a/src/js/ptds.js +++ b/src/js/ptds.js @@ -17,8 +17,8 @@ const d3 = Object.assign({}, { * Main class */ export default class PTDS { - constructor(inputData, options) { - this.data = new PTDataset(inputData, options.selectedDate); + constructor(inputData, options, markerData) { + this.data = new PTDataset(inputData, options.selectedDate, markerData); this.options = options; if (['dual', 'marey'].includes(options.mode)) { From 6be36b297e205d0cb97e10211252369ed887d74b Mon Sep 17 00:00:00 2001 From: Stefan de Konink Date: Sat, 9 Mar 2019 21:52:05 +0100 Subject: [PATCH 19/28] Onscreen remove --- src/js/app.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/js/app.js b/src/js/app.js index 95f78cf..8af7fe3 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -31,8 +31,6 @@ const options = { let indexData = {}; -const onscreen = false; - // Get the URL of the currently selected dataset const getSelectedDatasetURL = () => { // Get the publication currently selected (date) From 2e6059b9066b5fd7a97ba1b1ec5a0d98846f843d Mon Sep 17 00:00:00 2001 From: Stefan de Konink Date: Tue, 23 Apr 2019 15:38:28 +0200 Subject: [PATCH 20/28] Marker updates. --- src/js/viz_components/mareydiagram.js | 29 +++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/src/js/viz_components/mareydiagram.js b/src/js/viz_components/mareydiagram.js index 2e5a730..d7f0e1f 100644 --- a/src/js/viz_components/mareydiagram.js +++ b/src/js/viz_components/mareydiagram.js @@ -690,8 +690,14 @@ export default class MareyDiagram { marker.distance = xM; tripMarkers.push(marker); leftOverMarkers.splice(indexM, 1); - break; } + if ((indexP === staticSchedule.length - 2) + && marker.time.getTime() === nextPosition.time.getTime()) { + marker.distance = nextPosition.distance; + tripMarkers.push(marker); + leftOverMarkers.splice(indexM, 1); + } + if (leftOverMarkers.length === 0) break; } } } @@ -771,8 +777,14 @@ export default class MareyDiagram { marker.distance = xM; tripMarkers.push(marker); leftOverMarkers.splice(indexM, 1); - break; } + if ((indexP === staticSequence.length - 2) + && marker.time.getTime() === nextPosition.time.getTime()) { + marker.distance = nextPosition.distance; + tripMarkers.push(marker); + leftOverMarkers.splice(indexM, 1); + } + if (leftOverMarkers.length === 0) break; } } } @@ -839,8 +851,14 @@ export default class MareyDiagram { marker.distance = xM; tripMarkers.push(marker); leftOverMarkers.splice(indexM, 1); - break; } + if ((indexP === vehicleSequence.length - 2) + && marker.time.getTime() === nextPosition.time.getTime()) { + marker.distance = nextPosition.distance; + tripMarkers.push(marker); + leftOverMarkers.splice(indexM, 1); + } + if (leftOverMarkers.length === 0) break; } } } @@ -1088,6 +1106,9 @@ export default class MareyDiagram { // Trip enter + update > marker exit tripMarkersSel.exit().remove(); + const referenceJPstopsDistances = this.journeyPatternMix.referenceJP.distances; + const lastStopDistance = referenceJPstopsDistances[referenceJPstopsDistances.length - 1]; + // Trip enter + update > marker enter const tripMarkersGroup = tripMarkersSel.enter().append('g').attr('class', 'marker'); tripMarkersGroup @@ -1100,7 +1121,7 @@ export default class MareyDiagram { d3.select(this).select('text.message').remove(); }) .merge(tripMarkersSel) - .attr('transform', ({ distance, time }) => `translate(${this.xScale(distance)},${this.yScale(time)})`); + .attr('transform', ({ distance, time }) => `translate(${this.xScale(distance) + (distance === lastStopDistance ? -15 : 0)},${this.yScale(time)})`); tripMarkersGroup.append('image') .attr('width', 15) From 7b924333a82874ee9953deaa1e64c9c0bedc29b0 Mon Sep 17 00:00:00 2001 From: Stefan de Konink Date: Tue, 23 Apr 2019 16:47:06 +0200 Subject: [PATCH 21/28] Implement a line/direction label. --- src/js/ptds.js | 10 ++++++++++ src/js/viz_components/mareydiagram.js | 3 ++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/js/ptds.js b/src/js/ptds.js index 2769184..43ef7b5 100644 --- a/src/js/ptds.js +++ b/src/js/ptds.js @@ -145,6 +145,10 @@ export default class PTDS { this.dims.marey.innerWidth = this.dims.marey.outerWidth - margins.marey.left - margins.marey.right - this.dims.mareyScroll.width - this.dims.mareyStopSelection.width - 30; + margins.mareyLabel = { + left: margins.marey.left + this.dims.marey.innerWidth + 50, + top: 50, + }; margins.mareyScroll = { left: margins.marey.left + this.dims.marey.innerWidth + 100, top: margins.marey.top, @@ -174,6 +178,12 @@ export default class PTDS { // Create transformed groups and store their reference this.mareySVGgroups = { + label: mareySVG.append('g') + .attr('transform', `translate(${margins.mareyLabel.left}, ${margins.mareyLabel.top})`) + .append('text') + .text(`${this.options.line} - ${this.options.direction}`) + .attr('font-size', '16') + .attr('font-weight', 'bold'), diagram: mareySVG.append('g') .attr('transform', `translate(${margins.marey.left},${margins.marey.top})`), scroll: mareySVG.append('g') diff --git a/src/js/viz_components/mareydiagram.js b/src/js/viz_components/mareydiagram.js index d7f0e1f..fd6e1fd 100644 --- a/src/js/viz_components/mareydiagram.js +++ b/src/js/viz_components/mareydiagram.js @@ -33,7 +33,8 @@ export default class MareyDiagram { /** * * @param {Object} journeyPatternMix - Information to draw on the diagram - * @param {{diagram: Object, scroll: Object, stopSelection: Object}} svgGroups - SVG groups + * @param {{label: Object, diagram: Object, + * scroll: Object, stopSelection: Object}} svgGroups - SVG groups * for the diagram, the scroll and the stop selection * @param {Object} dims - Dimensions of the diagram * @param {Function} changeCallback - Callback for the time change From f46cdae9ddb5acfc3a688eac5bc9851706413233 Mon Sep 17 00:00:00 2001 From: Stefan de Konink Date: Wed, 24 Apr 2019 12:56:25 +0200 Subject: [PATCH 22/28] Support the feature request to only show a single line direction. --- src/index.html | 3 +++ src/js/app.js | 1 + src/js/ptds.js | 3 ++- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/index.html b/src/index.html index bdbfe1e..af6417d 100644 --- a/src/index.html +++ b/src/index.html @@ -37,6 +37,9 @@

Public Transport Decision Support System

diff --git a/src/js/app.js b/src/js/app.js index 8af7fe3..fbfa6cb 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -145,6 +145,7 @@ const formSubmit = (event) => { const [line, direction] = document.getElementById('line-direction').value.split(' - '); options.line = line; options.direction = parseInt(direction, 10); + options.overlap = document.getElementById('line-direction-overlap').checked; } else { options.mode = 'spiralSimulation'; } diff --git a/src/js/ptds.js b/src/js/ptds.js index 43ef7b5..7c7bcc9 100644 --- a/src/js/ptds.js +++ b/src/js/ptds.js @@ -80,7 +80,8 @@ export default class PTDS { // Compute the shared sequences between the longest journey pattern and all the other ones for (const journeyPattern of Object.values(this.data.journeyPatterns)) { - if (journeyPattern.code !== maxNstopsJP.code) { + if (journeyPattern.code !== maxNstopsJP.code + && (this.options.overlap || journeyPattern.line.code === maxNstopsJP.line.code)) { const sharedSequences = maxNstopsJP.sharedSequences(journeyPattern); if (sharedSequences) journeyPatternMix.otherJPs.push({ journeyPattern, sharedSequences }); } From 8606621027941741263d2b080a226b55e1f6588b Mon Sep 17 00:00:00 2001 From: Stefan de Konink Date: Thu, 25 Apr 2019 00:42:46 +0200 Subject: [PATCH 23/28] Very raw cut to allow trip selection by url arguments. --- src/js/app.js | 85 ++++++++++++++++++++++----- src/js/ptds.js | 8 +++ src/js/viz_components/mareydiagram.js | 23 +++++++- 3 files changed, 99 insertions(+), 17 deletions(-) diff --git a/src/js/app.js b/src/js/app.js index fbfa6cb..d474fe1 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -122,6 +122,30 @@ const processIndex = () => { .then((defaultData) => { new PTDS(defaultData, options, null); }); }; +// URL Hash trip selection +const urlHashTripSelection = (url, tripCode, date) => { + // Load the chosen dataset + fetch(url).then(r => r.json()) + .then((data) => { + // Empty the main div element + document.getElementById('main').innerHTML = ''; + // Remove the dat.GUI widget(s) if present + for (const dg of document.getElementsByClassName('dg main')) dg.remove(); + + const journeyPattern = data.journeyPatterns[data.vehicleJourneys[tripCode].journeyPatternRef]; + + // Create new visualization, using the specified mode. + const selectedMode = 'marey'; + options.mode = selectedMode; + options.line = journeyPattern.lineRef; + options.direction = journeyPattern.direction; + options.overlap = true; + options.trip = tripCode; + Object.assign(options, { selectedDate: date }); + new PTDS(data, options, null); + }); +}; + // Form submission handler const formSubmit = (event) => { // Prevent default form submit @@ -168,20 +192,49 @@ $(document).ready(() => { const indexFileURL = 'https://services.opengeo.nl/ptds/index.json'; fetch(indexFileURL).then(r => r.json()) // Process the index file when finished loading it - .then((data) => { indexData = data; processIndex(); }); - - // Activate sidebar plugin - $('#sidebar').simplerSidebar({ - init: 'opened', - selectors: { - trigger: '#toggle-sidebar', - quitter: '.close-sidebar', - }, - }); - // and make it visible again - document.getElementById('sidebar').style.visibility = 'visible'; - document.getElementById('navbar').style.visibility = 'visible'; - - // Handle new dataset/mode loading - document.getElementById('viz-options').onsubmit = formSubmit; + .then((data) => { + indexData = data; + + // Handle loading a trip from a URL + const { hash } = window.location; + if (hash !== '' && hash !== '#') { + const tripCode = hash.substring(1, hash.length); + const parts = tripCode.split(':'); + const lineCode = parts[1]; + const date = parts[3]; + const publication = indexData.publications.filter(e => (e.date === date)); + if (publication.length > 0) { + const publicationInUse = publication[0]; + const dataset = publicationInUse.datasets.filter(e => (e.lines.includes(lineCode))); + if (dataset.length > 0) { + const datasetInUse = dataset[0]; + const url = `${publicationInUse.url}${datasetInUse.filename}`; + urlHashTripSelection(url, tripCode, date); + document.getElementById('sidebar').style.visibility = 'hidden'; + document.getElementById('navbar').style.visibility = 'hidden'; + + return; + } + } + } + processIndex(); + }); + + const { hash } = window.location; + if (hash === '' || hash === '#') { + // Activate sidebar plugin + $('#sidebar').simplerSidebar({ + init: 'opened', + selectors: { + trigger: '#toggle-sidebar', + quitter: '.close-sidebar', + }, + }); + // and make it visible again + document.getElementById('sidebar').style.visibility = 'visible'; + document.getElementById('navbar').style.visibility = 'visible'; + + // Handle new dataset/mode loading + document.getElementById('viz-options').onsubmit = formSubmit; + } }); diff --git a/src/js/ptds.js b/src/js/ptds.js index 7c7bcc9..4acc573 100644 --- a/src/js/ptds.js +++ b/src/js/ptds.js @@ -296,6 +296,11 @@ export default class PTDS { ); } + let selectedTrip = null; + if (this.options.trip !== null) { + selectedTrip = this.data.vehicleJourneys[this.options.trip]; + } + // If we are in "dual" mode, draw the Marey diagram of the chosen journey pattern if (this.options.mode === 'dual') { // Callback that updates the map when the timeline is moved in the Marey diagram @@ -322,6 +327,7 @@ export default class PTDS { this.mareySVGgroups, this.dims, timelineChangeCallback, + selectedTrip, ); } else if (this.options.mode === 'marey') { // Creation of the Marey diagram @@ -329,6 +335,8 @@ export default class PTDS { this.journeyPatternMix, this.mareySVGgroups, this.dims, + null, + selectedTrip, ); } } diff --git a/src/js/viz_components/mareydiagram.js b/src/js/viz_components/mareydiagram.js index fd6e1fd..b7b8f70 100644 --- a/src/js/viz_components/mareydiagram.js +++ b/src/js/viz_components/mareydiagram.js @@ -38,11 +38,13 @@ export default class MareyDiagram { * for the diagram, the scroll and the stop selection * @param {Object} dims - Dimensions of the diagram * @param {Function} changeCallback - Callback for the time change + * @param {Object} trip - Initial selected trip */ - constructor(journeyPatternMix, svgGroups, dims, changeCallback) { + constructor(journeyPatternMix, svgGroups, dims, changeCallback, trip) { this.journeyPatternMix = journeyPatternMix; this.g = svgGroups; this.dims = dims; + this.trip = trip; // Compute information needed to draw the trips this.trips = this.computeTrips(); @@ -50,6 +52,25 @@ export default class MareyDiagram { this.initialSetup(changeCallback); // Draw the trips in the diagram this.drawTrips(); + + // Refactor this, so tripClick and below is the same + if (this.trip !== null) { + let { first, last } = this.trip.firstAndLastTimes; + first = d3.timeMinute.offset(first, -5); + last = d3.timeMinute.offset(last, +60); + // Update zoom status to reflect change in domain + this.g.diagram.call(this.zoomBehaviour.transform, d3.zoomIdentity + .scale(this.lastK) + .translate(0, -this.yScrollScale(first))); + // Update brush status to reflect change in domain + this.g.scroll + .call(this.brushBehaviour.move, [ + this.yScrollScale(first), + this.yScrollScale(last), + ]); + // Update Marey diagram domain + this.yScale.domain([first, last]); + } } /** From 8c5dd90f1c01a28d1b3f855d54d03b8ae59c1f8b Mon Sep 17 00:00:00 2001 From: Stefan de Konink Date: Thu, 25 Apr 2019 12:40:00 +0200 Subject: [PATCH 24/28] Highlight the selected trip --- src/js/viz_components/mareydiagram.js | 34 ++++++++++++++++++--------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/src/js/viz_components/mareydiagram.js b/src/js/viz_components/mareydiagram.js index b7b8f70..7b32050 100644 --- a/src/js/viz_components/mareydiagram.js +++ b/src/js/viz_components/mareydiagram.js @@ -26,6 +26,14 @@ const d3 = Object.assign({}, { zoomIdentity, }); +// TODO: move these constants in a separate config file +const selectedTripStaticStopRadius = 3; +const selectedTripRTposRadius = 3; +const selectedTripRadius = 6; +const deSelectedTripStaticStopRadius = 2; +const deSelectedTripRTposRadius = 2; +const deSelectedTripRadius = 3; + /** * This class manages the Marey diagram visualization. */ @@ -70,6 +78,14 @@ export default class MareyDiagram { ]); // Update Marey diagram domain this.yScale.domain([first, last]); + + const tripSel = d3.select(`g.trip[data-trip-code='${this.trip.code}']`); + // Add 'selected' class to the trip SVG group + tripSel.classed('selected', true); + tripSel.selectAll('circle.static-stop').attr('r', selectedTripStaticStopRadius); + tripSel.selectAll('circle.rt-position').attr('r', selectedTripRTposRadius); + // In the map, highlight the vehicle + d3.select(`#map g.trip[data-code='${this.trip.code}'] circle`).attr('r', selectedTripRadius); } } @@ -922,14 +938,6 @@ export default class MareyDiagram { * @param {number} transitionDuration - Duration of the transition in case of stop selection */ drawTrips(transitionDuration) { - // TODO: move these constants in a separate config file - const selectedTripStaticStopRadius = 3; - const selectedTripRTposRadius = 3; - const selectedTripRadius = 6; - const deSelectedTripStaticStopRadius = 2; - const deSelectedTripRTposRadius = 2; - const deSelectedTripRadius = 3; - // Determines if a trip is in the currently selected domain const tripInSelectedDomain = (trip) => { const [minShownTime, maxShownTime] = this.yScale.domain(); @@ -962,6 +970,7 @@ export default class MareyDiagram { function tripMouseOver(trip) { // Get the SVG g element corresponding to this trip const tripSel = d3.select(this); + // Get the current mouse position const [xPos, yPos] = d3.mouse(overlay.node()); // Add label with the code of the trip next to the mouse cursor @@ -983,12 +992,15 @@ export default class MareyDiagram { function tripMouseOut(trip) { // Similarly as above const tripSel = d3.select(this); - tripSel.select('text.tripLabel').remove(); - tripSel.classed('selected', false); + if (that.trip !== null && that.trip.code !== tripSel.datum().code) { + tripSel.classed('selected', false); + d3.select(`#map g.trip[data-code='${trip.code}'] circle`).attr('r', deSelectedTripRadius); + } + + tripSel.select('text.tripLabel').remove(); tripSel.selectAll('circle.static-stop').attr('r', deSelectedTripStaticStopRadius); tripSel.selectAll('circle.rt-position').attr('r', deSelectedTripRTposRadius); - d3.select(`#map g.trip[data-code='${trip.code}'] circle`).attr('r', deSelectedTripRadius); } // Handle click on a trip From 3677df0dcb1dbc0966a237729d746a4f97c37328 Mon Sep 17 00:00:00 2001 From: Stefan de Konink Date: Thu, 25 Apr 2019 13:32:10 +0200 Subject: [PATCH 25/28] Limit the view of the selection, and add the the trip code as hashvalue for the selection. --- src/js/viz_components/mareydiagram.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/js/viz_components/mareydiagram.js b/src/js/viz_components/mareydiagram.js index 7b32050..ac677c8 100644 --- a/src/js/viz_components/mareydiagram.js +++ b/src/js/viz_components/mareydiagram.js @@ -64,8 +64,8 @@ export default class MareyDiagram { // Refactor this, so tripClick and below is the same if (this.trip !== null) { let { first, last } = this.trip.firstAndLastTimes; - first = d3.timeMinute.offset(first, -5); - last = d3.timeMinute.offset(last, +60); + first = d3.timeMinute.offset(first, -1); + last = d3.timeMinute.offset(last, +1); // Update zoom status to reflect change in domain this.g.diagram.call(this.zoomBehaviour.transform, d3.zoomIdentity .scale(this.lastK) @@ -1021,6 +1021,9 @@ export default class MareyDiagram { // Update Marey diagram domain that.yScale.domain([first, last]); tripMouseOut.call(this, trip); + + // Update the Window Location Hash + window.location.hash = trip.code; } // Trip enter From 969c1a242d142db4d2d2362c55950a9b54845a72 Mon Sep 17 00:00:00 2001 From: Stefan de Konink Date: Thu, 25 Apr 2019 16:17:49 +0200 Subject: [PATCH 26/28] Bug fixes. --- src/js/ptds.js | 28 ++++++++++++++++++++------- src/js/viz_components/mareydiagram.js | 9 +++++---- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/src/js/ptds.js b/src/js/ptds.js index 4acc573..b58ed0f 100644 --- a/src/js/ptds.js +++ b/src/js/ptds.js @@ -177,14 +177,28 @@ export default class PTDS { .attr('width', this.dims.marey.outerWidth) .attr('height', this.dims.marey.outerHeight); + const label = mareySVG.append('g') + .attr('transform', `translate(${margins.mareyLabel.left}, ${margins.mareyLabel.top})`); + + label.append('text') + .text(`${this.options.line} - ${this.options.direction}`) + .attr('font-size', '16') + .attr('font-weight', 'bold'); + + label.append('text') + .attr('transform', 'translate(100, 0)') + .text('reverse') + .on('click', () => { + d3.select('#marey-container').remove(); + this.options.trip = null; + this.options.direction = (this.options.direction === 1 ? 2 : 1); + this.journeyPatternMix = this.computeJourneyPatternMix(); + this.createVisualizations(); + }); + // Create transformed groups and store their reference this.mareySVGgroups = { - label: mareySVG.append('g') - .attr('transform', `translate(${margins.mareyLabel.left}, ${margins.mareyLabel.top})`) - .append('text') - .text(`${this.options.line} - ${this.options.direction}`) - .attr('font-size', '16') - .attr('font-weight', 'bold'), + label, diagram: mareySVG.append('g') .attr('transform', `translate(${margins.marey.left},${margins.marey.top})`), scroll: mareySVG.append('g') @@ -297,7 +311,7 @@ export default class PTDS { } let selectedTrip = null; - if (this.options.trip !== null) { + if (this.options.trip !== undefined) { selectedTrip = this.data.vehicleJourneys[this.options.trip]; } diff --git a/src/js/viz_components/mareydiagram.js b/src/js/viz_components/mareydiagram.js index ac677c8..d232dc5 100644 --- a/src/js/viz_components/mareydiagram.js +++ b/src/js/viz_components/mareydiagram.js @@ -52,7 +52,8 @@ export default class MareyDiagram { this.journeyPatternMix = journeyPatternMix; this.g = svgGroups; this.dims = dims; - this.trip = trip; + + this.trip = (trip !== undefined ? trip : null); // Compute information needed to draw the trips this.trips = this.computeTrips(); @@ -993,14 +994,14 @@ export default class MareyDiagram { // Similarly as above const tripSel = d3.select(this); - if (that.trip !== null && that.trip.code !== tripSel.datum().code) { + if (that.trip === null || that.trip.code !== tripSel.datum().code) { tripSel.classed('selected', false); d3.select(`#map g.trip[data-code='${trip.code}'] circle`).attr('r', deSelectedTripRadius); + tripSel.selectAll('circle.static-stop').attr('r', deSelectedTripStaticStopRadius); + tripSel.selectAll('circle.rt-position').attr('r', deSelectedTripRTposRadius); } tripSel.select('text.tripLabel').remove(); - tripSel.selectAll('circle.static-stop').attr('r', deSelectedTripStaticStopRadius); - tripSel.selectAll('circle.rt-position').attr('r', deSelectedTripRTposRadius); } // Handle click on a trip From b755f0a0c70f215fc068c5e6e8dfc52ce059d7ad Mon Sep 17 00:00:00 2001 From: Stefan de Konink Date: Thu, 25 Apr 2019 17:32:29 +0200 Subject: [PATCH 27/28] Update view --- src/js/app.js | 2 +- src/js/ptds.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/js/app.js b/src/js/app.js index d474fe1..b43138f 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -135,7 +135,7 @@ const urlHashTripSelection = (url, tripCode, date) => { const journeyPattern = data.journeyPatterns[data.vehicleJourneys[tripCode].journeyPatternRef]; // Create new visualization, using the specified mode. - const selectedMode = 'marey'; + const selectedMode = 'dual'; options.mode = selectedMode; options.line = journeyPattern.lineRef; options.direction = journeyPattern.direction; diff --git a/src/js/ptds.js b/src/js/ptds.js index b58ed0f..dcb5f43 100644 --- a/src/js/ptds.js +++ b/src/js/ptds.js @@ -189,6 +189,7 @@ export default class PTDS { .attr('transform', 'translate(100, 0)') .text('reverse') .on('click', () => { + d3.select('#map').remove(); d3.select('#marey-container').remove(); this.options.trip = null; this.options.direction = (this.options.direction === 1 ? 2 : 1); From bd94795488f798754e957939dbd822e0864d56c6 Mon Sep 17 00:00:00 2001 From: Stefan de Konink Date: Thu, 23 May 2019 01:26:56 +0200 Subject: [PATCH 28/28] Realtime disable options --- src/index.html | 6 ++ src/js/app.js | 49 +++++------ src/js/ptds.js | 23 +++-- src/js/viz_components/mareydiagram.js | 120 +++++++++++++------------- 4 files changed, 110 insertions(+), 88 deletions(-) diff --git a/src/index.html b/src/index.html index af6417d..7c9107b 100644 --- a/src/index.html +++ b/src/index.html @@ -41,6 +41,12 @@

Public Transport Decision Support System

+ + + + +
+ diff --git a/src/js/app.js b/src/js/app.js index b43138f..2558fca 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -116,10 +116,10 @@ const processIndex = () => { /* eslint no-new: "off" */ // Fetch default dataset and create its corresponding visualization - const defaultDatasetURL = `${publications[0].url}${publications[0].datasets[0].filename}`; + // const defaultDatasetURL = `${publications[0].url}${publications[0].datasets[0].filename}`; Object.assign(options, { selectedDate: publications[0].date }); - fetch(defaultDatasetURL).then(r => r.json()) - .then((defaultData) => { new PTDS(defaultData, options, null); }); + // fetch(defaultDatasetURL).then(r => r.json()) + // .then((defaultData) => { new PTDS(defaultData, options, null); }); }; // URL Hash trip selection @@ -135,11 +135,12 @@ const urlHashTripSelection = (url, tripCode, date) => { const journeyPattern = data.journeyPatterns[data.vehicleJourneys[tripCode].journeyPatternRef]; // Create new visualization, using the specified mode. - const selectedMode = 'dual'; + const selectedMode = 'marey'; options.mode = selectedMode; options.line = journeyPattern.lineRef; options.direction = journeyPattern.direction; options.overlap = true; + options.realtime = true; options.trip = tripCode; Object.assign(options, { selectedDate: date }); new PTDS(data, options, null); @@ -170,6 +171,7 @@ const formSubmit = (event) => { options.line = line; options.direction = parseInt(direction, 10); options.overlap = document.getElementById('line-direction-overlap').checked; + options.realtime = document.getElementById('realtime').checked; } else { options.mode = 'spiralSimulation'; } @@ -194,6 +196,7 @@ $(document).ready(() => { // Process the index file when finished loading it .then((data) => { indexData = data; + processIndex(); // Handle loading a trip from a URL const { hash } = window.location; @@ -202,39 +205,37 @@ $(document).ready(() => { const parts = tripCode.split(':'); const lineCode = parts[1]; const date = parts[3]; + document.getElementById('day').value = date; + document.getElementById('mode').value = 'marey'; + const publication = indexData.publications.filter(e => (e.date === date)); if (publication.length > 0) { const publicationInUse = publication[0]; const dataset = publicationInUse.datasets.filter(e => (e.lines.includes(lineCode))); if (dataset.length > 0) { const datasetInUse = dataset[0]; + document.getElementById('lines-groups').value = datasetInUse.filename; const url = `${publicationInUse.url}${datasetInUse.filename}`; urlHashTripSelection(url, tripCode, date); - document.getElementById('sidebar').style.visibility = 'hidden'; - document.getElementById('navbar').style.visibility = 'hidden'; - - return; } } } - processIndex(); }); const { hash } = window.location; - if (hash === '' || hash === '#') { - // Activate sidebar plugin - $('#sidebar').simplerSidebar({ - init: 'opened', - selectors: { - trigger: '#toggle-sidebar', - quitter: '.close-sidebar', - }, - }); - // and make it visible again - document.getElementById('sidebar').style.visibility = 'visible'; - document.getElementById('navbar').style.visibility = 'visible'; - // Handle new dataset/mode loading - document.getElementById('viz-options').onsubmit = formSubmit; - } + // Activate sidebar plugin + $('#sidebar').simplerSidebar({ + init: (hash === '' || hash === '#') ? 'opened' : 'closed', + selectors: { + trigger: '#toggle-sidebar', + quitter: '.close-sidebar', + }, + }); + // and make it visible again + document.getElementById('sidebar').style.visibility = 'visible'; + document.getElementById('navbar').style.visibility = 'visible'; + + // Handle new dataset/mode loading + document.getElementById('viz-options').onsubmit = formSubmit; }); diff --git a/src/js/ptds.js b/src/js/ptds.js index dcb5f43..61015f4 100644 --- a/src/js/ptds.js +++ b/src/js/ptds.js @@ -20,9 +20,10 @@ const d3 = Object.assign({}, { export default class PTDS { constructor(inputData, options, markerData) { this.marey = null; - this.data = new PTDataset(inputData, options.selectedDate, markerData); + this.options = options; + this.data = new PTDataset(inputData, this.options.selectedDate, markerData); - if (this.data.updateUrl !== undefined) { + if (this.options.realtime === true && this.data.updateUrl !== undefined) { this.dataUpdateTimer = d3.interval(() => { if (this.marey !== null) { fetch(this.data.updateUrl).then(r => r.json()).then((updateData) => { @@ -33,11 +34,9 @@ export default class PTDS { }, 15000, 15000); } - this.options = options; - - if (['dual', 'marey'].includes(options.mode)) { + if (['dual', 'marey'].includes(this.options.mode)) { this.journeyPatternMix = this.computeJourneyPatternMix(); - } else if (options.mode === 'spiralSimulation') { + } else if (this.options.mode === 'spiralSimulation') { this.widgetTimeFormat = d3.timeFormat('%Y-%m-%d %H:%M:%S'); this.createSimulationWidget(); } @@ -197,6 +196,16 @@ export default class PTDS { this.createVisualizations(); }); + label.append('text') + .attr('transform', 'translate(150, 0)') + .text('realtime') + .on('click', () => { + d3.select('#map').remove(); + d3.select('#marey-container').remove(); + this.options.realtime = !this.options.realtime; + this.createVisualizations(); + }); + // Create transformed groups and store their reference this.mareySVGgroups = { label, @@ -343,6 +352,7 @@ export default class PTDS { this.dims, timelineChangeCallback, selectedTrip, + this.options.realtime, ); } else if (this.options.mode === 'marey') { // Creation of the Marey diagram @@ -352,6 +362,7 @@ export default class PTDS { this.dims, null, selectedTrip, + this.options.realtime, ); } } diff --git a/src/js/viz_components/mareydiagram.js b/src/js/viz_components/mareydiagram.js index d232dc5..c8f9891 100644 --- a/src/js/viz_components/mareydiagram.js +++ b/src/js/viz_components/mareydiagram.js @@ -47,11 +47,13 @@ export default class MareyDiagram { * @param {Object} dims - Dimensions of the diagram * @param {Function} changeCallback - Callback for the time change * @param {Object} trip - Initial selected trip + * @param {Boolean} realtime - Show realtime information */ - constructor(journeyPatternMix, svgGroups, dims, changeCallback, trip) { + constructor(journeyPatternMix, svgGroups, dims, changeCallback, trip, realtime) { this.journeyPatternMix = journeyPatternMix; this.g = svgGroups; this.dims = dims; + this.realtime = realtime; this.trip = (trip !== undefined ? trip : null); @@ -1075,65 +1077,67 @@ export default class MareyDiagram { .attr('cx', ({ distance }) => this.xScale(distance)) .attr('cy', ({ time }) => this.yScale(time)); - // Trip enter + update > realtime vehicle sequences selection - const realtimeVehiclesSel = tripsEnterUpdateSel - .selectAll('g.vehicle') - .data(({ realtimeSequences }) => realtimeSequences); - - // Trip enter + update > realtime vehicle sequences exit - realtimeVehiclesSel.exit().remove(); - - // Trip enter + update > realtime vehicle sequences enter - const realtimeVehiclesEnterUpdateSel = realtimeVehiclesSel.enter() - .append('g') - .attr('class', 'vehicle') - .attr('data-vehicle-number', ({ vehicleNumber }) => vehicleNumber) - // Trip enter + update > realtime vehicle sequences enter + update - .merge(realtimeVehiclesSel); - - // Trip enter + update > realtime vehicle sequences > realtime link selection - const realtimeVehiclesLinksSel = realtimeVehiclesEnterUpdateSel - // const realtimeVehiclesEnterUpdateSel - .selectAll('path.rt-sequence') - // Compute the realtime links for each sequence and make a single array out of it - .data(({ sequences }) => flatten(sequences.map(sequence => MareyDiagram - .getRealtimePaths(sequence)))); - - // Trip enter + update > realtime vehicle sequences > realtime link exit - realtimeVehiclesLinksSel.exit().remove(); - - // // Trip enter + update > realtime vehicle sequences > realtime link enter - realtimeVehiclesLinksSel.enter() - .append('path') - // Trip enter + update > realtime vehicle sequences > realtime link enter + update - .merge(realtimeVehiclesLinksSel) - .attr('class', ({ status }) => `rt-sequence ${status}`) - .classed('prognosed', ({ prognosed }) => prognosed) - .transition() - .duration(transitionDuration) - .attr('d', ({ positions }) => this.tripLineGenerator(positions)); + if (this.realtime) { + // Trip enter + update > realtime vehicle sequences selection + const realtimeVehiclesSel = tripsEnterUpdateSel + .selectAll('g.vehicle') + .data(({ realtimeSequences }) => realtimeSequences); + + // Trip enter + update > realtime vehicle sequences exit + realtimeVehiclesSel.exit().remove(); + + // Trip enter + update > realtime vehicle sequences enter + const realtimeVehiclesEnterUpdateSel = realtimeVehiclesSel.enter() + .append('g') + .attr('class', 'vehicle') + .attr('data-vehicle-number', ({ vehicleNumber }) => vehicleNumber) + // Trip enter + update > realtime vehicle sequences enter + update + .merge(realtimeVehiclesSel); + + // Trip enter + update > realtime vehicle sequences > realtime link selection + const realtimeVehiclesLinksSel = realtimeVehiclesEnterUpdateSel + // const realtimeVehiclesEnterUpdateSel + .selectAll('path.rt-sequence') + // Compute the realtime links for each sequence and make a single array out of it + .data(({ sequences }) => flatten(sequences.map(sequence => MareyDiagram + .getRealtimePaths(sequence)))); + + // Trip enter + update > realtime vehicle sequences > realtime link exit + realtimeVehiclesLinksSel.exit().remove(); + + // Trip enter + update > realtime vehicle sequences > realtime link enter + realtimeVehiclesLinksSel.enter() + .append('path') + // Trip enter + update > realtime vehicle sequences > realtime link enter + update + .merge(realtimeVehiclesLinksSel) + .attr('class', ({ status }) => `rt-sequence ${status}`) + .classed('prognosed', ({ prognosed }) => prognosed) + .transition() + .duration(transitionDuration) + .attr('d', ({ positions }) => this.tripLineGenerator(positions)); + + // Trip enter + update > realtime vehicle sequences > realtime position selection + const realtimeVehiclesPositionsSel = realtimeVehiclesEnterUpdateSel + .selectAll('circle.rt-position') + // Draw the circles representing the positions only at the maximum zoom level + .data(({ sequences }) => (this.currentApproximation.showDots ? flatten(sequences) : [])); + + // Trip enter + update > realtime vehicle sequences > realtime position exit + realtimeVehiclesPositionsSel.exit().remove(); - // Trip enter + update > realtime vehicle sequences > realtime position selection - const realtimeVehiclesPositionsSel = realtimeVehiclesEnterUpdateSel - .selectAll('circle.rt-position') - // Draw the circles representing the positions only at the maximum zoom level - .data(({ sequences }) => (this.currentApproximation.showDots ? flatten(sequences) : [])); - - // Trip enter + update > realtime vehicle sequences > realtime position exit - realtimeVehiclesPositionsSel.exit().remove(); - - // Trip enter + update > realtime vehicle sequences > realtime position enter - realtimeVehiclesPositionsSel.enter() - .append('circle') - .attr('class', ({ status }) => `rt-position ${status}`) - .classed('prognosed', ({ prognosed }) => prognosed) - .attr('r', deSelectedTripRTposRadius) - .merge(realtimeVehiclesPositionsSel) - .transition() - .duration(transitionDuration) - .attr('cx', ({ distance }) => this.xScale(distance)) // Trip enter + update > realtime vehicle sequences > realtime position enter - .attr('cy', ({ time }) => this.yScale(time)); + realtimeVehiclesPositionsSel.enter() + .append('circle') + .attr('class', ({ status }) => `rt-position ${status}`) + .classed('prognosed', ({ prognosed }) => prognosed) + .attr('r', deSelectedTripRTposRadius) + .merge(realtimeVehiclesPositionsSel) + .transition() + .duration(transitionDuration) + .attr('cx', ({ distance }) => this.xScale(distance)) + // Trip enter + update > realtime vehicle sequences > realtime position enter + .attr('cy', ({ time }) => this.yScale(time)); + } // Draw the markers at the end so that they are on top of everything else // Trip enter + update > marker selection