From 99d1b8fbc1e230ff4954b45685c1091d220354e0 Mon Sep 17 00:00:00 2001 From: Lachlan Kermode Date: Mon, 7 Jan 2019 09:19:49 +0000 Subject: [PATCH 01/10] increase min zoom --- src/components/Map.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Map.jsx b/src/components/Map.jsx index 36608ba..d934cff 100644 --- a/src/components/Map.jsx +++ b/src/components/Map.jsx @@ -52,7 +52,7 @@ class Map extends React.Component { const map = L.map(this.props.mapId) .setView(this.props.app.mapAnchor, 14) - .setMinZoom(10) + .setMinZoom(7) .setMaxZoom(18) .setMaxBounds([[180, -180], [-180, 180]]) From ff68fb1cadc18a67355a92c5102a3887f7bbca21 Mon Sep 17 00:00:00 2001 From: Lachlan Kermode Date: Tue, 8 Jan 2019 10:11:18 +0000 Subject: [PATCH 02/10] styleLocation prop in MapEvents --- src/components/Map.jsx | 1 + src/components/MapEvents.jsx | 49 +++++++++++++++++++++++++++++------- 2 files changed, 41 insertions(+), 9 deletions(-) diff --git a/src/components/Map.jsx b/src/components/Map.jsx index d934cff..5e9abe1 100644 --- a/src/components/Map.jsx +++ b/src/components/Map.jsx @@ -162,6 +162,7 @@ class Map extends React.Component { ({ /* TODO: add styles by location */ })} categories={this.props.domain.categories} map={this.map} mapTransformX={this.state.mapTransformX} diff --git a/src/components/MapEvents.jsx b/src/components/MapEvents.jsx index 70cac85..9d08144 100644 --- a/src/components/MapEvents.jsx +++ b/src/components/MapEvents.jsx @@ -27,7 +27,7 @@ class MapEvents extends React.Component { } renderCategory(events, category) { - let styleProps = ({ + let styles = ({ fill: this.props.getCategoryColor(category), fillOpacity: 0.8 }) @@ -38,8 +38,8 @@ class MapEvents extends React.Component { const eventsInNarrative = events.filter(onlyIfInNarrative) if (eventsInNarrative.length <= 0) { - styleProps = { - ...styleProps, + styles = { + ...styles, fillOpacity: 0.1 } } @@ -48,8 +48,8 @@ class MapEvents extends React.Component { return ( 0) ? Math.sqrt(16 * events.length) + 3 : 0} - style={styleProps} + r={7} + style={styles} onClick={() => this.props.onSelect(events)} > @@ -57,17 +57,48 @@ class MapEvents extends React.Component { } renderLocation(location) { + /** + { + events: [...], + label: 'Location name', + latitude: '47.7', + longitude: '32.2' + } + */ const { x, y } = this.projectPoint([location.latitude, location.longitude]); - const eventsByCategory = this.getLocationEventsDistribution(location); + // const eventsByCategory = this.getLocationEventsDistribution(location); + + const locCategory = location.events.length > 0 ? location.events[0].category : 'default' + const customStyles = this.props.styleLocation ? this.props.styleLocation(location) : null + const styles = ({ + fill: this.props.getCategoryColor(locCategory), + fillOpacity: 1, + ...customStyles + }) + + // in narrative mode, only render events in narrative + if (this.props.narrative) { + const { steps } = this.props.narrative + const onlyIfInNarrative = e => steps.map(s => s.id).includes(e.id) + const eventsInNarrative = location.events.filter(onlyIfInNarrative) + + if (eventsInNarrative.length <= 0) { + return null + } + } return ( - {Object.keys(eventsByCategory).map(cat => { - return this.renderCategory(eventsByCategory[cat], cat) - })} + this.props.onSelect(events)} + > + ) } From 8e6c07d19409691daa819553302694e986fff0a9 Mon Sep 17 00:00:00 2001 From: Lachlan Kermode Date: Tue, 8 Jan 2019 11:37:47 +0000 Subject: [PATCH 03/10] add datetime (analog of location in Map) in Timeline --- src/components/Map.jsx | 15 ++- src/components/Timeline.jsx | 116 +++++++++--------- .../presentational/TimelineEvents.js | 42 ++++--- src/selectors/index.js | 41 ++++++- src/store/initial.js | 1 - 5 files changed, 134 insertions(+), 81 deletions(-) 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, From 8526bf069aeceb5294801c8b36dc68282b853e1d Mon Sep 17 00:00:00 2001 From: Lachlan Kermode Date: Tue, 8 Jan 2019 11:42:06 +0000 Subject: [PATCH 04/10] fix timeline selection --- src/components/presentational/TimelineEvents.js | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/components/presentational/TimelineEvents.js b/src/components/presentational/TimelineEvents.js index 93b45dc..d604202 100644 --- a/src/components/presentational/TimelineEvents.js +++ b/src/components/presentational/TimelineEvents.js @@ -10,13 +10,6 @@ const TimelineEvents = ({ transitionDuration, styleDatetime }) => { - function getAllEventsAtOnce(eventPoint) { - const datetime = eventPoint.datetime; - const category = eventPoint.category; - return events - .filter(event => (event.datetime === datetime && category === event.category)) - } - function renderDatetime(datetime) { const customStyles = styleDatetime ? styleDatetime(datetime) : null const styleProps = ({ @@ -43,7 +36,7 @@ const TimelineEvents = ({ cy={0} style={styleProps} r={5} - onClick={() => {onSelect(getAllEventsAtOnce(event))}} + onClick={() => onSelect(datetime.events)} > ) From fcfe84ce9034c4b0d63a7b0137e75c163d3d2b24 Mon Sep 17 00:00:00 2001 From: Lachlan Kermode Date: Tue, 8 Jan 2019 12:02:02 +0000 Subject: [PATCH 05/10] allow custom styles in datetimes passed from Timeline --- src/components/Map.jsx | 7 ++-- src/components/MapEvents.jsx | 40 ++++--------------- src/components/Timeline.jsx | 7 ++-- .../presentational/TimelineEvents.js | 28 ++++++++----- 4 files changed, 33 insertions(+), 49 deletions(-) diff --git a/src/components/Map.jsx b/src/components/Map.jsx index 0e31603..ef6f89d 100644 --- a/src/components/Map.jsx +++ b/src/components/Map.jsx @@ -165,9 +165,10 @@ class Map extends React.Component { * necessary. The function should return a regular style object. */ styleLocation(location) { - return { - fill: 'orange' - } + return [ + { fill: 'orange' }, + () => ciao + ] } renderEvents() { diff --git a/src/components/MapEvents.jsx b/src/components/MapEvents.jsx index 9d08144..3c2141c 100644 --- a/src/components/MapEvents.jsx +++ b/src/components/MapEvents.jsx @@ -26,36 +26,6 @@ class MapEvents extends React.Component { return eventCount; } - renderCategory(events, category) { - let styles = ({ - fill: this.props.getCategoryColor(category), - fillOpacity: 0.8 - }) - - if (this.props.narrative) { - const { steps } = this.props.narrative - const onlyIfInNarrative = e => steps.map(s => s.id).includes(e.id) - const eventsInNarrative = events.filter(onlyIfInNarrative) - - if (eventsInNarrative.length <= 0) { - styles = { - ...styles, - fillOpacity: 0.1 - } - } - } - - return ( - this.props.onSelect(events)} - > - - ); - } - renderLocation(location) { /** { @@ -64,16 +34,19 @@ class MapEvents extends React.Component { latitude: '47.7', longitude: '32.2' } - */ + */ const { x, y } = this.projectPoint([location.latitude, location.longitude]); - // const eventsByCategory = this.getLocationEventsDistribution(location); + // const eventsByCategory = this.getLocationEventsDistribution(location); const locCategory = location.events.length > 0 ? location.events[0].category : 'default' const customStyles = this.props.styleLocation ? this.props.styleLocation(location) : null + const extraStyles = customStyles[0] + const extraRender = customStyles[1] + const styles = ({ fill: this.props.getCategoryColor(locCategory), fillOpacity: 1, - ...customStyles + ...customStyles[0] }) // in narrative mode, only render events in narrative @@ -99,6 +72,7 @@ class MapEvents extends React.Component { onClick={() => this.props.onSelect(events)} > + {extraRender ? extraRender() : null} ) } diff --git a/src/components/Timeline.jsx b/src/components/Timeline.jsx index 9043fc2..7f9bb2e 100644 --- a/src/components/Timeline.jsx +++ b/src/components/Timeline.jsx @@ -212,9 +212,10 @@ class Timeline extends React.Component { } styleDatetime(timestamp) { - return { - fill: 'orange' - } + return [ + { fill: 'orange' }, + () => ciao + ] } render() { diff --git a/src/components/presentational/TimelineEvents.js b/src/components/presentational/TimelineEvents.js index d604202..c4c1bb5 100644 --- a/src/components/presentational/TimelineEvents.js +++ b/src/components/presentational/TimelineEvents.js @@ -12,12 +12,14 @@ const TimelineEvents = ({ }) => { function renderDatetime(datetime) { const customStyles = styleDatetime ? styleDatetime(datetime) : null + const extraStyles = customStyles[0] + const extraRender = customStyles[1] + 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 + ...extraStyles }); if (narrative) { @@ -30,15 +32,21 @@ const TimelineEvents = ({ } return ( - onSelect(datetime.events)} + - + onSelect(datetime.events)} + > + + { extraRender ? extraRender() : null } + ) } From 56b86778278e05fda0abd0a0fc61d41231a7a525 Mon Sep 17 00:00:00 2001 From: Lachlan Kermode Date: Tue, 8 Jan 2019 12:23:13 +0000 Subject: [PATCH 06/10] clean component interfaces --- src/components/Dashboard.jsx | 1 - src/components/Map.jsx | 18 ++++++++++-------- src/components/Timeline.jsx | 10 +++++++++- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/src/components/Dashboard.jsx b/src/components/Dashboard.jsx index 18a77f6..077e0f2 100644 --- a/src/components/Dashboard.jsx +++ b/src/components/Dashboard.jsx @@ -103,7 +103,6 @@ class Dashboard extends React.Component { }} /> for each location. - * A location consists of an array of events (location.events). The function + * A location consists of an array of events (see selectors). 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. + * necessary. The function should return an array, where the value at the + * first index is a styles object for the SVG at the location, and the value + * at the second index is an optional function that renders additional + * components in the div. */ styleLocation(location) { return [ { fill: 'orange' }, - () => ciao ] } @@ -217,8 +219,8 @@ class Map extends React.Component { return (
-
- {(this.map !== null) ? this.renderSVG() : ''} +
+ {(this.map !== null) ? this.renderTiles() : ''} {(this.map !== null) ? this.renderMarkers() : ''} {(this.map !== null) && isShowingSites ? this.renderSites() : ''} {(this.map !== null) ? this.renderEvents() : ''} diff --git a/src/components/Timeline.jsx b/src/components/Timeline.jsx index 7f9bb2e..4accb68 100644 --- a/src/components/Timeline.jsx +++ b/src/components/Timeline.jsx @@ -211,10 +211,18 @@ class Timeline extends React.Component { this.props.methods.onUpdateTimerange(this.state.timerange); } + /** + * Determines additional styles on the for each location. + * A datetime consists of an array of events (see selectors). The function + * also has full access to the domain and redux state to derive values if + * necessary. The function should return an array, where the value at the + * first index is a styles object for the SVG at the location, and the value + * at the second index is an optional function that renders additional + * components in the div. + */ styleDatetime(timestamp) { return [ { fill: 'orange' }, - () => ciao ] } From 899e06d560468b9bb95fefc73a39d02b12435f46 Mon Sep 17 00:00:00 2001 From: Lachlan Kermode Date: Tue, 8 Jan 2019 13:00:33 +0000 Subject: [PATCH 07/10] add event counts for locations --- src/components/Map.jsx | 4 +++- src/components/MapEvents.jsx | 2 +- src/components/Timeline.jsx | 4 +--- src/components/presentational/TimelineEvents.js | 2 +- src/scss/map.scss | 14 +++++++++----- 5 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/components/Map.jsx b/src/components/Map.jsx index 42334c2..5849087 100644 --- a/src/components/Map.jsx +++ b/src/components/Map.jsx @@ -168,8 +168,10 @@ class Map extends React.Component { * components in the div. */ styleLocation(location) { + const noEvents = location.events.length return [ - { fill: 'orange' }, + null, + () => noEvents > 1 ? {noEvents} : null ] } diff --git a/src/components/MapEvents.jsx b/src/components/MapEvents.jsx index 3c2141c..68c4fe4 100644 --- a/src/components/MapEvents.jsx +++ b/src/components/MapEvents.jsx @@ -64,12 +64,12 @@ class MapEvents extends React.Component { this.props.onSelect(location.events)} > this.props.onSelect(events)} > {extraRender ? extraRender() : null} diff --git a/src/components/Timeline.jsx b/src/components/Timeline.jsx index 4accb68..7673d7f 100644 --- a/src/components/Timeline.jsx +++ b/src/components/Timeline.jsx @@ -221,9 +221,7 @@ class Timeline extends React.Component { * components in the div. */ styleDatetime(timestamp) { - return [ - { fill: 'orange' }, - ] + return [] } render() { diff --git a/src/components/presentational/TimelineEvents.js b/src/components/presentational/TimelineEvents.js index c4c1bb5..32f7448 100644 --- a/src/components/presentational/TimelineEvents.js +++ b/src/components/presentational/TimelineEvents.js @@ -35,6 +35,7 @@ const TimelineEvents = ({ onSelect(datetime.events)} > onSelect(datetime.events)} > { extraRender ? extraRender() : null } diff --git a/src/scss/map.scss b/src/scss/map.scss index a49b34c..a1d90b2 100644 --- a/src/scss/map.scss +++ b/src/scss/map.scss @@ -165,14 +165,13 @@ * Elements */ +.location { + cursor: pointer; +} + .location-event-marker { fill: $event_default; stroke-width: 0; - cursor: pointer; - - &:hover { - fill-opacity: 1; - } } .path-polyline { @@ -180,5 +179,10 @@ stroke-width: 2px; } +.location-count { + z-index: 100; + fill: #a4a4a4; +} + From 1506c62f502deb7087c36adb0f17cd69ad61a7b2 Mon Sep 17 00:00:00 2001 From: Lachlan Kermode Date: Wed, 9 Jan 2019 15:15:04 +0000 Subject: [PATCH 08/10] render one dot per category in a datetime --- src/components/Timeline.jsx | 33 ++++---- src/components/presentational/DatetimeDot.js | 27 +++++++ .../presentational/TimelineEvents.js | 77 ++++++++++++------- 3 files changed, 88 insertions(+), 49 deletions(-) create mode 100644 src/components/presentational/DatetimeDot.js diff --git a/src/components/Timeline.jsx b/src/components/Timeline.jsx index 7673d7f..a107f55 100644 --- a/src/components/Timeline.jsx +++ b/src/components/Timeline.jsx @@ -19,6 +19,7 @@ class Timeline extends React.Component { constructor(props) { super(props); this.styleDatetime = this.styleDatetime.bind(this) + this.getDatetimeX = this.getDatetimeX.bind(this) this.svgRef = React.createRef() this.state = { isFolded: false, @@ -82,22 +83,6 @@ class Timeline extends React.Component { } } - /** - * Get x position of eventPoint, considering the time scale - * @param {object} eventPoint: regular eventPoint data - */ - getEventX(eventPoint) { - return this.state.scaleX(parseDate(eventPoint.timestamp)); - } - - /** - * Get y height of eventPoint, considering the ordinal Y scale - * @param {object} eventPoint: regular eventPoint data - */ - getEventY(eventPoint) { - return this.state.scaleY(eventPoint.category); - } - /** * Returns the time scale (x) extent in minutes */ @@ -211,8 +196,16 @@ class Timeline extends React.Component { this.props.methods.onUpdateTimerange(this.state.timerange); } + getDatetimeX(dt) { + return this.state.scaleX(parseDate(dt.timestamp)) + } + /** - * Determines additional styles on the for each location. + * Determines additional styles on the for each timestamp. Note that + * timestamp visualisation functions slightly differently from locations, as + * a timestamp can be shown as multiple s (one per category of the + * events contained therein). Thus the function below has a category as an + * argumnent as well, in case timestamps ought to be styled per category. * A datetime consists of an array of events (see selectors). The function * also has full access to the domain and redux state to derive values if * necessary. The function should return an array, where the value at the @@ -220,7 +213,7 @@ class Timeline extends React.Component { * at the second index is an optional function that renders additional * components in the div. */ - styleDatetime(timestamp) { + styleDatetime(timestamp, category) { return [] } @@ -285,8 +278,8 @@ class Timeline extends React.Component { datetimes={this.props.domain.datetimes} styleDatetime={this.styleDatetime} narrative={this.props.app.narrative} - getDatetimeX={(e) => this.getEventX(e)} - getDatetimeY={(e) => this.getEventY(e)} + getDatetimeX={this.getDatetimeX} + getCategoryY={this.state.scaleY} getCategoryColor={this.props.methods.getCategoryColor} transitionDuration={this.state.transitionDuration} onSelect={this.props.methods.onSelect} diff --git a/src/components/presentational/DatetimeDot.js b/src/components/presentational/DatetimeDot.js new file mode 100644 index 0000000..3167821 --- /dev/null +++ b/src/components/presentational/DatetimeDot.js @@ -0,0 +1,27 @@ +import React from 'react' + +export default ({ + category, + events, + x, + y, + styleProps, + extraRender +}) => ( + onSelect(datetime.events)} + > + + + { extraRender ? extraRender() : null } + +) + diff --git a/src/components/presentational/TimelineEvents.js b/src/components/presentational/TimelineEvents.js index 32f7448..17966ed 100644 --- a/src/components/presentational/TimelineEvents.js +++ b/src/components/presentational/TimelineEvents.js @@ -1,27 +1,38 @@ import React from 'react'; +import DatetimeDot from './DatetimeDot' + +// return a list of lists, where each list corresponds to a single category +function getDotsToRender(events) { + // each datetime needs to render as many dots as there are distinct + // categories in the events contained by the datetime. + // To this end, eventsByCategory is an intermediate data structure that + // groups a datetime's events by distinct categories + const eventsByCategory = {} + events.forEach(ev => { + if (eventsByCategory[ev.category]) { + eventsByCategory[ev.category].events.push((ev)) + } else { + eventsByCategory[ev.category] = { + category: ev.category, + events: [ ev ] + } + } + }) + + return Object.values(eventsByCategory) +} const TimelineEvents = ({ datetimes, narrative, getDatetimeX, - getDatetimeY, + getCategoryY, getCategoryColor, onSelect, transitionDuration, styleDatetime }) => { function renderDatetime(datetime) { - const customStyles = styleDatetime ? styleDatetime(datetime) : null - const extraStyles = customStyles[0] - const extraRender = customStyles[1] - - const styleProps = ({ - fill: getCategoryColor(datetime.events[0].category), - fillOpacity: 1, - transition: `transform ${transitionDuration / 1000}s ease`, - ...extraStyles - }); - if (narrative) { const { steps } = narrative const isInNarrative = steps.map(s => s.id).includes(event.id) @@ -31,23 +42,31 @@ const TimelineEvents = ({ } } - return ( - onSelect(datetime.events)} - > - - - { extraRender ? extraRender() : null } - - ) + const dotsToRender = getDotsToRender(datetime.events) + + return dotsToRender.map(dot => { + const customStyles = styleDatetime ? styleDatetime(datetime, dot.category) : null + const extraStyles = customStyles[0] + const extraRender = customStyles[1] + + const styleProps = ({ + fill: getCategoryColor(dot.category), + fillOpacity: 1, + transition: `transform ${transitionDuration / 1000}s ease`, + ...extraStyles + }) + + return ( + + ) + }) } return ( From c9c4854f3532fe83f06233cc9d4656652ed8b296 Mon Sep 17 00:00:00 2001 From: Lachlan Kermode Date: Wed, 9 Jan 2019 17:19:18 +0000 Subject: [PATCH 09/10] fix props in TimelineMarkers --- src/components/Timeline.jsx | 8 ++++---- src/components/presentational/DatetimeDot.js | 2 +- src/components/presentational/TimelineMarkers.js | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/components/Timeline.jsx b/src/components/Timeline.jsx index a107f55..61a2c11 100644 --- a/src/components/Timeline.jsx +++ b/src/components/Timeline.jsx @@ -270,16 +270,16 @@ class Timeline extends React.Component { /> this.getEventX(e)} - getEventY={(e) => this.getEventY(e)} + getEventX={this.getDatetimeX} + getCategoryY={this.getCategoryY} transitionDuration={this.state.transitionDuration} /> onSelect(datetime.events)} + onClick={() => onSelect(events)} > { +const TimelineMarkers = ({ getEventX, getCategoryY, transitionDuration, selected }) => { function renderMarker(event) { return ( @@ -28,4 +28,4 @@ const TimelineMarkers = ({ getEventX, getEventY, transitionDuration, selected }) ); } -export default TimelineMarkers; \ No newline at end of file +export default TimelineMarkers; From ebc3eee45e0c5868cd78b3ea6e8a64903e2b0a2f Mon Sep 17 00:00:00 2001 From: Lachlan Kermode Date: Wed, 9 Jan 2019 17:57:46 +0000 Subject: [PATCH 10/10] fixes from rebase --- src/components/Timeline.jsx | 6 +++--- src/components/presentational/DatetimeDot.js | 1 + src/components/presentational/TimelineEvents.js | 1 + 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/Timeline.jsx b/src/components/Timeline.jsx index 61a2c11..fc3f15b 100644 --- a/src/components/Timeline.jsx +++ b/src/components/Timeline.jsx @@ -271,15 +271,15 @@ class Timeline extends React.Component { ( diff --git a/src/components/presentational/TimelineEvents.js b/src/components/presentational/TimelineEvents.js index 17966ed..035ab64 100644 --- a/src/components/presentational/TimelineEvents.js +++ b/src/components/presentational/TimelineEvents.js @@ -58,6 +58,7 @@ const TimelineEvents = ({ return (