Public
Edited
May 11
1 fork
Importers
Insert cell
Insert cell
Insert cell
Insert cell
<div id="frame">
<div id="display">${spelsObject.spansString}</div>
</div>
Insert cell
Insert cell
play = {
var idx,
loopCount,
loopMsg,
rdnScore,
score,
scoreNum,
secondsBeforeAndBetween,
yieldMsg;
secondsBeforeAndBetween = 3;
loopCount = 0;
yield "wait for it ...";
await Promises.delay(secondsBeforeAndBetween * 1000); // an initial pause
while (true) {
// loop forever ...
loopMsg = `loop: ${loopCount++}`;
for (scoreNum = 0; scoreNum < scores.length; scoreNum++) {
// Following on here there is a slight complication in the algorithm that introduces an element of indeterminancy into our poetic translation, something else that is not easily possible in print.
// readThrough_rdnPath(), below, constructs a version of the score named readThrough_path with a random order of this score’s constituent ‘lines.’ Each time round the loop, we inject this newly constructed score into the last two elements of our scores array so that on first encounter in each loop the lines become visible in the constructed order and then on second encounter (last in the scores array) they are rendered invisible in the same order.
if (scoreNum === scores.length - 2) {
// are we two from the end of the array?
rdnScore = readThrough_rdnPath(); // if so make a random line order
scores[scores.length - 2] = scores[scores.length - 1] = rdnScore; // put it into the last two
}
score = scores[scoreNum];

// This is where we inner-loop through each item in the current score and display the string of its spel for the length of time in its pause property.
for (idx = 0; idx < score.length; idx++) {
// provide some info:
let spelId = score[idx].id;
yieldMsg =
loopMsg + `, score: ${scoreNum}, item: ${idx}, id: ${spelId}, `;
yieldMsg += `string: '${spels.get(spelId).string}', pause: ${score[
idx
].pause.toFixed(2)}`;
yield yieldMsg;
// these two lines do all the work:
document.getElementById(spelId).classList.toggle("visible"); // triggers fade in/out
await Promises.delay(score[idx].pause * 1000); // pauses taken from the temporal data
}
await Promises.delay(secondsBeforeAndBetween * 1000); // longer pause between readings
}
}
}
Insert cell
Insert cell
<pre class="set">
rich scarlet
deepens
herb path
faint turquoise
fills
mountain window

envy you
butterfly
through dreams
under flowers

