Skip to content

Commit a3a7b77

Browse files
authored
Merge pull request #676 from nteract/optimize
Optimize
2 parents d00f2e9 + 0ebc4dc commit a3a7b77

39 files changed

Lines changed: 3459 additions & 860 deletions

docs/src/App.js

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import Landing from "./Landing"
77

88
// Existing page components
99
import Home from "./Home"
10-
import { GuidesIndex, ExamplesIndex, ApiIndex } from "./IndexPages"
10+
import { GuidesIndex, ExamplesIndex, ApiIndex, RecipesIndex, PlaygroundIndex } from "./IndexPages"
1111
import Accessibility from "./markdown/accessibility.mdx"
1212
import Xyframe from "./markdown/xyframe.mdx"
1313
import Ordinalframe from "./markdown/ordinalframe.mdx"
@@ -114,8 +114,17 @@ import TimelineCookbookPage from "./pages/cookbook/TimelinePage"
114114
import RadarPlotPage from "./pages/cookbook/RadarPlotPage"
115115
import IsotypeChartPage from "./pages/cookbook/IsotypeChartPage"
116116
import MatrixCookbookPage from "./pages/cookbook/MatrixPage"
117+
import KpiCardSparklinePage from "./pages/recipes/KpiCardSparklinePage"
118+
import TimeSeriesBrushPage from "./pages/recipes/TimeSeriesBrushPage"
119+
import NetworkExplorerPage from "./pages/recipes/NetworkExplorerPage"
117120
import UsingSSRPage from "./pages/UsingSSRPage"
118121

122+
// Playground pages
123+
import LineChartPlayground from "./pages/playground/LineChartPlayground"
124+
import BarChartPlayground from "./pages/playground/BarChartPlayground"
125+
import ScatterplotPlayground from "./pages/playground/ScatterplotPlayground"
126+
import ForceDirectedGraphPlayground from "./pages/playground/ForceDirectedGraphPlayground"
127+
119128
import semioticLogo from "../public/assets/img/semiotic.png"
120129

121130
import { useScrollRestoration } from "./useScrollRestoration"
@@ -325,6 +334,23 @@ export default function DocsApp() {
325334
<Route path="matrix" element={<MatrixCookbookPage />} />
326335
</Route>
327336

337+
{/* Recipes routes */}
338+
<Route path="recipes" element={<Outlet />}>
339+
<Route path="" element={<><h1>Recipes</h1><RecipesIndex /></>} />
340+
<Route path="kpi-card-sparkline" element={<KpiCardSparklinePage />} />
341+
<Route path="time-series-brush" element={<TimeSeriesBrushPage />} />
342+
<Route path="network-explorer" element={<NetworkExplorerPage />} />
343+
</Route>
344+
345+
{/* Playground routes */}
346+
<Route path="playground" element={<Outlet />}>
347+
<Route path="" element={<><h1>Playground</h1><PlaygroundIndex /></>} />
348+
<Route path="line-chart" element={<LineChartPlayground />} />
349+
<Route path="bar-chart" element={<BarChartPlayground />} />
350+
<Route path="scatterplot" element={<ScatterplotPlayground />} />
351+
<Route path="force-directed-graph" element={<ForceDirectedGraphPlayground />} />
352+
</Route>
353+
328354
{/* Frames routes */}
329355
<Route path="frames" element={<Outlet />}>
330356
<Route path="xy-frame" element={<XYFramePage />} />

docs/src/IndexPages.js

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,65 @@ export function ExamplesIndex() {
259259
)
260260
}
261261

