Published
Edited
Nov 25, 2020
Importers
7 stars
Also listed in…
Math
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
function make_plot(input) {
let F, xp_parsed, xp, yp_parsed, yp;
d3.select(parse_warning).html('');
try {
try {
xp_parsed = math.compile(input.xp);
xp = (x, y) => xp_parsed.evaluate({ x: x, y: y });
yp_parsed = math.compile(input.yp);
yp = (x, y) => yp_parsed.evaluate({ x: x, y: y });
F = ([x, y]) => [xp(x, y), yp(x, y)];
} catch (e1) {
d3.select(parse_warning).html(
'<span style="color:red">Parse error</span>'
);
F = () => [0, 0];
}
F([1, 2]);
} catch (e2) {
F = () => [0, 0];

let message;
if (e2.toString().indexOf('not a function') != -1) {
message =
'Parse error, consider using explicit multiplication - for example, write y*(t+1), instead of y(t+1).';
} else {
message = e2;
}
d3.select(parse_warning).html(
`<span style="color:green">${message}</span>`
);
F = () => [0, 0];
}

let xmin = input.xmin;
let xmax = input.xmax;
let ymin = input.ymin;
let ymax = input.ymax;
let aspect = (ymax - ymin) / (xmax - xmin);
let w = 0.9 * width;
let h = aspect * w;
let pad = 35;

let xScale = d3
.scaleLinear()
.domain([xmin, xmax])
.range([pad, w - pad]);
let yScale = d3
.scaleLinear()
.domain([ymin, ymax])
.range([h - pad, pad]);
let pts_to_path = d3
.line()
.x(function(d) {
return xScale(d[0]);
})
.y(function(d) {
return yScale(d[1]);
});

let dx = (xmax - xmin) / 10;
let dy = dx;
let grid = d3
.range(ymin + dy, ymax, dy)
.map(y => d3.range(xmin + dx, xmax, dx).map(x => [x, y]));
grid = grid.reduce(function(accumulated, currentValue) {
return accumulated.concat(currentValue);
}, []);

let svg = d3
.create('svg')
.attr('width', w)
.attr('height', h)
.style('background-color', 'white');

let arrow_group = svg.append('g');
arrow_group // from http://thenewcode.com/1068/Making-Arrows-in-SVG
.append('svg:defs')
.append('marker')
.attr('id', 'arrowhead')
.attr("markerWidth", 10)
.attr("markerHeight", 7)
.attr("refX", 0)
.attr("refY", 3.5)
.attr("orient", "auto")
.append('polygon')
.attr('points', '0 0, 10 3.5, 0 7');

arrow_group
.selectAll('path')
.data(grid)
.join('path')
.attr('d', d => pts_to_path([d, add(d, mul(0.1, F(d)))]))
.style('stroke', 'black')
.style('stroke-width', 0.8)
.style('fill', 'none')
.attr("marker-end", function(d) {
let Fd = F(d);
let mag2 = Fd[0] ** 2 + Fd[1] ** 2;
if (mag2 > 0.01) {
return "url(#arrowhead)";
} else {
return null;
}
});

function draw_solution([x0, y0]) {
let pts = rk4(F, [x0, y0], 0, -10, 10, 500);
svg.selectAll('.solution').remove();
let solution_group = svg.append('g').attr('class', 'solution');
solution_group
.append('path')
.attr('class', 'solution')
.attr('d', pts_to_path(pts))
.style('stroke', '#1f77b4')
.style('stroke-width', 4)
.style('fill', 'none');
solution_group
.append('circle')
.attr('cx', xScale(x0))
.attr('cy', yScale(y0))
.attr('r', 5)
.attr('fill', '#2ca02c')
.attr('stroke', 'black');
}
// draw_solution();

let tracking = true;
svg
.on('mousemove', function(evt) {
if (tracking) {
let [i, j] = d3.pointer(evt);
let [x0, y0] = [xScale.invert(i), yScale.invert(j)];
draw_solution([x0, y0]);
}
})
.on('mouseleave', function() {
if (tracking) {
svg.selectAll('.solution').remove();
}
})
.on('click', function(evt) {
if (tracking) {
tracking = false;
} else {
tracking = true;
let [i, j] = d3.pointer(evt);
let [x0, y0] = [xScale.invert(i), yScale.invert(j)];
draw_solution([x0, y0]);
}
});

svg
.append('g')
.attr('transform', `translate(0, ${h - pad})`)
.call(d3.axisBottom(xScale));
svg
.append('g')
.attr('transform', `translate(${pad})`)
.call(d3.axisLeft(yScale));

return svg.node();
}
Insert cell
examples = {
let examples = new Map();
examples.set('identity', {
xp: 'x',
yp: 'y',
xmin: -2,
xmax: 2,
ymin: -2,
ymax: 2
});
examples.set('harmonic', {
xp: 'y',
yp: '-x',
xmin: -2,
xmax: 2,
ymin: -2,
ymax: 2
});
examples.set('spiral_in', {
xp: 'y-x/2',
yp: '-x-y/2',
xmin: -2,
xmax: 2,
ymin: -2,
ymax: 2
});
examples.set('damped_pendulum', {
xp: 'y',
yp: '-sin(x)-y',
xmin: -2,
xmax: 2,
ymin: -2,
ymax: 2
});
examples.set('lotka-volterra', {
xp: 'x-x*y',
yp: '-y+x*y',
xmin: -0.2,
xmax: 3,
ymin: -0.2,
ymax: 3
});
examples.set('circle_limit', {
xp: 'y+x*(1-x^2-y^2)',
yp: '-x+y*(1-x^2-y^2)',
xmin: -1.5,
xmax: 1.5,
ymin: -1.5,
ymax: 1.5
});
return examples;
}
Insert cell
Insert cell
import { rk4, add, mul } from '@mcmcclur/runge-kutta-for-systems-of-odes'
Insert cell
math = require('mathjs@7.6.0')
Insert cell
d3 = require('d3-selection@2', 'd3-scale@3', 'd3-shape@2', 'd3-axis@2', 'd3-array@2')
Insert cell
_ = require('lodash')
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