From ef03469fec14f1f0358b690934fc173d744f4e7d Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 17:26:01 +0200 Subject: Adding debian version 5.6.0-1. Signed-off-by: Daniel Baumann --- debian/missing-sources/epoch/.travis.yml | 4 + debian/missing-sources/epoch/CHANGELOG.md | 102 +++ debian/missing-sources/epoch/LICENSE | 21 + debian/missing-sources/epoch/README.md | 135 ++++ debian/missing-sources/epoch/bower.json | 37 + debian/missing-sources/epoch/composer.json | 22 + debian/missing-sources/epoch/gulpfile.js | 108 +++ debian/missing-sources/epoch/package.json | 55 ++ debian/missing-sources/epoch/sass/_core.scss | 99 +++ debian/missing-sources/epoch/sass/epoch.scss | 12 + .../missing-sources/epoch/sass/themes/_dark.scss | 95 +++ .../epoch/sass/themes/_default.scss | 189 ++++++ debian/missing-sources/epoch/src/adapters.coffee | 13 + .../epoch/src/adapters/MooTools.coffee | 19 + .../epoch/src/adapters/jQuery.coffee | 18 + .../epoch/src/adapters/zepto.coffee | 27 + debian/missing-sources/epoch/src/basic.coffee | 243 +++++++ debian/missing-sources/epoch/src/basic/area.coffee | 51 ++ debian/missing-sources/epoch/src/basic/bar.coffee | 273 ++++++++ .../epoch/src/basic/histogram.coffee | 61 ++ debian/missing-sources/epoch/src/basic/line.coffee | 46 ++ debian/missing-sources/epoch/src/basic/pie.coffee | 59 ++ .../missing-sources/epoch/src/basic/scatter.coffee | 59 ++ debian/missing-sources/epoch/src/core/chart.coffee | 361 ++++++++++ .../missing-sources/epoch/src/core/context.coffee | 25 + debian/missing-sources/epoch/src/core/css.coffee | 131 ++++ debian/missing-sources/epoch/src/core/d3.coffee | 25 + .../missing-sources/epoch/src/core/format.coffee | 15 + debian/missing-sources/epoch/src/core/util.coffee | 236 +++++++ debian/missing-sources/epoch/src/data.coffee | 311 +++++++++ debian/missing-sources/epoch/src/epoch.coffee | 17 + debian/missing-sources/epoch/src/model.coffee | 55 ++ debian/missing-sources/epoch/src/time.coffee | 615 +++++++++++++++++ debian/missing-sources/epoch/src/time/area.coffee | 80 +++ debian/missing-sources/epoch/src/time/bar.coffee | 48 ++ debian/missing-sources/epoch/src/time/gauge.coffee | 209 ++++++ .../missing-sources/epoch/src/time/heatmap.coffee | 261 ++++++++ debian/missing-sources/epoch/src/time/line.coffee | 39 ++ .../epoch/tests/render/basic/area.html | 450 +++++++++++++ .../epoch/tests/render/basic/bar.html | 745 +++++++++++++++++++++ .../epoch/tests/render/basic/histogram.html | 155 +++++ .../epoch/tests/render/basic/line.html | 471 +++++++++++++ .../epoch/tests/render/basic/model.html | 94 +++ .../epoch/tests/render/basic/options.html | 267 ++++++++ .../epoch/tests/render/basic/pie.html | 346 ++++++++++ .../epoch/tests/render/basic/scatter.html | 398 +++++++++++ .../epoch/tests/render/css/tests.css | 12 + .../missing-sources/epoch/tests/render/index.html | 57 ++ .../missing-sources/epoch/tests/render/js/data.js | 210 ++++++ .../epoch/tests/render/real-time/area.html | 494 ++++++++++++++ .../epoch/tests/render/real-time/bar.html | 484 +++++++++++++ .../epoch/tests/render/real-time/gauge.html | 283 ++++++++ .../epoch/tests/render/real-time/heatmap.html | 559 ++++++++++++++++ .../epoch/tests/render/real-time/line.html | 596 +++++++++++++++++ .../epoch/tests/render/real-time/model.html | 85 +++ .../epoch/tests/render/real-time/options.html | 299 +++++++++ .../epoch/tests/render/themes/dark.html | 211 ++++++ .../epoch/tests/render/themes/default.html | 70 ++ .../epoch/tests/unit/core/charts.coffee | 573 ++++++++++++++++ .../epoch/tests/unit/core/copy.coffee | 47 ++ .../epoch/tests/unit/core/css.coffee | 85 +++ .../epoch/tests/unit/core/d3.coffee | 40 ++ .../epoch/tests/unit/core/events.coffee | 111 +++ .../epoch/tests/unit/core/format.coffee | 68 ++ .../epoch/tests/unit/core/is.coffee | 88 +++ .../epoch/tests/unit/core/util.coffee | 92 +++ .../epoch/tests/unit/data/array_format.coffee | 106 +++ .../epoch/tests/unit/data/chart.coffee | 121 ++++ .../epoch/tests/unit/data/keyvalue_format.coffee | 101 +++ .../epoch/tests/unit/data/tuple_format.coffee | 76 +++ .../missing-sources/epoch/tests/unit/init.coffee | 55 ++ .../missing-sources/epoch/tests/unit/time.coffee | 67 ++ .../epoch/tests/unit/time/line.coffee | 15 + 73 files changed, 12107 insertions(+) create mode 100644 debian/missing-sources/epoch/.travis.yml create mode 100644 debian/missing-sources/epoch/CHANGELOG.md create mode 100644 debian/missing-sources/epoch/LICENSE create mode 100644 debian/missing-sources/epoch/README.md create mode 100644 debian/missing-sources/epoch/bower.json create mode 100644 debian/missing-sources/epoch/composer.json create mode 100644 debian/missing-sources/epoch/gulpfile.js create mode 100644 debian/missing-sources/epoch/package.json create mode 100644 debian/missing-sources/epoch/sass/_core.scss create mode 100644 debian/missing-sources/epoch/sass/epoch.scss create mode 100644 debian/missing-sources/epoch/sass/themes/_dark.scss create mode 100644 debian/missing-sources/epoch/sass/themes/_default.scss create mode 100644 debian/missing-sources/epoch/src/adapters.coffee create mode 100644 debian/missing-sources/epoch/src/adapters/MooTools.coffee create mode 100644 debian/missing-sources/epoch/src/adapters/jQuery.coffee create mode 100644 debian/missing-sources/epoch/src/adapters/zepto.coffee create mode 100644 debian/missing-sources/epoch/src/basic.coffee create mode 100644 debian/missing-sources/epoch/src/basic/area.coffee create mode 100644 debian/missing-sources/epoch/src/basic/bar.coffee create mode 100644 debian/missing-sources/epoch/src/basic/histogram.coffee create mode 100644 debian/missing-sources/epoch/src/basic/line.coffee create mode 100644 debian/missing-sources/epoch/src/basic/pie.coffee create mode 100644 debian/missing-sources/epoch/src/basic/scatter.coffee create mode 100644 debian/missing-sources/epoch/src/core/chart.coffee create mode 100644 debian/missing-sources/epoch/src/core/context.coffee create mode 100644 debian/missing-sources/epoch/src/core/css.coffee create mode 100644 debian/missing-sources/epoch/src/core/d3.coffee create mode 100644 debian/missing-sources/epoch/src/core/format.coffee create mode 100644 debian/missing-sources/epoch/src/core/util.coffee create mode 100644 debian/missing-sources/epoch/src/data.coffee create mode 100644 debian/missing-sources/epoch/src/epoch.coffee create mode 100644 debian/missing-sources/epoch/src/model.coffee create mode 100644 debian/missing-sources/epoch/src/time.coffee create mode 100644 debian/missing-sources/epoch/src/time/area.coffee create mode 100644 debian/missing-sources/epoch/src/time/bar.coffee create mode 100644 debian/missing-sources/epoch/src/time/gauge.coffee create mode 100644 debian/missing-sources/epoch/src/time/heatmap.coffee create mode 100644 debian/missing-sources/epoch/src/time/line.coffee create mode 100644 debian/missing-sources/epoch/tests/render/basic/area.html create mode 100644 debian/missing-sources/epoch/tests/render/basic/bar.html create mode 100644 debian/missing-sources/epoch/tests/render/basic/histogram.html create mode 100644 debian/missing-sources/epoch/tests/render/basic/line.html create mode 100644 debian/missing-sources/epoch/tests/render/basic/model.html create mode 100644 debian/missing-sources/epoch/tests/render/basic/options.html create mode 100644 debian/missing-sources/epoch/tests/render/basic/pie.html create mode 100644 debian/missing-sources/epoch/tests/render/basic/scatter.html create mode 100644 debian/missing-sources/epoch/tests/render/css/tests.css create mode 100644 debian/missing-sources/epoch/tests/render/index.html create mode 100644 debian/missing-sources/epoch/tests/render/js/data.js create mode 100644 debian/missing-sources/epoch/tests/render/real-time/area.html create mode 100644 debian/missing-sources/epoch/tests/render/real-time/bar.html create mode 100644 debian/missing-sources/epoch/tests/render/real-time/gauge.html create mode 100644 debian/missing-sources/epoch/tests/render/real-time/heatmap.html create mode 100644 debian/missing-sources/epoch/tests/render/real-time/line.html create mode 100644 debian/missing-sources/epoch/tests/render/real-time/model.html create mode 100644 debian/missing-sources/epoch/tests/render/real-time/options.html create mode 100644 debian/missing-sources/epoch/tests/render/themes/dark.html create mode 100644 debian/missing-sources/epoch/tests/render/themes/default.html create mode 100644 debian/missing-sources/epoch/tests/unit/core/charts.coffee create mode 100644 debian/missing-sources/epoch/tests/unit/core/copy.coffee create mode 100644 debian/missing-sources/epoch/tests/unit/core/css.coffee create mode 100644 debian/missing-sources/epoch/tests/unit/core/d3.coffee create mode 100644 debian/missing-sources/epoch/tests/unit/core/events.coffee create mode 100644 debian/missing-sources/epoch/tests/unit/core/format.coffee create mode 100644 debian/missing-sources/epoch/tests/unit/core/is.coffee create mode 100644 debian/missing-sources/epoch/tests/unit/core/util.coffee create mode 100644 debian/missing-sources/epoch/tests/unit/data/array_format.coffee create mode 100644 debian/missing-sources/epoch/tests/unit/data/chart.coffee create mode 100644 debian/missing-sources/epoch/tests/unit/data/keyvalue_format.coffee create mode 100644 debian/missing-sources/epoch/tests/unit/data/tuple_format.coffee create mode 100644 debian/missing-sources/epoch/tests/unit/init.coffee create mode 100644 debian/missing-sources/epoch/tests/unit/time.coffee create mode 100644 debian/missing-sources/epoch/tests/unit/time/line.coffee (limited to 'debian/missing-sources/epoch') diff --git a/debian/missing-sources/epoch/.travis.yml b/debian/missing-sources/epoch/.travis.yml new file mode 100644 index 0000000..c3bbff7 --- /dev/null +++ b/debian/missing-sources/epoch/.travis.yml @@ -0,0 +1,4 @@ +language: node_js +node_js: + - "4.1.1" +sudo: false diff --git a/debian/missing-sources/epoch/CHANGELOG.md b/debian/missing-sources/epoch/CHANGELOG.md new file mode 100644 index 0000000..0492850 --- /dev/null +++ b/debian/missing-sources/epoch/CHANGELOG.md @@ -0,0 +1,102 @@ +# Epoch Changelog + +## 0.8.4 - October 30th, 2015 +### Bug Fixes +* Fixed bower css path (@ftaiolivista) + +## 0.8.3 - October 17th, 2015 +### Enhancements / Features +* Added `redraw` method for clearing styles on canvas based charts (#196, @woozyking) + +## 0.8.2 - October 13th, 2015 +### Enhancements / Features +* Charts now auto draw on construction (#195) + +## 0.8.1 - October 13th, 2015 +### Enhancements / Features +* Added packagist/composer package manager support (#202) + +### Bug Fixes +* Real-time charts no-longer error when pushing first data point after initialized + with empty data layers. (#203) + +## 0.8.0 - October 10th, 2015 +### Enhancements / Features +* Multi-axis support for basic and real-time line plots +* Added new gulp build-system (for development) + +## 0.7.1 - October 4th, 2015 +* Moved minified source to `dist/js` and `dist/css` respectively +* Added non-minified source to aforementioned directories + +## 0.7.0 - October 4th, 2015 + +### Enhancements / Features +* New basic chart: Histogram +* New Feature: Data formatters +* Chart layers can now be hidden/shown + +### Bug Fixes +* Ticks now working for ordinal scaled bar charts +* Fixed CSS builds by updating NPM sass-node package +* Removed versions from minified release files (@RyanNielson) +* Time based graphs can now have fixed ranges (@willwhitney) +* NPM Package: epoch-charting (@sompylasar) +* Right axes now using correct formatters (@Dav1dde) +* Add 'main' attribute enabling webpack support. (@WRidder) +* Fixed Bower D3 Dependencies (@loopj) +* Fixed CSS errors by using `transparent` instead of `none` (@mwsmith2) +* Fixed bower "version" property (@kkirsche) + +## 0.6.0 - July 21st, 2014 + +### Enhancements / Features + +* Source code restructure for easier programming +* Replaced Compass with node-sass +* Removed put.js from the repository +* Removed dependency on jQuery +* Added CSS controlled themes + * New "Dark" theme for dark backgrounds +* Registered with bower +* Added option accessor / mutator to all charts (making them adaptive) +* Added bubble charts (special case of scatter plots) +* Added MooTools and Zepto Adapters +* Added Core Library Unit Testing +* New `domain` and `range` options for basic charts + +### Bug Fixes + +* Event `.off` method was completely busted, fixed +* Swapped terminology for horizontal and vertical bar plots +* Removed `isVisible` and related rendering hacks (caused all sorts of woe) + + +## 0.5.2 - June 24th, 2014 + +### Enhancements / Features + +* #36 - Fixed the readme to focus on development +* #54 - Added vertical orientation option to the basic bar chart + +## 0.5.1 - June 23rd, 2014 + +### Bug Fixes + +* #52 - Replaced instances of `$` with `jQuery` (ambiguous, otherwise) + +## 0.5.0 - June 23rd, 2014 + +### Enhancements / Features + +* #32 - QueryCSS greatly enhanced - now builds a full DOM context when computing styles +* #42 - Heat map now allows for painting of "zero" values via a new `paintZeroValues` option +* #43 - Heat map color computation abstracted out of `_paintEntry` (makes it easier to extend) + +### Bug Fixes + +* #22 - Fixed an issue with pie chart transitions +* #30 - Layers without labels now correctly render on a various basic charts +* #31 - Real-time Line Chart thickness fixed by taking pixel ratio into account +* #41 - Fixed bucketing issues with the Heat Map +* #46 - Removed default black stroke from the Real-Time Area chart diff --git a/debian/missing-sources/epoch/LICENSE b/debian/missing-sources/epoch/LICENSE new file mode 100644 index 0000000..3e8d8f8 --- /dev/null +++ b/debian/missing-sources/epoch/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Fastly, Inc. + +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: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +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. diff --git a/debian/missing-sources/epoch/README.md b/debian/missing-sources/epoch/README.md new file mode 100644 index 0000000..5095f3a --- /dev/null +++ b/debian/missing-sources/epoch/README.md @@ -0,0 +1,135 @@ +## Epoch +By Ryan Sandor Richards + +[![Build Status](https://travis-ci.org/epochjs/epoch.svg?branch=master)](https://travis-ci.org/epochjs/epoch) +[![Dependency Status](https://david-dm.org/epochjs/epoch.svg)](https://david-dm.org/epochjs/epoch) +[![devDependency Status](https://david-dm.org/epochjs/epoch/dev-status.svg)](https://david-dm.org/epochjs/epoch#info=devDependencies) + +Epoch is a general purpose charting library for application developers and visualization designers. It focuses on two different aspects of visualization programming: **basic charts** for creating historical reports, and **real-time charts** for displaying frequently updating timeseries data. + +To get started using Epoch, please refer to the [Epoch Project Site](http://epochjs.github.io/epoch/). There you can find full documentation and guides to help you start using Epoch right away. + +### Installation +Epoch can be easily installed via the following package managers: + +* [npm](https://www.npmjs.com/package/epoch-charting) +* [bower](http://bower.io/search/?q=epoch) +* [packagist](https://packagist.org/packages/epochjs/epoch) + +If you don't see your favorite package manager in the list above feel free to +[open up an issue](https://github.com/epochjs/epoch/issues/new) and let us know. +Finally, you can download any release of the library from the +[project releases page](https://github.com/epochjs/epoch/releases). + +**Important:** Epoch requires [d3](https://github.com/mbostock/d3). In order to +work properly your page must load d3 before epoch. + +#### Public CDN URLs +If you don't want to host the files yourself, you can use +[jsDelivr](http://www.jsdelivr.com/) to serve the files: + +1. Visit [epoch page on jsDelvr](http://www.jsdelivr.com/projects/epoch). +2. Copy the provided URL's and link to them in your project. + +### Developing Epoch + +Developing Epoch is a reasonably straight forward process. In this section we'll +cover the basic on how to develop Epoch by detailing common build task, exploring +how the source is arranged, and finally show how to use rendering tests to aid +development. + +#### Configuring Development Environment + +Epoch requires the following for development: + +1. [Node.js](https://nodejs.org/en/) (v4.1.1+) +2. [NPM](https://www.npmjs.com/) (v2.1.0+) + +Once both are installed on your machine you will need to run `npm install` from +the repository's root directory in order to install the npm packages required +to develop epoch. + +Once you have installed the required npm packages you can use `gulp build` to +fully rebuild the source (see more information about gulp tasks below). + + +#### Basic Development Process + +The best way to start contributing to Epoch is to follow these steps: + +1. Change to the source directory for the project +2. Run `gulp watch` to recompile the project after source files change +3. Make changes in a source file (either in `src/` or `sass/`) +4. In a web browser open the `test/index.html` and browse the rendering tests +5. Use the rendering tests to see if your changes had the desired result +6. Ensure unit tests with pass `npm test` + +#### Testing + +Epoch uses two types of testing to ensure that changes do not cause unintended +side effects. The first, unit tests, ensure that the core functional components +of the library work as expected. The second, rendering tests, allow you to +ensure that charts and graphs are correctly rendered. + +It is important to keep both unit test and rendering tests up-to-date! When +developing, use the following guidelines: + +* When adding new features make sure to add new tests +* When changing existing functionality, ensure that the appropriate both types + of tests still pass +* If you want to make a new type of chart, add a whole new test suite for that + chart! + +Keeping the tests current makes it easier for others to review your code and +spot issues. Also, pull requests without appropriate testing will not be +merged. + + +#### Gulp Tasks + +Epoch uses [gulp](https://github.com/gulpjs/gulp) to perform various tasks. The +`gulpfile.js` file defines the following tasks: + +* `gulp clean` - Cleans the `dist/` directory. +* `gulp build` - Builds the CoffeeScript and Sass source into the `dist/` + directory. +* `gulp watch` - Starts a watch script to recompile CoffeeScript and Sass when + any files change. + +#### Source Structure + +The directory structure for the Epoch project follows some basic guidelines, here's an overview of how it is structured: + +``` +dist/ - Compiled JavaScript and CSS source +src/ - Main source directory + core/ - Core Epoch Library Files + util.coffee - Library Utility Routines + d3.coffee - d3 Extensions + format.coffee - Data formatters + chart.coffee - Base Chart Classes + css.coffee - CSS Querying Engine + adapters/ - 3rd Party Library Adapters (currently only jQuery) + basic/ - Basic Chart Classes + time/ - Real-time Chart Classes + adapters.coffee - Options / Global Classes for Adapter Implementations + basic.coffee - Base Classes for Basic Charts + data.coffee - Data Formatting + epoch.coffee - Main source file, defines name spaces, etc. + model.coffee - Data Model + time.coffee - Base Classes for Real-Time Charts +sass/ - Scss source for the default epoch stylesheet +tests/ + render/ - Rendering tests + basic/ - Basic chart rendering tests + real-time/ - Real-time rendering tests + unit/ - Unit tests +``` + +### Release Checklist + +- Run `npm test` and ensure all tests pass +- Run `npm version [major|minor|patch]` +- Run `npm publish` +- Update CHANGELOG.md with the changes since last release +- Update the `gh-pages` branch's library version in `_config.yml` diff --git a/debian/missing-sources/epoch/bower.json b/debian/missing-sources/epoch/bower.json new file mode 100644 index 0000000..36ee8e6 --- /dev/null +++ b/debian/missing-sources/epoch/bower.json @@ -0,0 +1,37 @@ +{ + "name": "epoch", + "description": "A general purpose, real-time visualization library.", + "main": [ + "dist/js/epoch.min.js", + "dist/css/epoch.min.css" + ], + "license": "MIT", + "ignore": [ + "**/.*", + "/src", + "/sass", + "/tests", + "/Cakefile", + "/CHANGELOG.md", + "/package.json" + ], + "authors": [ + "Ryan Sandor Richards " + ], + "keywords": [ + "fastly", + "realtime", + "graph", + "chart", + "stats", + "visualization" + ], + "homepage": "https://fastly.github.io/epoch/", + "repository": { + "type": "git", + "url": "git://github.com/fastly/epoch.git" + }, + "dependencies": { + "d3": "^3.4.13" + } +} diff --git a/debian/missing-sources/epoch/composer.json b/debian/missing-sources/epoch/composer.json new file mode 100644 index 0000000..184efe0 --- /dev/null +++ b/debian/missing-sources/epoch/composer.json @@ -0,0 +1,22 @@ +{ + "name": "epochjs/epoch", + "description": "A general purpose, real-time visualization library.", + "keywords": [ + "epoch", + "d3", + "chart", + "graph", + "plot", + "real-time" + ], + "homepage": "https://github.com/epochjs/epoch", + "license": "MIT", + "authors": [ + { + "name": "Ryan Sandor Richards" + } + ], + "require": { + "mbostock/d3": "@stable" + } +} diff --git a/debian/missing-sources/epoch/gulpfile.js b/debian/missing-sources/epoch/gulpfile.js new file mode 100644 index 0000000..4f3b975 --- /dev/null +++ b/debian/missing-sources/epoch/gulpfile.js @@ -0,0 +1,108 @@ +'use strict'; + +require('coffee-script/register'); // For coffee-script mocha unit tests + +var gulp = require('gulp'); +var coffee = require('gulp-coffee'); +var concat = require('gulp-concat'); +var mocha = require('gulp-mocha'); +var order = require('gulp-order'); +var rename = require('gulp-rename'); +var sass = require('gulp-sass'); +var uglify = require('gulp-uglify'); +var gutil = require('gulp-util'); +var del = require('del'); +var exec = require('child_process').exec; + +/** + * Common directories used by tasks below. + * @type {object} + */ +var path = { + source: { + coffee: 'src/', + sass: 'sass/' + }, + dist: { + js: 'dist/js/', + css: 'dist/css/' + }, + test: { + unit: 'tests/unit/' + }, + doc: 'doc/' +}; + +/** + * The default task simply calls the master 'build' task. + */ +gulp.task('default', ['build']); + +/** + * Builds the distribution files by packaging the compiled javascript source + * into the `dist/js/` directory and building the css into the `dist/css` + * directory + */ +gulp.task('build', ['sass', 'sass-minify'], function () { + gulp.src(path.source.coffee + '**/*.coffee') + .pipe(coffee({bare: true}).on('error', gutil.log)) + .pipe(order([ + 'epoch.js', + 'core/context.js', + 'core/util.js', + 'core/d3.js', + 'core/format.js', + 'core/chart.js', + 'core/css.js', + 'data.js', + 'model.js', + 'basic.js', + 'basic/*.js', + 'time.js', + 'time/*.js', + 'adapters.js', + 'adapters/*.js' + ])) + .pipe(concat('epoch.js')) + .pipe(gulp.dest(path.dist.js)) + .pipe(uglify().on('error', gutil.log)) + .pipe(rename('epoch.min.js')) + .pipe(gulp.dest(path.dist.js)); +}); + +/** + * Generates epoch CSS from Sass source. + */ +gulp.task('sass', function () { + gulp.src(path.source.sass + 'epoch.scss') + .pipe(sass({ outputStyle: 'compact' })) + .pipe(rename('epoch.css')) + .pipe(gulp.dest(path.dist.css)); +}); + +/** + * Generates the minified version of the epoch css from sass source. + */ +gulp.task('sass-minify', function () { + gulp.src(path.source.sass + 'epoch.scss') + .pipe(sass({ outputStyle: 'compressed' })) + .pipe(rename('epoch.min.css')) + .pipe(gulp.dest(path.dist.css)); +}); + +/** + * Watch script for recompiling JavaScript and CSS + */ +gulp.task('watch', function () { + gulp.watch(path.source.coffee + '**/*.coffee', ['build']); + gulp.watch(path.source.sass + '**/*.scss', ['sass', 'sass-minify']); +}); + +/** + * Cleans all build and distribution files. + */ +gulp.task('clean', function (cb) { + del([ path.dist.js, path.dist.css]).then(function () { + cb(); + }); +}); diff --git a/debian/missing-sources/epoch/package.json b/debian/missing-sources/epoch/package.json new file mode 100644 index 0000000..80f14ef --- /dev/null +++ b/debian/missing-sources/epoch/package.json @@ -0,0 +1,55 @@ +{ + "name": "epoch-charting", + "version": "0.8.4", + "description": "A general purpose real-time charting library for building beautiful, smooth, and high performance visualizations.", + "keywords": [ + "chart", + "charting", + "visualization", + "svg", + "animation", + "canvas", + "d3" + ], + "homepage": "http://fastly.github.io/epoch/", + "author": { + "name": "rsandor", + "url": "https://github.com/rsandor" + }, + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/fastly/epoch.git" + }, + "main": "dist/js/epoch.js", + "dependencies": { + "d3": "^3.4.13" + }, + "devDependencies": { + "chai": "^3.3.0", + "codo": "^2.0.11", + "coffee-script": "^1.10.0", + "del": "^2.0.2", + "gulp": "^3.9.0", + "gulp-coffee": "^2.3.1", + "gulp-concat": "^2.6.0", + "gulp-mocha": "^2.1.3", + "gulp-order": "^1.1.1", + "gulp-rename": "^1.2.2", + "gulp-sass": "^2.0.4", + "gulp-uglify": "^1.4.1", + "gulp-util": "^3.0.6", + "jsdom": "^7.0.2", + "mocha": "^2.3.3", + "node-minify": "^1.2.1", + "node-sass": "^3.3.3", + "sinon": "^1.17.1", + "xmlhttprequest": "^1.7.0" + }, + "scripts": { + "build": "gulp build", + "unit": "mocha --recursive --compilers coffee:coffee-script/register tests/unit/", + "test": "npm run build && npm run unit", + "codo": "./node_modules/.bin/codo --quiet --private --name Epoch --readme README.md --title 'Epoch Documentation' --output codo-doc src - LICENSE" + } +} diff --git a/debian/missing-sources/epoch/sass/_core.scss b/debian/missing-sources/epoch/sass/_core.scss new file mode 100644 index 0000000..93bc767 --- /dev/null +++ b/debian/missing-sources/epoch/sass/_core.scss @@ -0,0 +1,99 @@ +/* + * Core Epoch Styles + */ + +/** + * Generates the styles needed to define a fill color for a given class name. + * @param $name Name of the class to use (sans the leading .) + * @param $color Fill color to associate with the class name. + */ +@mixin epoch-color($name, $color) { + div.ref.#{$name} { + background-color: $color; + } + .#{$name} { + .line { stroke: $color; } + .area, .dot { fill: $color; } + } + .arc.#{$name} path { + fill: $color; + } + .bar.#{$name} { + fill: $color; + } + .#{$name} { + .bucket { fill: $color; } + } +} + +/** + * Produces categorical color classes for plots (excluding heatmaps). + * @param $list List of colors to use for each category. + * @param $entries Size of the input list. + */ +@mixin epoch-category-colors($list, $entries) { + @for $i from 1 through $entries { + div.ref.category#{$i} { + background-color: nth($list, $i); + } + .category#{$i} { + .line { stroke: nth($list, $i); } + .area, .dot { fill: nth($list, $i); stroke: rgba(0,0,0,0); } + } + .arc.category#{$i} path { + fill: nth($list, $i); + } + .bar.category#{$i} { + fill: nth($list, $i); + } + } +} + +/** + * Produces categorical colors for heatmaps. + * @param $list List of colors to use for categories. + * @param $entries Size of the input list. + */ +@mixin epoch-heatmap-colors($list, $entries) { + @for $i from 1 through $entries { + .category#{$i} { + .bucket { fill: nth($list, $i); } + } + } +} + +// Axis and Tick Shape Rendering +.epoch { + .axis path, .axis line { + shape-rendering: crispEdges; + } + + .axis.canvas .tick line { + shape-rendering: geometricPrecision; + } +} + +/* + * Canvas Styles Reference Container + * + * The reference container is an SVG that is automatically created when Epoch is loaded. It is used + * by the canvas based plots to obtain color information from the page styles by creating reference + * elements and then reading their computed styles. + * + * Note: don't mess with this ;) + */ +div#_canvas_css_reference { + width: 0; + height: 0; + position: absolute; + top: -1000px; + left: -1000px; + svg { + position: absolute; + width: 0; + height: 0; + top: -1000px; + left: -1000px; + } +} + diff --git a/debian/missing-sources/epoch/sass/epoch.scss b/debian/missing-sources/epoch/sass/epoch.scss new file mode 100644 index 0000000..2689262 --- /dev/null +++ b/debian/missing-sources/epoch/sass/epoch.scss @@ -0,0 +1,12 @@ +/* + * Epoch Master SCSS + * + * Includes the core styles and all the themes to produce the complete epoch css file. + * + * By Ryan Sandor Richards + * Copyright 2013 Fastly, Inc. + */ + +@import "core"; +@import "themes/default"; +@import "themes/dark"; diff --git a/debian/missing-sources/epoch/sass/themes/_dark.scss b/debian/missing-sources/epoch/sass/themes/_dark.scss new file mode 100644 index 0000000..e343eff --- /dev/null +++ b/debian/missing-sources/epoch/sass/themes/_dark.scss @@ -0,0 +1,95 @@ +/* + * theme/_dark.scss - Theme design for dark page backgrounds. + * Designed by Ryan Sandor Richards + */ + +$axisAndText: #d0d0d0; +$background: #333; + +.epoch-theme-dark { + // Axes and Ticks + .epoch { + .axis path, .axis line { + stroke: $axisAndText; + } + .axis .tick text { + fill: $axisAndText; + } + } + + // Pie Charts + .arc.pie { + stroke: $background; + } + + .arc.pie text { + fill: $background; + } + + // Gauges + .epoch .gauge-labels .value { + fill: #BBB; + } + + .epoch .gauge { + .arc.outer { + stroke: #999; + } + .arc.inner { + stroke: #AAA; + } + .tick { + stroke: #AAA; + } + + .needle { + fill: #F3DE88; + } + + .needle-base { + fill: #999; + } + } + + // Categorical Colors + $dark_category10: + #909CFF, #FFAC89, #E889E8, #78E8D3, #C2FF97, + #B7BCD1, #FF857F, #F3DE88, #C9935E, #A488FF; + + .epoch, .epoch.category10 { + @include epoch-category-colors($dark_category10, 10); + } + + $dark_category20: + #909CFF, #626AAD, #FFAC89, #BD7F66, + #E889E8, #995A99, #78E8D3, #4F998C, + #C2FF97, #789E5E, #B7BCD1, #7F8391, + #CCB889, #A1906B, #F3DE88, #A89A5E, + #FF857F, #BA615D, #A488FF, #7662B8; + + .epoch.category20 { + @include epoch-category-colors($dark_category20, 20); + } + + $dark_category20b: + #909CFF, #7680D1, #656DB2, #525992, + #FFAC89, #D18D71, #AB735C, #92624E, + #E889E8, #BA6EBA, #9B5C9B, #7B487B, + #78E8D3, #60BAAA, #509B8D, #3F7B70, + #C2FF97, #9FD17C, #7DA361, #65854E; + + .epoch.category20b { + @include epoch-category-colors($dark_category20b, 20); + } + + $dark_category20c: + #B7BCD1, #979DAD, #6E717D, #595C66, + #FF857F, #DE746E, #B55F5A, #964E4B, + #F3DE88, #DBC87B, #BAAA68, #918551, + #C9935E, #B58455, #997048, #735436, + #A488FF, #8670D1, #705CAD, #52447F; + + .epoch.category20c { + @include epoch-category-colors($dark_category20c, 20); + } +} diff --git a/debian/missing-sources/epoch/sass/themes/_default.scss b/debian/missing-sources/epoch/sass/themes/_default.scss new file mode 100644 index 0000000..d3075c8 --- /dev/null +++ b/debian/missing-sources/epoch/sass/themes/_default.scss @@ -0,0 +1,189 @@ +/* + * theme/_default.scss - Default Color Theme + * Categorical Colors Adapted from d3: + * https://github.com/mbostock/d3/wiki/Ordinal-Scales#categorical-colors + */ + +// Fonts +.epoch { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 12pt; +} + +// Axes and Ticks +.epoch { + .axis path, .axis line { + fill: transparent; + stroke: #000; + } + + .axis .tick text { + font-size: 9pt; + } +} + +// Line Charts +.epoch .line { + fill: transparent; + stroke-width: 2px; +} + +.epoch.sparklines .line { + stroke-width: 1px; +} + + +// Area Charts +.epoch .area { + stroke: transparent; +} + +// Pie Charts +.epoch { + .arc.pie { + stroke: #fff; + stroke-width: 1.5px; + } + + .arc.pie text { + stroke: transparent; + fill: white; + font-size: 9pt; + } +} + +// Gauge Charts +.epoch .gauge-labels { + .value { + text-anchor: middle; + font-size: 140%; + fill: #666; + } +} + +.epoch.gauge-tiny { + width: 120px; height: 90px; + + .gauge-labels { + .value { font-size: 80%; } + } + + .gauge { + .arc.outer { + stroke-width: 2px; + } + } +} + +.epoch.gauge-small { + width: 180px; height: 135px; + + .gauge-labels { + .value { font-size: 120%; } + } + + .gauge { + .arc.outer { + stroke-width: 3px; + } + } +} + +.epoch.gauge-medium { + width: 240px; height: 180px; + + .gauge { + .arc.outer { + stroke-width: 3px; + } + } +} + +.epoch.gauge-large { + width: 320px; height: 240px; + .gauge-labels { + .value { font-size: 180%; } + } +} + +.epoch .gauge { + .arc.outer { + stroke-width: 4px; + stroke: #666; + } + .arc.inner { + stroke-width: 1px; + stroke: #555; + } + .tick { + stroke-width: 1px; + stroke: #555; + } + + .needle { + fill: orange; + } + + .needle-base { + fill: #666; + } +} + +// Categorical Colors +$category10: + #1f77b4, #ff7f0e, #2ca02c, #d62728, #9467bd, + #8c564b, #e377c2, #7f7f7f, #bcbd22, #17becf; + +.epoch, .epoch.category10 { + @include epoch-category-colors($category10, 10); +} + +$category20: + #1f77b4, #aec7e8, #ff7f0e, #ffbb78, #2ca02c, + #98df8a, #d62728, #ff9896, #9467bd, #c5b0d5, + #8c564b, #c49c94, #e377c2, #f7b6d2, #7f7f7f, + #c7c7c7, #bcbd22, #dbdb8d, #17becf, #9edae5; + +.epoch.category20 { + @include epoch-category-colors($category20, 20); +} + +$category20b: + #393b79, #5254a3, #6b6ecf, #9c9ede, #637939, + #8ca252, #b5cf6b, #cedb9c, #8c6d31, #bd9e39, + #e7ba52, #e7cb94, #843c39, #ad494a, #d6616b, + #e7969c, #7b4173, #a55194, #ce6dbd, #de9ed6; + +.epoch.category20b { + @include epoch-category-colors($category20b, 20); +} + +$category20c: + #3182bd, #6baed6, #9ecae1, #c6dbef, + #e6550d, #fd8d3c, #fdae6b, #fdd0a2, + #31a354, #74c476, #a1d99b, #c7e9c0, + #756bb1, #9e9ac8, #bcbddc, #dadaeb, + #636363, #969696, #bdbdbd, #d9d9d9; + +.epoch.category20c { + @include epoch-category-colors($category20c, 20); +} + + +/* + * Heatmap Colors + * + * The heatmap real-time graph uses color blending to choose the color for which to render each bucket. + * Basic d3 categorical colors do not work well in this instance because the colors in the sequence + * vary wildly in precieved luminosity. + * + * Below we define small subsets that should work well together because they are of similar luminosities. + * Note: darker colors work better since we use opacity to denote "height" or "intensity" for each bucket + * after picking a mix color. + */ +$heatmap5: + #1f77b4, #2ca02c, #d62728, #8c564b, #7f7f7f; + +.epoch, .epoch.heatmap5 { + @include epoch-heatmap-colors($heatmap5, 5); +} diff --git a/debian/missing-sources/epoch/src/adapters.coffee b/debian/missing-sources/epoch/src/adapters.coffee new file mode 100644 index 0000000..e378af5 --- /dev/null +++ b/debian/missing-sources/epoch/src/adapters.coffee @@ -0,0 +1,13 @@ +# Maps short string names to classes for library adapters. +Epoch._typeMap = + 'area': Epoch.Chart.Area + 'bar': Epoch.Chart.Bar + 'line': Epoch.Chart.Line + 'pie': Epoch.Chart.Pie + 'scatter': Epoch.Chart.Scatter + 'histogram': Epoch.Chart.Histogram + 'time.area': Epoch.Time.Area + 'time.bar': Epoch.Time.Bar + 'time.line': Epoch.Time.Line + 'time.gauge': Epoch.Time.Gauge + 'time.heatmap': Epoch.Time.Heatmap diff --git a/debian/missing-sources/epoch/src/adapters/MooTools.coffee b/debian/missing-sources/epoch/src/adapters/MooTools.coffee new file mode 100644 index 0000000..8b3d544 --- /dev/null +++ b/debian/missing-sources/epoch/src/adapters/MooTools.coffee @@ -0,0 +1,19 @@ +MooToolsModule = -> + # Data key to use for storing a reference to the chart instance on an element. + DATA_NAME = 'epoch-chart' + + # Adds an Epoch chart of the given type to the referenced element. + # @param [Object] options Options for the chart. + # @option options [String] type The type of chart to append to the referenced element. + # @return [Object] The chart instance that was associated with the containing element. + Element.implement 'epoch', (options) -> + self = $$(this) + unless (chart = self.retrieve(DATA_NAME)[0])? + options.el = this + klass = Epoch._typeMap[options.type] + unless klass? + Epoch.exception "Unknown chart type '#{options.type}'" + self.store DATA_NAME, (chart = new klass options) + return chart + +MooToolsModule() if window.MooTools? diff --git a/debian/missing-sources/epoch/src/adapters/jQuery.coffee b/debian/missing-sources/epoch/src/adapters/jQuery.coffee new file mode 100644 index 0000000..e115bad --- /dev/null +++ b/debian/missing-sources/epoch/src/adapters/jQuery.coffee @@ -0,0 +1,18 @@ +jQueryModule = ($) -> + # Data key to use for storing a reference to the chart instance on an element. + DATA_NAME = 'epoch-chart' + + # Adds an Epoch chart of the given type to the referenced element. + # @param [Object] options Options for the chart. + # @option options [String] type The type of chart to append to the referenced element. + # @return [Object] The chart instance that was associated with the containing element. + $.fn.epoch = (options) -> + options.el = @get(0) + unless (chart = @data(DATA_NAME))? + klass = Epoch._typeMap[options.type] + unless klass? + Epoch.exception "Unknown chart type '#{options.type}'" + @data DATA_NAME, (chart = new klass options) + return chart + +jQueryModule(jQuery) if window.jQuery? diff --git a/debian/missing-sources/epoch/src/adapters/zepto.coffee b/debian/missing-sources/epoch/src/adapters/zepto.coffee new file mode 100644 index 0000000..81153dc --- /dev/null +++ b/debian/missing-sources/epoch/src/adapters/zepto.coffee @@ -0,0 +1,27 @@ +zeptoModule = ($) -> + # For mapping charts to selected elements + DATA_NAME = 'epoch-chart' + chartMap = {} + chartId = 0 + next_cid = -> "#{DATA_NAME}-#{++chartId}" + + # Adds an Epoch chart of the given type to the referenced element. + # @param [Object] options Options for the chart. + # @option options [String] type The type of chart to append to the referenced element. + # @return [Object] The chart instance that was associated with the containing element. + $.extend $.fn, + epoch: (options) -> + return chartMap[cid] if (cid = @data(DATA_NAME))? + options.el = @get(0) + + klass = Epoch._typeMap[options.type] + unless klass? + Epoch.exception "Unknown chart type '#{options.type}'" + + @data DATA_NAME, (cid = next_cid()) + chart = new klass options + chartMap[cid] = chart + + return chart + +zeptoModule(Zepto) if window.Zepto? diff --git a/debian/missing-sources/epoch/src/basic.coffee b/debian/missing-sources/epoch/src/basic.coffee new file mode 100644 index 0000000..8af3465 --- /dev/null +++ b/debian/missing-sources/epoch/src/basic.coffee @@ -0,0 +1,243 @@ +# Base class for all two-dimensional basic d3 charts. This class handles axes and +# margins so that subclasses can focus on the construction of particular chart +# types. +class Epoch.Chart.Plot extends Epoch.Chart.SVG + defaults = + domain: null, + range: null, + axes: ['left', 'bottom'] + ticks: + top: 14 + bottom: 14 + left: 5 + right: 5 + tickFormats: + top: Epoch.Formats.regular + bottom: Epoch.Formats.regular + left: Epoch.Formats.si + right: Epoch.Formats.si + + defaultAxisMargins = + top: 25 + right: 50 + bottom: 25 + left: 50 + + optionListeners = + 'option:margins.top': 'marginsChanged' + 'option:margins.right': 'marginsChanged' + 'option:margins.bottom': 'marginsChanged' + 'option:margins.left': 'marginsChanged' + 'option:axes': 'axesChanged' + 'option:ticks.top': 'ticksChanged' + 'option:ticks.right': 'ticksChanged' + 'option:ticks.bottom': 'ticksChanged' + 'option:ticks.left': 'ticksChanged' + 'option:tickFormats.top': 'tickFormatsChanged' + 'option:tickFormats.right': 'tickFormatsChanged' + 'option:tickFormats.bottom': 'tickFormatsChanged' + 'option:tickFormats.left': 'tickFormatsChanged' + 'option:domain': 'domainChanged' + 'option:range': 'rangeChanged' + + # Creates a new plot chart. + # @param [Object] options Options to use when constructing the plot. + # @option options [Object] margins For setting explicit values for the top, + # right, bottom, and left margins in the visualization. Normally these can + # be omitted and the class will set appropriately sized margins given which + # axes are specified. + # @option options [Array] axes A list of axes to display (top, left, bottom, right). + # @option options [Object] ticks Number of ticks to place on the top, left bottom + # and right axes. + # @option options [Object] tickFormats What tick formatting functions to use for + # the top, bottom, left, and right axes. + constructor: (@options={}) -> + givenMargins = Epoch.Util.copy(@options.margins) or {} + super(@options = Epoch.Util.defaults(@options, defaults)) + + # Margins are used in a special way and only for making room for axes. + # However, a user may explicitly set margins in the options, so we need + # to determine if they did so, and zero out the ones they didn't if no + # axis is present. + @margins = {} + for pos in ['top', 'right', 'bottom', 'left'] + @margins[pos] = if @options.margins? and @options.margins[pos]? + @options.margins[pos] + else if @hasAxis(pos) + defaultAxisMargins[pos] + else + 6 + + # Add a translation for the top and left margins + @g = @svg.append("g") + .attr("transform", "translate(#{@margins.left}, #{@margins.top})") + + # Register option change events + @onAll optionListeners + + # Sets the tick formatting function to use on the given axis. + # @param [String] axis Name of the axis. + # @param [Function] fn Formatting function to use. + setTickFormat: (axis, fn) -> + @options.tickFormats[axis] = fn + + # @return [Boolean] true if the chart has an axis with a given name, false otherwise. + # @param [String] axis Name of axis to check. + hasAxis: (axis) -> + @options.axes.indexOf(axis) > -1 + + # @return [Number] Width of the visualization portion of the chart (width - margins). + innerWidth: -> + @width - (@margins.left + @margins.right) + + # @return [Number] Height of the visualization portion of the chart (height - margins). + innerHeight: -> + @height - (@margins.top + @margins.bottom) + + # @return [Function] The x scale for the visualization. + x: -> + domain = @options.domain ? @extent((d) -> d.x) + d3.scale.linear() + .domain(domain) + .range([0, @innerWidth()]) + + # @return [Function] The y scale for the visualization. + y: (givenDomain) -> + d3.scale.linear() + .domain(@_getScaleDomain(givenDomain)) + .range([@innerHeight(), 0]) + + # @return [Function] d3 axis to use for the bottom of the visualization. + bottomAxis: -> + d3.svg.axis().scale(@x()).orient('bottom') + .ticks(@options.ticks.bottom) + .tickFormat(@options.tickFormats.bottom) + + # @return [Function] d3 axis to use for the top of the visualization. + topAxis: -> + d3.svg.axis().scale(@x()).orient('top') + .ticks(@options.ticks.top) + .tickFormat(@options.tickFormats.top) + + # @return [Function] d3 axis to use on the left of the visualization. + leftAxis: -> + range = if @options.range then @options.range.left else null + d3.svg.axis().scale(@y(range)).orient('left') + .ticks(@options.ticks.left) + .tickFormat(@options.tickFormats.left) + + # @return [Function] d3 axis to use on the right of the visualization. + rightAxis: -> + range = if @options.range then @options.range.right else null + d3.svg.axis().scale(@y(range)).orient('right') + .ticks(@options.ticks.right) + .tickFormat(@options.tickFormats.right) + + # Renders the axes for the visualization (subclasses must implement specific + # drawing routines). + draw: -> + if @_axesDrawn + @_redrawAxes() + else + @_drawAxes() + super() + + # Redraws the axes for the visualization. + _redrawAxes: -> + if @hasAxis('bottom') + @g.selectAll('.x.axis.bottom').transition() + .duration(500) + .ease('linear') + .call(@bottomAxis()) + if @hasAxis('top') + @g.selectAll('.x.axis.top').transition() + .duration(500) + .ease('linear') + .call(@topAxis()) + if @hasAxis('left') + @g.selectAll('.y.axis.left').transition() + .duration(500) + .ease('linear') + .call(@leftAxis()) + if @hasAxis('right') + @g.selectAll('.y.axis.right').transition() + .duration(500) + .ease('linear') + .call(@rightAxis()) + + # Draws the initial axes for the visualization. + _drawAxes: -> + if @hasAxis('bottom') + @g.append("g") + .attr("class", "x axis bottom") + .attr("transform", "translate(0, #{@innerHeight()})") + .call(@bottomAxis()) + if @hasAxis('top') + @g.append("g") + .attr('class', 'x axis top') + .call(@topAxis()) + if @hasAxis('left') + @g.append("g") + .attr("class", "y axis left") + .call(@leftAxis()) + if @hasAxis('right') + @g.append('g') + .attr('class', 'y axis right') + .attr('transform', "translate(#{@innerWidth()}, 0)") + .call(@rightAxis()) + @_axesDrawn = true + + dimensionsChanged: -> + super() + @g.selectAll('.axis').remove() + @_axesDrawn = false + @draw() + + # Updates margins in response to a option:margin.* event. + marginsChanged: -> + return unless @options.margins? + for own pos, size of @options.margins + unless size? + @margins[pos] = 6 + else + @margins[pos] = size + + @g.transition() + .duration(750) + .attr("transform", "translate(#{@margins.left}, #{@margins.top})") + + @draw() + + # Updates axes in response to a option:axes event. + axesChanged: -> + # Remove default axis margins + for pos in ['top', 'right', 'bottom', 'left'] + continue if @options.margins? and @options.margins[pos]? + if @hasAxis(pos) + @margins[pos] = defaultAxisMargins[pos] + else + @margins[pos] = 6 + + # Update the margin offset + @g.transition() + .duration(750) + .attr("transform", "translate(#{@margins.left}, #{@margins.top})") + + # Remove the axes and redraw + @g.selectAll('.axis').remove() + @_axesDrawn = false + @draw() + + # Updates ticks in response to a option:ticks.* event. + ticksChanged: -> @draw() + + # Updates tick formats in response to a option:tickFormats.* event. + tickFormatsChanged: -> @draw() + + # Updates chart in response to a option:domain event. + domainChanged: -> @draw() + + # Updates chart in response to a option:range event. + rangeChanged: -> @draw() + +# "They will see us waving from such great heights, come down now..." - The Postal Service diff --git a/debian/missing-sources/epoch/src/basic/area.coffee b/debian/missing-sources/epoch/src/basic/area.coffee new file mode 100644 index 0000000..e44b480 --- /dev/null +++ b/debian/missing-sources/epoch/src/basic/area.coffee @@ -0,0 +1,51 @@ + +# Static stacked area chart implementation using d3. +class Epoch.Chart.Area extends Epoch.Chart.Plot + constructor: (@options={}) -> + @options.type ?= 'area' + super(@options) + @draw() + + # Generates a scale needed to appropriately render the stacked visualization. + # @return [Function] The y scale for the visualization. + y: -> + a = [] + for layer in @getVisibleLayers() + for own k, v of layer.values + a[k] += v.y if a[k]? + a[k] = v.y unless a[k]? + d3.scale.linear() + .domain(@options.range ? [0, d3.max(a)]) + .range([@height - @margins.top - @margins.bottom, 0]) + + # Renders the SVG elements needed to display the stacked area chart. + draw: -> + [x, y, layers] = [@x(), @y(), @getVisibleLayers()] + + @g.selectAll('.layer').remove() + return if layers.length == 0 + + area = d3.svg.area() + .x((d) -> x(d.x)) + .y0((d) -> y(d.y0)) + .y1((d) -> y(d.y0 + d.y)) + + stack = d3.layout.stack() + .values((d) -> d.values) + + data = stack layers + + layer = @g.selectAll('.layer') + .data(layers, (d) -> d.category) + + layer.select('.area') + .attr('d', (d) -> area(d.values)) + + layer.enter().append('g') + .attr('class', (d) -> d.className) + + layer.append('path') + .attr('class', 'area') + .attr('d', (d) -> area(d.values)) + + super() diff --git a/debian/missing-sources/epoch/src/basic/bar.coffee b/debian/missing-sources/epoch/src/basic/bar.coffee new file mode 100644 index 0000000..8fbc427 --- /dev/null +++ b/debian/missing-sources/epoch/src/basic/bar.coffee @@ -0,0 +1,273 @@ +# Static bar chart implementation (using d3). +class Epoch.Chart.Bar extends Epoch.Chart.Plot + defaults = + type: 'bar' + style: 'grouped' + orientation: 'vertical' + padding: + bar: 0.08 + group: 0.1 + outerPadding: + bar: 0.08 + group: 0.1 + + horizontal_specific = + tickFormats: + top: Epoch.Formats.si + bottom: Epoch.Formats.si + left: Epoch.Formats.regular + right: Epoch.Formats.regular + + horizontal_defaults = Epoch.Util.defaults(horizontal_specific, defaults) + + optionListeners = + 'option:orientation': 'orientationChanged' + 'option:padding': 'paddingChanged' + 'option:outerPadding': 'paddingChanged' + 'option:padding:bar': 'paddingChanged' + 'option:padding:group': 'paddingChanged' + 'option:outerPadding:bar': 'paddingChanged' + 'option:outerPadding:group': 'paddingChanged' + + constructor: (@options={}) -> + if @_isHorizontal() + @options = Epoch.Util.defaults(@options, horizontal_defaults) + else + @options = Epoch.Util.defaults(@options, defaults) + super(@options) + @onAll optionListeners + @draw() + + # @return [Boolean] True if the chart is vertical, false otherwise + _isVertical: -> + @options.orientation == 'vertical' + + # @return [Boolean] True if the chart is horizontal, false otherwise + _isHorizontal: -> + @options.orientation == 'horizontal' + + # @return [Function] The scale used to generate the chart's x scale. + x: -> + if @_isVertical() + d3.scale.ordinal() + .domain(Epoch.Util.domain(@getVisibleLayers())) + .rangeRoundBands([0, @innerWidth()], @options.padding.group, @options.outerPadding.group) + else + extent = @extent((d) -> d.y) + extent[0] = Math.min(0, extent[0]) + d3.scale.linear() + .domain(extent) + .range([0, @width - @margins.left - @margins.right]) + + # @return [Function] The x scale used to render the horizontal bar chart. + x1: (x0) -> + d3.scale.ordinal() + .domain((layer.category for layer in @getVisibleLayers())) + .rangeRoundBands([0, x0.rangeBand()], @options.padding.bar, @options.outerPadding.bar) + + # @return [Function] The y scale used to render the bar chart. + y: -> + if @_isVertical() + extent = @extent((d) -> d.y) + extent[0] = Math.min(0, extent[0]) + d3.scale.linear() + .domain(extent) + .range([@height - @margins.top - @margins.bottom, 0]) + else + d3.scale.ordinal() + .domain(Epoch.Util.domain(@getVisibleLayers())) + .rangeRoundBands([0, @innerHeight()], @options.padding.group, @options.outerPadding.group) + + # @return [Function] The x scale used to render the vertical bar chart. + y1: (y0) -> + d3.scale.ordinal() + .domain((layer.category for layer in @getVisibleLayers())) + .rangeRoundBands([0, y0.rangeBand()], @options.padding.bar, @options.outerPadding.bar) + + # Remaps the bar chart data into a form that is easier to display. + # @return [Array] The reorganized data. + _remapData: -> + map = {} + for layer in @getVisibleLayers() + className = 'bar ' + layer.className.replace(/\s*layer\s*/, '') + for entry in layer.values + map[entry.x] ?= [] + map[entry.x].push { label: layer.category, y: entry.y, className: className } + ({group: k, values: v} for own k, v of map) + + # Draws the bar char. + draw: -> + if @_isVertical() + @_drawVertical() + else + @_drawHorizontal() + super() + + # Draws the bar chart with a vertical orientation + _drawVertical: -> + [x0, y] = [@x(), @y()] + x1 = @x1(x0) + height = @height - @margins.top - @margins.bottom + data = @_remapData() + + # 1) Join + layer = @g.selectAll(".layer") + .data(data, (d) -> d.group) + + # 2) Update + layer.transition().duration(750) + .attr("transform", (d) -> "translate(#{x0(d.group)}, 0)") + + # 3) Enter / Create + layer.enter().append("g") + .attr('class', 'layer') + .attr("transform", (d) -> "translate(#{x0(d.group)}, 0)") + + rects = layer.selectAll('rect') + .data((group) -> group.values) + + rects.attr('class', (d) -> d.className) + + rects.transition().duration(600) + .attr('x', (d) -> x1(d.label)) + .attr('y', (d) -> y(d.y)) + .attr('width', x1.rangeBand()) + .attr('height', (d) -> height - y(d.y)) + + rects.enter().append('rect') + .attr('class', (d) -> d.className) + .attr('x', (d) -> x1(d.label)) + .attr('y', (d) -> y(d.y)) + .attr('width', x1.rangeBand()) + .attr('height', (d) -> height - y(d.y)) + + rects.exit().transition() + .duration(150) + .style('opacity', '0') + .remove() + + # 4) Update new and existing + + # 5) Exit / Remove + layer.exit() + .transition() + .duration(750) + .style('opacity', '0') + .remove() + + # Draws the bar chart with a horizontal orientation + _drawHorizontal: -> + [x, y0] = [@x(), @y()] + y1 = @y1(y0) + width = @width - @margins.left - @margins.right + data = @_remapData() + + # 1) Join + layer = @g.selectAll(".layer") + .data(data, (d) -> d.group) + + # 2) Update + layer.transition().duration(750) + .attr("transform", (d) -> "translate(0, #{y0(d.group)})") + + # 3) Enter / Create + layer.enter().append("g") + .attr('class', 'layer') + .attr("transform", (d) -> "translate(0, #{y0(d.group)})") + + rects = layer.selectAll('rect') + .data((group) -> group.values) + + rects.attr('class', (d) -> d.className) + + rects.transition().duration(600) + .attr('x', (d) -> 0) + .attr('y', (d) -> y1(d.label)) + .attr('height', y1.rangeBand()) + .attr('width', (d) -> x(d.y)) + + rects.enter().append('rect') + .attr('class', (d) -> d.className) + .attr('x', (d) -> 0) + .attr('y', (d) -> y1(d.label)) + .attr('height', y1.rangeBand()) + .attr('width', (d) -> x(d.y)) + + rects.exit().transition() + .duration(150) + .style('opacity', '0') + .remove() + + # 4) Update new and existing + + # 5) Exit / Remove + layer.exit() + .transition() + .duration(750) + .style('opacity', '0') + .remove() + + # Generates specific tick marks to emulate d3's linear scale axis ticks + # for ordinal scales. Note: this should only be called if the user has + # defined a set number of ticks for a given axis. + # @param [Number] numTicks Number of ticks to generate + # @param [String] dataKey Property name of a datum to use for the tick value + # @return [Array] The ticks for the given axis + _getTickValues: (numTicks, dataKey='x') -> + return [] unless @data[0]? + total = @data[0].values.length + step = Math.ceil(total / numTicks)|0 + tickValues = (@data[0].values[i].x for i in [0...total] by step) + + # @return [Function] d3 axis to use for the bottom of the visualization. + bottomAxis: -> + axis = d3.svg.axis().scale(@x()).orient('bottom') + .ticks(@options.ticks.bottom) + .tickFormat(@options.tickFormats.bottom) + if @_isVertical() and @options.ticks.bottom? + axis.tickValues @_getTickValues(@options.ticks.bottom) + axis + + # @return [Function] d3 axis to use for the top of the visualization. + topAxis: -> + axis = d3.svg.axis().scale(@x()).orient('top') + .ticks(@options.ticks.top) + .tickFormat(@options.tickFormats.top) + if @_isVertical() and @options.ticks.top? + axis.tickValues @_getTickValues(@options.ticks.top) + axis + + # @return [Function] d3 axis to use on the left of the visualization. + leftAxis: -> + axis = d3.svg.axis().scale(@y()).orient('left') + .ticks(@options.ticks.left) + .tickFormat(@options.tickFormats.left) + if @_isHorizontal() and @options.ticks.left? + axis.tickValues @_getTickValues(@options.ticks.left) + axis + + # @return [Function] d3 axis to use on the right of the visualization. + rightAxis: -> + axis = d3.svg.axis().scale(@y()).orient('right') + .ticks(@options.ticks.right) + .tickFormat(@options.tickFormats.right) + if @_isHorizontal() and @options.ticks.right? + axis.tickValues @_getTickValues(@options.ticks.right) + axis + + # Updates orientation in response option:orientation. + orientationChanged: -> + top = @options.tickFormats.top + bottom = @options.tickFormats.bottom + left = @options.tickFormats.left + right = @options.tickFormats.right + + @options.tickFormats.left = top + @options.tickFormats.right = bottom + @options.tickFormats.top = left + @options.tickFormats.bottom = right + + @draw() + + # Updates padding in response to option:padding:* and option:outerPadding:*. + paddingChanged: -> @draw() diff --git a/debian/missing-sources/epoch/src/basic/histogram.coffee b/debian/missing-sources/epoch/src/basic/histogram.coffee new file mode 100644 index 0000000..4548e67 --- /dev/null +++ b/debian/missing-sources/epoch/src/basic/histogram.coffee @@ -0,0 +1,61 @@ +class Epoch.Chart.Histogram extends Epoch.Chart.Bar + defaults = + type: 'histogram' + domain: [0, 100] + bucketRange: [0, 100] + buckets: 10 + cutOutliers: false + + optionListeners = + 'option:bucketRange': 'bucketRangeChanged' + 'option:buckets': 'bucketsChanged' + 'option:cutOutliers': 'cutOutliersChanged' + + constructor: (@options={}) -> + super(@options = Epoch.Util.defaults(@options, defaults)) + @onAll optionListeners + @draw() + + # Prepares data by sorting it into histogram buckets as instructed by the chart options. + # @param [Array] data Data to prepare for rendering. + # @return [Array] The data prepared to be displayed as a histogram. + _prepareData: (data) -> + bucketSize = (@options.bucketRange[1] - @options.bucketRange[0]) / @options.buckets + + prepared = [] + for layer in data + buckets = (0 for i in [0...@options.buckets]) + for point in layer.values + index = parseInt((point.x - @options.bucketRange[0]) / bucketSize) + + if @options.cutOutliers and ((index < 0) or (index >= @options.buckets)) + continue + if index < 0 + index = 0 + else if index >= @options.buckets + index = @options.buckets - 1 + + buckets[index] += parseInt point.y + + preparedLayer = { values: (buckets.map (d, i) -> {x: parseInt(i) * bucketSize, y: d}) } + for own k, v of layer + preparedLayer[k] = v unless k == 'values' + + prepared.push preparedLayer + + return prepared + + # Called when options change, this prepares the raw data for the chart according to the new + # options, sets it, and renders the chart. + resetData: -> + @setData @rawData + @draw() + + # Updates the chart in response to an option:bucketRange event. + bucketRangeChanged: -> @resetData() + + # Updates the chart in response to an option:buckets event. + bucketsChanged: -> @resetData() + + # Updates the chart in response to an option:cutOutliers event. + cutOutliersChanged: -> @resetData() diff --git a/debian/missing-sources/epoch/src/basic/line.coffee b/debian/missing-sources/epoch/src/basic/line.coffee new file mode 100644 index 0000000..de56780 --- /dev/null +++ b/debian/missing-sources/epoch/src/basic/line.coffee @@ -0,0 +1,46 @@ +# Static line chart implementation (using d3). +class Epoch.Chart.Line extends Epoch.Chart.Plot + constructor: (@options={}) -> + @options.type ?= 'line' + super(@options) + @draw() + + # @return [Function] The line generator used to construct the plot. + line: (layer) -> + [x, y] = [@x(), @y(layer.range)] + d3.svg.line() + .x((d) -> x(d.x)) + .y((d) -> y(d.y)) + + # Draws the line chart. + draw: -> + [x, y, layers] = [@x(), @y(), @getVisibleLayers()] + + # Zero visible layers, just drop all and get out + if layers.length == 0 + return @g.selectAll('.layer').remove() + + # 1) Join + layer = @g.selectAll('.layer') + .data(layers, (d) -> d.category) + + # 2) Update (only existing) + layer.select('.line').transition().duration(500) + .attr('d', (l) => @line(l)(l.values)) + + # 3) Enter (Create) + layer.enter().append('g') + .attr('class', (l) -> l.className) + .append('path') + .attr('class', 'line') + .attr('d', (l) => @line(l)(l.values)) + + # 4) Update (existing & new) + # Nuuupp + + # 5) Exit (Remove) + layer.exit().transition().duration(750) + .style('opacity', '0') + .remove() + + super() diff --git a/debian/missing-sources/epoch/src/basic/pie.coffee b/debian/missing-sources/epoch/src/basic/pie.coffee new file mode 100644 index 0000000..e794a2f --- /dev/null +++ b/debian/missing-sources/epoch/src/basic/pie.coffee @@ -0,0 +1,59 @@ + +# Static Pie Chart implementation (using d3). +class Epoch.Chart.Pie extends Epoch.Chart.SVG + defaults = + type: 'pie' + margin: 10 + inner: 0 + + # Creates a new pie chart. + # @param [Object] options Options for the pie chart. + # @option options [Number] margin Margins to add around the pie chart (default: 10). + # @option options [Number] inner The inner radius for the chart (default: 0). + constructor: (@options={}) -> + super(@options = Epoch.Util.defaults(@options, defaults)) + @pie = d3.layout.pie().sort(null) + .value (d) -> d.value + @arc = d3.svg.arc() + .outerRadius(=> (Math.max(@width, @height) / 2) - @options.margin) + .innerRadius(=> @options.inner) + @g = @svg.append('g') + .attr("transform", "translate(#{@width/2}, #{@height/2})") + @on 'option:margin', 'marginChanged' + @on 'option:inner', 'innerChanged' + @draw() + + # Draws the pie chart + draw: -> + @g.selectAll('.arc').remove() + + arcs = @g.selectAll(".arc") + .data(@pie(@getVisibleLayers()), (d) -> d.data.category) + + arcs.enter().append('g') + .attr('class', (d) -> "arc pie " + d.data.className) + + arcs.select('path') + .attr('d', @arc) + + arcs.select('text') + .attr("transform", (d) => "translate(#{@arc.centroid(d)})") + .text((d) -> d.data.label or d.data.category) + + path = arcs.append("path") + .attr("d", @arc) + .each((d) -> @._current = d) + + text = arcs.append("text") + .attr("transform", (d) => "translate(#{@arc.centroid(d)})") + .attr("dy", ".35em") + .style("text-anchor", "middle") + .text((d) -> d.data.label or d.data.category) + + super() + + # Updates margins in response to an option:margin event. + marginChanged: -> @draw() + + # Updates inner margin in response to an option:inner event. + innerChanged: -> @draw() diff --git a/debian/missing-sources/epoch/src/basic/scatter.coffee b/debian/missing-sources/epoch/src/basic/scatter.coffee new file mode 100644 index 0000000..b8563ec --- /dev/null +++ b/debian/missing-sources/epoch/src/basic/scatter.coffee @@ -0,0 +1,59 @@ + +# Static scatter plot implementation (using d3). +class Epoch.Chart.Scatter extends Epoch.Chart.Plot + defaults = + type: 'scatter' + radius: 3.5 + axes: ['top', 'bottom', 'left', 'right'] + + # Creates a new scatter plot. + # @param [Object] options Options for the plot. + # @option options [Number] radius The default radius to use for the points in + # the plot (default 3.5). This can be overrwitten by individual points. + constructor: (@options={}) -> + super(@options = Epoch.Util.defaults(@options, defaults)) + @on 'option:radius', 'radiusChanged' + @draw() + + # Draws the scatter plot. + draw: -> + [x, y, layers] = [@x(), @y(), @getVisibleLayers()] + radius = @options.radius + + if layers.length == 0 + return @g.selectAll('.layer').remove() + + layer = @g.selectAll('.layer') + .data(layers, (d) -> d.category) + + layer.enter().append('g') + .attr('class', (d) -> d.className) + + dots = layer.selectAll('.dot') + .data((l) -> l.values) + + dots.transition().duration(500) + .attr("r", (d) -> d.r ? radius) + .attr("cx", (d) -> x(d.x)) + .attr("cy", (d) -> y(d.y)) + + dots.enter().append('circle') + .attr('class', 'dot') + .attr("r", (d) -> d.r ? radius) + .attr("cx", (d) -> x(d.x)) + .attr("cy", (d) -> y(d.y)) + + dots.exit().transition() + .duration(750) + .style('opacity', 0) + .remove() + + layer.exit().transition() + .duration(750) + .style('opacity', 0) + .remove() + + super() + + # Updates radius in response to an option:radius event. + radiusChanged: -> @draw() diff --git a/debian/missing-sources/epoch/src/core/chart.coffee b/debian/missing-sources/epoch/src/core/chart.coffee new file mode 100644 index 0000000..068335a --- /dev/null +++ b/debian/missing-sources/epoch/src/core/chart.coffee @@ -0,0 +1,361 @@ +# The base class for all charts in Epoch. Defines chart dimensions, keeps a reference +# of the chart's containing elements. And defines core method for handling data and +# drawing. +class Epoch.Chart.Base extends Epoch.Events + defaults = + width: 320 + height: 240 + dataFormat: null + + optionListeners = + 'option:width': 'dimensionsChanged' + 'option:height': 'dimensionsChanged' + 'layer:shown': 'layerChanged' + 'layer:hidden': 'layerChanged' + + # Creates a new base chart. + # @param [Object] options Options to set for this chart. + # @option options [Integer] width Sets an explicit width for the visualization. + # @option options [Integer] height Sets an explicit height for the visualization. + # @option options [Object, String] dataFormat Specific data format for the chart. + # @option options [Object] model Data model for the chart. + constructor: (@options={}) -> + super() + + if @options.model + if @options.model.hasData()? + @setData(@options.model.getData(@options.type, @options.dataFormat)) + else + @setData(@options.data or []) + @options.model.on 'data:updated', => @setDataFromModel() + else + @setData(@options.data or []) + + if @options.el? + @el = d3.select(@options.el) + + @width = @options.width + @height = @options.height + + if @el? + @width = @el.width() unless @width? + @height = @el.height() unless @height? + else + @width = defaults.width unless @width? + @height = defaults.height unless @height? + @el = d3.select(document.createElement('DIV')) + .attr('width', @width) + .attr('height', @height) + + @onAll optionListeners + + # @return [Object] A copy of this charts options. + _getAllOptions: -> + Epoch.Util.defaults({}, @options) + + # Chart option accessor. + # @param key Name of the option to fetch. Can be hierarchical, e.g. 'margins.left' + # @return The requested option if found, undefined otherwise. + _getOption: (key) -> + parts = key.split('.') + scope = @options + while parts.length and scope? + subkey = parts.shift() + scope = scope[subkey] + scope + + # Chart option mutator. + # @param key Name of the option to fetch. Can be hierarchical, e.g. 'margins.top' + # @param value Value to set for the option. + # @event option:`key` Triggers an option event with the given key being set. + _setOption: (key, value) -> + parts = key.split('.') + scope = @options + while parts.length + subkey = parts.shift() + if parts.length == 0 + scope[subkey] = arguments[1] + @trigger "option:#{arguments[0]}" + return + unless scope[subkey]? + scope[subkey] = {} + scope = scope[subkey] + + # Sets all options given an object of mixed hierarchical keys and nested objects. + # @param [Object] options Options to set. + # @event option:* Triggers an option event for each key that was set + _setManyOptions: (options, prefix='') -> + for own key, value of options + if Epoch.isObject(value) + @_setManyOptions value, "#{prefix + key}." + else + @_setOption prefix + key, value + + # General accessor / mutator for chart options. + # + # @overload option() + # Fetches chart options. + # @return a copy of this chart's options. + # + # @overload option(name) + # Fetches the value the option with the given name. + # @param [String] name Name of the option to fetch. Can be hierarchical, e.g. 'margins.left' + # @return The requested option if found, undefined otherwise. + # + # @overload option(name, value) + # Sets an option and triggers the associated event. + # @param [String] name Name of the option to fetch. Can be hierarchical, e.g. 'margins.top' + # @param value Value to set for the option. + # @event option:`name` Triggers an option event with the given key being set. + # + # @overload option(options) + # Sets multiple options at once. + # @param [Object] options Options to set for the chart. + # @event option:* Triggers an option event for each key that was set. + option: -> + if arguments.length == 0 + @_getAllOptions() + else if arguments.length == 1 and Epoch.isString(arguments[0]) + @_getOption arguments[0] + else if arguments.length == 2 and Epoch.isString(arguments[0]) + @_setOption arguments[0], arguments[1] + else if arguments.length == 1 and Epoch.isObject(arguments[0]) + @_setManyOptions arguments[0] + + # Retrieves and sets data from the chart's model + setDataFromModel: -> + prepared = @_prepareData @options.model.getData(@options.type, @options.dataFormat) + @data = @_annotateLayers(prepared) + @draw() + + # Set the initial data for the chart. + # @param data Data to initially set for the given chart. The data format can vary + # from chart to chart. The base class assumes that the data provided will be an + # array of layers. + setData: (data, options={}) -> + prepared = @_prepareData (@rawData = @_formatData(data)) + @data = @_annotateLayers(prepared) + + # Performs post formatted data preparation. + # @param data Data to prepare before setting. + # @return The prepared data. + _prepareData: (data) -> data + + # Performs data formatting before setting the charts data + # @param data Data to be formatted. + # @return The chart specific formatted data. + _formatData: (data) -> + Epoch.Data.formatData(data, @options.type, @options.dataFormat) + + # Annotates data to add class names, categories, and initial visibility states + _annotateLayers: (data) -> + category = 1 + for layer in data + classes = ['layer'] + classes.push "category#{category}" + layer.category = category + layer.visible = true + classes.push(Epoch.Util.dasherize layer.label) if layer.label? + layer.className = classes.join(' ') + category++ + return data + + # Finds a layer in the chart's current data that has the given label or index. + # @param [String, Number] labelOrIndex The label or index of the layer to find. + _findLayer: (labelOrIndex) -> + layer = null + if Epoch.isString(labelOrIndex) + for l in @data + if l.label == labelOrIndex + layer = l + break + else if Epoch.isNumber(labelOrIndex) + index = parseInt(labelOrIndex) + layer = @data[index] unless index < 0 or index >= @data.length + return layer + + # Instructs the chart that a data layer should be displayed. + # @param [String, Number] labelOrIndex The label or index of the layer to show. + # @event 'layer:shown' If a layer that was previously hidden now became visible. + showLayer: (labelOrIndex) -> + return unless (layer = @_findLayer labelOrIndex) + return if layer.visible + layer.visible = true + @trigger 'layer:shown' + + # Instructs the chart that a data layer should not be displayed. + # @param [String, Number] labelOrIndex The label or index of the layer to hide. + # @event 'layer:hidden' If a layer that was visible was made hidden. + hideLayer: (labelOrIndex) -> + return unless (layer = @_findLayer labelOrIndex) + return unless layer.visible + layer.visible = false + @trigger 'layer:hidden' + + # Instructs the chart that a data layer's visibility should be toggled. + # @param [String, Number] labelOrIndex The label or index of the layer to toggle. + # @event 'layer:shown' If the layer was made visible + # @event 'layer:hidden' If the layer was made invisible + toggleLayer: (labelOrIndex) -> + return unless (layer = @_findLayer labelOrIndex) + layer.visible = !layer.visible + if layer.visible + @trigger 'layer:shown' + else + @trigger 'layer:hidden' + + # Determines whether or not a data layer is visible. + # @param [String, Number] labelOrIndex The label or index of the layer to toggle. + # @return true if the layer is visible, false otherwise. + isLayerVisible: (labelOrIndex) -> + return null unless (layer = @_findLayer labelOrIndex) + layer.visible + + # Calculates an array of layers in the charts data that are flagged as visible. + # @return [Array] The chart's visible layers. + getVisibleLayers: -> + return @data.filter((layer) -> layer.visible) + + # Updates the chart with new data. + # @param data Data to replace the current data for the chart. + # @param [Boolean] draw Whether or not to redraw the chart after the data has been set. + # Default: true. + update: (data, draw=true) -> + @setData data + @draw() if draw + + # Draws the chart. Triggers the 'draw' event, subclasses should call super() after drawing to + # ensure that the event is triggered. + # @abstract Must be overriden in child classes to perform chart specific drawing. + draw: -> @trigger 'draw' + + # Determines a resulting scale domain for the y axis given a domain + # @param [Mixed] givenDomain A set domain, a label associated with mutliple + # layers, or null. + # @return The domain for the y scale + _getScaleDomain: (givenDomain) -> + # Explicitly set given domain + if Array.isArray(givenDomain) + return givenDomain + + # Check for "labeled" layer ranges + if Epoch.isString(givenDomain) + layers = @getVisibleLayers() + .filter((l) -> l.range == givenDomain) + .map((l) -> l.values) + if layers? && layers.length + values = Epoch.Util.flatten(layers).map((d) -> d.y) + minFn = (memo, curr) -> if curr < memo then curr else memo + maxFn = (memo, curr) -> if curr > memo then curr else memo + return [values.reduce(minFn, values[0]), values.reduce(maxFn, values[0])] + + # Find the domain based on chart options + if Array.isArray(@options.range) + @options.range + else if @options.range && Array.isArray(@options.range.left) + @options.range.left + else if @options.range && Array.isArray(@options.range.right) + @options.range.right + else + @extent((d) -> d.y) + + # Calculates an extent throughout the layers based on the given comparator. + # @param [Function] cmp Comparator to use for performing the min and max for the extent + # calculation. + # @return [Array] an extent array with the first element as the minimum value in the + # chart's data set and the second element as the maximum. + extent: (cmp) -> + [ + d3.min(@getVisibleLayers(), (layer) -> d3.min(layer.values, cmp)), + d3.max(@getVisibleLayers(), (layer) -> d3.max(layer.values, cmp)) + ] + + # Updates the width and height members and container dimensions in response to an + # 'option:width' or 'option:height' event. + dimensionsChanged: -> + @width = @option('width') or @width + @height = @option('height') or @height + @el.width(@width) + @el.height(@height) + + # Updates the chart in response to a layer being shown or hidden + layerChanged: -> + @draw() + +# Base class for all SVG charts (via d3). +class Epoch.Chart.SVG extends Epoch.Chart.Base + # Initializes the chart and places the rendering SVG in the specified HTML + # containing element. + # @param [Object] options Options for the SVG chart. + # @option options [HTMLElement] el Container element for the chart. + # @option options [Array] data Layered data used to render the chart. + constructor: (@options={}) -> + super(@options) + if @el? + @svg = @el.append('svg') + else + @svg = d3.select(document.createElement('svg')) + @svg.attr + xmlns: 'http://www.w3.org/2000/svg', + width: @width, + height: @height + + # Resizes the svg element in response to a 'option:width' or 'option:height' event. + dimensionsChanged: -> + super() + @svg.attr('width', @width).attr('height', @height) + +# Base Class for all Canvas based charts. +class Epoch.Chart.Canvas extends Epoch.Chart.Base + # Initializes the chart and places the rendering canvas in the specified + # HTML container element. + # @param [Object] options Options for the SVG chart. + # @option options [HTMLElement] el Container element for the chart. + # @option options [Array] data Layered data used to render the chart. + constructor: (@options={}) -> + super(@options) + + if @options.pixelRatio? + @pixelRatio = @options.pixelRatio + else if window.devicePixelRatio? + @pixelRatio = window.devicePixelRatio + else + @pixelRatio = 1 + + @canvas = d3.select( document.createElement('CANVAS') ) + @canvas.style + 'width': "#{@width}px" + 'height': "#{@height}px" + + @canvas.attr + width: @getWidth() + height: @getHeight() + + @el.node().appendChild @canvas.node() if @el? + @ctx = Epoch.Util.getContext @canvas.node() + + # @return [Number] width of the canvas with respect to the pixel ratio of the display + getWidth: -> @width * @pixelRatio + + # @return [Number] height of the canvas with respect to the pixel ratio of the display + getHeight: -> @height * @pixelRatio + + # Clears the render canvas. + clear: -> + @ctx.clearRect(0, 0, @getWidth(), @getHeight()) + + # @return [Object] computed styles for the given selector in the context of this chart. + # @param [String] selector The selector used to compute the styles. + getStyles: (selector) -> + Epoch.QueryCSS.getStyles(selector, @el) + + # Resizes the canvas element when the dimensions of the container change + dimensionsChanged: -> + super() + @canvas.style {'width': "#{@width}px", 'height': "#{@height}px"} + @canvas.attr { width: @getWidth(), height: @getHeight() } + + # Purges QueryCSS cache and redraws the Canvas based chart. + redraw: -> + Epoch.QueryCSS.purge() + @draw() diff --git a/debian/missing-sources/epoch/src/core/context.coffee b/debian/missing-sources/epoch/src/core/context.coffee new file mode 100644 index 0000000..8566552 --- /dev/null +++ b/debian/missing-sources/epoch/src/core/context.coffee @@ -0,0 +1,25 @@ +# Rendering context used for unit testing. +class Epoch.TestContext + VOID_METHODS = [ + 'arc', 'arcTo', 'beginPath', 'bezierCurveTo', 'clearRect', + 'clip', 'closePath', 'drawImage', 'fill', 'fillRect', 'fillText', + 'moveTo', 'quadraticCurveTo', 'rect', 'restore', 'rotate', 'save', + 'scale', 'scrollPathIntoView', 'setLineDash', 'setTransform', + 'stroke', 'strokeRect', 'strokeText', 'transform', 'translate', 'lineTo' + ] + + # Creates a new test rendering context. + constructor: -> + @_log = [] + @_makeFauxMethod(method) for method in VOID_METHODS + + # Creates a fake method with the given name that logs the method called + # and arguments passed when executed. + # @param name Name of the fake method to create. + _makeFauxMethod: (name) -> + @[name] = -> @_log.push "#{name}(#{(arg.toString() for arg in arguments).join(',')})" + + # Faux method that emulates the "getImageData" method + getImageData: -> + @_log.push "getImageData(#{(arg.toString() for arg in arguments).join(',')})" + return { width: 0, height: 0, resolution: 1.0, data: [] } diff --git a/debian/missing-sources/epoch/src/core/css.coffee b/debian/missing-sources/epoch/src/core/css.coffee new file mode 100644 index 0000000..03a7310 --- /dev/null +++ b/debian/missing-sources/epoch/src/core/css.coffee @@ -0,0 +1,131 @@ +# Singelton class used to query CSS styles by way of reference elements. +# This allows canvas based visualizations to use the same styles as their +# SVG counterparts. +class QueryCSS + # Reference container id + REFERENCE_CONTAINER_ID = '_canvas_css_reference' + + # Container Hash Attribute + CONTAINER_HASH_ATTR = 'data-epoch-container-id' + + # Handles automatic container id generation + containerCount = 0 + nextContainerId = -> "epoch-container-#{containerCount++}" + + # Expression used to derive tag name, id, and class names from + # selectors given the the put method. + PUT_EXPR = /^([^#. ]+)?(#[^. ]+)?(\.[^# ]+)?$/ + + # Whether or not to log full selector lists + logging = false + + # Converts selectors into actual dom elements (replaces put.js) + # Limited the functionality to what Epoch actually needs to + # operate correctly. We detect class names, ids, and element + # tag names. + put = (selector) -> + match = selector.match(PUT_EXPR) + return Epoch.error('Query CSS cannot match given selector: ' + selector) unless match? + [whole, tag, id, classNames] = match + tag = (tag ? 'div').toUpperCase() + + element = document.createElement(tag) + element.id = id.substr(1) if id? + if classNames? + element.className = classNames.substr(1).replace(/\./g, ' ') + + return element + + # Lets the user set whether or not to log selector lists and resulting DOM trees. + # Useful for debugging QueryCSS itself. + @log: (b) -> + logging = b + + # Key-Value cache for computed styles that we found using this class. + @cache = {} + + # List of styles to pull from the full list of computed styles + @styleList = ['fill', 'stroke', 'stroke-width'] + + # The svg reference container + @container = null + + # Purges the selector to style cache + @purge: -> + QueryCSS.cache = {} + + # Gets the reference element container. + @getContainer: -> + return QueryCSS.container if QueryCSS.container? + container = document.createElement('DIV') + container.id = REFERENCE_CONTAINER_ID + document.body.appendChild(container) + QueryCSS.container = d3.select(container) + + # @return [String] A unique identifier for the given container and selector. + # @param [String] selector Selector from which to derive the styles + # @param container The containing element for a chart. + @hash: (selector, container) -> + containerId = container.attr(CONTAINER_HASH_ATTR) + unless containerId? + containerId = nextContainerId() + container.attr(CONTAINER_HASH_ATTR, containerId) + return "#{containerId}__#{selector}" + + # @return The computed styles for the given selector in the given container element. + # @param [String] selector Selector from which to derive the styles. + # @param container HTML containing element in which to place the reference SVG. + @getStyles: (selector, container) -> + # 0) Check for cached styles + cacheKey = QueryCSS.hash(selector, container) + cache = QueryCSS.cache[cacheKey] + return cache if cache? + + # 1) Build a full reference tree (parents, container, and selector elements) + parents = [] + parentNode = container.node().parentNode + + while parentNode? and parentNode.nodeName.toLowerCase() != 'body' + parents.unshift parentNode + parentNode = parentNode.parentNode + parents.push container.node() + + selectorList = [] + for element in parents + sel = element.nodeName.toLowerCase() + if element.id? and element.id.length > 0 + sel += '#' + element.id + if element.className? and element.className.length > 0 + sel += '.' + Epoch.Util.trim(element.className).replace(/\s+/g, '.') + selectorList.push sel + + selectorList.push('svg') + + for subSelector in Epoch.Util.trim(selector).split(/\s+/) + selectorList.push(subSelector) + + console.log(selectorList) if logging + + parent = root = put(selectorList.shift()) + while selectorList.length + el = put(selectorList.shift()) + parent.appendChild el + parent = el + + console.log(root) if logging + + # 2) Place the reference tree and fetch styles given the selector + QueryCSS.getContainer().node().appendChild(root) + + ref = d3.select('#' + REFERENCE_CONTAINER_ID + ' ' + selector) + styles = {} + for name in QueryCSS.styleList + styles[name] = ref.style(name) + QueryCSS.cache[cacheKey] = styles + + # 3) Cleanup and return the styles + QueryCSS.getContainer().html('') + return styles + + +Epoch.QueryCSS = QueryCSS \ No newline at end of file diff --git a/debian/missing-sources/epoch/src/core/d3.coffee b/debian/missing-sources/epoch/src/core/d3.coffee new file mode 100644 index 0000000..a33d717 --- /dev/null +++ b/debian/missing-sources/epoch/src/core/d3.coffee @@ -0,0 +1,25 @@ +# Gets the width of the first node, or sets the width of all the nodes +# in a d3 selection. +# @param value [Number, String] (optional) Width to set for all the nodes in the selection. +# @return The selection if setting the width of the nodes, or the width +# in pixels of the first node in the selection. +d3.selection::width = (value) -> + if value? and Epoch.isString(value) + @style('width', value) + else if value? and Epoch.isNumber(value) + @style('width', "#{value}px") + else + +Epoch.Util.getComputedStyle(@node(), null).width.replace('px', '') + +# Gets the height of the first node, or sets the height of all the nodes +# in a d3 selection. +# @param value (optional) Height to set for all the nodes in the selection. +# @return The selection if setting the height of the nodes, or the height +# in pixels of the first node in the selection. +d3.selection::height = (value) -> + if value? and Epoch.isString(value) + @style('height', value) + else if value? and Epoch.isNumber(value) + @style('height', "#{value}px") + else + +Epoch.Util.getComputedStyle(@node(), null).height.replace('px', '') \ No newline at end of file diff --git a/debian/missing-sources/epoch/src/core/format.coffee b/debian/missing-sources/epoch/src/core/format.coffee new file mode 100644 index 0000000..d65932f --- /dev/null +++ b/debian/missing-sources/epoch/src/core/format.coffee @@ -0,0 +1,15 @@ +# Tick formatter identity. +Epoch.Formats.regular = (d) -> d + +# Tick formatter that formats the numbers using standard SI postfixes. +Epoch.Formats.si = (d) -> Epoch.Util.formatSI(d) + +# Tick formatter for percentages. +Epoch.Formats.percent = (d) -> (d*100).toFixed(1) + "%" + +# Tick formatter for seconds from timestamp data. +Epoch.Formats.seconds = (t) -> d3Seconds(new Date(t*1000)) +d3Seconds = d3.time.format('%I:%M:%S %p') + +# Tick formatter for bytes +Epoch.Formats.bytes = (d) -> Epoch.Util.formatBytes(d) diff --git a/debian/missing-sources/epoch/src/core/util.coffee b/debian/missing-sources/epoch/src/core/util.coffee new file mode 100644 index 0000000..30995bf --- /dev/null +++ b/debian/missing-sources/epoch/src/core/util.coffee @@ -0,0 +1,236 @@ +typeFunction = (objectName) -> (v) -> + Object::toString.call(v) == "[object #{objectName}]" + +# @return [Boolean] true if the given value is an array, false otherwise. +# @param v Value to test. +Epoch.isArray = Array.isArray ? typeFunction('Array') + +# @return [Boolean] true if the given value is an object, false otherwise. +# @param v Value to test. +Epoch.isObject = typeFunction('Object') + +# @return [Boolean] true if the given value is a string, false otherwise. +# @param v Value to test. +Epoch.isString = typeFunction('String') + +# @return [Boolean] true if the given value is a function, false otherwise. +# @param v Value to test. +Epoch.isFunction = typeFunction('Function') + +# @return [Boolean] true if the given value is a number, false otherwise. +# @param v Value to test. +Epoch.isNumber = typeFunction('Number') + +# Attempts to determine if a given value represents a DOM element. The result is always correct if the +# browser implements DOM Level 2, but one can fool it on certain versions of IE. Adapted from: +# Stack Overflow #384286. +# @return [Boolean] true if the given value is a DOM element, false otherwise. +# @param v Value to test. +Epoch.isElement = (v) -> + if HTMLElement? + v instanceof HTMLElement + else + v? and Epoch.isObject(v) and v.nodeType == 1 and Epoch.isString(v.nodeName) + +# Determines if a given value is a non-empty array. +# @param v Value to test. +# @return [Boolean] true if the given value is an array with at least one element. +Epoch.isNonEmptyArray = (v) -> + Epoch.isArray(v) and v.length > 0 + +# Generates shallow copy of an object. +# @return A shallow copy of the given object. +# @param [Object] original Object for which to make the shallow copy. +Epoch.Util.copy = (original) -> + return null unless original? + copy = {} + copy[k] = v for own k, v of original + return copy + +# Creates a deep copy of the given options filling in missing defaults. +# @param [Object] options Options to copy. +# @param [Object] defaults Default values for the options. +Epoch.Util.defaults = (options, defaults) -> + result = Epoch.Util.copy(options) + for own k, v of defaults + opt = options[k] + def = defaults[k] + bothAreObjects = Epoch.isObject(opt) and Epoch.isObject(def) + + if opt? and def? + if bothAreObjects and not Epoch.isArray(opt) + result[k] = Epoch.Util.defaults(opt, def) + else + result[k] = opt + else if opt? + result[k] = opt + else + result[k] = def + + return result + +# Formats numbers with standard postfixes (e.g. K, M, G) +# @param [Number] v Value to format. +# @param [Integer] fixed Number of floating point digits to fix after conversion. +# @param [Boolean] fixIntegers Whether or not to add floating point digits to non-floating point results. +# @example Formatting a very large number +# Epoch.Util.formatSI(1120000) == "1.1 M" +Epoch.Util.formatSI = (v, fixed=1, fixIntegers=false) -> + if v < 1000 + q = v + q = q.toFixed(fixed) unless (q|0) == q and !fixIntegers + return q + + for own i, label of ['K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'] + base = Math.pow(10, ((i|0)+1)*3) + if v >= base and v < Math.pow(10, ((i|0)+2)*3) + q = v/base + q = q.toFixed(fixed) unless (q % 1) == 0 and !fixIntegers + return "#{q} #{label}" + +# Formats large bandwidth and disk space usage numbers with byte postfixes (e.g. KB, MB, GB, etc.) +# @param [Number] v Value to format. +# @param [Integer] fixed Number of floating point digits to fix after conversion. +# @param [Boolean] fixIntegers Whether or not to add floating point digits to non-floating point results. +# @example Formatting a large number of bytes +# Epoch.Util.formatBytes(5.21 * Math.pow(2, 20)) == "5.2 MB" +Epoch.Util.formatBytes = (v, fixed=1, fix_integers=false) -> + if v < 1024 + q = v + q = q.toFixed(fixed) unless (q % 1) == 0 and !fix_integers + return "#{q} B" + + for own i, label of ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] + base = Math.pow(1024, (i|0)+1) + if v >= base and v < Math.pow(1024, (i|0)+2) + q = v/base + q = q.toFixed(fixed) unless (q % 1) == 0 and !fix_integers + return "#{q} #{label}" + +# @return a "dasherized" css class names from a given string +# @example Using dasherize +# Epoch.Util.dasherize('My Awesome Name') == 'my-awesome-name' +Epoch.Util.dasherize = (str) -> + Epoch.Util.trim(str).replace("\n", '').replace(/\s+/g, '-').toLowerCase() + +# @return the full domain of a given variable from an array of layers +# @param [Array] layers Layered plot data. +# @param [String] key The key name of the value at on each entry in the layers. +Epoch.Util.domain = (layers, key='x') -> + set = {} + domain = [] + for layer in layers + for entry in layer.values + continue if set[entry[key]]? + domain.push(entry[key]) + set[entry[key]] = true + return domain + +# Strips whitespace from the beginning and end of a string. +# @param [String] string String to trim. +# @return [String] The string without leading or trailing whitespace. +# Returns null if the given parameter was not a string. +Epoch.Util.trim = (string) -> + return null unless Epoch.isString(string) + string.replace(/^\s+/g, '').replace(/\s+$/g, '') + +# Returns the computed styles of an element in the document +# @param [HTMLElement] Element for which to fetch the styles. +# @param [String] pseudoElement Pseudo selectors on which to search for the element. +# @return [Object] The styles for the given element. +Epoch.Util.getComputedStyle = (element, pseudoElement) -> + if Epoch.isFunction(window.getComputedStyle) + window.getComputedStyle(element, pseudoElement) + else if element.currentStyle? + element.currentStyle + +# Converts a CSS color string into an RGBA string with the given opacity +# @param [String] color Color string to convert into an rgba +# @param [Number] opacity Opacity to use for the resulting color. +# @return the resulting rgba color string. +Epoch.Util.toRGBA = (color, opacity) -> + if (parts = color.match /^rgba\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*[0-9\.]+\)/) + [all, r, g, b] = parts + result = "rgba(#{r},#{g},#{b},#{opacity})" + else if (v = d3.rgb color) + result = "rgba(#{v.r},#{v.g},#{v.b},#{opacity})" + return result + +# Obtains a graphics context for the given canvas node. Nice to have +# this abstracted out in case we want to support WebGL in the future. +# Also allows us to setup a special context when unit testing, as +# jsdom doesn't have canvas support, and node-canvas is a pain in the +# butt to install properly across different platforms. +Epoch.Util.getContext = (node, type='2d') -> + node.getContext(type) + +# Basic eventing base class for all Epoch classes. +class Epoch.Events + constructor: -> + @_events = {} + + # Registers a callback to a given event. + # @param [String] name Name of the event. + # @param [Function, String] callback Either a closure to call when the event fires + # or a string that denotes a method name to call on this object. + on: (name, callback) -> + return unless callback? + @_events[name] ?= [] + @_events[name].push callback + + # Registers a map of event names to given callbacks. This method calls .on + # directly for each of the events given. + # @param [Object] map A map of event names to callbacks. + onAll: (map) -> + return unless Epoch.isObject(map) + @on(name, callback) for own name, callback of map + + # Removes a specific callback listener or all listeners for a given event. + # @param [String] name Name of the event. + # @param [Function, String] callback (Optional) Callback to remove from the listener list. + # If this parameter is not provided then all listeners will be removed for the event. + off: (name, callback) -> + return unless Epoch.isArray(@_events[name]) + return delete(@_events[name]) unless callback? + while (i = @_events[name].indexOf(callback)) >= 0 + @_events[name].splice(i, 1) + + # Removes a set of callback listeners for all events given in the map or array of strings. + # This method calls .off directly for each event and callback to remove. + # @param [Object, Array] mapOrList Either a map that associates event names to specific callbacks + # or an array of event names for which to completely remove listeners. + offAll: (mapOrList) -> + if Epoch.isArray(mapOrList) + @off(name) for name in mapOrList + else if Epoch.isObject(mapOrList) + @off(name, callback) for own name, callback of mapOrList + + # Triggers an event causing all active listeners to be executed. + # @param [String] name Name of the event to fire. + trigger: (name) -> + return unless @_events[name]? + args = (arguments[i] for i in [1...arguments.length]) + for callback in @_events[name] + fn = null + if Epoch.isString(callback) + fn = @[callback] + else if Epoch.isFunction(callback) + fn = callback + unless fn? + Epoch.exception "Callback for event '#{name}' is not a function or reference to a method." + fn.apply @, args + +# Performs a single pass flatten on a multi-array +# @param [Array] multiarray A deep multi-array to flatten +# @returns [Array] A single pass flatten of the multi-array +Epoch.Util.flatten = (multiarray) -> + if !Array.isArray(multiarray) + throw new Error('Epoch.Util.flatten only accepts arrays') + result = [] + for array in multiarray + if Array.isArray(array) + for item in array + result.push item + else + result.push array + result diff --git a/debian/missing-sources/epoch/src/data.coffee b/debian/missing-sources/epoch/src/data.coffee new file mode 100644 index 0000000..7627fd4 --- /dev/null +++ b/debian/missing-sources/epoch/src/data.coffee @@ -0,0 +1,311 @@ +Epoch.Data ?= {} +Epoch.Data.Format ?= {} + +# Private Helper Function for data formats below +applyLayerLabel = (layer, options, i, keys=[]) -> + [labels, autoLabels, keyLabels] = [options.labels, options.autoLabels, options.keyLabels] + if labels? and Epoch.isArray(labels) and labels.length > i + layer.label = labels[i] + else if keyLabels and keys.length > i + layer.label = keys[i] + else if autoLabels + label = [] + while i >= 0 + label.push String.fromCharCode(65+(i%26)) + i -= 26 + layer.label = label.join('') + return layer + +# Formats a given input array for the chart of the specified type. Notes: +# +# * Basic pie charts require a flat array of numbers +# * Real-time histogram charts require sparse histogram objects +# +# @param data Data array to format (can be multidimensional to allow for multiple layers). +# @option options [String] type Type of chart for which to format the data. +# @option options [Function] x(d, i) Maps the data to x values given a data point and the index of the point. +# @option options [Function] y(d, i) Maps the data to y values given a data point and the index of the point. +# @option options [Function] time(d, i, startTime) Maps the data to time values for real-time plots given the point and index. +# @option options [Array] labels Labels to apply to each data layer. +# @option options [Boolean] autoLabels Apply labels of ascending capital letters to each layer if true. +# @option options [Number] startTime Unix timestamp used as the starting point for auto acsending times in +# real-time data formatting. +Epoch.Data.Format.array = (-> + defaultOptions = + x: (d, i) -> i + y: (d, i) -> d + time: (d, i, startTime) -> parseInt(startTime) + parseInt(i) + type: 'area' + autoLabels: false + labels: [] + startTime: parseInt(new Date().getTime() / 1000) + + buildLayers = (data, options, mapFn) -> + result = [] + if Epoch.isArray(data[0]) + for own i, series of data + result.push applyLayerLabel({values: series.map(mapFn)}, options, parseInt(i)) + else + result.push applyLayerLabel({values: data.map(mapFn)}, options, 0) + return result + + formatBasicPlot = (data, options) -> + buildLayers data, options, (d, i) -> + { x: options.x(d, i), y: options.y(d, i) } + + formatTimePlot = (data, options) -> + buildLayers data, options, (d, i) -> + { time: options.time(d, i, options.startTime), y: options.y(d, i) } + + formatHeatmap = (data, options) -> + buildLayers data, options, (d, i) -> + { time: options.time(d, i, options.startTime), histogram: d } + + formatPie = (data, options) -> + result = [] + for own i, v of data + return [] unless Epoch.isNumber(data[0]) + result.push applyLayerLabel({ value: v }, options, i) + return result + + format = (data=[], options={}) -> + return [] unless Epoch.isNonEmptyArray(data) + opt = Epoch.Util.defaults options, defaultOptions + + if opt.type == 'time.heatmap' + formatHeatmap data, opt + else if opt.type.match /^time\./ + formatTimePlot data, opt + else if opt.type == 'pie' + formatPie data, opt + else + formatBasicPlot data, opt + + format.entry = (datum, options={}) -> + if options.type == 'time.gauge' + return 0 unless datum? + opt = Epoch.Util.defaults options, defaultOptions + d = if Epoch.isArray(datum) then datum[0] else datum + return opt.y(d, 0) + + return [] unless datum? + unless options.startTime? + options.startTime = parseInt(new Date().getTime() / 1000) + + if Epoch.isArray(datum) + data = datum.map (d) -> [d] + else + data = [datum] + + (layer.values[0] for layer in format(data, options)) + + return format +)() + +# Formats an input array of tuples such that the first element of the tuple is set +# as the x-coordinate and the second element as the y-coordinate. Supports layers +# of tupled series. For real-time plots the first element of a tuple is set as the +# time component of the value. +# +# This formatter will return an empty array if the chart type option is +# set as 'time.heatmap', 'time.gauge', or 'pie'. +# +# @param data Data array to format (can be multidimensional to allow for multiple layers). +# @option options [String] type Type of chart for which to format the data. +# @option options [Function] x(d, i) Maps the data to x values given a data point and the index of the point. +# @option options [Function] y(d, i) Maps the data to y values given a data point and the index of the point. +# @option options [Function] time(d, i, startTime) Maps the data to time values for real-time plots given the point and index. +# @option options [Array] labels Labels to apply to each data layer. +# @option options [Boolean] autoLabels Apply labels of ascending capital letters to each layer if true. +Epoch.Data.Format.tuple = (-> + defaultOptions = + x: (d, i) -> d + y: (d, i) -> d + time: (d, i) -> d + type: 'area' + autoLabels: false + labels: [] + + buildLayers = (data, options, mapFn) -> + return [] unless Epoch.isArray(data[0]) + result = [] + if Epoch.isArray(data[0][0]) + for own i, series of data + result.push applyLayerLabel({values: series.map(mapFn)}, options, parseInt(i)) + else + result.push applyLayerLabel({values: data.map(mapFn)}, options, 0) + return result + + format = (data=[], options={}) -> + return [] unless Epoch.isNonEmptyArray(data) + opt = Epoch.Util.defaults options, defaultOptions + + if opt.type == 'pie' or opt.type == 'time.heatmap' or opt.type == 'time.gauge' + return [] + else if opt.type.match /^time\./ + buildLayers data, opt, (d, i) -> + {time: opt.time(d[0], parseInt(i)), y: opt.y(d[1], parseInt(i))} + else + buildLayers data, opt, (d, i) -> + {x: opt.x(d[0], parseInt(i)), y: opt.y(d[1], parseInt(i))} + + format.entry = (datum, options={}) -> + return [] unless datum? + unless options.startTime? + options.startTime = parseInt(new Date().getTime() / 1000) + + if Epoch.isArray(datum) and Epoch.isArray(datum[0]) + data = datum.map (d) -> [d] + else + data = [datum] + + (layer.values[0] for layer in format(data, options)) + + return format +)() + +# This formatter expects to be passed a flat array of objects and a list of keys. +# It then extracts the value for each key across each of the objects in the array +# to produce multi-layer plot data of the given chart type. Note that this formatter +# also can be passed an x or time option as a string that +# allows the programmer specify a key to use for the value of the first component +# (x or time) of each resulting layer value. +# +# Note that this format does not work with basic pie charts nor real-time gauge charts. +# +# @param [Array] data Flat array of objects to format. +# @param [Array] keys List of keys used to extract data from each of the objects. +# @option options [String] type Type of chart for which to format the data. +# @option options [Function, String] x Either the key to use for the x-componet of +# the resulting values or a function of the data at that point and index of the data. +# @option options [Function, String] time Either an object key or function to use for the +# time-component of resulting real-time plot values. +# @option options [Function] y(d, i) Maps the data to y values given a data point and the index of the point. +# @option options [Array] labels Labels to apply to each data layer. +# @option options [Boolean] autoLabels Apply labels of ascending capital letters to each layer if true. +# @option options [Boolean] keyLabels Apply labels using the keys passed to the formatter (defaults to true). +# @option options [Number] startTime Unix timestamp used as the starting point for auto acsending times in +# real-time data formatting. +Epoch.Data.Format.keyvalue = (-> + defaultOptions = + type: 'area', + x: (d, i) -> parseInt(i) + y: (d, i) -> d + time: (d, i, startTime) -> parseInt(startTime) + parseInt(i) + labels: [] + autoLabels: false + keyLabels: true + startTime: parseInt(new Date().getTime() / 1000) + + buildLayers = (data, keys, options, mapFn) -> + result = [] + for own j, key of keys + values = [] + for own i, d of data + values.push mapFn(d, key, parseInt(i)) + result.push applyLayerLabel({ values: values }, options, parseInt(j), keys) + return result + + formatBasicPlot = (data, keys, options) -> + buildLayers data, keys, options, (d, key, i) -> + if Epoch.isString(options.x) + x = d[options.x] + else + x = options.x(d, parseInt(i)) + { x: x, y: options.y(d[key], parseInt(i)) } + + formatTimePlot = (data, keys, options, rangeName='y') -> + buildLayers data, keys, options, (d, key, i) -> + if Epoch.isString(options.time) + value = { time: d[options.time] } + else + value = { time: options.time(d, parseInt(i), options.startTime) } + value[rangeName] = options.y(d[key], parseInt(i)) + value + + format = (data=[], keys=[], options={}) -> + return [] unless Epoch.isNonEmptyArray(data) and Epoch.isNonEmptyArray(keys) + opt = Epoch.Util.defaults options, defaultOptions + + if opt.type == 'pie' or opt.type == 'time.gauge' + return [] + else if opt.type == 'time.heatmap' + formatTimePlot data, keys, opt, 'histogram' + else if opt.type.match /^time\./ + formatTimePlot data, keys, opt + else + formatBasicPlot data, keys, opt + + format.entry = (datum, keys=[], options={}) -> + return [] unless datum? and Epoch.isNonEmptyArray(keys) + unless options.startTime? + options.startTime = parseInt(new Date().getTime() / 1000) + (layer.values[0] for layer in format([datum], keys, options)) + + return format +)() + +# Convenience data formatting method for easily accessing the various formatters. +# @param [String] formatter Name of the formatter to use. +# @param [Array] data Data to format. +# @param [Object] options Options to pass to the formatter (if any). +Epoch.data = (formatter, args...) -> + return [] unless (formatFn = Epoch.Data.Format[formatter])? + formatFn.apply formatFn, args + + +# Method used by charts and models for handling option based data formatting. +# Abstracted here because we'd like to allow models and indivisual charts to +# perform this action depending on the context. +Epoch.Data.formatData = (data=[], type, dataFormat) -> + return data unless Epoch.isNonEmptyArray(data) + + if Epoch.isString(dataFormat) + opts = { type: type } + return Epoch.data(dataFormat, data, opts) + + return data unless Epoch.isObject(dataFormat) + return data unless dataFormat.name? and Epoch.isString(dataFormat.name) + return data unless Epoch.Data.Format[dataFormat.name]? + + args = [dataFormat.name, data] + if dataFormat.arguments? and Epoch.isArray(dataFormat.arguments) + args.push(a) for a in dataFormat.arguments + + if dataFormat.options? + opts = dataFormat.options + if type? + opts.type ?= type + args.push opts + else if type? + args.push {type: type} + + Epoch.data.apply(Epoch.data, args) + +# Method used to format incoming entries for real-time charts. +Epoch.Data.formatEntry = (datum, type, format) -> + return datum unless format? + + if Epoch.isString(format) + opts = { type: type } + return Epoch.Data.Format[format].entry datum, opts + + return datum unless Epoch.isObject(format) + return datum unless format.name? and Epoch.isString(format.name) + return datum unless Epoch.Data.Format[format.name]? + + dataFormat = Epoch.Util.defaults format, {} + + args = [datum] + if dataFormat.arguments? and Epoch.isArray(dataFormat.arguments) + args.push(a) for a in dataFormat.arguments + + if dataFormat.options? + opts = dataFormat.options + opts.type = type + args.push opts + else if type? + args.push {type: type} + + entry = Epoch.Data.Format[dataFormat.name].entry + entry.apply entry, args diff --git a/debian/missing-sources/epoch/src/epoch.coffee b/debian/missing-sources/epoch/src/epoch.coffee new file mode 100644 index 0000000..b2d06ca --- /dev/null +++ b/debian/missing-sources/epoch/src/epoch.coffee @@ -0,0 +1,17 @@ +window.Epoch ?= {} +window.Epoch.Chart ?= {} +window.Epoch.Time ?= {} +window.Epoch.Util ?= {} +window.Epoch.Formats ?= {} + +# Sends a warning to the developer console with the given message. +# @param [String] msg Message for the warning. +Epoch.warn = (msg) -> + (console.warn or console.log)("Epoch Warning: #{msg}") + +# Raises an exception with the given message (with the 'Epoch Error:' preamble). +# @param [String] msg Specific message for the exception. +Epoch.exception = (msg) -> + throw "Epoch Error: #{msg}" + +# "I think, baby, I was born just a little late!" -- Middle Class Rut diff --git a/debian/missing-sources/epoch/src/model.coffee b/debian/missing-sources/epoch/src/model.coffee new file mode 100644 index 0000000..76f6acd --- /dev/null +++ b/debian/missing-sources/epoch/src/model.coffee @@ -0,0 +1,55 @@ +# Data model for epoch charts. By instantiating a model and passing it to each +# of the charts on a page the application programmer can update data once and +# have each of the charts respond accordingly. +# +# In addition to setting basic / historical data via the setData method, the +# model also supports the push method, which when used will cause real-time +# plots to automatically update and animate. +class Epoch.Model extends Epoch.Events + defaults = + dataFormat: null + + # Creates a new Model. + # @option options dataFormat The default data fromat for the model. + # @option data Initial data for the model. + constructor: (options={}) -> + super() + options = Epoch.Util.defaults options, defaults + @dataFormat = options.dataFormat + @data = options.data + @loading = false + + # Sets the model's data. + # @param data Data to set for the model. + # @event data:updated Instructs listening charts that new data is available. + setData: (data) -> + @data = data + @trigger 'data:updated' + + # Pushes a new entry into the model. + # @param entry Entry to push. + # @event data:push Instructs listening charts that a new data entry is available. + push: (entry) -> + @entry = entry + @trigger 'data:push' + + # Determines if the model has data. + # @return true if the model has data, false otherwise. + hasData: -> + @data? + + # Retrieves and formats adata for the specific chart type and data format. + # @param [String] type Type of the chart for which to fetch the data. + # @param [String, Object] dataFormat (optional) Used to override the model's default data format. + # @return The model's data formatted based the parameters. + getData: (type, dataFormat) -> + dataFormat ?= @dataFormat + Epoch.Data.formatData @data, type, dataFormat + + # Retrieves the latest data entry that was pushed into the model. + # @param [String] type Type of the chart for which to fetch the data. + # @param [String, Object] dataFormat (optional) Used to override the model's default data format. + # @return The model's next data entry formatted based the parameters. + getNext: (type, dataFormat) -> + dataFormat ?= @dataFormat + Epoch.Data.formatEntry @entry, type, dataFormat diff --git a/debian/missing-sources/epoch/src/time.coffee b/debian/missing-sources/epoch/src/time.coffee new file mode 100644 index 0000000..402cc50 --- /dev/null +++ b/debian/missing-sources/epoch/src/time.coffee @@ -0,0 +1,615 @@ +# Real-time Plot Base Class. Uses an html5 canvas to recreate the basic d3 drawing routines +# while simultaneously reducing the load on the viewer's cpu (and, you know, not leaking +# memory which ultimately leads to a crashed browser). +# +# The class also handles the creation of axes and margins common to all time-series plots. +# Furthermore it layers the canvas below an SVG element to keep visual consistency when +# rendering text, glyphs, etc. +class Epoch.Time.Plot extends Epoch.Chart.Canvas + defaults = + range: null + fps: 24 + historySize: 120 + windowSize: 40 + queueSize: 10 + axes: ['bottom'] + ticks: + time: 15 + left: 5 + right: 5 + tickFormats: + top: Epoch.Formats.seconds + bottom: Epoch.Formats.seconds + left: Epoch.Formats.si + right: Epoch.Formats.si + + defaultAxisMargins = + top: 25 + right: 50 + bottom: 25 + left: 50 + + optionListeners = + 'option:margins': 'marginsChanged' + 'option:margins.top': 'marginsChanged' + 'option:margins.right': 'marginsChanged' + 'option:margins.bottom': 'marginsChanged' + 'option:margins.left': 'marginsChanged' + 'option:axes': 'axesChanged' + 'option:ticks': 'ticksChanged' + 'option:ticks.top': 'ticksChanged' + 'option:ticks.right': 'ticksChanged' + 'option:ticks.bottom': 'ticksChanged' + 'option:ticks.left': 'ticksChanged' + 'option:tickFormats': 'tickFormatsChanged' + 'option:tickFormats.top': 'tickFormatsChanged' + 'option:tickFormats.right': 'tickFormatsChanged' + 'option:tickFormats.bottom': 'tickFormatsChanged' + 'option:tickFormats.left': 'tickFormatsChanged' + + # Creates a new real-time plot. + # + # @param [Object] options Options for the plot. + # @option options [Integer] fps Number of frames per second to use when animating + # the plot. + # @option options [Integer] historySize Maximum number of elements to keep in history + # for the plot. + # @option options [Integer] windowSize Number of entries to simultaneously display + # when rendering the visualization. + # @option options [Integer] queueSize Number of elements to queue while not animating + # but still recieving elements. In some browsers, intervals will not fire if the + # page containing them is not the active tab. By setting a maximum limit to the + # number of unprocessed data points we can ensure that the memory footprint of the + # page does not get out of hand. + # @option options [Object] margins Explicit margins to use for the visualization. Note + # that these are optional and will be automatically generated based on which axes are + # used for the visualization. Margins are keyed by their position (top, left, bottom + # and/or right) and should map to [Integer] values. + # @option options [Array] axes Which axes to display when rendering the visualization + # (top, left, bottom, and/or right). + # @option options [Object] ticks Number of ticks to display on each axis available axes + # ares: time, left, and right. The number provided for the left and right axes are in + # absolute terms (i.e. there will be exactly that number of ticks). The time ticks + # denote how often a tick should be generated (e.g. if 5 is provided then a tick will + # be added every fifth time you push a new data entry into the visualization). + # @option options [Object] tickFormats Formatting functions for ticks on the given axes. + # The avaiable axes are: top, bottom, left, and right. + constructor: (@options) -> + givenMargins = Epoch.Util.copy(@options.margins) or {} + super(@options = Epoch.Util.defaults(@options, defaults)) + + if @options.model + @options.model.on 'data:push', => @pushFromModel() + + # Queue entering data to get around memory bloat and "non-active" tab issues + @_queue = [] + + # Margins + @margins = {} + for pos in ['top', 'right', 'bottom', 'left'] + @margins[pos] = if @options.margins? and @options.margins[pos]? + @options.margins[pos] + else if @hasAxis(pos) + defaultAxisMargins[pos] + else + 6 + + # SVG Overlay + @svg = @el.insert('svg', ':first-child') + .attr('width', @width) + .attr('height', @height) + .style('z-index', '1000') + + # Position the canvas "under" the SVG element + if @el.style('position') != 'absolute' and @el.style('position') != 'relative' + @el.style('position', 'relative') + + @canvas.style { position: 'absolute', 'z-index': '999' } + @_sizeCanvas() + + # Animation / Transitions + @animation = + interval: null + active: false + delta: => -(@w() / @options.fps), + tickDelta: => -( (@w() / @pixelRatio) / @options.fps ) + frame: 0, + duration: @options.fps + + # Add SVG Axes + @_buildAxes() + + # Callback used for animation + @animationCallback = => @_animate() + + # Listen for specific option changes + @onAll optionListeners + + # Positions and sizes the canvas based on margins and axes. + _sizeCanvas: -> + @canvas.attr + width: @innerWidth() + height: @innerHeight() + + @canvas.style + width: "#{@innerWidth() / @pixelRatio}px" + height: "#{@innerHeight() / @pixelRatio}px" + top: "#{@margins.top}px" + left: "#{@margins.left}px" + + # Removes any axes found in the SVG and adds both the time and range axes to the plot. + _buildAxes: -> + @svg.selectAll('.axis').remove() + @_prepareTimeAxes() + @_prepareRangeAxes() + + # Works exactly as in Epoch.Chart.Base with the addition of truncating value arrays + # to that of the historySize defined in the chart's options. + _annotateLayers: (prepared) -> + data = [] + for own i, layer of prepared + copy = Epoch.Util.copy(layer) + start = Math.max(0, layer.values.length - @options.historySize) + copy.values = layer.values.slice(start) + classes = ['layer'] + classes.push "category#{(i|0)+1}" + classes.push(Epoch.Util.dasherize layer.label) if layer.label? + copy.className = classes.join(' ') + copy.visible = true + data.push copy + return data + + # This method is called to provide a small offset for placement of horizontal ticks. + # The value returned will be added to the x value of each tick as they are being + # rendered. + # + # @return [Number] The horizontal offset for the top and bottom axes ticks. + _offsetX: -> 0 + + # Builds time axes (bottom and top) + _prepareTimeAxes: -> + if @hasAxis('bottom') + axis = @bottomAxis = @svg.append('g') + .attr('class', "x axis bottom canvas") + .attr('transform', "translate(#{@margins.left-1}, #{@innerHeight()/@pixelRatio+@margins.top})") + axis.append('path') + .attr('class', 'domain') + .attr('d', "M0,0H#{@innerWidth()/@pixelRatio+1}") + + if @hasAxis('top') + axis = @topAxis = @svg.append('g') + .attr('class', "x axis top canvas") + .attr('transform', "translate(#{@margins.left-1}, #{@margins.top})") + axis.append('path') + .attr('class', 'domain') + .attr('d', "M0,0H#{@innerWidth()/@pixelRatio+1}") + + @_resetInitialTimeTicks() + + # Resets the initial ticks for the time axes. + _resetInitialTimeTicks: -> + tickInterval = @options.ticks.time + @_ticks = [] + @_tickTimer = tickInterval + + @bottomAxis.selectAll('.tick').remove() if @bottomAxis? + @topAxis.selectAll('.tick').remove() if @topAxis? + + for layer in @data + continue unless Epoch.isNonEmptyArray(layer.values) + [i, k] = [@options.windowSize-1, layer.values.length-1] + while i >= 0 and k >= 0 + @_pushTick i, layer.values[k].time, false, true + i -= tickInterval + k -= tickInterval + break + + # Builds the range axes (left and right) + _prepareRangeAxes: -> + if @hasAxis('left') + @svg.append("g") + .attr("class", "y axis left") + .attr('transform', "translate(#{@margins.left-1}, #{@margins.top})") + .call(@leftAxis()) + + if @hasAxis('right') + @svg.append('g') + .attr('class', 'y axis right') + .attr('transform', "translate(#{@width - @margins.right}, #{@margins.top})") + .call(@rightAxis()) + + # @return [Object] The d3 left axis. + leftAxis: -> + ticks = @options.ticks.left + axis = d3.svg.axis().scale(@ySvgLeft()).orient('left') + .tickFormat(@options.tickFormats.left) + if ticks == 2 + axis.tickValues @extent((d) -> d.y) + else + axis.ticks(ticks) + + # @return [Object] The d3 right axis. + rightAxis: -> + extent = @extent((d) -> d.y) + ticks = @options.ticks.right + axis = d3.svg.axis().scale(@ySvgRight()).orient('right') + .tickFormat(@options.tickFormats.right) + if ticks == 2 + axis.tickValues @extent((d) -> d.y) + else + axis.ticks(ticks) + + # Determines if the visualization is displaying the axis with the given name. + # @param [String] name Name of the axis + # @return [Boolean] true if the axis was set in the options, false otherwise. + hasAxis: (name) -> + @options.axes.indexOf(name) > -1 + + # @return [Number] the width of the visualization area of the plot (full width - margins) + innerWidth: -> + (@width - (@margins.left + @margins.right)) * @pixelRatio + + # @return [Number] the height of the visualization area of the plot (full height - margins) + innerHeight: -> + (@height - (@margins.top + @margins.bottom)) * @pixelRatio + + # Abstract method for performing any preprocessing before queuing new entries + # @param entry [Object] The entry to prepare. + # @return [Object] The prepared entry. + _prepareEntry: (entry) -> entry + + # Abstract method for preparing a group of layered entries entering the visualization + # @param [Array] layers The layered entries to prepare. + # @return [Array] The prepared layers. + _prepareLayers: (layers) -> layers + + # This method will remove the first incoming entry from the visualization's queue + # and shift it into the working set (aka window). It then starts the animating the + # transition of the element into the visualization. + # @event transition:start in the case that animation is actually started. + _startTransition: -> + return if @animation.active == true or @_queue.length == 0 + @trigger 'transition:start' + @_shift() + @animation.active = true + @animation.interval = setInterval(@animationCallback, 1000/@options.fps) + + # Stops animating and clears the animation interval given there is no more + # incoming data to process. Also finalizes tick entering and exiting. + # @event transition:end After the transition has completed. + _stopTransition: -> + return unless @inTransition() + + # Shift data off the end + for layer in @data + continue unless layer.values.length > @options.windowSize + 1 + layer.values.shift() + + # Finalize tick transitions + [firstTick, lastTick] = [@_ticks[0], @_ticks[@_ticks.length-1]] + + if lastTick? and lastTick.enter + lastTick.enter = false + lastTick.opacity = 1 + + if firstTick? and firstTick.exit + @_shiftTick() + + # Reset the animation frame modulus + @animation.frame = 0 + + # Trigger that we are done transitioning + @trigger 'transition:end' + + # Clear the transition interval unless another entry is already queued + if @_queue.length > 0 + @_shift() + else + @animation.active = false + clearInterval @animation.interval + + # Determines if the plot is currently animating a transition. + # @return [Boolean] true if the plot is animating, false otherwise. + inTransition: -> + @animation.active + + # This method is used by the application programmer to introduce new data into + # the timeseries plot. The method queues the incoming data, ensures a fixed size + # for the data queue, and finally calls _startTransition method to + # begin animating the plot. + # @param [Array] layers Layered incoming visualization data. + # @event push Triggered after the new data has been pushed into the queue. + push: (layers) -> + layers = @_prepareLayers(layers) + + # Handle entry queue maximum size + if @_queue.length > @options.queueSize + @_queue.splice @options.queueSize, (@_queue.length - @options.queueSize) + return false if @_queue.length == @options.queueSize + + # Push the entry into the queue + @_queue.push layers.map((entry) => @_prepareEntry(entry)) + + @trigger 'push' + + # Begin the transition unless we are already doing so + @_startTransition() unless @inTransition() + + # Fetches new entry data from the model in response to a 'data:push' event. + pushFromModel: -> + @push @options.model.getNext(@options.type, @options.dataFormat) + + # Shift elements off the incoming data queue (see the implementation of + # push above). + # + # If there's data to be shoved into the visualization it will pull it + # off the queue and put it into the working dataset. It also calls through + # to @_updateTicks to handle horizontal (or "time") axes tick transitions + # since we're implementing independent of d3 as well. + # + # @event before:shift Before an element has been shifted off the queue. + # @event after:shift After the element has been shifted off the queue. + _shift: -> + @trigger 'before:shift' + entry = @_queue.shift() + layer.values.push(entry[i]) for own i, layer of @data + @_updateTicks(entry[0].time) + @_transitionRangeAxes() + @trigger 'after:shift' + + # Transitions the left and right axes when the range of the plot has changed. + _transitionRangeAxes: -> + if @hasAxis('left') + @svg.selectAll('.y.axis.left').transition() + .duration(500) + .ease('linear') + .call(@leftAxis()) + + if @hasAxis('right') + @svg.selectAll('.y.axis.right').transition() + .duration(500) + .ease('linear') + .call(@rightAxis()) + + # Performs the animation for transitioning elements in the visualization. + _animate: -> + return unless @inTransition() + @_stopTransition() if ++@animation.frame == @animation.duration + @draw(@animation.frame * @animation.delta()) + @_updateTimeAxes() + + # @param [Array] givenDomain A given domain for the scale + # @return [Function] The y scale for the plot + y: (givenDomain) -> + d3.scale.linear() + .domain(@_getScaleDomain(givenDomain)) + .range([@innerHeight(), 0]) + + # @param [Array] givenDomain Optional domain to override default + # @return [Function] The y scale for the svg portions of the plot + ySvg: (givenDomain) -> + d3.scale.linear() + .domain(@_getScaleDomain(givenDomain)) + .range([@innerHeight() / @pixelRatio, 0]) + + # @return [Function] The y scale for the svg portion of the plot for the left axis + ySvgLeft: -> + if @options.range? + @ySvg @options.range.left + else + @ySvg() + + # @return [Function] The y scale for the svg portion of the plot for the right axis + ySvgRight: -> + if @options.range? + @ySvg @options.range.right + else + @ySvg() + + # @return [Number] The width of a single section of the graph pertaining to a data point + w: -> + @innerWidth() / @options.windowSize + + # This is called every time we introduce new data (as a result of _shift) + # it checks to see if we also need to update the working tick set and + # makes the approriate changes for handling tick animation (enter, exit, + # and update in the d3 model). + # + # @param [Integer] newTime Current newest timestamp in the data + _updateTicks: (newTime) -> + return unless @hasAxis('top') or @hasAxis('bottom') + + # Incoming ticks + unless (++@_tickTimer) % @options.ticks.time + @_pushTick(@options.windowSize, newTime, true) + + # Outgoing ticks + return unless @_ticks.length > 0 + unless @_ticks[0].x - (@w()/@pixelRatio) >= 0 + @_ticks[0].exit = true + + # Makes and pushes a new tick into the visualization. + # + # @param bucket Index in the data window where the tick should initially be position + # @param time The unix timestamp associated with the tick + # @param enter Whether or not the tick should be considered as "newly entering" + # Used primarily for performing the tick opacity tween. + _pushTick: (bucket, time, enter=false, reverse=false) -> + return unless @hasAxis('top') or @hasAxis('bottom') + tick = + time: time + x: bucket*(@w()/@pixelRatio) + @_offsetX() + opacity: if enter then 0 else 1 + enter: if enter then true else false + exit: false + + if @hasAxis('bottom') + g = @bottomAxis.append('g') + .attr('class', 'tick major') + .attr('transform', "translate(#{tick.x+1},0)") + .style('opacity', tick.opacity) + + g.append('line') + .attr('y2', 6) + + g.append('text') + .attr('text-anchor', 'middle') + .attr('dy', 19) + .text(@options.tickFormats.bottom(tick.time)) + + tick.bottomEl = g + + if @hasAxis('top') + g = @topAxis.append('g') + .attr('class', 'tick major') + .attr('transform', "translate(#{tick.x+1},0)") + .style('opacity', tick.opacity) + + g.append('line') + .attr('y2', -6) + + g.append('text') + .attr('text-anchor', 'middle') + .attr('dy', -10) + .text(@options.tickFormats.top(tick.time)) + + tick.topEl = g + + if reverse + @_ticks.unshift tick + else + @_ticks.push tick + return tick + + # Shifts a tick that is no longer needed out of the visualization. + _shiftTick: -> + return unless @_ticks.length > 0 + tick = @_ticks.shift() + tick.topEl.remove() if tick.topEl? + tick.bottomEl.remove() if tick.bottomEl? + + # This performs animations for the time axes (top and bottom). + _updateTimeAxes: -> + return unless @hasAxis('top') or @hasAxis('bottom') + [dx, dop] = [@animation.tickDelta(), 1 / @options.fps] + + for tick in @_ticks + tick.x += dx + if @hasAxis('bottom') + tick.bottomEl.attr('transform', "translate(#{tick.x+1},0)") + if @hasAxis('top') + tick.topEl.attr('transform', "translate(#{tick.x+1},0)") + + if tick.enter + tick.opacity += dop + else if tick.exit + tick.opacity -= dop + + if tick.enter or tick.exit + tick.bottomEl.style('opacity', tick.opacity) if @hasAxis('bottom') + tick.topEl.style('opacity', tick.opacity) if @hasAxis('top') + + # Draws the visualization in the plot's canvas. + # @param delta The current x offset to apply to all elements when rendering. This number + # will be 0 when the plot is not animating and negative when it is. + # @abstract It does nothing on its own but is provided so that subclasses can + # define a custom rendering routine. + draw: (delta=0) -> super() + + dimensionsChanged: -> + super() + @svg.attr('width', @width).attr('height', @height) + @_sizeCanvas() + @_buildAxes() + @draw(@animation.frame * @animation.delta()) + + # Updates axes in response to an option:axes event. + axesChanged: -> + for pos in ['top', 'right', 'bottom', 'left'] + continue if @options.margins? and @options.margins[pos]? + if @hasAxis(pos) + @margins[pos] = defaultAxisMargins[pos] + else + @margins[pos] = 6 + @_sizeCanvas() + @_buildAxes() + @draw(@animation.frame * @animation.delta()) + + # Updates ticks in response to an option.ticks.* event. + ticksChanged: -> + @_resetInitialTimeTicks() + @_transitionRangeAxes() + @draw(@animation.frame * @animation.delta()) + + # Updates tick formats in response to an option.tickFormats.* event. + tickFormatsChanged: -> + @_resetInitialTimeTicks() + @_transitionRangeAxes() + @draw(@animation.frame * @animation.delta()) + + # Updates margins in response to an option.margins.* event. + marginsChanged: -> + return unless @options.margins? + for own pos, size of @options.margins + unless size? + @margins[pos] = 6 + else + @margins[pos] = size + + @_sizeCanvas() + @draw(@animation.frame * @animation.delta()) + + layerChanged: -> + @_transitionRangeAxes() + super() + + +# Base class for all "stacked" plot types (e.g. bar charts, area charts, etc.) +# @abstract It does not perform rendering but instead formats the data +# so as to ease the process of rendering stacked plots. +class Epoch.Time.Stack extends Epoch.Time.Plot + # Sets stacking information (y0) for each of the points in each layer + _stackLayers: -> + return unless (layers = @getVisibleLayers()).length > 0 + for i in [0...layers[0].values.length] + y0 = 0 + for layer in layers + layer.values[i].y0 = y0 + y0 += layer.values[i].y + + # Adds stacking information for layers entering the visualization. + # @param [Array] layers Layers to stack. + _prepareLayers: (layers) -> + y0 = 0 + for own i, d of layers + continue unless @data[i].visible + d.y0 = y0 + y0 += d.y + return layers + + # Ensures that elements are stacked when setting the initial data. + # @param [Array] data Layered data to set for the visualization. + setData: (data) -> + super(data) + @_stackLayers() + + # Finds the correct extent to use for range axes (left and right). + # @return [Array] An extent array with the first element equal to 0 + # and the second element equal to the maximum value amongst the + # stacked entries. + extent: -> + [max, layers] = [0, @getVisibleLayers()] + return [0, 0] unless layers.length + + for i in [0...layers[0].values.length] + sum = 0 + for j in [0...layers.length] + sum += layers[j].values[i].y + max = sum if sum > max + + [0, max] + + layerChanged: -> + @_stackLayers() + @_prepareLayers(layers) for layers in @_queue + super() diff --git a/debian/missing-sources/epoch/src/time/area.coffee b/debian/missing-sources/epoch/src/time/area.coffee new file mode 100644 index 0000000..22bf9db --- /dev/null +++ b/debian/missing-sources/epoch/src/time/area.coffee @@ -0,0 +1,80 @@ + +# Real-time stacked area chart implementation. +class Epoch.Time.Area extends Epoch.Time.Stack + constructor: (@options={}) -> + @options.type ?= 'time.area' + super(@options) + @draw() + + # Sets the appropriate styles to the graphics context given a particular layer. + # @param [Object] layer Layer for which to set the styles. + setStyles: (layer) -> + if layer? && layer.className? + styles = @getStyles "g.#{layer.className.replace(/\s/g,'.')} path.area" + else + styles = @getStyles "g path.area" + @ctx.fillStyle = styles.fill + if styles.stroke? + @ctx.strokeStyle = styles.stroke + if styles['stroke-width']? + @ctx.lineWidth = styles['stroke-width'].replace('px', '') + + # Draws areas for the chart + _drawAreas: (delta=0) -> + [y, w, layers] = [@y(), @w(), @getVisibleLayers()] + + for i in [layers.length-1..0] + continue unless (layer = layers[i]) + + @setStyles layer + @ctx.beginPath() + + [j, k, trans] = [@options.windowSize, layer.values.length, @inTransition()] + firstX = null + while (--j >= -2) and (--k >= 0) + entry = layer.values[k] + args = [(j+1)*w+delta, y(entry.y + entry.y0)] + args[0] += w if trans + if i == @options.windowSize - 1 + @ctx.moveTo.apply @ctx, args + else + @ctx.lineTo.apply @ctx, args + + if trans + borderX = (j+3)*w+delta + else + borderX = (j+2)*w+delta + + @ctx.lineTo(borderX, @innerHeight()) + @ctx.lineTo(@width*@pixelRatio+w+delta, @innerHeight()) + @ctx.closePath() + @ctx.fill() + + # Draws strokes for the chart + _drawStrokes: (delta=0) -> + [y, w, layers] = [@y(), @w(), @getVisibleLayers()] + + for i in [layers.length-1..0] + continue unless (layer = layers[i]) + @setStyles layer + @ctx.beginPath() + + [i, k, trans] = [@options.windowSize, layer.values.length, @inTransition()] + firstX = null + while (--i >= -2) and (--k >= 0) + entry = layer.values[k] + args = [(i+1)*w+delta, y(entry.y + entry.y0)] + args[0] += w if trans + if i == @options.windowSize - 1 + @ctx.moveTo.apply @ctx, args + else + @ctx.lineTo.apply @ctx, args + + @ctx.stroke() + + # Draws the area chart. + draw: (delta=0) -> + @clear() + @_drawAreas(delta) + @_drawStrokes(delta) + super() diff --git a/debian/missing-sources/epoch/src/time/bar.coffee b/debian/missing-sources/epoch/src/time/bar.coffee new file mode 100644 index 0000000..7bcb7be --- /dev/null +++ b/debian/missing-sources/epoch/src/time/bar.coffee @@ -0,0 +1,48 @@ + +# Real-time Bar Chart implementation. +class Epoch.Time.Bar extends Epoch.Time.Stack + constructor: (@options={}) -> + @options.type ?= 'time.bar' + super(@options) + @draw() + + # @return [Number] An offset used to align the ticks to the center of the rendered bars. + _offsetX: -> + 0.5 * @w() / @pixelRatio + + # Sets the styles for the graphics context given a layer class name. + # @param [String] className The class name to use when deriving the styles. + setStyles: (className) -> + styles = @getStyles "rect.bar.#{className.replace(/\s/g,'.')}" + @ctx.fillStyle = styles.fill + + if !styles.stroke? or styles.stroke == 'none' + @ctx.strokeStyle = 'transparent' + else + @ctx.strokeStyle = styles.stroke + + if styles['stroke-width']? + @ctx.lineWidth = styles['stroke-width'].replace('px', '') + + # Draws the stacked bar chart. + draw: (delta=0) -> + @clear() + [y, w] = [@y(), @w()] + + for layer in @getVisibleLayers() + continue unless Epoch.isNonEmptyArray(layer.values) + @setStyles(layer.className) + + [i, k, trans] = [@options.windowSize, layer.values.length, @inTransition()] + iBoundry = if trans then -1 else 0 + + while (--i >= iBoundry) and (--k >= 0) + entry = layer.values[k] + [ex, ey, ey0] = [i*w+delta, entry.y, entry.y0] + ex += w if trans + args = [ex+1, y(ey+ey0), w-2, @innerHeight()-y(ey)+0.5*@pixelRatio] + + @ctx.fillRect.apply(@ctx, args) + @ctx.strokeRect.apply(@ctx, args) + + super() diff --git a/debian/missing-sources/epoch/src/time/gauge.coffee b/debian/missing-sources/epoch/src/time/gauge.coffee new file mode 100644 index 0000000..8efadb2 --- /dev/null +++ b/debian/missing-sources/epoch/src/time/gauge.coffee @@ -0,0 +1,209 @@ + +# Real-time Gauge Visualization. Note: Looks best with a 4:3 aspect ratio (w:h) +class Epoch.Time.Gauge extends Epoch.Chart.Canvas + defaults = + type: 'time.gauge' + domain: [0, 1] + ticks: 10 + tickSize: 5 + tickOffset: 5 + fps: 34 + format: Epoch.Formats.percent + + optionListeners = + 'option:domain': 'domainChanged' + 'option:ticks': 'ticksChanged' + 'option:tickSize': 'tickSizeChanged' + 'option:tickOffset': 'tickOffsetChanged' + 'option:format': 'formatChanged' + + # Creates the new gauge chart. + # @param [Object] options Options for the gauge chart. + # @option options [Array] domain The domain to use when rendering values (default: [0, 1]). + # @option options [Integer] ticks Number of ticks to render (default: 10). + # @option options [Integer] tickSize The length (in pixels) for each tick (default: 5). + # @option options [Integer] tickOffset The number of pixels by which to offset ticks from the outer arc (default: 5). + # @option options [Integer] fps The number of animation frames to render per second (default: 34). + # @option options [Function] format The formatting function to use when rendering the gauge label + # (default: Epoch.Formats.percent). + constructor: (@options={}) -> + super(@options = Epoch.Util.defaults(@options, defaults)) + @value = @options.value or 0 + + if @options.model + @options.model.on 'data:push', => @pushFromModel() + + # SVG Labels Overlay + if @el.style('position') != 'absolute' and @el.style('position') != 'relative' + @el.style('position', 'relative') + + @svg = @el.insert('svg', ':first-child') + .attr('width', @width) + .attr('height', @height) + .attr('class', 'gauge-labels') + + @svg.style + 'position': 'absolute' + 'z-index': '1' + + @svg.append('g') + .attr('transform', "translate(#{@textX()}, #{@textY()})") + .append('text') + .attr('class', 'value') + .text(@options.format(@value)) + + # Animations + @animation = + interval: null + active: false + delta: 0 + target: 0 + + @_animate = => + if Math.abs(@animation.target - @value) < Math.abs(@animation.delta) + @value = @animation.target + clearInterval @animation.interval + @animation.active = false + else + @value += @animation.delta + + @svg.select('text.value').text(@options.format(@value)) + @draw() + + @onAll optionListeners + @draw() + + # Sets the value for the gauge to display and begins animating the guage. + # @param [Number] value Value to set for the gauge. + update: (value) -> + @animation.target = value + @animation.delta = (value - @value) / @options.fps + unless @animation.active + @animation.interval = setInterval @_animate, (1000/@options.fps) + @animation.active = true + + # Alias for the update() method. + # @param [Number] value Value to set for the gauge. + push: (value) -> + @update value + + # Responds to a model's 'data:push' event. + pushFromModel: -> + next = @options.model.getNext(@options.type, @options.dataFormat) + @update next + + # @return [Number] The radius for the gauge. + radius: -> @getHeight() / 1.58 + + # @return [Number] The center position x-coordinate for the gauge. + centerX: -> @getWidth() / 2 + + # @return [Number] The center position y-coordinate for the gauge. + centerY: -> 0.68 * @getHeight() + + # @return [Number] The x-coordinate for the gauge text display. + textX: -> @width / 2 + + # @return [Number] The y-coordinate for the gauge text display. + textY: -> 0.48 * @height + + # @return [Number] The angle to set for the needle given a value within the domain. + # @param [Number] value Value to translate into a needle angle. + getAngle: (value) -> + [a, b] = @options.domain + ((value - a) / (b - a)) * (Math.PI + 2*Math.PI/8) - Math.PI/2 - Math.PI/8 + + # Sets context styles given a particular selector. + # @param [String] selector The selector to use when setting the styles. + setStyles: (selector) -> + styles = @getStyles selector + @ctx.fillStyle = styles.fill + @ctx.strokeStyle = styles.stroke + @ctx.lineWidth = styles['stroke-width'].replace('px', '') if styles['stroke-width']? + + # Draws the gauge. + draw: -> + [cx, cy, r] = [@centerX(), @centerY(), @radius()] + [tickOffset, tickSize] = [@options.tickOffset, @options.tickSize] + + @clear() + + # Draw Ticks + t = d3.scale.linear() + .domain([0, @options.ticks]) + .range([ -(9/8)*Math.PI, Math.PI/8 ]) + + @setStyles '.epoch .gauge .tick' + @ctx.beginPath() + for i in [0..@options.ticks] + a = t(i) + [c, s] = [Math.cos(a), Math.sin(a)] + + x1 = c * (r-tickOffset) + cx + y1 = s * (r-tickOffset) + cy + x2 = c * (r-tickOffset-tickSize) + cx + y2 = s * (r-tickOffset-tickSize) + cy + + @ctx.moveTo x1, y1 + @ctx.lineTo x2, y2 + + @ctx.stroke() + + # Outer arc + @setStyles '.epoch .gauge .arc.outer' + @ctx.beginPath() + @ctx.arc cx, cy, r, -(9/8)*Math.PI, (1/8)*Math.PI, false + @ctx.stroke() + + # Inner arc + @setStyles '.epoch .gauge .arc.inner' + @ctx.beginPath() + @ctx.arc cx, cy, r-10, -(9/8)*Math.PI, (1/8)*Math.PI, false + @ctx.stroke() + + @drawNeedle() + + super() + + # Draws the needle. + drawNeedle: -> + [cx, cy, r] = [@centerX(), @centerY(), @radius()] + ratio = @value / @options.domain[1] + + @setStyles '.epoch .gauge .needle' + @ctx.beginPath() + @ctx.save() + @ctx.translate cx, cy + @ctx.rotate @getAngle(@value) + + @ctx.moveTo 4 * @pixelRatio, 0 + @ctx.lineTo -4 * @pixelRatio, 0 + @ctx.lineTo -1 * @pixelRatio, 19-r + @ctx.lineTo 1, 19-r + @ctx.fill() + + @setStyles '.epoch .gauge .needle-base' + @ctx.beginPath() + @ctx.arc 0, 0, (@getWidth() / 25), 0, 2*Math.PI + @ctx.fill() + + @ctx.restore() + + # Correctly responds to an option: + domainChanged: -> @draw() + + # Correctly responds to an option: + ticksChanged: -> @draw() + + # Correctly responds to an option: + tickSizeChanged: -> @draw() + + # Correctly responds to an option: + tickOffsetChanged: -> @draw() + + # Correctly responds to an option: + formatChanged: -> @svg.select('text.value').text(@options.format(@value)) + + + +# "The mother of a million sons... CIVILIZATION!" -- Justice diff --git a/debian/missing-sources/epoch/src/time/heatmap.coffee b/debian/missing-sources/epoch/src/time/heatmap.coffee new file mode 100644 index 0000000..d7f2c50 --- /dev/null +++ b/debian/missing-sources/epoch/src/time/heatmap.coffee @@ -0,0 +1,261 @@ + +# Real-time Heatmap Implementation. +class Epoch.Time.Heatmap extends Epoch.Time.Plot + defaults = + type: 'time.heatmap' + buckets: 10 + bucketRange: [0, 100] + opacity: 'linear' + bucketPadding: 2 + paintZeroValues: false + cutOutliers: false + + # Easy to use "named" color functions + colorFunctions = + root: (value, max) -> Math.pow(value/max, 0.5) + linear: (value, max) -> value / max + quadratic: (value, max) -> Math.pow(value/max, 2) + cubic: (value, max) -> Math.pow(value/max, 3) + quartic: (value, max) -> Math.pow(value/max, 4) + quintic: (value, max) -> Math.pow(value/max, 5) + + optionListeners = + 'option:buckets': 'bucketsChanged' + 'option:bucketRange': 'bucketRangeChanged' + 'option:opacity': 'opacityChanged' + 'option:bucketPadding': 'bucketPaddingChanged' + 'option:paintZeroValues': 'paintZeroValuesChanged' + 'option:cutOutliers': 'cutOutliersChanged' + + # Creates a new heatmap. + # @param [Object] options Options for the heatmap. + # @option options [Integer] buckets Number of vertical buckets to use when normalizing the + # incoming histogram data for visualization in the heatmap (default: 10). + # @option options [Array] bucketRange A range of acceptable values to be bucketed (default: [0, 100]). + # @option options [String, Function] opacity The opacity coloring function to use when rendering buckets + # in a column. The built-in functions (referenced by string) are: 'root', 'linear', 'quadratic', 'cubic', + # 'quartic', and 'quintic'. A custom function can be supplied given it accepts two parameters (value, max) + # and returns a numeric value from 0 to 1. Default: linear. + # @option options [Number] bucketPadding Amount of padding to apply around buckets (default: 2). + constructor: (@options={}) -> + super(@options = Epoch.Util.defaults(@options, defaults)) + @_setOpacityFunction() + @_setupPaintCanvas() + @onAll optionListeners + @draw() + + _setOpacityFunction: -> + if Epoch.isString(@options.opacity) + @_opacityFn = colorFunctions[@options.opacity] + Epoch.exception "Unknown coloring function provided '#{@options.opacity}'" unless @_opacityFn? + else if Epoch.isFunction(@options.opacity) + @_opacityFn = @options.opacity + else + Epoch.exception "Unknown type for provided coloring function." + + # Prepares initially set data for rendering. + # @param [Array] data Layered histogram data for the visualization. + setData: (data) -> + super(data) + for layer in @data + layer.values = layer.values.map((entry) => @_prepareEntry(entry)) + + # Distributes the full histogram in the entry into the defined buckets + # for the visualization. + # @param [Object] entry Entry to prepare for visualization. + _getBuckets: (entry) -> + prepared = + time: entry.time + max: 0 + buckets: (0 for i in [0...@options.buckets]) + + # Bucket size = (Range[1] - Range[0]) / number of buckets + bucketSize = (@options.bucketRange[1] - @options.bucketRange[0]) / @options.buckets + + for own value, count of entry.histogram + index = parseInt((value - @options.bucketRange[0]) / bucketSize) + + # Remove outliers from the preprared buckets if instructed to do so + if @options.cutOutliers and ((index < 0) or (index >= @options.buckets)) + continue + + # Bound the histogram to the range (aka, handle out of bounds values) + if index < 0 + index = 0 + else if index >= @options.buckets + index = @options.buckets - 1 + + prepared.buckets[index] += parseInt count + + for i in [0...prepared.buckets.length] + prepared.max = Math.max(prepared.max, prepared.buckets[i]) + + return prepared + + # @return [Function] The y scale for the heatmap. + y: -> + d3.scale.linear() + .domain(@options.bucketRange) + .range([@innerHeight(), 0]) + + # @return [Function] The y scale for the svg portions of the heatmap. + ySvg: -> + d3.scale.linear() + .domain(@options.bucketRange) + .range([@innerHeight() / @pixelRatio, 0]) + + # @return [Number] The height to render each bucket in a column (disregards padding). + h: -> + @innerHeight() / @options.buckets + + # @return [Number] The offset needed to center ticks at the middle of each column. + _offsetX: -> + 0.5 * @w() / @pixelRatio + + # Creates the painting canvas which is used to perform all the actual drawing. The contents + # of the canvas are then copied into the actual display canvas and through some image copy + # trickery at the end of a transition the illusion of motion over time is preserved. + # + # Using two canvases in this way allows us to render an incredible number of buckets in the + # visualization and animate them at high frame rates without smashing the cpu. + _setupPaintCanvas: -> + # Size the paint canvas to have a couple extra columns so we can perform smooth transitions + @paintWidth = (@options.windowSize + 1) * @w() + @paintHeight = @height * @pixelRatio + + # Create the "memory only" canvas and nab the drawing context + @paint = document.createElement('CANVAS') + @paint.width = @paintWidth + @paint.height = @paintHeight + @p = Epoch.Util.getContext @paint + + # Paint the initial data (rendering backwards from just before the fixed paint position) + @redraw() + + # Hook into the events to paint the next row after it's been shifted into the data + @on 'after:shift', '_paintEntry' + + # At the end of a transition we must reset the paint canvas by shifting the viewable + # buckets to the left (this allows for a fixed cut point and single renders below in @draw) + @on 'transition:end', '_shiftPaintCanvas' + @on 'transition:end', => @draw(@animation.frame * @animation.delta()) + + # Redraws the entire heatmap for the current data. + redraw: -> + return unless Epoch.isNonEmptyArray(@data) and Epoch.isNonEmptyArray(@data[0].values) + entryIndex = @data[0].values.length + drawColumn = @options.windowSize + + # This addresses a strange off-by-one issue when the chart is transitioning + drawColumn++ if @inTransition() + + while (--entryIndex >= 0) and (--drawColumn >= 0) + @_paintEntry(entryIndex, drawColumn) + @draw(@animation.frame * @animation.delta()) + + # Computes the correct color for a given bucket. + # @param [Integer] value Normalized value at the bucket. + # @param [Integer] max Normalized maximum for the column. + # @param [String] color Computed base color for the bucket. + _computeColor: (value, max, color) -> + Epoch.Util.toRGBA(color, @_opacityFn(value, max)) + + # Paints a single entry column on the paint canvas at the given column. + # @param [Integer] entryIndex Index of the entry to paint. + # @param [Integer] drawColumn Column on the paint canvas to place the visualized entry. + _paintEntry: (entryIndex=null, drawColumn=null) -> + [w, h] = [@w(), @h()] + + entryIndex ?= @data[0].values.length - 1 + drawColumn ?= @options.windowSize + + entries = [] + bucketTotals = (0 for i in [0...@options.buckets]) + maxTotal = 0 + + for layer in @getVisibleLayers() + entry = @_getBuckets( layer.values[entryIndex] ) + for own bucket, count of entry.buckets + bucketTotals[bucket] += count + maxTotal += entry.max + styles = @getStyles ".#{layer.className.split(' ').join('.')} rect.bucket" + entry.color = styles.fill + entries.push entry + + xPos = drawColumn * w + + @p.clearRect xPos, 0, w, @paintHeight + + j = @options.buckets + + for own bucket, sum of bucketTotals + color = @_avgLab(entries, bucket) + max = 0 + for entry in entries + max += (entry.buckets[bucket] / sum) * maxTotal + if sum > 0 or @options.paintZeroValues + @p.fillStyle = @_computeColor(sum, max, color) + @p.fillRect xPos, (j-1) * h, w-@options.bucketPadding, h-@options.bucketPadding + j-- + + # This shifts the image contents of the paint canvas to the left by 1 column width. + # It is called after a transition has ended (yay, slight of hand). + _shiftPaintCanvas: -> + data = @p.getImageData @w(), 0, @paintWidth-@w(), @paintHeight + @p.putImageData data, 0, 0 + + # Performs an averaging of the colors for muli-layer heatmaps using the lab color space. + # @param [Array] entries The layers for which the colors are to be averaged. + # @param [Number] bucket The bucket in the entries that must be averaged. + # @return [String] The css color code for the average of all the layer colors. + _avgLab: (entries, bucket) -> + [l, a, b, total] = [0, 0, 0, 0] + for entry in entries + continue unless entry.buckets[bucket]? + total += entry.buckets[bucket] + + for own i, entry of entries + if entry.buckets[bucket]? + value = entry.buckets[bucket]|0 + else + value = 0 + ratio = value / total + color = d3.lab(entry.color) + l += ratio * color.l + a += ratio * color.a + b += ratio * color.b + + d3.lab(l, a, b).toString() + + # Copies the paint canvas onto the display canvas, thus rendering the heatmap. + draw: (delta=0) -> + @clear() + @ctx.drawImage @paint, delta, 0 + super() + + # Changes the number of buckets in response to an option:buckets event. + bucketsChanged: -> @redraw() + + # Changes the range of the buckets in response to an option:bucketRange event. + bucketRangeChanged: -> + @_transitionRangeAxes() + @redraw() + + # Changes the opacity function in response to an option:opacity event. + opacityChanged: -> + @_setOpacityFunction() + @redraw() + + # Changes the bucket padding in response to an option:bucketPadding event. + bucketPaddingChanged: -> @redraw() + + # Changes whether or not to paint zeros in response to an option:paintZeroValues event. + paintZeroValuesChanged: -> @redraw() + + # Changes whether or not to cut outliers when bucketing in response to an + # option:cutOutliers event. + cutOutliersChanged: -> @redraw() + + layerChanged: -> @redraw() + +# "Audio... Audio... Audio... Video Disco..." - Justice diff --git a/debian/missing-sources/epoch/src/time/line.coffee b/debian/missing-sources/epoch/src/time/line.coffee new file mode 100644 index 0000000..8a4271b --- /dev/null +++ b/debian/missing-sources/epoch/src/time/line.coffee @@ -0,0 +1,39 @@ + +# Real-time line chart implementation +class Epoch.Time.Line extends Epoch.Time.Plot + constructor: (@options={}) -> + @options.type ?= 'time.line' + super(@options) + @draw() + + # Sets the graphics context styles based ont he given layer class name. + # @param [String] className The class name of the layer for which to set the styles. + setStyles: (className) -> + styles = @getStyles "g.#{className.replace(/\s/g,'.')} path.line" + @ctx.fillStyle = styles.fill + @ctx.strokeStyle = styles.stroke + @ctx.lineWidth = @pixelRatio * styles['stroke-width'].replace('px', '') + + # Draws the line chart. + draw: (delta=0) -> + @clear() + w = @w() + for layer in @getVisibleLayers() + continue unless Epoch.isNonEmptyArray(layer.values) + @setStyles(layer.className) + @ctx.beginPath() + y = @y(layer.range) + [i, k, trans] = [@options.windowSize, layer.values.length, @inTransition()] + + while (--i >= -2) and (--k >= 0) + entry = layer.values[k] + args = [(i+1)*w+delta, y(entry.y)] + args[0] += w if trans + if i == @options.windowSize - 1 + @ctx.moveTo.apply @ctx, args + else + @ctx.lineTo.apply @ctx, args + + @ctx.stroke() + + super() diff --git a/debian/missing-sources/epoch/tests/render/basic/area.html b/debian/missing-sources/epoch/tests/render/basic/area.html new file mode 100644 index 0000000..ea23851 --- /dev/null +++ b/debian/missing-sources/epoch/tests/render/basic/area.html @@ -0,0 +1,450 @@ + + + + + + + + + + + +

