@eeacms/volto-marine-policy 2.0.7 → 2.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,42 @@ All notable changes to this project will be documented in this file. Dates are d
4
4
 
5
5
  Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
6
6
 
7
+ ### [2.0.9](https://github.com/eea/volto-marine-policy/compare/2.0.8...2.0.9) - 7 May 2025
8
+
9
+ #### :rocket: Dependency updates
10
+
11
+ - Release @eeacms/volto-searchlib@2.0.16 [EEA Jenkins - [`9d425b4`](https://github.com/eea/volto-marine-policy/commit/9d425b44fa813cf7d08d95d36af6c0f01d0e52ce)]
12
+
13
+ #### :house: Internal changes
14
+
15
+ - style: Automated code fix [eea-jenkins - [`1c3cbdc`](https://github.com/eea/volto-marine-policy/commit/1c3cbdcc74127d45e46db9ae4fd1ac3b237ad32c)]
16
+ - style: Automated code fix [eea-jenkins - [`b00b251`](https://github.com/eea/volto-marine-policy/commit/b00b2515e54b8ffdbc51bf301f0b0bb232b83efe)]
17
+ - style: Automated code fix [eea-jenkins - [`e02c515`](https://github.com/eea/volto-marine-policy/commit/e02c5156ac34e4cf2fef61a27dfe64d60aad97af)]
18
+ - style: Automated code fix [eea-jenkins - [`678b9bf`](https://github.com/eea/volto-marine-policy/commit/678b9bf2b8f83f9b3a6facaa42f5851a013462ad)]
19
+ - style: Automated code fix [eea-jenkins - [`b1cccfb`](https://github.com/eea/volto-marine-policy/commit/b1cccfbd674890392948ad5227bfe334f55a2721)]
20
+
21
+ #### :hammer_and_wrench: Others
22
+
23
+ - small fixes demmo sites map [laszlocseh - [`f83d070`](https://github.com/eea/volto-marine-policy/commit/f83d070d0b1c7a5bdf1136eb542782477c9266b0)]
24
+ - update objectives [laszlocseh - [`0c09eb1`](https://github.com/eea/volto-marine-policy/commit/0c09eb1b093ffba34deafa079127175629f3f2b9)]
25
+ - fix eslint [laszlocseh - [`128fbb0`](https://github.com/eea/volto-marine-policy/commit/128fbb04277cc11bc6406df8cc815ea4a41291d0)]
26
+ - fix eslint [laszlocseh - [`68e7168`](https://github.com/eea/volto-marine-policy/commit/68e7168d6322cb07fe7df7c2376a26e297cb015f)]
27
+ - fix eslint [laszlocseh - [`1be214a`](https://github.com/eea/volto-marine-policy/commit/1be214accdac3657f156691f09c149f9f678d344)]
28
+ - fix eslint [laszlocseh - [`fbed39c`](https://github.com/eea/volto-marine-policy/commit/fbed39c59d7841234cd8ffe699a9e84766a501bc)]
29
+ - fix eslint [laszlocseh - [`ed3a2d2`](https://github.com/eea/volto-marine-policy/commit/ed3a2d26194d18aed50b1781e2d84a3d1482b0ab)]
30
+ - fix eslint [laszlocseh - [`15805ce`](https://github.com/eea/volto-marine-policy/commit/15805ce9adb0ca66a99368a432d54d74fcbc8077)]
31
+ - WiP demo sites map [laszlocseh - [`ca84585`](https://github.com/eea/volto-marine-policy/commit/ca84585f692b03c7011d46393fa5b937fa985e4d)]
32
+ - WiP demo sites map [laszlocseh - [`b51ca84`](https://github.com/eea/volto-marine-policy/commit/b51ca842dba4fe0700100b5a6328d1bd4b962acb)]
33
+ ### [2.0.8](https://github.com/eea/volto-marine-policy/compare/2.0.7...2.0.8) - 16 April 2025
34
+
35
+ #### :house: Internal changes
36
+
37
+ - style: Automated code fix [eea-jenkins - [`c400842`](https://github.com/eea/volto-marine-policy/commit/c4008422b3480259c2a1101994c544b44e5b9002)]
38
+
39
+ #### :hammer_and_wrench: Others
40
+
41
+ - eslint [laszlocseh - [`ebdba1e`](https://github.com/eea/volto-marine-policy/commit/ebdba1e06ed02f3bf03442dfd578f2c1db5b1241)]
42
+ - demo sites map viewer zoom improvements [laszlocseh - [`1680f64`](https://github.com/eea/volto-marine-policy/commit/1680f644f00e1bf62e1a5471aaf25a95d152ba05)]
7
43
  ### [2.0.7](https://github.com/eea/volto-marine-policy/compare/2.0.6...2.0.7) - 15 April 2025
8
44
 
9
45
  #### :house: Internal changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eeacms/volto-marine-policy",
3
- "version": "2.0.7",
3
+ "version": "2.0.9",
4
4
  "description": "@eeacms/volto-marine-policy: Volto add-on",
5
5
  "main": "src/index.js",
6
6
  "author": "European Environment Agency: IDM2 A-Team",
@@ -36,13 +36,14 @@
36
36
  "@eeacms/volto-embed": "*",
37
37
  "@eeacms/volto-globalsearch": "2.0.11",
38
38
  "@eeacms/volto-openlayers-map": "0.3.2",
39
- "@eeacms/volto-searchlib": "2.0.15",
39
+ "@eeacms/volto-searchlib": "2.0.16",
40
40
  "@eeacms/volto-tabs-block": "*",
41
41
  "axios": "0.30.0",
42
42
  "d3-array": "^2.12.1",
43
43
  "jquery": "3.6.0",
44
44
  "razzle-plugin-scss": "^4.2.18",
45
45
  "react-lazy-load-image-component": "^1.4.0",
46
+ "react-plotly.js": "2.6.0",
46
47
  "react-slick": "^0.24.0",
47
48
  "slick-carousel": "^1.8.1"
48
49
  },
@@ -2,9 +2,11 @@ import React from 'react';
2
2
  import { Grid } from 'semantic-ui-react'; // Dropdown,
3
3
  import { addAppURL } from '@plone/volto/helpers';
4
4
  import config from '@plone/volto/registry';
5
+ import { VisibilitySensor } from '@eeacms/volto-datablocks/components';
6
+
5
7
  import DemoSitesMap from './DemoSitesMap';
6
8
  import { ActiveFilters, DemoSitesFilters } from './DemoSitesFilters';
7
-
9
+ import ObjectivesChart from './ObjectivesChart';
8
10
  import { filterCases, getFilters } from './utils';
9
11
  import { useCases } from './hooks';
10
12
 
@@ -23,6 +25,7 @@ export default function DemoSitesExplorerView(props) {
23
25
 
24
26
  const [activeFilters, setActiveFilters] = React.useState({
25
27
  objective_filter: [],
28
+ target_filter: [],
26
29
  indicator_filter: [],
27
30
  project_filter: [],
28
31
  country_filter: [],
@@ -31,6 +34,7 @@ export default function DemoSitesExplorerView(props) {
31
34
  const [activeItems, setActiveItems] = React.useState(cases);
32
35
  const [filters, setFilters] = React.useState([]);
33
36
  const [map, setMap] = React.useState();
37
+ const [highlightedIndex, setHighlightedIndex] = React.useState(-1);
34
38
 
35
39
  React.useEffect(() => {
36
40
  const _filters = getFilters(cases, indicatorOnly);
@@ -38,6 +42,7 @@ export default function DemoSitesExplorerView(props) {
38
42
  }, [
39
43
  cases,
40
44
  activeFilters.objective_filter,
45
+ activeFilters.target_filter,
41
46
  activeFilters.indicator_filter,
42
47
  activeFilters.project_filter,
43
48
  activeFilters.country_filter,
@@ -74,33 +79,62 @@ export default function DemoSitesExplorerView(props) {
74
79
  <Grid.Row>
75
80
  {cases.length ? (
76
81
  <Grid columns={12}>
77
- <Grid.Column mobile={10} tablet={10} computer={10}>
82
+ <Grid.Column mobile={8} tablet={8} computer={8}>
78
83
  <DemoSitesMap
79
84
  items={cases}
80
85
  activeItems={activeItems}
86
+ setActiveFilters={setActiveFilters}
81
87
  hideFilters={hideFilters}
82
88
  selectedCase={selectedCase}
83
89
  onSelectedCase={onSelectedCase}
84
90
  map={map}
85
91
  setMap={setMap}
92
+ highlightedIndex={highlightedIndex}
93
+ setHighlightedIndex={setHighlightedIndex}
86
94
  />
87
95
  </Grid.Column>
88
- <Grid.Column mobile={2} tablet={2} computer={2}>
89
- <div className="legend">
90
- <div className="legend-row legend-subtitle">Legend</div>
91
- <div className="legend-row">
92
- <div className="circle">
93
- <div className="dot-demosite"></div>
96
+ <Grid.Column
97
+ mobile={4}
98
+ tablet={4}
99
+ computer={4}
100
+ className="right-side-filters"
101
+ >
102
+ <Grid.Row>
103
+ {!hideFilters ? (
104
+ <VisibilitySensor>
105
+ <ObjectivesChart
106
+ items={cases}
107
+ activeItems={activeItems}
108
+ filters={filters}
109
+ activeFilters={activeFilters}
110
+ hideFilters={hideFilters}
111
+ setActiveFilters={setActiveFilters}
112
+ map={map}
113
+ highlightedIndex={highlightedIndex}
114
+ setHighlightedIndex={setHighlightedIndex}
115
+ />
116
+ </VisibilitySensor>
117
+ ) : (
118
+ ''
119
+ )}
120
+ </Grid.Row>
121
+ <Grid.Row>
122
+ <div className="legend">
123
+ {/* <div className="legend-row legend-subtitle">Legend</div> */}
124
+ <div className="legend-row">
125
+ <div className="circle">
126
+ <div className="dot-demosite"></div>
127
+ </div>
128
+ <div>Demo site</div>
94
129
  </div>
95
- <div>Demo site</div>
96
- </div>
97
- <div className="legend-row">
98
- <div className="circle">
99
- <div className="dot-region"></div>
130
+ <div className="legend-row">
131
+ <div className="circle">
132
+ <div className="dot-region"></div>
133
+ </div>
134
+ <div>Associated region</div>
100
135
  </div>
101
- <div>Associated region</div>
102
136
  </div>
103
- </div>
137
+ </Grid.Row>
104
138
  </Grid.Column>
105
139
  </Grid>
106
140
  ) : null}
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
 
3
- import { centerAndResetMapZoom } from './utils';
3
+ import { centerAndResetMapZoom, clearFilters } from './utils';
4
4
 
5
5
  const normalizeSearchInput = (searchInput) => {
6
6
  let normInput = searchInput
@@ -132,7 +132,7 @@ export function DemoSitesFilters(props) {
132
132
 
133
133
  return (
134
134
  <>
135
- {!hideFilters ? (
135
+ {/* {!hideFilters ? (
136
136
  <DemoSitesFilter
137
137
  filterTitle="Objective/Enabler"
138
138
  filterName="objective_filter"
@@ -140,13 +140,19 @@ export function DemoSitesFilters(props) {
140
140
  activeFilters={activeFilters}
141
141
  setActiveFilters={setActiveFilters}
142
142
  map={map}
143
- customOrder={[
144
- 'Protect and restore marine and freshwater ecosystems',
145
- 'Prevent and eliminate pollution of waters',
146
- 'Carbon-neutral and circular blue economy',
147
- 'Digital twin of the ocean',
148
- 'Public mobilisation and engagement',
149
- ]}
143
+ customOrder={objectivesCustomOrder}
144
+ />
145
+ ) : (
146
+ ''
147
+ )} */}
148
+ {!hideFilters ? (
149
+ <DemoSitesFilter
150
+ filterTitle="Target"
151
+ filterName="target_filter"
152
+ filters={filters}
153
+ activeFilters={activeFilters}
154
+ setActiveFilters={setActiveFilters}
155
+ map={map}
150
156
  />
151
157
  ) : (
152
158
  ''
@@ -266,30 +272,16 @@ export function SearchBox(props) {
266
272
 
267
273
  export function ActiveFilters(props) {
268
274
  const { filters, activeFilters, setActiveFilters } = props;
269
- const hasActiveFilters = Object.entries(activeFilters).some(
270
- ([filterName, filterList]) => {
271
- if (filterList.length > 0) {
272
- return true;
273
- }
274
- return false;
275
- },
276
- );
277
-
278
- const clearFilters = () => {
279
- const filterInputs = document.querySelectorAll(
280
- '#cse-filter .filter-input input',
281
- );
282
- for (let i = 0; i < filterInputs.length; i++) {
283
- filterInputs[i].checked = false;
284
- }
285
- setActiveFilters({
286
- objective_filter: [],
287
- indicator_filter: [],
288
- project_filter: [],
289
- country_filter: [],
290
- });
291
- // scrollToElement('search-input');
292
- };
275
+ const hasActiveFilters = false;
276
+ // const hasActiveFilters = Object.entries(activeFilters).some(
277
+ // ([filterName, filterList]) => {
278
+ // return false;
279
+ // if (filterList.length > 0) {
280
+ // return true;
281
+ // }
282
+ // return false;
283
+ // },
284
+ // );
293
285
 
294
286
  const removeFilter = (filterName, filterCode) => {
295
287
  const temp = JSON.parse(JSON.stringify(activeFilters));
@@ -316,7 +308,7 @@ export function ActiveFilters(props) {
316
308
  <div className="filter-list-header">
317
309
  <h4 className="filter-list-title">Active filters</h4>
318
310
  <button
319
- onClick={clearFilters}
311
+ onClick={() => clearFilters(setActiveFilters)}
320
312
  className="ui mini basic compact button clear-btn"
321
313
  >
322
314
  clear all
@@ -349,6 +341,31 @@ export function ActiveFilters(props) {
349
341
  ) : (
350
342
  ''
351
343
  )}
344
+ {activeFilters.target_filter.length > 0 ? (
345
+ <div className="filter-wrapper">
346
+ <div className="filter-label">Target:</div>
347
+ {activeFilters.target_filter.map((filterCode) => {
348
+ const filterLabel = filters.target_filter[filterCode];
349
+ return (
350
+ <div className="ui basic label filter-value">
351
+ <span>{filterLabel}</span>
352
+ <i
353
+ tabIndex="0"
354
+ onKeyPress={() => {}}
355
+ onClick={() => {
356
+ removeFilter('target_filter', filterCode);
357
+ // scrollToElement('search-input');
358
+ }}
359
+ role="button"
360
+ className="close icon"
361
+ ></i>
362
+ </div>
363
+ );
364
+ })}
365
+ </div>
366
+ ) : (
367
+ ''
368
+ )}
352
369
  {activeFilters.indicator_filter.length > 0 ? (
353
370
  <div className="filter-wrapper">
354
371
  <div className="filter-label">Indicator:</div>
@@ -9,7 +9,12 @@ import InfoOverlay from './InfoOverlay';
9
9
  import FeatureInteraction from './FeatureInteraction';
10
10
  import { useMapContext } from '@eeacms/volto-openlayers-map/api';
11
11
 
12
- import { centerAndResetMapZoom, getFeatures } from './utils';
12
+ import {
13
+ // centerAndResetMapZoom,
14
+ clearFilters,
15
+ getFeatures,
16
+ zoomMapToFeatures,
17
+ } from './utils';
13
18
 
14
19
  const styleCache = {};
15
20
  const MapContextGateway = ({ setMap }) => {
@@ -24,11 +29,13 @@ export default function DemoSitesMap(props) {
24
29
  const {
25
30
  items,
26
31
  activeItems,
32
+ setActiveFilters,
27
33
  hideFilters,
28
34
  selectedCase,
29
35
  onSelectedCase,
30
36
  map,
31
37
  setMap,
38
+ setHighlightedIndex,
32
39
  } = props;
33
40
  const features = getFeatures(items);
34
41
  const [resetMapButtonClass, setResetMapButtonClass] =
@@ -60,11 +67,15 @@ export default function DemoSitesMap(props) {
60
67
  );
61
68
 
62
69
  React.useEffect(() => {
70
+ if (!map) return null;
71
+
63
72
  if (activeItems) {
64
73
  pointsSource.clear();
65
74
  pointsSource.addFeatures(getFeatures(activeItems));
75
+ // hideFilters && zoomMapToFeatures(map, getFeatures(activeItems));
76
+ zoomMapToFeatures(map, getFeatures(activeItems));
66
77
  }
67
- }, [activeItems, pointsSource]);
78
+ }, [map, activeItems, pointsSource, hideFilters]);
68
79
 
69
80
  React.useEffect(() => {
70
81
  if (!map) return null;
@@ -119,7 +130,7 @@ export default function DemoSitesMap(props) {
119
130
  view={{
120
131
  center: ol.proj.fromLonLat([10, 54]),
121
132
  showFullExtent: true,
122
- zoom: 2.5,
133
+ zoom: 3,
123
134
  }}
124
135
  pixelRatio={1}
125
136
  // controls={ol.control.defaults({ attribution: false })}
@@ -134,11 +145,18 @@ export default function DemoSitesMap(props) {
134
145
  onClick={() => {
135
146
  // scrollToElement('search-input');
136
147
  onSelectedCase(null);
137
- centerAndResetMapZoom(map);
148
+ clearFilters(setActiveFilters);
149
+ setHighlightedIndex(5);
150
+ if (hideFilters) {
151
+ zoomMapToFeatures(map, getFeatures(activeItems));
152
+ } else {
153
+ // centerAndResetMapZoom(map);
154
+ zoomMapToFeatures(map, getFeatures(activeItems));
155
+ }
138
156
  map.getInteractions().array_[9].getFeatures().clear();
139
157
  }}
140
158
  >
141
- <span className="result-info-title">Reset map</span>
159
+ <span className="result-info-title">Reset filters</span>
142
160
  <i className="icon ri-map-2-line"></i>
143
161
  </button>
144
162
  <InfoOverlay
@@ -78,24 +78,28 @@ export default function FeatureDisplay({ feature }) {
78
78
  ''
79
79
  )} */}
80
80
 
81
- <div>
82
- <span className="popup-title blue">Indicators</span>
83
- <ul>
84
- {feature.indicators.map((item, index) => {
85
- return (
86
- <li key={index}>
87
- <a
88
- target="_blank"
89
- rel="noopener noreferrer"
90
- href={item['path']}
91
- >
92
- {item['title']}
93
- </a>
94
- </li>
95
- );
96
- })}
97
- </ul>
98
- </div>
81
+ {feature.indicators.length > 0 ? (
82
+ <div>
83
+ <span className="popup-title blue">Indicators</span>
84
+ <ul>
85
+ {feature.indicators.map((item, index) => {
86
+ return (
87
+ <li key={index}>
88
+ <span
89
+ // target="_blank"
90
+ // rel="noopener noreferrer"
91
+ // href={item['path']}
92
+ >
93
+ {item['title']}
94
+ </span>
95
+ </li>
96
+ );
97
+ })}
98
+ </ul>
99
+ </div>
100
+ ) : (
101
+ ''
102
+ )}
99
103
  {/* <div>
100
104
  <h4>Indicators</h4>
101
105
  <ul>
@@ -0,0 +1,224 @@
1
+ import React, { useRef } from 'react';
2
+ import cx from 'classnames';
3
+ import loadable from '@loadable/component';
4
+ import { Grid } from 'semantic-ui-react'; // Dropdown,
5
+
6
+ import { objectivesCustomOrder } from './utils';
7
+
8
+ const Plot = loadable(() => import('react-plotly.js'));
9
+
10
+ const ObjectivesChart = ({
11
+ items,
12
+ // activeItems,
13
+ // filters,
14
+ activeFilters,
15
+ setActiveFilters,
16
+ // hideFilters,
17
+ // map,
18
+ highlightedIndex,
19
+ setHighlightedIndex,
20
+ }) => {
21
+ const initialized = useRef(false);
22
+ const chartRef = useRef(null);
23
+ const objectiveCounts = {};
24
+
25
+ for (const item of items) {
26
+ item.properties.objective.map((obj) => {
27
+ objectiveCounts[obj] = (objectiveCounts[obj] || 0) + 1;
28
+ return [];
29
+ });
30
+ }
31
+ const _sorted = Object.entries(objectiveCounts).sort(
32
+ (a, b) =>
33
+ objectivesCustomOrder.indexOf(a[0]) - objectivesCustomOrder.indexOf(b[0]),
34
+ );
35
+ const objectives = Object.fromEntries(_sorted);
36
+ const objectivesLength = Object.keys(objectives).length;
37
+
38
+ React.useEffect(() => {
39
+ // set the objectives and the count
40
+ if (!items) return;
41
+ if (initialized.current) return;
42
+
43
+ const interval = setInterval(() => {
44
+ setHighlightedIndex((prevIndex) => {
45
+ if (prevIndex + 1 >= objectivesLength + 1) {
46
+ clearInterval(interval); // Stop after last item
47
+ initialized.current = true;
48
+ return prevIndex;
49
+ }
50
+ return prevIndex + 1;
51
+ });
52
+ }, 2000);
53
+ // console.log('init');
54
+ return () => clearInterval(interval); // Cleanup on unmount
55
+ }, [items, objectivesLength, highlightedIndex, setHighlightedIndex]);
56
+
57
+ React.useEffect(() => {
58
+ // if (!objectives) return;
59
+ if (highlightedIndex === -1) {
60
+ setActiveFilters({
61
+ objective_filter: [undefined],
62
+ target_filter: [],
63
+ indicator_filter: [],
64
+ project_filter: [],
65
+ country_filter: [],
66
+ });
67
+ return;
68
+ }
69
+
70
+ const currentObjective = Object.keys(objectives)[highlightedIndex];
71
+ const filterKey = 'objective_filter';
72
+ const newValue = currentObjective ? [currentObjective] : [];
73
+
74
+ setActiveFilters((prev) => {
75
+ const currentValues = prev[filterKey];
76
+ // Don't update if no change
77
+ const isSame =
78
+ currentValues.length === newValue.length &&
79
+ currentValues.every((v, i) => v === newValue[i]);
80
+
81
+ if (isSame) return prev;
82
+
83
+ return {
84
+ ...prev,
85
+ [filterKey]: newValue,
86
+ };
87
+ });
88
+ // console.log(highlightedIndex, activeFilters);
89
+ }, [objectives, activeFilters, setActiveFilters, highlightedIndex]);
90
+
91
+ const handleClick = (event) => {
92
+ const point = event.points[0];
93
+ const label = point.label;
94
+ // const value = point.value;
95
+
96
+ const tempFilters = JSON.parse(JSON.stringify(activeFilters));
97
+ if (tempFilters['objective_filter'].includes(label)) {
98
+ tempFilters['objective_filter'] = tempFilters['objective_filter'].filter(
99
+ (i) => i !== label,
100
+ );
101
+ setHighlightedIndex(5);
102
+ } else {
103
+ tempFilters['objective_filter'] = [];
104
+ tempFilters['objective_filter'].push(label);
105
+ setHighlightedIndex(Object.keys(objectives).indexOf(label));
106
+ }
107
+ setActiveFilters(tempFilters);
108
+ };
109
+
110
+ const handleHover = () => {
111
+ if (chartRef.current) {
112
+ chartRef.current.style.cursor = 'pointer'; // Change cursor on hover
113
+ }
114
+ };
115
+
116
+ const handleUnhover = () => {
117
+ if (chartRef.current) {
118
+ chartRef.current.style.cursor = 'default'; // Reset cursor
119
+ }
120
+ };
121
+
122
+ const labels = Object.keys(objectives);
123
+ const values = Object.values(objectives);
124
+ const totalCount = values.reduce((acc, curr) => acc + curr, 0);
125
+ const customColors = ['#007b6c', '#fdaf20', '#004b7f', '#f9eb8a', '#9e83b6']; // Adjust colors as needed
126
+ const grayColor = '#d3d3d3';
127
+ // const pull = labels.map((_, i) => (i === highlightedIndex ? 0.1 : 0));
128
+ const inactiveColors = labels.map((_, i) =>
129
+ i === highlightedIndex ? customColors[i % customColors.length] : grayColor,
130
+ );
131
+
132
+ return highlightedIndex >= -1 ? (
133
+ <div className="objectives-chart fade-in">
134
+ <Grid.Row className="chart-title">
135
+ Demo sites and Associated regions
136
+ </Grid.Row>
137
+ <Grid.Row className="chart-title">Objective/Enabler</Grid.Row>
138
+ <Grid.Row className="chart-container">
139
+ <div ref={chartRef}>
140
+ <Plot
141
+ useResizeHandler
142
+ data={[
143
+ {
144
+ type: 'pie',
145
+ labels: labels,
146
+ values: values,
147
+ textinfo: 'none',
148
+ // textinfo: highlightedIndex === 5 ? 'value' : 'none',
149
+ hole: 0.4,
150
+ insidetextorientation: 'radial',
151
+ hoverinfo: 'label+value',
152
+ marker: {
153
+ colors:
154
+ highlightedIndex === 5 ? customColors : inactiveColors, // Apply custom colors here
155
+ },
156
+ direction: 'clockwise',
157
+ // pull,
158
+ },
159
+ ]}
160
+ layout={{
161
+ width: 250,
162
+ height: 250,
163
+ // title: 'Objectives Distribution',
164
+ showlegend: false,
165
+ margin: {
166
+ t: 20, // Top margin
167
+ b: 20, // Bottom margin
168
+ l: 0, // Left margin
169
+ r: 0, // Right margin
170
+ },
171
+ annotations: [
172
+ {
173
+ text:
174
+ highlightedIndex === 5
175
+ ? `${totalCount}`
176
+ : values[highlightedIndex] || '', // Display total count in the center
177
+ font: {
178
+ size: 24, // Adjust font size as needed
179
+ weight: 'bold',
180
+ },
181
+ showarrow: false, // No arrow pointing to the center
182
+ x: 0.5, // Position in the center (horizontal)
183
+ y: 0.5, // Position in the center (vertical)
184
+ align: 'center',
185
+ valign: 'middle',
186
+ bgcolor: 'rgba(255, 255, 255, 0.6)', // Optional background color
187
+ borderpad: 10, // Padding around the text
188
+ },
189
+ ],
190
+ }}
191
+ config={{
192
+ responsive: true,
193
+ displayModeBar: true,
194
+ modeBarButtonsToRemove: ['toImage'], // Removes download button
195
+ displaylogo: false, // Removes "Produced with Plotly.js"
196
+ }}
197
+ onClick={handleClick}
198
+ onHover={handleHover}
199
+ onUnhover={handleUnhover}
200
+ />
201
+ </div>
202
+ </Grid.Row>
203
+ <Grid.Row>
204
+ <ul>
205
+ {Object.entries(objectives).map(([item, count], index) => (
206
+ <li
207
+ key={item}
208
+ className={cx(
209
+ index === highlightedIndex ? 'active' : '',
210
+ customColors[index].replace('#', 'C'),
211
+ )}
212
+ >
213
+ {item}
214
+ </li>
215
+ ))}
216
+ </ul>
217
+ </Grid.Row>
218
+ </div>
219
+ ) : (
220
+ ''
221
+ );
222
+ };
223
+
224
+ export default ObjectivesChart;
@@ -388,12 +388,12 @@
388
388
  }
389
389
 
390
390
  .legend {
391
- position: absolute;
391
+ // position: absolute;
392
392
  bottom: 2em;
393
393
  display: flex;
394
- flex-direction: column;
394
+ flex-direction: row;
395
395
  align-items: flex-start;
396
- gap: 0.5em;
396
+ gap: 1em;
397
397
 
398
398
  .legend-row {
399
399
  display: flex;
@@ -439,3 +439,99 @@
439
439
  transform: translate(-50%, -50%);
440
440
  }
441
441
  }
442
+
443
+ .right-side-filters {
444
+ display: flex !important;
445
+ flex-direction: column;
446
+ align-items: center;
447
+ justify-content: space-around;
448
+ }
449
+
450
+ .objectives-chart {
451
+ &.fade-in {
452
+ animation: fadeIn 1s ease-in forwards;
453
+ opacity: 0;
454
+ /* Start invisible */
455
+ }
456
+
457
+ @keyframes fadeIn {
458
+ to {
459
+ opacity: 1;
460
+ }
461
+ }
462
+
463
+ .main-svg {
464
+ overflow: visible !important;
465
+ }
466
+
467
+ .hoverlayer {
468
+ overflow: visible !important;
469
+ }
470
+
471
+ .chart-title {
472
+ font-size: 1em;
473
+ font-weight: 500;
474
+ text-align: center;
475
+ }
476
+
477
+ .chart-container {
478
+ text-align: center;
479
+ }
480
+
481
+ ul {
482
+ display: flex;
483
+ flex-direction: column;
484
+ padding-left: 1.3em;
485
+ gap: 0.5em;
486
+ list-style: none;
487
+ }
488
+
489
+ li {
490
+ display: flex;
491
+ flex-direction: row;
492
+ align-items: center;
493
+ font-size: 0.8em;
494
+ line-height: 1.5em;
495
+
496
+ &.C007b6c::before {
497
+ background-color: #007b6c;
498
+ }
499
+
500
+ &.Cfdaf20::before {
501
+ background-color: #fdaf20;
502
+ }
503
+
504
+ &.C004b7f::before {
505
+ background-color: #004b7f;
506
+ }
507
+
508
+ &.Cf9eb8a::before {
509
+ background-color: #f9eb8a;
510
+ }
511
+
512
+ &.C9e83b6::before {
513
+ background-color: #9e83b6;
514
+ }
515
+
516
+ &::before {
517
+ width: 0.7em;
518
+ height: 0.7em;
519
+ flex-shrink: 0;
520
+ border-radius: 2px;
521
+ /* change as needed */
522
+ margin-right: 8px;
523
+ content: '';
524
+ }
525
+
526
+ &.active {
527
+ font-size: 1em;
528
+ // color: red;
529
+ font-weight: 500;
530
+
531
+ &::before {
532
+ width: 1em;
533
+ height: 1em;
534
+ }
535
+ }
536
+ }
537
+ }
@@ -1,5 +1,30 @@
1
1
  import { openlayers as ol } from '@eeacms/volto-openlayers-map';
2
2
 
3
+ export const objectivesCustomOrder = [
4
+ 'Objective 1: Protect and restore marine and freshwater ecosystems and biodiversity',
5
+ 'Objective 2: Prevent and eliminate pollution of our oceans, seas and waters',
6
+ 'Objective 3: Make the sustainable blue economy carbon-neutral and circular',
7
+ 'Enabler 1: Digital twin of the ocean',
8
+ 'Enabler 2: Public mobilisation and engagement',
9
+ ];
10
+
11
+ export const clearFilters = (setActiveFilters) => {
12
+ const filterInputs = document.querySelectorAll(
13
+ '#cse-filter .filter-input input',
14
+ );
15
+ for (let i = 0; i < filterInputs.length; i++) {
16
+ filterInputs[i].checked = false;
17
+ }
18
+ setActiveFilters({
19
+ objective_filter: [],
20
+ target_filter: [],
21
+ indicator_filter: [],
22
+ project_filter: [],
23
+ country_filter: [],
24
+ });
25
+ // scrollToElement('search-input');
26
+ };
27
+
3
28
  export const truncateText = (str, max = 50) => {
4
29
  if (str.length <= max) {
5
30
  return str;
@@ -39,20 +64,42 @@ export function getExtentOfFeatures(features) {
39
64
 
40
65
  export function zoomMapToFeatures(map, features, threshold = 500) {
41
66
  const extent = getExtentOfFeatures(features);
42
- let extentBuffer = (extent[3] - extent[1] + extent[2] - extent[0]) / 4;
67
+
68
+ // let extentBuffer = (extent[3] - extent[1] + extent[2] - extent[0]) / 4;
69
+ // extentBuffer = extentBuffer < threshold ? threshold : extentBuffer;
70
+ // const paddedExtent = ol.extent.buffer(extent, extentBuffer);
71
+
72
+ const width = extent[2] - extent[0];
73
+ const height = extent[3] - extent[1];
74
+ const bufferFactor = 0.05; // 5% buffer
75
+
76
+ let extentBuffer = Math.max(width, height) * bufferFactor;
43
77
  extentBuffer = extentBuffer < threshold ? threshold : extentBuffer;
44
78
  const paddedExtent = ol.extent.buffer(extent, extentBuffer);
45
- map.getView().fit(paddedExtent, { ...map.getSize(), duration: 1000 });
79
+
80
+ try {
81
+ // map.getView().fit(paddedExtent, { ...map.getSize(), duration: 1000 });
82
+ map.getView().fit(paddedExtent, {
83
+ size: map.getSize(), // pass size explicitly
84
+ // padding: [0, 0, 0, 0], // top, right, bottom, left
85
+ duration: 1000,
86
+ });
87
+ } catch (error) {
88
+ // no features available, zooming fails
89
+ }
46
90
  }
47
91
 
48
92
  export function getFeatures(cases) {
49
93
  const Feature = ol.ol.Feature;
50
94
  const colors = {
51
- 'Carbon-neutral and circular blue economy': '#f9eb8a',
52
- 'Digital twin of the ocean': '#004b7f',
53
- 'Prevent and eliminate pollution of waters': '#fdaf20',
54
- 'Protect and restore marine and freshwater ecosystems': '#007b6c',
55
- 'Public mobilisation and engagement': '#9e83b6',
95
+ 'Objective 1: Protect and restore marine and freshwater ecosystems and biodiversity':
96
+ '#007b6c',
97
+ 'Objective 2: Prevent and eliminate pollution of our oceans, seas and waters':
98
+ '#fdaf20',
99
+ 'Objective 3: Make the sustainable blue economy carbon-neutral and circular':
100
+ '#004b7f',
101
+ 'Enabler 1: Digital twin of the ocean': '#f9eb8a',
102
+ 'Enabler 2: Public mobilisation and engagement': '#9e83b6',
56
103
  };
57
104
  const width = {
58
105
  'Demo site': 6,
@@ -85,10 +132,11 @@ export function getFeatures(cases) {
85
132
  info: c.properties.info,
86
133
  website: c.properties.website,
87
134
  objective: c.properties.objective,
135
+ target: c.properties.target,
88
136
  description: c.properties.description,
89
137
  index: index,
90
138
  path: c.properties.path,
91
- color: colors[c.properties.objective] || '#B83230',
139
+ color: colors[c.properties.objective[0]] || '#B83230',
92
140
  width: width[c.properties.type_is_region],
93
141
  radius: radius[c.properties.type_is_region],
94
142
  },
@@ -101,6 +149,7 @@ export function getFeatures(cases) {
101
149
  export function filterCases(cases, activeFilters, indicatorOnly) {
102
150
  const data = cases.filter((_case) => {
103
151
  let flag_objective = false;
152
+ let flag_target = false;
104
153
  let flag_indicator = false;
105
154
  let flag_project = false;
106
155
  let flag_country = false;
@@ -122,7 +171,17 @@ export function filterCases(cases, activeFilters, indicatorOnly) {
122
171
  let objective = _case.properties.objective;
123
172
 
124
173
  activeFilters.objective_filter.forEach((filter) => {
125
- if (objective === filter) flag_objective = true;
174
+ if (objective?.includes(filter)) flag_objective = true;
175
+ });
176
+ }
177
+
178
+ if (!activeFilters.target_filter.length) {
179
+ flag_target = true;
180
+ } else {
181
+ let target = _case.properties.target;
182
+
183
+ activeFilters.target_filter.forEach((filter) => {
184
+ if (target?.includes(filter)) flag_target = true;
126
185
  });
127
186
  }
128
187
 
@@ -162,6 +221,7 @@ export function filterCases(cases, activeFilters, indicatorOnly) {
162
221
 
163
222
  return flag_indicatorOnly &&
164
223
  flag_objective &&
224
+ flag_target &&
165
225
  flag_indicator &&
166
226
  flag_country &&
167
227
  flag_project
@@ -175,6 +235,7 @@ export function filterCases(cases, activeFilters, indicatorOnly) {
175
235
  export function getFilters(cases, indicatorOnly) {
176
236
  let _filters = {
177
237
  objective_filter: {},
238
+ target_filter: {},
178
239
  indicator_filter: {},
179
240
  project_filter: {},
180
241
  country_filter: {},
@@ -188,6 +249,7 @@ export function getFilters(cases, indicatorOnly) {
188
249
  indicators.map((item) => {
189
250
  if (
190
251
  item['title'] &&
252
+ item['title'] !== '0' &&
191
253
  !_filters.indicator_filter.hasOwnProperty('_' + item['id'])
192
254
  ) {
193
255
  _filters.indicator_filter['_' + item['id']] = item['title'];
@@ -196,9 +258,20 @@ export function getFilters(cases, indicatorOnly) {
196
258
  });
197
259
 
198
260
  let objective = _case.properties.objective;
199
- if (objective && !_filters.objective_filter.hasOwnProperty(objective)) {
200
- _filters.objective_filter[objective] = objective;
201
- }
261
+ objective.map((item) => {
262
+ if (item && !_filters.objective_filter.hasOwnProperty(item)) {
263
+ _filters.objective_filter[item] = item;
264
+ }
265
+ return [];
266
+ });
267
+
268
+ let target = _case.properties.target;
269
+ target.map((item) => {
270
+ if (item && !_filters.target_filter.hasOwnProperty(item)) {
271
+ _filters.target_filter[item] = item;
272
+ }
273
+ return [];
274
+ });
202
275
 
203
276
  let project = _case.properties.project;
204
277
  if (project && !_filters.project_filter.hasOwnProperty(project)) {