Joel Woodfield
Add ... to dataset options
5ea577f
Raw
History Blame Contribute Delete
9.54 kB
import { useEffect, useState } from "react";
import ReactMarkdown from "react-markdown";
import usageMarkdown from "./usage.md?raw";
import {
Button,
Card,
Dropdown,
FileUploader,
InputField,
InputSection,
Tabs,
} from "@elvis/ui";
import { OKABE_ITO_COLORS } from "./colors";
import { CLASSIFIERS } from "./classifiers";
import type { CsvSettings, ModelSettings } from "./types";
import { DATASETS } from "./datasets";
interface SidebarProps {
pointLabel: string;
setPointLabel: (label: string) => void;
setCanAddPoints: (canAddPoints: boolean) => void;
undoDataPoint: () => void;
clearDataPoints: () => void;
modelSettings: ModelSettings;
setModelSettings: (settings: ModelSettings) => void;
csvSettings: CsvSettings;
onGetDecisionBoundary: () => void;
onCsvUpload: (file: File, settings?: CsvSettings) => void;
onCsvSettingsChange: (settings: CsvSettings) => void;
csvError: string | null;
onCsvExport: () => void;
onSvgExport: () => void;
}
export default function Sidebar({
pointLabel,
setPointLabel,
setCanAddPoints,
undoDataPoint,
clearDataPoints,
modelSettings,
setModelSettings,
csvSettings,
onGetDecisionBoundary,
onCsvUpload,
onCsvSettingsChange,
csvError,
onCsvExport,
onSvgExport,
}: SidebarProps) {
const tabs = ["Data", "Model", "Export", "Usage"] as const;
const [activeTab, setActiveTab] = useState<(typeof tabs)[number]>("Data");
const presetOptions = DATASETS.map((dataset) => dataset.name) as string[];
const customDatasetOptions = ["Manual...", "Upload..."] as string[];
const [datasetOption, setDatasetOption] = useState<string>("Manual...");
function isPresetOption(option: string) {
return presetOptions.includes(option);
}
function onDatasetOptionChange(option: string) {
setDatasetOption(option);
if (isPresetOption(option)) {
loadPresetDataset(option);
}
}
async function loadPresetDataset(datasetName: string) {
const dataset = DATASETS.find((d) => d.name === datasetName);
if (!dataset) {
return;
}
const response = await fetch(dataset.url);
const blob = await response.blob();
const file = new File([blob], `${dataset.name}.csv`, { type: "text/csv" });
const presetCsvSettings = {
...csvSettings,
inputColumns: dataset.inputColumns,
outputColumn: dataset.outputColumn,
};
onCsvUpload(file, presetCsvSettings);
}
useEffect(() => {
clearDataPoints();
setCanAddPoints(datasetOption === "Manual...");
}, [datasetOption]);
return (
<Card className="bg-white flex flex-col h-full min-w-0 min-h-0 overflow-auto border border-gray-200 rounded p-6 gap-4">
<Tabs tabs={tabs} activeTab={activeTab} onChange={setActiveTab} />
{ /* Tab content */}
<div className="flex flex-col gap-8 min-h-0 overflow-auto">
{ activeTab === "Data" && (
<>
<InputSection label="Dataset">
<Dropdown
label="Input"
options={customDatasetOptions}
groupedOptions={[{ label: "Built-in...", options: presetOptions }]}
activeOption={datasetOption}
onChange={onDatasetOptionChange}
/>
{ datasetOption === "Manual..." && (
<>
<Dropdown
label="Point Label"
options={OKABE_ITO_COLORS.map(color => color.name)}
activeOption={pointLabel}
onChange={(option: string) => setPointLabel(option)}
/>
<div className="grid grid-cols-2 gap-2">
<Button
label="Undo"
onClick={undoDataPoint}
/>
<Button
label="Clear"
onClick={clearDataPoints}
/>
</div>
</>
)}
{ datasetOption === "Upload..." && (
<FileUploader label="Upload CSV" onFileUpload={onCsvUpload} />
)}
{ (datasetOption === "Upload..." || isPresetOption(datasetOption)) && (
<>
<div className="grid grid-cols-[minmax(0,2fr)_minmax(0,1fr)] gap-2">
<InputField
label="Input Columns"
value={csvSettings.inputColumns}
onChange={(value: string) => {onCsvSettingsChange({ ...csvSettings, inputColumns: value })}}
/>
<InputField
label="Output Column"
value={csvSettings.outputColumn}
onChange={(value: string) => {onCsvSettingsChange({ ...csvSettings, outputColumn: value })}}
/>
</div>
</>
)}
</InputSection>
{ (datasetOption === "Upload..." || isPresetOption(datasetOption)) && (
<>
<InputSection label="Preprocessing">
<Dropdown
label="Normalizer"
options={["None", "MinMax", "Standard"]}
activeOption={csvSettings.normalizerType}
onChange={(option: string) => {onCsvSettingsChange({ ...csvSettings, normalizerType: option })}}
/>
<InputField
label="Normal noise standard deviation"
value={csvSettings.normalNoiseStd}
onChange={(value: string) => {onCsvSettingsChange({ ...csvSettings, normalNoiseStd: value })}}
/>
</InputSection>
<InputSection label="Plotting">
<Dropdown
label="Projection"
options={["Coordinates", "PCA"]}
activeOption={csvSettings.projectionType}
onChange={(option: string) => {onCsvSettingsChange({ ...csvSettings, projectionType: option })}}
/>
{csvSettings.projectionType === "Coordinates" && (
<div className="grid grid-cols-2 gap-2">
<InputField
label="X1 Column"
value={csvSettings.x1Column || ""}
onChange={(value: string) => {onCsvSettingsChange({ ...csvSettings, x1Column: value })}}
/>
<InputField
label="X2 Column"
value={csvSettings.x2Column || ""}
onChange={(value: string) => {onCsvSettingsChange({ ...csvSettings, x2Column: value })}}
/>
</div>
)}
</InputSection>
{csvError && (
<InputSection label="Error">
<p className="text-red-600 font-semibold">CSV Error: {csvError}</p>
</InputSection>
)}
</>
)}
</>
)}
{ activeTab === "Model" && (
<>
<InputSection label="Classifier Options">
<Dropdown
label="Type"
options={CLASSIFIERS}
activeOption={modelSettings.type}
onChange={(option: string) => {
setModelSettings({ ...modelSettings, type: option });
}}
/>
<InputField
label="Arguments"
value={modelSettings.arguments}
onChange={(value: string) => {
setModelSettings({ ...modelSettings, arguments: value });
}}
/>
</InputSection>
<Button
label="Get Decision Boundary"
onClick={onGetDecisionBoundary}
/>
</>
)}
{ activeTab === "Export" && (
<div className="flex flex-col gap-4">
<Button
label="Dataset as CSV"
onClick={onCsvExport}
/>
<Button
label="Plot as SVG"
onClick={onSvgExport}
/>
{/* <Button
label="Python code"
/> */}
</div>
)}
{ activeTab === "Usage" && (
<div className="prose leading-loose overflow-auto">
<ReactMarkdown
components={{
h1: ({ children }) => <h1 className="text-2xl font-bold mt-12 mb-2 first:mt-4">{children}</h1>,
h2: ({ children }) => <h2 className="text-xl font-semibold mt-12 mb-2 first:mt-4">{children}</h2>,
h3: ({ children }) => <h3 className="text-lg font-semibold mt-12 mb-2 first:mt-4">{children}</h3>,
p: ({ children }) => <p className="leading-6 mb-3 last:mb-0">{children}</p>,
ul: ({ children }) => <ul className="list-disc pl-5 mb-3">{children}</ul>,
ol: ({ children }) => <ol className="list-decimal pl-5 mb-3">{children}</ol>,
li: ({ children }) => <li className="mb-1">{children}</li>,
code: ({ children }) => <code className="bg-gray-200 px-1 py-0.5 rounded">{children}</code>
}}
>
{usageMarkdown}
</ReactMarkdown>
</div>
)}
</div>
</Card>
)
}