Basic Area Chart Test

+ + +
    +
  1. Single Series
  2. +
  3. Single Series II
  4. +
  5. Multi Series
  6. +
  7. Multi Series II
  8. +
  9. Single Series Transition
  10. +
  11. Multi Series Transition
  12. +
  13. Single Series to Multi Series Transition
  14. +
  15. Layer Color Override
  16. +
  17. Categorical Color Switching
  18. +
  19. Multi Series without Labels
  20. +
  21. Hide/Show Layers
  22. +
  23. Data Format
  24. +
+ + + +
+

1. Single Series

+

It should display a plot of y = cos(x) + 1 over the range [0, 2π).

+
+
+ + + + + +
+

2. Single Series II

+

It should display a plot of y = sin(x) + 1 over the range [0, 2π).

+
+
+ + + + + +
+

3. Multi-series Plot

+

+ It should display a plot of the following functions stacked atop one another: +

    +
  • y = x
  • +
  • y = 2x
  • +
  • y = 3x
  • +
+ over the range [0, 10). +

+
+
+ + + + + +
+

4. Multi-series Plot II

+

+ It should display a plot of the following functions stacked atop one another: +

    +
  • y = |x|
  • +
  • y = x2
  • +
  • y = |x3|
  • +
+ over the range [-1, 1). +

