datastake-daf 0.6.817 → 0.6.818

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.
@@ -0,0 +1,330 @@
1
+ /* Isolated Mapbox GL CSS - Scoped to prevent Leaflet conflicts */
2
+
3
+ /* Mapbox GL Core Styles - Scoped with .mapbox-gl-scope */
4
+ .mapbox-gl-scope .mapboxgl-map {
5
+ font: 12px/20px Helvetica Neue, Arial, Helvetica, sans-serif;
6
+ overflow: hidden;
7
+ position: relative;
8
+ -webkit-tap-highlight-color: rgb(0 0 0/0);
9
+ }
10
+
11
+ .mapbox-gl-scope .mapboxgl-canvas {
12
+ left: 0;
13
+ position: absolute;
14
+ top: 0;
15
+ }
16
+
17
+ .mapbox-gl-scope .mapboxgl-map:-webkit-full-screen {
18
+ height: 100%;
19
+ width: 100%;
20
+ }
21
+
22
+ .mapbox-gl-scope .mapboxgl-canary {
23
+ background-color: salmon;
24
+ }
25
+
26
+ .mapbox-gl-scope .mapboxgl-canvas-container.mapboxgl-interactive,
27
+ .mapbox-gl-scope .mapboxgl-ctrl-group button.mapboxgl-ctrl-compass {
28
+ cursor: grab;
29
+ -webkit-user-select: none;
30
+ user-select: none;
31
+ }
32
+
33
+ .mapbox-gl-scope .mapboxgl-canvas-container.mapboxgl-interactive.mapboxgl-track-pointer {
34
+ cursor: pointer;
35
+ }
36
+
37
+ .mapbox-gl-scope .mapboxgl-canvas-container.mapboxgl-interactive:active,
38
+ .mapbox-gl-scope .mapboxgl-ctrl-group button.mapboxgl-ctrl-compass:active {
39
+ cursor: grabbing;
40
+ }
41
+
42
+ .mapbox-gl-scope .mapboxgl-canvas-container.mapboxgl-touch-zoom-rotate,
43
+ .mapbox-gl-scope .mapboxgl-canvas-container.mapboxgl-touch-zoom-rotate .mapboxgl-canvas {
44
+ touch-action: pan-x pan-y;
45
+ }
46
+
47
+ .mapbox-gl-scope .mapboxgl-canvas-container.mapboxgl-touch-drag-pan,
48
+ .mapbox-gl-scope .mapboxgl-canvas-container.mapboxgl-touch-drag-pan .mapboxgl-canvas {
49
+ touch-action: pinch-zoom;
50
+ }
51
+
52
+ .mapbox-gl-scope .mapboxgl-canvas-container.mapboxgl-touch-zoom-rotate.mapboxgl-touch-drag-pan,
53
+ .mapbox-gl-scope .mapboxgl-canvas-container.mapboxgl-touch-zoom-rotate.mapboxgl-touch-drag-pan .mapboxgl-canvas {
54
+ touch-action: none;
55
+ }
56
+
57
+ /* Control positioning */
58
+ .mapbox-gl-scope .mapboxgl-ctrl-bottom,
59
+ .mapbox-gl-scope .mapboxgl-ctrl-bottom-left,
60
+ .mapbox-gl-scope .mapboxgl-ctrl-bottom-right,
61
+ .mapbox-gl-scope .mapboxgl-ctrl-left,
62
+ .mapbox-gl-scope .mapboxgl-ctrl-right,
63
+ .mapbox-gl-scope .mapboxgl-ctrl-top,
64
+ .mapbox-gl-scope .mapboxgl-ctrl-top-left,
65
+ .mapbox-gl-scope .mapboxgl-ctrl-top-right {
66
+ pointer-events: none;
67
+ position: absolute;
68
+ z-index: 2;
69
+ }
70
+
71
+ .mapbox-gl-scope .mapboxgl-ctrl-top-left {
72
+ left: 0;
73
+ top: 0;
74
+ }
75
+
76
+ .mapbox-gl-scope .mapboxgl-ctrl-top {
77
+ left: 50%;
78
+ top: 0;
79
+ transform: translateX(-50%);
80
+ }
81
+
82
+ .mapbox-gl-scope .mapboxgl-ctrl-top-right {
83
+ right: 0;
84
+ top: 0;
85
+ }
86
+
87
+ .mapbox-gl-scope .mapboxgl-ctrl-right {
88
+ right: 0;
89
+ top: 50%;
90
+ transform: translateY(-50%);
91
+ }
92
+
93
+ .mapbox-gl-scope .mapboxgl-ctrl-bottom-right {
94
+ bottom: 0;
95
+ right: 0;
96
+ }
97
+
98
+ .mapbox-gl-scope .mapboxgl-ctrl-bottom {
99
+ bottom: 0;
100
+ left: 50%;
101
+ transform: translateX(-50%);
102
+ }
103
+
104
+ .mapbox-gl-scope .mapboxgl-ctrl-bottom-left {
105
+ bottom: 0;
106
+ left: 0;
107
+ }
108
+
109
+ .mapbox-gl-scope .mapboxgl-ctrl-left {
110
+ left: 0;
111
+ top: 50%;
112
+ transform: translateY(-50%);
113
+ }
114
+
115
+ .mapbox-gl-scope .mapboxgl-ctrl {
116
+ clear: both;
117
+ pointer-events: auto;
118
+ transform: translate(0);
119
+ }
120
+
121
+ .mapbox-gl-scope .mapboxgl-ctrl-top-left .mapboxgl-ctrl {
122
+ float: left;
123
+ margin: 10px 0 0 10px;
124
+ }
125
+
126
+ .mapbox-gl-scope .mapboxgl-ctrl-top .mapboxgl-ctrl {
127
+ float: left;
128
+ margin: 10px 0;
129
+ }
130
+
131
+ .mapbox-gl-scope .mapboxgl-ctrl-top-right .mapboxgl-ctrl {
132
+ float: right;
133
+ margin: 10px 10px 0 0;
134
+ }
135
+
136
+ .mapbox-gl-scope .mapboxgl-ctrl-bottom-right .mapboxgl-ctrl,
137
+ .mapbox-gl-scope .mapboxgl-ctrl-right .mapboxgl-ctrl {
138
+ float: right;
139
+ margin: 0 10px 10px 0;
140
+ }
141
+
142
+ .mapbox-gl-scope .mapboxgl-ctrl-bottom .mapboxgl-ctrl {
143
+ float: left;
144
+ margin: 10px 0;
145
+ }
146
+
147
+ .mapbox-gl-scope .mapboxgl-ctrl-bottom-left .mapboxgl-ctrl,
148
+ .mapbox-gl-scope .mapboxgl-ctrl-left .mapboxgl-ctrl {
149
+ float: left;
150
+ margin: 0 0 10px 10px;
151
+ }
152
+
153
+ /* Control group styling */
154
+ .mapbox-gl-scope .mapboxgl-ctrl-group {
155
+ background: #fff;
156
+ border-radius: 4px;
157
+ }
158
+
159
+ .mapbox-gl-scope .mapboxgl-ctrl-group:not(:empty) {
160
+ box-shadow: 0 0 0 2px #0000001a;
161
+ }
162
+
163
+ .mapbox-gl-scope .mapboxgl-ctrl-group button {
164
+ background-color: initial;
165
+ border: 0;
166
+ box-sizing: border-box;
167
+ cursor: pointer;
168
+ display: block;
169
+ height: 29px;
170
+ outline: none;
171
+ overflow: hidden;
172
+ padding: 0;
173
+ width: 29px;
174
+ }
175
+
176
+ .mapbox-gl-scope .mapboxgl-ctrl-group button+button {
177
+ border-top: 1px solid #ddd;
178
+ }
179
+
180
+ .mapbox-gl-scope .mapboxgl-ctrl button .mapboxgl-ctrl-icon {
181
+ background-position: 50%;
182
+ background-repeat: no-repeat;
183
+ display: block;
184
+ height: 100%;
185
+ width: 100%;
186
+ }
187
+
188
+ .mapbox-gl-scope .mapboxgl-ctrl-attrib-button:focus,
189
+ .mapbox-gl-scope .mapboxgl-ctrl-group button:focus {
190
+ box-shadow: 0 0 2px 2px #0096ff;
191
+ }
192
+
193
+ .mapbox-gl-scope .mapboxgl-ctrl button:disabled {
194
+ cursor: not-allowed;
195
+ }
196
+
197
+ .mapbox-gl-scope .mapboxgl-ctrl button:disabled .mapboxgl-ctrl-icon {
198
+ opacity: .25;
199
+ }
200
+
201
+ .mapbox-gl-scope .mapboxgl-ctrl-group button:first-child {
202
+ border-radius: 4px 4px 0 0;
203
+ }
204
+
205
+ .mapbox-gl-scope .mapboxgl-ctrl-group button:last-child {
206
+ border-radius: 0 0 4px 4px;
207
+ }
208
+
209
+ .mapbox-gl-scope .mapboxgl-ctrl-group button:only-child {
210
+ border-radius: inherit;
211
+ }
212
+
213
+ .mapbox-gl-scope .mapboxgl-ctrl button:not(:disabled):hover {
214
+ background-color: #0000000d;
215
+ }
216
+
217
+ /* Marker styles */
218
+ .mapbox-gl-scope .mapboxgl-marker {
219
+ position: absolute;
220
+ z-index: 1;
221
+ }
222
+
223
+ .mapbox-gl-scope .mapboxgl-marker svg {
224
+ display: block;
225
+ }
226
+
227
+ /* Popup styles */
228
+ .mapbox-gl-scope .mapboxgl-popup {
229
+ position: absolute;
230
+ text-align: center;
231
+ margin-bottom: 20px;
232
+ }
233
+
234
+ .mapbox-gl-scope .mapboxgl-popup-content-wrapper {
235
+ padding: 1px;
236
+ text-align: left;
237
+ border-radius: 12px;
238
+ }
239
+
240
+ .mapbox-gl-scope .mapboxgl-popup-content {
241
+ margin: 13px 24px 13px 20px;
242
+ line-height: 1.3;
243
+ font-size: 13px;
244
+ min-height: 1px;
245
+ }
246
+
247
+ .mapbox-gl-scope .mapboxgl-popup-content p {
248
+ margin: 17px 0;
249
+ }
250
+
251
+ .mapbox-gl-scope .mapboxgl-popup-tip-container {
252
+ width: 40px;
253
+ height: 20px;
254
+ position: absolute;
255
+ left: 50%;
256
+ margin-top: -1px;
257
+ margin-left: -20px;
258
+ overflow: hidden;
259
+ pointer-events: none;
260
+ }
261
+
262
+ .mapbox-gl-scope .mapboxgl-popup-tip {
263
+ width: 17px;
264
+ height: 17px;
265
+ padding: 1px;
266
+ margin: -10px auto 0;
267
+ pointer-events: auto;
268
+ -webkit-transform: rotate(45deg);
269
+ -moz-transform: rotate(45deg);
270
+ -ms-transform: rotate(45deg);
271
+ transform: rotate(45deg);
272
+ }
273
+
274
+ .mapbox-gl-scope .mapboxgl-popup-content-wrapper,
275
+ .mapbox-gl-scope .mapboxgl-popup-tip {
276
+ background: white;
277
+ color: #333;
278
+ box-shadow: 0 3px 14px rgba(0, 0, 0, 0.4);
279
+ }
280
+
281
+ .mapbox-gl-scope .mapboxgl-popup-close-button {
282
+ position: absolute;
283
+ top: 0;
284
+ right: 0;
285
+ border: none;
286
+ text-align: center;
287
+ width: 24px;
288
+ height: 24px;
289
+ font: 16px/24px Tahoma, Verdana, sans-serif;
290
+ color: #757575;
291
+ text-decoration: none;
292
+ background: transparent;
293
+ }
294
+
295
+ .mapbox-gl-scope .mapboxgl-popup-close-button:hover,
296
+ .mapbox-gl-scope .mapboxgl-popup-close-button:focus {
297
+ color: #585858;
298
+ }
299
+
300
+ /* Attribution */
301
+ .mapbox-gl-scope .mapboxgl-ctrl-attribution {
302
+ background: #fff;
303
+ background: rgba(255, 255, 255, 0.8);
304
+ margin: 0;
305
+ }
306
+
307
+ .mapbox-gl-scope .mapboxgl-ctrl-attribution,
308
+ .mapbox-gl-scope .mapboxgl-ctrl-scale-line {
309
+ padding: 0 5px;
310
+ color: #333;
311
+ line-height: 1.4;
312
+ }
313
+
314
+ .mapbox-gl-scope .mapboxgl-ctrl-attribution a {
315
+ text-decoration: none;
316
+ }
317
+
318
+ .mapbox-gl-scope .mapboxgl-ctrl-attribution a:hover,
319
+ .mapbox-gl-scope .mapboxgl-ctrl-attribution a:focus {
320
+ text-decoration: underline;
321
+ }
322
+
323
+ /* Hide attribution by default */
324
+ .mapbox-gl-scope .mapboxgl-ctrl-attribution {
325
+ display: none !important;
326
+ }
327
+
328
+ .mapbox-gl-scope .mapboxgl-ctrl-logo {
329
+ display: none !important;
330
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "datastake-daf",
3
- "version": "0.6.817",
3
+ "version": "0.6.818",
4
4
  "dependencies": {
5
5
  "@ant-design/icons": "^5.2.5",
6
6
  "@antv/g2": "^5.1.1",
@@ -8,3 +8,4 @@
8
8
 
9
9
 
10
10
 
11
+
@@ -1,4 +1,4 @@
1
- import React, { useRef, useEffect } from "react";
1
+ import React, { useRef, useEffect, useCallback } from "react";
2
2
  import { Radar } from "@antv/g2plot";
3
3
  import { theme } from "antd";
4
4
  import { renderTooltip } from "../../../../utils/tooltip";
@@ -39,6 +39,46 @@ const RadarChart = ({
39
39
  legendStyle
40
40
  } = useLegendConfig({legendConfig, isPdf});
41
41
 
42
+ // Helper function to wrap long text labels to multiple lines
43
+ // Ensures text is never hidden - wraps to multiple lines if needed
44
+ const wrapLabel = useCallback((text, maxLength = 10) => {
45
+ if (!text) {
46
+ return '';
47
+ }
48
+
49
+ const formattedText = formattedXAxis(text);
50
+
51
+ // If text is short enough, return as is
52
+ if (formattedText.length <= maxLength) {
53
+ return formattedText;
54
+ }
55
+
56
+ // Split by spaces to find word boundaries
57
+ const words = formattedText.split(' ');
58
+ const lines = [];
59
+ let currentLine = '';
60
+
61
+ words.forEach((word, index) => {
62
+ // If adding this word would exceed maxLength, start a new line
63
+ if (currentLine && (currentLine + ' ' + word).length > maxLength) {
64
+ if (currentLine) {
65
+ lines.push(currentLine);
66
+ }
67
+ currentLine = word;
68
+ } else {
69
+ currentLine = currentLine ? currentLine + ' ' + word : word;
70
+ }
71
+
72
+ // If it's the last word, add it to lines
73
+ if (index === words.length - 1 && currentLine) {
74
+ lines.push(currentLine);
75
+ }
76
+ });
77
+
78
+ // Ensure we always return something - never empty
79
+ return lines.length > 0 ? lines.join('\n') : formattedText;
80
+ }, [formattedXAxis]);
81
+
42
82
  useEffect(() => {
43
83
  if (!containerRef.current) {
44
84
  return;
@@ -59,9 +99,19 @@ const RadarChart = ({
59
99
  }
60
100
  : { showCrosshairs: false, showMarkers: true, ...tooltipConfig },
61
101
  color: color || token.colorPrimary7,
102
+ // Increased padding to ensure labels are never clipped
103
+ padding: [50, 50, 50, 50],
62
104
  xAxis: {
63
105
  label: {
64
- formatter: formattedXAxis,
106
+ formatter: (text) => wrapLabel(text, 10),
107
+ offset: 15,
108
+ autoRotate: false,
109
+ autoHide: false, // Never hide labels
110
+ autoEllipsis: false, // Never truncate with ellipsis
111
+ style: {
112
+ fontSize: 12,
113
+ fill: '#666',
114
+ },
65
115
  },
66
116
  line: null,
67
117
  tickLine: null,
@@ -129,6 +179,7 @@ const RadarChart = ({
129
179
  formattedXAxis,
130
180
  score,
131
181
  token.colorPrimary7,
182
+ wrapLabel,
132
183
  rest,
133
184
  ]);
134
185
 
@@ -2,7 +2,7 @@ import styled from "styled-components";
2
2
 
3
3
  const Container = styled.div`
4
4
  height: ${props => props.height || '300px'};
5
- width: ${props => props.isPdf ? (props.width ? props.width : '1000px') : 'calc(100% - 48px)'};
5
+ width: ${props => props.isPdf ? (props.width ? props.width : '1000px') : 'calc(100% - 48px)'};
6
6
  `;
7
7
 
8
8
  export default Container;
@@ -16,16 +16,12 @@ export default function VegetationWidget({
16
16
  }) {
17
17
  let vegetationConfig = getVegetationConfig();
18
18
 
19
- // Get all VEGETATION_KEYS values before filtering (needed for mapping check)
20
19
  const allVegetationKeys = vegetationConfig.map(item => item.key);
21
20
 
22
- // Filter to show only specific keys if filterKeys is provided
23
21
  if (filterKeys && Array.isArray(filterKeys)) {
24
22
  vegetationConfig = vegetationConfig.filter(item => filterKeys.includes(item.key));
25
23
  }
26
24
 
27
- // Map growthObservations to VEGETATION_KEYS
28
- // Handle both formats: growthObservations keys (e.g., "yellowing_leaves") and VEGETATION_KEYS (e.g., "yellowing")
29
25
  const mappedGrowthObservations = Array.isArray(growthObservations)
30
26
  ? growthObservations
31
27
  .map(obs => {
@@ -1,6 +1,6 @@
1
1
  import React, { useMemo } from 'react';
2
2
  import { Widget, BarChart } from '../../../../../../../../src/index.js';
3
- import { calculateNiceAxisConfig, mergeDefaultCategories } from '../chartHelpers.js';
3
+ import { calculateNaturalAxisConfig, mergeDefaultCategories } from '../chartHelpers.js';
4
4
 
5
5
  const ObservedFauna = ({
6
6
  observedFaunaChart,
@@ -32,16 +32,11 @@ const ObservedFauna = ({
32
32
  }, [observedFaunaChart, options]);
33
33
 
34
34
  const xAxisConfig = useMemo(() => {
35
- return calculateNiceAxisConfig(
36
- chartData,
37
- 'value',
38
- 1.2, // multiplier: 20% padding
39
- {
40
- min: 0,
41
- max: 10,
42
- tickMethod: () => [0, 2, 4, 6, 8, 10]
43
- }
44
- );
35
+ const maxValue = chartData && chartData.length > 0
36
+ ? Math.max(...chartData.map(item => Number(item?.value) || 0))
37
+ : 0;
38
+
39
+ return calculateNaturalAxisConfig(maxValue);
45
40
  }, [chartData]);
46
41
 
47
42
  return (
@@ -83,8 +83,8 @@ const BiodiversityHabitat = ({
83
83
  t={t}
84
84
  />
85
85
 
86
- <div style={{ display: "flex", gap: "24px" }}>
87
- <section style={{ flex: 1 }}>
86
+ <div style={{ display: "flex", gap: "24px", flexWrap: "wrap" }}>
87
+ <section style={{ flex: 1, minWidth: "300px" }}>
88
88
  <ObservedFauna
89
89
  observedFaunaChart={observedFauna}
90
90
  t={t}
@@ -92,11 +92,13 @@ const BiodiversityHabitat = ({
92
92
  />
93
93
  </section>
94
94
 
95
+ <section style={{ flex: 1, minWidth: "300px" }}>
95
96
  <InvasiveSpecies
96
97
  invasiveSpeciesChart={invasiveSpecies}
97
98
  t={t}
98
99
  options={options}
99
100
  />
101
+ </section>
100
102
 
101
103
  </div>
102
104
  </Widget>
@@ -2,7 +2,14 @@ import React, { useMemo, useCallback } from 'react';
2
2
  import { Widget, PieChart } from '../../../../../../../../src/index.js';
3
3
  import { renderTooltipJsx } from '../../../../../../utils/tooltip.js';
4
4
 
5
- const COLORS = ['#6AD99E', '#F5C2AC', '#F0A888', '#DF571E', '#C04B19', '#9B3D14', '#7A2F0F'];
5
+ // Color mapping for planted species
6
+ const PLANTED_SPECIES_COLORS = {
7
+ rhyzophora_mangle: '#016C6E',
8
+ rhyzophora: '#00AEB1',
9
+ rhyzophora_sp: '#A0EBEC',
10
+ };
11
+
12
+ const DEFAULT_COLOR = '#9E9E9E';
6
13
 
7
14
  const PlantedSpecies = ({
8
15
  plantedSpeciesChart,
@@ -27,7 +34,7 @@ const PlantedSpecies = ({
27
34
  return data.map((item, index) => ({
28
35
  value: Number(item?.count) || 0,
29
36
  percent: total > 0 ? (Number(item?.count) || 0) / total : 0,
30
- color: COLORS[index % COLORS.length],
37
+ color: PLANTED_SPECIES_COLORS[item?.name] || DEFAULT_COLOR,
31
38
  label: optionsMap[item?.name] || item?.name || '',
32
39
  key: item?.name || `item-${index}`,
33
40
  }));
@@ -44,18 +51,26 @@ const PlantedSpecies = ({
44
51
  return null;
45
52
  }
46
53
 
54
+ // Calculate total from all items
55
+ const total = pieData.reduce((sum, dataItem) => sum + (dataItem.value || 0), 0);
56
+
57
+ // Show all items in the tooltip, sorted by value (descending)
58
+ const allItems = pieData
59
+ .filter(dataItem => dataItem.value > 0)
60
+ .sort((a, b) => b.value - a.value)
61
+ .map(dataItem => ({
62
+ color: dataItem.color,
63
+ label: dataItem.label || '',
64
+ value: dataItem.value || 0,
65
+ }));
66
+
47
67
  return renderTooltipJsx({
48
68
  title: t("Planted Species"),
49
- items: [
50
- {
51
- color: item.color,
52
- label: item.label || '',
53
- value: item.value || 0,
54
- },
55
- ],
69
+ subTitle: total.toLocaleString(),
70
+ items: allItems,
56
71
  });
57
72
  },
58
- [t, isEmpty]
73
+ [t, isEmpty, pieData]
59
74
  );
60
75
 
61
76
  return (
@@ -1,6 +1,6 @@
1
1
  import React, { useMemo } from 'react';
2
2
  import { Widget, ColumnChart } from '../../../../../../../../src/index.js';
3
- import { calculateNiceAxisConfig, mergeDefaultCategories } from '../chartHelpers.js';
3
+ import { calculateNaturalAxisConfig, mergeDefaultCategories } from '../chartHelpers.js';
4
4
 
5
5
  // Default height ranges to always display
6
6
  const DEFAULT_HEIGHT_RANGES = [
@@ -26,16 +26,13 @@ const SeedlingsHeight = ({
26
26
  }, [seedlingsHeightChart]);
27
27
 
28
28
  const yAxisConfig = useMemo(() => {
29
- return calculateNiceAxisConfig(
30
- chartData,
31
- 'value',
32
- 2, // multiplier: double the max value
33
- {
34
- min: 0,
35
- max: 20,
36
- tickMethod: () => [0, 5, 10, 15, 20]
37
- }
38
- );
29
+ // Calculate max value from data
30
+ const maxValue = chartData && chartData.length > 0
31
+ ? Math.max(...chartData.map(item => Number(item?.value) || 0))
32
+ : 0;
33
+
34
+ // Use the helper function to calculate natural number axis configuration
35
+ return calculateNaturalAxisConfig(maxValue);
39
36
  }, [chartData]);
40
37
 
41
38
  return (
@@ -58,10 +55,10 @@ const SeedlingsHeight = ({
58
55
  const item = data[0]?.data || data[0];
59
56
  return {
60
57
  title: t("Seedlings Height"),
61
- subTitle: title,
62
58
  items: [
63
59
  {
64
- label: t("Count"),
60
+ color: "#016C6E",
61
+ label: title,
65
62
  value: item?.value || 0,
66
63
  },
67
64
  ],
@@ -2,7 +2,22 @@ import React, { useMemo, useCallback } from 'react';
2
2
  import { Widget, PieChart } from '../../../../../../../../src/index.js';
3
3
  import { renderTooltipJsx } from '../../../../../../utils/tooltip.js';
4
4
 
5
- const COLORS = ['#016C6E', '#F5C2AC', '#F0A888', '#DF571E', '#C04B19', '#9B3D14', '#7A2F0F'];
5
+ // Color mapping for vegetation health conditions
6
+ const VEGETATION_HEALTH_COLORS = {
7
+ healthy_leaves: '#6AD99E',
8
+ white_spots: '#E8F0F0',
9
+ yellowing_leaves: '#CDC14F',
10
+ black_spots: '#2E3131',
11
+ reddish_spots: '#CB2525',
12
+ leaf_mosaic: '#B59E76',
13
+ spider_webs: '#F8F6EF',
14
+ damage_from_insects: '#DE8954',
15
+ dry_dead_leaves: '#767870',
16
+ no_leaves: '#F97066',
17
+ other: '#BDBDBD',
18
+ };
19
+
20
+ const DEFAULT_COLOR = '#9E9E9E';
6
21
 
7
22
  const VegetationHealth = ({
8
23
  vegetationHealthChart,
@@ -26,15 +41,15 @@ const VegetationHealth = ({
26
41
  return data.map((item, index) => ({
27
42
  value: Number(item?.count) || 0,
28
43
  percent: total > 0 ? (Number(item?.count) || 0) / total : 0,
29
- color: COLORS[index % COLORS.length],
44
+ color: VEGETATION_HEALTH_COLORS[item?.name] || DEFAULT_COLOR,
30
45
  label: optionsMap[item?.name] || item?.name || '',
31
46
  key: item?.name || `item-${index}`,
32
47
  }));
33
48
  }, [vegetationHealthChart, optionsMap]);
34
49
 
35
50
  const isEmpty = useMemo(() => {
36
- return !vegetationHealthChart || vegetationHealthChart.length === 0 ||
37
- vegetationHealthChart.every(item => !item?.count || Number(item.count) === 0);
51
+ return !vegetationHealthChart || vegetationHealthChart.length === 0 ||
52
+ vegetationHealthChart.every(item => !item?.count || Number(item.count) === 0);
38
53
  }, [vegetationHealthChart]);
39
54
 
40
55
  const getTooltipChildren = useCallback(