plot_plot_symbol.js

import { create } from "../container/create";
import { render } from "../container/render";
import { implantation, propertiesentries } from "../helpers/utils";
import { picto } from "../helpers/picto";

/**
 * @function plot/symbol
 * @description With the `plot({type = "symbol"})` function, you can quickly draw a layer with symbols.<br/><br/>
 * ![choro](img/thumb_symbols.svg)
 * @see {@link https://observablehq.com/@neocartocnrs/symbols}
 * @property {object} data - GeoJSON FeatureCollection. Use data to be able to iterate
 * @property {string} var - a variable name in a geoJSON containig qualitative values. Or the name of a symbol.
 * @property {array} [symbols] - an array of symbols.
 * @property {boolean} [alphabetical = true] - to order the items in the legend in alphabetical order
 * @property {boolean} [legend = true] - boolean to add or not the legend
 * @property {string} [leg_type = "vertical"] - legend orientation ("horizontal" or "vertical")
 * @property {array} [leg_pos = [10, 10]] - position of the legend
 * @property {*} [*] - You can also modify numerous parameters to customize the map. To do this, you can use all the parameters of the [path](#path) and [tool.typo](#tool/typo) functions. For example: `strokeWidth: 0.3`.
 * @property {*} [leg_*] - You can also modify a wide range of parameters to customize the legend. To do this, you can use all the parameters of the [legend.typo_horizontal](#legend/typo_horizontal) and [legend.typo_vertical](#legend/typo_vertical) functions with the prefix `"leg_"`. For example: `leg_missing_text: "not available"` or `leg_values_fill: "red"`.
 * @property {*} [svg_*]  - *parameters of the svg container created if the layer is not called inside a container (e.g svg_width)*
 * @example // Usage
 * geoviz.plot({type:"symbol", data: usa, var: "states"})
 */
export function plot_symbol(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 = {
    legend: true,
    var: "star",
    rotate: 0,
    skewX: 0,
    skewY: 0,
    strokeWidth: 0.2,
    leg_type: "nested",
    leg_pos: [10, 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;

  // BASEMAP

  if (implantation(opts.data) == 3 && newcontainer) {
    svg.path({ datum: opts.data, fill: "#CCC", fillOpacity: 0.5 });
  }

  // LAYER OPTS
  opts.symbol = opts.symbol ? opts.symbol : opts.var;

  if (!opts.types) {
    if (propertiesentries(opts.data).includes(opts.var)) {
      opts.types = [
        ...new Set(opts.data.features.map((d) => d.properties[opts.var])),
      ];
    } else {
      opts.types = [opts.symbol || opts.var];
    }
  }

  let fieldtosymbol = getsymb(
    opts.order || opts.data.features.map((d) => d.properties[opts.symbol]),
    { symbols: opts.symbols, missing: opts.missing, picto }
  );

  opts.symbols = opts.symbols
    ? opts.symbols
    : opts.types.map((d) => fieldtosymbol(d));

  let layeropts = {};
  Object.keys(opts)
    .filter((str) => str.slice(0, 4) != "leg_")
    .forEach((d) => Object.assign(layeropts, { [d]: opts[d] }));

  // SYMBOLS

  svg.symbol(layeropts);

  // Legend
  if (opts.legend) {
    let legopts = {};
    Object.keys(opts)
      .filter(
        (str) =>
          str.slice(0, 4) == "leg_" ||
          [
            "background",
            "k",
            "fixmax",
            "id",
            "alphabetical",
            "missing",
            "types",
            "symbols",
          ].includes(str)
      )
      .forEach((d) =>
        Object.assign(legopts, {
          [d.slice(0, 4) == "leg_" ? d.slice(4) : d]: opts[d],
        })
      );

    legopts.symbol_fill = legopts.symbol_fill ? legopts.symbol_fill : opts.fill;
    legopts.symbol_stroke = legopts.symbol_stroke
      ? legopts.symbol_stroke
      : opts.stroke;
    legopts.symbol_background = legopts.symbol_background
      ? legopts.symbol_background
      : opts.background;
    legopts.symbol_scale = legopts.symbol_scale
      ? legopts.symbol_scale
      : opts.scale;
    legopts.symbol_rotate = legopts.symbol_rotate
      ? legopts.symbol_rotate
      : opts.rotate;
    legopts.symbol_skewX = legopts.symbol_skewX
      ? legopts.symbol_skewX
      : opts.skewX;
    legopts.symbol_skewY = legopts.symbol_skewY
      ? legopts.symbol_skewY
      : opts.skewY;

    opts.leg_type == "horizontal"
      ? svg.legend.symbol_horizontal(legopts)
      : svg.legend.symbol_vertical(legopts);
    ids = [`#${opts.id}`, `#${legopts.id}`];
  }

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

function getsymb(
  data,
  { symbols = undefined, missing = "missing", picto } = {}
) {
  let arr = data.filter((d) => d !== "" && d != null && d != undefined);
  let types = Array.from(new Set(arr));
  const arrsymb = symbols || picto.slice(0, types.length).map((d) => d.name);

  const symbolmap = new Map(types.map((d, i) => [d, arrsymb[i]]));

  return function (d) {
    if (types.includes(d)) {
      return symbolmap.get(d);
    } else if (typeof missing === "string") {
      return missing;
    }
  };
}