+
+
+ + + + + +
+

5. Single Series Transition

+

+ It should correctly transition between the plots y = |x| over the range [-10, 10) and y = x2 over the range [-20, 20). The transition should be initiated when pressing the buttons below the plot. +

+
+

+ + +

+
+ + + + +
+

6. Multi Series Transition

+

+ It should correctly render and transition between Set A: +

    +
  • y = x
  • +
  • y = 2*x
  • +
  • y = 3*x
  • +
+ over the range [1, 100). and Set B: +
    +
  • y = ln(x)
  • +
  • y = 2*ln(x)
  • +
  • y = 3*ln(x)
  • +
+ over the range [1, 100). The transition should be initiated when pressing the buttons below the plot. +
+

+ + +

+
+ + + + + +
+

7. Single Series to Multi Series Transition

+

+ It should correctly transition between a single series, plotting the functions: +

    +
  • y = x2 - 0.5*x
  • +
+ To a multi series set, plotting the functions: +
    +
  • y = ln(x)
  • +
  • y = x
  • +
  • y = x * ln(x)
  • +
+ over the range [1, 4) for all plots. The transition should be initiated when pressing the buttons below the plot. +

+
+

+ + +

+
+ + + + +
+

8. Layer Color Override

+

+ It should display the first layer of the plot as pink, the second layer as green, and the third layer as blue. +

