mirror of
https://github.com/bellingcat/ukraine-timemap.git
synced 2026-06-07 19:08:37 +03:00
Merge pull request #80 from forensic-architecture/first-zoom-narrative
First zoom narrative
This commit is contained in:
@@ -35,16 +35,21 @@ 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]);
|
||||
}
|
||||
// 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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
initializeMap() {
|
||||
/**
|
||||
@@ -246,6 +251,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
|
||||
|
||||
@@ -58,10 +58,21 @@ 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 && nextProps.app.selected.length > 0) {
|
||||
this.onCenterTime(parseDate(nextProps.app.selected[0].timestamp));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addEventListeners() {
|
||||
window.addEventListener('resize', () => { this.computeDims(); });
|
||||
let element = document.querySelector('.timeline-wrapper');
|
||||
element.addEventListener("transitionend", (event) => {
|
||||
this.computeDims();
|
||||
}, { once: true });
|
||||
|
||||
}
|
||||
|
||||
makeScaleX() {
|
||||
@@ -103,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() })
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
* WITHOUT updating the store, or data shown.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -36,11 +36,61 @@ function updateSelected(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 {
|
||||
...appState,
|
||||
narrative: action.narrative,
|
||||
narrativeState: {
|
||||
current: !!action.narrative ? 0 : null
|
||||
},
|
||||
filters: {
|
||||
...appState.filters,
|
||||
timerange: [minTime, maxTime],
|
||||
mapBounds: (action.narrative) ? [cornerBound0, cornerBound1] : null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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: {
|
||||
|
||||
Reference in New Issue
Block a user