mark_minimap.js

import { create } from "../container/create";
import { camelcasetodash, unique } from "../helpers/utils";
import { subsetobj } from "../helpers/utils_legend.js";
import { location } from "../helpers/location.js";
import { land } from "../helpers/land.js";
import { select } from "d3-selection";
import { geoPath } from "d3-geo";
const d3 = Object.assign({}, { select, geoPath });

/**
 * Create a minimap inset showing a basemap and a location geometry.
 * The map’s projection must provide an invert() function.
 *
 * @function minimap
 * @async
 * @property {Object} svg - SVG container (d3 selection) where the minimap is appended.
 * @property {string} [id] - Unique identifier of the minimap. Defaults to a generated id.
 * @property {Object} [basemap_data] - GeoJSON data used as basemap. Defaults to `land`.
 * @property {string} [basemap_fill="white"] - Fill color of the basemap.
 * @property {number} [basemap_fillOpacity=0.5] - Fill opacity of the basemap.
 * @property {string} [basemap_stroke="none"] - Stroke color of the basemap.
 * @property {number} [width=200] - Width of the minimap.
 * @property {string} [mark="minimap"] - Mark type identifier.
 * @property {string} [projection="EqualEarth"] - Projection used for the minimap.
 * @property {number} [precision=10] - Precision used for geometry simplification.
 * @property {Array<number>} [pos=[10, 10]] - Position of the minimap (top-left corner).
 * @property {string} [location_type="polygon"] - Type of location geometry ("polygon" or "point").
 * @property {number} [location_r=5] - Radius used when `location_type` is "point".
 * @property {string} [location_fill] - Fill color of the location geometry.
 * @property {string} [location_stroke] - Stroke color of the location geometry.
 * @property {number} [location_strokeWidth=1.2] - Stroke width of the location geometry.
 * @property {Object} [domain] - Optional projection domain.
 * @property {Object} [margin] - Margin configuration.
 * @property {Object} [outline_*] - Outline styling properties (prefixed with `outline_`).
 * @property {Object} [basemap_*] - Additional basemap styling properties (prefixed with `basemap_`).
 * @property {Object} [location_*] - Additional location styling properties (prefixed with `location_`).
 * @example
 * svg.minimap({
 *   width: 150,
 *   projection: "Mercator",
 *   pos: [20, 20],
 * }); // where svg is the container
 */
export async function minimap(arg1, arg2) {
  // Arguments by default
  const options = {
    id: unique(),
    basemap_data: land,
    basemap_fill: "white",
    basemap_fillOpacity: 0.5,
    basemap_stroke: "none",
    width: 200,
    mark: "minimap",
    projection: "EqualEarth",
    precision: 10,
    pos: [10, 10],
    location_strokeWidth: 1.2,
    location_type: "polygon",
    location_r: 5,
  };

  let opts = { ...options, ...arg2 };
  if (opts.location_type == "point") {
    opts.location_fill = opts.location_fill ?? "#1f1f1f";
    opts.location_stroke = opts.location_stroke ?? "none";
  } else {
    opts.location_fill = opts.location_fill ?? "none";
    opts.location_stroke = opts.location_stroke ?? "#1f1f1f";
  }

  let svg = arg1;

  let locationarea = location(svg, opts.precision, opts.location_type);

  svg.select(`#${opts.id}`).remove();

  let inset = create({
    id: opts.id,
    parent: svg,
    projection: opts.projection,
    pos: opts.pos,
    width: opts.width,
    coords: "geo",
    domain: opts.domain,
    margin: opts.margin,
  });

  // Outline
  const outlineStyles = Object.fromEntries(
    Object.entries(subsetobj(opts, { prefix: "outline_" })).map(
      ([key, value]) => [camelcasetodash(key), value],
    ),
  );
  inset.outline(outlineStyles);

  // Add basemap
  const basemapStyles = Object.fromEntries(
    Object.entries(subsetobj(opts, { prefix: "basemap_" })).map(
      ([key, value]) => [camelcasetodash(key), value],
    ),
  );
  inset.path({
    id: opts.id + "_basemap",
    datum: opts.basemap_data,
    fill: opts.basemap_fill,
    ...basemapStyles,
  });

  const locationStyles = Object.fromEntries(
    Object.entries(subsetobj(opts, { prefix: "location_" })).map(
      ([key, value]) => [camelcasetodash(key), value],
    ),
  );
  inset.path({
    id: opts.id + "_location",
    data: locationarea,
    ...locationStyles,
  });

  // Zoom;
  if (svg.zoomable) {
    if (!svg.zoomablelayers.map((d) => d.id).includes(opts.id)) {
      svg.zoomablelayers.push({
        id: opts.id,
        mark: "minimap",
        projection: inset.projection,
        precision: opts.precision,
        location_type: opts.location_type,
        location_r: opts.location_r,
      });
    }
  }

  // Render
  return `#${opts.id}`;
}