+
+
+ + + + + + +
+

9. Categorical Color Switching

+

+ It should change layer colors automatically when switching between the following categorical color classes on the containing element: +

    +
  • category10
  • +
  • category20
  • +
  • category20b
  • +
  • category20c
  • +
+ The colors should change when pressing the buttons for each categorical type below the chart. +

+ +
+ +

+ + + + +

+
+ + + +
+

10. Multi Series without Labels

+

+ Correctly render a multi-series plot of: +

    +
  • y = sin(x) + 1
  • +
  • y = cos(x) + 1
  • +
+ where the layers are given without labels. +

+
+
+ + + +
+

11. Hide/Show Layers

+

Correctly hide and show multiple layers

+
+

+ + + +

+
+ + + + diff --git a/debian/missing-sources/epoch/tests/render/basic/bar.html b/debian/missing-sources/epoch/tests/render/basic/bar.html new file mode 100644 index 0000000..07b5122 --- /dev/null +++ b/debian/missing-sources/epoch/tests/render/basic/bar.html @@ -0,0 +1,745 @@ + + + + + + + + + + + +

Basic Bar Chart Test

+ + +
    +
  1. Single Series
  2. +
  3. Single Series II
  4. +
  5. Multi Series
  6. +
  7. Multi Series II
  8. +
  9. Single Series Transition
  10. +
  11. Multi Series Transition
  12. +
  13. Single Series to Multi Series Transition
  14. +
  15. Layer Color Override
  16. +
  17. Categorical Color Switching
  18. +
  19. Multi Series without Labels
  20. +
  21. Horizontally Oriented Single Series
  22. +
  23. Horizontally Oriented Multi Series
  24. +
  25. Horizontally Oriented Multi Series Transition
  26. +
  27. Vertical to Horizontal Transition
  28. +
  29. Padding Changes
  30. +
  31. Hide/Show Layers
  32. +
  33. Data Formatting
  34. +
  35. Many bars
  36. +
