diff --git a/.gitattributes b/.gitattributes index 6b00c4c7f..d6b34c872 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,2 @@ -build/* binary \ No newline at end of file +build/* binary +*.js eol=lf \ No newline at end of file diff --git a/.jscsrc b/.jscsrc new file mode 100644 index 000000000..3b387bd38 --- /dev/null +++ b/.jscsrc @@ -0,0 +1,10 @@ +{ + "preset": "yandex", + "esnext":true, + "excludeFiles":[ + "src/addons/color-brewer.js" + ], + "requireLineFeedAtFileEnd":null, + "disallowMultipleVarDecl":null, + "disallowSpacesInsideParentheses":null +} \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index e5e17fc04..175c9dd0d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,5 @@ notifications: slack: targetprocess:A8M6sVcJSfLnVOYtoMrPL7BA language: node_js -before_script: - - grunt travis node_js: - - "0.10" + - "0.12" diff --git a/Gruntfile.js b/Gruntfile.js index 5cd48af7c..471e34112 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,31 +1,42 @@ /*global module:false*/ var autoprefixer = require('autoprefixer-core'); +var webpack = require('webpack'); +var webpackConfig = require('./config/webpack.test.config'); module.exports = function (grunt) { - // Project configuration. var src = [ - "*.js", - "**/*.js", - "!charts/bar.js", - '!tau.plugins.js', - '!tau.svg.js', - '!charts/line.js', - '!charts/scatterplot.js', - '!addons/*.js', - '!tau.charts.js', - '!class.js', - '!tau.data.js', - '!tau.data.types.js' - ]; + '*.js', + '**/*.js', + '!addons/*.js' + ], webpackConf = { + entry: './src/tau.charts.js', + output: { + library: 'tauCharts', + libraryTarget: 'umd', + path: 'build/development', + filename: 'tauCharts.js' + }, + externals: { + d3: 'd3', + _: 'underscore' + }, + module: { + loaders: [{ + test: /\.js$/, + exclude: /node_modules/, + loader: 'babel-loader' + }] + } + }; grunt.initConfig({ - // Metadata. pkg: grunt.file.readJSON('package.json'), - banner: '/*! <%= pkg.title || pkg.name %> - v<%= pkg.version %> - ' + - '<%= grunt.template.today("yyyy-mm-dd") %>\n' + - '<%= pkg.homepage ? "* " + pkg.homepage + "\\n" : "" %>' + - '* Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %>;' + - ' Licensed <%= _.pluck(pkg.licenses, "type").join(", ") %> */\n', - // Task configuration. + banner: [ + '/*! <%= pkg.title || pkg.name %> - v<%= pkg.version %> - ', + '<%= grunt.template.today("yyyy-mm-dd") %>\n', + '<%= pkg.homepage ? "* " + pkg.homepage + "\\n" : "" %>', + '* Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %>;', + ' Licensed <%= _.pluck(pkg.licenses, "type").join(", ") %> */\n' + ].join(''), concat: { options: { banner: '<%= banner %>', @@ -36,7 +47,11 @@ module.exports = function (grunt) { dest: 'build/development/tauCharts.js' }, prodJS: { - src: ['build/development/tauCharts.js', 'build/development/tauCharts.color-brewer.js', 'build/development/plugins/*.js'], + src: [ + 'build/development/tauCharts.js', + 'build/development/tauCharts.color-brewer.js', + 'build/development/plugins/*.js' + ], dest: 'build/production/tauCharts.min.js' }, prodCSS: { @@ -53,36 +68,37 @@ module.exports = function (grunt) { }, compile: { build: { - cwd: "src/", + cwd: 'src/', src: src, - dest: "build/development/tauCharts.js" + dest: 'build/development/tauCharts.js' }, dev: { - cwd: "src/", + cwd: 'src/', src: src } }, karma: { - options: { - configFile: 'config/karma.conf.js' - }, + options: {configFile: 'config/karma.conf.js'}, dev: { - reporters: ["dots"], - browsers: ["Chrome"], - singleRun: false + reporters: ['dots'], + browsers: ['Chrome'], + singleRun: false, + webpack: webpackConfig.default }, unit: { - reporters: ["dots", "coverage"], - preprocessors: {"tau_modules/**/*.js": "coverage", "plugins/*.js": "coverage"}, + webpack: webpackConfig.coverage, + reporters: [ + 'coverage', + 'dots' + ], coverageReporter: { - + type: 'html', + dir: 'coverage/' } } }, uglify: { - options: { - banner: '<%= banner %>' - }, + options: {banner: '<%= banner %>'}, dist: { src: '<%= concat.prodJS.dest %>', dest: 'build/production/tauCharts.min.js' @@ -103,11 +119,7 @@ module.exports = function (grunt) { } }, postcss: { - options: { - processors: [ - autoprefixer({browsers: ['last 2 version']}).postcss - ] - }, + options: {processors: [autoprefixer({browsers: ['last 2 version']}).postcss]}, dist: {src: 'css/*.css'} }, copy: { @@ -205,72 +217,92 @@ module.exports = function (grunt) { 'git add build/tauCharts.js', 'git add build/tauCharts.min.js' ].join('&&'), - options: { - stdout: true - } + options: {stdout: true} } }, jshint: { all: { src: [ - "src/**/*.js", "Gruntfile.js" + 'src/**/*.js', + 'Gruntfile.js' ], options: { - "loopfunc": true, - "esnext": true + loopfunc: true, + esnext: true } } }, less: { development: { - options: { - paths: ["less"] - }, + options: {paths: ['less']}, files: { - "css/tooltip.css": "less/tooltip.less", - "css/export.css": "less/export.less", - "css/colorbrewer.css": "less/colorbrewer.less", - "css/base.css": "less/base.less", - "css/tauCharts.css": "less/tauCharts.less", - "css/layout.css": "less/layout.less", - "css/legend.css": "less/legend.less", - "css/trendline.css": "less/trendline.less" + 'css/tooltip.css': 'less/tooltip.less', + 'css/export.css': 'less/export.less', + 'css/colorbrewer.css': 'less/colorbrewer.less', + 'css/base.css': 'less/base.less', + 'css/tauCharts.css': 'less/tauCharts.less', + 'css/layout.css': 'less/layout.less', + 'css/legend.css': 'less/legend.less', + 'css/trendline.css': 'less/trendline.less' } } }, - clean: ['build/production/', 'build/development/'], + clean: [ + 'build/production/', + 'build/development/' + ], bowercopy: { - options: { - // clean: true - }, + options: {}, libs: { - options: { - destPrefix: "libs" - }, + options: {destPrefix: 'libs'}, files: { - "d3.js": "d3/d3.js", - "underscore.js": "underscore/underscore.js", - "jquery.js": "jquery/dist/jquery.js", - "modernizer.js": "modernizer/modernizr.js", - "js-schema.js": "js-schema/js-schema.debug.js", - "es5-shim.js": "es5-shim/es5-shim.js" + 'd3.js': 'd3/d3.js', + 'underscore.js': 'underscore/underscore.js', + 'jquery.js': 'jquery/dist/jquery.js', + 'js-schema.js': 'js-schema/js-schema.debug.js', + 'es5-shim.js': 'es5-shim/es5-shim.js' } } }, watch: { js: { files: ['<%= jshint.all.src %>'], - tasks: ['jshint', 'compile:dev', 'less'] + tasks: [ + 'jshint', + 'compile:dev', + 'less' + ] }, less: { files: ['less/*.less'], tasks: ['less'] } + }, + webpack: {build: webpackConf}, + 'webpack-dev-server': { + options: { + webpack: webpackConf, + publicPath: '/' + }, + start: { + port: 9000, + keepAlive: true, + webpack: { + devtool: 'eval', + debug: true + } + } + }, + jscs: { + src: [ + 'plugins/*.js', + 'src/**' + ], + options: {config: '.jscsrc'} } }); // load local tasks - grunt.loadTasks("tasks"); - + grunt.loadTasks('tasks'); // These plugins provide necessary tasks. grunt.loadNpmTasks('grunt-contrib-concat'); grunt.loadNpmTasks('grunt-contrib-uglify'); @@ -286,12 +318,39 @@ module.exports = function (grunt) { grunt.loadNpmTasks('grunt-contrib-rename'); grunt.loadNpmTasks('grunt-postcss'); grunt.loadNpmTasks('grunt-contrib-clean'); + grunt.loadNpmTasks('grunt-webpack'); + grunt.loadNpmTasks('grunt-jscs'); // Default task. - //grunt.registerTask('default', ['jshint', 'qunit', 'concat', 'uglify']); - grunt.registerTask('default', ['bowercopy', 'less', 'compile:dev', 'jshint', 'watch:js']); - var buildWithoutPublish = ['bowercopy', 'less', 'postcss', 'copy:build', 'compile:build', 'concat:dist', 'concat:prodJS', 'concat:prodCSS', 'uglify', 'cssmin']; + // grunt.registerTask('default', ['jshint', 'qunit', 'concat', 'uglify']); + grunt.registerTask('default', [ + 'bowercopy', + 'less', + 'compile:dev', + 'jshint', + 'watch:js' + ]); + var buildWithoutPublish = [ + 'bowercopy', + 'less', + 'postcss', + 'copy:build', + 'compile:build', + 'concat:dist', + 'concat:prodJS', + 'concat:prodCSS', + 'uglify', + 'cssmin' + ]; grunt.registerTask('build', buildWithoutPublish); - grunt.registerTask('publish', buildWithoutPublish.concat(['copy:copybuild', 'clean'])); - grunt.registerTask('travis', ['bowercopy', 'jshint', 'build']); + grunt.registerTask('publish', buildWithoutPublish.concat([ + 'copy:copybuild', + 'clean' + ])); + grunt.registerTask('travis', [ + 'bowercopy', + 'jshint', + 'jscs', + 'build' + ]); grunt.registerTask('watching', ['default']); -}; +}; \ No newline at end of file diff --git a/bower.json b/bower.json index 8e4d768c8..6dfd74dc3 100644 --- a/bower.json +++ b/bower.json @@ -19,11 +19,10 @@ "!license.txt" ], "dependencies": { - "d3": "~3.4.3", - "underscore": "~1.6.0" + "d3": "~3.5.5", + "underscore": "~1.8.2" }, "devDependencies": { - "jquery": "2.1.1", "modernizer": "2.8.2", "js-schema": "0.7.0", "es5-shim": "latest", @@ -31,5 +30,9 @@ "FileSaver.js": "https://github.com/Mavrin/FileSaver.js.git", "fetch": "~0.6.1", "es6-promise": "~2.0.1" + }, + "resolutions": { + "d3": "~3.5.5", + "underscore": "~1.8.2" } } \ No newline at end of file diff --git a/config.rb b/config.rb deleted file mode 100644 index 95d45372a..000000000 --- a/config.rb +++ /dev/null @@ -1,19 +0,0 @@ -# Require any additional compass plugins here. - -# Set this to the root of your project when deployed: -http_path = "/" -css_dir = "css" -sass_dir = "sass" -images_dir = "images" -javascripts_dir = "src" - -# You can select your preferred output style here (can be overridden via the command line): -# output_style = :expanded or :nested or :compact or :compressed - -# To enable relative paths to assets via compass helper functions. Uncomment: -# relative_assets = true - -# To disable debugging comments that display the original location of your selectors. Uncomment: -line_comments = false - -preferred_syntax = :sass diff --git a/config/karma.conf.js b/config/karma.conf.js index a47e39b31..866af8dd5 100644 --- a/config/karma.conf.js +++ b/config/karma.conf.js @@ -1,43 +1,33 @@ module.exports = function (config) { + var webpackConfig = require('./webpack.test.config'); config.set({ // base path, that will be used to resolve files and exclude basePath: '..', // frameworks to use - frameworks: ['mocha', 'requirejs'], + frameworks: ['mocha'], // list of files / patterns to load in the browser files: [ - /*'test/utils/utils.js', - 'libs/underscore.js', - 'libs/js-schema.js', - 'libs/d3.js', - */ - {pattern: 'src/addons/color-brewer.js', included: false}, - {pattern: 'bower_components/**', included: false}, - {pattern: 'node_modules/requirejs-text/**', included: false}, - //'build/tauCharts.js', - {pattern: 'test/utils/*.js', included: false}, - {pattern: 'plugins/**', included: false}, {pattern: 'css/tooltip.css', included: true}, {pattern: 'css/tauCharts.css', included: true}, {pattern: 'test/utils/test.css', included: true}, {pattern: 'css/base.css', included: true}, - {pattern: 'libs/**', included: false}, - {pattern: 'node_modules/chai/*.js', included: false}, - {pattern: 'tau_modules/**', included: false}, - {pattern: 'test/*test.js', included: false}, 'test/tests-main.js' ], - browsers: ["PhantomJS"], - preprocessors: {"tau_modules/**/*.js": "coverage"}, - reporters: ["coverage", "dots", "coveralls"], + browsers: ['PhantomJS'], + preprocessors: {'test/tests-main.js': ['webpack', 'sourcemap']}, + reporters: ['coverage', 'dots', 'coveralls'], coverageReporter: { - type: "lcovonly", - dir: "coverage/" + type: 'lcovonly', + dir: 'coverage/' }, - // web server port + webpackMiddleware: { + noInfo: true + }, + webpack: webpackConfig.coverage, + browserNoActivityTimeout: 100000, port: 9876, // enable / disable colors in the output (reporters and logs) diff --git a/config/webpack.test.config.js b/config/webpack.test.config.js new file mode 100644 index 000000000..edb198448 --- /dev/null +++ b/config/webpack.test.config.js @@ -0,0 +1,73 @@ +var webpack = require('webpack'); +var path = require('path'); +var coverage = [{ + test: /\.js$/, + exclude: /test|addons|plugins|node_modules|bower_components|libs\//, + loader: 'istanbul-instrumenter' +}]; +var generateConf = function (postLoader) { + return { + resolve: { + root: [ + path.resolve('.') + ], + modulesDirectories: [ + 'bower_components', + 'node_modules' + ], + alias: { + 'schemes': 'test/utils/schemes.js', + 'testUtils': 'test/utils/utils.js', + 'es5-shim': 'libs/es5-shim.js', + 'brewer': 'src/addons/color-brewer.js', + 'tauCharts': 'src/tau.charts.js', + 'print.style.css': 'plugins/print.style.css', + 'rgbcolor': 'bower_components/canvg/rgbcolor.js', + 'stackblur': 'bower_components/canvg/StackBlur.js', + 'canvg': 'bower_components/canvg/canvg.js', + 'FileSaver': 'test/utils/saveAs.js', + 'fetch': 'bower_components/fetch/fetch.js', + 'promise': 'bower_components/es6-promise/promise.js' + }, + extensions: ['', '.js', '.json'] + }, + devtool: 'inline-source-map', + module: { + loaders: [ + {test: /\.css$/, loader: 'css-loader'}, + { + test: /modernizer[\\\/]modernizr\.js$/, + loader: 'imports?this=>window!exports?window.Modernizr' + }, + { + test: /\.js$/, + exclude: /node_modules|libs|bower_components/, + loader: 'babel-loader' + } + ], + postLoaders: postLoader + }, + externals: { + _: 'underscore' + }, + + plugins: [ + new webpack.ProvidePlugin({ + d3: 'd3', + _: 'underscore' + }) + ], + debug: false, + stats: { + colors: true, + reasons: true + }, + progress: true + } +}; +//console.log(generateConf(coverage)); +//console.log(generateConf([])); +module.exports = { + coverage: generateConf(coverage), + default: generateConf([]) +}; \ No newline at end of file diff --git a/examples/dsl.html b/examples/dsl.html index 86ab16bb6..8f891ebf2 100644 --- a/examples/dsl.html +++ b/examples/dsl.html @@ -2,7 +2,7 @@ - + @@ -100,6 +100,8 @@ +
+
@@ -119,8 +121,7 @@
-
-
+
@@ -143,16 +144,125 @@
-
-
- + + + + - diff --git a/examples/gpl.html b/examples/gpl.html new file mode 100644 index 000000000..03bb1a4cd --- /dev/null +++ b/examples/gpl.html @@ -0,0 +1,1070 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+ +
+ + + + + + + + + + + + diff --git a/examples/scatter.html b/examples/scatter.html index 56581c8a3..02ccb48a3 100644 --- a/examples/scatter.html +++ b/examples/scatter.html @@ -34,7 +34,7 @@ .color-us { stroke: blue; - fill:blue; + fill: blue; } .color-bug { @@ -101,23 +101,30 @@ baseUrl: location.pathname.replace(/(.+)(examples\/\w+\.html)/, '$1'), paths: { 'd3': '//cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3', - 'underscore': '//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.7.0/underscore-min' + 'underscore': '//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.2/underscore-min' }, map: { '*': { - 'tauCharts': 'tau_modules/tau.newCharts', + 'tauCharts': 'http://localhost:9000/tauCharts.js', 'print.style.css': 'node_modules/requirejs-text/text!plugins/print.style.css', 'rgbcolor': 'bower_components/canvg/rgbcolor', 'stackblur': 'bower_components/canvg/StackBlur', - 'canvg':'bower_components/canvg/canvg', - 'FileSaver':'bower_components/FileSaver.js/FileSaver', - 'fetch':'bower_components/fetch/fetch', - 'promise':'bower_components/es6-promise/promise' + 'canvg': 'bower_components/canvg/canvg', + 'FileSaver': 'bower_components/FileSaver.js/FileSaver', + 'fetch': 'bower_components/fetch/fetch', + 'promise': 'bower_components/es6-promise/promise' } } }); - require(['tau_modules/tau.newCharts', 'src/addons/color-brewer', 'plugins/legend', 'plugins/trendline', 'plugins/export', 'plugins/tooltip', 'underscore'], function (tauCharts, brewer, legend, trendline, exportTo, tooltip) { + require([ + 'tauCharts', + 'src/addons/color-brewer', + 'plugins/legend', + 'plugins/trendline', + 'plugins/export', + 'plugins/tooltip', + 'underscore'], function (tauCharts, brewer, legend, trendline, exportTo, tooltip) { /** @class Tooltip * @extends Plugin */ var defData = [ @@ -139,31 +146,98 @@ {type: 'task', count: 1.16, date: 6}, {type: 'task', count: 1.1313, date: 7} ]; + var offsetHrs = new Date().getTimezoneOffset() / 60; + var offsetISO = '0' + Math.abs(offsetHrs) + ':00'; + var iso = function (str) { + return (str + '+' + offsetISO); + }; + var chart = new tauCharts.Chart({ - /* guide: { - color: { - brewer: { - us: 'color-us', - bug: 'color-bug' + "type": "line", + "color": "colorValue", + "size": "sizeValue", + "x": [ + "complex" + ], + "y": [ + "date", + "simple" + ], + "guide": [ + { + "y": { + "label": "Create Date By Day", + "tickPeriod": "day" } }, - y: { - label: { - text: 'Count of completed entities', - padding: 50 + { + "x": { + "label": "Project", + "tickLabel": "name" + }, + "y": { + "label": "Progress", + "tickFormat": "percent" + }, + "color": { + "label": "Entity Type" + }, + "size": { + "label": "Effort" } + + } + ], + "dimensions": { + "complex": { + "type": "category", + "scale": "ordinal", + "value": "id" + }, + "date": { + "type": "order", + "scale": "period" + }, + "simple": { + "type": "measure", + "scale": "linear" + }, + "colorValue": { + "type": "category", + "scale": "ordinal" + }, + "sizeValue": { + "type": "measure", + "scale": "linear" + } + }, + plugins: [tooltip({fields: ['complex.name', 'date', 'simple', 'colorValue', 'sizeValue']})], + data: [ + { + "complex": { + "id": 1, + "name": "TP3" + }, + "date": new Date(iso("2015-01-08T00:00:00")), + "simple": 0.1, + "colorValue": "UserStory", + "sizeValue": 10 + }, + { + "complex": null, + "date": new Date(iso("2015-01-08T00:00:00")), + "simple": 0.1, + "colorValue": "UserStory", + "sizeValue": 10 }, - x: { - label: 'Month' + { + "complex": null, + "date": new Date(iso("2015-01-09T00:00:00")), + "simple": 0.9, + "colorValue": "Bug", + "sizeValue": 20 } - },*/ - plugins: [legend(), trendline(), exportTo({cssPaths:['../css/tauCharts.css']}), tooltip()], - data: defData, - type: 'scatterplot', - x: 'date', - y: 'count', - size:'count', - color: 'type' + ] }); chart.renderTo('#line'); diff --git a/examples/streaming-demo.html b/examples/streaming-demo.html new file mode 100644 index 000000000..c64609f8b --- /dev/null +++ b/examples/streaming-demo.html @@ -0,0 +1,294 @@ + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ +
+ + + + + + + \ No newline at end of file diff --git a/examples/streaming.html b/examples/streaming.html new file mode 100644 index 000000000..e085263a2 --- /dev/null +++ b/examples/streaming.html @@ -0,0 +1,287 @@ + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + \ No newline at end of file diff --git a/package.json b/package.json index 611e91594..1d46295ef 100644 --- a/package.json +++ b/package.json @@ -17,34 +17,45 @@ } ], "dependencies": { - "d3": "~3.4.3", - "underscore": "~1.6.0" + "d3": "^3.5.5", + "underscore": "~1.8.2" }, "repository": { "type": "git", "url": "https://github.com/TargetProcess/tauCharts.git" }, "devDependencies": { - "6to5": "3.0.9", - "almond": "0.3.0", - "autoprefixer-core": "^5.0.0", - "chai": "1.10.0", + "almond": "0.3.1", + "autoprefixer-core": "^5.1.7", + "babel": "^4.7.9", + "babel-core": "^4.7.9", + "babel-loader": "^4.1.0", + "chai": "2.1.1", + "css-loader": "^0.9.1", + "d3": "^3.5.5", + "exports-loader": "^0.6.2", "grunt": "~0.4.5", "grunt-bowercopy": "^1.2.0", "grunt-cli": "0.1.13", "grunt-contrib-clean": "^0.6.0", - "grunt-contrib-concat": "^0.5.0", - "grunt-contrib-copy": "0.7.0", - "grunt-contrib-cssmin": "0.11.0", + "grunt-contrib-concat": "^0.5.1", + "grunt-contrib-copy": "0.8.0", + "grunt-contrib-cssmin": "0.12.2", "grunt-contrib-jshint": "^0.11.0", "grunt-contrib-less": "^1.0.0", "grunt-contrib-rename": "0.0.3", - "grunt-contrib-uglify": "^0.7.0", + "grunt-contrib-uglify": "^0.8.0", "grunt-contrib-watch": "^0.6.1", - "grunt-gh-pages": "^0.9.1", + "grunt-gh-pages": "^0.10.0", + "grunt-jscs": "^1.5.0", "grunt-karma": "0.10.1", "grunt-postcss": "^0.3.0", "grunt-shell": "", + "grunt-webpack": "^1.0.8", + "imports-loader": "^0.6.3", + "istanbul": "^0.3.7", + "istanbul-instrumenter-loader": "^0.1.2", + "jquery": "^2.1.3", "karma": "~0.12.31", "karma-chrome-launcher": "~0.1.7", "karma-cli": "0.0.4", @@ -53,11 +64,16 @@ "karma-mocha": "^0.1.10", "karma-phantomjs-launcher": "~0.1.4", "karma-requirejs": "0.2.2", - "requirejs": "2.1.15", - "requirejs-text": "^2.0.12" + "karma-webpack": "^1.5.0", + "requirejs": "2.1.16", + "requirejs-text": "^2.0.12", + "webpack": "^1.7.3", + "webpack-dev-server": "^1.7.0", + "karma-sourcemap-loader": "^0.3.4" }, "scripts": { - "test": "./node_modules/.bin/karma start config/karma.conf.js", + "test": "npm run travis && karma start config/karma.conf.js", + "travis":"grunt travis", "release":"grunt publish && grunt gh-pages" } } diff --git a/plugins/export.js b/plugins/export.js index ec8ba0763..baf69bf91 100644 --- a/plugins/export.js +++ b/plugins/export.js @@ -1,9 +1,10 @@ (function (factory) { - if (typeof define === "function" && define.amd) { - define(['tauCharts', 'canvg', 'FileSaver', 'promise', 'print.style.css', 'fetch'], function (tauPlugins, canvg, saveAs, Promise, printCss) { - window.Promise = window.Promise || Promise.Promise; - return factory(tauPlugins, canvg, saveAs, window.Promise, printCss); - }); + if (typeof define === 'function' && define.amd) { + define(['tauCharts', 'canvg', 'FileSaver', 'promise', 'print.style.css', 'fetch'], + function (tauPlugins, canvg, saveAs, Promise, printCss) { + window.Promise = window.Promise || Promise.Promise; + return factory(tauPlugins, canvg, saveAs, window.Promise, printCss); + }); } else { factory(this.tauCharts, this.canvg, this.saveAs); } @@ -14,7 +15,7 @@ if (predicate(node)) { return node; } - var i, children = node.unit || [], child, found; + var i, children = node.units || [], child, found; for (i = 0; i < children.length; i += 1) { child = children[i]; found = dfs(child, predicate); @@ -36,22 +37,22 @@ } var keyCode = { - "BACKSPACE": 8, - "COMMA": 188, - "DELETE": 46, - "DOWN": 40, - "END": 35, - "ENTER": 13, - "ESCAPE": 27, - "HOME": 36, - "LEFT": 37, - "PAGE_DOWN": 34, - "PAGE_UP": 33, - "PERIOD": 190, - "RIGHT": 39, - "SPACE": 32, - "TAB": 9, - "UP": 38 + BACKSPACE: 8, + COMMA: 188, + DELETE: 46, + DOWN: 40, + END: 35, + ENTER: 13, + ESCAPE: 27, + HOME: 36, + LEFT: 37, + PAGE_DOWN: 34, + PAGE_UP: 33, + PERIOD: 190, + RIGHT: 39, + SPACE: 32, + TAB: 9, + UP: 38 }; var createStyleElement = function (styles, mediaType) { mediaType = mediaType || 'all'; @@ -76,8 +77,7 @@ if (!isPhantomJS) { if ('onafterprint' in window) { window.addEventListener('afterprint', removePrintStyles); - } - else { + } else { window.matchMedia('screen').addListener(function (exp) { if (exp.matches) { removePrintStyles(); @@ -98,7 +98,7 @@ } var a = document.createElement('a'); - a.href = "#"; + a.href = '#'; a.addEventListener('focusin', swap, false); document.body.appendChild(a); @@ -126,34 +126,52 @@ var div = document.createElement('div'); var svg = chart.getSVG().cloneNode(true); div.appendChild(svg); - d3.select(svg).attr("version", 1.1) - .attr("xmlns", "http://www.w3.org/2000/svg"); + d3.select(svg).attr('version', 1.1) + .attr('xmlns', 'http://www.w3.org/2000/svg'); svg.insertBefore(style, svg.firstChild); this._renderAdditionalInfo(svg, chart); var canvas = document.createElement('canvas'); canvas.height = svg.getAttribute('height'); canvas.width = svg.getAttribute('width'); canvg(canvas, svg.parentNode.innerHTML); - return canvas.toDataURL("image/png"); + return canvas.toDataURL('image/png'); }.bind(this)); }, _findUnit: function (chart) { var conf = chart.getConfig(); - return dfs(conf.spec.unit, function (node) { - return node.color || node.size && conf.dimensions[node.size].type === 'measure'; + var spec = chart.getConfig(); + var checkNotEmpty = function (dimName) { + var sizeScaleCfg = spec.scales[dimName]; + return ( + sizeScaleCfg && + sizeScaleCfg.dim && + sizeScaleCfg.source && + spec.sources[sizeScaleCfg.source].dims[sizeScaleCfg.dim] + ); + }; + return dfs(conf.unit, function (node) { + + if (checkNotEmpty(node.color)) { + return true; + } + + if (checkNotEmpty(node.size)) { + var sizeScaleCfg = spec.scales[node.size]; + return spec.sources[sizeScaleCfg.source].dims[sizeScaleCfg.dim].type === 'measure'; + } }); }, _toPng: function (chart) { this._createDataUrl(chart) .then(function (dataURL) { - var data = atob(dataURL.substring("data:image/png;base64,".length)), + var data = atob(dataURL.substring('data:image/png;base64,'.length)), asArray = new Uint8Array(data.length); for (var i = 0, len = data.length; i < len; ++i) { asArray[i] = data.charCodeAt(i); } - var blob = new Blob([asArray.buffer], {type: "image/png"}); + var blob = new Blob([asArray.buffer], {type: 'image/png'}); saveAs(blob, (this._fileName || 'export') + '.png'); }.bind(this)); }, @@ -172,10 +190,21 @@ }); }, _renderColorLegend: function (configUnit, svg, chart, width) { + var colorScale = this._unit.color; + var colorDimension = this._unit.color.dim; configUnit.guide = configUnit.guide || {}; - configUnit.guide.color = this._unit.guide.color; - var colorScaleName = configUnit.guide.color.label.text || this._unit.options.color.dimension; - var data = this._getColorMap(chart); + configUnit.guide.color = configUnit.guide.color || {}; + + var colorLabelText = (_.isObject(configUnit.guide.color.label)) ? + configUnit.guide.color.label.text : + configUnit.guide.color.label; + + var colorScaleName = colorLabelText || colorScale.dim; + var data = this._getColorMap( + chart.getData({excludeFilter: ['legend']}), + colorScale, + colorDimension + ).values; var draw = function () { this.attr('transform', function (d, index) { return 'translate(5,' + 20 * (index + 1) + ')'; @@ -217,12 +246,11 @@ return {h: (data.length * 20 + 20), w: 0}; }, - //'<%=value%>', _renderSizeLegend: function (configUnit, svg, chart, width, offset) { - var sizeScale = this._unit.options.sizeScale; + var sizeScale = this._unit.size; var sizeDimension = this._unit.size.scaleDim; configUnit.guide = configUnit.guide || {}; - configUnit.guide.size = this._unit.guide.size; + configUnit.guide.size = this._unit.config.guide.size; var sizeScaleName = configUnit.guide.size.label.text || sizeDimension; var chartData = _.sortBy(chart.getData(), function (el) { return sizeScale(el[sizeDimension]); @@ -267,7 +295,7 @@ this.attr('transform', function (d) { offsetInner += maxDiameter; - var transform = 'translate(5,' + (offsetInner)+ ')'; + var transform = 'translate(5,' + (offsetInner) + ')'; offsetInner += 10; return transform; }); @@ -279,17 +307,17 @@ .attr('class', function (d) { return d.className; }) - .style({'opacity': 0.4}); + .style({opacity: 0.4}); this.append('g') - .attr('transform', function(d) { + .attr('transform', function (d) { return 'translate(' + maxDiameter + ',' + (fontSize / 2) + ')'; }) .append('text') - .attr('x', function(d) { + .attr('x', function (d) { return 0;// d.diameter; }) - .attr('y', function(d) { + .attr('y', function (d) { return 0;// d.radius-6.5; }) .text(function (d) { @@ -325,43 +353,56 @@ return; } var offset = {h: 0, w: 0}; + var spec = chart.getConfig(); svg = d3.select(svg); var width = parseInt(svg.attr('width'), 10); var height = svg.attr('height'); svg.attr('width', width + 160); - if (configUnit.color) { + var checkNotEmpty = function (dimName) { + var sizeScaleCfg = spec.scales[dimName]; + return ( + sizeScaleCfg && + sizeScaleCfg.dim && + sizeScaleCfg.source && + spec.sources[sizeScaleCfg.source].dims[sizeScaleCfg.dim] + ); + }; + if (checkNotEmpty(configUnit.color)) { var offsetColorLegend = this._renderColorLegend(configUnit, svg, chart, width); offset.h = offsetColorLegend.h; offset.w = offsetColorLegend.w; } - if (configUnit.size && chart.getConfig().spec.dimensions[configUnit.size].type === 'measure') { + var spec = chart.getConfig(); + var sizeScaleCfg = spec.scales[configUnit.size]; + if (configUnit.size && + sizeScaleCfg.dim && + spec.sources[sizeScaleCfg.source].dims[sizeScaleCfg.dim].type === 'measure') { this._renderSizeLegend(configUnit, svg, chart, width, offset); } - // document.body.appendChild(svg.node()); }, - onUnitReady: function (chart, unit) { - if (unit.type.indexOf('ELEMENT') !== -1) { + onUnitDraw: function (chart, unit) { + if (tauCharts.api.isChartElement(unit)) { this._unit = unit; } }, - _getColorMap: function (chart) { - var colorScale = this._unit.options.color; - var colorDimension = this._unit.color.scaleDim; - var data = chart.getData(); + _getColorMap: function (data, colorScale, colorDimension) { + return _(data) .chain() .map(function (item) { - return colorScale.legend(item[colorDimension]); + var value = item[colorDimension]; + return {color: colorScale(value), value: value, label: value}; }) .uniq(function (legendItem) { return legendItem.value; }) .value() .reduce(function (memo, item) { - memo.push(item); + memo.brewer[item.value] = item.color; + memo.values.push(item); return memo; }, - []); + {brewer: {}, values: []}); }, _select: function (value, chart) { value = value || ''; @@ -434,7 +475,10 @@ this._fileName = settings.fileName; if (!this._cssPaths) { this._cssPaths = []; - tauCharts.api.globalSettings.log('You should specified cssPath for correct work export plugin', 'warn'); + tauCharts.api.globalSettings.log( + 'You should specified cssPath for correct work export plugin', + 'warn' + ); } settings = _.defaults(settings, { @@ -447,12 +491,14 @@ place: 'bottom-left' }); this._popup = popup; + // jscs:disable maximumLineLength popup.content([ '' ].join('')); + // jscs:enable maximumLineLength popup.attach(this._container); var popupElement = popup.getElement(); popupElement.setAttribute('tabindex', '-1'); @@ -462,7 +508,7 @@ }.bind(this)); }, destroy: function () { - if(this._popup) { + if (this._popup) { this._popup.destroy(); } } diff --git a/plugins/highlighter.js b/plugins/highlighter.js deleted file mode 100644 index 8bff2e4f1..000000000 --- a/plugins/highlighter.js +++ /dev/null @@ -1,24 +0,0 @@ -(function (factory) { - if (typeof define === "function" && define.amd) { - define(['tauCharts'],function(tauPlugins){return factory(tauPlugins);}); - } else if (typeof module === "object" && module.exports) { - var tauPlugins = require('tauCharts'); - module.exports = factory(); - } else { - factory(this.tauCharts); - } -})(function (tauCharts) { - /** @class Tooltip - * @extends Plugin */ - var highlighter = { - onElementMouseOver: function (chart, data) { - data.element.classList.toggle('highlighted', true); - }, - onElementMouseOut: function (chart, data) { - data.element.classList.toggle('highlighted', false); - } - }; - tauCharts.api.plugins.add('highlighter', function() { - return highlighter; - }); -}); \ No newline at end of file diff --git a/plugins/legend.js b/plugins/legend.js index e6447cf93..903026bb7 100644 --- a/plugins/legend.js +++ b/plugins/legend.js @@ -1,9 +1,9 @@ (function (factory) { - if (typeof define === "function" && define.amd) { + if (typeof define === 'function' && define.amd) { define(['tauCharts'], function (tauPlugins) { return factory(tauPlugins); }); - } else if (typeof module === "object" && module.exports) { + } else if (typeof module === 'object' && module.exports) { var tauPlugins = require('tauCharts'); module.exports = factory(tauPlugins); } else { @@ -14,7 +14,7 @@ if (predicate(node)) { return node; } - var i, children = node.unit || [], child, found; + var i, children = node.units || [], child, found; for (i = 0; i < children.length; i += 1) { child = children[i]; found = dfs(child, predicate); @@ -52,8 +52,26 @@ }, _findUnit: function (chart) { var conf = chart.getConfig(); - return dfs(conf.spec.unit, function (node) { - return node.color || node.size && conf.dimensions[node.size].type === 'measure'; + var spec = chart.getConfig(); + var checkNotEmpty = function (dimName) { + var sizeScaleCfg = spec.scales[dimName]; + return ( + sizeScaleCfg && + sizeScaleCfg.dim && + sizeScaleCfg.source && + spec.sources[sizeScaleCfg.source].dims[sizeScaleCfg.dim] + ); + }; + return dfs(conf.unit, function (node) { + + if (checkNotEmpty(node.color)) { + return true; + } + + if (checkNotEmpty(node.size)) { + var sizeScaleCfg = spec.scales[node.size]; + return spec.sources[sizeScaleCfg.source].dims[sizeScaleCfg.dim].type === 'measure'; + } }); }, init: function (chart) { @@ -61,19 +79,35 @@ this._currentFilters = {}; this._storageValues = {}; this._container = chart.insertToRightSidebar(this._containerTemplate); - this._delegateEvent(this._container, 'click', 'graphical-report__legend__item-color', function (e, currentTarget) { - this._toggleLegendItem(currentTarget, chart); - }.bind(this)); - this._delegateEvent(this._container, 'mouseover', 'graphical-report__legend__item-color', function (e, currentTarget) { - this._highlightToggle(currentTarget, chart, true); - }.bind(this)); - this._delegateEvent(this._container, 'mouseout', 'graphical-report__legend__item-color', function (e, currentTarget) { - this._highlightToggle(currentTarget, chart, false); - }.bind(this)); + this._delegateEvent( + this._container, + 'click', + 'graphical-report__legend__item-color', + function (e, currentTarget) { + this._toggleLegendItem(currentTarget, chart); + }.bind(this) + ); + + this._delegateEvent( + this._container, + 'mouseover', + 'graphical-report__legend__item-color', + function (e, currentTarget) { + this._highlightToggle(currentTarget, chart, true); + }.bind(this) + ); + this._delegateEvent( + this._container, + 'mouseout', + 'graphical-report__legend__item-color', + function (e, currentTarget) { + this._highlightToggle(currentTarget, chart, false); + }.bind(this) + ); } }, _highlightToggle: function (target, chart, toggle) { - var colorScale = this._unit.options.color; + var svg = chart.getSVG(); var d3Chart = d3.select(svg); if (target.classList.contains('disabled')) { @@ -93,9 +127,10 @@ .filter(function (item) { var propObject = item.hasOwnProperty(originValue.dimension) ? item[originValue.dimension] : - _.chain(item.values).pluck(originValue.dimension).unique().first().value(); + item.tags[originValue.dimension]; + // _.chain(item.values).pluck(originValue.dimension).unique().first().value(); - return colorScale.legend(propObject).value === originValue.value; + return propObject === originValue.value; }) .classed({'graphical-report__highlighted': true}); @@ -106,7 +141,7 @@ } }, _toggleLegendItem: function (target, chart) { - var colorScale = this._unit.options.color; + var value = target.getAttribute('data-value'); var keys = _.keys(this._currentFilters); @@ -126,7 +161,7 @@ tag: 'legend', predicate: function (item) { var propObject = item[originValue.dimension]; - return colorScale.legend(propObject).value !== originValue.value; + return propObject !== originValue.value; } }; target.classList.add('disabled'); @@ -138,8 +173,8 @@ return Boolean(this._findUnit(chart)); }, - onUnitReady: function (chart, unit) { - if (unit.type.indexOf('ELEMENT') !== -1) { + onUnitDraw: function (chart, unit) { + if (tauCharts.api.isChartElement(unit)) { this._unit = unit; } }, @@ -149,7 +184,8 @@ return _(data) .chain() .map(function (item) { - return colorScale.legend(item[colorDimension]); + var value = item[colorDimension]; + return {color: colorScale(value), value: value, label: value}; }) .uniq(function (legendItem) { return legendItem.value; @@ -162,7 +198,7 @@ }, {brewer: {}, values: []}); }, - + // jscs:disable maximumLineLength _containerTemplate: '
', _template: _.template('
<%=name%>
<%=items%>
'), _itemTemplate: _.template([ @@ -172,29 +208,50 @@ ].join('')), _itemSizeTemplate: _.template([ '
', - '
', - '', - '
<%=value%>', + '
', + '', + '
<%=value%>', '
' ].join('')), + // jscs:enable maximumLineLength _renderColorLegend: function (configUnit, chart) { - if (!configUnit.color) { + var spec = chart.getConfig(); + var checkNotEmpty = function (dimName) { + var sizeScaleCfg = spec.scales[dimName]; + return ( + sizeScaleCfg && + sizeScaleCfg.dim && + sizeScaleCfg.source && + spec.sources[sizeScaleCfg.source].dims[sizeScaleCfg.dim] + ); + }; + if (!checkNotEmpty(configUnit.color)) { return; } - var colorScale = this._unit.options.color; - var colorDimension = this._unit.color.scaleDim; + var colorScale = this._unit.color; + var colorDimension = this._unit.color.dim; configUnit.guide = configUnit.guide || {}; - configUnit.guide.color = this._unit.guide.color; - var colorScaleName = configUnit.guide.color.label.text || colorScale.dimension; - var colorMap = this._getColorMap(chart.getData({excludeFilter: ['legend']}), colorScale, colorDimension); - configUnit.guide.color.brewer = colorMap.brewer; + configUnit.guide.color = configUnit.guide.color || {}; + + var colorLabelText = (_.isObject(configUnit.guide.color.label)) ? + configUnit.guide.color.label.text : + configUnit.guide.color.label; + + var colorScaleName = colorLabelText || colorScale.dim; + var colorMap = this._getColorMap( + chart.getData({excludeFilter: ['legend']}), + colorScale, + colorDimension + ); + + chart.configGPL.scales[this._unit.config.color].brewer = colorMap.brewer; + var data = _.reduce( colorMap.values, function (data, item) { var originValue = { dimension: colorDimension, - value: item.value, - color: item.color + value: item.value }; var value = JSON.stringify(originValue); var label = _.escape(isEmpty(item.label) ? ('No ' + colorScaleName) : item.label); @@ -204,7 +261,7 @@ label: label, value: _.escape(value) })); - data.storageValues[value] = originValue; + data.storageValues[value] = _.extend({color: item.color}, originValue); return data; }, {items: [], storageValues: {}}, @@ -217,14 +274,18 @@ this._colorScaleSize = data.items.length; }, _renderSizeLegend: function (configUnit, chart) { - if (!configUnit.size || chart.getConfig().spec.dimensions[configUnit.size].type !== 'measure') { + var spec = chart.getConfig(); + var sizeScaleCfg = spec.scales[configUnit.size]; + if (!configUnit.size + || !sizeScaleCfg.dim + || spec.sources[sizeScaleCfg.source].dims[sizeScaleCfg.dim].type !== 'measure') { return; } - var sizeScale = this._unit.options.sizeScale; + var sizeScale = this._unit.size; var sizeDimension = this._unit.size.scaleDim; configUnit.guide = configUnit.guide || {}; - configUnit.guide.size = this._unit.guide.size; + configUnit.guide.size = this._unit.config.guide.size; var sizeScaleName = configUnit.guide.size.label.text || sizeDimension; var chartData = _.sortBy(chart.getData(), function (el) { return sizeScale(el[sizeDimension]); @@ -250,7 +311,6 @@ values = [first]; } - var items = _.map(values, function (value) { var radius = sizeScale(value); diff --git a/plugins/tooltip.js b/plugins/tooltip.js index 363c2125f..643975517 100644 --- a/plugins/tooltip.js +++ b/plugins/tooltip.js @@ -1,393 +1,417 @@ -(function (factory) { - if (typeof define === "function" && define.amd) { - define(['tauCharts'], function (tauPlugins) { - return factory(tauPlugins); - }); - } else if (typeof module === "object" && module.exports) { - var tauPlugins = require('tauCharts'); - module.exports = factory(tauPlugins); - } else { - factory(this.tauCharts); - } -})(function (tauCharts) { - /** @class Tooltip - * @extends Plugin */ - /* Usage - .plugins(tau.plugins.tooltip('effort', 'priority')) - accepts a list of data fields names as properties - */ - var _ = tauCharts.api._; - var d3 = tauCharts.api.d3; - var dim = function (x0, x1, y0, y1) { - return Math.sqrt((x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0)); - }; - - var dfs = function (node, predicate) { - if (predicate(node)) { - return node; - } - var i, children = node.unit || [], child, found; - for (i = 0; i < children.length; i += 1) { - child = children[i]; - found = dfs(child, predicate); - if (found) { - return found; - } - } - }; - - function tooltip(settings) { - settings = settings || {}; - return { - template: [ - '
', - '
', - '
', - 'Exclude', - '
', - '
' - ].join(''), - itemTemplate: [ - '
', - '
<%=label%>
', - '
<%=value%>
', - '
' - ].join(''), - onExcludeData: function () { - - }, - _drawPoint: function (container, x, y, color) { - if (this.circle) { - this.circle.remove(); - } - this.circle = container - .append("circle") - .attr("cx", x) - .attr("cy", y) - .attr('class', color) - .attr("r", 4); - this.circle.node().addEventListener('mouseover', function () { - clearTimeout(this._timeoutHideId); - }.bind(this), false); - this.circle.node().addEventListener('mouseleave', function () { - this._hide(); - }.bind(this), false); - }, - formatters: {}, - labels: {}, - - init: function (chart) { - this._chart = chart; - this._dataFields = settings.fields; - this._getDataFields = settings.getFields; - - _.extend(this, _.omit(settings, 'fields', 'getFields')); - this._timeoutHideId = null; - this._dataWithCoords = {}; - this._unitMeta = {}; - this._templateItem = _.template(this.itemTemplate); - this._tooltip = chart.addBalloon({spacing: 3, auto: true, effectClass: 'fade'}); - this._elementTooltip = this._tooltip.getElement(); - - var spec = chart.getConfig().spec; - - var dimensionGuides = this._findDimensionGuides(spec); - - var lastGuides = _.reduce(dimensionGuides, function (memo, guides, key) { - memo[key] = _.last(guides); - return memo; - }, {}); - - var formatters = this._generateDefaultFormatters(lastGuides, spec.dimensions); - _.extend(this.formatters, formatters); - - var labels = this._generateDefaultLabels(lastGuides); - _.extend(this.labels, labels); - - - var elementTooltip = this._elementTooltip; - elementTooltip.addEventListener('mouseover', function () { - clearTimeout(this._timeoutHideId); - }.bind(this), false); - elementTooltip.addEventListener('mouseleave', function () { - this._hide(); - }.bind(this), false); - elementTooltip.addEventListener('click', function (e) { - var target = e.target; - while (target !== e.currentTarget && target !== null) { - if (target.classList.contains('i-role-exclude')) { - this._exclude(); - this._hide(); - } - target = target.parentNode; - } - }.bind(this), false); - elementTooltip.insertAdjacentHTML('afterbegin', this.template); - this.afterInit(this._elementTooltip); - }, - - afterInit: function (elementTooltip) { - - }, - - - onUnitReady: function (chart, unitMeta) { - if (unitMeta.type && unitMeta.type.indexOf('ELEMENT') === 0) { - var key = this._generateKey(unitMeta.$where); - this._unitMeta[key] = unitMeta; - var values = unitMeta.partition(); - this._dataWithCoords[key] = values.map(function (item) { - return { - x: unitMeta.options.xScale(item[unitMeta.x.scaleDim]), - y: unitMeta.options.yScale(item[unitMeta.y.scaleDim]), - item: item - }; - }, this); - - } - }, - - renderItem: function (label, formattedValue, field, rawValue) { - return this._templateItem({ - label: label, - value: formattedValue - }); - }, - - render: function (data, fields) { - fields = _.unique(fields); - return fields.map(function (field) { - var rawValue = data[field]; - var formattedValue = this._getFormatter(field)(rawValue); - var label = this._getLabel(field); - - return this.renderItem(label, formattedValue, field, rawValue); - }, this).join(''); - }, - afterRender: function (toolteipElement) { - - }, - - onRender: function (chart) { - if (_.isFunction(this._getDataFields)) { - this._dataFields = this._getDataFields(chart); - } - this._hide(); - }, - - _getFormatter: function (field) { - return this.formatters[field] || _.identity; - }, - _getLabel: function (field) { - return this.labels[field] || field; - }, - - _generateDefaultLabels: function (lastGuides) { - return _.reduce(lastGuides, function (memo, lastGuide, key) { - memo[key] = lastGuide.label || key; - return memo; - }, {}); - }, - - _generateDefaultFormatters: function (lastGuides, dimensions) { - return _.reduce(lastGuides, function (memo, lastGuide, key) { - var getValue = function (rawValue) { - if (rawValue == null) { - return null; - } else { - var format = lastGuide.tickPeriod || lastGuide.tickFormat; - if (format) { - // very special case for dates - var xFormat = (format === 'x-time-auto') ? 'day' : format; - return tauCharts.api.tickFormat.get(xFormat)(rawValue); - } else if (lastGuide.tickLabel) { - return rawValue[lastGuide.tickLabel]; - } else if (dimensions[key].value) { - return rawValue[dimensions[key].value]; - } else { - return rawValue; - } - } - }; - - memo[key] = function (rawValue) { - var value = getValue(rawValue); - return value == null ? 'No ' + lastGuide.label : value; - }; - - return memo; - }, {}); - }, - - _findDimensionGuides: function (spec) { - var dimensionGuideMap = {}; - - var collect = function (field, unit) { - var property = unit[field]; - if (property) { - var guide = (unit.guide || {})[field]; - - if (guide) { - if (!dimensionGuideMap[property]) { - dimensionGuideMap[property] = []; - } - - dimensionGuideMap[property].push(guide) - } - } - }; - - dfs(spec.unit, function (unit) { - collect('x', unit); - collect('y', unit); - collect('color', unit); - collect('size', unit); - - return false; - }); - - return dimensionGuideMap; - - }, - - _exclude: function () { - this._chart.addFilter({ - tag: 'exclude', - predicate: (function (element) { - return function (item) { - return JSON.stringify(item) !== JSON.stringify(element); - }; - }(this._currentElement)) - }); - this.onExcludeData(this._currentElement); - }, - _calculateLength: function (x1, y1, x2, y2) { - return (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1); - }, - _calculateLengthToLine: function (x0, y0, x1, y1, x2, y2) { - var a1 = {x: (x1 - x0), y: (y1 - y0)}; - var b1 = {x: (x2 - x0), y: (y2 - y0)}; - var a1b1 = a1.x * b1.x + a1.y * b1.y; - if (a1b1 < 0) { - return dim(x0, x2, y0, y2); - } - - var a2 = {x: (x0 - x1), y: (y0 - y1)}; - var b2 = {x: (x2 - x1), y: (y2 - y1)}; - var a2b2 = a2.x * b2.x + a2.y * b2.y; - if (a2b2 < 0) { - return dim(x1, x2, y1, y2); - } - - return Math.abs(((x1 - x0) * (y2 - y0) - (y1 - y0) * (x2 - x0)) / dim(x0, x1, y0, y1)); - }, - _generateKey: function (data) { - return JSON.stringify(data); - }, - _getFields: function (unit) { - if (this._dataFields) { - return this._dataFields; - } - - var fields = [unit.size && unit.size.scaleDim, unit.color && unit.color.scaleDim]; - var x = []; - var y = []; - while (unit = unit.parentUnit) { - x.push(unit.x.scaleDim); - y.push(unit.y.scaleDim); - } - - return _.compact(fields.concat(y, x).reverse()); - - }, - isLine: function (data) { - return data.elementData.hasOwnProperty('key') && Array.isArray(data.elementData.values); - }, - _onElementMouseOver: function (chart, data, mosueCoord, placeCoord) { - clearTimeout(this._timeoutHideId); - var key = this._generateKey(data.cellData.$where); - var item = data.elementData; - var isLine = this.isLine(data); - if (isLine) { - var dataWithCoord = this._dataWithCoords[key]; - var filteredData = dataWithCoord.filter(function (value) { - return _.contains(item.values, value.item); - }); - var nearLine = _.reduce(filteredData, function (memo, point, index, data) { - var secondPoint; - if ((index + 1) === data.length) { - var temp = point; - point = data[index - 1]; - secondPoint = temp; - } else { - secondPoint = data[index + 1]; - } - var h = this._calculateLengthToLine(point.x, point.y, secondPoint.x, secondPoint.y, mosueCoord[0], mosueCoord[1]); - if (h < memo.h) { - memo.h = h; - memo.points = { - point1: point, - point2: secondPoint - }; - } - return memo; - }.bind(this), {h: Infinity, points: {}}); - - var itemWithCoord = _.min(nearLine.points, function (a) { - return this._calculateLength(a.x, a.y, mosueCoord[0], mosueCoord[1]); - }, this); - item = itemWithCoord.item; - this._drawPoint(d3.select(data.element.parentNode), itemWithCoord.x, itemWithCoord.y, this._unitMeta[key].options.color.get(data.elementData.key)); - } - if (this._currentElement === item) { - return; - } - var content = this._elementTooltip.querySelectorAll('.i-role-content'); - if (content[0]) { - var fields = this._getFields(this._unitMeta[key]); - content[0].innerHTML = this.render(item, fields); - } else { - console.log('template should contain i-role-content class'); - } - - this._show(placeCoord); - this._currentElement = item; - }, - onElementMouseOver: function (chart, data) { - var placeCoord = d3.mouse(document.body); - var coord = d3.mouse(data.element); - clearTimeout(this._timeoutShowId); - this._timeoutShowId = _.delay(this._onElementMouseOver.bind(this), 200, chart, data, coord, placeCoord); - }, - onElementMouseOut: function (mouseСoord, placeCoord) { - this._hide(); - }, - _show: function (placeCoord) { - this._tooltip.show(placeCoord[0], placeCoord[1]).updateSize(); - }, - _hide: function () { - clearTimeout(this._timeoutShowId); - this._timeoutHideId = setTimeout(function () { - this._currentElement = null; - this._tooltip.hide(); - if (this.circle) { - this.circle.remove(); - } - }.bind(this), 300); - }, - _destroyTooltip: function () { - if (this.circle) { - this.circle.remove(); - } - this._tooltip.destroy(); - }, - destroy: function () { - this._destroyTooltip(); - } - }; - - } - - tauCharts.api.plugins.add('tooltip', tooltip); - return tooltip; +(function (factory) { + if (typeof define === 'function' && define.amd) { + define(['tauCharts'], function (tauPlugins) { + return factory(tauPlugins); + }); + } else if (typeof module === 'object' && module.exports) { + var tauPlugins = require('tauCharts'); + module.exports = factory(tauPlugins); + } else { + factory(this.tauCharts); + } +})(function (tauCharts) { + /** @class Tooltip + * @extends Plugin */ + /* Usage + .plugins(tau.plugins.tooltip('effort', 'priority')) + accepts a list of data fields names as properties + */ + var _ = tauCharts.api._; + var d3 = tauCharts.api.d3; + var dim = function (x0, x1, y0, y1) { + return Math.sqrt((x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0)); + }; + + var dfs = function (node, predicate) { + if (predicate(node)) { + return node; + } + var i, children = node.units || [], child, found; + for (i = 0; i < children.length; i += 1) { + child = children[i]; + found = dfs(child, predicate); + if (found) { + return found; + } + } + }; + + function tooltip(settings) { + settings = settings || {}; + return { + template: [ + '
', + '
', + '
', + 'Exclude', + '
', + '
' + ].join(''), + itemTemplate: [ + '
', + '
<%=label%>
', + '
<%=value%>
', + '
' + ].join(''), + onExcludeData: function () { + + }, + _drawPoint: function (container, x, y, color) { + if (this.circle) { + this.circle.remove(); + } + this.circle = container + .append('circle') + .attr('cx', x) + .attr('cy', y) + .attr('class', color) + .attr('r', 4); + this.circle.node().addEventListener('mouseover', function () { + clearTimeout(this._timeoutHideId); + }.bind(this), false); + this.circle.node().addEventListener('mouseleave', function () { + this._hide(); + }.bind(this), false); + }, + formatters: {}, + labels: {}, + + init: function (chart) { + this._chart = chart; + this._dataFields = settings.fields; + this._getDataFields = settings.getFields; + + _.extend(this, _.omit(settings, 'fields', 'getFields')); + this._timeoutHideId = null; + this._dataWithCoords = {}; + this._unitMeta = {}; + this._templateItem = _.template(this.itemTemplate); + this._tooltip = chart.addBalloon({spacing: 3, auto: true, effectClass: 'fade'}); + this._elementTooltip = this._tooltip.getElement(); + + var spec = chart.getConfig(); // .spec; + + var dimensionGuides = this._findDimensionGuides(spec); + + var lastGuides = _.reduce(dimensionGuides, function (memo, guides, key) { + memo[key] = _.last(guides); + return memo; + }, {}); + var formatters = this._generateDefaultFormatters(lastGuides, spec.scales); + _.extend(this.formatters, formatters); + + var labels = this._generateDefaultLabels(lastGuides); + _.extend(this.labels, labels); + + var elementTooltip = this._elementTooltip; + elementTooltip.addEventListener('mouseover', function () { + clearTimeout(this._timeoutHideId); + }.bind(this), false); + elementTooltip.addEventListener('mouseleave', function () { + this._hide(); + }.bind(this), false); + elementTooltip.addEventListener('click', function (e) { + var target = e.target; + while (target !== e.currentTarget && target !== null) { + if (target.classList.contains('i-role-exclude')) { + this._exclude(); + this._hide(); + } + target = target.parentNode; + } + }.bind(this), false); + elementTooltip.insertAdjacentHTML('afterbegin', this.template); + this.afterInit(this._elementTooltip); + }, + + afterInit: function (elementTooltip) { + + }, + + onUnitDraw: function (chart, unitMeta) { + if (tauCharts.api.isChartElement(unitMeta)) { + var key = this._generateKey(unitMeta.config.options.frameId); + this._unitMeta[key] = unitMeta; + var values = unitMeta.config.frames.reduce(function (data, item) { + return data.concat(item.data) + }, []); + this._dataWithCoords[key] = values.map(function (item) { + return { + x: unitMeta.xScale(item[unitMeta.xScale.dim]), + y: unitMeta.yScale(item[unitMeta.yScale.dim]), + item: item + }; + }, this); + + } + }, + + renderItem: function (label, formattedValue, field, rawValue) { + return this._templateItem({ + label: label, + value: formattedValue + }); + }, + + render: function (data, fields) { + fields = _.unique(fields); + return fields.map(function (field) { + var rawValue = data[field]; + var value = this._getFormatter(field)(rawValue); + var formattedValue = _.isObject(value) ? value.name : value; + var label = this._getLabel(field); + + return this.renderItem(label, formattedValue, field, rawValue); + }, this).join(''); + }, + afterRender: function (toolteipElement) { + + }, + + onRender: function (chart) { + if (_.isFunction(this._getDataFields)) { + this._dataFields = this._getDataFields(chart); + } + this._hide(); + }, + + _getFormatter: function (field) { + return this.formatters[field] || _.identity; + }, + _getLabel: function (field) { + return this.labels[field] || field; + }, + + _generateDefaultLabels: function (lastGuides) { + return _.reduce(lastGuides, function (memo, lastGuide, key) { + if (lastGuide.label) { + memo[key] = lastGuide.label; + if (lastGuide.label.hasOwnProperty('text')) { + memo[key] = lastGuide.label.text; + } + } else { + memo[key] = key; + } + return memo; + }, {}); + }, + + _generateDefaultFormatters: function (lastGuides, dimensions) { + return _.reduce(lastGuides, function (memo, lastGuide, key) { + var getValue = function (rawValue) { + if (rawValue == null) { + return null; + } else { + var format = lastGuide.tickPeriod || lastGuide.tickFormat; + if (format) { + // very special case for dates + var xFormat = (format === 'x-time-auto') ? 'day' : format; + return tauCharts.api.tickFormat.get(xFormat)(rawValue); + } else { + return rawValue; + } + /*else if (lastGuide.tickLabel) { + return rawValue[lastGuide.tickLabel]; + } else if (dimensions[key] && dimensions[key].value) { + return rawValue[dimensions[key].value]; + }*/ + } + }; + + memo[key] = function (rawValue) { + var value = getValue(rawValue); + return value == null ? 'No ' + lastGuide.label : value; + }; + + return memo; + }, {}); + }, + + _findDimensionGuides: function (spec) { + var dimensionGuideMap = {}; + var scales = spec.scales; + var collect = function (field, unit) { + var property = unit[field]; + if (property) { + var guide = (unit.guide || {})[field]; + var dim = scales[property].dim; + if (dim && guide) { + if (!dimensionGuideMap[dim]) { + dimensionGuideMap[dim] = []; + } + + dimensionGuideMap[dim].push(guide); + } + } + }; + + dfs(spec.unit, function (unit) { + collect('x', unit); + collect('y', unit); + collect('color', unit); + collect('size', unit); + + return false; + }); + + return dimensionGuideMap; + + }, + + _exclude: function () { + this._chart.addFilter({ + tag: 'exclude', + predicate: (function (element) { + return function (item) { + return JSON.stringify(item) !== JSON.stringify(element); + }; + }(this._currentElement)) + }); + this.onExcludeData(this._currentElement); + }, + _calculateLength: function (x1, y1, x2, y2) { + return (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1); + }, + _calculateLengthToLine: function (x0, y0, x1, y1, x2, y2) { + var a1 = {x: (x1 - x0), y: (y1 - y0)}; + var b1 = {x: (x2 - x0), y: (y2 - y0)}; + var a1b1 = a1.x * b1.x + a1.y * b1.y; + if (a1b1 < 0) { + return dim(x0, x2, y0, y2); + } + + var a2 = {x: (x0 - x1), y: (y0 - y1)}; + var b2 = {x: (x2 - x1), y: (y2 - y1)}; + var a2b2 = a2.x * b2.x + a2.y * b2.y; + if (a2b2 < 0) { + return dim(x1, x2, y1, y2); + } + + return Math.abs(((x1 - x0) * (y2 - y0) - (y1 - y0) * (x2 - x0)) / dim(x0, x1, y0, y1)); + }, + _generateKey: function (data) { + return JSON.stringify(data); + }, + _getFields: function (unit) { + if (this._dataFields) { + return this._dataFields; + } + + var fields = [unit.size && unit.size.dim, unit.color && unit.color.dim]; + var x = []; + var y = []; + while (unit = unit.parentUnit) { + x.push(unit.xScale.dim); + y.push(unit.yScale.dim); + } + + return _.compact(fields.concat(y, x).reverse()); + + }, + _handleLineElement: function (data, key, mouseCoord) { + var elementData = data.elementData; + var dataWithCoord = this._dataWithCoords[key]; + var filteredData = dataWithCoord.filter(function (value) { + return _.contains(elementData.data, value.item); + }); + if (filteredData.length === 1) { + return filteredData[0].item; + } + var nearLine = _.reduce(filteredData, function (memo, point, index, data) { + var secondPoint; + if ((index + 1) === data.length) { + var temp = point; + point = data[index - 1]; + secondPoint = temp; + } else { + secondPoint = data[index + 1]; + } + var h = this._calculateLengthToLine( + point.x, + point.y, + secondPoint.x, + secondPoint.y, + mouseCoord[0], + mouseCoord[1] + ); + if (h < memo.h) { + memo.h = h; + memo.points = { + point1: point, + point2: secondPoint + }; + } + return memo; + }.bind(this), {h: Infinity, points: {}}); + + var itemWithCoord = _.min(nearLine.points, function (a) { + return this._calculateLength(a.x, a.y, mouseCoord[0], mouseCoord[1]); + }, this); + this._drawPoint( + d3.select(data.element.parentNode), + itemWithCoord.x, + itemWithCoord.y, + this._unitMeta[key].color(data.elementData.tags[this._unitMeta[key].color.dim]) + ); + return itemWithCoord.item; + }, + _onElementMouseOver: function (chart, data, mouseCoord, placeCoord) { + clearTimeout(this._timeoutHideId); + var key = this._generateKey(data.cellData.hash && data.cellData.hash() || + data.cellData.options.frameId); + var item = data.elementData; + if (tauCharts.api.isLineElement(data.unit)) { + item = this._handleLineElement(data, key, mouseCoord) + } + if (this._currentElement === item) { + return; + } + var content = this._elementTooltip.querySelectorAll('.i-role-content'); + if (content[0]) { + var fields = this._getFields(this._unitMeta[key]); + content[0].innerHTML = this.render(item, fields); + } else { + console.log('template should contain i-role-content class'); + } + + this._show(placeCoord); + this._currentElement = item; + }, + onElementMouseOver: function (chart, data) { + var placeCoord = d3.mouse(document.body); + var coord = d3.mouse(data.element); + clearTimeout(this._timeoutShowId); + this._timeoutShowId = _.delay(this._onElementMouseOver.bind(this), 200, chart, data, coord, placeCoord); + }, + onElementMouseOut: function (mouseСoord, placeCoord) { + this._hide(); + }, + _show: function (placeCoord) { + this._tooltip.show(placeCoord[0], placeCoord[1]).updateSize(); + }, + _hide: function () { + clearTimeout(this._timeoutShowId); + this._timeoutHideId = setTimeout(function () { + this._currentElement = null; + this._tooltip.hide(); + if (this.circle) { + this.circle.remove(); + } + }.bind(this), 300); + }, + _destroyTooltip: function () { + if (this.circle) { + this.circle.remove(); + } + this._tooltip.destroy(); + }, + destroy: function () { + this._destroyTooltip(); + } + }; + + } + + tauCharts.api.plugins.add('tooltip', tooltip); + return tooltip; }); \ No newline at end of file diff --git a/plugins/trendline.js b/plugins/trendline.js index e5f635f90..f0c7aad9b 100644 --- a/plugins/trendline.js +++ b/plugins/trendline.js @@ -1,16 +1,16 @@ (function (factory) { - if (typeof define === "function" && define.amd) { + if (typeof define === 'function' && define.amd) { define(['tauCharts'], function (tauPlugins) { return factory(tauPlugins); }); - } else if (typeof module === "object" && module.exports) { + } else if (typeof module === 'object' && module.exports) { var tauPlugins = require('tauCharts'); module.exports = factory(tauPlugins); } else { factory(this.tauCharts); } })(function (tauCharts) { - + // jscs:disable var regressionsHub = (function () { 'use strict'; @@ -217,11 +217,15 @@ return {equation: [lastvalue], points: results, string: "" + lastvalue}; }, - loess: function(data) { + loess: function (data) { //adapted from the LoessInterpolator in org.apache.commons.math function loess_pairs(pairs, bandwidth) { - var xval = pairs.map(function(pair){return pair[0]}); - var yval = pairs.map(function(pair){return pair[1]}); + var xval = pairs.map(function (pair) { + return pair[0] + }); + var yval = pairs.map(function (pair) { + return pair[1] + }); var res = loess(xval, yval, bandwidth); return xval.map(function (x, i) { return [x, res[i]]; @@ -245,7 +249,7 @@ if (i > 0) { if (right < xval.length - 1 && - xval[right+1] - xval[i] < xval[i] - xval[left]) { + xval[right + 1] - xval[i] < xval[i] - xval[left]) { left++; right++; } @@ -263,8 +267,7 @@ var sumX = 0, sumXSquared = 0, sumY = 0, sumXY = 0; var k = left; - while(k <= right) - { + while (k <= right) { var xk = xval[k]; var yk = yval[k]; var dist; @@ -316,90 +319,10 @@ } }); }()); - + // jscs:enable var _ = tauCharts.api._; var d3 = tauCharts.api.d3; - var drawTrendLine = function (trendLineId, dots, xScale, yScale, trendColor, container, originData) { - - var trendCssClass = [ - 'graphical-report__trendline', - - 'graphical-report__line', - 'i-role-element', - - 'graphical-report__line-width-1', - 'graphical-report__line-opacity-1', - - trendLineId, - trendColor].join(' '); - - var line = d3.svg.line() - .interpolate('basis') - .x(function (d) { - return xScale(d[0]); - }) - .y(function (d) { - return yScale(d[1]); - }); - - var updatePaths = function () { - this.attr('d', line); - }; - - var updateLines = function () { - this.attr('class', trendCssClass); - - var paths = this.selectAll('path').data(function (d) { - return [d.dots]; - }); - paths.call(updatePaths); - paths.enter().append('path').call(updatePaths); - paths.exit().remove(); - }; - - var lines = container.selectAll('.' + trendLineId).data([{dots: dots, values: originData}]); - lines.call(updateLines); - lines.enter().append('g').call(updateLines); - lines.exit().remove(); - }; - var isElement = function (unitMeta) { - return (unitMeta.type && unitMeta.type.indexOf('ELEMENT.') === 0); - }; - var coordHasElements = function (units) { - return _.any(units, function (unit) { - return isElement(unit); - }); - }; - var isApplicable = function (dimensions) { - return function (unitMeta) { - var hasElement = (unitMeta.type && unitMeta.type.indexOf('COORDS.') === 0 && coordHasElements(unitMeta.unit)); - if (!hasElement) { - return false; - } - var x = dimensions[unitMeta.x].type; - var y = dimensions[unitMeta.y].type; - return _.every([x, y], function (dimType) { - return dimType && (dimType !== 'category'); - }); - }; - }; - - var dfs = function (node, predicate) { - if (predicate(node)) { - return node; - } - var i, children = node.unit || [], child, found; - for (i = 0; i < children.length; i += 1) { - child = children[i]; - found = dfs(child, predicate); - if (found) { - return found; - } - } - }; - - function trendline(xSettings) { var settings = _.defaults( @@ -417,20 +340,17 @@ init: function (chart) { this._chart = chart; - var conf = chart.getConfig(); - this._isApplicable = dfs(conf.spec.unit, isApplicable(conf.spec.dimensions)); + + this._isApplicable = true; if (settings.showPanel) { this._container = chart.insertToRightSidebar(this.containerTemplate); - var classToAdd = this._isApplicable ? 'applicable-true' : 'applicable-false'; - if (!this._isApplicable) { - this._error = "Trend line can't be computed for categorical data. Each axis should be either a measure or a date."; - } - this._container.classList.add(classToAdd); if (settings.hideError) { - this._container.classList.add('hide-trendline-error'); + this._container + .classList + .add('hide-trendline-error'); } this.uiChangeEventsDispatcher = function (e) { @@ -450,80 +370,89 @@ }.bind(this); - this._container.addEventListener('change', this.uiChangeEventsDispatcher, false); + this._container + .addEventListener('change', this.uiChangeEventsDispatcher, false); } }, - onUnitReady: function (chart, unitMeta) { + onSpecReady: function (chart, specRef) { - if (!settings.showTrend || !isElement(unitMeta) || !this._isApplicable) { + if (!settings.showTrend) { return; } - var options = unitMeta.options; + specRef.transformations = specRef.transformations || {}; + specRef.transformations.regression = function (data, props) { - var x = unitMeta.x.scaleDim; - var y = unitMeta.y.scaleDim; - var c = unitMeta.color.scaleDim; + var x = props.x; + var y = props.y; - //var xAutoScaleVals = unitMeta.scaleMeta(x, unitMeta.guide.x).values; - var yAutoScaleVals = unitMeta.scaleMeta(y, unitMeta.guide.y).values; - - var minY = _.min(yAutoScaleVals); - var maxY = _.max(yAutoScaleVals); - - var categories = unitMeta.groupBy(unitMeta.partition(), c); - - categories.forEach(function (segment, index) { - var sKey = segment.key; - var sVal = segment.values; - - var src = sVal.map(function (item) { + var src = data.map(function (item) { var ix = _.isDate(item[x]) ? item[x].getTime() : item[x]; var iy = _.isDate(item[y]) ? item[y].getTime() : item[y]; return [ix, iy]; }); - var regression = regressionsHub(settings.type, src); - var dots = _(regression.points) + var regression = regressionsHub(props.type, src); + return _(regression.points) .chain() .sortBy(function (p) { return p[0]; }) - .filter(function (p) { - return ((minY <= p[1]) && (p[1] <= maxY)); + .map(function (p) { + var item = {}; + item[x] = p[0]; + item[y] = p[1]; + return item; }) .value(); + }; - if (dots.length > 1) { - drawTrendLine( - 'i-trendline-' + index, - dots, - options.xScale, - options.yScale, - options.color(sKey), - options.container, - sVal); - } - }); - - var handleMouse = function (isActive) { - return function () { - var g = d3.select(this); - g.classed({ - 'active': isActive, - 'graphical-report__line-width-1': !isActive, - 'graphical-report__line-width-2': isActive + var isApplicable = false; + chart.traverseSpec( + specRef, + function (unit, parentUnit) { + + if (parentUnit && parentUnit.type !== 'COORDS.RECT') { + return; + } + + if (unit.type.indexOf('ELEMENT.') === -1) { + return; + } + + var xScale = specRef.scales[unit.x]; + var yScale = specRef.scales[unit.y]; + + if (xScale.type === 'ordinal' || yScale.type === 'ordinal') { + return; + } + + isApplicable = true; + + var trend = JSON.parse(JSON.stringify(unit)); + + trend.type = 'ELEMENT.LINE'; + trend.transformation = trend.transformation || []; + trend.transformation.push({ + type: 'regression', + args: { + type: settings.type, // 'linear', + x: xScale.dim, + y: yScale.dim + } }); - }; - }; + trend.guide = trend.guide || {}; + trend.guide.cssClass = 'graphical-report__trendline'; + trend.guide.widthCssClass = 'graphical-report__line-width-1'; - options.container - .selectAll('.graphical-report__trendline') - .on('mouseenter', handleMouse(true)) - .on('mouseleave', handleMouse(false)); + parentUnit.units.push(trend); + }); + + this._isApplicable = isApplicable; }, +// jscs:disable maximumLineLength containerTemplate: '
', template: _.template([ '