├── .eslintignore ├── .gitignore ├── test ├── .eslintrc.yml └── specs │ └── scale.spec.js ├── docs ├── sidebars.js ├── src │ ├── pages │ │ ├── index.js │ │ └── styles.module.css │ └── css │ │ └── custom.css ├── package.json ├── docs │ ├── examples.mdx │ └── index.md └── docusaurus.config.js ├── .eslintrc.yml ├── bower.json ├── CONTRIBUTING.md ├── .codeclimate.yml ├── .github ├── workflows │ ├── release-drafter.yml │ ├── ci.yml │ ├── documentation.yml │ └── npmpublish.yml └── release-drafter.yml ├── README.md ├── src ├── index.js ├── controller.js └── scale.js ├── LICENSE.md ├── rollup.config.js ├── scripts └── release.sh ├── samples └── smith.html ├── package.json ├── gulpfile.js └── karma.config.js /.eslintignore: -------------------------------------------------------------------------------- 1 | **/*{.,-}min.js 2 | dist/**/* 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .vscode/ 3 | bower.json 4 | cc-test-reporter 5 | coverage/ 6 | dist/ 7 | node_modules/ 8 | *.stackdump 9 | docs/.docusaurus/ 10 | docs/build/ -------------------------------------------------------------------------------- /test/.eslintrc.yml: -------------------------------------------------------------------------------- 1 | env: 2 | jasmine: true 3 | 4 | globals: 5 | __karma__: true 6 | 7 | # http://eslint.org/docs/rules/ 8 | rules: 9 | # Best Practices 10 | complexity: 0 11 | no-new-func: 0 12 | -------------------------------------------------------------------------------- /docs/sidebars.js: -------------------------------------------------------------------------------- 1 | const pkg = require('../package.json'); 2 | const docsVersion = pkg.version.indexOf('-') > -1 ? 'next' : 'latest'; 3 | 4 | module.exports = { 5 | someSidebar: { 6 | Introduction: ['index'], 7 | Examples: ['examples'], 8 | }, 9 | }; 10 | -------------------------------------------------------------------------------- /docs/src/pages/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import {Redirect} from '@docusaurus/router'; 4 | import useBaseUrl from '@docusaurus/useBaseUrl'; 5 | 6 | function Home() { 7 | return ; 8 | } 9 | 10 | export default Home; 11 | -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | extends: 2 | - chartjs 3 | - esnext 4 | 5 | parserOptions: 6 | ecmaVersion: 7 7 | sourceType: module 8 | ecmaFeatures: 9 | impliedStrict: true 10 | modules: true 11 | experimentalObjectRestSpread: true 12 | 13 | env: 14 | browser: true 15 | es6: true 16 | node: true 17 | 18 | parser: babel-eslint -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Chart.Smtih.js", 3 | "version": "0.0.1", 4 | "description": "Smith chart implementation for Chart.js", 5 | "homepage": "https://github.com/nnnick/Chart.js", 6 | "author": "etimberg", 7 | "main": [ 8 | "Chart.Smith.js" 9 | ], 10 | "dependencies": { 11 | "Chart.js": ">=2.0.0-beta2" 12 | } 13 | } -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing 2 | ============ 3 | 4 | New contributions to this extension are welcome, just a couple of guidelines: 5 | 6 | * Tabs for indentation, not spaces please. 7 | * Please check that your code will pass jshint code standards, `gulp jshint` will run this for you. 8 | * Please keep pull requests concise, and document new functionality in the relevant .md file. 9 | * Please ensure that tests correctly run and pass before opening a pull request. -------------------------------------------------------------------------------- /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | checks: 3 | complex-logic: 4 | enabled: false 5 | method-complexity: 6 | enabled: false 7 | method-lines: 8 | enabled: false 9 | similar-code: 10 | enabled: false 11 | plugins: 12 | duplication: 13 | enabled: true 14 | config: 15 | languages: 16 | - javascript 17 | fixme: 18 | enabled: true 19 | exclude_patterns: 20 | - "coverage/" 21 | - "docs/" 22 | - "samples/" 23 | - "scripts/" 24 | - "test/" 25 | - "*.js" 26 | - "*.json" 27 | - "*.md" 28 | - ".*" 29 | -------------------------------------------------------------------------------- /.github/workflows/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name: Release Drafter 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | correct_repository: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: fail on fork 13 | if: github.repository_owner != 'chartjs' 14 | run: exit 1 15 | 16 | update_release_draft: 17 | needs: correct_repository 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: release-drafter/release-drafter@v5 21 | env: 22 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # chartjs-chart-smith 2 | 3 | [![MIT license](https://img.shields.io/github/license/chartjs/chartjs-chart-smith)](https://github.com/chartjs/chartjs-chart-smith/blob/master/LICENSE.md) 4 | ![GitHub Workflow Status](https://img.shields.io/github/workflow/status/chartjs/chartjs-chart-smith/CI) 5 | [![Coverage Status](https://coveralls.io/repos/github/chartjs/chartjs-chart-smith/badge.svg?branch=master)](https://coveralls.io/github/chartjs/chartjs-chart-smith?branch=master) 6 | 7 | Provides a Smith Chart for use with [Chart.js](http://www.chartjs.org) 8 | 9 | ## Documentation 10 | 11 | Documentation is available at [https://www.chartjs.org/chartjs-chart-smith/](https://www.chartjs.org/chartjs-chart-smith/) -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import Chart from 'chart.js'; 4 | import Controller from './controller'; 5 | import Scale, {defaults} from './scale'; 6 | 7 | // Register the Controller and Scale 8 | Chart.controllers.smith = Controller; 9 | Chart.defaults.smith = { 10 | aspectRatio: 1, 11 | scale: { 12 | type: 'smith', 13 | }, 14 | tooltips: { 15 | callbacks: { 16 | title: () => null, 17 | label: (bodyItem, data) => { 18 | const dataset = data.datasets[bodyItem.datasetIndex]; 19 | const d = dataset.data[bodyItem.index]; 20 | return dataset.label + ': ' + d.real + ' + ' + d.imag + 'i'; 21 | } 22 | } 23 | } 24 | }; 25 | Chart.scaleService.registerScaleType('smith', Scale, defaults); 26 | -------------------------------------------------------------------------------- /docs/src/pages/styles.module.css: -------------------------------------------------------------------------------- 1 | /* stylelint-disable docusaurus/copyright-header */ 2 | /** 3 | * CSS files with the .module.css suffix will be treated as CSS modules 4 | * and scoped locally. 5 | */ 6 | 7 | .heroBanner { 8 | padding: 4rem 0; 9 | text-align: center; 10 | position: relative; 11 | overflow: hidden; 12 | } 13 | 14 | @media screen and (max-width: 966px) { 15 | .heroBanner { 16 | padding: 2rem; 17 | } 18 | } 19 | 20 | .buttons { 21 | display: flex; 22 | align-items: center; 23 | justify-content: center; 24 | } 25 | 26 | .features { 27 | display: flex; 28 | align-items: center; 29 | padding: 2rem 0; 30 | width: 100%; 31 | } 32 | 33 | .featureImage { 34 | height: 200px; 35 | width: 200px; 36 | } 37 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docs", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "docusaurus start", 7 | "build": "docusaurus build", 8 | "swizzle": "docusaurus swizzle", 9 | "deploy": "docusaurus deploy" 10 | }, 11 | "dependencies": { 12 | "@docusaurus/core": "^3.1.0", 13 | "@docusaurus/preset-classic": "^3.1.0", 14 | "@docusaurus/theme-live-codeblock": "^2.0.0-alpha.39", 15 | "classnames": "^2.2.6", 16 | "react": "^16.13.1", 17 | "react-dom": "^16.13.1" 18 | }, 19 | "browserslist": { 20 | "production": [ 21 | ">0.2%", 22 | "not dead", 23 | "not op_mini all" 24 | ], 25 | "development": [ 26 | "last 1 chrome version", 27 | "last 1 firefox version", 28 | "last 1 safari version" 29 | ] 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /docs/docs/examples.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | id: examples 3 | title: General 4 | --- 5 | 6 | ```jsx live 7 | function example() { 8 | useEffect(() => { 9 | const ctx = document.getElementById('chartjs-0').getContext('2d'); 10 | const cfg = { 11 | type: 'smith', 12 | options: { 13 | aspectRatio: 1, 14 | elements: { 15 | point: { 16 | pointStyle: 'cross', 17 | radius: 10, 18 | hoverRadius: 10, 19 | borderColor: 'black' 20 | } 21 | } 22 | }, 23 | data: { 24 | datasets: [{ 25 | label: 'My Dataset', 26 | data: [ 27 | { real : 1.0, imag : 2.0}, 28 | { real : 1.0, imag : 1.0}, 29 | { real : 1.0, imag : -1.0} 30 | ], 31 | }] 32 | } 33 | }; 34 | new Chart(ctx, cfg); 35 | }); 36 | return
; 37 | } 38 | ``` -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | lint: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v2 16 | - uses: actions/setup-node@v2-beta 17 | with: 18 | node-version: '12' 19 | - run: npm install 20 | - run: gulp lint 21 | test: 22 | runs-on: ubuntu-latest 23 | steps: 24 | - uses: actions/checkout@v2 25 | - uses: actions/setup-node@v2-beta 26 | with: 27 | node-version: '12' 28 | - run: npm install 29 | - name: RUN CI 30 | run: xvfb-run --auto-servernum gulp test --coverage 31 | - name: Publish Test Results 32 | env: 33 | COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} 34 | run: cat ./coverage/lcov.info | ./node_modules/.bin/coveralls 35 | shell: bash 36 | continue-on-error: true 37 | -------------------------------------------------------------------------------- /docs/src/css/custom.css: -------------------------------------------------------------------------------- 1 | /* stylelint-disable docusaurus/copyright-header */ 2 | /** 3 | * Any CSS included here will be global. The classic template 4 | * bundles Infima by default. Infima is a CSS framework designed to 5 | * work well for content-centric websites. 6 | */ 7 | 8 | /* You can override the default Infima variables here. */ 9 | :root { 10 | --ifm-color-primary: #25c2a0; 11 | --ifm-color-primary-dark: rgb(33, 175, 144); 12 | --ifm-color-primary-darker: rgb(31, 165, 136); 13 | --ifm-color-primary-darkest: rgb(26, 136, 112); 14 | --ifm-color-primary-light: rgb(70, 203, 174); 15 | --ifm-color-primary-lighter: rgb(102, 212, 189); 16 | --ifm-color-primary-lightest: rgb(146, 224, 208); 17 | --ifm-code-font-size: 95%; 18 | } 19 | 20 | .docusaurus-highlight-code-line { 21 | background-color: rgb(72, 77, 91); 22 | display: block; 23 | margin: 0 calc(-1 * var(--ifm-pre-padding)); 24 | padding: 0 var(--ifm-pre-padding); 25 | } 26 | 27 | .chartjs-wrapper { 28 | height: 500px; 29 | /* width: 500px; */ 30 | } -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Evert Timberg 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-commonjs */ 2 | /* eslint-env es6 */ 3 | 4 | const terser = require('rollup-plugin-terser').terser; 5 | const pkg = require('./package.json'); 6 | 7 | const banner = `/*! 8 | * ${pkg.name} v${pkg.version} 9 | * ${pkg.homepage} 10 | * (c) ${new Date().getFullYear()} Chart.js Contributors 11 | * Released under the ${pkg.license} license 12 | */`; 13 | 14 | module.exports = [ 15 | { 16 | input: 'src/index.js', 17 | output: { 18 | file: `dist/${pkg.name}.js`, 19 | banner, 20 | format: 'umd', 21 | indent: false, 22 | globals: { 23 | 'chart.js': 'Chart' 24 | } 25 | }, 26 | external: [ 27 | 'chart.js' 28 | ] 29 | }, 30 | { 31 | input: 'src/index.js', 32 | output: { 33 | file: `dist/${pkg.name}.min.js`, 34 | format: 'umd', 35 | indent: false, 36 | globals: { 37 | 'chart.js': 'Chart' 38 | } 39 | }, 40 | plugins: [ 41 | terser({ 42 | output: { 43 | preamble: banner 44 | } 45 | }) 46 | ], 47 | external: [ 48 | 'chart.js' 49 | ] 50 | } 51 | ]; 52 | -------------------------------------------------------------------------------- /scripts/release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | if [ "$TRAVIS_BRANCH" != "release" ]; then 6 | echo "Skipping release because this is not the 'release' branch" 7 | exit 0 8 | fi 9 | 10 | # Travis executes this script from the repository root, so at the same level than package.json 11 | VERSION=$(node -p -e "require('./package.json').version") 12 | 13 | # Make sure that the associated tag doesn't already exist 14 | GITTAG=$(git ls-remote origin refs/tags/v$VERSION) 15 | if [ "$GITTAG" != "" ]; then 16 | echo "Tag for package.json version already exists, aborting release" 17 | exit 1 18 | fi 19 | 20 | git remote add auth-origin https://$GITHUB_AUTH_TOKEN@github.com/$TRAVIS_REPO_SLUG.git 21 | git config --global user.email "$GITHUB_AUTH_EMAIL" 22 | git config --global user.name "Chart.js" 23 | git checkout --detach --quiet 24 | git add -f dist/*.js bower.json 25 | git commit -m "Release $VERSION" 26 | git tag -a "v$VERSION" -m "Version $VERSION" 27 | git push -q auth-origin refs/tags/v$VERSION 2>/dev/null 28 | git remote rm auth-origin 29 | git checkout -f @{-1} 30 | -------------------------------------------------------------------------------- /test/specs/scale.spec.js: -------------------------------------------------------------------------------- 1 | import Chart from 'chart.js'; 2 | import Scale, {defaults} from '../../src/scale.js'; 3 | 4 | describe('Smith Scale', () => { 5 | it('should get the correct value for points', () => { 6 | // Create a mock scale for now 7 | const scaleConfig = Chart.helpers.clone(defaults); 8 | const scale = new Scale({ 9 | options: scaleConfig, 10 | }); 11 | 12 | scale.left = 100; 13 | scale.right = 300; 14 | scale.top = 100; 15 | scale.bottom = 300; 16 | scale.update(200, 200); 17 | 18 | expect(scale.getPointPosition(1, 0)).toEqual({ 19 | x: 200, 20 | y: 200 21 | }); 22 | expect(scale.getPointPosition(0, 0)).toEqual({ 23 | x: 100, 24 | y: 200 25 | }); 26 | expect(scale.getPointPosition(0, -1)).toEqual({ 27 | x: 200, 28 | y: 300 29 | }); 30 | expect(scale.getPointPosition(1, -1)).toEqual({ 31 | x: 220, 32 | y: 240 33 | }); 34 | expect(scale.getPointPosition(0, 1)).toEqual({ 35 | x: 200, 36 | y: 100.00000000000001, 37 | }); 38 | expect(scale.getPointPosition(1, 1)).toEqual({ 39 | x: 220, 40 | y: 160, 41 | }); 42 | expect(scale.getPointPosition(1000, 0)).toEqual({ 43 | x: 299.80019980019983, 44 | y: 200 45 | }); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /samples/smith.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Smith Chart 5 | 6 | 7 | 16 | 17 | 18 |
19 | 20 |
21 | 22 | 23 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name-template: 'v$RESOLVED_VERSION' 2 | tag-template: 'v$RESOLVED_VERSION' 3 | categories: 4 | - title: 'Breaking Changes' 5 | labels: 6 | - 'breaking change' 7 | - title: 'Enhancements' 8 | labels: 9 | - 'type: enhancement' 10 | - title: 'Performance' 11 | labels: 12 | - 'type: performance' 13 | - title: 'Bugs Fixed' 14 | labels: 15 | - 'type: bug' 16 | - title: 'Types' 17 | labels: 18 | - 'type: types' 19 | - title: 'Documentation' 20 | labels: 21 | - 'type: documentation' 22 | - title: 'Development' 23 | labels: 24 | - 'type: chore' 25 | exclude-labels: 26 | - 'type: infrastructure' 27 | change-template: '- #$NUMBER $TITLE' 28 | change-title-escapes: '\<*_&`#@' 29 | version-resolver: 30 | major: 31 | labels: 32 | - 'breaking change' 33 | minor: 34 | labels: 35 | - 'type: enhancement' 36 | patch: 37 | labels: 38 | - 'type: bug' 39 | - 'type: chore' 40 | - 'type: types' 41 | default: patch 42 | template: | 43 | # Essential Links 44 | 45 | * [npm](https://www.npmjs.com/package/chartjs-chart-smith) 46 | * [Docs](https://www.chartjs.org/chartjs-chart-smith/index) 47 | 48 | $CHANGES 49 | 50 | Thanks to $CONTRIBUTORS 51 | -------------------------------------------------------------------------------- /.github/workflows/documentation.yml: -------------------------------------------------------------------------------- 1 | name: documentation 2 | 3 | on: 4 | pull_request: 5 | branches: [master] 6 | push: 7 | branches: [master] 8 | 9 | jobs: 10 | checks: 11 | if: github.event_name != 'push' 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v1 15 | - uses: actions/setup-node@v1 16 | with: 17 | node-version: '12.x' 18 | run: | 19 | cd docs 20 | npm ci 21 | npm run build 22 | gh-release: 23 | if: github.event_name != 'pull_request' 24 | runs-on: ubuntu-latest 25 | steps: 26 | - uses: actions/checkout@v1 27 | - uses: actions/setup-node@v1 28 | with: 29 | node-version: '12.x' 30 | - name: Add key to allow access to repository 31 | env: 32 | SSH_AUTH_SOCK: /tmp/ssh_agent.sock 33 | run: | 34 | mkdir -p ~/.ssh 35 | ssh-keyscan github.com >> ~/.ssh/known_hosts 36 | echo "${{ secrets.GH_PAGES_DEPLOY }}" > ~/.ssh/id_rsa 37 | chmod 600 ~/.ssh/id_rsa 38 | cat <> ~/.ssh/config 39 | Host github.com 40 | HostName github.com 41 | IdentityFile ~/.ssh/id_rsa 42 | EOT 43 | - name: Release to GitHub Pages 44 | env: 45 | USE_SSH: true 46 | GIT_USER: git 47 | run: | 48 | git config --global user.email "actions@gihub.com" 49 | git config --global user.name "gh-actions" 50 | cd docs 51 | npm ci 52 | npx docusaurus deploy 53 | -------------------------------------------------------------------------------- /docs/docusaurus.config.js: -------------------------------------------------------------------------------- 1 | // VERSION replaced by deploy script 2 | module.exports = { 3 | title: 'chartjs-chart-smith', 4 | tagline: 'Smith chart extension to Chart.js', 5 | url: 'https://chartjs.org', 6 | baseUrl: '/chartjs-chart-smith/', 7 | favicon: 'img/favicon.ico', 8 | organizationName: 'chartjs', // Usually your GitHub org/user name. 9 | projectName: 'chartjs-chart-smith', // Usually your repo name. 10 | plugins: [], 11 | scripts: [ 12 | 'https://cdn.jsdelivr.net/npm/chart.js@2.8.0/dist/Chart.js', 13 | 'https://cdn.jsdelivr.net/npm/chartjs-chart-smith/dist/chartjs-chart-smith.min.js' 14 | ], 15 | themes: ['@docusaurus/theme-live-codeblock'], 16 | themeConfig: { 17 | disableDarkMode: true, // Would need to implement for Charts embedded in docs 18 | navbar: { 19 | title: 'Smith Chart - chartjs-chart-smith', 20 | }, 21 | footer: { 22 | style: 'dark', 23 | links: [ 24 | { 25 | title: 'Developers', 26 | items: [ 27 | { 28 | label: 'GitHub', 29 | href: 'https://github.com/chartjs/chartjs-chart-smith', 30 | }, 31 | ], 32 | }, 33 | ], 34 | copyright: `Copyright © ${new Date().getFullYear()} Chart.js contributors.`, 35 | }, 36 | }, 37 | presets: [ 38 | [ 39 | '@docusaurus/preset-classic', 40 | { 41 | docs: { 42 | routeBasePath: '', 43 | editUrl: 'https://github.com/chartjs/chartjs-chart-smith/edit/master/docs/', 44 | sidebarPath: require.resolve('./sidebars.js'), 45 | }, 46 | theme: { 47 | customCss: require.resolve('./src/css/custom.css'), 48 | }, 49 | }, 50 | ], 51 | ], 52 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chartjs-chart-smith", 3 | "author": { 4 | "name": "Evert Timberg" 5 | }, 6 | "description": "Create Smith Chart with Chart.js", 7 | "version": "0.3.0", 8 | "license": "MIT", 9 | "homepage": "https://github.com/chartjs/chartjs-chart-smith", 10 | "jsdelivr": "dist/chartjs-chart-smith.min.js", 11 | "unpkg": "dist/chartjs-chart-smith.min.js", 12 | "main": "dist/chartjs-chart-smith.min.js", 13 | "keywords": [ 14 | "chartjs", 15 | "smith chart" 16 | ], 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/chartjs/chartjs-chart-smith.git" 20 | }, 21 | "files": [ 22 | "bower.json", 23 | "dist/*.js" 24 | ], 25 | "scripts": { 26 | "docs": "cd docs && npm install && npm run build && mkdir -p ../dist && cp -r build ../dist/docs" 27 | }, 28 | "devDependencies": { 29 | "@rollup/plugin-commonjs": "^11.1.0", 30 | "@rollup/plugin-node-resolve": "^7.1.3", 31 | "chart.js": "~2.9.3", 32 | "coveralls": "^3.1.0", 33 | "eslint-config-chartjs": "^0.2.0", 34 | "eslint-config-esnext": "^4.0.0", 35 | "gulp": "^4.0.2", 36 | "gulp-eslint": "^6.0.0", 37 | "gulp-file": "^0.4.0", 38 | "gulp-replace": "^1.0.0", 39 | "gulp-streamify": "^1.0.2", 40 | "gulp-zip": "^5.0.1", 41 | "jasmine-core": "^3.5.0", 42 | "karma": "^6.3.14", 43 | "karma-coverage": "^2.0.2", 44 | "karma-firefox-launcher": "^1.3.0", 45 | "karma-jasmine": "^3.1.1", 46 | "karma-jasmine-html-reporter": "^1.5.4", 47 | "karma-rollup-preprocessor": "^7.0.5", 48 | "karma-spec-reporter": "^0.0.32", 49 | "merge2": "^1.3.0", 50 | "pixelmatch": "^5.2.0", 51 | "rollup": "^2.8.2", 52 | "rollup-plugin-istanbul": "^2.0.1", 53 | "rollup-plugin-terser": "^5.3.0", 54 | "yargs": "^15.3.1" 55 | }, 56 | "peerDependencies": { 57 | "chart.js": ">= 2.7.0 < 3" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-nodejs-modules, import/no-commonjs, no-use-before-define */ 2 | 3 | const gulp = require('gulp'); 4 | const eslint = require('gulp-eslint'); 5 | const file = require('gulp-file'); 6 | const karma = require('karma'); 7 | const path = require('path'); 8 | const {exec} = require('child_process'); 9 | const pkg = require('./package.json'); 10 | 11 | const argv = require('yargs') 12 | .option('output', {alias: 'o', default: 'dist'}) 13 | .option('docs-dir', {default: 'docs'}) 14 | .option('www-dir', {default: 'www'}) 15 | .argv; 16 | 17 | function run(bin, args) { 18 | return new Promise((resolve, reject) => { 19 | const exe = '"' + process.execPath + '"'; 20 | const src = require.resolve(bin); 21 | const ps = exec([exe, src].concat(args || []).join(' ')); 22 | 23 | ps.stdout.pipe(process.stdout); 24 | ps.stderr.pipe(process.stderr); 25 | ps.on('close', (error) => { 26 | if (error) { 27 | reject(error); 28 | } else { 29 | resolve(); 30 | } 31 | }); 32 | }); 33 | } 34 | 35 | gulp.task('build', () => run('rollup/dist/bin/rollup', ['-c', argv.watch ? '--watch' : ''])); 36 | 37 | gulp.task('test', (done) => { 38 | new karma.Server({ 39 | configFile: path.join(__dirname, 'karma.config.js'), 40 | singleRun: !argv.watch, 41 | args: { 42 | coverage: !!argv.coverage, 43 | inputs: (argv.inputs || 'test/specs/**/*.js').split(';'), 44 | watch: argv.watch 45 | } 46 | }, 47 | (error) => { 48 | // https://github.com/karma-runner/gulp-karma/issues/18 49 | error = error ? new Error('Karma returned with the error code: ' + error) : undefined; 50 | done(error); 51 | }).start(); 52 | }); 53 | 54 | gulp.task('lint', () => { 55 | const files = [ 56 | 'src/**/*.js', 57 | 'test/**/*.js', 58 | '*.js' 59 | ]; 60 | 61 | return gulp.src(files) 62 | .pipe(eslint()) 63 | .pipe(eslint.format()) 64 | .pipe(eslint.failAfterError()); 65 | }); 66 | 67 | gulp.task('docs', () => { 68 | const mode = argv.watch ? 'dev' : 'build'; 69 | const out = path.join(argv.output, argv.docsDir); 70 | const args = argv.watch ? '' : '--dest ' + out; 71 | return run('vuepress', [mode, 'docs', args]); 72 | }); 73 | 74 | gulp.task('bower', () => { 75 | const json = JSON.stringify({ 76 | name: pkg.name, 77 | description: pkg.description, 78 | homepage: pkg.homepage, 79 | license: pkg.license, 80 | version: pkg.version, 81 | main: argv.output + '/' + pkg.name + '.js' 82 | }, null, 2); 83 | 84 | return file('bower.json', json, {src: true}) 85 | .pipe(gulp.dest('./')); 86 | }); 87 | 88 | gulp.task('default', gulp.parallel('build')); 89 | -------------------------------------------------------------------------------- /karma.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-commonjs */ 2 | 3 | const commonjs = require('@rollup/plugin-commonjs'); 4 | const istanbul = require('rollup-plugin-istanbul'); 5 | const resolve = require('@rollup/plugin-node-resolve'); 6 | const builds = require('./rollup.config'); 7 | 8 | module.exports = function(karma) { 9 | const args = karma.args || {}; 10 | const regex = args.watch ? /h\.js$/ : /h\.min\.js$/; 11 | const build = builds.filter((v) => v.output.file.match(regex))[0]; 12 | 13 | if (args.watch) { 14 | build.output.sourcemap = 'inline'; 15 | } 16 | 17 | karma.set({ 18 | browsers: ['firefox'], 19 | frameworks: ['jasmine'], 20 | reporters: ['spec', 'kjhtml'], 21 | logLevel: karma.LOG_WARN, 22 | 23 | files: [ 24 | {pattern: './test/fixtures/**/*.js', included: false}, 25 | {pattern: './test/fixtures/**/*.png', included: false}, 26 | 'node_modules/chart.js/dist/Chart.js', 27 | 'test/index.js', 28 | 'src/index.js' 29 | ].concat(args.inputs), 30 | 31 | // Explicitly disable hardware acceleration to make image 32 | // diff more stable when ran on Travis and dev machine. 33 | // https://github.com/chartjs/Chart.js/pull/5629 34 | customLaunchers: { 35 | firefox: { 36 | base: 'Firefox', 37 | prefs: { 38 | 'layers.acceleration.disabled': true 39 | } 40 | } 41 | }, 42 | 43 | preprocessors: { 44 | 'test/fixtures/**/*.js': ['fixtures'], 45 | 'test/specs/**/*.js': ['rollup'], 46 | 'test/index.js': ['rollup'], 47 | 'src/index.js': ['sources'] 48 | }, 49 | 50 | rollupPreprocessor: { 51 | plugins: [ 52 | resolve(), 53 | commonjs() 54 | ], 55 | external: [ 56 | 'chart.js' 57 | ], 58 | output: { 59 | format: 'umd', 60 | globals: { 61 | 'chart.js': 'Chart' 62 | } 63 | } 64 | }, 65 | 66 | customPreprocessors: { 67 | fixtures: { 68 | base: 'rollup', 69 | options: { 70 | output: { 71 | format: 'iife', 72 | name: 'fixture' 73 | } 74 | } 75 | }, 76 | sources: { 77 | base: 'rollup', 78 | options: build 79 | } 80 | } 81 | }); 82 | 83 | if (args.coverage) { 84 | karma.reporters.push('coverage'); 85 | karma.coverageReporter = { 86 | dir: 'coverage/', 87 | reporters: [ 88 | {type: 'html', subdir: 'html'}, 89 | {type: 'lcovonly', subdir: '.'} 90 | ] 91 | }; 92 | [ 93 | karma.rollupPreprocessor, 94 | karma.customPreprocessors.sources.options 95 | ].forEach((v) => { 96 | (v.plugins || (v.plugins = [])).push( 97 | istanbul({ 98 | include: 'src/**/*.js' 99 | })); 100 | }); 101 | } 102 | }; 103 | -------------------------------------------------------------------------------- /.github/workflows/npmpublish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages 3 | 4 | name: Node.js Package 5 | 6 | on: 7 | release: 8 | types: [published] 9 | 10 | jobs: 11 | setup: 12 | runs-on: ubuntu-latest 13 | outputs: 14 | version: ${{ steps.trim.outputs.version }} 15 | steps: 16 | - id: trim 17 | run: echo "::set-output name=version::${TAG:1}" 18 | env: 19 | TAG: ${{ github.event.release.tag_name }} 20 | 21 | test: 22 | runs-on: ubuntu-latest 23 | steps: 24 | - uses: actions/checkout@v2 25 | - name: Use Node.js 26 | uses: actions/setup-node@v1 27 | - name: Test 28 | run: | 29 | npm ci 30 | xvfb-run --auto-servernum npm test 31 | 32 | publish-npm: 33 | needs: [test, setup] 34 | runs-on: ubuntu-latest 35 | steps: 36 | - uses: actions/checkout@v2 37 | - uses: actions/setup-node@v1 38 | with: 39 | node-version: 12 40 | registry-url: https://registry.npmjs.org/ 41 | - name: Setup and build 42 | run: | 43 | npm ci 44 | npm install -g json 45 | json -I -f package.json -e "this.version=\"$VERSION\"" 46 | json -I -f package-lock.json -e "this.version=\"$VERSION\"" 47 | npm run build 48 | ./scripts/docs-config.sh "${VERSION}" 49 | npm run docs 50 | npm run typedoc 51 | npm pack 52 | env: 53 | VERSION: ${{ needs.setup.outputs.version }} 54 | - name: Publish to NPM 55 | run: ./scripts/publish.sh 56 | env: 57 | NODE_AUTH_TOKEN: ${{secrets.NPM_AUTH_TOKEN}} 58 | VERSION: ${{ needs.setup.outputs.version }} 59 | - name: Deploy Docs 60 | run: ./scripts/deploy-docs.sh "$VERSION" 61 | env: 62 | GITHUB_TOKEN: ${{ secrets.GH_AUTH_TOKEN }} 63 | GH_AUTH_EMAIL: ${{ secrets.GH_AUTH_EMAIL }} 64 | VERSION: ${{ needs.setup.outputs.version }} 65 | - name: Upload NPM package file 66 | id: upload-npm-package-file 67 | uses: actions/upload-release-asset@v1 68 | env: 69 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 70 | VERSION: ${{ needs.setup.outputs.version }} 71 | with: 72 | upload_url: ${{ github.event.release.upload_url }} 73 | asset_path: ${{ format('chartjs-chart-smith-{0}.tgz', needs.setup.outputs.version) }} 74 | asset_name: ${{ format('chartjs-chart-smith-{0}.tgz', needs.setup.outputs.version) }} 75 | asset_content_type: application/gzip 76 | -------------------------------------------------------------------------------- /src/controller.js: -------------------------------------------------------------------------------- 1 | import Chart from 'chart.js'; 2 | 3 | const helpers = Chart.helpers; 4 | const resolve = helpers.options.resolve; 5 | const valueOrDefault = helpers.valueOrDefault; 6 | 7 | class Controller extends Chart.controllers.line { 8 | // Not needed since there is only a single scale 9 | // eslint-disable-next-line class-methods-use-this, no-empty-function 10 | linkScales() {} 11 | 12 | updateElement(point, index) { 13 | const me = this; 14 | const meta = me.getMeta(); 15 | const custom = point.custom || {}; 16 | const datasetIndex = me.index; 17 | const yScale = me.getScaleForId(meta.yAxisID); 18 | const xScale = me.getScaleForId(meta.xAxisID); 19 | const lineModel = meta.dataset._model; 20 | 21 | const options = me._resolvePointOptions(point, index); 22 | const {x, y} = me.calculatePointPosition(index); 23 | 24 | // Utility 25 | point._xScale = xScale; 26 | point._yScale = yScale; 27 | point._options = options; 28 | point._datasetIndex = datasetIndex; 29 | point._index = index; 30 | 31 | // Desired view properties 32 | point._model = { 33 | x, 34 | y, 35 | skip: custom.skip || isNaN(x) || isNaN(y), 36 | // Appearance 37 | radius: options.radius, 38 | pointStyle: options.pointStyle, 39 | rotation: options.rotation, 40 | backgroundColor: options.backgroundColor, 41 | borderColor: options.borderColor, 42 | borderWidth: options.borderWidth, 43 | tension: valueOrDefault(custom.tension, lineModel ? lineModel.tension : 0), 44 | steppedLine: lineModel ? lineModel.steppedLine : false, 45 | // Tooltip 46 | hitRadius: options.hitRadius 47 | }; 48 | } 49 | 50 | /** 51 | * @private 52 | */ 53 | _resolvePointOptions(element, index) { 54 | const me = this; 55 | const chart = me.chart; 56 | const dataset = chart.data.datasets[me.index]; 57 | const custom = element.custom || {}; 58 | const options = chart.options.elements.point; 59 | const values = {}; 60 | let i, ilen, key; 61 | 62 | // Scriptable options 63 | const context = { 64 | chart, 65 | dataIndex: index, 66 | dataset, 67 | datasetIndex: me.index 68 | }; 69 | 70 | const ELEMENT_OPTIONS = { 71 | backgroundColor: 'pointBackgroundColor', 72 | borderColor: 'pointBorderColor', 73 | borderWidth: 'pointBorderWidth', 74 | hitRadius: 'pointHitRadius', 75 | hoverBackgroundColor: 'pointHoverBackgroundColor', 76 | hoverBorderColor: 'pointHoverBorderColor', 77 | hoverBorderWidth: 'pointHoverBorderWidth', 78 | hoverRadius: 'pointHoverRadius', 79 | pointStyle: 'pointStyle', 80 | radius: 'pointRadius', 81 | rotation: 'pointRotation' 82 | }; 83 | const keys = Object.keys(ELEMENT_OPTIONS); 84 | 85 | for (i = 0, ilen = keys.length; i < ilen; ++i) { 86 | key = keys[i]; 87 | values[key] = resolve([ 88 | custom[key], 89 | dataset[ELEMENT_OPTIONS[key]], 90 | dataset[key], 91 | options[key] 92 | ], context, index); 93 | } 94 | 95 | return values; 96 | } 97 | 98 | calculatePointPosition(dataIndex) { 99 | const scale = this.chart.scale; 100 | const data = this.getDataset().data[dataIndex]; 101 | return scale.getPointPosition(data.real, data.imag); 102 | } 103 | } 104 | 105 | export default Controller; 106 | -------------------------------------------------------------------------------- /docs/docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: index 3 | title: Smith Charts with Chart.js 4 | sidebar: index 5 | --- 6 | 7 | ## Installation 8 | 9 | ```bash 10 | > npm install chartjs-chart-smith 11 | ``` 12 | 13 | To create a Smith Chart, include `chartjs-chart-smith.js` after `Chart.js` and then create the chart by setting the `type` attribute to `'smith'`. 14 | 15 | ```javascript 16 | var mySmithChart = new Chart({ 17 | type: 'smith', 18 | data: dataObject 19 | }); 20 | ``` 21 | 22 | ### Data Representation 23 | 24 | The smith chart can graph multiple datasets at once. The data for each dataset is in the form of complex numbers. 25 | 26 | ```javascript 27 | var smithChartData = { 28 | datasets: [{ 29 | label: 'Dataset 1', 30 | data: [{ 31 | real: 0, 32 | imag: 1 33 | }, { 34 | real: 1, 35 | imag: 1 36 | }] 37 | }] 38 | }; 39 | ``` 40 | 41 | ### Scale Configuration 42 | The smith chart scale can be configured by placing options into the config that is passed to the chart upon creation. 43 | 44 | ```javascript 45 | new Chart({ 46 | config: { 47 | scale: { 48 | display: true, // setting false will hide the scale 49 | gridLines: { 50 | // setting false will hide the grid lines 51 | display: true, 52 | 53 | // the color of the grid lines 54 | color: rgba(0, 0, 0, 0.1), 55 | 56 | // thickness of grid lines 57 | lineWidth: 1, 58 | }, 59 | ticks: { 60 | // The color of the scale label text 61 | fontColor: 'black', 62 | 63 | // The font family used to render labels 64 | fontFamily: 'Helvetica', 65 | 66 | // The font size in px 67 | fontSize: 12, 68 | 69 | // Style of font 70 | fontStyle: 'normal' 71 | 72 | // Function used to convert real valued ticks to strings 73 | rCallback: function(tick, index, ticks) {} 74 | 75 | // Function used to convert imaginary valued ticks to strings 76 | xCallback: function(tick, index, ticks) {} 77 | } 78 | } 79 | } 80 | }); 81 | ``` 82 | 83 | ### Dataset Configuration 84 | 85 | The datasets for smith charts support many of the same options as the line chart 86 | 87 | ```javascript 88 | { 89 | // Bezier Curve tension. Set to 0 for straight lines 90 | tension: 0, 91 | 92 | // Fill color for dataset 93 | backgroundColor: 'rgba(0, 0, 0, 0.1)', 94 | 95 | // Width of line 96 | borderWidth: 1, 97 | 98 | // Line color 99 | borderColor: 'rgba(0, 0, 0, 0.1)', 100 | 101 | // Line ending style 102 | borderCapStyle: 'butt', 103 | 104 | // Line dash style 105 | borderDash: [], 106 | 107 | // Dash offset. Used in conjunction with borderDash property 108 | borderDashOffset: 0, 109 | 110 | // Line join style 111 | borderJoinStyle: 'miter', 112 | 113 | // Do we fill the line? 114 | fill: true, 115 | 116 | // Point radius 117 | radius: 3, 118 | 119 | // Point style (circle, cross, etc) 120 | pointStyle: 'circle', 121 | 122 | // Point fill color 123 | pointBackgroundColor: 'rgba(0, 0, 0, 0.1)', 124 | 125 | // Point stroke color 126 | pointBorderColor: 'rgba(0, 0, 0, 0.1)', 127 | 128 | // Point stroke width 129 | pointBorderWidth: 1, 130 | 131 | // Used for hit detection 132 | hitRadius: 3 133 | } 134 | ``` 135 | 136 | ## License 137 | 138 | chartjs-chart-smith is available under the [MIT license](http://opensource.org/licenses/MIT). 139 | 140 | ## Bugs & issues 141 | 142 | When reporting bugs or issues, if you could include a link to a simple [jsbin](http://jsbin.com) or similar demonstrating the issue, that would be really helpful. -------------------------------------------------------------------------------- /src/scale.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Defines the scale for the smith chart. 3 | * When built, Chart will be passed via the UMD header 4 | */ 5 | import Chart from 'chart.js'; 6 | const helpers = Chart.helpers; 7 | 8 | const defaults = { 9 | position: 'chartArea', 10 | display: true, 11 | ticks: { 12 | padding: 5, 13 | rCallback: (tick) => tick.toString(), 14 | xCallback: (tick) => tick.toString() + 'i', 15 | } 16 | }; 17 | 18 | class SmithScale extends Chart.Scale { 19 | setDimensions() { 20 | this.height = this.maxHeight; 21 | this.width = this.maxWidth; 22 | this.xCenter = this.left + Math.round(this.width / 2); 23 | this.yCenter = this.top + Math.round(this.height / 2); 24 | 25 | this.paddingLeft = 0; 26 | this.paddingTop = 0; 27 | this.paddingRight = 0; 28 | this.paddingBottom = 0; 29 | } 30 | 31 | buildTicks() { 32 | this.rTicks = [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.2, 1.4, 1.6, 1.8, 2.0, 3.0, 4.0, 5.0, 10.0, 20.0, 50.0]; 33 | this.xTicks = [-50.0, -20.0, -10.0, -5.0, -4.0, -3.0, -2.0, -1.0, -0.8, -0.6, -0.4, -0.2, 0, 0.2, 0.4, 0.6, 0.8, 1.0, 2.0, 3.0, 4.0, 5.0, 10.0, 20.0, 50.0]; 34 | 35 | // Need to do this to make the core scale work 36 | return []; 37 | } 38 | 39 | convertTicksToLabels() { 40 | this.rLabels = this.rTicks.map(function(tick, index, ticks) { 41 | return this.options.ticks.rCallback.apply(this, [tick, index, ticks]); 42 | }, this); 43 | 44 | this.xLabels = this.xTicks.map(function(tick, index, ticks) { 45 | return this.options.ticks.xCallback.apply(this, [tick, index, ticks]); 46 | }, this); 47 | 48 | // Need to do this to make the core scale work 49 | return []; 50 | } 51 | 52 | // There is no tick rotation to calculate, so this needs to be overridden 53 | // eslint-disable-next-line class-methods-use-this, no-empty-function 54 | calculateTickRotation() {} 55 | 56 | // fit function similar to the radial linear scale 57 | fit() { 58 | const me = this; 59 | me.xCenter = (me.left + me.right) / 2; 60 | me.yCenter = (me.top + me.bottom) / 2; 61 | const fontSize = helpers.getValueOrDefault(me.options.ticks.fontSize, Chart.defaults.global.defaultFontSize); 62 | 63 | if (me.options.ticks.display) { 64 | const fontStyle = helpers.getValueOrDefault(me.options.ticks.fontStyle, Chart.defaults.global.defaultFontStyle); 65 | const fontFamily = helpers.getValueOrDefault(me.options.ticks.fontFamily, Chart.defaults.global.defaultFontFamily); 66 | const labelFont = helpers.fontString(fontSize, fontStyle, fontFamily); 67 | me.ctx.font = labelFont; 68 | 69 | const xLabelLengths = me.xLabels.map((tick) => me.ctx.measureText(tick).width); 70 | 71 | // Figure out where these points will go, and assuming they are drawn there, how much will it go outside of the chart area. 72 | // We use that to determine how much padding we nede on each side 73 | me.minDimension = Math.min(me.right - me.left, me.bottom - me.top); 74 | 75 | helpers.each(me.xTicks, (xTick, index) => { 76 | if (xTick !== 0) { 77 | const halfDimension = me.minDimension / 2; 78 | const labelStart = me.getPointPosition(0, xTick); 79 | const cosPhi = (labelStart.x - me.xCenter) / halfDimension; 80 | const sinPhi = (labelStart.y - me.yCenter) / halfDimension; 81 | const labelWidth = xLabelLengths[index] + me.options.ticks.padding; 82 | const pts = [{ 83 | x: labelStart.x + (cosPhi * labelWidth) + (sinPhi * fontSize), 84 | y: labelStart.y + (sinPhi * labelWidth) - (cosPhi * fontSize) 85 | }, { 86 | x: labelStart.x + (cosPhi * labelWidth) - (sinPhi * fontSize), 87 | y: labelStart.y + (sinPhi * labelWidth) + (cosPhi * fontSize) 88 | }]; 89 | 90 | helpers.each(pts, pt => { 91 | me.paddingLeft = Math.max(me.paddingLeft, me.left - pt.x); 92 | me.paddingTop = Math.max(me.paddingTop, me.top - pt.y); 93 | me.paddingRight = Math.max(me.paddingRight, pt.x - me.right); 94 | me.paddingBottom = Math.max(me.paddingBottom, pt.y - me.bottom); 95 | }); 96 | } 97 | }); 98 | } 99 | 100 | me.minDimension = Math.min(me.right - me.left - me.paddingLeft - me.paddingRight, me.bottom - me.top - me.paddingBottom - me.paddingTop); 101 | 102 | // Store data about the arcs that we will draw 103 | me.arcs = []; 104 | me.rLabelPoints = []; 105 | me.xLabelPoints = []; 106 | 107 | // How do we draw the circles? From http://care.iitd.ac.in/People/Faculty/bspanwar/crl713/smith_chart_basics.pdf 108 | // we have that constant resistance circles obey the following 109 | // Center { r / (1 + r), 0}, Radius = 1 / (1 + r) 110 | // 111 | // The center point and radius will need to be scaled based on the size of the canvas 112 | // Draw each of the circles 113 | helpers.each(me.rTicks, r => { 114 | const radius = 1 / (1 + r) * (me.minDimension / 2); // scale for the min dimension 115 | const x = me.xCenter + ((r / (1 + r)) * (me.minDimension / 2)); 116 | 117 | me.arcs.push({ 118 | x, 119 | y: me.yCenter, 120 | r: radius, 121 | s: 0, 122 | e: 2 * Math.PI, 123 | cc: false 124 | }); 125 | 126 | me.rLabelPoints.push({ 127 | x: x - radius, 128 | y: me.yCenter 129 | }); 130 | }); 131 | 132 | helpers.each(me.xTicks, x => { 133 | if (x !== 0) { 134 | const xRadius = (1 / Math.abs(x)) * (me.minDimension / 2); 135 | const xCoord = me.xCenter + (me.minDimension / 2); // far right side of the drawing area 136 | const yCoord = x > 0 ? me.yCenter - xRadius : me.yCenter + xRadius; 137 | 138 | // Ok, these circles are a pain. They need to only be drawn in the region that intersects the 139 | // resistance == 0 circle. This circle has a radius of 0.5 * this.minDimension and is centered 140 | // at (xCenter, yCenter). We will solve the intersection in polar coordinates and define the 141 | // center of our coordinate system as the center of the xCircle, ie (xCoord, yCoord) 142 | 143 | const r0 = Math.sqrt(Math.pow(xCoord - me.xCenter, 2) + Math.pow(yCoord - me.yCenter, 2)); 144 | const phi0 = Math.atan2(me.yCenter - yCoord, me.xCenter - xCoord); 145 | 146 | // A circle with center location r0,phi0 with radius a is defined in polar coordinates by the equation 147 | // r = r0 * cos(phi - phi0) + sqrt(a^2 - ((r0^2) * sin^2(phi - phi0))) 148 | // Our xCircle is defined by r = xRadius because of where we defined the 0,0 point 149 | // Solving the intersection of these equations yields 150 | // phi = 0.5 * arccos((xRadius^2 - a^2) / (r0^2)) + phi0 151 | const arccos = Math.acos((Math.pow(xRadius, 2) - Math.pow(me.minDimension / 2, 2)) / Math.pow(r0, 2)); 152 | const phi2 = ((x > 0 ? 0.5 : -0.5) * arccos) + phi0; 153 | const startAngle = x > 0 ? 0.5 * Math.PI : -0.5 * Math.PI; 154 | 155 | me.arcs.push({ 156 | x: xCoord, 157 | y: yCoord, 158 | r: xRadius, 159 | s: startAngle, 160 | e: phi2, 161 | cc: x <= 0 162 | }); 163 | 164 | me.xLabelPoints.push({ 165 | x: xCoord + (Math.cos(phi2) * xRadius), 166 | y: yCoord + (Math.sin(phi2) * xRadius), 167 | }); 168 | } else { 169 | me.xLabelPoints.push(null); 170 | } 171 | }); 172 | } 173 | 174 | // Need a custom draw function here 175 | draw() { 176 | const me = this; 177 | 178 | if (me.options.display) { 179 | if (me.options.gridLines.display) { 180 | me.ctx.strokeStyle = me.options.gridLines.color; 181 | me.ctx.lineWidth = me.options.gridLines.lineWidth; 182 | 183 | // Draw horizontal line for x === 0 184 | me.ctx.beginPath(); 185 | me.ctx.moveTo(me.xCenter - (me.minDimension / 2), me.yCenter); 186 | me.ctx.lineTo(me.xCenter + (me.minDimension / 2), me.yCenter); 187 | me.ctx.stroke(); 188 | 189 | // Draw each of the arcs 190 | helpers.each(me.arcs, arc => { 191 | me.ctx.beginPath(); 192 | me.ctx.arc(arc.x, arc.y, arc.r, arc.s, arc.e, arc.cc); 193 | me.ctx.stroke(); 194 | }); 195 | } else { 196 | // Simply draw a border line 197 | me.ctx.strokeStyle = me.options.gridLines.color; 198 | me.ctx.lineWidth = me.options.gridLines.lineWidth; 199 | me.ctx.beginPath(); 200 | me.ctx.arc(me.xCenter, me.yCenter, me.minDimension / 2, 0, 2 * Math.PI, false); 201 | me.ctx.stroke(); 202 | } 203 | 204 | if (me.options.ticks.display) { 205 | const fontSize = helpers.getValueOrDefault(me.options.ticks.fontSize, Chart.defaults.global.defaultFontSize); 206 | const fontStyle = helpers.getValueOrDefault(me.options.ticks.fontStyle, Chart.defaults.global.defaultFontStyle); 207 | const fontFamily = helpers.getValueOrDefault(me.options.ticks.fontFamily, Chart.defaults.global.defaultFontFamily); 208 | 209 | const labelFont = helpers.fontString(fontSize, fontStyle, fontFamily); 210 | me.ctx.font = labelFont; 211 | 212 | me.ctx.fillStyle = helpers.getValueOrDefault(me.options.ticks.fontColor, Chart.defaults.global.defaultFontColor); 213 | 214 | helpers.each(me.rLabels, (rLabel, index) => { 215 | const pt = me.rLabelPoints[index]; 216 | 217 | me.ctx.save(); 218 | me.ctx.translate(pt.x, pt.y); 219 | me.ctx.rotate(-0.5 * Math.PI); 220 | me.ctx.textBaseline = 'middle'; 221 | me.ctx.textAlign = 'center'; 222 | me.ctx.fillText(rLabel, 0, 0); 223 | me.ctx.restore(); 224 | }); 225 | 226 | helpers.each(me.xLabels, (xLabel, index) => { 227 | const pt = me.xLabelPoints[index]; 228 | 229 | if (pt) { 230 | let align = 'left'; 231 | let ang = Math.atan2(pt.y - me.yCenter, pt.x - me.xCenter); 232 | let textPadding = me.options.ticks.padding; 233 | 234 | if (pt.x < me.xCenter) { 235 | ang += Math.PI; 236 | align = 'right'; 237 | textPadding *= -1; 238 | } 239 | 240 | me.ctx.save(); 241 | me.ctx.translate(pt.x, pt.y); 242 | me.ctx.rotate(ang); 243 | me.ctx.textBaseline = 'middle'; 244 | me.ctx.textAlign = align; 245 | me.ctx.fillText(xLabel, textPadding, 0); 246 | me.ctx.restore(); 247 | } 248 | }); 249 | } 250 | } 251 | } 252 | getPointPosition(real, imag) { 253 | // look for the intersection of the r circle and the x circle that is not the one along the right side of the canvas 254 | const realRadius = 1 / (1 + real) * (this.minDimension / 2); // scale for the minDimension size 255 | const realCenterX = this.xCenter + ((real / (1 + real)) * (this.minDimension / 2)); 256 | const realCenterY = this.yCenter; 257 | 258 | const imagRadius = (1 / Math.abs(imag)) * (this.minDimension / 2); 259 | const imagCenterX = this.xCenter + (this.minDimension / 2); // far right side of the drawing area 260 | const imagCenterY = imag > 0 ? this.yCenter - imagRadius : this.yCenter + imagRadius; 261 | 262 | const r0 = Math.sqrt(Math.pow(imagCenterX - realCenterX, 2) + Math.pow(imagCenterY - realCenterY, 2)); 263 | const angle = Math.atan2(realCenterY - imagCenterY, realCenterX - imagCenterX); 264 | const arccos = Math.acos((Math.pow(imagRadius, 2) - Math.pow(realRadius, 2)) / Math.pow(r0, 2)); 265 | const phi = imag > 0 ? 0.5 * arccos + angle : -0.5 * arccos + angle; 266 | 267 | // We have an r and a phi from the point (imagCenterX, imagCenterY) 268 | // translate to an x and a undefined 269 | return { 270 | x: imag === 0 ? realCenterX - realRadius : (Math.cos(phi) * imagRadius) + imagCenterX, 271 | y: imag === 0 ? this.yCenter : (Math.sin(phi) * imagRadius) + imagCenterY 272 | }; 273 | } 274 | getLabelForIndex(index, datasetIndex) { 275 | const d = this.chart.data.datasets[datasetIndex].data[index]; 276 | return d.real + ' + ' + d.imag + 'i'; 277 | } 278 | } 279 | 280 | export {defaults}; 281 | export default SmithScale; 282 | --------------------------------------------------------------------------------