Sorry but it doesn’t help that much.
Let’s be more precise.
Here is an attempt I did
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Wind Speed and Direction Visualization</title>
</head>
<body>
<div id="myDiv" style="width: 100%; height: 600px"></div>
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
<script>
// Wait for Plotly to load before executing
window.addEventListener("load", function () {
// Wind data from CSV
const windData = [
{ datetime: "2025-09-01T00:00Z", ws: 8, wd: 175 },
{ datetime: "2025-09-01T01:00Z", ws: 6, wd: 170 },
{ datetime: "2025-09-01T02:00Z", ws: 4, wd: 170 },
{ datetime: "2025-09-01T03:00Z", ws: 5.4, wd: 120 },
{ datetime: "2025-09-01T04:00Z", ws: 8.3, wd: 60 },
{ datetime: "2025-09-01T05:00Z", ws: 12.6, wd: 20 },
{ datetime: "2025-09-01T06:00Z", ws: 21.1, wd: 20 },
{ datetime: "2025-09-01T07:00Z", ws: 30.8, wd: 20 },
{ datetime: "2025-09-01T08:00Z", ws: 39.1, wd: 0 },
{ datetime: "2025-09-01T09:00Z", ws: 43.8, wd: 0 },
{ datetime: "2025-09-01T10:00Z", ws: 46.2, wd: 0 },
{ datetime: "2025-09-01T11:00Z", ws: 46.8, wd: 0 },
{ datetime: "2025-09-01T12:00Z", ws: 46.3, wd: 0 },
{ datetime: "2025-09-01T13:00Z", ws: 44.6, wd: 350 },
{ datetime: "2025-09-01T14:00Z", ws: 42.7, wd: 350 },
{ datetime: "2025-09-01T15:00Z", ws: 41.8, wd: 350 },
{ datetime: "2025-09-01T16:00Z", ws: 40.8, wd: 340 },
{ datetime: "2025-09-01T17:00Z", ws: 39.7, wd: 340 },
{ datetime: "2025-09-01T18:00Z", ws: 37.7, wd: 340 },
{ datetime: "2025-09-01T19:00Z", ws: 35.1, wd: 330 },
{ datetime: "2025-09-01T20:00Z", ws: 32.8, wd: 280 },
{ datetime: "2025-09-01T21:00Z", ws: 30.7, wd: 290 },
{ datetime: "2025-09-01T22:00Z", ws: 28.7, wd: 290 },
{ datetime: "2025-09-01T23:00Z", ws: 26.8, wd: 280 },
{ datetime: "2025-09-02T00:00Z", ws: 25.8, wd: 270 },
];
// Convert wind direction to u,v components
function windToUV(direction, speed) {
// Convert degrees to radians
// Add 180 to show where wind is going (not coming from)
const radians = (((direction + 180) % 360) * Math.PI) / 180;
const u = Math.sin(radians);
const v = Math.cos(radians);
return { u, v };
}
// Prepare data arrays
const times = windData.map((d) => d.datetime);
const speeds = windData.map((d) => d.ws);
const directions = windData.map((d) => d.wd);
const minSpeed = Math.min(...speeds);
const maxSpeed = Math.max(...speeds);
// Create wind direction arrows using annotations with RdYlGn colorscale
const annotations = [];
windData.forEach((point, i) => {
// Calculate arrow end position
const scale = 0.8; // Scale factor for arrow length
const { u: uComp, v: vComp } = windToUV(point.wd, 1); // Normalized direction
// Create time offset for horizontal component (scaled by wind speed)
const timeMs = new Date(point.datetime).getTime();
const speedScale = point.ws / maxSpeed; // Normalize speed
const arrowEndTime = new Date(
timeMs + uComp * scale * speedScale * 3600000
); // 1 hour offset max
const arrowEndSpeed = point.ws + vComp * scale * speedScale * 10; // Speed offset
// Get color based on wind speed using RdYlGn colorscale
const normalizedSpeed = (point.ws - minSpeed) / (maxSpeed - minSpeed);
let arrowColor;
if (normalizedSpeed < 0.33) {
arrowColor = '#1a9850'; // Green for low speeds
} else if (normalizedSpeed < 0.66) {
arrowColor = '#ffffbf'; // Yellow for medium speeds
} else {
arrowColor = '#d73027'; // Red for high speeds
}
annotations.push({
x: arrowEndTime,
y: arrowEndSpeed,
ax: point.datetime,
ay: point.ws,
xref: "x",
yref: "y",
axref: "x",
ayref: "y",
arrowhead: 2,
arrowsize: 1.8,
arrowwidth: 4,
arrowcolor: arrowColor,
showarrow: true,
hovertext: `Time: ${point.datetime}<br>Speed: ${point.ws} km/h<br>Direction: ${point.wd}°`,
});
});
// Create scatter points for proper hover information with RdYlGn colorscale
const hoverTrace = {
x: times,
y: speeds,
type: "scatter",
mode: "markers",
marker: {
size: 12,
color: speeds,
colorscale: "RdYlGn",
reversescale: true,
colorbar: {
title: "Wind Speed (km/h)",
titleside: "right",
},
opacity: 0.7,
},
name: "Wind Data",
hovertemplate:
"Time: %{x}<br>Speed: %{y} km/h<br>Direction: %{text}°<extra></extra>",
text: directions,
};
// Layout configuration
const layout = {
title: {
text: "Wind Speed and Direction Over Time",
font: { size: 18 },
},
xaxis: {
title: "Time",
type: "date",
tickformat: "%H:%M",
},
yaxis: {
title: "Wind Speed (km/h)",
range: [0, Math.max(...speeds) * 1.2],
},
annotations: annotations,
hovermode: "closest",
showlegend: false,
plot_bgcolor: "rgba(240,240,240,0.1)",
};
// Plot configuration
const config = {
responsive: true,
displayModeBar: true,
};
// Create the plot
Plotly.newPlot("myDiv", [hoverTrace], layout, config);
// Add legend below the plot
const legendDiv = document.createElement("div");
legendDiv.innerHTML = `
<div style="margin-top: 20px; padding: 15px; background-color: #f8f9fa; border-radius: 8px; border-left: 4px solid #007bff;">
<h4 style="margin-top: 0; color: #333;">Chart Legend:</h4>
<ul style="margin: 0; padding-left: 20px;">
<li><strong>Arrows:</strong> Wind direction (pointing where wind is going)</li>
<li><strong>Arrow colors:</strong> RdYlGn colorscale (green = low, yellow = medium, red = high)</li>
<li><strong>Arrow length:</strong> Proportional to wind speed</li>
<li><strong>Colorbar:</strong> Shows wind speed scale (green = low, yellow = medium, red = high)</li>
<li><strong>Hover:</strong> Mouse over markers for detailed information</li>
</ul>
</div>
`;
document.body.appendChild(legendDiv);
}); // End of window load event listener
</script>
</body>
</html>
but result is not visually satisfying
- arrows don’t use colorscale
- legend is not showing colorscale for green to yellow and red
- arrows are not centered on points (which shouldn’t appears)
- arrows direction seems to be buggy.
Any idea how to fix that?