Published
Edited
Nov 17, 2021
2 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
seir_graph = {
let graph = d3.create('div').style('width', `${width}px`);

// Here's the source code describing the graph to graphviz.
// Note that nodes and edge labels contain LaTeX code that
// will be passed to MathJax. I guess it gets piped through
// a couple of things; hence, the double escape leading to
// quadruple backslashes \\\\.
let source_code = `digraph {
S [pos="0,0!"]
E [pos="2.7,0!"]
I_1 [pos="4,0!"]
I_2 [pos="6,0.5!"]
I_3 [pos="8,0!"]
D [pos="10,0!"]
R [pos="6,-1.5!"]
S -> E [label="\\\\beta_1 I_1 S + \\\\beta_2 I_2 S + \\\\beta_3 I_3"]
E -> I_1 [label="\\\\alpha E"]
I_1 -> I_2 [label="p_1 I_1"]
I_2 -> I_3 [label="p_2 I_2"]
I_3-> D [label="\\\\mu I_3"]
I_1 -> R [label="\\\\gamma_1 I_1"]
I_2 -> R [label="\\\\gamma_2 I_2"]
I_3 -> R [label="\\\\gamma_3 I_3"]
}`;
d3.graphviz(graph.node())
.width(width)
.fit(true) // Doesn't quite work; see transform in penultimate line.
.zoom(false) // Re-transform for fit breaks the zoom.
.engine('neato')
.renderDot(source_code);

// The image is completely contained in a top level group,
// which we're going to manipulate
let main_group = graph.select('g');

// Don't really want the title
main_group.select('title').remove();

// Typeset the nodes
main_group.selectAll('.node').each(function(e, i) {
let text = d3.select(this).select('text');
if (text.node() != null) {
let x = parseFloat(text.attr('x'));
let y = parseFloat(text.attr('y'));
let tex_group = main_group
.append('g')
.attr('transform', `translate(${x - 8} ${y - 10})`)
.append(() =>
MathJax.tex2svg(String.raw`${text.text()}`).querySelector("svg")
);
text.remove();
}
});

// Placement of the typeset edge labels is a bit trickier. The following
// list of shifts adjusts the placement of the labels from the location
// specified by graphviz.
let shifts = [
[60, -30],
[-9, -27],
[-37, -15],
[10, 5],
[-20, -10],
[10, 0],
[-25, -12],
[44, 5]
];
main_group.selectAll('.edge').each(function(e, i) {
let text = d3.select(this).select('text');
if (text.node() != null) {
let x = parseFloat(text.attr('x'));
let y = parseFloat(text.attr('y'));
let tex_group = main_group
.append('g')
.attr(
'transform',
`translate(${x + shifts[i][0]} ${y + shifts[i][1]}) scale(0.75)`
)
.append(() =>
MathJax.tex2svg(String.raw`${text.text()}`).querySelector("svg")
);
text.remove();
}
});

// There's far more space to the left of the graph than I'd expect;
// I guess the reason is that the first, pre-shifted edge label extends
// quite far to the left. A hacky fix is to redefine the main transform
// to fit it a bit better. Unfortunately, this breaks zoom.
main_group.attr('transform', `translate(-130, 204) scale(1.15)`);
return graph.node();
}
Insert cell
tex`
\begin{aligned}
\dot{S} &= -\beta_1 I_1 S -\beta_2 I_2 S - \beta_3 I_3 S\\
\dot{E} &=\beta_1 I_1 S +\beta_2 I_2 S + \beta_3 I_3 S - a E \\
\dot{I_1} &= a E - \gamma_1 I_1 - p_1 I_1 \\
\dot{I_2} &= p_1 I_1 -\gamma_2 I_2 - p_2 I_2 \\
\dot{I_3} & = p_2 I_2 -\gamma_3 I_3 - \mu I_3 \\
\dot{R} & = \gamma_1 I_1 + \gamma_2 I_2 + \gamma_3 I_3 \\
\dot{D} & = \mu I_3
\end{aligned}`
Insert cell
import { select, checkbox } from "@jashkenas/inputs"
Insert cell
MathJax = require('https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-svg.js').catch(
() => window['MathJax']
)
Insert cell
d3 = require("d3@7", "d3-graphviz@2")
Insert cell
// Not sure why download svg or png don't work.
// You can grab the svg as follows:
// d3.select(graph).html()
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