update_attr.js

import { select, selectAll } from "d3-selection";
import { transition } from "d3-transition";
const d3 = { select, selectAll, transition };

/**
 * Modify one or multiple layers in one or more SVG maps with a D3 transition.
 *
 * @param {Object} options - Options object
 * @param {string|string[]} [options.map] - SVG id or array of SVG ids (optional). If omitted, modifies all matching layers in the document.
 * @param {string|string[]} options.layer - Layer id or array of layer ids to modify.
 * @param {Object} [options.attrs={}] - SVG attributes to modify. Values can be static or functions (d, i, nodes) => value.
 * @param {Object} [options.styles={}] - CSS styles to modify. Values can be static or functions.
 * @param {number} [options.duration=500] - Duration of the transition in milliseconds.
 * @param {number} [options.delay=0] - Delay before the transition starts in milliseconds.
 * @param {Function} [options.onEnd] - Callback function to execute after the transition ends.
 */
export function attr({
  map,
  layer,
  attrs = {},
  styles = {},
  duration = 500,
  delay = 0,
  onEnd,
}) {
  // Normalize map and layer inputs to arrays for uniform processing
  const maps = map ? (Array.isArray(map) ? map : [map]) : [null];
  const layers = Array.isArray(layer) ? layer : [layer];

  maps.forEach((mapId) => {
    // Select container: specific SVG or document if mapId is null
    const container = mapId ? d3.select(`#${mapId}`) : d3.select(document);

    if (mapId && container.empty()) {
      console.warn(`Map with id "${mapId}" not found`);
      return;
    }

    layers.forEach((layerId) => {
      // Select all matching layers inside the container
      const layerSel = mapId
        ? container.select(`#${layerId}`)
        : container.selectAll(`#${layerId}`);

      if (layerSel.empty()) {
        console.warn(
          `Layer "${layerId}" not found${mapId ? ` in map "${mapId}"` : ""}`
        );
        return;
      }

      // Apply transition
      const t = layerSel.transition().delay(delay).duration(duration);

      // Apply SVG attributes
      Object.entries(attrs).forEach(([key, value]) => t.attr(key, value));
      // Apply CSS styles
      Object.entries(styles).forEach(([key, value]) => t.style(key, value));

      // Callback at the end of the transition
      if (onEnd) t.on("end", onEnd);
    });
  });
}