Inserting an interactive figure
Authorea enables embedding of interactive, dynamic, data-driven figures directly into articles. These can be diagrams from external services like Carto or diagrams from a javascript library like d3.js, flotchart, bokeh, or Plotly.
To insert an interactive figure, click Insert Interactive Figure and select the HTML source code from your computer that you wish to add to your document.
Example
Save the following code in a file called Network.html
and then insert it in your document via Insert -> Interactive Figure.
<!DOCTYPE html>
<meta charset="utf-8">
<style>
svg {
background-color: #FFF;
cursor: default;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
-o-user-select: none;
user-select: none;
margin-top:-100px;
}
svg:not(.active):not(.ctrl) {
cursor: crosshair;
}
path.link {
fill: none;
stroke: #000;
stroke-width: 4px;
cursor: default;
}
svg:not(.active):not(.ctrl) path.link {
cursor: pointer;
}
path.link.selected {
stroke-dasharray: 10,2;
}
path.link.dragline {
pointer-events: none;
}
path.link.hidden {
stroke-width: 0;
}
circle.node {
stroke-width: 1.5px;
cursor: pointer;
}
circle.node.reflexive {
stroke: #000 !important;
stroke-width: 2.5px;
}
text {
font: 12px sans-serif;
pointer-events: none;
}
text.id {
text-anchor: middle;
font-weight: bold;
}
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.3/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<body style="max-height:400px !important;">
<div id="svg"><svg></svg></div>
<div id="controls">
<ul class="navigation">
</ul>
</div>
<div id="caption"></div>
<script type="text/javascript">
// set up SVG for D3
var width = 660,
height = 400,
colors = d3.scale.category10();
var svg = d3.select('body')
.append('svg')
.attr('width', width)
.attr('height', height);
// set up initial nodes and links
// - nodes are known by 'id', not by index in array.
// - reflexive edges are indicated on the node (as a bold black circle).
// - links are always source < target; edge directions are set by 'left' and 'right'.
var nodes = [
{id: 0, reflexive: false},
{id: 1, reflexive: true },
{id: 2, reflexive: false}
],
lastNodeId = 2,
links = [
{source: nodes[0], target: nodes[1], left: false, right: true },
{source: nodes[1], target: nodes[2], left: false, right: true }
];
// init D3 force layout
var force = d3.layout.force()
.nodes(nodes)
.links(links)
.size([width, height])
.linkDistance(150)
.charge(-500)
.on('tick', tick)
// define arrow markers for graph links
svg.append('svg:defs').append('svg:marker')
.attr('id', 'end-arrow')
.attr('viewBox', '0 -5 10 10')
.attr('refX', 6)
.attr('markerWidth', 3)
.attr('markerHeight', 3)
.attr('orient', 'auto')
.append('svg:path')
.attr('d', 'M0,-5L10,0L0,5')
.attr('fill', '#000');
svg.append('svg:defs').append('svg:marker')
.attr('id', 'start-arrow')
.attr('viewBox', '0 -5 10 10')
.attr('refX', 4)
.attr('markerWidth', 3)
.attr('markerHeight', 3)
.attr('orient', 'auto')
.append('svg:path')
.attr('d', 'M10,-5L0,0L10,5')
.attr('fill', '#000');
// line displayed when dragging new nodes
var drag_line = svg.append('svg:path')
.attr('class', 'link dragline hidden')
.attr('d', 'M0,0L0,0');
// handles to link and node element groups
var path = svg.append('svg:g').selectAll('path'),
circle = svg.append('svg:g').selectAll('g');
// mouse event vars
var selected_node = null,
selected_link = null,
mousedown_link = null,
mousedown_node = null,
mouseup_node = null;
function resetMouseVars() {
mousedown_node = null;
mouseup_node = null;
mousedown_link = null;
}
// update force layout (called automatically each iteration)
function tick() {
// draw directed edges with proper padding from node centers
path.attr('d', function(d) {
var deltaX = d.target.x - d.source.x,
deltaY = d.target.y - d.source.y,
dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY),
normX = deltaX / dist,
normY = deltaY / dist,
sourcePadding = d.left ? 17 : 12,
targetPadding = d.right ? 17 : 12,
sourceX = d.source.x + (sourcePadding * normX),
sourceY = d.source.y + (sourcePadding * normY),
targetX = d.target.x - (targetPadding * normX),
targetY = d.target.y - (targetPadding * normY);
return 'M' + sourceX + ',' + sourceY + 'L' + targetX + ',' + targetY;
});
circle.attr('transform', function(d) {
return 'translate(' + d.x + ',' + d.y + ')';
});
}
// update graph (called when needed)
function restart() {
// path (link) group
path = path.data(links);
// update existing links
path.classed('selected', function(d) { return d === selected_link; })
.style('marker-start', function(d) { return d.left ? 'url(#start-arrow)' : ''; })
.style('marker-end', function(d) { return d.right ? 'url(#end-arrow)' : ''; });
// add new links
path.enter().append('svg:path')
.attr('class', 'link')
.classed('selected', function(d) { return d === selected_link; })
.style('marker-start', function(d) { return d.left ? 'url(#start-arrow)' : ''; })
.style('marker-end', function(d) { return d.right ? 'url(#end-arrow)' : ''; })
.on('mousedown', function(d) {
if(d3.event.ctrlKey) return;
// select link
mousedown_link = d;
if(mousedown_link === selected_link) selected_link = null;
else selected_link = mousedown_link;
selected_node = null;
restart();
});
// remove old links
path.exit().remove();
// circle (node) group
// NB: the function arg is crucial here! nodes are known by id, not by index!
circle = circle.data(nodes, function(d) { return d.id; });
// update existing nodes (reflexive & selected visual states)
circle.selectAll('circle')
.style('fill', function(d) { return (d === selected_node) ? d3.rgb(colors(d.id)).brighter().toString() : colors(d.id); })
.classed('reflexive', function(d) { return d.reflexive; });
// add new nodes
var g = circle.enter().append('svg:g');
g.append('svg:circle')
.attr('class', 'node')
.attr('r', 12)
.style('fill', function(d) { return (d === selected_node) ? d3.rgb(colors(d.id)).brighter().toString() : colors(d.id); })
.style('stroke', function(d) { return d3.rgb(colors(d.id)).darker().toString(); })
.classed('reflexive', function(d) { return d.reflexive; })
.on('mouseover', function(d) {
if(!mousedown_node || d === mousedown_node) return;
// enlarge target node
d3.select(this).attr('transform', 'scale(1.1)');
})
.on('mouseout', function(d) {
if(!mousedown_node || d === mousedown_node) return;
// unenlarge target node
d3.select(this).attr('transform', '');
})
.on('mousedown', function(d) {
if(d3.event.ctrlKey) return;
// select node
mousedown_node = d;
if(mousedown_node === selected_node) selected_node = null;
else selected_node = mousedown_node;
selected_link = null;
// reposition drag line
drag_line
.style('marker-end', 'url(#end-arrow)')
.classed('hidden', false)
.attr('d', 'M' + mousedown_node.x + ',' + mousedown_node.y + 'L' + mousedown_node.x + ',' + mousedown_node.y);
restart();
})
.on('mouseup', function(d) {
if(!mousedown_node) return;
// needed by FF
drag_line
.classed('hidden', true)
.style('marker-end', '');
// check for drag-to-self
mouseup_node = d;
if(mouseup_node === mousedown_node) { resetMouseVars(); return; }
// unenlarge target node
d3.select(this).attr('transform', '');
// add link to graph (update if exists)
// NB: links are strictly source < target; arrows separately specified by booleans
var source, target, direction;
if(mousedown_node.id < mouseup_node.id) {
source = mousedown_node;
target = mouseup_node;
direction = 'right';
} else {
source = mouseup_node;
target = mousedown_node;
direction = 'left';
}
var link;
link = links.filter(function(l) {
return (l.source === source && l.target === target);
})[0];
if(link) {
link[direction] = true;
} else {
link = {source: source, target: target, left: false, right: false};
link[direction] = true;
links.push(link);
}
// select new link
selected_link = link;
selected_node = null;
restart();
});
// show node IDs
g.append('svg:text')
.attr('x', 0)
.attr('y', 4)
.attr('class', 'id')
.text(function(d) { return d.id; });
// remove old nodes
circle.exit().remove();
// set the graph in motion
force.start();
}
function mousedown() {
// prevent I-bar on drag
//d3.event.preventDefault();
// because :active only works in WebKit?
svg.classed('active', true);
if(d3.event.ctrlKey || mousedown_node || mousedown_link) return;
// insert new node at point
var point = d3.mouse(this),
node = {id: ++lastNodeId, reflexive: false};
node.x = point[0];
node.y = point[1];
nodes.push(node);
restart();
}
function mousemove() {
if(!mousedown_node) return;
// update drag line
drag_line.attr('d', 'M' + mousedown_node.x + ',' + mousedown_node.y + 'L' + d3.mouse(this)[0] + ',' + d3.mouse(this)[1]);
restart();
}
function mouseup() {
if(mousedown_node) {
// hide drag line
drag_line
.classed('hidden', true)
.style('marker-end', '');
}
// because :active only works in WebKit?
svg.classed('active', false);
// clear mouse event vars
resetMouseVars();
}
function spliceLinksForNode(node) {
var toSplice = links.filter(function(l) {
return (l.source === node || l.target === node);
});
toSplice.map(function(l) {
links.splice(links.indexOf(l), 1);
});
}
// only respond once per keydown
var lastKeyDown = -1;
function keydown() {
d3.event.preventDefault();
if(lastKeyDown !== -1) return;
lastKeyDown = d3.event.keyCode;
// ctrl
if(d3.event.keyCode === 17) {
circle.call(force.drag);
svg.classed('ctrl', true);
}
if(!selected_node && !selected_link) return;
switch(d3.event.keyCode) {
case 8: // backspace
case 46: // delete
if(selected_node) {
nodes.splice(nodes.indexOf(selected_node), 1);
spliceLinksForNode(selected_node);
} else if(selected_link) {
links.splice(links.indexOf(selected_link), 1);
}
selected_link = null;
selected_node = null;
restart();
break;
case 66: // B
if(selected_link) {
// set link direction to both left and right
selected_link.left = true;
selected_link.right = true;
}
restart();
break;
case 76: // L
if(selected_link) {
// set link direction to left only
selected_link.left = true;
selected_link.right = false;
}
restart();
break;
case 82: // R
if(selected_node) {
// toggle node reflexivity
selected_node.reflexive = !selected_node.reflexive;
} else if(selected_link) {
// set link direction to right only
selected_link.left = false;
selected_link.right = true;
}
restart();
break;
}
}
function keyup() {
lastKeyDown = -1;
// ctrl
if(d3.event.keyCode === 17) {
circle
.on('mousedown.drag', null)
.on('touchstart.drag', null);
svg.classed('ctrl', false);
}
}
// app starts here
svg.on('mousedown', mousedown)
.on('mousemove', mousemove)
.on('mouseup', mouseup);
d3.select(window)
.on('keydown', keydown)
.on('keyup', keyup);
restart();
</script>
</body>
</html>
Updated on: 03/18/2019