Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,7 @@
science/data
*.env
/.idea
/science/src/iasa.egg-info/dependency_links.txt
/science/src/iasa.egg-info/PKG-INFO
/science/src/iasa.egg-info/SOURCES.txt
/science/src/iasa.egg-info/top_level.txt
10 changes: 10 additions & 0 deletions client/src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@
--color-data-pink: var(--data-pink);
--color-data-purple: var(--data-purple);
--color-data-blue: var(--data-blue);

--color-compass-blue: var(--compass-blue);
--color-compass-sand: var(--compass-sand);
--color-compass-slate: var(--compass-slate);
--color-compass-body: var(--compass-body);
}

:root {
Expand Down Expand Up @@ -111,6 +116,11 @@
--data-pink: #ec8d82;
--data-purple: #832da4;
--data-blue: #4599df;

--compass-blue: #1b4f72;
--compass-sand: #d9d2c5;
--compass-slate: #7a7a80;
--compass-body: #3d3d40;
}

[data-slot="skeleton"].custom-skeleton-pulse,
Expand Down
14 changes: 14 additions & 0 deletions client/src/app/guided-exploration/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { ScenarioDashboardCrossLink } from "@/components/cross-links/scenario-dashboard-cross-link";

export default function GuidedExplorationPageLayout({ children }: { children: React.ReactNode }) {
return (
<>
{children}
<div className="flex w-full justify-center bg-white">
<div className="content-container py-20">
<ScenarioDashboardCrossLink />
</div>
</div>
</>
);
}
10 changes: 2 additions & 8 deletions client/src/app/guided-exploration/page.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
import ComingSoon from "@/containers/coming-soon-container";
import { Navbar } from "@/components/layout/navbar/navbar";
import { GuidedExplorationPageContainer } from "@/containers/guided-exploration-container";

