Public
Edited
Apr 22
Insert cell
Insert cell
initialStatuses = ["Online", "Offline", "Overheated", "Underperforming", "Maintenance"]
Insert cell
colorMap = ({
"Online": "#28a745",
"Offline": "#dc3545",
"Overheated": "#fd7e14",
"Underperforming": "#ffc107",
"Maintenance": "#6c757d"
})
Insert cell
viewof fileInput = html`<input type="file" accept=".csv">`
Insert cell
viewof uploadedMachines = {
const file = await fileInput;
const text = await file.text();
const lines = text.split("\n").filter(line => line.trim() !== "");
const headers = lines[0].split(",").map(h => h.trim());

const machines = lines.slice(1).map((line, i) => {
const values = line.split(",");
const obj = {};
headers.forEach((header, index) => {
let value = values[index]?.trim() || "";
if (["temperature", "hashrate", "uptime"].includes(header)) {
value = parseFloat(value) || 0;
}
obj[header] = value;
});
return obj;
});

const container = html`<div></div>`;
container.value = machines;
return container;
}

Insert cell
function MiningMonitorWidget(config = {}) {
const {
title = "Mining Machine Status Monitor",
initialStatuses = [],
colorMap = {},
showSummaryChart = true,
showFilteredTable = true,
maxTableEntries = 5,
machines = []
} = config;

const container = html`<div class="mining-monitor-widget"></div>`;
container.style.fontFamily = "system-ui, sans-serif";
container.style.maxWidth = "800px";
container.style.margin = "0 auto";
container.style.padding = "16px";
container.style.borderRadius = "8px";
container.style.boxShadow = "0 4px 12px rgba(0,0,0,0.1)";
container.style.backgroundColor = "#f8f9fa";

const titleEl = html`<h2 style="color: #343a40; margin: 0 0 12px 0;">${title}</h2>`;
const descEl = html`<p style="color: #6c757d; margin: 0 0 16px 0;">Filter by status and location:</p>`;
container.appendChild(titleEl);
container.appendChild(descEl);

const chartDiv = html`<div style="height: 40px; width: 100%; background: #e9ecef; margin-bottom: 20px; display: flex; border-radius: 4px;"></div>`;
const tableDiv = html`<div><table style="width: 100%; border-collapse: collapse;"><thead>
<tr>
<th>ID</th><th>Status</th><th>Temp</th><th>Hashrate</th><th>Uptime (min)</th><th>Location</th>
</tr></thead><tbody></tbody></table></div>`;

const statsDiv = html`<div style="display: flex; justify-content: space-around; margin-top: 20px;"></div>`;
container.appendChild(chartDiv);
container.appendChild(tableDiv);
container.appendChild(statsDiv);

let showAll = false;

function updateUI() {
const selectedStatuses = container.value?.status ?? initialStatuses;
const selectedLocation = container.value?.location ?? null;

const filtered = machines.filter(m =>
selectedStatuses.includes(m.status) &&
(!selectedLocation || m.location === selectedLocation)
);

chartDiv.innerHTML = "";
const total = machines.length;
initialStatuses.forEach(status => {
const count = machines.filter(m =>
m.status === status &&
(!selectedLocation || m.location === selectedLocation)
).length;
const width = total > 0 ? (count / total) * 100 : 0;
const bar = html`<div title="${status}: ${count}" style="height: 100%; background: ${colorMap[status]}; width: ${width}%;"></div>`;
chartDiv.appendChild(bar);
});

const body = tableDiv.querySelector("tbody");
body.innerHTML = "";

const displayed = showAll ? filtered : filtered.slice(0, maxTableEntries);
displayed.forEach(m => {
const row = html`<tr>
<td>${m.id}</td>
<td>${m.status}</td>
<td>${m.temperature}°C</td>
<td>${m.hashrate} MH/s</td>
<td>${m.uptime}</td>
<td>${m.location}</td>
</tr>`;
body.appendChild(row);
});

if (filtered.length > maxTableEntries) {
const toggleRow = html`<tr><td colspan="7" style="text-align: center;">
<button style="background: none; border: none; color: #007bff; cursor: pointer; font-size: 14px;">
${showAll ? "Show Less" : `+ ${filtered.length - maxTableEntries} more...`}
</button>
</td></tr>`;

toggleRow.querySelector("button").onclick = () => {
showAll = !showAll;
updateUI();
};

body.appendChild(toggleRow);
}

statsDiv.innerHTML = "";
const avg = (arr, key) => arr.reduce((sum, m) => sum + m[key], 0) / (arr.length || 1);
const statEl = (label, value) => html`<div><div style="font-size: 20px; font-weight: bold;">${value}</div><div style="font-size: 12px; color: #777;">${label}</div></div>`;
statsDiv.appendChild(statEl("Machines", `${filtered.length} / ${total}`));
statsDiv.appendChild(statEl("Avg Temp", `${avg(filtered, "temperature").toFixed(1)}°C`));
statsDiv.appendChild(statEl("Avg Hashrate", `${avg(filtered, "hashrate").toFixed(1)} MH/s`));
statsDiv.appendChild(statEl("Avg Uptime", `${avg(filtered, "uptime").toFixed(0)} min`));
}

return ReactiveWidget(container, {
value: {
status: initialStatuses,
location: null
},
showValue: updateUI
});
}

