Add nice transitions
This commit is contained in:
95
app.qmd
95
app.qmd
@@ -26,29 +26,32 @@ maxKnots = Math.max(...knotValues)
|
|||||||
muValues = Array.from(new Set(bsplineData.map(d => d.mu))).sort((a, b) => a - b)
|
muValues = Array.from(new Set(bsplineData.map(d => d.mu))).sort((a, b) => a - b)
|
||||||
minMu = Math.min(...muValues)
|
minMu = Math.min(...muValues)
|
||||||
maxMu = Math.max(...muValues)
|
maxMu = Math.max(...muValues)
|
||||||
|
|
||||||
// Create a more compact layout for controls
|
|
||||||
viewof controls = Inputs.form({
|
|
||||||
knots: Inputs.range([minKnots, maxKnots], {value: minKnots, step: 1, label: "Knots:", width: 200}),
|
|
||||||
mu: Inputs.range([minMu, maxMu], {value: 0.5, step: 0.1, label: "μ:", width: 200})
|
|
||||||
}, {
|
|
||||||
submit: false,
|
|
||||||
layout: 'horizontal',
|
|
||||||
style: 'display: flex; gap: 20px; align-items: center; margin-bottom: 10px; font-size: 0.9em;'
|
|
||||||
})
|
|
||||||
|
|
||||||
selectedKnots = controls.knots
|
|
||||||
selectedMu = controls.mu
|
|
||||||
|
|
||||||
filteredBspline = bsplineData.filter(function(row) {
|
|
||||||
return selectedKnots == row.knots && Math.abs(selectedMu - row.mu) < 0.001;
|
|
||||||
})
|
|
||||||
```
|
```
|
||||||
|
|
||||||
```{ojs}
|
```{ojs}
|
||||||
// D3-based visualization for B-spline basis functions
|
|
||||||
chart = {
|
chart = {
|
||||||
// Create chart dimensions
|
// State variables for selected parameters
|
||||||
|
let selectedKnots = minKnots;
|
||||||
|
let selectedMu = 0.5;
|
||||||
|
const filteredData = () => bsplineData.filter(d => selectedKnots == d.knots && Math.abs(selectedMu - d.mu) < 0.001);
|
||||||
|
const container = d3.create("div").style("margin-bottom", "20px");
|
||||||
|
// Knots slider control
|
||||||
|
const knotsControl = container.append("div").style("display","flex").style("align-items","center").style("gap","10px");
|
||||||
|
knotsControl.append("label").text("Knots:");
|
||||||
|
knotsControl.append("input")
|
||||||
|
.attr("type","range").attr("min",minKnots).attr("max",maxKnots).attr("step",1)
|
||||||
|
.property("value",selectedKnots)
|
||||||
|
.on("input", function() { selectedKnots = +this.value; knotsControl.select("span").text(selectedKnots); updateChart(filteredData()); });
|
||||||
|
knotsControl.append("span").text(selectedKnots);
|
||||||
|
// μ slider control
|
||||||
|
const muControl = container.append("div").style("display","flex").style("align-items","center").style("gap","10px");
|
||||||
|
muControl.append("label").text("μ:");
|
||||||
|
muControl.append("input")
|
||||||
|
.attr("type","range").attr("min",minMu).attr("max",maxMu).attr("step",0.1)
|
||||||
|
.property("value",selectedMu)
|
||||||
|
.on("input", function() { selectedMu = +this.value; muControl.select("span").text(selectedMu); updateChart(filteredData()); });
|
||||||
|
muControl.append("span").text(selectedMu);
|
||||||
|
// Build SVG
|
||||||
const width = 800;
|
const width = 800;
|
||||||
const height = 400;
|
const height = 400;
|
||||||
const margin = {top: 40, right: 20, bottom: 40, left: 40};
|
const margin = {top: 40, right: 20, bottom: 40, left: 40};
|
||||||
@@ -90,9 +93,11 @@ chart = {
|
|||||||
// Add axes
|
// Add axes
|
||||||
const xAxis = g.append("g")
|
const xAxis = g.append("g")
|
||||||
.attr("transform", `translate(0,${innerHeight})`)
|
.attr("transform", `translate(0,${innerHeight})`)
|
||||||
|
.attr("class", "x-axis")
|
||||||
.call(d3.axisBottom(x).ticks(10));
|
.call(d3.axisBottom(x).ticks(10));
|
||||||
|
|
||||||
const yAxis = g.append("g")
|
const yAxis = g.append("g")
|
||||||
|
.attr("class", "y-axis")
|
||||||
.call(d3.axisLeft(y).ticks(5));
|
.call(d3.axisLeft(y).ticks(5));
|
||||||
|
|
||||||
// Add axis labels
|
// Add axis labels
|
||||||
@@ -134,46 +139,64 @@ chart = {
|
|||||||
// Create a line generator
|
// Create a line generator
|
||||||
const line = d3.line()
|
const line = d3.line()
|
||||||
.x(d => x(d.x))
|
.x(d => x(d.x))
|
||||||
.y(d => y(d.y));
|
.y(d => y(d.y))
|
||||||
|
.curve(d3.curveBasis);
|
||||||
|
|
||||||
// Group to contain the basis function lines
|
// Group to contain the basis function lines
|
||||||
const linesGroup = g.append("g")
|
const linesGroup = g.append("g")
|
||||||
.attr("class", "basis-functions");
|
.attr("class", "basis-functions");
|
||||||
|
|
||||||
|
// Store the current basis functions for transition
|
||||||
|
let currentBasisFunctions = new Map();
|
||||||
|
|
||||||
// Function to update the chart with new data
|
// Function to update the chart with new data
|
||||||
function updateChart(data) {
|
function updateChart(data) {
|
||||||
|
// Update axes with transitions
|
||||||
|
x.domain([0, d3.max(data, d => d.x)]);
|
||||||
|
g.select(".x-axis")
|
||||||
|
.transition().duration(750)
|
||||||
|
.call(d3.axisBottom(x).ticks(10));
|
||||||
|
y.domain([0, d3.max(data, d => d.y)]);
|
||||||
|
g.select(".y-axis")
|
||||||
|
.transition().duration(750)
|
||||||
|
.call(d3.axisLeft(y).ticks(5));
|
||||||
|
|
||||||
// Group data by basis function
|
// Group data by basis function
|
||||||
const groupedData = d3.group(data, d => d.b);
|
const dataByFunction = Array.from(d3.group(data, d => d.b));
|
||||||
|
|
||||||
// Update the chart title
|
// Update the chart title
|
||||||
svg.select(".chart-title")
|
svg.select(".chart-title")
|
||||||
.text(`B-spline Basis Functions (${selectedKnots} knots, μ = ${selectedMu})`);
|
.text(`B-spline Basis Functions (${selectedKnots} knots, μ = ${selectedMu})`);
|
||||||
|
|
||||||
// Update or create paths
|
// Create a key function to track basis functions
|
||||||
const paths = linesGroup.selectAll("path")
|
const keyFn = d => d[0];
|
||||||
.data(Array.from(groupedData.values()));
|
|
||||||
|
|
||||||
// Remove paths that are no longer needed
|
// Update basis function lines with proper enter/update/exit pattern
|
||||||
paths.exit().remove();
|
const u = linesGroup.selectAll("path")
|
||||||
|
.data(dataByFunction, keyFn);
|
||||||
// Add new paths
|
u.join(
|
||||||
paths.enter()
|
enter => enter.append("path")
|
||||||
.append("path")
|
|
||||||
.attr("fill", "none")
|
.attr("fill", "none")
|
||||||
.attr("stroke-width", 3)
|
.attr("stroke-width", 3)
|
||||||
.attr("stroke", (_, i) => color(i))
|
.attr("stroke", (_, i) => color(i))
|
||||||
.merge(paths) // Merge with existing paths for transition
|
.attr("d", d => line(d[1].map(pt => ({x: pt.x, y: 0}))))
|
||||||
.transition()
|
.style("opacity", 0),
|
||||||
.duration(750)
|
update => update,
|
||||||
.attr("d", line);
|
exit => exit.transition().duration(750).style("opacity", 0).remove()
|
||||||
|
)
|
||||||
|
.transition().duration(750)
|
||||||
|
.attr("d", d => line(d[1]))
|
||||||
|
.attr("stroke", (_, i) => color(i))
|
||||||
|
.style("opacity", 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store the update function
|
// Store the update function
|
||||||
svg.node().update = updateChart;
|
svg.node().update = updateChart;
|
||||||
|
|
||||||
// Initial render
|
// Initial render
|
||||||
updateChart(filteredBspline);
|
updateChart(filteredData());
|
||||||
|
|
||||||
return svg.node();
|
container.node().appendChild(svg.node());
|
||||||
|
return container.node();
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
Reference in New Issue
Block a user