Public
Edited
Apr 22, 2024
Insert cell
Insert cell
// for an attribute map we need:
// the name of the field,
// the name of the scale (unless we make some fancy function)

// ideally we'd make a GUI for this.

attMap = ({
cy: {
name: "duration",
scale: d3
.scaleLinear()
.domain(d3.extent(data, (d) => d.duration))
.range([height - margin, 0])
},
cx: {
name: "date",
scale: d3
.scaleUtc()
.range([margin, width])
.domain(d3.extent(data, (d) => d.date))
},
fill: {
name: "choice",
scale: d3.scaleOrdinal(d3.schemeCategory10).domain(data, (d) => d.choice)
}
})
Insert cell
Insert cell
tasks = [
"Doing Taxes",
"Going Skiing",
"Moving a Friend",
"respond to emails",
"get groceries",
"make supper",
"clean bedroom",
"reroof house"
]
Insert cell
Insert cell
(data.fieldTypes = {
date: "date",
task: "desc",
duration: "int",
choice: "categorical"
})
Insert cell
{
jForm;
boxed2; // necessary maintain reactive dependency
return data;
}
Insert cell
viewof boxed2 = {
// jForm;
return box2(data);
}
Insert cell
viewof boxed2.value
Insert cell
boxed2
Insert cell
(data[0].duration = 1)
Insert cell
mutable dog = "meow"
Insert cell
md`this is a sentence with a ${(mutable dog =
Math.floor(now / 1000) % 6 > 3 ? "dog" : "cat")}`
Insert cell
md`this makes no sense: ${dog}`
Insert cell
Insert cell
{
data;
return data.selected;
}
Insert cell
viewof jForm = {
boxed2; // necessary to make cell reactive
return createForm(data);
}
Insert cell
{
jForm;
// yield now;
if (data.selected) {
data.selected.duration = jForm.option2;
data.bla = 3;
}
return data;
}
Insert cell
jForm
Insert cell
Insert cell
margin = 20
Insert cell
dateScale = d3.scaleUtc().domain(d3.extent(data, (d) => d.date))
Insert cell
intScale = d3.scaleLinear().domain(d3.extent(data, d=>d.duration))
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
{
box; // create an explicit dependency, also the box must emit an "input" and event.
return circlesData;
}
Insert cell
Insert cell
yAxis = (g, y) =>
g
// yAxis = (g, y, title) => g
.attr("transform", `translate(${margin + 5},0)`)
.call(d3.axisLeft(y))
Insert cell
xAxis = (g, x, height) =>
g.attr("transform", `translate(0,${height - margin})`).call(
d3
.axisBottom(x)
.ticks(width / 80)
.tickSizeOuter(0)
)
Insert cell
box2 = (data) => {
// viewof box2 = {
// Set up the SVG container

const svg = d3
.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [0, 0, width, height])
.attr("style", "max-width: 100%; height: auto; height: intrinsic;");

// Create circles and make them draggable
const circles = svg
.selectAll("circle")
// .data(newCircles)
.data(data)
.enter()
.append("circle")
// .attr("cx", (d) => x(d.duration))
.attr("cx", (d) => attMap.cx.scale(d[attMap.cx.name]))
.attr("cy", (d) => attMap.cy.scale(d[attMap.cy.name]))
.attr("r", 20)
.attr("fill", (d) => attMap.fill.scale(d[attMap.fill.name]))
.attr("id", (d) => "circle-" + d.id)
.text((d) => "Circle " + d.id)
.call(d3.drag().on("drag", dragged));

// Define the drag function
function dragged(event, d) {
// Update the circle's position based on the drag event
const newX = event.x;
const newY = event.y;

// move selected circle
d3.select(this).attr("cx", newX).attr("cy", newY);

// change the value of referenced circle (do we know if the data has the properties cx and cy. actually if it doesn't it just adds them.. )
// ideally, I would know to translate these graphical coordinates back into what they were (if possible)
// d.cx = newX;

// shouldn't this mapping be in the attMap?
// eg. d[attMap.cy.name] = attMap.cy.scale.invert(newY);
// d.duration = attMap.cy.scale.invert(newY); // old
d[attMap.cy.name] = attMap.cy.scale.invert(newY);
// d.date = attMap.cx.scale.invert(newX); //old
d[attMap.cx.name] = attMap.cx.scale.invert(newX);
// d.cy = newY;

// experiment:
data.selected = d;
svg.property("value", data);

// critical to get reactivity happening eg. data will change but not trigger dependencies
svg.dispatch("input");

// Log the new coordinates and identifier to the console
// console.log(
// "Circle " + d.id + " - New Position: (" + newX + ", " + newY + ")"
// );
}

svg.append("g").call(xAxis, attMap.cx.scale, height);
svg.append("g").call(yAxis, attMap.cy.scale);
return svg.node();
}
Insert cell
// updated when circlesData0 is refreshed
newCircles = circlesData0.map((r) => ({ id: r.id, cx: x(r.cx), cy: y(r.cy) }))
Insert cell
circlesData0 = [1, 2, 3, 4, 5].map((d) => ({
id: d,
cx: Math.random(),
cy: Math.random()
}))
Insert cell
Insert cell
height = 400
Insert cell
x = xg(data, "duration")

// d3
// .scaleLinear()
// .domain(d3.extent(circlesData0, (d) => d.cx))
// .range([0,width])
Insert cell
xg = (data, param) =>
d3
.scaleLinear()
.domain(d3.extent(data, (d) => d[param]))
.range([0, width])
Insert cell
data.map(r => x(r.duration))
Insert cell
y = d3
.scaleLinear()
.domain(d3.extent(circlesData0, (d) => d.cy))
.range([height, 0])
Insert cell
d3.extent(circlesData0, (d) => d.cx)
Insert cell
mutable wall = null
Insert cell
w = 500
Insert cell
h = 200
Insert cell
mutableWalls = {
var c = DOM.context2d(w, h);
var ball = { x: w / 2, y: h / 2, r: 8, dx: -2, dy: -2 };

while (true) {
(ball.x += ball.dx), (ball.y += ball.dy);

c.fillStyle = "black";
c.fillRect(0, 0, w, h);
c.beginPath();
c.fillStyle = "blue";
c.arc(ball.x, ball.y, ball.r, 0, 2 * Math.PI);
c.fill();

if (ball.x - ball.r < 0) (mutable wall = "left"), (ball.dx = 2);
if (ball.x + ball.r > w) (mutable wall = "right"), (ball.dx = -2);
if (ball.y - ball.r < 0) (mutable wall = "top"), (ball.dy = 2);
if (ball.y + ball.r > h) (mutable wall = "bottom"), (ball.dy = -2);

yield c.canvas;
}
}
Insert cell
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