From 8d36671a486364f783ef5d4efd1ceb3eb4681014 Mon Sep 17 00:00:00 2001 From: Franc Camps-Febrer Date: Tue, 8 Jan 2019 14:09:24 +0100 Subject: [PATCH 1/9] Focus timeline zoom on narrative --- src/reducers/app.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/reducers/app.js b/src/reducers/app.js index 8140d7a..d2ea62a 100644 --- a/src/reducers/app.js +++ b/src/reducers/app.js @@ -36,11 +36,29 @@ function updateSelected(appState, action) { } function updateNarrative(appState, action) { + let minTime = appState.filters.timerange[0]; + let maxTime = appState.filters.timerange[1]; + + if (!!action.narrative) { + minTime = parseDate('2100-01-01T00:00:00'); + maxTime = parseDate('1900-01-01T00:00:00'); + + action.narrative.steps.forEach(step => { + const stepTime = parseDate(step.timestamp); + if (stepTime < minTime) minTime = stepTime; + if (stepTime > maxTime) maxTime = stepTime; + }); + } + return { ...appState, narrative: action.narrative, narrativeState: { current: !!action.narrative ? 0 : null + }, + filters: { + ...appState.filters, + timerange: [minTime, maxTime] } } } From e1dc09301bc426126ae87d80132225593b40bf43 Mon Sep 17 00:00:00 2001 From: Franc Camps-Febrer Date: Tue, 8 Jan 2019 15:43:07 +0100 Subject: [PATCH 2/9] Set map bounds on narrative --- src/components/Map.jsx | 6 +++++- src/reducers/app.js | 12 +++++++++++- src/store/initial.js | 1 + 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/components/Map.jsx b/src/components/Map.jsx index 5849087..894fcf2 100644 --- a/src/components/Map.jsx +++ b/src/components/Map.jsx @@ -44,7 +44,10 @@ class Map extends React.Component { this.map.setView([eventPoint.latitude, eventPoint.longitude]); } } - } + if (hash(nextProps.app.mapBounds) !== hash(this.props.app.mapBounds) && nextProps.app.mapBounds !== null) { + this.map.fitBounds(nextProps.app.mapBounds); + } + } initializeMap() { /** @@ -246,6 +249,7 @@ function mapStateToProps(state) { selected: state.app.selected, highlighted: state.app.highlighted, mapAnchor: state.app.mapAnchor, + mapBounds: state.app.filters.mapBounds narrative: state.app.narrative, flags: { isShowingSites: state.app.flags.isShowingSites diff --git a/src/reducers/app.js b/src/reducers/app.js index d2ea62a..7b8f828 100644 --- a/src/reducers/app.js +++ b/src/reducers/app.js @@ -39,6 +39,10 @@ function updateNarrative(appState, action) { let minTime = appState.filters.timerange[0]; let maxTime = appState.filters.timerange[1]; + let cornerBound0 = [180, 180]; + let cornerBound1 = [-180, -180]; + + // Compute narrative time range and map bounds if (!!action.narrative) { minTime = parseDate('2100-01-01T00:00:00'); maxTime = parseDate('1900-01-01T00:00:00'); @@ -47,6 +51,11 @@ function updateNarrative(appState, action) { const stepTime = parseDate(step.timestamp); if (stepTime < minTime) minTime = stepTime; if (stepTime > maxTime) maxTime = stepTime; + + if (+step.longitude < cornerBound0[1]) cornerBound0[1] = +step.longitude; + if (+step.longitude > cornerBound1[1]) cornerBound1[1] = +step.longitude; + if (+step.latitude < cornerBound0[0]) cornerBound0[0] = +step.latitude; + if (+step.latitude > cornerBound1[0]) cornerBound1[0] = +step.latitude; }); } @@ -58,7 +67,8 @@ function updateNarrative(appState, action) { }, filters: { ...appState.filters, - timerange: [minTime, maxTime] + timerange: [minTime, maxTime], + mapBounds: [cornerBound0, cornerBound1] } } } diff --git a/src/store/initial.js b/src/store/initial.js index ba29a5d..dbfbd19 100644 --- a/src/store/initial.js +++ b/src/store/initial.js @@ -41,6 +41,7 @@ const initial = { d3.timeParse("%Y-%m-%dT%H:%M:%S")("2013-02-23T12:00:00"), d3.timeParse("%Y-%m-%dT%H:%M:%S")("2016-02-23T12:00:00") ], + mapBounds: null, tags: [], categories: [], views: { From 88092c711de4b6b4340af902fe37c6b67ec3af37 Mon Sep 17 00:00:00 2001 From: Franc Camps-Febrer Date: Tue, 8 Jan 2019 17:37:20 +0100 Subject: [PATCH 3/9] Add buffer to time zoom on narrative --- src/reducers/app.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/reducers/app.js b/src/reducers/app.js index 7b8f828..2dd4832 100644 --- a/src/reducers/app.js +++ b/src/reducers/app.js @@ -57,6 +57,9 @@ function updateNarrative(appState, action) { if (+step.latitude < cornerBound0[0]) cornerBound0[0] = +step.latitude; if (+step.latitude > cornerBound1[0]) cornerBound1[0] = +step.latitude; }); + + minTime = new Date(minTime.getTime() - Math.abs((maxTime - minTime) / 10)); + maxTime = new Date(maxTime.getTime() + Math.abs((maxTime - minTime) / 10)); } return { From b84e59cd2885a4974b83dde5d23b958dfcd6b3bb Mon Sep 17 00:00:00 2001 From: Franc Camps-Febrer Date: Tue, 8 Jan 2019 17:44:21 +0100 Subject: [PATCH 4/9] Center timeline on event select --- src/components/Timeline.jsx | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/components/Timeline.jsx b/src/components/Timeline.jsx index fc3f15b..eded3c1 100644 --- a/src/components/Timeline.jsx +++ b/src/components/Timeline.jsx @@ -58,6 +58,12 @@ class Timeline extends React.Component { scaleY: this.makeScaleY(nextProps.domain.categories) }); } + + if (hash(nextProps.app.selected) !== hash(this.props.app.selected)) { + if (nextProps.app.selected !== null) { + this.onCenterTime(parseDate(nextProps.app.selected[0].timestamp)); + } + } } addEventListeners() { @@ -132,6 +138,17 @@ class Timeline extends React.Component { }); } + onCenterTime(newCentralTime) { + const extent = this.getTimeScaleExtent(); + + const domain0 = d3.timeMinute.offset(newCentralTime, -extent/2); + const domainF = d3.timeMinute.offset(newCentralTime, +extent/2); + + this.setState({ timerange: [domain0, domainF] }, () => { + this.props.methods.onUpdateTimerange(this.state.timerange); + }); + } + /** * Change display of time range * WITHOUT updating the store, or data shown. From aa3da2d744f86d8bd2c0d6e8c156eb95ace613b4 Mon Sep 17 00:00:00 2001 From: Franc Camps-Febrer Date: Tue, 8 Jan 2019 17:52:09 +0100 Subject: [PATCH 5/9] Add secure check to set map bounds with narrative --- src/reducers/app.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/reducers/app.js b/src/reducers/app.js index 2dd4832..3f9eb9c 100644 --- a/src/reducers/app.js +++ b/src/reducers/app.js @@ -52,10 +52,12 @@ function updateNarrative(appState, action) { if (stepTime < minTime) minTime = stepTime; if (stepTime > maxTime) maxTime = stepTime; - if (+step.longitude < cornerBound0[1]) cornerBound0[1] = +step.longitude; - if (+step.longitude > cornerBound1[1]) cornerBound1[1] = +step.longitude; - if (+step.latitude < cornerBound0[0]) cornerBound0[0] = +step.latitude; - if (+step.latitude > cornerBound1[0]) cornerBound1[0] = +step.latitude; + if (!!step.longitude && !!step.latitude) { + if (+step.longitude < cornerBound0[1]) cornerBound0[1] = +step.longitude; + if (+step.longitude > cornerBound1[1]) cornerBound1[1] = +step.longitude; + if (+step.latitude < cornerBound0[0]) cornerBound0[0] = +step.latitude; + if (+step.latitude > cornerBound1[0]) cornerBound1[0] = +step.latitude; + } }); minTime = new Date(minTime.getTime() - Math.abs((maxTime - minTime) / 10)); @@ -71,7 +73,7 @@ function updateNarrative(appState, action) { filters: { ...appState.filters, timerange: [minTime, maxTime], - mapBounds: [cornerBound0, cornerBound1] + mapBounds: (action.narrative) ? [cornerBound0, cornerBound1] : null } } } From 7beb5f4039ebe30ab7fb8f1e7ee5bf7f4649ed29 Mon Sep 17 00:00:00 2001 From: Franc Camps-Febrer Date: Wed, 9 Jan 2019 07:52:52 +0100 Subject: [PATCH 6/9] Make map on narrative center in first, keep eth in scope --- src/components/Map.jsx | 24 +++++++++++++----------- src/components/Timeline.jsx | 4 ++-- src/reducers/app.js | 17 +++++++++++++++++ 3 files changed, 32 insertions(+), 13 deletions(-) diff --git a/src/components/Map.jsx b/src/components/Map.jsx index 894fcf2..14f3fd2 100644 --- a/src/components/Map.jsx +++ b/src/components/Map.jsx @@ -35,17 +35,19 @@ class Map extends React.Component { } componentWillReceiveProps(nextProps) { - if (hash(nextProps.app.selected) !== hash(this.props.app.selected)) { - - // Fly to first of events selected - const eventPoint = (nextProps.app.selected.length > 0) ? nextProps.app.selected[0] : null; - - if (eventPoint !== null && eventPoint.latitude && eventPoint.longitude) { - this.map.setView([eventPoint.latitude, eventPoint.longitude]); - } - } - if (hash(nextProps.app.mapBounds) !== hash(this.props.app.mapBounds) && nextProps.app.mapBounds !== null) { - this.map.fitBounds(nextProps.app.mapBounds); + // Set appropriate zoom for narrative + if (hash(nextProps.app.mapBounds) !== hash(this.props.app.mapBounds) + && nextProps.app.mapBounds !== null) { + this.map.fitBounds(nextProps.app.mapBounds); + } else { + if (hash(nextProps.app.selected) !== hash(this.props.app.selected)) { + // Fly to first of events selected + const eventPoint = (nextProps.app.selected.length > 0) ? nextProps.app.selected[0] : null; + + if (eventPoint !== null && eventPoint.latitude && eventPoint.longitude) { + this.map.setView([eventPoint.latitude, eventPoint.longitude]); + } + } } } diff --git a/src/components/Timeline.jsx b/src/components/Timeline.jsx index eded3c1..e454ec7 100644 --- a/src/components/Timeline.jsx +++ b/src/components/Timeline.jsx @@ -60,8 +60,8 @@ class Timeline extends React.Component { } if (hash(nextProps.app.selected) !== hash(this.props.app.selected)) { - if (nextProps.app.selected !== null) { - this.onCenterTime(parseDate(nextProps.app.selected[0].timestamp)); + if (!!nextProps.app.selected && nextProps.app.selected.length > 0) { + this.onCenterTime(parseDate(nextProps.app.selected[0].timestamp)); } } } diff --git a/src/reducers/app.js b/src/reducers/app.js index 3f9eb9c..a17ec9a 100644 --- a/src/reducers/app.js +++ b/src/reducers/app.js @@ -47,6 +47,7 @@ function updateNarrative(appState, action) { minTime = parseDate('2100-01-01T00:00:00'); maxTime = parseDate('1900-01-01T00:00:00'); + // Find max and mins coordinates of narrative events action.narrative.steps.forEach(step => { const stepTime = parseDate(step.timestamp); if (stepTime < minTime) minTime = stepTime; @@ -59,7 +60,23 @@ function updateNarrative(appState, action) { if (+step.latitude > cornerBound1[0]) cornerBound1[0] = +step.latitude; } }); + // Adjust bounds to center around first event, while keeping visible all others + // Takes first event, finds max ditance with first attempt bounds, and use this max distance + // on the other side, both in latitude and longitude + const first = action.narrative.steps[0]; + if (!!first.longitude && !!first.latitude) { + const firstToLong0 = Math.abs(+first.longitude - cornerBound0[1]); + const firstToLong1 = Math.abs(+first.longitude - cornerBound1[1]); + const firstToLat0 = Math.abs(+first.latitude - cornerBound0[0]); + const firstToLat1 = Math.abs(+first.latitude - cornerBound1[0]); + if (firstToLong0 > firstToLong1) cornerBound1[1] = +first.longitude + firstToLong0; + if (firstToLong0 < firstToLong1) cornerBound0[1] = +first.longitude - firstToLong1; + if (firstToLat0 > firstToLat1) cornerBound1[0] = +first.latitude + firstToLat0; + if (firstToLat0 < firstToLat1) cornerBound0[0] = +first.latitude - firstToLat1; + } + + // Add some buffer on both sides of the time extent minTime = new Date(minTime.getTime() - Math.abs((maxTime - minTime) / 10)); maxTime = new Date(maxTime.getTime() + Math.abs((maxTime - minTime) / 10)); } From 34c18a61021f9ff12c970afdf029a65bcf96bd9e Mon Sep 17 00:00:00 2001 From: Franc Camps-Febrer Date: Wed, 9 Jan 2019 08:19:16 +0100 Subject: [PATCH 7/9] Resize timeline on entering narrative mode --- src/components/Timeline.jsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/components/Timeline.jsx b/src/components/Timeline.jsx index e454ec7..e912529 100644 --- a/src/components/Timeline.jsx +++ b/src/components/Timeline.jsx @@ -68,6 +68,11 @@ class Timeline extends React.Component { addEventListeners() { window.addEventListener('resize', () => { this.computeDims(); }); + let element = document.querySelector('.timeline-wrapper'); + element.addEventListener("transitionend", (event) => { + this.computeDims(); + }, false); + } makeScaleX() { @@ -109,7 +114,8 @@ class Timeline extends React.Component { const boundingClient = document.querySelector(`#${dom}`).getBoundingClientRect(); this.setState({ - dims: Object.assign({}, this.state.dims, { width: boundingClient.width }) + dims: Object.assign({}, this.state.dims, { width: boundingClient.width }) + }, () => { this.setState({ scaleX: this.makeScaleX() }) }); } } From 0df32f6a4dba5c6405ab2217a4a7078f107a2410 Mon Sep 17 00:00:00 2001 From: Franc Camps-Febrer Date: Wed, 9 Jan 2019 17:09:53 +0100 Subject: [PATCH 8/9] Avoid transition end event trigger multiple times --- src/components/Map.jsx | 2 +- src/components/Timeline.jsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Map.jsx b/src/components/Map.jsx index 14f3fd2..8b0dd40 100644 --- a/src/components/Map.jsx +++ b/src/components/Map.jsx @@ -251,7 +251,7 @@ function mapStateToProps(state) { selected: state.app.selected, highlighted: state.app.highlighted, mapAnchor: state.app.mapAnchor, - mapBounds: state.app.filters.mapBounds + mapBounds: state.app.filters.mapBounds, narrative: state.app.narrative, flags: { isShowingSites: state.app.flags.isShowingSites diff --git a/src/components/Timeline.jsx b/src/components/Timeline.jsx index e912529..535fa27 100644 --- a/src/components/Timeline.jsx +++ b/src/components/Timeline.jsx @@ -71,7 +71,7 @@ class Timeline extends React.Component { let element = document.querySelector('.timeline-wrapper'); element.addEventListener("transitionend", (event) => { this.computeDims(); - }, false); + }, { once: true }); } From 41e49b2c38f44221fe05a26678e4f1d9d233dcde Mon Sep 17 00:00:00 2001 From: Lachlan Kermode Date: Sun, 13 Jan 2019 11:53:08 +0100 Subject: [PATCH 9/9] fix merge break --- src/components/presentational/TimelineEvents.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/components/presentational/TimelineEvents.js b/src/components/presentational/TimelineEvents.js index 035ab64..8c9678f 100644 --- a/src/components/presentational/TimelineEvents.js +++ b/src/components/presentational/TimelineEvents.js @@ -35,7 +35,15 @@ const TimelineEvents = ({ function renderDatetime(datetime) { if (narrative) { const { steps } = narrative - const isInNarrative = steps.map(s => s.id).includes(event.id) + // check all events in the datetime before rendering in narrative + let isInNarrative = false + for (let i = 0; i < datetime.events.length; i++) { + const event = datetime.events[i] + if (steps.map(s => s.id).includes(event.id)) { + isInNarrative = true + break; + } + } if (!isInNarrative) { return null