Merge pull request #80 from forensic-architecture/first-zoom-narrative

First zoom narrative
This commit is contained in:
Franc Camps-Febrer
2019-01-14 09:00:19 -05:00
committed by GitHub
5 changed files with 99 additions and 11 deletions

View File

@@ -35,16 +35,21 @@ class Map extends React.Component {
} }
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps) {
if (hash(nextProps.app.selected) !== hash(this.props.app.selected)) { // Set appropriate zoom for narrative
if (hash(nextProps.app.mapBounds) !== hash(this.props.app.mapBounds)
// Fly to first of events selected && nextProps.app.mapBounds !== null) {
const eventPoint = (nextProps.app.selected.length > 0) ? nextProps.app.selected[0] : null; this.map.fitBounds(nextProps.app.mapBounds);
} else {
if (eventPoint !== null && eventPoint.latitude && eventPoint.longitude) { if (hash(nextProps.app.selected) !== hash(this.props.app.selected)) {
this.map.setView([eventPoint.latitude, eventPoint.longitude]); // 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]);
}
}
} }
} }
initializeMap() { initializeMap() {
/** /**
@@ -246,6 +251,7 @@ function mapStateToProps(state) {
selected: state.app.selected, selected: state.app.selected,
highlighted: state.app.highlighted, highlighted: state.app.highlighted,
mapAnchor: state.app.mapAnchor, mapAnchor: state.app.mapAnchor,
mapBounds: state.app.filters.mapBounds,
narrative: state.app.narrative, narrative: state.app.narrative,
flags: { flags: {
isShowingSites: state.app.flags.isShowingSites isShowingSites: state.app.flags.isShowingSites

View File

@@ -58,10 +58,21 @@ class Timeline extends React.Component {
scaleY: this.makeScaleY(nextProps.domain.categories) scaleY: this.makeScaleY(nextProps.domain.categories)
}); });
} }
if (hash(nextProps.app.selected) !== hash(this.props.app.selected)) {
if (!!nextProps.app.selected && nextProps.app.selected.length > 0) {
this.onCenterTime(parseDate(nextProps.app.selected[0].timestamp));
}
}
} }
addEventListeners() { addEventListeners() {
window.addEventListener('resize', () => { this.computeDims(); }); window.addEventListener('resize', () => { this.computeDims(); });
let element = document.querySelector('.timeline-wrapper');
element.addEventListener("transitionend", (event) => {
this.computeDims();
}, { once: true });
} }
makeScaleX() { makeScaleX() {
@@ -103,7 +114,8 @@ class Timeline extends React.Component {
const boundingClient = document.querySelector(`#${dom}`).getBoundingClientRect(); const boundingClient = document.querySelector(`#${dom}`).getBoundingClientRect();
this.setState({ 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() })
}); });
} }
} }
@@ -132,6 +144,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 * Change display of time range
* WITHOUT updating the store, or data shown. * WITHOUT updating the store, or data shown.

View File

@@ -35,7 +35,15 @@ const TimelineEvents = ({
function renderDatetime(datetime) { function renderDatetime(datetime) {
if (narrative) { if (narrative) {
const { steps } = 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) { if (!isInNarrative) {
return null return null

View File

@@ -36,11 +36,61 @@ function updateSelected(appState, action) {
} }
function updateNarrative(appState, action) { 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');
// Find max and mins coordinates of narrative events
action.narrative.steps.forEach(step => {
const stepTime = parseDate(step.timestamp);
if (stepTime < minTime) minTime = stepTime;
if (stepTime > maxTime) maxTime = stepTime;
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;
}
});
// 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));
}
return { return {
...appState, ...appState,
narrative: action.narrative, narrative: action.narrative,
narrativeState: { narrativeState: {
current: !!action.narrative ? 0 : null current: !!action.narrative ? 0 : null
},
filters: {
...appState.filters,
timerange: [minTime, maxTime],
mapBounds: (action.narrative) ? [cornerBound0, cornerBound1] : null
} }
} }
} }

View File

@@ -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")("2013-02-23T12:00:00"),
d3.timeParse("%Y-%m-%dT%H:%M:%S")("2016-02-23T12:00:00") d3.timeParse("%Y-%m-%dT%H:%M:%S")("2016-02-23T12:00:00")
], ],
mapBounds: null,
tags: [], tags: [],
categories: [], categories: [],
views: { views: {