Insert cell
viewof statusWidget = {
const widget = MiningMonitorWidget({
initialStatuses,
colorMap,
maxTableEntries: 10,
machines: uploadedMachines
});
Inputs.bind(widget, viewof filters);
return widget;
}


Insert cell
import { ReactiveWidget } from "@john-guerra/reactive-widgets"
Insert cell
viewof filters = {
const form = html`<form style="display: flex; flex-direction: column; gap: 0.5rem;"></form>`;

const statusBox = Inputs.checkbox(initialStatuses, {
label: "Status",
value: initialStatuses
});

const allLocations = ["All", ...Array.from(new Set(uploadedMachines.map(m => m.location))).filter(Boolean).sort()];
const locationDropdown = Inputs.select(allLocations, {
label: "Location",
value: "All"
});

const resetButton = html`<button type="button">Reset Filters</button>`;
resetButton.onclick = () => {
statusBox.value = initialStatuses;
locationDropdown.value = "All";
};

form.appendChild(statusBox);
form.appendChild(locationDropdown);
form.appendChild(resetButton);

form.value = {
get status() {
return statusBox.value;
},
get location() {
return locationDropdown.value === "All" ? null : locationDropdown.value;
}
};

return form;
}


Insert cell
aq = require("arquero")
Insert cell
Plot = require("@observablehq/plot")
Insert cell
import { Inputs } from "@observablehq/inputs"
Insert cell
d3 = require("d3")
Insert cell
viewof fileInputElectricity = html`<input type="file" accept=".csv" />`;
Insert cell
function processElectricityData(csvText) {
return d3.csvParse(csvText, d => {
// Combine Date and Time into one timestamp string
const timestamp = `${d.Date} ${d.Time}`;
// Convert price string to number
const price = +d["Electricity Price ($/kWh)"];
return { timestamp, price };
});
}

Insert cell
parsedResult = (async function*() {
// Initial state: no file yet
yield { status: "waiting", data: null };
for await (const file of Generators.input(viewof fileInputElectricity)) {
if (!file) {
// If no file (e.g., file input cleared), revert to waiting
yield { status: "waiting", data: null };
continue;
}
// File selected: update status to loading
yield { status: "loading", data: null };
try {
// Read the file's text content (returns a Promise)
const text = await file.text();
// Parse the CSV text into data objects
const data = processElectricityData(text);
// Success: yield parsed data with status
yield { status: "success", data: data };
} catch (err) {
// On any error (e.g., malformed CSV), yield error status
yield { status: "error", error: err, data: null };
}
}
})();

