Skip to content

Commit 3026a1e

Browse files
Copilotgatopeich
andauthored
Optimize marker render pipeline via SVG <symbol>/<use> (#7)
* Initial plan * Initial plan: replace symbolFuncs with symbolPaths and use SVG symbol/use rendering Co-authored-by: gatopeich <7722268+gatopeich@users.noreply.github.com> Agent-Logs-Url: https://github.com/gatopeich/plotly.js/sessions/a8812772-a901-48c4-a259-cf15aee5b4ff * Replace symbolFuncs/makePointPath with symbolPaths + SVG symbol/use rendering - symbol_defs.js: replace f() functions with p: precomputed SVG paths at r=20 with integer coordinates; remove n: magic numbers, align(), parse-svg-path - index.js: add lookupSymbol() (handles names, numeric codes, custom SVG paths), ensureSymbolDef() with overflow=visible, singlePointStyle/selectedPointStyle use <use> elements; symbolNumber() kept for compat; translatePoint() handles <use> - scatter/plot.js, scatter/style.js: use.point instead of path.point - scattergeo/plot.js, box/plot.js, box/style.js: same - legend/style.js: use.scatterpts instead of path.scatterpts - scattergl/convert.js: use lookupSymbol + rotatePath; viewBox matches SYMBOL_SIZE=20 - Tests: fix getMarkerSize helper (* 40), update path.point selectors, remove custom-function tests (feature removed), add scatter_symbol_perf_test.js Co-authored-by: gatopeich <7722268+gatopeich@users.noreply.github.com> Agent-Logs-Url: https://github.com/gatopeich/plotly.js/sessions/a8812772-a901-48c4-a259-cf15aee5b4ff * Address review feedback: revert whitespace changes, remove fallback, revert package-lock.json Co-authored-by: gatopeich <7722268+gatopeich@users.noreply.github.com> Agent-Logs-Url: https://github.com/gatopeich/plotly.js/sessions/8c978e3d-55d6-407b-bdb5-647e5dcac295 * Clarify rotatePath() is only for scattergl SDF pipeline; SVG markers use transform Co-authored-by: gatopeich <7722268+gatopeich@users.noreply.github.com> Agent-Logs-Url: https://github.com/gatopeich/plotly.js/sessions/5157d817-2ab4-4a91-bd4c-2b190a527478 * Update weather map demos to use new SVG symbol/use API with static paths at r=20; rebuild dist Co-authored-by: gatopeich <7722268+gatopeich@users.noreply.github.com> Agent-Logs-Url: https://github.com/gatopeich/plotly.js/sessions/e0bacfbc-a436-49b7-803f-385d56b0c46a * Clean up decimal precision in SVG path strings in demo files Co-authored-by: gatopeich <7722268+gatopeich@users.noreply.github.com> Agent-Logs-Url: https://github.com/gatopeich/plotly.js/sessions/e0bacfbc-a436-49b7-803f-385d56b0c46a * Add weather map SVG output file to repo Co-authored-by: gatopeich <7722268+gatopeich@users.noreply.github.com> Agent-Logs-Url: https://github.com/gatopeich/plotly.js/sessions/36cb92ca-28a3-4ade-85ab-c2834b756f7e * Remove temporary weather demo capture files from repo Co-authored-by: gatopeich <7722268+gatopeich@users.noreply.github.com> Agent-Logs-Url: https://github.com/gatopeich/plotly.js/sessions/36cb92ca-28a3-4ade-85ab-c2834b756f7e * Scope _symMap to defs DOM node (per-SVG, not per-layout) Co-authored-by: gatopeich <7722268+gatopeich@users.noreply.github.com> Agent-Logs-Url: https://github.com/gatopeich/plotly.js/sessions/7dd7be3b-6019-4fc2-9a09-e71a0c331eab * Remove temp verify_ids.html demo file Co-authored-by: gatopeich <7722268+gatopeich@users.noreply.github.com> Agent-Logs-Url: https://github.com/gatopeich/plotly.js/sessions/7dd7be3b-6019-4fc2-9a09-e71a0c331eab * Pre-build dot-variant paths at init; ensureSymbolDef reduced to single append Co-authored-by: gatopeich <7722268+gatopeich@users.noreply.github.com> Agent-Logs-Url: https://github.com/gatopeich/plotly.js/sessions/742adff1-2175-4ddc-9511-71fb5d0778a9 * ensureSymbolDef: per-SVG map on defs node; custom paths get c0,c1,… ids; no global singleton Co-authored-by: gatopeich <7722268+gatopeich@users.noreply.github.com> Agent-Logs-Url: https://github.com/gatopeich/plotly.js/sessions/76f3a75a-762e-4119-ba75-436d372c01d8 * ensureSymbolDef: uniform path→id map; single lookup for all symbol types Co-authored-by: gatopeich <7722268+gatopeich@users.noreply.github.com> Agent-Logs-Url: https://github.com/gatopeich/plotly.js/sessions/6904d12d-0514-441d-803a-460cad616d39 * plan: give n=100 its own <symbol> via open-encoded sym.n Co-authored-by: gatopeich <7722268+gatopeich@users.noreply.github.com> Agent-Logs-Url: https://github.com/gatopeich/plotly.js/sessions/bcb0d524-eaad-4505-aa70-46ef5f8ed344 * Document why Z cannot replace CSS for open/closed markers; pre-build all 4 variant paths; sym.n matches legacy numeric encoding Co-authored-by: gatopeich <7722268+gatopeich@users.noreply.github.com> Agent-Logs-Url: https://github.com/gatopeich/plotly.js/sessions/bcb0d524-eaad-4505-aa70-46ef5f8ed344 * gitignore: stop tracking dist build artifacts (JS bundles, plot-schema.json, translation-keys.txt) Co-authored-by: gatopeich <7722268+gatopeich@users.noreply.github.com> Agent-Logs-Url: https://github.com/gatopeich/plotly.js/sessions/5ab0bc3d-aecd-4fe4-b7f6-21de2d1f96c1 * drawing: remove expanded Z-command reasoning comment Co-authored-by: gatopeich <7722268+gatopeich@users.noreply.github.com> Agent-Logs-Url: https://github.com/gatopeich/plotly.js/sessions/33b07737-3bca-440f-9dbd-77fc6b0f8e1d --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: gatopeich <7722268+gatopeich@users.noreply.github.com>
1 parent cef92be commit 3026a1e

118 files changed

Lines changed: 747 additions & 1589702 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ build/*
66

77
dist/*.LICENSE.txt
88
dist/*.css
9+
dist/*.js
10+
dist/*.min.js
11+
dist/plot-schema.json
12+
dist/translation-keys.txt
913

1014
npm-debug.log*
1115
*.sublime*

devtools/demos/all_demos.html

Lines changed: 110 additions & 151 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<html>
33
<head>
44
<meta charset="utf-8">
5-
<title>Custom Marker Functions - All Demos</title>
5+
<title>Custom Markers - SVG path strings (New API)</title>
66
<script src="../../dist/plotly.js"></script>
77
<style>
88
body { font-family: Arial, sans-serif; margin: 20px; max-width: 900px; margin: 0 auto; padding: 20px; }
@@ -15,195 +15,135 @@
1515
</style>
1616
</head>
1717
<body>
18-
<h1>Custom Marker Functions</h1>
18+
<h1>Custom Markers — SVG path strings</h1>
1919
<div class="info">
20-
Pass functions as <code>marker.symbol</code> to create custom shapes.<br>
21-
<strong>Simple:</strong> <code>function(r)</code> — r is marker radius<br>
22-
<strong>Data-aware:</strong> <code>function(r, customdata)</code> — access per-point data
20+
Pass SVG path strings as <code>marker.symbol</code> to create custom shapes.<br>
21+
Paths are precomputed at <strong>r=20</strong>; Plotly scales them by <code>size/20</code>.<br>
22+
Use an <strong>array</strong> for per-point shapes. Rotation uses <code>marker.angle</code>
23+
(applied as <code>transform="rotate()"</code> via SVG <code>&lt;use&gt;</code>).
2324
</div>
2425

2526
<!-- Demo 1: Basic custom markers -->
2627
<h2>1. Basic Custom Markers</h2>
27-
<p>Define functions returning SVG path strings. Mix with built-in symbols.</p>
28+
<p>Precomputed SVG paths at r=20. Mix with built-in symbol names.</p>
2829
<div id="plot1" class="plot"></div>
2930
<pre>
30-
// Heart shape
31-
function heart(r) {
32-
var x = r * 0.6, y = r * 0.8;
33-
return 'M0,' + (-y/2) +
34-
'C' + (-x) + ',' + (-y) + ' ' + (-x*2) + ',' + (-y/3) + ' ' + (-x*2) + ',0' +
35-
'C' + (-x*2) + ',' + (y/2) + ' 0,' + y + ' 0,' + (y*1.5) +
36-
'C0,' + y + ' ' + (x*2) + ',' + (y/2) + ' ' + (x*2) + ',0' +
37-
'C' + (x*2) + ',' + (-y/3) + ' ' + x + ',' + (-y) + ' 0,' + (-y/2) + 'Z';
38-
}
31+
// Heart shape at r=20
32+
var HEART = 'M0,-8C-12,-16 -24,-5.33 -24,0C-24,8 0,16 0,24C0,16 24,8 24,0C24,-5.33 12,-16 0,-8Z';
3933

40-
// 5-point star
41-
function star(r) {
42-
var path = 'M';
43-
for (var i = 0; i < 10; i++) {
44-
var rad = i % 2 === 0 ? r : r * 0.4;
45-
var ang = i * Math.PI / 5 - Math.PI / 2;
46-
path += (i ? 'L' : '') + (rad * Math.cos(ang)).toFixed(2) + ',' + (rad * Math.sin(ang)).toFixed(2);
47-
}
48-
return path + 'Z';
49-
}
34+
// 5-point star at r=20
35+
var STAR = 'M0,-20L4.70,-6.47L19.02,-6.18L7.61,2.47L11.76,16.18' +
36+
'L0,8L-11.76,16.18L-7.61,2.47L-19.02,-6.18L-4.70,-6.47Z';
5037

5138
Plotly.newPlot('plot1', [{
5239
x: [1, 2, 3, 4, 5],
5340
y: [2, 3, 4, 3, 2],
5441
mode: 'markers+lines',
5542
marker: {
56-
symbol: [heart, star, 'circle', star, heart], // mix functions and strings
43+
// mix path strings and built-in symbol names
44+
symbol: [HEART, STAR, 'circle', STAR, HEART],
5745
size: 25,
5846
color: ['red', 'gold', 'blue', 'gold', 'red']
5947
}
6048
}]);</pre>
6149

62-
<!-- Demo 2: Data-aware markers -->
63-
<h2>2. Data-Aware Markers</h2>
64-
<p>Access <code>customdata[i]</code> to vary shape per point.</p>
50+
<!-- Demo 2: Per-point shapes driven by data -->
51+
<h2>2. Per-Point Shapes Driven by Data</h2>
52+
<p>Build a per-point symbol array from your data instead of using a function.</p>
6553
<div id="plot2" class="plot"></div>
6654
<pre>
67-
function shapeByData(r, customdata) {
68-
if (customdata === 'star') {
69-
// Star shape
70-
var path = 'M';
71-
for (var i = 0; i < 10; i++) {
72-
var rad = i % 2 === 0 ? r : r * 0.4;
73-
var ang = i * Math.PI / 5 - Math.PI / 2;
74-
path += (i ? 'L' : '') + (rad * Math.cos(ang)).toFixed(2) + ',' + (rad * Math.sin(ang)).toFixed(2);
75-
}
76-
return path + 'Z';
77-
}
78-
if (customdata === 'big') {
79-
r *= 1.4; // Larger diamond
80-
}
81-
// Default: diamond
82-
return 'M' + r + ',0L0,' + r + 'L-' + r + ',0L0,-' + r + 'Z';
83-
}
55+
var DIAMOND = 'M20,0L0,20L-20,0L0,-20Z';
56+
var BIG_DIAMOND = 'M28,0L0,28L-28,0L0,-28Z'; // 1.4× size
57+
var STAR = '...'; // (same as Demo 1)
58+
59+
var types = ['normal', 'big', 'star', 'normal'];
60+
var symbols = types.map(function(t) {
61+
if (t === 'big') return BIG_DIAMOND;
62+
if (t === 'star') return STAR;
63+
return DIAMOND;
64+
});
8465

8566
Plotly.newPlot('plot2', [{
8667
x: [1, 2, 3, 4],
8768
y: [1, 1, 1, 1],
88-
customdata: ['normal', 'big', 'star', 'normal'],
8969
mode: 'markers',
90-
marker: { symbol: shapeByData, size: 25, color: '#10b981' }
70+
marker: { symbol: symbols, size: 25, color: '#10b981' }
9171
}]);</pre>
9272

9373
<!-- Demo 3: Weather map -->
94-
<h2>3. Weather Map</h2>
95-
<p>Complex example: sun, cloud, and wind barbs based on weather data.</p>
74+
<h2>3. Weather Map with Rotation</h2>
75+
<p>Per-point symbols + <code>marker.angle</code> for wind direction rotation via SVG.</p>
9676
<div id="plot3" class="plot"></div>
9777
<pre>
98-
function weatherMarker(r, data) {
99-
if (data.type === 'sunny') {
100-
// Sun: circle with rays
101-
var cr = r * 0.5, path = 'M' + cr + ',0A' + cr + ',' + cr + ' 0 1,1 -' + cr + ',0A' + cr + ',' + cr + ' 0 1,1 ' + cr + ',0';
102-
for (var i = 0; i < 8; i++) {
103-
var ang = i * Math.PI / 4;
104-
path += 'M' + ((cr+2) * Math.cos(ang)).toFixed(1) + ',' + ((cr+2) * Math.sin(ang)).toFixed(1) +
105-
'L' + ((cr+r*0.4) * Math.cos(ang)).toFixed(1) + ',' + ((cr+r*0.4) * Math.sin(ang)).toFixed(1);
106-
}
107-
return path;
108-
}
109-
if (data.type === 'cloudy') {
110-
return 'M-8,3 A6,6 0 1,1 -2,-4 A7,7 0 1,1 8,-2 A5,5 0 1,1 10,3 Z';
111-
}
112-
if (data.type === 'wind') {
113-
// Wind barb: staff + barbs based on speed
114-
var path = 'M0,' + r + 'L0,-' + r, y = -r;
115-
for (var i = 0; i < Math.min(data.speed, 3); i++) {
116-
path += 'M0,' + y + 'L' + (r*0.6) + ',' + (y + r*0.3);
117-
y += r * 0.3;
118-
}
119-
return path;
120-
}
121-
return 'M' + r + ',0A' + r + ',' + r + ' 0 1,0 -' + r + ',0A' + r + ',' + r + ' 0 1,0 ' + r + ',0';
122-
}
78+
// Paths at r=20 for each weather type
79+
var SUN_PATH = 'M10,0A10,10 0 1,1 0,-10A10,10 0 0,1 10,0Z' + /* rays... */;
80+
var CLOUD_PATH = 'M-12,4A7,7 0 1,1 -2,-4A8,8 0 1,1 10,-2A6,6 0 1,1 14,4L-12,4Z';
81+
var WIND_PATHS = {
82+
1: 'M0,24L0,-24M0,-24L12,-18',
83+
2: 'M0,24L0,-24M0,-24L12,-18M0,-18L12,-12',
84+
3: 'M0,24L0,-24M0,-24L12,-18M0,-18L12,-12M0,-12L12,-6'
85+
};
12386

124-
var locations = [
125-
{ name: 'Seattle', lon: -122, lat: 47, weather: { type: 'cloudy' } },
126-
{ name: 'SF', lon: -122, lat: 38, weather: { type: 'sunny' } },
127-
{ name: 'Denver', lon: -105, lat: 40, weather: { type: 'sunny' } },
128-
{ name: 'Chicago', lon: -88, lat: 42, weather: { type: 'cloudy' } },
129-
{ name: 'NYC', lon: -74, lat: 41, weather: { type: 'cloudy' } },
130-
{ name: 'Miami', lon: -80, lat: 26, weather: { type: 'sunny' } },
131-
// Wind arrows (jet stream)
132-
{ lon: -115, lat: 46, weather: { type: 'wind', direction: 100, speed: 3 } },
133-
{ lon: -100, lat: 42, weather: { type: 'wind', direction: 120, speed: 2 } },
134-
{ lon: -85, lat: 36, weather: { type: 'wind', direction: 150, speed: 1 } }
135-
];
87+
var symbols = locations.map(function(l) {
88+
if (l.weather.type === 'sunny') return SUN_PATH;
89+
if (l.weather.type === 'cloudy') return CLOUD_PATH;
90+
return WIND_PATHS[l.weather.speed];
91+
});
13692

13793
Plotly.newPlot('plot3', [{
138-
x: locations.map(l => l.lon),
139-
y: locations.map(l => l.lat),
140-
customdata: locations.map(l => l.weather),
141-
text: locations.map(l => l.name || ''),
14294
mode: 'markers+text',
143-
textposition: 'bottom center',
14495
marker: {
145-
symbol: weatherMarker,
146-
size: 30,
147-
color: locations.map(l => ({ sunny: '#FFD700', cloudy: '#708090', wind: '#4169E1' }[l.weather.type])),
148-
angle: locations.map(l => l.weather.direction || 0)
96+
symbol: symbols,
97+
angle: locations.map(l => l.weather.direction || 0), // SVG rotate()
98+
size: 30 // scale = 30/20 = 1.5×
14999
}
150-
}], { xaxis: { range: [-130, -70] }, yaxis: { range: [20, 52], scaleanchor: 'x' } });</pre>
100+
}]);</pre>
151101

