diff --git a/src/components/Map.jsx b/src/components/Map.jsx index 5e9abe1..0e31603 100644 --- a/src/components/Map.jsx +++ b/src/components/Map.jsx @@ -25,6 +25,7 @@ class Map extends React.Component { mapTransformX: 0, mapTransformY: 0 } + this.styleLocation = this.styleLocation.bind(this) } componentDidMount(){ @@ -157,12 +158,24 @@ class Map extends React.Component { ); } + /** + * Determines additional styles on the for each location. + * A location consists of an array of events (location.events). The function + * also has full access to the domain and redux state to derive values if + * necessary. The function should return a regular style object. + */ + styleLocation(location) { + return { + fill: 'orange' + } + } + renderEvents() { return ( ({ /* TODO: add styles by location */ })} + styleLocation={this.styleLocation} categories={this.props.domain.categories} map={this.map} mapTransformX={this.state.mapTransformX} diff --git a/src/components/Timeline.jsx b/src/components/Timeline.jsx index a833272..9043fc2 100644 --- a/src/components/Timeline.jsx +++ b/src/components/Timeline.jsx @@ -18,6 +18,7 @@ import TimelineCategories from './TimelineCategories.jsx'; class Timeline extends React.Component { constructor(props) { super(props); + this.styleDatetime = this.styleDatetime.bind(this) this.svgRef = React.createRef() this.state = { isFolded: false, @@ -210,67 +211,18 @@ class Timeline extends React.Component { this.props.methods.onUpdateTimerange(this.state.timerange); } - renderSVG() { - const dims = this.state.dims; - - return ( - - - - { this.onDragStart() }} - onDrag={() => { this.onDrag() }} - onDragEnd={() => { this.onDragEnd() }} - categories={this.props.domain.categories} - /> - { this.onMoveTime(dir) }} - /> - { this.onApplyZoom(zoom); }} - /> - - this.getEventX(e)} - getEventY={(e) => this.getEventY(e)} - transitionDuration={this.state.transitionDuration} - /> - this.getEventX(e)} - getEventY={(e) => this.getEventY(e)} - getCategoryColor={this.props.methods.getCategoryColor} - transitionDuration={this.state.transitionDuration} - onSelect={this.props.methods.onSelect} - /> - - ) + styleDatetime(timestamp) { + return { + fill: 'orange' + } } render() { const { isNarrative, app, ui } = this.props let classes = `timeline-wrapper ${(this.state.isFolded) ? ' folded' : ''}`; classes += (app.narrative !== null) ? ' narrative-mode' : ''; + const dims = this.state.dims; + return ( - {this.renderSVG()} + + + + { this.onDragStart() }} + onDrag={() => { this.onDrag() }} + onDragEnd={() => { this.onDragEnd() }} + categories={this.props.domain.categories} + /> + { this.onMoveTime(dir) }} + /> + { this.onApplyZoom(zoom); }} + /> + + this.getEventX(e)} + getEventY={(e) => this.getEventY(e)} + transitionDuration={this.state.transitionDuration} + /> + this.getEventX(e)} + getDatetimeY={(e) => this.getEventY(e)} + getCategoryColor={this.props.methods.getCategoryColor} + transitionDuration={this.state.transitionDuration} + onSelect={this.props.methods.onSelect} + /> + @@ -294,7 +296,7 @@ function mapStateToProps(state) { return { isNarrative: !!state.app.narrative, domain: { - events: state.domain.events, + datetimes: selectors.selectDatetimes(state), categories: selectors.selectCategories(state), narratives: state.domain.narratives }, diff --git a/src/components/presentational/TimelineEvents.js b/src/components/presentational/TimelineEvents.js index 2ce4086..93b45dc 100644 --- a/src/components/presentational/TimelineEvents.js +++ b/src/components/presentational/TimelineEvents.js @@ -1,21 +1,30 @@ import React from 'react'; -const TimelineEvents = ({ events, narrative, getEventX, getEventY, - getCategoryColor, onSelect, transitionDuration }) => { - +const TimelineEvents = ({ + datetimes, + narrative, + getDatetimeX, + getDatetimeY, + getCategoryColor, + onSelect, + transitionDuration, + styleDatetime +}) => { function getAllEventsAtOnce(eventPoint) { - const timestamp = eventPoint.timestamp; + const datetime = eventPoint.datetime; const category = eventPoint.category; return events - .filter(event => (event.timestamp === timestamp && category === event.category)) + .filter(event => (event.datetime === datetime && category === event.category)) } - function renderEvent(event) { - let styleProps = ({ - fill: getCategoryColor(event.category), - fillOpacity: 0.8, - transform: `translate(${getEventX(event)}px, ${getEventY(event)}px)`, - transition: `transform ${transitionDuration / 1000}s ease` + function renderDatetime(datetime) { + const customStyles = styleDatetime ? styleDatetime(datetime) : null + const styleProps = ({ + fill: getCategoryColor(datetime.events[0].category), + fillOpacity: 1, + transform: `translate(${getDatetimeX(datetime)}px, ${getDatetimeY(datetime)}px)`, + transition: `transform ${transitionDuration / 1000}s ease`, + ...customStyles }); if (narrative) { @@ -23,10 +32,7 @@ const TimelineEvents = ({ events, narrative, getEventX, getEventY, const isInNarrative = steps.map(s => s.id).includes(event.id) if (!isInNarrative) { - styleProps = { - ...styleProps, - fillOpacity: 0.1 - } + return null } } @@ -36,7 +42,7 @@ const TimelineEvents = ({ events, narrative, getEventX, getEventY, cx={0} cy={0} style={styleProps} - r={5} + r={5} onClick={() => {onSelect(getAllEventsAtOnce(event))}} > @@ -47,9 +53,9 @@ const TimelineEvents = ({ events, narrative, getEventX, getEventY, - {events.map(event => renderEvent(event))} + {datetimes.map(datetime => renderDatetime(datetime))} ); } -export default TimelineEvents; \ No newline at end of file +export default TimelineEvents; diff --git a/src/selectors/index.js b/src/selectors/index.js index d560a00..4f03b5e 100644 --- a/src/selectors/index.js +++ b/src/selectors/index.js @@ -23,7 +23,6 @@ export const getTagsFilter = state => state.app.filters.tags export const getTimeRange = state => state.app.filters.timerange - /** * Some handy helpers */ @@ -148,9 +147,15 @@ export const selectActiveNarrative = createSelector( ? { ...narrative, current } : null ) + /** - * Of all the filtered events, group them by location and return a list of - * locations with at least one event in it, based on the time range and tags + * Group events by location. Each location is an object: + { + events: [...], + label: 'Location name', + latitude: '47.7', + longitude: '32.2' + } */ export const selectLocations = createSelector( [selectEvents], @@ -171,11 +176,39 @@ export const selectLocations = createSelector( } } }) - return Object.values(selectedLocations) } ) +/** + * Group events by 'datetime'. Each datetime is an object: + { + timestamp: '', + date: '8/23/2016', + time: '12:00', + events: [...] + } + */ +export const selectDatetimes = createSelector( + [selectEvents], + events => { + const datetimes = {} + events.forEach(event => { + const { timestamp } = event + if (datetimes.hasOwnProperty(timestamp)) { + datetimes[timestamp].events.push(event) + } else { + datetimes[timestamp] = { + timestamp: event.timestamp, + date: event.date, + time: event.time, + events: [event] + } + } + }) + return Object.values(datetimes) + } +) /** diff --git a/src/store/initial.js b/src/store/initial.js index ceed1e4..ba29a5d 100644 --- a/src/store/initial.js +++ b/src/store/initial.js @@ -113,7 +113,6 @@ const initial = { beta: '#ff0000', other: '#f3de2c' }, - narratives: { default: { opacity: 0.9,