Record flags in history and draw them as steps
A bool was excluded from the history as "not a measurement", so a true/false port had no curve in the node panel and none on an edge — only the word. It is recorded as 0/1 now and drawn as steps, since a bezier through two states slopes through readings that never happened. The axis is pinned to 0..1, so a flag that was never on sits at the floor rather than mid-box. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -28,15 +28,17 @@ const READOUT_DIGITS = 4
|
||||
* that never moved has no span to divide by, and one spanning decades is only
|
||||
* legible once the exponent is what varies.
|
||||
*/
|
||||
function shape(points: HistoryPoint[]) {
|
||||
function shape(points: HistoryPoint[], step = false) {
|
||||
const values = points.map((point) => point.value)
|
||||
const low = Math.min(...values)
|
||||
const high = Math.max(...values)
|
||||
// Logs need every reading on the same side of zero.
|
||||
const logged = low > 0 && high / low >= LOG_RATIO
|
||||
const logged = !step && low > 0 && high / low >= LOG_RATIO
|
||||
const project = (value: number) => (logged ? Math.log10(value) : value)
|
||||
const floor = project(low)
|
||||
const span = project(high) - floor
|
||||
// A flag's axis is the two values it has, so one that was never on sits at
|
||||
// the floor rather than in the middle of the box the way a flat curve does.
|
||||
const floor = step ? 0 : project(low)
|
||||
const span = step ? 1 : project(high) - floor
|
||||
|
||||
const y = (value: number) =>
|
||||
span === 0
|
||||
@@ -46,10 +48,15 @@ function shape(points: HistoryPoint[]) {
|
||||
points.length === 1 ? 100 : (index / (points.length - 1)) * 100
|
||||
|
||||
const line = points
|
||||
.map(
|
||||
(point, index) =>
|
||||
`${index ? "L" : "M"}${x(index).toFixed(2)},${y(point.value).toFixed(2)}`,
|
||||
)
|
||||
.map((point, index) => {
|
||||
const at = `${x(index).toFixed(2)},${y(point.value).toFixed(2)}`
|
||||
if (!index) return `M${at}`
|
||||
// A flag holds its last value until it changes, so it turns a corner
|
||||
// rather than sloping through readings it never took.
|
||||
return step
|
||||
? `L${x(index).toFixed(2)},${y(points[index - 1].value).toFixed(2)} L${at}`
|
||||
: `L${at}`
|
||||
})
|
||||
.join(" ")
|
||||
|
||||
return {
|
||||
@@ -79,6 +86,7 @@ export function Sparkline({
|
||||
height = "h-8",
|
||||
dot = true,
|
||||
readout = true,
|
||||
bool = false,
|
||||
}: {
|
||||
points: HistoryPoint[]
|
||||
/** The token the curve, its area and the dot are drawn in. */
|
||||
@@ -89,9 +97,11 @@ export function Sparkline({
|
||||
dot?: boolean
|
||||
/** The current value and the range, beside the curve. */
|
||||
readout?: boolean
|
||||
/** A flag: drawn as steps between 0 and 1, and read as true/false. */
|
||||
bool?: boolean
|
||||
}) {
|
||||
const id = useId()
|
||||
const { low, high, line, area, end } = shape(points)
|
||||
const { low, high, line, area, end } = shape(points, bool)
|
||||
|
||||
return (
|
||||
// The gap leaves the live dot room to sit on the last reading without
|
||||
@@ -181,10 +191,15 @@ export function Sparkline({
|
||||
{readout ? (
|
||||
<div className="min-w-24 shrink-0 whitespace-nowrap text-right font-mono text-xs leading-tight">
|
||||
<div className="font-medium">
|
||||
{si(points[points.length - 1].value, READOUT_DIGITS)}
|
||||
{bool
|
||||
? points[points.length - 1].value
|
||||
? "true"
|
||||
: "false"
|
||||
: si(points[points.length - 1].value, READOUT_DIGITS)}
|
||||
</div>
|
||||
{/* A series that never moved has no range worth repeating. */}
|
||||
{low === high ? null : (
|
||||
{/* A series that never moved has no range worth repeating, and
|
||||
"false–true" is not a range anyone reads. */}
|
||||
{low === high || bool ? null : (
|
||||
<div className="text-muted-foreground">
|
||||
{si(low, READOUT_DIGITS)}–{si(high, READOUT_DIGITS)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user