diff --git a/src/common/utilities.js b/src/common/utilities.js index 25c1718..4050da9 100644 --- a/src/common/utilities.js +++ b/src/common/utilities.js @@ -180,5 +180,6 @@ export function getEventOpacity (events) { * other events there are in the same render. The idea here is that the * overlaying of events builds up a 'heat map' of the event space, where * darker areas represent more events with proportion */ - return 0.3 + (Math.min(0.5, 0.08 * (events.length - 1))) + const base = events.length >= 1 ? 0.3 : 0 + return base + (Math.min(0.5, 0.08 * (events.length - 1))) } diff --git a/src/components/Timeline.jsx b/src/components/Timeline.jsx index d532ba1..89fca83 100644 --- a/src/components/Timeline.jsx +++ b/src/components/Timeline.jsx @@ -303,7 +303,7 @@ class Timeline extends React.Component { onDragStart={() => { this.onDragStart() }} onDrag={() => { this.onDrag() }} onDragEnd={() => { this.onDragEnd() }} - categories={this.props.domain.categories} + categories={this.props.domain.categoriesWithTimeline} /> + {this.props.categories.map((cat, idx) => this.renderCategory(cat, idx))} ( + +) diff --git a/src/components/presentational/Timeline/DatetimeDot.js b/src/components/presentational/Timeline/DatetimeDot.js index 24a7abd..505701e 100644 --- a/src/components/presentational/Timeline/DatetimeDot.js +++ b/src/components/presentational/Timeline/DatetimeDot.js @@ -9,18 +9,12 @@ export default ({ styleProps, extraRender }) => ( - onSelect(events)} - > - - { extraRender ? extraRender() : null } - + ) diff --git a/src/components/presentational/Timeline/Events.js b/src/components/presentational/Timeline/Events.js index bd80ea2..b6c6e34 100644 --- a/src/components/presentational/Timeline/Events.js +++ b/src/components/presentational/Timeline/Events.js @@ -1,5 +1,6 @@ import React from 'react' import DatetimeDot from './DatetimeDot' +import DatetimeBar from './DatetimeBar' import { getEventOpacity } from '../../../common/utilities' // return a list of lists, where each list corresponds to a single category @@ -57,43 +58,52 @@ const TimelineEvents = ({ const customStyles = styleDatetime ? styleDatetime(datetime, dot.category) : null const extraStyles = customStyles[0] - // const isLocated = dot.events.map(ev => !ev.latitude || !ev.longitude) + const categoryColor = getCategoryColor(dot.category) + const locatedEvents = dot.events.filter(ev => ev.latitude && ev.longitude) + const unlocatedEvents = dot.events.filter(ev => !ev.latitude || !ev.longitude) // TODO: work out smarter way to manage opacity w.r.t. length // i.e. render (count - 1) extra dots with a bit of noise in position // and that, when clicked, all open the same events. - const styleProps = ({ - fill: getCategoryColor(dot.category), - fillOpacity: getEventOpacity(dot.events), + const locatedProps = ({ + fill: categoryColor, + fillOpacity: getEventOpacity(locatedEvents), transition: `transform ${transitionDuration / 1000}s ease`, ...extraStyles }) - const extraRender = () => ( - - {customStyles[1]} - - ) + const unlocatedProps = { + fill: categoryColor, + fillOpacity: getEventOpacity(unlocatedEvents) + } - return ( - - + const extraRender = customStyles[1] + + return ( + + {locatedEvents.length >= 1 && onSelect(locatedEvents)} + category={dot.category} + events={locatedEvents} + x={getDatetimeX(datetime)} + y={getCategoryY(dot.category)} + styleProps={locatedProps} + extraRender={extraRender} + />} + {unlocatedEvents.length >= 1 && onSelect(unlocatedEvents)} + category={dot.category} + events={unlocatedEvents} + x={getDatetimeX(datetime)} + y={40} + styleProps={unlocatedProps} + />} + {extraRender ? extraRender() : null} + ) }) } - // console.log(datetimes - // .filter(d => d.events.some(e => e.category !== 'Legislation')) - // ) - return ( { function renderMarker (event) { - return ( + const isLocated = !!event.latitude && !!event.longitude + return isLocated ? ( + ) : ( + + ) } diff --git a/src/selectors/helpers.js b/src/selectors/helpers.js index 273d70f..d7e7257 100644 --- a/src/selectors/helpers.js +++ b/src/selectors/helpers.js @@ -14,3 +14,16 @@ export function isTimeRangedIn (event, timeRange) { eventTime < timeRange[1] ) } + +/** + * Shuffles array in place. ES6 version + * @param {Array} a items An array containing the items. + * https://stackoverflow.com/questions/6274339/how-can-i-shuffle-an-array + */ +export function shuffle (a) { + for (let i = a.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [a[i], a[j]] = [a[j], a[i]] + } + return a +} diff --git a/src/selectors/index.js b/src/selectors/index.js index 933fa44..42df9f3 100644 --- a/src/selectors/index.js +++ b/src/selectors/index.js @@ -1,6 +1,6 @@ import { createSelector } from 'reselect' import { compareTimestamp, insetSourceFrom } from '../common/utilities' -import { isTimeRangedIn } from './helpers' +import { isTimeRangedIn, shuffle } from './helpers' // Input selectors export const getEvents = state => state.domain.events @@ -181,3 +181,27 @@ export const selectSelected = createSelector( return selected.map(insetSourceFrom(sources)) } ) + +/** + * Only categories that have events which are located should show on the + * timeline. + */ +export const selectCategoriesWithTimeline = createSelector( + [getCategories, getEvents], + (categories, events) => { + if (categories.length === 0) { + return categories + } + // check for located events in category + // shuffle first to improve chances of stopping more quickly + const hasLocated = {} + for (let event of shuffle(events)) { + if (Object.keys(hasLocated).length === categories.length) break + const cat = event.category + if (hasLocated[cat]) continue + const isLocated = !!event.longitude && !!event.latitude + if (isLocated) hasLocated[cat] = true + } + return categories.filter(cat => hasLocated[cat.category]) + } +)