├── .nvmrc ├── website ├── static │ ├── .nojekyll │ └── img │ │ ├── favicon.ico │ │ ├── chartist-guy.gif │ │ └── logo.svg ├── CNAME ├── docs │ ├── api │ │ ├── .gitignore │ │ ├── basics.md │ │ └── docs.js │ ├── plugins.md │ ├── docs.js │ ├── examples │ │ ├── docs.js │ │ └── index.mdx │ ├── what-is-it-made-for.md │ ├── whats-new-in-v1.md │ └── index.mdx ├── babel.config.js ├── .gitignore ├── tsconfig.json ├── src │ ├── components │ │ └── ContextProvider.tsx │ ├── css │ │ ├── custom.css │ │ └── recoloring.css │ └── prism-theme.js ├── README.md ├── sidebars.js ├── package.json └── docusaurus.config.js ├── .gitattributes ├── .npmrc ├── pnpm-workspace.yaml ├── src ├── event │ ├── index.ts │ └── EventEmitter.ts ├── charts │ ├── BarChart │ │ ├── index.ts │ │ └── BarChart.types.ts │ ├── PieChart │ │ ├── index.ts │ │ └── PieChart.stories.ts │ ├── LineChart │ │ └── index.ts │ ├── index.ts │ └── types.ts ├── utils │ ├── index.ts │ ├── types.ts │ ├── extend.ts │ ├── functional.ts │ └── utils.ts ├── svg │ ├── index.ts │ ├── types.ts │ └── SvgList.ts ├── axes │ ├── index.ts │ ├── types.ts │ ├── StepAxis.ts │ ├── StepAxis.spec.ts │ ├── FixedScaleAxis.spec.ts │ ├── FixedScaleAxis.ts │ ├── AutoScaleAxis.ts │ └── Axis.spec.ts ├── interpolation │ ├── index.ts │ ├── none.ts │ ├── simple.ts │ └── step.ts ├── core │ ├── data │ │ ├── index.ts │ │ ├── serialize.spec.ts │ │ ├── data.ts │ │ ├── serialize.ts │ │ ├── segments.spec.ts │ │ ├── segments.ts │ │ └── highLow.ts │ ├── index.ts │ ├── constants.ts │ ├── lang.spec.ts │ ├── lang.ts │ ├── optionsProvider.ts │ └── math.ts ├── index.ts └── styles │ └── _settings.scss ├── .browserslistrc ├── .czrc ├── .storybook ├── package.json ├── manager.js ├── theme.js ├── preview.js └── main.js ├── .clean-publish ├── .nano-staged.json ├── .github ├── FUNDING.yml ├── renovate.json ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── feature-request.yml │ └── bug-report.yml └── workflows │ ├── commit.yml │ ├── release.yml │ ├── update-storyshots.yml │ ├── ci.yml │ ├── website.yml │ └── checks.yml ├── test ├── setup.js ├── __image_snapshots__ │ ├── BarChart__Default-snap.png │ ├── BarChart__Labels-snap.png │ ├── BarChart__Stack-snap.png │ ├── LineChart__Area-snap.png │ ├── LineChart__Holes-snap.png │ ├── LineChart__Labels-snap.png │ ├── PieChart__Default-snap.png │ ├── PieChart__Donut-snap.png │ ├── PieChart__Labels-snap.png │ ├── PieChart__Solid-snap.png │ ├── BarChart__Adaptive-snap.png │ ├── BarChart__Bi-Polar-snap.png │ ├── LineChart__Default-snap.png │ ├── LineChart__Scatter-snap.png │ ├── BarChart__Horizontal-snap.png │ ├── BarChart__Multi-Series-snap.png │ ├── BarChart__Peak-Circles-snap.png │ ├── BarChart__Reverse-Data-snap.png │ ├── LineChart__Auto-Scale-snap.png │ ├── LineChart__Full-Width-snap.png │ ├── PieChart__Gauge-Donut-snap.png │ ├── PieChart__Small-Slices-snap.png │ ├── PieChart__Start-Angle-snap.png │ ├── BarChart__Adaptive__iPad-snap.png │ ├── LineChart__Bi-Polar-Area-snap.png │ ├── LineChart__Custom-Points-snap.png │ ├── LineChart__Filled-Holes-snap.png │ ├── LineChart__Multi-Series-snap.png │ ├── LineChart__Reverse-Data-snap.png │ ├── BarChart__Labels-Placement-snap.png │ ├── BarChart__Multiline-Labels-snap.png │ ├── LineChart__Path-Animation-snap.png │ ├── LineChart__Series-Overrides-snap.png │ ├── BarChart__Adaptive__iPhone-X-snap.png │ ├── BarChart__Distributed-Series-snap.png │ ├── LineChart__Only-Whole-Numbers-snap.png │ ├── PieChart__Ignore-Empty-Values-snap.png │ ├── PieChart__Label-Interpolation-snap.png │ ├── PieChart__Relative-Donut-Width-snap.png │ ├── BarChart__Accumulate-Relative-Stack-snap.png │ ├── BarChart__Overlapping-Bars-On-Mobile-snap.png │ ├── BarChart__Adaptive__iPhone-X-landscape-snap.png │ ├── LineChart__No-Interpolation-With-Holes-snap.png │ ├── LineChart__Simple-Interpolation-With-Holes-snap.png │ ├── LineChart__Step-Interpolation-With-Holes-snap.png │ ├── LineChart__Cardinal-Interpolation-With-Holes-snap.png │ ├── BarChart__Overlapping-Bars-On-Mobile__iPhone-X-snap.png │ ├── LineChart__Monotone-Cubic-Interpolation-With-Holes-snap.png │ └── LineChart__Step-No-Postpone-Interpolation-With-Holes-snap.png ├── utils │ ├── storyshots │ │ ├── index.js │ │ ├── viewport.ts │ │ ├── storybook.js │ │ ├── initStoryshots.js │ │ └── imageSnapshotWithStoryParameters.js │ └── skipable.js ├── mock │ ├── cssModule.js │ └── dom.ts └── storyshots.spec.js ├── .commitlintrc.json ├── tsconfig.build.json ├── .simple-git-hooks.json ├── sandboxes ├── bar │ ├── stacked │ │ ├── sandbox.config.json │ │ ├── package.json │ │ ├── index.html │ │ └── index.ts │ ├── horizontal │ │ ├── sandbox.config.json │ │ ├── package.json │ │ ├── index.html │ │ └── index.ts │ ├── multiline │ │ ├── sandbox.config.json │ │ ├── package.json │ │ ├── index.html │ │ └── index.ts │ ├── distributed-series │ │ ├── sandbox.config.json │ │ ├── package.json │ │ ├── index.html │ │ └── index.ts │ ├── extreme-responsive │ │ ├── sandbox.config.json │ │ ├── package.json │ │ ├── index.html │ │ └── index.ts │ ├── label-position │ │ ├── sandbox.config.json │ │ ├── package.json │ │ ├── index.html │ │ └── index.ts │ ├── overlapping-bars │ │ ├── sandbox.config.json │ │ ├── package.json │ │ ├── index.html │ │ └── index.ts │ ├── bi-polar-interpolated │ │ ├── sandbox.config.json │ │ ├── package.json │ │ ├── index.html │ │ └── index.ts │ ├── stacked-accumulate-relative │ │ ├── sandbox.config.json │ │ ├── index.html │ │ ├── package.json │ │ └── index.ts │ └── with-circle-modify-drawing │ │ ├── sandbox.config.json │ │ ├── index.html │ │ ├── package.json │ │ └── index.ts ├── line │ ├── area │ │ ├── sandbox.config.json │ │ ├── package.json │ │ ├── index.html │ │ └── index.ts │ ├── simple │ │ ├── sandbox.config.json │ │ ├── package.json │ │ ├── index.html │ │ └── index.ts │ ├── axis-auto │ │ ├── sandbox.config.json │ │ ├── package.json │ │ ├── index.html │ │ └── index.ts │ ├── bipolar-area │ │ ├── sandbox.config.json │ │ ├── package.json │ │ ├── index.html │ │ └── index.ts │ ├── data-holes │ │ ├── sandbox.config.json │ │ ├── package.json │ │ ├── index.html │ │ └── index.ts │ ├── only-integer │ │ ├── sandbox.config.json │ │ ├── package.json │ │ ├── index.html │ │ └── index.ts │ ├── timeseries │ │ ├── sandbox.config.json │ │ ├── package.json │ │ ├── index.html │ │ └── index.ts │ ├── data-fill-holes │ │ ├── sandbox.config.json │ │ ├── package.json │ │ ├── index.html │ │ └── index.ts │ ├── modify-drawing │ │ ├── sandbox.config.json │ │ ├── package.json │ │ ├── index.html │ │ └── index.ts │ ├── path-animation │ │ ├── sandbox.config.json │ │ ├── package.json │ │ ├── index.html │ │ └── index.ts │ ├── scatter-random │ │ ├── sandbox.config.json │ │ ├── index.html │ │ ├── package.json │ │ └── index.ts │ ├── series-override │ │ ├── sandbox.config.json │ │ ├── package.json │ │ ├── index.html │ │ └── index.ts │ ├── simple-responsive │ │ ├── sandbox.config.json │ │ ├── package.json │ │ ├── index.html │ │ └── index.ts │ ├── simple-smoothing │ │ ├── sandbox.config.json │ │ ├── package.json │ │ ├── index.html │ │ └── index.ts │ ├── svg-animation │ │ ├── sandbox.config.json │ │ ├── package.json │ │ └── index.html │ ├── axis-fixed-and-auto │ │ ├── sandbox.config.json │ │ ├── package.json │ │ ├── index.html │ │ └── index.ts │ └── simple-svg-animation │ │ ├── sandbox.config.json │ │ ├── package.json │ │ ├── index.html │ │ └── index.ts ├── pie │ ├── simple │ │ ├── sandbox.config.json │ │ ├── package.json │ │ ├── index.html │ │ └── index.ts │ ├── custom-labels │ │ ├── sandbox.config.json │ │ ├── package.json │ │ ├── index.html │ │ └── index.ts │ ├── donut-chart │ │ ├── sandbox.config.json │ │ ├── package.json │ │ ├── index.html │ │ └── index.ts │ ├── simple-gauge │ │ ├── sandbox.config.json │ │ ├── package.json │ │ ├── index.html │ │ └── index.ts │ └── donut-animation │ │ ├── sandbox.config.json │ │ ├── package.json │ │ ├── index.html │ │ └── index.ts └── tsconfig.json ├── .prettierrc ├── .gitignore ├── postcss.config.cjs ├── .editorconfig ├── LICENSE-WTFPL ├── .size-limit.json ├── jest.config.json ├── tsconfig.json ├── LICENSE-MIT ├── CONTRIBUTING.md ├── scripts └── styles.cjs ├── rollup.config.js └── .eslintrc.json /.nvmrc: -------------------------------------------------------------------------------- 1 | lts/* 2 | -------------------------------------------------------------------------------- /website/static/.nojekyll: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /website/CNAME: -------------------------------------------------------------------------------- 1 | chartist.js.org 2 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | strict-peer-dependencies=false 2 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - 'website' 3 | -------------------------------------------------------------------------------- /src/event/index.ts: -------------------------------------------------------------------------------- 1 | export * from './EventEmitter'; 2 | -------------------------------------------------------------------------------- /.browserslistrc: -------------------------------------------------------------------------------- 1 | defaults 2 | not ie 11 3 | not ie_mob 11 4 | -------------------------------------------------------------------------------- /.czrc: -------------------------------------------------------------------------------- 1 | { 2 | "path": "@commitlint/cz-commitlint" 3 | } 4 | -------------------------------------------------------------------------------- /.storybook/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "commonjs" 3 | } 4 | -------------------------------------------------------------------------------- /website/docs/api/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | !docs.js 4 | !basics.md 5 | -------------------------------------------------------------------------------- /.clean-publish: -------------------------------------------------------------------------------- 1 | { 2 | "withoutPublish": true, 3 | "tempDir": "package" 4 | } 5 | -------------------------------------------------------------------------------- /.nano-staged.json: -------------------------------------------------------------------------------- 1 | { 2 | "**/*.{js,ts}": ["prettier --write", "eslint"] 3 | } 4 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: gionkunz 4 | -------------------------------------------------------------------------------- /src/charts/BarChart/index.ts: -------------------------------------------------------------------------------- 1 | export * from './BarChart'; 2 | export * from './BarChart.types'; 3 | -------------------------------------------------------------------------------- /src/charts/PieChart/index.ts: -------------------------------------------------------------------------------- 1 | export * from './PieChart'; 2 | export * from './PieChart.types'; 3 | -------------------------------------------------------------------------------- /src/charts/LineChart/index.ts: -------------------------------------------------------------------------------- 1 | export * from './LineChart'; 2 | export * from './LineChart.types'; 3 | -------------------------------------------------------------------------------- /test/setup.js: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom/extend-expect'; 2 | 3 | window.matchMedia = () => ({}); 4 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base", 4 | ":preserveSemverRanges" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /website/static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamkin-skuf/chartist/HEAD/website/static/img/favicon.ico -------------------------------------------------------------------------------- /website/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')] 3 | }; 4 | -------------------------------------------------------------------------------- /website/static/img/chartist-guy.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamkin-skuf/chartist/HEAD/website/static/img/chartist-guy.gif -------------------------------------------------------------------------------- /website/docs/plugins.md: -------------------------------------------------------------------------------- 1 | --- 2 | slug: /docs/plugins 3 | description: Plugins 4 | --- 5 | 6 | # Plugins 7 | 8 | Coming soon. 9 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './types'; 2 | export * from './extend'; 3 | export * from './functional'; 4 | export * from './utils'; 5 | -------------------------------------------------------------------------------- /.commitlintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@commitlint/config-conventional"], 3 | "rules": { 4 | "body-max-line-length": [0] 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": [ 4 | "**/*.stories.ts", 5 | "**/*.spec.ts" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.simple-git-hooks.json: -------------------------------------------------------------------------------- 1 | { 2 | "commit-msg": "pnpm commitlint --edit \"$1\"", 3 | "pre-commit": "pnpm nano-staged", 4 | "pre-push": "pnpm test" 5 | } 6 | -------------------------------------------------------------------------------- /test/__image_snapshots__/BarChart__Default-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamkin-skuf/chartist/HEAD/test/__image_snapshots__/BarChart__Default-snap.png -------------------------------------------------------------------------------- /test/__image_snapshots__/BarChart__Labels-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamkin-skuf/chartist/HEAD/test/__image_snapshots__/BarChart__Labels-snap.png -------------------------------------------------------------------------------- /test/__image_snapshots__/BarChart__Stack-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamkin-skuf/chartist/HEAD/test/__image_snapshots__/BarChart__Stack-snap.png -------------------------------------------------------------------------------- /test/__image_snapshots__/LineChart__Area-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamkin-skuf/chartist/HEAD/test/__image_snapshots__/LineChart__Area-snap.png -------------------------------------------------------------------------------- /test/__image_snapshots__/LineChart__Holes-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamkin-skuf/chartist/HEAD/test/__image_snapshots__/LineChart__Holes-snap.png -------------------------------------------------------------------------------- /test/__image_snapshots__/LineChart__Labels-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamkin-skuf/chartist/HEAD/test/__image_snapshots__/LineChart__Labels-snap.png -------------------------------------------------------------------------------- /test/__image_snapshots__/PieChart__Default-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamkin-skuf/chartist/HEAD/test/__image_snapshots__/PieChart__Default-snap.png -------------------------------------------------------------------------------- /test/__image_snapshots__/PieChart__Donut-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamkin-skuf/chartist/HEAD/test/__image_snapshots__/PieChart__Donut-snap.png -------------------------------------------------------------------------------- /test/__image_snapshots__/PieChart__Labels-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamkin-skuf/chartist/HEAD/test/__image_snapshots__/PieChart__Labels-snap.png -------------------------------------------------------------------------------- /test/__image_snapshots__/PieChart__Solid-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamkin-skuf/chartist/HEAD/test/__image_snapshots__/PieChart__Solid-snap.png -------------------------------------------------------------------------------- /test/utils/storyshots/index.js: -------------------------------------------------------------------------------- 1 | export * from './initStoryshots'; 2 | export * from './imageSnapshotWithStoryParameters'; 3 | export * from './storybook'; 4 | -------------------------------------------------------------------------------- /test/__image_snapshots__/BarChart__Adaptive-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamkin-skuf/chartist/HEAD/test/__image_snapshots__/BarChart__Adaptive-snap.png -------------------------------------------------------------------------------- /test/__image_snapshots__/BarChart__Bi-Polar-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamkin-skuf/chartist/HEAD/test/__image_snapshots__/BarChart__Bi-Polar-snap.png -------------------------------------------------------------------------------- /test/__image_snapshots__/LineChart__Default-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamkin-skuf/chartist/HEAD/test/__image_snapshots__/LineChart__Default-snap.png -------------------------------------------------------------------------------- /test/__image_snapshots__/LineChart__Scatter-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamkin-skuf/chartist/HEAD/test/__image_snapshots__/LineChart__Scatter-snap.png -------------------------------------------------------------------------------- /test/__image_snapshots__/BarChart__Horizontal-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamkin-skuf/chartist/HEAD/test/__image_snapshots__/BarChart__Horizontal-snap.png -------------------------------------------------------------------------------- /test/__image_snapshots__/BarChart__Multi-Series-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamkin-skuf/chartist/HEAD/test/__image_snapshots__/BarChart__Multi-Series-snap.png -------------------------------------------------------------------------------- /test/__image_snapshots__/BarChart__Peak-Circles-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamkin-skuf/chartist/HEAD/test/__image_snapshots__/BarChart__Peak-Circles-snap.png -------------------------------------------------------------------------------- /test/__image_snapshots__/BarChart__Reverse-Data-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamkin-skuf/chartist/HEAD/test/__image_snapshots__/BarChart__Reverse-Data-snap.png -------------------------------------------------------------------------------- /test/__image_snapshots__/LineChart__Auto-Scale-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamkin-skuf/chartist/HEAD/test/__image_snapshots__/LineChart__Auto-Scale-snap.png -------------------------------------------------------------------------------- /test/__image_snapshots__/LineChart__Full-Width-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamkin-skuf/chartist/HEAD/test/__image_snapshots__/LineChart__Full-Width-snap.png -------------------------------------------------------------------------------- /test/__image_snapshots__/PieChart__Gauge-Donut-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamkin-skuf/chartist/HEAD/test/__image_snapshots__/PieChart__Gauge-Donut-snap.png -------------------------------------------------------------------------------- /test/__image_snapshots__/PieChart__Small-Slices-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamkin-skuf/chartist/HEAD/test/__image_snapshots__/PieChart__Small-Slices-snap.png -------------------------------------------------------------------------------- /test/__image_snapshots__/PieChart__Start-Angle-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamkin-skuf/chartist/HEAD/test/__image_snapshots__/PieChart__Start-Angle-snap.png -------------------------------------------------------------------------------- /test/__image_snapshots__/BarChart__Adaptive__iPad-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamkin-skuf/chartist/HEAD/test/__image_snapshots__/BarChart__Adaptive__iPad-snap.png -------------------------------------------------------------------------------- /test/__image_snapshots__/LineChart__Bi-Polar-Area-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamkin-skuf/chartist/HEAD/test/__image_snapshots__/LineChart__Bi-Polar-Area-snap.png -------------------------------------------------------------------------------- /test/__image_snapshots__/LineChart__Custom-Points-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamkin-skuf/chartist/HEAD/test/__image_snapshots__/LineChart__Custom-Points-snap.png -------------------------------------------------------------------------------- /test/__image_snapshots__/LineChart__Filled-Holes-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamkin-skuf/chartist/HEAD/test/__image_snapshots__/LineChart__Filled-Holes-snap.png -------------------------------------------------------------------------------- /test/__image_snapshots__/LineChart__Multi-Series-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamkin-skuf/chartist/HEAD/test/__image_snapshots__/LineChart__Multi-Series-snap.png -------------------------------------------------------------------------------- /test/__image_snapshots__/LineChart__Reverse-Data-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamkin-skuf/chartist/HEAD/test/__image_snapshots__/LineChart__Reverse-Data-snap.png -------------------------------------------------------------------------------- /test/mock/cssModule.js: -------------------------------------------------------------------------------- 1 | const mock = new Proxy( 2 | {}, 3 | { 4 | get() { 5 | return ''; 6 | } 7 | } 8 | ); 9 | 10 | module.exports = mock; 11 | -------------------------------------------------------------------------------- /sandboxes/bar/stacked/sandbox.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "infiniteLoopProtection": true, 3 | "hardReloadOnChange": true, 4 | "view": "browser", 5 | "template": "parcel" 6 | } 7 | -------------------------------------------------------------------------------- /sandboxes/line/area/sandbox.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "infiniteLoopProtection": true, 3 | "hardReloadOnChange": true, 4 | "view": "browser", 5 | "template": "parcel" 6 | } 7 | -------------------------------------------------------------------------------- /sandboxes/line/simple/sandbox.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "infiniteLoopProtection": true, 3 | "hardReloadOnChange": true, 4 | "view": "browser", 5 | "template": "parcel" 6 | } 7 | -------------------------------------------------------------------------------- /sandboxes/pie/simple/sandbox.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "infiniteLoopProtection": true, 3 | "hardReloadOnChange": true, 4 | "view": "browser", 5 | "template": "parcel" 6 | } 7 | -------------------------------------------------------------------------------- /src/charts/index.ts: -------------------------------------------------------------------------------- 1 | export * from './BaseChart'; 2 | export * from './LineChart'; 3 | export * from './BarChart'; 4 | export * from './PieChart'; 5 | export * from './types'; 6 | -------------------------------------------------------------------------------- /src/svg/index.ts: -------------------------------------------------------------------------------- 1 | export { easings } from './animation'; 2 | export * from './Svg'; 3 | export * from './SvgPath'; 4 | export * from './SvgList'; 5 | export * from './types'; 6 | -------------------------------------------------------------------------------- /test/__image_snapshots__/BarChart__Labels-Placement-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamkin-skuf/chartist/HEAD/test/__image_snapshots__/BarChart__Labels-Placement-snap.png -------------------------------------------------------------------------------- /test/__image_snapshots__/BarChart__Multiline-Labels-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamkin-skuf/chartist/HEAD/test/__image_snapshots__/BarChart__Multiline-Labels-snap.png -------------------------------------------------------------------------------- /test/__image_snapshots__/LineChart__Path-Animation-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamkin-skuf/chartist/HEAD/test/__image_snapshots__/LineChart__Path-Animation-snap.png -------------------------------------------------------------------------------- /test/__image_snapshots__/LineChart__Series-Overrides-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamkin-skuf/chartist/HEAD/test/__image_snapshots__/LineChart__Series-Overrides-snap.png -------------------------------------------------------------------------------- /sandboxes/bar/horizontal/sandbox.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "infiniteLoopProtection": true, 3 | "hardReloadOnChange": true, 4 | "view": "browser", 5 | "template": "parcel" 6 | } 7 | -------------------------------------------------------------------------------- /sandboxes/bar/multiline/sandbox.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "infiniteLoopProtection": true, 3 | "hardReloadOnChange": true, 4 | "view": "browser", 5 | "template": "parcel" 6 | } 7 | -------------------------------------------------------------------------------- /sandboxes/line/axis-auto/sandbox.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "infiniteLoopProtection": true, 3 | "hardReloadOnChange": true, 4 | "view": "browser", 5 | "template": "parcel" 6 | } 7 | -------------------------------------------------------------------------------- /sandboxes/line/bipolar-area/sandbox.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "infiniteLoopProtection": true, 3 | "hardReloadOnChange": true, 4 | "view": "browser", 5 | "template": "parcel" 6 | } 7 | -------------------------------------------------------------------------------- /sandboxes/line/data-holes/sandbox.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "infiniteLoopProtection": true, 3 | "hardReloadOnChange": true, 4 | "view": "browser", 5 | "template": "parcel" 6 | } 7 | -------------------------------------------------------------------------------- /sandboxes/line/only-integer/sandbox.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "infiniteLoopProtection": true, 3 | "hardReloadOnChange": true, 4 | "view": "browser", 5 | "template": "parcel" 6 | } 7 | -------------------------------------------------------------------------------- /sandboxes/line/timeseries/sandbox.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "infiniteLoopProtection": true, 3 | "hardReloadOnChange": true, 4 | "view": "browser", 5 | "template": "parcel" 6 | } 7 | -------------------------------------------------------------------------------- /sandboxes/pie/custom-labels/sandbox.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "infiniteLoopProtection": true, 3 | "hardReloadOnChange": true, 4 | "view": "browser", 5 | "template": "parcel" 6 | } 7 | -------------------------------------------------------------------------------- /sandboxes/pie/donut-chart/sandbox.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "infiniteLoopProtection": true, 3 | "hardReloadOnChange": true, 4 | "view": "browser", 5 | "template": "parcel" 6 | } 7 | -------------------------------------------------------------------------------- /sandboxes/pie/simple-gauge/sandbox.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "infiniteLoopProtection": true, 3 | "hardReloadOnChange": true, 4 | "view": "browser", 5 | "template": "parcel" 6 | } 7 | -------------------------------------------------------------------------------- /src/axes/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Axis'; 2 | export * from './AutoScaleAxis'; 3 | export * from './FixedScaleAxis'; 4 | export * from './StepAxis'; 5 | export * from './types'; 6 | -------------------------------------------------------------------------------- /src/interpolation/index.ts: -------------------------------------------------------------------------------- 1 | export * from './none'; 2 | export * from './simple'; 3 | export * from './step'; 4 | export * from './cardinal'; 5 | export * from './monotoneCubic'; 6 | -------------------------------------------------------------------------------- /test/__image_snapshots__/BarChart__Adaptive__iPhone-X-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamkin-skuf/chartist/HEAD/test/__image_snapshots__/BarChart__Adaptive__iPhone-X-snap.png -------------------------------------------------------------------------------- /test/__image_snapshots__/BarChart__Distributed-Series-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamkin-skuf/chartist/HEAD/test/__image_snapshots__/BarChart__Distributed-Series-snap.png -------------------------------------------------------------------------------- /test/__image_snapshots__/LineChart__Only-Whole-Numbers-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamkin-skuf/chartist/HEAD/test/__image_snapshots__/LineChart__Only-Whole-Numbers-snap.png -------------------------------------------------------------------------------- /test/__image_snapshots__/PieChart__Ignore-Empty-Values-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamkin-skuf/chartist/HEAD/test/__image_snapshots__/PieChart__Ignore-Empty-Values-snap.png -------------------------------------------------------------------------------- /test/__image_snapshots__/PieChart__Label-Interpolation-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamkin-skuf/chartist/HEAD/test/__image_snapshots__/PieChart__Label-Interpolation-snap.png -------------------------------------------------------------------------------- /sandboxes/bar/distributed-series/sandbox.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "infiniteLoopProtection": true, 3 | "hardReloadOnChange": true, 4 | "view": "browser", 5 | "template": "parcel" 6 | } 7 | -------------------------------------------------------------------------------- /sandboxes/bar/extreme-responsive/sandbox.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "infiniteLoopProtection": true, 3 | "hardReloadOnChange": true, 4 | "view": "browser", 5 | "template": "parcel" 6 | } 7 | -------------------------------------------------------------------------------- /sandboxes/bar/label-position/sandbox.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "infiniteLoopProtection": true, 3 | "hardReloadOnChange": true, 4 | "view": "browser", 5 | "template": "parcel" 6 | } 7 | -------------------------------------------------------------------------------- /sandboxes/bar/overlapping-bars/sandbox.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "infiniteLoopProtection": true, 3 | "hardReloadOnChange": true, 4 | "view": "browser", 5 | "template": "parcel" 6 | } 7 | -------------------------------------------------------------------------------- /sandboxes/line/data-fill-holes/sandbox.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "infiniteLoopProtection": true, 3 | "hardReloadOnChange": true, 4 | "view": "browser", 5 | "template": "parcel" 6 | } 7 | -------------------------------------------------------------------------------- /sandboxes/line/modify-drawing/sandbox.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "infiniteLoopProtection": true, 3 | "hardReloadOnChange": true, 4 | "view": "browser", 5 | "template": "parcel" 6 | } 7 | -------------------------------------------------------------------------------- /sandboxes/line/path-animation/sandbox.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "infiniteLoopProtection": true, 3 | "hardReloadOnChange": true, 4 | "view": "browser", 5 | "template": "parcel" 6 | } 7 | -------------------------------------------------------------------------------- /sandboxes/line/scatter-random/sandbox.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "infiniteLoopProtection": true, 3 | "hardReloadOnChange": true, 4 | "view": "browser", 5 | "template": "parcel" 6 | } 7 | -------------------------------------------------------------------------------- /sandboxes/line/series-override/sandbox.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "infiniteLoopProtection": true, 3 | "hardReloadOnChange": true, 4 | "view": "browser", 5 | "template": "parcel" 6 | } 7 | -------------------------------------------------------------------------------- /sandboxes/line/simple-responsive/sandbox.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "infiniteLoopProtection": true, 3 | "hardReloadOnChange": true, 4 | "view": "browser", 5 | "template": "parcel" 6 | } 7 | -------------------------------------------------------------------------------- /sandboxes/line/simple-smoothing/sandbox.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "infiniteLoopProtection": true, 3 | "hardReloadOnChange": true, 4 | "view": "browser", 5 | "template": "parcel" 6 | } 7 | -------------------------------------------------------------------------------- /sandboxes/line/svg-animation/sandbox.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "infiniteLoopProtection": true, 3 | "hardReloadOnChange": true, 4 | "view": "browser", 5 | "template": "parcel" 6 | } 7 | -------------------------------------------------------------------------------- /sandboxes/pie/donut-animation/sandbox.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "infiniteLoopProtection": true, 3 | "hardReloadOnChange": true, 4 | "view": "browser", 5 | "template": "parcel" 6 | } 7 | -------------------------------------------------------------------------------- /test/__image_snapshots__/PieChart__Relative-Donut-Width-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamkin-skuf/chartist/HEAD/test/__image_snapshots__/PieChart__Relative-Donut-Width-snap.png -------------------------------------------------------------------------------- /.storybook/manager.js: -------------------------------------------------------------------------------- 1 | import { addons } from '@storybook/addons'; 2 | 3 | import { theme } from './theme'; 4 | 5 | addons.setConfig({ 6 | theme, 7 | panelPosition: 'right' 8 | }); 9 | -------------------------------------------------------------------------------- /sandboxes/bar/bi-polar-interpolated/sandbox.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "infiniteLoopProtection": true, 3 | "hardReloadOnChange": true, 4 | "view": "browser", 5 | "template": "parcel" 6 | } 7 | -------------------------------------------------------------------------------- /sandboxes/line/axis-fixed-and-auto/sandbox.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "infiniteLoopProtection": true, 3 | "hardReloadOnChange": true, 4 | "view": "browser", 5 | "template": "parcel" 6 | } 7 | -------------------------------------------------------------------------------- /sandboxes/line/simple-svg-animation/sandbox.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "infiniteLoopProtection": true, 3 | "hardReloadOnChange": true, 4 | "view": "browser", 5 | "template": "parcel" 6 | } 7 | -------------------------------------------------------------------------------- /website/docs/docs.js: -------------------------------------------------------------------------------- 1 | exports.docs = [ 2 | { title: "What's new in v1?", slug: '/docs/whats-new-in-v1' }, 3 | { title: 'What is it made for?', slug: '/docs/what-is-it-made-for' } 4 | ]; 5 | -------------------------------------------------------------------------------- /sandboxes/bar/stacked-accumulate-relative/sandbox.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "infiniteLoopProtection": true, 3 | "hardReloadOnChange": true, 4 | "view": "browser", 5 | "template": "parcel" 6 | } 7 | -------------------------------------------------------------------------------- /sandboxes/bar/with-circle-modify-drawing/sandbox.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "infiniteLoopProtection": true, 3 | "hardReloadOnChange": true, 4 | "view": "browser", 5 | "template": "parcel" 6 | } 7 | -------------------------------------------------------------------------------- /test/__image_snapshots__/BarChart__Accumulate-Relative-Stack-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamkin-skuf/chartist/HEAD/test/__image_snapshots__/BarChart__Accumulate-Relative-Stack-snap.png -------------------------------------------------------------------------------- /test/__image_snapshots__/BarChart__Overlapping-Bars-On-Mobile-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamkin-skuf/chartist/HEAD/test/__image_snapshots__/BarChart__Overlapping-Bars-On-Mobile-snap.png -------------------------------------------------------------------------------- /sandboxes/line/area/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "line-area", 3 | "description": "Line chart with area", 4 | "main": "index.ts", 5 | "dependencies": { 6 | "chartist": "^1.0.0" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /sandboxes/pie/simple/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pie-simple", 3 | "description": "Simple pie chart", 4 | "main": "index.ts", 5 | "dependencies": { 6 | "chartist": "^1.0.0" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/__image_snapshots__/BarChart__Adaptive__iPhone-X-landscape-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamkin-skuf/chartist/HEAD/test/__image_snapshots__/BarChart__Adaptive__iPhone-X-landscape-snap.png -------------------------------------------------------------------------------- /test/__image_snapshots__/LineChart__No-Interpolation-With-Holes-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamkin-skuf/chartist/HEAD/test/__image_snapshots__/LineChart__No-Interpolation-With-Holes-snap.png -------------------------------------------------------------------------------- /sandboxes/bar/multiline/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bar-multiline", 3 | "description": "Multi-line labels", 4 | "main": "index.ts", 5 | "dependencies": { 6 | "chartist": "^1.0.0" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /sandboxes/bar/stacked/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bar-stacked", 3 | "description": "Stacked bar chart", 4 | "main": "index.ts", 5 | "dependencies": { 6 | "chartist": "^1.0.0" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /sandboxes/line/axis-auto/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "line-axis-auto", 3 | "description": "Auto scale axis", 4 | "main": "index.ts", 5 | "dependencies": { 6 | "chartist": "^1.0.0" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /sandboxes/line/data-holes/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "line-data-holes", 3 | "description": "Holes in data", 4 | "main": "index.ts", 5 | "dependencies": { 6 | "chartist": "^1.0.0" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /sandboxes/line/simple/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "line-simple", 3 | "description": "Simple line chart", 4 | "main": "index.ts", 5 | "dependencies": { 6 | "chartist": "^1.0.0" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /sandboxes/line/timeseries/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "line-timeseries", 3 | "description": "Timeseries", 4 | "main": "index.ts", 5 | "dependencies": { 6 | "chartist": "^1.0.0" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /sandboxes/pie/donut-chart/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pie-donut-chart", 3 | "description": "Donut chart", 4 | "main": "index.ts", 5 | "dependencies": { 6 | "chartist": "^1.0.0" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /sandboxes/pie/simple-gauge/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pie-simple-gauge", 3 | "description": "Gauge chart", 4 | "main": "index.ts", 5 | "dependencies": { 6 | "chartist": "^1.0.0" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/__image_snapshots__/LineChart__Simple-Interpolation-With-Holes-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamkin-skuf/chartist/HEAD/test/__image_snapshots__/LineChart__Simple-Interpolation-With-Holes-snap.png -------------------------------------------------------------------------------- /test/__image_snapshots__/LineChart__Step-Interpolation-With-Holes-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamkin-skuf/chartist/HEAD/test/__image_snapshots__/LineChart__Step-Interpolation-With-Holes-snap.png -------------------------------------------------------------------------------- /sandboxes/bar/horizontal/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bar-horizontal", 3 | "description": "Horizontal bar chart", 4 | "main": "index.ts", 5 | "dependencies": { 6 | "chartist": "^1.0.0" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/core/data/index.ts: -------------------------------------------------------------------------------- 1 | export * from './bounds'; 2 | export * from './data'; 3 | export * from './highLow'; 4 | export * from './normalize'; 5 | export * from './segments'; 6 | export * from './serialize'; 7 | -------------------------------------------------------------------------------- /test/__image_snapshots__/LineChart__Cardinal-Interpolation-With-Holes-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamkin-skuf/chartist/HEAD/test/__image_snapshots__/LineChart__Cardinal-Interpolation-With-Holes-snap.png -------------------------------------------------------------------------------- /sandboxes/bar/label-position/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bar-label-position", 3 | "description": "Label placement", 4 | "main": "index.ts", 5 | "dependencies": { 6 | "chartist": "^1.0.0" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /sandboxes/line/only-integer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "line-only-integer", 3 | "description": "Only whole numbers", 4 | "main": "index.ts", 5 | "dependencies": { 6 | "chartist": "^1.0.0" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/__image_snapshots__/BarChart__Overlapping-Bars-On-Mobile__iPhone-X-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamkin-skuf/chartist/HEAD/test/__image_snapshots__/BarChart__Overlapping-Bars-On-Mobile__iPhone-X-snap.png -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "jsxSingleQuote": true, 4 | "semi": true, 5 | "tabWidth": 2, 6 | "bracketSpacing": true, 7 | "arrowParens": "avoid", 8 | "trailingComma": "none" 9 | } 10 | -------------------------------------------------------------------------------- /sandboxes/line/path-animation/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "line-path-animation", 3 | "description": "SVG Path animation", 4 | "main": "index.ts", 5 | "dependencies": { 6 | "chartist": "^1.0.0" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /sandboxes/line/series-override/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "line-series-override", 3 | "description": "Series Overrides", 4 | "main": "index.ts", 5 | "dependencies": { 6 | "chartist": "^1.0.0" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.storybook/theme.js: -------------------------------------------------------------------------------- 1 | import { create } from '@storybook/theming'; 2 | 3 | export const theme = create({ 4 | base: 'light', 5 | brandTitle: 'chartist', 6 | brandUrl: 'https://github.com/chartist-js/chartist' 7 | }); 8 | -------------------------------------------------------------------------------- /sandboxes/bar/distributed-series/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bar-distributed-series", 3 | "description": "Distributed series", 4 | "main": "index.ts", 5 | "dependencies": { 6 | "chartist": "^1.0.0" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /sandboxes/line/data-fill-holes/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "line-data-fill-holes", 3 | "description": "Filled holes in data", 4 | "main": "index.ts", 5 | "dependencies": { 6 | "chartist": "^1.0.0" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /sandboxes/line/svg-animation/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "line-svg-animation", 3 | "description": "Advanced SMIL Animations", 4 | "main": "index.ts", 5 | "dependencies": { 6 | "chartist": "^1.0.0" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /sandboxes/pie/custom-labels/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pie-custom-labels", 3 | "description": "Pie chart with custom labels", 4 | "main": "index.ts", 5 | "dependencies": { 6 | "chartist": "^1.0.0" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /sandboxes/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "paths": { 6 | "chartist": ["../src"] 7 | } 8 | }, 9 | "include": ["."] 10 | } 11 | -------------------------------------------------------------------------------- /test/__image_snapshots__/LineChart__Monotone-Cubic-Interpolation-With-Holes-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamkin-skuf/chartist/HEAD/test/__image_snapshots__/LineChart__Monotone-Cubic-Interpolation-With-Holes-snap.png -------------------------------------------------------------------------------- /test/__image_snapshots__/LineChart__Step-No-Postpone-Interpolation-With-Holes-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mamkin-skuf/chartist/HEAD/test/__image_snapshots__/LineChart__Step-No-Postpone-Interpolation-With-Holes-snap.png -------------------------------------------------------------------------------- /sandboxes/bar/bi-polar-interpolated/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bar-bi-polar-interpolated", 3 | "description": "Bi-polar bar chart", 4 | "main": "index.ts", 5 | "dependencies": { 6 | "chartist": "^1.0.0" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /sandboxes/bar/overlapping-bars/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bar-overlapping-bars", 3 | "description": "Overlapping bars on mobile", 4 | "main": "index.ts", 5 | "dependencies": { 6 | "chartist": "^1.0.0" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /sandboxes/line/bipolar-area/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "line-bipolar-area", 3 | "description": "Bi-polar Line chart with area only", 4 | "main": "index.ts", 5 | "dependencies": { 6 | "chartist": "^1.0.0" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /website/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /node_modules 3 | 4 | # Production 5 | /build 6 | 7 | # Generated files 8 | .docusaurus 9 | .cache-loader 10 | 11 | # Misc 12 | .DS_Store 13 | .env.* 14 | 15 | npm-debug.log* 16 | -------------------------------------------------------------------------------- /sandboxes/line/area/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /sandboxes/line/axis-fixed-and-auto/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "line-axis-fixed-and-auto", 3 | "description": "Fixed and auto scale axis", 4 | "main": "index.ts", 5 | "dependencies": { 6 | "chartist": "^1.0.0" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /sandboxes/line/modify-drawing/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "line-modify-drawing", 3 | "description": "Using events to replace graphics", 4 | "main": "index.ts", 5 | "dependencies": { 6 | "chartist": "^1.0.0" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /sandboxes/line/simple-responsive/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "line-simple-responsive", 3 | "description": "Simple responsive options", 4 | "main": "index.ts", 5 | "dependencies": { 6 | "chartist": "^1.0.0" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /sandboxes/line/simple-smoothing/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "line-simple-smoothing", 3 | "description": "Line Interpolation / Smoothing", 4 | "main": "index.ts", 5 | "dependencies": { 6 | "chartist": "^1.0.0" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /sandboxes/line/simple-svg-animation/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "line-simple-svg-animation", 3 | "description": "Simple SMIL Animations", 4 | "main": "index.ts", 5 | "dependencies": { 6 | "chartist": "^1.0.0" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /sandboxes/pie/donut-animation/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pie-donut-animation", 3 | "description": "Animating a Donut with Svg.animate", 4 | "main": "index.ts", 5 | "dependencies": { 6 | "chartist": "^1.0.0" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/core/index.ts: -------------------------------------------------------------------------------- 1 | export * from './constants'; 2 | export * from './lang'; 3 | export * from './math'; 4 | export * from './data'; 5 | export * from './creation'; 6 | export * from './optionsProvider'; 7 | export * from './types'; 8 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './core'; 2 | export * from './event'; 3 | export * from './charts'; 4 | export * from './axes'; 5 | export * as Interpolation from './interpolation'; 6 | export * from './svg'; 7 | export * from './utils'; 8 | -------------------------------------------------------------------------------- /website/docs/examples/docs.js: -------------------------------------------------------------------------------- 1 | exports.docs = [ 2 | { title: 'Bar Chart', slug: '/examples/bar-chart' }, 3 | { title: 'Line Chart', slug: '/examples/line-chart' }, 4 | { title: 'Pie Chart', slug: '/examples/pie-chart' } 5 | ]; 6 | -------------------------------------------------------------------------------- /sandboxes/bar/extreme-responsive/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bar-extreme-responsive", 3 | "description": "Extreme responsive configuration", 4 | "main": "index.ts", 5 | "dependencies": { 6 | "chartist": "^1.0.0" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /sandboxes/bar/horizontal/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /sandboxes/bar/multiline/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /sandboxes/bar/stacked/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /sandboxes/line/axis-auto/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /sandboxes/line/simple/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /sandboxes/pie/simple/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /website/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // This file is not used in compilation. It is here just for a nice editor experience. 3 | "extends": "@tsconfig/docusaurus/tsconfig.json", 4 | "compilerOptions": { 5 | "baseUrl": "." 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: 🤔 Have a Question? 4 | url: https://stackoverflow.com/questions/tagged/chartist.js 5 | about: Feel free to ask questions on Stack Overflow. 6 | -------------------------------------------------------------------------------- /sandboxes/bar/label-position/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /sandboxes/line/bipolar-area/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /sandboxes/line/data-holes/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /sandboxes/line/modify-drawing/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /sandboxes/line/only-integer/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /sandboxes/line/path-animation/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /sandboxes/line/scatter-random/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /sandboxes/line/scatter-random/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "line-scatter-random", 3 | "description": "Line scatter diagram with responsive settings", 4 | "main": "index.ts", 5 | "dependencies": { 6 | "chartist": "^1.0.0" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /sandboxes/line/svg-animation/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /sandboxes/line/timeseries/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /sandboxes/pie/custom-labels/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /sandboxes/pie/donut-animation/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /sandboxes/pie/donut-chart/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /sandboxes/pie/simple-gauge/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /sandboxes/bar/distributed-series/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /sandboxes/bar/extreme-responsive/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /sandboxes/bar/overlapping-bars/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /sandboxes/line/axis-fixed-and-auto/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /sandboxes/line/data-fill-holes/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /sandboxes/line/series-override/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /sandboxes/line/simple-responsive/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /sandboxes/line/simple-smoothing/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /sandboxes/bar/bi-polar-interpolated/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /sandboxes/line/simple-svg-animation/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /sandboxes/bar/stacked-accumulate-relative/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /sandboxes/bar/stacked-accumulate-relative/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bar-stacked", 3 | "description": "Stacked bar chart with accumulate-relative stack mode", 4 | "main": "index.ts", 5 | "dependencies": { 6 | "chartist": "^1.0.0" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /sandboxes/bar/with-circle-modify-drawing/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /sandboxes/bar/with-circle-modify-drawing/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bar-with-circle-modify-drawing", 3 | "description": "Add peak circles using the draw events", 4 | "main": "index.ts", 5 | "dependencies": { 6 | "chartist": "^1.0.0" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | 6 | # builds 7 | package 8 | dist 9 | storybook-static 10 | 11 | # misc 12 | .DS_Store 13 | 14 | npm-debug.log* 15 | 16 | # testing 17 | coverage 18 | -------------------------------------------------------------------------------- /postcss.config.cjs: -------------------------------------------------------------------------------- 1 | const isProd = process.env.NODE_ENV !== 'development'; 2 | 3 | module.exports = { 4 | plugins: [ 5 | require('postcss-preset-env'), 6 | isProd && 7 | require('cssnano')({ 8 | preset: 'default' 9 | }) 10 | ].filter(Boolean) 11 | }; 12 | -------------------------------------------------------------------------------- /src/axes/types.ts: -------------------------------------------------------------------------------- 1 | import type { AutoScaleAxis } from './AutoScaleAxis'; 2 | import type { FixedScaleAxis } from './FixedScaleAxis'; 3 | import type { StepAxis } from './StepAxis'; 4 | 5 | export type AxisType = 6 | | typeof AutoScaleAxis 7 | | typeof FixedScaleAxis 8 | | typeof StepAxis; 9 | -------------------------------------------------------------------------------- /test/utils/skipable.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Make block definition method skipable. 3 | * @param fn - Jest's block definition method. 4 | * @param skip - Skip test block. 5 | * @returns Skipable block definition methid. 6 | */ 7 | export function skipable(fn, skip) { 8 | return skip ? fn.skip : fn; 9 | } 10 | -------------------------------------------------------------------------------- /sandboxes/line/area/index.ts: -------------------------------------------------------------------------------- 1 | import 'chartist/dist/index.css'; 2 | import { LineChart } from 'chartist'; 3 | 4 | new LineChart( 5 | '#chart', 6 | { 7 | labels: [1, 2, 3, 4, 5, 6, 7, 8], 8 | series: [[5, 9, 7, 8, 5, 3, 5, 4]] 9 | }, 10 | { 11 | low: 0, 12 | showArea: true 13 | } 14 | ); 15 | -------------------------------------------------------------------------------- /sandboxes/pie/donut-chart/index.ts: -------------------------------------------------------------------------------- 1 | import 'chartist/dist/index.css'; 2 | import { PieChart } from 'chartist'; 3 | 4 | new PieChart( 5 | '#chart', 6 | { 7 | series: [20, 10, 30, 40] 8 | }, 9 | { 10 | donut: true, 11 | donutWidth: 60, 12 | startAngle: 270, 13 | showLabel: true 14 | } 15 | ); 16 | -------------------------------------------------------------------------------- /sandboxes/pie/simple/index.ts: -------------------------------------------------------------------------------- 1 | import 'chartist/dist/index.css'; 2 | import { PieChart } from 'chartist'; 3 | 4 | const data = { 5 | series: [5, 3, 4] 6 | }; 7 | 8 | new PieChart('#chart', data, { 9 | labelInterpolationFnc: value => 10 | Math.round((+value / data.series.reduce((a, b) => a + b)) * 100) + '%' 11 | }); 12 | -------------------------------------------------------------------------------- /sandboxes/bar/distributed-series/index.ts: -------------------------------------------------------------------------------- 1 | import 'chartist/dist/index.css'; 2 | import { BarChart } from 'chartist'; 3 | 4 | new BarChart( 5 | '#chart', 6 | { 7 | labels: ['XS', 'S', 'M', 'L', 'XL', 'XXL', 'XXXL'], 8 | series: [20, 60, 120, 200, 180, 20, 10] 9 | }, 10 | { 11 | distributeSeries: true 12 | } 13 | ); 14 | -------------------------------------------------------------------------------- /sandboxes/pie/simple-gauge/index.ts: -------------------------------------------------------------------------------- 1 | import 'chartist/dist/index.css'; 2 | import { PieChart } from 'chartist'; 3 | 4 | new PieChart( 5 | '#chart', 6 | { 7 | series: [20, 10, 30, 40] 8 | }, 9 | { 10 | donut: true, 11 | donutWidth: 60, 12 | startAngle: 270, 13 | total: 200, 14 | showLabel: false 15 | } 16 | ); 17 | -------------------------------------------------------------------------------- /src/utils/types.ts: -------------------------------------------------------------------------------- 1 | export type FilterByKey = T extends Record 2 | ? T 3 | : T extends Partial> 4 | ? T & { [key in K]: T[K] } 5 | : never; 6 | 7 | export type RequiredKeys = T & 8 | Required> & { [key in V]: Required }; 9 | -------------------------------------------------------------------------------- /website/docs/examples/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | slug: /examples 3 | description: List of Chartist usage examples. 4 | --- 5 | 6 | import Link from '@docusaurus/Link'; 7 | import { docs } from './docs'; 8 | 9 | # Examples 10 | 11 |
    12 | {docs.map(({ title, slug }, i) => ( 13 |
  • 14 | {title} 15 |
  • 16 | ))} 17 |
18 | -------------------------------------------------------------------------------- /.github/workflows/commit.yml: -------------------------------------------------------------------------------- 1 | name: Commit 2 | on: 3 | push: 4 | jobs: 5 | commitlint: 6 | runs-on: ubuntu-latest 7 | name: commitlint 8 | steps: 9 | - name: Checkout the repository 10 | uses: actions/checkout@v3 11 | with: 12 | fetch-depth: 0 13 | - name: Run commitlint 14 | uses: wagoid/commitlint-github-action@v4 15 | -------------------------------------------------------------------------------- /test/utils/storyshots/viewport.ts: -------------------------------------------------------------------------------- 1 | export const Viewport = { 2 | Default: 'default', 3 | Mobile: 'iPhone X', 4 | MobileLandscape: 'iPhone X landscape', 5 | SmallMobile: 'iPhone SE', 6 | SmallMobileLandscape: 'iPhone SE landscape', 7 | Tablet: 'iPad', 8 | TabletLandscape: 'iPad landscape', 9 | SmallTablet: 'Nexus 7', 10 | SmallTabletLandscape: 'Nexus 7 landscape' 11 | }; 12 | -------------------------------------------------------------------------------- /sandboxes/bar/stacked-accumulate-relative/index.ts: -------------------------------------------------------------------------------- 1 | import 'chartist/dist/index.css'; 2 | import { BarChart } from 'chartist'; 3 | 4 | new BarChart( 5 | '#chart', 6 | { 7 | labels: ['Monday', 'Tuesday', 'Wednesday', 'Thursday'], 8 | series: [ 9 | [5, 4, -3, -5], 10 | [5, -4, 3, -5] 11 | ] 12 | }, 13 | { 14 | stackBars: true, 15 | stackMode: 'accumulate-relative' 16 | } 17 | ); 18 | -------------------------------------------------------------------------------- /sandboxes/line/simple/index.ts: -------------------------------------------------------------------------------- 1 | import 'chartist/dist/index.css'; 2 | import { LineChart } from 'chartist'; 3 | 4 | new LineChart( 5 | '#chart', 6 | { 7 | labels: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'], 8 | series: [ 9 | [12, 9, 7, 8, 5], 10 | [2, 1, 3.5, 7, 3], 11 | [1, 3, 4, 5, 6] 12 | ] 13 | }, 14 | { 15 | fullWidth: true, 16 | chartPadding: { 17 | right: 40 18 | } 19 | } 20 | ); 21 | -------------------------------------------------------------------------------- /.storybook/preview.js: -------------------------------------------------------------------------------- 1 | import { INITIAL_VIEWPORTS } from '@storybook/addon-viewport'; 2 | import { configureActions } from '@storybook/addon-actions'; 3 | import faker from 'faker'; 4 | 5 | const SEED_VALUE = 584; 6 | 7 | if (process.env.STORYBOOK_STORYSHOTS) { 8 | // Make faker values reproducible. 9 | faker.seed(SEED_VALUE); 10 | } 11 | 12 | configureActions({ 13 | depth: 5 14 | }); 15 | 16 | export const parameters = { 17 | viewport: { 18 | viewports: INITIAL_VIEWPORTS 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /sandboxes/line/axis-auto/index.ts: -------------------------------------------------------------------------------- 1 | import 'chartist/dist/index.css'; 2 | import { LineChart, AutoScaleAxis } from 'chartist'; 3 | 4 | new LineChart( 5 | '#chart', 6 | { 7 | series: [ 8 | [ 9 | { x: 1, y: 100 }, 10 | { x: 2, y: 50 }, 11 | { x: 3, y: 25 }, 12 | { x: 5, y: 12.5 }, 13 | { x: 8, y: 6.25 } 14 | ] 15 | ] 16 | }, 17 | { 18 | axisX: { 19 | type: AutoScaleAxis, 20 | onlyInteger: true 21 | } 22 | } 23 | ); 24 | -------------------------------------------------------------------------------- /src/charts/types.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | DataEvent, 3 | OptionsChangedEvent, 4 | DrawEvent, 5 | CreatedEvent 6 | } from '../core'; 7 | import type { AnimationEvent } from '../svg'; 8 | 9 | export interface BaseChartEventsTypes< 10 | TCreateEvent = CreatedEvent, 11 | TDrawEvents = DrawEvent 12 | > { 13 | data: DataEvent; 14 | options: OptionsChangedEvent; 15 | animationBegin: AnimationEvent; 16 | animationEnd: AnimationEvent; 17 | created: TCreateEvent; 18 | draw: TDrawEvents; 19 | } 20 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | 10 | # Change these settings to your own preference 11 | indent_style = space 12 | indent_size = 2 13 | 14 | # We recommend you to keep these unchanged 15 | end_of_line = lf 16 | charset = utf-8 17 | trim_trailing_whitespace = true 18 | insert_final_newline = true 19 | 20 | [*.md] 21 | trim_trailing_whitespace = false 22 | -------------------------------------------------------------------------------- /LICENSE-WTFPL: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2004 Sam Hocevar 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | -------------------------------------------------------------------------------- /sandboxes/bar/bi-polar-interpolated/index.ts: -------------------------------------------------------------------------------- 1 | import 'chartist/dist/index.css'; 2 | import { BarChart, BarChartOptions } from 'chartist'; 3 | 4 | const data = { 5 | labels: ['W1', 'W2', 'W3', 'W4', 'W5', 'W6', 'W7', 'W8', 'W9', 'W10'], 6 | series: [[1, 2, 4, 8, 6, -2, -1, -4, -6, -2]] 7 | }; 8 | 9 | const options: BarChartOptions = { 10 | high: 10, 11 | low: -10, 12 | axisX: { 13 | labelInterpolationFnc(value, index) { 14 | return index % 2 === 0 ? value : null; 15 | } 16 | } 17 | }; 18 | 19 | new BarChart('#chart', data, options); 20 | -------------------------------------------------------------------------------- /sandboxes/bar/label-position/index.ts: -------------------------------------------------------------------------------- 1 | import 'chartist/dist/index.css'; 2 | import { BarChart } from 'chartist'; 3 | 4 | new BarChart( 5 | '#chart', 6 | { 7 | labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'], 8 | series: [ 9 | [5, 4, 3, 7, 5, 10, 3], 10 | [3, 2, 9, 5, 4, 6, 4] 11 | ] 12 | }, 13 | { 14 | axisX: { 15 | // On the x-axis start means top and end means bottom 16 | position: 'start' 17 | }, 18 | axisY: { 19 | // On the y-axis start means left and end means right 20 | position: 'end' 21 | } 22 | } 23 | ); 24 | -------------------------------------------------------------------------------- /sandboxes/bar/horizontal/index.ts: -------------------------------------------------------------------------------- 1 | import 'chartist/dist/index.css'; 2 | import { BarChart } from 'chartist'; 3 | 4 | new BarChart( 5 | '#chart', 6 | { 7 | labels: [ 8 | 'Monday', 9 | 'Tuesday', 10 | 'Wednesday', 11 | 'Thursday', 12 | 'Friday', 13 | 'Saturday', 14 | 'Sunday' 15 | ], 16 | series: [ 17 | [5, 4, 3, 7, 5, 10, 3], 18 | [3, 2, 9, 5, 4, 6, 4] 19 | ] 20 | }, 21 | { 22 | seriesBarDistance: 10, 23 | reverseData: true, 24 | horizontalBars: true, 25 | axisY: { 26 | offset: 70 27 | } 28 | } 29 | ); 30 | -------------------------------------------------------------------------------- /test/storyshots.spec.js: -------------------------------------------------------------------------------- 1 | import { skipable } from './utils/skipable'; 2 | import { initStoryshots } from './utils/storyshots'; 3 | 4 | const testTimeout = 60 * 1000 * 10; 5 | const config = { 6 | url: 'http://localhost:6006', 7 | setupTimeout: testTimeout, 8 | testTimeout, 9 | getGotoOptions() { 10 | return { 11 | waitUntil: 'networkidle0', 12 | timeout: 0 13 | }; 14 | } 15 | }; 16 | 17 | const describeWhenLinux = skipable( 18 | describe, 19 | process.platform !== 'linux' || Boolean(process.env.STORYSHOTS_SKIP) 20 | ); 21 | 22 | describeWhenLinux('Storyshots', () => { 23 | initStoryshots(config); 24 | }); 25 | -------------------------------------------------------------------------------- /.size-limit.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "path": "dist/index.cjs", 4 | "limit": "36.45 kB", 5 | "webpack": false, 6 | "running": false 7 | }, 8 | { 9 | "path": "dist/index.cjs", 10 | "limit": "7.45 kB", 11 | "import": "{ BarChart }" 12 | }, 13 | { 14 | "path": "dist/index.js", 15 | "limit": "36.2 kB", 16 | "webpack": false, 17 | "running": false 18 | }, 19 | { 20 | "path": "dist/index.js", 21 | "limit": "7.4 kB", 22 | "import": "{ BarChart }" 23 | }, 24 | { 25 | "path": "dist/index.css", 26 | "limit": "1.3 kB", 27 | "webpack": false, 28 | "running": false 29 | } 30 | ] 31 | -------------------------------------------------------------------------------- /sandboxes/line/bipolar-area/index.ts: -------------------------------------------------------------------------------- 1 | import 'chartist/dist/index.css'; 2 | import { LineChart } from 'chartist'; 3 | 4 | new LineChart( 5 | '#chart', 6 | { 7 | labels: [1, 2, 3, 4, 5, 6, 7, 8], 8 | series: [ 9 | [1, 2, 3, 1, -2, 0, 1, 0], 10 | [-2, -1, -2, -1, -2.5, -1, -2, -1], 11 | [0, 0, 0, 1, 2, 2.5, 2, 1], 12 | [2.5, 2, 1, 0.5, 1, 0.5, -1, -2.5] 13 | ] 14 | }, 15 | { 16 | high: 3, 17 | low: -3, 18 | showArea: true, 19 | showLine: false, 20 | showPoint: false, 21 | fullWidth: true, 22 | axisX: { 23 | showLabel: false, 24 | showGrid: false 25 | } 26 | } 27 | ); 28 | -------------------------------------------------------------------------------- /sandboxes/bar/stacked/index.ts: -------------------------------------------------------------------------------- 1 | import 'chartist/dist/index.css'; 2 | import { BarChart } from 'chartist'; 3 | 4 | new BarChart( 5 | '#chart', 6 | { 7 | labels: ['Q1', 'Q2', 'Q3', 'Q4'], 8 | series: [ 9 | [800000, 1200000, 1400000, 1300000], 10 | [200000, 400000, 500000, 300000], 11 | [100000, 200000, 400000, 600000] 12 | ] 13 | }, 14 | { 15 | stackBars: true, 16 | axisY: { 17 | labelInterpolationFnc: value => +value / 1000 + 'k' 18 | } 19 | } 20 | ).on('draw', data => { 21 | if (data.type === 'bar') { 22 | data.element.attr({ 23 | style: 'stroke-width: 30px' 24 | }); 25 | } 26 | }); 27 | -------------------------------------------------------------------------------- /sandboxes/line/only-integer/index.ts: -------------------------------------------------------------------------------- 1 | import 'chartist/dist/index.css'; 2 | import { LineChart } from 'chartist'; 3 | 4 | new LineChart( 5 | '#chart', 6 | { 7 | labels: [1, 2, 3, 4, 5, 6, 7, 8], 8 | series: [ 9 | [1, 2, 3, 1, -2, 0, 1, 0], 10 | [-2, -1, -2, -1, -3, -1, -2, -1], 11 | [0, 0, 0, 1, 2, 3, 2, 1], 12 | [3, 2, 1, 0.5, 1, 0, -1, -3] 13 | ] 14 | }, 15 | { 16 | high: 3, 17 | low: -3, 18 | fullWidth: true, 19 | // As this is axis specific we need to tell Chartist to use whole numbers only on the concerned axis 20 | axisY: { 21 | onlyInteger: true, 22 | offset: 20 23 | } 24 | } 25 | ); 26 | -------------------------------------------------------------------------------- /website/docs/api/basics.md: -------------------------------------------------------------------------------- 1 | --- 2 | slug: /api/basics 3 | description: List of Chartist basic APIs. 4 | --- 5 | 6 | # List of basic APIs: 7 | 8 | - Charts 9 | - [BarChart](/api/classes/BarChart) 10 | - [LineChart](/api/classes/LineChart) 11 | - [PieChart](/api/classes/PieChart) 12 | - Axes types 13 | - [AutoScaleAxis](/api/classes/AutoScaleAxis) 14 | - [FixedScaleAxis](/api/classes/FixedScaleAxis) 15 | - [StepAxis](/api/classes/StepAxis) 16 | - Svg wrappers 17 | - [Svg](/api/classes/Svg) 18 | - [SvgPath](/api/classes/SvgPath) 19 | - [SvgList](/api/classes/SvgList) 20 | - [Interpolation](/api/namespaces/Interpolation) 21 | - [EventEmitter](/api/classes/EventEmitter) 22 | -------------------------------------------------------------------------------- /sandboxes/line/simple-smoothing/index.ts: -------------------------------------------------------------------------------- 1 | import 'chartist/dist/index.css'; 2 | import { LineChart, Interpolation } from 'chartist'; 3 | 4 | new LineChart( 5 | '#chart', 6 | { 7 | labels: [1, 2, 3, 4, 5], 8 | series: [ 9 | [1, 5, 10, 0, 1], 10 | [10, 15, 0, 1, 2] 11 | ] 12 | }, 13 | { 14 | // Remove this configuration to see that chart rendered with cardinal spline interpolation 15 | // Sometimes, on large jumps in data values, it's better to use simple smoothing. 16 | lineSmooth: Interpolation.simple({ 17 | divisor: 2 18 | }), 19 | fullWidth: true, 20 | chartPadding: { 21 | right: 20 22 | }, 23 | low: 0 24 | } 25 | ); 26 | -------------------------------------------------------------------------------- /website/src/components/ContextProvider.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from 'react'; 2 | import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; 3 | import useThemeContext from '@theme/hooks/useThemeContext'; 4 | 5 | interface IContext { 6 | branch: string; 7 | theme: 'light' | 'dark'; 8 | } 9 | 10 | export default function ContextProvider({ 11 | children, 12 | }: { 13 | children(context: IContext): ReactNode; 14 | }) { 15 | const ctx = useDocusaurusContext(); 16 | const { isDarkTheme } = useThemeContext(); 17 | const context = { 18 | branch: ctx.siteConfig.customFields.branch as string, 19 | theme: isDarkTheme ? ('dark' as const) : ('light' as const), 20 | }; 21 | 22 | return children(context); 23 | } 24 | -------------------------------------------------------------------------------- /sandboxes/bar/multiline/index.ts: -------------------------------------------------------------------------------- 1 | import 'chartist/dist/index.css'; 2 | import { BarChart } from 'chartist'; 3 | 4 | new BarChart( 5 | '#chart', 6 | { 7 | labels: [ 8 | 'First quarter of the year', 9 | 'Second quarter of the year', 10 | 'Third quarter of the year', 11 | 'Fourth quarter of the year' 12 | ], 13 | series: [ 14 | [60000, 40000, 80000, 70000], 15 | [40000, 30000, 70000, 65000], 16 | [8000, 3000, 10000, 6000] 17 | ] 18 | }, 19 | { 20 | seriesBarDistance: 10, 21 | axisX: { 22 | offset: 60 23 | }, 24 | axisY: { 25 | offset: 80, 26 | labelInterpolationFnc: value => value + ' CHF', 27 | scaleMinSpace: 15 28 | } 29 | } 30 | ); 31 | -------------------------------------------------------------------------------- /jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "testEnvironment": "jsdom", 3 | "testRegex": "(test|src)/.*\\.spec\\.(jsx?|tsx?)$", 4 | "setupFilesAfterEnv": ["/test/setup.js"], 5 | "transform": { 6 | "^.+\\.(t|j)sx?$": ["@swc/jest", { 7 | "env": { 8 | "targets": { 9 | "node": 14 10 | } 11 | } 12 | }] 13 | }, 14 | "moduleNameMapper": { 15 | "^chartist-dev$": "/src", 16 | "^chartist-dev/styles$": "/test/mock/cssModule.js" 17 | }, 18 | "collectCoverage": true, 19 | "collectCoverageFrom": [ 20 | "src/**/*.{js,jsx,ts,tsx}", 21 | "!**/node_modules/**", 22 | "!**/*.stories.*" 23 | ], 24 | "coverageReporters": [ 25 | "lcovonly", 26 | "text" 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /sandboxes/line/axis-fixed-and-auto/index.ts: -------------------------------------------------------------------------------- 1 | import 'chartist/dist/index.css'; 2 | import { 3 | LineChart, 4 | AutoScaleAxis, 5 | FixedScaleAxis, 6 | Interpolation 7 | } from 'chartist'; 8 | 9 | new LineChart( 10 | '#chart', 11 | { 12 | series: [ 13 | [ 14 | { x: 1, y: 100 }, 15 | { x: 2, y: 50 }, 16 | { x: 3, y: 25 }, 17 | { x: 5, y: 12.5 }, 18 | { x: 8, y: 6.25 } 19 | ] 20 | ] 21 | }, 22 | { 23 | axisX: { 24 | type: AutoScaleAxis, 25 | onlyInteger: true 26 | }, 27 | axisY: { 28 | type: FixedScaleAxis, 29 | ticks: [0, 50, 75, 87.5, 100], 30 | low: 0 31 | }, 32 | lineSmooth: Interpolation.step(), 33 | showPoint: false 34 | } 35 | ); 36 | -------------------------------------------------------------------------------- /src/axes/StepAxis.ts: -------------------------------------------------------------------------------- 1 | import type { ChartRect, AxisOptions } from '../core'; 2 | import { AxisUnits, Axis } from './Axis'; 3 | 4 | export class StepAxis extends Axis { 5 | private readonly stepLength: number; 6 | public readonly stretch: boolean; 7 | 8 | constructor( 9 | axisUnit: AxisUnits, 10 | _data: unknown, 11 | chartRect: ChartRect, 12 | options: AxisOptions 13 | ) { 14 | const ticks = options.ticks || []; 15 | 16 | super(axisUnit, chartRect, ticks); 17 | 18 | const calc = Math.max(1, ticks.length - (options.stretch ? 1 : 0)); 19 | this.stepLength = this.axisLength / calc; 20 | this.stretch = Boolean(options.stretch); 21 | } 22 | 23 | projectValue(_value: unknown, index: number) { 24 | return this.stepLength * index; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /website/docs/api/docs.js: -------------------------------------------------------------------------------- 1 | exports.docs = [ 2 | { title: 'Table of Contents', slug: '/api/basics' }, 3 | { title: 'BarChart', slug: '/api/classes/BarChart' }, 4 | { title: 'LineChart', slug: '/api/classes/LineChart' }, 5 | { title: 'PieChart', slug: '/api/classes/PieChart' }, 6 | { title: 'AutoScaleAxis', slug: '/api/classes/AutoScaleAxis' }, 7 | { title: 'FixedScaleAxis', slug: '/api/classes/FixedScaleAxis' }, 8 | { title: 'StepAxis', slug: '/api/classes/StepAxis' }, 9 | { title: 'Svg', slug: '/api/classes/Svg' }, 10 | { title: 'SvgPath', slug: '/api/classes/SvgPath' }, 11 | { title: 'SvgList', slug: '/api/classes/SvgList' }, 12 | { title: 'Interpolation', slug: '/api/namespaces/Interpolation' }, 13 | { title: 'EventEmitter', slug: '/api/classes/EventEmitter' } 14 | ]; 15 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | release: 4 | types: [created] 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | name: Publish package 9 | steps: 10 | - name: Checkout the repository 11 | uses: actions/checkout@v3 12 | - name: Install pnpm 13 | uses: pnpm/action-setup@v2 14 | with: 15 | version: 7 16 | - name: Install Node.js 17 | uses: actions/setup-node@v3 18 | with: 19 | node-version: 16 20 | cache: 'pnpm' 21 | registry-url: 'https://registry.npmjs.org' 22 | - name: Install dependencies 23 | run: pnpm install 24 | - name: Publish 25 | run: pnpm publish --no-git-checks 26 | env: 27 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 28 | -------------------------------------------------------------------------------- /src/axes/StepAxis.spec.ts: -------------------------------------------------------------------------------- 1 | import { StepAxis } from './StepAxis'; 2 | 3 | describe('Axes', () => { 4 | describe('StepAxis', () => { 5 | it('should return 0 if options.ticks.length == 1', () => { 6 | const ticks = [1]; 7 | const axisUnit = { 8 | pos: 'y', 9 | len: 'height', 10 | dir: 'vertical', 11 | rectStart: 'y2', 12 | rectEnd: 'y1', 13 | rectOffset: 'x1' 14 | } as const; 15 | const data = [[1]]; 16 | const chartRect: any = { 17 | y2: 0, 18 | y1: 15, 19 | x1: 50, 20 | x2: 100 21 | }; 22 | const options = { 23 | ticks 24 | }; 25 | const stepAxis: any = new StepAxis(axisUnit, data, chartRect, options); 26 | expect(stepAxis.stepLength).toEqual(15); 27 | }); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /src/core/constants.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This object contains all namespaces used within Chartist. 3 | */ 4 | export const namespaces: Record = { 5 | svg: 'http://www.w3.org/2000/svg', 6 | xmlns: 'http://www.w3.org/2000/xmlns/', 7 | xhtml: 'http://www.w3.org/1999/xhtml', 8 | xlink: 'http://www.w3.org/1999/xlink', 9 | ct: 'http://gionkunz.github.com/chartist-js/ct' 10 | }; 11 | 12 | /** 13 | * Precision level used internally in Chartist for rounding. If you require more decimal places you can increase this number. 14 | */ 15 | export const precision = 8; 16 | 17 | /** 18 | * A map with characters to escape for strings to be safely used as attribute values. 19 | */ 20 | export const escapingMap: Record = { 21 | '&': '&', 22 | '<': '<', 23 | '>': '>', 24 | '"': '"', 25 | "'": ''' 26 | }; 27 | -------------------------------------------------------------------------------- /sandboxes/pie/custom-labels/index.ts: -------------------------------------------------------------------------------- 1 | import 'chartist/dist/index.css'; 2 | import { PieChart, PieChartOptions, ResponsiveOptions } from 'chartist'; 3 | 4 | const data = { 5 | labels: ['Bananas', 'Apples', 'Grapes'], 6 | series: [20, 15, 40] 7 | }; 8 | 9 | const options: PieChartOptions = { 10 | labelInterpolationFnc: value => String(value)[0] 11 | }; 12 | 13 | const responsiveOptions: ResponsiveOptions = [ 14 | [ 15 | 'screen and (min-width: 640px)', 16 | { 17 | chartPadding: 30, 18 | labelOffset: 100, 19 | labelDirection: 'explode', 20 | labelInterpolationFnc: value => value 21 | } 22 | ], 23 | [ 24 | 'screen and (min-width: 1024px)', 25 | { 26 | labelOffset: 80, 27 | chartPadding: 20 28 | } 29 | ] 30 | ]; 31 | 32 | new PieChart('#chart', data, options, responsiveOptions); 33 | -------------------------------------------------------------------------------- /website/README.md: -------------------------------------------------------------------------------- 1 | # Website 2 | 3 | This website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website generator. 4 | 5 | ### Installation 6 | 7 | ``` 8 | $ pnpm install 9 | ``` 10 | 11 | ### Local Development 12 | 13 | ``` 14 | $ pnpm start 15 | ``` 16 | 17 | This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. 18 | 19 | ### Build 20 | 21 | ``` 22 | $ pnpm build 23 | ``` 24 | 25 | This command generates static content into the `build` directory and can be served using any static contents hosting service. 26 | 27 | ### Deployment 28 | 29 | ``` 30 | $ GIT_USER= USE_SSH=true pnpm deploy 31 | ``` 32 | 33 | If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch. 34 | -------------------------------------------------------------------------------- /.github/workflows/update-storyshots.yml: -------------------------------------------------------------------------------- 1 | name: Update storyshots 2 | on: workflow_dispatch 3 | jobs: 4 | update-storyshots: 5 | runs-on: ubuntu-latest 6 | name: storyshots 7 | steps: 8 | - name: Checkout the repository 9 | uses: actions/checkout@v3 10 | - name: Install pnpm 11 | uses: pnpm/action-setup@v2 12 | with: 13 | version: 7 14 | - name: Install Node.js 15 | uses: actions/setup-node@v3 16 | with: 17 | node-version: 16 18 | cache: 'pnpm' 19 | - name: Install dependencies 20 | run: pnpm install 21 | - name: Update snapshots 22 | run: pnpm test:storyshots -u 23 | - name: Collect artifacts 24 | uses: actions/upload-artifact@v3 25 | if: always() 26 | with: 27 | name: Updated storyshots 28 | path: test/__image_snapshots__/ 29 | -------------------------------------------------------------------------------- /.storybook/main.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'], 5 | addons: [ 6 | '@storybook/addon-docs', 7 | '@storybook/addon-controls', 8 | '@storybook/addon-actions', 9 | '@storybook/addon-viewport' 10 | ], 11 | webpackFinal: async config => { 12 | config.module.rules[0].use = [require.resolve('swc-loader')]; 13 | config.module.rules.push({ 14 | test: /\.scss$/, 15 | use: ['style-loader', 'css-loader', 'postcss-loader', 'sass-loader'].map( 16 | require.resolve 17 | ) 18 | }); 19 | 20 | config.resolve.alias['chartist-dev/styles$'] = path.resolve( 21 | __dirname, 22 | '..', 23 | 'src', 24 | 'styles', 25 | 'index.scss' 26 | ); 27 | config.resolve.alias['chartist-dev$'] = path.resolve( 28 | __dirname, 29 | '..', 30 | 'src' 31 | ); 32 | 33 | return config; 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /sandboxes/line/path-animation/index.ts: -------------------------------------------------------------------------------- 1 | import 'chartist/dist/index.css'; 2 | import { LineChart, easings } from 'chartist'; 3 | 4 | const chart = new LineChart( 5 | '#chart', 6 | { 7 | labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], 8 | series: [ 9 | [1, 5, 2, 5, 4, 3], 10 | [2, 3, 4, 8, 1, 2], 11 | [5, 4, 3, 2, 1, 0.5] 12 | ] 13 | }, 14 | { 15 | low: 0, 16 | showArea: true, 17 | showPoint: false, 18 | fullWidth: true 19 | } 20 | ); 21 | 22 | chart.on('draw', data => { 23 | if (data.type === 'line' || data.type === 'area') { 24 | data.element.animate({ 25 | d: { 26 | begin: 2000 * data.index, 27 | dur: 2000, 28 | from: data.path 29 | .clone() 30 | .scale(1, 0) 31 | .translate(0, data.chartRect.height()) 32 | .stringify(), 33 | to: data.path.clone().stringify(), 34 | easing: easings.easeOutQuint 35 | } 36 | }); 37 | } 38 | }); 39 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Type Checking */ 4 | "strict": true, 5 | "strictBindCallApply": true, 6 | "noFallthroughCasesInSwitch": true, 7 | "noImplicitOverride": true, 8 | "noImplicitReturns": true, 9 | "noUnusedLocals": true, 10 | "noUnusedParameters": true, 11 | /* Modules */ 12 | "baseUrl": ".", 13 | "module": "esnext", 14 | "moduleResolution": "node", 15 | "resolveJsonModule": true, 16 | "paths": { 17 | "chartist-dev": ["src"] 18 | }, 19 | /* Emit */ 20 | "declaration": true, 21 | "declarationMap": true, 22 | "inlineSourceMap": true, 23 | "outDir": "dist", 24 | /* Interop Constraints */ 25 | "allowSyntheticDefaultImports": true, 26 | "isolatedModules": true, 27 | /* Language and Environment */ 28 | "lib": [ 29 | "dom", 30 | "esnext" 31 | ], 32 | "target": "esnext", 33 | /* Completeness */ 34 | "skipLibCheck": true 35 | }, 36 | "include": [ 37 | "src" 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /sandboxes/line/scatter-random/index.ts: -------------------------------------------------------------------------------- 1 | import 'chartist/dist/index.css'; 2 | import { LineChart, times } from 'chartist'; 3 | 4 | const data = times(52).reduce<{ 5 | labels: number[]; 6 | series: number[][]; 7 | }>( 8 | (accData, _, index) => { 9 | accData.labels.push(index + 1); 10 | accData.series.forEach(series => { 11 | series.push(Math.random() * 100); 12 | }); 13 | 14 | return accData; 15 | }, 16 | { 17 | labels: [], 18 | series: times(4).map(() => []) 19 | } 20 | ); 21 | 22 | new LineChart( 23 | '#chart', 24 | data, 25 | { 26 | showLine: false, 27 | axisX: { 28 | labelInterpolationFnc(value, index) { 29 | return index % 13 === 0 ? 'W' + value : null; 30 | } 31 | } 32 | }, 33 | [ 34 | [ 35 | 'screen and (min-width: 640px)', 36 | { 37 | axisX: { 38 | labelInterpolationFnc(value, index) { 39 | return index % 4 === 0 ? 'W' + value : null; 40 | } 41 | } 42 | } 43 | ] 44 | ] 45 | ); 46 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Gion Kunz 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /sandboxes/line/data-holes/index.ts: -------------------------------------------------------------------------------- 1 | import 'chartist/dist/index.css'; 2 | import { LineChart } from 'chartist'; 3 | 4 | new LineChart( 5 | '#chart', 6 | { 7 | labels: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], 8 | series: [ 9 | [5, 5, 10, 8, 7, 5, 4, null, null, null, 10, 10, 7, 8, 6, 9], 10 | [ 11 | 10, 12 | 15, 13 | null, 14 | 12, 15 | null, 16 | 10, 17 | 12, 18 | 15, 19 | null, 20 | null, 21 | 12, 22 | null, 23 | 14, 24 | null, 25 | null, 26 | null 27 | ], 28 | [null, null, null, null, 3, 4, 1, 3, 4, 6, 7, 9, 5, null, null, null], 29 | [ 30 | { x: 3, y: 3 }, 31 | { x: 4, y: 3 }, 32 | { x: 5, y: undefined }, 33 | { x: 6, y: 4 }, 34 | { x: 7, y: null }, 35 | { x: 8, y: 4 }, 36 | { x: 9, y: 4 } 37 | ] 38 | ] 39 | }, 40 | { 41 | fullWidth: true, 42 | chartPadding: { 43 | right: 10 44 | }, 45 | low: 0 46 | } 47 | ); 48 | -------------------------------------------------------------------------------- /src/core/lang.spec.ts: -------------------------------------------------------------------------------- 1 | import { quantity } from './lang'; 2 | 3 | describe('Core', () => { 4 | describe('Lang', () => { 5 | describe('quantity', () => { 6 | it('should return value for numbers', () => { 7 | expect(quantity(100)).toEqual({ value: 100 }); 8 | expect(quantity(0)).toEqual({ value: 0 }); 9 | expect(quantity(NaN)).toEqual({ value: NaN }); 10 | expect(quantity(null)).toEqual({ value: 0 }); 11 | expect(quantity(undefined)).toEqual({ value: NaN }); 12 | }); 13 | 14 | it('should return value without unit from string', () => { 15 | expect(quantity('100')).toEqual({ value: 100, unit: undefined }); 16 | expect(quantity('0')).toEqual({ value: 0, unit: undefined }); 17 | }); 18 | 19 | it('should return value and unit from string', () => { 20 | expect(quantity('100%')).toEqual({ value: 100, unit: '%' }); 21 | expect(quantity('100 %')).toEqual({ value: 100, unit: '%' }); 22 | expect(quantity('0px')).toEqual({ value: 0, unit: 'px' }); 23 | }); 24 | }); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /website/docs/what-is-it-made-for.md: -------------------------------------------------------------------------------- 1 | --- 2 | slug: /docs/what-is-it-made-for 3 | description: What is Chartist made for? 4 | --- 5 | 6 | # What is it made for? 7 | 8 | Chartist's goal is to provide a simple, lightweight and unintrusive library to responsively craft charts on your website. 9 | It's important to understand that one of the main intentions of Chartist is to rely on standards rather than providing 10 | it's own solution to a problem which is already solved by those standards. We need to leverage the power of browsers 11 | today and say good bye to the idea of solving all problems ourselves. 12 | 13 | Chartist works with inline-SVG and therefore leverages the power of the DOM to provide parts of its functionality. This 14 | also means that Chartist does not provide it's own event handling, labels, behaviors or anything else that can just be 15 | done with plain HTML, JavaScript and CSS. The single and only responsibility of Chartist is to help you drawing "Simple 16 | responsive Charts" using inline-SVG in the DOM, CSS to style and JavaScript to provide an API for configuring your charts. 17 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Chartist 2 | 3 | - [Issues and Bugs](#issue) 4 | - [Submission Guidelines](#submit) 5 | 6 | ## Found an Issue? 7 | 8 | If you find a bug in the source code or a mistake in the documentation, you can help us by 9 | submitting an issue to our [GitHub Repository][github]. Even better you can submit a Pull Request 10 | with a fix. 11 | 12 | ## Pre-requisites 13 | 14 | You will need the following to run a local development enviroment. 15 | 16 | - Node.js & npm 17 | - pnpm (`npm install -g pnpm`) 18 | - Text editor of your choice 19 | 20 | ## How to Run a Local Distribution 21 | 22 | 1. `cd` into your local copy of the repository. 23 | 2. Run `pnpm i` to install dependencies located in `package.json`. 24 | 5. Run `pnpm start:storybook` to start Storybook, or run `pnpm jest --watch` to run tests in watch mode. Congrats, you should now be able to see your local copy of the Chartist testbed. 25 | 26 | ## Submission Guidelines 27 | 28 | If you are creating a Pull Request, fork the repository and make any changes on the `develop` branch. 29 | -------------------------------------------------------------------------------- /website/static/img/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /sandboxes/bar/overlapping-bars/index.ts: -------------------------------------------------------------------------------- 1 | import 'chartist/dist/index.css'; 2 | import { BarChart, BarChartOptions, ResponsiveOptions } from 'chartist'; 3 | 4 | const data = { 5 | labels: [ 6 | 'Jan', 7 | 'Feb', 8 | 'Mar', 9 | 'Apr', 10 | 'Mai', 11 | 'Jun', 12 | 'Jul', 13 | 'Aug', 14 | 'Sep', 15 | 'Oct', 16 | 'Nov', 17 | 'Dec' 18 | ], 19 | series: [ 20 | [5, 4, 3, 7, 5, 10, 3, 4, 8, 10, 6, 8], 21 | [3, 2, 9, 5, 4, 6, 4, 6, 7, 8, 7, 4] 22 | ] 23 | }; 24 | 25 | const options = { 26 | seriesBarDistance: 15 27 | }; 28 | 29 | const responsiveOptions: ResponsiveOptions = [ 30 | [ 31 | 'screen and (min-width: 641px) and (max-width: 1024px)', 32 | { 33 | seriesBarDistance: 10, 34 | axisX: { 35 | labelInterpolationFnc: value => value 36 | } 37 | } 38 | ], 39 | [ 40 | 'screen and (max-width: 640px)', 41 | { 42 | seriesBarDistance: 5, 43 | axisX: { 44 | labelInterpolationFnc: value => String(value)[0] 45 | } 46 | } 47 | ] 48 | ]; 49 | 50 | new BarChart('#chart', data, options, responsiveOptions); 51 | -------------------------------------------------------------------------------- /website/src/css/custom.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Any CSS included here will be global. The classic template 3 | * bundles Infima by default. Infima is a CSS framework designed to 4 | * work well for content-centric websites. 5 | */ 6 | 7 | /* You can override the default Infima variables here. */ 8 | :root { 9 | --ifm-color-primary: #7a77ff; 10 | --ifm-color-primary-dark: #5552ff; 11 | --ifm-color-primary-darker: #433fff; 12 | --ifm-color-primary-darkest: #0c07ff; 13 | --ifm-color-primary-light: #9f9cff; 14 | --ifm-color-primary-lighter: #b1afff; 15 | --ifm-color-primary-lightest: #e8e7ff; 16 | --ifm-code-font-size: 95%; 17 | } 18 | 19 | .docusaurus-highlight-code-line { 20 | background-color: rgba(0, 0, 0, 0.1); 21 | display: block; 22 | margin: 0 calc(-1 * var(--ifm-pre-padding)); 23 | padding: 0 var(--ifm-pre-padding); 24 | } 25 | 26 | html[data-theme='dark'] .docusaurus-highlight-code-line { 27 | background-color: rgba(0, 0, 0, 0.3); 28 | } 29 | 30 | .logo { 31 | float: right; 32 | } 33 | 34 | @media (max-width: 768px) { 35 | .logo { 36 | float: none; 37 | display: block; 38 | margin: 0 auto; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/utils/extend.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Simple recursive object extend 3 | * @param target Target object where the source will be merged into 4 | * @param sources This object (objects) will be merged into target and then target is returned 5 | * @return An object that has the same reference as target but is extended and merged with the properties of source 6 | */ 7 | export function extend(target: T): T; 8 | export function extend(target: T, a: A): T & A; 9 | export function extend(target: T, a: A, b: B): T & A & B; 10 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 11 | export function extend(target: any = {}, ...sources: any[]) { 12 | for (let i = 0; i < sources.length; i++) { 13 | const source = sources[i]; 14 | for (const prop in source) { 15 | const sourceProp = source[prop]; 16 | if ( 17 | typeof sourceProp === 'object' && 18 | sourceProp !== null && 19 | !(sourceProp instanceof Array) 20 | ) { 21 | target[prop] = extend(target[prop], sourceProp); 22 | } else { 23 | target[prop] = sourceProp; 24 | } 25 | } 26 | } 27 | 28 | return target; 29 | } 30 | -------------------------------------------------------------------------------- /src/core/lang.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Converts a number to a string with a unit. If a string is passed then this will be returned unmodified. 3 | * @return Returns the passed number value with unit. 4 | */ 5 | export function ensureUnit(value: T, unit: string) { 6 | if (typeof value === 'number') { 7 | return value + unit; 8 | } 9 | 10 | return value; 11 | } 12 | 13 | /** 14 | * Converts a number or string to a quantity object. 15 | * @return Returns an object containing the value as number and the unit as string. 16 | */ 17 | export function quantity(input: T) { 18 | if (typeof input === 'string') { 19 | const match = /^(\d+)\s*(.*)$/g.exec(input); 20 | return { 21 | value: match ? +match[1] : 0, 22 | unit: match?.[2] || undefined 23 | }; 24 | } 25 | 26 | return { 27 | value: Number(input) 28 | }; 29 | } 30 | 31 | /** 32 | * Generates a-z from a number 0 to 26 33 | * @param n A number from 0 to 26 that will result in a letter a-z 34 | * @return A character from a-z based on the input number n 35 | */ 36 | export function alphaNumerate(n: number) { 37 | // Limit to a-z 38 | return String.fromCharCode(97 + (n % 26)); 39 | } 40 | -------------------------------------------------------------------------------- /sandboxes/line/data-fill-holes/index.ts: -------------------------------------------------------------------------------- 1 | import 'chartist/dist/index.css'; 2 | import { LineChart, Interpolation } from 'chartist'; 3 | 4 | new LineChart( 5 | '#chart', 6 | { 7 | labels: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], 8 | series: [ 9 | [5, 5, 10, 8, 7, 5, 4, null, null, null, 10, 10, 7, 8, 6, 9], 10 | [ 11 | 10, 12 | 15, 13 | null, 14 | 12, 15 | null, 16 | 10, 17 | 12, 18 | 15, 19 | null, 20 | null, 21 | 12, 22 | null, 23 | 14, 24 | null, 25 | null, 26 | null 27 | ], 28 | [null, null, null, null, 3, 4, 1, 3, 4, 6, 7, 9, 5, null, null, null], 29 | [ 30 | { x: 3, y: 3 }, 31 | { x: 4, y: 3 }, 32 | { x: 5, y: undefined }, 33 | { x: 6, y: 4 }, 34 | { x: 7, y: null }, 35 | { x: 8, y: 4 }, 36 | { x: 9, y: 4 } 37 | ] 38 | ] 39 | }, 40 | { 41 | fullWidth: true, 42 | chartPadding: { 43 | right: 10 44 | }, 45 | lineSmooth: Interpolation.cardinal({ 46 | fillHoles: true 47 | }), 48 | low: 0 49 | } 50 | ); 51 | -------------------------------------------------------------------------------- /sandboxes/line/modify-drawing/index.ts: -------------------------------------------------------------------------------- 1 | import 'chartist/dist/index.css'; 2 | import { LineChart, Svg } from 'chartist'; 3 | 4 | const chart = new LineChart('#chart', { 5 | labels: [1, 2, 3, 4, 5], 6 | series: [[12, 9, 7, 8, 5]] 7 | }); 8 | 9 | // Listening for draw events that get emitted by the Chartist chart 10 | chart.on('draw', data => { 11 | // If the draw event was triggered from drawing a point on the line chart 12 | if (data.type === 'point') { 13 | // We are creating a new path SVG element that draws a triangle around the point coordinates 14 | const triangle = new Svg( 15 | 'path', 16 | { 17 | d: [ 18 | 'M', 19 | data.x, 20 | data.y - 15, 21 | 'L', 22 | data.x - 15, 23 | data.y + 8, 24 | 'L', 25 | data.x + 15, 26 | data.y + 8, 27 | 'z' 28 | ].join(' '), 29 | style: 'fill-opacity: 1' 30 | }, 31 | 'ct-area' 32 | ); 33 | 34 | // With data.element we get the Chartist SVG wrapper and we can replace the original point drawn by Chartist with our newly created triangle 35 | data.element.replace(triangle); 36 | } 37 | }); 38 | -------------------------------------------------------------------------------- /sandboxes/bar/with-circle-modify-drawing/index.ts: -------------------------------------------------------------------------------- 1 | import 'chartist/dist/index.css'; 2 | import { BarChart, Svg, getMultiValue } from 'chartist'; 3 | 4 | // Create a simple bi-polar bar chart 5 | const chart = new BarChart( 6 | '#chart', 7 | { 8 | labels: ['W1', 'W2', 'W3', 'W4', 'W5', 'W6', 'W7', 'W8', 'W9', 'W10'], 9 | series: [[1, 2, 4, 8, 6, -2, -1, -4, -6, -2]] 10 | }, 11 | { 12 | high: 10, 13 | low: -10, 14 | axisX: { 15 | labelInterpolationFnc: (value, index) => (index % 2 === 0 ? value : null) 16 | } 17 | } 18 | ); 19 | 20 | // Listen for draw events on the bar chart 21 | chart.on('draw', data => { 22 | // If this draw event is of type bar we can use the data to create additional content 23 | if (data.type === 'bar') { 24 | // We use the group element of the current series to append a simple circle with the bar peek coordinates and a circle radius that is depending on the value 25 | data.group.append( 26 | new Svg( 27 | 'circle', 28 | { 29 | cx: data.x2, 30 | cy: data.y2, 31 | r: Math.abs(Number(getMultiValue(data.value))) * 2 + 5 32 | }, 33 | 'ct-slice-pie' 34 | ) 35 | ); 36 | } 37 | }); 38 | -------------------------------------------------------------------------------- /website/sidebars.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | const { docs } = require('./docs/docs'); 4 | const { docs: api } = require('./docs/api/docs'); 5 | const { docs: examples } = require('./docs/examples/docs'); 6 | 7 | /** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ 8 | const sidebars = { 9 | docsSidebar: [ 10 | { 11 | type: 'doc', 12 | id: 'index', 13 | label: 'Quickstart' 14 | }, 15 | ...docs.map(({ slug, title }) => ({ 16 | /** @type {'doc'} */ 17 | type: 'doc', 18 | id: slug.replace('/docs/', ''), 19 | label: title 20 | })) 21 | ], 22 | apiSidebar: [ 23 | ...api.map(({ slug, title }) => ({ 24 | /** @type {'doc'} */ 25 | type: 'doc', 26 | id: slug.replace('/', ''), 27 | label: title 28 | })), 29 | { 30 | type: 'doc', 31 | id: 'api/index' 32 | } 33 | ], 34 | examplesSidebar: [ 35 | { 36 | type: 'doc', 37 | id: 'examples/index', 38 | label: 'Table of Contents' 39 | }, 40 | ...examples.map(({ slug, title }) => ({ 41 | /** @type {'doc'} */ 42 | type: 'doc', 43 | id: slug.replace('/', ''), 44 | label: title 45 | })) 46 | ] 47 | }; 48 | 49 | module.exports = sidebars; 50 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | pull_request: 4 | push: 5 | branches: 6 | - main 7 | jobs: 8 | test: 9 | runs-on: ubuntu-latest 10 | strategy: 11 | matrix: 12 | stage: 13 | - unit 14 | - storyshots 15 | fail-fast: false 16 | name: ${{ matrix.stage }} tests 17 | steps: 18 | - name: Checkout the repository 19 | uses: actions/checkout@v3 20 | - name: Install pnpm 21 | uses: pnpm/action-setup@v2 22 | with: 23 | version: 7 24 | - name: Install Node.js 25 | uses: actions/setup-node@v3 26 | with: 27 | node-version: 16 28 | cache: 'pnpm' 29 | - name: Install dependencies 30 | run: pnpm install 31 | - name: Run tests 32 | run: pnpm test:${{ matrix.stage }} 33 | - name: Collect coverage 34 | uses: codecov/codecov-action@v3 35 | if: "success() && matrix.stage == 'unit'" 36 | with: 37 | files: ./coverage/lcov.info 38 | fail_ci_if_error: true 39 | - name: Collect artifacts 40 | uses: actions/upload-artifact@v3 41 | if: "failure() && matrix.stage != 'unit'" 42 | with: 43 | name: Image snapshots (${{ matrix.stage }}) 44 | path: test/__image_snapshots__/ 45 | -------------------------------------------------------------------------------- /website/docs/whats-new-in-v1.md: -------------------------------------------------------------------------------- 1 | --- 2 | slug: /docs/whats-new-in-v1 3 | description: What's new in Chartist v1? 4 | --- 5 | 6 | # What's new in v1? 7 | 8 | ## ESM 9 | 10 | Now Chartist is truly an ES module and exposes its API through the exports, thus making Chartist [tree-shakable](https://developer.mozilla.org/en-US/docs/Glossary/Tree_shaking). 11 | 12 | ### Migration from v0.11 13 | 14 | - Each property of Chartist object now is named export. 15 | - Chart classes were renamed. 16 | - Easing object now is named export. 17 | 18 | ```js 19 | const Chartist = require('chartist') 20 | 21 | new Chartist.Bar(/* ... */); 22 | new Chartist.Line(/* ... */); 23 | new Chartist.Pie(/* ... */); 24 | new Chartist.Svg(/* ... */); 25 | Chartist.Svg.Easing 26 | // ... 27 | 28 | // -> 29 | 30 | import { BarChart, LineChart, PieChart, Svg, easings } from 'chartist' 31 | 32 | new BarChart(/* ... */) 33 | new LineChart(/* ... */) 34 | new PieChart(/* ... */) 35 | new Svg(/* ... */) 36 | easings 37 | // ... 38 | ``` 39 | 40 | ## TypeScript 41 | 42 | Chartist was rewritten and fully typed with TypeScript. 43 | 44 | ### Some of exposed types 45 | 46 | ```ts 47 | import type { 48 | BarChartData, 49 | BarChartOptions, 50 | LineChartData, 51 | LineChartOptions, 52 | PieChartData, 53 | PieChartOptions 54 | } from 'chartist' 55 | ``` 56 | -------------------------------------------------------------------------------- /sandboxes/line/timeseries/index.ts: -------------------------------------------------------------------------------- 1 | import 'chartist/dist/index.css'; 2 | import { LineChart, FixedScaleAxis } from 'chartist'; 3 | 4 | new LineChart( 5 | '#chart', 6 | { 7 | series: [ 8 | { 9 | name: 'series-1', 10 | data: [ 11 | { x: new Date(143134652600), y: 53 }, 12 | { x: new Date(143234652600), y: 40 }, 13 | { x: new Date(143340052600), y: 45 }, 14 | { x: new Date(143366652600), y: 40 }, 15 | { x: new Date(143410652600), y: 20 }, 16 | { x: new Date(143508652600), y: 32 }, 17 | { x: new Date(143569652600), y: 18 }, 18 | { x: new Date(143579652600), y: 11 } 19 | ] 20 | }, 21 | { 22 | name: 'series-2', 23 | data: [ 24 | { x: new Date(143134652600), y: 53 }, 25 | { x: new Date(143234652600), y: 35 }, 26 | { x: new Date(143334652600), y: 30 }, 27 | { x: new Date(143384652600), y: 30 }, 28 | { x: new Date(143568652600), y: 10 } 29 | ] 30 | } 31 | ] 32 | }, 33 | { 34 | axisX: { 35 | type: FixedScaleAxis, 36 | divisor: 5, 37 | labelInterpolationFnc: value => 38 | new Date(value).toLocaleString(undefined, { 39 | month: 'short', 40 | day: 'numeric' 41 | }) 42 | } 43 | } 44 | ); 45 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.yml: -------------------------------------------------------------------------------- 1 | name: "🚀 Feature Request" 2 | description: "I have a specific suggestion!" 3 | labels: ["enhancement"] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: Thanks for taking the time to suggest a new feature! Please fill out this form as completely as possible. 8 | 9 | - type: checkboxes 10 | id: input1 11 | attributes: 12 | label: Would you like to work on this feature? 13 | options: 14 | - label: Check this if you would like to implement a PR, we are more than happy to help you go through the process. 15 | 16 | - type: textarea 17 | attributes: 18 | label: What problem are you trying to solve? 19 | description: | 20 | A concise description of what the problem is. 21 | placeholder: | 22 | I have an issue when [...] 23 | validations: 24 | required: true 25 | 26 | - type: textarea 27 | attributes: 28 | label: Describe the solution you'd like 29 | validations: 30 | required: true 31 | 32 | - type: textarea 33 | attributes: 34 | label: Describe alternatives you've considered 35 | 36 | - type: textarea 37 | attributes: 38 | label: Documentation, Adoption, Migration Strategy 39 | description: | 40 | If you can, explain how users will be able to use this and how it might be documented. Maybe a mock-up? 41 | -------------------------------------------------------------------------------- /.github/workflows/website.yml: -------------------------------------------------------------------------------- 1 | name: Website 2 | on: 3 | push: 4 | branches: 5 | - main 6 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 7 | permissions: 8 | contents: read 9 | pages: write 10 | id-token: write 11 | # Allow one concurrent deployment 12 | concurrency: 13 | group: "pages" 14 | cancel-in-progress: true 15 | jobs: 16 | deploy: 17 | environment: 18 | name: github-pages 19 | url: ${{ steps.deployment.outputs.page_url }} 20 | runs-on: ubuntu-latest 21 | name: deploy website 22 | steps: 23 | - name: Checkout the repository 24 | uses: actions/checkout@v3 25 | - name: Install pnpm 26 | uses: pnpm/action-setup@v2 27 | with: 28 | version: 7 29 | - name: Install Node.js 30 | uses: actions/setup-node@v3 31 | with: 32 | node-version: 16 33 | cache: 'pnpm' 34 | - name: Install dependencies 35 | run: pnpm install 36 | - name: Build website 37 | run: pnpm build 38 | working-directory: ./website 39 | - name: Setup Pages 40 | uses: actions/configure-pages@v2 41 | - name: Upload artifact 42 | uses: actions/upload-pages-artifact@v1 43 | with: 44 | path: './website/build' 45 | - name: Deploy to GitHub Pages 46 | id: deployment 47 | uses: actions/deploy-pages@v1 48 | -------------------------------------------------------------------------------- /src/axes/FixedScaleAxis.spec.ts: -------------------------------------------------------------------------------- 1 | import { FixedScaleAxis } from './FixedScaleAxis'; 2 | 3 | describe('Axes', () => { 4 | describe('FixedScaleAxis', () => { 5 | it('should order the tick array', () => { 6 | const ticks = [10, 5, 0, -5, -10]; 7 | const axisUnit = { 8 | pos: 'y', 9 | len: 'height', 10 | dir: 'vertical', 11 | rectStart: 'y2', 12 | rectEnd: 'y1', 13 | rectOffset: 'x1' 14 | } as const; 15 | const data = [ 16 | [ 17 | { x: 1, y: 10 }, 18 | { x: 2, y: 5 }, 19 | { x: 3, y: -5 } 20 | ] 21 | ]; 22 | const chartRect: any = { 23 | padding: { 24 | top: 15, 25 | right: 15, 26 | bottom: 5, 27 | left: 10 28 | }, 29 | y2: 15, 30 | y1: 141, 31 | x1: 50, 32 | x2: 269 33 | }; 34 | const options = { 35 | offset: 40, 36 | position: 'start' as const, 37 | labelOffset: { x: 0, y: 0 }, 38 | showLabel: true, 39 | showGrid: true, 40 | scaleMinSpace: 20, 41 | onlyInteger: false, 42 | ticks 43 | }; 44 | const fsaxis: any = new FixedScaleAxis( 45 | axisUnit, 46 | data, 47 | chartRect, 48 | options 49 | ); 50 | expect(fsaxis.ticks).toEqual([-10, -5, 0, 5, 10]); 51 | }); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /website/src/prism-theme.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plain: { 3 | color: '#f7f2ea', 4 | backgroundColor: '#453D3F' 5 | }, 6 | styles: [ 7 | { 8 | types: ['prolog', 'constant', 'builtin'], 9 | style: { 10 | color: '#F05B4F' 11 | } 12 | }, 13 | { 14 | types: ['inserted', 'function'], 15 | style: { 16 | color: '#F05B4F' 17 | } 18 | }, 19 | { 20 | types: ['deleted'], 21 | style: { 22 | color: 'rgb(255, 85, 85)' 23 | } 24 | }, 25 | { 26 | types: ['changed'], 27 | style: { 28 | color: 'rgb(255, 184, 108)' 29 | } 30 | }, 31 | { 32 | types: ['punctuation', 'symbol'], 33 | style: { 34 | color: '#f7f2ea' 35 | } 36 | }, 37 | { 38 | types: ['number'], 39 | style: { 40 | color: '#F4C63D' 41 | } 42 | }, 43 | { 44 | types: ['string', 'char', 'tag', 'selector'], 45 | style: { 46 | color: '#F4C63D' 47 | } 48 | }, 49 | { 50 | types: ['keyword', 'variable'], 51 | style: { 52 | color: '#F05B4F', 53 | fontStyle: 'italic' 54 | } 55 | }, 56 | { 57 | types: ['comment'], 58 | style: { 59 | color: '#7b6d70' 60 | } 61 | }, 62 | { 63 | types: ['attr-name'], 64 | style: { 65 | color: '#F4C63D' 66 | } 67 | } 68 | ] 69 | }; 70 | -------------------------------------------------------------------------------- /src/axes/FixedScaleAxis.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | ChartRect, 3 | AxisOptions, 4 | NormalizedSeries, 5 | NormalizedSeriesPrimitiveValue 6 | } from '../core'; 7 | import { getMultiValue, getHighLow } from '../core/data'; 8 | import { times } from '../utils'; 9 | import { AxisUnits, Axis } from './Axis'; 10 | 11 | export class FixedScaleAxis extends Axis { 12 | public override readonly range: { 13 | min: number; 14 | max: number; 15 | }; 16 | 17 | constructor( 18 | axisUnit: AxisUnits, 19 | data: NormalizedSeries[], 20 | chartRect: ChartRect, 21 | options: AxisOptions 22 | ) { 23 | const highLow = options.highLow || getHighLow(data, options, axisUnit.pos); 24 | const divisor = options.divisor || 1; 25 | const ticks = ( 26 | options.ticks || 27 | times( 28 | divisor, 29 | index => highLow.low + ((highLow.high - highLow.low) / divisor) * index 30 | ) 31 | ).sort((a, b) => Number(a) - Number(b)); 32 | const range = { 33 | min: highLow.low, 34 | max: highLow.high 35 | }; 36 | 37 | super(axisUnit, chartRect, ticks); 38 | 39 | this.range = range; 40 | } 41 | 42 | projectValue(value: NormalizedSeriesPrimitiveValue) { 43 | const finalValue = Number(getMultiValue(value, this.units.pos)); 44 | 45 | return ( 46 | (this.axisLength * (finalValue - this.range.min)) / 47 | (this.range.max - this.range.min) 48 | ); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/axes/AutoScaleAxis.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | ChartRect, 3 | AxisOptions, 4 | Bounds, 5 | NormalizedSeries, 6 | NormalizedSeriesPrimitiveValue 7 | } from '../core'; 8 | import { getBounds, getHighLow, getMultiValue } from '../core'; 9 | import { AxisUnits, Axis } from './Axis'; 10 | 11 | export class AutoScaleAxis extends Axis { 12 | private readonly bounds: Bounds; 13 | public override readonly range: { 14 | min: number; 15 | max: number; 16 | }; 17 | 18 | constructor( 19 | axisUnit: AxisUnits, 20 | data: NormalizedSeries[], 21 | chartRect: ChartRect, 22 | options: AxisOptions 23 | ) { 24 | // Usually we calculate highLow based on the data but this can be overriden by a highLow object in the options 25 | const highLow = options.highLow || getHighLow(data, options, axisUnit.pos); 26 | const bounds = getBounds( 27 | chartRect[axisUnit.rectEnd] - chartRect[axisUnit.rectStart], 28 | highLow, 29 | options.scaleMinSpace || 20, 30 | options.onlyInteger 31 | ); 32 | const range = { 33 | min: bounds.min, 34 | max: bounds.max 35 | }; 36 | 37 | super(axisUnit, chartRect, bounds.values); 38 | 39 | this.bounds = bounds; 40 | this.range = range; 41 | } 42 | 43 | projectValue(value: NormalizedSeriesPrimitiveValue) { 44 | const finalValue = Number(getMultiValue(value, this.units.pos)); 45 | 46 | return ( 47 | (this.axisLength * (finalValue - this.bounds.min)) / this.bounds.range 48 | ); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /sandboxes/bar/extreme-responsive/index.ts: -------------------------------------------------------------------------------- 1 | import 'chartist/dist/index.css'; 2 | import { BarChart, noop } from 'chartist'; 3 | 4 | new BarChart( 5 | '#chart', 6 | { 7 | labels: ['Quarter 1', 'Quarter 2', 'Quarter 3', 'Quarter 4'], 8 | series: [ 9 | [5, 4, 3, 7], 10 | [3, 2, 9, 5], 11 | [1, 5, 8, 4], 12 | [2, 3, 4, 6], 13 | [4, 1, 2, 1] 14 | ] 15 | }, 16 | { 17 | // Default mobile configuration 18 | stackBars: true, 19 | axisX: { 20 | labelInterpolationFnc: value => 21 | String(value) 22 | .split(/\s+/) 23 | .map(word => word[0]) 24 | .join('') 25 | }, 26 | axisY: { 27 | offset: 20 28 | } 29 | }, 30 | [ 31 | // Options override for media > 400px 32 | [ 33 | 'screen and (min-width: 400px)', 34 | { 35 | reverseData: true, 36 | horizontalBars: true, 37 | axisX: { 38 | labelInterpolationFnc: noop 39 | }, 40 | axisY: { 41 | offset: 60 42 | } 43 | } 44 | ], 45 | // Options override for media > 800px 46 | [ 47 | 'screen and (min-width: 800px)', 48 | { 49 | stackBars: false, 50 | seriesBarDistance: 10 51 | } 52 | ], 53 | // Options override for media > 1000px 54 | [ 55 | 'screen and (min-width: 1000px)', 56 | { 57 | reverseData: false, 58 | horizontalBars: false, 59 | seriesBarDistance: 15 60 | } 61 | ] 62 | ] 63 | ); 64 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.yml: -------------------------------------------------------------------------------- 1 | name: "🐛 Bug Report" 2 | description: "If something isn't working as expected." 3 | title: "[Bug]: " 4 | labels: ["bug"] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: Thanks for taking the time to file a bug report! Please fill out this form as completely as possible. 9 | 10 | - type: checkboxes 11 | id: input1 12 | attributes: 13 | label: Would you like to work on a fix? 14 | options: 15 | - label: Check this if you would like to implement a PR, we are more than happy to help you go through the process. 16 | 17 | - type: textarea 18 | attributes: 19 | label: Current and expected behavior 20 | description: A clear and concise description of what the library is doing and what you would expect. 21 | validations: 22 | required: true 23 | 24 | - type: input 25 | attributes: 26 | label: Reproduction 27 | description: | 28 | Please provide issue reproduction. 29 | You can give a link to a repository with the reproduction or make a [sandbox](https://codesandbox.io/) and reproduce the issue there. 30 | validations: 31 | required: true 32 | 33 | - type: input 34 | attributes: 35 | label: Chartist version 36 | description: Which version of Chartist are you using? 37 | placeholder: v0.0.0 38 | validations: 39 | required: true 40 | 41 | - type: textarea 42 | attributes: 43 | label: Possible solution 44 | description: If you have suggestions on a fix for the bug. 45 | -------------------------------------------------------------------------------- /sandboxes/line/simple-responsive/index.ts: -------------------------------------------------------------------------------- 1 | import 'chartist/dist/index.css'; 2 | import { LineChart, LineChartOptions, ResponsiveOptions } from 'chartist'; 3 | 4 | /* Add a basic data series with six labels and values */ 5 | const data = { 6 | labels: ['1', '2', '3', '4', '5', '6'], 7 | series: [ 8 | { 9 | data: [1, 2, 3, 5, 8, 13] 10 | } 11 | ] 12 | }; 13 | 14 | /* Set some base options (settings will override the default settings in js *see default settings*). We are adding a basic label interpolation function for the xAxis labels. */ 15 | const options: LineChartOptions = { 16 | axisX: { 17 | labelInterpolationFnc: value => 'Calendar Week ' + value 18 | } 19 | }; 20 | 21 | /* Now we can specify multiple responsive settings that will override the base settings based on order and if the media queries match. In this example we are changing the visibility of dots and lines as well as use different label interpolations for space reasons. */ 22 | const responsiveOptions: ResponsiveOptions = [ 23 | [ 24 | 'screen and (min-width: 641px) and (max-width: 1024px)', 25 | { 26 | showPoint: false, 27 | axisX: { 28 | labelInterpolationFnc: value => 'Week ' + value 29 | } 30 | } 31 | ], 32 | [ 33 | 'screen and (max-width: 640px)', 34 | { 35 | showLine: false, 36 | axisX: { 37 | labelInterpolationFnc: value => 'W' + value 38 | } 39 | } 40 | ] 41 | ]; 42 | 43 | /* Initialize the chart with the above settings */ 44 | new LineChart('#chart', data, options, responsiveOptions); 45 | -------------------------------------------------------------------------------- /src/interpolation/none.ts: -------------------------------------------------------------------------------- 1 | import type { SegmentData } from '../core'; 2 | import { getMultiValue } from '../core'; 3 | import { SvgPath } from '../svg'; 4 | 5 | export interface NoneInterpolationOptions { 6 | fillHoles?: boolean; 7 | } 8 | 9 | /** 10 | * This interpolation function does not smooth the path and the result is only containing lines and no curves. 11 | * 12 | * @example 13 | * ```ts 14 | * const chart = new LineChart('.ct-chart', { 15 | * labels: [1, 2, 3, 4, 5], 16 | * series: [[1, 2, 8, 1, 7]] 17 | * }, { 18 | * lineSmooth: Interpolation.none({ 19 | * fillHoles: false 20 | * }) 21 | * }); 22 | * ``` 23 | */ 24 | export function none(options?: NoneInterpolationOptions) { 25 | const finalOptions = { 26 | fillHoles: false, 27 | ...options 28 | }; 29 | 30 | return function noneInterpolation( 31 | pathCoordinates: number[], 32 | valueData: SegmentData[] 33 | ) { 34 | const path = new SvgPath(); 35 | let hole = true; 36 | 37 | for (let i = 0; i < pathCoordinates.length; i += 2) { 38 | const currX = pathCoordinates[i]; 39 | const currY = pathCoordinates[i + 1]; 40 | const currData = valueData[i / 2]; 41 | 42 | if (getMultiValue(currData.value) !== undefined) { 43 | if (hole) { 44 | path.move(currX, currY, false, currData); 45 | } else { 46 | path.line(currX, currY, false, currData); 47 | } 48 | 49 | hole = false; 50 | } else if (!finalOptions.fillHoles) { 51 | hole = true; 52 | } 53 | } 54 | 55 | return path; 56 | }; 57 | } 58 | -------------------------------------------------------------------------------- /scripts/styles.cjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const fs = require('fs').promises; 4 | const path = require('path'); 5 | const sass = require('sass'); 6 | const postcss = require('postcss'); 7 | 8 | const { plugins } = require('../postcss.config.cjs'); 9 | const pkg = require('../package.json'); 10 | 11 | const cwd = process.cwd(); 12 | const input = process.argv[2]; 13 | const output = pkg.style; 14 | const sourceMapOutput = output.replace('.css', '.css.map'); 15 | 16 | async function compile() { 17 | let styles; 18 | 19 | styles = sass.compile(input, { 20 | sourceMap: true 21 | }); 22 | 23 | styles.sourceMap.sources = styles.sourceMap.sources.map(_ => 24 | _.replace(cwd, '') 25 | ); 26 | 27 | styles = await postcss(plugins).process(styles.css, { 28 | from: input, 29 | to: output, 30 | map: { 31 | prev: styles.sourceMap 32 | } 33 | }); 34 | 35 | const map = styles.map.toString(); 36 | const css = 37 | styles.css + `\n/*# sourceMappingURL=${path.basename(sourceMapOutput)} */`; 38 | 39 | await fs.mkdir(path.dirname(output), { 40 | recursive: true 41 | }); 42 | await Promise.all([ 43 | fs.writeFile(output, css), 44 | fs.writeFile(sourceMapOutput, map) 45 | ]); 46 | } 47 | 48 | async function copySrc() { 49 | const srcDir = path.dirname(input); 50 | const distDir = path.dirname(output); 51 | const srcFiles = await fs.readdir(srcDir); 52 | 53 | await Promise.all( 54 | srcFiles.map(file => 55 | fs.copyFile(path.join(srcDir, file), path.join(distDir, file)) 56 | ) 57 | ); 58 | } 59 | 60 | Promise.all([compile(), copySrc()]); 61 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import swc from 'rollup-plugin-swc'; 2 | import { nodeResolve } from '@rollup/plugin-node-resolve'; 3 | import { terser } from 'rollup-plugin-terser'; 4 | import pkg from './package.json'; 5 | 6 | const extensions = ['.js', '.ts', '.tsx']; 7 | const external = _ => /node_modules/.test(_) && !/@swc\/helpers/.test(_); 8 | const plugins = (targets, minify) => 9 | [ 10 | nodeResolve({ 11 | extensions 12 | }), 13 | swc({ 14 | jsc: { 15 | parser: { 16 | syntax: 'typescript' 17 | }, 18 | externalHelpers: true 19 | }, 20 | env: { 21 | targets 22 | }, 23 | module: { 24 | type: 'es6' 25 | }, 26 | sourceMaps: true 27 | }), 28 | minify && terser() 29 | ].filter(Boolean); 30 | 31 | export default [ 32 | { 33 | input: pkg.main, 34 | plugins: plugins('defaults, not ie 11, not ie_mob 11'), 35 | external, 36 | output: { 37 | file: pkg.publishConfig.main, 38 | format: 'cjs', 39 | exports: 'named', 40 | sourcemap: true 41 | } 42 | }, 43 | { 44 | input: pkg.main, 45 | plugins: plugins('defaults, not ie 11, not ie_mob 11', true), 46 | external: () => false, 47 | output: { 48 | file: pkg.unpkg, 49 | format: 'umd', 50 | name: 'Chartist', 51 | exports: 'named', 52 | sourcemap: true 53 | } 54 | }, 55 | { 56 | input: pkg.main, 57 | plugins: plugins('defaults and supports es6-module'), 58 | external, 59 | output: { 60 | file: pkg.publishConfig.module, 61 | format: 'es', 62 | sourcemap: true 63 | } 64 | } 65 | ]; 66 | -------------------------------------------------------------------------------- /website/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "website", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "docusaurus": "docusaurus", 7 | "start": "docusaurus start", 8 | "build": "docusaurus build", 9 | "swizzle": "docusaurus swizzle", 10 | "deploy": "docusaurus deploy", 11 | "clear": "docusaurus clear", 12 | "serve": "docusaurus serve", 13 | "write-translations": "docusaurus write-translations", 14 | "write-heading-ids": "docusaurus write-heading-ids", 15 | "typecheck": "tsc" 16 | }, 17 | "dependencies": { 18 | "@algolia/client-search": "^4.11.0", 19 | "@docusaurus/core": "2.0.0-beta.14", 20 | "@docusaurus/preset-classic": "2.0.0-beta.14", 21 | "@docusaurus/theme-search-algolia": "^2.0.0-beta.9", 22 | "@mdx-js/react": "^1.6.21", 23 | "@svgr/webpack": "^6.0.0", 24 | "clsx": "^1.1.1", 25 | "file-loader": "^6.2.0", 26 | "git-branch": "^2.0.1", 27 | "prism-react-renderer": "^1.2.1", 28 | "react": "^17.0.1", 29 | "react-dom": "^17.0.1", 30 | "url-loader": "^4.1.1" 31 | }, 32 | "devDependencies": { 33 | "@docusaurus/module-type-aliases": "2.0.0-beta.14", 34 | "@tsconfig/docusaurus": "^1.0.4", 35 | "@types/react": "^17.0.36", 36 | "docusaurus-plugin-typedoc": "^0.17.4", 37 | "typedoc": "^0.23.0", 38 | "typedoc-plugin-markdown": "^3.12.1", 39 | "typescript": "^4.3.5", 40 | "webpack": "^5.64.2" 41 | }, 42 | "browserslist": { 43 | "production": [ 44 | ">0.5%", 45 | "not dead", 46 | "not op_mini all" 47 | ], 48 | "development": [ 49 | "last 1 chrome version", 50 | "last 1 firefox version", 51 | "last 1 safari version" 52 | ] 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/svg/types.ts: -------------------------------------------------------------------------------- 1 | import type { SegmentData } from '../core'; 2 | import type { easings } from './animation'; 3 | import type { Svg } from './Svg'; 4 | 5 | export interface BasePathParams { 6 | x: number; 7 | y: number; 8 | } 9 | 10 | export type MoveParams = BasePathParams; 11 | 12 | export type LineParams = BasePathParams; 13 | 14 | export interface CurveParams extends BasePathParams { 15 | x1: number; 16 | y1: number; 17 | x2: number; 18 | y2: number; 19 | } 20 | 21 | export interface ArcParams extends BasePathParams { 22 | rx: number; 23 | ry: number; 24 | xAr: number; 25 | lAf: number; 26 | sf: number; 27 | } 28 | 29 | export type PathParams = MoveParams | LineParams | CurveParams | ArcParams; 30 | 31 | export type PathCommand = { 32 | command: string; 33 | data?: SegmentData; 34 | } & T; 35 | 36 | export interface SvgPathOptions { 37 | accuracy?: number; 38 | } 39 | 40 | export type Attributes = Record; 41 | 42 | export interface AnimationDefinition { 43 | id?: string; 44 | easing?: number[] | keyof typeof easings; 45 | calcMode?: 'discrete' | 'linear' | 'paced' | 'spline'; 46 | restart?: 'always' | 'whenNotActive' | 'never'; 47 | repeatCount?: number | 'indefinite'; 48 | repeatDur?: string | 'indefinite'; 49 | keySplines?: string; 50 | keyTimes?: string; 51 | fill?: string; 52 | min?: number | string; 53 | max?: number | string; 54 | begin?: number | string; 55 | end?: number | string; 56 | dur: number | string; 57 | from: number | string; 58 | to: number | string; 59 | } 60 | 61 | export interface AnimationEvent { 62 | element: Svg; 63 | animate: Element; 64 | params: AnimationDefinition; 65 | } 66 | -------------------------------------------------------------------------------- /src/utils/functional.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Helps to simplify functional style code 3 | * @param n This exact value will be returned by the noop function 4 | * @return The same value that was provided to the n parameter 5 | */ 6 | export const noop = (n: T) => n; 7 | 8 | /** 9 | * Functional style helper to produce array with given length initialized with undefined values 10 | */ 11 | export function times(length: number): undefined[]; 12 | export function times( 13 | length: number, 14 | filler: (index: number) => T 15 | ): T[]; 16 | export function times( 17 | length: number, 18 | filler?: (index: number) => T 19 | ) { 20 | return Array.from({ length }, filler ? (_, i) => filler(i) : () => void 0); 21 | } 22 | 23 | /** 24 | * Sum helper to be used in reduce functions 25 | */ 26 | export const sum = (previous: number, current: number) => 27 | previous + (current ? current : 0); 28 | 29 | /** 30 | * Map for multi dimensional arrays where their nested arrays will be mapped in serial. The output array will have the length of the largest nested array. The callback function is called with variable arguments where each argument is the nested array value (or undefined if there are no more values). 31 | * 32 | * For example: 33 | * @example 34 | * ```ts 35 | * const data = [[1, 2], [3], []]; 36 | * serialMap(data, cb); 37 | * 38 | * // where cb will be called 2 times 39 | * // 1. call arguments: (1, 3, undefined) 40 | * // 2. call arguments: (2, undefined, undefined) 41 | * ``` 42 | */ 43 | export const serialMap = (array: T[][], callback: (...args: T[]) => K) => 44 | times(Math.max(...array.map(element => element.length)), index => 45 | callback(...array.map(element => element[index])) 46 | ); 47 | -------------------------------------------------------------------------------- /sandboxes/line/series-override/index.ts: -------------------------------------------------------------------------------- 1 | import 'chartist/dist/index.css'; 2 | import { LineChart, Interpolation } from 'chartist'; 3 | 4 | new LineChart( 5 | '#chart', 6 | { 7 | labels: ['1', '2', '3', '4', '5', '6', '7', '8'], 8 | // Naming the series with the series object array notation 9 | series: [ 10 | { 11 | name: 'series-1', 12 | data: [5, 2, -4, 2, 0, -2, 5, -3] 13 | }, 14 | { 15 | name: 'series-2', 16 | data: [4, 3, 5, 3, 1, 3, 6, 4] 17 | }, 18 | { 19 | name: 'series-3', 20 | data: [2, 4, 3, 1, 4, 5, 3, 2] 21 | } 22 | ] 23 | }, 24 | { 25 | fullWidth: true, 26 | // Within the series options you can use the series names 27 | // to specify configuration that will only be used for the 28 | // specific series. 29 | series: { 30 | 'series-1': { 31 | lineSmooth: Interpolation.step() 32 | }, 33 | 'series-2': { 34 | lineSmooth: Interpolation.simple(), 35 | showArea: true 36 | }, 37 | 'series-3': { 38 | showPoint: false 39 | } 40 | } 41 | }, 42 | [ 43 | // You can even use responsive configuration overrides to 44 | // customize your series configuration even further! 45 | [ 46 | 'screen and (max-width: 320px)', 47 | { 48 | series: { 49 | 'series-1': { 50 | lineSmooth: Interpolation.none() 51 | }, 52 | 'series-2': { 53 | lineSmooth: Interpolation.none(), 54 | showArea: false 55 | }, 56 | 'series-3': { 57 | lineSmooth: Interpolation.none(), 58 | showPoint: true 59 | } 60 | } 61 | } 62 | ] 63 | ] 64 | ); 65 | -------------------------------------------------------------------------------- /src/core/data/serialize.spec.ts: -------------------------------------------------------------------------------- 1 | import { serialize, deserialize } from './serialize'; 2 | 3 | describe('Core', () => { 4 | describe('Data', () => { 5 | describe('Serialize', () => { 6 | it('should serialize and deserialize regular strings', () => { 7 | const input = 'String test'; 8 | expect(input).toMatch(deserialize(serialize(input))); 9 | }); 10 | 11 | it('should serialize and deserialize strings with critical characters', () => { 12 | const input = 'String test with critical characters " < > \' & &'; 13 | expect(input).toMatch(deserialize(serialize(input))); 14 | }); 15 | 16 | it('should serialize and deserialize numbers', () => { 17 | const input = 12345.6789; 18 | expect(input).toEqual(deserialize(serialize(input))); 19 | }); 20 | 21 | it('should serialize and deserialize dates', () => { 22 | const input = new Date(0); 23 | expect(+input).toEqual(+new Date(deserialize(serialize(input)))); 24 | }); 25 | 26 | it('should serialize and deserialize complex object types', () => { 27 | const input = { 28 | a: { 29 | b: 100, 30 | c: 'String test', 31 | d: 'String test with critical characters " < > \' & &', 32 | e: { 33 | f: 'String test' 34 | } 35 | } 36 | }; 37 | 38 | expect(input).toEqual(deserialize(serialize(input))); 39 | }); 40 | 41 | it('should serialize and deserialize null, undefined and NaN', () => { 42 | expect(null).toEqual(deserialize(serialize(null))); 43 | expect(undefined).toEqual(deserialize(serialize(undefined))); 44 | expect(deserialize(serialize(NaN))).toBeNaN(); 45 | }); 46 | }); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["eslint:recommended", "plugin:prettier/recommended"], 3 | "parser": "@babel/eslint-parser", 4 | "parserOptions": { 5 | "ecmaVersion": "latest", 6 | "requireConfigFile": false 7 | }, 8 | "env": { 9 | "es6": true, 10 | "browser": true, 11 | "node": true 12 | }, 13 | "rules": { 14 | "no-console": 2, 15 | "curly": 2, 16 | "dot-notation": 1, 17 | "eqeqeq": 2, 18 | "no-alert": 2, 19 | "no-caller": 2, 20 | "no-eval": 2, 21 | "no-extra-bind": 2, 22 | "no-implied-eval": 2, 23 | "no-multi-spaces": 2, 24 | "no-with": 2, 25 | "no-shadow": 2, 26 | "no-shadow-restricted-names": 2, 27 | "brace-style": ["error", "1tbs"], 28 | "camelcase": 2, 29 | "comma-style": ["error", "last"], 30 | "eol-last": 2, 31 | "key-spacing": 2, 32 | "new-cap": 1, 33 | "no-array-constructor": 2, 34 | "no-mixed-spaces-and-tabs": 2, 35 | "no-multiple-empty-lines": 2, 36 | "semi-spacing": 2, 37 | "no-spaced-func": 2, 38 | "no-trailing-spaces": 2, 39 | "space-before-blocks": 2, 40 | "spaced-comment": 1, 41 | "no-var": 2 42 | }, 43 | "overrides": [ 44 | { 45 | "files": ["**/*.ts"], 46 | "parser": "@typescript-eslint/parser", 47 | "plugins": ["@typescript-eslint"], 48 | "extends": ["plugin:@typescript-eslint/recommended"] 49 | }, 50 | { 51 | "files": ["test/**/*.{js,ts}", "*.spec.{js,ts}", "*.stories.{js,ts}"], 52 | "plugins": [ 53 | "jest", 54 | "testing-library", 55 | "jest-dom" 56 | ], 57 | "extends": ["plugin:jest-dom/recommended"], 58 | "env": { 59 | "jest/globals": true 60 | }, 61 | "rules": { 62 | "no-console": 0, 63 | "no-shadow": 0, 64 | "@typescript-eslint/no-explicit-any": 0 65 | } 66 | } 67 | ] 68 | } 69 | -------------------------------------------------------------------------------- /test/mock/dom.ts: -------------------------------------------------------------------------------- 1 | export type Fixture = ReturnType; 2 | 3 | export let container: HTMLDivElement | null = null; 4 | 5 | const getBoundingClientRect = SVGElement.prototype.getBoundingClientRect; 6 | 7 | export function mockDom() { 8 | if (!container) { 9 | container = document.createElement('div'); 10 | container.setAttribute('data-fixture-container', `${+new Date()}`); 11 | document.body.appendChild(container); 12 | } 13 | } 14 | 15 | export function destroyMockDom() { 16 | if (container) { 17 | document.body.removeChild(container); 18 | container = null; 19 | } 20 | } 21 | 22 | export function addMockWrapper(fixture: string) { 23 | const wrapper = document.createElement('div'); 24 | wrapper.innerHTML += fixture; 25 | container?.appendChild(wrapper); 26 | return { 27 | wrapper, 28 | container, 29 | fixture 30 | }; 31 | } 32 | 33 | export function mockDomRects() { 34 | // @ts-expect-error Mock DOM API. 35 | SVGElement.prototype.getBoundingClientRect = () => ({ 36 | x: 0, 37 | y: 0, 38 | width: 500, 39 | height: 500, 40 | top: 0, 41 | right: 0, 42 | bottom: 0, 43 | left: 0 44 | }); 45 | 46 | Object.defineProperties(SVGElement.prototype, { 47 | clientWidth: { 48 | configurable: true, 49 | get: () => 500 50 | }, 51 | clientHeight: { 52 | configurable: true, 53 | get: () => 500 54 | } 55 | }); 56 | } 57 | 58 | export function destroyMockDomRects() { 59 | SVGElement.prototype.getBoundingClientRect = getBoundingClientRect; 60 | 61 | // Redefine clientWidth and clientHeight properties from the prototype of SVGElement 62 | const ElementPrototype = Object.getPrototypeOf(SVGElement.prototype); 63 | Object.defineProperties(SVGElement.prototype, { 64 | clientWidth: Object.getOwnPropertyDescriptor( 65 | ElementPrototype, 66 | 'clientWidth' 67 | )!, 68 | clientHeight: Object.getOwnPropertyDescriptor( 69 | ElementPrototype, 70 | 'clientHeight' 71 | )! 72 | }); 73 | } 74 | -------------------------------------------------------------------------------- /sandboxes/line/simple-svg-animation/index.ts: -------------------------------------------------------------------------------- 1 | import 'chartist/dist/index.css'; 2 | import { LineChart, easings } from 'chartist'; 3 | 4 | const chart = new LineChart( 5 | '#chart', 6 | { 7 | labels: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12'], 8 | series: [ 9 | [12, 4, 2, 8, 5, 4, 6, 2, 3, 3, 4, 6], 10 | [4, 8, 9, 3, 7, 2, 10, 5, 8, 1, 7, 10] 11 | ] 12 | }, 13 | { 14 | low: 0, 15 | showLine: false, 16 | axisX: { 17 | showLabel: false, 18 | offset: 0 19 | }, 20 | axisY: { 21 | showLabel: false, 22 | offset: 0 23 | } 24 | } 25 | ); 26 | 27 | // Let's put a sequence number aside so we can use it in the event callbacks 28 | let seq = 0; 29 | 30 | // Once the chart is fully created we reset the sequence 31 | chart.on('created', () => { 32 | seq = 0; 33 | }); 34 | 35 | // On each drawn element by Chartist we use the Svg API to trigger SMIL animations 36 | chart.on('draw', data => { 37 | if (data.type === 'point') { 38 | // If the drawn element is a line we do a simple opacity fade in. This could also be achieved using CSS3 animations. 39 | data.element.animate({ 40 | opacity: { 41 | // The delay when we like to start the animation 42 | begin: seq++ * 80, 43 | // Duration of the animation 44 | dur: 500, 45 | // The value where the animation should start 46 | from: 0, 47 | // The value where it should end 48 | to: 1 49 | }, 50 | x1: { 51 | begin: seq++ * 80, 52 | dur: 500, 53 | from: data.x - 100, 54 | to: data.x, 55 | // You can specify an easing function name or use easing functions from `easings` directly 56 | easing: easings.easeOutQuart 57 | } 58 | }); 59 | } 60 | }); 61 | 62 | let timerId: any; 63 | 64 | // For the sake of the example we update the chart every time it's created with a delay of 8 seconds 65 | chart.on('created', () => { 66 | if (timerId) { 67 | clearTimeout(timerId); 68 | } 69 | 70 | timerId = setTimeout(chart.update.bind(chart), 8000); 71 | }); 72 | -------------------------------------------------------------------------------- /.github/workflows/checks.yml: -------------------------------------------------------------------------------- 1 | name: Checks 2 | on: 3 | pull_request: 4 | branches: 5 | - main 6 | jobs: 7 | size: 8 | runs-on: ubuntu-latest 9 | name: size-limit 10 | steps: 11 | - name: Checkout the repository 12 | uses: actions/checkout@v3 13 | - name: Install pnpm 14 | uses: pnpm/action-setup@v2 15 | with: 16 | version: 7 17 | - name: Install Node.js 18 | uses: actions/setup-node@v3 19 | with: 20 | node-version: 16 21 | cache: 'pnpm' 22 | - name: Check size 23 | uses: andresz1/size-limit-action@master 24 | with: 25 | github_token: ${{ secrets.GITHUB_TOKEN }} 26 | storybook: 27 | runs-on: ubuntu-latest 28 | name: storybook 29 | steps: 30 | - name: Checkout the repository 31 | uses: actions/checkout@v3 32 | - name: Install pnpm 33 | uses: pnpm/action-setup@v2 34 | with: 35 | version: 7 36 | - name: Install Node.js 37 | uses: actions/setup-node@v3 38 | with: 39 | node-version: 16 40 | cache: 'pnpm' 41 | - name: Install dependencies 42 | run: pnpm install 43 | - name: Check storybook 44 | run: pnpm build:storybook 45 | editorconfig: 46 | runs-on: ubuntu-latest 47 | name: editorconfig 48 | steps: 49 | - name: Checkout the repository 50 | uses: actions/checkout@v3 51 | - name: Check editorconfig 52 | uses: editorconfig-checker/action-editorconfig-checker@v1 53 | website: 54 | runs-on: ubuntu-latest 55 | name: website 56 | steps: 57 | - name: Checkout the repository 58 | uses: actions/checkout@v3 59 | - name: Install pnpm 60 | uses: pnpm/action-setup@v2 61 | with: 62 | version: 7 63 | - name: Install Node.js 64 | uses: actions/setup-node@v3 65 | with: 66 | node-version: 16 67 | cache: 'pnpm' 68 | - name: Install dependencies 69 | run: pnpm install 70 | - name: Check website 71 | run: pnpm build 72 | working-directory: ./website 73 | -------------------------------------------------------------------------------- /src/core/data/data.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | Multi, 3 | AxisName, 4 | FlatSeriesValue, 5 | Series, 6 | SeriesObject 7 | } from '../types'; 8 | import { safeHasProperty, getNumberOrUndefined } from '../../utils'; 9 | 10 | /** 11 | * Get meta data of a specific value in a series. 12 | */ 13 | export function getMetaData( 14 | seriesData: FlatSeriesValue | Series | SeriesObject, 15 | index: number 16 | ) { 17 | const value = Array.isArray(seriesData) 18 | ? seriesData[index] 19 | : safeHasProperty(seriesData, 'data') 20 | ? seriesData.data[index] 21 | : null; 22 | return safeHasProperty(value, 'meta') ? value.meta : undefined; 23 | } 24 | 25 | /** 26 | * Checks if a value is considered a hole in the data series. 27 | * @returns True if the value is considered a data hole 28 | */ 29 | export function isDataHoleValue(value: unknown): value is null | undefined; 30 | export function isDataHoleValue(value: unknown) { 31 | return ( 32 | value === null || 33 | value === undefined || 34 | (typeof value === 'number' && isNaN(value)) 35 | ); 36 | } 37 | 38 | /** 39 | * Checks if value is array of series objects. 40 | */ 41 | export function isArrayOfSeries( 42 | value: unknown 43 | ): value is (Series | SeriesObject)[] { 44 | return ( 45 | Array.isArray(value) && 46 | value.every(_ => Array.isArray(_) || safeHasProperty(_, 'data')) 47 | ); 48 | } 49 | 50 | /** 51 | * Checks if provided value object is multi value (contains x or y properties) 52 | */ 53 | export function isMultiValue(value: unknown): value is Multi { 54 | return ( 55 | typeof value === 'object' && 56 | value !== null && 57 | (Reflect.has(value, 'x') || Reflect.has(value, 'y')) 58 | ); 59 | } 60 | 61 | /** 62 | * Gets a value from a dimension `value.x` or `value.y` while returning value directly if it's a valid numeric value. If the value is not numeric and it's falsey this function will return `defaultValue`. 63 | */ 64 | export function getMultiValue( 65 | value: Multi | number | unknown, 66 | dimension: AxisName = 'y' 67 | ) { 68 | if (isMultiValue(value) && safeHasProperty(value, dimension)) { 69 | return getNumberOrUndefined(value[dimension]); 70 | } else { 71 | return getNumberOrUndefined(value); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /sandboxes/pie/donut-animation/index.ts: -------------------------------------------------------------------------------- 1 | import 'chartist/dist/index.css'; 2 | import { PieChart, easings, AnimationDefinition } from 'chartist'; 3 | 4 | const chart = new PieChart( 5 | '#chart', 6 | { 7 | series: [10, 20, 50, 20, 5, 50, 15], 8 | labels: [1, 2, 3, 4, 5, 6, 7] 9 | }, 10 | { 11 | donut: true, 12 | showLabel: false 13 | } 14 | ); 15 | 16 | chart.on('draw', data => { 17 | if (data.type === 'slice') { 18 | // Get the total path length in order to use for dash array animation 19 | const pathLength = data.element 20 | .getNode() 21 | .getTotalLength(); 22 | 23 | // Set a dasharray that matches the path length as prerequisite to animate dashoffset 24 | data.element.attr({ 25 | 'stroke-dasharray': pathLength + 'px ' + pathLength + 'px' 26 | }); 27 | 28 | // Create animation definition while also assigning an ID to the animation for later sync usage 29 | const animationDefinition: Record = { 30 | 'stroke-dashoffset': { 31 | id: 'anim' + data.index, 32 | dur: 1000, 33 | from: -pathLength + 'px', 34 | to: '0px', 35 | easing: easings.easeOutQuint, 36 | // We need to use `fill: 'freeze'` otherwise our animation will fall back to initial (not visible) 37 | fill: 'freeze' 38 | } 39 | }; 40 | 41 | // If this was not the first slice, we need to time the animation so that it uses the end sync event of the previous animation 42 | if (data.index !== 0) { 43 | animationDefinition['stroke-dashoffset'].begin = 44 | 'anim' + (data.index - 1) + '.end'; 45 | } 46 | 47 | // We need to set an initial value before the animation starts as we are not in guided mode which would do that for us 48 | data.element.attr({ 49 | 'stroke-dashoffset': -pathLength + 'px' 50 | }); 51 | 52 | // We can't use guided mode as the animations need to rely on setting begin manually 53 | data.element.animate(animationDefinition, false); 54 | } 55 | }); 56 | 57 | let timerId: any; 58 | 59 | // For the sake of the example we update the chart every time it's created with a delay of 8 seconds 60 | chart.on('created', () => { 61 | if (timerId) { 62 | clearTimeout(timerId); 63 | } 64 | 65 | timerId = setTimeout(chart.update.bind(chart), 10000); 66 | }); 67 | -------------------------------------------------------------------------------- /test/utils/storyshots/storybook.js: -------------------------------------------------------------------------------- 1 | import { spawn } from 'child_process'; 2 | 3 | import { createServer } from 'http-server'; 4 | import del from 'del'; 5 | 6 | const STORYBOOK_STATIC = 'storybook-static'; 7 | const errorMatcher = /ERR!|Error:|ERROR in|UnhandledPromiseRejectionWarning/; 8 | 9 | /** 10 | * Run storybook static build. 11 | * @param options - Build options. 12 | * @param [options.env] - Environment variables. 13 | * @param [options.verbose] - Print verbose messages. 14 | * @returns Build process promise. 15 | */ 16 | export async function buildStorybook({ env = {}, verbose = false }) { 17 | return new Promise((resolve, reject) => { 18 | const buildProcess = spawn('build-storybook', [], { 19 | cwd: process.cwd(), 20 | env: { 21 | ...process.env, 22 | NODE_ENV: 'production', 23 | ...env 24 | }, 25 | detached: true 26 | }); 27 | const onData = data => { 28 | const message = data.toString('utf8'); 29 | 30 | if (verbose) { 31 | process.stdout.write(message); 32 | } 33 | 34 | if (errorMatcher.test(message)) { 35 | reject(new Error(message)); 36 | } 37 | }; 38 | 39 | buildProcess.on('exit', (code, signal) => { 40 | if (code === 0) { 41 | resolve(); 42 | return; 43 | } 44 | 45 | reject(new Error(`Exit code: ${code || signal || 'unknown'}`)); 46 | }); 47 | buildProcess.stdout.on('data', onData); 48 | buildProcess.stderr.on('data', onData); 49 | }); 50 | } 51 | 52 | /** 53 | * Build static and start storybook server. 54 | * @param options - Storybook build and start options. 55 | * @returns Server controls. 56 | */ 57 | export function startStorybook(options) { 58 | const { url, skipBuild } = options; 59 | const parsedUrl = new URL(url); 60 | const server = createServer({ 61 | root: STORYBOOK_STATIC 62 | }); 63 | 64 | return { 65 | async start() { 66 | if (!skipBuild) { 67 | await buildStorybook(options); 68 | } 69 | 70 | await new Promise(resolve => { 71 | server.listen( 72 | parseInt(parsedUrl.port, 10), 73 | parsedUrl.hostname, 74 | resolve 75 | ); 76 | }); 77 | }, 78 | async stop() { 79 | server.close(); 80 | 81 | if (!skipBuild) { 82 | await del(STORYBOOK_STATIC); 83 | } 84 | } 85 | }; 86 | } 87 | -------------------------------------------------------------------------------- /src/utils/utils.ts: -------------------------------------------------------------------------------- 1 | import type { FilterByKey } from './types'; 2 | 3 | /** 4 | * This function safely checks if an objects has an owned property. 5 | * @param target The object where to check for a property 6 | * @param property The property name 7 | * @returns Returns true if the object owns the specified property 8 | */ 9 | export function safeHasProperty( 10 | target: T, 11 | property: K 12 | ): target is FilterByKey; 13 | export function safeHasProperty(target: unknown, property: string) { 14 | return ( 15 | target !== null && 16 | typeof target === 'object' && 17 | Reflect.has(target, property) 18 | ); 19 | } 20 | 21 | /** 22 | * Checks if a value can be safely coerced to a number. This includes all values except null which result in finite numbers when coerced. This excludes NaN, since it's not finite. 23 | */ 24 | export function isNumeric(value: number): true; 25 | export function isNumeric(value: unknown): boolean; 26 | export function isNumeric(value: unknown) { 27 | return value !== null && isFinite(value as number); 28 | } 29 | 30 | /** 31 | * Returns true on all falsey values except the numeric value 0. 32 | */ 33 | export function isFalseyButZero( 34 | value: unknown 35 | ): value is undefined | null | false | '' { 36 | return !value && value !== 0; 37 | } 38 | 39 | /** 40 | * Returns a number if the passed parameter is a valid number or the function will return undefined. On all other values than a valid number, this function will return undefined. 41 | */ 42 | export function getNumberOrUndefined(value: number): number; 43 | export function getNumberOrUndefined(value: unknown): number | undefined; 44 | export function getNumberOrUndefined(value: unknown) { 45 | return isNumeric(value) ? Number(value) : undefined; 46 | } 47 | 48 | /** 49 | * Checks if value is array of arrays or not. 50 | */ 51 | export function isArrayOfArrays(data: unknown): data is unknown[][] { 52 | if (!Array.isArray(data)) { 53 | return false; 54 | } 55 | 56 | return data.every(Array.isArray); 57 | } 58 | 59 | /** 60 | * Loop over array. 61 | */ 62 | export function each( 63 | list: T[], 64 | callback: (item: T, index: number, itemIndex: number) => void, 65 | reverse = false 66 | ) { 67 | let index = 0; 68 | 69 | list[reverse ? 'reduceRight' : 'reduce']( 70 | (_, item, itemIndex) => callback(item, index++, itemIndex), 71 | void 0 72 | ); 73 | } 74 | -------------------------------------------------------------------------------- /src/core/optionsProvider.ts: -------------------------------------------------------------------------------- 1 | import type { EventEmitter } from '../event'; 2 | import type { OptionsChangedEvent, ResponsiveOptions } from './types'; 3 | import { extend } from '../utils'; 4 | 5 | export interface OptionsProvider { 6 | removeMediaQueryListeners(): void; 7 | getCurrentOptions(): T; 8 | } 9 | 10 | /** 11 | * Provides options handling functionality with callback for options changes triggered by responsive options and media query matches 12 | * @param options Options set by user 13 | * @param responsiveOptions Optional functions to add responsive behavior to chart 14 | * @param eventEmitter The event emitter that will be used to emit the options changed events 15 | * @return The consolidated options object from the defaults, base and matching responsive options 16 | */ 17 | export function optionsProvider( 18 | options: T, 19 | responsiveOptions: ResponsiveOptions | undefined, 20 | eventEmitter: EventEmitter 21 | ): OptionsProvider { 22 | let currentOptions: T; 23 | const mediaQueryListeners: MediaQueryList[] = []; 24 | 25 | function updateCurrentOptions(mediaEvent?: Event) { 26 | const previousOptions = currentOptions; 27 | currentOptions = extend({}, options); 28 | 29 | if (responsiveOptions) { 30 | responsiveOptions.forEach(responsiveOption => { 31 | const mql = window.matchMedia(responsiveOption[0]); 32 | if (mql.matches) { 33 | currentOptions = extend(currentOptions, responsiveOption[1]); 34 | } 35 | }); 36 | } 37 | 38 | if (eventEmitter && mediaEvent) { 39 | eventEmitter.emit>('optionsChanged', { 40 | previousOptions, 41 | currentOptions 42 | }); 43 | } 44 | } 45 | 46 | function removeMediaQueryListeners() { 47 | mediaQueryListeners.forEach(mql => 48 | mql.removeEventListener('change', updateCurrentOptions) 49 | ); 50 | } 51 | 52 | if (!window.matchMedia) { 53 | throw new Error( 54 | "window.matchMedia not found! Make sure you're using a polyfill." 55 | ); 56 | } else if (responsiveOptions) { 57 | responsiveOptions.forEach(responsiveOption => { 58 | const mql = window.matchMedia(responsiveOption[0]); 59 | mql.addEventListener('change', updateCurrentOptions); 60 | mediaQueryListeners.push(mql); 61 | }); 62 | } 63 | // Execute initially without an event argument so we get the correct options 64 | updateCurrentOptions(); 65 | 66 | return { 67 | removeMediaQueryListeners, 68 | getCurrentOptions() { 69 | return currentOptions; 70 | } 71 | }; 72 | } 73 | -------------------------------------------------------------------------------- /src/core/data/serialize.ts: -------------------------------------------------------------------------------- 1 | import { escapingMap } from '../constants'; 2 | 3 | /** 4 | * This function serializes arbitrary data to a string. In case of data that can't be easily converted to a string, this function will create a wrapper object and serialize the data using JSON.stringify. The outcoming string will always be escaped using Chartist.escapingMap. 5 | * If called with null or undefined the function will return immediately with null or undefined. 6 | */ 7 | export function serialize(data: number | string | object): string; 8 | export function serialize( 9 | data: number | string | object | null | undefined | unknown 10 | ): string | null | undefined; 11 | export function serialize( 12 | data: number | string | object | null | undefined | unknown 13 | ) { 14 | let serialized = ''; 15 | 16 | if (data === null || data === undefined) { 17 | return data; 18 | } else if (typeof data === 'number') { 19 | serialized = '' + data; 20 | } else if (typeof data === 'object') { 21 | serialized = JSON.stringify({ data: data }); 22 | } else { 23 | serialized = String(data); 24 | } 25 | 26 | return Object.keys(escapingMap).reduce( 27 | (result, key) => result.replaceAll(key, escapingMap[key]), 28 | serialized 29 | ); 30 | } 31 | 32 | /** 33 | * This function de-serializes a string previously serialized with Chartist.serialize. The string will always be unescaped using Chartist.escapingMap before it's returned. Based on the input value the return type can be Number, String or Object. JSON.parse is used with try / catch to see if the unescaped string can be parsed into an Object and this Object will be returned on success. 34 | */ 35 | export function deserialize( 36 | data: string 37 | ): T; 38 | export function deserialize( 39 | data: string | null | undefined 40 | ): T | null | undefined; 41 | export function deserialize(data: unknown) { 42 | if (typeof data !== 'string') { 43 | return data; 44 | } 45 | 46 | if (data === 'NaN') { 47 | return NaN; 48 | } 49 | 50 | data = Object.keys(escapingMap).reduce( 51 | (result, key) => result.replaceAll(escapingMap[key], key), 52 | data 53 | ); 54 | 55 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 56 | let parsedData: any = data; 57 | 58 | if (typeof data === 'string') { 59 | try { 60 | parsedData = JSON.parse(data); 61 | parsedData = parsedData.data !== undefined ? parsedData.data : parsedData; 62 | } catch (e) { 63 | /* Ingore */ 64 | } 65 | } 66 | 67 | return parsedData; 68 | } 69 | -------------------------------------------------------------------------------- /src/svg/SvgList.ts: -------------------------------------------------------------------------------- 1 | import { Svg } from './Svg'; 2 | 3 | type SvgMethods = Exclude< 4 | keyof Svg, 5 | | 'constructor' 6 | | 'parent' 7 | | 'querySelector' 8 | | 'querySelectorAll' 9 | | 'replace' 10 | | 'append' 11 | | 'classes' 12 | | 'height' 13 | | 'width' 14 | >; 15 | 16 | type SvgListMethods = { 17 | [method in SvgMethods]: (...args: Parameters) => SvgList; 18 | }; 19 | 20 | /** 21 | * This helper class is to wrap multiple `Svg` elements into a list where you can call the `Svg` functions on all elements in the list with one call. This is helpful when you'd like to perform calls with `Svg` on multiple elements. 22 | * An instance of this class is also returned by `Svg.querySelectorAll`. 23 | */ 24 | export class SvgList implements SvgListMethods { 25 | private svgElements: Svg[] = []; 26 | 27 | /** 28 | * @param nodeList An Array of SVG DOM nodes or a SVG DOM NodeList (as returned by document.querySelectorAll) 29 | */ 30 | constructor(nodeList: ArrayLike) { 31 | for (let i = 0; i < nodeList.length; i++) { 32 | this.svgElements.push(new Svg(nodeList[i])); 33 | } 34 | } 35 | 36 | private call(method: T, args: Parameters) { 37 | this.svgElements.forEach(element => 38 | Reflect.apply(element[method], element, args) 39 | ); 40 | return this; 41 | } 42 | 43 | attr(...args: Parameters) { 44 | return this.call('attr', args); 45 | } 46 | 47 | elem(...args: Parameters) { 48 | return this.call('elem', args); 49 | } 50 | 51 | root(...args: Parameters) { 52 | return this.call('root', args); 53 | } 54 | 55 | getNode(...args: Parameters) { 56 | return this.call('getNode', args); 57 | } 58 | 59 | foreignObject(...args: Parameters) { 60 | return this.call('foreignObject', args); 61 | } 62 | 63 | text(...args: Parameters) { 64 | return this.call('text', args); 65 | } 66 | 67 | empty(...args: Parameters) { 68 | return this.call('empty', args); 69 | } 70 | 71 | remove(...args: Parameters) { 72 | return this.call('remove', args); 73 | } 74 | 75 | addClass(...args: Parameters) { 76 | return this.call('addClass', args); 77 | } 78 | 79 | removeClass(...args: Parameters) { 80 | return this.call('removeClass', args); 81 | } 82 | 83 | removeAllClasses(...args: Parameters) { 84 | return this.call('removeAllClasses', args); 85 | } 86 | 87 | animate(...args: Parameters) { 88 | return this.call('animate', args); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/interpolation/simple.ts: -------------------------------------------------------------------------------- 1 | import type { SegmentData } from '../core/types'; 2 | import { SvgPath } from '../svg'; 3 | 4 | export interface SimpleInteractionOptions { 5 | divisor?: number; 6 | fillHoles?: boolean; 7 | } 8 | 9 | /** 10 | * Simple smoothing creates horizontal handles that are positioned with a fraction of the length between two data points. You can use the divisor option to specify the amount of smoothing. 11 | * 12 | * Simple smoothing can be used instead of `Chartist.Smoothing.cardinal` if you'd like to get rid of the artifacts it produces sometimes. Simple smoothing produces less flowing lines but is accurate by hitting the points and it also doesn't swing below or above the given data point. 13 | * 14 | * All smoothing functions within Chartist are factory functions that accept an options parameter. The simple interpolation function accepts one configuration parameter `divisor`, between 1 and ∞, which controls the smoothing characteristics. 15 | * 16 | * @example 17 | * ```ts 18 | * const chart = new LineChart('.ct-chart', { 19 | * labels: [1, 2, 3, 4, 5], 20 | * series: [[1, 2, 8, 1, 7]] 21 | * }, { 22 | * lineSmooth: Interpolation.simple({ 23 | * divisor: 2, 24 | * fillHoles: false 25 | * }) 26 | * }); 27 | * ``` 28 | * 29 | * @param options The options of the simple interpolation factory function. 30 | */ 31 | export function simple(options?: SimpleInteractionOptions) { 32 | const finalOptions = { 33 | divisor: 2, 34 | fillHoles: false, 35 | ...options 36 | }; 37 | 38 | const d = 1 / Math.max(1, finalOptions.divisor); 39 | 40 | return function simpleInterpolation( 41 | pathCoordinates: number[], 42 | valueData: SegmentData[] 43 | ) { 44 | const path = new SvgPath(); 45 | let prevX = 0; 46 | let prevY = 0; 47 | let prevData; 48 | 49 | for (let i = 0; i < pathCoordinates.length; i += 2) { 50 | const currX = pathCoordinates[i]; 51 | const currY = pathCoordinates[i + 1]; 52 | const length = (currX - prevX) * d; 53 | const currData = valueData[i / 2]; 54 | 55 | if (currData.value !== undefined) { 56 | if (prevData === undefined) { 57 | path.move(currX, currY, false, currData); 58 | } else { 59 | path.curve( 60 | prevX + length, 61 | prevY, 62 | currX - length, 63 | currY, 64 | currX, 65 | currY, 66 | false, 67 | currData 68 | ); 69 | } 70 | 71 | prevX = currX; 72 | prevY = currY; 73 | prevData = currData; 74 | } else if (!finalOptions.fillHoles) { 75 | prevX = prevY = 0; 76 | prevData = undefined; 77 | } 78 | } 79 | 80 | return path; 81 | }; 82 | } 83 | -------------------------------------------------------------------------------- /src/core/data/segments.spec.ts: -------------------------------------------------------------------------------- 1 | import { splitIntoSegments } from './segments'; 2 | 3 | describe('Core', () => { 4 | describe('Data', () => { 5 | describe('Segments', () => { 6 | function makeValues(arr: T[]) { 7 | return arr.map((x, i) => ({ value: x, valueIndex: i })); 8 | } 9 | 10 | it('should return empty array for empty input', () => { 11 | expect(splitIntoSegments([], [])).toEqual([]); 12 | }); 13 | 14 | it('should remove undefined values', () => { 15 | const coords = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]; 16 | const values = makeValues([1, undefined, undefined, 4, undefined, 6]); 17 | 18 | expect(splitIntoSegments(coords, values)).toEqual([ 19 | { 20 | pathCoordinates: [1, 2], 21 | valueData: [{ value: 1, valueIndex: 0 }] 22 | }, 23 | { 24 | pathCoordinates: [7, 8], 25 | valueData: [{ value: 4, valueIndex: 3 }] 26 | }, 27 | { 28 | pathCoordinates: [11, 12], 29 | valueData: [{ value: 6, valueIndex: 5 }] 30 | } 31 | ]); 32 | }); 33 | 34 | it('should respect fillHoles option', () => { 35 | const coords = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]; 36 | const values = makeValues([1, undefined, undefined, 4, undefined, 6]); 37 | const options = { 38 | fillHoles: true 39 | }; 40 | 41 | expect(splitIntoSegments(coords, values, options)).toEqual([ 42 | { 43 | pathCoordinates: [1, 2, 7, 8, 11, 12], 44 | valueData: [ 45 | { value: 1, valueIndex: 0 }, 46 | { value: 4, valueIndex: 3 }, 47 | { value: 6, valueIndex: 5 } 48 | ] 49 | } 50 | ]); 51 | }); 52 | 53 | it('should respect increasingX option', () => { 54 | const coords = [1, 2, 3, 4, 5, 6, 5, 6, 7, 8, 1, 2]; 55 | const values = makeValues([1, 2, 3, 4, 5, 6]); 56 | const options = { 57 | increasingX: true 58 | }; 59 | 60 | expect(splitIntoSegments(coords, values, options)).toEqual([ 61 | { 62 | pathCoordinates: [1, 2, 3, 4, 5, 6], 63 | valueData: [ 64 | { value: 1, valueIndex: 0 }, 65 | { value: 2, valueIndex: 1 }, 66 | { value: 3, valueIndex: 2 } 67 | ] 68 | }, 69 | { 70 | pathCoordinates: [5, 6, 7, 8], 71 | valueData: [ 72 | { value: 4, valueIndex: 3 }, 73 | { value: 5, valueIndex: 4 } 74 | ] 75 | }, 76 | { 77 | pathCoordinates: [1, 2], 78 | valueData: [{ value: 6, valueIndex: 5 }] 79 | } 80 | ]); 81 | }); 82 | }); 83 | }); 84 | }); 85 | -------------------------------------------------------------------------------- /src/interpolation/step.ts: -------------------------------------------------------------------------------- 1 | import type { SegmentData } from '../core'; 2 | import { SvgPath } from '../svg'; 3 | 4 | export interface StepInterpolationOptions { 5 | postpone?: boolean; 6 | fillHoles?: boolean; 7 | } 8 | 9 | /** 10 | * Step interpolation will cause the line chart to move in steps rather than diagonal or smoothed lines. This interpolation will create additional points that will also be drawn when the `showPoint` option is enabled. 11 | * 12 | * All smoothing functions within Chartist are factory functions that accept an options parameter. The step interpolation function accepts one configuration parameter `postpone`, that can be `true` or `false`. The default value is `true` and will cause the step to occur where the value actually changes. If a different behaviour is needed where the step is shifted to the left and happens before the actual value, this option can be set to `false`. 13 | * 14 | * @example 15 | * ```ts 16 | * const chart = new Chartist.Line('.ct-chart', { 17 | * labels: [1, 2, 3, 4, 5], 18 | * series: [[1, 2, 8, 1, 7]] 19 | * }, { 20 | * lineSmooth: Interpolation.step({ 21 | * postpone: true, 22 | * fillHoles: false 23 | * }) 24 | * }); 25 | * ``` 26 | */ 27 | export function step(options?: StepInterpolationOptions) { 28 | const finalOptions = { 29 | postpone: true, 30 | fillHoles: false, 31 | ...options 32 | }; 33 | 34 | return function stepInterpolation( 35 | pathCoordinates: number[], 36 | valueData: SegmentData[] 37 | ) { 38 | const path = new SvgPath(); 39 | 40 | let prevX = 0; 41 | let prevY = 0; 42 | let prevData; 43 | 44 | for (let i = 0; i < pathCoordinates.length; i += 2) { 45 | const currX = pathCoordinates[i]; 46 | const currY = pathCoordinates[i + 1]; 47 | const currData = valueData[i / 2]; 48 | 49 | // If the current point is also not a hole we can draw the step lines 50 | if (currData.value !== undefined) { 51 | if (prevData === undefined) { 52 | path.move(currX, currY, false, currData); 53 | } else { 54 | if (finalOptions.postpone) { 55 | // If postponed we should draw the step line with the value of the previous value 56 | path.line(currX, prevY, false, prevData); 57 | } else { 58 | // If not postponed we should draw the step line with the value of the current value 59 | path.line(prevX, currY, false, currData); 60 | } 61 | // Line to the actual point (this should only be a Y-Axis movement 62 | path.line(currX, currY, false, currData); 63 | } 64 | 65 | prevX = currX; 66 | prevY = currY; 67 | prevData = currData; 68 | } else if (!finalOptions.fillHoles) { 69 | prevX = prevY = 0; 70 | prevData = undefined; 71 | } 72 | } 73 | 74 | return path; 75 | }; 76 | } 77 | -------------------------------------------------------------------------------- /test/utils/storyshots/initStoryshots.js: -------------------------------------------------------------------------------- 1 | import baseInitStoryshots from '@storybook/addon-storyshots'; 2 | 3 | import { imageSnapshotWithStoryParameters } from './imageSnapshotWithStoryParameters'; 4 | import { startStorybook } from './storybook'; 5 | 6 | /** 7 | * Default page customizer. 8 | * @param page - Puppeteer's page instance. 9 | * @returns Task promise. 10 | */ 11 | export function defaultCustomizePage(page) { 12 | return page.setViewport({ 13 | width: 1920, 14 | height: 1080 15 | }); 16 | } 17 | 18 | /** 19 | * Prepare identifier for use in filename. 20 | * @param indentifierPart - Identifier string part. 21 | * @returns Sanitized identifier ready for use in filename. 22 | */ 23 | export function sanitizeSnapshotIdentifierPart(indentifierPart) { 24 | return indentifierPart.replace(/[\s/]|%20/g, '-').replace(/"|%22/g, ''); 25 | } 26 | 27 | /** 28 | * Default match options creator. 29 | * @param storyOptions - Story info. 30 | * @param storyOptions.context - Story context. 31 | * @param storyOptions.context.kind - Story kind. 32 | * @param storyOptions.context.story - Story name. 33 | * @param storyOptions.context.storyshots - Storyshots metadata. 34 | * @returns Match options. 35 | */ 36 | export function defaultGetMatchOptions({ 37 | context: { kind, story, storyshots } 38 | }) { 39 | const currentViewport = storyshots?.currentViewport; 40 | const sanitizedKind = sanitizeSnapshotIdentifierPart(kind); 41 | const sanitizedStory = sanitizeSnapshotIdentifierPart(story); 42 | const sanitizedParams = currentViewport 43 | ? `__${sanitizeSnapshotIdentifierPart(currentViewport)}` 44 | : ''; 45 | 46 | process.stdout.write(`📷 ${kind} ${story} ${currentViewport || ''}\n`); 47 | 48 | return { 49 | customSnapshotIdentifier: `${sanitizedKind}__${sanitizedStory}${sanitizedParams}` 50 | }; 51 | } 52 | 53 | /** 54 | * Initialize and run storyshots. 55 | * @param config - Storyshots config. 56 | */ 57 | export function initStoryshots(config) { 58 | process.env.STORYBOOK_STORYSHOTS = JSON.stringify(true); 59 | 60 | const finalOptions = { 61 | getMatchOptions: defaultGetMatchOptions, 62 | customizePage: defaultCustomizePage, 63 | ...config 64 | }; 65 | const storybook = startStorybook(config); 66 | const test = imageSnapshotWithStoryParameters({ 67 | storybookUrl: config.url, 68 | ...finalOptions 69 | }); 70 | const { beforeAll, afterAll } = test; 71 | const { warn } = console; 72 | 73 | test.beforeAll = async () => { 74 | await storybook.start(); 75 | await beforeAll(); 76 | }; 77 | test.beforeAll.timeout = beforeAll.timeout; 78 | 79 | test.afterAll = async () => { 80 | await storybook.stop(); 81 | await afterAll(); 82 | }; 83 | 84 | console.warn = () => undefined; 85 | baseInitStoryshots({ 86 | framework: 'html', 87 | suite: 'Storyshots', 88 | test 89 | }); 90 | console.warn = warn; 91 | } 92 | -------------------------------------------------------------------------------- /src/core/data/segments.ts: -------------------------------------------------------------------------------- 1 | import type { Segment, SegmentData } from '../types'; 2 | import { getMultiValue } from './data'; 3 | 4 | /** 5 | * Splits a list of coordinates and associated values into segments. Each returned segment contains a pathCoordinates 6 | * valueData property describing the segment. 7 | * 8 | * With the default options, segments consist of contiguous sets of points that do not have an undefined value. Any 9 | * points with undefined values are discarded. 10 | * 11 | * **Options** 12 | * The following options are used to determine how segments are formed 13 | * ```javascript 14 | * var options = { 15 | * // If fillHoles is true, undefined values are simply discarded without creating a new segment. Assuming other options are default, this returns single segment. 16 | * fillHoles: false, 17 | * // If increasingX is true, the coordinates in all segments have strictly increasing x-values. 18 | * increasingX: false 19 | * }; 20 | * ``` 21 | * 22 | * @param pathCoordinates List of point coordinates to be split in the form [x1, y1, x2, y2 ... xn, yn] 23 | * @param valueData List of associated point values in the form [v1, v2 .. vn] 24 | * @param options Options set by user 25 | * @return List of segments, each containing a pathCoordinates and valueData property. 26 | */ 27 | export function splitIntoSegments( 28 | pathCoordinates: number[], 29 | valueData: SegmentData[], 30 | options?: { 31 | increasingX?: boolean; 32 | fillHoles?: boolean; 33 | } 34 | ) { 35 | const finalOptions = { 36 | increasingX: false, 37 | fillHoles: false, 38 | ...options 39 | }; 40 | 41 | const segments: Segment[] = []; 42 | let hole = true; 43 | 44 | for (let i = 0; i < pathCoordinates.length; i += 2) { 45 | // If this value is a "hole" we set the hole flag 46 | if (getMultiValue(valueData[i / 2].value) === undefined) { 47 | // if(valueData[i / 2].value === undefined) { 48 | if (!finalOptions.fillHoles) { 49 | hole = true; 50 | } 51 | } else { 52 | if ( 53 | finalOptions.increasingX && 54 | i >= 2 && 55 | pathCoordinates[i] <= pathCoordinates[i - 2] 56 | ) { 57 | // X is not increasing, so we need to make sure we start a new segment 58 | hole = true; 59 | } 60 | 61 | // If it's a valid value we need to check if we're coming out of a hole and create a new empty segment 62 | if (hole) { 63 | segments.push({ 64 | pathCoordinates: [], 65 | valueData: [] 66 | }); 67 | // As we have a valid value now, we are not in a "hole" anymore 68 | hole = false; 69 | } 70 | 71 | // Add to the segment pathCoordinates and valueData 72 | segments[segments.length - 1].pathCoordinates.push( 73 | pathCoordinates[i], 74 | pathCoordinates[i + 1] 75 | ); 76 | segments[segments.length - 1].valueData.push(valueData[i / 2]); 77 | } 78 | } 79 | 80 | return segments; 81 | } 82 | -------------------------------------------------------------------------------- /src/event/EventEmitter.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | export type EventListener = (data: T) => void; 3 | 4 | export type AllEventsListener = (event: string, data: T) => void; 5 | 6 | export class EventEmitter { 7 | private readonly listeners = new Map>(); 8 | private readonly allListeners = new Set(); 9 | 10 | /** 11 | * Add an event handler for a specific event 12 | * @param event The event name 13 | * @param listener A event handler function 14 | */ 15 | on(event: '*', listener: AllEventsListener): void; 16 | on(event: string, listener: EventListener): void; 17 | on(event: string, listener: EventListener | AllEventsListener) { 18 | const { allListeners, listeners } = this; 19 | 20 | if (event === '*') { 21 | allListeners.add(listener); 22 | } else { 23 | if (!listeners.has(event)) { 24 | listeners.set(event, new Set()); 25 | } 26 | 27 | (listeners.get(event) as Set).add( 28 | listener as EventListener 29 | ); 30 | } 31 | } 32 | 33 | /** 34 | * Remove an event handler of a specific event name or remove all event handlers for a specific event. 35 | * @param event The event name where a specific or all handlers should be removed 36 | * @param [listener] An optional event handler function. If specified only this specific handler will be removed and otherwise all handlers are removed. 37 | */ 38 | off(event: '*', listener?: AllEventsListener): void; 39 | off(event: string, listener?: EventListener): void; 40 | off(event: string, listener?: EventListener | AllEventsListener) { 41 | const { allListeners, listeners } = this; 42 | 43 | if (event === '*') { 44 | if (listener) { 45 | allListeners.delete(listener); 46 | } else { 47 | allListeners.clear(); 48 | } 49 | } else if (listeners.has(event)) { 50 | const eventListeners = listeners.get(event) as Set; 51 | 52 | if (listener) { 53 | eventListeners.delete(listener as EventListener); 54 | } else { 55 | eventListeners.clear(); 56 | } 57 | 58 | if (!eventListeners.size) { 59 | listeners.delete(event); 60 | } 61 | } 62 | } 63 | 64 | /** 65 | * Use this function to emit an event. All handlers that are listening for this event will be triggered with the data parameter. 66 | * @param event The event name that should be triggered 67 | * @param data Arbitrary data that will be passed to the event handler callback functions 68 | */ 69 | emit(event: string, data: T) { 70 | const { allListeners, listeners } = this; 71 | 72 | // Only do something if there are event handlers with this name existing 73 | if (listeners.has(event)) { 74 | (listeners.get(event) as Set).forEach(listener => 75 | listener(data) 76 | ); 77 | } 78 | 79 | // Emit event to star event handlers 80 | allListeners.forEach(listener => listener(event, data)); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /test/utils/storyshots/imageSnapshotWithStoryParameters.js: -------------------------------------------------------------------------------- 1 | import { devices } from 'puppeteer'; 2 | import { imageSnapshot } from '@storybook/addon-storyshots-puppeteer'; 3 | 4 | import { Viewport } from './viewport'; 5 | 6 | const captureRoot = true; 7 | const offset = '40'; 8 | 9 | /** 10 | * Handle story parameters. 11 | * @param page - Page instance. 12 | * @param options - Story options. 13 | * @returns Promise. 14 | */ 15 | async function beforeScreenshotHook(page, options) { 16 | const { 17 | storyshots: { currentViewport } = {}, 18 | parameters: { storyshots: { beforeScreenshot } = {} } 19 | } = options.context; 20 | 21 | if (currentViewport && currentViewport !== Viewport.Default) { 22 | await page.emulate(devices[currentViewport]); 23 | } 24 | 25 | if (beforeScreenshot) { 26 | await beforeScreenshot(page, options); 27 | } 28 | 29 | if (captureRoot) { 30 | await page.$eval( 31 | '#root', 32 | (root, offset) => { 33 | root.style.padding = `${offset}px`; 34 | }, 35 | offset 36 | ); 37 | 38 | return page.$('#root'); 39 | } 40 | 41 | return null; 42 | } 43 | 44 | function getCaptureRootScreenshotOptions() { 45 | return { 46 | encoding: 'base64', // encoding: 'base64' is a property required by puppeteer 47 | fullPage: !captureRoot 48 | }; 49 | } 50 | 51 | /** 52 | * Create snapshot tests function with story parameters. 53 | * @param config - Snapshots config. 54 | * @returns Snapshot tests function. 55 | */ 56 | export function imageSnapshotWithStoryParameters(config) { 57 | const { beforeScreenshot, getScreenshotOptions } = config; 58 | const configWithBeforeScreenshot = { 59 | ...config, 60 | getScreenshotOptions: getScreenshotOptions 61 | ? options => ({ 62 | ...getCaptureRootScreenshotOptions(options), 63 | ...getScreenshotOptions(options) 64 | }) 65 | : getCaptureRootScreenshotOptions, 66 | beforeScreenshot: beforeScreenshot 67 | ? async (page, options) => { 68 | const captureTarget = await beforeScreenshotHook(page, options); 69 | 70 | await beforeScreenshot(page, options); 71 | 72 | return captureTarget; 73 | } 74 | : beforeScreenshotHook 75 | }; 76 | const test = imageSnapshot(configWithBeforeScreenshot); 77 | const testFn = async options => { 78 | const { context } = options; 79 | const { storyshots: { viewports = [] } = {} } = context.parameters; 80 | 81 | if (!viewports.length) { 82 | await test(options); 83 | return; 84 | } 85 | 86 | for (const viewport of viewports) { 87 | const currentViewport = 88 | viewport === Viewport.Default ? undefined : viewport; 89 | const originalId = context.id; 90 | 91 | context.storyshots = { 92 | currentViewport 93 | }; 94 | 95 | await test(options); 96 | 97 | context.id = originalId; 98 | } 99 | 100 | expect.assertions(viewports.length); 101 | }; 102 | 103 | testFn.timeout = test.timeout; 104 | testFn.beforeAll = test.beforeAll; 105 | testFn.afterAll = test.afterAll; 106 | 107 | return testFn; 108 | } 109 | -------------------------------------------------------------------------------- /src/core/math.ts: -------------------------------------------------------------------------------- 1 | import type { Bounds } from './types'; 2 | import { precision as globalPrecision } from './constants'; 3 | 4 | export const EPSILON = 2.221e-16; 5 | 6 | /** 7 | * Calculate the order of magnitude for the chart scale 8 | * @param value The value Range of the chart 9 | * @return The order of magnitude 10 | */ 11 | export function orderOfMagnitude(value: number) { 12 | return Math.floor(Math.log(Math.abs(value)) / Math.LN10); 13 | } 14 | 15 | /** 16 | * Project a data length into screen coordinates (pixels) 17 | * @param axisLength The svg element for the chart 18 | * @param length Single data value from a series array 19 | * @param bounds All the values to set the bounds of the chart 20 | * @return The projected data length in pixels 21 | */ 22 | export function projectLength( 23 | axisLength: number, 24 | length: number, 25 | bounds: Bounds 26 | ) { 27 | return (length / bounds.range) * axisLength; 28 | } 29 | 30 | /** 31 | * This helper function can be used to round values with certain precision level after decimal. This is used to prevent rounding errors near float point precision limit. 32 | * @param value The value that should be rounded with precision 33 | * @param [digits] The number of digits after decimal used to do the rounding 34 | * @returns Rounded value 35 | */ 36 | export function roundWithPrecision(value: number, digits?: number) { 37 | const precision = Math.pow(10, digits || globalPrecision); 38 | return Math.round(value * precision) / precision; 39 | } 40 | 41 | /** 42 | * Pollard Rho Algorithm to find smallest factor of an integer value. There are more efficient algorithms for factorization, but this one is quite efficient and not so complex. 43 | * @param num An integer number where the smallest factor should be searched for 44 | * @returns The smallest integer factor of the parameter num. 45 | */ 46 | export function rho(num: number) { 47 | if (num === 1) { 48 | return num; 49 | } 50 | 51 | function gcd(p: number, q: number): number { 52 | if (p % q === 0) { 53 | return q; 54 | } else { 55 | return gcd(q, p % q); 56 | } 57 | } 58 | 59 | function f(x: number) { 60 | return x * x + 1; 61 | } 62 | 63 | let x1 = 2; 64 | let x2 = 2; 65 | let divisor: number; 66 | 67 | if (num % 2 === 0) { 68 | return 2; 69 | } 70 | 71 | do { 72 | x1 = f(x1) % num; 73 | x2 = f(f(x2)) % num; 74 | divisor = gcd(Math.abs(x1 - x2), num); 75 | } while (divisor === 1); 76 | 77 | return divisor; 78 | } 79 | 80 | /** 81 | * Calculate cartesian coordinates of polar coordinates 82 | * @param centerX X-axis coordinates of center point of circle segment 83 | * @param centerY X-axis coordinates of center point of circle segment 84 | * @param radius Radius of circle segment 85 | * @param angleInDegrees Angle of circle segment in degrees 86 | * @return Coordinates of point on circumference 87 | */ 88 | export function polarToCartesian( 89 | centerX: number, 90 | centerY: number, 91 | radius: number, 92 | angleInDegrees: number 93 | ) { 94 | const angleInRadians = ((angleInDegrees - 90) * Math.PI) / 180.0; 95 | 96 | return { 97 | x: centerX + radius * Math.cos(angleInRadians), 98 | y: centerY + radius * Math.sin(angleInRadians) 99 | }; 100 | } 101 | -------------------------------------------------------------------------------- /website/docusaurus.config.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | const branch = require('git-branch'); 4 | const codeTheme = require('./src/prism-theme'); 5 | 6 | const currentBranch = process.env.BRANCH || branch.sync(); 7 | /** @type {import('@docusaurus/types').Config} */ 8 | const config = { 9 | title: 'Chartist', 10 | tagline: 'A simple responsive charting library built with SVG', 11 | url: 'https://chartist.dev', 12 | baseUrl: '/', 13 | onBrokenLinks: 'throw', 14 | onBrokenMarkdownLinks: 'warn', 15 | favicon: 'img/favicon.ico', 16 | trailingSlash: false, 17 | organizationName: 'chartist-js', 18 | projectName: 'chartist', 19 | noIndex: currentBranch !== 'main', 20 | 21 | customFields: { 22 | branch: currentBranch 23 | }, 24 | 25 | presets: [ 26 | [ 27 | '@docusaurus/preset-classic', 28 | /** @type {import('@docusaurus/preset-classic').Options} */ 29 | { 30 | docs: { 31 | routeBasePath: '/', 32 | sidebarPath: require.resolve('./sidebars.js'), 33 | editUrl: 'https://github.com/chartist-js/chartist/edit/main/website/' 34 | }, 35 | theme: { 36 | customCss: [ 37 | require.resolve('./src/css/custom.css'), 38 | require.resolve('./src/css/recoloring.css') 39 | ] 40 | } 41 | } 42 | ] 43 | ], 44 | 45 | themeConfig: 46 | /** @type {import('@docusaurus/preset-classic').ThemeConfig} */ 47 | { 48 | navbar: { 49 | title: 'Chartist', 50 | logo: { 51 | alt: 'Chartist logo', 52 | src: 'img/logo.svg' 53 | }, 54 | items: [ 55 | { 56 | type: 'doc', 57 | docId: 'api/basics', 58 | position: 'left', 59 | label: 'API' 60 | }, 61 | { 62 | type: 'doc', 63 | docId: 'examples/index', 64 | position: 'left', 65 | label: 'Examples' 66 | }, 67 | { 68 | type: 'doc', 69 | docId: 'plugins', 70 | position: 'left', 71 | label: 'Plugins' 72 | }, 73 | { 74 | href: 'https://stackoverflow.com/questions/tagged/chartist.js', 75 | label: 'Stack Overflow', 76 | position: 'right' 77 | }, 78 | { 79 | href: 'https://gitter.im/gionkunz/chartist-js', 80 | label: 'Gitter', 81 | position: 'right' 82 | }, 83 | { 84 | href: 'https://github.com/chartist-js/chartist/discussions', 85 | label: 'Discussions', 86 | position: 'right' 87 | }, 88 | { 89 | href: 'https://github.com/chartist-js/chartist', 90 | label: 'GitHub', 91 | position: 'right' 92 | } 93 | ] 94 | }, 95 | colorMode: { 96 | defaultMode: 'light', 97 | disableSwitch: true, 98 | respectPrefersColorScheme: false 99 | }, 100 | prism: { 101 | theme: codeTheme 102 | } 103 | // algolia: { 104 | // appId: '', 105 | // apiKey: '', 106 | // indexName: '' 107 | // } 108 | }, 109 | 110 | plugins: [ 111 | [ 112 | 'docusaurus-plugin-typedoc', 113 | 114 | // Plugin / TypeDoc options 115 | { 116 | entryPoints: ['../src/index.ts'], 117 | tsconfig: '../tsconfig.json', 118 | excludeExternals: true, 119 | readme: 'none', 120 | sort: ['source-order'] 121 | } 122 | ] 123 | ] 124 | }; 125 | 126 | module.exports = config; 127 | -------------------------------------------------------------------------------- /src/styles/_settings.scss: -------------------------------------------------------------------------------- 1 | @use "sass:math"; 2 | 3 | // Scales for responsive SVG containers 4 | $ct-scales: ((1), math.div(15, 16), math.div(8, 9), math.div(5, 6), math.div(4, 5), math.div(3, 4), math.div(2, 3), math.div(5, 8), math.div(1, 1.618), math.div(3, 5), math.div(9, 16), math.div(8, 15), math.div(1, 2), math.div(2, 5), math.div(3, 8), math.div(1, 3), math.div(1, 4)) !default; 5 | $ct-scales-names: (ct-square, ct-minor-second, ct-major-second, ct-minor-third, ct-major-third, ct-perfect-fourth, ct-perfect-fifth, ct-minor-sixth, ct-golden-section, ct-major-sixth, ct-minor-seventh, ct-major-seventh, ct-octave, ct-major-tenth, ct-major-eleventh, ct-major-twelfth, ct-double-octave) !default; 6 | 7 | // Class names to be used when generating CSS 8 | $ct-class-chart: ct-chart !default; 9 | $ct-class-chart-line: ct-chart-line !default; 10 | $ct-class-chart-bar: ct-chart-bar !default; 11 | $ct-class-horizontal-bars: ct-horizontal-bars !default; 12 | $ct-class-chart-pie: ct-chart-pie !default; 13 | $ct-class-chart-donut: ct-chart-donut !default; 14 | $ct-class-label: ct-label !default; 15 | $ct-class-series: ct-series !default; 16 | $ct-class-line: ct-line !default; 17 | $ct-class-point: ct-point !default; 18 | $ct-class-area: ct-area !default; 19 | $ct-class-bar: ct-bar !default; 20 | $ct-class-slice-pie: ct-slice-pie !default; 21 | $ct-class-slice-donut: ct-slice-donut !default; 22 | $ct-class-grid: ct-grid !default; 23 | $ct-class-grid-background: ct-grid-background !default; 24 | $ct-class-vertical: ct-vertical !default; 25 | $ct-class-horizontal: ct-horizontal !default; 26 | $ct-class-start: ct-start !default; 27 | $ct-class-end: ct-end !default; 28 | 29 | // Container ratio 30 | $ct-container-ratio: math.div(1, 1.618) !default; 31 | 32 | // Text styles for labels 33 | $ct-text-color: rgba(0, 0, 0, 0.4) !default; 34 | $ct-text-size: 0.75rem !default; 35 | $ct-text-align: flex-start !default; 36 | $ct-text-justify: flex-start !default; 37 | $ct-text-line-height: 1 !default; 38 | 39 | // Grid styles 40 | $ct-grid-color: rgba(0, 0, 0, 0.2) !default; 41 | $ct-grid-dasharray: 2px !default; 42 | $ct-grid-width: 1px !default; 43 | $ct-grid-background-fill: none !default; 44 | 45 | // Line chart properties 46 | $ct-line-width: 4px !default; 47 | $ct-line-dasharray: false !default; 48 | $ct-point-size: 10px !default; 49 | // Line chart point, can be either round or square 50 | $ct-point-shape: round !default; 51 | // Area fill transparency between 0 and 1 52 | $ct-area-opacity: 0.1 !default; 53 | 54 | // Bar chart bar width 55 | $ct-bar-width: 10px !default; 56 | 57 | // Donut width (If donut width is to big it can cause issues where the shape gets distorted) 58 | $ct-donut-width: 60px !default; 59 | 60 | // If set to true it will include the default classes and generate CSS output. If you're planning to use the mixins you 61 | // should set this property to false 62 | $ct-include-classes: true !default; 63 | 64 | // If this is set to true the CSS will contain colored series. You can extend or change the color with the 65 | // properties below 66 | $ct-include-colored-series: $ct-include-classes !default; 67 | 68 | // If set to true this will include all responsive container variations using the scales defined at the top of the script 69 | $ct-include-alternative-responsive-containers: $ct-include-classes !default; 70 | 71 | // Series names and colors. This can be extended or customized as desired. Just add more series and colors. 72 | $ct-series-names: (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o) !default; 73 | $ct-series-colors: ( 74 | #d70206, 75 | #f05b4f, 76 | #f4c63d, 77 | #d17905, 78 | #453d3f, 79 | #59922b, 80 | #0544d3, 81 | #6b0392, 82 | #e6805e, 83 | #dda458, 84 | #eacf7d, 85 | #86797d, 86 | #b2c326, 87 | #6188e2, 88 | #a748ca 89 | ) !default; 90 | -------------------------------------------------------------------------------- /src/core/data/highLow.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | Options, 3 | AxisName, 4 | NormalizedSeries, 5 | NormalizedSeriesValue 6 | } from '../types'; 7 | import { safeHasProperty } from '../../utils'; 8 | import { isDataHoleValue } from './data'; 9 | 10 | /** 11 | * Get highest and lowest value of data array. This Array contains the data that will be visualized in the chart. 12 | * @param data The array that contains the data to be visualized in the chart 13 | * @param options The Object that contains the chart options 14 | * @param dimension Axis dimension 'x' or 'y' used to access the correct value and high / low configuration 15 | * @return An object that contains the highest and lowest value that will be visualized on the chart. 16 | */ 17 | export function getHighLow( 18 | data: NormalizedSeries[], 19 | options: Options, 20 | dimension?: AxisName 21 | ) { 22 | // TODO: Remove workaround for deprecated global high / low config. Axis high / low configuration is preferred 23 | options = { 24 | ...options, 25 | ...(dimension ? (dimension === 'x' ? options.axisX : options.axisY) : {}) 26 | }; 27 | 28 | const highLow = { 29 | high: options.high === undefined ? -Number.MAX_VALUE : +options.high, 30 | low: options.low === undefined ? Number.MAX_VALUE : +options.low 31 | }; 32 | const findHigh = options.high === undefined; 33 | const findLow = options.low === undefined; 34 | 35 | // Function to recursively walk through arrays and find highest and lowest number 36 | function recursiveHighLow( 37 | sourceData: NormalizedSeriesValue | NormalizedSeries | NormalizedSeries[] 38 | ) { 39 | if (isDataHoleValue(sourceData)) { 40 | return; 41 | } else if (Array.isArray(sourceData)) { 42 | for (let i = 0; i < sourceData.length; i++) { 43 | recursiveHighLow(sourceData[i]); 44 | } 45 | } else { 46 | const value = Number( 47 | dimension && safeHasProperty(sourceData, dimension) 48 | ? sourceData[dimension] 49 | : sourceData 50 | ); 51 | 52 | if (findHigh && value > highLow.high) { 53 | highLow.high = value; 54 | } 55 | 56 | if (findLow && value < highLow.low) { 57 | highLow.low = value; 58 | } 59 | } 60 | } 61 | 62 | // Start to find highest and lowest number recursively 63 | if (findHigh || findLow) { 64 | recursiveHighLow(data); 65 | } 66 | 67 | // Overrides of high / low based on reference value, it will make sure that the invisible reference value is 68 | // used to generate the chart. This is useful when the chart always needs to contain the position of the 69 | // invisible reference value in the view i.e. for bipolar scales. 70 | if (options.referenceValue || options.referenceValue === 0) { 71 | highLow.high = Math.max(options.referenceValue, highLow.high); 72 | highLow.low = Math.min(options.referenceValue, highLow.low); 73 | } 74 | 75 | // If high and low are the same because of misconfiguration or flat data (only the same value) we need 76 | // to set the high or low to 0 depending on the polarity 77 | if (highLow.high <= highLow.low) { 78 | // If both values are 0 we set high to 1 79 | if (highLow.low === 0) { 80 | highLow.high = 1; 81 | } else if (highLow.low < 0) { 82 | // If we have the same negative value for the bounds we set bounds.high to 0 83 | highLow.high = 0; 84 | } else if (highLow.high > 0) { 85 | // If we have the same positive value for the bounds we set bounds.low to 0 86 | highLow.low = 0; 87 | } else { 88 | // If data array was empty, values are Number.MAX_VALUE and -Number.MAX_VALUE. Set bounds to prevent errors 89 | highLow.high = 1; 90 | highLow.low = 0; 91 | } 92 | } 93 | 94 | return highLow; 95 | } 96 | -------------------------------------------------------------------------------- /src/charts/PieChart/PieChart.stories.ts: -------------------------------------------------------------------------------- 1 | import 'chartist-dev/styles'; 2 | import { PieChart } from 'chartist-dev'; 3 | 4 | export default { 5 | title: 'PieChart', 6 | argTypes: {} 7 | }; 8 | 9 | export function Default() { 10 | const root = document.createElement('div'); 11 | 12 | new PieChart( 13 | root, 14 | { 15 | series: [5, 3, 4] 16 | }, 17 | { 18 | width: 100, 19 | height: 100, 20 | chartPadding: 10 21 | } 22 | ); 23 | 24 | return root; 25 | } 26 | 27 | export function Labels() { 28 | const root = document.createElement('div'); 29 | 30 | new PieChart( 31 | root, 32 | { 33 | labels: ['A', 'B', 'C'], 34 | series: [5, 8, 1] 35 | }, 36 | {} 37 | ); 38 | 39 | return root; 40 | } 41 | 42 | export function LabelInterpolation() { 43 | const root = document.createElement('div'); 44 | const data = { 45 | series: [5, 3, 4] 46 | }; 47 | const sum = (a: number, b: number) => a + b; 48 | 49 | new PieChart(root, data, { 50 | width: 100, 51 | height: 100, 52 | chartPadding: 10, 53 | labelInterpolationFnc: value => 54 | `${Math.round((Number(value) / data.series.reduce(sum)) * 100)}%` 55 | }); 56 | 57 | return root; 58 | } 59 | 60 | export function StartAngle() { 61 | const root = document.createElement('div'); 62 | 63 | new PieChart( 64 | root, 65 | { 66 | series: [5, 3, 4] 67 | }, 68 | { 69 | startAngle: 90 70 | } 71 | ); 72 | 73 | return root; 74 | } 75 | 76 | export function SmallSlices() { 77 | const root = document.createElement('div'); 78 | 79 | new PieChart( 80 | root, 81 | { 82 | series: [0.001, 2] 83 | }, 84 | { 85 | width: 100, 86 | height: 100, 87 | chartPadding: 0 88 | } 89 | ); 90 | 91 | return root; 92 | } 93 | 94 | export function IgnoreEmptyValues() { 95 | const root = document.createElement('div'); 96 | 97 | new PieChart( 98 | root, 99 | { 100 | series: [1, 2, 0, 4] 101 | }, 102 | { 103 | ignoreEmptyValues: true 104 | } 105 | ); 106 | 107 | return root; 108 | } 109 | 110 | export function Donut() { 111 | const root = document.createElement('div'); 112 | 113 | new PieChart( 114 | root, 115 | { 116 | series: [5, 3, 4] 117 | }, 118 | { 119 | donut: true 120 | } 121 | ); 122 | 123 | return root; 124 | } 125 | 126 | export function GaugeDonut() { 127 | const root = document.createElement('div'); 128 | 129 | new PieChart( 130 | root, 131 | { 132 | series: [20, 10, 30, 40] 133 | }, 134 | { 135 | chartPadding: 50, 136 | height: 500, 137 | width: 500, 138 | donut: true, 139 | donutWidth: 60, 140 | startAngle: 270, 141 | total: 200, 142 | showLabel: false 143 | } 144 | ); 145 | 146 | return root; 147 | } 148 | 149 | export function RelativeDonutWidth() { 150 | const root = document.createElement('div'); 151 | 152 | new PieChart( 153 | root, 154 | { 155 | series: [20, 10, 30, 40] 156 | }, 157 | { 158 | chartPadding: 50, 159 | height: 500, 160 | width: 500, 161 | donut: true, 162 | donutWidth: '25%', 163 | showLabel: false 164 | } 165 | ); 166 | 167 | return root; 168 | } 169 | 170 | export function Solid() { 171 | const root = document.createElement('div'); 172 | 173 | new PieChart( 174 | root, 175 | { 176 | series: [20, 10, 30, 40] 177 | }, 178 | { 179 | donut: true, 180 | donutWidth: 60, 181 | // donutSolid: true, 182 | startAngle: 270, 183 | showLabel: true 184 | } 185 | ); 186 | 187 | return root; 188 | } 189 | -------------------------------------------------------------------------------- /website/docs/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | slug: / 3 | description: A simple responsive charting library built with SVG 4 | --- 5 | 6 | import Tabs from '@theme/Tabs'; 7 | import TabItem from '@theme/TabItem'; 8 | import logoUrl from '@site/static/img/chartist-guy.gif'; 9 | 10 | # Big welcome by the Chartist Guy 11 | 12 | [![NPM version][npm]][npm-url] 13 | [![Downloads][downloads]][downloads-url] 14 | [![Build status][build]][build-url] 15 | [![Coverage status][coverage]][coverage-url] 16 | [![Bundle size][size]][size-url] 17 | [![Join the chat at https://gitter.im/gionkunz/chartist-js][chat]][chat-url] 18 | 19 | [npm]: https://img.shields.io/npm/v/chartist.svg 20 | [npm-url]: https://www.npmjs.com/package/chartist 21 | 22 | [downloads]: https://img.shields.io/npm/dm/chartist.svg 23 | [downloads-url]: https://www.npmjs.com/package/chartist 24 | 25 | [build]: https://img.shields.io/github/workflow/status/chartist-js/chartist/CI.svg 26 | [build-url]: https://github.com/chartist-js/chartist/actions 27 | 28 | [coverage]: https://img.shields.io/codecov/c/github/chartist-js/chartist.svg 29 | [coverage-url]: https://app.codecov.io/gh/chartist-js/chartist 30 | 31 | [size]: https://img.shields.io/bundlephobia/minzip/chartist 32 | [size-url]: https://bundlephobia.com/package/chartist 33 | 34 | [chat]: https://badges.gitter.im/gionkunz/chartist-js.svg 35 | [chat-url]: https://gitter.im/gionkunz/chartist-js?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge 36 | 37 |

38 | The Chartist Guy 39 |

40 | 41 | Chartist is a simple responsive charting library built with SVG. There are hundreds of nice charting libraries already 42 | out there, but they are either: 43 | 44 | - use the wrong technologies for illustration (canvas) 45 | - weighs hundreds of kilobytes 46 | - are not flexible enough while keeping the configuration simple 47 | - are not friendly to designers 48 | - more annoying things 49 | 50 | That's why we started Chartist and our goal is to solve all of the above issues. 51 | 52 | ## Quickstart 53 | 54 | Install this library using your favorite package manager: 55 | 56 | 57 | 58 | 59 | ```bash 60 | pnpm add chartist 61 | ``` 62 | 63 | 64 | 65 | 66 | ```bash 67 | yarn add chartist 68 | ``` 69 | 70 | 71 | 72 | 73 | ```bash 74 | npm install --save chartist 75 | ``` 76 | 77 | 78 | 79 | 80 | Then, just import chart you want and use it: 81 | 82 | ```js 83 | import { BarChart } from 'chartist'; 84 | 85 | new BarChart('#chart', { 86 | labels: ['W1', 'W2', 'W3', 'W4', 'W5', 'W6', 'W7', 'W8', 'W9', 'W10'], 87 | series: [ 88 | [1, 2, 4, 8, 6, -2, -1, -4, -6, -2] 89 | ] 90 | }, { 91 | high: 10, 92 | low: -10, 93 | axisX: { 94 | labelInterpolationFnc: (value, index) => (index % 2 === 0 ? value : null) 95 | } 96 | }); 97 | ``` 98 | 99 | :::tip Need an API to fetch data? 100 | Please consider [Cube](https://cube.dev/?ref=eco-chartist), an open-source API for data apps. 101 | ::: 102 | 103 | [![supported by Cube](https://user-images.githubusercontent.com/986756/154330861-d79ab8ec-aacb-4af8-9e17-1b28f1eccb01.svg)](https://cube.dev/?ref=eco-chartist) 104 | 105 | ## Examples 106 | 107 | Please see [live examples](/examples). 108 | 109 | ## Getting Help 110 | 111 | Need help? Ask your question on [Gitter](https://gitter.im/gionkunz/chartist-js), [GitHub Discussions](https://github.com/chartist-js/chartist/discussions) or [Stack Overflow](https://stackoverflow.com/questions/tagged/chartist.js). 112 | 113 | If you've encountered an issue, please [file it on GitHub](https://github.com/chartist-js/chartist/issues). 114 | -------------------------------------------------------------------------------- /src/charts/BarChart/BarChart.types.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | Options, 3 | AxisOptions, 4 | Data, 5 | CreatedEvent, 6 | DrawEvent, 7 | NormalizedMulti, 8 | AxesDrawEvent 9 | } from '../../core'; 10 | import type { RequiredKeys } from '../../utils'; 11 | import type { BaseChartEventsTypes } from '../types'; 12 | 13 | export type BarChartData = Data; 14 | 15 | export interface BarChartOptions< 16 | TXAxisOptions = AxisOptions, 17 | TYAxisOptions = TXAxisOptions 18 | > extends Options { 19 | /** 20 | * Override the class names that get used to generate the SVG structure of the chart 21 | */ 22 | classNames?: { 23 | chart?: string; 24 | horizontalBars?: string; 25 | label?: string; 26 | labelGroup?: string; 27 | series?: string; 28 | bar?: string; 29 | grid?: string; 30 | gridGroup?: string; 31 | gridBackground?: string; 32 | vertical?: string; 33 | horizontal?: string; 34 | start?: string; 35 | end?: string; 36 | }; 37 | /** 38 | * Specify the distance in pixel of bars in a group 39 | */ 40 | seriesBarDistance?: number; 41 | /** 42 | * If set to true this property will cause the series bars to be stacked. Check the `stackMode` option for further stacking options. 43 | */ 44 | stackBars?: boolean; 45 | /** 46 | * If set to true this property will force the stacked bars to draw from the zero line. 47 | * If set to 'accumulate' this property will form a total for each series point. This will also influence the y-axis and the overall bounds of the chart. In stacked mode the seriesBarDistance property will have no effect. 48 | * If set to 'accumulate-relative' positive and negative values will be handled separately. 49 | */ 50 | stackMode?: 'accumulate' | 'accumulate-relative' | boolean; 51 | /** 52 | * Inverts the axes of the bar chart in order to draw a horizontal bar chart. Be aware that you also need to invert your axis settings as the Y Axis will now display the labels and the X Axis the values. 53 | */ 54 | horizontalBars?: boolean; 55 | /** 56 | * If set to true then each bar will represent a series and the data array is expected to be a one dimensional array of data values rather than a series array of series. This is useful if the bar chart should represent a profile rather than some data over time. 57 | */ 58 | distributeSeries?: boolean; 59 | /** 60 | * If true the whole data is reversed including labels, the series order as well as the whole series data arrays. 61 | */ 62 | reverseData?: boolean; 63 | /** 64 | * If the bar chart should add a background fill to the .ct-grids group. 65 | */ 66 | showGridBackground?: boolean; 67 | } 68 | 69 | export type BarChartOptionsWithDefaults = RequiredKeys< 70 | BarChartOptions< 71 | RequiredKeys< 72 | AxisOptions, 73 | | 'offset' 74 | | 'position' 75 | | 'labelOffset' 76 | | 'showLabel' 77 | | 'showGrid' 78 | | 'labelInterpolationFnc' 79 | | 'scaleMinSpace' 80 | >, 81 | RequiredKeys< 82 | AxisOptions, 83 | | 'offset' 84 | | 'position' 85 | | 'labelOffset' 86 | | 'showLabel' 87 | | 'showGrid' 88 | | 'labelInterpolationFnc' 89 | | 'scaleMinSpace' 90 | > 91 | >, 92 | | 'referenceValue' 93 | | 'chartPadding' 94 | | 'seriesBarDistance' 95 | | 'stackMode' 96 | | 'axisX' 97 | | 'axisY', 98 | 'classNames' 99 | >; 100 | 101 | export type BarChartCreatedEvent = CreatedEvent; 102 | 103 | export interface BarDrawEvent extends DrawEvent { 104 | type: 'bar'; 105 | value: number | NormalizedMulti; 106 | x1: number; 107 | y1: number; 108 | x2: number; 109 | y2: number; 110 | } 111 | 112 | export type BarChartEventsTypes = BaseChartEventsTypes< 113 | BarChartCreatedEvent, 114 | AxesDrawEvent | BarDrawEvent 115 | >; 116 | -------------------------------------------------------------------------------- /src/axes/Axis.spec.ts: -------------------------------------------------------------------------------- 1 | import type { ChartRect } from '../core'; 2 | import { Svg } from '../svg'; 3 | import { EventEmitter } from '../event'; 4 | import { Axis, axisUnits } from './Axis'; 5 | 6 | class MockAxis extends Axis { 7 | projectValue(value: number) { 8 | return value; 9 | } 10 | } 11 | 12 | describe('Axes', () => { 13 | describe('Axis', () => { 14 | let ticks: number[]; 15 | let chartRect: ChartRect; 16 | let chartOptions: any; 17 | let eventEmitter: EventEmitter; 18 | let gridGroup: Svg; 19 | let labelGroup: Svg; 20 | 21 | beforeEach(() => { 22 | eventEmitter = new EventEmitter(); 23 | gridGroup = new Svg('g'); 24 | labelGroup = new Svg('g'); 25 | ticks = [1, 2]; 26 | chartRect = { 27 | padding: { 28 | bottom: 5, 29 | left: 10, 30 | right: 15, 31 | top: 15 32 | }, 33 | y2: 15, 34 | y1: 250, 35 | x1: 50, 36 | x2: 450, 37 | width() { 38 | return this.x2 - this.x1; 39 | }, 40 | height() { 41 | return this.y1 - this.y2; 42 | } 43 | }; 44 | 45 | chartOptions = { 46 | axisX: { 47 | offset: 30, 48 | position: 'end', 49 | labelOffset: { 50 | x: 0, 51 | y: 0 52 | }, 53 | showLabel: true, 54 | showGrid: true 55 | }, 56 | classNames: { 57 | label: 'ct-label', 58 | labelGroup: 'ct-labels', 59 | grid: 'ct-grid', 60 | gridGroup: 'ct-grids', 61 | vertical: 'ct-vertical', 62 | horizontal: 'ct-horizontal', 63 | start: 'ct-start', 64 | end: 'ct-end' 65 | } 66 | }; 67 | }); 68 | 69 | it('should skip all grid lines and labels for interpolated value of null', () => { 70 | chartOptions.axisX.labelInterpolationFnc = ( 71 | value: number, 72 | index: number 73 | ) => (index === 0 ? null : value); 74 | 75 | const axis = new MockAxis(axisUnits.x, chartRect, ticks); 76 | 77 | axis.createGridAndLabels( 78 | gridGroup, 79 | labelGroup, 80 | chartOptions, 81 | eventEmitter 82 | ); 83 | expect( 84 | (gridGroup.querySelectorAll('.ct-grid') as any).svgElements.length 85 | ).toBe(1); 86 | expect( 87 | (labelGroup.querySelectorAll('.ct-label') as any).svgElements.length 88 | ).toBe(1); 89 | }); 90 | 91 | it('should skip all grid lines and labels for interpolated value of undefined', () => { 92 | chartOptions.axisX.labelInterpolationFnc = ( 93 | value: number, 94 | index: number 95 | ) => (index === 0 ? undefined : value); 96 | 97 | const axis = new MockAxis(axisUnits.x, chartRect, ticks); 98 | 99 | axis.createGridAndLabels( 100 | gridGroup, 101 | labelGroup, 102 | chartOptions, 103 | eventEmitter 104 | ); 105 | expect( 106 | (gridGroup.querySelectorAll('.ct-grid') as any).svgElements.length 107 | ).toBe(1); 108 | expect( 109 | (labelGroup.querySelectorAll('.ct-label') as any).svgElements.length 110 | ).toBe(1); 111 | }); 112 | 113 | it('should include all grid lines and labels for interpolated value of empty strings', () => { 114 | chartOptions.axisX.labelInterpolationFnc = ( 115 | value: number, 116 | index: number 117 | ) => (index === 0 ? '' : value); 118 | 119 | const axis = new MockAxis(axisUnits.x, chartRect, ticks); 120 | 121 | axis.createGridAndLabels( 122 | gridGroup, 123 | labelGroup, 124 | chartOptions, 125 | eventEmitter 126 | ); 127 | expect( 128 | (gridGroup.querySelectorAll('.ct-grid') as any).svgElements.length 129 | ).toBe(2); 130 | expect( 131 | (labelGroup.querySelectorAll('.ct-label') as any).svgElements.length 132 | ).toBe(2); 133 | }); 134 | }); 135 | }); 136 | -------------------------------------------------------------------------------- /website/src/css/recoloring.css: -------------------------------------------------------------------------------- 1 | /* palette */ 2 | :root { 3 | --brown-dark: #5B4421; 4 | --red: #D70205; 5 | --red_light: #F05B4F; 6 | --white-dirty: #F7F2E9; 7 | --white: #FFFFFF; 8 | --brown-light: #e3d8c3; 9 | --brown-lighter: #DFCFBB; 10 | --brown-ligher_a08: #f7f2e980; 11 | --brown-light_a02: rgba(68, 61, 63, 0.2); 12 | --brown-darken: #443D3F; 13 | --yellow: #f4c63e; 14 | 15 | --dark: #363133; 16 | --dark-01: #2e2929; 17 | --dark_03_a01: rgba(247, 242, 233, 0.1); 18 | --white_a02: rgba(255, 255, 255, 0.2); 19 | } 20 | 21 | 22 | :root[data-theme='light'] { 23 | --accent: var(--red); 24 | 25 | --text-primary: var(--brown-dark); 26 | --text-link: var(--red); 27 | --text_header: var(--white-dirty); 28 | 29 | --bg_main: var(--brown-light); 30 | --bg-highlithed: var(--brown-ligher_a08); 31 | --bg-header: var(--brown-darken); 32 | --bg_menu: var(--brown-lighter); 33 | 34 | --separator: var(--brown-light_a02); 35 | } 36 | 37 | :root[data-theme='dark'] { 38 | --accent: var(--red_light); 39 | 40 | --text-primary: var(--white); 41 | --text-link: var(--red_light); 42 | 43 | --separator: var(--white_a02); 44 | 45 | --text_header: var(--white-dirty); 46 | 47 | --bg_main: var(--dark-01); 48 | --bg-highlithed: var(--dark_03_a01); 49 | --bg-header: var(--brown-darken); 50 | --bg_menu: var(--dark); 51 | } 52 | 53 | html { 54 | background-color: var(--bg_main) !important; 55 | color: var(--text-primary) !important; 56 | } 57 | 58 | a { 59 | color: var(--text-link); 60 | } 61 | 62 | a:hover { 63 | color: var(--text-link); 64 | } 65 | 66 | /* nav */ 67 | 68 | .navbar { 69 | background-color: var(--bg-header); 70 | color: var(--text_header); 71 | } 72 | 73 | .navbar a { 74 | color: var(--white); 75 | } 76 | 77 | .menu__list-item a { 78 | color: var(--text-primary); 79 | } 80 | 81 | .navbar .navbar__link:hover, 82 | .navbar .navbar__brand:hover { 83 | color: var(--yellow); 84 | } 85 | 86 | .menu { 87 | background-color: var(--bg_menu); 88 | } 89 | 90 | .menu__link { 91 | color: var(--text-primary); 92 | } 93 | 94 | .menu__link:hover { 95 | color: var(--text-link); 96 | background-color: initial; 97 | } 98 | 99 | .menu__link.menu__link--active { 100 | background-color: var(--bg-highlithed); 101 | } 102 | 103 | .table-of-contents { 104 | border-left: 3px solid var(--separator); 105 | } 106 | 107 | .table-of-contents__link { 108 | color: inherit; 109 | } 110 | 111 | .table-of-contents__link:hover { 112 | color: var(--text-link); 113 | } 114 | 115 | .hash-link { 116 | text-decoration: none; 117 | } 118 | 119 | .hash-link:hover { 120 | text-decoration: underline currentColor; 121 | } 122 | 123 | .pagination-nav__link { 124 | border-color: var(--separator); 125 | transition: background-color 0.3s; 126 | } 127 | 128 | .pagination-nav__link:hover { 129 | border-color: var(--separator); 130 | background-color: var(--bg-highlithed); 131 | } 132 | 133 | .pagination-nav__sublabel { 134 | color: var(--text-primary); 135 | } 136 | 137 | .alert { 138 | background-color: var(--bg-highlithed); 139 | border-color: var(--accent); 140 | color: var(--text-primary); 141 | } 142 | 143 | .alert a { 144 | text-decoration-color: currentColor; 145 | } 146 | 147 | .tabs__item--active { 148 | color: var(--text-link); 149 | border-color: var(--accent); 150 | } 151 | 152 | .tabs__item { 153 | color: var(--text-primary); 154 | } 155 | 156 | .admonition-icon svg { 157 | stroke: var(--accent); 158 | fill: var(--accent); 159 | } 160 | 161 | hr { 162 | border-color: var(--separator); 163 | } 164 | 165 | table th, table td, table thead tr { 166 | border-color: var(--separator); 167 | } 168 | 169 | code { 170 | background-color: var(--bg-highlithed); 171 | border-color: var(--separator); 172 | } 173 | 174 | table tr:nth-child(2n) { 175 | background-color: var(--bg_menu); 176 | } 177 | 178 | .navbar__logo { 179 | background-color: var(--yellow); 180 | padding: 3px; 181 | height: calc(2em + 6px); 182 | border-radius: 50%; 183 | box-sizing: border-box; 184 | } 185 | 186 | .navbar-sidebar__back { 187 | background-color: var(--bg-highlithed); 188 | color: var(--text-primary); 189 | } 190 | 191 | .navbar-sidebar__brand { 192 | background-color: var(--dark-01); 193 | } 194 | --------------------------------------------------------------------------------