Public
Edited
May 9, 2024
Insert cell
Insert cell
Insert cell
screenshot20240425At45825Pm = FileAttachment("Screenshot 2024-04-25 at 4.58.25 PM.png").image()
Insert cell
screenshot20240425At45847Pm = FileAttachment("Screenshot 2024-04-25 at 4.58.47 PM.png").image()
Insert cell
Insert cell
Insert cell
drink_nounit = d3.csvParse(
await FileAttachment("no_unit.csv").text(),
d3.autoType
)
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
d3 = require("d3@5")
Insert cell
d4 = require('d3@6')
Insert cell
d5 = require('d3@7')
Insert cell
_ = require('lodash')
Insert cell
vegalite = require("@observablehq/vega-lite")
Insert cell
md`### Models`
Insert cell
groupedByCategory = d4.group(drink, d => d.Beverage_category)
Insert cell
// Extract unique categories
uniqueCategories = Array.from(new Set(drink.map(d => d.Beverage_category)))

Insert cell
// Now you can use selectedCategory in other cells to reactively update visualizations
selectedCategoryValue = selectedCategory.value
Insert cell
viewof selectedCategory = html`
<select>
${uniqueCategories.map(category => html`<option value="${category}">${category}</option>`)}
</select>
`
Insert cell
{
const category = selectedCategory;
const filteredData = drink.filter(d => d.Beverage_category === category);
filteredData.sort((a, b) => d4.descending(a.Calories, b.Calories));

const margin = { top: 20, right: 30, bottom: 110, left: 200 };
const height = 500 - margin.top - margin.bottom;
const barWidth = 20; // Set a fixed bar width
const totalWidth = Math.max(800, filteredData.length * (barWidth + 10)) + margin.left + margin.right;

const svg = d4.create("svg")
.attr("viewBox", `0 0 ${totalWidth} ${height + margin.top + margin.bottom}`);

const x = d4.scaleLinear()
.domain([0, filteredData.length])
.range([margin.left, filteredData.length * (barWidth + 10) + margin.left]);

const y = d4.scaleLinear()
.domain([0, d4.max(filteredData, d => d.Calories)])
.range([height - margin.bottom, margin.top]);

svg.append("g")
.attr("fill", 'brown')
.selectAll("rect")
.data(filteredData)
.join("rect")
.attr("x", (d, i) => x(i))
.attr("y", d => y(d.Calories))
.attr("height", d => y(0) - y(d.Calories))
.attr("width", barWidth);

svg.append("g")
.attr("transform", `translate(0,${height - margin.bottom})`)
.call(d3.axisBottom(x)
.tickFormat((d, i) => filteredData[i] ? filteredData[i].Beverage + " (" + filteredData[i].Beverage_prep + ")": "")
.ticks(filteredData.length))
.selectAll("text")
.attr("transform", "translate(-10,0)rotate(-45)")
.style("text-anchor", "end");

svg.append("g")
.attr("transform", `translate(${margin.left},0)`)
.call(d3.axisLeft(y));

return svg.node();
}
Insert cell
{
const svg = d3.select("body").append("svg")
.attr("width", 200)
.attr("height", 300)
.style("border", "1px solid black")
}// Adding a border to see the SVG boundaries easily
Insert cell
// viewof selectedCupSize = html`
// <div>
// <input type="radio" id="short" name="cupSize" value="1" checked> <label for="short">Short</label><br>
// <input type="radio" id="tall" name="cupSize" value="1.5"> <label for="tall">Tall</label><br>
// <input type="radio" id="grande" name="cupSize" value="2"> <label for="grande">Grande</label><br>
// <input type="radio" id="venti" name="cupSize" value="2.5"> <label for="venti">Venti</label>
// </div>
// `;

Insert cell
viewof size = Inputs.radio(new Map([["Short", 1], ["Tall", 1.5], ["Grande", 2], ["Venti", 2.5]]), {value: 1, label: "Size"})
Insert cell
canvas4 = {
const scaleFactor = size; // Use the reactive value directly from the radio selection
const width = 300;
const height = scaleFactor * 200;
const context = DOM.context2d(width, height); // Define canvas dimensions
const canvas = context.canvas;
let currentDropdownLayer = -1; // No layer has a dropdown initially

// Clear the canvas
context.clearRect(0, 0, width, height);

// Define scaled dimensions based on the selected size
const cupWidthTop = 60 * scaleFactor;
const cupWidthBottom = 40 * scaleFactor;
const cupHeight = 120 * scaleFactor;
const cupX = width / 2; // Center the cup horizontally
const cupY = 60; // Set top margin

const layers = [];
const colors = ["grey", "#f5f5dc", "brown", "#fffdd0"];
const layerHeight = cupHeight / 4;


function cupWidthAtHeight(yPosition)
{
return cupWidthTop + (cupWidthBottom - cupWidthTop) * ((yPosition - cupY) / cupHeight)
; }

function drawLayers() {
for (let i = 0; i < 4; i++) {
const topY = cupY + i * layerHeight;
const bottomY = topY + layerHeight;
const topWidth = cupWidthAtHeight(topY);
const bottomWidth = cupWidthAtHeight(bottomY);

layers[i] = { topY, bottomY, topWidth, bottomWidth };

context.fillStyle = i === currentDropdownLayer ? 'yellow' : colors[i];
context.beginPath();
context.moveTo(cupX - topWidth / 2, topY);
context.lineTo(cupX + topWidth / 2, topY);
context.lineTo(cupX + bottomWidth / 2, bottomY);
context.lineTo(cupX - bottomWidth / 2, bottomY);
context.closePath();
context.fill();
}
}

drawLayers();

// Draw the outline of the cup
context.beginPath();
context.moveTo(cupX - cupWidthTop / 2, cupY); // Start from the top left corner
context.lineTo(cupX + cupWidthTop / 2, cupY); // Line to top right corner
context.lineTo(cupX + cupWidthBottom / 2, cupY + cupHeight); // Line to bottom right corner
context.lineTo(cupX - cupWidthBottom / 2, cupY + cupHeight); // Line to bottom left corner
context.closePath(); // Close the path back to the start point

// Set styles for the outline
context.strokeStyle = "rgb(0, 128, 0)"; // Green color for the stroke
context.lineWidth = 2; // Set the line width for the outline
context.stroke(); // Apply the stroke to the path
// Prepare the dropdown
const dropdown = document.createElement('select');
dropdown.innerHTML = `<option>Option 1</option><option>Option 2</option><option>Option 3</option><option>Option 4</option>`;
dropdown.style.position = 'absolute';
dropdown.style.display = 'none'; // Initially hidden
document.body.appendChild(dropdown);

canvas.addEventListener('click', event => {
const mouseX = event.offsetX;
const mouseY = event.offsetY;
const clickedIndex = layers.findIndex(layer =>
mouseX >= (cupX - layer.topWidth / 2) && mouseX <= (cupX + layer.topWidth / 2) &&
mouseY >= layer.topY && mouseY <= layer.bottomY
);

if (clickedIndex === currentDropdownLayer) {
// Toggle dropdown visibility
dropdown.style.display = 'none';
currentDropdownLayer = -1;
} else if (clickedIndex !== -1) {
currentDropdownLayer = clickedIndex;
dropdown.style.display = 'block';
dropdown.style.left = `${event.pageX}px`;
dropdown.style.top = `${event.pageY + 10}px`; // Position below the click
} else {
dropdown.style.display = 'none';
currentDropdownLayer = -1;
}

// Redraw layers with potential new highlighting
drawLayers();
});

return canvas;
}