export default function GuidedExplorationPage() {
return (
<div className="flex flex-1 flex-col">
<Navbar theme="light" sheetTheme="white" />
<ComingSoon />
</div>
);
return <GuidedExplorationPageContainer />;
}
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,9 @@ export function ComboboxVariableSelect({
<Button
variant="outline"
role="combobox"
size="sm"
size="lg"
aria-expanded={open}
className="w-64 justify-between border-1 border-stone-300 font-normal"
className="w-full justify-between rounded-[4px] border-1 border-stone-300 px-3 py-2 text-sm font-normal"
>
<p className="truncate text-sm">{selectedLabel ?? "Select variable..."}</p>
<ChevronsUpDown className="opacity-50" size={16} />
Expand Down
129 changes: 80 additions & 49 deletions client/src/components/plots/components/variable-select/tree-node.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { TreeNode } from "@/components/plots/components/variable-select/build-tree";
import { Variable } from "@iiasa/ixmp4-ts";
import { Check, ChevronRight } from "lucide-react";
import { ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils";
import { useMemo, useState } from "react";
import * as React from "react";
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
import { Checkbox } from "@/components/ui/checkbox";

interface TreeNodeComponentProps {
node: TreeNode<Variable>;
Expand Down Expand Up @@ -43,34 +44,93 @@ export const TreeNodeComponent = ({
if (searchQuery.length > 0) setIsOpen(true);
}, [searchQuery]);

const hasSelectableItemsAtCurrentLevel = node.selectableItems.length > 0;
const hasChildren = Object.keys(node.children).length > 0;
const isSelectable = node.selectableItems.length > 0;
const indent = level * 12;

const content = (
<div className="w-full">
// Pure leaf — no children, directly selectable
if (!hasChildren && isSelectable) {
const variable = node.selectableItems[0];
const isSelected = selectedValue === variable.id;
return (
<div
className={cn(
"hover:bg-accent flex items-center gap-2 px-2 py-1.5",
hasSelectedDescendant && "bg-accent/50",
"hover:bg-accent flex cursor-pointer items-center gap-2 px-2 py-1.5",
isSelected && "bg-accent",
)}
style={{ paddingLeft: `${indent + 8}px` }}
style={{ paddingLeft: `${indent + 24}px` }}
onClick={() => onSelect(variable.id)}
>
{(hasChildren || hasSelectableItemsAtCurrentLevel) && (
<CollapsibleTrigger asChild onClick={() => setIsOpen((prev) => !prev)}>
<button className="flex flex-1 items-center gap-1 text-left text-sm font-medium">
<ChevronRight className={cn("h-4 w-4 transition-transform", isOpen && "rotate-90")} />
<span className={cn(hasSelectedDescendant && "font-semibold")}>{node.name}</span>
<span className="ml-1 rounded-full bg-black px-2 text-xs font-bold text-white">
{node.count}
</span>
</button>
</CollapsibleTrigger>
)}
<Checkbox
checked={isSelected}
onCheckedChange={() => onSelect(variable.id)}
onClick={(e) => e.stopPropagation()}
className={cn("shrink-0", !isSelected && "border-stone-400")}
/>
<span className={cn("flex-1 text-sm", isSelected && "font-semibold")}>
{variable.name.split("|").pop()?.trim()}
</span>
</div>
);
}

// Branch node — has children, optionally selectable via header
const variable = isSelectable ? node.selectableItems[0] : null;
const isSelected = variable ? selectedValue === variable.id : false;

{!hasChildren && !hasSelectableItemsAtCurrentLevel && (
<span className="flex-1 text-sm font-medium">{node.name}</span>
return (
<Collapsible open={isOpen} onOpenChange={setIsOpen} data-path={node.path}>
<div
className={cn(
"hover:bg-accent flex items-center gap-2 px-2 py-1.5",
isSelected ? "bg-accent" : hasSelectedDescendant && "bg-accent/50",
)}
style={{ paddingLeft: `${indent + 8}px` }}
>
<CollapsibleTrigger asChild>
<button className="flex flex-1 items-center gap-1 text-left text-sm font-medium">
<ChevronRight
className={cn("h-4 w-4 shrink-0 transition-transform", isOpen && "rotate-90")}
onClick={(e) => {
e.stopPropagation();
setIsOpen((prev) => !prev);
}}
/>
{variable && (
<Checkbox
checked={isSelected}
onCheckedChange={() => onSelect(variable.id)}
onClick={(e) => {
e.stopPropagation();
onSelect(variable.id);
}}
className={cn("shrink-0", !isSelected && "border-stone-400")}
/>
)}
<span
className={cn("flex", (isSelected || hasSelectedDescendant) && "font-semibold")}
onClick={
variable
? (e) => {
e.stopPropagation();
onSelect(variable.id);
}
: undefined
}
>
{node.name}
</span>
<span
className="ml-1 rounded-full bg-black px-2 text-xs font-bold text-white"
onClick={(e) => {
e.stopPropagation();
setIsOpen((prev) => !prev);
}}
>
{node.count - node.selectableItems.length}
</span>
</button>
</CollapsibleTrigger>
</div>

<CollapsibleContent>
Expand All @@ -84,36 +144,7 @@ export const TreeNodeComponent = ({
searchQuery={searchQuery}
/>
))}

{hasSelectableItemsAtCurrentLevel &&
node.selectableItems.map((variable) => {
const isSelected = selectedValue === variable.id;
return (
<div
key={variable.id}
className={cn(
"hover:bg-accent flex cursor-pointer items-center gap-2 px-2 py-1.5",
isSelected && "bg-accent",
)}
style={{ paddingLeft: `${(level + 1) * 12 + 8}px` }}
onClick={() => onSelect(variable.id)}
>
<span className={cn("flex-1 text-sm", isSelected && "font-semibold")}>
{variable.name.split("|").pop()?.trim()}
</span>
<Check
className={cn("ml-auto h-4 w-4", isSelected ? "opacity-100" : "opacity-0")}
/>
</div>
);
})}
</CollapsibleContent>
</div>
);

return (
<Collapsible open={isOpen} onOpenChange={setIsOpen} data-path={node.path}>
{content}
</Collapsible>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { FigureThreeData } from "@/hooks/guided-exploration/figure-three/use-figure-three";

export type BenchmarkGroupKey = keyof Pick<FigureThreeData, "groupA" | "groupB">;

export interface BenchmarkMargin {
TOP: number;
RIGHT: number;
BOTTOM: number;
LEFT: number;
}

export interface BenchmarkGroupColors {
range: string;
rangeBorder: string;
dot: string;
}

export const BASELINE_YEAR = 2020;

export const BENCHMARK_GROUP_KEYS: BenchmarkGroupKey[] = ["groupA", "groupB"];

export const BENCHMARK_GROUP_LABELS: Record<BenchmarkGroupKey, string> = {
groupA: "GW3",
groupB: "GW2+GW1",
};

export const BENCHMARK_COLORS: Record<BenchmarkGroupKey, BenchmarkGroupColors> = {
groupA: {
range: "rgba(99, 132, 220, 0.25)",
rangeBorder: "rgba(99, 132, 220, 0.5)",
dot: "#4266C8",
},
groupB: {
range: "rgba(134, 193, 134, 0.25)",
rangeBorder: "rgba(134, 193, 134, 0.5)",
dot: "#3A9B3A",
},
};

export const MARGIN: BenchmarkMargin = {
TOP: 40,
RIGHT: 48,
BOTTOM: 30,
LEFT: 65,
};

export const DOT_RADIUS = 4;
export const DOT_FILL_OPACITY = 0.85;
export const DOT_STROKE_WIDTH = 0.5;

export const RANGE_BAR_RADIUS = 2;
export const RANGE_BAR_STROKE_WIDTH = 1;
export const MIN_RANGE_BAR_HEIGHT = 2;
export const UNVETTED_WHISKER_STROKE_WIDTH = 2;
export const UNVETTED_WHISKER_CAP_WIDTH_RATIO = 0.45;
export const UNVETTED_WHISKER_DASH_ARRAY = "3,3";

export const ZERO_LINE_COLOR = "#374151";
export const ZERO_LINE_STROKE_WIDTH = 1.5;
export const ZERO_LINE_DASH_ARRAY = "6,4";

export const Y_AXIS_LABEL = "% compared to 2020";
export const Y_AXIS_LABEL_OFFSET = -52;

export const HOVER_GUIDE_LINE_COLOR = "#111827";
export const HOVER_GUIDE_LINE_WIDTH = 1.25;
export const HOVER_GUIDE_LINE_DASH_ARRAY = "4,4";
export const HOVER_GUIDE_LINE_OPACITY = 0.85;

export const HOVER_RANGE_FILL_OPACITY = 0.08;
export const HOVER_RANGE_STROKE_WIDTH = 2;

export const HOVER_LABEL_FONT_SIZE = "11px";
export const HOVER_LABEL_TEXT_COLOR = "white";
export const HOVER_LABEL_STROKE_COLOR = "#ffffff";
export const HOVER_LABEL_STROKE_WIDTH = 4;
export const HOVER_LABEL_RIGHT_OFFSET = 6;

export const HOVER_PILL_LINE_GAP = 4;
export const HOVER_PILL_CHART_PADDING = 4;

export const HOVER_PILL_PADDING_X = 8;
export const HOVER_PILL_PADDING_Y = 5;
export const HOVER_PILL_RADIUS = 10;
export const HOVER_PILL_BACKGROUND_COLOR = "#000000";
export const HOVER_PILL_BACKGROUND_OPACITY = 0.85;

export const HOVER_HEADER_LABEL_Y = 14;
export const HOVER_MAX_LABEL_Y_OFFSET = -6;
export const HOVER_MIN_LABEL_Y_OFFSET = 14;

export const HOVER_ZONE_X_PADDING = 6;
Loading
Loading