├── .eslintignore ├── config.jshintrc ├── .gitignore ├── src ├── charts │ ├── Chart.Bar.js │ ├── Chart.Bubble.js │ ├── Chart.Line.js │ ├── Chart.Doughnut.js │ ├── Chart.PolarArea.js │ ├── Chart.Radar.js │ └── Chart.Scatter.js ├── core │ ├── core.scaleService.js │ ├── core.plugin.js │ ├── core.datasetController.js │ ├── core.element.js │ ├── core.js │ ├── core.animation.js │ └── core.title.js ├── chart.js ├── elements │ ├── element.arc.js │ ├── element.rectangle.js │ ├── element.point.js │ └── element.line.js └── scales │ └── scale.category.js ├── karma.conf.js ├── bower.json ├── .codeclimate.yml ├── karma.conf.ci.js ├── .travis.yml ├── composer.json ├── karma.coverage.conf.js ├── LICENSE.md ├── test ├── core.plugin.tests.js ├── core.element.tests.js ├── mockContext.js ├── element.arc.tests.js └── core.title.tests.js ├── package.json ├── README.md ├── docs ├── 08-Notes.md ├── 05-Polar-Area-Chart.md ├── 04-Radar-Chart.md ├── 06-Pie-Doughnut-Chart.md └── 03-Bar-Chart.md ├── samples ├── combo-bar-line.html ├── bar-stacked.html ├── data_label_combo-bar-line.html ├── polar-area.html ├── line-multi-axis.html ├── pie.html ├── bar-multi-axis.html ├── timeScale │ ├── line-time-point-data.html │ ├── combo-time-scale.html │ └── line-time-scale.html ├── scatter-logX.html ├── line-customTooltips.html ├── radar.html ├── radar-skip-points.html ├── pie-customTooltips.html ├── line-logarithmic.html ├── line-stacked-area.html ├── scatter-multi-axis.html ├── bar.html ├── line-x-axis-filter.html ├── doughnut.html ├── line-skip-points.html └── AnimationCallbacks │ └── progress-bar.html ├── CONTRIBUTING.md ├── .eslintrc └── gulpfile.js /.eslintignore: -------------------------------------------------------------------------------- 1 | **/*{.,-}min.js 2 | -------------------------------------------------------------------------------- /config.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "predef": [ "require", "module" ] 4 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .DS_Store 3 | 4 | node_modules/* 5 | custom/* 6 | 7 | docs/index.md 8 | 9 | bower_components/ 10 | 11 | coverage/* 12 | 13 | nbproject/* 14 | -------------------------------------------------------------------------------- /src/charts/Chart.Bar.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = function(Chart) { 4 | 5 | Chart.Bar = function(context, config) { 6 | config.type = 'bar'; 7 | 8 | return new Chart(context, config); 9 | }; 10 | 11 | }; -------------------------------------------------------------------------------- /src/charts/Chart.Bubble.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = function(Chart) { 4 | 5 | Chart.Bubble = function(context, config) { 6 | config.type = 'bubble'; 7 | return new Chart(context, config); 8 | }; 9 | 10 | }; -------------------------------------------------------------------------------- /src/charts/Chart.Line.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = function(Chart) { 4 | 5 | Chart.Line = function(context, config) { 6 | config.type = 'line'; 7 | 8 | return new Chart(context, config); 9 | }; 10 | 11 | }; -------------------------------------------------------------------------------- /src/charts/Chart.Doughnut.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = function(Chart) { 4 | 5 | Chart.Doughnut = function(context, config) { 6 | config.type = 'doughnut'; 7 | 8 | return new Chart(context, config); 9 | }; 10 | 11 | }; -------------------------------------------------------------------------------- /src/charts/Chart.PolarArea.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = function(Chart) { 4 | 5 | Chart.PolarArea = function(context, config) { 6 | config.type = 'polarArea'; 7 | 8 | return new Chart(context, config); 9 | }; 10 | 11 | }; -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function(config) { 2 | config.set({ 3 | browsers: ['Chrome', 'Firefox'], 4 | frameworks: ['browserify', 'jasmine'], 5 | reporters: ['progress', 'html'], 6 | 7 | preprocessors: { 8 | 'src/**/*.js': ['browserify'] 9 | }, 10 | browserify: { 11 | debug: true 12 | } 13 | }); 14 | }; -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Chart.js", 3 | "version": "2.0.2", 4 | "description": "Simple HTML5 Charts using the canvas element", 5 | "homepage": "https://github.com/nnnick/Chart.js", 6 | "author": "nnnick", 7 | "main": [ 8 | "dist/Chart.js" 9 | ], 10 | "devDependencies": { 11 | "jquery": "~2.1.4" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | engines: 2 | duplication: 3 | enabled: true 4 | config: 5 | languages: 6 | - javascript 7 | eslint: 8 | enabled: true 9 | fixme: 10 | enabled: true 11 | ratings: 12 | paths: 13 | - "src/**/*.js" 14 | exclude_paths: 15 | - dist/**/* 16 | - node_modules/**/* 17 | - test/**/* 18 | - coverage/**/* 19 | -------------------------------------------------------------------------------- /src/charts/Chart.Radar.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = function(Chart) { 4 | 5 | var helpers = Chart.helpers; 6 | 7 | var defaultConfig = { 8 | aspectRatio: 1 9 | }; 10 | 11 | Chart.Radar = function(context, config) { 12 | config.options = helpers.configMerge(defaultConfig, config.options); 13 | config.type = 'radar'; 14 | 15 | return new Chart(context, config); 16 | }; 17 | 18 | }; 19 | -------------------------------------------------------------------------------- /karma.conf.ci.js: -------------------------------------------------------------------------------- 1 | module.exports = function(config) { 2 | var configuration = { 3 | browsers: ['Firefox'], 4 | customLaunchers: { 5 | Chrome_travis_ci: { 6 | base: 'Chrome', 7 | flags: ['--no-sandbox'] 8 | } 9 | }, 10 | frameworks: ['browserify', 'jasmine'], 11 | reporters: ['progress', 'html'], 12 | preprocessors: { 13 | 'src/**/*.js': ['browserify'] 14 | }, 15 | browserify: { 16 | debug: true 17 | } 18 | }; 19 | 20 | if (process.env.TRAVIS) { 21 | configuration.browsers.push('Chrome_travis_ci'); 22 | } 23 | 24 | config.set(configuration); 25 | }; -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "5.6" 4 | - "4.3" 5 | 6 | before_install: 7 | - "export CHROME_BIN=/usr/bin/google-chrome" 8 | - "export DISPLAY=:99.0" 9 | - "sh -e /etc/init.d/xvfb start" 10 | 11 | before_script: 12 | - npm install 13 | 14 | script: 15 | - gulp test 16 | - gulp coverage 17 | - cat ./coverage/lcov.info | ./node_modules/.bin/coveralls 18 | 19 | notifications: 20 | slack: chartjs:pcfCZR6ugg5TEcaLtmIfQYuA 21 | 22 | sudo: required 23 | dist: trusty 24 | 25 | addons: 26 | firefox: latest 27 | apt: 28 | sources: 29 | - google-chrome 30 | packages: 31 | - google-chrome-stable 32 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nnnick/chartjs", 3 | "type": "library", 4 | "description": "Simple HTML5 charts using the canvas element.", 5 | "keywords": [ 6 | "chart", 7 | "js" 8 | ], 9 | "homepage": "http://www.chartjs.org/", 10 | "license": "MIT", 11 | "authors": [ 12 | { 13 | "name": "NICK DOWNIE", 14 | "email": "hello@nickdownie.com" 15 | } 16 | ], 17 | "require": { 18 | "php": ">=5.3.3" 19 | }, 20 | "minimum-stability": "stable", 21 | "extra": { 22 | "branch-alias": { 23 | "release/2.0": "v2.0-dev" 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /karma.coverage.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function(config) { 2 | var configuration = { 3 | browsers: ['Firefox'], 4 | 5 | frameworks: ['browserify', 'jasmine'], 6 | 7 | preprocessors: { 8 | 'src/**/*.js': ['browserify'] 9 | }, 10 | browserify: { 11 | debug: true, 12 | transform: [['browserify-istanbul', { 13 | instrumenterConfig: { 14 | embed: true 15 | } 16 | }]] 17 | }, 18 | 19 | reporters: ['progress', 'coverage'], 20 | coverageReporter: { 21 | dir: 'coverage/', 22 | reporters: [ 23 | { type: 'html', subdir: 'report-html' }, 24 | { type: 'lcovonly', subdir: '.', file: 'lcov.info' } 25 | ] 26 | } 27 | }; 28 | 29 | // If on the CI, use the CI chrome launcher 30 | if (process.env.TRAVIS) { 31 | configuration.browsers.push('Chrome_travis_ci'); 32 | configuration.customLaunchers = { 33 | Chrome_travis_ci: { 34 | base: 'Chrome', 35 | flags: ['--no-sandbox'] 36 | } 37 | }; 38 | } else { 39 | configuration.browsers.push('Chrome'); 40 | } 41 | 42 | config.set(configuration); 43 | }; -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2013-2016 Nick Downie 3 | 4 | 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: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | 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. 9 | -------------------------------------------------------------------------------- /src/charts/Chart.Scatter.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = function(Chart) { 4 | 5 | var defaultConfig = { 6 | hover: { 7 | mode: 'single' 8 | }, 9 | 10 | scales: { 11 | xAxes: [{ 12 | type: "linear", // scatter should not use a category axis 13 | position: "bottom", 14 | id: "x-axis-1" // need an ID so datasets can reference the scale 15 | }], 16 | yAxes: [{ 17 | type: "linear", 18 | position: "left", 19 | id: "y-axis-1" 20 | }] 21 | }, 22 | 23 | tooltips: { 24 | callbacks: { 25 | title: function(tooltipItems, data) { 26 | // Title doesn't make sense for scatter since we format the data as a point 27 | return ''; 28 | }, 29 | label: function(tooltipItem, data) { 30 | return '(' + tooltipItem.xLabel + ', ' + tooltipItem.yLabel + ')'; 31 | } 32 | } 33 | } 34 | }; 35 | 36 | // Register the default config for this type 37 | Chart.defaults.scatter = defaultConfig; 38 | 39 | // Scatter charts use line controllers 40 | Chart.controllers.scatter = Chart.controllers.line; 41 | 42 | Chart.Scatter = function(context, config) { 43 | config.type = 'scatter'; 44 | return new Chart(context, config); 45 | }; 46 | 47 | }; -------------------------------------------------------------------------------- /test/core.plugin.tests.js: -------------------------------------------------------------------------------- 1 | // Plugin tests 2 | describe('Test the plugin system', function() { 3 | beforeEach(function() { 4 | Chart.plugins = []; 5 | }); 6 | 7 | it ('Should register plugins', function() { 8 | var myplugin = {}; 9 | Chart.pluginService.register(myplugin); 10 | expect(Chart.plugins.length).toBe(1); 11 | 12 | // Should only add plugin once 13 | Chart.pluginService.register(myplugin); 14 | expect(Chart.plugins.length).toBe(1); 15 | }); 16 | 17 | it ('Should allow unregistering plugins', function() { 18 | var myplugin = {}; 19 | Chart.pluginService.register(myplugin); 20 | expect(Chart.plugins.length).toBe(1); 21 | 22 | // Should only add plugin once 23 | Chart.pluginService.remove(myplugin); 24 | expect(Chart.plugins.length).toBe(0); 25 | 26 | // Removing a plugin that doesn't exist should not error 27 | Chart.pluginService.remove(myplugin); 28 | }); 29 | 30 | it ('Should allow notifying plugins', function() { 31 | var myplugin = { 32 | count: 0, 33 | trigger: function(chart) { 34 | myplugin.count = chart.count; 35 | } 36 | }; 37 | Chart.pluginService.register(myplugin); 38 | 39 | Chart.pluginService.notifyPlugins('trigger', [{ count: 10 }]); 40 | 41 | expect(myplugin.count).toBe(10); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /src/core/core.scaleService.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = function(Chart) { 4 | 5 | var helpers = Chart.helpers; 6 | 7 | Chart.scaleService = { 8 | // Scale registration object. Extensions can register new scale types (such as log or DB scales) and then 9 | // use the new chart options to grab the correct scale 10 | constructors: {}, 11 | // Use a registration function so that we can move to an ES6 map when we no longer need to support 12 | // old browsers 13 | 14 | // Scale config defaults 15 | defaults: {}, 16 | registerScaleType: function(type, scaleConstructor, defaults) { 17 | this.constructors[type] = scaleConstructor; 18 | this.defaults[type] = helpers.clone(defaults); 19 | }, 20 | getScaleConstructor: function(type) { 21 | return this.constructors.hasOwnProperty(type) ? this.constructors[type] : undefined; 22 | }, 23 | getScaleDefaults: function(type) { 24 | // Return the scale defaults merged with the global settings so that we always use the latest ones 25 | return this.defaults.hasOwnProperty(type) ? helpers.scaleMerge(Chart.defaults.scale, this.defaults[type]) : {}; 26 | }, 27 | addScalesToLayout: function(chartInstance) { 28 | // Adds each scale to the chart.boxes array to be sized accordingly 29 | helpers.each(chartInstance.scales, function(scale) { 30 | Chart.layoutService.addBox(chartInstance, scale); 31 | }); 32 | } 33 | }; 34 | }; -------------------------------------------------------------------------------- /test/core.element.tests.js: -------------------------------------------------------------------------------- 1 | // Test the core element functionality 2 | describe('Core element tests', function() { 3 | it ('should transition model properties', function() { 4 | var element = new Chart.Element({ 5 | _model: { 6 | numberProp: 0, 7 | numberProp2: 100, 8 | _underscoreProp: 0, 9 | stringProp: 'abc', 10 | objectProp: { 11 | myObject: true 12 | }, 13 | colorProp: 'rgb(0, 0, 0)' 14 | } 15 | }); 16 | 17 | // First transition clones model into view 18 | element.transition(0.25); 19 | expect(element._view).toEqual(element._model); 20 | expect(element._start).toEqual(element._model); // also cloned 21 | 22 | expect(element._view.objectProp).toBe(element._model.objectProp); // not cloned 23 | expect(element._start.objectProp).toEqual(element._model.objectProp); // not cloned 24 | 25 | element._model.numberProp = 100; 26 | element._model.numberProp2 = 250; 27 | element._model._underscoreProp = 200; 28 | element._model.stringProp = 'def' 29 | element._model.newStringProp = 'newString'; 30 | element._model.colorProp = 'rgb(255, 255, 0)' 31 | 32 | element.transition(0.25); 33 | expect(element._view).toEqual({ 34 | numberProp: 25, 35 | numberProp2: 137.5, 36 | _underscoreProp: 0, // underscore props are not transition to a new value 37 | stringProp: 'def', 38 | newStringProp: 'newString', 39 | objectProp: { 40 | myObject: true 41 | }, 42 | colorProp: 'rgb(64, 64, 0)', 43 | }); 44 | }); 45 | }); -------------------------------------------------------------------------------- /src/core/core.plugin.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = function(Chart) { 4 | var helpers = Chart.helpers; 5 | 6 | // Plugins are stored here 7 | Chart.plugins = []; 8 | Chart.pluginService = { 9 | // Register a new plugin 10 | register: function(plugin) { 11 | if (Chart.plugins.indexOf(plugin) === -1) { 12 | Chart.plugins.push(plugin); 13 | } 14 | }, 15 | 16 | // Remove a registered plugin 17 | remove: function(plugin) { 18 | var idx = Chart.plugins.indexOf(plugin); 19 | if (idx !== -1) { 20 | Chart.plugins.splice(idx, 1); 21 | } 22 | }, 23 | 24 | // Iterate over all plugins 25 | notifyPlugins: function(method, args, scope) { 26 | helpers.each(Chart.plugins, function(plugin) { 27 | if (plugin[method] && typeof plugin[method] === 'function') { 28 | plugin[method].apply(scope, args); 29 | } 30 | }, scope); 31 | } 32 | }; 33 | 34 | Chart.PluginBase = Chart.Element.extend({ 35 | // Plugin methods. All functions are passed the chart instance 36 | 37 | // Called at start of chart init 38 | beforeInit: helpers.noop, 39 | 40 | // Called at end of chart init 41 | afterInit: helpers.noop, 42 | 43 | // Called at start of update 44 | beforeUpdate: helpers.noop, 45 | 46 | // Called at end of update 47 | afterUpdate: helpers.noop, 48 | 49 | // Called at start of draw 50 | beforeDraw: helpers.noop, 51 | 52 | // Called at end of draw 53 | afterDraw: helpers.noop, 54 | 55 | // Called during destroy 56 | destory: helpers.noop, 57 | }); 58 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chart.js", 3 | "homepage": "http://www.chartjs.org", 4 | "description": "Simple HTML5 charts using the canvas element.", 5 | "version": "2.0.2", 6 | "main": "src/chart.js", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/nnnick/Chart.js.git" 10 | }, 11 | "devDependencies": { 12 | "browserify": "^13.0.0", 13 | "browserify-istanbul": "^0.2.1", 14 | "coveralls": "^2.11.6", 15 | "gulp": "3.9.x", 16 | "gulp-concat": "~2.1.x", 17 | "gulp-connect": "~2.0.5", 18 | "gulp-html-validator": "^0.0.2", 19 | "gulp-jshint": "~1.5.1", 20 | "gulp-karma": "0.0.4", 21 | "gulp-replace": "^0.4.0", 22 | "gulp-size": "~0.4.0", 23 | "gulp-streamify": "^1.0.2", 24 | "gulp-uglify": "~0.2.x", 25 | "gulp-umd": "~0.2.0", 26 | "gulp-util": "~2.2.x", 27 | "inquirer": "^0.5.1", 28 | "jasmine": "^2.3.2", 29 | "jasmine-core": "^2.3.4", 30 | "jquery": "^2.1.4", 31 | "jshint-stylish": "~2.1.0", 32 | "karma": "^0.12.37", 33 | "karma-browserify": "^5.0.1", 34 | "karma-chrome-launcher": "^0.2.0", 35 | "karma-coverage": "^0.5.1", 36 | "karma-firefox-launcher": "^0.1.6", 37 | "karma-jasmine": "^0.3.6", 38 | "karma-jasmine-html-reporter": "^0.1.8", 39 | "merge-stream": "^1.0.0", 40 | "semver": "^3.0.1", 41 | "vinyl-source-stream": "^1.1.0", 42 | "watchify": "^3.7.0" 43 | }, 44 | "spm": { 45 | "main": "Chart.js" 46 | }, 47 | "dependencies": { 48 | "chartjs-color": "^1.0.2", 49 | "moment": "^2.10.6" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Chart.js 2 | 3 | [![Build Status](https://travis-ci.org/nnnick/Chart.js.svg?branch=master)](https://travis-ci.org/nnnick/Chart.js) [![Code Climate](https://codeclimate.com/github/nnnick/Chart.js/badges/gpa.svg)](https://codeclimate.com/github/nnnick/Chart.js)[![Coverage Status](https://coveralls.io/repos/nnnick/Chart.js/badge.svg?branch=master)](https://coveralls.io/r/nnnick/Chart.js?branch=master) 4 | 5 | [![Chart.js on Slack](https://img.shields.io/badge/slack-Chart.js-blue.svg)](https://chartjs-slack-automation.herokuapp.com/) 6 | 7 | *Simple HTML5 Charts using the canvas element* [chartjs.org](http://www.chartjs.org) 8 | 9 | ## Installation 10 | 11 | To download a zip, go to the Chart.js on Github 12 | 13 | To install via npm / bower: 14 | 15 | ```bash 16 | npm install chart.js --save 17 | bower install Chart.js --save 18 | ``` 19 | CDN: https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.0.0/Chart.js 20 | 21 | ## Documentation 22 | 23 | You can find documentation at [nnnick.github.io/Chart.js/docs-v2/](http://nnnick.github.io/Chart.js/docs-v2/). The markdown files that build the site are available under `/docs`. Please note - in some of the json examples of configuration you might notice some liquid tags - this is just for the generating the site html, please disregard. 24 | 25 | ## Contributing 26 | 27 | Before submitting an issue or a pull request to the project, please take a moment to look over the [contributing guidelines](https://github.com/nnnick/Chart.js/blob/master/CONTRIBUTING.md) first. 28 | 29 | For support using Chart.js, please post questions with the [`chartjs` tag on Stack Overflow](http://stackoverflow.com/questions/tagged/chartjs). 30 | 31 | ## Building and Testing 32 | `gulp build`, `gulp test` 33 | 34 | ## License 35 | 36 | Chart.js is available under the [MIT license](http://opensource.org/licenses/MIT). 37 | -------------------------------------------------------------------------------- /src/chart.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Chart.js 3 | * http://chartjs.org/ 4 | * Version: {{ version }} 5 | * 6 | * Copyright 2015 Nick Downie 7 | * Released under the MIT license 8 | * https://github.com/nnnick/Chart.js/blob/master/LICENSE.md 9 | */ 10 | 11 | 12 | var Chart = require('./core/core.js')(); 13 | 14 | require('./core/core.helpers')(Chart); 15 | require('./core/core.element')(Chart); 16 | require('./core/core.animation')(Chart); 17 | require('./core/core.controller')(Chart); 18 | require('./core/core.datasetController')(Chart); 19 | require('./core/core.layoutService')(Chart); 20 | require('./core/core.legend')(Chart); 21 | require('./core/core.plugin.js')(Chart); 22 | require('./core/core.scale')(Chart); 23 | require('./core/core.scaleService')(Chart); 24 | require('./core/core.title')(Chart); 25 | require('./core/core.tooltip')(Chart); 26 | 27 | require('./controllers/controller.bar')(Chart); 28 | require('./controllers/controller.bubble')(Chart); 29 | require('./controllers/controller.doughnut')(Chart); 30 | require('./controllers/controller.line')(Chart); 31 | require('./controllers/controller.polarArea')(Chart); 32 | require('./controllers/controller.radar')(Chart); 33 | 34 | require('./scales/scale.category')(Chart); 35 | require('./scales/scale.linear')(Chart); 36 | require('./scales/scale.logarithmic')(Chart); 37 | require('./scales/scale.radialLinear')(Chart); 38 | require('./scales/scale.time')(Chart); 39 | 40 | require('./elements/element.arc')(Chart); 41 | require('./elements/element.line')(Chart); 42 | require('./elements/element.point')(Chart); 43 | require('./elements/element.rectangle')(Chart); 44 | 45 | require('./charts/Chart.Bar')(Chart); 46 | require('./charts/Chart.Bubble')(Chart); 47 | require('./charts/Chart.Doughnut')(Chart); 48 | require('./charts/Chart.Line')(Chart); 49 | require('./charts/Chart.PolarArea')(Chart); 50 | require('./charts/Chart.Radar')(Chart); 51 | require('./charts/Chart.Scatter')(Chart); 52 | 53 | window.Chart = module.exports = Chart; 54 | -------------------------------------------------------------------------------- /src/core/core.datasetController.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = function(Chart) { 4 | 5 | var helpers = Chart.helpers; 6 | 7 | // Base class for all dataset controllers (line, bar, etc) 8 | Chart.DatasetController = function(chart, datasetIndex) { 9 | this.initialize.call(this, chart, datasetIndex); 10 | }; 11 | 12 | helpers.extend(Chart.DatasetController.prototype, { 13 | initialize: function(chart, datasetIndex) { 14 | this.chart = chart; 15 | this.index = datasetIndex; 16 | this.linkScales(); 17 | this.addElements(); 18 | }, 19 | updateIndex: function(datasetIndex) { 20 | this.index = datasetIndex; 21 | }, 22 | 23 | linkScales: function() { 24 | if (!this.getDataset().xAxisID) { 25 | this.getDataset().xAxisID = this.chart.options.scales.xAxes[0].id; 26 | } 27 | 28 | if (!this.getDataset().yAxisID) { 29 | this.getDataset().yAxisID = this.chart.options.scales.yAxes[0].id; 30 | } 31 | }, 32 | 33 | getDataset: function() { 34 | return this.chart.data.datasets[this.index]; 35 | }, 36 | 37 | getScaleForId: function(scaleID) { 38 | return this.chart.scales[scaleID]; 39 | }, 40 | 41 | reset: function() { 42 | this.update(true); 43 | }, 44 | 45 | buildOrUpdateElements: function buildOrUpdateElements() { 46 | // Handle the number of data points changing 47 | var numData = this.getDataset().data.length; 48 | var numMetaData = this.getDataset().metaData.length; 49 | 50 | // Make sure that we handle number of datapoints changing 51 | if (numData < numMetaData) { 52 | // Remove excess bars for data points that have been removed 53 | this.getDataset().metaData.splice(numData, numMetaData - numData); 54 | } else if (numData > numMetaData) { 55 | // Add new elements 56 | for (var index = numMetaData; index < numData; ++index) { 57 | this.addElementAndReset(index); 58 | } 59 | } 60 | }, 61 | 62 | // Controllers should implement the following 63 | addElements: helpers.noop, 64 | addElementAndReset: helpers.noop, 65 | draw: helpers.noop, 66 | removeHoverStyle: helpers.noop, 67 | setHoverStyle: helpers.noop, 68 | update: helpers.noop 69 | }); 70 | 71 | Chart.DatasetController.extend = helpers.inherits; 72 | 73 | }; -------------------------------------------------------------------------------- /src/core/core.element.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = function(Chart) { 4 | 5 | var helpers = Chart.helpers; 6 | 7 | Chart.elements = {}; 8 | 9 | Chart.Element = function(configuration) { 10 | helpers.extend(this, configuration); 11 | this.initialize.apply(this, arguments); 12 | }; 13 | helpers.extend(Chart.Element.prototype, { 14 | initialize: function() {}, 15 | pivot: function() { 16 | if (!this._view) { 17 | this._view = helpers.clone(this._model); 18 | } 19 | this._start = helpers.clone(this._view); 20 | return this; 21 | }, 22 | transition: function(ease) { 23 | if (!this._view) { 24 | this._view = helpers.clone(this._model); 25 | } 26 | 27 | // No animation -> No Transition 28 | if (ease === 1) { 29 | this._view = this._model; 30 | this._start = null; 31 | return this; 32 | } 33 | 34 | if (!this._start) { 35 | this.pivot(); 36 | } 37 | 38 | helpers.each(this._model, function(value, key) { 39 | 40 | if (key[0] === '_' || !this._model.hasOwnProperty(key)) { 41 | // Only non-underscored properties 42 | } 43 | 44 | // Init if doesn't exist 45 | else if (!this._view.hasOwnProperty(key)) { 46 | if (typeof value === 'number' && !isNaN(this._view[key])) { 47 | this._view[key] = value * ease; 48 | } else { 49 | this._view[key] = value; 50 | } 51 | } 52 | 53 | // No unnecessary computations 54 | else if (value === this._view[key]) { 55 | // It's the same! Woohoo! 56 | } 57 | 58 | // Color transitions if possible 59 | else if (typeof value === 'string') { 60 | try { 61 | var color = helpers.color(this._start[key]).mix(helpers.color(this._model[key]), ease); 62 | this._view[key] = color.rgbString(); 63 | } catch (err) { 64 | this._view[key] = value; 65 | } 66 | } 67 | // Number transitions 68 | else if (typeof value === 'number') { 69 | var startVal = this._start[key] !== undefined && isNaN(this._start[key]) === false ? this._start[key] : 0; 70 | this._view[key] = ((this._model[key] - startVal) * ease) + startVal; 71 | } 72 | // Everything else 73 | else { 74 | this._view[key] = value; 75 | } 76 | }, this); 77 | 78 | return this; 79 | }, 80 | tooltipPosition: function() { 81 | return { 82 | x: this._model.x, 83 | y: this._model.y 84 | }; 85 | }, 86 | hasValue: function() { 87 | return helpers.isNumber(this._model.x) && helpers.isNumber(this._model.y); 88 | } 89 | }); 90 | 91 | Chart.Element.extend = helpers.inherits; 92 | 93 | }; 94 | -------------------------------------------------------------------------------- /src/elements/element.arc.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = function(Chart, moment) { 4 | 5 | var helpers = Chart.helpers; 6 | 7 | Chart.defaults.global.elements.arc = { 8 | backgroundColor: Chart.defaults.global.defaultColor, 9 | borderColor: "#fff", 10 | borderWidth: 2 11 | }; 12 | 13 | Chart.elements.Arc = Chart.Element.extend({ 14 | inLabelRange: function(mouseX) { 15 | var vm = this._view; 16 | 17 | if (vm) { 18 | return (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hoverRadius, 2)); 19 | } else { 20 | return false; 21 | } 22 | }, 23 | inRange: function(chartX, chartY) { 24 | 25 | var vm = this._view; 26 | 27 | if (vm) { 28 | var pointRelativePosition = helpers.getAngleFromPoint(vm, { 29 | x: chartX, 30 | y: chartY 31 | }); 32 | 33 | //Sanitise angle range 34 | var startAngle = vm.startAngle; 35 | var endAngle = vm.endAngle; 36 | while (endAngle < startAngle) { 37 | endAngle += 2.0 * Math.PI; 38 | } 39 | while (pointRelativePosition.angle > endAngle) { 40 | pointRelativePosition.angle -= 2.0 * Math.PI; 41 | } 42 | while (pointRelativePosition.angle < startAngle) { 43 | pointRelativePosition.angle += 2.0 * Math.PI; 44 | } 45 | 46 | //Check if within the range of the open/close angle 47 | var betweenAngles = (pointRelativePosition.angle >= startAngle && pointRelativePosition.angle <= endAngle), 48 | withinRadius = (pointRelativePosition.distance >= vm.innerRadius && pointRelativePosition.distance <= vm.outerRadius); 49 | 50 | return (betweenAngles && withinRadius); 51 | } else { 52 | return false; 53 | } 54 | }, 55 | tooltipPosition: function() { 56 | var vm = this._view; 57 | 58 | var centreAngle = vm.startAngle + ((vm.endAngle - vm.startAngle) / 2), 59 | rangeFromCentre = (vm.outerRadius - vm.innerRadius) / 2 + vm.innerRadius; 60 | return { 61 | x: vm.x + (Math.cos(centreAngle) * rangeFromCentre), 62 | y: vm.y + (Math.sin(centreAngle) * rangeFromCentre) 63 | }; 64 | }, 65 | draw: function() { 66 | 67 | var ctx = this._chart.ctx; 68 | var vm = this._view; 69 | 70 | ctx.beginPath(); 71 | 72 | ctx.arc(vm.x, vm.y, vm.outerRadius, vm.startAngle, vm.endAngle); 73 | 74 | ctx.arc(vm.x, vm.y, vm.innerRadius, vm.endAngle, vm.startAngle, true); 75 | 76 | ctx.closePath(); 77 | ctx.strokeStyle = vm.borderColor; 78 | ctx.lineWidth = vm.borderWidth; 79 | 80 | ctx.fillStyle = vm.backgroundColor; 81 | 82 | ctx.fill(); 83 | ctx.lineJoin = 'bevel'; 84 | 85 | if (vm.borderWidth) { 86 | ctx.stroke(); 87 | } 88 | } 89 | }); 90 | }; 91 | -------------------------------------------------------------------------------- /docs/08-Notes.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Notes 3 | anchor: notes 4 | --- 5 | 6 | ### Browser support 7 | Browser support for the canvas element is available in all modern & major mobile browsers (caniuse.com/canvas). 8 | 9 | For IE8 & below, I would recommend using the polyfill ExplorerCanvas - available at https://code.google.com/p/explorercanvas/. It falls back to Internet explorer's format VML when canvas support is not available. Example use: 10 | 11 | ```html 12 | 13 | 16 | 17 | ``` 18 | 19 | Usually I would recommend feature detection to choose whether or not to load a polyfill, rather than IE conditional comments, however in this case, VML is a Microsoft proprietary format, so it will only work in IE. 20 | 21 | Some important points to note in my experience using ExplorerCanvas as a fallback. 22 | 23 | - Initialise charts on load rather than DOMContentReady when using the library, as sometimes a race condition will occur, and it will result in an error when trying to get the 2d context of a canvas. 24 | - New VML DOM elements are being created for each animation frame and there is no hardware acceleration. As a result animation is usually slow and jerky, with flashing text. It is a good idea to dynamically turn off animation based on canvas support. I recommend using the excellent Modernizr to do this. 25 | - When declaring fonts, the library explorercanvas requires the font name to be in single quotes inside the string. For example, instead of your scaleFontFamily property being simply "Arial", explorercanvas support, use "'Arial'" instead. Chart.js does this for default values. 26 | 27 | ### Bugs & issues 28 | 29 | Please report these on the GitHub page - at github.com/nnnick/Chart.js. If you could include a link to a simple jsbin or similar to demonstrate the issue, that'd be really helpful. 30 | 31 | 32 | ### Contributing 33 | New contributions to the library are welcome, just a couple of guidelines: 34 | 35 | - Tabs for indentation, not spaces please. 36 | - Please ensure you're changing the individual files in `/src`, not the concatenated output in the `Chart.js` file in the root of the repo. 37 | - Please check that your code will pass `jshint` code standards, `gulp jshint` will run this for you. 38 | - Please keep pull requests concise, and document new functionality in the relevant `.md` file. 39 | - Consider whether your changes are useful for all users, or if creating a Chart.js extension would be more appropriate. 40 | 41 | ### License 42 | Chart.js is open source and available under the MIT license. -------------------------------------------------------------------------------- /src/scales/scale.category.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = function(Chart) { 4 | 5 | var helpers = Chart.helpers; 6 | // Default config for a category scale 7 | var defaultConfig = { 8 | position: "bottom" 9 | }; 10 | 11 | var DatasetScale = Chart.Scale.extend({ 12 | // Implement this so that 13 | determineDataLimits: function() { 14 | this.minIndex = 0; 15 | this.maxIndex = this.chart.data.labels.length; 16 | var findIndex; 17 | 18 | if (this.options.ticks.min !== undefined) { 19 | // user specified min value 20 | findIndex = helpers.indexOf(this.chart.data.labels, this.options.ticks.min); 21 | this.minIndex = findIndex !== -1 ? findIndex : this.minIndex; 22 | } 23 | 24 | if (this.options.ticks.max !== undefined) { 25 | // user specified max value 26 | findIndex = helpers.indexOf(this.chart.data.labels, this.options.ticks.max); 27 | this.maxIndex = findIndex !== -1 ? findIndex : this.maxIndex; 28 | } 29 | 30 | this.min = this.chart.data.labels[this.minIndex]; 31 | this.max = this.chart.data.labels[this.maxIndex]; 32 | }, 33 | 34 | buildTicks: function(index) { 35 | // If we are viewing some subset of labels, slice the original array 36 | this.ticks = (this.minIndex === 0 && this.maxIndex === this.chart.data.labels.length) ? this.chart.data.labels : this.chart.data.labels.slice(this.minIndex, this.maxIndex + 1); 37 | }, 38 | 39 | getLabelForIndex: function(index, datasetIndex) { 40 | return this.ticks[index]; 41 | }, 42 | 43 | // Used to get data value locations. Value can either be an index or a numerical value 44 | getPixelForValue: function(value, index, datasetIndex, includeOffset) { 45 | // 1 is added because we need the length but we have the indexes 46 | var offsetAmt = Math.max((this.ticks.length - ((this.options.gridLines.offsetGridLines) ? 0 : 1)), 1); 47 | 48 | if (this.isHorizontal()) { 49 | var innerWidth = this.width - (this.paddingLeft + this.paddingRight); 50 | var valueWidth = innerWidth / offsetAmt; 51 | var widthOffset = (valueWidth * (index - this.minIndex)) + this.paddingLeft; 52 | 53 | if (this.options.gridLines.offsetGridLines && includeOffset) { 54 | widthOffset += (valueWidth / 2); 55 | } 56 | 57 | return this.left + Math.round(widthOffset); 58 | } else { 59 | var innerHeight = this.height - (this.paddingTop + this.paddingBottom); 60 | var valueHeight = innerHeight / offsetAmt; 61 | var heightOffset = (valueHeight * (index - this.minIndex)) + this.paddingTop; 62 | 63 | if (this.options.gridLines.offsetGridLines && includeOffset) { 64 | heightOffset += (valueHeight / 2); 65 | } 66 | 67 | return this.top + Math.round(heightOffset); 68 | } 69 | }, 70 | getPixelForTick: function(index, includeOffset) { 71 | return this.getPixelForValue(this.ticks[index], index + this.minIndex, null, includeOffset); 72 | } 73 | }); 74 | 75 | Chart.scaleService.registerScaleType("category", DatasetScale, defaultConfig); 76 | 77 | }; -------------------------------------------------------------------------------- /src/elements/element.rectangle.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = function(Chart) { 4 | 5 | var helpers = Chart.helpers; 6 | 7 | Chart.defaults.global.elements.rectangle = { 8 | backgroundColor: Chart.defaults.global.defaultColor, 9 | borderWidth: 0, 10 | borderColor: Chart.defaults.global.defaultColor, 11 | borderSkipped: 'bottom' 12 | }; 13 | 14 | Chart.elements.Rectangle = Chart.Element.extend({ 15 | draw: function() { 16 | 17 | var ctx = this._chart.ctx; 18 | var vm = this._view; 19 | 20 | var halfWidth = vm.width / 2, 21 | leftX = vm.x - halfWidth, 22 | rightX = vm.x + halfWidth, 23 | top = vm.base - (vm.base - vm.y), 24 | halfStroke = vm.borderWidth / 2; 25 | 26 | // Canvas doesn't allow us to stroke inside the width so we can 27 | // adjust the sizes to fit if we're setting a stroke on the line 28 | if (vm.borderWidth) { 29 | leftX += halfStroke; 30 | rightX -= halfStroke; 31 | top += halfStroke; 32 | } 33 | 34 | ctx.beginPath(); 35 | 36 | ctx.fillStyle = vm.backgroundColor; 37 | ctx.strokeStyle = vm.borderColor; 38 | ctx.lineWidth = vm.borderWidth; 39 | 40 | // Corner points, from bottom-left to bottom-right clockwise 41 | // | 1 2 | 42 | // | 0 3 | 43 | var corners = [ 44 | [leftX, vm.base], 45 | [leftX, top], 46 | [rightX, top], 47 | [rightX, vm.base] 48 | ]; 49 | 50 | // Find first (starting) corner with fallback to 'bottom' 51 | var borders = ['bottom', 'left', 'top', 'right']; 52 | var startCorner = borders.indexOf(vm.borderSkipped, 0); 53 | if (startCorner === -1) 54 | startCorner = 0; 55 | 56 | function cornerAt(index) { 57 | return corners[(startCorner + index) % 4]; 58 | } 59 | 60 | // Draw rectangle from 'startCorner' 61 | ctx.moveTo.apply(ctx, cornerAt(0)); 62 | for (var i = 1; i < 4; i++) 63 | ctx.lineTo.apply(ctx, cornerAt(i)); 64 | 65 | ctx.fill(); 66 | if (vm.borderWidth) { 67 | ctx.stroke(); 68 | } 69 | }, 70 | height: function() { 71 | var vm = this._view; 72 | return vm.base - vm.y; 73 | }, 74 | inRange: function(mouseX, mouseY) { 75 | var vm = this._view; 76 | var inRange = false; 77 | 78 | if (vm) { 79 | if (vm.y < vm.base) { 80 | inRange = (mouseX >= vm.x - vm.width / 2 && mouseX <= vm.x + vm.width / 2) && (mouseY >= vm.y && mouseY <= vm.base); 81 | } else { 82 | inRange = (mouseX >= vm.x - vm.width / 2 && mouseX <= vm.x + vm.width / 2) && (mouseY >= vm.base && mouseY <= vm.y); 83 | } 84 | } 85 | 86 | return inRange; 87 | }, 88 | inLabelRange: function(mouseX) { 89 | var vm = this._view; 90 | 91 | if (vm) { 92 | return (mouseX >= vm.x - vm.width / 2 && mouseX <= vm.x + vm.width / 2); 93 | } else { 94 | return false; 95 | } 96 | }, 97 | tooltipPosition: function() { 98 | var vm = this._view; 99 | return { 100 | x: vm.x, 101 | y: vm.y 102 | }; 103 | } 104 | }); 105 | 106 | }; -------------------------------------------------------------------------------- /samples/combo-bar-line.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Combo Bar-Line Chart 6 | 7 | 8 | 15 | 16 | 17 | 18 |
19 | 20 |
21 | 22 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /docs/05-Polar-Area-Chart.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Polar Area Chart 3 | anchor: polar-area-chart 4 | --- 5 | ### Introduction 6 | Polar area charts are similar to pie charts, but each segment has the same angle - the radius of the segment differs depending on the value. 7 | 8 | This type of chart is often useful when we want to show a comparison data similar to a pie chart, but also show a scale of values for context. 9 | 10 |
11 | 12 |
13 | 14 | ### Example usage 15 | 16 | ```javascript 17 | new Chart(ctx, { 18 | data: data, 19 | type: 'polarArea', 20 | options: options 21 | }); 22 | ``` 23 | 24 | ### Data structure 25 | 26 | ```javascript 27 | var data = { 28 | datasets: [{ 29 | data: [ 30 | 10, 31 | 32, 32 | 53, 33 | 14, 34 | 22, 35 | ], 36 | backgroundColor: [ 37 | "#F7464A", 38 | "#46BFBD", 39 | "#FDB45C", 40 | "#949FB1", 41 | "#4D5360", 42 | ], 43 | label: 'My dataset' // for legend 44 | }], 45 | labels: [ 46 | "Red", 47 | "Green", 48 | "Yellow", 49 | "Grey", 50 | "Dark Grey" 51 | ] 52 | }; 53 | ``` 54 | As you can see, for the chart data you pass in an array of objects, with a value and a colour. The value attribute should be a number, while the color attribute should be a string. Similar to CSS, for this string you can use HEX notation, RGB, RGBA or HSL. 55 | 56 | ### Chart options 57 | 58 | These are the customisation options specific to Polar Area charts. These options are merged with the [global chart configuration options](#getting-started-global-chart-configuration), and form the options of the chart. 59 | 60 | Name | Type | Default | Description 61 | --- | --- | --- | --- 62 | scale | Array | [See Scales](#scales) and [Defaults for Radial Linear Scale](#getting-started-radial-linear-scale) | Options for the one scale used on the chart. Use this to style the ticks, labels, and grid. 63 | *scale*.type | String |"radialLinear" | As defined in ["Radial Linear"](#scales-radial-linear-scale). 64 | *scale*.lineArc | Boolean | true | When true, lines are circular. 65 | animateRotate | Boolean |true | If true, will animate the rotation of the chart. 66 | animateScale | Boolean | true | If true, will animate scaling the chart. 67 | *legend*.*labels*.generateLabels | Function | `function(data) {} ` | Returns labels for each the legend 68 | *legend*.onClick | Function | function(event, legendItem) {} ` | Handles clicking an individual legend item 69 | legendCallback | Function | `function(chart) ` | Generates the HTML legend via calls to `generateLegend` 70 | 71 | You can override these for your `Chart` instance by passing a second argument into the `PolarArea` method as an object with the keys you want to override. 72 | 73 | For example, we could have a polar area chart with a black stroke on each segment like so: 74 | 75 | ```javascript 76 | new Chart(ctx, { 77 | data: data, 78 | type: "polarArea", 79 | options: { 80 | elements: { 81 | arc: { 82 | borderColor: "#000000" 83 | } 84 | } 85 | } 86 | }); 87 | // This will create a chart with all of the default options, merged from the global config, 88 | // and the PolarArea chart defaults but this particular instance will have `elements.arc.borderColor` set to `"#000000"`. 89 | ``` 90 | 91 | We can also change these defaults values for each PolarArea type that is created, this object is available at `Chart.defaults.polarArea`. 92 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing to Chart.js 2 | ======================== 3 | 4 | Contributions to Chart.js are welcome and encouraged, but please have a look through the guidelines in this document before raising an issue, or writing code for the project. 5 | 6 | 7 | Using issues 8 | ------------ 9 | 10 | The [issue tracker](https://github.com/nnnick/Chart.js/issues) is the preferred channel for reporting bugs, requesting new features and submitting pull requests. 11 | 12 | If you're suggesting a new chart type, please take a look at [writing new chart types](https://github.com/nnnick/Chart.js/blob/master/docs/06-Advanced.md#writing-new-chart-types) in the documentation, and some of the [community extensions](https://github.com/nnnick/Chart.js/blob/master/docs/06-Advanced.md#community-extensions) that have been created already. 13 | 14 | To keep the library lightweight for everyone, it's unlikely we'll add many more chart types to the core of Chart.js, but issues are a good medium to design and spec out how new chart types could work and look. 15 | 16 | Please do not use issues for support requests. For help using Chart.js, please take a look at the [`chartjs`](http://stackoverflow.com/questions/tagged/chartjs) tag on Stack Overflow. 17 | 18 | 19 | Reporting bugs 20 | -------------- 21 | 22 | Well structured, detailed bug reports are hugely valuable for the project. 23 | 24 | Guidlines for reporting bugs: 25 | 26 | - Check the issue search to see if it has already been reported 27 | - Isolate the problem to a simple test case 28 | - Provide a demonstration of the problem on [JS Bin](http://jsbin.com) or similar 29 | 30 | Please provide any additional details associated with the bug, if it's browser or screen density specific, or only happens with a certain configuration or data. 31 | 32 | 33 | Local development 34 | ----------------- 35 | 36 | Run `npm install` to install all the libraries, then run `gulp dev --test` to build and run tests as you make changes. 37 | 38 | 39 | Pull requests 40 | ------------- 41 | 42 | Clear, concise pull requests are excellent at continuing the project's community driven growth. But please review [these guidelines](https://github.com/blog/1943-how-to-write-the-perfect-pull-request) and the guidelines below before starting work on the project. 43 | 44 | Be advised that **Chart.js 1.0.2 is in feature-complete status**. Pull requests adding new features to the 1.x branch will be disregarded. 45 | 46 | Guidlines: 47 | 48 | - Please create an issue first: 49 | - For bugs, we can discuss the fixing approach 50 | - For enhancements, we can discuss if it is within the project scope and avoid duplicate effort 51 | - Please make changes to the files in [`/src`](https://github.com/nnnick/Chart.js/tree/master/src), not `Chart.js` or `Chart.min.js` in the repo root directory, this avoids merge conflicts 52 | - Tabs for indentation, not spaces please 53 | - If adding new functionality, please also update the relevant `.md` file in [`/docs`](https://github.com/nnnick/Chart.js/tree/master/docs) 54 | - Please make your commits in logical sections with clear commit messages 55 | 56 | Joining the project 57 | ------------- 58 | - Active committers and contributors are invited to introduce yourself and request commit access to this project. Please send an email to hello@nickdownie.com or file an issue. 59 | 60 | License 61 | ------- 62 | 63 | By contributing your code, you agree to license your contribution under the [MIT license](https://github.com/nnnick/Chart.js/blob/master/LICENSE.md). 64 | -------------------------------------------------------------------------------- /samples/bar-stacked.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Stacked Bar Chart 6 | 7 | 8 | 15 | 16 | 17 | 18 |
19 | 20 |
21 | 22 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /test/mockContext.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | // Code from http://stackoverflow.com/questions/4406864/html-canvas-unit-testing 3 | var Context = function() { 4 | this._calls = []; // names/args of recorded calls 5 | this._initMethods(); 6 | 7 | this._fillStyle = null; 8 | this._lineCap = null; 9 | this._lineDashOffset = null; 10 | this._lineJoin = null; 11 | this._lineWidth = null; 12 | this._strokeStyle = null; 13 | 14 | // Define properties here so that we can record each time they are set 15 | Object.defineProperties(this, { 16 | "fillStyle": { 17 | 'get': function() { return this._fillStyle; }, 18 | 'set': function(style) { 19 | this._fillStyle = style; 20 | this.record('setFillStyle', [style]); 21 | } 22 | }, 23 | 'lineCap': { 24 | 'get': function() { return this._lineCap; }, 25 | 'set': function(cap) { 26 | this._lineCap = cap; 27 | this.record('setLineCap', [cap]); 28 | } 29 | }, 30 | 'lineDashOffset': { 31 | 'get': function() { return this._lineDashOffset; }, 32 | 'set': function(offset) { 33 | this._lineDashOffset = offset; 34 | this.record('setLineDashOffset', [offset]); 35 | } 36 | }, 37 | 'lineJoin': { 38 | 'get': function() { return this._lineJoin; }, 39 | 'set': function(join) { 40 | this._lineJoin = join; 41 | this.record('setLineJoin', [join]); 42 | } 43 | }, 44 | 'lineWidth': { 45 | 'get': function() { return this._lineWidth; }, 46 | 'set': function (width) { 47 | this._lineWidth = width; 48 | this.record('setLineWidth', [width]); 49 | } 50 | }, 51 | 'strokeStyle': { 52 | 'get': function() { return this._strokeStyle; }, 53 | 'set': function(style) { 54 | this._strokeStyle = style; 55 | this.record('setStrokeStyle', [style]); 56 | } 57 | }, 58 | }); 59 | }; 60 | 61 | Context.prototype._initMethods = function() { 62 | // define methods to test here 63 | // no way to introspect so we have to do some extra work :( 64 | var methods = { 65 | arc: function() {}, 66 | beginPath: function() {}, 67 | bezierCurveTo: function() {}, 68 | clearRect: function() {}, 69 | closePath: function() {}, 70 | fill: function() {}, 71 | fillRect: function() {}, 72 | fillText: function() {}, 73 | lineTo: function(x, y) {}, 74 | measureText: function(text) { 75 | // return the number of characters * fixed size 76 | return text ? { width: text.length * 10 } : {width: 0}; 77 | }, 78 | moveTo: function(x, y) {}, 79 | quadraticCurveTo: function() {}, 80 | restore: function() {}, 81 | rotate: function() {}, 82 | save: function() {}, 83 | setLineDash: function() {}, 84 | stroke: function() {}, 85 | strokeRect: function(x, y, w, h) {}, 86 | setTransform: function(a, b, c, d, e, f) {}, 87 | translate: function(x, y) {}, 88 | }; 89 | 90 | // attach methods to the class itself 91 | var scope = this; 92 | var addMethod = function(name, method) { 93 | scope[methodName] = function() { 94 | scope.record(name, arguments); 95 | return method.apply(scope, arguments); 96 | }; 97 | } 98 | 99 | for (var methodName in methods) { 100 | var method = methods[methodName]; 101 | 102 | addMethod(methodName, method); 103 | } 104 | }; 105 | 106 | Context.prototype.record = function(methodName, args) { 107 | this._calls.push({ 108 | name: methodName, 109 | args: Array.prototype.slice.call(args) 110 | }); 111 | }, 112 | 113 | Context.prototype.getCalls = function() { 114 | return this._calls; 115 | } 116 | 117 | Context.prototype.resetCalls = function() { 118 | this._calls = []; 119 | }; 120 | 121 | window.createMockContext = function() { 122 | return new Context(); 123 | }; 124 | })(); -------------------------------------------------------------------------------- /docs/04-Radar-Chart.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Radar Chart 3 | anchor: radar-chart 4 | --- 5 | 6 | ### Introduction 7 | A radar chart is a way of showing multiple data points and the variation between them. 8 | 9 | They are often useful for comparing the points of two or more different data sets. 10 | 11 |
12 | 13 |
14 | 15 | ### Example usage 16 | 17 | ```javascript 18 | var myRadarChart = new Chart(ctx, { 19 | type: 'radar', 20 | data: data, 21 | options: options 22 | }); 23 | ``` 24 | 25 | ### Data structure 26 | ```javascript 27 | var data = { 28 | labels: ["Eating", "Drinking", "Sleeping", "Designing", "Coding", "Cycling", "Running"], 29 | datasets: [ 30 | { 31 | label: "My First dataset", 32 | backgroundColor: "rgba(220,220,220,0.2)", 33 | borderColor: "rgba(220,220,220,1)", 34 | pointBackgroundColor: "rgba(220,220,220,1)", 35 | pointBorderColor: "#fff", 36 | pointHoverBackgroundColor: "#fff", 37 | pointHoverBorderColor: "rgba(220,220,220,1)", 38 | data: [65, 59, 90, 81, 56, 55, 40] 39 | }, 40 | { 41 | label: "My Second dataset", 42 | backgroundColor: "rgba(151,187,205,0.2)", 43 | borderColor: "rgba(151,187,205,1)", 44 | pointBackgroundColor: "rgba(151,187,205,1)", 45 | pointBorderColor: "#fff", 46 | pointHoverBackgroundColor: "#fff", 47 | pointHoverBorderColor: "rgba(151,187,205,1)", 48 | data: [28, 48, 40, 19, 96, 27, 100] 49 | } 50 | ] 51 | }; 52 | ``` 53 | For a radar chart, to provide context of what each point means, we include an array of strings that show around each point in the chart. 54 | For the radar chart data, we have an array of datasets. Each of these is an object, with a fill colour, a stroke colour, a colour for the fill of each point, and a colour for the stroke of each point. We also have an array of data values. 55 | The label key on each dataset is optional, and can be used when generating a scale for the chart. 56 | 57 | 58 | ### Chart Options 59 | 60 | These are the customisation options specific to Radar charts. These options are merged with the [global chart configuration options](#getting-started-global-chart-configuration), and form the options of the chart. 61 | 62 | The default options for radar chart are defined in `Chart.defaults.radar`. 63 | 64 | Name | Type | Default | Description 65 | --- | --- | --- | --- 66 | scale | Array | [See Scales](#scales) and [Defaults for Radial Linear Scale](#scales-radial-linear-scale) | Options for the one scale used on the chart. Use this to style the ticks, labels, and grid lines. 67 | *scale*.type | String |"radialLinear" | As defined in ["Radial Linear"](#scales-radial-linear-scale). 68 | *elements*.line | Array | | Options for all line elements used on the chart, as defined in the global elements, duplicated here to show Radar chart specific defaults. 69 | *elements.line*.tension | Number | 0 | Tension exhibited by lines when calculating splineCurve. Setting to 0 creates straight lines. 70 | 71 | You can override these for your `Chart` instance by passing a second argument into the `Radar` method as an object with the keys you want to override. 72 | 73 | For example, we could have a radar chart without a point for each on piece of data by doing the following: 74 | 75 | ```javascript 76 | new Chart(ctx, { 77 | type: "radar", 78 | data: data, 79 | options: { 80 | scale: { 81 | reverse: true, 82 | ticks: { 83 | beginAtZero: true 84 | } 85 | } 86 | } 87 | }); 88 | // This will create a chart with all of the default options, merged from the global config, 89 | // and the Radar chart defaults but this particular instance's scale will be reversed as 90 | // well as the ticks beginning at zero. 91 | ``` 92 | 93 | We can also change these defaults values for each Radar type that is created, this object is available at `Chart.defaults.radar`. 94 | -------------------------------------------------------------------------------- /src/core/core.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = function() { 4 | 5 | //Occupy the global variable of Chart, and create a simple base class 6 | var Chart = function(context, config) { 7 | this.config = config; 8 | 9 | // Support a jQuery'd canvas element 10 | if (context.length && context[0].getContext) { 11 | context = context[0]; 12 | } 13 | 14 | // Support a canvas domnode 15 | if (context.getContext) { 16 | context = context.getContext("2d"); 17 | } 18 | 19 | this.ctx = context; 20 | this.canvas = context.canvas; 21 | 22 | // Figure out what the size of the chart will be. 23 | // If the canvas has a specified width and height, we use those else 24 | // we look to see if the canvas node has a CSS width and height. 25 | // If there is still no height, fill the parent container 26 | this.width = context.canvas.width || parseInt(Chart.helpers.getStyle(context.canvas, 'width')) || Chart.helpers.getMaximumWidth(context.canvas); 27 | this.height = context.canvas.height || parseInt(Chart.helpers.getStyle(context.canvas, 'height')) || Chart.helpers.getMaximumHeight(context.canvas); 28 | 29 | this.aspectRatio = this.width / this.height; 30 | 31 | if (isNaN(this.aspectRatio) || isFinite(this.aspectRatio) === false) { 32 | // If the canvas has no size, try and figure out what the aspect ratio will be. 33 | // Some charts prefer square canvases (pie, radar, etc). If that is specified, use that 34 | // else use the canvas default ratio of 2 35 | this.aspectRatio = config.aspectRatio !== undefined ? config.aspectRatio : 2; 36 | } 37 | 38 | // Store the original style of the element so we can set it back 39 | this.originalCanvasStyleWidth = context.canvas.style.width; 40 | this.originalCanvasStyleHeight = context.canvas.style.height; 41 | 42 | // High pixel density displays - multiply the size of the canvas height/width by the device pixel ratio, then scale. 43 | Chart.helpers.retinaScale(this); 44 | 45 | if (config) { 46 | this.controller = new Chart.Controller(this); 47 | } 48 | 49 | // Always bind this so that if the responsive state changes we still work 50 | var _this = this; 51 | Chart.helpers.addResizeListener(context.canvas.parentNode, function() { 52 | if (_this.controller && _this.controller.config.options.responsive) { 53 | _this.controller.resize(); 54 | } 55 | }); 56 | 57 | return this.controller ? this.controller : this; 58 | 59 | }; 60 | 61 | //Globally expose the defaults to allow for user updating/changing 62 | Chart.defaults = { 63 | global: { 64 | responsive: true, 65 | responsiveAnimationDuration: 0, 66 | maintainAspectRatio: true, 67 | events: ["mousemove", "mouseout", "click", "touchstart", "touchmove"], 68 | hover: { 69 | onHover: null, 70 | mode: 'single', 71 | animationDuration: 400 72 | }, 73 | onClick: null, 74 | defaultColor: 'rgba(0,0,0,0.1)', 75 | defaultFontColor: '#666', 76 | defaultFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif", 77 | defaultFontSize: 12, 78 | defaultFontStyle: 'normal', 79 | showLines: true, 80 | 81 | // Element defaults defined in element extensions 82 | elements: {}, 83 | 84 | // Legend callback string 85 | legendCallback: function(chart) { 86 | var text = []; 87 | text.push(''); 96 | 97 | return text.join(""); 98 | } 99 | } 100 | }; 101 | 102 | return Chart; 103 | 104 | }; 105 | -------------------------------------------------------------------------------- /samples/data_label_combo-bar-line.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Combo Bar-Line Chart 7 | 8 | 9 | 16 | 17 | 18 | 19 |
20 | 21 |
22 | 23 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /samples/polar-area.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Polar Area Chart 6 | 7 | 8 | 15 | 16 | 17 | 18 |
19 | 20 |
21 | 22 | 23 | 24 | 120 | 121 | 122 | 123 | -------------------------------------------------------------------------------- /samples/line-multi-axis.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Line Chart Multiple Axes 6 | 7 | 8 | 15 | 16 | 17 | 18 |
19 | 20 |
21 | 22 | 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /test/element.arc.tests.js: -------------------------------------------------------------------------------- 1 | // Test the rectangle element 2 | 3 | describe('Arc element tests', function() { 4 | it ('Should be constructed', function() { 5 | var arc = new Chart.elements.Arc({ 6 | _datasetIndex: 2, 7 | _index: 1 8 | }); 9 | 10 | expect(arc).not.toBe(undefined); 11 | expect(arc._datasetIndex).toBe(2); 12 | expect(arc._index).toBe(1); 13 | }); 14 | 15 | it ('should determine if in range', function() { 16 | var arc = new Chart.elements.Arc({ 17 | _datasetIndex: 2, 18 | _index: 1 19 | }); 20 | 21 | // Make sure we can run these before the view is added 22 | expect(arc.inRange(2, 2)).toBe(false); 23 | expect(arc.inLabelRange(2)).toBe(false); 24 | 25 | // Mock out the view as if the controller put it there 26 | arc._view = { 27 | startAngle: 0, 28 | endAngle: Math.PI / 2, 29 | x: 0, 30 | y: 0, 31 | innerRadius: 5, 32 | outerRadius: 10, 33 | }; 34 | 35 | expect(arc.inRange(2, 2)).toBe(false); 36 | expect(arc.inRange(7, 0)).toBe(true); 37 | expect(arc.inRange(0, 11)).toBe(false); 38 | expect(arc.inRange(Math.sqrt(32), Math.sqrt(32))).toBe(true); 39 | expect(arc.inRange(-1.0 * Math.sqrt(7), Math.sqrt(7))).toBe(false); 40 | }); 41 | 42 | it ('should get the tooltip position', function() { 43 | var arc = new Chart.elements.Arc({ 44 | _datasetIndex: 2, 45 | _index: 1 46 | }); 47 | 48 | // Mock out the view as if the controller put it there 49 | arc._view = { 50 | startAngle: 0, 51 | endAngle: Math.PI / 2, 52 | x: 0, 53 | y: 0, 54 | innerRadius: 0, 55 | outerRadius: Math.sqrt(2), 56 | }; 57 | 58 | var pos = arc.tooltipPosition(); 59 | expect(pos.x).toBeCloseTo(0.5); 60 | expect(pos.y).toBeCloseTo(0.5); 61 | }); 62 | 63 | it ('should draw correctly with no border', function() { 64 | var mockContext = window.createMockContext(); 65 | var arc = new Chart.elements.Arc({ 66 | _datasetIndex: 2, 67 | _index: 1, 68 | _chart: { 69 | ctx: mockContext, 70 | } 71 | }); 72 | 73 | // Mock out the view as if the controller put it there 74 | arc._view = { 75 | startAngle: 0, 76 | endAngle: Math.PI / 2, 77 | x: 10, 78 | y: 5, 79 | innerRadius: 1, 80 | outerRadius: 3, 81 | 82 | backgroundColor: 'rgb(0, 0, 255)', 83 | borderColor: 'rgb(255, 0, 0)', 84 | }; 85 | 86 | arc.draw(); 87 | 88 | expect(mockContext.getCalls()).toEqual([{ 89 | name: 'beginPath', 90 | args: [] 91 | }, { 92 | name: 'arc', 93 | args: [10, 5, 3, 0, Math.PI / 2] 94 | }, { 95 | name: 'arc', 96 | args: [10, 5, 1, Math.PI / 2, 0, true] 97 | }, { 98 | name: 'closePath', 99 | args: [] 100 | }, { 101 | name: 'setStrokeStyle', 102 | args: ['rgb(255, 0, 0)'] 103 | }, { 104 | name: 'setLineWidth', 105 | args: [undefined] 106 | }, { 107 | name: 'setFillStyle', 108 | args: ['rgb(0, 0, 255)'] 109 | }, { 110 | name: 'fill', 111 | args: [] 112 | }, { 113 | name: 'setLineJoin', 114 | args: ['bevel'] 115 | }]); 116 | }); 117 | 118 | it ('should draw correctly with a border', function() { 119 | var mockContext = window.createMockContext(); 120 | var arc = new Chart.elements.Arc({ 121 | _datasetIndex: 2, 122 | _index: 1, 123 | _chart: { 124 | ctx: mockContext, 125 | } 126 | }); 127 | 128 | // Mock out the view as if the controller put it there 129 | arc._view = { 130 | startAngle: 0, 131 | endAngle: Math.PI / 2, 132 | x: 10, 133 | y: 5, 134 | innerRadius: 1, 135 | outerRadius: 3, 136 | 137 | backgroundColor: 'rgb(0, 0, 255)', 138 | borderColor: 'rgb(255, 0, 0)', 139 | borderWidth: 5 140 | }; 141 | 142 | arc.draw(); 143 | 144 | expect(mockContext.getCalls()).toEqual([{ 145 | name: 'beginPath', 146 | args: [] 147 | }, { 148 | name: 'arc', 149 | args: [10, 5, 3, 0, Math.PI / 2] 150 | }, { 151 | name: 'arc', 152 | args: [10, 5, 1, Math.PI / 2, 0, true] 153 | }, { 154 | name: 'closePath', 155 | args: [] 156 | }, { 157 | name: 'setStrokeStyle', 158 | args: ['rgb(255, 0, 0)'] 159 | }, { 160 | name: 'setLineWidth', 161 | args: [5] 162 | }, { 163 | name: 'setFillStyle', 164 | args: ['rgb(0, 0, 255)'] 165 | }, { 166 | name: 'fill', 167 | args: [] 168 | }, { 169 | name: 'setLineJoin', 170 | args: ['bevel'] 171 | }, { 172 | name: 'stroke', 173 | args: [] 174 | }]); 175 | }); 176 | }); -------------------------------------------------------------------------------- /samples/pie.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Pie Chart 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 | 16 | 17 | 120 | 121 | 122 | 123 | -------------------------------------------------------------------------------- /samples/bar-multi-axis.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Bar Chart Multi Axis 6 | 7 | 8 | 15 | 16 | 17 | 18 |
19 | 20 |
21 | 22 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /docs/06-Pie-Doughnut-Chart.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Pie & Doughnut Charts 3 | anchor: doughnut-pie-chart 4 | --- 5 | ### Introduction 6 | Pie and doughnut charts are probably the most commonly used chart there are. They are divided into segments, the arc of each segment shows the proportional value of each piece of data. 7 | 8 | They are excellent at showing the relational proportions between data. 9 | 10 | Pie and doughnut charts are effectively the same class in Chart.js, but have one different default value - their `percentageInnerCutout`. This equates what percentage of the inner should be cut out. This defaults to `0` for pie charts, and `50` for doughnuts. 11 | 12 | They are also registered under two aliases in the `Chart` core. Other than their different default value, and different alias, they are exactly the same. 13 | 14 |
15 | 16 |
17 | 18 |
19 | 20 |
21 | 22 | 23 | ### Example usage 24 | 25 | ```javascript 26 | // For a pie chart 27 | var myPieChart = new Chart(ctx,{ 28 | type: 'pie', 29 | data: data, 30 | options: options 31 | }); 32 | ``` 33 | 34 | ```javascript 35 | // And for a doughnut chart 36 | var myDoughnutChart = new Chart(ctx, { 37 | type: 'doughnut', 38 | data: data, 39 | options: options 40 | }); 41 | ``` 42 | 43 | ### Data structure 44 | 45 | ```javascript 46 | var data = { 47 | labels: [ 48 | "Red", 49 | "Green", 50 | "Yellow" 51 | ], 52 | datasets: [ 53 | { 54 | data: [300, 50, 100], 55 | backgroundColor: [ 56 | "#F7464A", 57 | "#46BFBD", 58 | "#FDB45C" 59 | ], 60 | hoverBackgroundColor: [ 61 | "#FF5A5E", 62 | "#5AD3D1", 63 | "#FFC870" 64 | ] 65 | }] 66 | }; 67 | ``` 68 | 69 | For a pie chart, you must pass in an array of objects with a value and an optional color property. The value attribute should be a number, Chart.js will total all of the numbers and calculate the relative proportion of each. The color attribute should be a string. Similar to CSS, for this string you can use HEX notation, RGB, RGBA or HSL. 70 | 71 | ### Chart options 72 | 73 | These are the customisation options specific to Pie & Doughnut charts. These options are merged with the [global chart configuration options](#getting-started-global-chart-configuration), and form the options of the chart. 74 | 75 | Name | Type | Default | Description 76 | --- | --- | --- | --- 77 | cutoutPercentage | Number | 50 - for doughnut, 0 - for pie | The percentage of the chart that is cut out of the middle. 78 | rotation | Number | -0.5 * Math.PI | Starting angle to draw arcs from 79 | circumference | Number | 2 * Math.PI | Sweep to allow arcs to cover 80 | scale | Array | [See Scales](#scales) and [Defaults for Radial Linear Scale](#getting-started-radial-linear-scale) | Options for the one scale used on the chart. Use this to style the ticks, labels, and grid. 81 | *scale*.type | String |"radialLinear" | As defined in ["Radial Linear"](#scales-radial-linear-scale). 82 | *scale*.lineArc | Boolean | true | When true, lines are arced compared to straight when false. 83 | *animation*.animateRotate | Boolean |true | If true, will animate the rotation of the chart. 84 | *animation*.animateScale | Boolean | false | If true, will animate scaling the Doughnut from the centre. 85 | *legend*.*labels*.generateLabels | Function | `function(data) {} ` | Returns labels for each the legend 86 | *legend*.onClick | Function | function(event, legendItem) {} ` | Handles clicking an individual legend item 87 | 88 | You can override these for your `Chart` instance by passing a second argument into the `Doughnut` method as an object with the keys you want to override. 89 | 90 | For example, we could have a doughnut chart that animates by scaling out from the centre like so: 91 | 92 | ```javascript 93 | new Chart(ctx,{ 94 | type:"doughnut", 95 | animation:{ 96 | animateScale:true 97 | } 98 | }); 99 | // This will create a chart with all of the default options, merged from the global config, 100 | // and the Doughnut chart defaults but this particular instance will have `animateScale` set to `true`. 101 | ``` 102 | 103 | We can also change these default values for each Doughnut type that is created, this object is available at `Chart.defaults.doughnut`. Pie charts also have a clone of these defaults available to change at `Chart.defaults.pie`, with the only difference being `percentageInnerCutout` being set to 0. -------------------------------------------------------------------------------- /samples/timeScale/line-time-point-data.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Time Scale Point Data 6 | 7 | 8 | 9 | 16 | 17 | 18 | 19 |
20 | 21 |
22 |
23 |
24 | 25 | 26 | 27 | 162 | 163 | 164 | 165 | -------------------------------------------------------------------------------- /src/core/core.animation.js: -------------------------------------------------------------------------------- 1 | /*global window: false */ 2 | "use strict"; 3 | 4 | module.exports = function(Chart) { 5 | 6 | var helpers = Chart.helpers; 7 | 8 | Chart.defaults.global.animation = { 9 | duration: 1000, 10 | easing: "easeOutQuart", 11 | onProgress: helpers.noop, 12 | onComplete: helpers.noop 13 | }; 14 | 15 | Chart.Animation = Chart.Element.extend({ 16 | currentStep: null, // the current animation step 17 | numSteps: 60, // default number of steps 18 | easing: "", // the easing to use for this animation 19 | render: null, // render function used by the animation service 20 | 21 | onAnimationProgress: null, // user specified callback to fire on each step of the animation 22 | onAnimationComplete: null // user specified callback to fire when the animation finishes 23 | }); 24 | 25 | Chart.animationService = { 26 | frameDuration: 17, 27 | animations: [], 28 | dropFrames: 0, 29 | request: null, 30 | addAnimation: function(chartInstance, animationObject, duration, lazy) { 31 | 32 | if (!lazy) { 33 | chartInstance.animating = true; 34 | } 35 | 36 | for (var index = 0; index < this.animations.length; ++index) { 37 | if (this.animations[index].chartInstance === chartInstance) { 38 | // replacing an in progress animation 39 | this.animations[index].animationObject = animationObject; 40 | return; 41 | } 42 | } 43 | 44 | this.animations.push({ 45 | chartInstance: chartInstance, 46 | animationObject: animationObject 47 | }); 48 | 49 | // If there are no animations queued, manually kickstart a digest, for lack of a better word 50 | if (this.animations.length === 1) { 51 | this.requestAnimationFrame(); 52 | } 53 | }, 54 | // Cancel the animation for a given chart instance 55 | cancelAnimation: function(chartInstance) { 56 | var index = helpers.findIndex(this.animations, function(animationWrapper) { 57 | return animationWrapper.chartInstance === chartInstance; 58 | }); 59 | 60 | if (index !== -1) { 61 | this.animations.splice(index, 1); 62 | chartInstance.animating = false; 63 | } 64 | }, 65 | requestAnimationFrame: function() { 66 | var me = this; 67 | if (me.request === null) { 68 | // Skip animation frame requests until the active one is executed. 69 | // This can happen when processing mouse events, e.g. 'mousemove' 70 | // and 'mouseout' events will trigger multiple renders. 71 | me.request = helpers.requestAnimFrame.call(window, function() { 72 | me.request = null; 73 | me.startDigest(); 74 | }); 75 | } 76 | }, 77 | startDigest: function() { 78 | 79 | var startTime = Date.now(); 80 | var framesToDrop = 0; 81 | 82 | if (this.dropFrames > 1) { 83 | framesToDrop = Math.floor(this.dropFrames); 84 | this.dropFrames = this.dropFrames % 1; 85 | } 86 | 87 | var i = 0; 88 | while (i < this.animations.length) { 89 | if (this.animations[i].animationObject.currentStep === null) { 90 | this.animations[i].animationObject.currentStep = 0; 91 | } 92 | 93 | this.animations[i].animationObject.currentStep += 1 + framesToDrop; 94 | 95 | if (this.animations[i].animationObject.currentStep > this.animations[i].animationObject.numSteps) { 96 | this.animations[i].animationObject.currentStep = this.animations[i].animationObject.numSteps; 97 | } 98 | 99 | this.animations[i].animationObject.render(this.animations[i].chartInstance, this.animations[i].animationObject); 100 | if (this.animations[i].animationObject.onAnimationProgress && this.animations[i].animationObject.onAnimationProgress.call) { 101 | this.animations[i].animationObject.onAnimationProgress.call(this.animations[i].chartInstance, this.animations[i]); 102 | } 103 | 104 | if (this.animations[i].animationObject.currentStep === this.animations[i].animationObject.numSteps) { 105 | if (this.animations[i].animationObject.onAnimationComplete && this.animations[i].animationObject.onAnimationComplete.call) { 106 | this.animations[i].animationObject.onAnimationComplete.call(this.animations[i].chartInstance, this.animations[i]); 107 | } 108 | 109 | // executed the last frame. Remove the animation. 110 | this.animations[i].chartInstance.animating = false; 111 | 112 | this.animations.splice(i, 1); 113 | } else { 114 | ++i; 115 | } 116 | } 117 | 118 | var endTime = Date.now(); 119 | var dropFrames = (endTime - startTime) / this.frameDuration; 120 | 121 | this.dropFrames += dropFrames; 122 | 123 | // Do we have more stuff to animate? 124 | if (this.animations.length > 0) { 125 | this.requestAnimationFrame(); 126 | } 127 | } 128 | }; 129 | }; -------------------------------------------------------------------------------- /samples/scatter-logX.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Scatter Chart 6 | 7 | 8 | 15 | 16 | 17 | 18 |
19 |
20 | 21 |
22 |
23 | 183 | 184 | 185 | 186 | -------------------------------------------------------------------------------- /samples/line-customTooltips.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Line Chart with Custom Tooltips 6 | 7 | 8 | 33 | 34 | 35 | 36 |
37 | 38 |
39 | 139 | 140 | 141 | 142 | -------------------------------------------------------------------------------- /test/core.title.tests.js: -------------------------------------------------------------------------------- 1 | // Test the rectangle element 2 | 3 | describe('Title block tests', function() { 4 | it('Should be constructed', function() { 5 | var title = new Chart.Title({}); 6 | expect(title).not.toBe(undefined); 7 | }); 8 | 9 | it('Should have the correct default config', function() { 10 | expect(Chart.defaults.global.title).toEqual({ 11 | display: false, 12 | position: 'top', 13 | fullWidth: true, 14 | fontStyle: 'bold', 15 | padding: 10, 16 | text: '' 17 | }) 18 | }); 19 | 20 | it('should update correctly', function() { 21 | var chart = {}; 22 | 23 | var options = Chart.helpers.clone(Chart.defaults.global.title); 24 | options.text = "My title"; 25 | 26 | var title = new Chart.Title({ 27 | chart: chart, 28 | options: options 29 | }); 30 | 31 | var minSize = title.update(400, 200); 32 | 33 | expect(minSize).toEqual({ 34 | width: 400, 35 | height: 0 36 | }); 37 | 38 | // Now we have a height since we display 39 | title.options.display = true; 40 | 41 | minSize = title.update(400, 200); 42 | 43 | expect(minSize).toEqual({ 44 | width: 400, 45 | height: 32 46 | }); 47 | }); 48 | 49 | it('should update correctly when vertical', function() { 50 | var chart = {}; 51 | 52 | var options = Chart.helpers.clone(Chart.defaults.global.title); 53 | options.text = "My title"; 54 | options.position = 'left'; 55 | 56 | var title = new Chart.Title({ 57 | chart: chart, 58 | options: options 59 | }); 60 | 61 | var minSize = title.update(200, 400); 62 | 63 | expect(minSize).toEqual({ 64 | width: 0, 65 | height: 400 66 | }); 67 | 68 | // Now we have a height since we display 69 | title.options.display = true; 70 | 71 | minSize = title.update(200, 400); 72 | 73 | expect(minSize).toEqual({ 74 | width: 32, 75 | height: 400 76 | }); 77 | }); 78 | 79 | it('should draw correctly horizontally', function() { 80 | var chart = {}; 81 | var context = window.createMockContext(); 82 | 83 | var options = Chart.helpers.clone(Chart.defaults.global.title); 84 | options.text = "My title"; 85 | 86 | var title = new Chart.Title({ 87 | chart: chart, 88 | options: options, 89 | ctx: context 90 | }); 91 | 92 | title.update(400, 200); 93 | title.draw(); 94 | 95 | expect(context.getCalls()).toEqual([]); 96 | 97 | // Now we have a height since we display 98 | title.options.display = true; 99 | 100 | var minSize = title.update(400, 200); 101 | title.top = 50; 102 | title.left = 100; 103 | title.bottom = title.top + minSize.height; 104 | title.right = title.left + minSize.width; 105 | title.draw(); 106 | 107 | expect(context.getCalls()).toEqual([{ 108 | name: 'setFillStyle', 109 | args: ['#666'] 110 | }, { 111 | name: 'fillText', 112 | args: ['My title', 300, 66] 113 | }]); 114 | }); 115 | 116 | it ('should draw correctly vertically', function() { 117 | var chart = {}; 118 | var context = window.createMockContext(); 119 | 120 | var options = Chart.helpers.clone(Chart.defaults.global.title); 121 | options.text = "My title"; 122 | options.position = 'left'; 123 | 124 | var title = new Chart.Title({ 125 | chart: chart, 126 | options: options, 127 | ctx: context 128 | }); 129 | 130 | title.update(200, 400); 131 | title.draw(); 132 | 133 | expect(context.getCalls()).toEqual([]); 134 | 135 | // Now we have a height since we display 136 | title.options.display = true; 137 | 138 | var minSize = title.update(200, 400); 139 | title.top = 50; 140 | title.left = 100; 141 | title.bottom = title.top + minSize.height; 142 | title.right = title.left + minSize.width; 143 | title.draw(); 144 | 145 | expect(context.getCalls()).toEqual([{ 146 | name: 'setFillStyle', 147 | args: ['#666'] 148 | }, { 149 | name: 'save', 150 | args: [] 151 | }, { 152 | name: 'translate', 153 | args: [106, 250] 154 | }, { 155 | name: 'rotate', 156 | args: [-0.5 * Math.PI] 157 | }, { 158 | name: 'fillText', 159 | args: ['My title', 0, 0] 160 | }, { 161 | name: 'restore', 162 | args: [] 163 | }]); 164 | 165 | // Rotation is other way on right side 166 | title.options.position = 'right'; 167 | 168 | // Reset call tracker 169 | context.resetCalls(); 170 | 171 | minSize = title.update(200, 400); 172 | title.top = 50; 173 | title.left = 100; 174 | title.bottom = title.top + minSize.height; 175 | title.right = title.left + minSize.width; 176 | title.draw(); 177 | 178 | expect(context.getCalls()).toEqual([{ 179 | name: 'setFillStyle', 180 | args: ['#666'] 181 | }, { 182 | name: 'save', 183 | args: [] 184 | }, { 185 | name: 'translate', 186 | args: [126, 250] 187 | }, { 188 | name: 'rotate', 189 | args: [0.5 * Math.PI] 190 | }, { 191 | name: 'fillText', 192 | args: ['My title', 0, 0] 193 | }, { 194 | name: 'restore', 195 | args: [] 196 | }]); 197 | }); 198 | }); -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | ecmaFeatures: 2 | modules: true 3 | jsx: true 4 | 5 | env: 6 | amd: true 7 | browser: true 8 | es6: true 9 | jquery: true 10 | node: true 11 | 12 | # http://eslint.org/docs/rules/ 13 | rules: 14 | # Possible Errors 15 | comma-dangle: [2, never] 16 | no-cond-assign: 2 17 | no-console: 0 18 | no-constant-condition: 2 19 | no-control-regex: 2 20 | no-debugger: 2 21 | no-dupe-args: 2 22 | no-dupe-keys: 2 23 | no-duplicate-case: 2 24 | no-empty: 2 25 | no-empty-character-class: 2 26 | no-ex-assign: 2 27 | no-extra-boolean-cast: 2 28 | no-extra-parens: 0 29 | no-extra-semi: 2 30 | no-func-assign: 2 31 | no-inner-declarations: [2, functions] 32 | no-invalid-regexp: 2 33 | no-irregular-whitespace: 2 34 | no-negated-in-lhs: 2 35 | no-obj-calls: 2 36 | no-regex-spaces: 2 37 | no-sparse-arrays: 2 38 | no-unexpected-multiline: 2 39 | no-unreachable: 2 40 | use-isnan: 2 41 | valid-jsdoc: 0 42 | valid-typeof: 2 43 | 44 | # Best Practices 45 | accessor-pairs: 2 46 | block-scoped-var: 0 47 | complexity: [2, 6] 48 | consistent-return: 0 49 | curly: 0 50 | default-case: 0 51 | dot-location: 0 52 | dot-notation: 0 53 | eqeqeq: 2 54 | guard-for-in: 2 55 | no-alert: 2 56 | no-caller: 2 57 | no-case-declarations: 2 58 | no-div-regex: 2 59 | no-else-return: 0 60 | no-empty-label: 2 61 | no-empty-pattern: 2 62 | no-eq-null: 2 63 | no-eval: 2 64 | no-extend-native: 2 65 | no-extra-bind: 2 66 | no-fallthrough: 2 67 | no-floating-decimal: 0 68 | no-implicit-coercion: 0 69 | no-implied-eval: 2 70 | no-invalid-this: 0 71 | no-iterator: 2 72 | no-labels: 0 73 | no-lone-blocks: 2 74 | no-loop-func: 2 75 | no-magic-number: 0 76 | no-multi-spaces: 0 77 | no-multi-str: 0 78 | no-native-reassign: 2 79 | no-new-func: 2 80 | no-new-wrappers: 2 81 | no-new: 2 82 | no-octal-escape: 2 83 | no-octal: 2 84 | no-proto: 2 85 | no-redeclare: 2 86 | no-return-assign: 2 87 | no-script-url: 2 88 | no-self-compare: 2 89 | no-sequences: 0 90 | no-throw-literal: 0 91 | no-unused-expressions: 2 92 | no-useless-call: 2 93 | no-useless-concat: 2 94 | no-void: 2 95 | no-warning-comments: 0 96 | no-with: 2 97 | radix: 2 98 | vars-on-top: 0 99 | wrap-iife: 2 100 | yoda: 0 101 | 102 | # Strict 103 | strict: 0 104 | 105 | # Variables 106 | init-declarations: 0 107 | no-catch-shadow: 2 108 | no-delete-var: 2 109 | no-label-var: 2 110 | no-shadow-restricted-names: 2 111 | no-shadow: 0 112 | no-undef-init: 2 113 | no-undef: 0 114 | no-undefined: 0 115 | no-unused-vars: 0 116 | no-use-before-define: 0 117 | 118 | # Node.js and CommonJS 119 | callback-return: 2 120 | global-require: 2 121 | handle-callback-err: 2 122 | no-mixed-requires: 0 123 | no-new-require: 0 124 | no-path-concat: 2 125 | no-process-exit: 2 126 | no-restricted-modules: 0 127 | no-sync: 0 128 | 129 | # Stylistic Issues 130 | array-bracket-spacing: 0 131 | block-spacing: 0 132 | brace-style: 0 133 | camelcase: 0 134 | comma-spacing: 0 135 | comma-style: 0 136 | computed-property-spacing: 0 137 | consistent-this: 0 138 | eol-last: 0 139 | func-names: 0 140 | func-style: 0 141 | id-length: 0 142 | id-match: 0 143 | indent: 0 144 | jsx-quotes: 0 145 | key-spacing: 0 146 | linebreak-style: 0 147 | lines-around-comment: 0 148 | max-depth: 0 149 | max-len: 0 150 | max-nested-callbacks: 0 151 | max-params: 0 152 | max-statements: [2, 30] 153 | new-cap: 0 154 | new-parens: 0 155 | newline-after-var: 0 156 | no-array-constructor: 0 157 | no-bitwise: 0 158 | no-continue: 0 159 | no-inline-comments: 0 160 | no-lonely-if: 0 161 | no-mixed-spaces-and-tabs: 0 162 | no-multiple-empty-lines: 0 163 | no-negated-condition: 0 164 | no-nested-ternary: 0 165 | no-new-object: 0 166 | no-plusplus: 0 167 | no-restricted-syntax: 0 168 | no-spaced-func: 0 169 | no-ternary: 0 170 | no-trailing-spaces: 0 171 | no-underscore-dangle: 0 172 | no-unneeded-ternary: 0 173 | object-curly-spacing: 0 174 | one-var: 0 175 | operator-assignment: 0 176 | operator-linebreak: 0 177 | padded-blocks: 0 178 | quote-props: 0 179 | quotes: 0 180 | require-jsdoc: 0 181 | semi-spacing: 0 182 | semi: 0 183 | sort-vars: 0 184 | space-after-keywords: 0 185 | space-before-blocks: 0 186 | space-before-function-paren: 0 187 | space-before-keywords: 0 188 | space-in-parens: 0 189 | space-infix-ops: 0 190 | space-return-throw-case: 0 191 | space-unary-ops: 0 192 | spaced-comment: 0 193 | wrap-regex: 0 194 | 195 | # ECMAScript 6 196 | arrow-body-style: 0 197 | arrow-parens: 0 198 | arrow-spacing: 0 199 | constructor-super: 0 200 | generator-star-spacing: 0 201 | no-arrow-condition: 0 202 | no-class-assign: 0 203 | no-const-assign: 0 204 | no-dupe-class-members: 0 205 | no-this-before-super: 0 206 | no-var: 0 207 | object-shorthand: 0 208 | prefer-arrow-callback: 0 209 | prefer-const: 0 210 | prefer-reflect: 0 211 | prefer-spread: 0 212 | prefer-template: 0 213 | require-yield: 0 214 | -------------------------------------------------------------------------------- /samples/radar.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Radar Chart 6 | 7 | 8 | 15 | 16 | 17 | 18 |
19 | 20 |
21 | 22 | 23 | 24 | 25 | 26 | 137 | 138 | 139 | 140 | -------------------------------------------------------------------------------- /src/elements/element.point.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = function(Chart) { 4 | 5 | var helpers = Chart.helpers; 6 | 7 | Chart.defaults.global.elements.point = { 8 | radius: 3, 9 | pointStyle: 'circle', 10 | backgroundColor: Chart.defaults.global.defaultColor, 11 | borderWidth: 1, 12 | borderColor: Chart.defaults.global.defaultColor, 13 | // Hover 14 | hitRadius: 1, 15 | hoverRadius: 4, 16 | hoverBorderWidth: 1 17 | }; 18 | 19 | 20 | Chart.elements.Point = Chart.Element.extend({ 21 | inRange: function(mouseX, mouseY) { 22 | var vm = this._view; 23 | 24 | if (vm) { 25 | var hoverRange = vm.hitRadius + vm.radius; 26 | return ((Math.pow(mouseX - vm.x, 2) + Math.pow(mouseY - vm.y, 2)) < Math.pow(hoverRange, 2)); 27 | } else { 28 | return false; 29 | } 30 | }, 31 | inLabelRange: function(mouseX) { 32 | var vm = this._view; 33 | 34 | if (vm) { 35 | return (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hitRadius, 2)); 36 | } else { 37 | return false; 38 | } 39 | }, 40 | tooltipPosition: function() { 41 | var vm = this._view; 42 | return { 43 | x: vm.x, 44 | y: vm.y, 45 | padding: vm.radius + vm.borderWidth 46 | }; 47 | }, 48 | draw: function() { 49 | 50 | var vm = this._view; 51 | var ctx = this._chart.ctx; 52 | 53 | 54 | if (vm.skip) { 55 | return; 56 | } 57 | 58 | if (typeof vm.pointStyle === 'object' && ((vm.pointStyle.toString() === '[object HTMLImageElement]') || (vm.pointStyle.toString() === '[object HTMLCanvasElement]'))) { 59 | ctx.drawImage(vm.pointStyle, vm.x - vm.pointStyle.width / 2, vm.y - vm.pointStyle.height / 2); 60 | return; 61 | } 62 | 63 | if (!isNaN(vm.radius) && vm.radius > 0) { 64 | 65 | ctx.strokeStyle = vm.borderColor || Chart.defaults.global.defaultColor; 66 | ctx.lineWidth = helpers.getValueOrDefault(vm.borderWidth, Chart.defaults.global.elements.point.borderWidth); 67 | 68 | ctx.fillStyle = vm.backgroundColor || Chart.defaults.global.defaultColor; 69 | 70 | var radius = vm.radius; 71 | 72 | var xOffset; 73 | var yOffset; 74 | 75 | switch (vm.pointStyle) { 76 | // Default includes circle 77 | default: ctx.beginPath(); 78 | ctx.arc(vm.x, vm.y, radius, 0, Math.PI * 2); 79 | ctx.closePath(); 80 | ctx.fill(); 81 | break; 82 | case 'triangle': 83 | ctx.beginPath(); 84 | var edgeLength = 3 * radius / Math.sqrt(3); 85 | var height = edgeLength * Math.sqrt(3) / 2; 86 | ctx.moveTo(vm.x - edgeLength / 2, vm.y + height / 3); 87 | ctx.lineTo(vm.x + edgeLength / 2, vm.y + height / 3); 88 | ctx.lineTo(vm.x, vm.y - 2 * height / 3); 89 | ctx.closePath(); 90 | ctx.fill(); 91 | break; 92 | case 'rect': 93 | ctx.fillRect(vm.x - 1 / Math.SQRT2 * radius, vm.y - 1 / Math.SQRT2 * radius, 2 / Math.SQRT2 * radius, 2 / Math.SQRT2 * radius); 94 | ctx.strokeRect(vm.x - 1 / Math.SQRT2 * radius, vm.y - 1 / Math.SQRT2 * radius, 2 / Math.SQRT2 * radius, 2 / Math.SQRT2 * radius); 95 | break; 96 | case 'rectRot': 97 | ctx.translate(vm.x, vm.y); 98 | ctx.rotate(Math.PI / 4); 99 | ctx.fillRect(-1 / Math.SQRT2 * radius, -1 / Math.SQRT2 * radius, 2 / Math.SQRT2 * radius, 2 / Math.SQRT2 * radius); 100 | ctx.strokeRect(-1 / Math.SQRT2 * radius, -1 / Math.SQRT2 * radius, 2 / Math.SQRT2 * radius, 2 / Math.SQRT2 * radius); 101 | ctx.setTransform(1, 0, 0, 1, 0, 0); 102 | break; 103 | case 'cross': 104 | ctx.beginPath(); 105 | ctx.moveTo(vm.x, vm.y + radius); 106 | ctx.lineTo(vm.x, vm.y - radius); 107 | ctx.moveTo(vm.x - radius, vm.y); 108 | ctx.lineTo(vm.x + radius, vm.y); 109 | ctx.closePath(); 110 | break; 111 | case 'crossRot': 112 | ctx.beginPath(); 113 | xOffset = Math.cos(Math.PI / 4) * radius; 114 | yOffset = Math.sin(Math.PI / 4) * radius; 115 | ctx.moveTo(vm.x - xOffset, vm.y - yOffset); 116 | ctx.lineTo(vm.x + xOffset, vm.y + yOffset); 117 | ctx.moveTo(vm.x - xOffset, vm.y + yOffset); 118 | ctx.lineTo(vm.x + xOffset, vm.y - yOffset); 119 | ctx.closePath(); 120 | break; 121 | case 'star': 122 | ctx.beginPath(); 123 | ctx.moveTo(vm.x, vm.y + radius); 124 | ctx.lineTo(vm.x, vm.y - radius); 125 | ctx.moveTo(vm.x - radius, vm.y); 126 | ctx.lineTo(vm.x + radius, vm.y); 127 | xOffset = Math.cos(Math.PI / 4) * radius; 128 | yOffset = Math.sin(Math.PI / 4) * radius; 129 | ctx.moveTo(vm.x - xOffset, vm.y - yOffset); 130 | ctx.lineTo(vm.x + xOffset, vm.y + yOffset); 131 | ctx.moveTo(vm.x - xOffset, vm.y + yOffset); 132 | ctx.lineTo(vm.x + xOffset, vm.y - yOffset); 133 | ctx.closePath(); 134 | break; 135 | case 'line': 136 | ctx.beginPath(); 137 | ctx.moveTo(vm.x - radius, vm.y); 138 | ctx.lineTo(vm.x + radius, vm.y); 139 | ctx.closePath(); 140 | break; 141 | case 'dash': 142 | ctx.beginPath(); 143 | ctx.moveTo(vm.x, vm.y); 144 | ctx.lineTo(vm.x + radius, vm.y); 145 | ctx.closePath(); 146 | break; 147 | } 148 | 149 | ctx.stroke(); 150 | } 151 | } 152 | }); 153 | }; -------------------------------------------------------------------------------- /samples/timeScale/combo-time-scale.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Line Chart - Combo Time Scale 6 | 7 | 8 | 9 | 16 | 17 | 18 | 19 |
20 | 21 |
22 |
23 |
24 | 25 | 26 | 27 | 28 | 29 | 163 | 164 | 165 | 166 | -------------------------------------------------------------------------------- /samples/radar-skip-points.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Radar Chart 6 | 7 | 8 | 15 | 16 | 17 | 18 |
19 | 20 |
21 | 22 | 23 | 24 | 25 | 26 | 143 | 144 | 145 | 146 | -------------------------------------------------------------------------------- /samples/pie-customTooltips.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Pie Chart with Custom Tooltips 6 | 7 | 8 | 9 | 66 | 67 | 68 | 69 |
70 | 71 |
72 |
73 | 74 |
75 | 76 |
77 | 78 | 79 | 172 | 173 | 174 | 175 | -------------------------------------------------------------------------------- /samples/line-logarithmic.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Line Chart 6 | 7 | 8 | 15 | 16 | 17 | 18 |
19 | 20 |
21 | 22 | 23 | 24 | 25 | 26 | 150 | 151 | 152 | 153 | -------------------------------------------------------------------------------- /samples/line-stacked-area.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Line Chart 6 | 7 | 8 | 15 | 16 | 17 | 18 |
19 | 20 |
21 |
22 |
23 | 24 | 25 | 26 | 27 | 28 | 161 | 162 | 163 | 164 | -------------------------------------------------------------------------------- /src/elements/element.line.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = function(Chart) { 4 | 5 | var helpers = Chart.helpers; 6 | 7 | Chart.defaults.global.elements.line = { 8 | tension: 0.4, 9 | backgroundColor: Chart.defaults.global.defaultColor, 10 | borderWidth: 3, 11 | borderColor: Chart.defaults.global.defaultColor, 12 | borderCapStyle: 'butt', 13 | borderDash: [], 14 | borderDashOffset: 0.0, 15 | borderJoinStyle: 'miter', 16 | fill: true // do we fill in the area between the line and its base axis 17 | }; 18 | 19 | Chart.elements.Line = Chart.Element.extend({ 20 | lineToNextPoint: function(previousPoint, point, nextPoint, skipHandler, previousSkipHandler) { 21 | var ctx = this._chart.ctx; 22 | 23 | if (point._view.skip) { 24 | skipHandler.call(this, previousPoint, point, nextPoint); 25 | } else if (previousPoint._view.skip) { 26 | previousSkipHandler.call(this, previousPoint, point, nextPoint); 27 | } else if (point._view.tension === 0) { 28 | ctx.lineTo(point._view.x, point._view.y); 29 | } else { 30 | // Line between points 31 | ctx.bezierCurveTo( 32 | previousPoint._view.controlPointNextX, 33 | previousPoint._view.controlPointNextY, 34 | point._view.controlPointPreviousX, 35 | point._view.controlPointPreviousY, 36 | point._view.x, 37 | point._view.y 38 | ); 39 | } 40 | }, 41 | 42 | draw: function() { 43 | var _this = this; 44 | 45 | var vm = this._view; 46 | var ctx = this._chart.ctx; 47 | var first = this._children[0]; 48 | var last = this._children[this._children.length - 1]; 49 | 50 | function loopBackToStart(drawLineToCenter) { 51 | if (!first._view.skip && !last._view.skip) { 52 | // Draw a bezier line from last to first 53 | ctx.bezierCurveTo( 54 | last._view.controlPointNextX, 55 | last._view.controlPointNextY, 56 | first._view.controlPointPreviousX, 57 | first._view.controlPointPreviousY, 58 | first._view.x, 59 | first._view.y 60 | ); 61 | } else if (drawLineToCenter) { 62 | // Go to center 63 | ctx.lineTo(_this._view.scaleZero.x, _this._view.scaleZero.y); 64 | } 65 | } 66 | 67 | ctx.save(); 68 | 69 | // If we had points and want to fill this line, do so. 70 | if (this._children.length > 0 && vm.fill) { 71 | // Draw the background first (so the border is always on top) 72 | ctx.beginPath(); 73 | 74 | helpers.each(this._children, function(point, index) { 75 | var previous = helpers.previousItem(this._children, index); 76 | var next = helpers.nextItem(this._children, index); 77 | 78 | // First point moves to it's starting position no matter what 79 | if (index === 0) { 80 | if (this._loop) { 81 | ctx.moveTo(vm.scaleZero.x, vm.scaleZero.y); 82 | } else { 83 | ctx.moveTo(point._view.x, vm.scaleZero); 84 | } 85 | 86 | if (point._view.skip) { 87 | if (!this._loop) { 88 | ctx.moveTo(next._view.x, this._view.scaleZero); 89 | } 90 | } else { 91 | ctx.lineTo(point._view.x, point._view.y); 92 | } 93 | } else { 94 | this.lineToNextPoint(previous, point, next, function(previousPoint, point, nextPoint) { 95 | if (this._loop) { 96 | // Go to center 97 | ctx.lineTo(this._view.scaleZero.x, this._view.scaleZero.y); 98 | } else { 99 | ctx.lineTo(previousPoint._view.x, this._view.scaleZero); 100 | ctx.moveTo(nextPoint._view.x, this._view.scaleZero); 101 | } 102 | }, function(previousPoint, point) { 103 | // If we skipped the last point, draw a line to ourselves so that the fill is nice 104 | ctx.lineTo(point._view.x, point._view.y); 105 | }); 106 | } 107 | }, this); 108 | 109 | // For radial scales, loop back around to the first point 110 | if (this._loop) { 111 | loopBackToStart(true); 112 | } else { 113 | //Round off the line by going to the base of the chart, back to the start, then fill. 114 | ctx.lineTo(this._children[this._children.length - 1]._view.x, vm.scaleZero); 115 | ctx.lineTo(this._children[0]._view.x, vm.scaleZero); 116 | } 117 | 118 | ctx.fillStyle = vm.backgroundColor || Chart.defaults.global.defaultColor; 119 | ctx.closePath(); 120 | ctx.fill(); 121 | } 122 | 123 | // Now draw the line between all the points with any borders 124 | ctx.lineCap = vm.borderCapStyle || Chart.defaults.global.elements.line.borderCapStyle; 125 | 126 | // IE 9 and 10 do not support line dash 127 | if (ctx.setLineDash) { 128 | ctx.setLineDash(vm.borderDash || Chart.defaults.global.elements.line.borderDash); 129 | } 130 | 131 | ctx.lineDashOffset = vm.borderDashOffset || Chart.defaults.global.elements.line.borderDashOffset; 132 | ctx.lineJoin = vm.borderJoinStyle || Chart.defaults.global.elements.line.borderJoinStyle; 133 | ctx.lineWidth = vm.borderWidth || Chart.defaults.global.elements.line.borderWidth; 134 | ctx.strokeStyle = vm.borderColor || Chart.defaults.global.defaultColor; 135 | ctx.beginPath(); 136 | 137 | helpers.each(this._children, function(point, index) { 138 | var previous = helpers.previousItem(this._children, index); 139 | var next = helpers.nextItem(this._children, index); 140 | 141 | if (index === 0) { 142 | ctx.moveTo(point._view.x, point._view.y); 143 | } else { 144 | this.lineToNextPoint(previous, point, next, function(previousPoint, point, nextPoint) { 145 | ctx.moveTo(nextPoint._view.x, nextPoint._view.y); 146 | }, function(previousPoint, point) { 147 | // If we skipped the last point, move up to our point preventing a line from being drawn 148 | ctx.moveTo(point._view.x, point._view.y); 149 | }); 150 | } 151 | }, this); 152 | 153 | if (this._loop && this._children.length > 0) { 154 | loopBackToStart(); 155 | } 156 | 157 | ctx.stroke(); 158 | ctx.restore(); 159 | } 160 | }); 161 | }; -------------------------------------------------------------------------------- /samples/scatter-multi-axis.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Scatter Chart Multi Axis 6 | 7 | 8 | 15 | 16 | 17 | 18 |
19 |
20 | 21 |
22 |
23 | 24 | 184 | 185 | 186 | 187 | -------------------------------------------------------------------------------- /samples/bar.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Bar Chart 6 | 7 | 8 | 15 | 16 | 17 | 18 |
19 | 20 |
21 | 22 | 23 | 24 | 25 | 26 | 142 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /samples/line-x-axis-filter.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Chart with xAxis Filtering 6 | 7 | 8 | 15 | 16 | 17 | 18 |
19 | 20 |
21 |
22 |
23 | 24 | 25 | 26 | 27 | 28 | 149 | 150 | 151 | 152 | -------------------------------------------------------------------------------- /samples/doughnut.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Doughnut Chart 6 | 7 | 8 | 15 | 16 | 17 | 18 |
19 | 20 |
21 | 22 | 23 | 24 | 25 | 26 | 174 | 175 | 176 | 177 | -------------------------------------------------------------------------------- /src/core/core.title.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = function(Chart) { 4 | 5 | var helpers = Chart.helpers; 6 | 7 | Chart.defaults.global.title = { 8 | display: false, 9 | position: 'top', 10 | fullWidth: true, // marks that this box should take the full width of the canvas (pushing down other boxes) 11 | 12 | fontStyle: 'bold', 13 | padding: 10, 14 | 15 | // actual title 16 | text: '' 17 | }; 18 | 19 | Chart.Title = Chart.Element.extend({ 20 | 21 | initialize: function(config) { 22 | helpers.extend(this, config); 23 | this.options = helpers.configMerge(Chart.defaults.global.title, config.options); 24 | 25 | // Contains hit boxes for each dataset (in dataset order) 26 | this.legendHitBoxes = []; 27 | }, 28 | 29 | // These methods are ordered by lifecyle. Utilities then follow. 30 | 31 | beforeUpdate: helpers.noop, 32 | update: function(maxWidth, maxHeight, margins) { 33 | 34 | // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;) 35 | this.beforeUpdate(); 36 | 37 | // Absorb the master measurements 38 | this.maxWidth = maxWidth; 39 | this.maxHeight = maxHeight; 40 | this.margins = margins; 41 | 42 | // Dimensions 43 | this.beforeSetDimensions(); 44 | this.setDimensions(); 45 | this.afterSetDimensions(); 46 | // Labels 47 | this.beforeBuildLabels(); 48 | this.buildLabels(); 49 | this.afterBuildLabels(); 50 | 51 | // Fit 52 | this.beforeFit(); 53 | this.fit(); 54 | this.afterFit(); 55 | // 56 | this.afterUpdate(); 57 | 58 | return this.minSize; 59 | 60 | }, 61 | afterUpdate: helpers.noop, 62 | 63 | // 64 | 65 | beforeSetDimensions: helpers.noop, 66 | setDimensions: function() { 67 | // Set the unconstrained dimension before label rotation 68 | if (this.isHorizontal()) { 69 | // Reset position before calculating rotation 70 | this.width = this.maxWidth; 71 | this.left = 0; 72 | this.right = this.width; 73 | } else { 74 | this.height = this.maxHeight; 75 | 76 | // Reset position before calculating rotation 77 | this.top = 0; 78 | this.bottom = this.height; 79 | } 80 | 81 | // Reset padding 82 | this.paddingLeft = 0; 83 | this.paddingTop = 0; 84 | this.paddingRight = 0; 85 | this.paddingBottom = 0; 86 | 87 | // Reset minSize 88 | this.minSize = { 89 | width: 0, 90 | height: 0 91 | }; 92 | }, 93 | afterSetDimensions: helpers.noop, 94 | 95 | // 96 | 97 | beforeBuildLabels: helpers.noop, 98 | buildLabels: helpers.noop, 99 | afterBuildLabels: helpers.noop, 100 | 101 | // 102 | 103 | beforeFit: helpers.noop, 104 | fit: function() { 105 | 106 | var ctx = this.ctx; 107 | var fontSize = helpers.getValueOrDefault(this.options.fontSize, Chart.defaults.global.defaultFontSize); 108 | var fontStyle = helpers.getValueOrDefault(this.options.fontStyle, Chart.defaults.global.defaultFontStyle); 109 | var fontFamily = helpers.getValueOrDefault(this.options.fontFamily, Chart.defaults.global.defaultFontFamily); 110 | var titleFont = helpers.fontString(fontSize, fontStyle, fontFamily); 111 | 112 | // Width 113 | if (this.isHorizontal()) { 114 | this.minSize.width = this.maxWidth; // fill all the width 115 | } else { 116 | this.minSize.width = 0; 117 | } 118 | 119 | // height 120 | if (this.isHorizontal()) { 121 | this.minSize.height = 0; 122 | } else { 123 | this.minSize.height = this.maxHeight; // fill all the height 124 | } 125 | 126 | // Increase sizes here 127 | if (this.isHorizontal()) { 128 | 129 | // Title 130 | if (this.options.display) { 131 | this.minSize.height += fontSize + (this.options.padding * 2); 132 | } 133 | } else { 134 | if (this.options.display) { 135 | this.minSize.width += fontSize + (this.options.padding * 2); 136 | } 137 | } 138 | 139 | this.width = this.minSize.width; 140 | this.height = this.minSize.height; 141 | 142 | }, 143 | afterFit: helpers.noop, 144 | 145 | // Shared Methods 146 | isHorizontal: function() { 147 | return this.options.position === "top" || this.options.position === "bottom"; 148 | }, 149 | 150 | // Actualy draw the title block on the canvas 151 | draw: function() { 152 | if (this.options.display) { 153 | var ctx = this.ctx; 154 | var titleX, titleY; 155 | 156 | var fontColor = helpers.getValueOrDefault(this.options.fontColor, Chart.defaults.global.defaultFontColor); 157 | var fontSize = helpers.getValueOrDefault(this.options.fontSize, Chart.defaults.global.defaultFontSize); 158 | var fontStyle = helpers.getValueOrDefault(this.options.fontStyle, Chart.defaults.global.defaultFontStyle); 159 | var fontFamily = helpers.getValueOrDefault(this.options.fontFamily, Chart.defaults.global.defaultFontFamily); 160 | var titleFont = helpers.fontString(fontSize, fontStyle, fontFamily); 161 | 162 | ctx.fillStyle = fontColor; // render in correct colour 163 | ctx.font = titleFont; 164 | 165 | // Horizontal 166 | if (this.isHorizontal()) { 167 | // Title 168 | ctx.textAlign = "center"; 169 | ctx.textBaseline = 'middle'; 170 | 171 | titleX = this.left + ((this.right - this.left) / 2); // midpoint of the width 172 | titleY = this.top + ((this.bottom - this.top) / 2); // midpoint of the height 173 | 174 | ctx.fillText(this.options.text, titleX, titleY); 175 | } else { 176 | 177 | // Title 178 | titleX = this.options.position === 'left' ? this.left + (fontSize / 2) : this.right - (fontSize / 2); 179 | titleY = this.top + ((this.bottom - this.top) / 2); 180 | var rotation = this.options.position === 'left' ? -0.5 * Math.PI : 0.5 * Math.PI; 181 | 182 | ctx.save(); 183 | ctx.translate(titleX, titleY); 184 | ctx.rotate(rotation); 185 | ctx.textAlign = "center"; 186 | ctx.textBaseline = 'middle'; 187 | ctx.fillText(this.options.text, 0, 0); 188 | ctx.restore(); 189 | } 190 | } 191 | } 192 | }); 193 | }; -------------------------------------------------------------------------------- /samples/timeScale/line-time-scale.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Line Chart 6 | 7 | 8 | 9 | 16 | 17 | 18 | 19 |
20 | 21 |
22 |
23 |
24 | 25 | 26 | 27 | 28 | 29 | 196 | 197 | 198 | 199 | -------------------------------------------------------------------------------- /samples/line-skip-points.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Line Chart 6 | 7 | 8 | 15 | 16 | 17 | 18 |
19 | 20 |
21 |
22 |
23 | 24 | 25 | 26 | 27 | 28 | 162 | 163 | 164 | 165 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'), 2 | concat = require('gulp-concat'), 3 | uglify = require('gulp-uglify'), 4 | util = require('gulp-util'), 5 | jshint = require('gulp-jshint'), 6 | size = require('gulp-size'), 7 | connect = require('gulp-connect'), 8 | replace = require('gulp-replace'), 9 | htmlv = require('gulp-html-validator'), 10 | inquirer = require('inquirer'), 11 | semver = require('semver'), 12 | exec = require('child_process').exec, 13 | fs = require('fs'), 14 | package = require('./package.json'), 15 | bower = require('./bower.json'), 16 | karma = require('gulp-karma'), 17 | browserify = require('browserify'), 18 | streamify = require('gulp-streamify'), 19 | source = require('vinyl-source-stream'), 20 | merge = require('merge-stream'); 21 | 22 | var srcDir = './src/'; 23 | var outDir = './dist/'; 24 | var testDir = './test/'; 25 | 26 | var preTestFiles = [ 27 | './node_modules/moment/min/moment.min.js', 28 | ]; 29 | 30 | var testFiles = [ 31 | './test/mockContext.js', 32 | './test/*.js' 33 | ]; 34 | 35 | gulp.task('build', buildTask); 36 | gulp.task('coverage', coverageTask); 37 | gulp.task('watch', watchTask); 38 | gulp.task('bump', bumpTask); 39 | gulp.task('release', ['build'], releaseTask); 40 | gulp.task('jshint', jshintTask); 41 | gulp.task('test', ['jshint', 'validHTML', 'unittest']); 42 | gulp.task('size', ['library-size', 'module-sizes']); 43 | gulp.task('server', serverTask); 44 | gulp.task('validHTML', validHTMLTask); 45 | gulp.task('unittest', unittestTask); 46 | gulp.task('unittestWatch', unittestWatchTask); 47 | gulp.task('library-size', librarySizeTask); 48 | gulp.task('module-sizes', moduleSizesTask); 49 | gulp.task('_open', _openTask); 50 | gulp.task('dev', ['server', 'default']); 51 | 52 | gulp.task('default', ['build', 'watch']); 53 | 54 | 55 | function buildTask() { 56 | 57 | var bundled = browserify('./src/Chart.js') 58 | .bundle() 59 | .pipe(source('Chart.bundle.js')) 60 | .pipe(streamify(replace('{{ version }}', package.version))) 61 | .pipe(gulp.dest(outDir)) 62 | .pipe(streamify(uglify({ 63 | preserveComments: 'some' 64 | }))) 65 | .pipe(streamify(concat('Chart.bundle.min.js'))) 66 | .pipe(gulp.dest(outDir)); 67 | 68 | var nonBundled = browserify('./src/Chart.js') 69 | .ignore('moment') 70 | .bundle() 71 | .pipe(source('Chart.js')) 72 | .pipe(streamify(replace('{{ version }}', package.version))) 73 | .pipe(gulp.dest(outDir)) 74 | .pipe(streamify(uglify({ 75 | preserveComments: 'some' 76 | }))) 77 | .pipe(streamify(concat('Chart.min.js'))) 78 | .pipe(gulp.dest(outDir)); 79 | 80 | return merge(bundled, nonBundled); 81 | 82 | } 83 | 84 | /* 85 | * Usage : gulp bump 86 | * Prompts: Version increment to bump 87 | * Output: - New version number written into package.json & bower.json 88 | */ 89 | function bumpTask(complete) { 90 | util.log('Current version:', util.colors.cyan(package.version)); 91 | var choices = ['major', 'premajor', 'minor', 'preminor', 'patch', 'prepatch', 'prerelease'].map(function(versionType) { 92 | return versionType + ' (v' + semver.inc(package.version, versionType) + ')'; 93 | }); 94 | inquirer.prompt({ 95 | type: 'list', 96 | name: 'version', 97 | message: 'What version update would you like?', 98 | choices: choices 99 | }, function(res) { 100 | var increment = res.version.split(' ')[0], 101 | newVersion = semver.inc(package.version, increment); 102 | 103 | // Set the new versions into the bower/package object 104 | package.version = newVersion; 105 | bower.version = newVersion; 106 | 107 | // Write these to their own files, then build the output 108 | fs.writeFileSync('package.json', JSON.stringify(package, null, 2)); 109 | fs.writeFileSync('bower.json', JSON.stringify(bower, null, 2)); 110 | 111 | complete(); 112 | }); 113 | } 114 | 115 | 116 | function releaseTask() { 117 | exec('git tag -a v' + package.version); 118 | } 119 | 120 | 121 | function jshintTask() { 122 | return gulp.src(srcDir + '**/*.js') 123 | .pipe(jshint('config.jshintrc')) 124 | .pipe(jshint.reporter('jshint-stylish')) 125 | .pipe(jshint.reporter('fail')); 126 | } 127 | 128 | 129 | function validHTMLTask() { 130 | return gulp.src('samples/*.html') 131 | .pipe(htmlv()); 132 | } 133 | 134 | 135 | function unittestTask() { 136 | var files = ['./src/**/*.js']; 137 | Array.prototype.unshift.apply(files, preTestFiles); 138 | Array.prototype.push.apply(files, testFiles); 139 | 140 | return gulp.src(files) 141 | .pipe(karma({ 142 | configFile: 'karma.conf.ci.js', 143 | action: 'run' 144 | })); 145 | } 146 | 147 | function unittestWatchTask() { 148 | var files = ['./src/**/*.js']; 149 | Array.prototype.unshift.apply(files, preTestFiles); 150 | Array.prototype.push.apply(files, testFiles); 151 | 152 | return gulp.src(files) 153 | .pipe(karma({ 154 | configFile: 'karma.conf.js', 155 | action: 'watch' 156 | })); 157 | } 158 | 159 | function coverageTask() { 160 | var files = ['./src/**/*.js']; 161 | Array.prototype.unshift.apply(files, preTestFiles); 162 | Array.prototype.push.apply(files, testFiles); 163 | 164 | return gulp.src(files) 165 | .pipe(karma({ 166 | configFile: 'karma.coverage.conf.js', 167 | action: 'run' 168 | })); 169 | } 170 | 171 | function librarySizeTask() { 172 | return gulp.src('dist/Chart.bundle.min.js') 173 | .pipe(size({ 174 | gzip: true 175 | })); 176 | } 177 | 178 | function moduleSizesTask() { 179 | return gulp.src(srcDir + '**/*.js') 180 | .pipe(uglify({ 181 | preserveComments: 'some' 182 | })) 183 | .pipe(size({ 184 | showFiles: true, 185 | gzip: true 186 | })); 187 | } 188 | 189 | function watchTask() { 190 | if (util.env.test) { 191 | return gulp.watch('./src/**', ['build', 'unittest', 'unittestWatch']); 192 | } 193 | return gulp.watch('./src/**', ['build']); 194 | } 195 | 196 | function serverTask() { 197 | connect.server({ 198 | port: 8000 199 | }); 200 | } 201 | 202 | // Convenience task for opening the project straight from the command line 203 | 204 | function _openTask() { 205 | exec('open http://localhost:8000'); 206 | exec('subl .'); 207 | } 208 | -------------------------------------------------------------------------------- /docs/03-Bar-Chart.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Bar Chart 3 | anchor: bar-chart 4 | --- 5 | 6 | ### Introduction 7 | A bar chart is a way of showing data as bars. 8 | 9 | It is sometimes used to show trend data, and the comparison of multiple data sets side by side. 10 | 11 |
12 | 13 |
14 | 15 | ### Example usage 16 | ```javascript 17 | var myBarChart = new Chart(ctx, { 18 | type: 'bar', 19 | data: data, 20 | options: options 21 | }); 22 | ``` 23 | 24 | ### Data structure 25 | 26 | ```javascript 27 | var data = { 28 | labels: ["January", "February", "March", "April", "May", "June", "July"], 29 | datasets: [ 30 | { 31 | label: "My First dataset", 32 | 33 | // The properties below allow an array to be specified to change the value of the item at the given index 34 | // String or array - the bar color 35 | backgroundColor: "rgba(220,220,220,0.2)", 36 | 37 | // String or array - bar stroke color 38 | borderColor: "rgba(220,220,220,1)", 39 | 40 | // Number or array - bar border width 41 | borderWidth: 1, 42 | 43 | // String or array - fill color when hovered 44 | hoverBackgroundColor: "rgba(220,220,220,0.2)", 45 | 46 | // String or array - border color when hovered 47 | hoverBorderColor: "rgba(220,220,220,1)", 48 | 49 | // The actual data 50 | data: [65, 59, 80, 81, 56, 55, 40], 51 | 52 | // String - If specified, binds the dataset to a certain y-axis. If not specified, the first y-axis is used. 53 | yAxisID: "y-axis-0", 54 | }, 55 | { 56 | label: "My Second dataset", 57 | backgroundColor: "rgba(220,220,220,0.2)", 58 | borderColor: "rgba(220,220,220,1)", 59 | borderWidth: 1, 60 | hoverBackgroundColor: "rgba(220,220,220,0.2)", 61 | hoverBorderColor: "rgba(220,220,220,1)", 62 | data: [28, 48, 40, 19, 86, 27, 90] 63 | } 64 | ] 65 | }; 66 | ``` 67 | The bar chart has the a very similar data structure to the line chart, and has an array of datasets, each with colours and an array of data. Again, colours are in CSS format. 68 | We have an array of labels too for display. In the example, we are showing the same data as the previous line chart example. 69 | 70 | The label key on each dataset is optional, and can be used when generating a scale for the chart. 71 | 72 | ### Chart Options 73 | 74 | These are the customisation options specific to Bar charts. These options are merged with the [global chart configuration options](#getting-started-global-chart-configuration), and form the options of the chart. 75 | 76 | The default options for bar chart are defined in `Chart.defaults.Bar`. 77 | 78 | Name | Type | Default | Description 79 | --- |:---:| --- | --- 80 | stacked | Boolean | false | 81 | *hover*.mode | String | "label" | Label's hover mode. "label" is used since the x axis displays data by the index in the dataset. 82 | scales | Array | - | - 83 | *scales*.xAxes | Array | | The bar chart officially supports only 1 x-axis but uses an array to keep the API consistent. Use a scatter chart if you need multiple x axes. 84 | *Options for xAxes* | | | 85 | type | String | "Category" | As defined in [Scales](#scales-category-scale). 86 | display | Boolean | true | If true, show the scale. 87 | position | String | "bottom" | Position of the scale. Options are "top" and "bottom" for dataset scales. 88 | id | String | "x-axis-1" | Id of the axis so that data can bind to it 89 | categoryPercentage | Number | 0.8 | Percent (0-1) of the available width (the space between the gridlines for small datasets) for each data-point to use for the bars. [Read More](#bar-chart-barpercentage-vs-categorypercentage) 90 | barPercentage | Number | 0.9 | Percent (0-1) of the available width each bar should be within the category percentage. 1.0 will take the whole category width and put the bars right next to each other. [Read More](#bar-chart-barpercentage-vs-categorypercentage) 91 | gridLines | Array | [See Scales](#scales) | 92 | *gridLines*.offsetGridLines | Boolean | true | If true, the bars for a particular data point fall between the grid lines. If false, the grid line will go right down the middle of the bars. 93 | scaleLabel | Array | [See Scales](#scales) | 94 | ticks | Array | [See Scales](#scales) | 95 | | | | 96 | *scales*.yAxes | Array | `[{ type: "linear" }]` | 97 | *Options for xAxes* | | | 98 | type | String | "linear" | As defined in [Scales](#scales-linear-scale). 99 | display | Boolean | true | If true, show the scale. 100 | position | String | "left" | Position of the scale. Options are "left" and "right" for dataset scales. 101 | id | String | "y-axis-1" | Id of the axis so that data can bind to it. 102 | gridLines | Array | [See Scales](#scales) | 103 | scaleLabel | Array | [See Scales](#scales) | 104 | ticks | Array | [See Scales](#scales) | 105 | 106 | You can override these for your `Chart` instance by passing a second argument into the `Bar` method as an object with the keys you want to override. 107 | 108 | For example, we could have a bar chart without a stroke on each bar by doing the following: 109 | 110 | ```javascript 111 | new Chart(ctx, { 112 | type: "bar", 113 | data: data, 114 | options: { 115 | scales: { 116 | xAxes: [{ 117 | stacked: true 118 | }], 119 | yAxes: [{ 120 | stacked: true 121 | }] 122 | } 123 | } 124 | } 125 | }); 126 | // This will create a chart with all of the default options, merged from the global config, 127 | // and the Bar chart defaults but this particular instance will have `stacked` set to true 128 | // for both x and y axes. 129 | ``` 130 | 131 | We can also change these defaults values for each Bar type that is created, this object is available at `Chart.defaults.Bar`. 132 | 133 | #### barPercentage vs categoryPercentage 134 | 135 | The following shows the relationship between the bar percentage option and the category percentage option. 136 | 137 | ```text 138 | // categoryPercentage: 1.0 139 | // barPercentage: 1.0 140 | Bar: | 1.0 | 1.0 | 141 | Category: | 1.0 | 142 | Sample: |===========| 143 | 144 | // categoryPercentage: 1.0 145 | // barPercentage: 0.5 146 | Bar: |.5| |.5| 147 | Category: | 1.0 | 148 | Sample: |==============| 149 | 150 | // categoryPercentage: 0.5 151 | // barPercentage: 1.0 152 | Bar: |1.||1.| 153 | Category: | .5 | 154 | Sample: |==============| 155 | ``` -------------------------------------------------------------------------------- /samples/AnimationCallbacks/progress-bar.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Animation Callbacks 5 | 6 | 7 | 14 | 15 | 16 | 17 |
18 | 19 | 20 |
21 |
22 |
23 | 24 | 25 | 26 | 27 | 28 | 168 | 169 | 170 | --------------------------------------------------------------------------------