Insert cell
viewof priceThreshold = Inputs.range([0, 1], {
label: "Electricity Price Threshold ($/kWh)",
step: 0.01,
value: 0.25
})
Insert cell
dataOverview = {
if (!parsedResult || parsedResult.status !== "success" || !parsedResult.data) {
return html`<div style="color: #666;">Please upload an electricity price data file</div>`;
}

const data = parsedResult.data;

// Calculate statistics
const avgPrice = d3.mean(data, d => d.price);
const minPrice = d3.min(data, d => d.price);
const maxPrice = d3.max(data, d => d.price);
const aboveThreshold = data.filter(d => d.price > priceThreshold).length;
const percentAbove = (aboveThreshold / data.length * 100).toFixed(1);

return html`
<div style="background: #f8f9fa; padding: 16px; border-radius: 8px; margin-bottom: 20px;">
<h3 style="margin-top: 0;">Electricity Price Overview</h3>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px;">
<div style="background: white; padding: 12px; border-radius: 6px; box-shadow: 0 1px 3px rgba(0,0,0,0.1);">
<div style="font-size: 24px; font-weight: bold;">$${avgPrice.toFixed(4)}</div>
<div style="color: #666;">Average Price ($/kWh)</div>
</div>
<div style="background: white; padding: 12px; border-radius: 6px; box-shadow: 0 1px 3px rgba(0,0,0,0.1);">
<div style="font-size: 24px; font-weight: bold;">$${minPrice.toFixed(4)}</div>
<div style="color: #666;">Minimum Price ($/kWh)</div>
</div>
<div style="background: white; padding: 12px; border-radius: 6px; box-shadow: 0 1px 3px rgba(0,0,0,0.1);">
<div style="font-size: 24px; font-weight: bold;">$${maxPrice.toFixed(4)}</div>
<div style="color: #666;">Maximum Price ($/kWh)</div>
</div>
<div style="background: ${aboveThreshold > 0 ? '#fff8f8' : 'white'}; padding: 12px; border-radius: 6px; box-shadow: 0 1px 3px rgba(0,0,0,0.1);">
<div style="font-size: 24px; font-weight: bold; color: ${aboveThreshold > 0 ? '#dc3545' : 'inherit'};">${aboveThreshold}</div>
<div style="color: #666;">Time periods above threshold (${percentAbove}%)</div>
</div>
</div>
</div>
`;
}

Insert cell
priceTrendChart = {
if (!parsedResult || parsedResult.status !== "success" || !parsedResult.data) {
return html`<div style="color: #666;">Please upload an electricity price data file</div>`;
}

const data = parsedResult.data;

// Create electricity price trend chart
return Plot.plot({
title: "Electricity Price Trend",
subtitle: `Threshold: $${priceThreshold}/kWh`,
width: 800,
height: 400,
marginBottom: 60,
x: {
label: "Time",
tickRotate: -45
},
y: {
label: "Price ($/kWh)",
grid: true,
domain: [0, Math.max(d3.max(data, d => d.price) * 1.1, priceThreshold * 1.2)]
},
marks: [
// Threshold line
Plot.ruleY([priceThreshold], { stroke: "red", strokeWidth: 1.5, strokeDasharray: "4" }),
// Threshold label
Plot.text([priceThreshold], {
x: 790,
text: [`Threshold: $${priceThreshold.toFixed(2)}`],
fill: "red",
dy: -5,
frameAnchor: "right"
}),
// Price trend line
Plot.line(data, {
x: "timestamp",
y: "price",
stroke: "#4285F4",
strokeWidth: 1.5
}),
// Mark points above threshold
Plot.dot(data.filter(d => d.price > priceThreshold), {
x: "timestamp",
y: "price",
fill: "red",
r: 4
})
]
});
}

