├── .eslintignore ├── .gitignore ├── .npmignore ├── assets └── solid-chartjs-logo.png ├── src ├── index.tsx ├── types.ts ├── typedCharts.tsx └── chart.tsx ├── dev ├── tsconfig.json ├── index.tsx ├── index.html ├── utils │ └── index.ts ├── vite.config.ts ├── pages │ ├── TypedChart.tsx │ ├── Registerables.tsx │ └── DefaultChart.tsx ├── App.tsx └── styles │ └── App.module.css ├── tsconfig.node.json ├── .editorconfig ├── tsup.config.ts ├── .prettierrc ├── env.d.ts ├── tsconfig.json ├── LICENSE ├── vitest.config.ts ├── .eslintrc ├── scripts └── prepareCMD.sh ├── package.json └── README.md /.eslintignore: -------------------------------------------------------------------------------- 1 | .publish/* 2 | dist/* 3 | lib/* 4 | node_modules/* 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | gitignore 4 | .idea 5 | 6 | # tsup 7 | tsup.config.bundled_*.{m,c,}s 8 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | gitignore 4 | .idea 5 | 6 | # tsup 7 | tsup.config.bundled_*.{m,c,}s 8 | -------------------------------------------------------------------------------- /assets/solid-chartjs-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s0ftik3/solid-chartjs/HEAD/assets/solid-chartjs-logo.png -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import DefaultChart from './chart' 2 | export type { ChartProps } from './types.js' 3 | export { DefaultChart } 4 | export * from './typedCharts' 5 | -------------------------------------------------------------------------------- /dev/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "types": ["vite/client"] 5 | }, 6 | "exclude": ["node_modules", "dist"] 7 | } 8 | -------------------------------------------------------------------------------- /dev/index.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-non-null-assertion */ 2 | import { render } from 'solid-js/web' 3 | import App from './App' 4 | render(() => , document.getElementById('root')!) 5 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "esnext", 5 | "moduleResolution": "node" 6 | }, 7 | "include": ["vitest.config.ts"] 8 | } 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # This file is for unifying the coding style for different editors and IDEs 2 | # editorconfig.org 3 | root = true 4 | 5 | [*] 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | indent_style = space 11 | indent_size = 4 12 | -------------------------------------------------------------------------------- /tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'tsup-preset-solid' 2 | 3 | export default defineConfig( 4 | { 5 | entry: 'src/index.tsx', 6 | devEntry: true, 7 | dropConsole: true, 8 | }, 9 | { 10 | // Enable this to write export conditions to package.json 11 | // writePackageJson: true, 12 | }, 13 | ) 14 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "bracketSpacing": true, 3 | "quoteProps": "as-needed", 4 | "semi": false, 5 | "tabWidth": 4, 6 | "singleQuote": true, 7 | "printWidth": 100, 8 | "bracketSameLine": true, 9 | "arrowParens": "always", 10 | "endOfLine": "lf", 11 | "htmlWhitespaceSensitivity": "ignore", 12 | "jsxSingleQuote": false, 13 | "trailingComma": "all", 14 | "useTabs": false 15 | } 16 | -------------------------------------------------------------------------------- /env.d.ts: -------------------------------------------------------------------------------- 1 | declare global { 2 | interface ImportMeta { 3 | env: { 4 | NODE_ENV: 'production' | 'development' 5 | PROD: boolean 6 | DEV: boolean 7 | } 8 | } 9 | namespace NodeJS { 10 | interface ProcessEnv { 11 | NODE_ENV: 'production' | 'development' 12 | PROD: boolean 13 | DEV: boolean 14 | } 15 | } 16 | } 17 | 18 | export {} 19 | -------------------------------------------------------------------------------- /dev/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Solid App 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /dev/utils/index.ts: -------------------------------------------------------------------------------- 1 | import { ChartData, ChartDataset } from 'chart.js' 2 | 3 | export const generateRandomDataset = (labels: string[], index: number) => ({ 4 | label: 'Dataset ' + index, 5 | data: labels.map(() => Math.random() * 200 - 100), 6 | }) 7 | 8 | export const generateRandomChartData = (datasetsLength = 2): ChartData => { 9 | const labels = [ 10 | 'January', 11 | 'February', 12 | 'March', 13 | 'April', 14 | 'May', 15 | 'June', 16 | 'July', 17 | 'August', 18 | 'September', 19 | 'October', 20 | 'November', 21 | 'December', 22 | ] 23 | const datasets: ChartDataset[] = [] 24 | 25 | for (let i = 0; i < datasetsLength; i++) { 26 | datasets.push(generateRandomDataset(labels, i + 1)) 27 | } 28 | 29 | return { 30 | labels, 31 | datasets, 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "strict": true, 5 | "target": "ESNext", 6 | "module": "ESNext", 7 | "moduleResolution": "node", 8 | "allowSyntheticDefaultImports": true, 9 | "esModuleInterop": true, 10 | "jsx": "preserve", 11 | "jsxImportSource": "solid-js", 12 | "types": ["vite/client"], 13 | "useDefineForClassFields": true, 14 | "noImplicitAny": false, 15 | "isolatedModules": true, 16 | "declaration": true, 17 | "noEmit": false, 18 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 19 | "allowJs": true, 20 | "skipLibCheck": false, 21 | "forceConsistentCasingInFileNames": true, 22 | "resolveJsonModule": true, 23 | "paths": { 24 | "@/*": ["src/*"] 25 | } 26 | }, 27 | "include": ["src", "**/*.ts", "**/*.tsx", "index.js"], 28 | "references": [ 29 | { 30 | "path": "./tsconfig.node.json" 31 | } 32 | ], 33 | "exclude": ["node_modules"] 34 | } 35 | -------------------------------------------------------------------------------- /dev/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import solidPlugin from 'vite-plugin-solid' 3 | 4 | export default defineConfig({ 5 | plugins: [ 6 | solidPlugin(), 7 | { 8 | name: 'Replace env variables', 9 | transform(code, id) { 10 | if (id.includes('node_modules')) { 11 | return code 12 | } 13 | return code 14 | .replace(/process\.env\.SSR/g, 'false') 15 | .replace(/process\.env\.DEV/g, 'true') 16 | .replace(/process\.env\.PROD/g, 'false') 17 | .replace(/process\.env\.NODE_ENV/g, '"development"') 18 | .replace(/import\.meta\.env\.SSR/g, 'false') 19 | .replace(/import\.meta\.env\.DEV/g, 'true') 20 | .replace(/import\.meta\.env\.PROD/g, 'false') 21 | .replace(/import\.meta\.env\.NODE_ENV/g, '"development"') 22 | }, 23 | }, 24 | ], 25 | server: { 26 | port: 3000, 27 | }, 28 | build: { 29 | target: 'esnext', 30 | }, 31 | }) 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Vyacheslav 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import type { Ref } from '@solid-primitives/refs' 2 | import type { ChartData, ChartOptions, Plugin, ChartTypeRegistry } from 'chart.js' 3 | import type { JSXElement } from 'solid-js' 4 | 5 | /** 6 | * Chart props 7 | */ 8 | export interface ChartProps { 9 | /** 10 | * Chart.js chart type 11 | */ 12 | type: keyof ChartTypeRegistry 13 | /** 14 | * The data object that is passed into the Chart.js chart 15 | * @see https://www.chartjs.org/docs/latest/getting-started/ 16 | */ 17 | data?: ChartData 18 | /** 19 | * The options object that is passed into the Chart.js chart 20 | * @see https://www.chartjs.org/docs/latest/general/options.html 21 | * @default {} 22 | */ 23 | options?: ChartOptions 24 | /** 25 | * The plugins array that is passed into the Chart.js chart 26 | * @see https://www.chartjs.org/docs/latest/developers/plugins.html 27 | * @default [] 28 | */ 29 | plugins?: Plugin[] 30 | /** 31 | * The width of the canvas element 32 | * @default 512 33 | */ 34 | width?: number | undefined 35 | /** 36 | * The height of the canvas element 37 | * @default 512 38 | */ 39 | height?: number | undefined 40 | /** 41 | * The fallback element to render when the canvas cannot be rendered. 42 | * @default null 43 | */ 44 | fallback?: JSXElement | null 45 | /** 46 | * Support for any other Chart.js options 47 | * @default {} 48 | */ 49 | [key: string]: any 50 | /** 51 | * A ref to the Chart.js instance 52 | * @default null 53 | */ 54 | ref?: Ref 55 | } 56 | -------------------------------------------------------------------------------- /src/typedCharts.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Chart, 3 | LineController, 4 | BarController, 5 | RadarController, 6 | DoughnutController, 7 | PolarAreaController, 8 | BubbleController, 9 | PieController, 10 | ScatterController, 11 | CategoryScale, 12 | LinearScale, 13 | RadialLinearScale, 14 | PointElement, 15 | LineElement, 16 | BarElement, 17 | ArcElement, 18 | } from 'chart.js' 19 | import DefaultChart from './chart' 20 | import type { ChartProps } from './types' 21 | import type { ChartType, ChartComponentLike } from 'chart.js' 22 | 23 | export type TypedChartProps = Omit 24 | 25 | function createTypedChart(type: T, registerables: ChartComponentLike) { 26 | Chart.register(registerables) 27 | return (props: TypedChartProps) => 28 | } 29 | 30 | export const Line = /* #__PURE__ */ createTypedChart('line', [ 31 | LineController, 32 | CategoryScale, 33 | LinearScale, 34 | PointElement, 35 | LineElement, 36 | ]) 37 | export const Bar = /* #__PURE__ */ createTypedChart('bar', [ 38 | BarController, 39 | CategoryScale, 40 | BarElement, 41 | ]) 42 | export const Radar = /* #__PURE__ */ createTypedChart('radar', [RadarController, RadialLinearScale]) 43 | export const Doughnut = /* #__PURE__ */ createTypedChart('doughnut', [ 44 | DoughnutController, 45 | ArcElement, 46 | ]) 47 | export const PolarArea = /* #__PURE__ */ createTypedChart('polarArea', PolarAreaController) 48 | export const Bubble = /* #__PURE__ */ createTypedChart('bubble', BubbleController) 49 | export const Pie = /* #__PURE__ */ createTypedChart('pie', PieController) 50 | export const Scatter = /* #__PURE__ */ createTypedChart('scatter', ScatterController) 51 | -------------------------------------------------------------------------------- /dev/pages/TypedChart.tsx: -------------------------------------------------------------------------------- 1 | import 'chart.js/auto' 2 | import { ChartData } from 'chart.js' 3 | import { createSignal } from 'solid-js' 4 | import { createStore } from 'solid-js/store' 5 | import { Line } from '../../src' 6 | import styles from '../styles/App.module.css' 7 | import { generateRandomChartData } from '../utils' 8 | import type { Component } from 'solid-js' 9 | 10 | const TypedChartPage: Component = () => { 11 | const [chartData] = createSignal(generateRandomChartData()) 12 | const [chartConfig] = createStore({ 13 | width: 700, 14 | height: 400, 15 | }) 16 | 17 | const fallback = () => { 18 | return ( 19 |
20 |

