Files
ukraine-timemap/src/components/presentational/Map/Events.jsx
Ebrahem Farooqui c454775fcb Feature/add coloring algorithm (#169)
* Fixed bug: when all child filters unselected, turn off parent as well

* Refactored placement of onSelectFilter to be in Layout; working logic for updating coloring sets

* Linting fixes and removal of console logs

* Added separate component for colored markers which clusters and events will use; working calculation of color percentages based off of coloringset

* Working colors for clusters; need to implement for individual points as well

* Adding two new features to select whether to color by association or by category (can't do both)

* Working colors for filter list panel; text and checkbox change according to colorset groupings

* Working timeline events with coloring algorithm

* Handle select acts different on map when we don't render all points and only filter through clusters; can fix this by not filtering before passing in locations to events in map

* Removed extraneous prop

* Working point count on hover again; numbers were showing up below the colored markers

* Linting fixes and minor refactor of calculateColorPercentage for linting to ass

* Comments and more linting fixes

* add dev command for windows subsystem for linux

* return default styles for category toggles

* dynamically filter out timelines

* calibrate styling

* further calibrations

* correct contrast

* lint

Co-authored-by: efarooqui <efarooqui@pandora.com>
Co-authored-by: Lachlan Kermode <lachiekermode@gmail.com>
2020-10-27 13:33:32 +01:00

182 lines
4.9 KiB
JavaScript

import React from 'react'
import { Portal } from 'react-portal'
import colors from '../../../common/global.js'
import ColoredMarkers from './ColoredMarkers.jsx'
import { calcOpacity, getCoordinatesForPercent, calculateColorPercentages, zipColorsToPercentages } from '../../../common/utilities'
function MapEvents ({
getCategoryColor,
categories,
projectPoint,
styleLocation,
selected,
narrative,
onSelect,
svg,
locations,
eventRadius,
coloringSet,
filterColors,
features
}) {
function handleEventSelect (e, location) {
const events = e.shiftKey ? selected.concat(location.events) : location.events
onSelect(events)
}
function renderBorder () {
return (
<React.Fragment>
{<circle
class='event-hover'
cx='0'
cy='0'
r='10'
stroke={colors.primaryHighlight}
fill-opacity='0.0'
/>}
</React.Fragment>
)
}
function renderLocationSlicesByAssociation (location) {
const colorPercentages = calculateColorPercentages([location], coloringSet)
let styles = ({
stroke: colors.darkBackground,
strokeWidth: 0,
fillOpacity: narrative ? 1 : calcOpacity(location.events.length)
})
return (
<ColoredMarkers
radius={eventRadius}
colorPercentMap={zipColorsToPercentages(filterColors, colorPercentages)}
styles={{
...styles
}}
className={'location-event-marker'}
/>
)
}
function renderLocationSlicesByCategory (location) {
const locCategory = location.events.length > 0 ? location.events[0].category : 'default'
const customStyles = styleLocation ? styleLocation(location) : null
const extraStyles = customStyles[0]
let styles = ({
fill: getCategoryColor(locCategory),
stroke: colors.darkBackground,
strokeWidth: 0,
fillOpacity: narrative ? 1 : calcOpacity(location.events.length),
...extraStyles
})
const colorSlices = location.events.map(e => e.colour ? e.colour : getCategoryColor(e.category))
let cumulativeAngleSweep = 0
return (
<React.Fragment>
{colorSlices.map((color, idx) => {
const r = eventRadius
// Based on the number of events in each location,
// create a slice per event filled with its category color
const [startX, startY] = getCoordinatesForPercent(r, cumulativeAngleSweep)
cumulativeAngleSweep = (idx + 1) / colorSlices.length
const [endX, endY] = getCoordinatesForPercent(r, cumulativeAngleSweep)
// if the slices are less than 2, take the long arc
const largeArcFlag = (colorSlices.length === 1) ? 1 : 0
// create an array and join it just for code readability
const arc = [
`M ${startX} ${startY}`, // Move
`A ${r} ${r} 0 ${largeArcFlag} 1 ${endX} ${endY}`, // Arc
`L 0 0 `, // Line
`L ${startX} ${startY} Z` // Line
].join(' ')
const extraStyles = ({
...styles,
fill: color
})
return (
<path
class='location-event-marker'
id={`arc_${idx}`}
d={arc}
style={extraStyles}
/>
)
})}
</React.Fragment>
)
}
function renderLocation (location) {
/**
{
events: [...],
label: 'Location name',
latitude: '47.7',
longitude: '32.2'
}
*/
if (!location.latitude || !location.longitude) return null
const { x, y } = projectPoint([location.latitude, location.longitude])
// in narrative mode, only render events in narrative
// TODO: move this to a selector
if (narrative) {
const { steps } = narrative
const onlyIfInNarrative = e => steps.map(s => s.id).includes(e.id)
const eventsInNarrative = location.events.filter(onlyIfInNarrative)
if (eventsInNarrative.length <= 0) {
return null
}
}
const customStyles = styleLocation ? styleLocation(location) : null
const extraRender = () => (
<React.Fragment>
{customStyles[1]}
</React.Fragment>
)
const isSelected = selected.reduce((acc, event) => {
return acc || (event.latitude === location.latitude && event.longitude === location.longitude)
}, false)
return (
<g
className={`location-event ${narrative ? 'no-hover' : ''}`}
transform={`translate(${x}, ${y})`}
onClick={(e) => handleEventSelect(e, location)}
>
{features.COLOR_BY_ASSOCIATION ? renderLocationSlicesByAssociation(location) : null}
{features.COLOR_BY_CATEGORY ? renderLocationSlicesByCategory(location) : null}
{extraRender ? extraRender() : null}
{isSelected ? null : renderBorder()}
</g>
)
}
return (
<Portal node={svg}>
<g className='event-locations'>
{locations.map(renderLocation)}
</g>
</Portal>
)
}
export default MapEvents