@@ -118,13 +111,6 @@ class Toolbar extends React.Component {
renderToolbarCategoriesPanel() {
const { panels } = this.props.toolbarCopy;
- const panelTitle = panels.categories.label
- ? panels.categories.label
- : copy[this.props.language].toolbar.categories;
- const panelDescription = panels.categories.description
- ? panels.categories.description
- : copy[this.props.language].toolbar.explore_by_category__description;
-
if (this.props.features.USE_CATEGORIES) {
return (
@@ -133,8 +119,8 @@ class Toolbar extends React.Component {
activeCategories={this.props.activeCategories}
onCategoryFilter={this.props.methods.onCategoryFilter}
language={this.props.language}
- title={panelTitle}
- description={panelDescription}
+ title={panels.categories.label}
+ description={panels.categories.description}
/>
);
@@ -143,13 +129,6 @@ class Toolbar extends React.Component {
renderToolbarFilterPanel() {
const { panels } = this.props.toolbarCopy;
- const panelTitle = panels.filters.label
- ? panels.filters.label
- : copy[this.props.language].toolbar.filters;
- const panelDescription = panels.filters.description
- ? panels.filters.description
- : copy[this.props.language].toolbar.explore_by_filter__description;
-
return (
);
}
+ renderToolbarShapePanel() {
+ const { panels } = this.props.toolbarCopy;
+
+ if (this.props.features.USE_SHAPES) {
+ return (
+
+
+
+ );
+ }
+ }
+
renderToolbarTab(_selected, label, iconKey) {
const isActive = this.state._selected === _selected;
const classes = isActive ? "toolbar-tab active" : "toolbar-tab";
@@ -196,6 +194,7 @@ class Toolbar extends React.Component {
: null}
{features.USE_CATEGORIES ? this.renderToolbarCategoriesPanel() : null}
{features.USE_ASSOCIATIONS ? this.renderToolbarFilterPanel() : null}
+ {features.USE_SHAPES ? this.renderToolbarShapePanel() : null}
);
@@ -228,31 +227,17 @@ class Toolbar extends React.Component {
const narrativesExist = narratives && narratives.length !== 0;
let title = copy[this.props.language].toolbar.title;
if (process.env.display_title) title = process.env.display_title;
-
const { panels } = toolbarCopy;
- const narrativesLabel = copy[this.props.language].toolbar.narratives_label;
- const filtersLabel = panels.filters.label
- ? panels.filters.label
- : copy[this.props.language].toolbar.filters_label;
- const categoriesLabel = panels.categories.label
- ? panels.categories.label
- : copy[this.props.language].toolbar.categories_label;
-
- const filterIcon = panels.filters.icon
- ? panels.filters.icon
- : DEFAULT_TAB_ICONS.FILTER;
- const categoriesIcon = panels.categories.icon
- ? panels.categories.icon
- : DEFAULT_TAB_ICONS.CATEGORY;
const narrativesIdx = 0;
const categoriesIdx = narrativesExist ? 1 : 0;
const filtersIdx =
- narrativesExist && features.CATEGORIES_AS_FILTERS
+ narrativesExist && features.USE_CATEGORIES
? 2
- : narrativesExist || features.CATEGORIES_AS_FILTERS
+ : narrativesExist || features.USE_CATEGORIES
? 1
: 0;
+ const shapesIdx = filtersIdx + 1;
return (
@@ -260,17 +245,32 @@ class Toolbar extends React.Component {
{narrativesExist
- ? this.renderToolbarTab(narrativesIdx, narrativesLabel, "timeline")
+ ? this.renderToolbarTab(
+ narrativesIdx,
+ panels.narratives.label,
+ panels.narratives.icon
+ )
: null}
- {features.CATEGORIES_AS_FILTERS
+ {features.USE_CATEGORIES
? this.renderToolbarTab(
categoriesIdx,
- categoriesLabel,
- categoriesIcon
+ panels.categories.label,
+ panels.categories.icon
)
: null}
{features.USE_ASSOCIATIONS
- ? this.renderToolbarTab(filtersIdx, filtersLabel, filterIcon)
+ ? this.renderToolbarTab(
+ filtersIdx,
+ panels.filters.label,
+ panels.filters.icon
+ )
+ : null}
+ {features.USE_SHAPES
+ ? this.renderToolbarTab(
+ shapesIdx,
+ panels.shapes.label,
+ panels.shapes.icon
+ )
: null}
{
- const styles = {
- background: isActive ? color : "none",
- border: `1px solid ${color}`,
+const Checkbox = ({ label, isActive, onClickCheckbox, color, styleProps }) => {
+ const checkboxColor = color ? color : DEFAULT_CHECKBOX_COLOR;
+ const baseStyles = {
+ checkboxStyles: {
+ background: isActive ? checkboxColor : "none",
+ border: `1px solid ${checkboxColor}`,
+ },
};
-
+ const containerStyles = styleProps ? styleProps.containerStyles : {};
+ const checkboxStyles = styleProps
+ ? styleProps.checkboxStyles
+ : baseStyles.checkboxStyles;
return (
);
diff --git a/src/components/controls/CategoriesListPanel.js b/src/components/controls/CategoriesListPanel.js
index c86f6d7..47c85b7 100644
--- a/src/components/controls/CategoriesListPanel.js
+++ b/src/components/controls/CategoriesListPanel.js
@@ -1,6 +1,7 @@
import React from "react";
import marked from "marked";
-import Checkbox from "../atoms/Checkbox";
+import PanelTree from "./atoms/PanelTree";
+import { ASSOCIATION_MODES } from "../../common/constants";
const CategoriesListPanel = ({
categories,
@@ -10,28 +11,6 @@ const CategoriesListPanel = ({
title,
description,
}) => {
- function renderCategoryTree() {
- return (
-
- {categories.map((cat) => {
- return (
-
- onCategoryFilter(cat.title)}
- />
-
- );
- })}
-
- );
- }
-
return (
{title}
@@ -40,7 +19,12 @@ const CategoriesListPanel = ({
__html: marked(description),
}}
/>
- {renderCategoryTree()}
+
);
};
diff --git a/src/components/controls/ShapesListPanel.js b/src/components/controls/ShapesListPanel.js
new file mode 100644
index 0000000..f72669c
--- /dev/null
+++ b/src/components/controls/ShapesListPanel.js
@@ -0,0 +1,34 @@
+import React from "react";
+import marked from "marked";
+import PanelTree from "./atoms/PanelTree";
+import { mapStyleByShape } from "../../common/utilities";
+import { SHAPE } from "../../common/constants";
+
+const ShapesListPanel = ({
+ shapes,
+ activeShapes,
+ onShapeFilter,
+ language,
+ title,
+ description,
+}) => {
+ const styledShapes = mapStyleByShape(shapes, activeShapes);
+ return (
+
+ );
+};
+
+export default ShapesListPanel;
diff --git a/src/components/controls/atoms/PanelTree.js b/src/components/controls/atoms/PanelTree.js
new file mode 100644
index 0000000..6da7098
--- /dev/null
+++ b/src/components/controls/atoms/PanelTree.js
@@ -0,0 +1,30 @@
+import React from "react";
+import Checkbox from "../../atoms/Checkbox";
+import { ASSOCIATION_MODES } from "../../../common/constants";
+
+const PanelTree = ({ data, activeValues, onSelect, type }) => {
+ // If the parent panel is of type 'CATEGORY': filter on title. If panel is 'SHAPE': filter on id
+ const onSelectionType = type === ASSOCIATION_MODES.CATEGORY ? "title" : "id";
+ return (
+
+ {data.map((val) => {
+ return (
+
+ onSelect(val[onSelectionType])}
+ styleProps={val.styles}
+ />
+
+ );
+ })}
+
+ );
+};
+
+export default PanelTree;
diff --git a/src/components/space/carto/Map.js b/src/components/space/carto/Map.js
index a1fa588..13cd0cb 100644
--- a/src/components/space/carto/Map.js
+++ b/src/components/space/carto/Map.js
@@ -8,7 +8,7 @@ import { connect } from "react-redux";
import * as selectors from "../../../selectors";
import Sites from "./atoms/Sites";
-import Shapes from "./atoms/Shapes";
+import Regions from "./atoms/Regions";
import Events from "./atoms/Events";
import Clusters from "./atoms/Clusters";
import SelectedEvents from "./atoms/SelectedEvents";
@@ -324,13 +324,13 @@ class Map extends React.Component {
);
}
- renderShapes() {
+ renderRegions() {
return (
-
);
}
@@ -481,7 +481,7 @@ class Map extends React.Component {
{this.renderTiles()}
{this.renderMarkers()}
{isShowingSites ? this.renderSites() : null}
- {this.renderShapes()}
+ {this.renderRegions()}
{this.renderNarratives()}
{this.renderEvents()}
{this.renderClusters()}
@@ -510,7 +510,7 @@ function mapStateToProps(state) {
narratives: selectors.selectNarratives(state),
categories: selectors.getCategories(state),
sites: selectors.selectSites(state),
- shapes: selectors.selectShapes(state),
+ regions: selectors.selectRegions(state),
},
app: {
views: state.app.associations.views,
@@ -532,7 +532,7 @@ function mapStateToProps(state) {
dom: state.ui.dom,
narratives: state.ui.style.narratives,
mapSelectedEvents: state.ui.style.selectedEvents,
- shapes: state.ui.style.shapes,
+ regions: state.ui.style.regions,
eventRadius: state.ui.eventRadius,
radial: state.ui.style.clusters.radial,
filterColors: state.ui.coloring.colors,
diff --git a/src/components/space/carto/atoms/Shapes.js b/src/components/space/carto/atoms/Regions.js
similarity index 50%
rename from src/components/space/carto/atoms/Shapes.js
rename to src/components/space/carto/atoms/Regions.js
index ef0297f..2ec9aa9 100644
--- a/src/components/space/carto/atoms/Shapes.js
+++ b/src/components/space/carto/atoms/Regions.js
@@ -1,13 +1,13 @@
import React from "react";
import { Portal } from "react-portal";
-function MapShapes({ svg, shapes, projectPoint, styles }) {
- function renderShape(shape) {
+function MapRegions({ svg, regions, projectPoint, styles }) {
+ function renderRegion(region) {
const lineCoords = [];
- const points = shape.points.map(projectPoint);
+ const points = region.points.map(projectPoint);
points.forEach((p1, idx) => {
- if (idx < shape.points.length - 1) {
+ if (idx < region.points.length - 1) {
const p2 = points[idx + 1];
lineCoords.push({
x1: p1.x,
@@ -19,29 +19,29 @@ function MapShapes({ svg, shapes, projectPoint, styles }) {
});
return lineCoords.map((coords) => {
- const shapeStyles =
- shape.name in styles ? styles[shape.name] : styles.default;
+ const regionstyles =
+ region.name in styles ? styles[region.name] : styles.default;
return (
);
});
}
- if (!shapes || !shapes.length) return null;
+ if (!regions || !regions.length) return null;
return (
-
- {shapes.map(renderShape)}
+
+ {regions.map(renderRegion)}
);
}
-export default MapShapes;
+export default MapRegions;
diff --git a/src/components/time/atoms/DatetimePentagon.js b/src/components/time/atoms/DatetimePentagon.js
index 233f54f..fff72bc 100644
--- a/src/components/time/atoms/DatetimePentagon.js
+++ b/src/components/time/atoms/DatetimePentagon.js
@@ -7,7 +7,7 @@ const DatetimePentagon = ({ x, y, r, transform, onSelect, styleProps }) => {
onClick={onSelect}
className="event"
x={x}
- y={y - r}
+ y={y}
style={styleProps}
points={`${x},${y + s} ${x + s},${y} ${x + s},${y - s} ${x - s},${
y - s
diff --git a/src/components/time/atoms/DatetimeStar.js b/src/components/time/atoms/DatetimeStar.js
index 60c590f..7056901 100644
--- a/src/components/time/atoms/DatetimeStar.js
+++ b/src/components/time/atoms/DatetimeStar.js
@@ -15,7 +15,7 @@ const DatetimeStar = ({
onClick={onSelect}
className="event"
x={x}
- y={y - r}
+ y={y}
style={styleProps}
points={`${x + s},${y - s} ${x - r},${y} ${x + r},${y} ${x - s},${
y - s
diff --git a/src/components/time/atoms/DatetimeTriangle.js b/src/components/time/atoms/DatetimeTriangle.js
index b94c42c..d9b6075 100644
--- a/src/components/time/atoms/DatetimeTriangle.js
+++ b/src/components/time/atoms/DatetimeTriangle.js
@@ -7,7 +7,7 @@ const DatetimeTriangle = ({ x, y, r, transform, onSelect, styleProps }) => {
onClick={onSelect}
className="event"
x={x}
- y={y - r}
+ y={y}
style={styleProps}
points={`${x},${y + s} ${x + s},${y - s} ${x - s},${y - s}`}
transform={`rotate(180, ${x}, ${y})`}
diff --git a/src/components/time/atoms/Events.js b/src/components/time/atoms/Events.js
index ca11291..7f5a0e0 100644
--- a/src/components/time/atoms/Events.js
+++ b/src/components/time/atoms/Events.js
@@ -14,6 +14,7 @@ import {
isLatitude,
isLongitude,
} from "../../../common/utilities";
+import { AVAILABLE_SHAPES } from "../../../common/constants";
function renderDot(event, styles, props) {
const colorPercentages = calculateColorPercentages(
@@ -156,19 +157,21 @@ const TimelineEvents = ({
(isLatitude(event.latitude) && isLongitude(event.longitude)) ||
(features.GRAPH_NONLOCATED && event.projectOffset !== -1);
+ const { shape: eventShape } = event;
+
let renderShape = isDot ? renderDot : renderBar;
- if (event.shape) {
- if (event.shape === "bar") {
+ if (eventShape.shape) {
+ if (eventShape.shape === AVAILABLE_SHAPES.BAR) {
renderShape = renderBar;
- } else if (event.shape === "diamond") {
+ } else if (eventShape.shape === AVAILABLE_SHAPES.DIAMOND) {
renderShape = renderDiamond;
- } else if (event.shape === "star") {
+ } else if (eventShape.shape === AVAILABLE_SHAPES.STAR) {
renderShape = renderStar;
- } else if (event.shape === "triangle") {
+ } else if (eventShape.shape === AVAILABLE_SHAPES.TRIANGLE) {
renderShape = renderTriangle;
- } else if (event.shape === "pentagon") {
+ } else if (eventShape.shape === AVAILABLE_SHAPES.PENTAGON) {
renderShape = renderPentagon;
- } else if (event.shape === "square") {
+ } else if (eventShape.shape === AVAILABLE_SHAPES.SQUARE) {
renderShape = renderSquare;
} else {
renderShape = renderDot;
diff --git a/src/components/time/atoms/Markers.js b/src/components/time/atoms/Markers.js
index 00f3012..e0c06ff 100644
--- a/src/components/time/atoms/Markers.js
+++ b/src/components/time/atoms/Markers.js
@@ -5,6 +5,7 @@ import {
isLatitude,
isLongitude,
} from "../../../common/utilities";
+import { AVAILABLE_SHAPES } from "../../../common/constants";
const TimelineMarkers = ({
styles,
@@ -72,11 +73,11 @@ const TimelineMarkers = ({
function renderMarkerForEvent(y) {
switch (event.shape) {
case "circle":
- case "diamond":
- case "star":
+ case AVAILABLE_SHAPES.DIAMOND:
+ case AVAILABLE_SHAPES.STAR:
acc.push(renderCircle(y));
break;
- case "bar":
+ case AVAILABLE_SHAPES.BAR:
acc.push(renderBar(y));
break;
default:
diff --git a/src/reducers/app.js b/src/reducers/app.js
index d137850..829e8a8 100644
--- a/src/reducers/app.js
+++ b/src/reducers/app.js
@@ -8,6 +8,7 @@ import {
UPDATE_COLORING_SET,
CLEAR_FILTER,
TOGGLE_ASSOCIATIONS,
+ TOGGLE_SHAPES,
UPDATE_TIMERANGE,
UPDATE_DIMENSIONS,
UPDATE_NARRATIVE,
@@ -26,6 +27,7 @@ import {
SET_LOADING,
SET_NOT_LOADING,
SET_INITIAL_CATEGORIES,
+ SET_INITIAL_SHAPES,
UPDATE_SEARCH_QUERY,
} from "../actions";
@@ -153,6 +155,21 @@ function toggleAssociations(appState, action) {
};
}
+function toggleShapes(appState, action) {
+ let newShapes = [...appState.shapes];
+ if (newShapes.includes(action.shape)) {
+ const idx = newShapes.indexOf(action.shape);
+ newShapes.splice(idx, 1);
+ } else {
+ newShapes.push(action.shape);
+ }
+
+ return {
+ ...appState,
+ shapes: newShapes,
+ };
+}
+
function clearFilter(appState, action) {
return {
...appState,
@@ -256,6 +273,14 @@ function setInitialCategories(appState, action) {
};
}
+function setInitialShapes(appState, action) {
+ const shapeIds = action.values.map((sh) => sh.id);
+ return {
+ ...appState,
+ shapes: shapeIds,
+ };
+}
+
function updateSearchQuery(appState, action) {
return {
...appState,
@@ -275,6 +300,8 @@ function app(appState = initial.app, action) {
return clearFilter(appState, action);
case TOGGLE_ASSOCIATIONS:
return toggleAssociations(appState, action);
+ case TOGGLE_SHAPES:
+ return toggleShapes(appState, action);
case UPDATE_TIMERANGE:
return updateTimeRange(appState, action);
case UPDATE_DIMENSIONS:
@@ -313,6 +340,8 @@ function app(appState = initial.app, action) {
return setNotLoading(appState);
case SET_INITIAL_CATEGORIES:
return setInitialCategories(appState, action);
+ case SET_INITIAL_SHAPES:
+ return setInitialShapes(appState, action);
case UPDATE_SEARCH_QUERY:
return updateSearchQuery(appState, action);
default:
diff --git a/src/reducers/validate/regionSchema.js b/src/reducers/validate/regionSchema.js
new file mode 100644
index 0000000..dad2d88
--- /dev/null
+++ b/src/reducers/validate/regionSchema.js
@@ -0,0 +1,8 @@
+import Joi from "joi";
+
+const regionSchema = Joi.object().keys({
+ name: Joi.string().required(),
+ items: Joi.array().required(),
+});
+
+export default regionSchema;
diff --git a/src/reducers/validate/shapeSchema.js b/src/reducers/validate/shapeSchema.js
index 637f773..c222b4f 100644
--- a/src/reducers/validate/shapeSchema.js
+++ b/src/reducers/validate/shapeSchema.js
@@ -1,8 +1,10 @@
import Joi from "joi";
const shapeSchema = Joi.object().keys({
- name: Joi.string().required(),
- items: Joi.array().required(),
+ id: Joi.string().allow(""),
+ title: Joi.string().allow(""),
+ shape: Joi.string().allow(""),
+ colour: Joi.string().allow(""),
});
export default shapeSchema;
diff --git a/src/reducers/validate/validators.js b/src/reducers/validate/validators.js
index c520e84..8299575 100644
--- a/src/reducers/validate/validators.js
+++ b/src/reducers/validate/validators.js
@@ -4,6 +4,7 @@ import createEventSchema from "./eventSchema";
import siteSchema from "./siteSchema";
import associationsSchema from "./associationsSchema";
import sourceSchema from "./sourceSchema";
+import regionSchema from "./regionSchema";
import shapeSchema from "./shapeSchema";
import { calcDatetime, capitalize } from "../../common/utilities";
@@ -53,6 +54,7 @@ export function validateDomain(domain, features) {
sites: [],
associations: [],
sources: {},
+ regions: [],
shapes: [],
notifications: domain ? domain.notifications : null,
};
@@ -66,6 +68,7 @@ export function validateDomain(domain, features) {
sites: [],
associations: [],
sources: [],
+ regions: [],
shapes: [],
};
@@ -114,14 +117,31 @@ export function validateDomain(domain, features) {
validateArray(domain.sites, "sites", siteSchema);
validateArray(domain.associations, "associations", associationsSchema);
validateObject(domain.sources, "sources", sourceSchema);
- validateObject(domain.shapes, "shapes", shapeSchema);
+ validateArray(domain.regions, "regions", regionSchema);
+ validateArray(domain.shapes, "shapes", shapeSchema);
// NB: [lat, lon] array is best format for projecting into map
- sanitizedDomain.shapes = sanitizedDomain.shapes.map((shape) => ({
- name: shape.name,
- points: shape.items.map((coords) => coords.replace(/\s/g, "").split(",")),
+ sanitizedDomain.regions = sanitizedDomain.regions.map((region) => ({
+ name: region.name,
+ points: region.items.map((coords) => coords.replace(/\s/g, "").split(",")),
}));
+ sanitizedDomain.shapes = sanitizedDomain.shapes.reduce((acc, val) => {
+ if (!val.shape) {
+ discardedDomain.shapes.push({
+ ...val,
+ error: makeError(
+ "events",
+ val.id,
+ "Invalid event shape. Please specify a shape for this type of event."
+ ),
+ });
+ } else {
+ acc.push(val);
+ }
+ return acc;
+ }, []);
+
const duplicateAssociations = findDuplicateAssociations(domain.associations);
// Duplicated associations
if (duplicateAssociations.length > 0) {
@@ -136,6 +156,7 @@ export function validateDomain(domain, features) {
// append events with datetime and sort
sanitizedDomain.events = sanitizedDomain.events.filter((event, idx) => {
+ let errorMsg = "";
event.id = idx;
// event.associations comes in as a [association.ids...]; convert to actual association objects
event.associations = event.associations.reduce((acc, id) => {
@@ -145,19 +166,31 @@ export function validateDomain(domain, features) {
if (foundAssociation) acc.push(foundAssociation);
return acc;
}, []);
+
+ if (event.shape) {
+ const relatedShapeObj = sanitizedDomain.shapes.find(
+ (elem) => elem.id === event.shape
+ );
+ if (!relatedShapeObj)
+ errorMsg =
+ "Failed to find related shape. Please verify shape type for event.";
+ else {
+ event.shape = relatedShapeObj;
+ }
+ }
// if lat, long come in with commas, replace with decimal format
event.latitude = event.latitude.replace(",", ".");
event.longitude = event.longitude.replace(",", ".");
event.datetime = calcDatetime(event.date, event.time);
- if (!isValidDate(event.datetime)) {
+ if (!isValidDate(event.datetime))
+ errorMsg =
+ "Invalid date. It's been dropped, as otherwise timemap won't work as expected.";
+
+ if (errorMsg) {
discardedDomain.events.push({
...event,
- error: makeError(
- "events",
- event.id,
- "Invalid date. It's been dropped, as otherwise timemap won't work as expected."
- ),
+ error: makeError("events", event.id, errorMsg),
});
return false;
}
@@ -177,6 +210,5 @@ export function validateDomain(domain, features) {
});
}
});
-
return sanitizedDomain;
}
diff --git a/src/scss/toolbar.scss b/src/scss/toolbar.scss
index 5950bef..4546e5c 100644
--- a/src/scss/toolbar.scss
+++ b/src/scss/toolbar.scss
@@ -413,14 +413,26 @@
text-align: left;
float: left;
- .checkbox {
- display: inline-block;
- width: 12px;
- height: 12px;
- border: 1px solid $offwhite;
- box-sizing: border-box;
+ .border {
+ width: 16px;
+ height: 16px;
background: none;
- float: left;
+ box-sizing: border-box;
+ position: relative;
+
+ .checkbox {
+ display: inline-block;
+ width: 12px;
+ height: 12px;
+ border: 1px solid $offwhite;
+ box-sizing: border-box;
+ background: none;
+ float: left;
+ position: absolute;
+ top: 2px;
+ left: 2px;
+ background: none;
+ }
}
}
diff --git a/src/selectors/index.js b/src/selectors/index.js
index 6ad19d4..a5e3fe9 100644
--- a/src/selectors/index.js
+++ b/src/selectors/index.js
@@ -8,7 +8,7 @@ import {
createFilterPathString,
} from "../common/utilities";
import { isTimeRangedIn } from "./helpers";
-import { ASSOCIATION_MODES, TIMELINE_ONLY } from "../common/constants";
+import { ASSOCIATION_MODES, SHAPE } from "../common/constants";
// Input selectors
export const getEvents = (state) => state.domain.events;
@@ -24,6 +24,7 @@ export const getActiveNarrative = (state) => state.app.associations.narrative;
export const getSelected = (state) => state.app.selected;
export const getSites = (state) => state.domain.sites;
export const getSources = (state) => state.domain.sources;
+export const getRegions = (state) => state.domain.regions;
export const getShapes = (state) => state.domain.shapes;
export const getFilters = (state) =>
state.domain.associations.filter(
@@ -32,6 +33,7 @@ export const getFilters = (state) =>
export const getNotifications = (state) => state.domain.notifications;
export const getActiveFilters = (state) => state.app.associations.filters;
export const getActiveCategories = (state) => state.app.associations.categories;
+export const getActiveShapes = (state) => state.app.shapes;
export const getTimeRange = (state) => state.app.timeline.range;
export const getTimelineDimensions = (state) => state.app.timeline.dimensions;
export const selectNarrative = (state) => state.app.associations.narrative;
@@ -56,10 +58,10 @@ export const selectSources = createSelector(
}
);
-export const selectShapes = createSelector(
- [getShapes, getFeatures],
- (shapes, features) => {
- if (features.USE_SHAPES) return shapes;
+export const selectRegions = createSelector(
+ [getRegions, getFeatures],
+ (regions, features) => {
+ if (features.USE_REGIONS) return regions;
return [];
}
);
@@ -71,8 +73,22 @@ export const selectShapes = createSelector(
* 3. exist in an active category
*/
export const selectEvents = createSelector(
- [getEvents, getActiveFilters, getActiveCategories, getTimeRange, getFeatures],
- (events, activeFilters, activeCategories, timeRange, features) => {
+ [
+ getEvents,
+ getActiveFilters,
+ getActiveCategories,
+ getActiveShapes,
+ getTimeRange,
+ getFeatures,
+ ],
+ (
+ events,
+ activeFilters,
+ activeCategories,
+ activeShapes,
+ timeRange,
+ features
+ ) => {
return events.reduce((acc, event) => {
const isMatchingFilter =
(event.associations &&
@@ -95,8 +111,14 @@ export const selectEvents = createSelector(
isActiveTime = features.GRAPH_NONLOCATED
? (!event.latitude && !event.longitude) || isActiveTime
: isActiveTime;
- if (isActiveTime && isActiveCategory) {
- if (event.type === TIMELINE_ONLY || isActiveFilter) {
+ const isActiveShape =
+ event.shape && activeShapes.includes(event.shape.id);
+ if (event.type === SHAPE) {
+ if (isActiveShape && isActiveCategory && isActiveTime) {
+ acc[event.id] = { ...event };
+ }
+ } else {
+ if (isActiveFilter && isActiveCategory && isActiveTime) {
acc[event.id] = { ...event };
}
}
diff --git a/src/store/initial.js b/src/store/initial.js
index 40219f8..d0bd782 100644
--- a/src/store/initial.js
+++ b/src/store/initial.js
@@ -33,6 +33,8 @@ const initial = {
associations: [],
sources: {},
sites: [],
+ shapes: [],
+ regions: [],
notifications: [],
},
@@ -63,6 +65,7 @@ const initial = {
sites: true,
},
},
+ shapes: [],
isMobile: /Mobi/.test(navigator.userAgent),
language: "en-US",
cluster: {
@@ -126,6 +129,12 @@ const initial = {
title: copy[language].toolbar.explore_by_narrative__title,
description: copy[language].toolbar.explore_by_narrative__description,
},
+ shapes: {
+ icon: DEFAULT_TAB_ICONS.SHAPE,
+ label: copy[language].toolbar.shapes_label,
+ title: copy[language].toolbar.explore_by_shape__title,
+ description: copy[language].toolbar.explore_by_shape__description,
+ },
},
},
loading: false,
@@ -149,7 +158,7 @@ const initial = {
strokeWidth: 3,
},
},
- shapes: {
+ regions: {
default: {
stroke: "blue",
strokeWidth: 3,
@@ -182,7 +191,7 @@ const initial = {
USE_ASSOCIATIONS: false,
USE_SITES: false,
USE_SOURCES: false,
- USE_SHAPES: false,
+ USE_REGIONS: false,
GRAPH_NONLOCATED: false,
HIGHLIGHT_GROUPS: false,
},