Unlisted
Edited
Apr 18
Importers
Insert cell
Insert cell

viewof priorities = taskPriorityMatrix({
width: 1000,
height: 500
})

Insert cell
// export
taskPriorityMatrix
Insert cell
// Make it work with viewof
taskPriorityMatrix.value = function(container) {
return container.value;
};
Insert cell
// Task priority matrix main function
taskPriorityMatrix = function(config = {}) {
// Set default configuration
const width = config.width || 400;
const height = config.height || 400;
const margin = config.margin || { top: 20, right: 20, bottom: 30, left: 30 };
const initialTasks = config.tasks || defaultTasks;
// Create container and SVG elements
const {container, svgElement} = createMatrixContainer(width, height);
const svg = d3.select(svgElement);
// Add task group for rendering tasks
svg.append("g").attr("class", "tasks-group");
// Initialize tasks array
let tasks = [...initialTasks];
// Create matrix structure
const gridGroup = createMatrixBackground(svgElement, width, height, margin);
createQuadrants(gridGroup, width, height, margin, quadrantsConfig);
// Store drag behavior on container for reuse
const dragBehavior = createDragBehavior(svgElement, tasks, width, height, margin, container);
container._dragBehavior = dragBehavior; // Save for future reference
// Render initial tasks
renderTasks(svgElement, tasks, width, height, margin, dragBehavior);

// Add trash can for deletion
createTrashIcon(svg, width, height);
// Add keyboard navigation
addKeyboardSupport(svgElement, tasks, width, height, margin, container, renderTasks);
// Add task creation button
createAddTaskButton(container, tasks, svgElement, width, height, margin, dragBehavior);
// Setup resize handling
setupResizeHandler(svg, svgElement, container, tasks, width, height, margin, dragBehavior);
// Initialize value for Observable
container.value = tasks;
return container;
}
Insert cell
// Create matrix container elements
createMatrixContainer = function(width, height) {
// Create container element
const container = document.createElement("div");
container.className = "task-priority-matrix";
container.style.position = "relative";
container.style.width = `${width}px`;
container.style.height = `${height}px`;
// Create SVG element
const svgElement = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svgElement.setAttribute("width", width);
svgElement.setAttribute("height", height);
svgElement.setAttribute("aria-label", "Task Priority Matrix");
svgElement.setAttribute("role", "img");
svgElement.style.position = "absolute";
container.appendChild(svgElement);
return {container, svgElement};
}
Insert cell
// Create trash icon for task deletion
createTrashIcon = function(svg, width, height) {
const trashIcon = svg.append("g")
.attr("class", "trash-icon")
.attr("transform", `translate(${width/2}, ${height/2})`)
.style("opacity", 0.6)
.style("cursor", "pointer");

// Add background circle to increase clickable area
trashIcon.append("circle")
.attr("r", 20)
.attr("fill", "rgba(255,255,255,0.7)")
.attr("stroke", "#aaa")
.attr("stroke-width", 1)
.attr("stroke-dasharray", "3,3");

// Add trash icon using Unicode
trashIcon.append("text")
.attr("text-anchor", "middle")
.attr("dominant-baseline", "central")
.attr("font-family", "sans-serif")
.attr("font-size", "22px")
.attr("fill", "#666")
.text("🗑️");

// Add helper text
trashIcon.append("text")
.attr("text-anchor", "middle")
.attr("y", 24)
.attr("font-size", "9px")
.attr("fill", "#666")
.text("Drag here to delete");
return trashIcon;
}
Insert cell
// Create add task button
createAddTaskButton = function(container, tasks, svgElement, width, height, margin, dragBehavior) {
const addButton = document.createElement("button");
addButton.textContent = "Add new Task";
addButton.className = "add-task-button";
addButton.style.position = "absolute";
addButton.style.bottom = "5px";
addButton.style.right = "5px";
addButton.style.padding = "5px 10px";
addButton.style.borderRadius = "4px";
addButton.style.border = "1px solid #ccc";
addButton.style.backgroundColor = "#f8f8f8";
addButton.style.cursor = "pointer";
// Add hover effects
addButton.addEventListener("mouseover", () => {
addButton.style.backgroundColor = "#e8e8e8";
});
addButton.addEventListener("mouseout", () => {
addButton.style.backgroundColor = "#f8f8f8";
});
// Handle click event
addButton.onclick = function() {
console.log("Button clicked");
// Remove existing dialog if present
const existingDialog = container.querySelector(".task-dialog-container");
if (existingDialog) {
container.removeChild(existingDialog);
return;
}
// Create task dialog
createTaskDialog(function(text) {
if (text) { // User confirmed with text input
const newId = tasks.length > 0 ? Math.max(...tasks.map(t => t.id)) + 1 : 1;
const newTask = {
id: newId,
text: text,
x: 0.75, // Position in important, not urgent quadrant
y: 0.25
};
tasks.push(newTask);
renderTasks(svgElement, tasks, width, height, margin, dragBehavior);
// Trigger input event
container.value = [...tasks];
container.dispatchEvent(new Event("input"));
}
}, container);
};
container.appendChild(addButton);
return addButton;
}
Insert cell
// Setup resize handler for responsive matrix
setupResizeHandler = function(svg, svgElement, container, tasks, width, height, margin, dragBehavior) {
const resizeObserver = new ResizeObserver(entries => {
// Only rerender when container size actually changes
if (entries.length > 0) {
const newWidth = entries[0].contentRect.width;
const newHeight = entries[0].contentRect.height;
if (newWidth !== width || newHeight !== height) {
// Update SVG size
svg.attr("width", newWidth).attr("height", newHeight);
// Rerender with new dimensions
renderTasks(svgElement, tasks, newWidth, newHeight, margin, dragBehavior);
}
}
});
// Start observing container
resizeObserver.observe(container);
// Handle cleanup
if (typeof invalidation !== 'undefined') {
invalidation.then(() => {
resizeObserver.disconnect();
});
}
return resizeObserver;
}
Insert cell
// Create a custom dialog component
createTaskDialog = function(callback, container) {
// Create a dialog box that is positioned relative to the container
const modalContainer = document.createElement("div");
modalContainer.style.position = "absolute";
modalContainer.style.top = "0";
modalContainer.style.left = "0";
modalContainer.style.width = "100%";
modalContainer.style.height = "100%";
modalContainer.style.backgroundColor = "rgba(0,0,0,0.3)";
modalContainer.style.display = "flex";
modalContainer.style.alignItems = "center";
modalContainer.style.justifyContent = "center";
modalContainer.style.zIndex = "100";
modalContainer.className = "task-dialog-container";
const modalContent = document.createElement("div");
modalContent.style.backgroundColor = "white";
modalContent.style.padding = "15px";
modalContent.style.borderRadius = "5px";
modalContent.style.boxShadow = "0 2px 10px rgba(0,0,0,0.2)";
modalContent.style.maxWidth = "80%";
const heading = document.createElement("div");
heading.textContent = "Describe your task:";
heading.style.marginBottom = "10px";
heading.style.fontWeight = "bold";
const input = document.createElement("input");
input.type = "text";
input.value = "New Task";
input.style.width = "100%";
input.style.padding = "5px";
input.style.boxSizing = "border-box";
input.style.marginBottom = "10px";
const buttonContainer = document.createElement("div");
buttonContainer.style.display = "flex";
buttonContainer.style.justifyContent = "flex-end";
const cancelButton = document.createElement("button");
cancelButton.textContent = "Cancel";
cancelButton.style.marginRight = "5px";
cancelButton.style.padding = "3px 8px";
const okButton = document.createElement("button");
okButton.textContent = "OK";
okButton.style.padding = "3px 8px";
buttonContainer.appendChild(cancelButton);
buttonContainer.appendChild(okButton);
modalContent.appendChild(heading);
modalContent.appendChild(input);
modalContent.appendChild(buttonContainer);
modalContainer.appendChild(modalContent);
// Add the dialog box to the container, not the body
container.appendChild(modalContainer);
input.focus();
input.select();
// Handle the cancel button click
cancelButton.onclick = function() {
container.removeChild(modalContainer);
callback(null); // Return null to cancel
};
// Process confirmation button click
okButton.onclick = function() {
const text = input.value.trim();
container.removeChild(modalContainer);
callback(text); // Returns the input text
};
// You can also press Enter to confirm
input.addEventListener("keydown", function(event) {
if (event.key === "Enter") {
okButton.click();
} else if (event.key === "Escape") {
cancelButton.click();
}
});
}
Insert cell
// Create drag behavior
createDragBehavior = function(svg, tasks, width, height, margin, container, existingDragBehavior) {
// Ensure we use DOM element not D3 selector
const svgElement = svg.node ? svg.node() : svg;
// Debug dragging issues
console.log("Creating drag behavior");
// Use existing behavior if provided
if (existingDragBehavior) {
console.log("Using existing drag behavior");
return existingDragBehavior;
}
// Create handlers
const handlers = createDragEventHandlers(svgElement, tasks, width, height, margin, container);
// Create new drag behavior
const dragBehavior = d3.drag()
.on("start", handlers.onDragStart)
.on("drag", handlers.onDrag)
.on("end", function(event, d) {
handlers.onDragEnd.call(this, event, d, dragBehavior);
});
console.log("New drag behavior created");
return dragBehavior;
}
Insert cell
// Calculate matrix coordinates based on screen position
getMatrixCoordinates = function(eventX, eventY, svgElement, width, height, margin) {
const rect = svgElement.getBoundingClientRect();
const x = (eventX - rect.left - margin.left) / (width - margin.left - margin.right);
const y = (eventY - rect.top - margin.top) / (height - margin.top - margin.bottom);
return { x: Math.max(0, Math.min(1, x)), y: Math.max(0, Math.min(1, y)) };
}
Insert cell
// Check if a point is over the trash icon
isOverTrash = function(eventX, eventY, svgElement, width, height) {
const rect = svgElement.getBoundingClientRect();
const centerX = rect.left + width/2;
const centerY = rect.top + height/2;
// Calculate distance to center point
const dx = eventX - centerX;
const dy = eventY - centerY;
const distance = Math.sqrt(dx*dx + dy*dy);
// If distance is less than 20px, consider it over the trash
return distance < 20;
}
Insert cell
// Create drag event handlers
createDragEventHandlers = function(svgElement, tasks, width, height, margin, container) {
const svgSelection = d3.select(svgElement);
let draggedOverTrash = false;
return {
// Start drag handler
onDragStart: function(event, d) {
// Highlight dragged task
d3.select(this).raise().select("circle")
.transition()
.attr("stroke-width", 2)
.attr("stroke", "#000");
// Reset trash can highlight state
draggedOverTrash = false;
svgSelection.select(".trash-icon")
.style("opacity", 0.7) // Slightly highlight to indicate drag-drop available
.select("circle") // Changed from rect to circle
.attr("stroke", "#999")
.attr("stroke-width", 1);
},
// During drag handler
onDrag: function(event, d) {
// Check if over trash
const overTrash = isOverTrash(
event.sourceEvent.clientX,
event.sourceEvent.clientY,
svgElement,
width,
height
);
// Trash highlight effect
if (overTrash && !draggedOverTrash) {
svgSelection.select(".trash-icon")
.style("opacity", 1)
.select("circle") // Changed from rect to circle
.attr("stroke", "#f44")
.attr("stroke-width", 2)
.attr("filter", "drop-shadow(0 0 3px rgba(255,0,0,0.5))");
draggedOverTrash = true;
} else if (!overTrash && draggedOverTrash) {
svgSelection.select(".trash-icon")
.style("opacity", 0.7)
.select("circle") // Changed from rect to circle
.attr("stroke", "#999")
.attr("stroke-width", 1)
.attr("filter", null);
draggedOverTrash = false;
}
// Calculate new position
const coords = getMatrixCoordinates(
event.sourceEvent.clientX,
event.sourceEvent.clientY,
svgElement,
width,
height,
margin
);
// Update data
d.x = coords.x;
d.y = coords.y;
// Update visual position
d3.select(this)
.attr("transform", function() {
const cx = margin.left + d.x * (width - margin.left - margin.right);
const cy = margin.top + d.y * (height - margin.top - margin.bottom);
return `translate(${cx}, ${cy})`;
})
.select("circle")
.attr("fill", getTaskColor(d.x, d.y));
},
// End drag handler
onDragEnd: function(event, d, dragBehavior) {
// Check if released over trash
const overTrash = isOverTrash(
event.sourceEvent.clientX,
event.sourceEvent.clientY,
svgElement,
width,
height
);
// Restore trash icon style
svgSelection.select(".trash-icon")
.style("opacity", 0.5)
.select("circle") // Changed from rect to circle
.attr("stroke", "#999")
.attr("stroke-width", 1)
.attr("filter", null);
// If released over trash, show confirmation dialog
if (overTrash) {
createDeleteConfirmDialog(d, tasks, svgElement, width, height, margin, container, dragBehavior);
return; // Don't update task position
}
// Standard drag end operation
d3.select(this).select("circle")
.transition()
.attr("stroke-width", 1)
.attr("stroke", "#333");
// Update position
const coords = getMatrixCoordinates(
event.sourceEvent.clientX,
event.sourceEvent.clientY,
svgElement,
width,
height,
margin
);
d.x = coords.x;
d.y = coords.y;
// Trigger input event
container.value = tasks;
container.dispatchEvent(new Event("input"));
}
};
}
Insert cell
// Create delete confirmation dialog
createDeleteConfirmDialog = function(d, tasks, svgElement, width, height, margin, container, dragBehavior) {
const confirmDialog = document.createElement("div");
confirmDialog.className = "confirm-dialog";
confirmDialog.style.position = "absolute";
confirmDialog.style.top = "50%";
confirmDialog.style.left = "50%";
confirmDialog.style.transform = "translate(-50%, -50%)";
confirmDialog.style.backgroundColor = "white";
confirmDialog.style.padding = "15px";
confirmDialog.style.borderRadius = "5px";
confirmDialog.style.boxShadow = "0 2px 10px rgba(0,0,0,0.2)";
confirmDialog.style.zIndex = "100";
confirmDialog.innerHTML = `
<p>Are you sure you want to delete task "${d.text}"?</p>
<div style="text-align: right; margin-top: 10px;">
<button id="cancel-delete" style="margin-right: 10px;">Cancel</button>
<button id="confirm-delete">Delete</button>
</div>
`;
container.appendChild(confirmDialog);
// Handle cancel button
document.getElementById("cancel-delete").onclick = function() {
container.removeChild(confirmDialog);
// Restore task style
d3.select(svgElement).selectAll(".task").filter(t => t.id === d.id)
.select("circle")
.transition()
.attr("stroke-width", 1)
.attr("stroke", "#333");
};
// Handle confirm button
document.getElementById("confirm-delete").onclick = function() {
container.removeChild(confirmDialog);

// Remove task from array
const index = tasks.findIndex(t => t.id === d.id);
if (index !== -1) {
tasks.splice(index, 1);
// Completely re-render the task list
const tasksGroup = d3.select(svgElement).select(".tasks-group");
tasksGroup.selectAll("*").remove(); // First remove all existing tasks
renderTasks(svgElement, tasks, width, height, margin, dragBehavior);
// Trigger input event
container.value = [...tasks];
container.dispatchEvent(new Event("input"));
}
};
}
Insert cell
// Added keyboard access support
addKeyboardSupport = function(svg, tasks, width, height, margin, container, renderTasks) {
// Be sure to use DOM elements instead of D3 selectors
const svgElement = svg.node ? svg.node() : svg;
const svgSelection = d3.select(svgElement);
// Add tabindex to tasks so that they can receive focus
svgSelection.selectAll(".task")
.attr("tabindex", 0)
.attr("role", "button")
.attr("aria-label", d => `Task: ${d.text}`)
.on("keydown", function(event, d) {
const step = 0.05;
let updated = false;
// Use the arrow keys to move
switch(event.key) {
case "ArrowLeft":
d.x = Math.max(0, d.x - step);
updated = true;
break;
case "ArrowRight":
d.x = Math.min(1, d.x + step);
updated = true;
break;
case "ArrowUp":
d.y = Math.max(0, d.y - step);
updated = true;
break;
case "ArrowDown":
d.y = Math.min(1, d.y + step);
updated = true;
break;
}
if (updated) {
event.preventDefault();
renderTasks(svgElement, tasks, width, height, margin)
// trigger input event
container.value = tasks;
container.dispatchEvent(new Event("input"));
}
});
}
Insert cell
// Create matrix backgrounds and labels
createMatrixBackground = function(svgElement, width, height, margin) {
const svg = d3.select(svgElement);
const gridGroup = svg.append("g").attr("class", "grid-group");
// Add a horizontal divider
gridGroup.append("line")
.attr("x1", margin.left)
.attr("y1", height / 2)
.attr("x2", width - margin.right)
.attr("y2", height / 2)
.attr("stroke", "#ccc")
.attr("stroke-width", 1);
// Add a vertical divider
gridGroup.append("line")
.attr("x1", width / 2)
.attr("y1", margin.top)
.attr("x2", width / 2)
.attr("y2", height - margin.bottom)
.attr("stroke", "#ccc")
.attr("stroke-width", 1);
// Add axis labels
const labels = [
{ x: width / 4, y: margin.top / 2, text: "Unimportant" },
{ x: 3 * width / 4, y: margin.top / 2, text: "Important" },
{ x: margin.left / 2, y: height / 4, text: "non Ungent", rotate: -90 },
{ x: margin.left / 2, y: 3 * height / 4, text: "Ungent", rotate: -90 }
];
labels.forEach(label => {
const textElement = gridGroup.append("text")
.attr("x", label.x)
.attr("y", label.y)
.attr("text-anchor", "middle")
.attr("font-size", "12px")
.text(label.text);
if (label.rotate) {
textElement.attr("transform", `rotate(${label.rotate} ${label.x} ${label.y})`);
}
});
return gridGroup;
}
Insert cell
function createQuadrants(){return(
function(gridGroup, width, height, margin, quadrants) {
// Add a quadrant background color
quadrants.forEach((q, i) => {
const x = i % 2 === 0 ? width / 2 : margin.left;
const y = i < 2 ? margin.top : height / 2;
const w = i % 2 === 0 ? width - margin.right - width / 2 : width / 2 - margin.left;
const h = i < 2 ? height / 2 - margin.top : height - margin.bottom - height / 2;
gridGroup.insert("rect", ":first-child")
.attr("x", x)
.attr("y", y)
.attr("width", w)
.attr("height", h)
.attr("fill", q.color)
.attr("fill-opacity", 0.2)
.attr("rx", 4)
.attr("ry", 4);
});
// Add quadrant text labels where they are less likely to overlap
quadrants.forEach((q, i) => {
let labelX, labelY, textAnchor;
// Place the label in a corner away from the center
if (i === 0) { // bottom right
labelX = width - margin.right - 20;
labelY = height - margin.bottom - 20;
textAnchor = "end";
} else if (i === 1) { // up right
labelX = width - margin.right - 20;
labelY = margin.top + 30;
textAnchor = "end";
} else if (i === 2) { // bottom left
labelX = margin.left + 20;
labelY = height - margin.bottom - 20;
textAnchor = "start";
} else { // up left
labelX = margin.left + 20;
labelY = margin.top + 30;
textAnchor = "start";
}
// Add background rectangles to improve text readability
const textBg = gridGroup.append("rect")
.attr("fill", "white")
.attr("fill-opacity", 0.9)
.attr("rx", 5)
.attr("ry", 5);
// Add Text
const text = gridGroup.append("text")
.attr("x", labelX)
.attr("y", labelY)
.attr("text-anchor", textAnchor)
.attr("font-size", "14px")
.attr("font-weight", "bold")
.attr("fill", "#666")
.text(q.text);
// Gets a bounding box for text to resize the background rectangle
const bbox = text.node().getBBox();
// Adjust the background rectangle position and size
textBg
.attr("x", bbox.x - 5)
.attr("y", bbox.y - 2)
.attr("width", bbox.width + 10)
.attr("height", bbox.height + 4);
});
}
)}
Insert cell
// Create a quadrant background
createQuadrantBackgrounds = function(gridGroup, width, height, margin, quadrants) {
quadrants.forEach((q, i) => {
const x = i % 2 === 0 ? width / 2 : margin.left;
const y = i < 2 ? margin.top : height / 2;
const w = i % 2 === 0 ? width - margin.right - width / 2 : width / 2 - margin.left;
const h = i < 2 ? height / 2 - margin.top : height - margin.bottom - height / 2;
gridGroup.insert("rect", ":first-child")
.attr("x", x)
.attr("y", y)
.attr("width", w)
.attr("height", h)
.attr("fill", q.color)
.attr("fill-opacity", 0.2)
.attr("rx", 4)
.attr("ry", 4);
});
}
Insert cell
// Create a quadrant label
createQuadrantLabels = function(gridGroup, width, height, margin, quadrants) {
quadrants.forEach((q, i) => {
let labelX, labelY, textAnchor;
// Place the label in a corner away from the center
if (i === 0) { // bottom right
labelX = width - margin.right - 20;
labelY = height - margin.bottom - 20;
textAnchor = "end";
} else if (i === 1) { // up right
labelX = width - margin.right - 20;
labelY = margin.top + 30;
textAnchor = "end";
} else if (i === 2) { // bottom left
labelX = margin.left + 20;
labelY = height - margin.bottom - 20;
textAnchor = "start";
} else { // up left
labelX = margin.left + 20;
labelY = margin.top + 30;
textAnchor = "start";
}
// Add background rectangles to improve text readability
const textBg = gridGroup.append("rect")
.attr("fill", "white")
.attr("fill-opacity", 0.9)
.attr("rx", 5)
.attr("ry", 5);
// Add Text
const text = gridGroup.append("text")
.attr("x", labelX)
.attr("y", labelY)
.attr("text-anchor", textAnchor)
.attr("font-size", "14px")
.attr("font-weight", "bold")
.attr("fill", "#666")
.text(q.text);
// Gets a bounding box for text to resize the background rectangle
const bbox = text.node().getBBox();
// Adjust the background rectangle position and size
textBg
.attr("x", bbox.x - 5)
.attr("y", bbox.y - 2)
.attr("width", bbox.width + 10)
.attr("height", bbox.height + 4);
});
}
Insert cell
// Modify the rendering task function
renderTasks = function(svg, tasks, width, height, margin, dragBehavior) {
// Be sure to use DOM elements instead of D3 selectors
const svgElement = svg.node ? svg.node() : svg;
// Use D3 data binding mode
const tasksGroup = d3.select(svgElement).select(".tasks-group");
// data binding
const taskElements = tasksGroup
.selectAll("g.task")
.data(tasks, d => d.id)
.join(
// New entry elements
enter => createTaskElements(enter, dragBehavior),
// Updated element
update => updateTaskElements(update)
);
// update position
taskElements.attr("transform", d => calculateTaskPosition(d, margin, width, height));
}
Insert cell
// Create a new task element
createTaskElements = function(enter, dragBehavior) {
const g = enter.append("g")
.attr("class", "task")
.attr("data-id", d => d.id)
.style("cursor", "move")
.call(dragBehavior); // Apply drag and drop behavior
// Add dots
g.append("circle")
.attr("r", 8)
.attr("fill", d => getTaskColor(d.x, d.y))
.attr("stroke", "#333")
.attr("stroke-width", 1);
// Add Text
g.append("text")
.attr("x", 12)
.attr("y", 4)
.attr("font-size", "12px")
.text(d => d.text);
// Added hover effect
g.on("mouseover", function() {
d3.select(this).select("circle")
.transition()
.duration(150)
.attr("r", 10)
.transition()
.duration(150)
.attr("r", 8);
});
return g;
}
Insert cell
// Update existing task elements
updateTaskElements = function(update) {
update.select("circle")
.attr("fill", d => getTaskColor(d.x, d.y));
update.select("text")
.text(d => d.text);
return update;
}
Insert cell
// Compute task position
calculateTaskPosition = function(d, margin, width, height) {
const cx = margin.left + d.x * (width - margin.left - margin.right);
const cy = margin.top + d.y * (height - margin.top - margin.bottom);
return `translate(${cx}, ${cy})`;
}
Insert cell
// Color and style constants
COLORS = ({
important_urgent: "#ff6666", // Important & Urgent
important_not_urgent: "#66cc66", // Important, not Urgent
not_important_urgent: "#cccc66", // Unimportant, Urgent
not_important_not_urgent: "#6666cc" // Unimportant, not Urgent
})
Insert cell
// Helper function: Gets the task color based on location
getTaskColor = function(x, y) {
if (x > 0.5 && y > 0.5) return COLORS.important_urgent;
if (x > 0.5 && y <= 0.5) return COLORS.important_not_urgent;
if (x <= 0.5 && y > 0.5) return COLORS.not_important_urgent;
return COLORS.not_important_not_urgent;
}
Insert cell
// Default task data
defaultTasks = [
{ id: 1, text: "Important and urgent tasks", x: 0.8, y: 0.8 },
{ id: 2, text: "Important but non urgent tasks", x: 0.8, y: 0.2 },
{ id: 3, text: "Urgent but unimportant tasks", x: 0.2, y: 0.8 },
{ id: 4, text: "Tasks that are neither important nor urgent", x: 0.2, y: 0.2 }
]
Insert cell
// Quadrant configuration
quadrantsConfig = [
{ x: 3/4, y: 3/4, text: "Do it now", color: "#ffcccc" },
{ x: 3/4, y: 1/4, text: "Plan to do", color: "#ccffcc" },
{ x: 1/4, y: 3/4, text: "Appoint", color: "#ffffcc" },
{ x: 1/4, y: 1/4, text: "give up", color: "#ccccff" }
]
Insert cell
d3 = require("d3@7")
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