Chart is not available

21 |
22 | ) 23 | } 24 | 25 | return ( 26 |
27 |
28 | 47 |
48 |
49 | ) 50 | } 51 | 52 | export default TypedChartPage 53 | -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | import solidPlugin from 'vite-plugin-solid' 2 | import { defineConfig } from 'vitest/config' 3 | 4 | export default defineConfig(({ mode }) => { 5 | // to test in server environment, run with "--mode ssr" or "--mode test:ssr" flag 6 | // loads only server.test.ts file 7 | const testSSR = mode === 'test:ssr' || mode === 'ssr' 8 | 9 | return { 10 | plugins: [ 11 | solidPlugin({ 12 | // https://github.com/solidjs/solid-refresh/issues/29 13 | hot: false, 14 | // For testing SSR we need to do a SSR JSX transform 15 | solid: { generate: testSSR ? 'ssr' : 'dom' }, 16 | }), 17 | ], 18 | test: { 19 | watch: false, 20 | isolate: !testSSR, 21 | env: { 22 | NODE_ENV: testSSR ? 'production' : 'development', 23 | DEV: testSSR ? '' : '1', 24 | SSR: testSSR ? '1' : '', 25 | PROD: testSSR ? '1' : '', 26 | }, 27 | environment: testSSR ? 'node' : 'jsdom', 28 | deps: { 29 | inline: ['vitest-canvas-mock'], 30 | }, 31 | coverage: { 32 | reporter: ['lcovonly', 'text'], 33 | }, 34 | transformMode: { web: [/\.[jt]sx$/] }, 35 | ...(testSSR 36 | ? { 37 | include: ['test/server.test.{ts,tsx}'], 38 | } 39 | : { 40 | include: ['test/*.test.{ts,tsx}'], 41 | exclude: ['test/server.test.{ts,tsx}'], 42 | }), 43 | }, 44 | resolve: { 45 | conditions: testSSR ? ['node'] : ['browser', 'development'], 46 | }, 47 | } 48 | }) 49 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2021": true 5 | }, 6 | "extends": [ 7 | "prettier", 8 | "eslint:recommended", 9 | "plugin:@typescript-eslint/recommended", 10 | "plugin:solid/typescript", 11 | "plugin:import/typescript", 12 | "plugin:import/recommended" 13 | ], 14 | "parser": "@typescript-eslint/parser", 15 | "parserOptions": { 16 | "ecmaVersion": "latest", 17 | "sourceType": "module", 18 | "ecmaFeatures": { 19 | "jsx": true 20 | } 21 | }, 22 | "plugins": ["@typescript-eslint", "solid", "import"], 23 | "rules": { 24 | "solid/reactivity": "warn", 25 | "solid/no-destructure": "warn", 26 | "solid/jsx-no-undef": "error", 27 | "indent": ["error", 4], 28 | "quotes": ["error", "single"], 29 | "semi": ["error", "never"], 30 | "arrow-body-style": "off", 31 | "import/no-unresolved": "error", 32 | "import/extensions": [ 33 | "error", 34 | "ignorePackages", 35 | { 36 | "js": "never", 37 | "jsx": "never", 38 | "ts": "never", 39 | "tsx": "never" 40 | } 41 | ], 42 | "import/order": [ 43 | "error", 44 | { 45 | "groups": [ 46 | "builtin", 47 | "external", 48 | "parent", 49 | "sibling", 50 | "index", 51 | "object", 52 | "type" 53 | ], 54 | "pathGroups": [ 55 | { 56 | "pattern": "@/**/**", 57 | "group": "parent", 58 | "position": "before" 59 | } 60 | ], 61 | "alphabetize": { 62 | "order": "asc" 63 | } 64 | } 65 | ] 66 | }, 67 | "settings": { 68 | "import/resolver": { 69 | "typescript": "true", 70 | "node": "true" 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /dev/pages/Registerables.tsx: -------------------------------------------------------------------------------- 1 | import { Chart, ChartData, Title, Tooltip, Legend, Colors } from 'chart.js' 2 | import { createSignal, onMount } from 'solid-js' 3 | import { createStore } from 'solid-js/store' 4 | import { DefaultChart } from '../../src' 5 | import styles from '../styles/App.module.css' 6 | import { generateRandomChartData } from '../utils' 7 | import type { Component } from 'solid-js' 8 | 9 | const RegisterablesPage: Component = () => { 10 | const [refBar, setRefBar] = createSignal() 11 | const [chartData] = createSignal(generateRandomChartData()) 12 | const [chartConfig] = createStore({ 13 | width: 700, 14 | height: 400, 15 | }) 16 | 17 | const fallback = () => { 18 | return ( 19 |
20 |

Chart is not available

21 |
22 | ) 23 | } 24 | 25 | onMount(() => { 26 | Chart.register(Title, Tooltip, Legend, Colors) 27 | console.debug('[Chart Ref]: Bar', refBar()) 28 | }) 29 | 30 | return ( 31 |
32 |
33 | 54 |
55 |
56 | ) 57 | } 58 | 59 | export default RegisterablesPage 60 | -------------------------------------------------------------------------------- /scripts/prepareCMD.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # create a vairable to hold a passed in argument 4 | # this argument is the next release version 5 | # this is passed in from the .releaserc file 6 | 7 | sudo apt-get install -y jq 8 | 9 | nextReleaseVersion=$1 10 | TARGET_KEY="version" 11 | 12 | # parse all letters a-z and A-Z and replace with nothing 13 | # this will remove all letters from the version string 14 | # this is to ensure that the version string is a valid semver 15 | 16 | # check if there is a letter in the version string 17 | # if there is a letter, then remove it 18 | # if there is no letter, then do nothing 19 | if [[ $nextReleaseVersion =~ [a-zA-Z] ]]; then 20 | nextReleaseVersion=$(echo $nextReleaseVersion | sed 's/[a-zA-Z]//g') 21 | 22 | # check if there is a dash in the version string 23 | # if there is a dash, then replace it with a dot 24 | # if there is no dash, then do nothing 25 | if [[ $nextReleaseVersion =~ "-" ]]; then 26 | # parse all dashes and replace with dots 27 | # this is to ensure that the version string is a valid semver 28 | nextReleaseVersion=$(echo $nextReleaseVersion | sed 's/-/./g') 29 | 30 | # remove everything after the third dot and the dot itself 31 | # this is to ensure that the version string is a valid semver 32 | nextReleaseVersion=$(echo $nextReleaseVersion | sed 's/\.[0-9]*$//g') 33 | # remove the last dot 34 | nextReleaseVersion=$(echo $nextReleaseVersion | sed 's/\.$//g') 35 | fi 36 | fi 37 | 38 | # print the next release version 39 | 40 | printf "[prepareCMD.sh]: Next version: ${nextReleaseVersion}\n" 41 | 42 | # This script is used to execute the prepareCMD.sh script on the remote host 43 | printf "[prepareCMD.sh]: Executing prepareCMD.sh on remote host \n" 44 | 45 | printf "[prepareCMD.sh]: Updating the version in the package.json file \n" 46 | 47 | # make a temp file 48 | tmp=$(mktemp) 49 | 50 | jq --arg a "$nextReleaseVersion" '.version = $a' ./package.json > "$tmp" && mv "$tmp" ./package.json -f 51 | 52 | # Tar zip the entire project 53 | 54 | printf "[prepareCMD.sh]: Zipping the project \n" 55 | 56 | tar -czvf solid-chartjs.tar.gz ./dist 57 | 58 | printf "[prepareCMD.sh]: Done, continuing with release. \n" -------------------------------------------------------------------------------- /dev/App.tsx: -------------------------------------------------------------------------------- 1 | import { createSignal, For, Switch, Match } from 'solid-js' 2 | import DefaultChartPage from './pages/DefaultChart' 3 | import RegisterablesPage from './pages/Registerables' 4 | import TypedChartPage from './pages/TypedChart' 5 | import styles from './styles/App.module.css' 6 | import type { Component } from 'solid-js' 7 | 8 | type DemoPage = 'DefaultChart.tsx' | 'TypedChart.tsx' | 'Registerables.tsx' 9 | 10 | const App: Component = () => { 11 | const demoPages: DemoPage[] = ['DefaultChart.tsx', 'TypedChart.tsx', 'Registerables.tsx'] 12 | 13 | const [demoPage, setDemoPage] = createSignal(demoPages[0]) 14 | 15 | const onDemoPageSelect = (event: any) => { 16 | setDemoPage(event.target.value as DemoPage) 17 | } 18 | 19 | return ( 20 | <> 21 |
22 | 35 |

