From 63691f53b1dd021681bb392c362a176ecaa6fbdc Mon Sep 17 00:00:00 2001 From: Jonathan Berrisch Date: Sun, 18 May 2025 01:13:04 +0200 Subject: [PATCH] Make it d3 --- app.qmd | 166 +++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 134 insertions(+), 32 deletions(-) diff --git a/app.qmd b/app.qmd index e4a8bee..9fe3004 100644 --- a/app.qmd +++ b/app.qmd @@ -10,13 +10,15 @@ execute: highlight-style: github --- -#### B-spline Basis Functions +## B-spline Basis Functions ```{ojs} -bsplineData = FileAttachment("basis_functions.csv").csv({ typed: true }) +d3 = require("d3@7") ``` ```{ojs} +bsplineData = FileAttachment("basis_functions.csv").csv({ typed: true }) + knotValues = Array.from(new Set(bsplineData.map(d => d.knots))).sort((a, b) => a - b) minKnots = Math.min(...knotValues) maxKnots = Math.max(...knotValues) @@ -24,9 +26,7 @@ maxKnots = Math.max(...knotValues) muValues = Array.from(new Set(bsplineData.map(d => d.mu))).sort((a, b) => a - b) minMu = Math.min(...muValues) maxMu = Math.max(...muValues) -``` -```{ojs} // Create a more compact layout for controls viewof controls = Inputs.form({ knots: Inputs.range([minKnots, maxKnots], {value: minKnots, step: 1, label: "Knots:", width: 200}), @@ -39,39 +39,141 @@ viewof controls = Inputs.form({ selectedKnots = controls.knots selectedMu = controls.mu -``` -```{ojs} filteredBspline = bsplineData.filter(function(row) { return selectedKnots == row.knots && Math.abs(selectedMu - row.mu) < 0.001; }) ``` ```{ojs} -Plot.plot({ - grid: true, - y: {domain: [0, 0.7]}, - x: {label: "x", domain: [0, 1]}, - marks: [ - Plot.line(filteredBspline, { - x: "x", - y: "y", - stroke: "b", - strokeWidth: 5, - }), - Plot.ruleY([0]) - ], - color: { - legend: false, - label: "Basis Function" - }, - marginRight: 80, - width: 800, - height: 400, - // title: `B-spline Basis Functions (${selectedKnots} knots, μ = ${selectedMu})` -}) +// D3-based visualization for B-spline basis functions +chart = { + // Create chart dimensions + const width = 800; + const height = 400; + const margin = {top: 40, right: 20, bottom: 40, left: 40}; + const innerWidth = width - margin.left - margin.right; + const innerHeight = height - margin.top - margin.bottom; + + // Create scales + const x = d3.scaleLinear() + .domain([0, 1]) + .range([0, innerWidth]); + + const y = d3.scaleLinear() + .domain([0, 0.7]) + .range([innerHeight, 0]); + + // Create a color scale for the basis functions + const color = d3.scaleOrdinal(d3.schemeCategory10); + + // Create SVG + const svg = d3.create("svg") + .attr("width", width) + .attr("height", height) + .attr("viewBox", [0, 0, width, height]) + .attr("style", "max-width: 100%; height: auto;"); + + // Add chart title + svg.append("text") + .attr("class", "chart-title") + .attr("x", width / 2) + .attr("y", 20) + .attr("text-anchor", "middle") + .attr("font-size", "16px") + .attr("font-weight", "bold"); + + // Create the chart group + const g = svg.append("g") + .attr("transform", `translate(${margin.left},${margin.top})`); + + // Add axes + const xAxis = g.append("g") + .attr("transform", `translate(0,${innerHeight})`) + .call(d3.axisBottom(x).ticks(10)); + + const yAxis = g.append("g") + .call(d3.axisLeft(y).ticks(5)); + + // Add axis labels + g.append("text") + .attr("x", innerWidth / 2) + .attr("y", innerHeight + 35) + .attr("text-anchor", "middle") + .text("x"); + + g.append("text") + .attr("transform", "rotate(-90)") + .attr("x", -innerHeight / 2) + .attr("y", -30) + .attr("text-anchor", "middle") + .text("y"); + + // Add a horizontal line at y = 0 + g.append("line") + .attr("x1", 0) + .attr("x2", innerWidth) + .attr("y1", y(0)) + .attr("y2", y(0)) + .attr("stroke", "#000") + .attr("stroke-opacity", 0.2); + + // Add gridlines + g.append("g") + .attr("class", "grid-lines") + .selectAll("line") + .data(y.ticks(5)) + .join("line") + .attr("x1", 0) + .attr("x2", innerWidth) + .attr("y1", d => y(d)) + .attr("y2", d => y(d)) + .attr("stroke", "#ccc") + .attr("stroke-opacity", 0.5); + + // Create a line generator + const line = d3.line() + .x(d => x(d.x)) + .y(d => y(d.y)); + + // Group to contain the basis function lines + const linesGroup = g.append("g") + .attr("class", "basis-functions"); + + // Function to update the chart with new data + function updateChart(data) { + // Group data by basis function + const groupedData = d3.group(data, d => d.b); + + // Update the chart title + svg.select(".chart-title") + .text(`B-spline Basis Functions (${selectedKnots} knots, μ = ${selectedMu})`); + + // Update or create paths + const paths = linesGroup.selectAll("path") + .data(Array.from(groupedData.values())); + + // Remove paths that are no longer needed + paths.exit().remove(); + + // Add new paths + paths.enter() + .append("path") + .attr("fill", "none") + .attr("stroke-width", 3) + .attr("stroke", (_, i) => color(i)) + .merge(paths) // Merge with existing paths for transition + .transition() + .duration(750) + .attr("d", line); + } + + // Store the update function + svg.node().update = updateChart; + + // Initial render + updateChart(filteredBspline); + + return svg.node(); +} ``` - -::: {.callout-note} -TODO -:::