152102
<script>
103+
// Precomputed path strings at r=20
104+
var HEART = 'M0,-8C-12,-16 -24,-5.33 -24,0C-24,8 0,16 0,24C0,16 24,8 24,0C24,-5.33 12,-16 0,-8Z';
105+
var STAR = 'M0.00,-20.00L4.70,-6.47L19.02,-6.18L7.61,2.47L11.76,16.18L0.00,8.00L-11.76,16.18L-7.61,2.47L-19.02,-6.18L-4.70,-6.47Z';
106+
var DIAMOND = 'M20,0L0,20L-20,0L0,-20Z';
107+
var BIG_DIAMOND = 'M28,0L0,28L-28,0L0,-28Z';
108+
var SUN_PATH = 'M10,0A10,10 0 1,1 0,-10A10,10 0 0,1 10,0Z' +
109+
'M12,0L18,0M8.49,8.49L12.73,12.73' +
110+
'M0,12L0,18M-8.49,8.49L-12.73,12.73' +
111+
'M-12,0L-18,0M-8.49,-8.49L-12.73,-12.73' +
112+
'M0,-12L0,-18M8.49,-8.49L12.73,-12.73';
113+
var CLOUD_PATH = 'M-12,4A7,7 0 1,1 -2,-4A8,8 0 1,1 10,-2A6,6 0 1,1 14,4L-12,4Z';
114+
var WIND_PATHS = {
115+
1: 'M0,24L0,-24M0,-24L12,-18',
116+
2: 'M0,24L0,-24M0,-24L12,-18M0,-18L12,-12',
117+
3: 'M0,24L0,-24M0,-24L12,-18M0,-18L12,-12M0,-12L12,-6'
118+
};
119+
153120
// === Demo 1: Basic markers ===
154-
function heart(r) {
155-
var x = r * 0.6, y = r * 0.8;
156-
return 'M0,' + (-y/2) + 'C' + (-x) + ',' + (-y) + ' ' + (-x*2) + ',' + (-y/3) + ' ' + (-x*2) + ',0C' + (-x*2) + ',' + (y/2) + ' 0,' + y + ' 0,' + (y*1.5) + 'C0,' + y + ' ' + (x*2) + ',' + (y/2) + ' ' + (x*2) + ',0C' + (x*2) + ',' + (-y/3) + ' ' + x + ',' + (-y) + ' 0,' + (-y/2) + 'Z';
157-
}
158-
function star(r) {
159-
var path = 'M';
160-
for (var i = 0; i < 10; i++) {
161-
var rad = i % 2 === 0 ? r : r * 0.4, ang = i * Math.PI / 5 - Math.PI / 2;
162-
path += (i ? 'L' : '') + (rad * Math.cos(ang)).toFixed(2) + ',' + (rad * Math.sin(ang)).toFixed(2);
163-
}
164-
return path + 'Z';
165-
}
166121
Plotly.newPlot('plot1', [{
167-
x: [1, 2, 3, 4, 5], y: [2, 3, 4, 3, 2], mode: 'markers+lines',
168-
marker: { symbol: [heart, star, 'circle', star, heart], size: 25, color: ['red', 'gold', 'blue', 'gold', 'red'] }
169-
}], { title: 'Mix custom functions with built-in symbols' });
170-
171-
// === Demo 2: Data-aware ===
172-
function shapeByData(r, customdata) {
173-
if (customdata === 'star') {
174-
var path = 'M';
175-
for (var i = 0; i < 10; i++) {
176-
var rad = i % 2 === 0 ? r : r * 0.4, ang = i * Math.PI / 5 - Math.PI / 2;
177-
path += (i ? 'L' : '') + (rad * Math.cos(ang)).toFixed(2) + ',' + (rad * Math.sin(ang)).toFixed(2);
178-
}
179-
return path + 'Z';
122+
x: [1, 2, 3, 4, 5],
123+
y: [2, 3, 4, 3, 2],
124+
mode: 'markers+lines',
125+
marker: {
126+
symbol: [HEART, STAR, 'circle', STAR, HEART],
127+
size: 25,
128+
color: ['red', 'gold', 'blue', 'gold', 'red']
180129
}
181-
if (customdata === 'big') r *= 1.4;
182-
return 'M' + r + ',0L0,' + r + 'L-' + r + ',0L0,-' + r + 'Z';
183-
}
130+
}], { title: 'Mix custom path strings with built-in symbols' });
131+
132+
// === Demo 2: Per-point shapes from data ===
133+
var types = ['normal', 'big', 'star', 'normal'];
134+
var symbols2 = types.map(function(t) {
135+
if (t === 'big') return BIG_DIAMOND;
136+
if (t === 'star') return STAR;
137+
return DIAMOND;
138+
});
184139
Plotly.newPlot('plot2', [{
185-
x: [1, 2, 3, 4], y: [1, 1, 1, 1], customdata: ['normal', 'big', 'star', 'normal'],
186-
mode: 'markers', marker: { symbol: shapeByData, size: 25, color: '#10b981' }
187-
}], { title: 'Shape varies by customdata value', xaxis: { range: [0, 5] } });
140+
x: [1, 2, 3, 4],
141+
y: [1, 1, 1, 1],
142+
mode: 'markers',
143+
marker: { symbol: symbols2, size: 25, color: '#10b981' }
144+
}], { title: 'Per-point shapes mapped from data', xaxis: { range: [0, 5] } });
188145