+ + + +
+

1. Single Series

+

Display a plot of y = cos(x) + 1 over the range [0, 2π).

+
+
+ + + + + +
+

2. Single Series II

+

Display a plot of y = sin(x) + 1 over the range [0, 2π).

+
+
+ + + + + +
+

3. Multi-series Plot

+

+ Display a plot of the following functions stacked atop one another: +

    +
  • y = x
  • +
  • y = 2x
  • +
  • y = 3x
  • +
+ over the range [0, 10). +

+
+
+ + + + + +
+

4. Multi-series Plot II

+

+ Display a plot of the following functions stacked atop one another: +

    +
  • y = |x|
  • +
  • y = x2
  • +
  • y = |x3|
  • +
+ over the range [-1, 1). +

+
+
+ + + + + +
+

5. Single Series Transition

+

+ Correctly transition between the plots y = |x| over the range [-10, 10) and y = x2 over the range [-20, 20). The transition is initiated by pressing the buttons below the plot. +

+
+

+ + +

+
+ + + + +
+

6. Multi Series Transition

+

+ Correctly render and transition between Set A: +

    +
  • y = x
  • +
  • y = 2*x
  • +
  • y = 3*x
  • +
+ over the range [1, 100). and Set B: +
    +
  • y = ln(x)
  • +
  • y = 2*ln(x)
  • +
  • y = 3*ln(x)
  • +