36 | or{' '} 37 | 43 | view source 44 | 45 |

46 |
47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | ) 60 | } 61 | 62 | export default App 63 | -------------------------------------------------------------------------------- /dev/styles/App.module.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | height: 100%; 4 | margin: 0; 5 | padding: 0; 6 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 7 | 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; 8 | -webkit-font-smoothing: antialiased; 9 | -moz-osx-font-smoothing: grayscale; 10 | } 11 | 12 | .container { 13 | text-align: center; 14 | position: absolute; 15 | top: 50%; 16 | left: 50%; 17 | transform: translate(-50%, -50%); 18 | } 19 | 20 | .chart { 21 | /*height: 400px;*/ 22 | /*width: 700px;*/ 23 | } 24 | 25 | .buttonGroup { 26 | justify-content: center; 27 | margin-top: 20px; 28 | } 29 | 30 | .buttonGroup button { 31 | margin: 0 8px; 32 | } 33 | 34 | .inputGroup { 35 | justify-content: center; 36 | margin-top: 20px; 37 | } 38 | 39 | .inputGroup input { 40 | margin: 0 8px; 41 | } 42 | 43 | .header { 44 | background-color: #ffffff; 45 | min-height: 100vh; 46 | display: flex; 47 | flex-direction: column; 48 | align-items: center; 49 | justify-content: center; 50 | font-size: calc(10px + 2vmin); 51 | color: white; 52 | } 53 | 54 | button { 55 | transition: background 0.25s, border-color 0.25s; 56 | background: rgba(40, 44, 52, 0.05); 57 | border: 1px solid transparent; 58 | border-radius: 6px; 59 | color: #3080d0; 60 | text-decoration: none !important; 61 | display: inline-block; 62 | font-size: 0.8rem; 63 | padding: 8px 16px; 64 | margin: 0 8px 8px 0; 65 | cursor: pointer; 66 | -webkit-user-select: none; 67 | -moz-user-select: none; 68 | user-select: none; 69 | } 70 | 71 | button:hover { 72 | background: rgba(40, 44, 52, 0.1); 73 | border-color: #3080d0; 74 | } 75 | 76 | .inputField { 77 | transition: background 0.25s, border-color 0.25s; 78 | background: rgba(40, 44, 52, 0.05); 79 | border: 1px solid transparent; 80 | border-radius: 6px; 81 | color: #3080d0; 82 | font-size: 0.8rem; 83 | padding: 8px 16px; 84 | margin: 0 8px 8px 0; 85 | } 86 | 87 | .inputField:hover, 88 | .inputField:focus { 89 | background: rgba(40, 44, 52, 0.1); 90 | border-color: #3080d0; 91 | outline: none; 92 | } 93 | 94 | .selectField { 95 | -moz-appearance: none; 96 | -webkit-appearance: none; 97 | appearance: none; 98 | transition: background 0.25s, border-color 0.25s; 99 | background: rgba(40, 44, 52, 0.05); 100 | border: 1px solid transparent; 101 | border-radius: 6px; 102 | color: #3080d0; 103 | font-size: 0.8rem; 104 | padding: 8px 16px; 105 | } 106 | 107 | .selectField:hover, 108 | .selectField:focus { 109 | background: rgba(40, 44, 52, 0.1); 110 | border-color: #3080d0; 111 | outline: none; 112 | } 113 | 114 | .selectContainer { 115 | padding: 3vh; 116 | display: flex; 117 | align-items: center; 118 | justify-content: center; 119 | } 120 | 121 | .link { 122 | color: #3080d0; 123 | text-decoration: none; 124 | transition: ease-in 0.25s; 125 | position: relative; 126 | display: inline-block; 127 | } 128 | 129 | .link:hover, 130 | .link:focus { 131 | color: #183a5c; 132 | } 133 | -------------------------------------------------------------------------------- /src/chart.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-non-null-assertion */ 2 | import { mergeRefs } from '@solid-primitives/refs' 3 | import { Chart, ChartData, ChartItem, ChartOptions, Plugin } from 'chart.js' 4 | import { createEffect, mergeProps, on, onCleanup, onMount, createSignal } from 'solid-js' 5 | import { unwrap } from 'solid-js/store' 6 | import { ChartProps } from './types' 7 | 8 | export default function DefaultChart(props: ChartProps) { 9 | const [canvasRef, setCanvasRef] = createSignal() 10 | const [chart, setChart] = createSignal() 11 | 12 | const merged = mergeProps( 13 | { 14 | width: 512, 15 | height: 512, 16 | type: 'line', 17 | data: {} as ChartData, 18 | options: { responsive: true } as ChartOptions, 19 | plugins: [] as Plugin[], 20 | }, 21 | props, 22 | ) 23 | 24 | const init = () => { 25 | const ctx = canvasRef()?.getContext('2d') as ChartItem 26 | const config = unwrap(merged) 27 | 28 | if (config.type !== 'radar') { 29 | //* remove the RadialLinearScale from the chart's scales 30 | if (config.options.scales?.r) { 31 | console.debug('[Solid-ChartJS]: Removing un-needed RadialLinearScale') 32 | delete config.options.scales?.r 33 | } 34 | } 35 | 36 | const chart = new Chart(ctx, { 37 | type: config.type, 38 | data: config.data, 39 | options: config.options, 40 | plugins: config.plugins, 41 | }) 42 | setChart(chart) 43 | } 44 | 45 | onMount(() => { 46 | init() 47 | }) 48 | 49 | createEffect( 50 | on( 51 | () => merged.data, 52 | () => { 53 | chart()!.data = merged.data 54 | chart()!.update() 55 | }, 56 | { 57 | defer: true, 58 | }, 59 | ), 60 | ) 61 | 62 | createEffect( 63 | on( 64 | () => merged.options, 65 | () => { 66 | chart()!.options = merged.options 67 | chart()!.update() 68 | }, 69 | { 70 | defer: true, 71 | }, 72 | ), 73 | ) 74 | 75 | createEffect( 76 | on( 77 | [() => merged.width, () => merged.height], 78 | () => { 79 | chart()!.resize(merged.width, merged.height) 80 | }, 81 | { 82 | defer: true, 83 | }, 84 | ), 85 | ) 86 | 87 | createEffect( 88 | on( 89 | () => merged.type, 90 | () => { 91 | // save the chart's dimensions 92 | const dimensions = [chart()!.width, chart()!.height] 93 | 94 | chart()!.destroy() 95 | init() 96 | 97 | // restore the chart's dimensions before destroying 98 | chart()!.resize(...dimensions) 99 | }, 100 | { 101 | defer: true, 102 | }, 103 | ), 104 | ) 105 | 106 | onCleanup(() => { 107 | chart()?.destroy() 108 | mergeRefs(props.ref, null) 109 | }) 110 | 111 | return ( 112 | setCanvasRef(el))} 114 | height={merged.height} 115 | width={merged.width}> 116 | {merged.fallback} 117 | 118 | ) 119 | } 120 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "solid-chartjs", 3 | "type": "module", 4 | "version": "1.3.11", 5 | "description": "SolidJS components for Chart.js", 6 | "license": "MIT", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/s0ftik3/solid-chartjs.git" 10 | }, 11 | "bugs": { 12 | "url": "https://github.com/s0ftik3/solid-chartjs/issues" 13 | }, 14 | "files": [ 15 | "dist" 16 | ], 17 | "private": false, 18 | "sideEffects": false, 19 | "exports": { 20 | "solid": { 21 | "development": "./dist/index.dev.jsx", 22 | "import": "./dist/index.jsx" 23 | }, 24 | "browser": { 25 | "development": { 26 | "require": "./dist/index.dev.cjs", 27 | "import": { 28 | "types": "./dist/index.d.ts", 29 | "default": "./dist/index.dev.js" 30 | } 31 | }, 32 | "require": "./dist/index.cjs", 33 | "import": { 34 | "types": "./dist/index.d.ts", 35 | "default": "./dist/index.js" 36 | } 37 | }, 38 | "development": { 39 | "require": "./dist/index.dev.cjs", 40 | "import": { 41 | "types": "./dist/index.d.ts", 42 | "default": "./dist/index.dev.js" 43 | } 44 | }, 45 | "require": "./dist/index.cjs", 46 | "import": { 47 | "types": "./dist/index.d.ts", 48 | "default": "./dist/index.js" 49 | } 50 | }, 51 | "main": "./dist/index.cjs", 52 | "module": "./dist/index.js", 53 | "types": "./dist/index.d.ts", 54 | "browser": {}, 55 | "typesVersions": {}, 56 | "scripts": { 57 | "dev": "vite serve dev", 58 | "build": "tsup", 59 | "test": "concurrently pnpm:test:*", 60 | "test:client": "vitest", 61 | "test:ssr": "pnpm run test:client --mode ssr", 62 | "prepublishOnly": "pnpm build", 63 | "format": "prettier --ignore-path .gitignore -w \"src/**/*.{js,ts,json,css,tsx,jsx}\" \"dev/**/*.{js,ts,json,css,tsx,jsx}\"", 64 | "lint": "eslint --ext .js,.ts,.jsx,.tsx src", 65 | "format:eslint": "pnpm run lint --fix & pnpm prettier --write \"src/**/*.{js,jsx,ts,tsx}\"", 66 | "update-deps": "pnpm up -Li", 67 | "typecheck": "tsc --noEmit" 68 | }, 69 | "peerDependencies": { 70 | "chart.js": "^4.3.0", 71 | "solid-js": "^1.7.5" 72 | }, 73 | "devDependencies": { 74 | "@babel/core": "^7.21.8", 75 | "@babel/preset-env": "^7.21.5", 76 | "@rollup/plugin-node-resolve": "^15.0.2", 77 | "@size-limit/preset-big-lib": "^8.2.4", 78 | "@swc/core": "^1.3.58", 79 | "@swc/helpers": "^0.5.1", 80 | "@testing-library/jest-dom": "^5.16.5", 81 | "@types/node": "^20.1.4", 82 | "@types/testing-library__jest-dom": "^5.14.5", 83 | "@typescript-eslint/eslint-plugin": "^5.59.6", 84 | "@typescript-eslint/parser": "^5.59.6", 85 | "@vitest/coverage-c8": "^0.31.0", 86 | "@solid-primitives/refs": "^1.0.3", 87 | "babel-loader": "^9.1.2", 88 | "browserslist": "^4.21.5", 89 | "chart.js": "^4.3.0", 90 | "chartjs-adapter-date-fns": "^3.0.0", 91 | "chartjs-plugin-annotation": "^3.0.1", 92 | "chartjs-plugin-zoom": "^2.0.1", 93 | "concurrently": "^8.0.1", 94 | "esbuild": "^0.17.19", 95 | "esbuild-plugin-solid": "^0.5.0", 96 | "eslint": "^8.40.0", 97 | "eslint-config-google": "^0.14.0", 98 | "eslint-config-prettier": "^8.8.0", 99 | "eslint-config-standard": "^17.0.0", 100 | "eslint-config-standard-react": "^13.0.0", 101 | "eslint-import-resolver-typescript": "^3.5.5", 102 | "eslint-plugin-autofix": "^1.1.0", 103 | "eslint-plugin-import": "^2.27.5", 104 | "eslint-plugin-node": "^11.1.0", 105 | "eslint-plugin-prettier": "^4.2.1", 106 | "eslint-plugin-promise": "^6.1.1", 107 | "eslint-plugin-solid": "^0.12.1", 108 | "jsdom": "^22.0.0", 109 | "prettier": "^2.8.8", 110 | "solid-js": "^1.7.5", 111 | "tsd": "^0.28.1", 112 | "tsup": "^6.5.0", 113 | "tsup-preset-solid": "^0.0.6", 114 | "typescript": "^5.0.4", 115 | "vite": "^4.3.6", 116 | "vite-plugin-solid": "^2.7.0", 117 | "vitest": "^0.31.0", 118 | "vitest-canvas-mock": "^0.2.2" 119 | }, 120 | "keywords": [ 121 | "solid", 122 | "solidjs", 123 | "solid-js", 124 | "solid js", 125 | "chart", 126 | "chart-js", 127 | "chart.js", 128 | "solid-chartjs-2", 129 | "solidjs-chartjs-2", 130 | "solid chart.js", 131 | "solidjs chart.js", 132 | "solid-chart.js" 133 | ], 134 | "packageManager": "pnpm@7.22.0" 135 | } 136 | -------------------------------------------------------------------------------- /dev/pages/DefaultChart.tsx: -------------------------------------------------------------------------------- 1 | import 'chart.js/auto' 2 | import { ChartData, ChartTypeRegistry } from 'chart.js' 3 | import { createSignal, For } from 'solid-js' 4 | import { createStore } from 'solid-js/store' 5 | import { DefaultChart } from '../../src' 6 | import styles from '../styles/App.module.css' 7 | import { generateRandomChartData, generateRandomDataset } from '../utils' 8 | import type { Component } from 'solid-js' 9 | 10 | const DefaultChartPage: Component = () => { 11 | const [chartData, setChartData] = createSignal(generateRandomChartData()) 12 | const [chartConfig, setChartConfig] = createStore({ 13 | width: 700, 14 | height: 400, 15 | }) 16 | 17 | const chartTypes: (keyof ChartTypeRegistry)[] = [ 18 | 'line', 19 | 'bar', 20 | 'doughnut', 21 | 'radar', 22 | 'polarArea', 23 | 'bubble', 24 | 'pie', 25 | 'scatter', 26 | ] 27 | const [chartType, setChartType] = createSignal(chartTypes[0]) 28 | 29 | const onRandomizeClick = () => { 30 | setChartData((prev) => generateRandomChartData(prev.datasets.length)) 31 | } 32 | const onAddDatasetClick = () => { 33 | setChartData((prev) => { 34 | const datasets = prev.datasets 35 | datasets.push(generateRandomDataset(prev.labels as string[], prev.datasets.length + 1)) 36 | return { ...prev, datasets } 37 | }) 38 | } 39 | const onRemoveDatasetClick = () => { 40 | setChartData((prev) => { 41 | const datasets = prev.datasets 42 | datasets.pop() 43 | return { ...prev, datasets } 44 | }) 45 | } 46 | 47 | const onDimensionsInput = (type: 'width' | 'height', event: any) => { 48 | setChartConfig(type, () => +event.target.value) 49 | } 50 | 51 | const onTypeSelect = (event: any) => { 52 | setChartType(event.target.value as keyof ChartTypeRegistry) 53 | } 54 | 55 | const fallback = () => { 56 | return ( 57 |
58 |

Chart is not available

59 |
60 | ) 61 | } 62 | 63 | return ( 64 |
65 |
66 | 86 |
87 |
88 | 97 | onDimensionsInput('width', event)} 103 | /> 104 | onDimensionsInput('height', event)} 110 | /> 111 |
112 |
113 | 114 | 115 | 116 |
117 |
118 | ) 119 | } 120 | 121 | export default DefaultChartPage 122 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | solid-chartjs 3 |

