From 093014088c2319e456bf69bf357117cfcbb8664a Mon Sep 17 00:00:00 2001 From: Azgaar Date: Mon, 2 Sep 2024 02:35:50 +0200 Subject: [PATCH] feat: render states - use global fn --- modules/ui/layers.js | 165 ++++++++----------------------------------- utils/pathUtils.js | 2 +- 2 files changed, 30 insertions(+), 137 deletions(-) diff --git a/modules/ui/layers.js b/modules/ui/layers.js index d717792c..cde1b72c 100644 --- a/modules/ui/layers.js +++ b/modules/ui/layers.js @@ -989,151 +989,44 @@ function toggleStates(event) { function drawStates() { TIME && console.time("drawStates"); - regions.selectAll("path").remove(); + const {cells, states} = pack; - const {cells, vertices, features} = pack; - const states = pack.states; - const n = cells.i.length; - - const used = new Uint8Array(cells.i.length); - const vArray = new Array(states.length); // store vertices array - const body = new Array(states.length).fill(""); // path around each state - const gap = new Array(states.length).fill(""); // path along water for each state to fill the gaps - const halo = new Array(states.length).fill(""); // path around states, but not lakes - - const getStringPoint = v => vertices.p[v[0]].join(","); - - // define inner-state lakes to omit on border render - const innerLakes = features.map(feature => { - if (feature.type !== "lake") return false; - if (!feature.shoreline) Lakes.getShoreline(feature); - - const states = feature.shoreline.map(i => cells.state[i]); - return new Set(states).size > 1 ? false : true; + const renderHalo = shapeRendering.value === "geometricPrecision"; + const paths = getVertexPaths({ + getType: cellId => cells.state[cellId], + options: {fill: true, waterGap: true, halo: renderHalo} }); - for (const i of cells.i) { - if (!cells.state[i] || used[i]) continue; - const state = cells.state[i]; + const maxLength = states.length - 1; + const bodyPaths = new Array(maxLength); + const clipPaths = new Array(maxLength); + const haloPaths = new Array(maxLength); - const onborder = cells.c[i].some(n => cells.state[n] !== state); - if (!onborder) continue; + for (const [index, {fill, waterGap, halo}] of paths) { + const color = states[index].color; - const borderWith = cells.c[i].map(c => cells.state[c]).find(n => n !== state); - const vertex = cells.v[i].find(v => vertices.c[v].some(i => cells.state[i] === borderWith)); - const chain = connectVertices(vertex, state); + bodyPaths.push( + /* html */ `` + ); - const noInnerLakes = chain.filter(v => v[1] !== "innerLake"); - if (noInnerLakes.length < 3) continue; - - // get path around the state - if (!vArray[state]) vArray[state] = []; - const points = noInnerLakes.map(v => vertices.p[v[0]]); - vArray[state].push(points); - body[state] += "M" + points.join("L"); - - // connect path for halo - let discontinued = true; - halo[state] += noInnerLakes - .map(v => { - if (v[1] === "border") { - discontinued = true; - return ""; - } - - const operation = discontinued ? "M" : "L"; - discontinued = false; - return `${operation}${getStringPoint(v)}`; - }) - .join(""); - - // connect gaps between state and water into a single path - discontinued = true; - gap[state] += chain - .map(v => { - if (v[1] === "land") { - discontinued = true; - return ""; - } - - const operation = discontinued ? "M" : "L"; - discontinued = false; - return `${operation}${getStringPoint(v)}`; - }) - .join(""); - } - - // find state visual center - vArray.forEach((ar, i) => { - const sorted = ar.sort((a, b) => b.length - a.length); // sort by points number - states[i].pole = polylabel(sorted, 1.0); // pole of inaccessibility - }); - - const bodyData = body.map((p, s) => [p.length > 10 ? p : null, s, states[s].color]).filter(d => d[0]); - const gapData = gap.map((p, s) => [p.length > 10 ? p : null, s, states[s].color]).filter(d => d[0]); - - const bodyString = bodyData.map(d => ``).join(""); - const gapString = gapData.map(d => ``).join(""); - statesBody.html(bodyString + gapString); - - const isOptimized = shapeRendering.value === "optimizeSpeed"; - if (!isOptimized) { - const haloData = halo.map((p, s) => [p.length > 10 ? p : null, s, states[s].color]).filter(d => d[0]); - - const haloString = haloData - .map(d => { - const stroke = d3.color(d[2]) ? d3.color(d[2]).darker().hex() : "#666666"; - return ``; - }) - .join(""); - statesHalo.html(haloString); - - const clipString = bodyData - .map(d => ``) - .join(""); - defs.select("#statePaths").html(clipString); - } - - function connectVertices(start, state) { - const chain = []; // vertices chain to form a path - const getType = c => { - const borderCell = c.find(i => cells.b[i]); - if (borderCell) return "border"; - - const waterCell = c.find(i => cells.h[i] < 20); - if (!waterCell) return "land"; - if (innerLakes[cells.f[waterCell]]) return "innerLake"; - return features[cells.f[waterCell]].type; - }; - - for (let i = 0, current = start; i === 0 || (current !== start && i < 20000); i++) { - const prev = chain.length ? chain[chain.length - 1][0] : -1; // previous vertex in chain - - const c = vertices.c[current]; // cells adjacent to vertex - chain.push([current, getType(c)]); // add current vertex to sequence - - c.filter(c => cells.state[c] === state).forEach(c => (used[c] = 1)); - const c0 = c[0] >= n || cells.state[c[0]] !== state; - const c1 = c[1] >= n || cells.state[c[1]] !== state; - const c2 = c[2] >= n || cells.state[c[2]] !== state; - - const v = vertices.v[current]; // neighboring vertices - - if (v[0] !== prev && c0 !== c1) current = v[0]; - else if (v[1] !== prev && c1 !== c2) current = v[1]; - else if (v[2] !== prev && c0 !== c2) current = v[2]; - - if (current === prev) { - ERROR && console.error("Next vertex is not found"); - break; - } + if (renderHalo) { + const haloColor = d3.color(color)?.darker().hex() || "#666666"; + clipPaths.push(/* html */ ``); + haloPaths.push( + /* html */ `` + ); } - - if (chain.length) chain.push(chain[0]); - return chain; } - invokeActiveZooming(); + byId("statesBody").innerHTML = bodyPaths.join(""); + byId("statePaths").innerHTML = renderHalo ? clipPaths.join("") : ""; + byId("statesHalo").innerHTML = renderHalo ? haloPaths.join("") : ""; + + // vArray.forEach((ar, i) => { + // const sorted = ar.sort((a, b) => b.length - a.length); // sort by points number + // states[i].pole = polylabel(sorted, 1.0); // pole of inaccessibility + // }); + TIME && console.timeEnd("drawStates"); } diff --git a/utils/pathUtils.js b/utils/pathUtils.js index e148270b..df617e05 100644 --- a/utils/pathUtils.js +++ b/utils/pathUtils.js @@ -65,7 +65,7 @@ function getVertexPaths({getType, options}) { function isLandVertex(vertex) { const adjacentCells = vertices.c[vertex]; - return adjacentCells.every(i => cells.h[i] >= MIN_LAND_HEIGHT); + return adjacentCells.every(i => cells.h[i] >= 20); } function addPath(index, vertexChain) {