Insert cell
priceAlertTable = {
if (!parsedResult || parsedResult.status !== "success" || !parsedResult.data) {
return html`<div style="color: #666;">Please upload an electricity price data file</div>`;
}

const data = parsedResult.data;
const alerts = data.filter(d => d.price > priceThreshold)
.sort((a, b) => b.price - a.price);

if (alerts.length === 0) {
return html`
<div style="background: #f0fff0; padding: 16px; border-radius: 8px; margin-top: 20px;">
<h3 style="margin-top: 0; color: #28a745;">✓ No Price Threshold Breaches</h3>
<p>All time periods have electricity prices below the threshold of $${priceThreshold}/kWh.</p>
</div>
`;
}

return html`
<div style="background: #fff8f8; padding: 16px; border-radius: 8px; margin-top: 20px;">
<h3 style="margin-top: 0; color: #dc3545;">Electricity Price Threshold Alerts</h3>
<p>The following ${alerts.length} time periods exceeded the price threshold of $${priceThreshold}/kWh:</p>
<table style="width: 100%; border-collapse: collapse; margin-top: 12px;">
<thead>
<tr style="background: #f8f9fa;">
<th style="padding: 8px; text-align: left; border-bottom: 2px solid #dee2e6;">Timestamp</th>
<th style="padding: 8px; text-align: right; border-bottom: 2px solid #dee2e6;">Price ($/kWh)</th>
<th style="padding: 8px; text-align: right; border-bottom: 2px solid #dee2e6;">Exceeds Threshold</th>
<th style="padding: 8px; text-align: left; border-bottom: 2px solid #dee2e6;">Risk Level</th>
</tr>
</thead>
<tbody>
${alerts.map(d => {
const diff = d.price - priceThreshold;
const riskLevel = diff > 0.2 ? "High" : diff > 0.1 ? "Medium" : "Low";
const riskColor = diff > 0.2 ? "#dc3545" : diff > 0.1 ? "#fd7e14" : "#ffc107";
return `
<tr>
<td style="padding: 8px; border-bottom: 1px solid #dee2e6;">${d.timestamp}</td>
<td style="padding: 8px; border-bottom: 1px solid #dee2e6; text-align: right;">$${d.price.toFixed(4)}</td>
<td style="padding: 8px; border-bottom: 1px solid #dee2e6; text-align: right; color: #dc3545;">+$${diff.toFixed(4)}</td>
<td style="padding: 8px; border-bottom: 1px solid #dee2e6;">
<span style="display: inline-block; padding: 2px 8px; border-radius: 4px; background: ${riskColor}; color: white;">${riskLevel}</span>
</td>
</tr>
`;
}).join('')}
</tbody>
</table>
</div>
`;
}

Insert cell
function createElectricityForecastTable(priceData, threshold) {
if (!priceData || !Array.isArray(priceData) || priceData.length === 0) {
return html`<div>Please upload electricity price data first</div>`;
}
const predictions = predictFuturePrices(priceData);
return html`
<div style="margin-top: 20px;">
<h3>Electricity Price Forecast for the Next 5 Days</h3>
<table style="width: 100%; border-collapse: collapse;">
<thead>
<tr style="background: #f5f5f5;">
<th style="padding: 8px; border: 1px solid #ddd;">Date</th>
<th style="padding: 8px; border: 1px solid #ddd;">Time</th>
<th style="padding: 8px; border: 1px solid #ddd;">Predicted Price ($/kWh)</th>
<th style="padding: 8px; border: 1px solid #ddd;">Status</th>
</tr>
</thead>
<tbody>
${predictions.map(p => `
<tr>
<td style="padding: 8px; border: 1px solid #ddd;">${p.date}</td>
<td style="padding: 8px; border: 1px solid #ddd;">${p.time}</td>
<td style="padding: 8px; border: 1px solid #ddd;">$${p.price.toFixed(4)}</td>
<td style="padding: 8px; border: 1px solid #ddd; ${p.price > threshold ? "color: red;" : "color: green;"}">
${p.price > threshold ? "Above Threshold" : "✓ Normal"}
</td>
</tr>
`).join('')}
</tbody>
</table>
<p style="font-size: 12px; color: #888; margin-top: 8px;">
Note: Forecast is based on historical averages and for reference only.
</p>
</div>
`;
}
Insert cell
pricePredictionTable = {
if (!parsedResult || parsedResult.status !== "success" || !parsedResult.data) {
return html`<div>Please upload electricity price data first</div>`;
}
return createElectricityForecastTable(parsedResult.data, priceThreshold);
}
Insert cell
import {electricityPriceForecastTable} from "b8ad004b090ff363"
Insert cell
import {predictFuturePrices} from "b8ad004b090ff363"
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