262+
export function RecipesIndex() {
263+
return (
264+
<div className="margin-bottom">
265+
<div className="subpages">
266+
<div className="sub-header">Dashboard Panels</div>
267+
268+
<PageLink
269+
href="/recipes/kpi-card-sparkline"
270+
title="KPI Card + Sparkline"
271+
thumbnail={new URL("../public/assets/img/sparkline.png", import.meta.url)}
272+
/>
273+
<PageLink
274+
href="/recipes/time-series-brush"
275+
title="Time Series with Brush"
276+
thumbnail={new URL("../public/assets/img/xy-brush.png", import.meta.url)}
277+
/>
278+
<PageLink
279+
href="/recipes/network-explorer"
280+
title="Network Explorer"
281+
thumbnail={new URL("../public/assets/img/force.png", import.meta.url)}
282+
/>
283+
</div>
284+
</div>
285+
)
286+
}
287+
288+
export function PlaygroundIndex() {
289+
return (
290+
<div className="margin-bottom">
291+
<p>
292+
Interactively explore Semiotic's chart components. Adjust props via
293+
controls and see changes in real time with auto-generated code.
294+
</p>
295+
<div className="subpages">
296+
<PageLink
297+
href="/playground/line-chart"
298+
title="Line Chart"
299+
thumbnail={new URL("../public/assets/img/line-chart.png", import.meta.url)}
300+
/>
301+
<PageLink
302+
href="/playground/bar-chart"
303+
title="Bar Chart"
304+
thumbnail={new URL("../public/assets/img/bar-chart.png", import.meta.url)}
305+
/>
306+
<PageLink
307+
href="/playground/scatterplot"
308+
title="Scatterplot"
309+
thumbnail={new URL("../public/assets/img/scatterplot.png", import.meta.url)}
310+
/>
311+
<PageLink
312+
href="/playground/force-directed-graph"
313+
title="Force Directed Graph"
314+
thumbnail={new URL("../public/assets/img/force.png", import.meta.url)}
315+
/>
316+
</div>
317+
</div>
318+
)
319+
}
320+
262321
export function ApiIndex() {
263322
return (
264323
<div className="margin-bottom">
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
import React, { useState, useEffect, useRef, useCallback } from "react"
2+
import PageLayout from "./PageLayout"
3+
import PropControls from "./PropControls"
4+
import CodeBlock from "./CodeBlock"
5+
import { propertyToString } from "./LiveExample"
6+
7+
/**
8+
* Orchestrator for playground pages.
9+
* Owns knob state, renders chart, controls, and generated code.
10+
*
11+
* Props:
12+
* title - Page title
13+
* breadcrumbs - Array of { label, path }
14+
* prevPage - { title, path }
15+
* nextPage - { title, path }
16+
* chartComponent - React component (e.g. LineChart)
17+
* componentName - String name for code generation
18+
* controls - Schema array for PropControls
19+
* datasets - [{ label, data, codeString }]
20+
* dataProps - Function: (dataset) => object of data-related props
21+
* mapProps - Optional: (name, value) => transformed value (return undefined to skip)
22+
* children - Optional description content
23+
*/
24+
export default function PlaygroundLayout({
25+
title,
26+
breadcrumbs,
27+
prevPage,
28+
nextPage,
29+
chartComponent: ChartComponent,
30+
componentName,
31+
controls,
32+
datasets,
33+
dataProps,
34+
mapProps,
35+
children,
36+
}) {
37+
// Build defaults from control schema
38+
const defaults = {}
39+
for (const c of controls) {
40+
defaults[c.name] = c.default
41+
}
42+
43+
const [values, setValues] = useState(defaults)
44+
const [datasetIndex, setDatasetIndex] = useState(0)
45+
const [containerWidth, setContainerWidth] = useState(null)
46+
const vizRef = useRef(null)
47+
48+
// ResizeObserver for responsive chart width
49+
useEffect(() => {
50+
const el = vizRef.current
51+
if (!el) return
52+
const observer = new ResizeObserver((entries) => {
53+
for (const entry of entries) {
54+
setContainerWidth(entry.contentRect.width)
55+
}
56+
})
57+
observer.observe(el)
58+
return () => observer.disconnect()
59+
}, [])
60+
61+
const handleChange = useCallback((name, value) => {
62+
setValues((prev) => ({ ...prev, [name]: value }))
63+
}, [])
64+
65+
const handleReset = useCallback(() => {
66+
setValues(defaults)
67+
}, [])
68+
69+
// Build chart props from current knob values
70+
const currentDataset = datasets[datasetIndex]
71+
const chartDataProps = dataProps
72+
? dataProps(currentDataset)
73+
: { data: currentDataset.data }
74+
75+
const chartProps = { ...chartDataProps }
76+
for (const c of controls) {
77+
let v = values[c.name]
78+
// Only pass prop if it has a meaningful value
79+
if (c.type === "string" && v === "") continue
80+
// Allow per-page value transformation
81+
if (mapProps) {
82+
v = mapProps(c.name, v)
83+
if (v === undefined) continue
84+
}
85+
chartProps[c.name] = v
86+
}
87+
88+
// Apply responsive width
89+
if (containerWidth) {
90+
chartProps.width = containerWidth
91+
}
92+
93+
// Generate code string
94+
const code = generateCode(componentName, controls, values, defaults, currentDataset)
95+
96+
return (
97+
<PageLayout
98+
title={title}
99+
breadcrumbs={breadcrumbs}
100+
prevPage={prevPage}
101+
nextPage={nextPage}
102+
>
103+
{children}
104+
105+
{/* Dataset picker */}
106+
{datasets.length > 1 && (
107+
<div className="playground-dataset-picker">
108+
<label htmlFor="pg-dataset">Dataset:</label>
109+
<select
110+
id="pg-dataset"
111+
className="playground-select"
112+
value={datasetIndex}
113+
onChange={(e) => setDatasetIndex(parseInt(e.target.value, 10))}
114+
>
115+
{datasets.map((ds, i) => (
116+
<option key={i} value={i}>
117+
{ds.label}
118+
</option>
119+
))}
120+
</select>
121+
</div>
122+
)}
123+
124+
{/* Chart preview */}
125+
<div ref={vizRef} className="playground-chart-container">
126+
{containerWidth ? <ChartComponent {...chartProps} /> : null}
127+
</div>
128+
129+
{/* Controls */}
130+
<PropControls
131+
controls={controls}
132+
values={values}
133+
onChange={handleChange}
134+
onReset={handleReset}
135+
/>
136+
137+
{/* Generated code */}
138+
<h2 id="generated-code">Generated Code</h2>
139+
<CodeBlock code={code} language="jsx" />
140+
</PageLayout>
141+
)
142+
}
143+
144+
function generateCode(componentName, controls, values, defaults, dataset) {
145+
let code = `import { ${componentName} } from "semiotic"\n\n`
146+
code += `const data = ${dataset.codeString || "[\n // your data here\n]"}\n\n`
147+
code += `<${componentName}\n`
148+
149+
// For network charts use nodes={...} edges={...}, otherwise data={data}
150+
if (dataset.nodes) {
151+
code += ` nodes={nodes}\n`
152+
code += ` edges={edges}\n`
153+
} else {
154+
code += ` data={data}\n`
155+
}
156+
157+
for (const c of controls) {
158+
const v = values[c.name]
159+
// Skip defaults to keep output clean
160+
if (v === defaults[c.name]) continue
161+
// Skip empty strings
162+
if (c.type === "string" && v === "") continue
163+
164+
const propStr = formatPropValue(c, v)
165+
code += ` ${c.name}=${propStr}\n`
166+
}
167+
168+
code += `/>`
169+
return code
170+
}
171+
172+
function formatPropValue(control, value) {
173+
if (control.type === "string") {
174+
return `"${value}"`
175+
}
176+
if (control.type === "select") {
177+
return `"${value}"`
178+
}
179+
if (control.type === "boolean") {
180+
return `{${value}}`
181+
}
182+
if (control.type === "number") {
183+
return `{${value}}`
184+
}
185+
if (control.type === "color") {
186+
return `"${value}"`
187+
}
188+
return `{${propertyToString(value, 0, false)}}`
189+
}

0 commit comments

Comments
 (0)