4 | 5 | # solid-chartjs 6 | 7 | Logo 8 | 9 | [![version](https://badgen.net/npm/v/solid-chartjs)](https://npmjs.com/package/solid-chartjs) 10 | [![downloads](https://badgen.net/npm/dm/solid-chartjs)](https://www.npmjs.com/package/solid-chartjs) 11 | [![telegram chat](https://img.shields.io/badge/Ask%20a%20Question-Telegram-blue)](https://t.me/solid_chartjs) 12 | 13 | The `solid-chartjs` library is a SolidJS wrapper around the [`Chart.js`](https://www.chartjs.org) library, allowing you to easily create interactive charts in your SolidJS applications. 14 | 15 | - [Quick start](#quick-start) 16 | - [Chart Props](#chart-props) 17 | - [Examples](#examples) 18 | - [Credits](#credits) 19 | - [Contributing](#contributing) 20 | - [Contribution Guidelines](#contribution-guidelines) 21 | - [Code and Commit Standards](#code-and-commit-standards) 22 | - [License](#license) 23 | 24 | ## Quick start 25 | 26 | Installation: 27 | 28 | ```bash 29 | pnpm add solid-chartjs chart.js 30 | # or 31 | yarn add solid-chartjs chart.js 32 | # or 33 | npm i solid-chartjs chart.js 34 | ``` 35 | 36 | Demo: [solid-chartjs.vychs.com](https://solid-chartjs.vychs.com/) 37 | 38 | Usage: 39 | 40 | ```tsx 41 | import { onMount } from 'solid-js' 42 | import { Chart, Title, Tooltip, Legend, Colors } from 'chart.js' 43 | import { Line } from 'solid-chartjs' 44 | 45 | const MyChart = () => { 46 | /** 47 | * You must register optional elements before using the chart, 48 | * otherwise you will have the most primitive UI 49 | */ 50 | onMount(() => { 51 | Chart.register(Title, Tooltip, Legend, Colors) 52 | }) 53 | 54 | const chartData = { 55 | labels: ['January', 'February', 'March', 'April', 'May'], 56 | datasets: [ 57 | { 58 | label: 'Sales', 59 | data: [50, 60, 70, 80, 90], 60 | }, 61 | ], 62 | } 63 | const chartOptions = { 64 | responsive: true, 65 | maintainAspectRatio: false, 66 | } 67 | 68 | return ( 69 |
70 | 71 |
72 | ) 73 | } 74 | ``` 75 | 76 | If you don't want to import and register the controllers, elements, scales, and plugins you want to use, you can use the following solution: 77 | 78 | > [!NOTE]\ 79 | > It is considered to better use the tree-shakable way, to decrease the bundle size. 80 | 81 | ```tsx 82 | import 'chart.js/auto' 83 | import { DefaultChart } from 'solid-chartjs' 84 | 85 | 86 | ``` 87 | 88 | ## Chart Props 89 | 90 | | Prop | Description | Type | 91 | | -------- | ----------------------------------------------- | ----------------------------------------------------------------------------------------------------- | 92 | | width | The width of the chart canvas in pixels. | number \| undefined | 93 | | height | The height of the chart canvas in pixels. | number \| undefined | 94 | | fallback | A fallback element to display when chart fails. | JSX.Element | 95 | | type | The type of the chart. | keyof [ChartTypeRegistry](https://www.chartjs.org/docs/latest/api/interfaces/ChartTypeRegistry.html) | 96 | | data | The chart data object. | [ChartData](https://www.chartjs.org/docs/latest/api/interfaces/ChartData.html) \| undefined | 97 | | options | The chart options object. | [ChartOptions](https://www.chartjs.org/docs/latest/api/interfaces/CoreChartOptions.html) \| undefined | 98 | | plugins | The chart plugins object. | [Plugin](https://www.chartjs.org/docs/latest/api/interfaces/Plugin.html)[] \| undefined | 99 | 100 | ## Examples 101 | 102 | Check out `/dev` folder and run the SolidJs application to see how it works. 103 | 104 | You can also use the `DefaultChart` components: 105 | 106 | > [!NOTE]\ 107 | > `DefaultChart` is a wrapper around `Chart` component, so you can use all the props from `Chart` component. 108 | > `DefaultChart` component does _not_ have its registrable elements registered by default, so you need to register them yourself unless you use `chart.js/auto`. 109 | 110 | ```tsx 111 | import { onMount } from 'solid-js' 112 | import { 113 | Chart, 114 | LineController, 115 | CategoryScale, 116 | PointElement, 117 | LineElement, 118 | LinearScale, 119 | } from 'chart.js' 120 | import { DefaultChart } from 'solid-chartjs' 121 | 122 | const MyChart = () => { 123 | onMount(() => { 124 | Chart.register(LineController, CategoryScale, PointElement, LineElement, LinearScale) 125 | }) 126 | 127 | // ... your data and options objects 128 | 129 | return 130 | } 131 | ``` 132 | 133 | Usage of `fallback` prop: 134 | 135 | ```tsx 136 | const fallback = () => { 137 | return ( 138 |
139 |

Chart is not available

140 |
141 | ) 142 | } 143 | 144 | 145 | ``` 146 | 147 | ## Credits 148 | 149 | - This library is _heavily_ inspired by [react-chartjs-2](https://react-chartjs-2.js.org/) 150 | - Awesome charting library [Chart.js](https://www.chartjs.org) 151 | - Flexible library for building user interfaces [SolidJs](https://www.solidjs.com/) 152 | --------------------------------------------------------------------------------