Public
Edited
Feb 9, 2023
1 fork
3 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
function vecField(x, y){
let vec={}
if (field=="Solid body rotation"){
vec.x = -y;
vec.y = x;
}
if (field=="Rankine vortex"){
let r = Math.sqrt(x*x + y*y)
let t = Math.atan2(y,x)
if ( r < 0.4){
var Vt = r
} else {
var Vt = 0.4*0.4/r
}
vec.x = -Vt * Math.sin(t);
vec.y = Vt * Math.cos(t);
}
if (field=="Irrotational"){
vec.x = x;
vec.y = -y;
}
return vec
}
Insert cell
Insert cell
Insert cell
mutable yBox=0 // top left corner of the box (pixels)
Insert cell
mutable xBox=0 // top left corner of the box (pixels)
Insert cell
xStart=x.invert(xBox)
Insert cell
yStart=y.invert(yBox)
Insert cell
boxSize=0.5
Insert cell
boxSizePix=x(boxSize)-x(0)
Insert cell
// x and y coordinates of each corner of the box
boxLine=[{x:xStart+boxSize,y:yStart-boxSize}, {x:xStart+boxSize,y:yStart}, {x:xStart,y:yStart}, {x:xStart,y:yStart-boxSize},{x:xStart+boxSize, y:yStart-boxSize}]
Insert cell
// circulation along each edge
circ = {
let nElem=20;
let circ=[];
for (let nSide = 0; nSide < 4; ++nSide) {
let x0 = boxLine[nSide].x;
let x1 = boxLine[nSide+1].x;
let y0 = boxLine[nSide].y;
let y1 = boxLine[nSide+1].y;
let dx=(x1-x0)/nElem
let dy=(y1-y0)/nElem
let circNow=0;
for (let n = 0; n < nElem; ++n) {
let xNow = x0 + n*dx + dx/2;
let yNow = y0 + n*dy + dy/2;
let vecNow = vecField(xNow,yNow);
circNow = circNow + dx * vecNow.x + dy * vecNow.y;
}
circ.push(circNow/(boxSize*boxSize));
}
return circ
}

Insert cell
circTot = circ.reduce( (a, b) => a + b, 0 )
Insert cell
contWidth = width*0.5
Insert cell
contHeight=contWidth
Insert cell
xDomain=[-2 , 2]
Insert cell
yDomain = [-2,2]
Insert cell
vectors = {
const dx = (xDomain[1] - xDomain[0])/width;
const dy = (yDomain[1] - yDomain[0])/height;
const n = Math.ceil((grid.x1 - grid.x0) / vecQ);
const m = Math.ceil((grid.y1 - grid.y0) / vecQ);
const vector = new Array(n*m);
for (let j = 0; j < m; ++j) {
for (let i = 0; i < n; ++i) {
const xnow = x.invert(i*vecQ + grid.x0);
const ynow = y.invert(j*vecQ + grid.y0);
const delx = vecField(xnow,ynow).x;
const dely = vecField(xnow,ynow).y;
vector[j*n+i] = {x:xnow, y:ynow, dx:delx, dy:dely};
}
}
return vector;
}

Insert cell
color = d3.scaleLinear()
.domain(d3.extent(thresholds))
.interpolate(d => d3.interpolateViridis)
Insert cell
thresholds=d3.range(d3.min(grid),d3.max(grid),(d3.max(grid)-d3.min(grid))/21)
Insert cell
grid = {
const q = 4; // The level of detail, e.g., sample every 4 pixels in x and y.
const x0 = -q / 2, x1 = contWidth + q;
const y0 = -q / 2, y1 = contHeight + q;
const n = Math.ceil((x1 - x0) / q);
const m = Math.ceil((y1 - y0) / q);
const grid = new Array(n * m)
for (let j = 0; j < m; ++j) {
for (let i = 0; i < n; ++i) {
let xNow = x.invert(i * q + x0);
let yNow = y.invert(j * q + y0);
let vecNow=vecField(xNow,yNow);
let Vx = vecNow.x;
let Vy = vecNow.y;
grid[j * n + i] = Math.sqrt(Vx*Vx + Vy*Vy)
}
}
grid.x = -q;
grid.y = -q;
grid.k = q;
grid.n = n;
grid.m = m;
grid.x0 = x0;
grid.x1 = x1;
grid.y0 = y0;
grid.y1 = y1;
return grid;
}
Insert cell
// Converts from grid coordinates (indexes) to screen coordinates (pixels).
transform = ({type, value, coordinates}) => {
return {type, value, coordinates: coordinates.map(rings => {
return rings.map(points => {
return points.map(([x, y]) => ([
grid.x + grid.k * x,
grid.y + grid.k * y
]));
});
})};
}
Insert cell
contours = d3.contours()
.size([grid.n, grid.m])
.thresholds(thresholds)
(grid)
.map(transform)
Insert cell
function dragged(event, d) {
mutable xBox += event.dx;
mutable yBox += event.dy;
}

Insert cell
x = d3.scaleLinear().domain(xDomain).range([0, contWidth ])
Insert cell
y = d3.scaleLinear().domain(yDomain).range([contHeight, 0])
Insert cell
barData=[{label:"E",value:circ[0],color:"steelBlue"},{label:"N",value:circ[1],color:"steelBlue"},{label:"W",value:circ[2],color:"steelBlue"},{label:"S",value:circ[3],color:"steelBlue"},{label:"sum", value:circTot,color:"green"}]
Insert cell
xBar = d3.scaleBand()
.domain(barData.map(d => d.label))
.range([contWidth+50, width])
.padding(0.1)
Insert cell
yBar = d3.scaleLinear()
.domain([-2., 2.])
.range([height-50,50])
Insert cell
line = d3.line()
.x( d => x(d.x) )
.y( d => y(d.y) )
Insert cell
xAxis = g => g
.attr("transform", `translate(0,${height})`)
.call(d3.axisTop(x).ticks(width / height * 10))
.call(g => g.select(".domain").remove())
.call(g => g.selectAll(".tick").filter(d => x.domain().includes(d)).remove())
Insert cell
yAxis = g => g
.attr("transform", "translate(-1,0)")
.call(d3.axisRight(y))
.call(g => g.select(".domain").remove())
.call(g => g.selectAll(".tick").filter(d => y.domain().includes(d)).remove())
Insert cell
yAxisBar = g => g
.attr("transform", `translate(${contWidth+50},0)`)
.call(d3.axisLeft(yBar))
Insert cell
xAxisBar = g => g
.attr("transform", `translate(0,${yBar(0)})`)
.call(d3.axisBottom(xBar)
.tickSizeOuter(0))
Insert cell
height = contHeight
Insert cell
import {slider, select} from "@jashkenas/inputs"
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