Public
Edited
Dec 15
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
showMap({ points: pointsGridOptimized, height: 800, r: 3 })
Insert cell
Insert cell
showMap = (input) => {
const xextent = d3.extent(input.points, (d) => d[0]);
const yextent = d3.extent(input.points, (d) => d[1]);
const xyratio = (xextent[1] - xextent[0]) / (yextent[1] - yextent[0]);

const margin = { top: 20, right: 30, bottom: 30, left: 40 };
const w = d3.min([input.height || 900, width]);
const h = w / xyratio;

const r = input.r || 3;

const color = d3.scaleOrdinal().range(d3.schemeCategory10);

const x = d3
.scaleUtc()
.domain(xextent)
.range([margin.left, w - margin.right]);

const y = d3
.scaleLinear()
.domain(yextent)
.range([h - margin.bottom, margin.top]);

const svg = d3.create("svg").attr("width", w).attr("height", h);

svg
.selectAll("circle")
.data(input.points)
.join("circle")
.attr("cx", (d) => x(d[0]))
.attr("cy", (d) => y(d[1]))
.attr("r", r)
.attr("fill", "steelblue");
if (input.xgrid) {
svg
.selectAll("line.gridx")
.data(input.xgrid)
.join("line")
.attr("class", "gridx")
.attr("x1", (d) => x(d))
.attr("y1", margin.top)
.attr("x2", (d) => x(d))
.attr("y2", h - margin.bottom)
.attr("stroke", "black")
.attr("stroke-width", 0.2);
}
if (input.ygrid) {
svg
.selectAll("line.gridy")
.data(input.ygrid)
.join("line")
.attr("class", "gridy")
.attr("x1", margin.left)
.attr("y1", (d) => y(d))
.attr("x2", w - margin.right)
.attr("y2", (d) => y(d))
.attr("stroke", "black")
.attr("stroke-width", 0.2);
}
if (input.points2) {
svg
.selectAll("circlec")
.data(input.points2)
.join("circle")
.attr("class", "c")
.attr("cx", (d) => x(d[0]))
.attr("cy", (d) => y(d[1]))
.attr("r", 1)
.attr("fill", "#ffaaaa");
}

return svg.node();
}
Insert cell
Insert cell
testpoints = d3
.range(0, 10)
.map((d) => [Math.random() * 10, Math.random() * 10])
Insert cell
testx = unisplit(testpoints.map((d) => d[0]))
Insert cell
testy = unisplit(testpoints.map((d) => d[1]))
Insert cell
testall = combineSplits(testx, testy)
Insert cell
testgrid = testpoints.map((d) => [findSlot(testx, d[0]), findSlot(testy, d[1])])
Insert cell
showMap({ points: testpoints, r: 2, height: 200 })
Insert cell
showMap({ points: testgrid, r: 2, height: 200 })
Insert cell
showMap({ points: pointsGridded, height: 600, r: 2 })
Insert cell
showMap({ points: pointsGridOptimized, height: 600, r: 4 })
Insert cell
Insert cell
xsplits = unisplit(flat.map((d) => d[0]))
Insert cell
ysplits = unisplit(flat.map((d) => d[1]))
Insert cell
allSplits = combineSplits(xsplits, ysplits)
Insert cell
pointsGridded = {
//let all = sortSplits(allSplits);
return flat.map((d) => [findSlot(xsplits, d[0]), findSlot(ysplits, d[1])]);
}
Insert cell
pointsGridOptimized = {
let more = true;
let points = _.clone(pointsGridded);
let splits = _.clone(allSplits);
let count = 0;
while (more) {
// console.log("Splits", count, JSON.stringify(points));
let nextSplit = findShortestSplitThatCanCollapse(splits, points);

if (nextSplit !== null) {
collapseSplit(nextSplit, splits, points);
console.log(
"Next available split to collapse:",
nextSplit,
splits.length,
"points",
points
);
} else {
console.log("No more available splits");
more = false;
}
yield points;
//more = false;
await new Promise((resolve) => setTimeout(resolve, 100));

if (count++ > 1000) more = false;
}
}
Insert cell
Insert cell
collapseSplit = (split, splits, points) => {
if (split.type === "x") {
for (let i = 0; i < points.length; i++) {
if (points[i][0] > split.index) {
points[i][0] = points[i][0] - 1;
}
}

let removed = splits.find((d) => splitIsNextTo(split, d));
let cur = splits.find((d) => splitEquals(split, d));
if (removed) {
cur.distNext += removed.distNext;
} else {
delete cur.distNext;
}

let indexToRemove = splits.findIndex((d) => splitIsNextTo(split, d));
if (indexToRemove !== -1) {
splits.splice(indexToRemove, 1);
}
splits = splits.map((d) => splitNewIndex(split, d));
} else {
for (let i = 0; i < points.length; i++) {
if (points[i][1] > split.index) {
points[i][1] = points[i][1] - 1;
}
}

let removed = splits.find((d) => splitIsNextTo(split, d));
let cur = splits.find((d) => splitEquals(split, d));
if (removed) {
cur.distNext += removed.distNext;
} else {
delete cur.distNext;
}

let indexToRemove = splits.findIndex((d) => splitIsNextTo(split, d));
if (indexToRemove !== -1) {
splits.splice(indexToRemove, 1);
}
splits = splits.map((d) => splitNewIndex(split, d));
}
}
Insert cell
splitNewIndex = (splitAt, thisSplit) => {
if (thisSplit.type === splitAt.type && thisSplit.index > splitAt.index) {
thisSplit.index--
return thisSplit;
}
return thisSplit;
}
Insert cell
splitEquals = (split1, split2) => {
return split1.type === split2.type && split1.index == split2.index;
}
Insert cell
splitIsNextTo = (nextToThis, split) => {
return split.type === nextToThis.type && split.index == nextToThis.index + 1;
}
Insert cell
findSlot = (splits, input) => {
for (let i = 0; i < splits.length; i++) {
if (input < splits[i].split) return i;
}
return splits.length;
}
Insert cell
unisplit = (input) => {
let sorted = _.uniq(input.sort((a, b) => a - b));
let mids = [];
for (let i = 0; i < sorted.length - 1; i++) {
let o = {
index: i,
split: sorted[i] + (sorted[i + 1] - sorted[i]) / 2
};
mids.push(o);
}
for (let i = 0; i < mids.length - 1; i++) {
mids[i].distNext = mids[i + 1].split - mids[i].split;
}
return mids;
}
Insert cell
combineSplits = (x, y) => {
let allSplits = [];
allSplits.push(...x.map((d) => ({ ...d, type: "x" })));
allSplits.push(...y.map((d) => ({ ...d, type: "y" })));
return allSplits;
}
Insert cell
sortSplits = (allSplits) => {
return allSplits.sort((a, b) => a.distNext - b.distNext);
}
Insert cell
findShortestSplit = (allSplits) =>
allSplits.reduce((current, next) =>
next.distNext < current.distNext ? next : current
)
Insert cell
findShortestSplitThatCanCollapse = (splits, points) => {
let sortedSplits = sortSplits(
splits.filter((d) => d.hasOwnProperty("distNext"))
);
for (let i = 0; i < sortedSplits.length; i++) {
if (
sortedSplits[i].hasOwnProperty("distNext") &&
checkSplitCollapse(sortedSplits[i], points)
) {
return sortedSplits[i];
} else {
console.log("Skip this. cant collapse", sortedSplits[i]);
}
}
return null;
}
Insert cell
checkSplitCollapse = (split, points) => {
if (split.type === "x") {
let checkPoints = points.filter(
(d) => d[0] === split.index || d[0] === split.index + 1
);
const uniqueValues = new Set(checkPoints.map((d) => d[1]));
return uniqueValues.size === checkPoints.length;
} else {
let checkPoints = points.filter(
(d) => d[1] === split.index || d[1] === split.index + 1
);
const uniqueValues = new Set(checkPoints.map((d) => d[0]));
return uniqueValues.size === checkPoints.length;
}
}
Insert cell
splits = unisplit([1, 3, 4, 10, 2, 5, 4, 3, 4, 6, 200])
Insert cell
slot = findSlot(splits, 10)
Insert cell
Insert cell
//flat = _.flatten(kommuner.map((d) => d.c))
flat = kommuner.map((d) => d.centroid)
Insert cell
flatc = _.flatten(kommuner.map((d) => d.c))
Insert cell
Insert cell
kommuner = FileAttachment("kommuner2.json").json()
Insert cell
kommunerSamle = FileAttachment("kommunerSample.json").json()
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more