feat(studio): centralize chart styling and formatting
This commit is contained in:
parent
d66bc2760b
commit
1ae8fb402b
5 changed files with 86 additions and 37 deletions
|
|
@ -11,7 +11,15 @@ import { ChartAverageIcon } from "@hugeicons/core-free-icons";
|
|||
import { HugeiconsIcon } from "@hugeicons/react";
|
||||
import type { ReactElement } from "react";
|
||||
import { CartesianGrid, Line, LineChart, XAxis, YAxis } from "recharts";
|
||||
import { formatMetric, formatStepTick, placeholderEvalData } from "./utils";
|
||||
import {
|
||||
CHART_CONTAINER_CLASS,
|
||||
DEFAULT_CHART_MARGIN,
|
||||
DEFAULT_Y_AXIS_WIDTH,
|
||||
formatAxisMetric,
|
||||
formatMetric,
|
||||
formatStepTick,
|
||||
placeholderEvalData,
|
||||
} from "./utils";
|
||||
|
||||
const evalLossConfig = {
|
||||
loss: { label: "Eval Loss", color: "#ef4444" },
|
||||
|
|
@ -33,22 +41,17 @@ export function EvalLossChartCard({
|
|||
return (
|
||||
<Card data-tour="studio-eval-loss" size="sm">
|
||||
<CardHeader>
|
||||
<CardTitle
|
||||
className={`text-sm pl-1${data.length > 0 ? "" : " text-muted-foreground"}`}
|
||||
>
|
||||
<CardTitle className={`text-sm${data.length > 0 ? "" : " text-muted-foreground"}`}>
|
||||
Eval Loss
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{data.length > 0 ? (
|
||||
<ChartContainer
|
||||
config={evalLossConfig}
|
||||
className="-ml-3 h-[220px] w-full"
|
||||
>
|
||||
<ChartContainer config={evalLossConfig} className={CHART_CONTAINER_CLASS}>
|
||||
<LineChart
|
||||
data={data}
|
||||
accessibilityLayer={true}
|
||||
margin={{ left: 0, right: 8 }}
|
||||
margin={DEFAULT_CHART_MARGIN}
|
||||
>
|
||||
<CartesianGrid vertical={false} strokeDasharray="3 3" />
|
||||
<XAxis
|
||||
|
|
@ -71,10 +74,11 @@ export function EvalLossChartCard({
|
|||
allowDataOverflow={true}
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
tickMargin={4}
|
||||
tickMargin={8}
|
||||
tickCount={5}
|
||||
fontSize={10}
|
||||
width={80}
|
||||
tickFormatter={(value) => formatMetric(Number(value))}
|
||||
width={DEFAULT_Y_AXIS_WIDTH}
|
||||
tickFormatter={(value) => formatAxisMetric(Number(value))}
|
||||
/>
|
||||
<ChartTooltip
|
||||
content={
|
||||
|
|
@ -106,12 +110,12 @@ export function EvalLossChartCard({
|
|||
<div className="relative">
|
||||
<ChartContainer
|
||||
config={evalLossConfig}
|
||||
className="-ml-3 h-[220px] w-full blur"
|
||||
className={`${CHART_CONTAINER_CLASS} blur`}
|
||||
>
|
||||
<LineChart
|
||||
data={placeholderEvalData}
|
||||
accessibilityLayer={true}
|
||||
margin={{ left: 0, right: 8 }}
|
||||
margin={DEFAULT_CHART_MARGIN}
|
||||
>
|
||||
<CartesianGrid vertical={false} strokeDasharray="3 3" />
|
||||
<XAxis
|
||||
|
|
@ -127,9 +131,10 @@ export function EvalLossChartCard({
|
|||
<YAxis
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
tickMargin={4}
|
||||
tickMargin={8}
|
||||
tickCount={5}
|
||||
fontSize={10}
|
||||
width={40}
|
||||
width={DEFAULT_Y_AXIS_WIDTH}
|
||||
/>
|
||||
<Line
|
||||
type="monotone"
|
||||
|
|
|
|||
|
|
@ -12,6 +12,10 @@ import { CartesianGrid, Line, LineChart, XAxis, YAxis } from "recharts";
|
|||
import type { ScaleMode } from "./types";
|
||||
import {
|
||||
CHART_SYNC_ID,
|
||||
CHART_CONTAINER_CLASS,
|
||||
DEFAULT_CHART_MARGIN,
|
||||
DEFAULT_Y_AXIS_WIDTH,
|
||||
formatAxisMetric,
|
||||
formatMetric,
|
||||
formatStepTick,
|
||||
fromLog1p,
|
||||
|
|
@ -45,19 +49,16 @@ export function GradNormChartCard({
|
|||
return (
|
||||
<Card size="sm">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-sm pl-1">Gradient Norm</CardTitle>
|
||||
<CardTitle className="text-sm">Gradient Norm</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ChartContainer
|
||||
config={gradNormConfig}
|
||||
className="-ml-3 h-[220px] w-full"
|
||||
>
|
||||
<ChartContainer config={gradNormConfig} className={CHART_CONTAINER_CLASS}>
|
||||
<LineChart
|
||||
data={data}
|
||||
syncId={CHART_SYNC_ID}
|
||||
syncMethod="value"
|
||||
accessibilityLayer={true}
|
||||
margin={{ left: 0, right: 8 }}
|
||||
margin={DEFAULT_CHART_MARGIN}
|
||||
>
|
||||
<CartesianGrid vertical={false} strokeDasharray="3 3" />
|
||||
<XAxis
|
||||
|
|
@ -80,16 +81,17 @@ export function GradNormChartCard({
|
|||
allowDataOverflow={true}
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
tickMargin={4}
|
||||
tickMargin={8}
|
||||
tickCount={5}
|
||||
fontSize={10}
|
||||
width={80}
|
||||
width={DEFAULT_Y_AXIS_WIDTH}
|
||||
tickFormatter={(value) => {
|
||||
const num = Number(value);
|
||||
if (!Number.isFinite(num)) {
|
||||
return "0";
|
||||
}
|
||||
const shown = scale === "log" ? fromLog1p(num) : num;
|
||||
return formatMetric(shown);
|
||||
return formatAxisMetric(shown);
|
||||
}}
|
||||
/>
|
||||
<ChartTooltip
|
||||
|
|
|
|||
|
|
@ -10,7 +10,14 @@ import type { ChartConfig } from "@/components/ui/chart";
|
|||
import type { ReactElement } from "react";
|
||||
import { CartesianGrid, Line, LineChart, XAxis, YAxis } from "recharts";
|
||||
import type { ScaleMode } from "./types";
|
||||
import { CHART_SYNC_ID, formatStepTick, fromLog1p } from "./utils";
|
||||
import {
|
||||
CHART_CONTAINER_CLASS,
|
||||
CHART_SYNC_ID,
|
||||
DEFAULT_CHART_MARGIN,
|
||||
DEFAULT_Y_AXIS_WIDTH,
|
||||
formatStepTick,
|
||||
fromLog1p,
|
||||
} from "./utils";
|
||||
|
||||
const lrConfig = {
|
||||
displayLr: { label: "LR", color: "#8b5cf6" },
|
||||
|
|
@ -40,16 +47,16 @@ export function LearningRateChartCard({
|
|||
return (
|
||||
<Card size="sm">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-sm pl-1">Learning Rate</CardTitle>
|
||||
<CardTitle className="text-sm">Learning Rate</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ChartContainer config={lrConfig} className="-ml-1.5 h-[220px] w-full">
|
||||
<ChartContainer config={lrConfig} className={CHART_CONTAINER_CLASS}>
|
||||
<LineChart
|
||||
data={data}
|
||||
syncId={CHART_SYNC_ID}
|
||||
syncMethod="value"
|
||||
accessibilityLayer={true}
|
||||
margin={{ left: 0, right: 8 }}
|
||||
margin={DEFAULT_CHART_MARGIN}
|
||||
>
|
||||
<CartesianGrid vertical={false} strokeDasharray="3 3" />
|
||||
<XAxis
|
||||
|
|
@ -72,9 +79,10 @@ export function LearningRateChartCard({
|
|||
allowDataOverflow={true}
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
tickMargin={4}
|
||||
tickMargin={8}
|
||||
tickCount={5}
|
||||
fontSize={10}
|
||||
width={52}
|
||||
width={DEFAULT_Y_AXIS_WIDTH}
|
||||
tickFormatter={(value) => {
|
||||
const num = Number(value);
|
||||
if (!Number.isFinite(num)) {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,10 @@ import {
|
|||
import type { ScaleMode } from "./types";
|
||||
import {
|
||||
CHART_SYNC_ID,
|
||||
CHART_CONTAINER_CLASS,
|
||||
DEFAULT_CHART_MARGIN,
|
||||
DEFAULT_Y_AXIS_WIDTH,
|
||||
formatAxisMetric,
|
||||
formatMetric,
|
||||
formatStepTick,
|
||||
fromLog1p,
|
||||
|
|
@ -65,16 +69,16 @@ export function TrainingLossChartCard({
|
|||
return (
|
||||
<Card data-tour="studio-training-loss" size="sm">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-sm pl-1">Training Loss</CardTitle>
|
||||
<CardTitle className="text-sm">Training Loss</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ChartContainer config={lossConfig} className="-ml-3 h-[220px] w-full">
|
||||
<ChartContainer config={lossConfig} className={CHART_CONTAINER_CLASS}>
|
||||
<LineChart
|
||||
data={data}
|
||||
syncId={CHART_SYNC_ID}
|
||||
syncMethod="value"
|
||||
accessibilityLayer={true}
|
||||
margin={{ left: 0, right: 8 }}
|
||||
margin={DEFAULT_CHART_MARGIN}
|
||||
>
|
||||
<CartesianGrid vertical={false} strokeDasharray="3 3" />
|
||||
<XAxis
|
||||
|
|
@ -97,16 +101,17 @@ export function TrainingLossChartCard({
|
|||
allowDataOverflow={true}
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
tickMargin={4}
|
||||
tickMargin={8}
|
||||
tickCount={5}
|
||||
fontSize={10}
|
||||
width={80}
|
||||
width={DEFAULT_Y_AXIS_WIDTH}
|
||||
tickFormatter={(value) => {
|
||||
const num = Number(value);
|
||||
if (!Number.isFinite(num)) {
|
||||
return "0";
|
||||
}
|
||||
const shown = scale === "log" ? fromLog1p(num) : num;
|
||||
return formatMetric(shown);
|
||||
return formatAxisMetric(shown);
|
||||
}}
|
||||
/>
|
||||
<ChartTooltip
|
||||
|
|
|
|||
|
|
@ -3,6 +3,9 @@ import type { LossHistoryItem, OutlierMode, SmoothedLossItem } from "./types";
|
|||
export const CHART_SYNC_ID = "train-metrics-sync";
|
||||
export const MAX_RENDER_POINTS = 800;
|
||||
export const DEFAULT_VISIBLE_POINTS = 160;
|
||||
export const CHART_CONTAINER_CLASS = "h-[220px] w-full";
|
||||
export const DEFAULT_CHART_MARGIN = { top: 4, right: 8, bottom: 0, left: 4 };
|
||||
export const DEFAULT_Y_AXIS_WIDTH = 41;
|
||||
const TRAILING_ZEROES_RE = /\.?0+$/;
|
||||
const NEGATIVE_ZERO_RE = /^-0$/;
|
||||
|
||||
|
|
@ -50,6 +53,32 @@ export function formatMetric(value: number): string {
|
|||
.replace(NEGATIVE_ZERO_RE, "0");
|
||||
}
|
||||
|
||||
export function formatAxisMetric(value: number): string {
|
||||
if (!Number.isFinite(value)) {
|
||||
return "0";
|
||||
}
|
||||
|
||||
const abs = Math.abs(value);
|
||||
let decimals = 4;
|
||||
|
||||
if (abs >= 1000) {
|
||||
decimals = 0;
|
||||
} else if (abs >= 100) {
|
||||
decimals = 1;
|
||||
} else if (abs >= 1) {
|
||||
decimals = 3;
|
||||
} else if (abs >= 0.01) {
|
||||
decimals = 4;
|
||||
} else {
|
||||
decimals = 5;
|
||||
}
|
||||
|
||||
return value
|
||||
.toFixed(decimals)
|
||||
.replace(TRAILING_ZEROES_RE, "")
|
||||
.replace(NEGATIVE_ZERO_RE, "0");
|
||||
}
|
||||
|
||||
export function formatStepTick(value: number): string {
|
||||
if (value >= 1_000_000) {
|
||||
return `${(value / 1_000_000).toFixed(1)}M`;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue