diff --git a/assets/js/elevation_profile_hook.ts b/assets/js/elevation_profile_hook.ts index 0d201410..53932ee4 100644 --- a/assets/js/elevation_profile_hook.ts +++ b/assets/js/elevation_profile_hook.ts @@ -168,6 +168,8 @@ export const ElevationProfile: ElevationProfileHook = { const n = points.length - 1 const distances = points.map(p => p.dist_km * KM2MI) const showFresnel = data.freq_mhz >= 1000 + const station1 = this.el.dataset.station1 || "" + const station2 = this.el.dataset.station2 || "" const k = data.k_factor || (4 / 3) const R = 6371000 @@ -333,7 +335,18 @@ export const ElevationProfile: ElevationProfileHook = { ticks: { font: { size: 9 }, autoSkip: false, - callback: (val: number) => distances[val] !== undefined ? Math.round(distances[val]) : "" + // Chart.js renders string[] tick labels as stacked lines, so we + // tuck the station callsign onto the second line at the 0-mi + // and max-mi endpoints — gives each end of the profile a + // station identity without needing a separate axis. + callback: (val: number) => { + const mi = distances[val] + if (mi === undefined) return "" + const mileage = Math.round(mi) + if (val === 0 && station1) return [`${mileage}`, station1] + if (val === distances.length - 1 && station2) return [`${mileage}`, station2] + return `${mileage}` + } } }, y: { diff --git a/lib/microwaveprop_web/live/contact_live/show.ex b/lib/microwaveprop_web/live/contact_live/show.ex index 21c7b909..5c07f0e1 100644 --- a/lib/microwaveprop_web/live/contact_live/show.ex +++ b/lib/microwaveprop_web/live/contact_live/show.ex @@ -63,6 +63,7 @@ defmodule MicrowavepropWeb.ContactLive.Show do sounding_sort_order: "asc", queue_counts: fetch_queue_counts(), expanded_soundings: MapSet.new(), + hydration_pending: MapSet.new(), editing: false, edit_form: nil, pending_edit: load_pending_edit(contact, socket), @@ -90,6 +91,19 @@ defmodule MicrowavepropWeb.ContactLive.Show do # clauses below update the matching slot and re-run the derived analysis. defp kickoff_hydration(socket, contact) do socket + |> assign( + :hydration_pending, + MapSet.new([ + :weather, + :solar, + :hrrr_path, + :narr_path, + :native_profile, + :terrain, + :iemre, + :radar + ]) + ) |> start_async(:weather, fn -> load_weather(contact) end) |> start_async(:solar, fn -> load_solar(contact) end) |> start_async(:hrrr_path, fn -> Weather.hrrr_profiles_for_path(contact) end) @@ -100,6 +114,16 @@ defmodule MicrowavepropWeb.ContactLive.Show do |> start_async(:radar, fn -> load_radar(contact) end) end + defp mark_hydrated(socket, slot) do + assign(socket, :hydration_pending, MapSet.delete(socket.assigns.hydration_pending, slot)) + end + + defp atmospheric_loading?(pending) do + MapSet.member?(pending, :hrrr_path) or + MapSet.member?(pending, :narr_path) or + MapSet.member?(pending, :native_profile) + end + defp load_radar(contact) do import Ecto.Query @@ -336,6 +360,7 @@ defmodule MicrowavepropWeb.ContactLive.Show do surface_observations: weather.surface_observations, soundings: weather.soundings ) + |> mark_hydrated(:weather) |> refresh_computed() {:noreply, socket} @@ -350,7 +375,7 @@ defmodule MicrowavepropWeb.ContactLive.Show do solar end - {:noreply, assign(socket, :solar, solar)} + {:noreply, socket |> assign(:solar, solar) |> mark_hydrated(:solar)} end def handle_async(:hrrr_path, {:ok, hrrr_path}, socket) do @@ -363,6 +388,7 @@ defmodule MicrowavepropWeb.ContactLive.Show do socket = socket |> assign(contact: contact, hrrr: hrrr, hrrr_path: hrrr_path) + |> mark_hydrated(:hrrr_path) |> refresh_computed() {:noreply, socket} @@ -375,7 +401,10 @@ defmodule MicrowavepropWeb.ContactLive.Show do contact = if socket.assigns.can_enqueue, do: maybe_enqueue_narr(narr, contact), else: contact - {:noreply, assign(socket, contact: contact, narr: narr, narr_path: narr_path)} + {:noreply, + socket + |> assign(contact: contact, narr: narr, narr_path: narr_path) + |> mark_hydrated(:narr_path)} end def handle_async(:terrain, {:ok, terrain}, socket) do @@ -387,27 +416,28 @@ defmodule MicrowavepropWeb.ContactLive.Show do socket = socket |> assign(contact: contact, terrain: terrain) + |> mark_hydrated(:terrain) |> refresh_computed() {:noreply, socket} end def handle_async(:iemre, {:ok, iemre}, socket) do - {:noreply, assign(socket, :iemre, iemre)} + {:noreply, socket |> assign(:iemre, iemre) |> mark_hydrated(:iemre)} end def handle_async(:radar, {:ok, %{row: row, mechanism: mechanism}}, socket) do - {:noreply, assign(socket, radar: row, mechanism: mechanism)} + {:noreply, socket |> assign(radar: row, mechanism: mechanism) |> mark_hydrated(:radar)} end def handle_async(:native_profile, {:ok, native_profile}, socket) do - {:noreply, assign(socket, :native_profile, native_profile)} + {:noreply, socket |> assign(:native_profile, native_profile) |> mark_hydrated(:native_profile)} end def handle_async(slot, {:exit, reason}, socket) - when slot in [:weather, :solar, :hrrr_path, :narr_path, :native_profile, :terrain, :iemre] do + when slot in [:weather, :solar, :hrrr_path, :narr_path, :native_profile, :terrain, :iemre, :radar] do Logger.warning("contact hydrate task #{slot} exited: #{inspect(reason)}") - {:noreply, socket} + {:noreply, mark_hydrated(socket, slot)} end @impl true @@ -970,6 +1000,8 @@ defmodule MicrowavepropWeb.ContactLive.Show do phx-hook="ElevationProfile" phx-update="ignore" data-profile={Jason.encode!(@elevation_profile)} + data-station1={@contact.station1} + data-station2={@contact.station2} class="w-full h-96 border border-base-300 rounded-lg" > @@ -1120,13 +1152,18 @@ defmodule MicrowavepropWeb.ContactLive.Show do <% else %>