This software is dedicated to the memory of Tom G1PJB, SK, a friend and colleague who sadly passed away around the time I started writing it in Autumn 2025. I was looking forward to showing it to you when it was done.
-
+
{% end %}
\ No newline at end of file
diff --git a/templates/add_spot.html b/templates/add_spot.html
index eed98a5..afaff68 100644
--- a/templates/add_spot.html
+++ b/templates/add_spot.html
@@ -69,8 +69,8 @@
-
-
+
+
{% end %}
\ No newline at end of file
diff --git a/templates/alerts.html b/templates/alerts.html
index e49e0be..1ebace6 100644
--- a/templates/alerts.html
+++ b/templates/alerts.html
@@ -70,8 +70,8 @@
-
-
+
+
{% end %}
\ No newline at end of file
diff --git a/templates/bands.html b/templates/bands.html
index 568e75d..3b14bb7 100644
--- a/templates/bands.html
+++ b/templates/bands.html
@@ -76,9 +76,9 @@
-
-
-
+
+
+
{% end %}
\ No newline at end of file
diff --git a/templates/base.html b/templates/base.html
index 6d2bb8c..fe4bdde 100644
--- a/templates/base.html
+++ b/templates/base.html
@@ -24,7 +24,7 @@
Spothole
-
+
@@ -52,9 +52,9 @@
integrity="sha384-L1eE4eD41kpBIWe2I0eHy+GnEUC4RIpcvibVW2JCminuPlTl+2Bc528iPdVMg5Dn"
crossorigin="anonymous">
-
-
-
+
+
+
diff --git a/templates/conditions.html b/templates/conditions.html
index b728c80..62f11ce 100644
--- a/templates/conditions.html
+++ b/templates/conditions.html
@@ -249,8 +249,8 @@
-
-
+
+
diff --git a/templates/map.html b/templates/map.html
index da43c0c..62aa4ea 100644
--- a/templates/map.html
+++ b/templates/map.html
@@ -94,9 +94,9 @@
-
-
-
+
+
+
{% end %}
\ No newline at end of file
diff --git a/templates/spots.html b/templates/spots.html
index f0c0c03..42b80b2 100644
--- a/templates/spots.html
+++ b/templates/spots.html
@@ -104,9 +104,9 @@
-
-
-
+
+
+
{% end %}
\ No newline at end of file
diff --git a/templates/status.html b/templates/status.html
index d581a0e..b424407 100644
--- a/templates/status.html
+++ b/templates/status.html
@@ -59,8 +59,8 @@
-
-
+
+
diff --git a/webassets/js/conditions.js b/webassets/js/conditions.js
index 30ab86e..f5ee08b 100644
--- a/webassets/js/conditions.js
+++ b/webassets/js/conditions.js
@@ -376,6 +376,8 @@ function populateIonosondeDropdown(data) {
// Render the foF2/MUF data and line chart for the currently selected station
function renderIonosondeData() {
+ // First make sure that we have some data, that a station entry is selected in the drop-down box, and that the
+ // data contains an entry for that station. If not, bail out at this point.
if (!ionosondeData) return;
const ursi = $('#ionosonde-station').val();
if (!ursi) return;
@@ -384,12 +386,15 @@ function renderIonosondeData() {
});
if (!station) return;
+ // Set up some styles, matching the k-index chart. We use Bootstrap's "primary" and "danger" colours not for any
+ // real reason but just to get a suitable blue and red that match the other colours Spothole uses
const style = getComputedStyle(document.documentElement);
const fof2Color = style.getPropertyValue('--bs-primary').trim();
const mufColor = style.getPropertyValue('--bs-danger').trim();
const textColor = style.getPropertyValue('--bs-body-color').trim() || '#666';
const gridColor = style.getPropertyValue('--bs-border-color').trim() || 'rgba(128,128,128,0.3)';
+ // Utility function to convert the dict of timestamp-to-value into just a value array in timestamp key order
function toSeries(dict) {
if (!dict) return [];
return Object.entries(dict)
@@ -405,10 +410,11 @@ function renderIonosondeData() {
// Populate latest values summary (visible on all screen sizes)
const latestFof2 = fof2Entries.length ? fof2Entries[fof2Entries.length - 1].val : null;
const latestMuf = mufEntries.length ? mufEntries[mufEntries.length - 1].val : null;
- const latestTs = allTs.length ? Math.max(...allTs) : null;
- var latestTimeStr = '';
- if (latestTs != null) {
- const latestDate = moment.utc(latestTs * 1000);
+ const minTs = allTs.length ? Math.min(...allTs) : null;
+ const maxTs = allTs.length ? Math.max(...allTs) : null;
+ let latestTimeStr = '';
+ if (maxTs != null) {
+ const latestDate = moment.utc(maxTs * 1000);
latestTimeStr = latestDate.format('DD MMM YYYY HH:mm [UTC]') + ' (' + latestDate.fromNow() + ')';
}
$('#ionosonde-latest').html(
@@ -424,9 +430,6 @@ function renderIonosondeData() {
ionosondeChart.destroy();
}
- const minTs = Math.min(...allTs);
- const maxTs = Math.max(...allTs);
-
// Compute tick positions at 3-hour UTC boundaries so midnight always lands on a tick, which triggers the date being
// printed, and in general looks nicer than arbitrary ticks based on min & max timestamp
const tickStep = 3 * 3600;
@@ -437,6 +440,7 @@ function renderIonosondeData() {
}
tickValues.push(maxTs);
+ // Build time axis
const timeAxis = {
type: 'linear',
min: minTs,
@@ -450,6 +454,8 @@ function renderIonosondeData() {
maxRotation: 45,
minRotation: 0,
callback(value) {
+ // Use the same type of display as in the k-index chart, where the labels on the axis are just HH:mm
+ // unless that's 00:00, in which case add the short date as well.
const dt = new Date(value * 1000);
const h = dt.getUTCHours();
const m = dt.getUTCMinutes();
@@ -463,6 +469,8 @@ function renderIonosondeData() {
grid: {color: gridColor},
};
+ // Build frequency axis. This is pretty normal except there's no grid, because we draw extra horizontal lines for
+ // the amateur radio bands which function as grid lines for the frequency axis.
const freqAxis = {
min: 0,
title: {display: true, text: 'Frequency (MHz)', color: textColor},
@@ -470,6 +478,7 @@ function renderIonosondeData() {
grid: {display: false},
};
+ // List of ham bands for drawing horizontal lines
const AMATEUR_BANDS = [
{label: '160m', freq: 1.8},
{label: '80m', freq: 3.5},
@@ -483,6 +492,7 @@ function renderIonosondeData() {
{label: '10m', freq: 28.0},
];
+ // Build the horizontal lines for each ham band, including a label on the right-hand side.
const bandLinesPlugin = {
id: 'bandLines',
beforeDatasetsDraw(chart) {
@@ -492,6 +502,8 @@ function renderIonosondeData() {
ctx.strokeStyle = gridColor;
ctx.lineWidth = 1;
ctx.setLineDash([]);
+ // Add an extra vertical line for 30MHz, which should correspond to the top of the chart and avoid having
+ // no top "border" gridline
const y30 = scales.y.getPixelForValue(30);
if (y30 >= chartArea.top && y30 <= chartArea.bottom) {
ctx.beginPath();
@@ -501,6 +513,7 @@ function renderIonosondeData() {
}
ctx.font = '10px sans-serif';
ctx.fillStyle = textColor;
+ // Add the ham band "grid lines"
AMATEUR_BANDS.forEach(({label, freq}) => {
const y = scales.y.getPixelForValue(freq);
if (y < chartArea.top || y > chartArea.bottom) return;
@@ -516,6 +529,7 @@ function renderIonosondeData() {
}
};
+ // Create the chart itself
ionosondeChart = new Chart(document.getElementById('ionosonde-chart'), {
type: 'line',
data: {