{while (true) {
yield new Promise(resolve =>
setTimeout(() => resolve(new Date().toLocaleTimeString()), 1000)
;
)
} }
Example with Observable
Todo
đźš§
Introduction
Quarto lets you create static documents and websites. That’s great for reports or blogs where content doesn’t change.
What if we want to add interactive elements?
That’s where Observable helps: it lets you embed reactive JavaScript code that updates automatically.
For example, you can show a live clock or a countdown inside Quarto with Observable:
Adding css
Code
= {
viewof digitalClock const div = html`<div class="digital-clock">00:00:00</div>`;
const update = () => {
const now = new Date();
const time = now.toLocaleTimeString();
.textContent = time;
div;
}
update();
const interval = setInterval(update, 1000);
.then(() => clearInterval(interval)); // cleanup on cell re-run
invalidation
return div;
}
Code
= {
viewof circularClock const container = html`<div class="clock-container">
<div class="clock-dots"></div>
<div class="clock-time">00:00:00</div>
</div>`;
const timeDiv = container.querySelector(".clock-time");
const dotsDiv = container.querySelector(".clock-dots");
function updateClock() {
const now = new Date();
const hh = String(now.getHours()).padStart(2, "0");
const mm = String(now.getMinutes()).padStart(2, "0");
const ss = now.getSeconds(); // numeric, not string here
.textContent = `${hh}:${mm}`;
timeDiv
// Clear previous dots
.innerHTML = "";
dotsDiv
// Draw up dots
for (let i = 0; i < ss && i < 60; i++) {
const dot = document.createElement("span");
.className = "dot";
dot
const angle = (i * 6 - 90) * (Math.PI / 180);
const x = Math.cos(angle) * 90 + 100;
const y = Math.sin(angle) * 90 + 100;
.style.left = `${x}px`;
dot.style.top = `${y}px`;
dot.appendChild(dot);
dotsDiv
}
}
updateClock();
const interval = setInterval(updateClock, 1000);
.then(() => clearInterval(interval));
invalidation
return container;
}
Another simple example
= {
countdown for (let i = 100; i >= 0; i -= 1) {
yield i;
await new Promise(resolve => setTimeout(resolve, 200));
}yield "Time is over";
}
countdown
1 Run
In contrast to traditional Quarto usage, Observable-powered documents cannot be rendered as static HTML with quarto render.
You need to run the app on your own machine using a local server, typically by specifying a port.
To set the port (between 3000 and 8000), specify it in file _quarto.yml
.
_quarto.yml
project:
type: website
preview:
port: 7777
- To launch the preview:
quarto preview
2 Functions to compute pace and duration time
2.1 Running pace
/**
* Converts speed (km/h) to running pace in min/km.
* @param {number} speed_kmh - Speed in kilometers per hour.
* @returns {string} Pace string in the format "mm:ss"
*/
function pace(speed_kmh) {
const totalMinutes = 60 / speed_kmh;
const mins = Math.floor(totalMinutes);
const secs = Math.round((totalMinutes - mins) * 60);
return `${mins}:${secs.toString().padStart(2, '0')}`;
}
2.2 Race time
/**
* Calculates the estimated finish time for a race.
* @param {number} distance_km - Distance in kilometers.
* @param {number} speed_kmh - Speed in kilometers per hour.
* @returns {string} Formatted time string (e.g. "42m 05s", "1h 10m 30s")
*/
function raceTime(distance_km, speed_kmh) {
const totalSeconds = Math.round((distance_km / speed_kmh) * 3600);
const h = Math.floor(totalSeconds / 3600);
const m = Math.floor((totalSeconds % 3600) / 60);
const s = totalSeconds % 60;
return `${h > 0 ? `${h}h ` : ""}${String(m).padStart(2, '0')}m ${String(s).padStart(2, '0')}s`;
}
3 Inputs
3.1 Distances dictionnary
= [
distances name: "1 km", km: 1, percentMAS: 1.1 },
{ name: "5 km", km: 5, percentMAS: 0.93 },
{ name: "10 km", km: 10, percentMAS: 0.88 },
{ name: "Half Marathon", km: 21.0975, percentMAS: 0.82 },
{ name: "Marathon", km: 42.195, percentMAS: 0.75 },
{ name: "100 km", km: 100, percentMAS: 0.65 }
{ ]
MAS (Maximum Aerobic Speed) is the highest running speed at which your body reaches maximum oxygen consumption (VOâ‚‚ max).
It’s typically the fastest pace you can sustain for about 6 minutes.
The percentMAS column represents the estimated percentage of your MAS that you can sustain over each race distance.
For example, a value of 0.93 means you’re running at 93% of your MAS for that distance.
4 Choose input and compute
Let’s create an input:
= Inputs.range([8, 25], {
viewof mas value: 15,
step: 0.5,
label: "MAS (km/h)",
})
And then create a dataframe:
= distances.map(d => {
df const speed = mas * d.percentMAS;
return {
distanceStr: d.name,
distance: d.km,
speed: speed.toFixed(2),
paceStr: pace(speed),
timeStr: raceTime(d.km, speed),
timeHour: d.km / speed
} })
Display the dataframe:
.table(df, {
Inputscolumns: ["distanceStr", "speed", "paceStr", "timeStr"],
header: {
distanceStr: "Distance",
speed: "Speed (km/h)",
paceStr: "Pace (min/km)",
timeStr: "Estimated Time"
} })
5 Plot speed against distance
Code
.plot({
Plotx: { label: "Distance (km)" },
y: {
label: "Time (Hours)",
domain: [0, 5],
ticks: 5 },
marks: [
.line(df.filter(d => d.distance < 50), { x: "distance", y: "timeHour" }),
Plot.dot(df.filter(d => d.distance < 50), { x: "distance", y: "timeHour" })
Plot
] })