Insert cell
canvas3 = {
const scaleFactor = size; // Reactive value from radio selection
const width = 400;
const height = scaleFactor * 200;
const context = DOM.context2d(width, height);
const canvas = context.canvas;
const layerOptions = [
["2% reduced fat", "1% reduced fat", "Soy milk", "Almond milk", "Oat milk"], // Options for Layer 0
["Zero Calorie sugar", "Sugar(Normal)", "Honey", "Maple Syrup"], // Options for Layer 1
["Coffee Type 1", "Coffee Type 2", "Coffee Type 3"], // Options for Layer 2
["Cream", "No Cream"] // Options for Layer 3
];
const layerSizeMultipliers = [1,1,1,1];

context.clearRect(0, 0, width, height);

const cupWidthTop = 60 * scaleFactor;
const cupWidthBottom = 40 * scaleFactor;
const cupHeight = 120 * scaleFactor;
const cupX = width / 2;
const cupY = 60;

const colors = ["grey", "#f5f5dc", "brown", "#fffdd0"];
const layerHeight = cupHeight / 4;
const layers = [];

for (let i = 0; i < 4; i++) {
const topY = cupY + i * layerHeight;
const bottomY = topY + layerHeight;
const topWidth = cupWidthTop - (i * (cupWidthTop - cupWidthBottom) / 4);
const bottomWidth = topWidth - ((cupWidthTop - cupWidthBottom) / 4);

layers.push({
index: i,
top: topY,
bottom: bottomY,
left: cupX - bottomWidth / 2,
right: cupX + bottomWidth / 2,
color: colors[i]
});

context.fillStyle = colors[i];
context.beginPath();
context.moveTo(cupX - topWidth / 2, topY);
context.lineTo(cupX + topWidth / 2, topY);
context.lineTo(cupX + bottomWidth / 2, bottomY);
context.lineTo(cupX - bottomWidth / 2, bottomY);
context.closePath();
context.fill();
}

context.strokeStyle = "rgb(0, 128, 0)";
context.lineWidth = 2;
context.beginPath();
context.moveTo(cupX - cupWidthTop / 2, cupY);
context.lineTo(cupX + cupWidthTop / 2, cupY);
context.lineTo(cupX + cupWidthBottom / 2, cupY + cupHeight);
context.lineTo(cupX - cupWidthBottom / 2, cupY + cupHeight);
context.closePath();
context.stroke();

function redrawCanvas() {
context.clearRect(0, 0, width, height);
for (let i = 0; i < layers.length; i++) {
const layer = layers[i];
const scaleFactor = layerSizeMultipliers[i]; // Use the multiplier for the layer size
const heightAdjustment = layerHeight * scaleFactor;
const topY = cupY + (i * heightAdjustment);
const bottomY = topY + heightAdjustment;
const topWidth = cupWidthTop - (i * (cupWidthTop - cupWidthBottom) / 4) * scaleFactor;
const bottomWidth = topWidth - ((cupWidthTop - cupWidthBottom) / 4) * scaleFactor;

context.fillStyle = layer.color;
context.beginPath();
context.moveTo(cupX - topWidth / 2, topY);
context.lineTo(cupX + topWidth / 2, topY);
context.lineTo(cupX + bottomWidth / 2, bottomY);
context.lineTo(cupX - bottomWidth / 2, bottomY);
context.closePath();
context.fill();
}

context.strokeStyle = "rgb(0, 128, 0)";
context.lineWidth = 2;
context.beginPath();
context.moveTo(cupX - cupWidthTop / 2, cupY);
context.lineTo(cupX + cupWidthTop / 2, cupY);
context.lineTo(cupX + cupWidthBottom / 2, cupY + cupHeight);
context.lineTo(cupX - cupWidthBottom / 2, cupY + cupHeight);
context.closePath();
context.stroke();
}


// Setup dropdown for layer options
const dropdown = document.createElement('select');
dropdown.style.position = 'absolute';
dropdown.style.display = 'none';
document.body.appendChild(dropdown);

canvas.addEventListener('click', event => {
const { offsetX, offsetY } = event;

const clickedLayer = layers.find(layer =>
offsetX >= layer.left && offsetX <= layer.right &&
offsetY >= layer.top && offsetY <= layer.bottom
);

if (clickedLayer) {
dropdown.innerHTML = layerOptions[clickedLayer.index].map((opt, index)=> `<option value="${index +1}">${opt}</option>`).join('');
dropdown.style.left = `${event.pageX}px`;
dropdown.style.top = `${event.pageY}px`;
dropdown.style.display = 'block';
dropdown.onchange = () => {
layerSizeMultipliers[clickedLayer.index] = parseFloat(dropdown.value);
redrawCanvas();
};
} else {
dropdown.style.display = 'none';
}
});

return canvas;
}

Insert cell
canvas5 = {
// Create SVG canvas
const svg = d3.select("body").append("svg")
.attr("width", 800)
.attr("height", 600);

// Data for cup fragments including position, size, and color
const cupFragments = [
{ id: 'body', x: 350, y: 250, width: 100, height: 200, fill: '#006241', type: 'rect' },
{ id: 'lid', x: 350, y: 200, width: 120, height: 20, fill: '#f4f4f4', type: 'rect' },
...Array.from({length: 20}, (_, i) => ({
id: `drop${i}`,
x: 400 + Math.random() * 100 - 50, // Randomize initial positions a bit
y: 250 + Math.random() * 100 - 50,
r: 5 + Math.random() * 5,
fill: '#603813',
type: 'circle'
}))
];

// Append each fragment to the SVG
cupFragments.forEach(fragment => {
svg.append(fragment.type)
.attr('id', fragment.id)
.attr(fragment.type === 'circle' ? {
'cx': fragment.x,
'cy': fragment.y,
'r': fragment.r
} : {
'x': fragment.x,
'y': fragment.y,
'width': fragment.width,
'height': fragment.height
})
.attr('fill', fragment.fill);
});

// Explosion function
function explode() {
svg.selectAll("rect, circle")
.transition()
.duration(2000)
.ease(d3.easeExpOut)
.attr("transform", d => {
const angle = Math.random() * 2 * Math.PI; // Random angle
const distance = 150 + Math.random() * 150; // Random distance
const dx = Math.cos(angle) * distance;
const dy = Math.sin(angle) * distance;
return `translate(${dx},${dy})`;
})
.style("opacity", 0)
.on("end", function() {
d3.select(this).remove(); // Optionally remove elements after animation
});
}

// Trigger the explosion after 1 second
setTimeout(explode, 1000);

return svg.node(); // Ensure we return the node for rendering
}

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