wine flying
</pre>
Insert cell
Insert cell
draftSpanned = html`${await addSpansTo(draft)}`
Insert cell
Insert cell
addSpansTo = (draftElem) => {
let newText = ``; // this will hold html for the new draftSpanned cell
let text = draftElem.innerHTML; // get existing innerHTML from from the pre element of draft cell
let spelStart = text.search(/\S/); // find the first spel by finding first nonwhitespace
while (spelStart != -1) {
newText += text.substr(0, spelStart); // grab everything up to that point
text = text.substr(spelStart); // put the rest in text
let spelEnd = text.search(/\s/); // find where whitespace starts again
if (spelEnd == -1) spelEnd = text.length; // text ends with nonwhitespace
// wrap the spel we've found in span tags
newText += `<span>` + text.substr(0, spelEnd) + `</span>`; // add this to newText
text = text.substr(spelEnd); // put the rest in text
spelStart = text.search(/\S/); // find the next nonwhitespace (if any)
}
return `<pre class="set">${newText}</pre>`; // wrap in pre tags of same class
}
Insert cell
Insert cell
Insert cell
Insert cell
buildSpels = (draftSpannedElem) => {
// builds the spels array of spel objects and returns styled spans for display
let spansString = ``;
let spels = new Map();
let verseSpans = Array.from(draftSpannedElem.children);
verseSpans.forEach((span, idx) => {
let spel = {};
let spelId = "s" + idx;
spel.string = span.innerText;
spel.leftvw = pxToVw(span.offsetLeft);
spel.topvw = pxToVw(span.offsetTop);
spels.set(spelId, spel);
spansString += `<span id="${spelId}" class="text" style="left:${spel.leftvw}vw;top:${spel.topvw}vw">${span.innerHTML}</span>`;
});
return { spansString, spels };
}
Insert cell
Insert cell
Insert cell
Insert cell
scores = [
readThrough_linear,
toggleAll,
parallel_segments,
path_fading,
// randomly chosen segments will be injected into
// the following scores when play runs
readThrough_path,
readThrough_path
]
Insert cell
Insert cell
readThrough_linear = [
{ id: "s0", pause: 0.5999999999999999 },
{ id: "s1", pause: 1.35 },
{ id: "s2", pause: 1.2499999999999996 },
{ id: "s3", pause: 0.5200000000000005 },
{ id: "s4", pause: 2.01 },
{ id: "s5", pause: 0.5800000000000001 },
{ id: "s6", pause: 1.5199999999999996 },
{ id: "s7", pause: 1.1500000000000004 },
{ id: "s8", pause: 0.6299989999999998 },
{ id: "s9", pause: 2.4000009999999996 },
{ id: "s10", pause: 0.41000000000000014 },
{ id: "s11", pause: 0.9500000000000011 },
{ id: "s12", pause: 1.879998999999998 },
{ id: "s13", pause: 0.48000100000000145 },
{ id: "s14", pause: 1.3799990000000015 },
{ id: "s15", pause: 0.3299999999999983 },
{ id: "s16", pause: 1.759999999999998 },
{ id: "s17", pause: 0.5200000000000031 },
{ id: "s18", pause: 0.5799999999999983 }
]
Insert cell
Insert cell
readThrough_linear_generated = alignedSpels(temporalData.linear)
Insert cell
Insert cell
toggleAll = {
let newScore = [];
for (let [id, spel] of spels) {
newScore.push({ id: id, pause: 0 });
}
return newScore;
}
Insert cell
Insert cell
readThrough_parallel = alignedSpels(temporalData.parallel)
Insert cell
Insert cell
parallel_segments = {
let p = readThrough_parallel;
let newScore = [];
newScore = addFade(p.slice(0, 4), newScore);
newScore = addFade(p.slice(4, 6), newScore);
newScore = addFade(p.slice(6, 10), newScore);
newScore = addFade(p.slice(10, 13), newScore);
newScore = addFade(p.slice(13, 15), newScore);
newScore = addFade(p.slice(15, 19), newScore, 1.5);
return newScore;
}
Insert cell
Insert cell
readThrough_path = alignedSpels(temporalData.path)
Insert cell
Insert cell
path_fading = {
let p = readThrough_path;
let newScore = [];
newScore = addFade(p.slice(0, 6), newScore);
newScore = addFade(p.slice(6, 11), newScore);
newScore = addFade(p.slice(11, 14), newScore);
newScore = addFade(p.slice(14, 19), newScore, 1.5);
return newScore;
}
Insert cell
Insert cell
Insert cell
addFade = (segmentToFade, scoreSoFar, interPause = 0) => {
scoreSoFar = scoreSoFar.concat(segmentToFade);
if (interPause > 0) {
scoreSoFar[scoreSoFar.length - 1].pause = interPause;
}
segmentToFade.forEach((item) => scoreSoFar.push({ id: item.id, pause: 0 }));
scoreSoFar[scoreSoFar.length - 1] = segmentToFade[segmentToFade.length - 1];
return scoreSoFar;
}
Insert cell
Insert cell
alignedSpels = (alignmentJSON, _spels = spels) => {
let allWords = [];
// extract all the words (strings) from the alignment data
Object.keys(alignmentJSON).forEach((segment) => {
alignmentJSON[segment].forEach((speech) => {
allWords = allWords.concat(speech.wdlist);
});
});
// build spels based of these words
let aSpels = [];
allWords.forEach((alignedWord, idx) => {
let p = alignedWord.end - alignedWord.start;
if (idx < allWords.length - 1) {
p = p + (allWords[idx + 1].start - alignedWord.end);
}
let string = alignedWord.word.match(/\w*/)[0];
// TODO: needs serious work! this function is project-dependent,
// only works here because all words in the alignment data are unique
let id, spel;
for ([id, spel] of _spels) {
if (spel.string === string) break;
}
aSpels.push({ id: id, pause: p }); // spels[idx]
});
return aSpels;
}
Insert cell
Insert cell
readThrough_rdnPath = () => {
let segments = readThrough_path.slice(0);
segments[segments.length - 1].pause = 2.5;
segments = shuffle([
segments.slice(0, 6),
segments.slice(6, 11),
segments.slice(11, 14),
segments.slice(14)
]);
let flattened = segments.reduce((a, b) => a.concat(b), []);
return flattened;
}
Insert cell
Insert cell
<h4>CSS</h4>
<style>

@font-face {
font-family: 'NotoSerif';
src: local('NotoSerif'),
url(${await FileAttachment("NotoSerif-Regular.woff2").url()}) format('woff2'),
url(${await FileAttachment("NotoSerif-Regular.woff").url()}) format('woff'),
url(${await FileAttachment("NotoSerif-Regular.ttf").url()}) format('ttf')
}

:root {
--ffam: NotoSerif, serif;
--fsize: 2.3vw;
}

.set {
margin: 0;
font-family: var(--ffam);
font-size: var(--fsize);
}

.text {
position: absolute;
opacity: 0;
/* color: lightgrey; */
transition: all 1.5s ease-in-out; /* vendorless fallback */
-o-transition: all 1.5s ease-in-out; /* opera */
-ms-transition: all 1.5s ease-in-out; /* IE 10 */
-moz-transition: all 1.5s ease-in-out; /* Firefox */
-webkit-transition: all 1.5s ease-in-out; /*safari and chrome */
}

.text.visible {
opacity: 1;
/* color: black; */
}

#display {
position: relative;
background-color: floralwhite;
font-family: var(--ffam);
font-size: var(--fsize);
color: slate;
cursor: none;
overflow: hidden;
width: 55vw; /* This value is calculated in the verseWidth cell, manually */
height: 45vw; /* This value is calculated in the verseHeight cell, manually */
}

#frame {
position: relative;
background-color: ghostwhite;
padding: 1vw;
width: max-content;
}

</style>
Insert cell
Insert cell
widthHeight = {
let width = pxToVw(
Array.from(draftSpanned.children).reduce(
(a, b) => Math.max(a, b.offsetLeft + b.offsetWidth),
0
)
);
let height = pxToVw(
Array.from(draftSpanned.children).reduce(
(a, b) => Math.max(a, b.offsetTop + b.offsetHeight),
0
)
);
return `width: ${width}, height: ${height}`;
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
temporalData = ({
linear: await FileAttachment("wf_linear@2.json").json(),
parallel: await FileAttachment("wf_parallel@1.json").json(),
path: await FileAttachment("wf_path@1.json").json()
})
Insert cell
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