189146
// === Demo 3: Weather map ===
190-
function weatherMarker(r, data) {
191-
if (data.type === 'sunny') {
192-
var cr = r * 0.5, path = 'M' + cr + ',0A' + cr + ',' + cr + ' 0 1,1 -' + cr + ',0A' + cr + ',' + cr + ' 0 1,1 ' + cr + ',0';
193-
for (var i = 0; i < 8; i++) {
194-
var ang = i * Math.PI / 4;
195-
path += 'M' + ((cr+2) * Math.cos(ang)).toFixed(1) + ',' + ((cr+2) * Math.sin(ang)).toFixed(1) + 'L' + ((cr+r*0.4) * Math.cos(ang)).toFixed(1) + ',' + ((cr+r*0.4) * Math.sin(ang)).toFixed(1);
196-
}
197-
return path;
198-
}
199-
if (data.type === 'cloudy') return 'M-8,3 A6,6 0 1,1 -2,-4 A7,7 0 1,1 8,-2 A5,5 0 1,1 10,3 Z';
200-
if (data.type === 'wind') {
201-
var path = 'M0,' + r + 'L0,-' + r, y = -r;
202-
for (var i = 0; i < Math.min(data.speed, 3); i++) { path += 'M0,' + y + 'L' + (r*0.6) + ',' + (y + r*0.3); y += r * 0.3; }
203-
return path;
204-
}
205-
return 'M' + r + ',0A' + r + ',' + r + ' 0 1,0 -' + r + ',0A' + r + ',' + r + ' 0 1,0 ' + r + ',0';
206-
}
207147
var locations = [
208148
{ name: 'Seattle', lon: -122, lat: 47, weather: { type: 'cloudy' } },
209149
{ name: 'SF', lon: -122, lat: 38, weather: { type: 'sunny' } },
@@ -213,14 +153,33 @@ <h2>3. Weather Map</h2>
213153
{ name: 'Miami', lon: -80, lat: 26, weather: { type: 'sunny' } },
214154
{ lon: -115, lat: 46, weather: { type: 'wind', direction: 100, speed: 3 } },
215155
{ lon: -100, lat: 42, weather: { type: 'wind', direction: 120, speed: 2 } },
216-
{ lon: -85, lat: 36, weather: { type: 'wind', direction: 150, speed: 1 } }
156+
{ lon: -85, lat: 36, weather: { type: 'wind', direction: 150, speed: 1 } }
217157
];
158+
var symbols3 = locations.map(function(l) {
159+
if (l.weather.type === 'sunny') return SUN_PATH;
160+
if (l.weather.type === 'cloudy') return CLOUD_PATH;
161+
return WIND_PATHS[Math.min(l.weather.speed || 1, 3)];
162+
});
218163
Plotly.newPlot('plot3', [{
219-
x: locations.map(l => l.lon), y: locations.map(l => l.lat),
220-
customdata: locations.map(l => l.weather), text: locations.map(l => l.name || ''),
221-
mode: 'markers+text', textposition: 'bottom center',
222-
marker: { symbol: weatherMarker, size: 30, color: locations.map(l => ({ sunny: '#FFD700', cloudy: '#708090', wind: '#4169E1' }[l.weather.type])), angle: locations.map(l => l.weather.direction || 0) }
223-
}], { title: 'Weather icons with wind direction via marker.angle', xaxis: { range: [-130, -70] }, yaxis: { range: [20, 52], scaleanchor: 'x' } });
164+
x: locations.map(function(l) { return l.lon; }),
165+
y: locations.map(function(l) { return l.lat; }),
166+
text: locations.map(function(l) { return l.name || ''; }),
167+
mode: 'markers+text',
168+
textposition: 'bottom center',
169+
marker: {
170+
symbol: symbols3,
171+
size: 30,
172+
color: locations.map(function(l) {
173+
return { sunny: '#FFD700', cloudy: '#708090', wind: '#4169E1' }[l.weather.type];
174+
}),
175+
angle: locations.map(function(l) { return l.weather.direction || 0; }),
176+
line: { width: 1.5, color: '#333' }
177+
}
178+
}], {
179+
title: 'Weather icons with wind direction via marker.angle (SVG rotate)',
180+
xaxis: { range: [-130, -70] },
181+
yaxis: { range: [20, 52], scaleanchor: 'x' }
182+
});
224183
</script>
225184
</body>
226185
</html>

0 commit comments

Comments
 (0)