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
.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');
}
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();
}