mirror of
https://github.com/bellingcat/ukraine-timemap.git
synced 2026-06-13 05:48:36 +03:00
Feature/handle cluster select from timeline (#175)
* Updating some styles for cover; updating copy * Wrote up getSelectedClusters function; testing * Working on select with clusters being highlighted; rendering highlight around both clusters and individual points, so there's a little overlap * Removed extraneous props being passed down to cluster Co-authored-by: efarooqui <efarooqui@pandora.com>
This commit is contained in:
@@ -126,10 +126,9 @@
|
||||
"filters": "Filters",
|
||||
"filters_label": "Filters",
|
||||
"explore_by_filter__title": "Explore by filter",
|
||||
"explore_by_filter__description": "Selecting a filter will show you only those events that are annotated with the filter. If you select nothing, as well as everything, all data will be displayed.",
|
||||
"explore_by_filter__description": "‘Filters’ refer to the types of incident. Select multiple filters to introduce colour-coding, up to a maximum of six filters. If no filters are selected, all datapoints are displayed.",
|
||||
"explore_by_category__title": "Explore events by category",
|
||||
"explore_by_category__description": ""
|
||||
|
||||
"explore_by_category__description": "‘Categories’ refer to the victims of a given incident. If no categories are selected, all datapoints are displayed."
|
||||
},
|
||||
"timeline": {
|
||||
"zooms": [
|
||||
|
||||
@@ -283,6 +283,15 @@ export function calcClusterSize (pointCount, totalPoints) {
|
||||
return Math.min(maxSize, 10 + (pointCount / totalPoints) * 150)
|
||||
}
|
||||
|
||||
export function calculateTotalClusterPoints (clusters) {
|
||||
return clusters.reduce((total, cl) => {
|
||||
if (cl && cl.properties && cl.properties.cluster) {
|
||||
total += cl.properties.point_count
|
||||
}
|
||||
return total
|
||||
}, 0)
|
||||
}
|
||||
|
||||
export function isLatitude (lat) {
|
||||
return !!lat && isFinite(lat) && Math.abs(lat) <= 90
|
||||
}
|
||||
|
||||
@@ -261,7 +261,7 @@ class Dashboard extends React.Component {
|
||||
}
|
||||
|
||||
const popupStyles = {
|
||||
fontSize: 24,
|
||||
fontSize: 20,
|
||||
height: `calc(100vh - ${app.timeline.dimensions.height}px)`,
|
||||
width: '40vw',
|
||||
bottom: app.timeline.dimensions.height
|
||||
|
||||
@@ -17,7 +17,7 @@ import Narratives from './presentational/Map/Narratives'
|
||||
import DefsMarkers from './presentational/Map/DefsMarkers.jsx'
|
||||
import LoadingOverlay from '../components/Overlay/Loading'
|
||||
|
||||
import { mapClustersToLocations, isIdentical, isLatitude, isLongitude } from '../common/utilities'
|
||||
import { mapClustersToLocations, isIdentical, isLatitude, isLongitude, calculateTotalClusterPoints, calcClusterSize } from '../common/utilities'
|
||||
|
||||
// NB: important constants for map, TODO: make statics
|
||||
const supportedMapboxMap = ['streets', 'satellite']
|
||||
@@ -183,6 +183,29 @@ class Map extends React.Component {
|
||||
return []
|
||||
}
|
||||
|
||||
getSelectedClusters () {
|
||||
const { selected } = this.props.app
|
||||
const selectedIds = selected.map(sl => sl.id)
|
||||
|
||||
if (this.state.clusters && this.state.clusters.length > 0) {
|
||||
return this.state.clusters.reduce((acc, cl) => {
|
||||
if (cl.properties.cluster) {
|
||||
const children = this.getClusterChildren(cl.properties.cluster_id)
|
||||
if (children && children.length > 0) {
|
||||
children.forEach(child => {
|
||||
const clusterPresent = acc.findIndex(item => item.id === cl.id) >= 0
|
||||
if (selectedIds.includes(child.id) && !clusterPresent) {
|
||||
acc.push(cl)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
return acc
|
||||
}, [])
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
alignLayers () {
|
||||
const mapNode = document.querySelector('.leaflet-map-pane')
|
||||
if (mapNode === null) return { transformX: 0, transformY: 0 }
|
||||
@@ -341,10 +364,35 @@ class Map extends React.Component {
|
||||
}
|
||||
|
||||
renderSelected () {
|
||||
const selectedClusters = this.getSelectedClusters()
|
||||
const totalMarkers = []
|
||||
|
||||
this.props.app.selected.forEach(s => {
|
||||
const { latitude, longitude } = s
|
||||
totalMarkers.push({
|
||||
latitude,
|
||||
longitude,
|
||||
radius: this.props.ui.eventRadius
|
||||
})
|
||||
})
|
||||
|
||||
const totalClusterPoints = calculateTotalClusterPoints(this.state.clusters)
|
||||
|
||||
selectedClusters.forEach(cl => {
|
||||
if (cl.properties.cluster) {
|
||||
const { coordinates } = cl.geometry
|
||||
totalMarkers.push({
|
||||
latitude: String(coordinates[1]),
|
||||
longitude: String(coordinates[0]),
|
||||
radius: calcClusterSize(cl.properties.point_count, totalClusterPoints)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<SelectedEvents
|
||||
svg={this.svgRef.current}
|
||||
selected={this.props.app.selected}
|
||||
selected={totalMarkers}
|
||||
projectPoint={this.projectPoint}
|
||||
styles={this.props.ui.mapSelectedEvents}
|
||||
/>
|
||||
|
||||
@@ -8,7 +8,8 @@ import {
|
||||
isLatitude,
|
||||
isLongitude,
|
||||
calculateColorPercentages,
|
||||
zipColorsToPercentages } from '../../../common/utilities'
|
||||
zipColorsToPercentages,
|
||||
calculateTotalClusterPoints } from '../../../common/utilities'
|
||||
|
||||
const DefsClusters = () => (
|
||||
<defs>
|
||||
@@ -74,14 +75,10 @@ function ClusterEvents ({
|
||||
isRadial,
|
||||
svg,
|
||||
clusters,
|
||||
filterColors
|
||||
filterColors,
|
||||
selected
|
||||
}) {
|
||||
const totalPoints = clusters.reduce((total, cl) => {
|
||||
if (cl && cl.properties) {
|
||||
total += cl.properties.point_count
|
||||
}
|
||||
return total
|
||||
}, 0)
|
||||
const totalPoints = calculateTotalClusterPoints(clusters)
|
||||
|
||||
const styles = {
|
||||
fill: isRadial ? "url('#clusterGradient')" : colors.fallbackEventColor,
|
||||
|
||||
@@ -3,10 +3,10 @@ import { Portal } from 'react-portal'
|
||||
import colors from '../../../common/global.js'
|
||||
|
||||
class MapSelectedEvents extends React.Component {
|
||||
renderMarker (event) {
|
||||
const { x, y } = this.props.projectPoint([event.latitude, event.longitude])
|
||||
renderMarker (marker) {
|
||||
const { x, y } = this.props.projectPoint([marker.latitude, marker.longitude])
|
||||
const styles = this.props.styles
|
||||
const r = styles ? styles.r : 24
|
||||
const r = marker.radius ? marker.radius + 5 : 24
|
||||
return (
|
||||
<g
|
||||
className='location-marker'
|
||||
|
||||
@@ -145,8 +145,7 @@
|
||||
|
||||
.hero {
|
||||
min-width: 100%;
|
||||
max-width: 100%;
|
||||
min-height: 150px;
|
||||
min-height: 80px;
|
||||
margin: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -217,6 +216,20 @@
|
||||
h5 { margin-top: -15px; }
|
||||
|
||||
|
||||
.md-container {
|
||||
width: 100%;
|
||||
overflow-wrap: break-word;
|
||||
// white-space: pre-line;
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
li::before {
|
||||
content: "* ";
|
||||
}
|
||||
}
|
||||
|
||||
// mobile styles, remove overlay buttons
|
||||
@media only screen and (max-width: 1200px) {
|
||||
font-size: 22pt !important;
|
||||
|
||||
@@ -173,6 +173,7 @@ export const selectLocations = createSelector(
|
||||
activeLocations[location] = {
|
||||
label: location,
|
||||
events: [event],
|
||||
id: event.id,
|
||||
latitude: event.latitude,
|
||||
longitude: event.longitude
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user