Public
Edited
Apr 22, 2022
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
{
const text = await FileAttachment("desk_playground_3.svg").text();
const document = new DOMParser().parseFromString(text, "image/svg+xml");
const map_svg = d3.select(document.documentElement).remove();
const theMap = map_svg.node();

const svg = d3
.create("svg")
.attr("width", theMap.getAttribute("width"))
.attr("height", theMap.getAttribute("height"))
.attr("id", theMap.getAttribute("id"))
.attr("class", "modified") //theMap.getAttribute("class"))
.attr("viewBox", theMap.getAttribute("viewBox"))
.style("cursor", "crosshair");

try {
const deskSymbol = d3.select("svg.modified symbol#desk");
const oldVB = decompose_viewbox(deskSymbol.attr("viewBox"));
const desk_width = oldVB.width;
const desk_height = oldVB.height;

// move the symbol's insertion point to the desired insertion point
// at the moment, this doesn't work at all
if(true) {
const desks = d3.selectAll("svg.modified use");
desks.each((_, i, collection) => {
const el = d3.select(collection[i]);
if(true) { //(el.attr("xlink:href") == "#desk" && !el.classed("translated") {
const initial_transform = extract_transform(el.attr("transform"));
const newX = initial_transform.x + desk_width * 0.5;
const newY = initial_transform.y + desk_height * 0.5;
const newTranslate = `translate(${newX} ${newY})`; // rotate(${initial_transform.rot})`)
el.attr("transform", newTranslate);
el.classed("translated", true);
console.log(
el.attr("xlink:href"),
el.classed("translated"),
initial_transform,
newTranslate,
el.attr("transform")
)
}
});
}

// modify viewbox of symbol for the desk
if(false){
if (deskSymbol.classed("viewbox_moved")) {
console.log("viewbox already moved, not doing it again");
} else {
deskSymbol
.attr("viewBox", translate_viewbox(deskSymbol.attr("viewBox"), 0, 0, 3))
.attr("width", null)
.attr("height", null)
.classed("viewbox_moved", true);
console.log("viewBox", deskSymbol.attr("viewBox"));
}
}

// Move the contents of the symbol definition back into the right place again.
if(false) {
deskSymbol.selectChildren().each((_, i, collection) => {
const el = d3.select(collection[i]);
const vb = decompose_viewbox(deskSymbol.attr("viewBox"));
const x_trans = vb.width / 2;
const y_trans = vb.height / 2;
const trans = `translate(${-x_trans} ${-y_trans})`;
el.attr("transform", trans);
console.log(el, "trans", trans);
});
}
// and by here, everything should look the same
} catch (error) {
console.error(error, "probably the first time through?");
/* I don't know what's going on here. The svg is modified every time this cell
runs, rather than reloading it, and running the transform on a fresh version.
I assume it's a caching thing. There's a correct way to either force a fresh
load, or to do a deep copy, or something?
*/
}

//The rest of this is handling the zooming, and some decorations
// x and y are scales that project the data space to the ‘unzoomed’ pixel referential
const x = d3.scaleLinear([0, 1], [0, 1]);
const y = d3.scaleLinear([0, 1], [0, 1]);

const g = svg.append("g");

g.node().appendChild(theMap);

console.log("data", data);
const origin_points = g
.selectAll("circle")
.data(data)
.join("circle")
.attr("cx", (d) => Math.round(x(d.x)))
.attr("cy", (d) => Math.round(y(d.y)))
.attr("r", 1)
.attr("style", "fill: none; stroke: blue; stroke-width: 1;");

let transform;

const zoom = d3.zoom().on("zoom", (e) => {
g.attr("transform", (transform = e.transform));
g.style("stroke-width", 3 / Math.sqrt(transform.k));
});

return svg
.call(zoom)
.call(zoom.transform, d3.zoomIdentity)
.node();
}
Insert cell
decompose_viewbox = (viewbox_string) => {
const parts = viewbox_string.split(" ");
const min_x = parseFloat(parts[0]);
const min_y = parseFloat(parts[1]);
const width = parseFloat(parts[2]);
const height = parseFloat(parts[3]);
return {min_x, min_y, width, height}
}
Insert cell
translate_viewbox = (viewbox_string, newCenX, newCenY, dp) => {
const coords = decompose_viewbox(viewbox_string)
// console.log("coords", coords)
const nvb = {
min_x: (coords.min_x + newCenX - (coords.width * 0.5)).toFixed(dp),
min_y: (coords.min_y + newCenY - (coords.height * 0.5)).toFixed(dp),
width: coords.width.toFixed(dp),
height: coords.height.toFixed(dp)
}
return `${nvb.min_x} ${nvb.min_y} ${nvb.width} ${nvb.height} `
}
Insert cell
[
translate_viewbox("0 0 100 100", -10, -10),
translate_viewbox("0 0 100 100", 10, 10),
translate_viewbox("0 0 100 100", 0, 0),
translate_viewbox("0 0 100 100", 50, 50),
]
Insert cell
d3 = require("d3@6")
Insert cell
desk_data = get_desk_data()
Insert cell
construct_data = ()=> {
let data = []
desk_data.forEach(d => {
if (Math.random() > 0) { //Change this number to give sparser fills
const payload = {
x: d.x,
y: d.y,
rot: d.rot,
desk: d.desk,
dock: d.dock,
name: d.rot + ", desk:" + d.desk + " dock:" + d.dock
}
data.push(payload);
}
})
return data
}
Insert cell
data = construct_data()
Insert cell
construct_data()
Insert cell
get_desk_data()
Insert cell
async function get_desk_data(){
const text = await FileAttachment("desk_playground_3.svg").text();
const document = (new DOMParser).parseFromString(text, "image/svg+xml");
const map_svg = d3.select(document.documentElement).remove();
const desks = map_svg.selectAll("use")
console.log(desks)
let desk_attrs = [];
const deskData = desks.each((d, i, j) => {
const desk_group = d3.select(j[i])
const tranform_components = extract_transform(desk_group.attr("transform"))
console.log(tranform_components)
desk_attrs.push({...tranform_components})
})
return desk_attrs
}
Insert cell
function extract_transform(transform){
const regex = /translate\((\d+\.*\d*) (\d+\.*\d*)\)( rotate\((-*\d+)\))*/g;

let m;
while ((m = regex.exec(transform)) !== null) {
// This is necessary to avoid infinite loops with zero-width matches
if (m.index === regex.lastIndex) {
regex.lastIndex++;
}
let rot = parseInt(m[4], 10);
if (!isNaN(rot) ){rot = 0}
return {
x: parseInt(m[1], 10),
y: parseInt(m[2], 10),
rot: parseInt(m[4], 10) | 0,
check_transform_string: m[0]
}
}
console.log("failed transform", transform)
}
Insert cell
deg2Rad = (degrees) => degrees * Math.PI / 180
Insert cell
move_to_chair = (data, dist, angle_nudge_in_degrees) => {
const ang = deg2Rad(data.rot + angle_nudge_in_degrees)
const dx = dist * Math.cos(ang);
const dy = -dist * Math.sin(ang);
const trans_string = `translate(${dx.toFixed(1)} ${dy.toFixed(1)})`
console.log(
"move_to_chair " + data.message,
{data, dist, angle_nudge_in_degrees, ang, dx, dy, trans_string}
)
return trans_string
}
Insert cell
[
move_to_chair({rot:0, message: "🍟this one"}, 9, 135),
// move_to_chair({rot:45}, 10, 90),
// move_to_chair({rot:90}, 10, 90),
// move_to_chair({rot:270}, 10, 90),
// move_to_chair({rot:-45}, 10, 90),
// move_to_chair({rot:360}, 10, 90),
]
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more