diff --git a/app/javascript/controllers/maps_controller.js b/app/javascript/controllers/maps_controller.js
index 57f37b67..7b7ecc4e 100644
--- a/app/javascript/controllers/maps_controller.js
+++ b/app/javascript/controllers/maps_controller.js
@@ -9,7 +9,9 @@ import {
createPolylinesLayer,
updatePolylinesOpacity,
updatePolylinesColors,
- colorFormatEncode
+ colorFormatEncode,
+ colorFormatDecode,
+ colorStopsFallback
} from "../maps/polylines";
import { fetchAndDrawAreas, handleAreaCreated } from "../maps/areas";
@@ -37,14 +39,6 @@ export default class extends BaseController {
super.connect();
console.log("Map controller connected");
- const speedColorScaleDefault = [
- { speed: 0, color: '#00ff00' },
- { speed: 15, color: '#00ffff' },
- { speed: 30, color: '#ff00ff' },
- { speed: 50, color: '#ffff00' },
- { speed: 100, color: '#ff3300' }
- ];
-
this.apiKey = this.element.dataset.api_key;
this.markers = JSON.parse(this.element.dataset.coordinates);
this.timezone = this.element.dataset.timezone;
@@ -783,9 +777,11 @@ export default class extends BaseController {
?
+ Edit Scale
+
- Update
+ Update
`;
@@ -798,6 +794,12 @@ export default class extends BaseController {
// Prevent map interactions when interacting with the form
L.DomEvent.disableClickPropagation(div);
+ // Attach event listener to the "Edit Gradient" button:
+ const editBtn = div.querySelector("#edit-gradient-btn");
+ if (editBtn) {
+ editBtn.addEventListener("click", this.showGradientEditor.bind(this));
+ }
+
// Add event listener to the form submission
div.querySelector('#settings-form').addEventListener(
'submit', this.updateSettings.bind(this)
@@ -1381,4 +1383,143 @@ export default class extends BaseController {
container.innerHTML = html;
}
+
+ showGradientEditor() {
+ const modal = document.createElement("div");
+ modal.id = "gradient-editor-modal";
+ Object.assign(modal.style, {
+ position: "fixed",
+ top: "0",
+ left: "0",
+ right: "0",
+ bottom: "0",
+ backgroundColor: "rgba(0, 0, 0, 0.5)",
+ display: "flex",
+ justifyContent: "center",
+ alignItems: "center",
+ zIndex: "100",
+ });
+
+ const content = document.createElement("div");
+ Object.assign(content.style, {
+ backgroundColor: "#fff",
+ padding: "20px",
+ borderRadius: "5px",
+ minWidth: "300px",
+ maxHeight: "80vh",
+ display: "flex",
+ flexDirection: "column",
+ });
+
+ const title = document.createElement("h2");
+ title.textContent = "Edit Speed Color Scale";
+ content.appendChild(title);
+
+ const gradientContainer = document.createElement("div");
+ gradientContainer.id = "gradient-editor-container";
+ Object.assign(gradientContainer.style, {
+ marginTop: "15px",
+ overflowY: "auto",
+ flex: "1",
+ border: "1px solid #ccc",
+ padding: "5px",
+ });
+
+ const createRow = (stop = { speed: 0, color: "#000000" }) => {
+ const row = document.createElement("div");
+ row.style.display = "flex";
+ row.style.alignItems = "center";
+ row.style.gap = "10px";
+ row.style.marginBottom = "8px";
+
+ const speedInput = document.createElement("input");
+ speedInput.type = "number";
+ speedInput.value = stop.speed;
+ speedInput.style.width = "70px";
+
+ const colorInput = document.createElement("input");
+ colorInput.type = "color";
+ colorInput.value = stop.color;
+ colorInput.style.width = "70px";
+
+ const removeBtn = document.createElement("button");
+ removeBtn.textContent = "x";
+ removeBtn.style.color = "#cc3311";
+ removeBtn.style.flexShrink = "0";
+ removeBtn.addEventListener("click", () => {
+ if (gradientContainer.childElementCount > 1) {
+ gradientContainer.removeChild(row);
+ } else {
+ showFlashMessage('error', 'At least one gradient stop is required.');
+ }
+ });
+
+ row.appendChild(speedInput);
+ row.appendChild(colorInput);
+ row.appendChild(removeBtn);
+ return row;
+ };
+
+ let stops;
+ try {
+ stops = colorFormatDecode(this.speedColorScale);
+ } catch (error) {
+ stops = colorStopsFallback;
+ }
+ stops.forEach(stop => {
+ const row = createRow(stop);
+ gradientContainer.appendChild(row);
+ });
+
+ content.appendChild(gradientContainer);
+
+ const addRowBtn = document.createElement("button");
+ addRowBtn.textContent = "Add Row";
+ addRowBtn.style.marginTop = "10px";
+ addRowBtn.addEventListener("click", () => {
+ const newRow = createRow({ speed: 0, color: "#000000" });
+ gradientContainer.appendChild(newRow);
+ });
+ content.appendChild(addRowBtn);
+
+ const btnContainer = document.createElement("div");
+ btnContainer.style.display = "flex";
+ btnContainer.style.justifyContent = "flex-end";
+ btnContainer.style.gap = "10px";
+ btnContainer.style.marginTop = "15px";
+
+ const cancelBtn = document.createElement("button");
+ cancelBtn.textContent = "Cancel";
+ cancelBtn.addEventListener("click", () => {
+ document.body.removeChild(modal);
+ });
+
+ const saveBtn = document.createElement("button");
+ saveBtn.textContent = "Save";
+ saveBtn.addEventListener("click", () => {
+ const newStops = [];
+ gradientContainer.querySelectorAll("div").forEach(row => {
+ const inputs = row.querySelectorAll("input");
+ const speed = Number(inputs[0].value);
+ const color = inputs[1].value;
+ newStops.push({ speed, color });
+ });
+
+ const newGradient = colorFormatEncode(newStops);
+
+ this.speedColorScale = newGradient;
+ const speedColorScaleInput = document.getElementById("speed_color_scale");
+ if (speedColorScaleInput) {
+ speedColorScaleInput.value = newGradient;
+ }
+
+ document.body.removeChild(modal);
+ });
+
+ btnContainer.appendChild(cancelBtn);
+ btnContainer.appendChild(saveBtn);
+ content.appendChild(btnContainer);
+ modal.appendChild(content);
+ document.body.appendChild(modal);
+ }
}
diff --git a/app/javascript/maps/polylines.js b/app/javascript/maps/polylines.js
index 4a3cf888..197d7655 100644
--- a/app/javascript/maps/polylines.js
+++ b/app/javascript/maps/polylines.js
@@ -25,16 +25,13 @@ export function calculateSpeed(point1, point2) {
}
// Optimize getSpeedColor by pre-calculating color stops
-const colorStopsFallback = [
+export const colorStopsFallback = [
{ speed: 0, color: '#00ff00' }, // Stationary/very slow (green)
{ speed: 15, color: '#00ffff' }, // Walking/jogging (cyan)
{ speed: 30, color: '#ff00ff' }, // Cycling/slow driving (magenta)
{ speed: 50, color: '#ffff00' }, // Urban driving (yellow)
{ speed: 100, color: '#ff3300' } // Highway driving (red)
-].map(stop => ({
- ...stop,
- rgb: hexToRGB(stop.color)
-}));
+];
export function colorFormatEncode(arr) {
return arr.map(item => `${item.speed}:${item.color}`).join('|');
@@ -60,7 +57,10 @@ export function getSpeedColor(speedKmh, useSpeedColors, speedColorScale) {
rgb: hexToRGB(stop.color)
}));;
} catch (error) { // If user has given invalid values
- colorStops = colorStopsFallback;
+ colorStops = colorStopsFallback.map(stop => ({
+ ...stop,
+ rgb: hexToRGB(stop.color)
+ }));;
}
// Find the appropriate color segment
diff --git a/app/views/map/_settings_modals.html.erb b/app/views/map/_settings_modals.html.erb
index 8436795d..6703f5f2 100644
--- a/app/views/map/_settings_modals.html.erb
+++ b/app/views/map/_settings_modals.html.erb
@@ -152,6 +152,9 @@
Here you can set a custom color scale for speed colored routes. It uses color stops at specified km/h values and creates a gradient from it. The default value is 0:#00ff00|15:#00ffff|30:#ff00ff|50:#ffff00|100:#ff3300
+
+ You can also use the 'Edit Scale' button to edit it using an UI.
+
Close