Skip to content

Commit

Permalink
Add viridis generated chart themes
Browse files Browse the repository at this point in the history
  • Loading branch information
raix committed Jul 14, 2024
1 parent 6dd5a8f commit d373f65
Show file tree
Hide file tree
Showing 10 changed files with 657 additions and 19 deletions.
56 changes: 56 additions & 0 deletions generate-colors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import fs from "node:fs";
import path from "node:path";
import { type Color, Palette, type PaletteName } from "viridis";

const outputCssFile = path.join(__dirname, "ui", "theme", "chartThemes.css");
const outputTsFile = path.join(__dirname, "ui", "theme", "chartThemes.ts");

const scale = 9;
const totalScale = 12;

let outputCss = "";

for (const paletteName of Object.keys(Palette) as PaletteName[]) {
let light = "";
let dark = "";

light += `.chart-${paletteName.toLowerCase()} {\n`;
dark += `.dark.chart-${paletteName.toLowerCase()} {\n`;

for (let darkIndex = 0; darkIndex < totalScale; darkIndex++) {
const lightIndex = darkIndex - (totalScale - scale);
const color = Palette[paletteName].getColor(darkIndex + 1, 1, totalScale);
if (darkIndex > 0 && darkIndex < scale) {
dark += ` --chart-${darkIndex + 1}: ${color.toString()}; /* ${isLight(color.getContrastingColor()) ? "light" : "dark"} */\n`;
}
if (lightIndex >= 0 && lightIndex < scale) {
light += ` --chart-${lightIndex + 1}: ${color.toString()}; /* ${isLight(color.getContrastingColor()) ? "light" : "dark"} */\n`;
}
}
light += "}\n\n";
dark += "}\n\n";

outputCss += light;
outputCss += dark;
}

const outputTs = `import "./chartThemes.css";
export type ChartTheme =
${Object.keys(Palette)
.map((paletteName) => ` | "chart-${paletteName.toLowerCase()}"`)
.join("\n")};
export const chartThemes: ChartTheme[] = [
${Object.keys(Palette)
.map((paletteName) => ` "chart-${paletteName.toLowerCase()}"`)
.join(",\n")}
];
`;

fs.writeFileSync(outputCssFile, outputCss, "utf-8");
fs.writeFileSync(outputTsFile, outputTs, "utf-8");

function isLight(color: Color) {
return color.red + color.green + color.blue > 382;
}
27 changes: 27 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"tailwindcss-animate": "^1.0.7",
"tailwindcss-react-aria-components": "^1.1.3",
"typescript": "^5.5.3",
"viridis": "^1.1.4",
"zod": "^3.23.8"
}
}
1 change: 1 addition & 0 deletions styles/index.css
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
@import "../ui/theme/themes.css";
@import "../ui/theme/chartThemes.css";
@import "./rspress.css";
2 changes: 1 addition & 1 deletion ui/charts/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ function getColorIndex(index: number, length: number) {
return (length === 1 ? 1 : Math.ceil((index * 8) / (length - 1)) + 1) as ColorIndex;
}

const getChartColorHSL = (colorIndex: ColorIndex) => `hsl(var(--chart-${colorIndex}))`;
const getChartColorHSL = (colorIndex: ColorIndex) => `var(--chart-${colorIndex})`;

