diff --git a/frontend/src/components/RugnuxPage.tsx b/frontend/src/components/RugnuxPage.tsx index 8af057b73..463fa019f 100644 --- a/frontend/src/components/RugnuxPage.tsx +++ b/frontend/src/components/RugnuxPage.tsx @@ -3,7 +3,7 @@ import {green, indigo, lime, orange, red, grey} from "@mui/material/colors"; import {keyframes} from "@mui/system"; import {useQuery} from "@tanstack/react-query"; import {getConfigRugnuxOptions, getRugnuxStatusOptions} from "../client/@tanstack/react-query.gen"; -import {rugnux_live_status, rugnux_live_step} from "../client"; +import {rugnux_live_status} from "../client"; import {useTokenRequired} from "../auth"; import EmptyState from "./EmptyState"; import RugnuxTriage from "./RugnuxTriage"; @@ -16,7 +16,6 @@ import RugnuxProgress from "./RugnuxProgress"; import {lastImageTime, ModelScores, StepTable, VerdictAlert} from "./RugnuxParts"; import BoltIcon from "@mui/icons-material/Bolt"; import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; -import CheckIcon from "@mui/icons-material/Check"; import {lazy, Suspense, useEffect, useRef, useState} from "react"; // The live page as one control-room screen: a dark stage the height of the window, edged in the colour of the @@ -110,34 +109,21 @@ function Later({text}: { text: string }) { return {text}; } -// The run's steps as a console - the running one large with its progress, the ones behind it as a list with -// their durations - filling the centre until there is a model to show. Their notes are in the Details fold. +// The step rugnux is on, large with its progress, filling the centre until there is a model to show; the +// steps behind it are on the strip under the bars. function RunConsole({s}: { s: rugnux_live_status }) { const running = s.state === "Running"; const current = running && s.steps.length > 0 && s.steps[s.steps.length - 1].duration_s === undefined ? s.steps[s.steps.length - 1] : undefined; - const done = current ? s.steps.slice(0, -1) : s.steps; - const row = (step: rugnux_live_step, i: number) => ( - - - {step.title} - - {step.duration_s !== undefined ? `${step.duration_s.toFixed(1)} s` : ""} - - - ); + if (!current) + return ; return ( - - {current && - now - - {current.title}{s.step_progress !== undefined && {(100 * s.step_progress).toFixed(0)} %} - - } - {s.steps.length === 0 && } - {done.map(row)} + + now + + {current.title}{s.step_progress !== undefined && {(100 * s.step_progress).toFixed(0)} %} + ); } diff --git a/frontend/src/components/RugnuxProgress.tsx b/frontend/src/components/RugnuxProgress.tsx index 827974b36..2daddc425 100644 --- a/frontend/src/components/RugnuxProgress.tsx +++ b/frontend/src/components/RugnuxProgress.tsx @@ -1,15 +1,16 @@ import {useEffect, useRef, useState} from "react"; import {Box, Tooltip, Typography} from "@mui/material"; import {keyframes} from "@mui/system"; -import {deepOrange, grey, indigo, lightBlue, lime} from "@mui/material/colors"; -import {rugnux_live_status} from "../client"; +import {deepOrange, grey, indigo, lime} from "@mui/material/colors"; +import {rugnux_live_status, rugnux_live_step} from "../client"; import {lastImageTime} from "./RugnuxParts"; // The detector's and rugnux's progress one under the other on one time axis (seconds since the first image): // the detector's bar fills as images arrive, up to the collection's expected end; rugnux's bar is its steps, // one segment each, as wide as the step took - the running one growing to "now" - so what takes how long is -// read off the bar, and the strip under the bars names every step with its duration. The last image and the -// deadline are lines across both bars. +// read off the bar. Each kind of step has a tag of a few letters and a colour of its own, the same on every +// pass and on the strip under the bars that gives each kind its time; the full titles are on hover. The last +// image and the deadline are lines across both bars. const stripes = keyframes` from { background-position: 0 0; } @@ -28,7 +29,38 @@ const runningSx = { "@media (prefers-reduced-motion: reduce)": {animation: "none"}, }; -const STEP_COLOURS = [indigo[400], lightBlue[700], indigo[600], lightBlue[900], indigo[300], lightBlue[500]]; +// The step's tag from its title; a title no row knows is its first word. +const TAGS: [RegExp, string][] = [ + [/^pre-?(scan|pass)/i, "Pre"], [/spot/i, "Spots"], [/index/i, "Idx"], [/integrat/i, "Int"], [/gpu analysis/i, "GPU"], + [/scaling/i, "Scale"], [/^merge/i, "Merge"], [/molecular replacement/i, "MR"], [/geometry refinement/i, "Geom"], + [/refinement/i, "Ref"], [/validation/i, "Val"], [/analys/i, "Chk"], [/writ/i, "Out"], [/distance/i, "Dist"], + [/doubled-cell/i, "Cell"], [/calibration/i, "Cal"], [/reading/i, "Read"], +]; +const tagOf = (title: string) => TAGS.find(([re]) => re.test(title))?.[1] ?? title.split(/[\s,]/)[0].slice(0, 6); + +// One colour per kind of step - Okabe-Ito, which keeps its kinds apart for colour-blind readers too, with +// its blue lightened for the dark stage; a kind not in the table takes the next free one. +const KIND_COLOURS: Record = { + Pre: "#56B4E9", Spots: "#E69F00", Idx: "#F0E442", Int: "#3C8DD6", GPU: "#3C8DD6", Scale: "#009E73", + Merge: "#009E73", MR: "#CC79A7", Ref: "#D55E00", Out: "#9E9E9E", +}; +const SPARE_COLOURS = ["#8DD3C7", "#BEBADA", "#FB8072", "#FDB462", "#B3DE69"]; +function kindColours(steps: rugnux_live_step[]) { + const colours: Record = {...KIND_COLOURS}; + let spare = 0; + for (const st of steps) { + const tag = tagOf(st.title); + if (!(tag in colours)) + colours[tag] = SPARE_COLOURS[spare++ % SPARE_COLOURS.length]; + } + return colours; +} +// dark text on the light colours +const inkOn = (hex: string) => { + const [r, g, b] = [1, 3, 5].map(i => parseInt(hex.slice(i, i + 2), 16)); + return 0.299 * r + 0.587 * g + 0.114 * b > 150 ? "rgba(0,0,0,.85)" : "#fff"; +}; + const BAR = 20; const GAP = 6; const AXIS = 16; @@ -85,6 +117,21 @@ function RugnuxProgress({s, deadline_s}: { s: rugnux_live_status, deadline_s: nu : s.cut ? `cut (${s.cut})` : `${s.steps.length} steps · ${seconds(s.elapsed_s)}`; const stepEnd = (i: number) => s.steps[i].duration_s === undefined ? s.elapsed_s : s.steps[i].started_s + s.steps[i].duration_s!; + const colours = kindColours(s.steps); + const tags = s.steps.map(st => tagOf(st.title)); + // consecutive steps of one kind (the passes) as one chip, with their time together + const kinds: { tag: string, titles: string[], seconds: number, open: boolean }[] = []; + s.steps.forEach((st, i) => { + const last = kinds[kinds.length - 1]; + const open = st.duration_s === undefined; + const duration = open ? s.elapsed_s - st.started_s : st.duration_s!; + if (last && last.tag === tags[i]) { + last.titles.push(st.title); + last.seconds += duration; + last.open = open; + } else + kinds.push({tag: tags[i], titles: [st.title], seconds: duration, open}); + }); return ( @@ -107,15 +154,18 @@ function RugnuxProgress({s, deadline_s}: { s: rugnux_live_status, deadline_s: nu {s.steps.map((st, i) => { const open = st.duration_s === undefined; const width = Math.max(pct(stepEnd(i)) - pct(st.started_s), 0.3); - const showTitle = width / 100 * laneWidth >= Math.min(st.title.length * 6.5 + 10, 60); + const colour = colours[tags[i]]; + // a pass after a pass of the same kind: a thin divider, the tag only on the first + const samePass = i > 0 && tags[i - 1] === tags[i]; + const showTag = !samePass && width / 100 * laneWidth >= tags[i].length * 8 + 14; return ( - {showTitle && - {st.title} + {showTag && + {tags[i]} } @@ -154,25 +204,24 @@ function RugnuxProgress({s, deadline_s}: { s: rugnux_live_status, deadline_s: nu - {/* the steps, each with its duration */} - {s.steps.length > 0 && + {/* the kinds of step, each with its time */} + {kinds.length > 0 && - {s.steps.map((st, i) => { - const open = st.duration_s === undefined; - return ( - ( + + - - {st.title} + + {k.tag} - {!open ? seconds(st.duration_s!) - : running && i === s.steps.length - 1 && s.step_progress !== undefined ? `${(100 * s.step_progress).toFixed(0)} %` : ""} + {k.open && running && i === kinds.length - 1 && s.step_progress !== undefined + ? `${(100 * s.step_progress).toFixed(0)} %` : seconds(k.seconds)} - ); - })} + + ))} } );