├── .gitattributes ├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── .jsbeautifyrc ├── .jsdoc.json ├── .jshintignore ├── .jshintrc ├── .npmignore ├── .travis.yml ├── CONTRIBUTING.md ├── Gruntfile.js ├── LICENSE ├── NOTICE ├── README.md ├── benchmark ├── autobench.js ├── benchmarks.js ├── express.js ├── fail.html ├── gamingbench.js ├── index.css ├── index.html ├── index.js ├── pass.html └── tools.js ├── dist ├── .gitignore └── .npmignore ├── examples └── basic │ ├── demo.css │ ├── demo.details │ ├── demo.html │ ├── demo.js │ └── offline.html ├── fft-white.png ├── js ├── .jshintrc ├── CanvasInput.js ├── ColorMap.js ├── common.js ├── license.js ├── m.js ├── mx.dommenu.js ├── mx.js ├── plugins.js ├── sigplot.accordion.js ├── sigplot.annotations.js ├── sigplot.boxes.js ├── sigplot.js ├── sigplot.layer1d.js ├── sigplot.layer2d.js ├── sigplot.playback.js ├── sigplot.plugin.js └── sigplot.slider.js ├── karma.conf.js ├── package-lock.json ├── package.json ├── support └── google-closure-compiler │ └── build │ ├── COPYING │ ├── README │ └── compiler.jar └── test ├── dat ├── info.png ├── keyword_test_file.tmp ├── lots_of_keywords.tmp ├── penny.prm ├── pulse_cx.tmp ├── ramp.tmp ├── raster.tmp ├── scalarpacked.tmp ├── sin.mat └── sin.tmp ├── penny.prm ├── test.html └── tests.js /.gitattributes: -------------------------------------------------------------------------------- 1 | core.autocrlf=false 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Describe a problem with SigPlot 4 | 5 | --- 6 | 7 | **Describe the bug** 8 | A clear and concise description of what the bug is. 9 | 10 | **To Reproduce** 11 | 12 | Steps to reproduce the behavior: 13 | 1. Go to '...' 14 | 2. Click on '....' 15 | 3. Scroll down to '....' 16 | 4. See error 17 | 18 | **Expected behavior** 19 | A clear and concise description of what you expected to happen. 20 | 21 | **jsFiddle** 22 | 23 | If possible, demonstrate the issue by forking [this jsFiddle](https://jsfiddle.net/ha09baqu/#fork) and providing a link to your jsFiddle in the bug report. 24 | 25 | **Screenshots** 26 | If applicable, add screenshots to help explain your problem. 27 | 28 | **Desktop (please complete the following information):** 29 | - OS: [e.g. iOS] 30 | - Browser [e.g. chrome, safari] 31 | - Version [e.g. 22] 32 | 33 | **Smartphone (please complete the following information):** 34 | - Device: [e.g. iPhone6] 35 | - OS: [e.g. iOS8.1] 36 | - Browser [e.g. stock browser, safari] 37 | - Version [e.g. 22] 38 | 39 | **Additional context** 40 | Add any other context about the problem here. 41 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for SigPlot 4 | 5 | --- 6 | 7 | **Is your feature request related to a problem? Please describe.** 8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 9 | 10 | **Describe the solution you'd like** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Describe alternatives you've considered** 14 | A clear and concise description of any alternative solutions or features you've considered. 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.so 3 | *.exe 4 | .DS_Store 5 | *~ 6 | *.gitignore 7 | Git_History 8 | node_modules 9 | /C:\nppdf32Log\debuglog.txt 10 | .DS_Store 11 | doc/ 12 | .idea/ 13 | -------------------------------------------------------------------------------- /.jsbeautifyrc: -------------------------------------------------------------------------------- 1 | { 2 | "indentSize": 4, 3 | "indentWithTabs": false, 4 | "wrapLineLength": 0, 5 | "eol": "\n" 6 | } 7 | -------------------------------------------------------------------------------- /.jsdoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "tags": { 3 | "allowUnknownTags": true, 4 | "dictionaries": ["jsdoc"] 5 | }, 6 | "source": { 7 | "include": ["js", "package.json", "README.md"], 8 | "includePattern": ".js$", 9 | "excludePattern": "(node_modules/|doc)" 10 | }, 11 | "plugins": [ 12 | "plugins/markdown" 13 | ], 14 | "opts": { 15 | "destination": "./doc", 16 | "encoding": "utf8", 17 | "private": true, 18 | "recurse": true, 19 | "template": "./node_modules/minami" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.jshintignore: -------------------------------------------------------------------------------- 1 | js/typedarray.js 2 | js/CanvasInput.js 3 | js/spin.js 4 | js/tinycolor.js 5 | js/loglevel.js 6 | js/slider.js 7 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "curly": true, 3 | "eqeqeq": true, 4 | "immed": true, 5 | "latedef": true, 6 | "newcap": true, 7 | "noarg": true, 8 | "sub": true, 9 | "undef": true, 10 | "unused": true, 11 | "boss": true, 12 | "eqnull": true, 13 | "node": true 14 | } 15 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LGSInnovations/sigplot/480cec393280a41359924383d06bb6849eeab879/.npmignore -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "10.15.1" 4 | 5 | services: 6 | - docker 7 | 8 | before_install: 9 | - rm -rf dist/* 10 | - npm install -g grunt 11 | - npm install -q 12 | script: 13 | - grunt dist 14 | 15 | notifications: 16 | email: 17 | on_success: never 18 | 19 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to SigPlot 2 | 3 | ## Preparing Development Environment 4 | 5 | Using Grunt 6 | ------------- 7 | Building SigPlot requires that you have Node.js and the Node Package Manager 8 | (npm) installed. 9 | 10 | Node.js can be installed using the [official source](https://nodejs.org/en/), 11 | using your operating-systems package manager, or by using 12 | [nvm](https://github.com/creationix/nvm). 13 | 14 | Once you have `npm` working, install [Grunt](http://gruntjs.com) with the 15 | command `npm install -g grunt-cli`. 16 | 17 | ``` 18 | # Install NVM 19 | curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.34.0/install.sh | bash 20 | 21 | # Install stable Node.js 22 | nvm install stable 23 | 24 | # Use stable Node.js 25 | nvm use stable 26 | 27 | # Install Grunt 28 | npm install -g grunt-cli grunt 29 | ``` 30 | 31 | ## Getting started 32 | 33 | 1. Fork the SigPlot repository on GitHub. 34 | 2. Clone the repostory that you have just forked. 35 | 3. Create a branch with a meaningful name: `git checkout -b fix-some-issue`. 36 | 4. Run `npm install` to fetch all of the required SigPlot dependencies. 37 | 5. Run `grunt test` to ensure that your branch builds and passes all of the tests. 38 | 39 | ## Writing code 40 | 41 | All code for SigPlot is found within the `js` folder. Coding style is enforced 42 | using jsbeautifier during the build process. Prior to commiting your code you 43 | should cleanup your code using `grunt prep`. This will reformat your 44 | code and make it consistent with the SigPlot coding standards. 45 | 46 | Please use semantic commit messages following this format: 47 | 48 | ``` 49 | commit-type: commit summary 50 | 51 | Commit details as necessary... 52 | ``` 53 | 54 | The following commit-type values should be used: 55 | * feat (new feature) 56 | * fix (bug fix) 57 | * docs (changes to documentation) 58 | * style (formatting, missing semi colons, etc; no code change) 59 | * refactor (refactoring production code) 60 | * test (adding missing tests, refactoring tests; no production code change) 61 | * chore (updating grunt tasks etc; no production code change) 62 | 63 | ## Running test suite 64 | 65 | SigPlot includes an extensive set of unittests found in the `test/tests.js` 66 | file. There are two broad categories of test: those that exeucte without user 67 | interaction and those that require user interaction to confirm proper behavior. 68 | 69 | Non-interactive tests can be executed by simply running `grunt test`. 70 | Interactive tests can be executed by running `grunt web_server` and then 71 | opening your webrowser to http://localhost:1337/test/test.html. 72 | 73 | ## Preparing your code for submission 74 | 75 | Prior to submitting a pull request, the following steps are recommended to 76 | prepare your branch: 77 | 78 | 1. Fetch the latest code from the repository: `git fetch origin` 79 | 2. Rebase your branch against master `git rebase origin/master` 80 | 3. Run the build and test with `grunt test`. This must pass without the use of --force. 81 | 82 | Using Make 83 | ------------- 84 | Although Grunt is the canonical build system, a basic Makefile is provided for 85 | environment where Grunt cannot be used. You will need the following: 86 | 87 | * GNU Make 88 | * Java version 1.7+ 89 | 90 | If you wish to build the SigPlot API documention, you will need 91 | [jsdoc](https://github.com/jsdoc3/jsdoc) installed. 92 | 93 | ## Contributions 94 | 95 | All code in this repository is under the [Apache License, version 2.0](https://www.apache.org/licenses/LICENSE-2.0). 96 | 97 | SigPlot welcomes contributions from everyone. You will need to agree to the 98 | [LGS Contributor License Agreement](https://cla-assistant.lgsinnovations.com/LGSInnovations/) before 99 | your contributions can be incorporated into SigPlot. 100 | 101 | Contributions to SigPlot should be made in the form of GitHub pull requests. 102 | Each pull request will be reviewed by a core contributor and either included in 103 | the main tree or given feedback for changes that would be required. 104 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (grunt) { 4 | 5 | // Project configuration. 6 | grunt.initConfig({ 7 | // Metadata. 8 | pkg: grunt.file.readJSON('package.json'), 9 | jshint: { 10 | options: { 11 | jshintrc: '.jshintrc' 12 | }, 13 | gruntfile: { 14 | src: 'Gruntfile.js' 15 | }, 16 | js: { 17 | options: { 18 | jshintrc: 'js/.jshintrc' 19 | }, 20 | src: ['js/**/*.js', 'test/tests.js'] 21 | }, 22 | }, 23 | qunit: { 24 | options: { '--web-security': 'no', '--local-to-remote-url-access': 'yes' }, 25 | all: ['test/test.html'] 26 | }, 27 | 'closure-compiler': { 28 | sigplot_debug: { 29 | closurePath: 'support/google-closure-compiler', 30 | js: 'dist/sigplot.js', 31 | maxBuffer: 500, 32 | jsOutputFile: 'dist/sigplot-debug.js', 33 | options: { 34 | formatting: 'PRETTY_PRINT', 35 | compilation_level: 'WHITESPACE_ONLY', 36 | } 37 | }, 38 | sigplot_plugins_debug: { 39 | closurePath: 'support/google-closure-compiler', 40 | js: 'dist/sigplot.plugins.js', 41 | maxBuffer: 500, 42 | jsOutputFile: 'dist/sigplot.plugins-debug.js', 43 | options: { 44 | formatting: 'PRETTY_PRINT', 45 | compilation_level: 'WHITESPACE_ONLY' 46 | } 47 | }, 48 | sigplot_minimized: { 49 | closurePath: 'support/google-closure-compiler', 50 | js: 'dist/sigplot.js', 51 | maxBuffer: 500, 52 | jsOutputFile: 'dist/sigplot-minimized.js', 53 | options: { 54 | compilation_level: 'SIMPLE_OPTIMIZATIONS' 55 | } 56 | }, 57 | sigplot_plugins_minimized: { 58 | closurePath: 'support/google-closure-compiler', 59 | js: 'dist/sigplot.plugins.js', 60 | maxBuffer: 500, 61 | jsOutputFile: 'dist/sigplot.plugins-minimized.js', 62 | options: { 63 | compilation_level: 'SIMPLE_OPTIMIZATIONS' 64 | } 65 | } 66 | }, 67 | jsdoc: { 68 | sigplot: { 69 | src: ['js/*.js'], 70 | options: { 71 | destination: 'doc', 72 | template: './node_modules/minami/', 73 | configure: '.jsdoc.json' 74 | } 75 | } 76 | }, 77 | clean: { 78 | build: ["dist/**/*", "!dist/*.zip"], 79 | doc: ["doc/**/*"] 80 | }, 81 | compress: { 82 | main: { 83 | options: { 84 | archive: "dist/sigplot-<%= pkg.version %>-<%= grunt.template.today('yyyy-mm-dd') %>.zip", 85 | }, 86 | files: [ 87 | {expand: true, cwd: 'dist/', src: ['*-debug.js'], dest: 'sigplot-<%= pkg.version %>'}, 88 | {expand: true, cwd: 'dist/', src: ['*-minimized.js'], dest: 'sigplot-<%= pkg.version %>'}, 89 | {src: ['doc/**/*'], dest: 'sigplot-<%= pkg.version %>'} 90 | ] 91 | } 92 | }, 93 | githash: { 94 | main: { 95 | options: {} 96 | } 97 | }, 98 | replace: { 99 | version: { 100 | src: ["dist/*.js"], 101 | overwrite: true, 102 | replacements: [{ 103 | from: /version-PLACEHOLDER/g, 104 | to: "<%= pkg.version %>-<%= githash.main.short %>", 105 | }], 106 | } 107 | }, 108 | 'http-server': { 109 | 'test': { 110 | cache: 0, 111 | port: 1337 112 | }, 113 | }, 114 | jsbeautifier: { 115 | check: { 116 | // Only check a subset of the files 117 | src: [ 118 | 'js/m.js', 119 | 'js/mx.js', 120 | 'js/sigplot.layer1d.js', 121 | 'js/sigplot.layer2d.js', 122 | 'js/sigplot.js', 123 | 'js/sigplot.annotations.js', 124 | 'js/sigplot.slider.js', 125 | 'js/sigplot.accordion.js', 126 | 'js/sigplot.boxes.js', 127 | 'js/sigplot.playback.js', 128 | 'js/sigplot.plugin.js', 129 | 'test/tests.js' 130 | ], 131 | options: { 132 | mode: "VERIFY_ONLY", 133 | config: ".jsbeautifyrc" 134 | } 135 | }, 136 | cleanup: { 137 | // Only cleanup a subset of the files 138 | src: [ 139 | 'js/m.js', 140 | 'js/mx.js', 141 | 'js/sigplot.layer1d.js', 142 | 'js/sigplot.layer2d.js', 143 | 'js/sigplot.js', 144 | 'js/sigplot.annotations.js', 145 | 'js/sigplot.slider.js', 146 | 'js/sigplot.accordion.js', 147 | 'js/sigplot.boxes.js', 148 | 'js/sigplot.playback.js', 149 | 'js/sigplot.plugin.js', 150 | 'test/tests.js' 151 | ], 152 | options: { 153 | config: ".jsbeautifyrc" 154 | } 155 | } 156 | }, 157 | express: { 158 | test: { 159 | options: { 160 | script: 'benchmark/express.js' 161 | } 162 | } 163 | }, 164 | karma: { 165 | bench: { 166 | configFile: 'karma.conf.js' 167 | } 168 | }, 169 | browserify: { 170 | sigplot: { 171 | src: 'js/sigplot.js', 172 | dest: 'dist/sigplot.js', 173 | options: { 174 | browserifyOptions: { 175 | standalone: 'sigplot' 176 | }, 177 | transform: [ 178 | [ 179 | 'babelify', { 180 | "presets": ["@babel/preset-env"] 181 | } 182 | ] 183 | ] 184 | } 185 | }, 186 | plugins: { 187 | src: [ 'js/plugins.js' ], 188 | dest: 'dist/sigplot.plugins.js', 189 | options: { 190 | browserifyOptions: { 191 | standalone: 'sigplot_plugins' 192 | }, 193 | transform: [ 194 | [ 195 | 'babelify', { 196 | "presets": ["@babel/preset-env"] 197 | } 198 | ] 199 | ] 200 | } 201 | } 202 | } 203 | }); 204 | 205 | // These plugins provide necessary tasks. 206 | grunt.loadNpmTasks('grunt-closure-compiler'); 207 | grunt.loadNpmTasks('grunt-contrib-jshint'); 208 | grunt.loadNpmTasks('grunt-contrib-qunit'); 209 | grunt.loadNpmTasks('grunt-jsdoc'); 210 | grunt.loadNpmTasks('grunt-contrib-clean'); 211 | grunt.loadNpmTasks('grunt-contrib-compress'); 212 | grunt.loadNpmTasks('grunt-http-server'); 213 | grunt.loadNpmTasks('grunt-jsbeautifier'); 214 | grunt.loadNpmTasks('grunt-karma'); 215 | grunt.loadNpmTasks('grunt-express-server'); 216 | grunt.loadNpmTasks('grunt-browserify'); 217 | grunt.loadNpmTasks('grunt-text-replace'); 218 | grunt.loadNpmTasks('grunt-githash'); 219 | 220 | grunt.registerTask('build', ['jsbeautifier:check', 'jshint', 'browserify', 'githash', 'replace']); 221 | 222 | // Check everything is good 223 | grunt.registerTask('test', ['build', 'qunit']); 224 | 225 | // Beautify the code 226 | grunt.registerTask('prep', ['jsbeautifier:cleanup']); 227 | 228 | // Generate documentation 229 | grunt.registerTask('generate-docs', ['jsdoc']); 230 | 231 | // Build a distributable release 232 | grunt.registerTask('dist', ['clean', 'test', 'closure-compiler', 'jsdoc', 'compress']); 233 | 234 | // Default task. 235 | grunt.registerTask('default', 'test'); 236 | 237 | // Benchmark in browsers. 238 | grunt.registerTask('benchtest', ['express:test', 'karma:bench']); 239 | grunt.registerTask('build_and_test', ['build', 'benchtest']); 240 | 241 | // for compatibility with the old grunt commands 242 | grunt.registerTask('web_server', 'http-server'); 243 | 244 | }; 245 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | LGS SigPlot 2 | Copyright [2012-2017] LGS Innovations 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [SigPlot](http://sigplot.lgsinnovations.com) 2 | ======= 3 | 4 | [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) [![Build Status](https://travis-ci.org/LGSInnovations/sigplot.svg?branch=master)](https://travis-ci.org/LGSInnovations/sigplot) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](.github/CONTRIBUTING.md#pull-requests) [![npm version](https://badge.fury.io/js/sigplot.svg)](https://badge.fury.io/js/sigplot) 5 | 6 | SigPlot provides fast, interactive plotting for software defined radio 7 | applications using HTML5. 8 | 9 | ![SigPlot plotting the FFT of a signal](./fft-white.png) 10 | 11 | Getting Started 12 | ================= 13 | ```html 14 | 15 | 16 | SigPlot Standalone 17 | 23 | 24 | 25 |
26 | 27 | 31 | 32 | 33 | ``` 34 | 35 | See [this jsFiddle](https://jsfiddle.net/ha09baqu/) as an example. Additional 36 | [examples and demos](http://sigplot.lgsinnovations.com/). 37 | 38 | 39 | WebPack Quick Start 40 | ================================ 41 | 42 | These instructions assume you have Node.js/NPM correctly installed on your 43 | system. 44 | 45 | First install webpack: 46 | 47 | ``` 48 | npm install webpack -g 49 | ``` 50 | 51 | Then create a project for the SigPlot demo and install sigplot. 52 | 53 | ```bash 54 | mkdir sigplot-webpack 55 | cd sigplot-webpack 56 | npm install sigplot 57 | ``` 58 | 59 | Next, create a file called `demo.js` with the following contents: 60 | 61 | ```javascript 62 | let sigplot = require("sigplot"); 63 | let options = {}; 64 | let plot = new sigplot.Plot(document.getElementById('plot'), options); 65 | ``` 66 | 67 | Then, create a file called `index.html` with the following contents: 68 | 69 | ```html 70 | 71 | 72 | SigPlot Webpack 73 | 79 | 80 | 81 |
82 | 83 | 84 | 85 | ``` 86 | 87 | Use webpack to compile the bundle: 88 | 89 | ```bash 90 | webpack ./demo.js bundle.js 91 | ``` 92 | 93 | Then open index.html in your webbrowser. 94 | 95 | Help 96 | ============= 97 | Join the discussion on [Slack](https://join.slack.com/t/sigplot/shared_invite/zt-34gdt6eb-c1vAwXR48B9YvDZvT7i1DQ). 98 | 99 | Contributing 100 | ===================== 101 | See [CONTRIBUTING.md](CONTRIBUTING.md). 102 | 103 | License 104 | ===================== 105 | Licensed to the LGS Innovations (LGS) under one 106 | or more contributor license agreements. See the NOTICE file 107 | distributed with this work for additional information 108 | regarding copyright ownership. LGS licenses this file 109 | to you under the Apache License, Version 2.0 (the 110 | "License"); you may not use this file except in compliance 111 | with the License. You may obtain a copy of the License at 112 | 113 | http://www.apache.org/licenses/LICENSE-2.0 114 | 115 | Unless required by applicable law or agreed to in writing, 116 | software distributed under the License is distributed on an 117 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 118 | KIND, either express or implied. See the License for the 119 | specific language governing permissions and limitations 120 | under the License. 121 | -------------------------------------------------------------------------------- /benchmark/autobench.js: -------------------------------------------------------------------------------- 1 | QUnit.test( 'sigplot benchmark test', function(assert) { 2 | var countDone = assert.async(); 3 | var inner = document.createElement('iframe'); 4 | inner.name = 'innerframe'; 5 | inner.height = "600px"; 6 | inner.width = "1000px"; 7 | 8 | // Ask server how many scores are already in file for current browser 9 | inner.src = 'http://localhost:3000/countscores?browser=' + trimBrowser(); 10 | document.body.appendChild(inner); 11 | var numRuns = 1; 12 | var runNum = 1; 13 | var done = null; 14 | var countRuns = function() { 15 | var location = inner.contentWindow.location; 16 | if (location == null || location.href == undefined) { 17 | // Wait another half-second for redirect from server 18 | setTimeout(countRuns, 500); 19 | } else { 20 | var currentHref = location.href; 21 | // Server sends back number of runs to do as query parameter in redirect 22 | var index = currentHref.lastIndexOf('autolaunch='); 23 | if (index != -1) { 24 | numRuns = Number(currentHref.substring(index + 11)); 25 | } 26 | // Now that we know now many runs, expect that many async calls 27 | done = assert.async(numRuns); 28 | // And schedule the first benchmark run 29 | setTimeout(runBenches, 100); 30 | countDone(); 31 | } 32 | } 33 | 34 | var runBenches = function() { 35 | console.log("Starting benchmark run " + runNum + " of " + numRuns); 36 | // Click on the "launch" link to start the run 37 | inner.contentWindow.document.getElementById("home-launch").click(); 38 | var tofunc = function() { 39 | var doc = inner.contentWindow.document; 40 | var score = doc.getElementById("score-footer-text").innerHTML; 41 | if (score == "") { 42 | // Run not finished yet, wait another 5 seconds and check again 43 | setTimeout(tofunc, 5000); 44 | } else { 45 | sendScoreToServer(inner, score, function(status) { 46 | assert.ok(status, "Finished benchmark run " + runNum + " of " + numRuns); 47 | if (runNum < numRuns) { 48 | // Still have more runs to do, schedule the next one 49 | ++runNum; 50 | inner.src = 'http://localhost:9876/base/benchmark/index.html'; 51 | setTimeout(runBenches, 1000); 52 | } 53 | done(); 54 | }); 55 | } 56 | }; 57 | // Wait at least 10 seconds before checking to see if run is finished 58 | setTimeout(tofunc, 10000); 59 | } 60 | 61 | setTimeout(countRuns, 500); 62 | }); 63 | 64 | // Takes an HTMLCollection object and returns an array of its contents. 65 | function collectionToArray( collection ) { 66 | var size = collection.length; 67 | var arr = []; 68 | for (index = 0; index < size; ++index) { 69 | var bench = makeBenchObject(collection[index]); 70 | if (bench != null) { 71 | arr.push(bench); 72 | } 73 | } 74 | return arr; 75 | } 76 | 77 | // Takes a score-entry-block HTMLDivElement object and returns an object with: 78 | // "benchName", "benchScore", and "benchFPS" attributes 79 | function makeBenchObject( element ) { 80 | var ret = {}; 81 | var anchors = element.getElementsByTagName("a"); 82 | var len = anchors.length; 83 | if (len < 1) { 84 | return null; 85 | } 86 | var divs = element.getElementsByTagName("div"); 87 | ret.benchName = anchors[0].innerHTML; 88 | var bScore = Number(divs[0].innerHTML); 89 | if (isNaN(bScore)) { 90 | ret.benchScore = 0; 91 | } else { 92 | ret.benchScore = bScore; 93 | } 94 | var fps = Number(anchors[1].innerHTML); 95 | if (isNaN(fps)) { 96 | ret.benchFPS = 0; 97 | } else { 98 | ret.benchFPS = fps; 99 | } 100 | return ret; 101 | } 102 | 103 | // Creates and submits a form containing the scores of the latest benchmark run. 104 | function sendScoreToServer( inner, score, cb ) { 105 | var doc = inner.contentWindow.document; 106 | var benches = collectionToArray(doc.getElementsByClassName("score-entry-block")); 107 | var scoreForm = doc.createElement('form'); 108 | scoreForm.action = "http://localhost:3000/savescore"; 109 | scoreForm.method = "post"; 110 | scoreForm.name = "sendscore"; 111 | scoreForm.enctype = "application/x-www-form-urlencoded"; 112 | scoreForm.appendChild(hiddenInput(doc, "finalscore", score)); 113 | scoreForm.appendChild(hiddenInput(doc, "browser", trimBrowser())); 114 | scoreForm.appendChild(hiddenInput(doc, "benches", JSON.stringify(benches, null, 2))); 115 | doc.body.appendChild(scoreForm); 116 | scoreForm.submit(); 117 | 118 | var finishFunc = function() { 119 | var innerLoc = document.getElementsByTagName("iframe")[0].contentWindow.location; 120 | if (innerLoc.href.lastIndexOf("pass.html") != -1) { 121 | cb(true); 122 | } else { 123 | cb(false); 124 | } 125 | }; 126 | setTimeout(finishFunc, 1000); 127 | } 128 | 129 | // Creates a hidden input element to be added to a form in an HTML DOM document. 130 | function hiddenInput( doc, name, value ) { 131 | var input = doc.createElement('input'); 132 | input.type = "hidden"; 133 | input.name = name; 134 | input.value = value; 135 | return input; 136 | } 137 | 138 | // Returns an abbreviated form of the browser type. 139 | function trimBrowser() { 140 | var agent = navigator.userAgent; 141 | if (agent.lastIndexOf('Firefox') != -1) { 142 | return 'Firefox'; 143 | } else if (agent.lastIndexOf('Chrome') != -1) { 144 | return 'Chrome'; 145 | } else { 146 | return 'Other'; 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /benchmark/benchmarks.js: -------------------------------------------------------------------------------- 1 | /*jslint nomen: true, browser: true, devel: true */ 2 | 3 | (function () { 4 | /* global POTATOES */ 5 | /* global sigplot */ 6 | 7 | var plot = null; 8 | var container = null; 9 | var data = null; 10 | var render; 11 | 12 | var init = function (workbench, width, height) { 13 | // Getting container and context 14 | container = document.createElement('div'); 15 | workbench.appendChild(container); 16 | 17 | 18 | // Setting hardware scaling 19 | container.width = width; 20 | container.style.width = width+'px'; 21 | container.height = height; 22 | container.style.height = height+'px'; 23 | 24 | // Position 25 | container.style.position = "absolute"; 26 | container.style.left = (workbench.clientWidth - width) / 2 + "px"; 27 | container.style.top = (workbench.clientHeight - height) / 2 + "px"; 28 | }; 29 | 30 | var refreshLoop = function() { 31 | plot._refresh(); 32 | if (render) { // Run again 33 | POTATOES.Tools.queueNewFrame(refreshLoop); 34 | } else { // Cleanup 35 | plot = null; 36 | data = null; 37 | } 38 | }; 39 | 40 | var reloadLoop = function() { 41 | plot.reload(0, data); 42 | if (render) { // Run again 43 | POTATOES.Tools.queueNewFrame(reloadLoop); 44 | } else { // Cleanup 45 | plot = null; 46 | data = null; 47 | } 48 | }; 49 | 50 | var pushLoop = function() { 51 | plot.push(0, data, null, true); 52 | if (render) { // Run again 53 | POTATOES.Tools.queueNewFrame(pushLoop); 54 | } else { // Cleanup 55 | plot = null; 56 | data = null; 57 | } 58 | }; 59 | 60 | var emptyBench = new POTATOES.GamingBench.Bench("Empty SigPlot", "http://sigplot.lgsinnovations.com/sigplot", 61 | function (workbench) { // Init 62 | init(workbench, 600, 400); 63 | plot = new sigplot.Plot(container, {all: true, expand: true}); 64 | this.onInitCompleted(); 65 | }, function () { // Run 66 | render = true; 67 | POTATOES.Tools.queueNewFrame(refreshLoop); 68 | }, function () { // End 69 | render = false; 70 | }); 71 | 72 | POTATOES.GamingBench.registerBench(emptyBench); 73 | 74 | var Layer1DSmallBench = new POTATOES.GamingBench.Bench("Layer 1D (1k points)", "http://sigplot.lgsinnovations.com/sigplot", 75 | function (workbench) { // Init 76 | init(workbench, 600, 400); 77 | plot = new sigplot.Plot(container, {all: true, expand: true}); 78 | data = []; 79 | for (var i = 0; i < 1024; i++) { 80 | data.push(i); 81 | } 82 | plot.overlay_array(data, { 83 | file_name: "data" 84 | }); 85 | this.onInitCompleted(); 86 | }, function () { // Run 87 | render = true; 88 | POTATOES.Tools.queueNewFrame(refreshLoop); 89 | }, function () { // End 90 | render = false; 91 | }); 92 | 93 | POTATOES.GamingBench.registerBench(Layer1DSmallBench); 94 | 95 | var Layer1DLargeBench = new POTATOES.GamingBench.Bench("Layer 1D (1M points)", "http://sigplot.lgsinnovations.com/sigplot", 96 | function (workbench) { // Init 97 | init(workbench, 600, 400); 98 | plot = new sigplot.Plot(container, {all: true, expand: true}); 99 | data = []; 100 | for (var i = 0; i < 1048576; i++) { 101 | data.push(i); 102 | } 103 | plot.overlay_array(data, { 104 | file_name: "data" 105 | }); 106 | this.onInitCompleted(); 107 | }, function () { // Run 108 | render = true; 109 | POTATOES.Tools.queueNewFrame(refreshLoop); 110 | }, function () { // End 111 | render = false; 112 | }); 113 | 114 | POTATOES.GamingBench.registerBench(Layer1DLargeBench); 115 | 116 | var Layer1DSmallReloadBench = new POTATOES.GamingBench.Bench("Layer 1D Reload (1k points)", "http://sigplot.lgsinnovations.com/sigplot", 117 | function (workbench) { // Init 118 | init(workbench, 600, 400); 119 | plot = new sigplot.Plot(container, {all: true, expand: true}); 120 | data = []; 121 | for (var i = 0; i < 1024; i++) { 122 | data.push(i); 123 | } 124 | plot.overlay_array(data, { 125 | file_name: "data" 126 | }); 127 | this.onInitCompleted(); 128 | }, function () { // Run 129 | render = true; 130 | POTATOES.Tools.queueNewFrame(reloadLoop); 131 | }, function () { // End 132 | render = false; 133 | }); 134 | 135 | POTATOES.GamingBench.registerBench(Layer1DSmallReloadBench); 136 | 137 | var Layer1DLargeReloadBench = new POTATOES.GamingBench.Bench("Layer 1D Reload (1M points)", "http://sigplot.lgsinnovations.com/sigplot", 138 | function (workbench) { // Init 139 | init(workbench, 600, 400); 140 | plot = new sigplot.Plot(container, {all: true, expand: true}); 141 | data = []; 142 | for (var i = 0; i < 1048576; i++) { 143 | data.push(i); 144 | } 145 | plot.overlay_array(data, { 146 | file_name: "data" 147 | }); 148 | this.onInitCompleted(); 149 | }, function () { // Run 150 | render = true; 151 | POTATOES.Tools.queueNewFrame(reloadLoop); 152 | }, function () { // End 153 | render = false; 154 | }); 155 | 156 | POTATOES.GamingBench.registerBench(Layer1DLargeReloadBench); 157 | 158 | 159 | var Layer2DSmallPushBench = new POTATOES.GamingBench.Bench("Streaming 2D (1k points)", "http://sigplot.lgsinnovations.com/sigplot", 160 | function (workbench) { // Init 161 | init(workbench, 600, 400); 162 | plot = new sigplot.Plot(container, {all: true, expand: true}); 163 | 164 | plot.change_settings({ 165 | autol: 5, 166 | }); 167 | 168 | var framesize = 1024; 169 | data = []; 170 | for (var i = 0; i < framesize; i += 1) { 171 | data.push(i + 1); 172 | } 173 | plot.overlay_pipe({ 174 | type: 2000, 175 | subsize: framesize, 176 | file_name: "data" 177 | }); 178 | this.onInitCompleted(); 179 | }, function () { // Run 180 | render = true; 181 | POTATOES.Tools.queueNewFrame(pushLoop); 182 | }, function () { // End 183 | render = false; 184 | }); 185 | 186 | POTATOES.GamingBench.registerBench(Layer2DSmallPushBench); 187 | 188 | var Layer2DMediumPushBench = new POTATOES.GamingBench.Bench("Streaming 2D (64k points)", "http://sigplot.lgsinnovations.com/sigplot", 189 | function (workbench) { // Init 190 | init(workbench, 600, 400); 191 | plot = new sigplot.Plot(container, {all: true, expand: true}); 192 | 193 | plot.change_settings({ 194 | autol: 5, 195 | }); 196 | 197 | var framesize = 64*1024; 198 | data = []; 199 | for (var i = 0; i < framesize; i += 1) { 200 | data.push(i + 1); 201 | } 202 | plot.overlay_pipe({ 203 | type: 2000, 204 | subsize: framesize, 205 | file_name: "data" 206 | }); 207 | this.onInitCompleted(); 208 | }, function () { // Run 209 | render = true; 210 | POTATOES.Tools.queueNewFrame(pushLoop); 211 | }, function () { // End 212 | render = false; 213 | }); 214 | 215 | POTATOES.GamingBench.registerBench(Layer2DMediumPushBench); 216 | 217 | var Layer2DLargePushBench = new POTATOES.GamingBench.Bench("Streaming 2D (256k points)", "http://sigplot.lgsinnovations.com/sigplot", 218 | function (workbench) { // Init 219 | init(workbench, 600, 400); 220 | plot = new sigplot.Plot(container, {all: true, expand: true}); 221 | plot._refresh(); // refresh now so that it displays while we wait for the initial data to be created 222 | 223 | plot.change_settings({ 224 | autol: 5, 225 | }); 226 | 227 | var framesize = 256*1024; 228 | data = []; 229 | for (var i = 0; i < framesize; i += 1) { 230 | data.push(i + 1); 231 | } 232 | plot.overlay_pipe({ 233 | type: 2000, 234 | subsize: framesize, 235 | file_name: "data" 236 | }); 237 | this.onInitCompleted(); 238 | }, function () { // Run 239 | render = true; 240 | POTATOES.Tools.queueNewFrame(pushLoop); 241 | }, function () { // End 242 | render = false; 243 | }); 244 | 245 | POTATOES.GamingBench.registerBench(Layer2DLargePushBench); 246 | 247 | var Layer2DSmallBench = new POTATOES.GamingBench.Bench("Layer 2D (1k points)", "http://sigplot.lgsinnovations.com/sigplot", 248 | function (workbench) { // Init 249 | init(workbench, 600, 400); 250 | plot = new sigplot.Plot(container, {all: true, expand: true}); 251 | 252 | var framesize = 1024; 253 | var height = 1024; 254 | data = []; 255 | for (var j = 0; j < height; j += 1) { 256 | for (var i = 0; i < framesize; i += 1) { 257 | data.push(i + 1); 258 | } 259 | } 260 | plot.overlay_array(data, { 261 | type: 2000, 262 | subsize: framesize, 263 | file_name: "data" 264 | }); 265 | this.onInitCompleted(); 266 | }, function () { // Run 267 | render = true; 268 | POTATOES.Tools.queueNewFrame(refreshLoop); 269 | }, function () { // End 270 | render = false; 271 | }); 272 | 273 | POTATOES.GamingBench.registerBench(Layer2DSmallBench); 274 | 275 | var Layer2DLargeBench = new POTATOES.GamingBench.Bench("Layer 2D (64k points)", "http://sigplot.lgsinnovations.com/sigplot", 276 | function (workbench) { // Init 277 | init(workbench, 600, 400); 278 | plot = new sigplot.Plot(container, {all: true, expand: true}); 279 | plot._refresh(); // refresh now so that it displays while we wait for the initial data to be created 280 | 281 | var framesize = 65536; 282 | var height = 1024; 283 | data = []; 284 | for (var j = 0; j < height; j += 1) { 285 | for (var i = 0; i < framesize; i += 1) { 286 | data.push(i + 1); 287 | } 288 | } 289 | plot.overlay_array(data, { 290 | type: 2000, 291 | subsize: framesize, 292 | file_name: "data" 293 | }); 294 | this.onInitCompleted(); 295 | }, function () { // Run 296 | render = true; 297 | POTATOES.Tools.queueNewFrame(refreshLoop); 298 | }, function () { // End 299 | render = false; 300 | }); 301 | 302 | POTATOES.GamingBench.registerBench(Layer2DLargeBench); 303 | 304 | })(); 305 | -------------------------------------------------------------------------------- /benchmark/express.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var app = express(); 3 | var bodyParser = require('body-parser'); 4 | app.use(bodyParser.urlencoded({ extended: true })); 5 | 6 | app.post('/savescore', function (req, res) { 7 | // Client finished run, check score to see if it passes regression threshold 8 | var contents = req.body; 9 | var score = contents; 10 | var benches = JSON.parse(contents.benches); 11 | contents.finalscore = Number(contents.finalscore); 12 | contents.benches = benches; 13 | contents.timestamp = new Date(); 14 | var browser = contents.browser; 15 | writeScoreToFile(score, browser, function (passed) { 16 | // Use redirect to tell client whether run passed or failed 17 | if (passed) { 18 | res.redirect('http://localhost:9876/base/benchmark/pass.html?score=' + score.finalscore); 19 | } else { 20 | res.redirect('http://localhost:9876/base/benchmark/fail.html?score=' + score.finalscore); 21 | } 22 | }); 23 | }); 24 | 25 | app.get('/countscores', function (req, res) { 26 | // Client asking how many benchmark runs to do, check number of scores in existing file 27 | var browser = req.query.browser; 28 | countScores(browser, function(numRuns) { 29 | // Redirect client to HTML5 Potatoes test page with # runs to do as query parameter 30 | res.redirect('http://localhost:9876/base/benchmark/index.html?autolaunch=' + numRuns); 31 | }); 32 | }); 33 | 34 | app.listen(3000, function () { 35 | console.log('Benchmark score reader/writer listening on port 3000!'); 36 | }); 37 | 38 | // Determine how many benchmark runs client needs to do to populate scores file for browser 39 | function countScores( browser, cb ) { 40 | var fs = require("fs"); 41 | if (!fs.existsSync("benchmark/json")) { 42 | fs.mkdirSync("benchmark/json"); 43 | } 44 | var numRuns = 10; 45 | var fileName = "benchmark/json/Scores_" + browser + ".json"; 46 | if (fs.existsSync(fileName)) { 47 | var fileData = fs.readFileSync(fileName); 48 | var JSONdata = JSON.parse(fileData); 49 | var scoreData = JSONdata.scores; 50 | var numScores = scoreData.length; 51 | numRuns = 10 - numScores; 52 | } 53 | if (numRuns < 1) { 54 | // Already 10+ scores for comparison, so just do 1 run to check against them 55 | numRuns = 1; 56 | } 57 | cb(numRuns); 58 | } 59 | 60 | // Return false if final score lower than mean of last 10 successful runs by > 1 sigma 61 | function scorePasses( score, prevScores ) { 62 | testBenchScores(score, prevScores); 63 | var mean = getMean(prevScores); 64 | var stdDev = getStdDev(prevScores, mean); 65 | console.log("Mean: " + mean + " Sigma: " + stdDev + " Score: " + score.finalscore); 66 | var currentDiff = mean - score.finalscore; 67 | if (currentDiff > stdDev) { 68 | return false; 69 | } else { 70 | return true; 71 | } 72 | } 73 | 74 | // Attach "failed" attribute to any specific benchmark in run that shows regression 75 | function testBenchScores( score, prevScores ) { 76 | var benches = score.benches; 77 | var numBenches = benches.length; 78 | for (var benchIndex = 0; benchIndex < numBenches; ++benchIndex) { 79 | var bench = benches[benchIndex]; 80 | if (!benchScorePassesByIndex(score, prevScores, benchIndex)) { 81 | bench.failed = true; 82 | } 83 | } 84 | } 85 | 86 | // Return mean of last 10 successful runs' final scores 87 | function getMean( scoreArray ) { 88 | if (!Array.isArray(scoreArray)) { 89 | return 0; 90 | } 91 | var numValues = scoreArray.length; 92 | if (numValues == 0) { 93 | return 0; 94 | } 95 | var sum = 0; 96 | for (var index = 0; index < numValues; ++index) { 97 | sum += scoreArray[index].finalscore; 98 | } 99 | return sum / numValues; 100 | } 101 | 102 | // Return standard deviation (sigma) of last 10 successful runs' final scores 103 | function getStdDev( scoreArray, mean ) { 104 | if (!Array.isArray(scoreArray)) { 105 | return 0; 106 | } 107 | var numValues = scoreArray.length; 108 | if (numValues == 0) { 109 | return 0; 110 | } 111 | var totalDev = 0; 112 | for (var index = 0; index < numValues; ++index) { 113 | var diff = scoreArray[index].finalscore - mean; 114 | totalDev += (diff * diff); 115 | } 116 | var variance = totalDev / numValues; 117 | return Math.sqrt(variance); 118 | } 119 | 120 | // Get score of specific benchmark from results submitted by client 121 | function getBenchScore( score, benchName ) { 122 | var benches = score.benches; 123 | var numBenches = benches.length; 124 | for (var benchIndex = 0; benchIndex < numBenches; ++benchIndex) { 125 | var bench = benches[benchIndex]; 126 | if (bench.benchName == benchName) { 127 | return bench.benchScore; 128 | } 129 | } 130 | return 0; 131 | } 132 | 133 | // Get index of specific benchmark in score array 134 | function getBenchIndex( score, benchName ) { 135 | var benches = score.benches; 136 | var numBenches = benches.length; 137 | for (var benchIndex = 0; benchIndex < numBenches; ++benchIndex) { 138 | var bench = benches[benchIndex]; 139 | if (bench.benchName == benchName) { 140 | return benchIndex; 141 | } 142 | } 143 | return -1; 144 | } 145 | 146 | // Return false if benchmark score lower than mean of last 10 successful runs by > 1 sigma 147 | function benchScorePasses( score, prevScores, benchName ) { 148 | var mean = getBenchMean(prevScores, benchName); 149 | var stdDev = getBenchStdDev(prevScores, benchName, mean); 150 | var currentDiff = mean - getBenchScore(score, benchName); 151 | if (currentDiff > stdDev) { 152 | return false; 153 | } else { 154 | return true; 155 | } 156 | } 157 | 158 | // Return mean score for specific benchmark for last 10 successful runs 159 | function getBenchMean( scoreArray, benchName ) { 160 | if (!Array.isArray(scoreArray)) { 161 | return 0; 162 | } 163 | var numValues = scoreArray.length; 164 | if (numValues == 0) { 165 | return 0; 166 | } 167 | var sum = 0; 168 | for (var index = 0; index < numValues; ++index) { 169 | sum += getBenchScore(scoreArray[index], benchName); 170 | } 171 | return sum / numValues; 172 | } 173 | 174 | // Return standard deviation of score for specific benchmark for last 10 successful runs 175 | function getBenchStdDev( scoreArray, benchName, mean ) { 176 | if (!Array.isArray(scoreArray)) { 177 | return 0; 178 | } 179 | var numValues = scoreArray.length; 180 | if (numValues == 0) { 181 | return 0; 182 | } 183 | var totalDev = 0; 184 | for (var index = 0; index < numValues; ++index) { 185 | var benchScore = getBenchScore(scoreArray[index], benchName); 186 | var diff = benchScore - mean; 187 | totalDev += (diff * diff); 188 | } 189 | var variance = totalDev / numValues; 190 | return Math.sqrt(variance); 191 | } 192 | 193 | // Index-based version of benchScorePasses() to improve efficiency 194 | function benchScorePassesByIndex( score, prevScores, benchIndex ) { 195 | var mean = getBenchMeanByIndex(prevScores, benchIndex); 196 | var stdDev = getBenchStdDevByIndex(prevScores, benchIndex, mean); 197 | var currentDiff = mean - score.benches[benchIndex].benchScore; 198 | if (currentDiff > stdDev) { 199 | return false; 200 | } else { 201 | return true; 202 | } 203 | } 204 | 205 | // Index-based version of getBenchMean() to improve efficiency 206 | function getBenchMeanByIndex( scoreArray, benchIndex ) { 207 | if (!Array.isArray(scoreArray)) { 208 | return 0; 209 | } 210 | var numValues = scoreArray.length; 211 | if (numValues == 0) { 212 | return 0; 213 | } 214 | var sum = 0; 215 | for (var index = 0; index < numValues; ++index) { 216 | sum += scoreArray[index].benches[benchIndex].benchScore; 217 | } 218 | return sum / numValues; 219 | } 220 | 221 | // Index-based version of getBenchStdDev() to improve efficiency 222 | function getBenchStdDevByIndex( scoreArray, benchIndex, mean ) { 223 | if (!Array.isArray(scoreArray)) { 224 | return 0; 225 | } 226 | var numValues = scoreArray.length; 227 | if (numValues == 0) { 228 | return 0; 229 | } 230 | var totalDev = 0; 231 | for (var index = 0; index < numValues; ++index) { 232 | var benchScore = scoreArray[index].benches[benchIndex].benchScore; 233 | var diff = benchScore - mean; 234 | totalDev += (diff * diff); 235 | } 236 | var variance = totalDev / numValues; 237 | return Math.sqrt(variance); 238 | } 239 | 240 | // Test last score against 10 previous successful ones, and add to appropriate file 241 | function writeScoreToFile( score, browser, cb) { 242 | var fs = require("fs"); 243 | if (!fs.existsSync("benchmark/json")) { 244 | fs.mkdirSync("benchmark/json"); 245 | } 246 | var fileName = "benchmark/json/Scores_" + browser + ".json"; 247 | var fileData = "{\n\t\"scores\": []\n}"; 248 | if (fs.existsSync(fileName)) { 249 | fileData = fs.readFileSync(fileName); 250 | } 251 | var JSONdata = JSON.parse(fileData); 252 | var scoreData = JSONdata.scores; 253 | var numScores = scoreData.length; 254 | if (numScores < 10) { 255 | // Too few scores to do analysis yet, just add last run to browser's score file 256 | scoreData.push(score); 257 | cb(true); 258 | } else if (scorePasses(score, scoreData)) { 259 | // Add last run to browser's score file, and remove oldest one 260 | scoreData.push(score); 261 | scoreData.shift(); 262 | cb(true); 263 | } else { 264 | // Store last run in the list of failures 265 | fileName = "benchmark/json/FailedScores.json"; 266 | fileData = "{ \"failedScores\": [] }" 267 | if (fs.existsSync(fileName)) { 268 | fileData = fs.readFileSync(fileName); 269 | } 270 | JSONdata = JSON.parse(fileData); 271 | JSONdata.failedScores.push(score); 272 | cb(false); 273 | } 274 | fs.writeFileSync(fileName, JSON.stringify(JSONdata, null, 4)); 275 | } 276 | -------------------------------------------------------------------------------- /benchmark/fail.html: -------------------------------------------------------------------------------- 1 | Benchmark failed! 2 | -------------------------------------------------------------------------------- /benchmark/gamingbench.js: -------------------------------------------------------------------------------- 1 | var POTATOES = POTATOES || {}; 2 | 3 | (function () { 4 | // Internals 5 | var currentBenchIndex = -1; 6 | var currentBench = null; 7 | 8 | 9 | var state; 10 | 11 | var fpsDiv; 12 | var progress; 13 | var progressText; 14 | var benchName; 15 | var benchTitle; 16 | var benchTitleBackground; 17 | var workbench; 18 | 19 | var progressInteval; 20 | var durationTimeout; 21 | 22 | var startTime; 23 | 24 | var fpsFrame = 20; // fps window frame 25 | var fpsCap = 60; 26 | var previousFramesDuration = []; 27 | 28 | var handleMetrics = function () { 29 | if (!currentBench) { 30 | return; 31 | } 32 | 33 | currentBench.score++; 34 | 35 | previousFramesDuration.push(Date.now()); 36 | 37 | if (previousFramesDuration.length >= fpsFrame) { 38 | 39 | if (previousFramesDuration.length > fpsFrame) { 40 | previousFramesDuration.splice(0, 1); 41 | } 42 | 43 | var avg = 0; 44 | for (var id = 0; id < fpsFrame - 1; id++) { 45 | avg += previousFramesDuration[id + 1] - previousFramesDuration[id]; 46 | } 47 | avg /= fpsFrame - 1; 48 | 49 | POTATOES.GamingBench.currentFPS = Math.min(fpsCap, 1000.0 / (previousFramesDuration[fpsFrame - 1] - previousFramesDuration[fpsFrame - 2])); 50 | POTATOES.GamingBench.averageFPS = Math.min(fpsCap, 1000.0 / avg); 51 | 52 | if (state === "running" && isFinite(POTATOES.GamingBench.currentFPS)) { 53 | fpsDiv.innerHTML = POTATOES.GamingBench.currentFPS.toFixed() + " fps"; 54 | 55 | if (currentBench.stats.length == 0) { 56 | startTime = Date.now(); 57 | } 58 | 59 | currentBench.stats.push({ x: (Date.now() - startTime) / 1000, y: POTATOES.GamingBench.averageFPS }); 60 | } 61 | } 62 | 63 | if (state === "running") { 64 | POTATOES.Tools.queueNewFrame(handleMetrics); 65 | } 66 | }; 67 | 68 | var updateProgress = function () { 69 | if (progress.value > 100) 70 | return; 71 | 72 | progress.value += 5; 73 | progressText.innerHTML = progress.value + "%"; 74 | }; 75 | 76 | var stopCurrentBench = function (stop) { 77 | state = "stopped"; 78 | clearInterval(progressInteval); 79 | 80 | benchTitle.removeEventListener("animationend", onAnimationEnd); 81 | benchTitle.removeEventListener("webkitAnimationEnd", onAnimationEnd); 82 | 83 | // Stopping 84 | if (currentBench) { 85 | currentBench.onInitCompleted = function () { }; 86 | currentBench.stop(workbench); 87 | 88 | // Removing all children 89 | while (workbench.childNodes.length) { 90 | workbench.removeChild(workbench.childNodes[0]); 91 | } 92 | } 93 | 94 | // Running next one 95 | if (!stop) { 96 | runBench(currentBenchIndex + 1); 97 | } else { 98 | currentBench = null; 99 | } 100 | }; 101 | 102 | // When animations end 103 | var onAnimationEnd = function () { 104 | POTATOES.Tools.removeClass(benchTitle, "animate-bench-title"); 105 | POTATOES.Tools.removeClass(benchTitleBackground, "animate-bench-title-background"); 106 | 107 | benchTitle.removeEventListener("animationend", onAnimationEnd); 108 | benchTitle.removeEventListener("webkitAnimationEnd", onAnimationEnd); 109 | 110 | if (!currentBench) { 111 | return; 112 | } 113 | 114 | // Bench initialization 115 | benchName.innerHTML = currentBench.name; 116 | 117 | // On init completed 118 | currentBench.onInitCompleted = function () { 119 | // Set duration check 120 | durationTimeout = setTimeout(stopCurrentBench, currentBench.stepDuration); 121 | progressInteval = setInterval(updateProgress, currentBench.stepDuration / 20); 122 | 123 | // Launch bench 124 | if (currentBench.isSupported()) { 125 | state = "running"; 126 | currentBench.run(); 127 | 128 | // Preparing framework 129 | previousFramesDuration = []; 130 | POTATOES.Tools.queueNewFrame(handleMetrics); 131 | } else { 132 | stopCurrentBench(false); 133 | } 134 | }; 135 | 136 | // Launch bench initialization 137 | currentBench.init(workbench); 138 | }; 139 | 140 | // Launch bench 141 | var runBench = function (index) { 142 | if (index >= POTATOES.GamingBench.benches.length) { 143 | currentBench = null; 144 | if (POTATOES.GamingBench.onprocessended) { 145 | POTATOES.GamingBench.onprocessended(); 146 | } 147 | return; 148 | } 149 | 150 | currentBenchIndex = index; 151 | currentBench = POTATOES.GamingBench.benches[index]; 152 | currentBench.score = 0; 153 | currentBench.stats = []; 154 | 155 | fpsDiv.innerHTML = ""; 156 | previousFramesDuration = []; 157 | progress.value = 0; 158 | progressText.innerHTML = "0%"; 159 | benchName.innerHTML = ""; 160 | benchTitle.innerHTML = currentBench.name; 161 | 162 | if (benchTitle.style.webkitAnimationName !== undefined || benchTitle.style.animationName !== undefined) { 163 | benchTitle.addEventListener("animationend", onAnimationEnd, false); 164 | benchTitle.addEventListener("webkitAnimationEnd", onAnimationEnd, false); 165 | 166 | // Launching animations 167 | POTATOES.Tools.addClass(benchTitle, "animate-bench-title"); 168 | POTATOES.Tools.addClass(benchTitleBackground, "animate-bench-title-background"); 169 | } else { 170 | // No animations 171 | onAnimationEnd(); 172 | } 173 | }; 174 | 175 | // Gaming Bench engine 176 | POTATOES.GamingBench = { 177 | onprocessended: null, 178 | benches: [], 179 | currentFPS: 0 180 | }; 181 | 182 | POTATOES.GamingBench.Bench = function (name, url, onInit, onRun, onStop, isSupported) { 183 | return { 184 | run: onRun, 185 | init: onInit, 186 | stop: onStop, 187 | name: name, 188 | url: url, 189 | score: 0, 190 | stats: [], 191 | isSupported: function () { 192 | if (isSupported) { 193 | return isSupported(); 194 | } 195 | return true; 196 | }, 197 | stepDuration: 20000, 198 | onInitCompleted: null 199 | }; 200 | }; 201 | 202 | POTATOES.GamingBench.registerBench = function (bench) { 203 | POTATOES.GamingBench.benches.push(bench); 204 | }; 205 | 206 | POTATOES.GamingBench.cancel = function () { 207 | POTATOES.Tools.removeClass(benchTitle, "animate-bench-title"); 208 | POTATOES.Tools.removeClass(benchTitleBackground, "animate-bench-title-background"); 209 | clearTimeout(durationTimeout); 210 | previousFramesDuration = []; 211 | 212 | if (currentBench) { 213 | stopCurrentBench(true); 214 | } 215 | }; 216 | 217 | POTATOES.GamingBench.skipCurrent = function () { 218 | clearTimeout(durationTimeout); 219 | previousFramesDuration = []; 220 | 221 | if (currentBench) { 222 | stopCurrentBench(); 223 | } 224 | }; 225 | 226 | POTATOES.GamingBench.start = function () { 227 | // Getting fps display zone and progress indicator 228 | fpsDiv = document.getElementById("bench-fps"); 229 | progress = document.getElementById("bench-status-progress"); 230 | progressText = document.getElementById("bench-status-progress-text"); 231 | benchName = document.getElementById("bench-name"); 232 | benchTitle = document.getElementById("bench-title"); 233 | benchTitleBackground = document.getElementById("bench-title-background"); 234 | workbench = document.getElementById("bench-work"); 235 | 236 | // Preparing animations 237 | POTATOES.Tools.removeClass(benchTitle, "animate-bench-title"); 238 | POTATOES.Tools.removeClass(benchTitleBackground, "animate-bench-title-background"); 239 | 240 | // Launch first bench 241 | runBench(0); 242 | }; 243 | })(); -------------------------------------------------------------------------------- /benchmark/index.css: -------------------------------------------------------------------------------- 1 | * { 2 | font-family: Tahoma; 3 | cursor: default; 4 | } 5 | 6 | html, body { 7 | width: 100%; 8 | height: 100%; 9 | margin: 0; 10 | padding: 0; 11 | overflow: hidden; 12 | } 13 | 14 | a { 15 | text-decoration: none; 16 | color: white; 17 | } 18 | 19 | a:visited { 20 | color: white; 21 | } 22 | 23 | .screen { 24 | position: absolute; 25 | width: 100%; 26 | height: 100%; 27 | margin: 0; 28 | padding: 0; 29 | background-color: black; 30 | z-index: 1; 31 | transition: opacity 0.5s linear; 32 | -webkit-transition: opacity 0.5s linear; 33 | } 34 | 35 | .hidden { 36 | opacity: 0; 37 | z-index: -10 !important; 38 | } 39 | 40 | .buttonLink { 41 | font-size: 50px; 42 | font-weight: bold; 43 | color: white; 44 | position: absolute; 45 | text-decoration: none; 46 | } 47 | 48 | .buttonLink:hover { 49 | opacity: 0.8; 50 | } 51 | 52 | .small-button { 53 | font-size: 20px; 54 | color: white; 55 | width: 150px; 56 | height: 50px; 57 | } 58 | 59 | .button:hover { 60 | opacity: 0.8; 61 | } 62 | 63 | .green { 64 | color: white; 65 | border: 1px solid #005500; 66 | background-color: green; 67 | } 68 | 69 | .orange { 70 | color: white; 71 | border: 1px solid #FFFF85; 72 | background-color: orange; 73 | } 74 | 75 | .red { 76 | background-color: red; 77 | border: 1px solid #550000; 78 | } 79 | 80 | /*HOME*/ 81 | #home { 82 | background-color: white; 83 | } 84 | 85 | #home-top { 86 | position: absolute; 87 | width: 100%; 88 | height: 40%; 89 | margin-bottom: 5%; 90 | margin-top: 20px; 91 | } 92 | 93 | #home-logo { 94 | position: absolute; 95 | width: 100%; 96 | height: 100%; 97 | } 98 | 99 | #home-controls { 100 | position: absolute; 101 | width: 100%; 102 | height: 50%; 103 | top: 50%; 104 | text-align: center; 105 | background-color: black; 106 | z-index: 1; 107 | } 108 | 109 | #home-nohtml5 { 110 | position: absolute; 111 | width: 100%; 112 | height: 50%; 113 | top: 50%; 114 | text-align: center; 115 | background-color: black; 116 | color: white; 117 | font-size: 50px; 118 | font-weight: bold; 119 | } 120 | 121 | #home-version { 122 | position: absolute; 123 | bottom: 10px; 124 | right: 10px; 125 | font-size: 15px; 126 | color: #cccccc; 127 | } 128 | 129 | #home-brand { 130 | position: absolute; 131 | bottom: 10px; 132 | width: 100%; 133 | text-align: center; 134 | font-size: 12px; 135 | color: #cccccc; 136 | font-style: italic; 137 | } 138 | 139 | #home-launch { 140 | top: 50%; 141 | margin-top: -50px; 142 | height: 100px; 143 | right: 50%; 144 | margin-right: 20px; 145 | cursor: pointer; 146 | } 147 | 148 | #home-launch-mobile { 149 | top: 50%; 150 | margin-top: -50px; 151 | height: 100px; 152 | left: 50%; 153 | margin-left: 20px; 154 | cursor: pointer; 155 | } 156 | 157 | 158 | /*BENCH*/ 159 | #bench-header { 160 | position: absolute; 161 | width: 100%; 162 | height: 80px; 163 | z-index: 1; 164 | background-color: black; 165 | } 166 | 167 | #bench-work { 168 | position: absolute; 169 | width: 100%; 170 | top: 80px; 171 | bottom: 80px; 172 | background-color: #222222; 173 | } 174 | 175 | #bench-menu { 176 | position: absolute; 177 | width: 100%; 178 | height: 80px; 179 | bottom: 0px; 180 | z-index: 1; 181 | background-color: black; 182 | } 183 | 184 | #bench-cancel { 185 | position: absolute; 186 | top: 15px; 187 | right: 20px; 188 | } 189 | 190 | #bench-skip { 191 | position: absolute; 192 | top: 15px; 193 | right: 180px; 194 | } 195 | 196 | #bench-fps { 197 | font-size: 30px; 198 | color: white; 199 | position: absolute; 200 | top: 20px; 201 | right: 20px; 202 | } 203 | 204 | #bench-name { 205 | font-size: 30px; 206 | font-weight: bold; 207 | color: white; 208 | position: absolute; 209 | top: 20px; 210 | left: 20px; 211 | } 212 | 213 | #bench-title-background { 214 | position: absolute; 215 | background-color: white; 216 | top: 50%; 217 | margin-top: -75px; 218 | height: 150px; 219 | width: 100%; 220 | transform: translateX(-100%); 221 | opacity: 0; 222 | } 223 | 224 | #bench-title { 225 | font-size: 50px; 226 | font-weight: bold; 227 | text-align: center; 228 | position: absolute; 229 | color: black; 230 | top: 50%; 231 | margin-top: -40px; 232 | height: 50px; 233 | width: 100%; 234 | transform: translateX(100%); 235 | opacity: 0; 236 | } 237 | 238 | #bench-status { 239 | position: absolute; 240 | top: 30px; 241 | left: 10px; 242 | color: white; 243 | } 244 | 245 | #bench-status-progress { 246 | color: green; 247 | } 248 | 249 | @keyframes move-bench-title-background { 250 | 0% { 251 | transform: translateX(-100%); 252 | opacity: 1; 253 | } 254 | 255 | 10% { 256 | animation-timing-function: ease-in-out; 257 | transform: translateX(0); 258 | opacity: 1; 259 | } 260 | 261 | 90% { 262 | transform: translateX(0); 263 | opacity: 1; 264 | } 265 | 266 | 100% { 267 | animation-timing-function: ease-in-out; 268 | transform: translateX(-100%); 269 | opacity: 0; 270 | } 271 | } 272 | 273 | @keyframes move-bench-title { 274 | 0% { 275 | transform: translateX(100%); 276 | opacity: 1; 277 | } 278 | 279 | 10% { 280 | animation-timing-function: ease-in-out; 281 | transform: translateX(0); 282 | opacity: 1; 283 | } 284 | 285 | 90% { 286 | transform: translateX(0); 287 | opacity: 1; 288 | } 289 | 290 | 100% { 291 | animation-timing-function: ease-in-out; 292 | transform: translateX(100%); 293 | opacity: 0; 294 | } 295 | } 296 | 297 | @-webkit-keyframes move-bench-title-background { 298 | 0% { 299 | -webkit-transform: translateX(-100%); 300 | opacity: 1; 301 | } 302 | 303 | 10% { 304 | -webkit-animation-timing-function: ease-in-out; 305 | -webkit-transform: translateX(0); 306 | opacity: 1; 307 | } 308 | 309 | 90% { 310 | -webkit-transform: translateX(0); 311 | opacity: 1; 312 | } 313 | 314 | 100% { 315 | -webkit-animation-timing-function: ease-in-out; 316 | -webkit-transform: translateX(-100%); 317 | opacity: 0; 318 | } 319 | } 320 | 321 | @-webkit-keyframes move-bench-title { 322 | 0% { 323 | -webkit-transform: translateX(100%); 324 | opacity: 1; 325 | } 326 | 327 | 10% { 328 | -webkit-animation-timing-function: ease-in-out; 329 | -webkit-transform: translateX(0); 330 | opacity: 1; 331 | } 332 | 333 | 90% { 334 | -webkit-transform: translateX(0); 335 | opacity: 1; 336 | } 337 | 338 | 100% { 339 | -webkit-animation-timing-function: ease-in-out; 340 | -webkit-transform: translateX(100%); 341 | opacity: 0; 342 | } 343 | } 344 | 345 | .animate-bench-title { 346 | animation-delay: 0.5s; 347 | animation-duration: 3s; 348 | animation-iteration-count: 1; 349 | animation-name: move-bench-title; 350 | -webkit-animation-delay: 0.5s; 351 | -webkit-animation-duration: 3s; 352 | -webkit-animation-iteration-count: 1; 353 | -webkit-animation-name: move-bench-title; 354 | } 355 | 356 | .animate-bench-title-background { 357 | animation-delay: 0.5s; 358 | animation-duration: 3s; 359 | animation-iteration-count: 1; 360 | animation-name: move-bench-title-background; 361 | -webkit-animation-delay: 0.5s; 362 | -webkit-animation-duration: 3s; 363 | -webkit-animation-iteration-count: 1; 364 | -webkit-animation-name: move-bench-title-background; 365 | } 366 | 367 | 368 | /*SCORE*/ 369 | #score-header { 370 | position: absolute; 371 | width: 100%; 372 | height: 150px; 373 | } 374 | 375 | #score-footer { 376 | position: absolute; 377 | width: 100%; 378 | height: 150px; 379 | bottom: 0; 380 | } 381 | 382 | #score-footer-text { 383 | position: absolute; 384 | bottom: -5px; 385 | font-size: 100px; 386 | color: white; 387 | width: 100%; 388 | text-align: center; 389 | } 390 | 391 | #score-header-title { 392 | position: absolute; 393 | top: 10px; 394 | font-size: 120px; 395 | color: white; 396 | width: 100%; 397 | text-align: center; 398 | } 399 | 400 | #score-list-background { 401 | position: absolute; 402 | width: 100%; 403 | top: 150px; 404 | bottom: 100px; 405 | background-color: white; 406 | } 407 | 408 | #score-list { 409 | width: 80%; 410 | top: 200px; 411 | height: auto; 412 | bottom: 150px; 413 | margin-left: 10%; 414 | margin-right: 10%; 415 | background-color: #dedede; 416 | overflow: auto; 417 | padding: 10px; 418 | border: 1px solid #999999; 419 | } 420 | 421 | #score-stats { 422 | position: absolute; 423 | width: 100%; 424 | top: 150px; 425 | height: auto; 426 | bottom: 100px; 427 | text-align: center; 428 | background-color: white; 429 | } 430 | 431 | #score-footer-back { 432 | position: absolute; 433 | bottom: 25px; 434 | right: 20px; 435 | } 436 | 437 | .score-entry-block { 438 | width: 100%; 439 | height: 50px; 440 | } 441 | 442 | .score-entry { 443 | float: left; 444 | font-size: 30px; 445 | color: black; 446 | } 447 | 448 | .score-entry:hover { 449 | opacity: 0.7; 450 | } 451 | 452 | .score-entry:visited { 453 | color: black; 454 | } 455 | 456 | .score-entry-link { 457 | text-decoration: underline; 458 | color: #550000; 459 | font-size: 30px; 460 | float: right; 461 | width: 150px; 462 | text-align: right; 463 | cursor: pointer; 464 | } 465 | 466 | .score-entry-link:visited { 467 | text-decoration: underline; 468 | color: #550000; 469 | font-size: 30px; 470 | } 471 | 472 | .score-entry-score { 473 | width: 100px; 474 | text-align: right; 475 | color: #00CC00; 476 | font-size: 30px; 477 | float: right; 478 | } 479 | 480 | .score-entry-header-fps { 481 | width: 150px; 482 | text-align: right; 483 | color: black; 484 | font-size: 30px; 485 | float: right; 486 | } 487 | 488 | .score-entry-header-score { 489 | width: 100px; 490 | text-align: right; 491 | color: black; 492 | font-size: 30px; 493 | float: right; 494 | } 495 | 496 | 497 | /*Stats*/ 498 | .axis path, 499 | .axis line { 500 | fill: none; 501 | stroke: #000; 502 | shape-rendering: crispEdges; 503 | } 504 | 505 | .line { 506 | fill: none; 507 | stroke: steelblue; 508 | stroke-width: 1.5px; 509 | } 510 | 511 | #score-stats-graph { 512 | position: absolute; 513 | width: 100%; 514 | top: 0px; 515 | bottom: 100px; 516 | height: auto; 517 | } 518 | 519 | .score-stats-graph-svg { 520 | width: 100%; 521 | height: 100%; 522 | } 523 | 524 | #score-stats-title { 525 | position: absolute; 526 | width: 100%; 527 | height: 100px; 528 | bottom: 0px; 529 | text-align: center; 530 | color: black; 531 | font-size: 40px; 532 | } 533 | -------------------------------------------------------------------------------- /benchmark/index.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | HTML5 Potatoes - Gaming Bench 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |
19 |
20 |
21 |

22 | launch 23 | mobile 24 |

25 |
version 0.1
26 | 27 |
28 | 32 |
33 | 34 | 35 | 53 | 54 | 55 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /benchmark/index.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var scoreList; 3 | var scoreStats; 4 | var scoreStatsGraph; 5 | 6 | var homePage; 7 | var benchPage; 8 | var scorePage; 9 | 10 | var currentScreen = "home"; 11 | 12 | var displayStats = function (bench) { 13 | return function() { 14 | if (scoreStatsGraph.childNodes.length > 0) { 15 | scoreStatsGraph.removeChild(scoreStatsGraph.childNodes[0]); 16 | } 17 | 18 | // Animations 19 | POTATOES.Tools.addClass(scoreList, "hidden"); 20 | POTATOES.Tools.removeClass(scoreStats, "hidden"); 21 | 22 | currentScreen = "score-graph"; 23 | 24 | if (history.pushState) { 25 | history.pushState({}, "graph", "index.html"); 26 | } 27 | 28 | // Title 29 | document.getElementById("score-stats-title").innerHTML = bench.name; 30 | 31 | // Drawing stats 32 | var clientWidth = scoreStatsGraph.clientWidth; 33 | var clientHeight = scoreStatsGraph.clientHeight; 34 | 35 | var margin = { top: 20, right: 20, bottom: 30, left: 50 }, 36 | width = clientWidth - margin.left - margin.right, 37 | height = clientHeight - margin.top - margin.bottom; 38 | 39 | var x = d3.scale.linear() 40 | .range([0, width]); 41 | 42 | var y = d3.scale.linear() 43 | .range([height, 0]); 44 | 45 | var xAxis = d3.svg.axis() 46 | .scale(x) 47 | .orient("bottom"); 48 | 49 | var yAxis = d3.svg.axis() 50 | .scale(y) 51 | .orient("left"); 52 | 53 | var line = d3.svg.line() 54 | .x(function(d) { return x(d.x); }) 55 | .y(function(d) { return y(d.y); }); 56 | 57 | var svg = d3.select("#score-stats-graph").append("svg") 58 | .attr("viewBox", "0, 0, " + (width + margin.left + margin.right) + ", " + (height + margin.top + margin.bottom)) 59 | //.attr("height", height + margin.top + margin.bottom) 60 | .attr("class", "score-stats-graph-svg") 61 | .append("g") 62 | .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); 63 | 64 | x.domain(d3.extent(bench.stats, function(d) { return d.x; })); 65 | y.domain(d3.extent(bench.stats, function(d) { return d.y; })); 66 | 67 | svg.append("g") 68 | .attr("class", "x axis") 69 | .attr("transform", "translate(0," + height + ")") 70 | .call(xAxis); 71 | 72 | svg.append("g") 73 | .attr("class", "y axis") 74 | .call(yAxis) 75 | .append("text") 76 | .attr("transform", "rotate(-90)") 77 | .attr("y", 6) 78 | .attr("dy", ".71em") 79 | .style("text-anchor", "end") 80 | .text("FPS"); 81 | 82 | svg.append("path") 83 | .datum(bench.stats) 84 | .attr("class", "line") 85 | .attr("d", line); 86 | }; 87 | }; 88 | 89 | var goHome = function () { 90 | currentScreen = "home"; 91 | POTATOES.GamingBench.cancel(); 92 | 93 | // Switching pages 94 | POTATOES.Tools.addClass(scorePage, "hidden"); 95 | POTATOES.Tools.addClass(benchPage, "hidden"); 96 | POTATOES.Tools.removeClass(homePage, "hidden"); 97 | }; 98 | 99 | var goScoreList = function () { 100 | POTATOES.Tools.addClass(scoreStats, "hidden"); 101 | POTATOES.Tools.removeClass(scoreList, "hidden"); 102 | 103 | currentScreen = "score-list"; 104 | }; 105 | 106 | // Events 107 | var onInit = function () { 108 | scoreList = document.getElementById("score-list"); 109 | scoreStats = document.getElementById("score-stats"); 110 | if (scoreStats == null) { 111 | return; 112 | } 113 | scoreStatsGraph = document.getElementById("score-stats-graph"); 114 | 115 | scoreStats.onclick = function () { 116 | goScoreList(); 117 | }; 118 | 119 | // History 120 | if (window.onpopstate !== undefined) { 121 | window.onpopstate = function (evt) { 122 | switch (currentScreen) { 123 | case "home": 124 | break; 125 | case "bench": 126 | goHome(); 127 | break; 128 | case "score-list": 129 | goHome(); 130 | break; 131 | case "score-graph": 132 | goScoreList(); 133 | break; 134 | } 135 | }; 136 | } 137 | 138 | // Pages 139 | homePage = document.getElementById("home"); 140 | benchPage = document.getElementById("bench"); 141 | scorePage = document.getElementById("score"); 142 | 143 | // Buttons 144 | document.getElementById("home-launch").onclick = function () { 145 | // Switching pages 146 | POTATOES.Tools.addClass(homePage, "hidden"); 147 | POTATOES.Tools.removeClass(benchPage, "hidden"); 148 | 149 | if (history.pushState) { 150 | history.pushState({}, "launch", "index.html"); 151 | } 152 | currentScreen = "bench"; 153 | 154 | // Starting benchmark 155 | POTATOES.GamingBench.start(); 156 | }; 157 | 158 | document.getElementById("bench-skip").onclick = function () { 159 | POTATOES.GamingBench.skipCurrent(); 160 | }; 161 | 162 | document.getElementById("bench-cancel").onclick = function () { 163 | goHome(); 164 | }; 165 | 166 | document.getElementById("score-footer-back").onclick = function () { 167 | goScoreList(); 168 | goHome(); 169 | }; 170 | 171 | POTATOES.GamingBench.onprocessended = function () { 172 | POTATOES.Tools.addClass(benchPage, "hidden"); 173 | POTATOES.Tools.removeClass(scorePage, "hidden"); 174 | 175 | currentScreen = "score-list"; 176 | 177 | // Clearing list 178 | while (scoreList.childNodes.length) { 179 | scoreList.removeChild(scoreList.childNodes[0]); 180 | } 181 | 182 | // Headers 183 | var paragraphHeader = document.createElement("div"); 184 | POTATOES.Tools.addClass(paragraphHeader, "score-entry-block"); 185 | scoreList.appendChild(paragraphHeader); 186 | var fpsHeader = document.createElement("div"); 187 | POTATOES.Tools.addClass(fpsHeader, "score-entry-header-fps"); 188 | fpsHeader.innerHTML = "FPS"; 189 | paragraphHeader.appendChild(fpsHeader); 190 | 191 | var scoreHeader = document.createElement("div"); 192 | POTATOES.Tools.addClass(scoreHeader, "score-entry-header-score"); 193 | scoreHeader.innerHTML = "Score"; 194 | paragraphHeader.appendChild(scoreHeader); 195 | 196 | // Generating the result list 197 | var score = 0; 198 | for (var index = 0; index < POTATOES.GamingBench.benches.length; index++) { 199 | var bench = POTATOES.GamingBench.benches[index]; 200 | 201 | var paragraph = document.createElement("div"); 202 | POTATOES.Tools.addClass(paragraph, "score-entry-block"); 203 | scoreList.appendChild(paragraph); 204 | 205 | var linkTitle = document.createElement("a"); 206 | POTATOES.Tools.addClass(linkTitle, "score-entry"); 207 | linkTitle.innerHTML = bench.name + ": "; 208 | linkTitle.href = bench.url; 209 | linkTitle.target = "_blank"; 210 | paragraph.appendChild(linkTitle); 211 | 212 | var link = document.createElement("a"); 213 | POTATOES.Tools.addClass(link, "score-entry-link"); 214 | var avgFps = 0; 215 | for (var i = 0; i < bench.stats.length; i++) { 216 | avgFps += bench.stats[i].y; 217 | } 218 | 219 | avgFps /= bench.stats.length; 220 | 221 | link.innerHTML = isNaN(avgFps) ? " " : avgFps.toFixed(); 222 | link.onclick = displayStats(bench); 223 | paragraph.appendChild(link); 224 | 225 | var scoreSpan = document.createElement("div"); 226 | POTATOES.Tools.addClass(scoreSpan, "score-entry-score"); 227 | scoreSpan.innerHTML = bench.score; 228 | paragraph.appendChild(scoreSpan); 229 | 230 | score += bench.score; 231 | } 232 | 233 | document.getElementById("score-footer-text").innerHTML = score; 234 | }; 235 | }; 236 | 237 | var onResize = function () { 238 | 239 | }; 240 | 241 | document.addEventListener("DOMContentLoaded", onInit, false); 242 | window.addEventListener("resize", onResize, false); 243 | })(); 244 | 245 | 246 | var checkHTML5Support = function () { 247 | if (window.HTMLCanvasElement === undefined) { 248 | var controls = document.getElementById("home-controls"); 249 | var message = document.getElementById("home-nohtml5"); 250 | 251 | POTATOES.Tools.addClass(controls, "hidden"); 252 | POTATOES.Tools.removeClass(message, "hidden"); 253 | } 254 | }; 255 | -------------------------------------------------------------------------------- /benchmark/pass.html: -------------------------------------------------------------------------------- 1 | Benchmark passed! 2 | -------------------------------------------------------------------------------- /benchmark/tools.js: -------------------------------------------------------------------------------- 1 | var POTATOES = POTATOES || {}; 2 | (function () { 3 | POTATOES.Tools = {}; 4 | 5 | // Public methods 6 | POTATOES.Tools.addClass = function (e, name) { 7 | var className = e.className; 8 | var names = className.split(" "); 9 | var len = names.length; 10 | var toAdd; 11 | 12 | if (name.indexOf(" ") >= 0) { 13 | var namesToAdd = name.split(" "); 14 | removeEmpties(namesToAdd); 15 | for (var i = 0; i < l; i++) { 16 | var found = namesToAdd.indexOf(names[i]); 17 | if (found >= 0) { 18 | namesToAdd.splice(found, 1); 19 | } 20 | } 21 | if (namesToAdd.length > 0) { 22 | toAdd = namesToAdd.join(" "); 23 | } 24 | } 25 | else { 26 | var saw = false; 27 | for (var i = 0; i < len; i++) { 28 | if (names[i] === name) { 29 | saw = true; 30 | break; 31 | } 32 | } 33 | if (!saw) { toAdd = name; } 34 | 35 | } 36 | if (toAdd) { 37 | if (len > 0 && names[0].length > 0) { 38 | e.className = className + " " + toAdd; 39 | } 40 | else { 41 | e.className = toAdd; 42 | } 43 | } 44 | return e; 45 | }; 46 | 47 | POTATOES.Tools.removeClass = function(e, name) { 48 | var original = e.className; 49 | var namesToRemove; 50 | 51 | if (name.indexOf(" ") >= 0) { 52 | namesToRemove = name.split(" "); 53 | } else { 54 | if (original.indexOf(name) < 0) { 55 | return e; 56 | } 57 | namesToRemove = [name]; 58 | } 59 | var removed; 60 | var names = original.split(" "); 61 | var namesLen = names.length; 62 | 63 | for (var i = namesLen - 1; i >= 0; i--) { 64 | if (namesToRemove.indexOf(names[i]) >= 0) { 65 | names.splice(i, 1); 66 | removed = true; 67 | } 68 | } 69 | 70 | if (removed) { 71 | e.className = names.join(" "); 72 | } 73 | return e; 74 | }; 75 | 76 | POTATOES.Tools.queueNewFrame = function (func) { 77 | if (window.requestAnimationFrame) 78 | window.requestAnimationFrame(func); 79 | else if (window.msRequestAnimationFrame) 80 | window.msRequestAnimationFrame(func); 81 | else if (window.webkitRequestAnimationFrame) 82 | window.webkitRequestAnimationFrame(func); 83 | else if (window.mozRequestAnimationFrame) 84 | window.mozRequestAnimationFrame(func); 85 | else if (window.oRequestAnimationFrame) 86 | window.oRequestAnimationFrame(func); 87 | else { 88 | window.setTimeout(func, 16); 89 | } 90 | }; 91 | })(); -------------------------------------------------------------------------------- /dist/.gitignore: -------------------------------------------------------------------------------- 1 | *.js.report.txt 2 | *.js 3 | -------------------------------------------------------------------------------- /dist/.npmignore: -------------------------------------------------------------------------------- 1 | *.js.report.txt 2 | *.zip 3 | -------------------------------------------------------------------------------- /examples/basic/demo.css: -------------------------------------------------------------------------------- 1 | #plot { 2 | width: 100%; 3 | height: 100vh; 4 | } 5 | 6 | #body { 7 | margin: 0; 8 | } 9 | -------------------------------------------------------------------------------- /examples/basic/demo.details: -------------------------------------------------------------------------------- 1 | --- 2 | name: SigPlot Basic Example 3 | description: A simple example of using SigPlot to render an array. 4 | authors: 5 | - LGS Innovations 6 | resources: 7 | - https://cdn.jsdelivr.net/npm/sigplot/dist/sigplot-debug.js 8 | normalize_css: no 9 | ... 10 | -------------------------------------------------------------------------------- /examples/basic/demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | SigPlot Fiddle 4 | 5 | 6 |
7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /examples/basic/demo.js: -------------------------------------------------------------------------------- 1 | window.onload = function() { 2 | var plot_options = { 3 | autohide_panbars: true, 4 | hide_note: true 5 | }; 6 | var data = [1, 2, 3, 4, 5, 4, 3, 2, 1]; // the series of y-values 7 | var data_header = { 8 | xunits: "Time", 9 | xstart: 100, // the start of the x-axis 10 | xdelta: 50, // the x-axis step between each data point 11 | yunits: "Power" 12 | }; 13 | var layer_options = { 14 | name: "Sample Data" 15 | }; 16 | var plot = new sigplot.Plot(document.getElementById('plot'), plot_options); 17 | plot.overlay_array(data, data_header, layer_options); 18 | } 19 | -------------------------------------------------------------------------------- /examples/basic/offline.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /fft-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LGSInnovations/sigplot/480cec393280a41359924383d06bb6849eeab879/fft-white.png -------------------------------------------------------------------------------- /js/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "curly": true, 3 | "eqeqeq": true, 4 | "immed": true, 5 | "latedef": "nofunc", 6 | "newcap": true, 7 | "noarg": true, 8 | "sub": true, 9 | "undef": true, 10 | "boss": true, 11 | "eqnull": true, 12 | "browser": true, 13 | "shadow": true, 14 | "-W099": true, 15 | "esversion": 6, 16 | "globals": { 17 | "UInt8Array": false, 18 | "BlueHeader": false, 19 | "JSON": true 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /js/ColorMap.js: -------------------------------------------------------------------------------- 1 | /* global module */ 2 | /* global require */ 3 | (function() { 4 | var tinycolor = require("tinycolor2"); 5 | if (typeof Object.assign !== 'function') { 6 | // Must be writable: true, enumerable: false, configurable: true 7 | Object.defineProperty(Object, "assign", { 8 | value: function assign(target, varArgs) { // .length of function is 2 9 | 'use strict'; 10 | if (target == null) { // TypeError if undefined or null 11 | throw new TypeError('Cannot convert undefined or null to object'); 12 | } 13 | var to = Object(target); 14 | for (var index = 1; index < arguments.length; index++) { 15 | var nextSource = arguments[index]; 16 | if (nextSource != null) { // Skip over if undefined or null 17 | for (var nextKey in nextSource) { 18 | // Avoid bugs when hasOwnProperty is shadowed 19 | if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) { 20 | to[nextKey] = nextSource[nextKey]; 21 | } 22 | } 23 | } 24 | } 25 | return to; 26 | }, 27 | writable: true, 28 | configurable: true 29 | }); 30 | } 31 | var ColorMap = window.ColorMap = function(colors, options) { 32 | this.options = { 33 | ncolors: 500, 34 | alpha: 255 35 | }; 36 | this.options = Object.assign(this.options, options); 37 | this.map = []; 38 | var _min = 0; 39 | this._low = 0; 40 | this._high = 1; 41 | var ncolors = this.options.ncolors; 42 | this._fscale = ncolors / (this._high - this._low); 43 | var colorindex = 1; 44 | var colorBlockIndex = 1; 45 | colors = JSON.parse(JSON.stringify(colors)); //make a copy so we dont change the original colors 46 | colors = this._parseColors(colors); 47 | this.colors = colors; 48 | var col1 = colors[0]; 49 | var col2 = colors[1]; 50 | // pos is the percentage of scale (0-100), so 51 | // colorStop is how many percentage is allocated 52 | // to this band 53 | var colorStop = colors[1].pos - colors[0].pos; 54 | // now many colors are allocated to this block 55 | var colorsInBlock = ncolors * (colorStop / 100); 56 | // the interpolation step per color number 57 | var factorStep = 1 / colorsInBlock; 58 | for (var n = 0; n < ncolors - 2; n++) { 59 | if (colorBlockIndex > colorsInBlock) { 60 | col1 = colors[colorindex]; 61 | col2 = colors[colorindex + 1]; 62 | // if we are at the end of the color list 63 | if (col2 === undefined) { 64 | break; 65 | } 66 | if ((col1.pos >= 100) && (col2.pos >= 100)) { 67 | break; 68 | } 69 | var colorStop = col2.pos - col1.pos; 70 | var colorsInBlock = ncolors * (colorStop / 100); 71 | var factorStep = 1 / colorsInBlock; 72 | var colorBlockIndex = 1; 73 | colorindex += 1; 74 | } 75 | this._addColor(this.interpolate(col1, col2, factorStep * colorBlockIndex)); 76 | colorBlockIndex += 1; 77 | } 78 | 79 | this._addColor(colors[colorindex]); 80 | this._addColor(colors[0], true); 81 | 82 | }; 83 | ColorMap.prototype = { 84 | _addColor: function(color, front) { 85 | color.hex = this._rgbToHex(color.red, color.green, color.blue); 86 | color.color = (color.alpha << 24) | // alpha 87 | (color.blue << 16) | // blue 88 | (color.green << 8) | // green 89 | (color.red); 90 | if (front) { 91 | this.map.unshift(color); 92 | } else { 93 | this.map.push(color); 94 | } 95 | }, 96 | _parseColors: function(colors) { 97 | for (var i = 0, c = colors.length; i < c; i++) { 98 | var color = colors[i]; 99 | if (typeof color === "string") { 100 | colors[i] = this._hexToRgb(color); 101 | color = tinycolor(color); 102 | color = color.toRgb(); 103 | colors[i] = {red:color.r,green:color.g,blue:color.b,alpha:this.options.alpha}; 104 | 105 | } else if (color.hasOwnProperty("color")) { 106 | var newColor = tinycolor(color.color); 107 | newColor = newColor.toRgb(); 108 | newColor = {red:newColor.r,green:newColor.g,blue:newColor.b,alpha:this.options.alpha}; 109 | if (color.hasOwnProperty("pos")) { 110 | newColor.pos = color.pos; 111 | } 112 | colors[i] = newColor; 113 | } else { 114 | //assume if it has rgb values it is a percentage 115 | colors[i].red = Math.floor(Math.round(255 * (color.red / 100))); 116 | colors[i].green = Math.floor(Math.round(255 * (color.green / 100))); 117 | colors[i].blue = Math.floor(Math.round(255 * (color.blue / 100))); 118 | } 119 | if (!colors[i].hasOwnProperty("alpha")) { 120 | colors[i].alpha = this.options.alpha; 121 | } 122 | } 123 | return this._checkColorStops(colors); 124 | }, 125 | _checkColorStops: function(colors) { 126 | var lastStop = 0; 127 | var colorsWithNoStops = 0; 128 | for (var i = 0, c = colors.length; i < c; i++) { 129 | var color = colors[i]; 130 | if (!color.hasOwnProperty("pos")) { 131 | colorsWithNoStops += 1; 132 | } else { 133 | if (colorsWithNoStops) { 134 | var stopSize = (color.pos - lastStop) / colorsWithNoStops; 135 | var currentPos = color.pos; 136 | for (var z = 1; z <= colorsWithNoStops; z++) { 137 | colors[i - z].pos = currentPos - stopSize; 138 | currentPos -= stopSize; 139 | } 140 | } 141 | colorsWithNoStops = 0; 142 | } 143 | } 144 | if (colorsWithNoStops) { 145 | var currentPos = 100; 146 | colors[colors.length - 1].pos = currentPos; 147 | if (lastStop === 0) { 148 | colors[0].pos = 0; 149 | colorsWithNoStops -= 1; 150 | } 151 | var stopSize = (currentPos - lastStop) / colorsWithNoStops; 152 | var i = colors.length - 1; 153 | for (var z = 1; z < colorsWithNoStops; z++) { 154 | colors[i - z].pos = currentPos - stopSize; 155 | currentPos -= stopSize; 156 | } 157 | } 158 | return colors; 159 | }, 160 | _componentToHex: function(c) { 161 | var hex = c.toString(16); 162 | return hex.length === 1 ? "0" + hex : hex; 163 | }, 164 | _rgbToHex: function(r, g, b) { 165 | return "#" + this._componentToHex(r) + this._componentToHex(g) + this._componentToHex(b); 166 | }, 167 | _hexToRgb: function(hex) { 168 | var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); 169 | return result ? { 170 | red: parseInt(result[1], 16), 171 | green: parseInt(result[2], 16), 172 | blue: parseInt(result[3], 16) 173 | } : null; 174 | }, 175 | getColor: function(number) { 176 | var colorindex = this.getColorIndex(number); 177 | return this.map[colorindex]; 178 | }, 179 | getColorByIndex: function(colorindex) { 180 | return this.map[colorindex]; 181 | }, 182 | getColorIndex: function(number) { 183 | var n = (number - this._low) * this._fscale; 184 | var colorindex = ~~n; //make int fastest method 185 | if (colorindex > this.map.length - 1) { 186 | colorindex = this.map.length - 1; 187 | } else if (colorindex < 0) { 188 | colorindex = 0; 189 | } 190 | return colorindex; 191 | }, 192 | getNColors : function() { 193 | return this.map.length; 194 | }, 195 | setRange: function(low, high) { 196 | // only recalculate if a value has changed 197 | if ((this._low !== low) || (this._high !== high)) { 198 | this._low = low; 199 | this._high = high; 200 | this._fscale = this.map.length / Math.abs(this._high - this._low); 201 | } 202 | }, 203 | interpolate: function(col1, col2, factor) { 204 | return { 205 | red: col1.red + factor * (col2.red - col1.red), 206 | green: col1.green + factor * (col2.green - col1.green), 207 | blue: col1.blue + factor * (col2.blue - col1.blue), 208 | alpha: col1.alpha + factor * (col2.alpha - col1.alpha) 209 | }; 210 | } 211 | }; 212 | module.exports = ColorMap; 213 | })(); -------------------------------------------------------------------------------- /js/common.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * File: common.js 4 | * Copyright (c) 2012-2017, LGS Innovations Inc., All rights reserved. 5 | * 6 | * This file is part of SigPlot. 7 | * 8 | * Licensed to the LGS Innovations (LGS) under one 9 | * or more contributor license agreements. See the NOTICE file 10 | * distributed with this work for additional information 11 | * regarding copyright ownership. LGS licenses this file 12 | * to you under the Apache License, Version 2.0 (the 13 | * "License"); you may not use this file except in compliance 14 | * with the License. You may obtain a copy of the License at 15 | * 16 | * http://www.apache.org/licenses/LICENSE-2.0 17 | * 18 | * Unless required by applicable law or agreed to in writing, 19 | * software distributed under the License is distributed on an 20 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 21 | * KIND, either express or implied. See the License for the 22 | * specific language governing permissions and limitations 23 | * under the License. 24 | * 25 | */ 26 | 27 | /* global module */ 28 | /* global require */ 29 | 30 | (function() { 31 | 32 | module.exports = {}; 33 | 34 | if (window.ArrayBuffer) { 35 | if (!ArrayBuffer.prototype.slice) { 36 | //Monkey Patching for iOS and early Firefox 37 | ArrayBuffer.prototype.slice = function(start, end) { 38 | var that = new Uint8Array(this); 39 | if (end === undefined) { 40 | end = that.length; 41 | } 42 | var result = new ArrayBuffer(end - start); 43 | var resultArray = new Uint8Array(result); 44 | for (var i = 0; i < resultArray.length; i++) { 45 | resultArray[i] = that[i + start]; 46 | } 47 | return result; 48 | }; 49 | } 50 | 51 | if (!ArrayBuffer['isView']) { 52 | ArrayBuffer.isView = function(a) { 53 | return a !== null && typeof(a) === "object" && a['buffer'] instanceof ArrayBuffer; 54 | }; 55 | } 56 | } 57 | 58 | // Shim for requestAnimationFrame compatibility 59 | window.requestAnimFrame = (function(callback) { 60 | return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || 61 | function(callback) { 62 | return window.setTimeout(callback, 1000 / 60); 63 | }; 64 | })(); 65 | 66 | window.cancelAnimFrame = (function(callback) { 67 | return window.cancelAnimationFrame || window.webkitCancelAnimationFrame || window.mozCancelAnimationFrame || window.oCancelAnimationFrame || window.msCanelAnimationFrame || 68 | function(timeoutID) { 69 | window.clearTimeout(timeoutID); 70 | }; 71 | })(); 72 | 73 | // Handle various ways to draw dashed lines 74 | module.exports.dashOn = function(ctx, on, off) { 75 | if (ctx.setLineDash) { 76 | ctx.setLineDash([on, off]); 77 | return true; 78 | } else if (ctx.mozDash !== undefined) { // Gecko 7.0+ 79 | ctx.mozDash = [on, off]; 80 | return true; 81 | } else if (ctx.webkitLineDash && ctx.webkitLineDash.length === 0) { 82 | ctx.webkitLineDash = [on, off]; 83 | return true; 84 | } 85 | return false; 86 | }; 87 | 88 | module.exports.dashOff = function(ctx) { 89 | if (ctx.setLineDash) { 90 | ctx.setLineDash([]); 91 | } else if (ctx.mozDash) { // Gecko 7.0+ 92 | ctx.mozDash = null; 93 | } else if (ctx.webkitLineDash) { 94 | ctx.webkitLineDash = []; 95 | } 96 | }; 97 | 98 | // Firefox behaves differntly for keypress events 99 | module.exports.getKeyCode = function(e) { 100 | e = window.event || e; 101 | e = e.charCode || e.keyCode; 102 | return e; 103 | }; 104 | 105 | module.exports.setKeypressHandler = function(handler) { 106 | if (window.addEventListener) { window.addEventListener('keypress', handler, false); } 107 | else if (window.attachEvent) { 108 | window.attachEvent('onkeypress', handler); 109 | } 110 | }; 111 | 112 | // endsWith() is part of ECMAScript 6, include the Mozilla 113 | // Polyfill from https://developer.mozilla.org 114 | if (!String.prototype.endsWith) { 115 | String.prototype.endsWith = function(search, this_len) { 116 | if (this_len === undefined || this_len > this.length) { 117 | this_len = this.length; 118 | } 119 | return this.substring(this_len - search.length, this_len) === search; 120 | }; 121 | } 122 | 123 | if (!Uint8Array.prototype.slice) { 124 | Object.defineProperty(Uint8Array.prototype, 'slice', { 125 | value: Array.prototype.slice 126 | }); 127 | } 128 | 129 | if (!Int8Array.prototype.slice) { 130 | Object.defineProperty(Int8Array.prototype, 'slice', { 131 | value: Array.prototype.slice 132 | }); 133 | } 134 | 135 | if (!Int16Array.prototype.slice) { 136 | Object.defineProperty(Int16Array.prototype, 'slice', { 137 | value: Array.prototype.slice 138 | }); 139 | } 140 | 141 | if (!Int32Array.prototype.slice) { 142 | Object.defineProperty(Int32Array.prototype, 'slice', { 143 | value: Array.prototype.slice 144 | }); 145 | } 146 | 147 | if (!Float32Array.prototype.slice) { 148 | Object.defineProperty(Float32Array.prototype, 'slice', { 149 | value: Array.prototype.slice 150 | }); 151 | } 152 | 153 | if (!Float64Array.prototype.slice) { 154 | Object.defineProperty(Float64Array.prototype, 'slice', { 155 | value: Array.prototype.slice 156 | }); 157 | } 158 | 159 | // Array.isArray 160 | // FF 4+ 161 | // IE 9+ 162 | // SF 5+ 163 | // http://kangax.github.io/es5-compat-table/#Array.isArray 164 | if (!Array.isArray) { 165 | Array.isArray = function(obj) { 166 | return Object.prototype.toString.call(obj) === "[object Array]"; 167 | }; 168 | } 169 | 170 | if (!window.Float64Array) { 171 | //Monkey Patching for iOS 172 | // This is essentially ReadOnly because 173 | // if someone does x[i] = 5 174 | // the value will be set in the array 175 | // but not in the underlying buffer 176 | window.Float64Array = (function() { 177 | return window.Float64Array || 178 | function(buffer, byteOffset, length) { 179 | if (!(buffer instanceof ArrayBuffer)) { 180 | throw "Invalid type"; 181 | } 182 | var dv = new DataView(buffer); 183 | var b = []; 184 | var maxlength = (buffer.byteLength - byteOffset) / 8; 185 | if (length === undefined) { 186 | b.length = maxlength; 187 | } else { 188 | b.length = Math.min(length, maxlength); 189 | } 190 | 191 | for (var i = 0; i < b.length; i++) { 192 | b[i] = dv.getFloat64(i * 8 + byteOffset, true); 193 | } 194 | b.subarray = function(begin, end) { 195 | return b.slice(begin, end); 196 | }; 197 | return b; 198 | }; 199 | })(); 200 | } 201 | 202 | // from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer/transfer 203 | if (!ArrayBuffer.transfer) { 204 | ArrayBuffer.transfer = function(source, length) { 205 | if (!(source instanceof ArrayBuffer)) { 206 | throw new TypeError('Source must be an instance of ArrayBuffer'); 207 | } 208 | if (length <= source.byteLength) { 209 | return source.slice(0, length); 210 | } 211 | var sourceView = new Uint8Array(source), 212 | destView = new Uint8Array(new ArrayBuffer(length)); 213 | destView.set(sourceView); 214 | return destView.buffer; 215 | }; 216 | } 217 | 218 | // Shims 219 | (function() { 220 | /* console shim*/ 221 | var f = function() {}; 222 | if (!window.console) { 223 | window.console = { 224 | log: f, 225 | info: f, 226 | warn: f, 227 | debug: f, 228 | error: f 229 | }; 230 | } 231 | 232 | // Firefox 4 has a glaring subarray bug 233 | // http://ryanberdeen.com/2011/04/16/firefox-subarray-bug.html 234 | if (new Int8Array([0, 1, 0]).subarray(1).subarray(1)[0]) { 235 | var subarray = function(begin, end) { 236 | if (arguments.length === 0) { 237 | begin = 0; 238 | end = this.length; 239 | } else { 240 | if (begin < 0) { 241 | // relative to end 242 | begin += this.length; 243 | } 244 | // clamp to 0, length 245 | begin = Math.max(0, Math.min(this.length, begin)); 246 | if (arguments.length === 1) { 247 | // slice to end 248 | end = this.length; 249 | } else { 250 | if (end < 0) { 251 | // relative to end 252 | end += this.length; 253 | } 254 | // clamp to begin, length 255 | end = Math.max(begin, Math.min(this.length, end)); 256 | } 257 | } 258 | 259 | var byteOffset = this.byteOffset + begin * this.BYTES_PER_ELEMENT; 260 | return new this.constructor(this.buffer, byteOffset, end - begin); 261 | }; 262 | 263 | var typedArrays = [Int8Array, Uint8Array, Int16Array, Uint16Array, 264 | Int32Array, Uint32Array, Float32Array, Float64Array 265 | ]; 266 | typedArrays.forEach(function(cls) { 267 | cls.prototype.subarray = subarray; 268 | }); 269 | } 270 | 271 | }()); 272 | 273 | // https://developer.mozilla.org/en-US/docs/Web/Reference/Events/wheel?redirectlocale=en-US&redirectslug=DOM%2FMozilla_event_reference%2Fwheel#Listening_to_this_event_across_browser 274 | // creates a global "addWheelListener" method 275 | // example: addWheelListener( elem, function( e ) { console.log( e.deltaY ); e.preventDefault(); } ); 276 | (function(window, document) { 277 | 278 | var prefix = "", 279 | _addEventListener, onwheel, support; 280 | 281 | // detect event model 282 | if (window.addEventListener) { 283 | _addEventListener = "addEventListener"; 284 | } else { 285 | _addEventListener = "attachEvent"; 286 | prefix = "on"; 287 | } 288 | 289 | // detect available wheel event 290 | support = "onwheel" in document.createElement("div") ? "wheel" : // Modern browsers support "wheel" 291 | document.onmousewheel !== undefined ? "mousewheel" : // Webkit and IE support at least "mousewheel" 292 | "DOMMouseScroll"; // let's assume that remaining browsers are older Firefox 293 | 294 | window.addWheelListener = function(elem, callback, useCapture) { 295 | _addWheelListener(elem, support, callback, useCapture); 296 | 297 | // handle MozMousePixelScroll in older Firefox 298 | if (support === "DOMMouseScroll") { 299 | _addWheelListener(elem, "MozMousePixelScroll", callback, useCapture); 300 | } 301 | }; 302 | 303 | /* jshint -W030 */ 304 | function _addWheelListener(elem, eventName, callback, useCapture) { 305 | elem[_addEventListener](prefix + eventName, support === "wheel" ? callback : function(originalEvent) { 306 | !originalEvent && (originalEvent = window.event); 307 | 308 | // create a normalized event object 309 | var event = { 310 | // keep a ref to the original event object 311 | originalEvent: originalEvent, 312 | target: originalEvent.target || originalEvent.srcElement, 313 | type: "wheel", 314 | deltaMode: originalEvent.type === "MozMousePixelScroll" ? 0 : 1, 315 | deltaX: 0, 316 | delatZ: 0, 317 | preventDefault: function() { 318 | originalEvent.preventDefault ? 319 | originalEvent.preventDefault() : 320 | originalEvent.returnValue = false; 321 | } 322 | }; 323 | 324 | // calculate deltaY (and deltaX) according to the event 325 | if (support === "mousewheel") { 326 | event.deltaY = -1 / 40 * originalEvent.wheelDelta; 327 | // Webkit also support wheelDeltaX 328 | originalEvent.wheelDeltaX && (event.deltaX = -1 / 40 * originalEvent.wheelDeltaX); 329 | } else { 330 | event.deltaY = originalEvent.detail; 331 | } 332 | 333 | // it's time to fire the callback 334 | return callback(event); 335 | 336 | }, useCapture || false); 337 | } 338 | /* jshint +W030 */ 339 | 340 | })(window, document); 341 | //Add Proxy poly fill https://github.com/GoogleChrome/proxy-polyfill 342 | if(!window.Proxy){ 343 | /* jshint ignore:start */ 344 | (function(){function l(){function n(a){return a?"object"===typeof a||"function"===typeof a:!1}var p=null;var g=function(a,b){function f(){}if(!n(a)||!n(b))throw new TypeError("Cannot create proxy with a non-object as target or handler");p=function(){f=function(a){throw new TypeError("Cannot perform '"+a+"' on a proxy that has been revoked");}};var e=b;b={get:null,set:null,apply:null,construct:null};for(var k in e){if(!(k in b))throw new TypeError("Proxy polyfill does not support trap '"+k+"'");b[k]=e[k]}"function"=== 345 | typeof e&&(b.apply=e.apply.bind(e));var c=this,g=!1,q=!1;"function"===typeof a?(c=function(){var h=this&&this.constructor===c,d=Array.prototype.slice.call(arguments);f(h?"construct":"apply");return h&&b.construct?b.construct.call(this,a,d):!h&&b.apply?b.apply(a,this,d):h?(d.unshift(a),new (a.bind.apply(a,d))):a.apply(this,d)},g=!0):a instanceof Array&&(c=[],q=!0);var r=b.get?function(a){f("get");return b.get(this,a,c)}:function(a){f("get");return this[a]},v=b.set?function(a,d){f("set");b.set(this, 346 | a,d,c)}:function(a,b){f("set");this[a]=b},t={};Object.getOwnPropertyNames(a).forEach(function(b){if(!((g||q)&&b in c)){var d={enumerable:!!Object.getOwnPropertyDescriptor(a,b).enumerable,get:r.bind(a,b),set:v.bind(a,b)};Object.defineProperty(c,b,d);t[b]=!0}});e=!0;Object.setPrototypeOf?Object.setPrototypeOf(c,Object.getPrototypeOf(a)):c.__proto__?c.__proto__=a.__proto__:e=!1;if(b.get||!e)for(var m in a)t[m]||Object.defineProperty(c,m,{get:r.bind(a,m)});Object.seal(a);Object.seal(c);return c};g.revocable= 347 | function(a,b){return{proxy:new g(a,b),revoke:p}};return g};var u="undefined"!==typeof process&&"[object process]"==={}.toString.call(process)||"undefined"!==typeof navigator&&"ReactNative"===navigator.product?global:self;u.Proxy||(u.Proxy=l(),u.Proxy.revocable=u.Proxy.revocable);})(); 348 | /* jshint ignore:end */ 349 | } 350 | 351 | //Updates destenation object with source values 352 | module.exports.update = function update(dst, src) { 353 | for (var prop in src) { 354 | var val = src[prop]; 355 | if (typeof val === "object") { // recursive 356 | update(dst[prop], val); 357 | } else { 358 | dst[prop] = val; 359 | } 360 | } 361 | return dst; // return dst to allow method chaining 362 | }; 363 | 364 | // From https://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript 365 | // consider using the uuid module instead 366 | module.exports.uuidv4 = function uuidv4() { 367 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { 368 | var r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8); 369 | return v.toString(16); 370 | }); 371 | }; 372 | 373 | }()); 374 | -------------------------------------------------------------------------------- /js/license.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * File: license.js 4 | * Copyright (c) 2012-2017, LGS Innovations Inc., All rights reserved. 5 | * 6 | * This file is part of SigPlot. 7 | * 8 | * Licensed to the LGS Innovations (LGS) under one 9 | * or more contributor license agreements. See the NOTICE file 10 | * distributed with this work for additional information 11 | * regarding copyright ownership. LGS licenses this file 12 | * to you under the Apache License, Version 2.0 (the 13 | * "License"); you may not use this file except in compliance 14 | * with the License. You may obtain a copy of the License at 15 | * 16 | * http://www.apache.org/licenses/LICENSE-2.0 17 | * 18 | * Unless required by applicable law or agreed to in writing, 19 | * software distributed under the License is distributed on an 20 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 21 | * KIND, either express or implied. See the License for the 22 | * specific language governing permissions and limitations 23 | * under the License. 24 | 25 | * Portions of SigPlot may utilize the following open-source software: 26 | * 27 | * loglevel.js - MIT License; Copyright (c) 2014, Tim Perry 28 | * typedarray.js - MIT License; Copyright (c) 2010, Linden Research, Inc. 29 | * tinycolor.js - MIT License; Copyright (c) 2013, Brian Grinstead 30 | * CanvasInput.js - MIT License; Copyright (c) 2013, James Simpson of GoldFire Studios 31 | * spin.js - MIT License; Copyright (c) 2011-2013 Felix Gnass 32 | * Firefox subarray fix - Public Domain; Copyright (c) 2011, Ryan Berdeen 33 | */ 34 | -------------------------------------------------------------------------------- /js/mx.dommenu.js: -------------------------------------------------------------------------------- 1 | /* global module */ 2 | /* global require */ 3 | 4 | (function() { 5 | var common = require("./common"); 6 | 7 | var DomMenu = function(Mx, menu, options) { 8 | this.options = { 9 | itemClass: "sigplot-menu-item" 10 | }; 11 | common.update(this.options, options); 12 | this._Mx = Mx; 13 | this._container = Mx.root; 14 | this._menu = document.createElement("div"); 15 | var style = "z-index:2;float:left;position:relative;left:" + Mx.xpos + "px;top:" + Mx.ypos + "px;"; 16 | this._menu.classList.add("sigplot-menu"); 17 | var d = new Date(); 18 | this._menuId = "menu-" + d.getSeconds() + d.getMilliseconds(); 19 | this._menu.classList.add(this._menuId); 20 | this._menu.style = style; 21 | this._items = []; 22 | this.setCSS(); 23 | this.createMenu(menu); 24 | }; 25 | DomMenu.prototype = { 26 | createMenu: function(menu) { 27 | var self = this; 28 | var Mx = this._Mx; 29 | var originalFinalize = menu.finalize; 30 | menu.finalize = function() { 31 | self.remove(); 32 | originalFinalize(); 33 | }; 34 | this.finalize = menu.finalize; 35 | var title = document.createElement("div"); 36 | title.addEventListener("mousedown", function(e) { 37 | e.preventDefault(); 38 | self._movingOffsetX = e.offsetX; 39 | self._movingOffsetY = e.offsetY; 40 | self._moving = true; 41 | }); 42 | title.addEventListener("mouseup", function(e) { 43 | e.preventDefault(); 44 | self._moving = false; 45 | }); 46 | self._moveMenu = function(e) { 47 | if (self._moving) { 48 | self._menu.style.position = 'fixed'; 49 | self._menu.style.top = e.clientY - self._movingOffsetY + 'px'; 50 | self._menu.style.left = e.clientX - self._movingOffsetX + 'px'; 51 | } 52 | }; 53 | document.body.addEventListener("mousemove", self._moveMenu); 54 | title.classList.add("sigplot-menu-title"); 55 | title.innerText = menu.title; 56 | this._menu.append(title); 57 | var list = document.createElement("ul"); 58 | list.classList.add("sigplot-menu-list"); 59 | menu.items.forEach(function(item) { 60 | var li = self._createMenuItem(item, menu); 61 | list.append(li); 62 | }); 63 | this._menu.append(list); 64 | this._container.append(this._menu); 65 | this._menu.addEventListener("contextmenu", function(e) { 66 | e.preventDefault(); 67 | self.finalize(); 68 | }); 69 | Mx.menu = this; 70 | Mx.widget = { 71 | type: "MENU", 72 | callback: function(event) { 73 | if (event.type === "mousedown") { 74 | if (event.which === 1 || event.which === 2 || event.which === 3) { 75 | if ((self._Mx.menu === self) && (!event.target.classList.contains(self.options.itemClass))) { 76 | self.finalize(); 77 | } 78 | if (!self._Mx.menu) { 79 | self.finalize(); 80 | } 81 | } 82 | } 83 | if (event.type === "mouseup") { 84 | self._moving = false; 85 | } 86 | if (event.type === "keydown") { 87 | self._handleKeyEvents(event); 88 | } 89 | } 90 | }; 91 | }, 92 | _handleKeyEvents: function(event) { 93 | var self = this; 94 | if (event.key === "ArrowDown") { 95 | event.preventDefault(); 96 | if (!self._active) { 97 | self._setActive(self._items[0]); 98 | } else { 99 | var target = self._items.indexOf(self._active) + 1; 100 | 101 | if (target > self._items.length - 1) { 102 | return; //Last item in the list keep it active 103 | } 104 | self._setActive(self._items[target]); 105 | } 106 | } 107 | if (event.key === "ArrowUp") { 108 | event.preventDefault(); 109 | if (!self._active) { 110 | self._setActive(self._items[0]); 111 | } else { 112 | var target = self._items.indexOf(self._active) - 1; 113 | if (target < 0) { 114 | return; // First item in the list keep it active 115 | } 116 | self._setActive(self._items[target]); 117 | } 118 | } 119 | 120 | if (event.key === "Enter") { 121 | event.preventDefault(); 122 | if (!self._active) { 123 | self._setActive(self._items[0]); 124 | } 125 | 126 | var el = self._active; 127 | if (el.onclick) { 128 | el.onclick(); 129 | } else if (el.click) { 130 | el.click(); 131 | } 132 | } 133 | }, 134 | _setActive: function(li) { 135 | if (this._active) { 136 | this._clearActive(); 137 | } 138 | this._active = li; 139 | li.classList.add('active'); 140 | }, 141 | _clearActive: function() { 142 | this._active.classList.remove('active'); 143 | this._active = null; 144 | }, 145 | _createMenuItem: function(item, menu) { 146 | var self = this; 147 | var Mx = this._Mx; 148 | var li = document.createElement("li"); 149 | li.className += " " + self.options.itemClass; 150 | li.innerText = item.text; 151 | if (item.style) { 152 | li.className += " " + item.style; 153 | } 154 | if (item.hasOwnProperty("checked")) { 155 | li.className += " sigplot-menu-checkbox"; 156 | if (item.checked) { 157 | li.className += " checked"; 158 | } 159 | } 160 | li.addEventListener("click", function() { 161 | self.remove(); 162 | Mx.menu = undefined; 163 | Mx.widget = null; 164 | if (item.handler) { 165 | item.handler(); 166 | } else if (item.menu) { 167 | var newmenu = item.menu; 168 | if (typeof item.menu === 'function') { 169 | newmenu = item.menu(); 170 | } 171 | newmenu.finalize = menu.finalize; 172 | new DomMenu(Mx, newmenu); 173 | } 174 | 175 | if ((!Mx.menu) && (menu.finalize)) { 176 | menu.finalize(); 177 | } 178 | }); 179 | li.addEventListener("mouseenter", function(e) { 180 | self._setActive(e.target); 181 | }); 182 | li.addEventListener("mouseleave", function(e) { 183 | self._clearActive(); 184 | }); 185 | self._items.push(li); 186 | return li; 187 | }, 188 | remove: function() { 189 | 190 | var Mx = this._Mx; 191 | Mx.menu = undefined; 192 | Mx.widget = null; 193 | this._menu.remove(); 194 | document.body.removeEventListener("mousemove", this._moveMenu); 195 | }, 196 | setCSS: function() { 197 | var Mx = this._Mx; 198 | var cssId = "mx-menu-css"; // id so we can always replace the css if we want to update this with mx.setTheme.. 199 | var style = document.createElement('style'); 200 | var textContent; 201 | style.id = cssId; 202 | //This really sucks...... and I hate it. -Sean 203 | /* jshint ignore:start */ 204 | textContent = "" + 205 | "." + this._menuId + "{\n" + 206 | "background-color: " + Mx.xwbg + ";\n" + 207 | "font: " + Mx.font.font + ";\n" + 208 | "color:" + Mx.xwfg + "\n" + 209 | "} \n" + 210 | ".sigplot-menu-list {\n" + 211 | " margin: 0px;\n" + 212 | " list-style: none;\n" + 213 | " padding: 0px;\n" + 214 | "}\n" + 215 | "." + this._menuId + ">div {\n" + 216 | " cursor: move;\n" + 217 | " text-align: center;\n" + 218 | " border-bottom: 2px solid " + Mx.xwts + ";\n" + 219 | "}\n" + 220 | "." + this._menuId + ">ul>li{\n" + 221 | " border-top: 2px solid " + Mx.bg + ";\n" + 222 | " background-color: " + Mx.xwlo + ";\n" + 223 | " padding: 1px;\n" + 224 | " padding-right: 5px;\n" + 225 | " padding-left: 5px;\n" + 226 | " cursor:default;\n" + 227 | "}\n" + 228 | "." + this._menuId + ">ul>li.active{\n" + 229 | " background-color: " + Mx.hi + ";\n" + 230 | "}\n" + 231 | "." + this._menuId + " {\n" + 232 | " position: relative;\n" + 233 | " color: white;\n" + 234 | " float: left;\n" + 235 | " border-radius: 5px;\n" + 236 | " padding: 3px;\n" + 237 | " font: " + Mx.font.font + ";\n" + 238 | " color:" + Mx.xwfg + "\n" + 239 | "}\n" + 240 | "." + this._menuId + ">ul>li.separator {\n" + 241 | " background-color: " + Mx.xwbs + ";\n" + 242 | "}\n" + 243 | ".sigplot-menu-checkbox:before{\n" + 244 | " margin-right: 3px; \n" + 245 | "}\n" + 246 | ".sigplot-menu-checkbox.checked:before {\n" + 247 | " content: '\\25b8';\n" + 248 | " width: 2px;\n" + 249 | " height: 3px;\n" + 250 | "}\n" + 251 | ".sigplot-menu-checkbox.checkbox:before {\n" + 252 | " content: '\\25A1';\n" + 253 | " width: 2px;\n" + 254 | " height: 3px;\n" + 255 | "}\n" + 256 | ".sigplot-menu-checkbox.checkbox.checked:before {\n" + 257 | " content: '\\25A3';\n" + 258 | " width: 2px;\n" + 259 | " height: 3px;\n" + 260 | "}\n"; 261 | 262 | /* jshint ignore:end */ 263 | if (!this._container.getElementsByTagName("style").length) { 264 | var style = document.createElement('style'); 265 | style.textContent = textContent; 266 | this._container.appendChild(style); 267 | } else { 268 | var style = this._container.getElementsByTagName("style")[0]; 269 | style.textContent = textContent; 270 | } 271 | } 272 | }; 273 | 274 | module.exports = DomMenu; 275 | 276 | }()); 277 | 278 | -------------------------------------------------------------------------------- /js/plugins.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * File: plugins.js 4 | * Copyright (c) 2012-2017, LGS Innovations Inc., All rights reserved. 5 | * 6 | * This file is part of SigPlot. 7 | * 8 | * Licensed to the LGS Innovations (LGS) under one 9 | * or more contributor license agreements. See the NOTICE file 10 | * distributed with this work for additional information 11 | * regarding copyright ownership. LGS licenses this file 12 | * to you under the Apache License, Version 2.0 (the 13 | * "License"); you may not use this file except in compliance 14 | * with the License. You may obtain a copy of the License at 15 | * 16 | * http://www.apache.org/licenses/LICENSE-2.0 17 | * 18 | * Unless required by applicable law or agreed to in writing, 19 | * software distributed under the License is distributed on an 20 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 21 | * KIND, either express or implied. See the License for the 22 | * specific language governing permissions and limitations 23 | * under the License. 24 | */ 25 | 26 | /* global module */ 27 | /* global require */ 28 | 29 | (function() { 30 | 31 | // Bundle all the standard-plugins into this module 32 | module.exports = { 33 | Plugin : require("./sigplot.plugin"), 34 | AccordionPlugin : require("./sigplot.accordion"), 35 | AnnotationPlugin : require("./sigplot.annotations"), 36 | BoxesPlugin : require("./sigplot.boxes"), 37 | PlaybackControlsPlugin : require("./sigplot.playback"), 38 | SliderPlugin : require("./sigplot.slider") 39 | }; 40 | 41 | }()); 42 | -------------------------------------------------------------------------------- /js/sigplot.annotations.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * File: sigplot.annotations.js 4 | * Copyright (c) 2012-2017, LGS Innovations Inc., All rights reserved. 5 | * 6 | * This file is part of SigPlot. 7 | * 8 | * Licensed to the LGS Innovations (LGS) under one 9 | * or more contributor license agreements. See the NOTICE file 10 | * distributed with this work for additional information 11 | * regarding copyright ownership. LGS licenses this file 12 | * to you under the Apache License, Version 2.0 (the 13 | * "License"); you may not use this file except in compliance 14 | * with the License. You may obtain a copy of the License at 15 | * 16 | * http://www.apache.org/licenses/LICENSE-2.0 17 | * 18 | * Unless required by applicable law or agreed to in writing, 19 | * software distributed under the License is distributed on an 20 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 21 | * KIND, either express or implied. See the License for the 22 | * specific language governing permissions and limitations 23 | * under the License. 24 | */ 25 | 26 | /* global module */ 27 | /* global require */ 28 | 29 | (function() { 30 | 31 | var m = require("./m"); 32 | var mx = require("./mx"); 33 | 34 | /** 35 | * @constructor 36 | * @param options 37 | * @returns {AnnotationPlugin} 38 | */ 39 | var AnnotationPlugin = function(options) { 40 | this.options = (options === undefined) ? {} : options; 41 | 42 | if (this.options.display === undefined) { 43 | this.options.display = true; 44 | } 45 | 46 | this.options.textBaseline = this.options.textBaseline || "alphabetic"; 47 | this.options.textAlign = this.options.textAlign || "left"; 48 | 49 | this.annotations = []; 50 | }; 51 | 52 | AnnotationPlugin.prototype = { 53 | init: function(plot) { 54 | var self = this; 55 | this.plot = plot; 56 | var Mx = this.plot._Mx; 57 | 58 | this.onmousemove = function(evt) { 59 | // Ignore if there are no annotations 60 | if (self.annotations.length === 0) { 61 | return; 62 | } 63 | 64 | // Or if the user wants to prevent hover actions 65 | if (self.options.prevent_hover) { 66 | return; 67 | } 68 | 69 | // Ignore if the mouse is outside of the plot area, clear the highlights 70 | if ((evt.xpos < Mx.l) || (evt.xpos > Mx.r)) { 71 | self.set_highlight(false); 72 | return; 73 | } 74 | if ((evt.ypos > Mx.b) || (evt.ypos < Mx.t)) { 75 | self.set_highlight(false); 76 | return; 77 | } 78 | 79 | // If the mouse is close to an annotation, highlight it 80 | var need_refresh = false; 81 | for (var i = 0; i < self.annotations.length; i++) { 82 | var annotation = self.annotations[i]; 83 | 84 | var pxl = { 85 | x: undefined, 86 | y: undefined 87 | }; 88 | // Perserve the legacy API for now 89 | if (annotation.absolute_placement) { 90 | pxl.x = annotation.x; 91 | pxl.y = annotation.y; 92 | } 93 | // Provide the new API 94 | if (annotation.pxl_x !== undefined) { 95 | pxl.x = annotation.pxl_x; 96 | } 97 | if (annotation.pxl_y !== undefined) { 98 | pxl.y = annotation.pxl_y; 99 | } 100 | var res = mx.real_to_pixel(Mx, annotation.x, annotation.y); 101 | if (pxl.x === undefined) { 102 | pxl.x = res.x; 103 | } 104 | 105 | if (pxl.y === undefined) { 106 | pxl.y = res.y; 107 | } 108 | 109 | var rect_upperleft = { 110 | x: pxl.x, 111 | y: pxl.y 112 | }; 113 | if ((annotation.value instanceof HTMLImageElement) || 114 | (annotation.value instanceof HTMLCanvasElement) || 115 | ((typeof HTMLVideoElement !== 'undefined') && annotation.value instanceof HTMLVideoElement)) { 116 | // For image, pxl.x and pxl.y are center 117 | rect_upperleft.x -= annotation.width / 2; 118 | rect_upperleft.y -= annotation.height / 2; 119 | } else { 120 | // For text, pxl.x and pxl.y are lower left corner 121 | rect_upperleft.y -= annotation.height; 122 | } 123 | 124 | if (mx.inrect(evt.xpos, evt.ypos, rect_upperleft.x, rect_upperleft.y, annotation.width, annotation.height)) { 125 | if (!annotation.highlight) { 126 | self.set_highlight(true, [annotation], pxl.x, pxl.y); 127 | need_refresh = true; 128 | } 129 | } else { 130 | if (annotation.highlight) { 131 | self.set_highlight(false, [annotation]); 132 | need_refresh = true; 133 | } 134 | annotation.selected = undefined; 135 | } 136 | } 137 | 138 | // Refresh the plot 139 | if (self.plot && need_refresh) { 140 | self.plot.refresh(); // todo - add call to refresh only the plugin layer itself 141 | } 142 | }; 143 | this.plot.addListener("mmove", this.onmousemove); 144 | 145 | this.onmousedown = function(evt) { 146 | for (var i = 0; i < self.annotations.length; i++) { 147 | // leverage the fact that annotation.highlight is 148 | // set when the mouse is over the annotation 149 | if (self.annotations[i].highlight) { 150 | self.annotations[i].selected = true; 151 | } 152 | } 153 | }; 154 | this.plot.addListener("mdown", this.onmousedown); 155 | 156 | this.onmouseup = function(evt) { 157 | for (var i = 0; i < self.annotations.length; i++) { 158 | // leverage the fact that annotation.highlight is 159 | // set when the mouse is over the annotation 160 | if (self.annotations[i].selected) { 161 | // Issue a highlight event 162 | var evt = document.createEvent('Event'); 163 | evt.initEvent('annotationclick', true, true); 164 | evt.annotation = self.annotations[i]; 165 | var executeDefault = mx.dispatchEvent(self.plot._Mx, evt); 166 | if ((executeDefault) && (self.annotations[i].onclick)) { 167 | self.annotations[i].onclick(); 168 | } 169 | } 170 | self.annotations[i].selected = undefined; 171 | } 172 | }; 173 | document.addEventListener("mouseup", this.onmouseup, false); 174 | }, 175 | 176 | set_highlight: function(state, annotations, x, y) { 177 | var _annotations = annotations || this.annotations; 178 | for (var i = 0; i < _annotations.length; i++) { 179 | // Issue a highlight event 180 | var evt = document.createEvent('Event'); 181 | evt.initEvent('annotationhighlight', true, true); 182 | evt.annotation = _annotations[i]; 183 | evt.state = state; 184 | evt.x = x; 185 | evt.y = y; 186 | var executeDefault = mx.dispatchEvent(this.plot._Mx, evt); 187 | if (executeDefault) { 188 | _annotations[i].highlight = state; 189 | } 190 | } 191 | }, 192 | 193 | menu: function() { 194 | var _display_handler = (function(self) { 195 | return function() { 196 | self.options.display = !self.options.display; 197 | self.plot.redraw(); 198 | }; 199 | }(this)); 200 | 201 | var _clearall_handler = (function(self) { 202 | return function() { 203 | self.annotations = []; 204 | self.plot.redraw(); 205 | }; 206 | }(this)); 207 | 208 | return { 209 | text: "Annotations...", 210 | menu: { 211 | title: "ANNOTATIONS", 212 | items: [{ 213 | text: "Display", 214 | checked: this.options.display, 215 | style: "checkbox", 216 | handler: _display_handler 217 | }, { 218 | text: "Clear All", 219 | handler: _clearall_handler 220 | }] 221 | } 222 | }; 223 | }, 224 | 225 | add_annotation: function(annotation) { 226 | this.annotations.push(annotation); 227 | 228 | this.plot.redraw(); 229 | return this.annotations.length; 230 | }, 231 | 232 | clear_annotations: function() { 233 | this.annotations = []; 234 | 235 | this.plot.redraw(); 236 | }, 237 | 238 | refresh: function(canvas) { 239 | if (!this.options.display) { 240 | return; 241 | } 242 | var Gx = this.plot._Gx; 243 | var Mx = this.plot._Mx; 244 | var ctx = canvas.getContext("2d"); 245 | var self = this; 246 | 247 | ctx.save(); 248 | // Ensure annotations are clipped at the plot borders 249 | ctx.beginPath(); 250 | ctx.rect(Mx.l, Mx.t, Mx.r - Mx.l, Mx.b - Mx.t); 251 | ctx.clip(); 252 | 253 | mx.onCanvas(Mx, canvas, function() { 254 | 255 | // iterate backwards so we can remove from the end...in the future 256 | // if we decide to have annotations auto-remove 257 | for (var i = self.annotations.length - 1; i >= 0; i--) { 258 | var annotation = self.annotations[i]; 259 | 260 | var pxl = { 261 | x: undefined, 262 | y: undefined 263 | }; 264 | // Perserve the legacy API for now 265 | if (annotation.absolute_placement) { 266 | pxl.x = annotation.x; 267 | pxl.y = annotation.y; 268 | } 269 | // Provide the new API 270 | if (annotation.pxl_x !== undefined) { 271 | pxl.x = annotation.pxl_x; 272 | } 273 | if (annotation.pxl_y !== undefined) { 274 | pxl.y = annotation.pxl_y; 275 | } 276 | var res = mx.real_to_pixel(Mx, annotation.x, annotation.y); 277 | if (pxl.x === undefined) { 278 | pxl.x = res.x; 279 | } 280 | 281 | if (pxl.y === undefined) { 282 | pxl.y = res.y; 283 | } 284 | 285 | if (!mx.inrect(pxl.x, pxl.y, Mx.l, Mx.t, Mx.r - Mx.l, Mx.b - Mx.t)) { 286 | // do we want to auto-remove? 287 | //self.annotations.splice(i,1); 288 | continue; 289 | } 290 | 291 | if ((annotation.value instanceof HTMLImageElement) || 292 | (annotation.value instanceof HTMLCanvasElement) || 293 | ((typeof HTMLVideoElement !== 'undefined') && annotation.value instanceof HTMLVideoElement)) { 294 | annotation.width = annotation.value.width; 295 | annotation.height = annotation.value.height; 296 | ctx.drawImage(annotation.value, pxl.x - (annotation.width / 2), pxl.y - (annotation.height / 2)); 297 | } else { 298 | // Setup the text styles 299 | ctx.font = annotation.font || "bold italic 20px new century schoolbook"; 300 | if (!annotation.highlight) { 301 | ctx.fillStyle = annotation.color || Mx.fg; 302 | } else { 303 | ctx.fillStyle = annotation.highlight_color || Mx.hi; 304 | } 305 | ctx.globalAlpha = 1; 306 | // Measure the text 307 | annotation.width = ctx.measureText(annotation.value).width; 308 | annotation.height = ctx.measureText("M").width; // approximation of height 309 | 310 | // Render the text 311 | ctx.textBaseline = annotation.textBaseline || self.options.textBaseline; 312 | ctx.textAlign = annotation.textAlign || self.options.textAlign; 313 | ctx.fillText(annotation.value, pxl.x, pxl.y); 314 | } 315 | 316 | 317 | if (annotation.highlight && annotation.popup) { 318 | mx.render_message_box(Mx, annotation.popup, pxl.x + 5, pxl.y + 5, annotation.popupTextColor); 319 | } 320 | } 321 | 322 | }); 323 | 324 | ctx.restore(); 325 | }, 326 | 327 | dispose: function() { 328 | this.plot = undefined; 329 | this.annotations = undefined; 330 | } 331 | }; 332 | 333 | module.exports = AnnotationPlugin; 334 | 335 | }()); 336 | -------------------------------------------------------------------------------- /js/sigplot.boxes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * File: sigplot.boxes.js 4 | * Copyright (c) 2012-2017, LGS Innovations Inc., All rights reserved. 5 | * 6 | * This file is part of SigPlot. 7 | * 8 | * Licensed to the LGS Innovations (LGS) under one 9 | * or more contributor license agreements. See the NOTICE file 10 | * distributed with this work for additional information 11 | * regarding copyright ownership. LGS licenses this file 12 | * to you under the Apache License, Version 2.0 (the 13 | * "License"); you may not use this file except in compliance 14 | * with the License. You may obtain a copy of the License at 15 | * 16 | * http://www.apache.org/licenses/LICENSE-2.0 17 | * 18 | * Unless required by applicable law or agreed to in writing, 19 | * software distributed under the License is distributed on an 20 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 21 | * KIND, either express or implied. See the License for the 22 | * specific language governing permissions and limitations 23 | * under the License. 24 | */ 25 | 26 | /* global module */ 27 | /* global require */ 28 | 29 | (function() { 30 | 31 | var m = require("./m"); 32 | var mx = require("./mx"); 33 | 34 | /** 35 | * @constructor 36 | * @param options 37 | * @returns {BoxesPlugin} 38 | */ 39 | var BoxesPlugin = function(options) { 40 | this.options = (options === undefined) ? {} : options; 41 | 42 | if (this.options.display === undefined) { 43 | this.options.display = true; 44 | } 45 | 46 | this.boxes = []; 47 | }; 48 | 49 | BoxesPlugin.prototype = { 50 | init: function(plot) { 51 | this.plot = plot; 52 | }, 53 | 54 | menu: function() { 55 | var _display_handler = (function(self) { 56 | return function() { 57 | self.options.display = !self.options.display; 58 | self.plot.redraw(); 59 | }; 60 | }(this)); 61 | 62 | var _clearall_handler = (function(self) { 63 | return function() { 64 | self.boxes = []; 65 | self.plot.redraw(); 66 | }; 67 | }(this)); 68 | 69 | return { 70 | text: "Boxes...", 71 | menu: { 72 | title: "BOXES", 73 | items: [{ 74 | text: "Display", 75 | checked: this.options.display, 76 | style: "checkbox", 77 | handler: _display_handler 78 | }, { 79 | text: "Clear All", 80 | handler: _clearall_handler 81 | }] 82 | } 83 | }; 84 | }, 85 | 86 | add_box: function(box) { 87 | this.boxes.push(box); 88 | 89 | this.plot.redraw(); 90 | return this.boxes.length; 91 | }, 92 | 93 | clear_boxes: function() { 94 | this.boxes = []; 95 | this.plot.redraw(); 96 | }, 97 | 98 | refresh: function(canvas) { 99 | if (!this.options.display) { 100 | return; 101 | } 102 | var Gx = this.plot._Gx; 103 | var Mx = this.plot._Mx; 104 | 105 | var ctx = canvas.getContext("2d"); 106 | var box, pxl; 107 | var x, y, w, h; 108 | var ul, lr; 109 | 110 | 111 | ctx.save(); 112 | ctx.beginPath(); 113 | ctx.rect(Mx.l, Mx.t, Mx.r - Mx.l, Mx.b - Mx.t); 114 | ctx.clip(); 115 | 116 | for (var i = 0; i < this.boxes.length; i++) { 117 | box = this.boxes[i]; 118 | if (box.absolute_placement === true) { 119 | x = box.x + Mx.l; 120 | y = box.y + Mx.t; 121 | w = box.w; 122 | h = box.h; 123 | } else { 124 | ul = mx.real_to_pixel(Mx, box.x, box.y); 125 | lr = pxl = mx.real_to_pixel(Mx, box.x + box.w, box.y + box.h); 126 | x = ul.x; 127 | y = ul.y; 128 | w = lr.x - ul.x; 129 | h = ul.y - lr.y; 130 | } 131 | 132 | ctx.strokeStyle = box.strokeStyle || Mx.fg; 133 | ctx.lineWidth = box.lineWidth || 1; 134 | 135 | if (ctx.lineWidth % 2 === 1) { 136 | x += 0.5; 137 | y += 0.5; 138 | } 139 | 140 | if (box.fillStyle || box.fill) { 141 | ctx.globalAlpha = box.alpha || 0.5; 142 | ctx.fillStyle = box.fillStyle || ctx.strokeStyle; 143 | ctx.fillRect(x, y, w, h); 144 | ctx.globalAlpha = 1; 145 | } 146 | ctx.strokeRect(x, 147 | y, 148 | w, 149 | h); 150 | 151 | if (box.text) { 152 | ctx.save(); 153 | ctx.font = box.font || Mx.text_H + "px Courier New, monospace"; 154 | ctx.globalAlpha = 1; 155 | ctx.textAlign = "end"; 156 | ctx.fillStyle = ctx.strokeStyle; 157 | if (box.font) { 158 | ctx.font = box.font; 159 | } 160 | 161 | x = x - Mx.text_w; 162 | y = y - (Mx.text_h / 3); 163 | 164 | var text_w = ctx.measureText(box.text).width; 165 | 166 | if ((x - text_w) < Mx.l) { 167 | x = (x + w); 168 | } 169 | 170 | ctx.fillText(box.text, x, y); 171 | ctx.restore(); 172 | } 173 | } 174 | 175 | ctx.restore(); 176 | }, 177 | 178 | dispose: function() { 179 | this.plot = undefined; 180 | this.boxes = []; 181 | } 182 | }; 183 | 184 | module.exports = BoxesPlugin; 185 | 186 | }()); 187 | -------------------------------------------------------------------------------- /js/sigplot.playback.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * File: sigplot.playback.js 4 | * Copyright (c) 2012-2017, LGS Innovations Inc., All rights reserved. 5 | * 6 | * This file is part of SigPlot. 7 | * 8 | * Licensed to the LGS Innovations (LGS) under one 9 | * or more contributor license agreements. See the NOTICE file 10 | * distributed with this work for additional information 11 | * regarding copyright ownership. LGS licenses this file 12 | * to you under the Apache License, Version 2.0 (the 13 | * "License"); you may not use this file except in compliance 14 | * with the License. You may obtain a copy of the License at 15 | * 16 | * http://www.apache.org/licenses/LICENSE-2.0 17 | * 18 | * Unless required by applicable law or agreed to in writing, 19 | * software distributed under the License is distributed on an 20 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 21 | * KIND, either express or implied. See the License for the 22 | * specific language governing permissions and limitations 23 | * under the License. 24 | */ 25 | 26 | /* global module */ 27 | /* global require */ 28 | 29 | (function() { 30 | 31 | var m = require("./m"); 32 | var mx = require("./mx"); 33 | var common = require("./common"); 34 | 35 | /** 36 | * @constructor 37 | * @param options 38 | * @returns {PlaybackControlsPlugin} 39 | */ 40 | var PlaybackControlsPlugin = function(options) { 41 | this.options = { 42 | display: true, 43 | size: 25, 44 | lineWidth: 2, 45 | fillStyle: false 46 | }; 47 | common.update(this.options, options); 48 | this.state = "paused"; 49 | this.highlight = false; 50 | }; 51 | 52 | PlaybackControlsPlugin.prototype = { 53 | init: function(plot) { 54 | this.plot = plot; 55 | 56 | // Register for mouse events 57 | var self = this; 58 | var Mx = this.plot._Mx; 59 | this.onmousemove = function(evt) { 60 | if (Mx.warpbox) { 61 | return; 62 | } // Don't highlight if a warpbox is being drawn 63 | 64 | // Ignore if the mouse is outside of the control area 65 | if (self.ismouseover(evt.xpos, evt.ypos)) { 66 | self.set_highlight(true); 67 | } else { 68 | self.set_highlight(false); 69 | } 70 | }; 71 | this.plot.addListener("mmove", this.onmousemove); 72 | 73 | this.onmousedown = function(evt) { 74 | if (Mx.warpbox) { 75 | return; 76 | } // Don't handle if a warpbox is being drawn 77 | 78 | // Ignore if the mouse is outside of the control area 79 | if (self.ismouseover(evt.xpos, evt.ypos)) { 80 | evt.preventDefault(); 81 | } 82 | }; 83 | // Prevents zooms and stuff from occuring 84 | this.plot.addListener("mdown", this.onmousedown); 85 | 86 | this.onmouseclick = function(evt) { 87 | if (Mx.warpbox) { 88 | return; 89 | } // Don't handle if a warpbox is being drawn 90 | 91 | // Ignore if the mouse is outside of the control area 92 | if (self.ismouseover(evt.xpos, evt.ypos)) { 93 | self.toggle(); 94 | evt.preventDefault(); 95 | } 96 | }; 97 | this.plot.addListener("mclick", this.onmouseclick); 98 | }, 99 | 100 | set_highlight: function(ishighlight) { 101 | if (ishighlight !== this.highlight) { 102 | this.highlight = ishighlight; 103 | this.plot.redraw(); 104 | } 105 | }, 106 | 107 | toggle: function(new_state) { 108 | if (!new_state) { 109 | if (this.state === "paused") { 110 | new_state = "playing"; 111 | } else { 112 | new_state = "paused"; 113 | } 114 | } 115 | 116 | if (new_state !== this.state) { 117 | if (this.plot) { 118 | var Mx = this.plot._Mx; 119 | var evt = document.createEvent('Event'); 120 | evt.initEvent('playbackevt', true, true); 121 | evt.state = new_state; 122 | var executeDefault = mx.dispatchEvent(Mx, evt); 123 | if (executeDefault) { 124 | this.state = new_state; 125 | } 126 | this.plot.redraw(); 127 | } 128 | } 129 | }, 130 | 131 | addListener: function(what, callback) { 132 | var Mx = this.plot._Mx; 133 | mx.addEventListener(Mx, what, callback, false); 134 | }, 135 | 136 | removeListener: function(what, callback) { 137 | var Mx = this.plot._Mx; 138 | mx.removeEventListener(Mx, what, callback, false); 139 | }, 140 | 141 | ismouseover: function(xpos, ypos) { 142 | var position = this.position(); 143 | var distance_from_ctr = Math.pow(xpos - position.x, 2) + Math.pow(ypos - position.y, 2); 144 | var R = this.options.size / 2; 145 | 146 | return (distance_from_ctr < Math.pow(R, 2)); 147 | }, 148 | 149 | position: function() { 150 | if (this.options.position) { 151 | return this.options.position; 152 | } else if (this.plot) { 153 | var Mx = this.plot._Mx; 154 | var R = this.options.size / 2; 155 | return { 156 | x: Mx.l + R + this.options.lineWidth + 1, 157 | y: Mx.t + R + this.options.lineWidth + 1 158 | }; 159 | } else { 160 | return { 161 | x: null, 162 | y: null 163 | }; 164 | } 165 | }, 166 | 167 | refresh: function(canvas) { 168 | if (!this.options.display) { 169 | return; 170 | } 171 | var Gx = this.plot._Gx; 172 | var Mx = this.plot._Mx; 173 | 174 | var ctx = canvas.getContext("2d"); 175 | 176 | ctx.lineWidth = this.options.lineWidth; 177 | var R = this.options.size / 2; 178 | 179 | if (this.highlight) { 180 | ctx.lineWidth += 2; 181 | R += 1; 182 | } 183 | 184 | var position = this.position(); 185 | 186 | 187 | ctx.beginPath(); 188 | ctx.arc(position.x, position.y, R - ctx.lineWidth, 0, Math.PI * 2, true); 189 | ctx.closePath(); 190 | 191 | ctx.strokeStyle = this.options.strokeStyle || Mx.fg; 192 | ctx.stroke(); 193 | 194 | if (this.options.fillStyle) { 195 | ctx.fillStyle = this.options.fillStyle; 196 | ctx.fill(); 197 | } 198 | 199 | if (this.state === "paused") { 200 | var p1 = { 201 | x: R * 0.8, 202 | y: R * 0.56 203 | }; 204 | var p2 = { 205 | x: R * 1.45, 206 | y: R 207 | }; 208 | var p3 = { 209 | x: R * 0.8, 210 | y: R * 1.45 211 | }; 212 | 213 | p1.x += (position.x - R); 214 | p2.x += (position.x - R); 215 | p3.x += (position.x - R); 216 | p1.y += (position.y - R); 217 | p2.y += (position.y - R); 218 | p3.y += (position.y - R); 219 | 220 | ctx.beginPath(); 221 | ctx.moveTo(p1.x, p1.y); 222 | ctx.lineTo(p2.x, p2.y); 223 | ctx.lineTo(p3.x, p3.y); 224 | ctx.closePath(); 225 | 226 | ctx.fillStyle = this.options.strokeStyle || Mx.fg; 227 | ctx.fill(); 228 | } else { 229 | ctx.lineCap = 'round'; 230 | ctx.lineWidth = Math.floor(Math.min(1, this.options.size / 8)); 231 | 232 | var p1 = { 233 | x: R * 0.8, 234 | y: R / 2 235 | }; 236 | var p2 = { 237 | x: R * 0.8, 238 | y: R * 1.5 239 | }; 240 | p1.x += (position.x - R); 241 | p2.x += (position.x - R); 242 | p1.y += (position.y - R); 243 | p2.y += (position.y - R); 244 | 245 | ctx.beginPath(); 246 | ctx.moveTo(p1.x, p1.y); 247 | ctx.lineTo(p2.x, p2.y); 248 | ctx.closePath(); 249 | ctx.stroke(); 250 | 251 | var p1 = { 252 | x: R + (R / 5), 253 | y: R / 2 254 | }; 255 | var p2 = { 256 | x: R + (R / 5), 257 | y: R * 1.5 258 | }; 259 | p1.x += (position.x - R); 260 | p2.x += (position.x - R); 261 | p1.y += (position.y - R); 262 | p2.y += (position.y - R); 263 | 264 | ctx.beginPath(); 265 | ctx.moveTo(p1.x, p1.y); 266 | ctx.lineTo(p2.x, p2.y); 267 | ctx.closePath(); 268 | ctx.stroke(); 269 | } 270 | 271 | ctx.restore(); 272 | }, 273 | 274 | dispose: function() { 275 | this.plot = undefined; 276 | this.boxes = undefined; 277 | } 278 | }; 279 | 280 | module.exports = PlaybackControlsPlugin; 281 | 282 | }()); 283 | -------------------------------------------------------------------------------- /js/sigplot.plugin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * File: sigplot.plugin.js 4 | * Copyright (c) 2012-2019, LGS Innovations Inc., All rights reserved. 5 | * 6 | * This file is part of SigPlot. 7 | * 8 | * Licensed to the LGS Innovations (LGS) under one 9 | * or more contributor license agreements. See the NOTICE file 10 | * distributed with this work for additional information 11 | * regarding copyright ownership. LGS licenses this file 12 | * to you under the Apache License, Version 2.0 (the 13 | * "License"); you may not use this file except in compliance 14 | * with the License. You may obtain a copy of the License at 15 | * 16 | * http://www.apache.org/licenses/LICENSE-2.0 17 | * 18 | * Unless required by applicable law or agreed to in writing, 19 | * software distributed under the License is distributed on an 20 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 21 | * KIND, either express or implied. See the License for the 22 | * specific language governing permissions and limitations 23 | * under the License. 24 | */ 25 | 26 | /*jslint nomen: true, browser: true, devel: true */ 27 | 28 | /* global module */ 29 | /* global require */ 30 | 31 | (function() { 32 | 33 | var mx = require("./mx"); 34 | var common = require("./common"); 35 | 36 | class Plugin { 37 | /** 38 | * Plugins implement pluginConstructor to define properties (via this.defineProperty) 39 | * and any local variables. 40 | */ 41 | pluginConstructor() {} 42 | 43 | /** 44 | * pluginInit is called afer the plugin has been added to 45 | * a plot. 46 | * 47 | * @param {object} plot 48 | * The plot the plugin was added to. 49 | */ 50 | pluginInit() {} 51 | 52 | /** 53 | * pluginDispose is called after then plugin has been removed 54 | * from a plot. 55 | */ 56 | pluginDispose() {} 57 | 58 | /** 59 | * pluginRefresh is called whenever the plugin need to redraw. 60 | * 61 | * Plugins should render their current state to this.canvas. The canvas 62 | * is entirely under the control of the plugin can can be cleared or 63 | * completely filled 64 | */ 65 | pluginRefresh() {} 66 | 67 | /** 68 | * pluginGetMenu is called to obtain the menu structure for the 69 | * plugin. 70 | * 71 | * If a plugin does not have a menu, it does not need to implement this. 72 | */ 73 | pluginGetMenu() {} 74 | 75 | /** 76 | * Construct the plugin. 77 | * 78 | * @param {object} properties 79 | * The properties for this plugin. 80 | */ 81 | constructor(properties) { 82 | this.initial_properties = properties; 83 | this.properties = {}; 84 | 85 | this._plot = undefined; 86 | this._canvas = undefined; 87 | 88 | // All plugins have a display Property 89 | this.defineProperty("display", { 90 | defaultValue: true, 91 | refreshOnChange: true, 92 | help: "changes if the plugin is rendered on the plot or not" 93 | }); 94 | 95 | this.pluginSetup(); 96 | 97 | return this; 98 | } 99 | 100 | /** 101 | * Called when the plugin is added to the plot. 102 | * @param plot 103 | * The plot the plugin is attahced to 104 | * @param canvas 105 | * The canvas the plugin should render to 106 | */ 107 | init(plot, canvas) { 108 | if (this._plot) { 109 | throw "Plugins can only be added to one plot at a time"; 110 | } 111 | this._plot = plot; 112 | this._canvas = canvas; 113 | this.properties = {}; 114 | 115 | // When a plugin is added to a plot, it's properties are reset 116 | // to the initial values provided during construction. This 117 | // avoids confusion when a plugin is constructed, added to a plot 118 | // has it's state modified, then removed from a plot, and added 119 | // back to a plot 120 | this.resetProperties(this.initial_properties); 121 | 122 | this.pluginInit(); 123 | } 124 | 125 | /** 126 | * Get's the plot 127 | */ 128 | get plot() { 129 | return this._plot; 130 | } 131 | 132 | get Mx() { 133 | return (this._plot) ? this._plot._Mx : null; 134 | } 135 | 136 | get Gx() { 137 | return (this._plot) ? this._plot._Gx : null; 138 | } 139 | 140 | get canvas() { 141 | return this._canvas; 142 | } 143 | 144 | get Context() { 145 | return (this._canvas) ? this._canvas.getContext("2d") : null; 146 | } 147 | 148 | /** 149 | * Called when the plugin is removed from the plot. 150 | */ 151 | dispose() { 152 | this.pluginDispose(); 153 | 154 | this._plot = undefined; 155 | this._canvas = undefined; 156 | this.properties = {}; 157 | } 158 | 159 | /** 160 | * Refresh is called when the plugin needs to redraw itself. 161 | */ 162 | refresh() { 163 | if (!this._plot || !this._canvas) { 164 | return; 165 | } 166 | if (!this.properties.display) { 167 | return; 168 | } 169 | this.pluginRefresh(this.canvas); 170 | } 171 | 172 | /** 173 | * Provides the menu for the plugin 174 | * 175 | * @returns 176 | * A mx.menu compatible object or a function that creates one 177 | */ 178 | menu() { 179 | return this.pluginGetMenu(); 180 | } 181 | 182 | /** 183 | * Defines a new Property that the Plugin exposes. 184 | * 185 | * @param {string} PropertyName 186 | * @param {object} definition 187 | */ 188 | defineProperty(PropertyName, definition) { 189 | if (this.definedproperties === undefined) { 190 | this.definedproperties = {}; 191 | } 192 | 193 | definition = definition || {}; 194 | 195 | this.definedproperties[PropertyName] = definition; 196 | 197 | // Fluentize the API 198 | this[PropertyName] = function() { 199 | if (!arguments.length) { 200 | return this.properties[PropertyName]; 201 | } 202 | 203 | if (definition.readonly) { 204 | throw "property " + PropertyName + " is readonly"; 205 | } 206 | 207 | if (this.properties[PropertyName] !== arguments[0]) { 208 | this.properties[PropertyName] = arguments[0]; 209 | if (definition.callback) { 210 | definition.callback(arguments[0]); 211 | } 212 | if (definition.refreshOnChange) { 213 | this.refresh(); 214 | } 215 | return this; 216 | } 217 | }; 218 | } 219 | 220 | resetProperties(overrides) { 221 | for (let propName in this.definedproperties) { 222 | this.properties[propName] = this.definedproperties[propName].defaultValue; 223 | } 224 | this.assignProperties(overrides); 225 | } 226 | 227 | /** 228 | * Updates the Plugin's properties with new values. 229 | * 230 | * @param {object} properties 231 | */ 232 | assignProperties(properties) { 233 | let refresh = false; 234 | for (let propName in properties) { 235 | // don't let the user define new properties 236 | if (!this.definedproperties.hasOwnProperty(propName)) { 237 | continue; 238 | } 239 | 240 | // if the values are the same nothing to do 241 | if (this.properties[propName] === properties[propName]) { 242 | continue; 243 | } 244 | 245 | if (this.definedproperties[propName].readonly) { 246 | throw "property " + propName + " is readonly"; 247 | } 248 | 249 | // set the Property 250 | this.properties[propName] = properties[propName]; 251 | // make the callback if necessary 252 | if (this.definedproperties[propName].callback) { 253 | this.definedproperties[propName].callback(properties[propName]); 254 | } 255 | // if a refresh is necessary, call it later 256 | if (this.definedproperties[propName].refreshOnChange === true) { 257 | refresh = true; 258 | } 259 | } 260 | // refresh if necessary 261 | if (refresh) { 262 | this.refresh(); 263 | } 264 | } 265 | 266 | /** 267 | * Register to receive a plugin specific event 268 | * 269 | * @param type 270 | * The type of event 271 | * @param fn 272 | * The function callback 273 | * @param context 274 | * Context that will be provided to the callback 275 | */ 276 | on(type, fn, context) { 277 | if (!this._events) { 278 | this._events = {}; 279 | } 280 | if (!this._events[type]) { 281 | this._events[type] = []; 282 | } 283 | if (context === this) { 284 | // Less memory footprint. 285 | context = undefined; 286 | } 287 | this._events[type].push({ 288 | cb: fn, 289 | ctx: context 290 | }); 291 | } 292 | 293 | /** 294 | * Emit a plugin event. 295 | */ 296 | emit(type, data) { 297 | var event = Object.assign({}, data, { 298 | type: type, 299 | target: this 300 | }); 301 | if (this._events) { 302 | var listeners = this._events[type]; 303 | if (listeners) { 304 | for (var i = 0, len = listeners.length; i < len; i++) { 305 | var l = listeners[i]; 306 | l.cb.call(l.ctx || this, event); 307 | } 308 | } 309 | } 310 | return this; 311 | } 312 | 313 | /** 314 | * Unregister callback for a plugin specific event 315 | * 316 | * @param type 317 | * The type of event 318 | * @param fn 319 | * The function callback 320 | * @param context 321 | * Context that will be provided to the callback 322 | */ 323 | off(type, fn, context) { 324 | var listeners, 325 | i, 326 | len; 327 | if (!type) { 328 | // clear all listeners if called without arguments 329 | delete this._events; 330 | } 331 | if (!this._events) { 332 | return; 333 | } 334 | listeners = this._events[type]; 335 | if (!listeners) { 336 | return; 337 | } 338 | if (context === this) { 339 | context = undefined; 340 | } 341 | if (listeners) { 342 | // find fn and remove it 343 | for (i = 0, len = listeners.length; i < len; i++) { 344 | var l = listeners[i]; 345 | if (l.ctx !== context) { 346 | continue; 347 | } 348 | if (l.fn === fn) { 349 | listeners.splice(i, 1); 350 | return; 351 | } 352 | } 353 | } 354 | return this; 355 | } 356 | 357 | /** 358 | * Add a listener to a Plot event 359 | */ 360 | addListener(what, callback) { 361 | if (!this.Mx) { 362 | throw "listeners cannot be added until pluginInit is called"; 363 | } 364 | mx.addEventListener(this.Mx, what, callback, false); 365 | } 366 | 367 | /** 368 | * Remove a listener from the Plot 369 | */ 370 | removeListener(what, callback) { 371 | if (!this.Mx) { 372 | throw "listeners cannot be removed until pluginInit is called"; 373 | } 374 | mx.removeEventListener(this.Mx, what, callback, false); 375 | } 376 | } 377 | 378 | module.exports = { 379 | Plugin: Plugin 380 | }; 381 | }()); 382 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function(config) { 2 | config.set({ 3 | files: ['benchmark/autobench.js', 4 | "benchmark/index.html", 5 | "benchmark/pass.html", 6 | "benchmark/fail.html", 7 | "benchmark/gamingbench.js", 8 | "benchmark/tools.js", 9 | "benchmark/benchmarks.js", 10 | "benchmark/index.css", 11 | "benchmark/index.js", 12 | "dist/sigplot.js", 13 | "dist/bluefile.js", 14 | "dist/sigplot.plugins.js"], 15 | browsers: ['Firefox', 'Chrome'], 16 | frameworks: ['qunit'], 17 | concurrency: 1, 18 | browserNoActivityTimeout: 360000, 19 | singleRun: true, 20 | logLevel: config.LOG_DEBUG 21 | }); 22 | }; 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sigplot", 3 | "description": "Advanced plotting for signal processing applications", 4 | "version": "2.0.0-rc19", 5 | "homepage": "http://sigplot.lgsinnovations.com", 6 | "main": "js/sigplot.js", 7 | "files": [ 8 | "js", 9 | "dist/bluefile-debug.js", 10 | "dist/bluefile-minimized.js", 11 | "dist/matfile-debug.js", 12 | "dist/matfile-minimized.js", 13 | "dist/sigplot-debug.js", 14 | "dist/sigplot-minimized.js", 15 | "dist/sigplot.plugins-debug.js", 16 | "dist/sigplot.plugins-minimized.js" 17 | ], 18 | "author": { 19 | "name": "LGS Innovations, Inc.", 20 | "email": "sigplot@lgsinnovations.com", 21 | "url": "http://www.lgsinnovations.com" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "git://github.com/LGSInnovations/sigplot.git" 26 | }, 27 | "bugs": { 28 | "url": "https://github.com/LGSInnovations/sigplot/issues" 29 | }, 30 | "license": "apache-2.0", 31 | "licenses": [ 32 | { 33 | "type": "apache-2.0", 34 | "url": "http://www.apache.org/licenses/LICENSE-2.0" 35 | } 36 | ], 37 | "scripts": { 38 | "generate-docs": "rm -rf ./doc/; node_modules/.bin/jsdoc --configure .jsdoc.json --verbose; cp ./fft-white.png doc/sigplot/$npm_package_version/", 39 | "test": "grunt test", 40 | "prepublish": "grunt dist" 41 | }, 42 | "dependencies": { 43 | "loglevel": "^1.4.1", 44 | "sigfile": "^0.1.9", 45 | "spin": "0.0.1", 46 | "tinycolor2": "^1.4.1", 47 | "travis": "^0.1.1", 48 | "underscore": "^1.9.2" 49 | }, 50 | "devDependencies": { 51 | "@babel/core": "^7.3.3", 52 | "@babel/preset-env": "^7.3.1", 53 | "babelify": "^10.0.0", 54 | "catharsis": "^0.8.9", 55 | "express": "^4.13.4", 56 | "grunt": "^1.0.3", 57 | "grunt-browserify": "^5.0.0", 58 | "grunt-cli": "^1.3.2", 59 | "grunt-closure-compiler": "^0.0.21", 60 | "grunt-contrib-clean": "^1.1.0", 61 | "grunt-contrib-compress": "^1.4.3", 62 | "grunt-contrib-jshint": "^1.1.0", 63 | "grunt-contrib-qunit": "^2.0.0", 64 | "grunt-express-server": "^0.5.3", 65 | "grunt-githash": "^0.1.3", 66 | "grunt-http-server": "^2.1.0", 67 | "grunt-jsbeautifier": "^0.2.13", 68 | "grunt-jsdoc": "^2.2.1", 69 | "grunt-karma": "^2.0.0", 70 | "grunt-open": "^0.2.3", 71 | "grunt-services": "^0.1.0", 72 | "grunt-shell-spawn": "^0.3.12", 73 | "grunt-text-replace": "^0.4.0", 74 | "jasmine": "^3.1.0", 75 | "karma": "^2.0.2", 76 | "karma-chrome-launcher": "^2.0.2", 77 | "karma-firefox-launcher": "^1.0.0", 78 | "karma-jasmine": "^1.1.2", 79 | "karma-qunit": "^2.1.0", 80 | "marked": "^0.3.6", 81 | "minami": "^1.2.3", 82 | "qunit-assert-close": "^2.1.2", 83 | "qunitjs": "^2.4.1", 84 | "taffydb": "^2.7.3" 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /support/google-closure-compiler/build/COPYING: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /support/google-closure-compiler/build/README: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2009 The Closure Compiler Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // 18 | // Contents 19 | // 20 | 21 | The Closure Compiler performs checking, instrumentation, and 22 | optimizations on JavaScript code. The purpose of this README is to 23 | explain how to build and run the Closure Compiler. 24 | 25 | The Closure Compiler requires Java 6 or higher. 26 | http://www.java.com/ 27 | 28 | 29 | // 30 | // Building The Closure Compiler 31 | // 32 | 33 | There are three ways to get a Closure Compiler executable. 34 | 35 | 1) Use one we built for you. 36 | 37 | Pre-built Closure binaries can be found at 38 | http://code.google.com/p/closure-compiler/downloads/list 39 | 40 | 41 | 2) Check out the source and build it with Apache Ant. 42 | 43 | First, check out the full source tree of the Closure Compiler. There 44 | are instructions on how to do this at the project site. 45 | http://code.google.com/p/closure-compiler/source/checkout 46 | 47 | Apache Ant is a cross-platform build tool. 48 | http://ant.apache.org/ 49 | 50 | At the root of the source tree, there is an Ant file named 51 | build.xml. To use it, navigate to the same directory and type the 52 | command 53 | 54 | ant jar 55 | 56 | This will produce a jar file called "build/compiler.jar". 57 | 58 | 59 | 3) Check out the source and build it with Eclipse. 60 | 61 | Eclipse is a cross-platform IDE. 62 | http://www.eclipse.org/ 63 | 64 | Under Eclipse's File menu, click "New > Project ..." and create a 65 | "Java Project." You will see an options screen. Give the project a 66 | name, select "Create project from existing source," and choose the 67 | root of the checked-out source tree as the existing directory. Verify 68 | that you are using JRE version 6 or higher. 69 | 70 | Eclipse can use the build.xml file to discover rules. When you 71 | navigate to the build.xml file, you will see all the build rules in 72 | the "Outline" pane. Run the "jar" rule to build the compiler in 73 | build/compiler.jar. 74 | 75 | 76 | // 77 | // Running The Closure Compiler 78 | // 79 | 80 | Once you have the jar binary, running the Closure Compiler is straightforward. 81 | 82 | On the command line, type 83 | 84 | java -jar compiler.jar 85 | 86 | This starts the compiler in interactive mode. Type 87 | 88 | var x = 17 + 25; 89 | 90 | then hit "Enter", then hit "Ctrl-Z" (on Windows) or "Ctrl-D" (on Mac or Linux) 91 | and "Enter" again. The Compiler will respond: 92 | 93 | var x=42; 94 | 95 | The Closure Compiler has many options for reading input from a file, 96 | writing output to a file, checking your code, and running 97 | optimizations. To learn more, type 98 | 99 | java -jar compiler.jar --help 100 | 101 | You can read more detailed documentation about the many flags at 102 | http://code.google.com/closure/compiler/docs/gettingstarted_app.html 103 | 104 | 105 | // 106 | // Compiling Multiple Scripts 107 | // 108 | 109 | If you have multiple scripts, you should compile them all together with 110 | one compile command. 111 | 112 | java -jar compiler.jar --js=in1.js --js=in2.js ... --js_output_file=out.js 113 | 114 | The Closure Compiler will concatenate the files in the order they're 115 | passed at the command line. 116 | 117 | If you need to compile many, many scripts together, you may start to 118 | run into problems with managing dependencies between scripts. You 119 | should check out the Closure Library. It contains functions for 120 | enforcing dependencies between scripts, and a tool called calcdeps.py 121 | that knows how to give scripts to the Closure Compiler in the right 122 | order. 123 | 124 | http://code.google.com/p/closure-library/ 125 | 126 | // 127 | // Licensing 128 | // 129 | 130 | Unless otherwise stated, all source files are licensed under 131 | the Apache License, Version 2.0. 132 | 133 | 134 | ----- 135 | Code under: 136 | src/com/google/javascript/rhino 137 | test/com/google/javascript/rhino 138 | 139 | URL: http://www.mozilla.org/rhino 140 | Version: 1.5R3, with heavy modifications 141 | License: Netscape Public License and MPL / GPL dual license 142 | 143 | Description: A partial copy of Mozilla Rhino. Mozilla Rhino is an 144 | implementation of JavaScript for the JVM. The JavaScript parser and 145 | the parse tree data structures were extracted and modified 146 | significantly for use by Google's JavaScript compiler. 147 | 148 | Local Modifications: The packages have been renamespaced. All code not 149 | relevant to parsing has been removed. A JsDoc parser and static typing 150 | system have been added. 151 | 152 | 153 | ----- 154 | Code in: 155 | lib/rhino 156 | 157 | Rhino 158 | URL: http://www.mozilla.org/rhino 159 | Version: Trunk 160 | License: Netscape Public License and MPL / GPL dual license 161 | 162 | Description: Mozilla Rhino is an implementation of JavaScript for the JVM. 163 | 164 | Local Modifications: Minor changes to parsing JSDoc that usually get pushed 165 | up-stream to Rhino trunk. 166 | 167 | 168 | ----- 169 | Code in: 170 | lib/args4j.jar 171 | 172 | Args4j 173 | URL: https://args4j.dev.java.net/ 174 | Version: 2.0.16 175 | License: MIT 176 | 177 | Description: 178 | args4j is a small Java class library that makes it easy to parse command line 179 | options/arguments in your CUI application. 180 | 181 | Local Modifications: None. 182 | 183 | 184 | ----- 185 | Code in: 186 | lib/guava.jar 187 | 188 | Guava Libraries 189 | URL: http://code.google.com/p/guava-libraries/ 190 | Version: 15.0 191 | License: Apache License 2.0 192 | 193 | Description: Google's core Java libraries. 194 | 195 | Local Modifications: None. 196 | 197 | 198 | ----- 199 | Code in: 200 | lib/jsr305.jar 201 | 202 | Annotations for software defect detection 203 | URL: http://code.google.com/p/jsr-305/ 204 | Version: svn revision 47 205 | License: BSD License 206 | 207 | Description: Annotations for software defect detection. 208 | 209 | Local Modifications: None. 210 | 211 | 212 | ----- 213 | Code in: 214 | lib/jarjar.jar 215 | 216 | Jar Jar Links 217 | URL: http://jarjar.googlecode.com/ 218 | Version: 1.1 219 | License: Apache License 2.0 220 | 221 | Description: 222 | A utility for repackaging Java libraries. 223 | 224 | Local Modifications: None. 225 | 226 | 227 | ---- 228 | Code in: 229 | lib/junit.jar 230 | 231 | JUnit 232 | URL: http://sourceforge.net/projects/junit/ 233 | Version: 4.10 234 | License: Common Public License 1.0 235 | 236 | Description: A framework for writing and running automated tests in Java. 237 | 238 | Local Modifications: None. 239 | 240 | 241 | --- 242 | Code in: 243 | lib/protobuf-java.jar 244 | 245 | Protocol Buffers 246 | URL: http://code.google.com/p/protobuf/ 247 | Version: 2.4.1 248 | License: New BSD License 249 | 250 | Description: Supporting libraries for protocol buffers, 251 | an encoding of structured data. 252 | 253 | Local Modifications: None 254 | 255 | 256 | --- 257 | Code in: 258 | lib/ant.jar 259 | lib/ant-launcher.jar 260 | 261 | URL: http://ant.apache.org/bindownload.cgi 262 | Version: 1.8.1 263 | License: Apache License 2.0 264 | Description: 265 | Ant is a Java based build tool. In theory it is kind of like "make" 266 | without make's wrinkles and with the full portability of pure java code. 267 | 268 | Local Modifications: None 269 | 270 | 271 | --- 272 | Code in: 273 | lib/json.jar 274 | URL: http://json.org/java/index.html 275 | Version: JSON version 20090211 276 | License: MIT license 277 | Description: 278 | JSON is a set of java files for use in transmitting data in JSON format. 279 | 280 | Local Modifications: None 281 | 282 | --- 283 | Code in 284 | lib/mockito-core.jar 285 | URL: https://code.google.com/p/mockito 286 | Version: 1.9.5 287 | License: MIT license 288 | Description: 289 | Mockito is an open source testing framework for Java. The framework allows the creation of Test Double objects (called "Mock Objects") in automated unit tests for the purpose of Test-driven Development (TDD) or Behavior Driven Development (BDD). 290 | 291 | Local Modifications: None 292 | 293 | --- 294 | Code in 295 | lib/objenesis.ar 296 | URL: http://objenesis.org 297 | Version: 1.2 298 | License: Apache 2.0 license 299 | Description: 300 | Depended by lib/mockito-core.jar, not used directly. 301 | 302 | Local Modifications: None 303 | 304 | --- 305 | Code in: 306 | tools/maven-ant-tasks-2.1.3.jar 307 | URL: http://maven.apache.org 308 | Version 2.1.3 309 | License: Apache License 2.0 310 | Description: 311 | Maven Ant tasks are used to manage dependencies and to install/deploy to 312 | maven repositories. 313 | 314 | Local Modifications: None 315 | -------------------------------------------------------------------------------- /support/google-closure-compiler/build/compiler.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LGSInnovations/sigplot/480cec393280a41359924383d06bb6849eeab879/support/google-closure-compiler/build/compiler.jar -------------------------------------------------------------------------------- /test/dat/info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LGSInnovations/sigplot/480cec393280a41359924383d06bb6849eeab879/test/dat/info.png -------------------------------------------------------------------------------- /test/dat/keyword_test_file.tmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LGSInnovations/sigplot/480cec393280a41359924383d06bb6849eeab879/test/dat/keyword_test_file.tmp -------------------------------------------------------------------------------- /test/dat/lots_of_keywords.tmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LGSInnovations/sigplot/480cec393280a41359924383d06bb6849eeab879/test/dat/lots_of_keywords.tmp -------------------------------------------------------------------------------- /test/dat/penny.prm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LGSInnovations/sigplot/480cec393280a41359924383d06bb6849eeab879/test/dat/penny.prm -------------------------------------------------------------------------------- /test/dat/pulse_cx.tmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LGSInnovations/sigplot/480cec393280a41359924383d06bb6849eeab879/test/dat/pulse_cx.tmp -------------------------------------------------------------------------------- /test/dat/ramp.tmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LGSInnovations/sigplot/480cec393280a41359924383d06bb6849eeab879/test/dat/ramp.tmp -------------------------------------------------------------------------------- /test/dat/raster.tmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LGSInnovations/sigplot/480cec393280a41359924383d06bb6849eeab879/test/dat/raster.tmp -------------------------------------------------------------------------------- /test/dat/scalarpacked.tmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LGSInnovations/sigplot/480cec393280a41359924383d06bb6849eeab879/test/dat/scalarpacked.tmp -------------------------------------------------------------------------------- /test/dat/sin.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LGSInnovations/sigplot/480cec393280a41359924383d06bb6849eeab879/test/dat/sin.mat -------------------------------------------------------------------------------- /test/dat/sin.tmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LGSInnovations/sigplot/480cec393280a41359924383d06bb6849eeab879/test/dat/sin.tmp -------------------------------------------------------------------------------- /test/penny.prm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LGSInnovations/sigplot/480cec393280a41359924383d06bb6849eeab879/test/penny.prm -------------------------------------------------------------------------------- /test/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | WebSigPlot Unit Test 6 | 7 | 8 | 13 | 14 | 15 |
16 |
17 |
18 | 19 | 20 | 24 | 25 | 26 | 27 | 28 | 29 | --------------------------------------------------------------------------------