Published
Edited
Mar 19, 2021
3 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
encoding = new Map(literals)
Insert cell
literals = [
["a", ".-"], // full stop, hyphen-minus
["b", "-..."],
["c", "-.-."],
["d", "-.."],
["e", "."],
["f", "..-."],
["g", "--."],
["h", "...."],
["i", ".."],
["j", ".---"],
["k", "-.-"],
["l", ".-.."],
["m", "--"],
["n", "-."],
["o", "---"],
["p", ".--."],
["q", "--.-"],
["r", ".-."],
["s", "..."],
["t", "-"],
["u", "..-"],
["v", "...-"],
["w", ".--"],
["x", "-..-"],
["y", "-.--"],
["z", "--.."],
[".", ".-.-.-"],
[",", "--..--"]
]
Insert cell
Insert cell
defaults = ({
width: 640,
dotWidth: 4,
signalHeight: 4,
lineHeight: 4 * 3
});
Insert cell
chart = (text = "What hath God wrought", options = {}) => {
options = Object.assign({}, defaults, options);
const svg = d3.create("svg")
.attr("class", "morse-code")
.attr("width", options.width);
const words = getWords(text);
const spaceBetweenWords = options.dotWidth * 7;
let dx = 0;
let dy = 0;
for (let i = 0; i < words.length; i++) {
const lowerCasedWord = words[i].toLowerCase();
const wordContainer = createWordContainer(lowerCasedWord, options);
if ((dx + wordContainer.width) > width) {
dx = 0; // new line; words larger than a single line do overflow
dy += options.lineHeight;
}
wordContainer.attr("transform", `translate(${dx},${dy})`);
svg.append(() => wordContainer.node());
dx += wordContainer.width;
if (i < (words.length - 1)) {
dx += spaceBetweenWords;
}
}
svg.attr("height", dy + options.signalHeight);
return svg.node();
}
Insert cell
getWords = (text) => {
const simplifiedText = collapseAdjacentWhiteSpace(text);
const separator = ' '; // words may contain punctuation marks
return simplifiedText.split(separator);
}
Insert cell
collapseAdjacentWhiteSpace = (text) => text.replace(/\s+/g, ' ');
Insert cell
createWordContainer = (word, options) => {
const container = d3.create("svg:g")
.attr("class", "word")
.attr("data-word", word);
let dx = 0;
const spaceBetweenWordCharacters = options.dotWidth * 3;
for (let i = 0; i < word.length; i++) {
const character = word[i];
dx = appendCharacter(container, dx, character, options);
if (i < (word.length - 1)) {
dx += spaceBetweenWordCharacters;
}
}
container.width = dx;
return container;
}
Insert cell
appendCharacter = (container, dx, character, options) => {
const codeContainer = container.append("g")
.attr("class", "character")
.attr("data-character", character);

if (!encoding.has(character)) {
throw `Unsupported character: '${character}'`;
}
const morseCode = encoding.get(character);
for (const signal of morseCode) {
if (signal === '.') {
dx = appendDot(codeContainer, dx, options);
} else if (signal === '-') {
dx = appendDash(codeContainer, dx, options);
} else {
throw `Unsupported signal: '${signal}'`;
}
}

return dx;
}
Insert cell
appendDot = (container, x, options) => {
const margin = x ? options.dotWidth : 0;
container.append("rect")
.attr("class", "dot")
.attr("x", x + margin)
.attr("width", options.dotWidth)
.attr("height", options.signalHeight);

return x + margin + options.dotWidth;
}
Insert cell
appendDash = (container, x, options) => {
const dashWidth = options.dotWidth * 3;
const margin = x ? options.dotWidth : 0;
container.append("rect")
.attr("class", "dash")
.attr("x", x + margin)
.attr("width", dashWidth)
.attr("height", options.signalHeight);

return x + margin + dashWidth;
}
Insert cell
Insert cell
d3 = require("d3-selection@2")
Insert cell
html`<style>
abbr[title] {
text-underline-position: under; /* 'auto' appears broken (Chrome) */
}

.dot,
.dash {
fill: black;
stroke: none;
}
</style>`
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