WIP: overridden colours and shapes

This commit is contained in:
Lachlan Kermode
2020-05-14 15:02:14 +02:00
parent b23938b1da
commit 3641756539
12 changed files with 101 additions and 80 deletions

View File

@@ -35,7 +35,7 @@ export function fetchDomain () {
.catch(() => handleError(domainMsg('categories')))
let narPromise = Promise.resolve([])
if (process.env.features.USE_CATEGORIES) {
if (process.env.features.USE_NARRATIVES) {
narPromise = fetch(NARRATIVE_URL)
.then(response => response.json())
.catch(() => handleError(domainMsg('narratives')))

View File

@@ -10,6 +10,7 @@ export const sizes = {
}
export default {
fallbackEventColor: colors.fa_red,
darkBackground: colors.black,
primaryHighlight: colors.yellow,
secondaryHighlight: colors.white,

View File

@@ -175,13 +175,13 @@ export function selectTypeFromPathWithPoster (path, poster) {
return { type: typeForPath(path), path, poster }
}
export function getEventOpacity (events) {
export function calcOpacity (num) {
/* Events have opacity 0.5 by default, and get added to according to how many
* 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 */
const base = events.length >= 1 ? 0.3 : 0
return base + (Math.min(0.5, 0.08 * (events.length - 1)))
const base = num >= 1 ? 0.6 : 0
return base + (Math.min(0.5, 0.08 * (num - 1)))
}
export const dateMin = function () { return Array.prototype.slice.call(arguments).reduce(function (a, b) { return a < b ? a : b }) }

View File

@@ -69,7 +69,12 @@ class Dashboard extends React.Component {
}
getCategoryColor (category) {
return this.props.ui.style.categories[category] || this.props.ui.style.categories['default']
const cat = this.props.ui.style.categories[category]
if (cat) {
return cat
} else {
return this.props.ui.style.categories['default']
}
}
getNarrativeLinks (event) {

View File

@@ -49,7 +49,7 @@ class Timeline extends React.Component {
if ((hash(nextProps.domain.categories) !== hash(this.props.domain.categories)) || hash(nextProps.dimensions) !== hash(this.props.dimensions)) {
const { trackHeight, marginTop } = nextProps.dimensions
this.setState({
scaleY: this.makeScaleY(nextProps.domain.categoriesWithTimeline, trackHeight, marginTop)
scaleY: this.makeScaleY(nextProps.domain.categories, trackHeight, marginTop)
})
}
@@ -319,7 +319,7 @@ class Timeline extends React.Component {
onDragStart={() => { this.onDragStart() }}
onDrag={() => { this.onDrag() }}
onDragEnd={() => { this.onDragEnd() }}
categories={this.props.domain.categoriesWithTimeline}
categories={this.props.domain.categories}
/>
<Handles
dims={dims}
@@ -341,6 +341,7 @@ class Timeline extends React.Component {
noCategories={this.props.domain.categories && this.props.domain.categories.length}
/>
<Events
events={this.props.domain.events}
datetimes={this.props.domain.datetimes}
styleDatetime={this.styleDatetime}
narrative={this.props.app.narrative}
@@ -364,9 +365,9 @@ function mapStateToProps (state) {
dimensions: selectors.selectDimensions(state),
isNarrative: !!state.app.narrative,
domain: {
events: selectors.selectEvents(state),
datetimes: selectors.selectDatetimes(state),
categories: selectors.getCategories(state),
categoriesWithTimeline: selectors.selectCategoriesWithTimeline(state),
narratives: state.domain.narratives
},
app: {

View File

@@ -31,7 +31,7 @@ class TimelineAxis extends React.Component {
this.x0 =
d3.axisBottom(this.props.scaleX)
.ticks(10)
.tickPadding(5)
.tickPadding(0)
.tickSize(this.props.dims.trackHeight)
.tickFormat(d3.timeFormat(fstFmt))

View File

@@ -1,7 +1,7 @@
import React from 'react'
import { Portal } from 'react-portal'
import colors from '../../../common/global.js'
import { getEventOpacity } from '../../../common/utilities'
import { calcOpacity } from '../../../common/utilities'
function MapEvents ({ getCategoryColor, categories, projectPoint, styleLocation, selected, narrative, onSelect, svg, locations }) {
function getCoordinatesForPercent (radius, percent) {
@@ -34,7 +34,7 @@ function MapEvents ({ getCategoryColor, categories, projectPoint, styleLocation,
fill: getCategoryColor(locCategory),
stroke: colors.darkBackground,
strokeWidth: 0,
fillOpacity: getEventOpacity(location.events),
fillOpacity: calcOpacity(location.events.length),
...extraStyles
})

View File

@@ -2,7 +2,7 @@ import React from 'react'
import DatetimeDot from './DatetimeDot'
import DatetimeBar from './DatetimeBar'
import Project from './Project'
import { getEventOpacity } from '../../../common/utilities'
import { calcOpacity } from '../../../common/utilities'
import { sizes } from '../../../common/global'
// return a list of lists, where each list corresponds to a single category
@@ -28,6 +28,7 @@ function getDotsToRender (events) {
const HAS_PROJECTS = 'ASSOCIATIVE_EVENTS_BY_TAG' in process.env.features && process.env.features.ASSOCIATIVE_EVENTS_BY_TAG
const TimelineEvents = ({
events,
datetimes,
narrative,
getDatetimeX,
@@ -38,7 +39,45 @@ const TimelineEvents = ({
styleDatetime,
dims
}) => {
function renderDot (event, colour) {
const props = ({
fill: colour,
fillOpacity: calcOpacity(1),
transition: `transform ${transitionDuration / 1000}s ease`
})
return <DatetimeDot
onSelect={() => onSelect([event])}
category={event.category}
events={[event]}
x={getDatetimeX(event.timestamp)}
y={getCategoryY(event.category)}
r={sizes.eventDotR}
styleProps={props}
/>
}
function renderBar (event, colour) {
const evOpacity = calcOpacity(1)
const props = {
fill: colour,
fillOpacity: HAS_PROJECTS
? event.projectOffset >= 0 ? evOpacity : 0.05
: 0.6
}
return <DatetimeBar
onSelect={() => onSelect([event])}
category={event.category}
events={[event]}
x={getDatetimeX(event.timestamp)}
y={dims.marginTop}
width={sizes.eventDotR / 4}
height={dims.trackHeight}
styleProps={props}
/>
}
function renderDatetime (datetime) {
// narrative checking for non-rendering still uses datetimes as legacy TODO(lachlan)
if (narrative) {
const { steps } = narrative
// check all events in the datetime before rendering in narrative
@@ -56,35 +95,28 @@ const TimelineEvents = ({
}
}
/* DEFAULTS TODO(lachlan): clean up */
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 categoryColor = getCategoryColor(dot.category)
// default to category for colour, and located/unlocated for shape
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 locatedProps = ({
fill: categoryColor,
fillOpacity: getEventOpacity(locatedEvents),
transition: `transform ${transitionDuration / 1000}s ease`,
...extraStyles
})
const unlocatedProps = {
fill: categoryColor,
fillOpacity: HAS_PROJECTS
? unlocatedEvents.some(ev => ev.projectOffset >= 0) ? getEventOpacity(unlocatedEvents) : 0.05
: getEventOpacity(unlocatedEvents) / 4
? unlocatedEvents.some(ev => ev.projectOffset >= 0) ? calcOpacity(unlocatedEvents.length) : 0.05
: calcOpacity(unlocatedEvents.length) / 4
}
const extraRender = customStyles[1]
let bar = <DatetimeBar
onSelect={() => onSelect(unlocatedEvents)}
category={dot.category}
@@ -112,16 +144,7 @@ const TimelineEvents = ({
}
return (
<g className='datetime'>
{locatedEvents.length >= 1 && <DatetimeDot
onSelect={() => onSelect(locatedEvents)}
category={dot.category}
events={locatedEvents}
x={getDatetimeX(datetime.timestamp)}
y={getCategoryY(dot.category)}
r={sizes.eventDotR}
styleProps={locatedProps}
extraRender={extraRender}
/>}
{locatedEvents.length >= 1 && renderCircle()}
{unlocatedEvents.length >= 1 && bar}
{extraRender ? extraRender() : null}
</g>
@@ -129,16 +152,19 @@ const TimelineEvents = ({
})
}
// const projOffsets = {}
// const pEvents = datetimes.filter(dt => dt.events.some(ev => ev.project !== null))
// pEvents.forEach(({ events }) => {
// events.forEach(ev => {
// if (!projOffsets.hasOwnProperty(ev.project)) {
// projOffsets[ev.project] = ev.projectOffset
// }
// })
// })
function renderEvent (event) {
let renderShape = renderDot
if (event.shape) {
if (event.shape === 'bar') {
renderShape = renderBar
}
}
const colour = event.colour ? event.colour : getCategoryColor(event.category)
return renderShape(event, colour)
}
/* set `renderProjects` */
let renderProjects = () => null
if (process.env.features.ASSOCIATIVE_EVENTS_BY_TAG) {
const projects = datetimes[1]
@@ -160,7 +186,8 @@ const TimelineEvents = ({
clipPath={'url(#clip)'}
>
{renderProjects()}
{datetimes.map(datetime => renderDatetime(datetime))}
{/* {datetimes.map(datetime => renderDatetime(datetime))} */}
{events.map(event => renderEvent(event))}
</g>
)
}

View File

@@ -11,9 +11,8 @@ const TimelineMarkers = ({
noCategories
}) => {
function renderMarker (event) {
const isLocated = !!event.latitude && !!event.longitude
return isLocated ? (
<circle
function renderCircle () {
return <circle
className='timeline-marker'
cx={0}
cy={0}
@@ -30,12 +29,13 @@ const TimelineMarkers = ({
}}
r={sizes.eventDotR * 2}
/>
) : (
<rect
}
function renderBar () {
return <rect
className='timeline-marker'
x={0}
y={0}
width={sizes.eventDotR}
width={sizes.eventDotR / 3}
height={dims.contentHeight - 55}
stroke={styles ? styles.stroke : colors.primaryHighlight}
stroke-opacity='1'
@@ -43,10 +43,19 @@ const TimelineMarkers = ({
stroke-dasharray={styles ? styles['stroke-dasharray'] : '2,2'}
style={{
'transform': `translate(${getEventX(event.timestamp)}px)`,
'opacity': 0.9
'opacity': 0.7
}}
/>
)
}
const isLocated = !!event.latitude && !!event.longitude
switch (event.shape) {
case 'circle':
return renderCircle()
case 'bar':
return renderBar()
default:
return isLocated ? renderBar() : renderCircle()
}
}
return (

View File

@@ -20,7 +20,9 @@ const eventSchema = Joi.object().keys({
time_display: Joi.string().allow(''),
// nested
narrative___stepStyles: Joi.array()
narrative___stepStyles: Joi.array(),
shape: Joi.string().allow(''),
colour: Joi.string().allow('')
})
.and('latitude', 'longitude')
.and('date', 'timestamp')

View File

@@ -281,30 +281,6 @@ export const selectSelected = createSelector(
}
)
/**
* 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])
}
)
export const selectDimensions = createSelector(
[getTimelineDimensions],
(dimensions) => {

View File

@@ -1,5 +1,5 @@
import { mergeDeepLeft } from 'ramda'
import colors from '../common/global.js'
import global from '../common/global'
const initial = {
/*
@@ -105,12 +105,12 @@ const initial = {
tiles: 'openstreetmap', // ['openstreetmap', 'streets', 'satellite']
style: {
categories: {
default: colors.fa_red
default: global.fallbackEventColor
},
narratives: {
default: {
opacity: 0.9,
stroke: colors.fa_red,
stroke: global.fallbackEventColor,
strokeWidth: 3
}
},