+ over the range [1, 100). The transition is initiated by pressing the buttons below the plot. +
+

+ + +

+
+ + + + + +
+

7. Single Series to Multi Series Transition

+

+ Correctly transition between a single series, plotting the functions: +

    +
  • y = x2 - 0.5*x
  • +
+ To a multi series set, plotting the functions: +
    +
  • y = ln(x)
  • +
  • y = x
  • +
  • y = x * ln(x)
  • +
+ over the range [1, 4) for all plots. The transition is initiated by pressing the buttons below the plot. +

+
+

+ + +

+
+ + + + +
+

8. Layer Color Override

+

+ Display the first layer of the plot as pink, the second layer as green, and the third layer as blue. +

+
+
+ + + + + + +
+

9. Categorical Color Switching

+

+ Change layer colors automatically when switching between the following categorical color classes on the containing element: +

    +
  • category10
  • +
  • category20
  • +
  • category20b
  • +
  • category20c
  • +
+ Change the categorical colors by pressing the buttons below the chart. +

+ +
+ +

+ + + + +

+
+ + + +
+

10. Multi Series without Labels

+

+ Correctly render a multi-series plot of: +

    +
  • y = sin(x) + 1
  • +
  • y = cos(x) + 1
  • +
+ where the layers are given without labels. +

+
+
+ + + +
+

11. Horizontally Oriented Single Series

+

+ Correctly render the single series plot of: +

    +
  • A - 20
  • +
  • B - 30
  • +
  • C - 60
  • +
+ using a horizontal orientation. +

+
+
+ + + +
+

12. Horizontally Oriented Multi Series

+

+ Correctly render the multi series plot of: +

    +
  • A - 10, 30
  • +
  • B - 20, 50
  • +
  • C - 60, 10
  • +
+ using a horizontal orientation. +

+
+
+ + + +
+

13. Horizontally Oriented Multi Series Transition

+

+ Correctly render the Horizontally oriented multi series plot of: +

    +
  • A - 10, 10
  • +
  • B - 20, 20
  • +
  • C - 30, 30
  • +
+ and transition to the single series plot: +
    +
  • A - 5
  • +
  • B - 10
  • +
  • C - 40
  • +
+

+
+

+ + +

+
+ + +
+

14. Vertical to Horizontal Transition

+
+

+ + +

+
+ + +
+

15. Padding Changes

+
+

+ + +

+
+ + +
+

16. Hide/Show Layers

+
+

+ + + + | + +

+
+ + +
+

17. Data Formatting

+

Ensure the chart works with the array, tuple, and key-value data formats.

+

+
+
+
+ + +
+

18. Bar Ticks

+

Ensure that we can use ticks option with bar charts.

+

+
+ + + diff --git a/debian/missing-sources/epoch/tests/render/basic/histogram.html b/debian/missing-sources/epoch/tests/render/basic/histogram.html new file mode 100644 index 0000000..505054e --- /dev/null +++ b/debian/missing-sources/epoch/tests/render/basic/histogram.html @@ -0,0 +1,155 @@ + + + + + + + + + + + + + + +

Basic Bar Chart Test

+ + +
    +
  1. Beta(2, 5)
  2. +
  3. Beta(2, 5) Horizontal
  4. +
  5. Option: buckets
  6. +
  7. Options: bucketRange & cutOutliers
  8. +
+ + +
+

1. Beta(2, 5)

+

Plot a random selection of points from the Beta(2, 5) distribution as a histogram.

+
+

+
+ + + +
+

2. Beta(2, 5) Horizontal

+

Plot a random selection of points from Beta(2, 5) and display in a horizontal histogram.

+
+

+
+ + + +
+

3. Option: buckets

+

Plot Beta(2, 2) and change number of buckets on the fly.

+
+

+ + + + + | + +

+
+ + + +
+

4. Options: bucketRange & cutOutliers

+

Plot beta(2, 5) and change the bucket range on the fly.

+
+

+ + + | + + | + +

+
+ + + diff --git a/debian/missing-sources/epoch/tests/render/basic/line.html b/debian/missing-sources/epoch/tests/render/basic/line.html new file mode 100644 index 0000000..37de38d --- /dev/null +++ b/debian/missing-sources/epoch/tests/render/basic/line.html @@ -0,0 +1,471 @@ + + + + + + + + + + + +

Basic Line Chart Test

+ + +
    +
  1. Single Series
  2. +
  3. Single Series II
  4. +
  5. Multi Series
  6. +
  7. Single Series Transition
  8. +
  9. Multi Series Transition
  10. +
  11. Single to Multi Series Transition
  12. +
  13. Color Override
  14. +
  15. Categorical Colors
  16. +
  17. Multi Series without Labels
  18. +
  19. Multi Series with Fixed Domain
  20. +
  21. Show/Hide Layers
  22. +
+ + +
+

1. Single Series

+

Display a plot of y = cos(x) over the range [-2π, 2π).

+
+
+ + + + +
+

2. Single Series II

+

Display a plot of y = ex*sin(x) from [0, &pi).

+
+
+ + + + +
+

3. Multi Series

+

+ Display a plot of the following functions over the range [0, 2π]: +

    +
  • x*sin(x)
  • +
  • x*cos(x)
  • +
+

+
+
+ + + + +
+

4. Single Series Transition

+

+ Correctly transition between the functions y = 1 / x and y = x2 over the range [1, 2). + Use the buttons below the chart to initiate the transitions. +

+
+

+ + +

+
+ + + + +
+

5. Multi Series Transition

+

+ Correctly transition between Set A: +

    +
  • y = sin(x)
  • +
  • y = x - x3/3! + x5/5!
  • +
+ Set B: +
    +
  • y = cos(x)
  • +
  • y = 1 - x2/2! + x4/4!
  • +
+ and Set C: +
    +
  • y = sin(x) - (x - x3/3! + x5/5!)
  • +
  • y = cos(x) - (1 - x2/2! + x4/4!)
  • +
+

+
+

+ + + +

+
+ + + + +
+

6. Single to Multi Series Transition

+

+ Correctly transition between Set A: +

    +
  • y = ln(x)
  • +
+ Set B: +
    +
  • y = ln(x)
  • +
  • y = x * ln(x)
  • +
  • y = x * ln(x)2
  • +
+

+
+

+ + +

+
+ + + + +
+

7. Color Override

+

+ Display the first layer of the plot as pink, the second layer as green, and the third layer as blue. +

+
+
+ + + + + + +
+

Categorical Colors

+

+ Change layer colors automatically when switching between the following categorical color classes on the containing element: +

    +
  • category10
  • +
  • category20
  • +
  • category20b
  • +
  • category20c
  • +
+ Change the categorical colors by pressing the buttons below the chart. +

+
+

+ + + + +

+
+ + + + + +
+

9. Multi Series without Labels

+

+ Correctly render a multi-series plot of: +

    +
  • y = sin(x)
  • +
  • y = cos(x)
  • +
+ where the layers are given without labels. +

+
+
+ + + +
+

10. Multi Series with Fixed Domain

+

+ Display a plot of the following functions: +

    +
  • x*sin(x)
  • +
  • x*cos(x)
  • +
+ On the domain [0, 5] and range [0, 4]. +

+
+
+ + + + +
+

11. Show/Hide Layers

+
+

+ + + +

+
+ + + +
+

12. Multi-axis

+
+
+ + + diff --git a/debian/missing-sources/epoch/tests/render/basic/model.html b/debian/missing-sources/epoch/tests/render/basic/model.html new file mode 100644 index 0000000..2814fd8 --- /dev/null +++ b/debian/missing-sources/epoch/tests/render/basic/model.html @@ -0,0 +1,94 @@ + + + + + + + + + + + + +

Basic Chart Model / Data Test

+ + +

+ + + +

+ +
+
+
+ + + + \ No newline at end of file diff --git a/debian/missing-sources/epoch/tests/render/basic/options.html b/debian/missing-sources/epoch/tests/render/basic/options.html new file mode 100644 index 0000000..5ff2d7e --- /dev/null +++ b/debian/missing-sources/epoch/tests/render/basic/options.html @@ -0,0 +1,267 @@ + + + + + + + + + + + + +

Basic Chart Options and Events

+ +
    +
  1. Axes
  2. +
  3. Margins
  4. +
  5. Ticks and Tick Formats
  6. +
  7. Resize
  8. +
  9. Domain
  10. +
  11. Range
  12. +
+ + +
+

1. Axes

+

+ Correctly add and remove axes when options are set. +

+
+
+ + + + + + +
+
+ + + + +
+

2. Margins

+

+ Correctly resize margins when options are set. +

+
+
+ + + +
+
+ + + +
+

3. Ticks and Tick Formats

+

+ Correctly resize margins when options are set. +

+
+
+ + +
+
+ + + +
+

4. Resize

+

+ Correctly resize the chart when the width and height options are set. +

+
+
+ + +
+
+ + + +
+

6. Option: domain

+
+

+ + +

+
+ + + + +
+

7. Option: range

+
+

+ + +

+
+ + + + \ No newline at end of file diff --git a/debian/missing-sources/epoch/tests/render/basic/pie.html b/debian/missing-sources/epoch/tests/render/basic/pie.html new file mode 100644 index 0000000..fe105e8 --- /dev/null +++ b/debian/missing-sources/epoch/tests/render/basic/pie.html @@ -0,0 +1,346 @@ + + + + + + + + + + + +

Basic Pie Chart Test

+ +
    +
  1. Basic Pie Test
  2. +
  3. Basic Donut Test
  4. +
  5. Pie Tranisition I
  6. +
  7. Pie Tranisition II
  8. +
  9. Color Override
  10. +
  11. Categorical Colors
  12. +
  13. Pie Chart Layers without Labels
  14. +
  15. Margin Changes
  16. +
  17. Inner Changes
  18. +
  19. Show/Hide Layers
  20. +
+ + +
+

Basic Pie Test

+

+ Correctly render a pie chart with three categories: +

    +
  • A - 20
  • +
  • B - 45
  • +
  • C - 35
  • +
+

+
+
+ + + + +
+

2. Basic Donut Test

+

+ Correctly render a donut chart with three categories: +

    +
  • A - 50
  • +
  • B - 30
  • +
  • C - 20
  • +
+

+
+
+ + + + +
+

3. Pie Tranisition I

+

+ Correctly transition between set A: +

    +
  • A - 20
  • +
  • B - 80
  • +
+ and set B: +
    +
  • A - 20
  • +
  • B - 30
  • +
  • C - 50
  • +
+ Use the buttons below the chart to initiate the transitions. +

+
+

+ + +

+
+ + + + +
+

4. Pie Tranisition II

+

+ Correctly transition between set A: +

    +
  • A - 20
  • +
  • B - 80
  • +
+ and set B: +
    +
  • A - 50
  • +
  • B - 50
  • +
+ Use the buttons below the chart to initiate the transitions. +

+
+

+ + +

+
+ + + + +
+

5. Color Override

+

+ Override the colors as such: +

    +
  • A - Pink
  • +
  • B - Green
  • +
  • C - Blue
  • +
+

+
+
+ + + + + + +
+

6. Categorical Colors

+

+ Correctly transition between different categorical colors sets. +

+
+

+ + + + +

+
+ + + + + + +
+

7. Pie Chart Layers without Labels

+

+ Correctly render a pie chart with three categories: +

    +
  • 30
  • +
  • 35
  • +
  • 35
  • +
+ when the layers are not provided labels. +

+
+
+ + + + +
+

8. Margin Changes

+
+

+ + +

+
+ + + +
+

9. Inner Changes

+
+

+ + + +

+
+ + + +
+

10. Show/Hide Layers

+
+

+ + + +

+
+ + + \ No newline at end of file diff --git a/debian/missing-sources/epoch/tests/render/basic/scatter.html b/debian/missing-sources/epoch/tests/render/basic/scatter.html new file mode 100644 index 0000000..31f1e42 --- /dev/null +++ b/debian/missing-sources/epoch/tests/render/basic/scatter.html @@ -0,0 +1,398 @@ + + + + + + + + + + + +

Basic Scatter Plot Test

+ +
    +
  1. Single Series
  2. +
  3. Multi Series
  4. +
  5. Single Series Transition
  6. +
  7. Multi Series Transition
  8. +
  9. Multi Series Transition II
  10. +
  11. Color Override
  12. +
  13. Categorical Colors
  14. +
  15. Multi Series without Labels
  16. +
  17. Single Series with Radius
  18. +
  19. Radius Change
  20. +
  21. Show/Hide Layers
  22. +
+ + +
+

1. Single Series

+

Render a single random series scatter plot.

+
+
+ + + +
+

2. Multi Series

+

Render three random scatter series in the same plot.

+
+
+ + + +
+

3. Single Series Transition

+

+ Transition from one random series to another random series. Use the + buttons below the plot to initiate the transition. +

+
+

+ + +

+
+ + + +
+

4. Multi Series Transition

+

+ Transition from a set of multiple randoms series random series to another of multiple random series. Use the + buttons below the plot to initiate the transition. +

+
+

+ + +

+
+ + + +
+

5. Multi Series Transition II

+

+ Transition from a multi-series set of random data to a single series set of random data. +

+
+

+ + +

+
+ + + +
+

6. Color Override

+

The first series should be pink, the second green, and thrid blue.

+
+
+ + + + +
+

7. Categorical Colors

+

+ Correctly transition between different categorical color sets. +

+
+

+ + + + +

+
+ + + +
+

8. Multi Series without Labels

+

Correctly render two random scatter plots when labels are not specified for the layers.

+
+
+ + + +
+

9. Single Series with Radius

+

Render a single random series scatter plot with different radii.

+
+

+ +

+
+ + + +
+

10. Radius Change

+
+

+ + + + +

+
+ + + +
+

11. Show/Hide Layers

+
+

+ + + +

+
+ + + + diff --git a/debian/missing-sources/epoch/tests/render/css/tests.css b/debian/missing-sources/epoch/tests/render/css/tests.css new file mode 100644 index 0000000..7f4099e --- /dev/null +++ b/debian/missing-sources/epoch/tests/render/css/tests.css @@ -0,0 +1,12 @@ +body { + font-family: helvetica; +} + +/* Default test chart height of 220px. */ +.test .epoch { + height: 220px; +} + +.broken { + color: red; +} diff --git a/debian/missing-sources/epoch/tests/render/index.html b/debian/missing-sources/epoch/tests/render/index.html new file mode 100644 index 0000000..1ab15c1 --- /dev/null +++ b/debian/missing-sources/epoch/tests/render/index.html @@ -0,0 +1,57 @@ + + + + Epoch - Chart Rendering Tests + + + + + + + +

Epoch Chart Rendering Tests

+ +

Basic Charts

+

+

+

+ +

Real-time Charts

+

+

+

+ +

Themes

+

+

+

+ +

Data / Models

+

+

+

