Skip to content

Commit 76f08a5

Browse files
committed
feat: initial implementation of solid-plotly
1 parent 4153874 commit 76f08a5

17 files changed

+2846
-72
lines changed

README.md

Lines changed: 404 additions & 37 deletions
Large diffs are not rendered by default.

bun.lock

Lines changed: 735 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

jsr.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
{
2-
"name": "template-solidjs-library",
2+
"name": "@dschz/solid-plotly",
33
"version": "0.0.0",
44
"license": "MIT",
55
"exports": "./src/index.tsx",
66
"publish": {
7-
"include": ["LICENSE", "README.md", "CHANGELOG.md", "src/**/*.tsx"],
8-
"exclude": ["**/*.test.tsx"]
7+
"include": ["LICENSE", "README.md", "CHANGELOG.md", "src/**/*.tsx", "src/**/*.ts"],
8+
"exclude": ["**/*.test.tsx", "**/*.test.ts"]
99
}
1010
}

package.json

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
{
2-
"name": "template-solidjs-library",
2+
"name": "@dschz/solid-plotly",
33
"version": "0.0.0",
4-
"description": "Template for SolidJS library using tsup for bundling. Configured with Bun, NVM, TypeScript, ESLint, Prettier, Vitest, and GHA",
4+
"description": "SolidJS wrapper for Plotly.js – reactive and performant charts powered by Plotly, built for Solid.",
55
"type": "module",
6-
"author": "Daniel Sanchez <dsanc89@icloud.com>",
6+
"author": "Daniel Sanchez <dsanc89@pm.me>",
77
"license": "MIT",
8-
"homepage": "https://github.com/thedanchez/template-solidjs-library#readme",
8+
"homepage": "https://github.com/dsnchz/solid-plotly#readme",
99
"repository": {
1010
"type": "git",
11-
"url": "https://github.com/thedanchez/template-solidjs-library.git"
11+
"url": "https://github.com/dsnchz/solid-plotly.git"
1212
},
1313
"bugs": {
14-
"url": "https://github.com/thedanchez/template-solidjs-library/issues"
14+
"url": "https://github.com/dsnchz/solid-plotly/issues"
1515
},
1616
"publishConfig": {
1717
"access": "public"
@@ -25,6 +25,7 @@
2525
"browser": {},
2626
"exports": {
2727
"solid": "./dist/index.jsx",
28+
"factory": "./dist/factory.js",
2829
"import": {
2930
"types": "./dist/index.d.ts",
3031
"default": "./dist/index.js"
@@ -56,6 +57,7 @@
5657
"@tailwindcss/vite": "^4.1.5",
5758
"@testing-library/jest-dom": "^6.6.3",
5859
"@types/bun": "^1.2.12",
60+
"@types/plotly.js": "^3.0.0",
5961
"@typescript-eslint/eslint-plugin": "^8.32.0",
6062
"@typescript-eslint/parser": "^8.32.0",
6163
"@vitest/coverage-istanbul": "^3.1.3",
@@ -65,18 +67,25 @@
6567
"globals": "^16.1.0",
6668
"jiti": "^2.4.2",
6769
"jsdom": "^26.1.0",
70+
"plotly.js": "^3.0.1",
71+
"plotly.js-dist-min": "^3.0.1",
6872
"prettier": "^3.5.3",
73+
"process": "^0.11.10",
6974
"solid-js": "^1.9.6",
7075
"tailwindcss": "^4.1.5",
7176
"tsup": "^8.4.0",
7277
"tsup-preset-solid": "^2.2.0",
7378
"typescript": "^5.8.3",
7479
"typescript-eslint": "^8.32.0",
80+
"util": "^0.12.5",
7581
"vite": "^6.3.5",
7682
"vite-plugin-solid": "^2.11.6",
7783
"vitest": "^3.1.3"
7884
},
7985
"peerDependencies": {
8086
"solid-js": ">=1.6.0"
87+
},
88+
"dependencies": {
89+
"@dschz/try-catch": "^1.1.2"
8190
}
8291
}

playground/App.tsx

Lines changed: 89 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,99 @@
1-
import { Route, Router } from "@solidjs/router";
2-
import { ErrorBoundary, type ParentProps } from "solid-js";
1+
import { A, Route, Router } from "@solidjs/router";
2+
import { For, type ParentProps } from "solid-js";
33

4-
import { Navbar } from "./Navbar";
5-
import { ErrorPage } from "./pages/Error";
6-
import { Home } from "./pages/Home";
7-
import { NotFound } from "./pages/NotFound";
4+
import AnimationExample from "./pages/AnimationExample";
5+
import BasicExample from "./pages/BasicExample";
6+
import EventsExample from "./pages/EventsExample";
7+
import FactoryExample from "./pages/FactoryExample";
8+
import InteractiveExample from "./pages/InteractiveExample";
9+
import ResponsiveExample from "./pages/ResponsiveExample";
810

9-
const MainContent = (props: ParentProps) => {
11+
const examples = [
12+
{ path: "/basic", name: "Basic Example", component: BasicExample },
13+
{ path: "/factory", name: "Factory Pattern", component: FactoryExample },
14+
{ path: "/interactive", name: "Interactive", component: InteractiveExample },
15+
{ path: "/responsive", name: "Responsive", component: ResponsiveExample },
16+
{ path: "/events", name: "Events", component: EventsExample },
17+
{ path: "/animation", name: "Animation", component: AnimationExample },
18+
];
19+
20+
function Navigation() {
1021
return (
11-
<main class="flex flex-col h-full w-full grow overflow-auto bg-app-background">
12-
{props.children}
13-
</main>
22+
<nav class="bg-gray-800 text-white p-4">
23+
<div class="container mx-auto">
24+
<h1 class="text-2xl font-bold mb-4">Solid Plotly Playground</h1>
25+
<div class="flex flex-wrap gap-4">
26+
<For each={examples}>
27+
{(example) => (
28+
<A
29+
href={example.path}
30+
class="px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded transition-colors"
31+
activeClass="bg-blue-800"
32+
>
33+
{example.name}
34+
</A>
35+
)}
36+
</For>
37+
</div>
38+
</div>
39+
</nav>
1440
);
15-
};
41+
}
42+
43+
function RootLayout(props: ParentProps) {
44+
return (
45+
<div class="min-h-screen">
46+
<Navigation />
47+
<main class="min-h-screen bg-gray-50">{props.children}</main>
48+
</div>
49+
);
50+
}
51+
52+
function Home() {
53+
return (
54+
<div class="p-8">
55+
<h1 class="text-4xl font-bold mb-6">Welcome to Solid Plotly Playground</h1>
56+
<p class="text-lg text-gray-600 mb-8">
57+
This playground demonstrates various features and usage patterns of the Solid Plotly
58+
component. Navigate through the examples using the menu above.
59+
</p>
1660

17-
const RootLayout = (props: ParentProps) => (
18-
<div id="root-screen" class="h-screen w-screen">
19-
<Navbar />
20-
<MainContent>{props.children}</MainContent>
21-
</div>
22-
);
61+
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
62+
<For each={examples}>
63+
{(example) => (
64+
<div class="border rounded-lg p-6 hover:shadow-lg transition-shadow">
65+
<h3 class="text-xl font-semibold mb-2">{example.name}</h3>
66+
<p class="text-gray-600 mb-4">
67+
{example.path === "/basic" &&
68+
"Simple scatter and bar charts with basic configuration"}
69+
{example.path === "/factory" &&
70+
"Using the factory pattern with custom Plotly bundles"}
71+
{example.path === "/interactive" && "Real-time data updates and user interactions"}
72+
{example.path === "/responsive" && "Responsive charts that adapt to container size"}
73+
{example.path === "/events" && "Comprehensive event handling examples"}
74+
{example.path === "/animation" && "Animated transitions and frame-based animations"}
75+
</p>
76+
<A
77+
href={example.path}
78+
class="inline-block px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 transition-colors"
79+
>
80+
View Example
81+
</A>
82+
</div>
83+
)}
84+
</For>
85+
</div>
86+
</div>
87+
);
88+
}
2389

2490
export const App = () => {
2591
return (
26-
<ErrorBoundary fallback={(e, r) => <ErrorPage error={e} reset={r} />}>
27-
<Router root={RootLayout}>
28-
<Route path="/" component={Home} />
29-
<Route path="*" component={NotFound} />
30-
</Router>
31-
</ErrorBoundary>
92+
<Router root={RootLayout}>
93+
<Route path="/" component={Home} />
94+
<For each={examples}>
95+
{(example) => <Route path={example.path} component={example.component} />}
96+
</For>
97+
</Router>
3298
);
3399
};

playground/components/Plot.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import Plotly from "plotly.js-dist-min";
2+
3+
import { createPlotComponent } from "../../src";
4+
5+
// Create the Plot component using the factory with plotly.js-dist-min
6+
export const Plot = createPlotComponent(Plotly);
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
import { createEffect, createSignal, onCleanup } from "solid-js";
2+
3+
import { type PlotlyLayout } from "../../src";
4+
import { Plot } from "../components/Plot";
5+
6+
export default function AnimationExample() {
7+
const [isAnimating, setIsAnimating] = createSignal(false);
8+
const [currentFrame, setCurrentFrame] = createSignal(0);
9+
const [animationSpeed, setAnimationSpeed] = createSignal(500);
10+
11+
// Generate data for different frames
12+
const generateFrameData = (frameIndex: number) => {
13+
const t = frameIndex * 0.1;
14+
const x = Array.from({ length: 50 }, (_, i) => i * 0.1);
15+
const y = x.map((val) => Math.sin(val + t) * Math.exp(-val * 0.1));
16+
return { x, y };
17+
};
18+
19+
const [layout] = createSignal<PlotlyLayout>({
20+
width: 800,
21+
height: 500,
22+
title: { text: "Animation Example - Sine Wave" },
23+
xaxis: {
24+
title: { text: "X" },
25+
range: [0, 5],
26+
},
27+
yaxis: {
28+
title: { text: "Y" },
29+
range: [-1, 1],
30+
},
31+
transition: {
32+
duration: 300,
33+
easing: "cubic-in-out",
34+
},
35+
});
36+
37+
// Animation frames
38+
const frames = Array.from({ length: 60 }, (_, i) => ({
39+
name: i.toString(),
40+
data: [
41+
{
42+
...generateFrameData(i),
43+
type: "scatter" as const,
44+
mode: "lines" as const,
45+
line: { color: "blue", width: 3 },
46+
name: "Animated Wave",
47+
},
48+
],
49+
group: "main",
50+
traces: [0],
51+
baseframe: undefined,
52+
layout: {},
53+
}));
54+
55+
// Animation effect
56+
createEffect(() => {
57+
if (!isAnimating()) return;
58+
59+
const interval = setInterval(() => {
60+
setCurrentFrame((prev) => (prev + 1) % frames.length);
61+
}, animationSpeed());
62+
63+
onCleanup(() => clearInterval(interval));
64+
});
65+
66+
const startAnimation = () => {
67+
setIsAnimating(true);
68+
};
69+
70+
const stopAnimation = () => {
71+
setIsAnimating(false);
72+
};
73+
74+
const resetAnimation = () => {
75+
setIsAnimating(false);
76+
setCurrentFrame(0);
77+
};
78+
79+
return (
80+
<div class="p-4">
81+
<h1 class="text-2xl font-bold mb-4">Animation Example</h1>
82+
83+
<div class="mb-6 space-y-4">
84+
<p class="text-gray-600">
85+
This example demonstrates animated charts using frame-based animations. The sine wave
86+
moves continuously when animation is enabled.
87+
</p>
88+
89+
<div class="flex flex-wrap gap-4 items-center">
90+
<button
91+
onClick={startAnimation}
92+
disabled={isAnimating()}
93+
class="px-4 py-2 bg-green-600 hover:bg-green-700 disabled:bg-gray-400 text-white rounded"
94+
>
95+
Start Animation
96+
</button>
97+
98+
<button
99+
onClick={stopAnimation}
100+
disabled={!isAnimating()}
101+
class="px-4 py-2 bg-red-600 hover:bg-red-700 disabled:bg-gray-400 text-white rounded"
102+
>
103+
Stop Animation
104+
</button>
105+
106+
<button
107+
onClick={resetAnimation}
108+
class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded"
109+
>
110+
Reset
111+
</button>
112+
113+
<label class="flex items-center gap-2">
114+
Speed (ms):
115+
<input
116+
type="range"
117+
min="100"
118+
max="1000"
119+
step="50"
120+
value={animationSpeed()}
121+
onInput={(e) => setAnimationSpeed(parseInt(e.currentTarget.value))}
122+
class="w-32"
123+
/>
124+
<span class="w-12 text-center">{animationSpeed()}</span>
125+
</label>
126+
127+
<div class="text-sm text-gray-600">
128+
Frame: {currentFrame() + 1} / {frames.length}
129+
</div>
130+
</div>
131+
</div>
132+
133+
<Plot
134+
data={[
135+
{
136+
...generateFrameData(currentFrame()),
137+
type: "scatter",
138+
mode: "lines",
139+
line: { color: "blue", width: 3 },
140+
name: "Animated Wave",
141+
},
142+
]}
143+
layout={layout()}
144+
config={{
145+
displayModeBar: true,
146+
showTips: false,
147+
}}
148+
/>
149+
150+
<div class="mt-4 p-4 bg-yellow-50 rounded">
151+
<h3 class="font-semibold mb-2">Animation Features:</h3>
152+
<ul class="list-disc list-inside space-y-1 text-sm">
153+
<li>Frame-based animation using Plotly frames</li>
154+
<li>Smooth transitions between data states</li>
155+
<li>Configurable animation speed</li>
156+
<li>Start/stop/reset controls</li>
157+
<li>Real-time frame counter</li>
158+
</ul>
159+
</div>
160+
</div>
161+
);
162+
}

0 commit comments

Comments
 (0)