├── .githooks
└── pre-commit
├── .github
├── stale.yml
└── workflows
│ ├── build.yml
│ ├── pr.yml
│ └── release.yml
├── .gitignore
├── .storybook
├── main.js
├── manager.js
├── preview.js
└── theme.js
├── CHANGELOG.json
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── LICENSE
├── README.md
├── SECURITY.md
├── SUPPORT.md
├── eslint.config.js
├── jest.config.js
├── just.config.ts
├── lint-staged.config.js
├── package.json
├── pretter.config.js
├── scripts
├── DeleteBlob.ps1
├── UpdateBlobProperties.ps1
├── prepare.js
└── tasks
│ └── storybook.ts
├── src
├── chart
│ ├── chart-legend.tsx
│ ├── chart-render.tsx
│ ├── chart.tsx
│ └── index.ts
├── index.ts
├── lib
│ ├── builder.ts
│ ├── datasets.ts
│ ├── patterns.ts
│ ├── plugins.ts
│ ├── settings.ts
│ ├── storybook.tsx
│ └── utils.ts
└── types
│ ├── index.ts
│ └── types.ts
├── stories
├── area.stories.tsx
├── bar.stories.tsx
├── components
│ ├── container.tsx
│ └── index.ts
├── docs
│ ├── introduction.stories.mdx
│ └── line.stories.mdx
├── doughnut.stories.tsx
├── grouped-bar.stories.tsx
├── horizontal-bar.stories.tsx
├── line.stories.tsx
├── pie.stories.tsx
├── stacked-bar.stories.tsx
├── stacked-line.stories.tsx
├── trend-line.stories.tsx
└── utils
│ ├── index.ts
│ └── utils.ts
├── tsconfig.json
└── yarn.lock
/.githooks/pre-commit:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const fs = require('fs');
4 | const path = require('path');
5 |
6 | if (!fs.existsSync(path.resolve(__dirname, '../node_modules/lint-staged'))) {
7 | console.error('lint-staged is not installed. Please run `yarn` then try again.');
8 | process.exit(1);
9 | } else if (!fs.existsSync(path.resolve(__dirname, '../node_modules/lint-staged/bin/lint-staged.js'))) {
10 | // If someone updates from master but doesn't run yarn, by default they'd be unable to commit
11 | // with some cryptic error. Explicitly detect that condition and show a helpful error.
12 | console.error('lint-staged version is out of date! Please run `yarn` to update dependencies.');
13 | process.exit(1);
14 | }
15 |
16 | require('lint-staged/bin/lint-staged.js');
17 |
--------------------------------------------------------------------------------
/.github/stale.yml:
--------------------------------------------------------------------------------
1 | # Number of days of inactivity before an issue becomes stale
2 | daysUntilStale: 15
3 | # Number of days of inactivity before a stale issue is closed
4 | daysUntilClose: 7
5 | # Issues with these labels will never be considered stale
6 | exemptLabels:
7 | - pinned
8 | - security
9 | # Label to use when marking an issue as stale
10 | staleLabel: wontfix
11 | # Comment to post when marking an issue as stale. Set to `false` to disable
12 | markComment: >
13 | This issue has been automatically marked as stale because it has not had
14 | recent activity. It will be closed if no further activity occurs. Thank you
15 | for your contributions.
16 | # Comment to post when closing a stale issue. Set to `false` to disable
17 | closeComment: false
18 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build & Host
2 |
3 | on: push
4 | defaults:
5 | run:
6 | shell: bash # Run everything using bash
7 |
8 | jobs:
9 | build:
10 | name: Build
11 | runs-on: macos-latest
12 | steps:
13 | - name: Clone repository
14 | uses: actions/checkout@v2
15 |
16 | - name: Setup NodeJS 12.x
17 | uses: actions/setup-node@v1
18 | with:
19 | node-version: "12.x"
20 |
21 | - name: Set up build environment
22 | run: |
23 | yarn
24 | - name: Build app
25 | run: |
26 | yarn build:storybook
27 | - name: Deploy
28 | uses: peaceiris/actions-gh-pages@v3
29 | with:
30 | github_token: ${{ secrets.GITHUB_TOKEN }}
31 | publish_dir: ./dist-storybook
32 |
--------------------------------------------------------------------------------
/.github/workflows/pr.yml:
--------------------------------------------------------------------------------
1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
3 |
4 | name: PR
5 |
6 | on:
7 | pull_request:
8 | branches: [main]
9 |
10 | jobs:
11 | build:
12 | runs-on: ubuntu-latest
13 |
14 | strategy:
15 | matrix:
16 | node-version: [12.x]
17 |
18 | steps:
19 | - uses: actions/checkout@v2
20 | - name: Use Node.js ${{ matrix.node-version }}
21 | uses: actions/setup-node@v1
22 | with:
23 | node-version: ${{ matrix.node-version }}
24 | - run: yarn
25 | - run: yarn lint
26 | - run: yarn checkchange
27 | - run: yarn build
28 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
3 |
4 | name: Release
5 |
6 | on:
7 | workflow_dispatch:
8 | push:
9 | branches: [main]
10 |
11 | jobs:
12 | build:
13 | runs-on: ubuntu-latest
14 |
15 | strategy:
16 | matrix:
17 | node-version: [12.x]
18 |
19 | steps:
20 | - uses: actions/checkout@v2
21 | with:
22 | token: ${{ secrets.REPO_PAT }}
23 | - name: Use Node.js ${{ matrix.node-version }}
24 | uses: actions/setup-node@v1
25 | with:
26 | node-version: ${{ matrix.node-version }}
27 | - run: yarn
28 | - run: yarn build
29 | - run: |
30 | git config user.email "teamsappstudio@microsoft.com"
31 | git config user.name "Automation"
32 | - run: yarn release -y -n $NPM_TOKEN
33 | env:
34 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
35 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Mono auto generated files
17 | mono_crash.*
18 |
19 | # Build results
20 | [Dd]ebug/
21 | [Dd]ebugPublic/
22 | [Rr]elease/
23 | [Rr]eleases/
24 | x64/
25 | x86/
26 | [Aa][Rr][Mm]/
27 | [Aa][Rr][Mm]64/
28 | bld/
29 | [Bb]in/
30 | [Oo]bj/
31 | [Ll]og/
32 | [Ll]ogs/
33 |
34 | # Visual Studio 2015/2017 cache/options directory
35 | .vs/
36 | # Uncomment if you have tasks that create the project's static files in wwwroot
37 | #wwwroot/
38 |
39 | # Visual Studio 2017 auto generated files
40 | Generated\ Files/
41 |
42 | # MSTest test Results
43 | [Tt]est[Rr]esult*/
44 | [Bb]uild[Ll]og.*
45 |
46 | # NUnit
47 | *.VisualState.xml
48 | TestResult.xml
49 | nunit-*.xml
50 |
51 | # Build Results of an ATL Project
52 | [Dd]ebugPS/
53 | [Rr]eleasePS/
54 | dlldata.c
55 |
56 | # Benchmark Results
57 | BenchmarkDotNet.Artifacts/
58 |
59 | # .NET Core
60 | project.lock.json
61 | project.fragment.lock.json
62 | artifacts/
63 |
64 | # StyleCop
65 | StyleCopReport.xml
66 |
67 | # Files built by Visual Studio
68 | *_i.c
69 | *_p.c
70 | *_h.h
71 | *.ilk
72 | *.meta
73 | *.obj
74 | *.iobj
75 | *.pch
76 | *.pdb
77 | *.ipdb
78 | *.pgc
79 | *.pgd
80 | *.rsp
81 | *.sbr
82 | *.tlb
83 | *.tli
84 | *.tlh
85 | *.tmp
86 | *.tmp_proj
87 | *_wpftmp.csproj
88 | *.log
89 | *.vspscc
90 | *.vssscc
91 | .builds
92 | *.pidb
93 | *.svclog
94 | *.scc
95 |
96 | # Chutzpah Test files
97 | _Chutzpah*
98 |
99 | # Visual C++ cache files
100 | ipch/
101 | *.aps
102 | *.ncb
103 | *.opendb
104 | *.opensdf
105 | *.sdf
106 | *.cachefile
107 | *.VC.db
108 | *.VC.VC.opendb
109 |
110 | # Visual Studio profiler
111 | *.psess
112 | *.vsp
113 | *.vspx
114 | *.sap
115 |
116 | # Visual Studio Trace Files
117 | *.e2e
118 |
119 | # TFS 2012 Local Workspace
120 | $tf/
121 |
122 | # Guidance Automation Toolkit
123 | *.gpState
124 |
125 | # ReSharper is a .NET coding add-in
126 | _ReSharper*/
127 | *.[Rr]e[Ss]harper
128 | *.DotSettings.user
129 |
130 | # TeamCity is a build add-in
131 | _TeamCity*
132 |
133 | # DotCover is a Code Coverage Tool
134 | *.dotCover
135 |
136 | # AxoCover is a Code Coverage Tool
137 | .axoCover/*
138 | !.axoCover/settings.json
139 |
140 | # Visual Studio code coverage results
141 | *.coverage
142 | *.coveragexml
143 |
144 | # NCrunch
145 | _NCrunch_*
146 | .*crunch*.local.xml
147 | nCrunchTemp_*
148 |
149 | # MightyMoose
150 | *.mm.*
151 | AutoTest.Net/
152 |
153 | # Web workbench (sass)
154 | .sass-cache/
155 |
156 | # Installshield output folder
157 | [Ee]xpress/
158 |
159 | # DocProject is a documentation generator add-in
160 | DocProject/buildhelp/
161 | DocProject/Help/*.HxT
162 | DocProject/Help/*.HxC
163 | DocProject/Help/*.hhc
164 | DocProject/Help/*.hhk
165 | DocProject/Help/*.hhp
166 | DocProject/Help/Html2
167 | DocProject/Help/html
168 |
169 | # Click-Once directory
170 | publish/
171 |
172 | # Publish Web Output
173 | *.[Pp]ublish.xml
174 | *.azurePubxml
175 | # Note: Comment the next line if you want to checkin your web deploy settings,
176 | # but database connection strings (with potential passwords) will be unencrypted
177 | *.pubxml
178 | *.publishproj
179 |
180 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
181 | # checkin your Azure Web App publish settings, but sensitive information contained
182 | # in these scripts will be unencrypted
183 | PublishScripts/
184 |
185 | # NuGet Packages
186 | *.nupkg
187 | # NuGet Symbol Packages
188 | *.snupkg
189 | # The packages folder can be ignored because of Package Restore
190 | **/[Pp]ackages/*
191 | # except build/, which is used as an MSBuild target.
192 | !**/[Pp]ackages/build/
193 | # Uncomment if necessary however generally it will be regenerated when needed
194 | #!**/[Pp]ackages/repositories.config
195 | # NuGet v3's project.json files produces more ignorable files
196 | *.nuget.props
197 | *.nuget.targets
198 |
199 | # Microsoft Azure Build Output
200 | csx/
201 | *.build.csdef
202 |
203 | # Microsoft Azure Emulator
204 | ecf/
205 | rcf/
206 |
207 | # Windows Store app package directories and files
208 | AppPackages/
209 | BundleArtifacts/
210 | Package.StoreAssociation.xml
211 | _pkginfo.txt
212 | *.appx
213 | *.appxbundle
214 | *.appxupload
215 |
216 | # Visual Studio cache files
217 | # files ending in .cache can be ignored
218 | *.[Cc]ache
219 | # but keep track of directories ending in .cache
220 | !?*.[Cc]ache/
221 |
222 | # Others
223 | ClientBin/
224 | ~$*
225 | *~
226 | *.dbmdl
227 | *.dbproj.schemaview
228 | *.jfm
229 | *.pfx
230 | *.publishsettings
231 | orleans.codegen.cs
232 |
233 | # Including strong name files can present a security risk
234 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
235 | #*.snk
236 |
237 | # Since there are multiple workflows, uncomment next line to ignore bower_components
238 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
239 | #bower_components/
240 |
241 | # RIA/Silverlight projects
242 | Generated_Code/
243 |
244 | # Backup & report files from converting an old project file
245 | # to a newer Visual Studio version. Backup files are not needed,
246 | # because we have git ;-)
247 | _UpgradeReport_Files/
248 | Backup*/
249 | UpgradeLog*.XML
250 | UpgradeLog*.htm
251 | ServiceFabricBackup/
252 | *.rptproj.bak
253 |
254 | # SQL Server files
255 | *.mdf
256 | *.ldf
257 | *.ndf
258 |
259 | # Business Intelligence projects
260 | *.rdl.data
261 | *.bim.layout
262 | *.bim_*.settings
263 | *.rptproj.rsuser
264 | *- [Bb]ackup.rdl
265 | *- [Bb]ackup ([0-9]).rdl
266 | *- [Bb]ackup ([0-9][0-9]).rdl
267 |
268 | # Microsoft Fakes
269 | FakesAssemblies/
270 |
271 | # GhostDoc plugin setting file
272 | *.GhostDoc.xml
273 |
274 | # Node.js Tools for Visual Studio
275 | .ntvs_analysis.dat
276 | node_modules/
277 |
278 | # Visual Studio 6 build log
279 | *.plg
280 |
281 | # Visual Studio 6 workspace options file
282 | *.opt
283 |
284 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
285 | *.vbw
286 |
287 | # Visual Studio LightSwitch build output
288 | **/*.HTMLClient/GeneratedArtifacts
289 | **/*.DesktopClient/GeneratedArtifacts
290 | **/*.DesktopClient/ModelManifest.xml
291 | **/*.Server/GeneratedArtifacts
292 | **/*.Server/ModelManifest.xml
293 | _Pvt_Extensions
294 |
295 | # Paket dependency manager
296 | .paket/paket.exe
297 | paket-files/
298 |
299 | # FAKE - F# Make
300 | .fake/
301 |
302 | # CodeRush personal settings
303 | .cr/personal
304 |
305 | # Python Tools for Visual Studio (PTVS)
306 | __pycache__/
307 | *.pyc
308 |
309 | # Cake - Uncomment if you are using it
310 | # tools/**
311 | # !tools/packages.config
312 |
313 | # Tabs Studio
314 | *.tss
315 |
316 | # Telerik's JustMock configuration file
317 | *.jmconfig
318 |
319 | # BizTalk build output
320 | *.btp.cs
321 | *.btm.cs
322 | *.odx.cs
323 | *.xsd.cs
324 |
325 | # OpenCover UI analysis results
326 | OpenCover/
327 |
328 | # Azure Stream Analytics local run output
329 | ASALocalRun/
330 |
331 | # MSBuild Binary and Structured Log
332 | *.binlog
333 |
334 | # NVidia Nsight GPU debugger configuration file
335 | *.nvuser
336 |
337 | # MFractors (Xamarin productivity tool) working folder
338 | .mfractor/
339 |
340 | # Local History for Visual Studio
341 | .localhistory/
342 |
343 | # BeatPulse healthcheck temp database
344 | healthchecksdb
345 |
346 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
347 | MigrationBackup/
348 |
349 | # Ionide (cross platform F# VS Code tools) working folder
350 | .ionide/
351 |
352 |
353 | # Built JS files
354 | /lib
355 | dist-storybook
--------------------------------------------------------------------------------
/.storybook/main.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 | const tsconfig = path.resolve(__dirname, "../tsconfig.json");
3 |
4 | module.exports = {
5 | stories: [
6 | "../stories/**/*.stories.+(ts|tsx)",
7 | "../stories/docs/*.stories.mdx",
8 | ],
9 | addons: [
10 | // "@storybook/addon-actions",
11 | "@storybook/addon-links",
12 | "@storybook/addon-docs",
13 | "@storybook/addon-viewport/register",
14 | "@storybook/addon-a11y/register",
15 | // "@storybook/addon-knobs/register",
16 | ],
17 | typescript: {
18 | check: true,
19 | checkOptions: { tsconfig },
20 | reactDocgen: "react-docgen-typescript",
21 | reactDocgenTypescriptOptions: {
22 | shouldExtractLiteralValuesFromEnum: true,
23 | propFilter: (prop) =>
24 | prop.parent ? !/node_modules/.test(prop.parent.fileName) : true,
25 | },
26 | },
27 | };
28 |
--------------------------------------------------------------------------------
/.storybook/manager.js:
--------------------------------------------------------------------------------
1 | import { addons } from "@storybook/addons";
2 | import TeamsPlatformUITheme from "./theme";
3 |
4 | addons.setConfig({
5 | theme: TeamsPlatformUITheme,
6 | });
7 |
--------------------------------------------------------------------------------
/.storybook/preview.js:
--------------------------------------------------------------------------------
1 | // import { withKnobs } from "@storybook/addon-knobs";
2 | import { withA11y } from "@storybook/addon-a11y";
3 | import { withStorybookTheme } from "../src/lib/storybook";
4 |
5 | export const parameters = {
6 | options: {
7 | storySort: {
8 | order: [
9 | "Charts",
10 | [
11 | "Line",
12 | "Area",
13 | "Stacked line",
14 | "Bar",
15 | "Stacked bar",
16 | "Grouped bar",
17 | "Horizontal bar",
18 | "Doughnut",
19 | "Pie",
20 | "Bubble",
21 | ],
22 | ],
23 | },
24 | },
25 | // Remove an additional padding in canvas body (Added in v.6)
26 | layout: "fullscreen",
27 | docs: {
28 | page: null,
29 | inlineStories: false,
30 | },
31 | };
32 |
33 | export const decorators = [withA11y, withStorybookTheme];
34 |
--------------------------------------------------------------------------------
/.storybook/theme.js:
--------------------------------------------------------------------------------
1 | import { create } from "@storybook/theming/create";
2 |
3 | export default create({
4 | base: "light",
5 | brandTitle: "@fluentui/react-charts",
6 |
7 | colorPrimary: "#6264A7",
8 | colorSecondary: "#616161",
9 |
10 | // UI
11 | appBg: "#EBEBEB",
12 | appContentBg: "rgb(245, 245, 245)",
13 | appBorderColor: "#E0E0E0",
14 | appBorderRadius: 4,
15 |
16 | // Typography
17 | fontBase: `"Segoe UI", Segoe UI, system-ui, "Apple Color Emoji", "Segoe UI Emoji", sans-serif`,
18 | fontCode: "monospace",
19 |
20 | // Text colors
21 | textColor: "#242424",
22 | textInverseColor: "rgba(255,255,255,0.9)",
23 | textMutedColor: "red",
24 |
25 | // Toolbar default and active colors
26 | barTextColor: "#616161",
27 | barSelectedColor: "#6264A7",
28 | barBg: "#F5F5F5",
29 |
30 | // Form colors
31 | inputBg: "white",
32 | inputBorder: "#E0E0E0",
33 | inputTextColor: "#242424",
34 | inputBorderRadius: 4,
35 | });
36 |
--------------------------------------------------------------------------------
/CHANGELOG.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@fluentui/react-charts",
3 | "entries": [
4 | {
5 | "date": "Wed, 05 May 2021 23:09:27 GMT",
6 | "tag": "@fluentui/react-charts_v1.2.0",
7 | "version": "1.2.0",
8 | "comments": {
9 | "minor": [
10 | {
11 | "comment": "Trend Chart",
12 | "author": "olkatruk@microsoft.com",
13 | "commit": "43b5a032b5b07bd059415800dcd6e6a4c25e822f",
14 | "package": "@fluentui/react-charts"
15 | }
16 | ]
17 | }
18 | },
19 | {
20 | "date": "Tue, 27 Apr 2021 01:22:02 GMT",
21 | "tag": "@fluentui/react-charts_v1.1.0",
22 | "version": "1.1.0",
23 | "comments": {
24 | "minor": [
25 | {
26 | "comment": "Merge branch 'main' of github.com:OfficeDev/microsoft-data-visualization-library into legend",
27 | "author": "olkatruk@microsoft.com",
28 | "commit": "256d81637e1c24bffb0e35e3f9b65b4cd36fbc55",
29 | "package": "@fluentui/react-charts"
30 | }
31 | ]
32 | }
33 | },
34 | {
35 | "date": "Wed, 14 Apr 2021 23:04:35 GMT",
36 | "tag": "@fluentui/react-charts_v1.0.1",
37 | "version": "1.0.1",
38 | "comments": {
39 | "patch": [
40 | {
41 | "comment": "Publish 1.0.0 version of react-charts",
42 | "author": "nirsh@microsoft.com",
43 | "commit": "2002004c1249ab454c097a9cead14aa3a23b3311",
44 | "package": "@fluentui/react-charts"
45 | }
46 | ]
47 | }
48 | },
49 | {
50 | "date": "Wed, 14 Apr 2021 22:25:50 GMT",
51 | "tag": "@fluentui/react-charts_v0.1.0-alpha.3",
52 | "version": "0.1.0-alpha.3",
53 | "comments": {
54 | "prerelease": [
55 | {
56 | "comment": "Verify package publishing flow",
57 | "author": "nirsh@microsoft.com",
58 | "commit": "2a4abd15ef2a0fbf74bf64787be2fcc75f033cd8",
59 | "package": "@fluentui/react-charts"
60 | }
61 | ]
62 | }
63 | }
64 | ]
65 | }
66 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log - @fluentui/react-charts
2 |
3 | This log was last generated on Wed, 05 May 2021 23:09:27 GMT and should not be manually modified.
4 |
5 |
6 |
7 | ## 1.2.0
8 |
9 | Wed, 05 May 2021 23:09:27 GMT
10 |
11 | ### Minor changes
12 |
13 | - Trend Chart (olkatruk@microsoft.com)
14 |
15 | ## 1.1.0
16 |
17 | Tue, 27 Apr 2021 01:22:02 GMT
18 |
19 | ### Minor changes
20 |
21 | - Merge branch 'main' of github.com:OfficeDev/microsoft-data-visualization-library into legend (olkatruk@microsoft.com)
22 |
23 | ## 1.0.1
24 |
25 | Wed, 14 Apr 2021 23:04:35 GMT
26 |
27 | ### Patches
28 |
29 | - Publish 1.0.0 version of react-charts (nirsh@microsoft.com)
30 |
31 | ## 0.1.0-alpha.3
32 |
33 | Wed, 14 Apr 2021 22:25:50 GMT
34 |
35 | ### Changes
36 |
37 | - Verify package publishing flow (nirsh@microsoft.com)
38 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Microsoft Open Source Code of Conduct
2 |
3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
4 |
5 | Resources:
6 |
7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/)
8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/)
9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns
10 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) Microsoft Corporation.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Project
2 |
3 | > This repo has been populated by an initial template to help get you started. Please
4 | > make sure to update the content to build a great experience for community-building.
5 |
6 | As the maintainer of this project, please make a few updates:
7 |
8 | - Improving this README.MD file to provide a great experience
9 | - Updating SUPPORT.MD with content about this project's support experience
10 | - Understanding the security reporting process in SECURITY.MD
11 | - Remove this section from the README
12 |
13 | ## Contributing
14 |
15 | This project welcomes contributions and suggestions. Most contributions require you to agree to a
16 | Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
17 | the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com.
18 |
19 | When you submit a pull request, a CLA bot will automatically determine whether you need to provide
20 | a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions
21 | provided by the bot. You will only need to do this once across all repos using our CLA.
22 |
23 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
24 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
25 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
26 |
27 | ## Trademarks
28 |
29 | This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft
30 | trademarks or logos is subject to and must follow
31 | [Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general).
32 | Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship.
33 | Any use of third-party trademarks or logos are subject to those third-party's policies.
34 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## Security
4 |
5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/).
6 |
7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)), please report it to us as described below.
8 |
9 | ## Reporting Security Issues
10 |
11 | **Please do not report security vulnerabilities through public GitHub issues.**
12 |
13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report).
14 |
15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc).
16 |
17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc).
18 |
19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
20 |
21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
22 | * Full paths of source file(s) related to the manifestation of the issue
23 | * The location of the affected source code (tag/branch/commit or direct URL)
24 | * Any special configuration required to reproduce the issue
25 | * Step-by-step instructions to reproduce the issue
26 | * Proof-of-concept or exploit code (if possible)
27 | * Impact of the issue, including how an attacker might exploit the issue
28 |
29 | This information will help us triage your report more quickly.
30 |
31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs.
32 |
33 | ## Preferred Languages
34 |
35 | We prefer all communications to be in English.
36 |
37 | ## Policy
38 |
39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd).
40 |
41 |
--------------------------------------------------------------------------------
/SUPPORT.md:
--------------------------------------------------------------------------------
1 | # TODO: The maintainer of this repo has not yet edited this file
2 |
3 | **REPO OWNER**: Do you want Customer Service & Support (CSS) support for this product/project?
4 |
5 | - **No CSS support:** Fill out this template with information about how to file issues and get help.
6 | - **Yes CSS support:** Fill out an intake form at [aka.ms/spot](https://aka.ms/spot). CSS will work with/help you to determine next steps. More details also available at [aka.ms/onboardsupport](https://aka.ms/onboardsupport).
7 | - **Not sure?** Fill out a SPOT intake as though the answer were "Yes". CSS will help you decide.
8 |
9 | *Then remove this first heading from this SUPPORT.MD file before publishing your repo.*
10 |
11 | # Support
12 |
13 | ## How to file issues and get help
14 |
15 | This project uses GitHub Issues to track bugs and feature requests. Please search the existing
16 | issues before filing new issues to avoid duplicates. For new issues, file your bug or
17 | feature request as a new Issue.
18 |
19 | For help and questions about using this project, please **REPO MAINTAINER: INSERT INSTRUCTIONS HERE
20 | FOR HOW TO ENGAGE REPO OWNERS OR COMMUNITY FOR HELP. COULD BE A STACK OVERFLOW TAG OR OTHER
21 | CHANNEL. WHERE WILL YOU HELP PEOPLE?**.
22 |
23 | ## Microsoft Support Policy
24 |
25 | Support for this **PROJECT or PRODUCT** is limited to the resources listed above.
26 |
--------------------------------------------------------------------------------
/eslint.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: ["airbnb", "airbnb/hooks"],
3 | };
4 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | preset: "ts-jest",
3 | testEnvironment: "jsdom",
4 | };
5 |
--------------------------------------------------------------------------------
/just.config.ts:
--------------------------------------------------------------------------------
1 | import * as util from "util";
2 | import _rimraf from "rimraf";
3 | import { task, series, jestTask, tscTask, eslintTask } from "just-scripts";
4 | import {
5 | startStorybookTask,
6 | buildStorybookTask,
7 | } from "./scripts/tasks/storybook";
8 |
9 | const rimraf = util.promisify(_rimraf as any);
10 |
11 | task("clean", () => rimraf("lib"));
12 | task("build:tsc", tscTask());
13 | task("build", series("clean", "build:tsc"));
14 |
15 | task("test", jestTask());
16 | task("start", startStorybookTask());
17 | task("build:storybook", buildStorybookTask());
18 | task("lint", eslintTask());
19 |
--------------------------------------------------------------------------------
/lint-staged.config.js:
--------------------------------------------------------------------------------
1 | // @ts-check
2 |
3 | // https://www.npmjs.com/package/lint-staged
4 | module.exports = {
5 | // Run eslint in fix mode followed by prettier
6 | [`*.{ts,tsx}`]: ["yarn prettier --write"],
7 | };
8 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@fluentui/react-charts",
3 | "version": "1.2.0",
4 | "description": "Charts in the Fluent UI design language written in React",
5 | "repository": "https://github.com/OfficeDev/microsoft-data-visualization-library.git",
6 | "license": "MIT",
7 | "main": "lib/index.js",
8 | "scripts": {
9 | "build": "just-scripts build",
10 | "build:storybook": "just-scripts build:storybook",
11 | "change": "beachball change --branch origin/main",
12 | "checkchange": "beachball check --branch origin/main",
13 | "prepare": "node ./scripts/prepare",
14 | "lint": "just-scripts lint",
15 | "release": "beachball publish --branch origin/main",
16 | "start": "just-scripts start",
17 | "test": "just-scripts test"
18 | },
19 | "dependencies": {
20 | "chart.js": "^2.9.4",
21 | "chartjs-plugin-deferred": "^1.0.2",
22 | "react-perfect-scrollbar": "^1.5.8"
23 | },
24 | "peerDependencies": {
25 | "react": "^16.8.0",
26 | "react-dom": "^16.8.0"
27 | },
28 | "devDependencies": {
29 | "@babel/core": "^7.10.5",
30 | "@storybook/addon-a11y": "^5.3.19",
31 | "@storybook/addon-actions": "^6.1.21",
32 | "@storybook/addon-docs": "^6.1.21",
33 | "@storybook/addon-knobs": "^6.1.21",
34 | "@storybook/addon-links": "^6.0.26",
35 | "@storybook/addon-viewport": "^5.3.19",
36 | "@storybook/addons": "^6.1.11",
37 | "@storybook/cli": "^6.0.26",
38 | "@storybook/preset-typescript": "^3.0.0",
39 | "@storybook/react": "^6.0.26",
40 | "@storybook/theming": "^6.1.11",
41 | "@types/chart.js": "^2.9.31",
42 | "@types/classnames": "^2.2.10",
43 | "@types/faker": "^5.1.2",
44 | "@types/jest": "^26.0.4",
45 | "@types/lodash": "^4.14.158",
46 | "@types/react": "^16.8.25",
47 | "@types/react-beautiful-dnd": "^13.0.0",
48 | "@types/react-dom": "^16.8.4",
49 | "@typescript-eslint/eslint-plugin": "3.6.1",
50 | "babel-loader": "^8.1.0",
51 | "beachball": "^1.47.0",
52 | "chalk": "^4.1.0",
53 | "eslint": "7.2.0",
54 | "eslint-config-airbnb": "18.2.0",
55 | "eslint-plugin-import": "2.21.2",
56 | "eslint-plugin-jsx-a11y": "6.3.0",
57 | "eslint-plugin-react": "7.20.0",
58 | "eslint-plugin-react-hooks": "4.0.0",
59 | "faker": "thure/faker.js#v5.1.2",
60 | "jest": "^26.1.0",
61 | "jest-expect-message": "^1.0.2",
62 | "just-plop-helpers": "^1.1.2",
63 | "just-scripts": "^0.44.1",
64 | "lint-staged": "^10.2.11",
65 | "prettier": "^2.0.5",
66 | "react": "^16.8.0",
67 | "react-docgen-typescript": "^1.18.0",
68 | "react-dom": "^16.8.0",
69 | "rimraf": "^3.0.2",
70 | "storybook": "^5.3.19",
71 | "style-loader": "^1.2.1",
72 | "ts-jest": "^26.1.3",
73 | "ts-loader": "^8.0.1",
74 | "ts-node": "^7.0.0",
75 | "tslib": "^2.0.0",
76 | "typescript": "^3.9.7",
77 | "webpack": "~4.43.0"
78 | },
79 | "files": [
80 | "lib"
81 | ],
82 | "publishConfig": {
83 | "access": "public"
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/pretter.config.js:
--------------------------------------------------------------------------------
1 | // https://prettier.io/docs/en/configuration.html
2 | module.exports = {
3 | printWidth: 120,
4 | tabWidth: 2,
5 | singleQuote: true,
6 | trailingComma: "all",
7 | overrides: [
8 | {
9 | // These files may be run as-is in IE 11 and must not have ES5-incompatible trailing commas
10 | files: ["*.html", "*.htm"],
11 | options: {
12 | trailingComma: "es5",
13 | },
14 | },
15 | ],
16 | };
17 |
--------------------------------------------------------------------------------
/scripts/DeleteBlob.ps1:
--------------------------------------------------------------------------------
1 | #---------------------------------------------------------------------------------
2 | # Delete Blob
3 | #---------------------------------------------------------------------------------
4 |
5 | #requires -Version 3.0
6 |
7 | <#
8 | .SYNOPSIS
9 | This script can be used to change the content type of multiple/single file/s. .
10 | .DESCRIPTION
11 | This script is designed to change the content type of multiple/single file/s located in an Azure container
12 | .PARAMETER ContainerName
13 | Specifies the name of container.
14 | .PARAMETER StorageAccountName
15 | Specifies the name of the storage account to be connected.
16 | .PARAMETER ResourceGroupName
17 | Specifies the name of the resource group to be connected.
18 | .EXAMPLE
19 | C:\PS> C:\Script\UpdateContentTypes.ps1 -ResourceGroupName "ResourceGroup01" -StorageAccountName "AzureStorage01" -ContainerName "pics"
20 |
21 | #>
22 |
23 | Param
24 | (
25 | [Parameter(Mandatory = $true)]
26 | [Alias('CN')]
27 | [String]$ContainerName,
28 | [Parameter(Mandatory = $true)]
29 | [Alias('SN')]
30 | [String]$StorageAccountName,
31 | [Parameter(Mandatory = $true)]
32 | [Alias('RGN')]
33 | [String]$ResourceGroupName,
34 | [Parameter(Mandatory = $true)]
35 | [Alias('Name')]
36 | [String]$BlobName
37 | )
38 |
39 | #Check if Windows Azure PowerShell Module is avaliable
40 | If ((Get-Module -ListAvailable Azure) -eq $null) {
41 | Write-Warning "Windows Azure PowerShell module not found! Please install from http://www.windowsazure.com/en-us/downloads/#cmd-line-tools"
42 | }
43 | Else {
44 | If ($ResourceGroupName -and $StorageAccountName) {
45 | Get-AzureRmStorageAccount -ResourceGroupName $ResourceGroupName -StorageAccountName $StorageAccountName -ErrorAction SilentlyContinue `
46 | -ErrorVariable IsExistStorageError | Out-Null
47 | #Check if storage account is exist
48 | If ($IsExistStorageError.Exception -eq $null) {
49 | #Getting Azure storage account key
50 | $Keys = Get-AzureRmStorageAccountKey -ResourceGroupName $ResourceGroupName -StorageAccountName $StorageAccountName
51 | $StorageAccountKey = $Keys.Value[0]
52 | }
53 | Else {
54 | Write-Error "Cannot find storage account '$StorageAccountName' in resource group '$ResourceGroupName' because it does not exist. Please make sure thar the name of storage is correct."
55 | }
56 | }
57 |
58 | If ($ContainerName) {
59 | $StorageContext = New-AzureStorageContext -StorageAccountName $StorageAccountName -StorageAccountKey $StorageAccountKey
60 | Get-AzureStorageContainer -Context $StorageContext -Name $ContainerName -ErrorAction SilentlyContinue `
61 | -ErrorVariable IsExistContainerError | Out-Null
62 | #Check if container is exist
63 | If ($IsExistContainerError.Exception -eq $null) {
64 | Write-Warning "Deleting blob with prefix '$BlobName'"
65 | Get-AzureStorageBlob -Context $StorageContext -Container $ContainerName -Prefix $BlobName -ContinuationToken $Token | Remove-AzureStorageBlob -Force -WhatIf
66 | }
67 | Else {
68 | Write-Warning "Cannot find container '$ContainerName' because it does not exist. Please make sure thar the name of container is correct."
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/scripts/UpdateBlobProperties.ps1:
--------------------------------------------------------------------------------
1 | #---------------------------------------------------------------------------------
2 | # Update Blob Cache Control and Content Type
3 | #---------------------------------------------------------------------------------
4 |
5 | #requires -Version 3.0
6 |
7 | <#
8 | .SYNOPSIS
9 | This script can be used to change the content type of multiple/single file/s. .
10 | .DESCRIPTION
11 | This script is designed to change the content type of multiple/single file/s located in an Azure container
12 | .PARAMETER ContainerName
13 | Specifies the name of container.
14 | .PARAMETER StorageAccountName
15 | Specifies the name of the storage account to be connected.
16 | .PARAMETER ResourceGroupName
17 | Specifies the name of the resource group to be connected.
18 | .EXAMPLE
19 | C:\PS> C:\Script\UpdateContentTypes.ps1 -ResourceGroupName "ResourceGroup01" -StorageAccountName "AzureStorage01" -ContainerName "pics"
20 |
21 | #>
22 |
23 | Param
24 | (
25 | [Parameter(Mandatory = $true)]
26 | [Alias('CN')]
27 | [String]$ContainerName,
28 | [Parameter(Mandatory = $true)]
29 | [Alias('SN')]
30 | [String]$StorageAccountName,
31 | [Parameter(Mandatory = $true)]
32 | [Alias('RGN')]
33 | [String]$ResourceGroupName
34 | )
35 |
36 | #Check if Windows Azure PowerShell Module is avaliable
37 | If((Get-Module -ListAvailable Azure) -eq $null)
38 | {
39 | Write-Warning "Windows Azure PowerShell module not found! Please install from http://www.windowsazure.com/en-us/downloads/#cmd-line-tools"
40 | }
41 | Else
42 | {
43 | If($ResourceGroupName -and $StorageAccountName) {
44 | Get-AzureRmStorageAccount -ResourceGroupName $ResourceGroupName -StorageAccountName $StorageAccountName -ErrorAction SilentlyContinue `
45 | -ErrorVariable IsExistStorageError | Out-Null
46 | #Check if storage account is exist
47 | If($IsExistStorageError.Exception -eq $null) {
48 | #Getting Azure storage account key
49 | $Keys = Get-AzureRmStorageAccountKey -ResourceGroupName $ResourceGroupName -StorageAccountName $StorageAccountName
50 | $StorageAccountKey = $Keys.Value[0]
51 | } Else {
52 | Write-Error "Cannot find storage account '$StorageAccountName' in resource group '$ResourceGroupName' because it does not exist. Please make sure thar the name of storage is correct."
53 | }
54 | }
55 |
56 | If($ContainerName) {
57 | $StorageContext = New-AzureStorageContext -StorageAccountName $StorageAccountName -StorageAccountKey $StorageAccountKey
58 | Get-AzureStorageContainer -Context $StorageContext -Name $ContainerName -ErrorAction SilentlyContinue `
59 | -ErrorVariable IsExistContainerError | Out-Null
60 | #Check if container is exist
61 | If($IsExistContainerError.Exception -eq $null)
62 | {
63 | $Total = 0
64 | $Token = $NULL
65 | #Results are grouped in 10 000 (this number works very well with azure)
66 | $MaxReturn = 10000
67 | do
68 | {
69 | $BlobItems = Get-AzureStorageBlob -Context $StorageContext -Container $ContainerName -MaxCount $MaxReturn -ContinuationToken $Token
70 | Foreach($BlobItem in $BlobItems)
71 | {
72 | Try
73 | {
74 | $BlobName = $BlobItem.Name
75 | $blobext = [System.IO.Path]::GetExtension($BlobName)
76 | $blobext = $blobext.ToLower()
77 | $blobpath = [System.IO.Path]::GetDirectoryName($BlobName)
78 | $blobpath = $blobpath.ToLower()
79 |
80 | #You can add or remove more content typs if need be, the defult of none makes sure that nothing get's changed if extension is not matched
81 | switch ($blobext) {
82 | ".jpg" {$DesiredContentType = "image/jpeg"}
83 | ".jpeg" {$DesiredContentType = "image/jpeg"}
84 | ".jpe" {$DesiredContentType = "image/jpeg"}
85 | ".gif" {$DesiredContentType = "image/gif"}
86 | ".png" {$DesiredContentType = "image/png"}
87 | ".svg" {$DesiredContentType = "image/svg+xml"}
88 | ".eot" {$DesiredContentType = "application/vnd.ms-fontobject"}
89 | ".ttf" {$DesiredContentType = "application/x-font-ttf"}
90 | ".woff" {$DesiredContentType = "application/font-woff"}
91 | ".woff2" {$DesiredContentType = "application/font-woff2"}
92 | ".html" {$DesiredContentType = "text/html"}
93 | ".js" {$DesiredContentType = "application/javascript"}
94 | ".css" {$DesiredContentType = "text/css"}
95 | default {$DesiredContentType = "none"}
96 | }
97 |
98 | switch ($blobpath) {
99 | "hashed" {$DesiredCacheControl = "public, max-age=31536000"}
100 | default {$DesiredCacheControl = "public, max-age=3600"}
101 | }
102 |
103 | Write-Output "'$BlobName' - Has path '$blobpath' and extension '$blobext'."
104 | $CloudBlob = $BlobItem.ICloudBlob
105 | $Updated = $FALSE
106 | if ($DesiredContentType -ne "none") {
107 | $CurrentContentType = $CloudBlob.Properties.ContentType
108 | Write-Output "'$BlobName' - Has content type '$CurrentContentType'."
109 | if (-not $DesiredContentType.Equals($CurrentContentType, 3)) {
110 | $CloudBlob.Properties.ContentType = $DesiredContentType
111 | $Updated = $TRUE
112 | Write-Output "'$BlobName' - Changing content type to '$DesiredContentType'."
113 | } Else {
114 | Write-Information "'$BlobName' - Already has content type '$DesiredContentType'."
115 | }
116 | } Else {
117 | Write-Warning "'$BlobName' - Extension '$blobext' does not have a mapping."
118 | }
119 |
120 | if ($DesiredCacheControl -ne "none") {
121 | $CloudBlob = $BlobItem.ICloudBlob
122 | $CurrentCacheControl = $CloudBlob.Properties.CacheControl
123 | Write-Output "'$BlobName' - Has cache control '$CurrentCacheControl'."
124 |
125 | if (-not $DesiredCacheControl.Equals($CurrentCacheControl, 3)) {
126 | $CloudBlob.Properties.CacheControl = $DesiredCacheControl
127 | $Updated = $TRUE
128 | Write-Output "'$BlobName' - Changing cache control to '$DesiredCacheControl'."
129 | } Else {
130 | Write-Information "'$BlobName' - Already has cache control '$DesiredCacheControl'."
131 | }
132 | } Else {
133 | Write-Warning "'$BlobName' - Does not have a desired cache control setting."
134 | }
135 |
136 | if ($Updated) {
137 | $CloudBlob.SetProperties()
138 | Write-Information "'$BlobName' - Saved Successfully."
139 | }
140 | }
141 | Catch {
142 | $ErrorMessage = $_.Exception.Message
143 | Write-Error "Failed to change content type of '$BlobName'. Error: '$ErrorMessage'"
144 | }
145 | }
146 | $Total += $BlobItems.Count
147 | if($BlobItems.Length -le 0) { Break;}
148 | $Token = $BlobItems[$BlobItems.Count -1].ContinuationToken;
149 | }
150 | While ($Token -ne $Null)
151 | }
152 | Else
153 | {
154 | Write-Warning "Cannot find container '$ContainerName' because it does not exist. Please make sure thar the name of container is correct."
155 | }
156 | }
157 | }
158 |
--------------------------------------------------------------------------------
/scripts/prepare.js:
--------------------------------------------------------------------------------
1 | const { spawnSync } = require("child_process");
2 | const chalk = require("chalk");
3 |
4 | // git v2.9.0 supports a custom hooks directory. This means we just need to check in the hooks scripts.
5 | spawnSync("git", ["config", "core.hooksPath", ".githooks"]);
6 |
7 | console.log(chalk.green("\nAll dependencies are installed!"));
8 | console.log(`For inner loop development, run:
9 | ${chalk.yellow("yarn start")}
10 | `);
11 |
--------------------------------------------------------------------------------
/scripts/tasks/storybook.ts:
--------------------------------------------------------------------------------
1 | import { argv } from "just-scripts";
2 | import path from "path";
3 |
4 | import storybook from "@storybook/react/standalone";
5 |
6 | export function startStorybookTask(options?: any) {
7 | options = options || {};
8 | // This shouldn't be necessary but is needed due to strange logic in
9 | // storybook lib/core/src/server/config/utils.js
10 | process.env.NODE_ENV = "development";
11 |
12 | return async function () {
13 | let { port, quiet, ci } = argv();
14 |
15 | port = options.port || port;
16 | quiet = options.quiet || quiet;
17 | ci = options.ci || ci;
18 |
19 | const localConfigDir = path.join(process.cwd(), ".storybook");
20 |
21 | await storybook({
22 | mode: "dev",
23 | staticDir: [path.join(process.cwd(), "static")],
24 | configDir: localConfigDir,
25 | port: port || 3000,
26 | quiet,
27 | ci,
28 | });
29 | };
30 | }
31 |
32 | export function buildStorybookTask(options?: any) {
33 | options = options || {};
34 | return async function () {
35 | let { port, quiet, ci } = argv();
36 |
37 | port = options.port || port;
38 | quiet = options.quiet || quiet;
39 | ci = options.ci || ci;
40 |
41 | await storybook({
42 | mode: "static",
43 | staticDir: [path.join(process.cwd(), "static")],
44 | configDir: path.join(process.cwd(), ".storybook"),
45 | outputDir: path.join(process.cwd(), "dist-storybook"),
46 | quiet,
47 | port: port || 3000,
48 | ci,
49 | });
50 | };
51 | }
52 |
--------------------------------------------------------------------------------
/src/chart/chart-legend.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import Chart from "chart.js";
3 | import {
4 | ChartTypes,
5 | HighContrastColors,
6 | IChart,
7 | IChartOptions,
8 | } from "../types";
9 | import { HALF_PI, PI, QUARTER_PI, TWO_THIRDS_PI } from "../lib/utils";
10 |
11 | export const ChartLegend = ({
12 | config,
13 | onClick,
14 | }: {
15 | config: IChart;
16 | onClick: (index: number) => void;
17 | }) => {
18 | return (
19 |
28 | {config.data!.datasets!.map((item: any, index: number) => (
29 |
37 | ))}
38 |
39 | );
40 | };
41 |
42 | const LegendItem = ({
43 | id,
44 | label,
45 | dataset,
46 | highContrast,
47 | onClick,
48 | }: {
49 | id: number;
50 | label: string;
51 | dataset: Chart.ChartDataSets;
52 | highContrast?: boolean;
53 | onClick: (index: number) => void;
54 | }) => {
55 | const [selected, setSelected] = useState(false);
56 | const legendItemRef = React.useRef(null);
57 |
58 | React.useEffect(() => {
59 | if (!legendItemRef.current) return;
60 | }, []);
61 |
62 | const style: any = {
63 | display: "flex",
64 | margin: "0 10px",
65 | cursor: "pointer",
66 | transition: "opacity ease-out .3s",
67 | };
68 | if (selected) {
69 | style.opacity = 0.5;
70 | }
71 | if (highContrast) {
72 | style.color = selected
73 | ? HighContrastColors.Disabled
74 | : HighContrastColors.Foreground;
75 | }
76 |
77 | return (
78 | {
82 | onClick(id), setSelected(!selected);
83 | }}
84 | >
85 |
90 | {dataset.label}
91 |
92 | );
93 | };
94 |
95 | function elementInViewport(element: HTMLDivElement) {
96 | let top = element.offsetTop;
97 | let left = element.offsetLeft;
98 | let width = element.offsetWidth;
99 | let height = element.offsetHeight;
100 |
101 | while (element.offsetParent) {
102 | (element as any) = element.offsetParent;
103 | top += element.offsetTop;
104 | left += element.offsetLeft;
105 | }
106 |
107 | return (
108 | top >= window.pageYOffset &&
109 | left >= window.pageXOffset &&
110 | top + height <= window.pageYOffset + window.innerHeight &&
111 | left + width <= window.pageXOffset + window.innerWidth
112 | );
113 | }
114 |
115 | const LegendItemColor = ({
116 | dataset,
117 | selected,
118 | highContrast,
119 | }: {
120 | dataset: Chart.ChartDataSets;
121 | selected: boolean;
122 | highContrast?: boolean;
123 | }) => {
124 | const labelColorValueRef = React.useRef(null);
125 | React.useEffect(() => {
126 | if (!labelColorValueRef.current) return;
127 | const canvasRef: HTMLCanvasElement = labelColorValueRef.current;
128 | createLegendItemCanvas({ canvas: canvasRef, dataset, highContrast });
129 | }, []);
130 | return (
131 |
157 | );
158 | };
159 |
160 | function createLegendItemCanvas({
161 | canvas,
162 | dataset,
163 | highContrast,
164 | }: {
165 | canvas: HTMLCanvasElement;
166 | dataset: Chart.ChartDataSets;
167 | highContrast?: boolean;
168 | }) {
169 | if (!canvas) return;
170 | const ctx: CanvasRenderingContext2D = canvas.getContext("2d")!;
171 | if (!ctx) return;
172 | ctx.save();
173 | ctx.scale(10, 8);
174 |
175 | if (!dataset.borderDash) {
176 | ctx.fillStyle = (dataset.backgroundColor === "transparent" ||
177 | dataset.backgroundColor === "rgba(0,0,0,0)"
178 | ? dataset.borderColor
179 | : dataset.backgroundColor) as string;
180 | ctx.fillRect(0, 0, canvas.width, canvas.height);
181 | }
182 | if (highContrast) {
183 | ctx.strokeStyle = HighContrastColors.Foreground;
184 | ctx.lineWidth = dataset.borderWidth as number;
185 | ctx.strokeRect(0, 0, 30, 19);
186 | }
187 |
188 | if (dataset.type === ChartTypes.Line && dataset.borderDash) {
189 | if (highContrast) {
190 | const x = 22;
191 | const y = 10;
192 |
193 | ctx.strokeStyle = HighContrastColors.Background;
194 | ctx.lineWidth = dataset.borderWidth as number;
195 | ctx.strokeRect(0, 0, 30, 18);
196 | // Line
197 | ctx.beginPath();
198 | ctx.moveTo(-10, y);
199 | ctx.lineTo(18, y);
200 | ctx.setLineDash(dataset.borderDash!);
201 | ctx.lineWidth = (dataset.borderWidth as number) || 4;
202 | ctx.strokeStyle = HighContrastColors.Foreground;
203 | ctx.stroke();
204 |
205 | // Point
206 | const pointStyle = dataset.pointStyle;
207 | if (pointStyle) {
208 | const radius = 6;
209 | const rotation = 0;
210 | let xOffset = 0;
211 | let yOffset = 0;
212 | let cornerRadius = 1;
213 | let size = 5;
214 | let rad = 0;
215 |
216 | ctx.beginPath();
217 | ctx.setLineDash([]);
218 | switch (pointStyle) {
219 | // Default includes circle
220 | default:
221 | ctx.arc(x, y, radius * 0.85, 0, Math.PI * 2, true);
222 | ctx.closePath();
223 | break;
224 | case "triangle":
225 | ctx.moveTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius);
226 | rad += TWO_THIRDS_PI;
227 | ctx.lineTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius);
228 | rad += TWO_THIRDS_PI;
229 | ctx.lineTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius);
230 | ctx.closePath();
231 | break;
232 | case "rectRounded":
233 | cornerRadius = radius * 0.516;
234 | size = radius - cornerRadius;
235 | xOffset = Math.cos(rad + QUARTER_PI) * size;
236 | yOffset = Math.sin(rad + QUARTER_PI) * size;
237 | ctx.arc(
238 | x - xOffset,
239 | y - yOffset,
240 | cornerRadius,
241 | rad - PI,
242 | rad - HALF_PI
243 | );
244 | ctx.arc(x + yOffset, y - xOffset, cornerRadius, rad - HALF_PI, rad);
245 | ctx.arc(x + xOffset, y + yOffset, cornerRadius, rad, rad + HALF_PI);
246 | ctx.arc(
247 | x - yOffset,
248 | y + xOffset,
249 | cornerRadius,
250 | rad + HALF_PI,
251 | rad + PI
252 | );
253 | ctx.closePath();
254 | break;
255 | case "rect":
256 | if (!rotation) {
257 | size = Math.SQRT1_2 * radius;
258 | ctx.rect(x - size, y - size, 2 * size, 2 * size);
259 | break;
260 | }
261 | rad += QUARTER_PI;
262 | /* falls through */
263 | case "rectRot":
264 | xOffset = Math.cos(rad) * radius;
265 | yOffset = Math.sin(rad) * radius;
266 | ctx.moveTo(x - xOffset, y - yOffset);
267 | ctx.lineTo(x + yOffset, y - xOffset);
268 | ctx.lineTo(x + xOffset, y + yOffset);
269 | ctx.lineTo(x - yOffset, y + xOffset);
270 | ctx.closePath();
271 | break;
272 | case "crossRot":
273 | rad += QUARTER_PI;
274 | /* falls through */
275 | case "cross":
276 | xOffset = Math.cos(rad) * radius;
277 | yOffset = Math.sin(rad) * radius;
278 | ctx.moveTo(x - xOffset, y - yOffset);
279 | ctx.lineTo(x + xOffset, y + yOffset);
280 | ctx.moveTo(x + yOffset, y - xOffset);
281 | ctx.lineTo(x - yOffset, y + xOffset);
282 | break;
283 | case "star":
284 | xOffset = Math.cos(rad) * radius;
285 | yOffset = Math.sin(rad) * radius;
286 | ctx.moveTo(x - xOffset, y - yOffset);
287 | ctx.lineTo(x + xOffset, y + yOffset);
288 | ctx.moveTo(x + yOffset, y - xOffset);
289 | ctx.lineTo(x - yOffset, y + xOffset);
290 | rad += QUARTER_PI;
291 | xOffset = Math.cos(rad) * radius;
292 | yOffset = Math.sin(rad) * radius;
293 | ctx.moveTo(x - xOffset, y - yOffset);
294 | ctx.lineTo(x + xOffset, y + yOffset);
295 | ctx.moveTo(x + yOffset, y - xOffset);
296 | ctx.lineTo(x - yOffset, y + xOffset);
297 | break;
298 | case "line":
299 | xOffset = Math.cos(rad) * radius;
300 | yOffset = Math.sin(rad) * radius;
301 | ctx.moveTo(x - xOffset, y - yOffset);
302 | ctx.lineTo(x + xOffset, y + yOffset);
303 | break;
304 | case "dash":
305 | ctx.moveTo(x, y);
306 | ctx.lineTo(x + Math.cos(rad) * radius, y + Math.sin(rad) * radius);
307 | break;
308 | }
309 | ctx.lineWidth = 1;
310 | ctx.fillStyle = HighContrastColors.Foreground;
311 | ctx.strokeStyle = HighContrastColors.Foreground;
312 | ctx.closePath();
313 | ctx.fill();
314 | ctx.stroke();
315 | ctx.restore();
316 | }
317 | } else {
318 | ctx.fillStyle = dataset.borderColor as string;
319 | ctx.fillRect(0, 0, canvas.width, canvas.height);
320 | }
321 | }
322 |
323 | // switch (dataset.type) {
324 | // case ChartTypes.Line:
325 | // if (highContrast) {
326 | // const x = 22;
327 | // const y = 10;
328 | // // Line
329 | // ctx.beginPath();
330 | // ctx.moveTo(-10, y);
331 | // ctx.lineTo(18, y);
332 | // ctx.setLineDash(dataset.borderDash!);
333 | // ctx.lineWidth = (dataset.borderWidth as number) || 4;
334 | // ctx.strokeStyle = HighContrastColors.Foreground;
335 | // ctx.stroke();
336 |
337 | // // Point
338 | // const pointStyle = dataset.pointStyle;
339 | // if (pointStyle) {
340 | // const radius = 6;
341 | // const rotation = 0;
342 | // let xOffset = 0;
343 | // let yOffset = 0;
344 | // let cornerRadius = 1;
345 | // let size = 5;
346 | // let rad = 0;
347 |
348 | // ctx.beginPath();
349 | // ctx.setLineDash([]);
350 | // switch (pointStyle) {
351 | // // Default includes circle
352 | // default:
353 | // ctx.arc(x, y, radius * 0.85, 0, Math.PI * 2, true);
354 | // ctx.closePath();
355 | // break;
356 | // case "triangle":
357 | // ctx.moveTo(
358 | // x + Math.sin(rad) * radius,
359 | // y - Math.cos(rad) * radius
360 | // );
361 | // rad += TWO_THIRDS_PI;
362 | // ctx.lineTo(
363 | // x + Math.sin(rad) * radius,
364 | // y - Math.cos(rad) * radius
365 | // );
366 | // rad += TWO_THIRDS_PI;
367 | // ctx.lineTo(
368 | // x + Math.sin(rad) * radius,
369 | // y - Math.cos(rad) * radius
370 | // );
371 | // ctx.closePath();
372 | // break;
373 | // case "rectRounded":
374 | // cornerRadius = radius * 0.516;
375 | // size = radius - cornerRadius;
376 | // xOffset = Math.cos(rad + QUARTER_PI) * size;
377 | // yOffset = Math.sin(rad + QUARTER_PI) * size;
378 | // ctx.arc(
379 | // x - xOffset,
380 | // y - yOffset,
381 | // cornerRadius,
382 | // rad - PI,
383 | // rad - HALF_PI
384 | // );
385 | // ctx.arc(
386 | // x + yOffset,
387 | // y - xOffset,
388 | // cornerRadius,
389 | // rad - HALF_PI,
390 | // rad
391 | // );
392 | // ctx.arc(
393 | // x + xOffset,
394 | // y + yOffset,
395 | // cornerRadius,
396 | // rad,
397 | // rad + HALF_PI
398 | // );
399 | // ctx.arc(
400 | // x - yOffset,
401 | // y + xOffset,
402 | // cornerRadius,
403 | // rad + HALF_PI,
404 | // rad + PI
405 | // );
406 | // ctx.closePath();
407 | // break;
408 | // case "rect":
409 | // if (!rotation) {
410 | // size = Math.SQRT1_2 * radius;
411 | // ctx.rect(x - size, y - size, 2 * size, 2 * size);
412 | // break;
413 | // }
414 | // rad += QUARTER_PI;
415 | // /* falls through */
416 | // case "rectRot":
417 | // xOffset = Math.cos(rad) * radius;
418 | // yOffset = Math.sin(rad) * radius;
419 | // ctx.moveTo(x - xOffset, y - yOffset);
420 | // ctx.lineTo(x + yOffset, y - xOffset);
421 | // ctx.lineTo(x + xOffset, y + yOffset);
422 | // ctx.lineTo(x - yOffset, y + xOffset);
423 | // ctx.closePath();
424 | // break;
425 | // case "crossRot":
426 | // rad += QUARTER_PI;
427 | // /* falls through */
428 | // case "cross":
429 | // xOffset = Math.cos(rad) * radius;
430 | // yOffset = Math.sin(rad) * radius;
431 | // ctx.moveTo(x - xOffset, y - yOffset);
432 | // ctx.lineTo(x + xOffset, y + yOffset);
433 | // ctx.moveTo(x + yOffset, y - xOffset);
434 | // ctx.lineTo(x - yOffset, y + xOffset);
435 | // break;
436 | // case "star":
437 | // xOffset = Math.cos(rad) * radius;
438 | // yOffset = Math.sin(rad) * radius;
439 | // ctx.moveTo(x - xOffset, y - yOffset);
440 | // ctx.lineTo(x + xOffset, y + yOffset);
441 | // ctx.moveTo(x + yOffset, y - xOffset);
442 | // ctx.lineTo(x - yOffset, y + xOffset);
443 | // rad += QUARTER_PI;
444 | // xOffset = Math.cos(rad) * radius;
445 | // yOffset = Math.sin(rad) * radius;
446 | // ctx.moveTo(x - xOffset, y - yOffset);
447 | // ctx.lineTo(x + xOffset, y + yOffset);
448 | // ctx.moveTo(x + yOffset, y - xOffset);
449 | // ctx.lineTo(x - yOffset, y + xOffset);
450 | // break;
451 | // case "line":
452 | // xOffset = Math.cos(rad) * radius;
453 | // yOffset = Math.sin(rad) * radius;
454 | // ctx.moveTo(x - xOffset, y - yOffset);
455 | // ctx.lineTo(x + xOffset, y + yOffset);
456 | // break;
457 | // case "dash":
458 | // ctx.moveTo(x, y);
459 | // ctx.lineTo(
460 | // x + Math.cos(rad) * radius,
461 | // y + Math.sin(rad) * radius
462 | // );
463 | // break;
464 | // }
465 | // ctx.lineWidth = 1;
466 | // ctx.fillStyle = HighContrastColors.Foreground;
467 | // ctx.strokeStyle = HighContrastColors.Foreground;
468 | // ctx.closePath();
469 | // ctx.fill();
470 | // ctx.stroke();
471 | // ctx.restore();
472 | // }
473 | // } else {
474 | // ctx.fillStyle = dataset.borderColor as string;
475 | // ctx.fillRect(0, 0, canvas.width, canvas.height);
476 | // }
477 | // break;
478 | // default:
479 | // ctx.fillStyle = dataset.backgroundColor as string;
480 | // ctx.fillRect(0, 0, canvas.width, canvas.height);
481 | // if (highContrast) {
482 | // ctx.strokeStyle = HighContrastColors.Foreground;
483 | // ctx.lineWidth = dataset.borderWidth as number;
484 | // ctx.strokeRect(0, 0, 30, 18);
485 | // }
486 | // break;
487 | // }
488 | // ctx.fillRect(0, 0, canvas.width, canvas.height);
489 |
490 | // if (theme === TeamsTheme.HighContrast) {
491 | // if (patterns) {
492 | // ctx.setTransform(1.4, 0, 0, 1, 0, 0);
493 | // ctx.scale(12, 10);
494 | // (ctx.fillStyle as any) = buildPattern({
495 | // ...patterns(colorScheme)[index],
496 | // backgroundColor: colorScheme.default.background,
497 | // patternColor: colorScheme.brand.background,
498 | // });
499 | // ctx.fillRect(-15, -15, canvasRef.width, canvasRef.height);
500 | // ctx.restore();
501 | // } else {
502 | // ctx.scale(15, 15);
503 | // ctx.fillStyle = colorScheme.brand.shadow;
504 | // ctx.fillRect(-15, -15, canvasRef.width, canvasRef.height);
505 | // ctx.fillStyle = colorScheme.default.foreground3;
506 | // switch (lineChartPatterns[index].pointStyle) {
507 | // case PointStyles.Triangle:
508 | // ctx.moveTo(9.5, 2.5);
509 | // ctx.lineTo(5.5, 7.5);
510 | // ctx.lineTo(13.5, 7.5);
511 | // break;
512 | // case PointStyles.Rectangle:
513 | // ctx.rect(6.5, 2.5, 8, 5);
514 | // break;
515 | // case PointStyles.RectangleRotated:
516 | // ctx.moveTo(10, 2);
517 | // ctx.lineTo(14.5, 5);
518 | // ctx.lineTo(10, 8);
519 | // ctx.lineTo(5.5, 5);
520 | // break;
521 | // case PointStyles.Circle:
522 | // default:
523 | // ctx.ellipse(10, 5, 3.5, 2.5, 0, 0, 2 * Math.PI);
524 | // break;
525 | // }
526 | // ctx.fill();
527 |
528 | // // Line Style
529 | // ctx.strokeStyle = colorScheme.default.foreground3;
530 | // ctx.beginPath();
531 | // ctx.setLineDash(
532 | // lineChartPatterns[index].lineBorderDash.length ? [2, 2] : []
533 | // );
534 | // ctx.moveTo(-1.5, 5);
535 | // ctx.lineTo(20, 5);
536 | // ctx.stroke();
537 | // ctx.restore();
538 | // }
539 | // } else {
540 | // ctx.fillStyle = dataPointColor;
541 | // ctx.fillRect(0, 0, canvasRef.width, canvasRef.height);
542 | // }
543 | }
544 |
--------------------------------------------------------------------------------
/src/chart/chart-render.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 | import Chart from "chart.js";
3 | import { ChartLegend } from "./chart-legend";
4 | import { IChart } from "../types";
5 |
6 | export const ChartRender = (config: IChart) => {
7 | const { data, options, canvasProps } = config;
8 |
9 | const canvasRef = React.useRef(null);
10 | const chartRef = React.useRef();
11 | const chartId = React.useMemo(
12 | () => Math.random().toString(36).substr(2, 9),
13 | []
14 | );
15 |
16 | useEffect(() => {
17 | if (!canvasRef.current) return;
18 | const ctx = canvasRef.current.getContext("2d");
19 | if (!ctx) return;
20 | // Chart Init
21 | chartRef.current = new Chart(ctx, { ...config });
22 | }, []);
23 |
24 | function onLegendItemClick(index: number) {
25 | if (!chartRef.current) return;
26 | const ci = (chartRef.current as any).chart;
27 | const meta = ci.getDatasetMeta(index);
28 |
29 | // See controller.isDatasetVisible comment
30 | meta.hidden = meta.hidden === null ? !ci.data.datasets[index].hidden : null;
31 |
32 | // We hid a dataset ... rerender the chart
33 | ci.update();
34 | }
35 |
36 | return (
37 |
45 |
46 |
68 |
69 | {data && (options as any).legend.custom && (
70 |
71 | )}
72 |
73 | );
74 | };
75 |
--------------------------------------------------------------------------------
/src/chart/chart.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Chart from "chart.js";
3 | import { IChart } from "../types";
4 | import { ChartRender } from "./chart-render";
5 |
6 | (Chart as any).defaults.global.legend.display = false;
7 | (Chart as any).defaults.global.defaultFontFamily = `Segoe UI, system-ui, sans-serif`;
8 |
9 | export function ChartContainer(config: IChart) {
10 | return ;
11 | }
12 |
--------------------------------------------------------------------------------
/src/chart/index.ts:
--------------------------------------------------------------------------------
1 | export { ChartContainer as Chart } from "./chart";
2 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | export { Chart } from "./chart";
2 | export * from "./types";
3 | export * from "./lib/datasets";
4 | export * from "./lib/patterns";
5 | export * from "./lib/plugins";
6 | export * from "./lib/settings";
7 | export * from "./lib/utils";
8 |
--------------------------------------------------------------------------------
/src/lib/builder.ts:
--------------------------------------------------------------------------------
1 | import { PluginServiceRegistrationOptions } from "chart.js";
2 | import {
3 | axisXTeamsStyle,
4 | gradientPlugin,
5 | highLightDataOnHover,
6 | horizontalBarAxisYLabels,
7 | horizontalBarValue,
8 | keyboardAccessibility,
9 | removeAllListeners,
10 | } from "./plugins";
11 | import {
12 | ChartTypes,
13 | Entity,
14 | IChartConfig,
15 | IChartData,
16 | IChartOptions,
17 | ILineChartDataHighContrast,
18 | ILinePreSetupConfigHighContrast,
19 | IPreSetupConfig,
20 | IPreSetupConfigHighContrast,
21 | } from "../types";
22 | import {
23 | barOptions,
24 | defaultOptions,
25 | doughnutOptions,
26 | groupedBarOptions,
27 | highContrastOptions,
28 | horizontalBarOptions,
29 | pieOptions,
30 | stackedBarOptions,
31 | stackedLineOptions,
32 | trendLineOptions,
33 | } from "./settings";
34 | import {
35 | BarDataSetHCStyle,
36 | BarDataSetStyle,
37 | DoughnutDataSetHCStyle,
38 | DoughnutDataSetStyle,
39 | HorizontalBarDataSetHCStyle,
40 | HorizontalBarDataSetStyle,
41 | LineDataSetHCStyle,
42 | LineDataSetStyle,
43 | LineStackedDataSetHCStyle,
44 | LineStackedDataSetStyle,
45 | PieDataSetHCStyle,
46 | PieDataSetStyle,
47 | TrendLineDataSetHCStyle,
48 | TrendLineDataSetStyle,
49 | } from "./datasets";
50 | import { deepMerge } from "./utils";
51 |
52 | export class ChartBuilder extends Entity {
53 | type?: ChartTypes;
54 | data: IChartData;
55 | areaLabel: string;
56 | options: IChartOptions = defaultOptions;
57 | plugins: PluginServiceRegistrationOptions[] = [
58 | {
59 | afterInit: keyboardAccessibility,
60 | },
61 | {
62 | afterInit: axisXTeamsStyle,
63 | },
64 | {
65 | destroy: removeAllListeners,
66 | },
67 | ];
68 |
69 | constructor(fields: IChartConfig) {
70 | super(fields);
71 |
72 | this.areaLabel = fields.areaLabel;
73 | this.data = fields.data;
74 | this.type = fields.type;
75 |
76 | if (fields.plugins) {
77 | this.plugins = this.plugins.concat(fields.plugins);
78 | }
79 |
80 | if (fields.options) {
81 | this.options = deepMerge(this.options, fields.options);
82 | }
83 | }
84 | }
85 |
86 | export class LineChart extends ChartBuilder {
87 | type: ChartTypes;
88 |
89 | constructor(fields: IPreSetupConfig) {
90 | super(fields);
91 |
92 | this.type = ChartTypes.Line;
93 | this.data.datasets = Array.from(
94 | this.data.datasets,
95 | (set) => new LineDataSetStyle(set)
96 | );
97 |
98 | this.plugins.push({
99 | beforeDatasetsDraw: highLightDataOnHover,
100 | });
101 | }
102 | }
103 |
104 | export class TrendLineChart extends ChartBuilder {
105 | type: ChartTypes;
106 |
107 | constructor(fields: IPreSetupConfig) {
108 | super(fields);
109 |
110 | this.type = ChartTypes.Line;
111 | this.data.datasets = Array.from(
112 | this.data.datasets,
113 | (set) => new TrendLineDataSetStyle(set)
114 | );
115 |
116 | this.options = deepMerge(this.options, trendLineOptions);
117 |
118 | if (fields.options) {
119 | this.options = deepMerge(this.options, fields.options);
120 | }
121 |
122 | this.plugins.push({
123 | afterLayout: gradientPlugin,
124 | });
125 | this.plugins.push({
126 | beforeDatasetDraw: highLightDataOnHover,
127 | });
128 | }
129 | }
130 |
131 | export class TrendLineChartHighContrast extends TrendLineChart {
132 | constructor(fields: ILinePreSetupConfigHighContrast) {
133 | super(fields);
134 |
135 | this.data.datasets = Array.from(
136 | fields.data.datasets,
137 | (set: TrendLineDataSetHCStyle) => new TrendLineDataSetHCStyle(set)
138 | );
139 |
140 | this.options = deepMerge(this.options, highContrastOptions);
141 |
142 | this.plugins.push({
143 | afterLayout: gradientPlugin,
144 | });
145 |
146 | this.plugins.push({
147 | beforeDatasetsDraw: highLightDataOnHover,
148 | });
149 | }
150 | }
151 |
152 | export class BarChart extends ChartBuilder {
153 | type: ChartTypes;
154 |
155 | constructor(fields: IPreSetupConfig) {
156 | super(fields);
157 |
158 | this.type = ChartTypes.Bar;
159 | this.data.datasets = Array.from(
160 | this.data.datasets,
161 | (set) => new BarDataSetStyle(set)
162 | );
163 |
164 | this.options = deepMerge(this.options, barOptions);
165 |
166 | if (fields.options) {
167 | this.options = deepMerge(this.options, fields.options);
168 | }
169 |
170 | this.plugins.push({
171 | beforeDatasetsDraw: highLightDataOnHover,
172 | });
173 | }
174 | }
175 |
176 | export class PieChart extends ChartBuilder {
177 | type: ChartTypes;
178 |
179 | constructor(fields: IPreSetupConfig) {
180 | super(fields);
181 |
182 | this.type = ChartTypes.Pie;
183 | this.data.datasets = Array.from(
184 | this.data.datasets,
185 | (set) => new PieDataSetStyle(set)
186 | );
187 |
188 | this.options = deepMerge(this.options, pieOptions);
189 |
190 | if (fields.options) {
191 | this.options = deepMerge(this.options, fields.options);
192 | }
193 | }
194 | }
195 |
196 | export class DoughnutChart extends PieChart {
197 | type: ChartTypes;
198 |
199 | constructor(fields: IPreSetupConfig) {
200 | super(fields);
201 |
202 | this.type = ChartTypes.Doughnut;
203 | this.data.datasets = Array.from(
204 | this.data.datasets,
205 | (set) => new DoughnutDataSetStyle(set)
206 | );
207 |
208 | this.options = deepMerge(this.options, doughnutOptions);
209 |
210 | if (fields.options) {
211 | this.options = deepMerge(this.options, fields.options);
212 | }
213 | }
214 | }
215 |
216 | export class GroupedBarChart extends BarChart {
217 | constructor(fields: IPreSetupConfig) {
218 | super(fields);
219 |
220 | this.options = deepMerge(this.options, groupedBarOptions);
221 | }
222 | }
223 |
224 | export class StackedBarChart extends ChartBuilder {
225 | type: ChartTypes;
226 |
227 | constructor(fields: IPreSetupConfig) {
228 | super(fields);
229 |
230 | this.type = ChartTypes.Bar;
231 | this.data.datasets = Array.from(
232 | this.data.datasets,
233 | (set) => new BarDataSetStyle(set)
234 | );
235 |
236 | this.options = deepMerge(this.options, stackedBarOptions);
237 |
238 | if (fields.options) {
239 | this.options = deepMerge(this.options, fields.options);
240 | }
241 |
242 | this.plugins.push({
243 | beforeDatasetsDraw: highLightDataOnHover,
244 | });
245 | }
246 | }
247 |
248 | export class HorizontalBarChart extends ChartBuilder {
249 | type: ChartTypes;
250 |
251 | constructor(fields: IPreSetupConfig) {
252 | super(fields);
253 |
254 | this.type = ChartTypes.HorizontalBar;
255 | this.data.datasets = Array.from(
256 | this.data.datasets,
257 | (set) => new HorizontalBarDataSetStyle(set)
258 | );
259 |
260 | this.plugins.push({
261 | resize: horizontalBarAxisYLabels,
262 | });
263 | this.plugins.push({
264 | afterDatasetsDraw: horizontalBarValue,
265 | });
266 |
267 | this.options = deepMerge(this.options, horizontalBarOptions);
268 |
269 | if (fields.options) {
270 | this.options = deepMerge(this.options, fields.options);
271 | }
272 | }
273 | }
274 |
275 | export class AreaChart extends LineChart {
276 | constructor(fields: IPreSetupConfig) {
277 | super(fields);
278 |
279 | this.plugins.push({
280 | afterLayout: gradientPlugin,
281 | });
282 | this.plugins.push({
283 | beforeDatasetDraw: highLightDataOnHover,
284 | });
285 | }
286 | }
287 |
288 | export class LineStackedChart extends ChartBuilder {
289 | type: ChartTypes;
290 |
291 | constructor(fields: IPreSetupConfig) {
292 | super(fields);
293 |
294 | this.type = ChartTypes.Line;
295 | this.data.datasets = Array.from(
296 | this.data.datasets,
297 | (set) => new LineStackedDataSetStyle(set)
298 | );
299 | this.options = deepMerge(this.options, stackedLineOptions);
300 |
301 | if (fields.options) {
302 | this.options = deepMerge(this.options, fields.options);
303 | }
304 |
305 | this.plugins.push({
306 | beforeDatasetsDraw: highLightDataOnHover,
307 | });
308 | }
309 | }
310 |
311 | /**
312 | * HighContrast Chart Options
313 | */
314 |
315 | export class HorizontalBarChartHighContrast extends ChartBuilder {
316 | type: ChartTypes;
317 |
318 | constructor(fields: IPreSetupConfigHighContrast) {
319 | super(fields);
320 |
321 | this.type = ChartTypes.HorizontalBar;
322 | this.data.datasets = Array.from(
323 | this.data.datasets,
324 | (set) =>
325 | new HorizontalBarDataSetHCStyle(set as HorizontalBarDataSetHCStyle)
326 | );
327 |
328 | this.plugins.push({
329 | resize: horizontalBarAxisYLabels,
330 | });
331 | this.plugins.push({
332 | afterDatasetsDraw: horizontalBarValue,
333 | });
334 |
335 | this.options = deepMerge(this.options, highContrastOptions);
336 | this.options = deepMerge(this.options, horizontalBarOptions);
337 |
338 | if (fields.options) {
339 | this.options = deepMerge(this.options, fields.options);
340 | }
341 | }
342 | }
343 |
344 | export class StackedBarChartHighContrast extends ChartBuilder {
345 | type: ChartTypes;
346 |
347 | constructor(fields: IPreSetupConfigHighContrast) {
348 | super(fields);
349 |
350 | this.type = ChartTypes.Bar;
351 | this.data.datasets = Array.from(
352 | this.data.datasets,
353 | (set) => new BarDataSetHCStyle(set as BarDataSetHCStyle)
354 | );
355 |
356 | this.options = deepMerge(this.options, highContrastOptions);
357 | this.options = deepMerge(this.options, stackedBarOptions);
358 |
359 | if (fields.options) {
360 | this.options = deepMerge(this.options, fields.options);
361 | }
362 |
363 | this.plugins.push({
364 | beforeDatasetsDraw: highLightDataOnHover,
365 | });
366 | }
367 | }
368 |
369 | export class LineStackedChartHighContrast extends ChartBuilder {
370 | type: ChartTypes;
371 |
372 | constructor(fields: IPreSetupConfigHighContrast) {
373 | super(fields);
374 |
375 | this.type = ChartTypes.Line;
376 | this.data.datasets = Array.from(
377 | this.data.datasets,
378 | (set) => new LineStackedDataSetHCStyle(set as LineStackedDataSetHCStyle)
379 | );
380 | this.options = deepMerge(this.options, highContrastOptions);
381 | this.options = deepMerge(this.options, stackedLineOptions);
382 |
383 | if (fields.options) {
384 | this.options = deepMerge(this.options, fields.options);
385 | }
386 |
387 | this.plugins.push({
388 | beforeDatasetsDraw: highLightDataOnHover,
389 | });
390 | }
391 | }
392 |
393 | export class PieChartHighContrast extends PieChart {
394 | type: ChartTypes;
395 |
396 | constructor(fields: IPreSetupConfigHighContrast) {
397 | super(fields);
398 |
399 | this.type = ChartTypes.Pie;
400 | this.data.datasets = Array.from(
401 | this.data.datasets,
402 | (set) => new PieDataSetHCStyle(set as PieDataSetHCStyle)
403 | );
404 |
405 | this.options = deepMerge(this.options, highContrastOptions);
406 | this.options = deepMerge(this.options, pieOptions);
407 |
408 | if (fields.options) {
409 | this.options = deepMerge(this.options, fields.options);
410 | }
411 | }
412 | }
413 |
414 | export class DoughnutChartHighContrast extends DoughnutChart {
415 | type: ChartTypes;
416 |
417 | constructor(fields: IPreSetupConfigHighContrast) {
418 | super(fields);
419 |
420 | this.type = ChartTypes.Pie;
421 | this.data.datasets = Array.from(
422 | this.data.datasets,
423 | (set) => new DoughnutDataSetHCStyle(set as DoughnutDataSetHCStyle)
424 | );
425 |
426 | this.options = deepMerge(this.options, highContrastOptions);
427 | this.options = deepMerge(this.options, doughnutOptions);
428 |
429 | if (fields.options) {
430 | this.options = deepMerge(this.options, fields.options);
431 | }
432 | }
433 | }
434 |
435 | export class LineChartHighContrast extends ChartBuilder {
436 | type: ChartTypes;
437 | data: ILineChartDataHighContrast;
438 |
439 | constructor(fields: ILinePreSetupConfigHighContrast) {
440 | super(fields);
441 |
442 | this.type = ChartTypes.Line;
443 | this.data = fields.data;
444 | this.data.datasets = Array.from(
445 | fields.data.datasets,
446 | (set: LineDataSetHCStyle) => new LineDataSetHCStyle(set)
447 | );
448 |
449 | this.options = deepMerge(defaultOptions, highContrastOptions);
450 |
451 | if (fields.options) {
452 | this.options = deepMerge(this.options, fields.options);
453 | }
454 |
455 | this.plugins.push({
456 | beforeDatasetsDraw: highLightDataOnHover,
457 | });
458 | }
459 | }
460 |
461 | export class AreaChartHighContrast extends LineChartHighContrast {
462 | constructor(fields: ILinePreSetupConfigHighContrast) {
463 | super(fields);
464 |
465 | this.plugins.push({
466 | afterLayout: gradientPlugin,
467 | });
468 |
469 | this.plugins.push({
470 | beforeDatasetsDraw: highLightDataOnHover,
471 | });
472 | }
473 | }
474 |
475 | export class BarChartHighContrast extends ChartBuilder {
476 | type: ChartTypes;
477 |
478 | constructor(fields: IPreSetupConfigHighContrast) {
479 | super(fields);
480 |
481 | this.type = ChartTypes.Bar;
482 | this.data.datasets = Array.from(
483 | this.data.datasets,
484 | (set) => new BarDataSetHCStyle(set as BarDataSetHCStyle)
485 | );
486 |
487 | this.options = deepMerge(this.options, highContrastOptions);
488 | this.options = deepMerge(this.options, barOptions);
489 |
490 | if (fields.options) {
491 | this.options = deepMerge(this.options, fields.options);
492 | }
493 |
494 | this.plugins.push({
495 | beforeDatasetsDraw: highLightDataOnHover,
496 | });
497 | }
498 | }
499 |
500 | export class GroupedBarChartHighContrast extends BarChartHighContrast {
501 | constructor(fields: IPreSetupConfigHighContrast) {
502 | super(fields);
503 |
504 | this.options = deepMerge(this.options, groupedBarOptions);
505 | }
506 | }
507 |
--------------------------------------------------------------------------------
/src/lib/datasets.ts:
--------------------------------------------------------------------------------
1 | import {
2 | BorderWidth,
3 | ChartColor,
4 | ChartDataSets,
5 | PointStyle,
6 | PositionType,
7 | Scriptable,
8 | } from "chart.js";
9 | import {
10 | ChartTypes,
11 | Entity,
12 | HighContrastColors,
13 | IDraw,
14 | Point,
15 | Shapes,
16 | } from "../types";
17 | import { buildPattern } from "./patterns";
18 |
19 | export class ChartDataSet extends Entity {
20 | type?: string | undefined;
21 |
22 | constructor(fields: Partial) {
23 | super(fields);
24 |
25 | this.type = fields.type;
26 | }
27 | }
28 | export class LineDataSetStyle extends ChartDataSet implements ChartDataSets {
29 | borderCapStyle?: "butt" | "round" | "square";
30 | borderJoinStyle?: "bevel" | "round" | "miter";
31 | borderWidth?: BorderWidth | BorderWidth[] | Scriptable;
32 | hoverBackgroundColor?: ChartColor | ChartColor[] | Scriptable;
33 | hoverBorderWidth?: number | number[] | Scriptable;
34 | pointBorderWidth?: number | number[] | Scriptable;
35 | pointHoverBorderWidth?: number | number[] | Scriptable;
36 | pointHoverRadius?: number | number[] | Scriptable;
37 | pointRadius?: number | number[] | Scriptable;
38 | pointStyle?:
39 | | PointStyle
40 | | HTMLImageElement
41 | | HTMLCanvasElement
42 | | Array
43 | | Scriptable;
44 | backgroundColor?: ChartColor | ChartColor[] | Scriptable;
45 | color?: ChartColor | ChartColor[] | Scriptable;
46 | borderColor?: ChartColor | ChartColor[] | Scriptable;
47 | hoverBorderColor?: ChartColor | ChartColor[] | Scriptable;
48 | pointBorderColor?: ChartColor | ChartColor[] | Scriptable;
49 | pointBackgroundColor?: ChartColor | ChartColor[] | Scriptable;
50 | pointHoverBackgroundColor?:
51 | | ChartColor
52 | | ChartColor[]
53 | | Scriptable;
54 | pointHoverBorderColor?: ChartColor | ChartColor[] | Scriptable;
55 |
56 | constructor(fields: Partial) {
57 | super(fields);
58 |
59 | this.type = ChartTypes.Line;
60 | this.backgroundColor = fields.backgroundColor || "transparent";
61 | this.borderCapStyle = fields.borderCapStyle || "round";
62 | this.borderJoinStyle = fields.borderJoinStyle || "round";
63 | this.borderWidth = fields.borderWidth || 2;
64 | this.hoverBackgroundColor = fields.hoverBackgroundColor || "transparent";
65 | this.hoverBorderWidth = fields.hoverBorderWidth || 2.5;
66 | this.pointBorderWidth = fields.hoverBorderWidth || 0;
67 | this.pointHoverBorderWidth = fields.pointHoverBorderWidth || 0;
68 | this.pointHoverRadius = fields.pointHoverRadius || 2.5;
69 | this.pointRadius = fields.pointRadius || 2;
70 | this.pointStyle = fields.pointStyle || "circle";
71 | this.borderColor = fields.borderColor || fields.color || "rgba(0,0,0,.1)";
72 | this.hoverBorderColor =
73 | fields.hoverBorderColor || fields.color || "rgba(0,0,0,.1)";
74 | this.pointBorderColor =
75 | fields.pointBorderColor || fields.color || "rgba(0,0,0,.1)";
76 | this.pointBackgroundColor =
77 | fields.pointBackgroundColor || fields.color || "rgba(0,0,0,.1)";
78 | this.pointHoverBackgroundColor =
79 | fields.pointHoverBackgroundColor || fields.color || "rgba(0,0,0,.1)";
80 | this.pointHoverBorderColor =
81 | fields.pointHoverBorderColor || fields.color || "rgba(0,0,0,.1)";
82 | }
83 | }
84 |
85 | export class TrendLineDataSetStyle
86 | extends ChartDataSet
87 | implements ChartDataSets {
88 | borderCapStyle?: "butt" | "round" | "square";
89 | borderJoinStyle?: "bevel" | "round" | "miter";
90 | borderWidth?: BorderWidth | BorderWidth[] | Scriptable;
91 | hoverBackgroundColor?: ChartColor | ChartColor[] | Scriptable;
92 | hoverBorderWidth?: number | number[] | Scriptable;
93 | pointBorderWidth?: number | number[] | Scriptable;
94 | pointHoverBorderWidth?: number | number[] | Scriptable;
95 | pointHoverRadius?: number | number[] | Scriptable;
96 | pointRadius?: number | number[] | Scriptable;
97 | pointStyle?:
98 | | PointStyle
99 | | HTMLImageElement
100 | | HTMLCanvasElement
101 | | Array
102 | | Scriptable;
103 | backgroundColor?: ChartColor | ChartColor[] | Scriptable;
104 | color?: ChartColor | ChartColor[] | Scriptable;
105 | borderColor?: ChartColor | ChartColor[] | Scriptable;
106 | hoverBorderColor?: ChartColor | ChartColor[] | Scriptable;
107 | pointBorderColor?: ChartColor | ChartColor[] | Scriptable;
108 | pointBackgroundColor?: ChartColor | ChartColor[] | Scriptable;
109 | pointHoverBackgroundColor?:
110 | | ChartColor
111 | | ChartColor[]
112 | | Scriptable;
113 | pointHoverBorderColor?: ChartColor | ChartColor[] | Scriptable;
114 |
115 | constructor(fields: Partial) {
116 | super(fields);
117 |
118 | this.type = ChartTypes.Line;
119 | this.backgroundColor = fields.backgroundColor || "transparent";
120 | this.borderCapStyle = fields.borderCapStyle || "round";
121 | this.borderJoinStyle = fields.borderJoinStyle || "round";
122 | this.borderWidth = fields.borderWidth || 2;
123 | this.hoverBackgroundColor = fields.hoverBackgroundColor || "transparent";
124 | this.hoverBorderWidth = fields.hoverBorderWidth || 2.5;
125 | this.pointBorderWidth = fields.hoverBorderWidth || 0;
126 | this.pointHoverBorderWidth = fields.pointHoverBorderWidth || 0;
127 | this.pointHoverRadius = fields.pointHoverRadius || 0;
128 | this.pointRadius = fields.pointRadius || 0;
129 | this.pointStyle = fields.pointStyle || "circle";
130 | this.borderColor = fields.borderColor || fields.color || "rgba(0,0,0,.1)";
131 | this.hoverBorderColor =
132 | fields.hoverBorderColor || fields.color || "rgba(0,0,0,.1)";
133 | this.pointBorderColor =
134 | fields.pointBorderColor || fields.color || "rgba(0,0,0,.1)";
135 | this.pointBackgroundColor =
136 | fields.pointBackgroundColor || fields.color || "rgba(0,0,0,.1)";
137 | this.pointHoverBackgroundColor =
138 | fields.pointHoverBackgroundColor || fields.color || "rgba(0,0,0,.1)";
139 | this.pointHoverBorderColor =
140 | fields.pointHoverBorderColor || fields.color || "rgba(0,0,0,.1)";
141 | }
142 | }
143 |
144 | export class LineDataSetHCStyle extends LineDataSetStyle {
145 | borderColor?: string;
146 | hoverBorderColor?: string;
147 | pointBorderColor?: string;
148 | pointBackgroundColor?: string;
149 | pointHoverBackgroundColor?: string;
150 | pointHoverBorderColor?: string;
151 | hoverBorderWidth?: number | number[] | Scriptable;
152 | pointRadius?: number | number[] | Scriptable;
153 | pointHoverRadius?: number | number[] | Scriptable;
154 | borderDash: number[];
155 | pointStyle:
156 | | PointStyle
157 | | HTMLImageElement
158 | | HTMLCanvasElement
159 | | Array
160 | | Scriptable;
161 |
162 | constructor(fields: LineDataSetHCStyle) {
163 | super(fields);
164 | this.borderColor = HighContrastColors.Foreground;
165 | this.hoverBorderColor = HighContrastColors.Active;
166 | this.pointBorderColor = HighContrastColors.Foreground;
167 | this.pointBackgroundColor = HighContrastColors.Foreground;
168 | this.pointHoverBackgroundColor = HighContrastColors.Foreground;
169 | this.pointHoverBorderColor = HighContrastColors.Foreground;
170 | this.hoverBorderWidth = 4;
171 | this.pointRadius = 4;
172 | this.pointHoverRadius = 4;
173 | this.borderDash = fields.borderDash || [];
174 | this.pointStyle = fields.pointStyle || Point.Circle;
175 | }
176 | }
177 |
178 | export class TrendLineDataSetHCStyle extends LineDataSetHCStyle {
179 | constructor(fields: TrendLineDataSetHCStyle) {
180 | super(fields);
181 | this.pointBorderWidth = fields.hoverBorderWidth || 0;
182 | this.pointHoverBorderWidth = fields.pointHoverBorderWidth || 0;
183 | this.pointHoverRadius = fields.pointHoverRadius || 0;
184 | this.pointRadius = fields.pointRadius || 0;
185 | }
186 | }
187 |
188 | export class LineStackedDataSetStyle extends LineDataSetStyle {
189 | constructor(fields: LineStackedDataSetStyle) {
190 | super(fields);
191 | this.backgroundColor =
192 | fields.backgroundColor || fields.color || "rgba(0,0,0,.1)";
193 | this.hoverBackgroundColor =
194 | fields.hoverBackgroundColor || fields.color || "rgba(0,0,0,.1)";
195 | this.borderWidth = fields.borderWidth || 1;
196 | this.borderColor = fields.borderColor || "#ffffff";
197 | this.pointRadius = fields.pointRadius || 0;
198 | this.pointHoverBackgroundColor =
199 | fields.pointHoverBackgroundColor || "#ffffff";
200 | this.pointHoverRadius = fields.pointHoverRadius || 3;
201 | this.pointHoverBorderWidth = fields.pointHoverBorderWidth || 2;
202 | }
203 | }
204 |
205 | export class LineStackedDataSetHCStyle extends LineStackedDataSetStyle {
206 | pattern: IDraw;
207 |
208 | constructor(fields: LineStackedDataSetHCStyle) {
209 | super(fields);
210 | this.pattern = fields.pattern || {
211 | shape: Shapes.Square,
212 | size: 10,
213 | };
214 | this.backgroundColor = buildPattern({
215 | backgroundColor: HighContrastColors.Background,
216 | patternColor: HighContrastColors.Foreground,
217 | ...this.pattern,
218 | }) as any;
219 | this.hoverBackgroundColor = buildPattern({
220 | backgroundColor: HighContrastColors.Background,
221 | patternColor: HighContrastColors.Active,
222 | ...this.pattern,
223 | }) as any;
224 | this.borderColor = HighContrastColors.Foreground;
225 | this.hoverBorderColor = HighContrastColors.Active;
226 | this.borderWidth = fields.borderWidth || 3;
227 | this.hoverBorderWidth = fields.hoverBorderWidth || 4;
228 | this.hoverBorderWidth = fields.hoverBorderWidth || 5;
229 | this.pointHoverRadius = fields.pointHoverRadius || 5;
230 | }
231 | }
232 |
233 | export class BarDataSetStyle extends ChartDataSet implements ChartDataSets {
234 | backgroundColor?: ChartColor | ChartColor[] | Scriptable;
235 | borderColor?: ChartColor | ChartColor[] | Scriptable;
236 | borderWidth?: BorderWidth | BorderWidth[] | Scriptable;
237 | color?: ChartColor | ChartColor[] | Scriptable;
238 | hoverBackgroundColor?: ChartColor | ChartColor[] | Scriptable;
239 | hoverBorderWidth?: number | number[] | Scriptable;
240 | borderSkipped?: PositionType | PositionType[] | Scriptable;
241 |
242 | constructor(fields: BarDataSetStyle) {
243 | super(fields);
244 | this.borderWidth = fields.borderWidth || 0;
245 | this.backgroundColor =
246 | fields.backgroundColor || fields.color || "rgba(0,0,0,.1)";
247 | this.hoverBorderWidth = fields.hoverBorderWidth || 0;
248 | this.hoverBackgroundColor =
249 | fields.hoverBackgroundColor || fields.color || "rgba(0,0,0,.1)";
250 | this.borderSkipped = fields.borderSkipped || (false as any);
251 | this.type = ChartTypes.Bar as string;
252 | }
253 | }
254 |
255 | export class PieDataSetStyle extends ChartDataSet implements ChartDataSets {
256 | backgroundColor?: ChartColor | ChartColor[] | Scriptable;
257 | borderColor?: ChartColor | ChartColor[] | Scriptable;
258 | borderWidth?: BorderWidth | BorderWidth[] | Scriptable;
259 | color?: ChartColor[];
260 | hoverBackgroundColor?: ChartColor | ChartColor[] | Scriptable;
261 | hoverBorderColor?: ChartColor | ChartColor[] | Scriptable;
262 | borderSkipped?: PositionType | PositionType[] | Scriptable;
263 |
264 | constructor(fields: PieDataSetStyle) {
265 | super(fields);
266 | this.type = ChartTypes.Pie;
267 | this.borderWidth = fields.borderWidth || 2;
268 | this.borderColor = fields.borderColor || "#fff";
269 | this.hoverBorderColor = fields.hoverBorderColor || "#fff";
270 | this.backgroundColor =
271 | fields.backgroundColor || fields.color || "rgba(0,0,0,.1)";
272 | this.hoverBackgroundColor =
273 | fields.hoverBackgroundColor || fields.color || "rgba(0,0,0,.1)";
274 | this.borderSkipped = fields.borderSkipped || (false as any);
275 | }
276 | }
277 |
278 | export class DoughnutDataSetStyle extends PieDataSetStyle {
279 | constructor(fields: DoughnutDataSetStyle) {
280 | super(fields);
281 |
282 | this.type = ChartTypes.Doughnut;
283 | }
284 | }
285 |
286 | export class HorizontalBarDataSetStyle
287 | extends ChartDataSet
288 | implements ChartDataSets {
289 | backgroundColor?: ChartColor | ChartColor[] | Scriptable;
290 | barPercentage?: number;
291 | borderColor?: ChartColor | ChartColor[] | Scriptable;
292 | borderWidth?: BorderWidth | BorderWidth[] | Scriptable;
293 | color?: ChartColor | ChartColor[] | Scriptable;
294 | hoverBackgroundColor?: ChartColor | ChartColor[] | Scriptable;
295 | hoverBorderWidth?: number | number[] | Scriptable;
296 | borderSkipped?: PositionType | PositionType[] | Scriptable;
297 |
298 | constructor(fields: HorizontalBarDataSetStyle) {
299 | super(fields);
300 | this.type = ChartTypes.HorizontalBar;
301 | this.borderWidth = fields.borderWidth || 0;
302 | this.barPercentage = fields.barPercentage || 0.5;
303 | this.backgroundColor =
304 | fields.backgroundColor || fields.color || "rgba(0,0,0,.1)";
305 | this.hoverBorderWidth = fields.hoverBorderWidth || 0;
306 | this.hoverBackgroundColor =
307 | fields.hoverBackgroundColor || fields.color || "rgba(0,0,0,.1)";
308 | this.borderSkipped = fields.borderSkipped || (false as any);
309 | }
310 | }
311 |
312 | export class HorizontalBarDataSetHCStyle extends HorizontalBarDataSetStyle {
313 | hoverBorderColor?: ChartColor | ChartColor[] | Scriptable;
314 | pattern: IDraw;
315 |
316 | constructor(fields: BarDataSetHCStyle) {
317 | super(fields);
318 | this.pattern = fields.pattern || {
319 | shape: Shapes.Square,
320 | size: 10,
321 | };
322 | this.backgroundColor = buildPattern({
323 | backgroundColor: HighContrastColors.Background,
324 | patternColor: HighContrastColors.Foreground,
325 | ...this.pattern,
326 | }) as any;
327 | this.hoverBackgroundColor = buildPattern({
328 | backgroundColor: HighContrastColors.Background,
329 | patternColor: HighContrastColors.Active,
330 | ...this.pattern,
331 | }) as any;
332 |
333 | this.borderWidth = fields.borderWidth || 1;
334 | this.borderColor = HighContrastColors.Foreground;
335 | this.hoverBorderWidth = fields.hoverBorderWidth || 3;
336 | this.hoverBorderColor = HighContrastColors.Active;
337 | }
338 | }
339 |
340 | export class PieDataSetHCStyle extends PieDataSetStyle {
341 | pattern: IDraw[];
342 |
343 | constructor(fields: PieDataSetHCStyle) {
344 | super(fields);
345 | this.pattern = fields.pattern || [
346 | {
347 | shape: Shapes.Square,
348 | size: 10,
349 | },
350 | ];
351 | this.borderWidth = fields.borderWidth || 3;
352 | this.borderColor = HighContrastColors.Foreground;
353 | this.hoverBorderColor = HighContrastColors.Active;
354 | this.backgroundColor = Array.from(
355 | fields.pattern,
356 | (pat) =>
357 | buildPattern({
358 | backgroundColor: HighContrastColors.Background,
359 | patternColor: HighContrastColors.Foreground,
360 | ...pat,
361 | }) as any
362 | );
363 | this.hoverBackgroundColor = Array.from(
364 | fields.pattern,
365 | (pat) =>
366 | buildPattern({
367 | backgroundColor: HighContrastColors.Background,
368 | patternColor: HighContrastColors.Active,
369 | ...pat,
370 | }) as any
371 | );
372 | }
373 | }
374 |
375 | export class DoughnutDataSetHCStyle extends PieDataSetHCStyle {
376 | constructor(fields: PieDataSetHCStyle) {
377 | super(fields);
378 | }
379 | }
380 |
381 | export class BarDataSetHCStyle extends BarDataSetStyle {
382 | hoverBorderColor?: ChartColor | ChartColor[] | Scriptable;
383 | pattern: IDraw;
384 |
385 | constructor(fields: BarDataSetHCStyle) {
386 | super(fields);
387 | this.pattern = fields.pattern || {
388 | shape: Shapes.Square,
389 | size: 10,
390 | };
391 | this.backgroundColor = buildPattern({
392 | backgroundColor: HighContrastColors.Background,
393 | patternColor: HighContrastColors.Foreground,
394 | ...this.pattern,
395 | }) as any;
396 | this.hoverBackgroundColor = buildPattern({
397 | backgroundColor: HighContrastColors.Background,
398 | patternColor: HighContrastColors.Active,
399 | ...this.pattern,
400 | }) as any;
401 |
402 | this.borderWidth = fields.borderWidth || 1;
403 | this.borderColor = HighContrastColors.Foreground;
404 | this.hoverBorderWidth = fields.hoverBorderWidth || 3;
405 | this.hoverBorderColor = HighContrastColors.Active;
406 | }
407 | }
408 |
--------------------------------------------------------------------------------
/src/lib/patterns.ts:
--------------------------------------------------------------------------------
1 | import { Entity, IChartPatterns, Shapes } from "../types";
2 |
3 | const BACKGROUND_COLOR = "transparent";
4 | const PATTERN_COLOR = "rgba(0, 0, 0, 0.8)";
5 | const POINT_STYLE = "round";
6 | const SIZE = 20;
7 |
8 | export const Patterns = {
9 | Square: {
10 | shape: Shapes.Square,
11 | size: 10,
12 | },
13 | Diagonal: {
14 | shape: Shapes.DiagonalRightLeft,
15 | size: 5,
16 | },
17 | Diagonal2: {
18 | shape: Shapes.Diagonal,
19 | size: 5,
20 | },
21 | Grid: {
22 | shape: Shapes.Grid,
23 | size: 10,
24 | },
25 | Grid2: {
26 | shape: Shapes.GridRightLeft,
27 | size: 3,
28 | },
29 | Line: {
30 | shape: Shapes.VerticalLine,
31 | size: 10,
32 | },
33 | };
34 |
35 | // export const legendLabels = ({
36 | // canvasRef,
37 | // theme,
38 | // colorScheme,
39 | // dataPointColor,
40 | // index,
41 | // patterns,
42 | // }: {
43 | // canvasRef: HTMLCanvasElement;
44 | // theme: ChartTheme;
45 | // colorScheme: any;
46 | // dataPointColor: string;
47 | // index: number;
48 | // patterns?: IChartPatterns;
49 | // }) => {
50 | // if (!canvasRef) return;
51 | // const ctx: any = canvasRef.getContext("2d");
52 | // ctx.save();
53 | // if (!ctx) return;
54 | // if (theme === ChartTheme.HighContrast) {
55 | // if (patterns) {
56 | // ctx.setTransform(1.4, 0, 0, 1, 0, 0);
57 | // ctx.scale(12, 10);
58 | // (ctx.fillStyle as any) = buildPattern({
59 | // ...patterns(colorScheme)[index],
60 | // backgroundColor: colorScheme.default.background,
61 | // patternColor: colorScheme.brand.background,
62 | // });
63 | // ctx.fillRect(-15, -15, canvasRef.width, canvasRef.height);
64 | // ctx.restore();
65 | // } else {
66 | // ctx.scale(15, 15);
67 | // ctx.fillStyle = colorScheme.brand.shadow;
68 | // ctx.fillRect(-15, -15, canvasRef.width, canvasRef.height);
69 | // ctx.fillStyle = colorScheme.default.foreground3;
70 | // switch (lineChartPatterns[index].pointStyle) {
71 | // case PointStyles.Triangle:
72 | // ctx.moveTo(9.5, 2.5);
73 | // ctx.lineTo(5.5, 7.5);
74 | // ctx.lineTo(13.5, 7.5);
75 | // break;
76 | // case PointStyles.Rectangle:
77 | // ctx.rect(6.5, 2.5, 8, 5);
78 | // break;
79 | // case PointStyles.RectangleRotated:
80 | // ctx.moveTo(10, 2);
81 | // ctx.lineTo(14.5, 5);
82 | // ctx.lineTo(10, 8);
83 | // ctx.lineTo(5.5, 5);
84 | // break;
85 | // case PointStyles.Circle:
86 | // default:
87 | // ctx.ellipse(10, 5, 3.5, 2.5, 0, 0, 2 * Math.PI);
88 | // break;
89 | // }
90 | // ctx.fill();
91 |
92 | // // Line Style
93 | // ctx.strokeStyle = colorScheme.default.foreground3;
94 | // ctx.beginPath();
95 | // ctx.setLineDash(
96 | // lineChartPatterns[index].lineBorderDash.length ? [2, 2] : []
97 | // );
98 | // ctx.moveTo(-1.5, 5);
99 | // ctx.lineTo(20, 5);
100 | // ctx.stroke();
101 | // ctx.restore();
102 | // }
103 | // } else {
104 | // ctx.fillStyle = dataPointColor;
105 | // ctx.fillRect(0, 0, canvasRef.width, canvasRef.height);
106 | // }
107 | // };
108 |
109 | // export const chartLineStackedDataPointPatterns: IChartPatterns = (
110 | // colorScheme: any
111 | // ) => {
112 | // return [
113 | // {
114 | // shapeType: Shapes.Square,
115 | // size: 10,
116 | // },
117 | // {
118 | // shapeType: Shapes.DiagonalRightLeft,
119 | // size: 5,
120 | // },
121 | // {
122 | // shapeType: Shapes.Grid,
123 | // size: 10,
124 | // },
125 | // {
126 | // shapeType: Shapes.VerticalLine,
127 | // size: 10,
128 | // },
129 | // {
130 | // shapeType: Shapes.GridRightLeft,
131 | // size: 3,
132 | // },
133 | // {
134 | // shapeType: Shapes.Diagonal,
135 | // size: 5,
136 | // },
137 | // ];
138 | // };
139 |
140 | // export const chartBarDataPointPatterns: IChartPatterns = (colorScheme: any) => {
141 | // return [
142 | // {
143 | // shapeType: Shapes.DiagonalRightLeft,
144 | // size: 5,
145 | // },
146 | // {
147 | // shapeType: Shapes.Square,
148 | // size: 10,
149 | // },
150 | // {
151 | // shapeType: Shapes.Diagonal,
152 | // size: 5,
153 | // },
154 | // {
155 | // shapeType: Shapes.Grid,
156 | // size: 10,
157 | // },
158 | // {
159 | // shapeType: Shapes.GridRightLeft,
160 | // size: 3,
161 | // },
162 | // {
163 | // shapeType: Shapes.VerticalLine,
164 | // size: 7,
165 | // },
166 | // ];
167 | // };
168 |
169 | // export const chartBubbleDataPointPatterns: IChartPatterns = (
170 | // colorScheme: any
171 | // ) => {
172 | // return [
173 | // {
174 | // shapeType: Shapes.DiagonalRightLeft,
175 | // size: 5,
176 | // },
177 | // {
178 | // shapeType: Shapes.Square,
179 | // size: 10,
180 | // },
181 | // {
182 | // shapeType: Shapes.Diagonal,
183 | // size: 5,
184 | // },
185 | // {
186 | // shapeType: Shapes.Grid,
187 | // size: 10,
188 | // },
189 | // {
190 | // shapeType: Shapes.GridRightLeft,
191 | // size: 3,
192 | // },
193 | // {
194 | // shapeType: Shapes.VerticalLine,
195 | // size: 7,
196 | // },
197 | // ];
198 | // };
199 |
200 | export class Shape extends Entity {
201 | canvas?: HTMLCanvasElement;
202 | context?: CanvasRenderingContext2D | null;
203 | size = SIZE;
204 | backgroundColor: string = BACKGROUND_COLOR;
205 | patternColor: string = PATTERN_COLOR;
206 |
207 | constructor(fields: Partial) {
208 | super(fields);
209 |
210 | if (fields.size) {
211 | this.size = fields.size;
212 | }
213 | if (fields.backgroundColor) {
214 | this.backgroundColor = fields.backgroundColor;
215 | }
216 | if (fields.patternColor) {
217 | this.patternColor = fields.patternColor;
218 | }
219 |
220 | this.canvas = document.createElement("canvas");
221 | this.context = this.canvas.getContext("2d");
222 |
223 | this.canvas.width = this.size;
224 | this.canvas.height = this.size;
225 |
226 | if (this.context) {
227 | this.context.fillStyle = this.backgroundColor;
228 | this.context.fillRect(0, 0, this.canvas.width, this.canvas.height);
229 | }
230 | }
231 |
232 | setStrokeProps() {
233 | if (this.context) {
234 | this.context.strokeStyle = this.patternColor;
235 | this.context.lineWidth = this.size / 10;
236 | this.context.lineJoin = POINT_STYLE;
237 | this.context.lineCap = POINT_STYLE;
238 | }
239 | }
240 |
241 | setFillProps() {
242 | if (this.context) {
243 | this.context.fillStyle = this.patternColor;
244 | }
245 | }
246 | }
247 |
248 | class Square extends Shape {
249 | drawTile() {
250 | const halfSize = this.size / 2;
251 | if (this.context) {
252 | this.context.beginPath();
253 | this.setFillProps();
254 | this.drawSquare();
255 | this.drawSquare(halfSize, halfSize);
256 | this.context.fill();
257 | }
258 | return this.canvas;
259 | }
260 |
261 | drawSquare(offsetX = 0, offsetY = 0) {
262 | const halfSize = this.size / 2;
263 | const gap = this.size / 5;
264 | this.context!.fillRect(
265 | offsetX + gap,
266 | offsetY + gap,
267 | halfSize - gap * 2,
268 | halfSize - gap * 2
269 | );
270 | this.context!.closePath();
271 | }
272 | }
273 |
274 | class Diagonal extends Shape {
275 | drawTile() {
276 | const halfSize = this.size / 2;
277 |
278 | if (this.context) {
279 | this.context.beginPath();
280 |
281 | this.setStrokeProps();
282 |
283 | this.drawDiagonalLine();
284 | this.drawDiagonalLine(halfSize, halfSize);
285 |
286 | this.context.stroke();
287 | return this.canvas;
288 | }
289 | }
290 |
291 | drawDiagonalLine(offsetX = 0, offsetY = 0) {
292 | const size = this.size;
293 | const halfSize = size / 2;
294 | const gap = 1;
295 |
296 | if (this.context) {
297 | this.context.moveTo(halfSize - gap - offsetX, gap * -1 + offsetY);
298 | this.context.lineTo(size + 1 - offsetX, halfSize + 1 + offsetY);
299 |
300 | this.context.closePath();
301 | }
302 | }
303 | }
304 |
305 | class DiagonalRightLeft extends Diagonal {
306 | drawTile() {
307 | if (this.context) {
308 | this.context.translate(this.size, 0);
309 | this.context.rotate((90 * Math.PI) / 180);
310 |
311 | Diagonal.prototype.drawTile.call(this);
312 |
313 | return this.canvas;
314 | }
315 | }
316 | }
317 |
318 | class Grid extends Shape {
319 | drawTile() {
320 | const halfSize = this.size / 2;
321 |
322 | if (this.context) {
323 | this.context.beginPath();
324 |
325 | this.setStrokeProps();
326 |
327 | // this.drawDiagonalLine();
328 | // this.drawDiagonalLine(halfSize, halfSize);
329 |
330 | this.drawOpositeDiagonalLine();
331 | this.drawOpositeDiagonalLine(halfSize, halfSize);
332 |
333 | this.context.stroke();
334 | }
335 |
336 | return this.canvas;
337 | }
338 |
339 | drawDiagonalLine(offsetX = 0, offsetY = 0) {
340 | const size = this.size;
341 | const halfSize = size / 2;
342 | const gap = 1;
343 |
344 | if (this.context) {
345 | this.context.moveTo(halfSize - gap - offsetX, gap * -1 + offsetY);
346 | this.context.lineTo(size + 1 - offsetX, halfSize + 1 + offsetY);
347 |
348 | this.context.closePath();
349 | }
350 | }
351 |
352 | drawOpositeDiagonalLine(offsetX = 0, offsetY = 0) {
353 | const size = this.size;
354 | const halfSize = size / 2;
355 | const gap = 1;
356 |
357 | if (this.context) {
358 | this.context.moveTo(halfSize - gap + offsetX, gap * -1 - offsetY);
359 | this.context.lineTo(size + 1 + offsetX, halfSize + 1 - offsetY);
360 |
361 | this.context.closePath();
362 | }
363 | }
364 | }
365 |
366 | class Line extends Shape {
367 | drawTile() {
368 | if (this.context) {
369 | const halfSize = this.size / 2;
370 |
371 | this.context.beginPath();
372 |
373 | this.setStrokeProps();
374 |
375 | this.drawLine();
376 | this.drawLine(halfSize, halfSize);
377 |
378 | this.context.stroke();
379 |
380 | return this.canvas;
381 | }
382 | }
383 |
384 | drawLine(offsetX = 0, offsetY = 0) {
385 | if (this.context) {
386 | const size = this.size;
387 | const quarterSize = size / 4;
388 |
389 | this.context.moveTo(0, quarterSize + offsetY);
390 | this.context.lineTo(this.size, quarterSize + offsetY);
391 |
392 | this.context.closePath();
393 | }
394 | }
395 | }
396 |
397 | class VerticalLine extends Line {
398 | drawTile() {
399 | if (this.context) {
400 | this.context.translate(this.size, 0);
401 | this.context.rotate((90 * Math.PI) / 180);
402 |
403 | Line.prototype.drawTile.call(this);
404 |
405 | return this.canvas;
406 | }
407 | }
408 | }
409 |
410 | class GridRightLeft extends Grid {
411 | drawTile() {
412 | if (this.context) {
413 | this.context.translate(this.size, 0);
414 | this.context.rotate((90 * Math.PI) / 180);
415 |
416 | Grid.prototype.drawTile.call(this);
417 |
418 | return this.canvas;
419 | }
420 | }
421 | }
422 |
423 | const shapes = {
424 | [Shapes.Square]: Square,
425 | [Shapes.DiagonalRightLeft]: DiagonalRightLeft,
426 | [Shapes.Grid]: Grid,
427 | [Shapes.Diagonal]: Diagonal,
428 | [Shapes.VerticalLine]: VerticalLine,
429 | [Shapes.GridRightLeft]: GridRightLeft,
430 | };
431 |
432 | export function buildPattern({
433 | shape,
434 | backgroundColor,
435 | patternColor,
436 | size,
437 | }: {
438 | shape: Shapes;
439 | size: number;
440 | backgroundColor: string;
441 | patternColor: string;
442 | }) {
443 | const patternCanvas = document.createElement("canvas");
444 | const patternContext = patternCanvas.getContext("2d");
445 | const outerSize = size * 2;
446 |
447 | const Shape = shapes[shape];
448 | const _shape = new Shape({ size, backgroundColor, patternColor });
449 |
450 | const pattern: CanvasPattern | null = patternContext!.createPattern(
451 | _shape.drawTile()!,
452 | "repeat"
453 | );
454 |
455 | patternCanvas.width = outerSize;
456 | patternCanvas.height = outerSize;
457 |
458 | if (pattern) {
459 | (pattern as any).shape = shape;
460 | }
461 |
462 | return pattern;
463 | }
464 |
--------------------------------------------------------------------------------
/src/lib/plugins.ts:
--------------------------------------------------------------------------------
1 | import { ChartDataSets } from "chart.js";
2 | import { LineStackedDataSetHCStyle } from "..";
3 | import { ChartTypes, HighContrastColors, IChartOptions } from "../types";
4 | import { buildPattern } from "./patterns";
5 | import {
6 | HALF_PI,
7 | hexToRgb,
8 | PI,
9 | QUARTER_PI,
10 | showTooltipOnKayboard,
11 | TWO_THIRDS_PI,
12 | } from "./utils";
13 |
14 | export const gradientPlugin = (chartInstance: Chart) => {
15 | const { ctx } = chartInstance;
16 | if (!ctx) return;
17 | if (!chartInstance.config.data) return;
18 | if (!chartInstance.config.data.datasets) return;
19 |
20 | chartInstance.config.data.datasets.forEach((set: ChartDataSets) => {
21 | const color = String(set.borderColor);
22 |
23 | /**
24 | * TODO: Add more flexibility with color formats
25 | * Gradient works just with HEX | RGB values;
26 | */
27 |
28 | if (!(color.includes("#") || color.includes("rgb("))) return;
29 | const gradientStroke: any = ctx.createLinearGradient(
30 | 0,
31 | 0,
32 | 0,
33 | ctx.canvas.clientHeight * 0.8
34 | );
35 | const colorRGB = color.includes("#")
36 | ? hexToRgb(color)
37 | : color
38 | .substring(4, color.length - 1)
39 | .replace(/ /g, "")
40 | .split(",");
41 | gradientStroke.addColorStop(0, `rgba(${colorRGB}, .2)`);
42 | gradientStroke.addColorStop(1, `rgba(${colorRGB}, .0)`);
43 | set.backgroundColor = gradientStroke;
44 |
45 | const hoverColor = String(set.hoverBorderColor);
46 | if (!(hoverColor.includes("#") || hoverColor.includes("rgb("))) return;
47 | const hoverGradientStroke = ctx.createLinearGradient(
48 | 0,
49 | 0,
50 | 0,
51 | ctx.canvas.clientHeight * 0.8
52 | );
53 | const hoverColorRGB = hoverColor.includes("#")
54 | ? hexToRgb(hoverColor)
55 | : hoverColor
56 | .substring(4, hoverColor.length - 1)
57 | .replace(/ /g, "")
58 | .split(",");
59 | hoverGradientStroke.addColorStop(0, `rgba(${hoverColorRGB}, .4)`);
60 | hoverGradientStroke.addColorStop(1, `rgba(${hoverColorRGB}, .0)`);
61 | set.hoverBackgroundColor = hoverGradientStroke;
62 | });
63 | };
64 |
65 | export const highLightDataOnHover = (chartInstance: any) => {
66 | const { chart, ctx, tooltip, config } = chartInstance;
67 |
68 | if (tooltip._active && tooltip._active.length) {
69 | const activePoint = tooltip._active[0],
70 | y = activePoint.tooltipPosition().y,
71 | x = activePoint.tooltipPosition().x,
72 | x_axis = chart.scales["x-axis-0"],
73 | y_axis = chart.scales["y-axis-0"],
74 | leftX = x_axis.left,
75 | rightX = x_axis.right,
76 | topY = y_axis.top,
77 | bottomY = y_axis.bottom;
78 |
79 | switch (config.type) {
80 | case ChartTypes.Line:
81 | ctx.save();
82 | // Line
83 | ctx.beginPath();
84 | ctx.moveTo(leftX - 44, y);
85 | ctx.lineTo(rightX, y);
86 | ctx.setLineDash([5, 5]);
87 | ctx.lineWidth = 1;
88 | ctx.strokeStyle = chart.options.scales.yAxes[0].gridLines.color;
89 | ctx.stroke();
90 |
91 | // Point
92 | const pointStyle =
93 | activePoint._chart.config.data.datasets[activePoint._datasetIndex]
94 | .pointStyle;
95 | if (pointStyle) {
96 | const radius = 5;
97 | const rotation = 0;
98 | let xOffset = 0;
99 | let yOffset = 0;
100 | let cornerRadius = 1;
101 | let size = 5;
102 | let rad = 0;
103 |
104 | ctx.beginPath();
105 | ctx.setLineDash([]);
106 | switch (pointStyle) {
107 | // Default includes circle
108 | default:
109 | ctx.arc(x, y, radius, 0, Math.PI * 2, true);
110 | ctx.closePath();
111 | break;
112 | case "triangle":
113 | ctx.moveTo(
114 | x + Math.sin(rad) * radius,
115 | y - Math.cos(rad) * radius
116 | );
117 | rad += TWO_THIRDS_PI;
118 | ctx.lineTo(
119 | x + Math.sin(rad) * radius,
120 | y - Math.cos(rad) * radius
121 | );
122 | rad += TWO_THIRDS_PI;
123 | ctx.lineTo(
124 | x + Math.sin(rad) * radius,
125 | y - Math.cos(rad) * radius
126 | );
127 | ctx.closePath();
128 | break;
129 | case "rectRounded":
130 | cornerRadius = radius * 0.516;
131 | size = radius - cornerRadius;
132 | xOffset = Math.cos(rad + QUARTER_PI) * size;
133 | yOffset = Math.sin(rad + QUARTER_PI) * size;
134 | ctx.arc(
135 | x - xOffset,
136 | y - yOffset,
137 | cornerRadius,
138 | rad - PI,
139 | rad - HALF_PI
140 | );
141 | ctx.arc(
142 | x + yOffset,
143 | y - xOffset,
144 | cornerRadius,
145 | rad - HALF_PI,
146 | rad
147 | );
148 | ctx.arc(
149 | x + xOffset,
150 | y + yOffset,
151 | cornerRadius,
152 | rad,
153 | rad + HALF_PI
154 | );
155 | ctx.arc(
156 | x - yOffset,
157 | y + xOffset,
158 | cornerRadius,
159 | rad + HALF_PI,
160 | rad + PI
161 | );
162 | ctx.closePath();
163 | break;
164 | case "rect":
165 | if (!rotation) {
166 | size = Math.SQRT1_2 * radius;
167 | ctx.rect(x - size, y - size, 2 * size, 2 * size);
168 | break;
169 | }
170 | rad += QUARTER_PI;
171 | /* falls through */
172 | case "rectRot":
173 | xOffset = Math.cos(rad) * radius;
174 | yOffset = Math.sin(rad) * radius;
175 | ctx.moveTo(x - xOffset, y - yOffset);
176 | ctx.lineTo(x + yOffset, y - xOffset);
177 | ctx.lineTo(x + xOffset, y + yOffset);
178 | ctx.lineTo(x - yOffset, y + xOffset);
179 | ctx.closePath();
180 | break;
181 | case "crossRot":
182 | rad += QUARTER_PI;
183 | /* falls through */
184 | case "cross":
185 | xOffset = Math.cos(rad) * radius;
186 | yOffset = Math.sin(rad) * radius;
187 | ctx.moveTo(x - xOffset, y - yOffset);
188 | ctx.lineTo(x + xOffset, y + yOffset);
189 | ctx.moveTo(x + yOffset, y - xOffset);
190 | ctx.lineTo(x - yOffset, y + xOffset);
191 | break;
192 | case "star":
193 | xOffset = Math.cos(rad) * radius;
194 | yOffset = Math.sin(rad) * radius;
195 | ctx.moveTo(x - xOffset, y - yOffset);
196 | ctx.lineTo(x + xOffset, y + yOffset);
197 | ctx.moveTo(x + yOffset, y - xOffset);
198 | ctx.lineTo(x - yOffset, y + xOffset);
199 | rad += QUARTER_PI;
200 | xOffset = Math.cos(rad) * radius;
201 | yOffset = Math.sin(rad) * radius;
202 | ctx.moveTo(x - xOffset, y - yOffset);
203 | ctx.lineTo(x + xOffset, y + yOffset);
204 | ctx.moveTo(x + yOffset, y - xOffset);
205 | ctx.lineTo(x - yOffset, y + xOffset);
206 | break;
207 | case "line":
208 | xOffset = Math.cos(rad) * radius;
209 | yOffset = Math.sin(rad) * radius;
210 | ctx.moveTo(x - xOffset, y - yOffset);
211 | ctx.lineTo(x + xOffset, y + yOffset);
212 | break;
213 | case "dash":
214 | ctx.moveTo(x, y);
215 | ctx.lineTo(
216 | x + Math.cos(rad) * radius,
217 | y + Math.sin(rad) * radius
218 | );
219 | break;
220 | }
221 | ctx.lineWidth = 2;
222 | ctx.fillStyle = HighContrastColors.Foreground;
223 | ctx.strokeStyle =
224 | chart.data.datasets[activePoint._datasetIndex].hoverBorderColor;
225 | ctx.closePath();
226 | ctx.fill();
227 | ctx.stroke();
228 | ctx.restore();
229 | }
230 | break;
231 | case ChartTypes.Bar:
232 | default:
233 | ctx.save();
234 | // Line
235 | ctx.beginPath();
236 | ctx.moveTo(leftX - 20, y);
237 | ctx.lineTo(rightX, y);
238 | ctx.setLineDash([5, 5]);
239 | ctx.lineWidth = 1;
240 | ctx.strokeStyle = chart.options.scales.yAxes[0].gridLines.color;
241 | ctx.stroke();
242 | ctx.restore();
243 | break;
244 | }
245 | }
246 | };
247 |
248 | export const keyboardAccessibility = (chart: Chart) => {
249 | const { canvas, config } = chart;
250 | const { data, type } = config;
251 | let selectedIndex = -1;
252 | let selectedDataSet = 0;
253 | const orderedDataSet =
254 | type === ChartTypes.Line || !(config.options?.scales as any);
255 |
256 | if (!canvas) return;
257 | canvas.addEventListener("click", removeFocusStyleOnClick);
258 | canvas.addEventListener("keydown", changeFocus);
259 | canvas.addEventListener("focusout", resetChartStates);
260 |
261 | function meta() {
262 | return chart.getDatasetMeta(selectedDataSet);
263 | }
264 |
265 | function removeFocusStyleOnClick(): void {
266 | // Remove focus state style if selected by mouse
267 | if (canvas) {
268 | canvas.style.boxShadow = "none";
269 | }
270 | }
271 |
272 | function removeDataPointsHoverStates() {
273 | if (selectedIndex > -1) {
274 | meta().controller.removeHoverStyle(
275 | meta().data[selectedIndex],
276 | 0,
277 | selectedIndex
278 | );
279 | }
280 | }
281 |
282 | function hoverDataPoint(pointID: number) {
283 | meta().controller.setHoverStyle(
284 | meta().data[pointID],
285 | selectedDataSet,
286 | pointID
287 | );
288 | }
289 |
290 | function showFocusedDataPoint() {
291 | hoverDataPoint(selectedIndex);
292 | if (chart.config.data) {
293 | showTooltipOnKayboard({
294 | chart,
295 | setIndex: selectedDataSet,
296 | selectedPointIndex: selectedIndex,
297 | });
298 | }
299 | document
300 | .getElementById(
301 | `${canvas!.id}-tooltip-${selectedDataSet}-${selectedIndex}`
302 | )
303 | ?.focus();
304 | }
305 |
306 | function resetChartStates() {
307 | removeDataPointsHoverStates();
308 | const activeElements = (chart as any).tooltip._active;
309 | const requestedElem = chart.getDatasetMeta(selectedDataSet).data[
310 | selectedIndex
311 | ];
312 | activeElements.find((v: any, i: number) => {
313 | if (requestedElem._index === v._index) {
314 | activeElements.splice(i, 1);
315 | return true;
316 | }
317 | });
318 |
319 | for (let i = 0; i < activeElements.length; i++) {
320 | if (requestedElem._index === activeElements[i]._index) {
321 | activeElements.splice(i, 1);
322 | break;
323 | }
324 | }
325 | if ((config.options as IChartOptions).highContrastMode) {
326 | data!.datasets!.map((dataset: any) => {
327 | dataset.borderColor = HighContrastColors.Foreground;
328 | dataset.borderWidth = 2;
329 | });
330 | const fakeSet = new LineStackedDataSetHCStyle({} as any);
331 | config.data?.datasets?.forEach((set: any) => {
332 | if (typeof set.backgroundColor !== "string") {
333 | set.backgroundColor = buildPattern({
334 | backgroundColor: HighContrastColors.Background,
335 | patternColor: fakeSet.borderColor,
336 | ...set.pattern,
337 | }) as any;
338 | }
339 | });
340 | chart.update();
341 | }
342 | (chart as any).tooltip._active = activeElements;
343 | (chart as any).tooltip.update(true);
344 | (chart as any).draw();
345 | }
346 |
347 | function changeFocus(e: KeyboardEvent) {
348 | if (!data) return;
349 | if (!data.datasets) return;
350 | removeDataPointsHoverStates();
351 | switch (e.key) {
352 | case "ArrowRight":
353 | e.preventDefault();
354 | selectedIndex = (selectedIndex + 1) % meta().data.length;
355 | break;
356 | case "ArrowLeft":
357 | e.preventDefault();
358 | selectedIndex = (selectedIndex || meta().data.length) - 1;
359 | break;
360 | case "ArrowUp":
361 | case "ArrowDown":
362 | e.preventDefault();
363 | if (orderedDataSet) {
364 | if (e.key === "ArrowUp") {
365 | selectedDataSet += 1;
366 | if (selectedDataSet === data.datasets.length) {
367 | selectedDataSet = 0;
368 | }
369 | } else {
370 | selectedDataSet -= 1;
371 | if (selectedDataSet < 0) {
372 | selectedDataSet = data.datasets.length - 1;
373 | }
374 | }
375 | } else {
376 | // Get all values for the current data point
377 | const values = data.datasets.map(
378 | (dataset) => dataset.data![selectedIndex]
379 | );
380 | // Sort an array to define next available number
381 | const sorted = Array.from(new Set(values)).sort(
382 | (a, b) => Number(a) - Number(b)
383 | );
384 | let nextValue =
385 | sorted[
386 | sorted.findIndex((v) => v === values[selectedDataSet]) +
387 | (e.key === "ArrowUp" ? 1 : -1)
388 | ];
389 |
390 | // Find dataset ID by the next higher number after current
391 | let nextDataSet = values.findIndex((v) => v === nextValue);
392 |
393 | // If there is no next number that could selected, get number from oposite side
394 | if (nextDataSet < 0) {
395 | nextDataSet = values.findIndex(
396 | (v) =>
397 | v ===
398 | sorted[e.key === "ArrowUp" ? 0 : data!.datasets!.length - 1]
399 | );
400 | }
401 | selectedDataSet = nextDataSet;
402 | selectedIndex = selectedIndex % meta().data.length;
403 | }
404 | break;
405 | }
406 |
407 | showFocusedDataPoint();
408 | }
409 | };
410 |
411 | export const axisXTeamsStyle = ({ config, ctx }: Chart) => {
412 | const xAxes = (config as any).options?.scales?.xAxes;
413 | const color = xAxes[0].gridLines?.color;
414 | const axesXGridLines = ctx!.createLinearGradient(100, 100, 100, 0);
415 | axesXGridLines.addColorStop(0.01, color);
416 | axesXGridLines.addColorStop(0.01, "transparent");
417 | xAxes.forEach((xAxes: any, index: number) => {
418 | if (index < 1) {
419 | xAxes.gridLines.color = axesXGridLines;
420 | xAxes.gridLines.zeroLineColor = axesXGridLines;
421 | } else {
422 | xAxes.gridLines.color = "transparent";
423 | }
424 | });
425 | };
426 |
427 | export const removeAllListeners = (chartInstance: Chart) => {
428 | const { canvas } = chartInstance;
429 | if (!canvas) return;
430 | canvas.replaceWith(canvas.cloneNode(true));
431 | };
432 |
433 | /**
434 | * Required review
435 | */
436 | export const tooltipAxisXLine = ({ chart, ctx, tooltip }: any) => {
437 | if (tooltip._active && tooltip._active.length) {
438 | const activePoint = tooltip._active[0],
439 | y = activePoint.tooltipPosition().y,
440 | x_axis = chart.scales["x-axis-0"],
441 | leftX = x_axis.left,
442 | rightX = x_axis.right;
443 |
444 | ctx.save();
445 | // Line
446 | ctx.beginPath();
447 | ctx.moveTo(leftX - 20, y);
448 | ctx.lineTo(rightX, y);
449 | ctx.setLineDash([5, 5]);
450 | ctx.lineWidth = 1;
451 | ctx.strokeStyle = chart.options.scales.yAxes[0].gridLines.color;
452 | ctx.stroke();
453 | ctx.restore();
454 | }
455 | };
456 |
457 | export const horizontalBarValue = ({ chart, ctx, config }: any) => {
458 | ctx.font = "bold 11px Segoe UI, system-ui, sans-serif";
459 | ctx.textAlign = "left";
460 | ctx.textBaseline = "middle";
461 | ctx.fillStyle = config.options.scales.xAxes[0].ticks.fontColor;
462 | if (config.options.scales.yAxes[0].stacked) {
463 | const meta = chart.controller.getDatasetMeta(
464 | chart.data.datasets.length - 1
465 | );
466 | meta.data.forEach((bar: any, index: number) => {
467 | let data = 0;
468 | chart.data.datasets.map((dataset: ChartDataSets) => {
469 | const value = dataset!.data![index];
470 | if (typeof value === "number") {
471 | return (data += value);
472 | }
473 | });
474 | ctx.fillText(data, bar._model.x + 8, bar._model.y);
475 | });
476 | } else {
477 | chart.data.datasets.forEach((dataset: any, i: number) => {
478 | const meta = chart.controller.getDatasetMeta(i);
479 | meta.data.forEach((bar: any, index: number) => {
480 | const data = dataset.data[index];
481 | ctx.fillText(data, bar._model.x + 8, bar._model.y);
482 | });
483 | });
484 | }
485 | };
486 |
487 | export const horizontalBarAxisYLabels = (chartInstance: Chart) => {
488 | const { config, chartArea } = chartInstance;
489 | const { options, data } = config;
490 |
491 | if (!options) return;
492 | if (!chartArea) return;
493 | if (!options.scales) return;
494 | if (!options.scales.yAxes) return;
495 | if (!options.scales.yAxes[0]) return;
496 | if (!options.scales.yAxes[0].ticks) return;
497 |
498 | options.scales.yAxes[0].ticks.labelOffset =
499 | chartArea.bottom / data!.datasets![0]!.data!.length / 2 - 10;
500 | chartInstance.update();
501 | };
502 |
--------------------------------------------------------------------------------
/src/lib/settings.ts:
--------------------------------------------------------------------------------
1 | import { ChartDataSets } from "chart.js";
2 | import { HighContrastColors, IChartOptions } from "../types";
3 | import { usNumberFormat, shortTicks } from "./utils";
4 | import "chartjs-plugin-deferred";
5 |
6 | const stackedTooltipTitle = (
7 | item: Chart.ChartTooltipItem[],
8 | data: Chart.ChartData
9 | ): string => {
10 | let total = 0;
11 | if (!item) return "";
12 | if (!data) return "";
13 | if (!data.datasets) return "";
14 | data.datasets.map((dataset: ChartDataSets) => {
15 | const value = dataset!.data![item[0].index!];
16 | if (typeof value === "number") {
17 | return (total += value);
18 | }
19 | });
20 | return `${((Number(item[0].yLabel) / total) * 100).toPrecision(
21 | 2
22 | )}% (${usNumberFormat(Number(item[0].yLabel))})`;
23 | };
24 |
25 | export const defaultOptions: IChartOptions = {
26 | responsive: true,
27 | maintainAspectRatio: false,
28 | defaultColor: "#C8C6C4",
29 | legend: {
30 | display: false,
31 | custom: true,
32 | },
33 | animation: {
34 | duration: 1000,
35 | },
36 | layout: {
37 | padding: {
38 | left: 0,
39 | right: 16,
40 | top: 0,
41 | bottom: 0,
42 | },
43 | },
44 | elements: {
45 | line: {
46 | tension: 0.4,
47 | },
48 | },
49 | hover: {
50 | mode: "dataset",
51 | intersect: false,
52 | },
53 | tooltips: {
54 | yPadding: 12,
55 | xPadding: 20,
56 | caretPadding: 10,
57 | // Tooltip Title
58 | titleFontStyle: "200",
59 | titleFontSize: 20,
60 | // Tooltip Body
61 | bodySpacing: 4,
62 | bodyFontSize: 11.5,
63 | bodyFontStyle: "400",
64 | // Tooltip Footer
65 | footerFontStyle: "300",
66 | footerFontSize: 10,
67 |
68 | backgroundColor: "rgba(0, 0, 0, 0.88)",
69 |
70 | callbacks: {
71 | title: (tooltipItems: any) => {
72 | const value = tooltipItems[0].yLabel;
73 | return typeof value === "number" && value > 999
74 | ? usNumberFormat(value)
75 | : value;
76 | },
77 | label: (tooltipItem: any, data: any) =>
78 | data.datasets[tooltipItem.datasetIndex].label,
79 | footer: (tooltipItems: any) => {
80 | const value = tooltipItems[0].xLabel;
81 | return typeof value === "number" && value > 999
82 | ? usNumberFormat(value)
83 | : value;
84 | },
85 | },
86 | },
87 | scales: {
88 | xAxes: [
89 | {
90 | ticks: {
91 | fontSize: 10,
92 | padding: 0,
93 | maxRotation: 0,
94 | minRotation: 0,
95 | callback: shortTicks,
96 | fontColor: "#605E5C",
97 | },
98 | gridLines: {
99 | borderDash: [5, 9999],
100 | zeroLineBorderDash: [5, 9999],
101 | color: "#E1DFDD",
102 | },
103 | },
104 | ],
105 | yAxes: [
106 | {
107 | ticks: {
108 | callback: shortTicks,
109 | fontSize: 10,
110 | padding: -16,
111 | labelOffset: 10,
112 | maxTicksLimit: 5,
113 | fontColor: "#605E5C",
114 | },
115 | gridLines: {
116 | lineWidth: 1,
117 | drawBorder: false,
118 | drawTicks: true,
119 | tickMarkLength: 44,
120 | zeroLineColor: "#E1DFDD",
121 | color: "#E1DFDD",
122 | },
123 | },
124 | ],
125 | },
126 | plugins: {
127 | deferred: {
128 | delay: 150, // delay of 500 ms after the canvas is considered inside the viewport
129 | },
130 | },
131 | };
132 |
133 | export const highContrastOptions: IChartOptions = {
134 | highContrastMode: true,
135 | defaultColor: HighContrastColors.Foreground,
136 | tooltips: {
137 | backgroundColor: HighContrastColors.Background,
138 | borderColor: HighContrastColors.Active,
139 | multiKeyBackground: "transparent",
140 | titleFontColor: HighContrastColors.Foreground,
141 | bodyFontColor: HighContrastColors.Foreground,
142 | footerFontColor: HighContrastColors.Foreground,
143 | borderWidth: 2,
144 | displayColors: false,
145 | },
146 | hover: {
147 | mode: "dataset",
148 | intersect: false,
149 | },
150 | scales: {
151 | xAxes: [
152 | {
153 | ticks: {
154 | fontColor: HighContrastColors.Foreground,
155 | },
156 | },
157 | ],
158 | yAxes: [
159 | {
160 | ticks: {
161 | fontColor: HighContrastColors.Foreground,
162 | },
163 | gridLines: {
164 | zeroLineColor: "rgba(255,255,255, .3)",
165 | color: "rgba(255,255,255, .3)",
166 | },
167 | },
168 | ],
169 | },
170 | };
171 |
172 | export const stackedLineOptions: IChartOptions = {
173 | scales: {
174 | yAxes: [
175 | {
176 | stacked: true,
177 | },
178 | ],
179 | },
180 | tooltips: {
181 | callbacks: {
182 | title: stackedTooltipTitle,
183 | labelColor: (tooltipItem: any, chart: any) => {
184 | return {
185 | borderColor: "transparent",
186 | backgroundColor:
187 | chart.config.data.datasets[tooltipItem.datasetIndex]
188 | .backgroundColor,
189 | } as any;
190 | },
191 | },
192 | },
193 | };
194 |
195 | export const trendLineOptions: IChartOptions = {
196 | hover: {
197 | mode: "nearest",
198 | intersect: false,
199 | },
200 | legend: {
201 | display: false,
202 | custom: false,
203 | },
204 | layout: {
205 | padding: {
206 | left: -40,
207 | right: 0,
208 | top: 0,
209 | bottom: 0,
210 | },
211 | },
212 | scales: {
213 | xAxes: [
214 | {
215 | ticks: {
216 | display: false,
217 | },
218 | gridLines: {
219 | display: false,
220 | },
221 | },
222 | ],
223 | yAxes: [
224 | {
225 | ticks: {
226 | display: false,
227 | },
228 | gridLines: {
229 | display: false,
230 | },
231 | },
232 | ],
233 | },
234 | plugins: {
235 | deferred: {
236 | xOffset: 150, // defer until 150px of the canvas width are inside the viewport
237 | yOffset: "100%", // defer until 50% of the canvas height are inside the viewport
238 | delay: 150, // delay of 500 ms after the canvas is considered inside the viewport
239 | },
240 | },
241 | };
242 |
243 | export const barOptions: IChartOptions = {
244 | hover: {
245 | mode: "nearest",
246 | intersect: false,
247 | },
248 | scales: {
249 | xAxes: [
250 | {
251 | gridLines: {
252 | offsetGridLines: false,
253 | },
254 | },
255 | ],
256 | },
257 | };
258 |
259 | export const pieOptions: IChartOptions = {
260 | layout: {
261 | padding: {
262 | top: 32,
263 | right: 32,
264 | bottom: 32,
265 | left: -16,
266 | },
267 | },
268 | hover: {
269 | mode: "point",
270 | intersect: false,
271 | },
272 | scales: {
273 | xAxes: [
274 | {
275 | ticks: {
276 | display: false,
277 | },
278 | gridLines: {
279 | display: false,
280 | },
281 | },
282 | ],
283 | yAxes: [
284 | {
285 | ticks: {
286 | display: false,
287 | },
288 | gridLines: {
289 | display: false,
290 | },
291 | },
292 | ],
293 | },
294 | tooltips: {
295 | callbacks: {
296 | title: (tooltipItems: any, data: any) => {
297 | return `${(
298 | (Number(data.datasets[0].data[tooltipItems[0].index]) /
299 | (data.datasets[0].data as number[]).reduce((a, b) => a + b)) *
300 | 100
301 | ).toPrecision(2)}% (${usNumberFormat(
302 | Number(data.datasets[0].data[tooltipItems[0].index])
303 | )})`;
304 | },
305 | label: (tooltipItem: any, data: any) => data.labels[tooltipItem.index],
306 | },
307 | },
308 | };
309 |
310 | export const doughnutOptions: IChartOptions = {
311 | cutoutPercentage: 70,
312 | ...pieOptions,
313 | };
314 |
315 | export const groupedBarOptions: IChartOptions = {
316 | scales: {
317 | xAxes: [
318 | {
319 | gridLines: {
320 | offsetGridLines: true,
321 | },
322 | },
323 | ],
324 | },
325 | };
326 |
327 | export const stackedBarOptions: IChartOptions = {
328 | hover: {
329 | mode: "nearest",
330 | intersect: false,
331 | },
332 | scales: {
333 | xAxes: [
334 | {
335 | stacked: true,
336 | gridLines: {
337 | offsetGridLines: false,
338 | },
339 | },
340 | ],
341 | yAxes: [{ stacked: true }],
342 | },
343 | tooltips: {
344 | callbacks: {
345 | title: stackedTooltipTitle,
346 | },
347 | },
348 | };
349 |
350 | export const horizontalBarOptions: IChartOptions = {
351 | layout: {
352 | padding: {
353 | top: -6,
354 | left: -32,
355 | },
356 | },
357 | hover: {
358 | mode: "index",
359 | intersect: false,
360 | },
361 | scales: {
362 | xAxes: [
363 | {
364 | ticks: {
365 | display: false,
366 | },
367 | gridLines: {
368 | display: false,
369 | },
370 | },
371 | ],
372 | yAxes: [
373 | {
374 | ticks: {
375 | mirror: true,
376 | padding: 0,
377 | callback: (v: string) => v,
378 | labelOffset: 26,
379 | },
380 | gridLines: {
381 | display: false,
382 | },
383 | },
384 | ],
385 | },
386 | tooltips: {
387 | position: "nearest",
388 | },
389 | };
390 |
--------------------------------------------------------------------------------
/src/lib/storybook.tsx:
--------------------------------------------------------------------------------
1 | import { StoryFn } from "@storybook/addons";
2 | import React, { ReactNode } from "react";
3 |
4 | export interface IChartsProvider {
5 | children: ReactNode;
6 | }
7 |
8 | export const ChartsProvider = ({ children }: IChartsProvider) => {
9 | return (
10 | <>
11 |
23 | {children}
24 | >
25 | );
26 | };
27 |
28 | export interface IStorybookThemeProviderProps {
29 | children: ReactNode;
30 | }
31 |
32 | export const StorybookThemeProvider = ({
33 | children,
34 | }: IStorybookThemeProviderProps) => {children};
35 |
36 | export const withStorybookTheme = (storyFn: StoryFn) => (
37 | {storyFn()}
38 | );
39 |
--------------------------------------------------------------------------------
/src/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { ChartDataSets } from "chart.js";
2 | import { ChartTypes, HighContrastColors, IChartOptions } from "../types";
3 | import { buildPattern } from "./patterns";
4 | import { LineDataSetHCStyle, LineStackedDataSetHCStyle } from "./datasets";
5 |
6 | export const PI = Math.PI;
7 | export const HALF_PI = PI / 2;
8 | export const QUARTER_PI = PI / 4;
9 | export const TWO_THIRDS_PI = (PI * 2) / 3;
10 |
11 | export const random = (min: number, max: number): number =>
12 | Math.round(Math.random() * (max - min) + min);
13 |
14 | // TODO: Localization
15 | const suffixes = ["K", "M", "G", "T", "P", "E"];
16 |
17 | export const shortTicks = (value: number | string): string => {
18 | if (typeof value === "number") {
19 | if (value < 1000) {
20 | return String(value);
21 | }
22 | const exp = Math.floor(Math.log(Number(value)) / Math.log(1000));
23 | value = `${Number(value) / Math.pow(1000, exp)}${suffixes[exp - 1]}`;
24 | // There is no support for label aligment in Chart.js,
25 | // to be able align axis labels by left (right is by default)
26 | // add an additional spaces depends on label length
27 | switch (value.length) {
28 | case 2:
29 | return value + " ";
30 | case 1:
31 | return value + " ";
32 | case 3:
33 | default:
34 | return value;
35 | }
36 | } else {
37 | return value;
38 | }
39 | };
40 |
41 | export const hexToRgb = (hex: string) => {
42 | if (hex.length < 6) {
43 | hex = `#${hex[1]}${hex[1]}${hex[2]}${hex[2]}${hex[3]}${hex[3]}`;
44 | }
45 | const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
46 | return result
47 | ? `${parseInt(result[1], 16)}, ${parseInt(result[2], 16)}, ${parseInt(
48 | result[3],
49 | 16
50 | )}`
51 | : null;
52 | };
53 |
54 | export const getRgbValues = (color: string) => {
55 | if (color.indexOf("#")) {
56 | return hexToRgb(color);
57 | }
58 | if (color.indexOf("rgba")) {
59 | return color
60 | .substring(5, color.length - 1)
61 | .replace(/ /g, "")
62 | .split(",");
63 | }
64 | if (color.indexOf("rgb")) {
65 | return color
66 | .substring(4, color.length - 1)
67 | .replace(/ /g, "")
68 | .split(",");
69 | }
70 | };
71 |
72 | export const usNumberFormat = (value: number | string): string =>
73 | String(value)
74 | .split("")
75 | .reverse()
76 | .join("")
77 | .replace(/(\d{3})/g, "$1,")
78 | .replace(/\,$/, "")
79 | .split("")
80 | .reverse()
81 | .join("");
82 |
83 | export function isHCThemeApplied(chart: any): boolean {
84 | return (
85 | typeof chart.data.datasets[0].backgroundColor !== "string" ||
86 | chart.data.datasets[0].borderDash
87 | );
88 | }
89 |
90 | export function showTooltipOnKayboard({
91 | chart,
92 | setIndex,
93 | selectedPointIndex,
94 | }: {
95 | chart: Chart;
96 | setIndex: number;
97 | selectedPointIndex: number;
98 | }) {
99 | const { type, data, options } = chart.config;
100 | const { datasets } = data as any;
101 | if (
102 | type === ChartTypes.Line ||
103 | !(chart.config.options?.scales as any).yAxes[0].stacked
104 | ) {
105 | const duplicates: number[] = [];
106 | const segments: any[] = [];
107 | const fakeSet = new LineDataSetHCStyle({} as any);
108 | datasets!.filter((dataset: ChartDataSets, i: number) => {
109 | if (
110 | dataset!.data![selectedPointIndex] ===
111 | data!.datasets![setIndex].data![selectedPointIndex]
112 | ) {
113 | duplicates.push(i);
114 | }
115 | if ((options as IChartOptions).highContrastMode) {
116 | datasets[i].borderColor = fakeSet.borderColor;
117 | datasets[i].borderWidth = fakeSet.borderWidth;
118 | if (typeof datasets[i].backgroundColor !== "string") {
119 | datasets[i].backgroundColor = buildPattern({
120 | backgroundColor: HighContrastColors.Background,
121 | patternColor: fakeSet.borderColor,
122 | ...datasets[i].pattern,
123 | });
124 | }
125 | }
126 | });
127 | duplicates.forEach((segmentId) => {
128 | segments.push(chart.getDatasetMeta(segmentId).data[selectedPointIndex]);
129 | if ((options as IChartOptions).highContrastMode) {
130 | datasets[segmentId].borderColor = fakeSet.hoverBorderColor;
131 | datasets[segmentId].borderWidth = fakeSet.hoverBorderWidth;
132 | if (typeof datasets[segmentId].backgroundColor !== "string") {
133 | datasets[segmentId].backgroundColor = buildPattern({
134 | backgroundColor: HighContrastColors.Background,
135 | patternColor: fakeSet.hoverBorderColor,
136 | ...datasets[segmentId].pattern,
137 | });
138 | }
139 | }
140 | });
141 | if ((options as IChartOptions).highContrastMode) {
142 | chart.update();
143 | }
144 | (chart as any).tooltip._active = segments;
145 | } else {
146 | const fakeSet = new LineStackedDataSetHCStyle({} as any);
147 | const segment = chart.getDatasetMeta(setIndex).data[selectedPointIndex];
148 | (chart as any).tooltip._active = [segment];
149 | if ((options as IChartOptions).highContrastMode) {
150 | datasets.map((dataset: any, i: number) => {
151 | if (dataset.pattern) {
152 | dataset.borderColor = fakeSet.borderColor;
153 | dataset.borderWidth = fakeSet.borderWidth;
154 | dataset.backgroundColor = buildPattern({
155 | backgroundColor: HighContrastColors.Background,
156 | patternColor: fakeSet.borderColor,
157 | ...dataset.pattern,
158 | });
159 | }
160 | });
161 | datasets[setIndex].borderColor = fakeSet.hoverBorderColor;
162 | datasets[setIndex].borderWidth = fakeSet.hoverBorderWidth;
163 | datasets[setIndex].backgroundColor = datasets[
164 | setIndex
165 | ].backgroundColor = buildPattern({
166 | backgroundColor: HighContrastColors.Background,
167 | patternColor: fakeSet.hoverBorderColor,
168 | ...datasets[setIndex].pattern,
169 | });
170 | }
171 | }
172 |
173 | chart.update();
174 | (chart as any).tooltip.update();
175 | (chart as any).draw();
176 | }
177 |
178 | // export const setTooltipColorScheme = ({
179 | // chart,
180 | // siteVariables,
181 | // chartDataPointColors,
182 | // patterns,
183 | // verticalDataAlignment,
184 | // }: {
185 | // chart: Chart;
186 | // siteVariables: SiteVariablesPrepared;
187 | // chartDataPointColors: string[];
188 | // patterns?: IChartPatterns;
189 | // verticalDataAlignment?: boolean;
190 | // }) => {
191 | // const { colorScheme } = siteVariables;
192 | // chart.options.tooltips = {
193 | // ...chart.options.tooltips,
194 | // borderColor: colorScheme.default.borderHover,
195 | // multiKeyBackground: colorScheme.white.foreground,
196 | // titleFontColor: colorScheme.default.foreground3,
197 | // bodyFontColor: colorScheme.default.foreground3,
198 | // footerFontColor: colorScheme.default.foreground3,
199 | // // callbacks: {
200 | // // ...chart.options.tooltips?.callbacks,
201 | // // labelColor:
202 | // // patterns && theme === ChartTheme.HighContrast
203 | // // ? (tooltipItem: any) => ({
204 | // // borderColor: "transparent",
205 | // // backgroundColor: buildPattern({
206 | // // ...patterns(colorScheme)[
207 | // // verticalDataAlignment
208 | // // ? tooltipItem.index
209 | // // : tooltipItem.datasetIndex
210 | // // ],
211 | // // backgroundColor: colorScheme.default.background,
212 | // // patternColor: colorScheme.default.borderHover,
213 | // // }) as any,
214 | // // })
215 | // // : (tooltipItem: any) => ({
216 | // // borderColor: "transparent",
217 | // // backgroundColor:
218 | // // chartDataPointColors[
219 | // // verticalDataAlignment
220 | // // ? tooltipItem.index
221 | // // : tooltipItem.datasetIndex
222 | // // ],
223 | // // }),
224 | // // },
225 | // };
226 | // // if (siteVariables.theme === ChartTheme.HighContrast) {
227 | // // (chart as any).options.scales.yAxes[0].gridLines.lineWidth = 0.25;
228 | // // } else {
229 | // // (chart as any).options.scales.yAxes[0].gridLines.lineWidth = 1;
230 | // // }
231 | // };
232 |
233 | export const tooltipConfig = () => ({
234 | yPadding: 12,
235 | xPadding: 20,
236 | caretPadding: 10,
237 | // Tooltip Title
238 | titleFontStyle: "200",
239 | titleFontSize: 20,
240 | // Tooltip Body
241 | bodySpacing: 4,
242 | bodyFontSize: 11.5,
243 | bodyFontStyle: "400",
244 | // Tooltip Footer
245 | footerFontStyle: "300",
246 | footerFontSize: 10,
247 |
248 | callbacks: {
249 | title: (tooltipItems: any) => {
250 | const value = tooltipItems[0].yLabel;
251 | return typeof value === "number" && value > 999
252 | ? usNumberFormat(value)
253 | : value;
254 | },
255 | label: (tooltipItem: any, data: any) =>
256 | data.datasets[tooltipItem.datasetIndex].label,
257 | footer: (tooltipItems: any) => {
258 | const value = tooltipItems[0].xLabel;
259 | return typeof value === "number" && value > 999
260 | ? usNumberFormat(value)
261 | : value;
262 | },
263 | },
264 | });
265 |
266 | export const deepMerge = (
267 | target: any,
268 | source: any,
269 | isMergingArrays = true
270 | ): any => {
271 | target = ((obj) => Object.assign({}, obj))(target);
272 |
273 | const isObject = (obj: any) => obj && typeof obj === "object";
274 |
275 | if (!isObject(target) || !isObject(source)) return source;
276 |
277 | Object.keys(source).forEach((key) => {
278 | const targetValue = target[key];
279 | const sourceValue = source[key];
280 |
281 | if (Array.isArray(targetValue) && Array.isArray(sourceValue))
282 | if (isMergingArrays) {
283 | target[key] = targetValue.map((x, i) =>
284 | sourceValue.length <= i
285 | ? x
286 | : deepMerge(x, sourceValue[i], isMergingArrays)
287 | );
288 | if (sourceValue.length > targetValue.length)
289 | target[key] = target[key].concat(
290 | sourceValue.slice(targetValue.length)
291 | );
292 | } else {
293 | target[key] = targetValue.concat(sourceValue);
294 | }
295 | else if (isObject(targetValue) && isObject(sourceValue))
296 | target[key] = deepMerge(
297 | Object.assign({}, targetValue),
298 | sourceValue,
299 | isMergingArrays
300 | );
301 | else target[key] = sourceValue;
302 | });
303 |
304 | return target;
305 | };
306 |
--------------------------------------------------------------------------------
/src/types/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./types";
2 |
--------------------------------------------------------------------------------
/src/types/types.ts:
--------------------------------------------------------------------------------
1 | import Chart, {
2 | ChartDataSets,
3 | PluginServiceRegistrationOptions,
4 | PointStyle,
5 | Scriptable,
6 | } from "chart.js";
7 | import { Moment } from "moment";
8 | import { LineDataSetHCStyle } from "../lib/datasets";
9 |
10 | export class Entity {
11 | constructor(fields?: any) {
12 | Object.assign(this, fields);
13 | }
14 | }
15 |
16 | export interface IChart extends Chart.ChartConfiguration {
17 | areaLabel: string;
18 | type?: ChartTypes;
19 | canvasProps?: React.HTMLProps;
20 | }
21 |
22 | export interface IChartLegend extends Chart.ChartLegendOptions {
23 | custom: boolean;
24 | }
25 |
26 | export interface IChartOptions extends Chart.ChartOptions {
27 | highContrastMode?: boolean;
28 | legend?: IChartLegend;
29 | }
30 |
31 | export interface IBubbleChartData {
32 | x: number;
33 | y: number;
34 | r: number;
35 | }
36 |
37 | export interface IChartDataSet {
38 | label: string;
39 | data: number[] | IBubbleChartData[];
40 | hidden?: boolean;
41 | color?: string;
42 | }
43 |
44 | export enum ChartTypes {
45 | Line = "line",
46 | Bar = "bar",
47 | HorizontalBar = "horizontalBar",
48 | Pie = "pie",
49 | Doughnut = "doughnut",
50 | Bubble = "bubble",
51 | }
52 |
53 | export enum Point {
54 | Circle = "circle",
55 | Rectangle = "rect",
56 | Triangle = "triangle",
57 | RectangleRotated = "rectRot",
58 | }
59 |
60 | export enum Shapes {
61 | Square = "square",
62 | DiagonalRightLeft = "diagonalRightLeft",
63 | Grid = "grid",
64 | Diagonal = "diagonal",
65 | VerticalLine = "verticalLine",
66 | GridRightLeft = "gridRightLeft",
67 | }
68 |
69 | export interface IDraw {
70 | shape: Shapes;
71 | size: number;
72 | }
73 |
74 | export enum HighContrastColors {
75 | Foreground = "#fff",
76 | Background = "#000",
77 | Active = "#1aebff",
78 | Selected = "#ffff01",
79 | Disabled = "#3ff23f",
80 | }
81 |
82 | export type IChartPatterns = (colorScheme: any) => IDraw[];
83 |
84 | export interface IChartData {
85 | labels: Array<
86 | string | string[] | number | number[] | Date | Date[] | Moment | Moment[]
87 | >;
88 | datasets: ChartDataSets[];
89 | }
90 |
91 | export interface IChartDataHighContrast extends IChartData {
92 | datasets: HighContrastChartDataSets[];
93 | }
94 |
95 | export interface ILineChartDataHighContrast extends IChartData {
96 | datasets: LineDataSetHCStyle[];
97 | }
98 |
99 | export interface LineHighContrastChartDataSets extends ChartDataSets {
100 | borderDash: number[];
101 | pointStyle:
102 | | PointStyle
103 | | HTMLImageElement
104 | | HTMLCanvasElement
105 | | Array
106 | | Scriptable;
107 | }
108 |
109 | export interface HighContrastChartDataSets extends ChartDataSets {
110 | pattern: IDraw | IDraw[];
111 | }
112 |
113 | export interface IChartConfig {
114 | type?: ChartTypes;
115 | data: IChartData;
116 | areaLabel: string;
117 | options?: IChartOptions;
118 | plugins?: PluginServiceRegistrationOptions[];
119 | highContrastMode?: boolean;
120 | }
121 |
122 | export interface IPreSetupConfig {
123 | areaLabel: string;
124 | data: IChartData;
125 | options?: IChartOptions;
126 | plugins?: PluginServiceRegistrationOptions[];
127 | }
128 |
129 | export interface IPreSetupConfigHighContrast extends IPreSetupConfig {
130 | data: IChartDataHighContrast;
131 | }
132 | export interface ILinePreSetupConfigHighContrast extends IPreSetupConfig {
133 | data: ILineChartDataHighContrast;
134 | }
135 |
--------------------------------------------------------------------------------
/stories/area.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Chart } from "../src/chart";
3 | import { IChart, Point } from "../src/types";
4 | import { AreaChart, AreaChartHighContrast } from "../src/lib/builder";
5 | import { customOptions } from "./utils";
6 | import { Container, DarkContainer, HighContrastContainer } from "./components";
7 |
8 | export default {
9 | title: "Charts/Area",
10 | component: Chart,
11 | };
12 |
13 | const datasets = [
14 | {
15 | label: "Laptops",
16 | data: [1860, 7700, 4100, 3012, 2930],
17 | color: "#6264A7",
18 | },
19 | {
20 | label: "Watches",
21 | data: [200, 3600, 480, 5049, 4596],
22 | color: "#C8C6C4",
23 | },
24 | ];
25 |
26 | export const Default = () => {
27 | const config: IChart = new AreaChart({
28 | areaLabel: "Line chart sample",
29 | data: {
30 | labels: ["Jan", "Feb", "March", "April", "May"],
31 | datasets,
32 | },
33 | });
34 | return (
35 |
36 |
37 |
38 | );
39 | };
40 |
41 | const datasetsHighContrast = [
42 | {
43 | label: "Tablets",
44 | data: [860, 6700, 3100, 2012, 1930],
45 | borderDash: [],
46 | pointStyle: Point.Circle,
47 | },
48 | {
49 | label: "Phones",
50 | data: [100, 1600, 180, 3049, 3596],
51 | borderDash: [],
52 | pointStyle: Point.Rectangle,
53 | },
54 | ];
55 |
56 | export const HighContrast = () => {
57 | const config: IChart = new AreaChartHighContrast({
58 | areaLabel: "Line chart sample",
59 | data: {
60 | labels: ["Jan", "Feb", "March", "April", "May"],
61 | datasets: datasetsHighContrast,
62 | },
63 | });
64 | return (
65 |
66 |
67 |
68 | );
69 | };
70 |
71 | const datasetsCustomTheme = [
72 | {
73 | label: "Tablets",
74 | data: [860, 6700, 3100, 2012, 1930],
75 | color: "rgb(255, 99, 132)",
76 | },
77 | {
78 | label: "Phones",
79 | data: [100, 1600, 180, 3049, 3596],
80 | color: "rgb(255, 159, 64)",
81 | },
82 | ];
83 |
84 | export const CustomTheme = () => {
85 | const config: IChart = new AreaChart({
86 | areaLabel: "Line chart sample",
87 | data: {
88 | labels: ["Jan", "Feb", "March", "April", "May"],
89 | datasets: datasetsCustomTheme,
90 | },
91 | options: customOptions,
92 | });
93 | return (
94 |
95 |
96 |
97 | );
98 | };
99 |
--------------------------------------------------------------------------------
/stories/bar.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Chart } from "../src/chart";
3 | import { IChart } from "../src/types";
4 | import { Patterns } from "../src/lib/patterns";
5 | import { BarChart, BarChartHighContrast } from "../src/lib/builder";
6 | import { Container, DarkContainer, HighContrastContainer } from "./components";
7 | import { customOptions } from "./utils";
8 |
9 | export default {
10 | title: "Charts/Bar",
11 | component: Chart,
12 | };
13 |
14 | const datasets = [
15 | {
16 | label: "Tablets",
17 | data: [860, 6700, 3100, 2012, 1930],
18 | color: "#6264A7",
19 | },
20 | ];
21 |
22 | export const Default = () => {
23 | const config: IChart = new BarChart({
24 | areaLabel: "Bar chart sample",
25 | data: {
26 | labels: ["Jan", "Feb", "March", "April", "May"],
27 | datasets,
28 | },
29 | });
30 | return (
31 |
32 |
33 |
34 | );
35 | };
36 |
37 | const datasetsHighContrast = [
38 | {
39 | label: "Tablets",
40 | data: [860, 6700, 3100, 2012, 1930],
41 | pattern: Patterns.Diagonal,
42 | },
43 | ];
44 |
45 | export const HighContrast = () => {
46 | const config: IChart = new BarChartHighContrast({
47 | areaLabel: "Bar chart sample",
48 | data: {
49 | labels: ["Jan", "Feb", "March", "April", "May"],
50 | datasets: datasetsHighContrast,
51 | },
52 | });
53 | return (
54 |
55 |
56 |
57 | );
58 | };
59 |
60 | const datasetsCustomTheme = [
61 | {
62 | label: "Tablets",
63 | data: [860, 6700, 3100, 2012, 1930],
64 | color: "rgb(255, 99, 132)",
65 | },
66 | ];
67 |
68 | export const CustomTheme = () => {
69 | const config: IChart = new BarChart({
70 | areaLabel: "Bar chart sample",
71 | data: {
72 | labels: ["Jan", "Feb", "March", "April", "May"],
73 | datasets: datasetsCustomTheme,
74 | },
75 | options: customOptions,
76 | });
77 | return (
78 |
79 |
80 |
81 | );
82 | };
83 |
--------------------------------------------------------------------------------
/stories/components/container.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export const Container = (props) => (
4 |
19 |
35 | {props.children}
36 |
37 |
38 | );
39 |
40 | export const DarkContainer = (props) => (
41 |
57 |
73 | {props.children}
74 |
75 |
76 | );
77 |
78 | export const HighContrastContainer = (props) => (
79 |
89 |
114 |
{props.children}
115 |
116 | );
117 |
118 | export const TrendLineContainer = (props) => (
119 |
130 |
146 | {props.children}
147 |
148 |
149 | );
150 |
151 | export const TrendLineHighContrastContainer = (props) => (
152 |
169 |
194 |
{props.children}
195 |
196 | );
197 |
198 | export const TrendLineDarkContainer = (props) => (
199 |
216 |
232 | {props.children}
233 |
234 |
235 | );
236 |
--------------------------------------------------------------------------------
/stories/components/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./container";
2 |
--------------------------------------------------------------------------------
/stories/docs/introduction.stories.mdx:
--------------------------------------------------------------------------------
1 | import { Meta, Story } from "@storybook/addon-docs/blocks";
2 | import { Chart } from "../../src/chart";
3 |
4 |
5 |
6 | # `@fluentui/react-charts`
7 |
8 | # Introduction
9 |
10 | ## Installation
11 |
12 | You can get the latest version of `@fluentui/react-charts` from [npm](https://npmjs.com/package/@fluentui/react-charts), the [GitHub releases](https://github.com/OfficeDev/microsoft-data-visualization-library).
13 |
14 | ## Creating a Chart
15 |
16 | It's easy to get started with `@fluentui/react-charts`.
17 |
18 | In this React component example, we create a line chart for a single dataset and render that in our page.
19 |
20 | ```tsx
21 |
36 | ```
37 |
38 | ## Installation
39 |
40 | ```sh
41 | npm install @fluentui/react-charts
42 | ```
43 |
44 | ## Github
45 |
46 | You can download the latest version of [@fluentui/react-charts on GitHub](https://github.com/OfficeDev/microsoft-data-visualization-library/releases/latest).
47 |
--------------------------------------------------------------------------------
/stories/docs/line.stories.mdx:
--------------------------------------------------------------------------------
1 | import { Meta, Story, Canvas } from "@storybook/addon-docs/blocks";
2 | import { IChart } from "../../src";
3 | import { Chart } from "../../src/chart";
4 | import { Point } from "../../src/types";
5 | import { LineChart, LineChartHighContrast } from "../../src/lib/builder";
6 | import { Container, DarkContainer, HighContrastContainer } from "../components";
7 | import { customOptions } from "../utils";
8 |
9 |
10 |
11 | # Line
12 |
13 | A line chart is a way of plotting data points on a line. Often, it is used to show trend data, or the comparison of two data sets.
14 |
15 | ## Dataset Properties
16 |
17 | The line chart allows a number of properties to be specified for each dataset. These are used to set display properties for a specific dataset. For example, the colour of a line is generally set this way.
18 |
19 | `color` property will assing color value to all following dataset properties: `borderColor`, `hoverBorderColor`, `pointBorderColor`, `pointBackgroundColor`, `pointHoverBackgroundColor`, `pointHoverBorderColor` or you could reassign it manually.
20 |
21 | | Name | Type | [Scriptable](../general/options.md#scriptable-options) | [Indexable](../general/options.md#indexable-options) | Default |
22 | | --------------------------------------------------- | -------------------------------------------- | :----------------------------------------------------: | :--------------------------------------------------: | ---------------------- |
23 | | [`backgroundColor`](#line-styling) | [`Color`](../general/colors.md) | Yes | - | `'rgba(0, 0, 0, 0.1)'` |
24 | | [`borderCapStyle`](#line-styling) | `string` | Yes | - | `'butt'` |
25 | | [`borderColor`](#line-styling) | [`Color`](../general/colors.md) | Yes | - | `'rgba(0, 0, 0, 0.1)'` |
26 | | [`borderDash`](#line-styling) | `number[]` | Yes | - | `[]` |
27 | | [`borderDashOffset`](#line-styling) | `number` | Yes | - | `0.0` |
28 | | [`borderJoinStyle`](#line-styling) | `string` | Yes | - | `'miter'` |
29 | | [`borderWidth`](#line-styling) | `number` | Yes | - | `3` |
30 | | [`clip`](#general) | `number`\|`object` | - | - | `undefined` |
31 | | [`data`](#data-structure) | `object`\|`object[]`\|`number[]`\|`string[]` | - | - | **required** |
32 | | [`cubicInterpolationMode`](#cubicinterpolationmode) | `string` | Yes | - | `'default'` |
33 | | [`fill`](#line-styling) | `boolean`\|`string` | Yes | - | `false` |
34 | | [`hoverBackgroundColor`](#line-styling) | [`Color`](../general/colors.md) | Yes | - | `undefined` |
35 | | [`hoverBorderCapStyle`](#line-styling) | `string` | Yes | - | `undefined` |
36 | | [`hoverBorderColor`](#line-styling) | [`Color`](../general/colors.md) | Yes | - | `undefined` |
37 | | [`hoverBorderDash`](#line-styling) | `number[]` | Yes | - | `undefined` |
38 | | [`hoverBorderDashOffset`](#line-styling) | `number` | Yes | - | `undefined` |
39 | | [`hoverBorderJoinStyle`](#line-styling) | `string` | Yes | - | `undefined` |
40 | | [`hoverBorderWidth`](#line-styling) | `number` | Yes | - | `undefined` |
41 | | [`indexAxis`](#general) | `string` | - | - | `'x'` |
42 | | [`label`](#general) | `string` | - | - | `''` |
43 | | [`tension`](#line-styling) | `number` | - | - | `0` |
44 | | [`order`](#general) | `number` | - | - | `0` |
45 | | [`pointBackgroundColor`](#point-styling) | `Color` | Yes | Yes | `'rgba(0, 0, 0, 0.1)'` |
46 | | [`pointBorderColor`](#point-styling) | `Color` | Yes | Yes | `'rgba(0, 0, 0, 0.1)'` |
47 | | [`pointBorderWidth`](#point-styling) | `number` | Yes | Yes | `1` |
48 | | [`pointHitRadius`](#point-styling) | `number` | Yes | Yes | `1` |
49 | | [`pointHoverBackgroundColor`](#interactions) | `Color` | Yes | Yes | `undefined` |
50 | | [`pointHoverBorderColor`](#interactions) | `Color` | Yes | Yes | `undefined` |
51 | | [`pointHoverBorderWidth`](#interactions) | `number` | Yes | Yes | `1` |
52 | | [`pointHoverRadius`](#interactions) | `number` | Yes | Yes | `4` |
53 | | [`pointRadius`](#point-styling) | `number` | Yes | Yes | `3` |
54 | | [`pointRotation`](#point-styling) | `number` | Yes | Yes | `0` |
55 | | [`pointStyle`](#point-styling) | `string`\|`Image` | Yes | Yes | `'circle'` |
56 | | [`showLine`](#line-styling) | `boolean` | - | - | `true` |
57 | | [`spanGaps`](#line-styling) | `boolean`\|`number` | - | - | `undefined` |
58 | | [`stepped`](#stepped) | `boolean`\|`string` | - | - | `false` |
59 | | [`xAxisID`](#general) | `string` | - | - | first x axis |
60 | | [`yAxisID`](#general) | `string` | - | - | first y axis |
61 |
62 | ### General
63 |
64 | | Name | Description |
65 | | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
66 | | `clip` | How to clip relative to chartArea. Positive value allows overflow, negative value clips that many pixels inside chartArea. `0` = clip at chartArea. Clipping can also be configured per side: `clip: {left: 5, top: false, right: -2, bottom: 0}` |
67 | | `indexAxis` | The base axis of the dataset. `'x'` for horizontal lines and `'y'` for vertical lines. |
68 | | `label` | The label for the dataset which appears in the legend and tooltips. |
69 | | `order` | The drawing order of dataset. Also affects order for stacking, tooltip, and legend. |
70 | | `xAxisID` | The ID of the x-axis to plot this dataset on. |
71 | | `yAxisID` | The ID of the y-axis to plot this dataset on. |
72 |
73 | ### Point Styling
74 |
75 | The style of each point can be controlled with the following properties:
76 |
77 | | Name | Description |
78 | | ---------------------- | ------------------------------------------------------------------------ |
79 | | `pointBackgroundColor` | The fill color for points. |
80 | | `pointBorderColor` | The border color for points. |
81 | | `pointBorderWidth` | The width of the point border in pixels. |
82 | | `pointHitRadius` | The pixel size of the non-displayed point that reacts to mouse events. |
83 | | `pointRadius` | The radius of the point shape. If set to 0, the point is not rendered. |
84 | | `pointRotation` | The rotation of the point in degrees. |
85 | | `pointStyle` | Style of the point. [more...](../configuration/elements.md#point-styles) |
86 |
87 | All these values, if `undefined`, fallback first to the dataset options then to the associated [`elements.point.*`](../configuration/elements.md#point-configuration) options.
88 |
89 | ### Line Styling
90 |
91 | The style of the line can be controlled with the following properties:
92 |
93 | | Name | Description |
94 | | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
95 | | `backgroundColor` | The line fill color. |
96 | | `borderCapStyle` | Cap style of the line. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineCap). |
97 | | `borderColor` | The line color. |
98 | | `borderDash` | Length and spacing of dashes. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setLineDash). |
99 | | `borderDashOffset` | Offset for line dashes. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineDashOffset). |
100 | | `borderJoinStyle` | Line joint style. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineJoin). |
101 | | `borderWidth` | The line width (in pixels). |
102 | | `fill` | How to fill the area under the line. See [area charts](area.md). |
103 | | `tension` | Bezier curve tension of the line. Set to 0 to draw straightlines. This option is ignored if monotone cubic interpolation is used. |
104 | | `showLine` | If false, the line is not drawn for this dataset. |
105 | | `spanGaps` | If true, lines will be drawn between points with no or null data. If false, points with `null` data will create a break in the line. Can also be a number specifying the maximum gap length to span. The unit of the value depends on the scale used. |
106 |
107 | If the value is `undefined`, `showLine` and `spanGaps` fallback to the associated [chart configuration options](#configuration-options). The rest of the values fallback to the associated [`elements.line.*`](../configuration/elements.md#line-configuration) options.
108 |
109 | ### Interactions
110 |
111 | The interaction with each point can be controlled with the following properties:
112 |
113 | | Name | Description |
114 | | --------------------------- | ------------------------------------- |
115 | | `pointHoverBackgroundColor` | Point background color when hovered. |
116 | | `pointHoverBorderColor` | Point border color when hovered. |
117 | | `pointHoverBorderWidth` | Border width of point when hovered. |
118 | | `pointHoverRadius` | The radius of the point when hovered. |
119 |
120 | ### cubicInterpolationMode
121 |
122 | The following interpolation modes are supported.
123 |
124 | - `'default'`
125 | - `'monotone'`
126 |
127 | The `'default'` algorithm uses a custom weighted cubic interpolation, which produces pleasant curves for all types of datasets.
128 |
129 | The `'monotone'` algorithm is more suited to `y = f(x)` datasets: it preserves monotonicity (or piecewise monotonicity) of the dataset being interpolated, and ensures local extremums (if any) stay at input data points.
130 |
131 | If left untouched (`undefined`), the global `options.elements.line.cubicInterpolationMode` property is used.
132 |
133 | ### Stepped
134 |
135 | The following values are supported for `stepped`.
136 |
137 | - `false`: No Step Interpolation (default)
138 | - `true`: Step-before Interpolation (eq. `'before'`)
139 | - `'before'`: Step-before Interpolation
140 | - `'after'`: Step-after Interpolation
141 | - `'middle'`: Step-middle Interpolation
142 |
143 | If the `stepped` value is set to anything other than false, `tension` will be ignored.
144 |
145 | ## Configuration Options
146 |
147 | The line chart defines the following configuration options. These options are looked up on access, and form together with the global chart configuration, `Chart.defaults`, the options of the chart.
148 |
149 | | Name | Type | Default | Description |
150 | | ---------- | ------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
151 | | `showLine` | `boolean` | `true` | If false, the lines between points are not drawn. |
152 | | `spanGaps` | `boolean`\|`number` | `false` | If true, lines will be drawn between points with no or null data. If false, points with `null` data will create a break in the line. Can also be a number specifying the maximum gap length to span. The unit of the value depends on the scale used. |
153 |
154 | ## Default Options
155 |
156 | It is common to want to apply a configuration setting to all created line charts. The global line chart settings are stored in `Chart.overrides.line`. Changing the global options only affects charts created after the change. Existing charts are not changed.
157 |
158 | For example, to configure all line charts with `spanGaps = true` you would do:
159 |
160 | ```javascript
161 | Chart.overrides.line.spanGaps = true;
162 | ```
163 |
164 | ## Data Structure
165 |
166 | All of the supported [data structures](../general/data-structures.md) can be used with line charts.
167 |
168 | ## Customization example
169 |
170 | ```ts
171 | const dataset = [
172 | {
173 | label: "Tablets",
174 | data: [860, 6700, 3100, 2012, 1930],
175 | color: "rgb(255, 99, 132)",
176 | },
177 | {
178 | label: "Phones",
179 | data: [100, 1600, 180, 3049, 3596],
180 | color: "rgb(255, 159, 64)",
181 | },
182 | {
183 | label: "Laptops",
184 | data: [1860, 7700, 4100, 3012, 2930],
185 | color: "rgb(255, 205, 86)",
186 | },
187 | {
188 | label: "Watches",
189 | data: [200, 3600, 480, 5049, 4596],
190 | color: "rgb(75, 192, 192)",
191 | },
192 | {
193 | label: "TVs",
194 | data: [960, 8700, 5100, 5012, 3930],
195 | color: "rgb(54, 162, 235)",
196 | },
197 | {
198 | label: "Displays",
199 | data: [1000, 4600, 480, 4049, 3596],
200 | color: "rgb(153, 102, 255)",
201 | },
202 | ];
203 |
204 | const config = new LineChart({
205 | areaLabel: "Line chart sample",
206 | data: {
207 | labels: ["Jan", "Feb", "March", "April", "May"],
208 | datasets,
209 | },
210 | });
211 |
212 | ;
213 | ```
214 |
215 | export const datasetsCustomTheme = [
216 | {
217 | label: "Tablets",
218 | data: [860, 6700, 3100, 2012, 1930],
219 | color: "rgb(255, 99, 132)",
220 | },
221 | {
222 | label: "Phones",
223 | data: [100, 1600, 180, 3049, 3596],
224 | color: "rgb(255, 159, 64)",
225 | },
226 | {
227 | label: "Laptops",
228 | data: [1860, 7700, 4100, 3012, 2930],
229 | color: "rgb(255, 205, 86)",
230 | },
231 | {
232 | label: "Watches",
233 | data: [200, 3600, 480, 5049, 4596],
234 | color: "rgb(75, 192, 192)",
235 | },
236 | {
237 | label: "TVs",
238 | data: [960, 8700, 5100, 5012, 3930],
239 | color: "rgb(54, 162, 235)",
240 | },
241 | {
242 | label: "Displays",
243 | data: [1000, 4600, 480, 4049, 3596],
244 | color: "rgb(153, 102, 255)",
245 | },
246 | ];
247 |
248 | export const CustomTheme = () => {
249 | const config = new LineChart({
250 | areaLabel: "Line chart sample",
251 | data: {
252 | labels: ["Jan", "Feb", "March", "April", "May"],
253 | datasets: datasetsCustomTheme,
254 | },
255 | options: customOptions,
256 | });
257 | return (
258 |
259 |
260 |
261 | );
262 | };
263 |
264 |
280 |
--------------------------------------------------------------------------------
/stories/doughnut.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Chart } from "../src/chart";
3 | import { IChart } from "../src/types";
4 | import { Patterns } from "../src/lib/patterns";
5 | import { DoughnutChart, DoughnutChartHighContrast } from "../src/lib/builder";
6 | import { Container, DarkContainer, HighContrastContainer } from "./components";
7 | import { customPieOptions } from "./utils";
8 |
9 | export default {
10 | title: "Charts/Doughnut",
11 | component: Chart,
12 | };
13 |
14 | const datasets = [
15 | {
16 | label: "Sales",
17 | data: [2004, 1600, 480, 504, 1000],
18 | color: ["#6264A7", "#C8C6C4", "#BDBDE6", "#605E5C", "#464775", "#252423"],
19 | },
20 | ];
21 |
22 | export const Default = () => {
23 | const config: IChart = new DoughnutChart({
24 | areaLabel: "Doughnut chart sample",
25 | data: {
26 | labels: ["Laptops", "Tablets", "Phones", "Displays", "Watches"],
27 | datasets,
28 | },
29 | });
30 | return (
31 |
32 |
33 |
34 | );
35 | };
36 |
37 | const datasetsHighContrast = [
38 | {
39 | label: "Tablets",
40 | data: [860, 6700, 3100, 2012, 1930],
41 | pattern: [
42 | Patterns.Diagonal,
43 | Patterns.Square,
44 | Patterns.Grid,
45 | Patterns.Grid2,
46 | Patterns.Line,
47 | ],
48 | },
49 | ];
50 |
51 | export const HighContrast = () => {
52 | const config: IChart = new DoughnutChartHighContrast({
53 | areaLabel: "Pie chart sample",
54 | data: {
55 | labels: ["Laptops", "Tablets", "Phones", "Displays", "Watches"],
56 | datasets: datasetsHighContrast,
57 | },
58 | });
59 | return (
60 |
61 |
62 |
63 | );
64 | };
65 |
66 | const datasetsCustomTheme = [
67 | {
68 | label: "Tablets",
69 | data: [860, 6700, 3100, 2012, 1930],
70 | color: [
71 | "rgb(255, 99, 132)",
72 | "rgb(255, 159, 64)",
73 | "rgb(255, 205, 86)",
74 | "rgb(75, 192, 192)",
75 | "rgb(54, 162, 235)",
76 | ],
77 | },
78 | ];
79 |
80 | export const CustomTheme = () => {
81 | const config: IChart = new DoughnutChart({
82 | areaLabel: "Pie chart sample",
83 | data: {
84 | labels: ["Laptops", "Tablets", "Phones", "Displays", "Watches"],
85 | datasets: datasetsCustomTheme,
86 | },
87 | options: customPieOptions,
88 | });
89 | return (
90 |
91 |
92 |
93 | );
94 | };
95 |
--------------------------------------------------------------------------------
/stories/grouped-bar.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Chart } from "../src/chart";
3 | import { IChart } from "../src/types";
4 | import { Patterns } from "../src/lib/patterns";
5 | import {
6 | GroupedBarChartHighContrast,
7 | GroupedBarChart,
8 | } from "../src/lib/builder";
9 | import { Container, DarkContainer, HighContrastContainer } from "./components";
10 | import { customOptions } from "./utils";
11 |
12 | export default {
13 | title: "Charts/Grouped bar",
14 | component: Chart,
15 | };
16 |
17 | const datasets = [
18 | {
19 | label: "Tablets",
20 | data: [860, 6700, 3100, 2012, 1930],
21 | color: "#6264A7",
22 | },
23 | {
24 | label: "Phones",
25 | data: [100, 1600, 180, 3049, 3596],
26 | color: "#C8C6C4",
27 | },
28 | {
29 | label: "Laptops",
30 | data: [1860, 7700, 4100, 3012, 2930],
31 | color: "#BDBDE6",
32 | },
33 | {
34 | label: "Watches",
35 | data: [200, 3600, 480, 5049, 4596],
36 | color: "#605E5C",
37 | },
38 | ];
39 |
40 | export const Default = () => {
41 | const config: IChart = new GroupedBarChart({
42 | areaLabel: "Grouped bar chart sample",
43 | data: {
44 | labels: ["Jan", "Feb", "March", "April", "May"],
45 | datasets,
46 | },
47 | });
48 | return (
49 |
50 |
51 |
52 | );
53 | };
54 |
55 | const datasetsHighContrast = [
56 | {
57 | label: "Tablets",
58 | data: [860, 6700, 3100, 2012, 1930],
59 | pattern: Patterns.Diagonal,
60 | },
61 | {
62 | label: "Phones",
63 | data: [100, 1600, 180, 3049, 3596],
64 | pattern: Patterns.Square,
65 | },
66 | {
67 | label: "Laptops",
68 | data: [1860, 7700, 4100, 3012, 2930],
69 | pattern: Patterns.Grid2,
70 | },
71 | {
72 | label: "Watches",
73 | data: [200, 3600, 480, 5049, 4596],
74 | pattern: Patterns.Grid,
75 | },
76 | ];
77 |
78 | export const HighContrast = () => {
79 | const config: IChart = new GroupedBarChartHighContrast({
80 | areaLabel: "Grouped bar chart sample",
81 | data: {
82 | labels: ["Jan", "Feb", "March", "April", "May"],
83 | datasets: datasetsHighContrast,
84 | },
85 | });
86 | return (
87 |
88 |
89 |
90 | );
91 | };
92 |
93 | const datasetsCustomTheme = [
94 | {
95 | label: "Tablets",
96 | data: [860, 6700, 3100, 2012, 1930],
97 | color: "rgb(255, 99, 132)",
98 | },
99 | {
100 | label: "Phones",
101 | data: [100, 1600, 180, 3049, 3596],
102 | color: "rgb(255, 159, 64)",
103 | },
104 | {
105 | label: "Laptops",
106 | data: [1860, 7700, 4100, 3012, 2930],
107 | color: "rgb(255, 205, 86)",
108 | },
109 | {
110 | label: "Watches",
111 | data: [200, 3600, 480, 5049, 4596],
112 | color: "rgb(75, 192, 192)",
113 | },
114 | ];
115 |
116 | export const CustomTheme = () => {
117 | const config: IChart = new GroupedBarChart({
118 | areaLabel: "Grouped bar chart sample",
119 | data: {
120 | labels: ["Jan", "Feb", "March", "April", "May"],
121 | datasets: datasetsCustomTheme,
122 | },
123 | options: customOptions,
124 | });
125 | return (
126 |
127 |
128 |
129 | );
130 | };
131 |
--------------------------------------------------------------------------------
/stories/horizontal-bar.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Chart } from "../src/chart";
3 | import { IChart } from "../src/types";
4 | import { Patterns } from "../src/lib/patterns";
5 | import {
6 | BarChart,
7 | BarChartHighContrast,
8 | HorizontalBarChart,
9 | HorizontalBarChartHighContrast,
10 | } from "../src/lib/builder";
11 | import { Container, DarkContainer, HighContrastContainer } from "./components";
12 | import { customOptions } from "./utils";
13 |
14 | export default {
15 | title: "Charts/Horizontal bar",
16 | component: Chart,
17 | };
18 |
19 | const datasets = [
20 | {
21 | label: "Sales",
22 | data: [860, 6700, 3100, 2012, 1930],
23 | color: "#6264A7",
24 | },
25 | ];
26 |
27 | export const Default = () => {
28 | const config: IChart = new HorizontalBarChart({
29 | areaLabel: "Horizontal bar chart sample",
30 | data: {
31 | labels: ["Jan", "Feb", "March", "April", "May"],
32 | datasets,
33 | },
34 | });
35 | return (
36 |
37 |
38 |
39 | );
40 | };
41 |
42 | const datasetsHighContrast = [
43 | {
44 | label: "Sales",
45 | data: [860, 6700, 3100, 2012, 1930],
46 | pattern: Patterns.Diagonal,
47 | },
48 | ];
49 |
50 | export const HighContrast = () => {
51 | const config: IChart = new HorizontalBarChartHighContrast({
52 | areaLabel: "Horizontal bar chart sample",
53 | data: {
54 | labels: ["Jan", "Feb", "March", "April", "May"],
55 | datasets: datasetsHighContrast,
56 | },
57 | });
58 | return (
59 |
60 |
61 |
62 | );
63 | };
64 |
65 | const datasetsCustomTheme = [
66 | {
67 | label: "Sales",
68 | data: [860, 6700, 3100, 2012, 1930],
69 | color: "rgb(255, 99, 132)",
70 | },
71 | ];
72 |
73 | export const CustomTheme = () => {
74 | const config: IChart = new HorizontalBarChart({
75 | areaLabel: "Horizontal bar chart sample",
76 | data: {
77 | labels: ["Jan", "Feb", "March", "April", "May"],
78 | datasets: datasetsCustomTheme,
79 | },
80 | });
81 | return (
82 |
83 |
84 |
85 | );
86 | };
87 |
--------------------------------------------------------------------------------
/stories/line.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Chart } from "../src/chart";
3 | import { IChart, Point } from "../src/types";
4 | import { LineChart, LineChartHighContrast } from "../src/lib/builder";
5 | import { Container, DarkContainer, HighContrastContainer } from "./components";
6 | import { customOptions } from "./utils";
7 |
8 | export default {
9 | title: "Charts/Line",
10 | component: Chart,
11 | // parameters: {
12 | // docs: {
13 | // source: {
14 | // code: "Some custom string here",
15 | // },
16 | // },
17 | // },
18 | };
19 |
20 | const datasets = [
21 | {
22 | label: "Tablets",
23 | data: [860, 6700, 3100, 2012, 1930],
24 | color: "#6264A7",
25 | },
26 | {
27 | label: "Phones",
28 | data: [100, 1600, 180, 3049, 3596],
29 | color: "#C8C6C4",
30 | },
31 | {
32 | label: "Laptops",
33 | data: [1860, 7700, 4100, 3012, 2930],
34 | color: "#BDBDE6",
35 | },
36 | {
37 | label: "Watches",
38 | data: [200, 3600, 480, 5049, 4596],
39 | color: "#605E5C",
40 | },
41 | {
42 | label: "TVs",
43 | data: [960, 8700, 5100, 5012, 3930],
44 | color: "#464775",
45 | },
46 | {
47 | label: "Displays",
48 | data: [1000, 4600, 480, 4049, 3596],
49 | color: "#252423",
50 | },
51 | ];
52 |
53 | export const Default = () => {
54 | const config: IChart = new LineChart({
55 | areaLabel: "Line chart sample",
56 | data: {
57 | labels: ["Jan", "Feb", "March", "April", "May"],
58 | datasets,
59 | },
60 | });
61 | return (
62 |
63 |
64 |
65 | );
66 | };
67 |
68 | const datasetsHighContrast = [
69 | {
70 | label: "Tablets",
71 | data: [860, 6700, 3100, 2012, 1930],
72 | borderDash: [],
73 | pointStyle: Point.Circle,
74 | },
75 | {
76 | label: "Phones",
77 | data: [100, 1600, 180, 3049, 3596],
78 | borderDash: [],
79 | pointStyle: Point.Rectangle,
80 | },
81 | {
82 | label: "Laptops",
83 | data: [1860, 7700, 4100, 3012, 2930],
84 | borderDash: [],
85 | pointStyle: Point.Triangle,
86 | },
87 | {
88 | label: "Watches",
89 | data: [200, 3600, 480, 5049, 4596],
90 | borderDash: [5, 5],
91 | pointStyle: Point.Circle,
92 | },
93 | {
94 | label: "TVs",
95 | data: [960, 8700, 5100, 5012, 3930],
96 | borderDash: [5, 5],
97 | pointStyle: Point.Rectangle,
98 | },
99 | {
100 | label: "Displays",
101 | data: [1000, 4600, 480, 4049, 3596],
102 | borderDash: [5, 5],
103 | pointStyle: Point.Triangle,
104 | },
105 | ];
106 |
107 | export const HighContrast = () => {
108 | const config: IChart = new LineChartHighContrast({
109 | areaLabel: "Line chart sample",
110 | data: {
111 | labels: ["Jan", "Feb", "March", "April", "May"],
112 | datasets: datasetsHighContrast,
113 | },
114 | });
115 | return (
116 |
117 |
118 |
119 | );
120 | };
121 |
122 | const datasetsCustomTheme = [
123 | {
124 | label: "Tablets",
125 | data: [860, 6700, 3100, 2012, 1930],
126 | color: "rgb(255, 99, 132)",
127 | },
128 | {
129 | label: "Phones",
130 | data: [100, 1600, 180, 3049, 3596],
131 | color: "rgb(255, 159, 64)",
132 | },
133 | {
134 | label: "Laptops",
135 | data: [1860, 7700, 4100, 3012, 2930],
136 | color: "rgb(255, 205, 86)",
137 | },
138 | {
139 | label: "Watches",
140 | data: [200, 3600, 480, 5049, 4596],
141 | color: "rgb(75, 192, 192)",
142 | },
143 | {
144 | label: "TVs",
145 | data: [960, 8700, 5100, 5012, 3930],
146 | color: "rgb(54, 162, 235)",
147 | },
148 | {
149 | label: "Displays",
150 | data: [1000, 4600, 480, 4049, 3596],
151 | color: "rgb(153, 102, 255)",
152 | },
153 | ];
154 |
155 | export const CustomTheme = () => {
156 | const config: IChart = new LineChart({
157 | areaLabel: "Line chart sample",
158 | data: {
159 | labels: ["Jan", "Feb", "March", "April", "May"],
160 | datasets: datasetsCustomTheme,
161 | },
162 | options: customOptions,
163 | });
164 | return (
165 |
166 |
167 |
168 | );
169 | };
170 |
--------------------------------------------------------------------------------
/stories/pie.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Chart } from "../src/chart";
3 | import { IChart } from "../src/types";
4 | import { Patterns } from "../src/lib/patterns";
5 | import { PieChart, PieChartHighContrast } from "../src/lib/builder";
6 | import { Container, DarkContainer, HighContrastContainer } from "./components";
7 | import { customPieOptions } from "./utils";
8 |
9 | export default {
10 | title: "Charts/Pie",
11 | component: Chart,
12 | };
13 |
14 | const datasets = [
15 | {
16 | label: "Sales",
17 | data: [2004, 1600, 480, 504, 1000],
18 | color: ["#6264A7", "#C8C6C4", "#BDBDE6", "#605E5C", "#464775", "#252423"],
19 | },
20 | ];
21 |
22 | export const Default = () => {
23 | const config: IChart = new PieChart({
24 | areaLabel: "Pie chart sample",
25 | data: {
26 | labels: ["Laptops", "Tablets", "Phones", "Displays", "Watches"],
27 | datasets,
28 | },
29 | });
30 | return (
31 |
32 |
33 |
34 | );
35 | };
36 |
37 | const datasetsHighContrast = [
38 | {
39 | label: "Tablets",
40 | data: [860, 6700, 3100, 2012, 1930],
41 | pattern: [
42 | Patterns.Diagonal,
43 | Patterns.Square,
44 | Patterns.Grid,
45 | Patterns.Grid2,
46 | Patterns.Line,
47 | ],
48 | },
49 | ];
50 |
51 | export const HighContrast = () => {
52 | const config: IChart = new PieChartHighContrast({
53 | areaLabel: "Pie chart sample",
54 | data: {
55 | labels: ["Laptops", "Tablets", "Phones", "Displays", "Watches"],
56 | datasets: datasetsHighContrast,
57 | },
58 | });
59 | return (
60 |
61 |
62 |
63 | );
64 | };
65 |
66 | const datasetsCustomTheme = [
67 | {
68 | label: "Tablets",
69 | data: [860, 6700, 3100, 2012, 1930],
70 | color: [
71 | "rgb(255, 99, 132)",
72 | "rgb(255, 159, 64)",
73 | "rgb(255, 205, 86)",
74 | "rgb(75, 192, 192)",
75 | "rgb(54, 162, 235)",
76 | ],
77 | },
78 | ];
79 |
80 | export const CustomTheme = () => {
81 | const config: IChart = new PieChart({
82 | areaLabel: "Pie chart sample",
83 | data: {
84 | labels: ["Laptops", "Tablets", "Phones", "Displays", "Watches"],
85 | datasets: datasetsCustomTheme,
86 | },
87 | options: customPieOptions,
88 | });
89 | return (
90 |
91 |
92 |
93 | );
94 | };
95 |
--------------------------------------------------------------------------------
/stories/stacked-bar.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Chart } from "../src/chart";
3 | import { IChart } from "../src/types";
4 | import { Patterns } from "../src/lib/patterns";
5 | import {
6 | StackedBarChart,
7 | StackedBarChartHighContrast,
8 | } from "../src/lib/builder";
9 | import { Container, DarkContainer, HighContrastContainer } from "./components";
10 | import { customOptions } from "./utils";
11 |
12 | export default {
13 | title: "Charts/Stacked bar",
14 | component: Chart,
15 | };
16 |
17 | const datasets = [
18 | {
19 | label: "Laptops",
20 | data: [1860, 7700, 4100, 3012, 2930],
21 | color: "#585A96",
22 | },
23 | {
24 | label: "Watches",
25 | data: [1200, 3600, 2480, 5049, 4596],
26 | color: "#BDBDE6",
27 | },
28 | {
29 | label: "Phones",
30 | data: [1000, 1600, 1800, 3049, 3596],
31 | color: "#464775",
32 | },
33 | ];
34 |
35 | export const Default = () => {
36 | const config: IChart = new StackedBarChart({
37 | areaLabel: "Stacked bar chart sample",
38 | data: {
39 | labels: ["Jan", "Feb", "March", "April", "May"],
40 | datasets,
41 | },
42 | });
43 | return (
44 |
45 |
46 |
47 | );
48 | };
49 |
50 | const datasetsHighContrast = [
51 | {
52 | label: "Tablets",
53 | data: [860, 6700, 3100, 2012, 1930],
54 | pattern: Patterns.Diagonal,
55 | },
56 | {
57 | label: "Phones",
58 | data: [1000, 1600, 1800, 3049, 3596],
59 | pattern: Patterns.Square,
60 | },
61 | {
62 | label: "Laptops",
63 | data: [1860, 7700, 4100, 3012, 2930],
64 | pattern: Patterns.Grid,
65 | },
66 | ];
67 |
68 | export const HighContrast = () => {
69 | const config: IChart = new StackedBarChartHighContrast({
70 | areaLabel: "Stacked bar chart sample",
71 | data: {
72 | labels: ["Jan", "Feb", "March", "April", "May"],
73 | datasets: datasetsHighContrast,
74 | },
75 | });
76 | return (
77 |
78 |
79 |
80 | );
81 | };
82 |
83 | const datasetsCustomTheme = [
84 | {
85 | label: "Tablets",
86 | data: [860, 6700, 3100, 2012, 1930],
87 | color: "rgb(255, 99, 132)",
88 | },
89 | {
90 | label: "Watches",
91 | data: [1200, 3600, 2480, 5049, 4596],
92 | color: "rgb(255, 159, 64)",
93 | },
94 | {
95 | label: "Phones",
96 | data: [1000, 1600, 1800, 3049, 3596],
97 | color: "rgb(255, 205, 86)",
98 | },
99 | ];
100 |
101 | export const CustomTheme = () => {
102 | const config: IChart = new StackedBarChart({
103 | areaLabel: "Stacked bar chart sample",
104 | data: {
105 | labels: ["Jan", "Feb", "March", "April", "May"],
106 | datasets: datasetsCustomTheme,
107 | },
108 | options: customOptions,
109 | });
110 | return (
111 |
112 |
113 |
114 | );
115 | };
116 |
--------------------------------------------------------------------------------
/stories/stacked-line.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Chart } from "../src/chart";
3 | import { IChart } from "../src/types";
4 | import { Patterns } from "../src/lib/patterns";
5 | import {
6 | LineStackedChart,
7 | LineStackedChartHighContrast,
8 | } from "../src/lib/builder";
9 | import { Container, DarkContainer, HighContrastContainer } from "./components";
10 | import { customOptions } from "./utils";
11 |
12 | export default {
13 | title: "Charts/Stacked line",
14 | component: Chart,
15 | };
16 |
17 | const datasets = [
18 | {
19 | label: "Tablets",
20 | data: [860, 6700, 3100, 2012, 1930],
21 | color: "#6264A7",
22 | },
23 | {
24 | label: "Phones",
25 | data: [100, 1600, 180, 3049, 3596],
26 | color: "#E2E2F6",
27 | },
28 | {
29 | label: "Laptops",
30 | data: [1860, 7700, 4100, 3012, 2930],
31 | color: "#BDBDE6",
32 | },
33 | ];
34 |
35 | export const Default = () => {
36 | const config: IChart = new LineStackedChart({
37 | areaLabel: "Stacked line chart sample",
38 | data: {
39 | labels: ["Jan", "Feb", "March", "April", "May"],
40 | datasets,
41 | },
42 | });
43 | return (
44 |
45 |
46 |
47 | );
48 | };
49 |
50 | const datasetsHighContrast = [
51 | {
52 | label: "Tablets",
53 | data: [860, 6700, 3100, 2012, 1930],
54 | pattern: Patterns.Square,
55 | },
56 | {
57 | label: "Phones",
58 | data: [100, 1600, 180, 3049, 3596],
59 | pattern: Patterns.Diagonal,
60 | },
61 | {
62 | label: "Laptops",
63 | data: [1860, 7700, 4100, 3012, 2930],
64 | pattern: Patterns.Grid,
65 | },
66 | ];
67 |
68 | export const HighContrast = () => {
69 | const config: IChart = new LineStackedChartHighContrast({
70 | areaLabel: "Stacked line chart sample",
71 | data: {
72 | labels: ["Jan", "Feb", "March", "April", "May"],
73 | datasets: datasetsHighContrast,
74 | },
75 | });
76 | return (
77 |
78 |
79 |
80 | );
81 | };
82 |
83 | const datasetsCustomTheme = [
84 | {
85 | label: "Tablets",
86 | data: [860, 6700, 3100, 2012, 1930],
87 | color: "rgb(255, 99, 132)",
88 | },
89 | {
90 | label: "Phones",
91 | data: [100, 1600, 180, 3049, 3596],
92 | color: "rgb(255, 159, 64)",
93 | },
94 | {
95 | label: "Laptops",
96 | data: [1860, 7700, 4100, 3012, 2930],
97 | color: "rgb(255, 205, 86)",
98 | },
99 | ];
100 |
101 | export const CustomTheme = () => {
102 | const config: IChart = new LineStackedChart({
103 | areaLabel: "Stacked line chart sample",
104 | data: {
105 | labels: ["Jan", "Feb", "March", "April", "May"],
106 | datasets: datasetsCustomTheme,
107 | },
108 | options: customOptions,
109 | });
110 | return (
111 |
112 |
113 |
114 | );
115 | };
116 |
--------------------------------------------------------------------------------
/stories/trend-line.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Chart } from "../src/chart";
3 | import { IChart, IChartData, IChartDataSet, Point } from "../src/types";
4 | import {
5 | LineChart,
6 | LineChartHighContrast,
7 | TrendLineChart,
8 | TrendLineChartHighContrast,
9 | } from "../src/lib/builder";
10 | import {
11 | DarkContainer,
12 | HighContrastContainer,
13 | TrendLineContainer,
14 | TrendLineDarkContainer,
15 | TrendLineHighContrastContainer,
16 | } from "./components";
17 | import { customOptions } from "./utils";
18 | import { usNumberFormat } from "../src/lib/utils";
19 |
20 | export default {
21 | title: "Charts/TrendLine",
22 | component: Chart,
23 | // parameters: {
24 | // docs: {
25 | // source: {
26 | // code: "Some custom string here",
27 | // },
28 | // },
29 | // },
30 | };
31 |
32 | const datasetsTablets: IChartDataSet = {
33 | label: "Tablets",
34 | data: [860, 700, 910, 1201, 1300, 1530, 1490, 1400, 1600, 1550],
35 | color: "#6264A7",
36 | };
37 | const datasetsPhones: IChartDataSet = {
38 | label: "Phones",
39 | data: [1860, 1700, 1910, 1201, 1300, 1130, 990, 1050, 960, 950],
40 | color: "rgb(192,57,77)",
41 | };
42 | export const Default = () => {
43 | const configTablets: IChart = new TrendLineChart({
44 | areaLabel: "Trend line chart sample",
45 | data: {
46 | labels: [
47 | "Jan",
48 | "Feb",
49 | "March",
50 | "April",
51 | "May",
52 | "June",
53 | "July",
54 | "August",
55 | "September",
56 | "October",
57 | ],
58 | datasets: [datasetsTablets],
59 | },
60 | });
61 | const configPhones: IChart = new TrendLineChart({
62 | areaLabel: "Trend line chart sample",
63 | data: {
64 | labels: [
65 | "Jan",
66 | "Feb",
67 | "March",
68 | "April",
69 | "May",
70 | "June",
71 | "July",
72 | "August",
73 | "September",
74 | "October",
75 | ],
76 | datasets: [datasetsPhones],
77 | },
78 | });
79 | return (
80 |
81 | Sales trends
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 | );
90 | };
91 |
92 | const datasetsTabletsHC = {
93 | label: "Tablets",
94 | data: [860, 700, 910, 1201, 1300, 1530, 1490, 1400, 1600, 1550],
95 | borderDash: [],
96 | pointStyle: Point.Circle,
97 | };
98 | const datasetsPhonesHC = {
99 | label: "Phones",
100 | data: [1860, 1700, 1910, 1201, 1300, 1130, 990, 1050, 960, 950],
101 | borderDash: [],
102 | pointStyle: Point.Circle,
103 | };
104 |
105 | export const HighContrast = () => {
106 | const configTablets: IChart = new TrendLineChartHighContrast({
107 | areaLabel: "Trend line chart sample",
108 | data: {
109 | labels: [
110 | "Jan",
111 | "Feb",
112 | "March",
113 | "April",
114 | "May",
115 | "June",
116 | "July",
117 | "August",
118 | "September",
119 | "October",
120 | ],
121 | datasets: [datasetsTabletsHC],
122 | },
123 | });
124 | const configPhones: IChart = new TrendLineChartHighContrast({
125 | areaLabel: "Trend line chart sample",
126 | data: {
127 | labels: [
128 | "Jan",
129 | "Feb",
130 | "March",
131 | "April",
132 | "May",
133 | "June",
134 | "July",
135 | "August",
136 | "September",
137 | "October",
138 | ],
139 | datasets: [datasetsPhonesHC],
140 | },
141 | });
142 | return (
143 |
144 | Sales trends
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 | );
153 | };
154 |
155 | const datasetsTabletsCustom: IChartDataSet = {
156 | label: "Tablets",
157 | data: [860, 700, 910, 1201, 1300, 1530, 1490, 1400, 1600, 1550],
158 | color: "rgb(255, 99, 132)",
159 | };
160 | const datasetsPhonesCustom: IChartDataSet = {
161 | label: "Phones",
162 | data: [1860, 1700, 1910, 1201, 1300, 1130, 990, 1050, 960, 950],
163 | color: "rgb(255, 159, 64)",
164 | };
165 |
166 | export const CustomTheme = () => {
167 | const configTablets: IChart = new TrendLineChart({
168 | areaLabel: "Trend line chart sample",
169 | data: {
170 | labels: [
171 | "Jan",
172 | "Feb",
173 | "March",
174 | "April",
175 | "May",
176 | "June",
177 | "July",
178 | "August",
179 | "September",
180 | "October",
181 | ],
182 | datasets: [datasetsTabletsCustom],
183 | },
184 | });
185 | const configPhones: IChart = new TrendLineChart({
186 | areaLabel: "Trend line chart sample",
187 | data: {
188 | labels: [
189 | "Jan",
190 | "Feb",
191 | "March",
192 | "April",
193 | "May",
194 | "June",
195 | "July",
196 | "August",
197 | "September",
198 | "October",
199 | ],
200 | datasets: [datasetsPhonesCustom],
201 | },
202 | });
203 | return (
204 |
205 | Sales trends
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 | );
214 | };
215 |
216 | const TrendLineWidgetRow = ({ dataset, ...props }) => {
217 | return (
218 |
219 | {props.children}
220 |
228 |
229 | {usNumberFormat(dataset.data[dataset.data.length - 1] as number)}
230 |
231 | {dataset.label}
232 |
233 |
234 | );
235 | };
236 |
--------------------------------------------------------------------------------
/stories/utils/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./utils";
2 |
--------------------------------------------------------------------------------
/stories/utils/utils.ts:
--------------------------------------------------------------------------------
1 | export const customOptions = {
2 | defaultColor: "#605E5C",
3 | layout: {
4 | padding: {
5 | left: 0,
6 | right: 32,
7 | top: 0,
8 | bottom: 0,
9 | },
10 | },
11 | scales: {
12 | yAxes: [
13 | {
14 | ticks: {
15 | fontColor: "#979593",
16 | maxTicksLimit: 8,
17 | },
18 | gridLines: {
19 | color: "#484644",
20 | zeroLineColor: "#484644",
21 | },
22 | scaleLabel: {
23 | display: true,
24 | labelString: "Sales",
25 | },
26 | },
27 | ],
28 | xAxes: [
29 | {
30 | ticks: {
31 | fontColor: "#979593",
32 | },
33 | gridLines: {
34 | color: "#484644",
35 | },
36 | scaleLabel: {
37 | display: true,
38 | labelString: "Product category",
39 | },
40 | },
41 | ],
42 | },
43 | };
44 |
45 | export const customPieOptions = {
46 | defaultColor: "#605E5C",
47 | scales: {
48 | yAxes: [
49 | {
50 | ticks: {
51 | fontColor: "#979593",
52 | maxTicksLimit: 8,
53 | },
54 | gridLines: {
55 | color: "#484644",
56 | zeroLineColor: "#484644",
57 | },
58 | scaleLabel: {
59 | display: true,
60 | },
61 | },
62 | ],
63 | xAxes: [
64 | {
65 | ticks: {
66 | fontColor: "#979593",
67 | },
68 | gridLines: {
69 | color: "#484644",
70 | },
71 | scaleLabel: {
72 | display: true,
73 | },
74 | },
75 | ],
76 | },
77 | };
78 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES5",
4 | "module": "ES2020",
5 | "moduleResolution": "Node",
6 | "jsx": "react",
7 | "strict": true,
8 | "allowSyntheticDefaultImports": true,
9 | "esModuleInterop": true,
10 | "importHelpers": true,
11 | "declaration": true,
12 | "outDir": "lib",
13 | "sourceMap": true
14 | },
15 | "include": ["src"]
16 | }
17 |
--------------------------------------------------------------------------------