+ + diff --git a/debian/missing-sources/epoch/tests/render/js/data.js b/debian/missing-sources/epoch/tests/render/js/data.js new file mode 100644 index 0000000..a49da68 --- /dev/null +++ b/debian/missing-sources/epoch/tests/render/js/data.js @@ -0,0 +1,210 @@ +(function() { + + // Quick data generator + Data = function() { + this.layers = [] + }; + + Data.prototype.add = function(fn) { + fn = fn ? fn : function(x) { return x; }; + this.layers.push(fn); + return this; + }; + + Data.prototype.get = function(domain, step) { + domain = domain ? domain : [0, 10]; + step = step ? step : 1; + + var data = [] + for (var i = 0; i < this.layers.length; i++) { + layer = { label: String.fromCharCode(i + 65), values: [] }; + for (var x = domain[0]; x < domain[1]; x += step) { + layer.values.push({ x: x, y: this.layers[i](x) }); + } + data.push(layer); + } + return data; + }; + + Data.prototype.random = function(entries, domain, range) { + entries = entries ? entries : 50; + domain = domain ? domain : [0, 100]; + range = range ? range : [0, 100]; + + var values = []; + for (var i = 0; i < entries; i++) { + var x = (domain[1] - domain[0]) * Math.random() + domain[0], + y = (range[1] - range[0]) * Math.random() + range[1]; + values.push({ x: x, y: y }); + } + + return [{ label: 'A', values: values }]; + }; + + Data.prototype.multiRandom = function(numSeries, entries, domain, range) { + numSeries = numSeries ? numSeries : 3; + entries = entries ? entries : 50; + domain = domain ? domain : [0, 100]; + range = range ? range : [0, 100]; + + var data = []; + + for (var j = 0; j < numSeries; j++) { + var layer = { label: String.fromCharCode(65 + j), values: [] }; + for (var i = 0; i < entries; i++) { + var x = (domain[1] - domain[0]) * Math.random() + domain[0], + y = (range[1] - range[0]) * Math.random() + range[1]; + layer.values.push({ x: x, y: y }); + } + data.push(layer); + } + + return data; + }; + + window.data = function() { return new Data(); }; + + + // Quick real-time data generator + Time = function() { + Data.call(this); + }; + + Time.prototype = new Data() + + Time.prototype.get = function(domain, step) { + var data = Data.prototype.get.apply(this, arguments), + time = parseInt(new Date().getTime() / 1000); + + for (var i = 0; i < data[0].values.length; i++) { + for (var j = 0; j < this.layers.length; j++) { + delete data[j].values[i].x; + data[j].values[i].time = time + i; + } + } + + this.currentTime = time; + this.lastX = domain[1]; + + return data; + }; + + Time.prototype.next = function(step) { + this.currentTime++; + this.lastX += (step ? step : 1); + + var data = []; + for (var j = 0; j < this.layers.length; j++) { + data.push({ time: this.currentTime, y: this.layers[j](this.lastX) }) + } + + return data; + } + + window.time = function() { return new Time(); }; + + + + + window.nextTime = (function() { + var currentTime = parseInt(new Date().getTime() / 1000); + return function() { return currentTime++; } + })(); + + + /* + * Normal distribution random histogram data generator. + */ + var NormalData = function(layers) { + this.layers = layers; + this.timestamp = ((new Date()).getTime() / 1000)|0; + }; + + var normal = function() { + var U = Math.random(), + V = Math.random(); + return Math.sqrt(-2*Math.log(U)) * Math.cos(2*Math.PI*V); + }; + + NormalData.prototype.sample = function() { + return parseInt(normal() * 12.5 + 50); + } + + NormalData.prototype.rand = function() { + var histogram = {}; + + for (var i = 0; i < 1000; i ++) { + var r = this.sample(); + if (!histogram[r]) { + histogram[r] = 1; + } + else { + histogram[r]++; + } + } + + return histogram; + }; + + NormalData.prototype.history = function(entries) { + if (typeof(entries) != 'number' || !entries) { + entries = 60; + } + + var history = []; + for (var k = 0; k < this.layers; k++) { + history.push({ label: String.fromCharCode(65+k), values: [] }); + } + + for (var i = 0; i < entries; i++) { + for (var j = 0; j < this.layers; j++) { + history[j].values.push({time: this.timestamp, histogram: this.rand()}); + } + this.timestamp++; + } + + return history; + }; + + NormalData.prototype.next = function() { + var entry = []; + for (var i = 0; i < this.layers; i++) { + entry.push({ time: this.timestamp, histogram: this.rand() }); + } + this.timestamp++; + return entry; + } + + window.NormalData = NormalData; + + + /* + * Beta distribution histogram data generator. + */ + var BetaData = function(alpha, beta, layers) { + this.alpha = alpha; + this.beta = beta; + this.layers = layers; + this.timestamp = ((new Date()).getTime() / 1000)|0; + }; + + BetaData.prototype = new NormalData(); + + BetaData.prototype.sample = function() { + var X = 0, + Y = 0; + + for (var j = 1; j <= this.alpha; j++) + X += -Math.log(1 - Math.random()); + + for (var j = 1; j <= this.beta; j++) + Y += -Math.log(1 - Math.random()); + + return parseInt(100 * X / (X + Y)); + } + + window.BetaData = BetaData; + +})(); + + diff --git a/debian/missing-sources/epoch/tests/render/real-time/area.html b/debian/missing-sources/epoch/tests/render/real-time/area.html new file mode 100644 index 0000000..b66ab7d --- /dev/null +++ b/debian/missing-sources/epoch/tests/render/real-time/area.html @@ -0,0 +1,494 @@ + + + + + + + + + + + + +

Real-time Area Plot Test

+ +
    +
  1. Single Series
  2. +
  3. Single Series Single Transition
  4. +
  5. Single Seires Stream
  6. +
  7. Multi Series
  8. +
  9. Multi Series Single Transition
  10. +
  11. Multi Seires Stream
  12. +
  13. Color Override
  14. +
  15. Categorical Colors
  16. +
  17. Show/Hide Layers
  18. +
+ + +
+

1. Single Series

+

+ Correctly render a single series plot of y = cos(x) + 1 over the range [0, 2π]. +

+
+
+ + + + +
+

2. Single Series Single Transition

+

+ Correctly render a single series plot of y = sin(x) + 1. When the button is pressed push a new data point to the chart and correctly animate/render the transiton. +

+
+

+
+ + + + + +
+

3. Single Seires Stream

+

Correctly play / pause a single series stream of values from the plot y = cos(x) + 1.

+
+

+
+ + + + +
+

4. Multi Series

+

Correctly render a multi series plot of the following functions:

+
    +
  • y = log(x+1)
  • +
  • y = 2*log(x+1)
  • +
  • y = 3*log(x+1)
  • +
+
+
+ + + + + +
+

5. Multi Series Single Transition

+

+ Correctly render a multi series plot of the following functions: +

    +
  • y = x
  • +
  • y = x1.25
  • +
  • y = x1.5
  • +
+ and correctly render/animate the transiton after pressing the "Next" button. +

+
+

+ +

+
+ + + + +
+

6. Multi Seires Stream

+

+ Correctly play / pause a multi series stream of values over the following plots: +

    +
  • x
  • +
  • x * log(x)
  • +
  • x * log2(x)
  • +
+

+
+

+
+ + + + +
+

7. Color Override

+

The first layer should pink, the second green, and the third blue.

+
+
+ + + + + + +
+

8. Categorical Colors

+

Correctly render and switch between different categorical colors. Unlike with the basic charts this doesn't occur just by switching the class name on the containing element. The CSS query engine must be purge and the plot must be manually redrawn, like so:

+Epoch.QueryCSS.purge();
+chart.draw();
+
+

+ +
+ +

+ + + + +

+
+ + + + +
+

9. Show/Hide Layers

+
+

+ + + | + +

+
+ + + +
+

10. Fixed Range

+

+ Render a single series plot of y = cos(x). Instead of automatically setting the range to [-1, 1], the range is manually set to [-2, 5]. +

+
+
+ + + + \ No newline at end of file diff --git a/debian/missing-sources/epoch/tests/render/real-time/bar.html b/debian/missing-sources/epoch/tests/render/real-time/bar.html new file mode 100644 index 0000000..952119c --- /dev/null +++ b/debian/missing-sources/epoch/tests/render/real-time/bar.html @@ -0,0 +1,484 @@ + + + + + + + + + + + + +

Real-time Bar Test

+ +
    +
  1. Single Series
  2. +
  3. Single Series Single Transition
  4. +
  5. Single Seires Stream
  6. +
  7. Multi Series
  8. +
  9. Multi Series Single Transition
  10. +
  11. Multi Seires Stream
  12. +
  13. Color Override
  14. +
  15. Categorical Colors
  16. +
  17. Show/Hide Layers
  18. +
+ + +
+

1. Single Series

+

+ Correctly render a single series plot of y = cos(x) + 1 over the range [0, 2π]. +

+
+
+ + + + +
+

2. Single Series Single Transition

+

+ Correctly render a single series plot of y = sin(x) + 1. When the button is pressed push a new data point to the chart and correctly animate/render the transiton. +

+
+

+
+ + + + + +
+

3. Single Seires Stream

+

Correctly play / pause a single series stream of values from the plot y = cos(x) + 1.

+
+

+
+ + + + +
+

4. Multi Series

+

Correctly render a multi series plot of the following functions:

+
    +
  • y = log(x+1)
  • +
  • y = 2*log(x+1)
  • +
  • y = 3*log(x+1)
  • +
+
+
+ + + + + +
+

5. Multi Series Single Transition

+

+ Correctly render a multi series plot of the following functions: +

    +
  • y = x
  • +
  • y = x1.25
  • +
  • y = x1.5
  • +
+ and correctly render/animate the transiton after pressing the "Next" button. +

+
+

+ +

+
+ + + + +
+

6. Multi Seires Stream

+

+ Correctly play / pause a multi series stream of values over the following plots: +

    +
  • x
  • +
  • x * log(x)
  • +
  • x * log2(x)
  • +
+

+
+

+
+ + + + +
+

7. Color Override

+

The first layer should pink, the second green, and the third blue.

+
+
+ + + + + + +
+

8. Categorical Colors

+

Correctly render and switch between different categorical colors. Unlike with the basic charts this doesn't occur just by switching the class name on the containing element. The CSS query engine must be purge and the plot must be manually redrawn, like so:

+Epoch.QueryCSS.purge();
+chart.draw();
+
+

+ +
+ +

+ + + + +

+
+ + + + +
+

9. Show/Hide Layers

+
+

+ + + | + +

+
+ + + +
+

10. Fixed Range

+

+ Render a single series plot of y = cos(x) + 1. Instead of automatically setting the range to [0, 2], the range is manually set to [0, 5]. +

+
+
+ + + + \ No newline at end of file diff --git a/debian/missing-sources/epoch/tests/render/real-time/gauge.html b/debian/missing-sources/epoch/tests/render/real-time/gauge.html new file mode 100644 index 0000000..614334d --- /dev/null +++ b/debian/missing-sources/epoch/tests/render/real-time/gauge.html @@ -0,0 +1,283 @@ + + + + + + + + + + + + +

Real-time Gauge Test

+ +
    +
  1. Single Value Display
  2. +
  3. Single Transition
  4. +
  5. Stream Transition
  6. +
  7. Gauge Sizes
  8. +
  9. Color Override
  10. +
  11. Option: domain
  12. +
  13. Option: ticks
  14. +
  15. Option: tickSize
  16. +
  17. Option: tickOffset
  18. +
  19. Option: format
  20. +
+ + +
+

1. Single Value Display

+

Display a single value of 25%

+
+
+ + + + +
+

2. Single Transition

+

Display value of 0% and transition to a random value when the button is pressed.

+
+

+
+ + + + +
+

3. Stream Transition

+

Display value of 0% and transition to a random value every second when the button is pressed.

+
+

+
+ + + +
+

4. Gauge Sizes

+

Display the four built-in gauge sizes in this order: tiny, small, medium, large.

+
+
+
+
+
+ + + +
+

5. Color Override

+

+ Override the basic gauge styles with the following +

    +
  • Outer arc 8px thickness colored green
  • +
  • Inner arc 2px thickness colored orange
  • +
  • Ticks should be 3px in width and red
  • +
  • Needle colored blue.
  • +
  • Needle base colored black
  • +
+

+
+
+ + + + + + + + + + +
+

6. Option: domain

+
+

+ | + + +

+
+ + + +
+

7. Option: ticks

+
+

+ | + + + + +

+
+ + + +
+

8. Option: tickSize

+
+

+ | + + + + +

+
+ + + +
+

9. Option: tickOffset

+
+

+ | + + + +

+
+ + + +
+

10. Option: format

+
+

+ | + + +

+
+ + + diff --git a/debian/missing-sources/epoch/tests/render/real-time/heatmap.html b/debian/missing-sources/epoch/tests/render/real-time/heatmap.html new file mode 100644 index 0000000..ddabe2e --- /dev/null +++ b/debian/missing-sources/epoch/tests/render/real-time/heatmap.html @@ -0,0 +1,559 @@ + + + + + + + + + + + +

Real-time Heatmap Plot Test

+ +
    +
  1. Single Series - Normal
  2. +
  3. Single Series - Beta
  4. +
  5. Single Series - Normal, Single Update
  6. +
  7. Single Series - Beta, Stream Update
  8. +
  9. Single Series - Color Override
  10. +
  11. Multi Series - Normal + Beta
  12. +
  13. Multi Series Color Override
  14. +
  15. Range Independent Bucketing
  16. +
  17. Option: buckets
  18. +
  19. Option: bucketRange
  20. +
  21. Option: bucketPadding
  22. +
  23. Option: cutOutliers
  24. +
  25. Option: opacity
  26. +
  27. Show/Hide Layer
  28. +
+ + +
+

1. Single Series - Normal

+

Select random values from the normal distribution and display them with the heatmap.

+
+
+ + + +
+

2. Single Series - Beta

+

Select random values from the Beta(2, 5) distribution and display them with the heatmap.

+
+
+ + + +
+

3. Single Sieres - Normal, Single Update

+

+ Plot the normal distribution and transition a new element when the button is pressed. +

+
+

+
+ + + +
+

4. Single Series - Beta, Stream Update

+

+ Plot the Beta(2, 5) distribution and begin streaming new elements each second once the + button is pressed. +

+
+ +
+ + + + +
+

5. Single Series - Color Override

+

Change the bucket base color to orange and plot the Beta(2, 2) distribution.

+
+
+ + + + + + +
+

6. Multi Series - Normal + Beta

+

+ Plot the Beta(2,5) and Normal distributions as two layers in a single heatmap. Stream elements to the plot + by pressing the button. The normal distribution layer is in blue and the beta in green. +

+
+ +
+ + + +
+

7. Multi Series Color Override

+

+ Plot the normal distribution and the Beta(2, 5) distribution overrding normal to be in red, and beta to + be in purple. +

+
+
+ + + + + + +
+

8. Range Independent Bucketing (Issue #41)

+

+ Discrete bucketing of sparse histogram values should produce similar looking graphs regardless + of numeric relation between the range of the plot and the number of buckets. +

+

Range [0, 100] with 20 buckets

+
+ +

Range [0, 100] with 45 buckets

+
+
+ + + +
+

9. Option: buckets

+
+

+ | + + + +

+
+ + + +
+

10. Option: bucketRange

+
+

+ | + + + + + +

+
+ + + +
+

11. Option: bucketPadding

+
+

+ | + + + + +

+
+ + + +
+

12. Option: cutOutliers

+
+

+ | + + +

+
+ + +
+

13. Option: opacity

+
+

+ | + +

+
+ + +
+

14. Show/Hide Layer

+
+

+ + | + +

+
+ + + diff --git a/debian/missing-sources/epoch/tests/render/real-time/line.html b/debian/missing-sources/epoch/tests/render/real-time/line.html new file mode 100644 index 0000000..3346c98 --- /dev/null +++ b/debian/missing-sources/epoch/tests/render/real-time/line.html @@ -0,0 +1,596 @@ + + + + + + + + + + + + +

Real-time Line Plot Test

+ +
    +
  1. Single Series
  2. +
  3. Single Series Single Transition
  4. +
  5. Single Seires Stream
  6. +
  7. Multi Series
  8. +
  9. Multi Series Single Transition
  10. +
  11. Multi Seires Stream
  12. +
  13. Color Override
  14. +
  15. Categorical Colors
  16. +
  17. Show/Hide Layers
  18. +
  19. Show/Hide Layers
  20. +
  21. Multiaxes
  22. +
+ + +
+

1. Single Series

+

+ Correctly render a single series plot of y = cos(x) + 1 over the range [0, 2π]. +

+
+
+ + + + +
+

2. Single Series Single Transition

+

+ Correctly render a single series plot of y = sin(x) + 1. When the button is pressed push a new data point to the chart and correctly animate/render the transiton. +

+
+

+
+ + + + + +
+

3. Single Seires Stream

+

Correctly play / pause a single series stream of values from the plot y = cos(x) + 1.

+
+

+
+ + + + +
+

4. Multi Series

+

Correctly render a multi series plot of the following functions:

+
    +
  • y = log(x+1)
  • +
  • y = 2*log(x+1)
  • +
  • y = 3*log(x+1)
  • +
+
+
+ + + + + +
+

5. Multi Series Single Transition

+

+ Correctly render a multi series plot of the following functions: +

    +
  • y = x
  • +
  • y = x1.25
  • +
  • y = x1.5
  • +
+ and correctly render/animate the transiton after pressing the "Next" button. +

+
+

+ +

+
+ + + + +
+

6. Multi Seires Stream

+

+ Correctly play / pause a multi series stream of values over the following plots: +

    +
  • x
  • +
  • x * log(x)
  • +
  • x * log2(x)
  • +
+

+
+

+
+ + + + +
+

7. Color Override

+

The first layer should pink, the second green, and the third blue.

+
+
+ + + + + + +
+

8. Categorical Colors

+

Correctly render and switch between different categorical colors. Unlike with the basic charts this doesn't occur just by switching the class name on the containing element. The CSS query engine must be purge and the plot must be manually redrawn, like so:

+Epoch.QueryCSS.purge();
+chart.draw();
+
+

+ +
+ +

+ + + + +

+
+ + + + +
+

9. Show/Hide Layers

+
+

+ + + | + +

+
+ + + +
+

10. Fixed Range

+

+ Render a single series plot of y = cos(x). Instead of automatically setting the range to [-1, 1], the range is manually set to [-2, 5]. +

+
+
+ + + + +
+

11. Multi-axes

+

+ Render a plot that uses independent axes ranges for the left and + right sides. +

+
+
+ + + + +
+

12. Multi-axes with Labeled Ranges

+

+ Render a plot that uses independent axes ranges for the left and + right sides that are associated to labels in the chart data. +

+
+
+ + + + diff --git a/debian/missing-sources/epoch/tests/render/real-time/model.html b/debian/missing-sources/epoch/tests/render/real-time/model.html new file mode 100644 index 0000000..6f53963 --- /dev/null +++ b/debian/missing-sources/epoch/tests/render/real-time/model.html @@ -0,0 +1,85 @@ + + + + + + + + + + + + +

Real-time Chart Model / Data Test

+ + +

+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/debian/missing-sources/epoch/tests/render/real-time/options.html b/debian/missing-sources/epoch/tests/render/real-time/options.html new file mode 100644 index 0000000..a2da792 --- /dev/null +++ b/debian/missing-sources/epoch/tests/render/real-time/options.html @@ -0,0 +1,299 @@ + + + + + + + + + + + + +

Real-time Chart Options and Events

+ +
    +
  1. Resize
  2. +
  3. Axes
  4. +
  5. Ticks
  6. +
  7. Tick Formats
  8. +
  9. Margins
  10. +
+ + +
+

1. Resize

+

Correctly Resize a Real-time Chart.

+
+

+ | + + + +

+
+ + + +
+

2. Axes

+
+
+ | + + + + + + +
+
+ + + +
+

3. Ticks

+
+

+ | + + +

+
+ + + +
+

4. Tick Formats

+
+

+ | + + +

+
+ + + +
+

5. Margins

+
+

+ | + + + +

+
+ + + \ No newline at end of file diff --git a/debian/missing-sources/epoch/tests/render/themes/dark.html b/debian/missing-sources/epoch/tests/render/themes/dark.html new file mode 100644 index 0000000..265ad93 --- /dev/null +++ b/debian/missing-sources/epoch/tests/render/themes/dark.html @@ -0,0 +1,211 @@ + + + + + + + + + + + + +

Dark Theme

+ +

Category 10

+
+
+ +
+ +
+ + + + +

Category 20

+
+
+
+
+
+ +
+ +
+ + + +

Category 20b

+
+
+
+
+
+ +
+ +
+ + + +

Category 20c

+
+
+
+
+
+ +
+ +
+ + +

Chart Examples

+ +
+ + +
+ + + +
+ + +
+ + + + + + \ No newline at end of file diff --git a/debian/missing-sources/epoch/tests/render/themes/default.html b/debian/missing-sources/epoch/tests/render/themes/default.html new file mode 100644 index 0000000..9e8bd4e --- /dev/null +++ b/debian/missing-sources/epoch/tests/render/themes/default.html @@ -0,0 +1,70 @@ + + + + + + + + + + + + + +

Default Theme

+ +

Category 10

+
+
+ +

Category 20

+
+
+
+
+
+ +

Category 20b

+
+
+
+
+
+ +

Category 20c

+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/debian/missing-sources/epoch/tests/unit/core/charts.coffee b/debian/missing-sources/epoch/tests/unit/core/charts.coffee new file mode 100644 index 0000000..0f0bb9c --- /dev/null +++ b/debian/missing-sources/epoch/tests/unit/core/charts.coffee @@ -0,0 +1,573 @@ +sinon = require 'sinon' + +describe 'Epoch.Chart', -> + [defaultWidth, defaultHeight] = [320, 240] + + describe 'Base', -> + [testDivWidth, testDivHeight] = [800, 200] + [resizeDivWidth, resizeDivHeight] = [200, 200] + + before (done) -> + d3.select(doc.body).append('div').attr('id', 'testDiv').style + width: "#{testDivWidth}px" + height: "#{testDivHeight}px" + + d3.select(doc.body).append('div').attr('id', 'resizeDiv').style + width: "#{resizeDivWidth}px" + height: "#{resizeDivHeight}px" + + done() + + after (done) -> + d3.select('#testDiv').remove() + d3.select('#resizeDiv').remove() + done() + + describe 'constructor', -> + it 'should set default dimensions', -> + c = new Epoch.Chart.Base() + assert.equal c.width, defaultWidth, 'Did not set default width' + assert.equal c.height, defaultHeight, 'Did not set default height' + + it 'should allow dimensions to be set via options', -> + [width, height] = [500, 780] + c = new Epoch.Chart.Base({ width: width, height: height }) + assert.equal c.width, width, "Did not set width to #{width}" + assert.equal c.height, height, "Did not set height to #{height}" + + it 'should use the dimensions of the given element when applicable', -> + c = new Epoch.Chart.Base({ el: '#testDiv' }) + assert.equal c.width, testDivWidth, "Did not set width to that of the div" + assert.equal c.height, testDivHeight, "Did not set height to that of the div" + + it 'should set default data to an empty array', -> + c = new Epoch.Chart.Base() + assert.isArray c.data + assert.equal c.data.length, 0 + + it 'should set data when given as an option', -> + data = [ + {label: 'A', values: [{x: 0, y: 0}]}, + {label: 'B', values: [{x: 1, y: 1}]} + ] + c = new Epoch.Chart.Base({ data: data }) + assert.sameMembers(c.data, data) + + describe 'setData', -> + data = [ + {label: 'A', values: [{x: 10, y: 20}]}, + {label: 'B', values: [{x: 10, y: 20}]}, + {label: 'C', values: [{x: 10, y: 20}]} + ] + classNames = [ + ['layer', 'category1', 'a'], + ['layer', 'category2', 'b'], + ['layer', 'category3', 'c'] + ] + chart = null + + before (done) -> + (chart = new Epoch.Chart.Base()).setData(data) + done() + + it 'should set data correctly', -> + assert.sameMembers chart.data, data + for i in [0...data.length] + assert.equal chart.data[i].label, data[i].label + assert.equal chart.data[i].values[0], data[i].values[0] + + it 'should add the correct categories and class names', -> + for i in [0...data.length] + className = chart.data[i].className + for name in classNames[i] + assert (className.indexOf(name) > -1), "Missing class '#{name}'" + + describe 'draw', -> + it "should trigger the 'draw' event", (done) -> + errorCallback = -> + assert 'false', "The 'draw' event was never triggered" + done() + timeout = setTimeout(errorCallback, 1000) + chart = new Epoch.Chart.Base() + chart.on 'draw', -> + clearTimeout(timeout) + done() + chart.draw() + + describe 'update', -> + it 'should call draw by default', (done) -> + errorCallback = -> + assert false, "update did not call draw by default." + done() + timeout = setTimeout(errorCallback, 1000) + chart = new Epoch.Chart.Base() + chart.on 'draw', -> + clearTimeout(timeout) + done() + chart.update([]) + + it 'should not call draw when instructed', (done) -> + chart = new Epoch.Chart.Base() + chart.on 'draw', -> + assert false, "Update incorrectly called draw." + done() + chart.update([], false) + done() + + describe 'extent', -> + data = [ + {values: [ + {x: -1, y: 10}, + {x: 2, y: 20}, + {x: 4, y: 50}, + {x: 8, y: 9900} + ]}, + {values: [ + {x: 1, y: 170}, + {x: 7, y: -2380}, + {x: 19, y: 90}, + {x: 33, y: 17} + ]} + ] + + [xMin, xMax] = [-1, 33] + [yMin, yMax] = [-2380, 9900] + + chart = null + + before (done) -> + chart = new Epoch.Chart.Base({ data: data }) + done() + + it 'should find the correct extent given a y-comparitor', -> + [min, max] = chart.extent (d) -> d.y + assert.equal min, yMin, "Incorrect minimum y" + assert.equal max, yMax, "Incorrect maximum y" + + it 'should find the correct extent give an x-comparitor', -> + [min, max] = chart.extent (d) -> d.x + assert.equal min, xMin, "Incorrect minimum x" + assert.equal max, xMax, "Incorrect maximum x" + + describe 'option', -> + it 'should return all options for the chart when called with no arguments', -> + options = { a: 20, b: 30, c: { d: 40 } } + chart = new Epoch.Chart.Base options + assert.isObject chart.option() + assert.deepEqual chart.option(), options + + it 'should return a single value when given a key', -> + options = { a: 20, b: 30 } + chart = new Epoch.Chart.Base options + assert.equal chart.option('a'), options.a + assert.equal chart.option('b'), options.b + assert.isUndefined chart.option('c') + + it 'should return a deep value when given a hierarchical key', -> + options = + a: + b: 20 + c: + d: 30 + chart = new Epoch.Chart.Base options + assert.equal chart.option('a.b'), options.a.b + assert.equal chart.option('a.c.d'), options.a.c.d + + it 'should set an option given a string and a value', -> + chart = new Epoch.Chart.Base() + [key, value] = ['a', 'hello world'] + chart.option(key, value) + assert.equal chart.option(key), value + + it 'should set a deep value when given a hierarchical key', -> + chart = new Epoch.Chart.Base() + + map = + 'a.b': 'deep' + 'a.c.d': 'deeper' + 'b': 'shallow' + + for key, value of map + chart.option(key, value) + assert.equal chart.option(key), value + + it 'should set all options given an object', -> + original = { a: 20, b: { c: 30 } } + newOptions = { a: 15, d: { e: 10, f: 30 } } + chart = new Epoch.Chart.Base() + chart.option(newOptions) + assert.deepEqual chart.option(), newOptions + + it 'should trigger an event when an option is changed', (done) -> + [key, value] = ['a', 20] + eventName = "option:#{key}" + + errorCallback = -> + assert false, "Setting an option did not trigger the appropriate event: #{eventName}" + done() + timeout = setTimeout(errorCallback, 1000) + + chart = new Epoch.Chart.Base() + chart.on eventName, -> + clearTimeout(timeout) + done() + chart.option(key, value) + + it 'should resize the containing element when the width option is changed', -> + newWidth = resizeDivWidth + 20 + chart = new Epoch.Chart.Base({ el: '#resizeDiv' }) + chart.option('width', newWidth) + assert.equal d3.select('#resizeDiv').width(), newWidth + + it 'should resize the containing element when the height option is changed', -> + newHeight = resizeDivHeight + 20 + chart = new Epoch.Chart.Base({ el: '#resizeDiv' }) + chart.option('height', newHeight) + assert.equal d3.select('#resizeDiv').height(), newHeight + + describe '_getScaleDomain', -> + chart = null + + beforeEach -> + chart = new Epoch.Chart.Base + data: [ layerWithRange(-2030, 5050) ] + + it 'returns a given array', -> + assert.deepEqual(chart._getScaleDomain([0,1]), [0,1]) + + it 'returns @options.range if it is an array', -> + chart.options.range = [-100, 100] + assert.equal chart._getScaleDomain(), chart.options.range + + it 'returns @options.range.left if it is an array', -> + chart.options.range = {left: [-100, 100]} + assert.equal chart._getScaleDomain(), chart.options.range.left + + it 'returns @options.range.right if it is an array', -> + chart.options.range = {right: [-100, 100]} + assert.equal chart._getScaleDomain(), chart.options.range.right + + it 'returns the extent of the data', -> + assert.deepEqual chart._getScaleDomain(), chart.extent((d) -> d.y) + + describe 'with range grouped layers', -> + beforeEach -> + chart = new Epoch.Chart.Base + data: [ + layerWithRange(0, 10, 'left'), + layerWithRange(-5000, 5000, 'right'), + layerWithRange(-10, -5, 'left') + ] + + it 'returns the extent of the layers with the given range label', -> + assert.deepEqual chart._getScaleDomain('left'), [-10, 10] + + it 'returns the extent of the data if the label is invalid', -> + assert.deepEqual chart._getScaleDomain('foobar'), chart.extent((d) -> d.y) + + describe 'layers', -> + [chart, eventChart] = [null, null] + labels = ['A', 'B', 'C'] + data = [ + { label: 'A', data: [{x: 0, y: 0}] }, + { label: 'B', data: [{x: 1, y: 1}] }, + { label: 'C', data: [{x: 2, y: 2}] } + ] + data2 = [ + { label: 'A', data: [{x: 0, y: 0}] } + ] + + before (done) -> + chart = new Epoch.Chart.Base + el: doc.createElement('div') + data: data + eventChart = new Epoch.Chart.Base + el: doc.createElement('div') + data: data2 + done() + + describe '_findLayer', -> + it 'should find layers given a label', -> + for label in labels + layer = chart._findLayer(label) + assert.equal label, layer.label, "Could not find layer with label #{label}" + + it 'should find layers given an index', -> + for i in [0...data.length] + layer = chart._findLayer(i) + assert.equal labels[i], layer.label, "Could not find layer with index #{i}" + + it 'should return null if given an invalid label', -> + assert.isNull (chart._findLayer 'D') + assert.isNull (chart._findLayer 'not a thing') + + it 'should return null if given an index that is out of bounds', -> + assert.isNull (chart._findLayer -1) + assert.isNull (chart._findLayer 5) + + describe 'hideLayer', -> + it 'should hide a visible layer', -> + chart.hideLayer('A') + assert.isFalse chart._findLayer('A').visible + + it 'should keep a hidden layer hidden', -> + assert.isFalse chart._findLayer('A').visible + chart.hideLayer('A') + assert.isFalse chart._findLayer('A').visible + + it 'should trigger layer:hidden when a layer is hidden', (done) -> + errorCallback = -> + assert false, "layer:hidden was not triggered" + done() + timeout = setTimeout errorCallback, 1000 + eventChart.on 'layer:hidden', -> + clearTimeout timeout + done() + eventChart.hideLayer('A') + + describe 'showLayer', -> + it 'should have keep a visible layer visible', -> + assert.isTrue chart._findLayer('B').visible + chart.showLayer('B') + assert.isTrue chart._findLayer('B').visible + + it 'should make a hidden layer visible', -> + assert.isFalse chart._findLayer('A').visible + chart.showLayer('A') + assert.isTrue chart._findLayer('A').visible + + it 'should trigger layer:shown when a layer is shown', (done) -> + errorCallback = -> + assert false, "layer:shown was not triggered" + done() + timeout = setTimeout errorCallback, 1000 + eventChart.on 'layer:shown', -> + clearTimeout timeout + done() + eventChart.showLayer('A') + + describe 'toggleLayer', -> + it 'should hide a visible layer', -> + chart.hideLayer('A') + chart.toggleLayer('A') + assert.isTrue chart._findLayer('A').visible + + it 'should show a hidden layer', -> + chart.showLayer('B') + chart.toggleLayer('B') + assert.isFalse chart._findLayer('B').visible + + describe 'isLayerVisible', -> + it 'should report true if a layer is visible', -> + chart.showLayer('A') + assert.isTrue chart.isLayerVisible('A') + + it 'should report false if a layer is not visible', -> + chart.hideLayer('A') + assert.isFalse chart.isLayerVisible('B') + + describe 'getVisibleLayers', -> + it 'should only return visible layers', -> + chart.showLayer('A') + chart.showLayer('B') + chart.hideLayer('C') + visible = chart.getVisibleLayers() + assert.equal visible.length, 2 + assert.equal visible[0].label, 'A' + assert.equal visible[1].label, 'B' + + describe 'SVG', -> + [containerWidth, containerHeight] = [1000, 280] + container = null + + before (done) -> + container = doc.createElement('DIV') + container.id = 'svg-container' + doc.body.appendChild(container) + d3.select('#svg-container').style + 'width': "#{containerWidth}px" + 'height': "#{containerHeight}px" + done() + + after (done) -> + doc.body.removeChild(container) + done() + + describe 'constructor', -> + it 'should create a new SVG when not given an element', -> + chart = new Epoch.Chart.SVG() + assert.ok chart.svg, "SVG not created" + + it 'should set the default width and height of the SVG', -> + chart = new Epoch.Chart.SVG() + assert.equal chart.svg.attr('width'), defaultWidth, "Default width not set" + assert.equal chart.svg.attr('height'), defaultHeight, "Default height not set" + + it 'should set custom dimensions for the SVG via options', -> + [customWidth, customHeight] = [500, 600] + chart = new Epoch.Chart.SVG({ width: customWidth, height: customHeight }) + assert.equal chart.svg.attr('width'), customWidth, "Custom width not set" + assert.equal chart.svg.attr('height'), customHeight, "Custom height not set" + + it 'should set the container dimensions for the SVG', -> + chart = new Epoch.Chart.SVG({ el: '#svg-container' }) + assert.equal chart.svg.attr('width'), containerWidth + assert.equal chart.svg.attr('height'), containerHeight + + describe 'dimensionsChanged', -> + [width, height, chart] = [200, 100, null] + + before (done) -> + d3.select(doc.body).append('div').attr('id', 'svgResize').style + width: width + 'px' + height: height + 'px' + chart = new Epoch.Chart.SVG { el: '#svgResize' } + done() + + after (done) -> + d3.select('#svgResize').remove() + done() + + it 'should resize the SVG element when the width option is changed', -> + newWidth = width + 500 + chart.option 'width', newWidth + assert.equal chart.svg.attr('width'), newWidth + + it 'should resize the SVG element when the height option is changed', -> + newHeight = height + 500 + chart.option 'height', newHeight + assert.equal chart.svg.attr('height'), newHeight + + describe 'Canvas', -> + [containerWidth, containerHeight] = [1000, 280] + container = null + container_id = 'canvas-container' + containedChart = null + + before (done) -> + container = doc.createElement('DIV') + container.id = container_id + doc.body.appendChild(container) + d3.select('#' + container_id).style + 'width': "#{containerWidth}px" + 'height': "#{containerHeight}px" + + containedChart = new Epoch.Chart.Canvas + el: '#' + container_id + pixelRatio: 1 + + done() + + after (done) -> + doc.body.removeChild(container) + done() + + describe 'constructor', -> + it 'should correctly detect the pixelRatio', -> + chart = new Epoch.Chart.Canvas() + assert.equal chart.pixelRatio, 2 + + it 'should allow the pixelRatio to be explicitly overriden', -> + customPixelRatio = 4.2 + chart = new Epoch.Chart.Canvas({ pixelRatio: customPixelRatio }) + assert.equal chart.pixelRatio, customPixelRatio + + it 'should create a child canvas', -> + chart = new Epoch.Chart.Canvas() + assert.ok chart.canvas, "Did not create canvas" + assert.equal chart.canvas.node().tagName.toLowerCase(), 'canvas', 'Did not create a canvas node' + + it 'should append the child canvas to the containing element', -> + assert.equal containedChart.canvas.node().parentNode.id, container_id + + it 'should set the default dimensions for the canvas', -> + chart = new Epoch.Chart.Canvas({ pixelRatio: 1 }) + assert.equal chart.canvas.attr('width'), defaultWidth + assert.equal chart.canvas.attr('height'), defaultHeight + + it 'should allow custom dimensions for the canvas', -> + [customWidth, customHeight] = [999, 888] + chart = new Epoch.Chart.Canvas + width: customWidth + height: customHeight + pixelRatio: 1 + assert.equal chart.canvas.attr('width'), customWidth + assert.equal chart.canvas.attr('height'), customHeight + + it 'should set container dimensions for the canvas', -> + assert.equal containedChart.canvas.attr('width'), containerWidth + assert.equal containedChart.canvas.attr('height'), containerHeight + + it 'should fetch a graphics context from the canvas', -> + assert.ok containedChart.ctx, "Did not fetch graphics context from canvas" + + it 'should take pixel ratio into account when setting canvas dimension attributes', -> + pixelRatio = 3 + chart = new Epoch.Chart.Canvas({ pixelRatio: pixelRatio }) + assert.equal chart.canvas.attr('width'), defaultWidth * pixelRatio + assert.equal chart.canvas.attr('height'), defaultHeight * pixelRatio + + it 'should not take pixel ratio into account when setting canvas dimension styles', -> + chart = new Epoch.Chart.Canvas({ pixelRatio: 2 }) + assert.equal +chart.canvas.style('width').replace('px', ''), defaultWidth + assert.equal +chart.canvas.style('height').replace('px', ''), defaultHeight + + describe 'getWidth', -> + it 'should take pixel ratio into account', -> + pixelRatio = 2 + chart = new Epoch.Chart.Canvas({ pixelRatio: pixelRatio }) + assert.equal chart.getWidth(), pixelRatio * defaultWidth + + describe 'getHeight', -> + it 'should take pixel ratio into account', -> + pixelRatio = 2 + chart = new Epoch.Chart.Canvas({ pixelRatio: pixelRatio }) + assert.equal chart.getHeight(), pixelRatio * defaultHeight + + describe 'dimensionsChanged', -> + [width, height, chart, pixelRatio] = [200, 100, null, 2] + + before (done) -> + d3.select(doc.body).append('div').attr('id', 'canvasResize').style + width: width + 'px' + height: height + 'px' + chart = new Epoch.Chart.Canvas { el: '#canvasResize', pixelRatio: pixelRatio } + done() + + after (done) -> + d3.select('#canvasResize').remove() + done() + + it 'should resize the canvas element when the width option is changed', -> + newWidth = width + 500 + chart.option 'width', newWidth + assert.equal chart.canvas.attr('width'), pixelRatio * newWidth + assert.equal chart.canvas.width(), newWidth + + it 'should resize the canvas element when the height option is changed', -> + newHeight = height + 500 + chart.option 'height', newHeight + assert.equal chart.canvas.attr('height'), pixelRatio * newHeight + assert.equal chart.canvas.height(), newHeight + + describe 'redraw', -> + chart = null + drawSpy = null + purgeSpy = null + + before -> + chart = new Epoch.Chart.Canvas() + + beforeEach -> + drawSpy = sinon.spy chart, 'draw' + purgeSpy = sinon.spy Epoch.QueryCSS, 'purge' + + afterEach -> + chart.draw.restore() + Epoch.QueryCSS.purge.restore() + + it 'should purge QueryCSS cache and redraw the canvas based chart with new styles', -> + chart.redraw() + + assert drawSpy.calledOnce + assert purgeSpy.calledOnce diff --git a/debian/missing-sources/epoch/tests/unit/core/copy.coffee b/debian/missing-sources/epoch/tests/unit/core/copy.coffee new file mode 100644 index 0000000..2043241 --- /dev/null +++ b/debian/missing-sources/epoch/tests/unit/core/copy.coffee @@ -0,0 +1,47 @@ +describe 'Epoch.Util', -> + describe 'copy', -> + it 'should correctly create a shallow copy', -> + object = + a: 20 + b: 'hello' + + copy = Epoch.Util.copy(object) + + assert.equal copy.a, object.a + assert.equal copy.b, object.b + + it 'should not recursively copy objects', -> + object = + a: + foo: 'bar' + + copy = Epoch.Util.copy(object) + object.a.foo = 'baz' + assert.equal object.a.foo, copy.a.foo + + describe 'defaults', -> + it 'should set default values when keys are missing', -> + options = {a: 'foo', b: 'bar'} + defaults = {c: 'baz'} + result = Epoch.Util.defaults(options, defaults) + assert.equal result.c, defaults.c + + it 'should not set default values when keys are present', -> + options = { a: 'foo', b: 'bar' } + defaults = { a: 'wow', b: 'neat' } + result = Epoch.Util.defaults(options, defaults) + assert.equal result.a, options.a + assert.equal result.b, options.b + + it 'should recursively set defaults from sub objects', -> + options = + a: + b: 'foo' + defaults = + a: + b: '' + c: 'bar' + result = Epoch.Util.defaults(options, defaults) + + assert.equal result.a.b, options.a.b + assert.equal result.a.c, defaults.a.c diff --git a/debian/missing-sources/epoch/tests/unit/core/css.coffee b/debian/missing-sources/epoch/tests/unit/core/css.coffee new file mode 100644 index 0000000..00cce68 --- /dev/null +++ b/debian/missing-sources/epoch/tests/unit/core/css.coffee @@ -0,0 +1,85 @@ +describe 'Epoch.QueryCSS', -> + styleMap = + '#container rect': + 'fill': 'blue' + 'stroke': 'red' + 'stroke-width': '5px' + '#container rect.a': + 'fill': 'green' + 'stroke': 'yellow' + 'stroke-width': '1px' + 'rect#byid': + 'fill': 'purple' + 'stroke': '#94105A' + 'stroke-width': '15px' + 'body.alt-color rect#byid': + 'fill': '#abcdef1' + 'stroke': '#48419A' + 'stroke-width': '2em' + + [container, svg, styleTag] = [null, null, null] + + makeStyleSheet = -> + cssStatements = [] + for selector, rules of styleMap + cssStatements.push (selector + "{" + ("#{k}: #{v}" for k, v of rules).join(';') + "}") + css = cssStatements.join('\n') + styleTag = addStyleSheet(css) + + makeContainer = -> + container = d3.select(doc.body).append('div') + .attr('id', 'container') + svg = container.append('svg') + .attr('width', 10) + .attr('height', 10) + + assertStyles = (object, selector) -> + unless object? + assert(false, "Object contains no styles") + + unless (mapping = styleMap[selector])? + assert(false, "Could not find styles with selector: #{selector}") + + for key, value of mapping + assert.equal object[key], value, "Style mismatch on rule '#{key}'" + + before (done) -> + makeStyleSheet() + makeContainer() + done() + + after (done) -> + doc.head.removeChild(styleTag) + doc.body.removeChild(container.node()) + done() + + describe 'getStyles', -> + it 'should find styles for an svg element', -> + styles = Epoch.QueryCSS.getStyles('rect', container) + assertStyles styles, '#container rect' + + it 'should find styles using a specific class name', -> + styles = Epoch.QueryCSS.getStyles('rect.a', container) + assertStyles styles, '#container rect.a' + + it 'should find styles using an id', -> + styles = Epoch.QueryCSS.getStyles('rect#byid', container) + assertStyles styles, 'rect#byid' + + describe 'purge', -> + before (done) -> + d3.select(doc.body).attr('class', 'alt-color') + done() + + after (done) -> + d3.select(doc.body).attr('class', null) + done() + + it 'should find cached styles before a purge', -> + styles = Epoch.QueryCSS.getStyles('rect#byid', container) + assertStyles styles, 'rect#byid' + + it 'should find new styles after purging the cache', -> + Epoch.QueryCSS.purge() + styles = Epoch.QueryCSS.getStyles('rect#byid', container) + assertStyles styles, 'body.alt-color rect#byid' diff --git a/debian/missing-sources/epoch/tests/unit/core/d3.coffee b/debian/missing-sources/epoch/tests/unit/core/d3.coffee new file mode 100644 index 0000000..1b65c55 --- /dev/null +++ b/debian/missing-sources/epoch/tests/unit/core/d3.coffee @@ -0,0 +1,40 @@ +describe 'd3.selection', -> + [width, height] = [345, 543, null] + [element, id] = [null, 'd3-element'] + + before (done) -> + element = doc.createElement('DIV') + element.id = id + doc.body.appendChild(element) + d3.select('#' + id).style + 'width': width + "px" + 'height': height + "px" + done() + + describe 'width', -> + it 'should return the width of an element', -> + assert.equal d3.select('#' + id).width(), width + + it 'should set the width of an element given a number', -> + widthNumber = 50 + d3.select('#'+id).width(widthNumber) + assert.equal d3.select('#'+id).width(), widthNumber + + it 'should set the width of an element given a css pixel length', -> + widthString = '500px' + d3.select('#'+id).width(widthString) + assert.equal d3.select('#'+id).width(), +widthString.replace('px', '') + + describe 'height', -> + it 'should return the height of an element', -> + assert.equal d3.select('#' + id).height(), height + + it 'should set the height of an element given a number', -> + heightNumber = 75 + d3.select('#'+id).height(heightNumber) + assert.equal d3.select('#'+id).height(), heightNumber + + it 'should set the height of an element given a css pixel length', -> + heightString = '343px' + d3.select('#'+id).height(heightString) + assert.equal d3.select('#'+id).height(), +heightString.replace('px', '') diff --git a/debian/missing-sources/epoch/tests/unit/core/events.coffee b/debian/missing-sources/epoch/tests/unit/core/events.coffee new file mode 100644 index 0000000..38d9356 --- /dev/null +++ b/debian/missing-sources/epoch/tests/unit/core/events.coffee @@ -0,0 +1,111 @@ +describe 'Epoch.Events', -> + eventsObject = null + + before (done) -> + eventsObject = new Epoch.Events() + done() + + it 'should execute callbacks when events are triggered', (done) -> + errorCallback = -> + assert false, 'Event callback never executed' + done() + timeout = setTimeout errorCallback, 1000 + eventsObject.on 'event', -> + clearTimeout(timeout) + done() + eventsObject.trigger 'event' + + it 'should not execute callbacks that have been removed', (done) -> + errorCallback = -> + assert false, 'Event callback still executed' + done() + eventsObject.on 'example', errorCallback + eventsObject.off 'example', errorCallback + eventsObject.trigger 'example' + done() + + it 'should execute all callbacks associated with an event name', (done) -> + total = 4 + + errorCallback = -> + assert false, 'Not all callbacks were executed' + done() + timeout = setTimeout errorCallback, 1000 + + makeCallback = -> -> + total-- + if total == 0 + clearTimeout(timeout) + done() + eventsObject.on('multi', makeCallback()) for i in [0...total] + eventsObject.trigger 'multi' + + it 'should remove all callbacks when using .off([String])', (done) -> + makeCallback = -> -> + assert false, "A callback was still executed" + done() + eventsObject.on('multi2', makeCallback()) for i in [0...4] + eventsObject.off('multi2') + eventsObject.trigger('multi2') + setTimeout (-> done()), 200 + + it 'should execute methods on the object when using .on([String], [String])', (done) -> + errorCallback = -> + assert false, 'Trigger did not call the appropriate method.' + done() + timeout = setTimeout(errorCallback, 1000) + + eventsObject.method = -> + clearTimeout(timeout) + done() + + eventsObject.on 'method-event', 'method' + eventsObject.trigger 'method-event' + + it 'should register all events when executing .onAll([Object])', (done) -> + errorCallback = -> + assert false, 'Not all events were triggered.' + done() + timeout = setTimeout(errorCallback, 1000) + + eventNames = ['multi:a', 'multi:b', 'multi:c', 'multi:d'] + total = 0 + + eventCallback = -> + total += 1 + if total == eventNames.length + clearTimeout(timeout) + done() + + eventMap = {} + eventMap[name] = eventCallback for name in eventNames + + eventsObject.onAll(eventMap) + eventsObject.trigger(name) for name in eventNames + + + it 'should remove all events when executing .offAll([Array])', -> + eventCallback = -> + assert false, 'A removed callback was still triggered.' + + eventNames = ['multi-off:a', 'multi-off:b', 'multi-off:c', 'multi-off:d'] + eventMap = {} + eventMap[name] = eventCallback for name in eventNames + + eventsObject.onAll(eventMap) + eventsObject.offAll(eventNames) + eventsObject.trigger(name) for name in eventNames + + + it 'should remove specific event callbacks when executing .offAll([Object])', (done) -> + makeEventCallback = -> -> + assert false, 'A removed callback was still triggered.' + + eventNames = ['multi-off:a', 'multi-off:b', 'multi-off:c', 'multi-off:d'] + eventMap = {} + eventMap[name] = makeEventCallback() for name in eventNames + + eventsObject.onAll(eventMap) + eventsObject.offAll(eventMap) + eventsObject.trigger(name) for name in eventNames + setTimeout (-> done()), 200 diff --git a/debian/missing-sources/epoch/tests/unit/core/format.coffee b/debian/missing-sources/epoch/tests/unit/core/format.coffee new file mode 100644 index 0000000..a645b49 --- /dev/null +++ b/debian/missing-sources/epoch/tests/unit/core/format.coffee @@ -0,0 +1,68 @@ +describe 'Epoch.Util', -> + describe 'formatSI', -> + it 'should produce the same number for integers < 1000', -> + number = 678 + assert.equal Epoch.Util.formatSI(number), number + + it 'should only set a fixed decimal for integers when instructed', -> + number = 20 + assert.equal Epoch.Util.formatSI(number), number + assert.equal Epoch.Util.formatSI(number, 1, true), "#{number}.0" + + it 'should set the appropriate number of fixed digits', -> + number = 3.1415 + for i in [1..5] + match = Epoch.Util.formatSI(number, i).split('.')[1] + assert.isNotNull match + assert.isString match + assert.equal match.length, i + + it 'should set the appropriate postfix based on the number\'s order of magnitude', -> + for i, postfix of ['K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'] + number = Math.pow(10, ((i|0)+1)*3) + assert.equal Epoch.Util.formatSI(number), "1 #{postfix}" + + + describe 'formatBytes', -> + it 'should postfix numbers < 1024 with "B"', -> + number = 512 + assert.equal Epoch.Util.formatBytes(number), "#{number} B" + + it 'should only set a fixed decimal for integers when instructed', -> + assert.equal Epoch.Util.formatBytes(128), '128 B' + assert.equal Epoch.Util.formatBytes(128, 1, true), '128.0 B' + assert.equal Epoch.Util.formatBytes(1024), '1 KB' + assert.equal Epoch.Util.formatBytes(1024, 1, true), '1.0 KB' + + it 'should set the appropriate number of fixed digits', -> + number = 3.1415 + for i in [1..5] + fixed = Epoch.Util.formatBytes(number, i).replace(/\sB$/, '') + assert.isString fixed + match = fixed.split('.')[1] + assert.isNotNull match + assert.equal match.length, i + + it 'should set the appropriate postfix based on the number\'s order of magnitude', -> + for i, postfix of ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] + number = Math.pow(1024, (i|0)+1) + regexp = new RegExp(" #{postfix}$") + assert.isNotNull Epoch.Util.formatBytes(number).match(regexp) + + +describe 'Epoch.Formats', -> + describe 'regular', -> + it 'should return what it was given', -> + assert.equal Epoch.Formats.regular(10), 10 + assert.equal Epoch.Formats.regular("hello"), "hello" + + describe 'percent', -> + it 'should return a percent given a number', -> + assert.equal Epoch.Formats.percent(0.1), '10.0%' + assert.equal Epoch.Formats.percent(0.5), '50.0%' + assert.equal Epoch.Formats.percent(1), '100.0%' + assert.equal Epoch.Formats.percent(23.245), '2324.5%' + + describe 'seconds', -> + it 'should return a well formatted date given a timestamp', -> + assert.match Epoch.Formats.seconds(1404385979), /\d{2}:\d{2}:\d{2} AM|PM/ diff --git a/debian/missing-sources/epoch/tests/unit/core/is.coffee b/debian/missing-sources/epoch/tests/unit/core/is.coffee new file mode 100644 index 0000000..af08eb2 --- /dev/null +++ b/debian/missing-sources/epoch/tests/unit/core/is.coffee @@ -0,0 +1,88 @@ +describe 'Epoch.Util', -> + describe 'isArray', -> + it 'should return true if given an array', -> + assert.ok Epoch.isArray([]) + assert.ok Epoch.isArray([1, 2, 3]) + + it 'should return false if not given an array', -> + assert.notOk Epoch.isArray(2) + assert.notOk Epoch.isArray("hello") + assert.notOk Epoch.isArray({}) + + describe 'isObject', -> + it 'should return true if given an flat object', -> + assert.ok Epoch.isObject({}) + + it 'should return false if given a number object', -> + assert.notOk Epoch.isObject(new Number()) + + it 'should return false if given a non-object', -> + assert.notOk Epoch.isObject([]) + assert.notOk Epoch.isObject(2) + assert.notOk Epoch.isObject("string") + + describe 'isString', -> + it 'should return true if given a string', -> + assert.ok Epoch.isString("example") + assert.ok Epoch.isString(new String()) + + it 'should return false if given a non-string', -> + assert.notOk Epoch.isString(2) + assert.notOk Epoch.isString([]) + assert.notOk Epoch.isString({}) + + describe 'isFunction', -> + it 'should return true if given a function', -> + assert.ok Epoch.isFunction(->) + + it 'should return false if given a non-function', -> + assert.notOk Epoch.isFunction([]) + assert.notOk Epoch.isFunction({}) + assert.notOk Epoch.isFunction(42) + assert.notOk Epoch.isFunction("cool") + + describe 'isNumber', -> + it 'should return true if given a number', -> + assert.ok Epoch.isNumber(new Number()) + + it 'should return true if given an integer literal', -> + assert.ok Epoch.isNumber(1983) + + it 'should return true if given a floating point literal', -> + assert.ok Epoch.isNumber(3.1415) + + it 'should return false if given a non-number', -> + assert.notOk Epoch.isNumber(->) + assert.notOk Epoch.isNumber([]) + assert.notOk Epoch.isNumber({}) + assert.notOk Epoch.isNumber("nan") + + describe 'isElement', -> + it 'should return true given an html element', -> + p = doc.createElement('P') + assert.ok Epoch.isElement(p) + + it 'should return false given a non-element', -> + assert.notOk Epoch.isElement(1) + assert.notOk Epoch.isElement("1") + assert.notOk Epoch.isElement({}) + assert.notOk Epoch.isElement([]) + assert.notOk Epoch.isElement(->) + + describe 'isNonEmptyArray', -> + it 'should return true given a non-empty array', -> + assert.ok Epoch.isNonEmptyArray([1]) + assert.ok Epoch.isNonEmptyArray([1, 3]) + assert.ok Epoch.isNonEmptyArray(["foo", 4, "bar"]) + + it 'should return false given a non-array', -> + assert.notOk Epoch.isNonEmptyArray(2) + assert.notOk Epoch.isNonEmptyArray("five") + assert.notOk Epoch.isNonEmptyArray({}) + assert.notOk Epoch.isNonEmptyArray(->) + + it 'should return false given a null value', -> + assert.notOk Epoch.isNonEmptyArray(null) + + it 'should return false given an empty array', -> + assert.notOk Epoch.isNonEmptyArray([]) diff --git a/debian/missing-sources/epoch/tests/unit/core/util.coffee b/debian/missing-sources/epoch/tests/unit/core/util.coffee new file mode 100644 index 0000000..e02280c --- /dev/null +++ b/debian/missing-sources/epoch/tests/unit/core/util.coffee @@ -0,0 +1,92 @@ +describe 'Epoch.Util', -> + describe 'trim', -> + it 'should return null unless given a string', -> + assert.isNotNull Epoch.Util.trim('test string') + assert.isNull Epoch.Util.trim(34) + + it 'should trim leading and trailing whitespace', -> + assert.equal Epoch.Util.trim("\t\n\r indeed \n\t\t\r"), 'indeed' + + it 'should leave inner whitespace', -> + assert.equal Epoch.Util.trim('Hello world'), 'Hello world' + + describe 'dasherize', -> + it 'should dasherize regular strings', -> + assert.equal Epoch.Util.dasherize('Hello World'), 'hello-world' + + it 'should trim leading and trailing whitespace before dasherizing', -> + assert.equal Epoch.Util.dasherize(' Airieee is KewL '), 'airieee-is-kewl' + + describe 'domain', -> + testLayers = [ + { values: [{x: 'A', y: 10}, {x: 'B', y: 20}, {x: 'C', y: 40}] } + ] + + testLayers2 = [ + { values: [{x: 'A', y: 10}, {x: 'B', y: 20}, {x: 'C', y: 40}] }, + { values: [{x: 'D', y: 15}, {x: 'E', y: 30}, {x: 'F', y: 90}] } + ] + + it 'should find the correct domain of a set of keys and values', -> + xDomain = Epoch.Util.domain(testLayers, 'x') + assert.sameMembers xDomain, ['A', 'B', 'C'] + yDomain = Epoch.Util.domain(testLayers, 'y') + assert.sameMembers yDomain, [10, 20, 40] + + it 'should find all the values across multiple layers', -> + xDomain = Epoch.Util.domain(testLayers2, 'x') + assert.sameMembers xDomain, ['A', 'B', 'C', 'D', 'E', 'F'] + yDomain = Epoch.Util.domain(testLayers2, 'y') + assert.sameMembers yDomain, [10, 20, 40, 15, 30, 90] + + describe 'toRGBA', -> + it 'should produce the correct rgba style when given an rgba color style', -> + assert.equal Epoch.Util.toRGBA('rgba(1, 2, 3, 0.4)', 0.1), 'rgba(1,2,3,0.1)' + + it 'should produce the correct rgba style when given any rgb color style', -> + assert.equal Epoch.Util.toRGBA('black', 0.25), 'rgba(0,0,0,0.25)' + assert.equal Epoch.Util.toRGBA('#FF0000', 0.9), 'rgba(255,0,0,0.9)' + assert.equal Epoch.Util.toRGBA('rgb(10, 20, 40)', 0.99), 'rgba(10,20,40,0.99)' + + describe 'getComputedStyle', -> + overrideStyles = + 'width': '320px' + 'height': '240px' + 'background-color': 'blue' + + [style, div] = [null, null] + + before (done) -> + style = addStyleSheet('#get-style-div { padding-left: 30px; background: green }') + div = doc.createElement('div') + div.id = 'get-style-div' + doc.body.appendChild(div) + d3.select('#get-style-div').style(overrideStyles) + done() + + after (done) -> + doc.body.removeChild(div) + doc.head.removeChild(style) + done() + + it 'should find