export function getChartColor(index: number, length: number) {
return getChartColorHSL(getColorIndex(index, length));
Expand Down
19 changes: 13 additions & 6 deletions ui/theme/ThemeContext.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { type ReactNode, createContext, useContext, useEffect, useMemo, useState } from "react";
import { type Color, parseColor } from "react-aria-components";
import type { ChartTheme } from "./chartThemes";
import type { Theme, ThemePanelOpacity, ThemeRadius } from "./theme";

export type ThemeContextType = {
Expand All @@ -11,6 +12,8 @@ export type ThemeContextType = {
setPanelOpacity: (opacity: ThemePanelOpacity) => void;
hue: Color;
setHue: (hue: Color) => void;
chartTheme: ChartTheme;
setChartTheme: (theme: ChartTheme) => void;
};

export const ThemeContext = createContext<ThemeContextType | null>(null);
Expand All @@ -19,8 +22,9 @@ const Provider = ThemeContext.Provider;
const defaultTheme = "theme-neutral";
const defaultThemeRadius = "radius-050";
const defaultPanelOpacity = "panel-solid";
const defaultChartTheme: ChartTheme = "chart-viridis";

document.documentElement.classList.add(defaultTheme, defaultThemeRadius);
document.documentElement.classList.add(defaultTheme, defaultThemeRadius, defaultChartTheme);

type ThemeProviderProps = {
children: ReactNode;
Expand All @@ -31,14 +35,15 @@ export function ThemeProvider({ children }: Readonly<ThemeProviderProps>) {
const [themeRadius, setThemeRadius] = useState<ThemeRadius>(defaultThemeRadius);
const [panelOpacity, setPanelOpacity] = useState<ThemePanelOpacity>(defaultPanelOpacity);
const [hue, setHue] = useState<Color>(parseColor("hsl(0, 100%, 50%)"));
const [chartTheme, setChartTheme] = useState<ChartTheme>(defaultChartTheme);

useEffect(() => {
const oldClasses = Array.from(document.documentElement.classList).filter(
(c) => c.startsWith("theme-") || c.startsWith("radius-") || c.startsWith("panel-")
(c) => c.startsWith("theme-") || c.startsWith("radius-") || c.startsWith("panel-") || c.startsWith("chart-")
);
document.documentElement.classList.remove(...oldClasses);
document.documentElement.classList.add(theme, themeRadius, panelOpacity);
}, [theme, themeRadius, panelOpacity]);
document.documentElement.classList.add(theme, themeRadius, panelOpacity, chartTheme);
}, [theme, themeRadius, panelOpacity, chartTheme]);

const context: ThemeContextType = useMemo(
() => ({
Expand All @@ -49,9 +54,11 @@ export function ThemeProvider({ children }: Readonly<ThemeProviderProps>) {
panelOpacity,
setPanelOpacity,
hue,
setHue
setHue,
chartTheme,
setChartTheme
}),
[theme, themeRadius, panelOpacity, hue]
[theme, themeRadius, panelOpacity, hue, chartTheme]
);

return <Provider value={context}>{children}</Provider>;
Expand Down
54 changes: 52 additions & 2 deletions ui/theme/ThemeSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,22 @@ import {
SliderOutput,
SliderTrack
} from "react-aria-components";
import { type ChartTheme, chartThemes } from "./chartThemes";
import { type Theme, type ThemePanelOpacity, type ThemeRadius, themes } from "./theme";

export function ThemeModeSelector() {
const { theme, setTheme, themeRadius, setThemeRadius, panelOpacity, setPanelOpacity, hue, setHue } =
useThemeContext();
const {
theme,
setTheme,
themeRadius,
setThemeRadius,
panelOpacity,
setPanelOpacity,
hue,
setHue,
chartTheme,
setChartTheme
} = useThemeContext();

return (
<div className="flex h-full w-full flex-col gap-4 px-2">
Expand All @@ -26,6 +37,8 @@ export function ThemeModeSelector() {
<ThemePicker theme={theme} onChange={setTheme} />
{/*<ThemeHueSelector label="Hue" channel="hue" value={hue} onChange={setHue} defaultValue="hsl(0, 100%, 50%)" /> */}
<Separator orientation="horizontal" />
<ChartThemePicker chartTheme={chartTheme} onChange={setChartTheme} />
<Separator orientation="horizontal" />
<ThemeRadiusPicker radius={themeRadius} onChange={setThemeRadius} />
<Separator orientation="horizontal" />
<ThemePanelOpacityPicker opacity={panelOpacity} onChange={setPanelOpacity} />
Expand Down Expand Up @@ -212,3 +225,40 @@ function ThemeHueSelector({ label, ...props }: Readonly<ThemeHueSelectorProps>)
</ColorSlider>
);
}

type ChartThemePickerProps = {
chartTheme: ChartTheme;
onChange: (theme: ChartTheme) => void;
};

function ChartThemePicker({ chartTheme, onChange }: Readonly<ChartThemePickerProps>) {
return (
<>
<span className="text-sm">Chart</span>
<div className="grid w-full grid-cols-5 gap-1">
{chartThemes.map((t) => (
<Button
key={t}
onPress={() => onChange(t)}
variant={"outline"}
size={"sm"}
className={`flex h-16 flex-col items-center ${chartTheme === t ? "border-2 border-primary" : ""}`}
>
<div className={`${t} grid w-fit shrink-0 grid-cols-3`}>
<div className="h-2 w-2" style={{ background: "var(--chart-1)" }} />
<div className="h-2 w-2" style={{ background: "var(--chart-2)" }} />
<div className="h-2 w-2" style={{ background: "var(--chart-3)" }} />
<div className="h-2 w-2" style={{ background: "var(--chart-4)" }} />
<div className="h-2 w-2" style={{ background: "var(--chart-5)" }} />
<div className="h-2 w-2" style={{ background: "var(--chart-6)" }} />
<div className="h-2 w-2" style={{ background: "var(--chart-7)" }} />
<div className="h-2 w-2" style={{ background: "var(--chart-8)" }} />
<div className="h-2 w-2" style={{ background: "var(--chart-9)" }} />
</div>
<div className="text-xs">{t.slice(6)}</div>
</Button>
))}
</div>
</>
);
}
Loading

0 comments on commit d373f65

Please sign in to comment.