plot_plot_gridprop.js

import { create } from "../container/create";
import { render } from "../container/render";
import { unique } from "../helpers/utils";

/**
 * @function plot/gridprop
 * @description Draw a proportional symbol map based on a grid aggregation.<br/><br/>
 * The function first generates a grid from the input data (see {@link tool/grid} for detailed grid options),
 * then displays values within each cell as proportional symbols.
 *
 * @property {object} data - GeoJSON FeatureCollection (polygons or points) to aggregate.
 * @property {string} var - Variable in the GeoJSON containing numeric values.
 * @property {string} [grid = "square"] - Type of grid used for aggregation. Available types:
 *   - "square"
 *   - "dot"
 *   - "diamond"
 *   - "hexbin" / "hex"
 *   - "triangle"
 *   - "arbitrary" / "random"
 *   - "h3" / "h3geo" / "hexgeo" / "hexbingeo"
 *   - "square_sph" (spherical squares for global maps)
 * @property {number} [step = 15] - Resolution of the grid (in SVG coordinates for planar grids).
 * @property {number} [level = 1] - Level of subdivision for hierarchical grids (e.g., H3).
 * @property {string} [coords = "geo"] - Coordinate system used internally ("geo" for geographic grids, "svg" for planar grids).
 * @property {string} [missing = "white"] - Fill color for missing values.
 * @property {string} [symbol = "circle"] - Choice of symbol to represent the value ("circle", "square", "spike", "halfcircle").
 * @property {number} [k = 10] - Size of the largest symbol (20 if symbol = "square").
 * @property {number} [width = 30] - Width of spikes (if symbol = "spike").
 * @property {number} [straight = 0] - Curvature of spikes (0 = curved, 1 = straight).
 * @property {number|null} [fixmax = null] - Maximum value for symbol scaling. Useful for comparing multiple maps.
 * @property {boolean} [legend = true] - Whether to display the legend.
 * @property {string} [leg_type = "separate"] - Type of legend ("nested" or "separate").
 * @property {Array.<number>} [leg_pos = [10, 10]] - Position of the legend.
 * @property {string} [leg_title] - Title of the legend (defaults to `var`).
 * @property {*} [svg_*] - Parameters for the SVG container (e.g., `svg_width`, `svg_height`).
 * @property {*} [*] - You can also use all parameters available in the {@link plot/prop} function to customize the rendering. See also {@link tool/grid}.
 *
 * @example // Basic usage
 * geoviz.plot({
 *   type: "gridprop",
 *   data: world,
 *   grid: "square",
 *   var: "pop"
 * });
 *
 * @example // Hexagonal grid
 * geoviz.plot({
 *   type: "gridprop",
 *   data: world,
 *   grid: "hex",
 *   var: "gdp",
 *   symbol: "square",
 *   k: 20
 * });
 */
export function plot_gridprop(arg1, arg2) {
  let newcontainer =
    (arguments.length <= 1 || arguments[1] == undefined) &&
    !arguments[0]?._groups
      ? true
      : false;

  // New container
  let options = newcontainer ? arg1 : arg2;

  // Default values
  let opts = {
    grid: "square",
    step: 15,
    level: 1,
    coords: "geo",
    legend: true,
    symbol: "circle",
    straight: 0,
    width: 30,
    id: unique(),
    missing: "white",
    k: 10,
    fixmax: null,
    leg_type: "separate",
    leg_pos: [10, 10],
  };

  opts.k = opts.symbol == "square" ? 20 : 10;

  opts = { ...opts, ...options };
  let ids = `#${opts.id}`;

  // leg title
  opts.leg_title = opts.leg_title ? opts.leg_title : opts.var;

  // New container
  let svgopts = { domain: opts.data || opts.datum };
  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;

  // GRID
  let grid = svg.grid({
    type: opts.grid,
    step: opts.step,
    data: opts.data,
    var: opts.var,
    level: opts.level,
  });

  svg.plot({
    ...opts,
    type: "prop",
    data: grid,
    var: opts.var || "count",
    coords: ["h3", "square_sph"].includes(opts.grid) ? "geo" : "svg",
  });

  if (newcontainer) {
    return render(svg);
  } else {
    return ids;
  }
}