Source code of plot #060 back to plot

Download full working sketch as 060.tar.gz.
Unzip, then start a local web server and load the page in a browser.

const groups = [];
const plotElmId = "plot";
const canvasElmId = "canvas";
const saveElmId = "save";
const hiddenLinkElmId = "hiddenLink";
const defaultLayerName = "0-base";
const svgNS = "http://www.w3.org/2000/svg";

let dims = {
  w: 1480, // Image width (10px/mm)
  h: 1050, // Image height (10px/mm)
  pw: 2100, // Paper width (10px/mm)
  ph: 1480, // Paper height (10px/mm)
}

let strokeWidthPx = 2;

function buildPieceName() {

  const d = new Date();
  const dateStr = d.getFullYear() + "-" + ("0" + (d.getMonth() + 1)).slice(-2) + "-" +
    ("0" + d.getDate()).slice(-2) + "!" +
    ("0" + d.getHours()).slice(-2) + "-" + ("0" + d.getMinutes()).slice(-2);
  return '115-3d-voronoi-octahedron!a' + "-" + dateStr;
}

export function initEnv(w, h, pw, ph, showCanvas) {

  dims.w = dims.pw = w;
  dims.h = dims.ph = h;
  dims.pw = pw;
  dims.ph = ph;
  document.title = buildPieceName();

  const elmSvg = document.getElementById(plotElmId);
  elmSvg.setAttribute("viewBox", `0, 0, ${w}, ${h}`);
  addLayer(defaultLayerName);

  sizeSvgAndCanvas(w, h, showCanvas);
  window.addEventListener("resize", () => {
    sizeSvgAndCanvas(w, h, showCanvas);
  });

  document.getElementById(saveElmId).addEventListener("click", e =>
  {
    e.preventDefault();
    saveSvg();
    return false;
  });
}

function sizeSvgAndCanvas(w, h, showCanvas) {
  const elmSvg = document.getElementById(plotElmId);
  const elmCanvas = document.getElementById(canvasElmId);
  const elmViz = document.getElementById("viz");

  if (!showCanvas) {
    const vspace = elmViz.clientHeight;
    const widthPx = Math.round(vspace / h * w);
    elmSvg.setAttribute("width", `${widthPx}px`);
    if (elmCanvas) elmCanvas.style.display = "none";
  }
  else {
    const elmContent = document.getElementById("content");
    const contentStyle = window.getComputedStyle(elmContent);
    const hwhite = parseFloat(contentStyle.borderLeftWidth) + parseFloat(contentStyle.borderRightWidth) +
      parseFloat(contentStyle.paddingLeft) + parseFloat(contentStyle.paddingRight);

    elmViz.style.width = null;
    elmViz.style.maxHeight = null;
    const vspace = elmViz.clientHeight;
    const widthPx = Math.round(vspace / h * w);

    const desiredContentWidth = 2 * widthPx + hwhite;
    const contentWidth = Math.min(desiredContentWidth, document.body.clientWidth);
    const matchingPanelHeight = (contentWidth - hwhite) / 2 / w * h;
    elmContent.style.width = `${contentWidth}px`;
    elmViz.style.maxHeight = `${matchingPanelHeight}px`;
    elmSvg.style.width = "50%";
    elmCanvas.style.width = "50%";
    elmCanvas.style.display = "block";
  }

}

export function info(str, seed) {
  document.getElementById("info").getElementsByTagName("label")[0].textContent = str;
}

export function spin(delay = 0) {
  return new Promise(resolve => {
    setTimeout(resolve, delay);
  });
}

function setPathAttributes(elmPath) {
  elmPath.setAttribute("stroke", "black");
  elmPath.setAttribute("stroke-width", `${strokeWidthPx}px`);
  elmPath.setAttribute("fill", "none");
}

function xCommaY(pt) {
  const x = Math.round(pt.x * 100) / 100;
  const y = Math.round(pt.y * 100) / 100;
  return `${x},${y}`;
}

export function addPath(pts, closed = false, layerIx = 0) {

  let pathData = "";
  for (let i = 0; i < pts.length; ++i) {
    const pt = pts[i];
    if (i == 0) pathData += "M";
    else pathData += " L";
    pathData += xCommaY(pt);
  }
  const lpt = pts[pts.length-1];
  if (closed && (lpt.x != pts[0].x || lpt.y != pts[0].y)) {
    pathData += " L" + xCommaY(pts[0]);
  }

  const elmGroup = groups[layerIx];
  const elmPath = document.createElementNS(svgNS, "path");
  setPathAttributes(elmPath);
  elmPath.setAttribute("d", pathData);
  elmGroup.appendChild(elmPath);

  return elmPath;
}

export function addLinesAsPath(lines, layerIx = 0) {

  let pathData = "";
  for (let i = 0; i < lines.length; ++i) {
    const ln = lines[i];
    pathData += `M${xCommaY(ln[0])}L${xCommaY(ln[1])}`
  }

  const elmGroup = groups[layerIx];
  const elmPath = document.createElementNS(svgNS, "path");
  setPathAttributes(elmPath);
  elmPath.setAttribute("d", pathData);
  elmGroup.appendChild(elmPath);

  return elmPath;
}

export function addGroup(g, layerIx = 0) {
  const elmGroup = groups[layerIx];
  elmGroup.appendChild(g);
}

export function addLayer(name) {
  const elmGroup = document.createElementNS(svgNS, "g");
  elmGroup.setAttribute("id", name);
  elmGroup.setAttribute("inkscape:groupmode", "layer");
  elmGroup.setAttribute("inkscape:label", name);
  const elmSvg = document.getElementById(plotElmId);
  elmSvg.appendChild(elmGroup);
  groups.push(elmGroup);
}

function saveSvg() {

  let svgStr = document.getElementById(plotElmId).outerHTML;
  const wmm = Math.round(dims.pw / 10);
  const hmm = Math.round(dims.ph / 10);
  const left = (dims.w - dims.pw) / 2;
  const top = (dims.h - dims.ph) / 2;
  const openTag = `<svg version="1.1" xmlns="${svgNS}" xmlns:xlink="http://www.w3.org/1999/xlink"
    xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
    width="${wmm}mm" height="${hmm}mm" viewBox="${left},${top},${dims.pw},${dims.ph}">\n`;
  svgStr = svgStr.replace(/<svg [^>]+>/g, openTag);

  let file;
  let data = [];
  data.push(svgStr);
  let properties = { type: 'image/svg+xml' };
  try { file = new File(data, buildPieceName() + ".svg", properties); }
  catch { file = new Blob(data, properties); }
  let url = URL.createObjectURL(file);
  const elmDownload = document.getElementById(hiddenLinkElmId);
  elmDownload.href = url;
  elmDownload.download = buildPieceName() + ".svg";
  elmDownload.click();
}