mark_graticule.js

import { create } from "../container/create";
import { render } from "../container/render";
import { camelcasetodash, unique } from "../helpers/utils";
import { geoGraticule, geoPath, geoNaturalEarth1 } from "d3-geo";
const d3 = Object.assign({}, { geoPath, geoGraticule, geoNaturalEarth1 });

/**
 * @function graticule
 * @description The `graticule` function allows to create a layer with lat/long lines. The function adds a layer to the SVG container and returns the layer identifier. If the container is not defined, then the layer is displayed directly.
 * @see {@link https://observablehq.com/@neocartocnrs/layout-marks}
 *
 * @property {string} [id] - id of the layer
 * @property {number|number[]} [step = 10] - gap between graticules. The value can be a number or an array of two values
 * @property {string} [stroke = "#9ad5e6"] - stroke color
 * @property {string} [fill = "none"] - fill color
 * @property {string} [strokeWidth = 0.8] - stroke width
 * @property {string} [strokeLinecap = "square"] - stroke-inecap
 * @property {string} [strokeLinejoin = "round"] - stroke-Linejoin
 * @property {number|number[]} [strokeDasharray = 2] - stroke-dasharray (default: 2)
 * @property {*} [*] - *other attributes that can be used to define the svg style (strokeDasharray, strokeWidth, opacity, strokeLinecap...)*
 * @property {*} [svg_*]  - *parameters of the svg container created if the layer is not called inside a container (e.g svg_width)*
 * @example
 * // There are several ways to use this function
 * geoviz.graticule(svg, { step: 2 }) // where svg is the container
 * svg.graticule({ step: [10,2] }) // where svg is the container
 * svg.plot({ type: "graticule", step: [10,2] }) // where svg is the container
 * geoviz.graticule({ step: 2 }) // no container
 */

export function graticule(arg1, arg2) {
  console.log(arguments.length);

  // Test if new container
  let newcontainer =
    (arguments.length <= 1 || arguments[1] == undefined) &&
    !arguments[0]?._groups
      ? true
      : false;
  arg1 = newcontainer && arg1 == undefined ? {} : arg1;
  arg2 = arg2 == undefined ? {} : arg2;

  // Arguments
  const options = {
    mark: "graticule",
    id: unique(),
    step: 10,
    fill: "none",
    stroke: "#9ad5e6",
    strokeWidth: 0.8,
    strokeLinecap: "square",
    strokeLinejoin: "round",
    strokeDasharray: 2,
    //svg_projection: d3.geoNaturalEarth1(),
  };
  let opts = { ...options, ...(newcontainer ? arg1 : arg2) };

  // New container
  let svgopts = { projection: d3.geoNaturalEarth1() };
  Object.keys(opts)
    .filter((str) => str.slice(0, 4) == "svg_")
    .forEach((d) => {
      Object.assign(svgopts, {
        [d.slice(0, 4) == "svg_" ? d.slice(4) : d]: opts[d],
      });
      delete opts[d];
    });
  let svg = newcontainer ? create(svgopts) : arg1;

  // Warning
  if (svg.initproj == "none" && svg.warning) {
    svg.warning_message.push(`Graticule mark`);
    svg.warning_message.push(
      `You must define a projection function in the SVG container`
    );
  }

  if (svg.initproj != "none") {
    // init layer
    let layer = svg.selectAll(`#${opts.id}`).empty()
      ? svg.append("g").attr("id", opts.id)
      : svg.select(`#${opts.id}`);
    layer.selectAll("*").remove();

    // zoomable layer
    if (svg.zoomable && !svg.parent) {
      if (!svg.zoomablelayers.map((d) => d.id).includes(opts.id)) {
        svg.zoomablelayers.push({
          mark: opts.mark,
          id: opts.id,
        });
      } else {
        let i = svg.zoomablelayers.indexOf(
          svg.zoomablelayers.find((d) => d.id == opts.id)
        );
        svg.zoomablelayers[i] = {
          mark: opts.mark,
          id: opts.id,
        };
      }
    }

    // Manage options
    let entries = Object.entries(opts).map((d) => d[0]);
    const layerattr = entries.filter(
      (d) => !["mark", "id", "step"].includes(d)
    );

    // layer attributes
    layerattr.forEach((d) => {
      layer.attr(camelcasetodash(d), opts[d]);
    });

    // Draw graticules
    let path = d3.geoPath(svg.projection);
    layer
      .append("path")
      .datum(
        d3
          .geoGraticule()
          .step(Array.isArray(opts.step) ? opts.step : [opts.step, opts.step])
      )
      .attr("d", path);

    // Output
    if (newcontainer) {
      return render(svg);
    } else {
      return `#${opts.id}`;
    }
  }
}