Smartdown's playable mechanism enables a variety of interactive content to be embedded into a document. By default, a playable is displayed in the way that a typical figure or graphic might be displayed, as a block that is separated from its preceding and following text. But there are many examples where an inline playable would be useful.
This document explores Smartdown's attempts to make this a practical feature.
An early experiment with inlining playables relies up using special syntax to inhibit a paragraph's normal display: block
policy and instead use an display: inline
policy so that it abuts adjacent elements such as playables that have been annotated with /inline
.
- Ordinarily, Smartdown text is formatted in paragraphs, which are rendered with
display: inline-block
, which is usually what you want. - Smartdown playables are
display: block
by default, although the recently added/inline
qualifier changes that toinline-block
. - In order to get a playable to abut a paragraph, we need to adjust the adjacent paragraphs'
display
to beinline
, when it detects that the playable is marked/inline
.
Here is a P5JS circle:
p5.setup = function() {
};
p5.draw = function() {
p5.ellipse(50, 50, 80, 80);
};
and here is a rectangle prefixed with math
p5.setup = function() {
};
p5.draw = function() {
p5.rect(5, 5, 80, 80);
};
So you can see how the circle and rectangle above were rendered inline with the text.
An alternative, and much more flexible, mechanism for adding inline playables is enabled by allowing Smartdown to create an inline target div easily, and then to reference that div in a subsequent playable. This allows the code that specifies an inline div's content to be placed elsewhere in the document source.
This initial example will demonstrate an animated sparkline adapted from the example from http://bl.ocks.org/benjchristensen/1148374.
First, we'll define a Javascript function drawSparkline
which we will use in a few different contexts.
function drawSparkline(id, width, height, interpolation, animate, updateDelay, transitionDelay) {
// create an SVG element inside the #graph div that fills 100% of the div
var graph = d3.select(id).append("svg:svg");
graph
.attr("width", `${width}px`)
.attr("height", `${height}px`);
// create a simple data array that we'll plot with a line (this array represents only the Y values, X will just be the index location)
var data = [3, 6, 2, 7, 5, 2, 1, 3, 8, 9, 2, 5, 9, 3, 6, 3, 6, 2, 7, 5, 2, 1, 3, 8, 9, 2, 5, 9, 2, 7, 5, 2, 1, 3, 8, 9, 2, 5, 9, 3, 6, 2, 7, 5, 2, 1, 3, 8, 9, 2, 9];
// X scale will fit values from 0-10 within pixels 0-100
var x = d3.scaleLinear().domain([0, 48]).range([-5, width]); // starting point is -5 so the first value doesn't show and slides off the edge as part of the transition
// Y scale will fit values from 0-10 within pixels 0-100
var y = d3.scaleLinear().domain([0, 10]).range([0, height]);
// create a line object that represents the SVN line we're creating
var line = d3.line()
// assign the X function to plot our line as we wish
.x(function(d,i) {
// verbose logging to show what's actually being done
//console.log('Plotting X value for data point: ' + d + ' using index: ' + i + ' to be at: ' + x(i) + ' using our xScale.');
// return the X coordinate where we want to plot this datapoint
return x(i);
})
.y(function(d) {
// verbose logging to show what's actually being done
//console.log('Plotting Y value for data point: ' + d + ' to be at: ' + y(d) + " using our yScale.");
// return the Y coordinate where we want to plot this datapoint
return y(d);
})
.curve(d3.curveBasis);
// display the line by appending an svg:path element with the data line we created above
graph.append("svg:path").attr("d", line(data));
// or it can be done like this
//graph.selectAll("path").data([data]).enter().append("svg:path").attr("d", line);
function redrawWithAnimation() {
// update with animation
graph.selectAll("path")
.data([data]) // set the new data
.attr("transform", "translate(" + x(1) + ")") // set the transform to the right by x(1) pixels (6 for the scale we've set) to hide the new value
.attr("d", line) // apply the new data values ... but the new value is hidden at this point off the right of the canvas
.transition() // start a transition to bring the new value into view
.ease("linear")
.duration(transitionDelay) // for this demo we want a continual slide so set this to the same as the setInterval amount below
.attr("transform", "translate(" + x(0) + ")"); // animate a slide to the left back to x(0) pixels to reveal the new value
/* thanks to 'barrym' for examples of transform: https://gist.github.com/1137131 */
}
function redrawWithoutAnimation() {
// static update without animation
graph.selectAll("path")
.data([data]) // set the new data
.attr("d", line); // apply the new data values
}
setInterval(function() {
var v = data.shift(); // remove the first element of the array
data.push(v); // add a new element to the array (we're just taking the number we just shifted off the front and appending to the end)
if(animate) {
redrawWithAnimation();
} else {
redrawWithoutAnimation();
}
}, updateDelay);
}
window.drawSparkline = drawSparkline;
The playable below will use the /&div1
syntax to direct its rendering to the div in the next paragraph. We'll test Smartdown's playable progress bar feature, so you should see a blueish progress bar in place of where the inline playable will eventually appear. That is, until you click this checkbox... , whereupon the doit
variable will be defined and the playable will continue. Toggling the checkbox will do the obvious thing.
This paragraph has an inline target div called div1
. It's right here: and uses the following syntax to specify that a Smartdown cell is to be created: [](:&div1)
. This div can be populated by a playable that refers to DOM id inline-target-div1
. Alternatively, the syntax /&div1
can be added to the playable's definition, which will inhibit the normal creation of a playable div, and will ensure that this.div
will refer to the targeted div inline-target-div
.
As another test, we'll also make the playable itself /inline
, so the play/stop button should appear inline right here ...
const targetDiv1 = this.div;
this.dependOn.doit = () => {
if (env.doit) {
this.div.innerHTML = '';
drawSparkline(this.div, 300, 30, "basis", false, 1000, 1000);
}
else {
this.div.innerHTML = '';
}
};
... and this text should follow the play/stop button.
An early experiment with inlining playables relies up using special syntax to inhibit a paragraph's normal display: block
policy and instead use an display: inline
policy so that it abuts adjacent elements such as playables that have been annotated with /inline
.
Check out my sparkline without break inhibition
drawSparkline(this.div, 300, 30, "basis", false, 1000, 1000);
Isn't it cool?
We can use the same inline div target mechanism for other playable languages, such as Mermaid or Graphviz.
Here's a little Graphviz diagram inlined into this paragraph right here ... ... and these words follow the graphic, which is defined in the playable below:
digraph L0 {
size = "2,2";
ordering=out;
node [shape = box];
n0 [label="E"];
n1 [label="T"];
n2 [label="F"];
n3 [label="IDENT : a "];
n4 [label="+"];
n5 [label="T"];
n6 [label="F"];
n7 [label="("];
n8 [label="E"];
n9 [label="T"];
n10 [label="F"];
n11 [label="IDENT : b "];
n12 [label="*"];
n13 [label="F"];
n14 [label="IDENT : c "];
n15 [label=")"];
n16 [label="*"];
n17 [label="F"];
n18 [label="("];
n19 [label="E"];
n20 [label="T"];
n21 [label="F"];
n22 [label="IDENT : d "];
n23 [label="*"];
n24 [label="F"];
n25 [label="IDENT : e "];
n26 [label="+"];
n27 [label="T"];
n28 [label="F"];
n29 [label="("];
n30 [label="E"];
n31 [label="T"];
n32 [label="F"];
n33 [label="IDENT : a "];
n34 [label="*"];
n35 [label="F"];
n36 [label="IDENT : b "];
n37 [label=")"];
n38 [label=")"];
n39 [label="+"];
n40 [label="T"];
n41 [label="F"];
n42 [label="IDENT : q "];
n0 -> { n1 n4 n5 n39 n40 };
n1 -> n2 ;
n2 -> n3 ;
n5 -> { n6 n16 n17 };
n6 -> { n7 n8 n15 };
n8 -> n9 ;
n9 -> { n10 n12 n13 };
n10 -> n11 ;
n13 -> n14 ;
n17 -> { n18 n19 n38 };
n19 -> { n20 n26 n27 };
n20 -> { n21 n23 n24 };
n21 -> n22 ;
n24 -> n25 ;
n27 -> n28 ;
n28 -> { n29 n30 n37 };
n30 -> n31 ;
n31 -> { n32 n34 n35 };
n32 -> n33 ;
n35 -> n36 ;
n40 -> n41 ;
n41 -> n42 ;
}
Back to Home