Public
Edited
Jan 25, 2024
Importers
1 star
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
{
const x = DOM.element("div");
x.appendChild(DOM.text("Text node using DOM..."));
return x;
}
Insert cell
DOM.text("Text node using DOM...")
Insert cell
Insert cell
tex.options({
trust: true
})`Termination\,Rate = \dfrac{\href{https://katex.org/}{Terminations}}{Average\,Headcount}*100`
Insert cell
Insert cell
Insert cell
chinook = FileAttachment("chinook.db").sqlite()
Insert cell
chinook.query(`SELECT * FROM albums`)
Insert cell
Inputs.table(await chinook.query(`SELECT * FROM albums`))
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
class ChartJSVisualiser {
constructor(container, options) {
this.container = container;
this.options = options;
this.element = undefined;
this.visual = undefined;
}

async build() {
// Load Chart.js library. Subsequent loads retrieved from browser cache.
const ChartJS = await loadChartJSLibrary();

this.container.style.position = "relative";
this.container.style.height = `500px`;
this.container.style.width = `${width - 300 - 32}px`;

this.element = document.createElement("canvas");
//if (this.container) {
// // TODO: Required when embedded in HelpScout widget.
// // Does not cause issue because it is deleted an re added by notebook visualise method.
// // Not sure why it is not required in other circumstances.
// // Need to review approach once things stabalise. Should create tempory placeholder so chart does not appear while building.
this.container.appendChild(this.element);

//this.element.style.height = `${
// this.container.clientHeight || defaultVisualHeight
//}px`;
//this.element.style.width = `${this.container.clientWidth || width}px`;
////} else {
//// this.element.style.height = `${defaultVisualHeight}px`;
//// this.element.style.width = `${width}px`;
////}
//this.element.style.padding = "0 16px";

this.visual = new ChartJS(this.element, this.options);

//const resizeVisual = () => this.visual.resize();
//window.addEventListener("resize", resizeVisual);

//resizeVisual();

return this;
}

resize(items) {
return this;
}
}
Insert cell
loadChartJSLibrary = async () => {
// Import Chart.js module.
const chartJS = await import(chartJSURL);
const chartJSChart = chartJS.Chart;
// Register controllers, elements, scales and plugins.
chartJSChart.register(chartJS.BarController);
chartJSChart.register(chartJS.BarElement);
chartJSChart.register(chartJS.CategoryScale);
chartJSChart.register(chartJS.Legend);
chartJSChart.register(chartJS.LineController);
chartJSChart.register(chartJS.LineElement);
chartJSChart.register(chartJS.LinearScale);
chartJSChart.register(chartJS.PointElement);
chartJSChart.register(chartJS.Title);
chartJSChart.register(chartJS.Tooltip);
// Modify default options.
chartJSChart.defaults.animation = false;
chartJSChart.defaults.font.size = 16;
chartJSChart.defaults.layout.padding = 2;
chartJSChart.defaults.plugins.legend.position = "bottom";
chartJSChart.defaults.plugins.legend.labels.boxHeight = 15;
chartJSChart.defaults.plugins.legend.labels.boxWidth = 30;
chartJSChart.defaults.plugins.title.display = true;
chartJSChart.defaults.plugins.title.font.size = 20;
chartJSChart.defaults.plugins.title.font.weight = "normal";
chartJSChart.defaults.maintainAspectRatio = false;
chartJSChart.defaults.responsive = true;
// Return library.
return chartJSChart;
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
class HighchartsVisualiser {
constructor(container, options) {
this.container = container;
this.options = options;
this.element = undefined;
this.visual = undefined;
}

async build() {
// Load Highcharts library. Subsequent loads retrieved from browser cache.
const Highcharts = await loadHighchartsLibrary();

this.element = document.createElement("div");
if (this.container) {
this.element.style.height = `${this.container.clientHeight ||
defaultVisualHeight}px`;
this.element.style.width = `${this.container.clientWidth || width}px`;
} else {
this.element.style.height = `${defaultVisualHeight}px`;
this.element.style.width = `${width}px`;
}

this.visual = Highcharts.chart(this.element, this.options, chart => {
addBorderToLegendSymbols(chart);
});

return this;
}

resize(items) {
return this;
}
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Narrative = narrative1
Insert cell
narrative1 = (markdown, options) => {
let truncate;
let truncateHeight;
if (options) {
truncate = options.truncate === false ? false : true;
truncateHeight = options.truncateHeight || defaultTruncateHeight1;
} else {
truncate = true;
truncateHeight = defaultTruncateHeight1;
}

const content = md`${markdown}`;
document.body.appendChild(content);
const height = content.clientHeight;
content.remove();

let button;
if (truncate && height > truncateHeight) {
content.style["-webkit-mask-image"] =
"linear-gradient(180deg, #000 40%, transparent)";
content.style.maxHeight = `${truncateHeight}px`;
button = buildButton1(truncateHeight);
} else {
button = "";
}

return htl.html`
<div class="nectis" style="position: relative">
<style scoped>${narrativeStyle}</style>
${content}${button}
</div>`;
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
panelStyle = `
:root {
--border_colour: #eee;
}

.nectis .tabBar {
height: 48px;
}

.nectis .tabButton {
border-bottom: 2px solid transparent;
cursor: pointer;
display: flex;
font-size: 16px;
flex-direction: column;
justify-content: center;
padding-left: 15px;
padding-right: 15px;
}
.nectis .tabButton:hover {
background: #f7f7f7;
}
.nectis .tabButton.selected {
border-bottom-color: #9e9e9e;
}
.nectis .tabButton.selected:hover {
background: #f7f7f7;
}

.nectis .optionsButton {
border-top: 2px solid transparent;
cursor: pointer;
font-size: 16px;
padding: 5px 10px 7px 10px;
}
.nectis .optionsButton:hover {
background: #f7f7f7;
}

.nectis .vendorButton {
border-top: 2px solid transparent;
align-items: center;
cursor: pointer;
display: flex;
font-size: 16px;
flex-direction: row;
padding: 5px 10px 7px 10px;
}
.nectis .vendorButton:hover {
background: #f7f7f7;
}
.nectis .vendorButton.selected {
border-top-color: #9e9e9e;
}
.nectis .vendorButton.selected:hover {
background: #f7f7f7;
}

.nectis .visualPanel {
background: #fcfcfc;
border: 1px solid var(--border_colour);
}`
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
renderVisual = async (visual, index, vendor) => {
while (visual.firstChild) visual.firstChild.remove();
// await loadVisualNotebook(index, vendor.notebookId, visual);
await renderNotebook(
"@jonathan-terrell",
"headcount-progression-chartjs",
visual
);
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
loadVisualNotebook1 = async (index, notebookId, container) => {
console.log(1111);
//
if (!notebookId) return;
// Retrieve notebook module for specified notebook identifier.
const urlPrefix = "https://api.observablehq.com/@jonathan-terrell/";
const module = await import(`${urlPrefix}${notebookId}.js?v=3`);

const runtime = new observable.Runtime();
runtime.module(module.default, (name) => {
switch (name) {
case "visualise":
return {
fulfilled: async (value) => {
const visualiser = await value(container);
if (container.dataset.vendorIndex === String(index))
container.replaceChildren(visualiser.element);
runtime.dispose();
},
rejected: (error) => {
console.error(error);
runtime.dispose();
}
};
default:
return false;
}
});
}
Insert cell
renderNotebook = async (account, name, visual) => {
const container = DOM.element("div");

const library = new observable.Library();
const contentWidth = () => {
return library.Generators.observe((change) => {
let currentWidth = change(container.clientWidth - 32);
const onResize = () => {
let resizedWidth = container.clientWidth - 32;
if (resizedWidth !== currentWidth)
change((currentWidth = resizedWidth));
};
window.addEventListener("resize", onResize);
return () => window.removeEventListener("resize", onResize);
});
};
Object.assign(library, { width: contentWidth });

const cellTypes = { h1: 1, h2: 1, n: 1, t: 1, v: 1 };
const urlPrefix = "https://api.observablehq.com/";
const notebook = await import(`${urlPrefix}${account}/${name}.js?v=3`);
const module = new observable.Runtime(library).module(
notebook.default,
(cellName) => {
if (!cellName) return true; // Run side-effects but do not render.
const cellType = cellName.split("_")[0];
if (!cellTypes[cellType]) return true; // Run side-effects but do not render.
return {
fulfilled: async (value) => {
// if (typeof value === "function") return;
// const visualiser = await value();
// container.replaceChildren(visualiser.container);
value.id = cellName;
const element = container.querySelector(`#${cellName}`);
if (element) element.replaceWith(value);
else container.appendChild(value);
//module.dispose();
},
rejected: (error) => {
console.error(error);
//module.dispose();
}
};
}
);
visual.replaceChildren(container);
return container;
}
Insert cell
Insert cell
Insert cell
Insert cell
buildToolbar = () => {
let indexPanel;
document.getElementsByTagName("head")[0].appendChild(toolbarStyle);
return htl.html`
<button class="home-button" onclick=${() => navigate("home")} />
<button class="index-button" onclick=${() =>
(indexPanel = toggleIndex(indexPanel))} />
<button class="home-button" onclick=${() => navigate("home")} />`;
}
Insert cell
Insert cell
toggleIndex = (indexPanel) => {
console.log("indexPanel", indexPanel);
if (indexPanel) {
indexPanel.remove();
return undefined;
}
return document.body.appendChild(htl.html`
<div class="nectis dropdown" style="width: 500px">
<div>
<div onclick=${() =>
navigate("visualise-movements-chord")}>Movements - Single Moves</div>
</div>
</div>`);
}
Insert cell
Insert cell
VisNetwork = ({ Visualiser: VisNetworkVisualiser })
Insert cell
visNetworkURL = `https://cdn.jsdelivr.net/npm/vis-network@${visNetworkVersion}/dist/vis-network.esm.min.js`
Insert cell
visNetworkVersion = "9.0.4"
Insert cell
class VisNetworkVisualiser {
constructor(container, options) {
this.container = container;
this.options = options;
this.element = undefined;
this.visual = undefined;
}

async build() {
const VisNetwork = await import(visNetworkURL);

this.element = document.createElement("div");
if (this.container) {
this.element.style.height = `${this.container.clientHeight ||
defaultVisualHeight}px`;
this.element.style.width = `${this.container.clientWidth || width}px`;
} else {
this.element.style.height = `${defaultVisualHeight}px`;
this.element.style.width = `${width}px`;
}

const nodes = new VisNetwork.DataSet(this.options.data.nodes);
const edges = new VisNetwork.DataSet(this.options.data.edges);
this.visual = new VisNetwork.Network(
this.element,
{ edges, nodes },
this.options.options
);

return this;
}

resize(items) {
return this;
}
}
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more