render one dot per category in a datetime

This commit is contained in:
Lachlan Kermode
2019-01-09 15:15:04 +00:00
parent 899e06d560
commit 1506c62f50
3 changed files with 88 additions and 49 deletions

View File

@@ -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 <circle> for each location.
* Determines additional styles on the <circle> for each timestamp. Note that
* timestamp visualisation functions slightly differently from locations, as
* a timestamp can be shown as multiple <circle>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 <g/> 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}

View File

@@ -0,0 +1,27 @@
import React from 'react'
export default ({
category,
events,
x,
y,
styleProps,
extraRender
}) => (
<g
className='datetime'
transform={`translate(${x}, ${y})`}
onClick={() => onSelect(datetime.events)}
>
<circle
className="event"
cx={0}
cy={0}
style={styleProps}
r={5}
>
</circle>
{ extraRender ? extraRender() : null }
</g>
)

View File

@@ -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 (
<g
className='datetime'
transform={`translate(${getDatetimeX(datetime)}, ${getDatetimeY(datetime)})`}
onClick={() => onSelect(datetime.events)}
>
<circle
className="event"
cx={0}
cy={0}
style={styleProps}
r={5}
>
</circle>
{ extraRender ? extraRender() : null }
</g>
)
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 (
<DatetimeDot
category={dot.category}
events={dot.events}
x={getDatetimeX(datetime)}
y={getCategoryY(dot.category)}
styleProps={styleProps}
extraRender={extraRender}
/>
)
})
}
return (