Skip to content

Commit a3568c8

Browse files
Fix #11814 Interactive legend is not showing the correct icons (#11823)
1 parent a916ccf commit a3568c8

File tree

6 files changed

+855
-28
lines changed

6 files changed

+855
-28
lines changed
Lines changed: 354 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,354 @@
1+
/*
2+
* Copyright 2025, GeoSolutions Sas.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
import React from 'react';
10+
11+
const MARKS_TYPES = {
12+
line: ({ mark }) => {
13+
return (
14+
<line
15+
x1={mark.x1}
16+
y1={mark.y1}
17+
x2={mark.x2}
18+
y2={mark.y2}
19+
stroke={mark.stroke}
20+
strokeWidth={mark["stroke-width"]}
21+
strokeOpacity={mark["stroke-opacity"]}
22+
strokeLinecap={mark["stroke-linecap"]}
23+
strokeLinejoin={mark["stroke-linejoin"]}
24+
opacity={mark.opacity}
25+
/>
26+
);
27+
},
28+
polygon: ({ mark }) => {
29+
return (
30+
<polygon
31+
points={mark.points}
32+
stroke={mark.stroke}
33+
strokeWidth={mark["stroke-width"]}
34+
strokeOpacity={mark["stroke-opacity"]}
35+
fill={mark.fill}
36+
fillOpacity={mark["fill-opacity"]}
37+
opacity={mark.opacity}
38+
/>
39+
);
40+
},
41+
circle: ({ mark }) => {
42+
return (
43+
<circle
44+
cx={mark.cx}
45+
cy={mark.cy}
46+
r={mark.r}
47+
fill={mark.fill}
48+
fillOpacity={mark["fill-opacity"]}
49+
stroke={mark.stroke}
50+
strokeWidth={mark["stroke-width"]}
51+
strokeOpacity={mark["stroke-opacity"]}
52+
/>
53+
);
54+
},
55+
polyline: ({ mark }) => {
56+
return (
57+
<polyline
58+
points={mark.points}
59+
stroke={mark.stroke}
60+
strokeWidth={mark["stroke-width"]}
61+
strokeOpacity={mark["stroke-opacity"]}
62+
opacity={mark.opacity}
63+
fill={mark.fill}
64+
fillOpacity={mark["fill-opacity"]}
65+
/>
66+
);
67+
}
68+
};
69+
70+
const GraphicPattern = ({ id, symbolizer, type }) => {
71+
const graphic = type === 'line' ? symbolizer["graphic-stroke"] : symbolizer["graphic-fill"];
72+
const mark = graphic?.graphics?.[0];
73+
74+
if (!graphic || !mark) return null;
75+
76+
const size = Number(graphic.size || 10);
77+
const rotation = Number(graphic.rotation || 0);
78+
const opacity = Number(graphic.opacity ?? 1);
79+
80+
const margin =
81+
symbolizer["vendor-options"]?.["graphic-margin"]
82+
?.split(" ")
83+
.map(Number) || [0, 0];
84+
85+
const patternWidth = size + margin[0];
86+
const patternHeight = size + margin[1];
87+
88+
const getMarkTypes = () => {
89+
switch (mark.mark) {
90+
case "shape://horline":
91+
return MARKS_TYPES.line({
92+
mark: {
93+
...mark,
94+
x1: 0,
95+
y1: size / 2,
96+
x2: size,
97+
y2: size / 2,
98+
opacity
99+
}
100+
});
101+
case "line":
102+
case "shape://vertline":
103+
return MARKS_TYPES.line({
104+
mark: {
105+
...mark,
106+
x1: size / 2,
107+
y1: 0,
108+
x2: size / 2,
109+
y2: size,
110+
opacity
111+
}
112+
});
113+
case "shape://slash":
114+
return MARKS_TYPES.line({
115+
mark: {
116+
...mark,
117+
x1: 0,
118+
y1: size,
119+
x2: size,
120+
y2: 0,
121+
opacity
122+
}
123+
});
124+
case "shape://backslash":
125+
return MARKS_TYPES.line({
126+
mark: {
127+
...mark,
128+
x1: 0,
129+
y1: 0,
130+
x2: size,
131+
y2: size,
132+
opacity
133+
}
134+
});
135+
case "circle":
136+
return MARKS_TYPES.circle({
137+
mark: {
138+
...mark,
139+
cx: size / 2,
140+
cy: size / 2,
141+
r: size / 4
142+
}
143+
});
144+
case "triangle":
145+
return MARKS_TYPES.polygon({ mark: { ...mark, points: `${size / 2},0 0,${size} ${size},${size}` } });
146+
case "star":
147+
const cx = size / 2;
148+
const cy = size / 2;
149+
const outerR = size / 2;
150+
const innerR = size / 2.5;
151+
152+
const points = [];
153+
for (let i = 0; i < 10; i++) {
154+
const angle = (Math.PI / 5) * i - Math.PI / 2;
155+
const r = i % 2 === 0 ? outerR : innerR;
156+
points.push(
157+
`${cx + r * Math.cos(angle)},${cy + r * Math.sin(angle)}`
158+
);
159+
}
160+
return MARKS_TYPES.polygon({ mark: { ...mark, points, fill: mark.fill ?? "#000" } });
161+
case "cross":
162+
return MARKS_TYPES.polygon({ mark: { ...mark, points: `${size / 2},0 ${size / 2},${size} ${size},${size / 2}` } });
163+
case "shape://dot":
164+
return MARKS_TYPES.circle({
165+
mark: {
166+
...mark,
167+
cx: size / 2,
168+
cy: size / 2,
169+
r: size / 8,
170+
opacity
171+
}
172+
});
173+
case "shape://plus":
174+
return <>
175+
{MARKS_TYPES.line({
176+
mark: {
177+
...mark,
178+
x1: 0,
179+
y1: size / 2,
180+
x2: size,
181+
y2: size / 2,
182+
opacity
183+
}
184+
})}
185+
{MARKS_TYPES.line({
186+
mark: {
187+
...mark,
188+
x1: size / 2,
189+
y1: 0,
190+
x2: size / 2,
191+
y2: size,
192+
opacity
193+
}
194+
})}
195+
</>;
196+
case "shape://times":
197+
return <>
198+
{MARKS_TYPES.line({
199+
mark: {
200+
...mark,
201+
x1: 0,
202+
y1: size,
203+
x2: size,
204+
y2: 0,
205+
opacity
206+
}
207+
})}
208+
{MARKS_TYPES.line({
209+
mark: {
210+
...mark,
211+
x1: 0,
212+
y1: size,
213+
x2: size,
214+
y2: 0,
215+
opacity
216+
}
217+
})}
218+
</>;
219+
case "shape://oarrow":
220+
return MARKS_TYPES.polyline({
221+
mark: {
222+
...mark,
223+
points: `
224+
0,0
225+
${size},${size / 2}
226+
0,${size}
227+
`,
228+
fill: "none",
229+
stroke: mark.stroke,
230+
strokeWidth: mark["stroke-width"],
231+
strokeOpacity: mark["stroke-opacity"],
232+
opacity
233+
}
234+
});
235+
236+
case "shape://carrow":
237+
return MARKS_TYPES.polygon({
238+
mark: {
239+
...mark,
240+
points: `0,0 ${size},${size / 2} 0,${size}`,
241+
opacity
242+
}
243+
});
244+
case "extshape://triangle":
245+
return MARKS_TYPES.polygon({ mark: { ...mark, points: `${size / 2},0 0,${size} ${size},${size}`, opacity, fill: mark.fill ?? "none" } });
246+
case "extshape://emicircle":
247+
const rx = size / 2;
248+
const ry = size / 4;
249+
return (
250+
<path
251+
d={`M 0,${ry * 2} A ${rx} ${ry} 0 0 1 ${rx * 2},${ry * 2} L ${rx * 2},${ry * 2} Z`}
252+
fill={mark.fill ?? "none"}
253+
stroke={mark.stroke}
254+
strokeWidth={mark["stroke-width"]}
255+
opacity={mark.opacity}
256+
/>
257+
);
258+
case "extshape://triangleemicircle": {
259+
const triangleHeight = size / 2;
260+
const semicircleRadius = size / 4;
261+
return (
262+
<>
263+
{MARKS_TYPES.polygon({
264+
mark: {
265+
...mark,
266+
points: `
267+
${size / 2},0
268+
0,${triangleHeight}
269+
${size},${triangleHeight}
270+
`,
271+
fill: mark.fill ?? "none",
272+
opacity
273+
}
274+
})}
275+
<path
276+
d={`
277+
M ${size / 2 - semicircleRadius},${triangleHeight}
278+
A ${semicircleRadius} ${semicircleRadius} 0 0 0 ${size / 2 + semicircleRadius},${triangleHeight}
279+
L ${size / 2 + semicircleRadius},${triangleHeight + semicircleRadius}
280+
L ${size / 2 - semicircleRadius},${triangleHeight + semicircleRadius}
281+
Z
282+
`}
283+
fill={mark.fill ?? "none"}
284+
stroke={mark.stroke}
285+
strokeWidth={mark["stroke-width"] || 1}
286+
strokeOpacity={mark["stroke-opacity"]}
287+
opacity={opacity}
288+
/>
289+
</>
290+
);
291+
}
292+
case "extshape://narrow":
293+
return MARKS_TYPES.polygon({
294+
mark: {
295+
...mark,
296+
points: `
297+
${size / 2},0
298+
0,${size}
299+
${size},${size}
300+
`,
301+
fill: mark.fill ?? "none",
302+
opacity
303+
}
304+
});
305+
case "extshape://sarrow":
306+
return MARKS_TYPES.polygon({
307+
mark: {
308+
...mark,
309+
points: `
310+
0,0
311+
${size},0
312+
${size / 2},${size}
313+
`,
314+
fill: mark.fill ?? "none",
315+
opacity
316+
}
317+
});
318+
case "square":
319+
default:
320+
return MARKS_TYPES.polygon({
321+
mark: {
322+
...mark,
323+
points: `0,0 ${size},0 ${size},${size} 0,${size}`,
324+
fill: mark.fill ?? "none",
325+
opacity
326+
}
327+
});
328+
}
329+
};
330+
331+
return (
332+
<defs>
333+
<pattern
334+
id={id}
335+
patternUnits="userSpaceOnUse"
336+
width={patternWidth}
337+
height={patternHeight}
338+
>
339+
<g
340+
transform={`
341+
rotate(${rotation}
342+
${patternWidth / 2}
343+
${patternHeight / 2})
344+
`}
345+
opacity={opacity}
346+
>
347+
{getMarkTypes()}
348+
</g>
349+
</pattern>
350+
</defs>
351+
);
352+
};
353+
354+
export default GraphicPattern;

0 commit comments

Comments
 (0)