Repository: Persper/js-callgraph Branch: master Commit: 7abc6bb04fed Files: 184 Total size: 1.1 MB Directory structure: gitextract_ps1zfdc6/ ├── .eslintrc ├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── index.js ├── input-scripts/ │ ├── coolclock/ │ │ ├── coolclock.js │ │ ├── excanvas.js │ │ └── moreskins.js │ ├── flotr/ │ │ ├── flotr-0.2.0-alpha.js │ │ ├── flotr.debug-0.2.0-alpha.js │ │ └── lib/ │ │ ├── base64.js │ │ ├── canvas2image.js │ │ ├── canvastext.js │ │ ├── excanvas.js │ │ └── prototype-1.6.0.2.js │ ├── fullcalendar/ │ │ ├── fullcalendar/ │ │ │ ├── fullcalendar.css │ │ │ ├── fullcalendar.js │ │ │ ├── fullcalendar.print.css │ │ │ └── gcal.js │ │ └── lib/ │ │ └── jquery-2.1.0.js │ └── simple-scripts/ │ ├── function_function.js │ ├── functioncall-arithmetic.js │ ├── jQuery.js │ ├── or_function.js │ └── use_before_definition.js ├── js-callgraph.js ├── package.json ├── saveSvgAsPng.js ├── scripts/ │ ├── install-hooks │ └── pre-commit ├── src/ │ ├── astutil.js │ ├── bindings.js │ ├── bitset.js │ ├── callbackCounter.js │ ├── callgraph.js │ ├── dftc.js │ ├── diagnostics.js │ ├── flowgraph.js │ ├── graph.js │ ├── harness.js │ ├── input-2.js │ ├── linkedList.js │ ├── module.js │ ├── natives.js │ ├── numset.js │ ├── olist.js │ ├── pessimistic.js │ ├── requireJsGraph.js │ ├── runner.js │ ├── semioptimistic.js │ ├── set.js │ ├── srcPreprocessor.js │ ├── symtab.js │ ├── tests.js │ ├── underscore.js │ └── utils.js └── tests/ ├── README.md ├── basics/ │ ├── arrow.js │ ├── arrow.truth │ ├── assignment.js │ ├── assignment.truth │ ├── global-as-prop.js │ ├── global-as-prop.truth │ ├── local-is-fine.js │ ├── local-is-fine.truth │ ├── method-def.js │ └── method-def.truth ├── callgraph.py ├── classes/ │ ├── anonymous-class.js │ ├── basic-class.js │ ├── basic-class.truth │ ├── basic-class2.js │ ├── basic-class2.truth │ ├── class-expression1.js │ ├── class-expression1.truth │ ├── class-expression2.js │ ├── class-expression2.truth │ ├── class.js │ ├── class.truth │ ├── export-class-expression1.js │ ├── export-class-expression2.js │ ├── import-anonymous-class.js │ ├── import-anonymous-class.truth │ ├── import-class-expression1.js │ ├── import-class-expression1.truth │ ├── import-class-expression2.js │ ├── import-class-expression2.truth │ ├── outer-fn.js │ └── outer-fn.truth ├── creating-vue-compiled.txt ├── es6/ │ ├── array-pattern.js │ ├── array-pattern.truth │ ├── array-pattern2.js │ ├── array-pattern2.truth │ ├── array-pattern3.js │ ├── array-pattern3.truth │ ├── array-pattern4.js │ ├── array-pattern4.truth │ ├── binding-pattern-global.js │ ├── binding-pattern-global.truth │ ├── destructured-parameter.js │ ├── destructured-parameter.truth │ ├── destructured-parameter2.js │ ├── destructured-parameter2.truth │ ├── destructured-parameter3.js │ ├── destructured-parameter3.truth │ ├── lib.js │ ├── main.js │ ├── main.truth │ ├── object-pattern.js │ ├── object-pattern.truth │ ├── object-pattern2.js │ ├── object-pattern2.truth │ ├── object-pattern3.js │ └── object-pattern3.truth ├── ground_truths/ │ ├── create-component.txt │ ├── create-element.txt │ ├── ground-truth.txt │ ├── patch.txt │ └── vnode.txt ├── import-export/ │ ├── define/ │ │ ├── arrow-func-no-block-statement-module.js │ │ ├── arrow-func-no-block-statement-require.js │ │ ├── arrow-func-no-block-statement-require.truth │ │ ├── define-module.js │ │ ├── define-require.js │ │ └── define-require.truth │ ├── es6/ │ │ ├── es6-cyclic-dependencies1.js │ │ ├── es6-cyclic-dependencies1.truth │ │ ├── es6-cyclic-dependencies2.js │ │ ├── es6-cyclic-dependencies2.truth │ │ ├── es6-export-default.js │ │ ├── es6-export-fns.js │ │ ├── es6-export-hasOwnProperty.js │ │ ├── es6-export-mixed.js │ │ ├── es6-import-default.js │ │ ├── es6-import-default.truth │ │ ├── es6-import-entire.js │ │ ├── es6-import-entire.truth │ │ ├── es6-import-hasOwnProperty.js │ │ ├── es6-import-hasOwnProperty.truth │ │ ├── es6-import-mixed.js │ │ ├── es6-import-mixed.truth │ │ ├── es6-import-named.js │ │ ├── es6-import-named.truth │ │ ├── es6-import-redirect.js │ │ ├── es6-import-redirect.truth │ │ └── redirect/ │ │ ├── auth.js │ │ ├── index.js │ │ ├── project.js │ │ └── search.js │ └── module.exports/ │ ├── module-exports-module.js │ ├── module-exports-module2.js │ ├── module-exports-require.js │ ├── module-exports-require.truth │ ├── module-exports-require2.js │ └── module-exports-require2.truth ├── jest/ │ ├── graph.test.js │ └── linkedList.test.js ├── limits/ │ ├── history.js │ ├── history.truth │ ├── overload.js │ └── overload.truth ├── process.py ├── required_files.py ├── test.py ├── typescript/ │ ├── simple.truth │ └── simple.ts ├── unexpected/ │ ├── stringiterator.js │ └── stringiterator.truth ├── unhandled/ │ ├── classes/ │ │ ├── class-compiled.js │ │ ├── class-compiled.truth │ │ ├── class-compiled2.js │ │ ├── class-compiled2.truth │ │ ├── class-getter.js │ │ └── class-getter.truth │ └── limits/ │ ├── history.js │ ├── history.truth │ ├── overload.js │ └── overload.truth └── vue/ ├── TodoList.truth ├── TodoList.vue └── example.vue ================================================ FILE CONTENTS ================================================ ================================================ FILE: .eslintrc ================================================ { "env": { "es6": true, "node": true, }, "rules": { "no-console": "off", "no-trailing-spaces": "error" }, "parserOptions": { "ecmaVersion": 2017, "sourceType": "module" }, "extends": "eslint:recommended" } ================================================ FILE: .gitignore ================================================ node_modules .idea *.pickle # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class ================================================ FILE: .travis.yml ================================================ language: node_js sudo: false node_js: - "node" before_install: - pyenv global 3.6 install: - npm install script: - npm test ================================================ FILE: CONTRIBUTING.md ================================================ # How to contribute Welcome! We are super happy that you intend to contribute to the javascript-call-graph! This is a great place to start. #### If you find a bug * If you can fix it, submit a PR. * If not, open up an issue and include any information that could help others reproduce and fix. #### If you have a feature proposal or want to contribute * Open up an issue for discussion. * Contribute a PR. * Respond to code review * Watch the PR be merged, and bathe in a job well done :icecream: :+1: :v: :palm_tree:. ## How to create a Pull Request * First you should **fork** this repository (hit the fork button) * **Clone** your forked repository * **Create a branch** for you new feature * **Commit your changes** to your branch * **Keep your fork synced** with the original repository if needed. See [Keeping the fork up to date](#keeping-the-fork-up-to-date) section for further details. * When you finished your work: * **Open a Pull Request** in the original repository and set master as base and your newly created branch as head. Please fill the description field for letting the others know what the contribution is about. * If your changes seem good for the maintainers they will accept your pull request and merge your commits into the original repository ## Keeping the fork up to date It is an optional step. It is recommended when you are working on a larger feature or a complex bug (not a tiny quick fix). Following the steps below ensures to track the original fork which is usually called __upstream__. First add the original repository as a remote: ``` # Add 'upstream' repo to list of remotes git remote add upstream https://github.com/UPSTREAM-USER/ORIGINAL-PROJECT.git # Verify the new remote named 'upstream' git remote -v ``` To update your fork to the latest version you have to fetch the changes from the upstream: ``` # Fetch from upstream remote git fetch upstream # View all branches, including those from upstream git branch -va ``` Now, checkout your own master branch and merge the upstream repo's master branch: ``` # Checkout your master branch and merge upstream git checkout master git merge upstream/master ``` Now your master is up to date with the remote upstream repository. Your feature branch should be also updated. For this purpose you can use a rebase command: ``` git checkout newfeature # Updates origin/master git fetch origin # Rebases current branch onto origin/master git rebase origin/master ``` ================================================ FILE: LICENSE ================================================ Eclipse Public License - v 2.0 THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 1. DEFINITIONS "Contribution" means: a) in the case of the initial Contributor, the initial content Distributed under this Agreement, and b) in the case of each subsequent Contributor: i) changes to the Program, and ii) additions to the Program; where such changes and/or additions to the Program originate from and are Distributed by that particular Contributor. A Contribution "originates" from a Contributor if it was added to the Program by such Contributor itself or anyone acting on such Contributor's behalf. Contributions do not include changes or additions to the Program that are not Modified Works. "Contributor" means any person or entity that Distributes the Program. "Licensed Patents" mean patent claims licensable by a Contributor which are necessarily infringed by the use or sale of its Contribution alone or when combined with the Program. "Program" means the Contributions Distributed in accordance with this Agreement. "Recipient" means anyone who receives the Program under this Agreement or any Secondary License (as applicable), including Contributors. "Derivative Works" shall mean any work, whether in Source Code or other form, that is based on (or derived from) the Program and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. "Modified Works" shall mean any work in Source Code or other form that results from an addition to, deletion from, or modification of the contents of the Program, including, for purposes of clarity any new file in Source Code form that contains any contents of the Program. Modified Works shall not include works that contain only declarations, interfaces, types, classes, structures, or files of the Program solely in each case in order to link to, bind by name, or subclass the Program or Modified Works thereof. "Distribute" means the acts of a) distributing or b) making available in any manner that enables the transfer of a copy. "Source Code" means the form of a Program preferred for making modifications, including but not limited to software source code, documentation source, and configuration files. "Secondary License" means either the GNU General Public License, Version 2.0, or any later versions of that license, including any exceptions or additional permissions as identified by the initial Contributor. 2. GRANT OF RIGHTS a) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, Distribute and sublicense the Contribution of such Contributor, if any, and such Derivative Works. b) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed Patents to make, use, sell, offer to sell, import and otherwise transfer the Contribution of such Contributor, if any, in Source Code or other form. This patent license shall apply to the combination of the Contribution and the Program if, at the time the Contribution is added by the Contributor, such addition of the Contribution causes such combination to be covered by the Licensed Patents. The patent license shall not apply to any other combinations which include the Contribution. No hardware per se is licensed hereunder. c) Recipient understands that although each Contributor grants the licenses to its Contributions set forth herein, no assurances are provided by any Contributor that the Program does not infringe the patent or other intellectual property rights of any other entity. Each Contributor disclaims any liability to Recipient for claims brought by any other entity based on infringement of intellectual property rights or otherwise. As a condition to exercising the rights and licenses granted hereunder, each Recipient hereby assumes sole responsibility to secure any other intellectual property rights needed, if any. For example, if a third party patent license is required to allow Recipient to Distribute the Program, it is Recipient's responsibility to acquire that license before distributing the Program. d) Each Contributor represents that to its knowledge it has sufficient copyright rights in its Contribution, if any, to grant the copyright license set forth in this Agreement. e) Notwithstanding the terms of any Secondary License, no Contributor makes additional grants to any Recipient (other than those set forth in this Agreement) as a result of such Recipient's receipt of the Program under the terms of a Secondary License (if permitted under the terms of Section 3). 3. REQUIREMENTS 3.1 If a Contributor Distributes the Program in any form, then: a) the Program must also be made available as Source Code, in accordance with section 3.2, and the Contributor must accompany the Program with a statement that the Source Code for the Program is available under this Agreement, and informs Recipients how to obtain it in a reasonable manner on or through a medium customarily used for software exchange; and b) the Contributor may Distribute the Program under a license different than this Agreement, provided that such license: i) effectively disclaims on behalf of all other Contributors all warranties and conditions, express and implied, including warranties or conditions of title and non-infringement, and implied warranties or conditions of merchantability and fitness for a particular purpose; ii) effectively excludes on behalf of all other Contributors all liability for damages, including direct, indirect, special, incidental and consequential damages, such as lost profits; iii) does not attempt to limit or alter the recipients' rights in the Source Code under section 3.2; and iv) requires any subsequent distribution of the Program by any party to be under a license that satisfies the requirements of this section 3. 3.2 When the Program is Distributed as Source Code: a) it must be made available under this Agreement, or if the Program (i) is combined with other material in a separate file or files made available under a Secondary License, and (ii) the initial Contributor attached to the Source Code the notice described in Exhibit A of this Agreement, then the Program may be made available under the terms of such Secondary Licenses, and b) a copy of this Agreement must be included with each copy of the Program. 3.3 Contributors may not remove or alter any copyright, patent, trademark, attribution notices, disclaimers of warranty, or limitations of liability ("notices") contained within the Program from any copy of the Program which they Distribute, provided that Contributors may add their own appropriate notices. 4. COMMERCIAL DISTRIBUTION Commercial distributors of software may accept certain responsibilities with respect to end users, business partners and the like. While this license is intended to facilitate the commercial use of the Program, the Contributor who includes the Program in a commercial product offering should do so in a manner which does not create potential liability for other Contributors. Therefore, if a Contributor includes the Program in a commercial product offering, such Contributor ("Commercial Contributor") hereby agrees to defend and indemnify every other Contributor ("Indemnified Contributor") against any losses, damages and costs (collectively "Losses") arising from claims, lawsuits and other legal actions brought by a third party against the Indemnified Contributor to the extent caused by the acts or omissions of such Commercial Contributor in connection with its distribution of the Program in a commercial product offering. The obligations in this section do not apply to any claims or Losses relating to any actual or alleged intellectual property infringement. In order to qualify, an Indemnified Contributor must: a) promptly notify the Commercial Contributor in writing of such claim, and b) allow the Commercial Contributor to control, and cooperate with the Commercial Contributor in, the defense and any related settlement negotiations. The Indemnified Contributor may participate in any such claim at its own expense. For example, a Contributor might include the Program in a commercial product offering, Product X. That Contributor is then a Commercial Contributor. If that Commercial Contributor then makes performance claims, or offers warranties related to Product X, those performance claims and warranties are such Commercial Contributor's responsibility alone. Under this section, the Commercial Contributor would have to defend claims against the other Contributors related to those performance claims and warranties, and if a court requires any other Contributor to pay any damages as a result, the Commercial Contributor must pay those damages. 5. NO WARRANTY EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the appropriateness of using and distributing the Program and assumes all risks associated with its exercise of rights under this Agreement, including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, programs or equipment, and unavailability or interruption of operations. 6. DISCLAIMER OF LIABILITY EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 7. GENERAL If any provision of this Agreement is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this Agreement, and without further action by the parties hereto, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. If Recipient institutes patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Program itself (excluding combinations of the Program with other software or hardware) infringes such Recipient's patent(s), then such Recipient's rights granted under Section 2(b) shall terminate as of the date such litigation is filed. All Recipient's rights under this Agreement shall terminate if it fails to comply with any of the material terms or conditions of this Agreement and does not cure such failure in a reasonable period of time after becoming aware of such noncompliance. If all Recipient's rights under this Agreement terminate, Recipient agrees to cease use and distribution of the Program as soon as reasonably practicable. However, Recipient's obligations under this Agreement and any licenses granted by Recipient relating to the Program shall continue and survive. Everyone is permitted to copy and distribute copies of this Agreement, but in order to avoid inconsistency the Agreement is copyrighted and may only be modified in the following manner. The Agreement Steward reserves the right to publish new versions (including revisions) of this Agreement from time to time. No one other than the Agreement Steward has the right to modify this Agreement. The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation may assign the responsibility to serve as the Agreement Steward to a suitable separate entity. Each new version of the Agreement will be given a distinguishing version number. The Program (including Contributions) may always be Distributed subject to the version of the Agreement under which it was received. In addition, after a new version of the Agreement is published, Contributor may elect to Distribute the Program (including its Contributions) under the new version. Except as expressly stated in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to the intellectual property of any Contributor under this Agreement, whether expressly, by implication, estoppel or otherwise. All rights in the Program not expressly granted under this Agreement are reserved. Nothing in this Agreement is intended to be enforceable by any entity that is not a Contributor or Recipient. No third-party beneficiary rights are created under this Agreement. Exhibit A - Form of Secondary Licenses Notice "This Source Code may also be made available under the following Secondary Licenses when the conditions for such availability set forth in the Eclipse Public License, v. 2.0 are satisfied: {name license(s), version(s), and exceptions or additional permissions here}." Simply including a copy of this Agreement, including this Exhibit A is not sufficient to license the Source Code under Secondary Licenses. If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. You may add additional accurate notices of copyright ownership. ================================================ FILE: README.md ================================================ # Field-based Call Graph Construction for JavaScript # [![Build Status](https://travis-ci.org/Persper/js-callgraph.svg?branch=master)](https://travis-ci.org/Persper/js-callgraph) [![NPM version](https://img.shields.io/badge/npm-v1.3.2-blue.svg)](https://www.npmjs.com/package/@persper/js-callgraph) [![License](https://img.shields.io/badge/license-EPL--2.0-green.svg)](https://www.eclipse.org/legal/epl-2.0/) This project implements a field-based call graph construction algorithm for JavaScript as described in > A. Feldthaus, M. Schäfer, M. Sridharan, J. Dolby, F. Tip. Efficient Construction of Approximate Call Graphs for JavaScript IDE Services. In *ICSE*, 2013. This repo builds upon [Max Schaefer](https://github.com/xiemaisi)'s original [acg.js](https://github.com/xiemaisi/acg.js) and adds * ES6 Support * Arrow functions * Destructuring assignments * Classes * Enhanced object literals * Rest/Spread operator * Module Support * ES6/CommonJS/AMD * More sophisticated scoping * Partial update to a large call graph * Unified JSON format representing a call graph * More flexible CLI * Take directory parameter * Support filtering files by regex * Vue.js support (.vue files) * More tests ## Get Started (CLI) ``` npm install -g @persper/js-callgraph js-callgraph -h # for a list of command line arguments # Running on simple input scripts js-callgraph --cg input-scripts/simple-scripts/functioncall-arithmetic.js # Running on a whole directory js-callgraph --cg input-scripts/fullcalendar/ # Running on mixed input js-callgraph --cg input-scripts/fullcalendar/fullcalendar/ input-scripts/fullcalendar/lib/jquery-2.1.0.js # Saving the result into a file js-callgraph --cg input-scripts/simple-scripts/functioncall-arithmetic.js --output filename.json # Running on a whole directory with filtering js-callgraph --cg input-scripts/fullcalendar/ --filter filename.filter ``` For an example of the output json, please see [here](#unified-json-format). For an example of filtering file, please see [here](#filter-file-format). ## Programming Interface ``` const JCG = require("./src/runner"); args = { ... }; JCG.setArgs(args); # Optional, specify a list of arguments JCG.setFiles(['file.js', 'directory/']); # List of files or directories to analyze JCG.setFilter(['-test[^\.]*.js', '+test576.js']); # Optional, please see "Filter file format" section for details JCG.setConsoleOutput(true); # Optional, the console output can be turned off. JCG.build(); # build returns the call graph as a JSON object, please see "Unified JSON Format" section ``` ## Running Tests To run the testing framework run: ``` npm test ``` To install the git hooks to run tests automatically pre-commit run: ``` scripts/install-hooks ``` ## Structure The call graph constructor can be run in two basic modes (selected using the `--strategy` flag to `javascript-call-graph`), *pessimistic* and *optimistic*, which differ in how interprocedural flows are handled. In the basic pessimistic approach (strategy `NONE`), interprocedural flow is not tracked at all; a slight refinement is strategy `ONESHOT`, where interprocedural flow is tracked only for one-shot closures that are invoked immediatel. The optimistic approach (strategy `DEMAND`) performs interprocedural propagation along edges that may ultimately end at a call site (and are thus interesting for call graph construction). Full interprocedural propagation (strategy `FULL`) is not implemented yet. All strategies use the same intraprocedural flow graph, in which properties are only identified by name; thus, like-named properties of different objects are conflated; this can lead to imprecise call graphs. Dynamic property reads and writes are ignored, as are reflective calls using `call` and `apply`; thus, the call graphs are intrinsically incomplete. Module `flowgraph.js` contains the code for extracting an intraprocedural flow graph from an [Esprima](esprima.org) AST annotated with name bindings for local variables (see `bindings.js`, which uses `symtab.js` and `astutil.js`). Modules `pessimistic.js` and `semioptimistic.js` implement the pessimistic and optimistic call graph builders, respectively. They both use `flowgraph.js` to build an intraprocedural flow graph, and then add some edges corresponding to interprocedural flow. Both use module `callgraph.js` for extracting a call graph from a given flow graph, by collecting, for every call site, all functions that can flow into the callee position. Both use module `natives.js` to add flow edges modelling well-known standard library functions. The remaining modules define key data structures, in several variants. Module `graph.js` implements graphs using adjacency sets, using sets of numbers as implemented by `numset.js`. The latter includes either `olist.js` to implement sets as ordered lists of numbers, or `bitset.js` to use bitsets (with disappointing performance, so we use ordered lists by default). Modules `dftc.js`, `heuristictc.js` and `nuutila.js` implement several transitive closure algorithms used by `callgraph.js`. By default, we use `dftc.js` which uses a simple, depth first-search based algorithm. `heuristictc.js` does something even simpler, which is very fast but unsound. Finally, `nuutila.js` implements Nuutila's algorithm for transitive closure, which for our graphs is usually slower than the depth first-based ones. ## Unified JSON Format ``` [ # The calls are represented with a list of objects. Each call is an object in this list. { "source": { # The source object represents the start point of a call (the caller node) "label": "global", "file": "...\\input-scripts\\simple-scripts\\functioncall-arithmetic.js", "start": { # The start point of the source with row-column based position. "row": 7, "column": 4 }, "end": { # The end point of the source node with row-column based position. "row": 7, "column": 8 }, "range": { # The position of the source node in index-based representation. "start": 59, "end": 63 } }, "target": { # The target object represents the end point of a call (this node is called by the source) "label": "f", "file": "...\\input-scripts\\simple-scripts\\functioncall-arithmetic.js", "start": { # The start point of the target node with row-column based position.. "row": 3, "column": 0 }, "end": { # The end point of the target node with row-column based position. "row": 5, "column": 1 }, "range": { # The position of the target node in index-based representation. "start": 14, "end": 51 } } } ] ``` ## Filter file format Any valid regular expression can be specified in the filter file. The order of the including and excluding lines are important since they are processed sequentially. The first character of each line represents the type of the filtering: ``` # Comment line - Exclude + Include ``` An example for filtering: ``` # Filter out all source files starting with "test": -test[^\.]*.js # But test576.js is needed: +test576.js # Furthermore, files beginning with "test1742" are also needed: +test1742[^\.]*.js # Finally, test1742_b.js is not needed: -test1742_b.js ``` ## List of arguments ``` -h : List of command line arguments --fg : print flow graph --cg : print call graph --time : print timings --strategy : interprocedural propagation strategy; one of NONE, ONESHOT (default), DEMAND, and FULL (not yet implemented) --countCB : Counts the number of callbacks. --reqJs : Make a RequireJS dependency graph. --output : The output file name into which the result JSON should be saved. (extension: .json) --filter : Path to the filter file. ``` # Contributing Looking to contribute something? [Here's how you can help](/CONTRIBUTING.md). # License # This code is licensed under the [Eclipse Public License (v2.0)](http://www.eclipse.org/legal/epl-2.0), a copy of which is included in this repository in file `LICENSE`. ================================================ FILE: index.js ================================================ /******************************************************************************* * Copyright (c) 2018 Persper Foundation * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-2.0. *******************************************************************************/ const JCG = require("./src/runner"); exports.setArgs = JCG.setArgs; exports.setFiles = JCG.setFiles; exports.setFilter = JCG.setFilter; exports.setConsoleOutput = JCG.setConsoleOutput; exports.build = JCG.build; ================================================ FILE: input-scripts/coolclock/coolclock.js ================================================ /** * CoolClock 2.1.4 * Copyright 2010, Simon Baird * Released under the BSD License. * * Display an analog clock using canvas. * http://randomibis.com/coolclock/ * */ // Constructor for CoolClock objects window.CoolClock = function(options) { return this.init(options); } // Config contains some defaults, and clock skins CoolClock.config = { tickDelay: 1000, longTickDelay: 15000, defaultRadius: 85, renderRadius: 100, defaultSkin: "chunkySwiss", // Should be in skin probably... // (TODO: allow skinning of digital display) showSecs: true, showAmPm: true, skins: { // There are more skins in moreskins.js // Try making your own skin by copy/pasting one of these and tweaking it swissRail: { outerBorder: { lineWidth: 2, radius:95, color: "black", alpha: 1 }, smallIndicator: { lineWidth: 2, startAt: 88, endAt: 92, color: "black", alpha: 1 }, largeIndicator: { lineWidth: 4, startAt: 79, endAt: 92, color: "black", alpha: 1 }, hourHand: { lineWidth: 8, startAt: -15, endAt: 50, color: "black", alpha: 1 }, minuteHand: { lineWidth: 7, startAt: -15, endAt: 75, color: "black", alpha: 1 }, secondHand: { lineWidth: 1, startAt: -20, endAt: 85, color: "red", alpha: 1 }, secondDecoration: { lineWidth: 1, startAt: 70, radius: 4, fillColor: "red", color: "red", alpha: 1 } }, chunkySwiss: { outerBorder: { lineWidth: 4, radius:97, color: "black", alpha: 1 }, smallIndicator: { lineWidth: 4, startAt: 89, endAt: 93, color: "black", alpha: 1 }, largeIndicator: { lineWidth: 8, startAt: 80, endAt: 93, color: "black", alpha: 1 }, hourHand: { lineWidth: 12, startAt: -15, endAt: 60, color: "black", alpha: 1 }, minuteHand: { lineWidth: 10, startAt: -15, endAt: 85, color: "black", alpha: 1 }, secondHand: { lineWidth: 4, startAt: -20, endAt: 85, color: "red", alpha: 1 }, secondDecoration: { lineWidth: 2, startAt: 70, radius: 8, fillColor: "red", color: "red", alpha: 1 } }, chunkySwissOnBlack: { outerBorder: { lineWidth: 4, radius:97, color: "white", alpha: 1 }, smallIndicator: { lineWidth: 4, startAt: 89, endAt: 93, color: "white", alpha: 1 }, largeIndicator: { lineWidth: 8, startAt: 80, endAt: 93, color: "white", alpha: 1 }, hourHand: { lineWidth: 12, startAt: -15, endAt: 60, color: "white", alpha: 1 }, minuteHand: { lineWidth: 10, startAt: -15, endAt: 85, color: "white", alpha: 1 }, secondHand: { lineWidth: 4, startAt: -20, endAt: 85, color: "red", alpha: 1 }, secondDecoration: { lineWidth: 2, startAt: 70, radius: 8, fillColor: "red", color: "red", alpha: 1 } } }, // Test for IE so we can nurse excanvas in a couple of places isIE: !!document.all, // Will store (a reference to) each clock here, indexed by the id of the canvas element clockTracker: {}, // For giving a unique id to coolclock canvases with no id noIdCount: 0 }; // Define the CoolClock object's methods CoolClock.prototype = { // Initialise using the parameters parsed from the colon delimited class init: function(options) { // Parse and store the options this.canvasId = options.canvasId; this.skinId = options.skinId || CoolClock.config.defaultSkin; this.displayRadius = options.displayRadius || CoolClock.config.defaultRadius; this.showSecondHand = typeof options.showSecondHand == "boolean" ? options.showSecondHand : true; this.gmtOffset = (options.gmtOffset != null && options.gmtOffset != '') ? parseFloat(options.gmtOffset) : null; this.showDigital = typeof options.showDigital == "boolean" ? options.showDigital : false; this.logClock = typeof options.logClock == "boolean" ? options.logClock : false; this.logClockRev = typeof options.logClock == "boolean" ? options.logClockRev : false; this.tickDelay = CoolClock.config[ this.showSecondHand ? "tickDelay" : "longTickDelay" ]; // Get the canvas element this.canvas = document.getElementById(this.canvasId); // Make the canvas the requested size. It's always square. this.canvas.setAttribute("width",this.displayRadius*2); this.canvas.setAttribute("height",this.displayRadius*2); this.canvas.style.width = this.displayRadius*2 + "px"; this.canvas.style.height = this.displayRadius*2 + "px"; // Explain me please...? this.renderRadius = CoolClock.config.renderRadius; this.scale = this.displayRadius / this.renderRadius; // Initialise canvas context this.ctx = this.canvas.getContext("2d"); this.ctx.scale(this.scale,this.scale); // Keep track of this object CoolClock.config.clockTracker[this.canvasId] = this; // Start the clock going this.tick(); return this; }, // Draw a circle at point x,y with params as defined in skin fullCircleAt: function(x,y,skin) { this.ctx.save(); this.ctx.globalAlpha = skin.alpha; this.ctx.lineWidth = skin.lineWidth; if (!CoolClock.config.isIE) { this.ctx.beginPath(); } if (CoolClock.config.isIE) { // excanvas doesn't scale line width so we will do it here this.ctx.lineWidth = this.ctx.lineWidth * this.scale; } this.ctx.arc(x, y, skin.radius, 0, 2*Math.PI, false); if (CoolClock.config.isIE) { // excanvas doesn't close the circle so let's fill in the tiny gap this.ctx.arc(x, y, skin.radius, -0.1, 0.1, false); } if (skin.fillColor) { this.ctx.fillStyle = skin.fillColor this.ctx.fill(); } else { // XXX why not stroke and fill this.ctx.strokeStyle = skin.color; this.ctx.stroke(); } this.ctx.restore(); }, // Draw some text centered vertically and horizontally drawTextAt: function(theText,x,y) { this.ctx.save(); this.ctx.font = '15px sans-serif'; var tSize = this.ctx.measureText(theText); if (!tSize.height) tSize.height = 15; // no height in firefox.. :( this.ctx.fillText(theText,x - tSize.width/2,y - tSize.height/2); this.ctx.restore(); }, lpad2: function(num) { return (num < 10 ? '0' : '') + num; }, tickAngle: function(second) { // Log algorithm by David Bradshaw var tweak = 3; // If it's lower the one second mark looks wrong (?) if (this.logClock) { return second == 0 ? 0 : (Math.log(second*tweak) / Math.log(60*tweak)); } else if (this.logClockRev) { // Flip the seconds then flip the angle (trickiness) second = (60 - second) % 60; return 1.0 - (second == 0 ? 0 : (Math.log(second*tweak) / Math.log(60*tweak))); } else { return second/60.0; } }, timeText: function(hour,min,sec) { var c = CoolClock.config; return '' + (c.showAmPm ? ((hour%12)==0 ? 12 : (hour%12)) : hour) + ':' + this.lpad2(min) + (c.showSecs ? ':' + this.lpad2(sec) : '') + (c.showAmPm ? (hour < 12 ? ' am' : ' pm') : '') ; }, // Draw a radial line by rotating then drawing a straight line // Ha ha, I think I've accidentally used Taus, (see http://tauday.com/) radialLineAtAngle: function(angleFraction,skin) { this.ctx.save(); this.ctx.translate(this.renderRadius,this.renderRadius); this.ctx.rotate(Math.PI * (2.0 * angleFraction - 0.5)); this.ctx.globalAlpha = skin.alpha; this.ctx.strokeStyle = skin.color; this.ctx.lineWidth = skin.lineWidth; if (CoolClock.config.isIE) // excanvas doesn't scale line width so we will do it here this.ctx.lineWidth = this.ctx.lineWidth * this.scale; if (skin.radius) { this.fullCircleAt(skin.startAt,0,skin) } else { this.ctx.beginPath(); this.ctx.moveTo(skin.startAt,0) this.ctx.lineTo(skin.endAt,0); this.ctx.stroke(); } this.ctx.restore(); }, render: function(hour,min,sec) { // Get the skin var skin = CoolClock.config.skins[this.skinId]; if (!skin) skin = CoolClock.config.skins[CoolClock.config.defaultSkin]; // Clear this.ctx.clearRect(0,0,this.renderRadius*2,this.renderRadius*2); // Draw the outer edge of the clock if (skin.outerBorder) this.fullCircleAt(this.renderRadius,this.renderRadius,skin.outerBorder); // Draw the tick marks. Every 5th one is a big one for (var i=0;i<60;i++) { (i%5) && skin.smallIndicator && this.radialLineAtAngle(this.tickAngle(i),skin.smallIndicator); !(i%5) && skin.largeIndicator && this.radialLineAtAngle(this.tickAngle(i),skin.largeIndicator); } // Write the time if (this.showDigital) { this.drawTextAt( this.timeText(hour,min,sec), this.renderRadius, this.renderRadius+this.renderRadius/2 ); } // Draw the hands if (skin.hourHand) this.radialLineAtAngle(this.tickAngle(((hour%12)*5 + min/12.0)),skin.hourHand); if (skin.minuteHand) this.radialLineAtAngle(this.tickAngle((min + sec/60.0)),skin.minuteHand); if (this.showSecondHand && skin.secondHand) this.radialLineAtAngle(this.tickAngle(sec),skin.secondHand); // Second hand decoration doesn't render right in IE so lets turn it off if (!CoolClock.config.isIE && this.showSecondHand && skin.secondDecoration) this.radialLineAtAngle(this.tickAngle(sec),skin.secondDecoration); }, // Check the time and display the clock refreshDisplay: function() { var now = new Date(); if (this.gmtOffset != null) { // Use GMT + gmtOffset var offsetNow = new Date(now.valueOf() + (this.gmtOffset * 1000 * 60 * 60)); this.render(offsetNow.getUTCHours(),offsetNow.getUTCMinutes(),offsetNow.getUTCSeconds()); } else { // Use local time this.render(now.getHours(),now.getMinutes(),now.getSeconds()); } }, // Set timeout to trigger a tick in the future nextTick: function() { setTimeout("CoolClock.config.clockTracker['"+this.canvasId+"'].tick()",this.tickDelay); }, // Check the canvas element hasn't been removed stillHere: function() { return document.getElementById(this.canvasId) != null; }, // Main tick handler. Refresh the clock then setup the next tick tick: function() { if (this.stillHere()) { this.refreshDisplay() this.nextTick(); } } }; // Find all canvas elements that have the CoolClock class and turns them into clocks CoolClock.findAndCreateClocks = function() { // (Let's not use a jQuery selector here so it's easier to use frameworks other than jQuery) var canvases = document.getElementsByTagName("canvas"); for (var i=0;i // If you do have jQuery and it's loaded already then we can do it right now if (window.jQuery) jQuery(document).ready(CoolClock.findAndCreateClocks); ================================================ FILE: input-scripts/coolclock/excanvas.js ================================================ // Copyright 2006 Google Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Known Issues: // // * Patterns are not implemented. // * Radial gradient are not implemented. The VML version of these look very // different from the canvas one. // * Clipping paths are not implemented. // * Coordsize. The width and height attribute have higher priority than the // width and height style values which isn't correct. // * Painting mode isn't implemented. // * Canvas width/height should is using content-box by default. IE in // Quirks mode will draw the canvas using border-box. Either change your // doctype to HTML5 // (http://www.whatwg.org/specs/web-apps/current-work/#the-doctype) // or use Box Sizing Behavior from WebFX // (http://webfx.eae.net/dhtml/boxsizing/boxsizing.html) // * Optimize. There is always room for speed improvements. // only add this code if we do not already have a canvas implementation if (!window.CanvasRenderingContext2D) { (function () { // alias some functions to make (compiled) code shorter var m = Math; var mr = m.round; var ms = m.sin; var mc = m.cos; // this is used for sub pixel precision var Z = 10; var Z2 = Z / 2; var G_vmlCanvasManager_ = { init: function (opt_doc) { var doc = opt_doc || document; if (/MSIE/.test(navigator.userAgent) && !window.opera) { var self = this; doc.attachEvent("onreadystatechange", function () { self.init_(doc); }); } }, init_: function (doc) { if (doc.readyState == "complete") { // create xmlns if (!doc.namespaces["g_vml_"]) { doc.namespaces.add("g_vml_", "urn:schemas-microsoft-com:vml"); } // setup default css var ss = doc.createStyleSheet(); ss.cssText = "canvas{display:inline-block;overflow:hidden;" + // default size is 300x150 in Gecko and Opera "text-align:left;width:300px;height:150px}" + "g_vml_\\:*{behavior:url(#default#VML)}"; // find all canvas elements var els = doc.getElementsByTagName("canvas"); for (var i = 0; i < els.length; i++) { if (!els[i].getContext) { this.initElement(els[i]); } } } }, fixElement_: function (el) { // in IE before version 5.5 we would need to add HTML: to the tag name // but we do not care about IE before version 6 var outerHTML = el.outerHTML; var newEl = el.ownerDocument.createElement(outerHTML); // if the tag is still open IE has created the children as siblings and // it has also created a tag with the name "/FOO" if (outerHTML.slice(-2) != "/>") { var tagName = "/" + el.tagName; var ns; // remove content while ((ns = el.nextSibling) && ns.tagName != tagName) { ns.removeNode(); } // remove the incorrect closing tag if (ns) { ns.removeNode(); } } el.parentNode.replaceChild(newEl, el); return newEl; }, /** * Public initializes a canvas element so that it can be used as canvas * element from now on. This is called automatically before the page is * loaded but if you are creating elements using createElement you need to * make sure this is called on the element. * @param {HTMLElement} el The canvas element to initialize. * @return {HTMLElement} the element that was created. */ initElement: function (el) { el = this.fixElement_(el); el.getContext = function () { if (this.context_) { return this.context_; } return this.context_ = new CanvasRenderingContext2D_(this); }; // do not use inline function because that will leak memory el.attachEvent('onpropertychange', onPropertyChange); el.attachEvent('onresize', onResize); var attrs = el.attributes; if (attrs.width && attrs.width.specified) { // TODO: use runtimeStyle and coordsize // el.getContext().setWidth_(attrs.width.nodeValue); el.style.width = attrs.width.nodeValue + "px"; } else { el.width = el.clientWidth; } if (attrs.height && attrs.height.specified) { // TODO: use runtimeStyle and coordsize // el.getContext().setHeight_(attrs.height.nodeValue); el.style.height = attrs.height.nodeValue + "px"; } else { el.height = el.clientHeight; } //el.getContext().setCoordsize_() return el; } }; function onPropertyChange(e) { var el = e.srcElement; switch (e.propertyName) { case 'width': el.style.width = el.attributes.width.nodeValue + "px"; el.getContext().clearRect(); break; case 'height': el.style.height = el.attributes.height.nodeValue + "px"; el.getContext().clearRect(); break; } } function onResize(e) { var el = e.srcElement; if (el.firstChild) { el.firstChild.style.width = el.clientWidth + 'px'; el.firstChild.style.height = el.clientHeight + 'px'; } } G_vmlCanvasManager_.init(); // precompute "00" to "FF" var dec2hex = []; for (var i = 0; i < 16; i++) { for (var j = 0; j < 16; j++) { dec2hex[i * 16 + j] = i.toString(16) + j.toString(16); } } function createMatrixIdentity() { return [ [1, 0, 0], [0, 1, 0], [0, 0, 1] ]; } function matrixMultiply(m1, m2) { var result = createMatrixIdentity(); for (var x = 0; x < 3; x++) { for (var y = 0; y < 3; y++) { var sum = 0; for (var z = 0; z < 3; z++) { sum += m1[x][z] * m2[z][y]; } result[x][y] = sum; } } return result; } function copyState(o1, o2) { o2.fillStyle = o1.fillStyle; o2.lineCap = o1.lineCap; o2.lineJoin = o1.lineJoin; o2.lineWidth = o1.lineWidth; o2.miterLimit = o1.miterLimit; o2.shadowBlur = o1.shadowBlur; o2.shadowColor = o1.shadowColor; o2.shadowOffsetX = o1.shadowOffsetX; o2.shadowOffsetY = o1.shadowOffsetY; o2.strokeStyle = o1.strokeStyle; o2.arcScaleX_ = o1.arcScaleX_; o2.arcScaleY_ = o1.arcScaleY_; } function processStyle(styleString) { var str, alpha = 1; styleString = String(styleString); if (styleString.substring(0, 3) == "rgb") { var start = styleString.indexOf("(", 3); var end = styleString.indexOf(")", start + 1); var guts = styleString.substring(start + 1, end).split(","); str = "#"; for (var i = 0; i < 3; i++) { str += dec2hex[Number(guts[i])]; } if ((guts.length == 4) && (styleString.substr(3, 1) == "a")) { alpha = guts[3]; } } else { str = styleString; } return [str, alpha]; } function processLineCap(lineCap) { switch (lineCap) { case "butt": return "flat"; case "round": return "round"; case "square": default: return "square"; } } /** * This class implements CanvasRenderingContext2D interface as described by * the WHATWG. * @param {HTMLElement} surfaceElement The element that the 2D context should * be associated with */ function CanvasRenderingContext2D_(surfaceElement) { this.m_ = createMatrixIdentity(); this.mStack_ = []; this.aStack_ = []; this.currentPath_ = []; // Canvas context properties this.strokeStyle = "#000"; this.fillStyle = "#000"; this.lineWidth = 1; this.lineJoin = "miter"; this.lineCap = "butt"; this.miterLimit = Z * 1; this.globalAlpha = 1; this.canvas = surfaceElement; var el = surfaceElement.ownerDocument.createElement('div'); el.style.width = surfaceElement.clientWidth + 'px'; el.style.height = surfaceElement.clientHeight + 'px'; el.style.overflow = 'hidden'; el.style.position = 'absolute'; surfaceElement.appendChild(el); this.element_ = el; this.arcScaleX_ = 1; this.arcScaleY_ = 1; }; var contextPrototype = CanvasRenderingContext2D_.prototype; contextPrototype.clearRect = function() { this.element_.innerHTML = ""; this.currentPath_ = []; }; contextPrototype.beginPath = function() { // TODO: Branch current matrix so that save/restore has no effect // as per safari docs. this.currentPath_ = []; }; contextPrototype.moveTo = function(aX, aY) { this.currentPath_.push({type: "moveTo", x: aX, y: aY}); this.currentX_ = aX; this.currentY_ = aY; }; contextPrototype.lineTo = function(aX, aY) { this.currentPath_.push({type: "lineTo", x: aX, y: aY}); this.currentX_ = aX; this.currentY_ = aY; }; contextPrototype.bezierCurveTo = function(aCP1x, aCP1y, aCP2x, aCP2y, aX, aY) { this.currentPath_.push({type: "bezierCurveTo", cp1x: aCP1x, cp1y: aCP1y, cp2x: aCP2x, cp2y: aCP2y, x: aX, y: aY}); this.currentX_ = aX; this.currentY_ = aY; }; contextPrototype.quadraticCurveTo = function(aCPx, aCPy, aX, aY) { // the following is lifted almost directly from // http://developer.mozilla.org/en/docs/Canvas_tutorial:Drawing_shapes var cp1x = this.currentX_ + 2.0 / 3.0 * (aCPx - this.currentX_); var cp1y = this.currentY_ + 2.0 / 3.0 * (aCPy - this.currentY_); var cp2x = cp1x + (aX - this.currentX_) / 3.0; var cp2y = cp1y + (aY - this.currentY_) / 3.0; this.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, aX, aY); }; contextPrototype.arc = function(aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise) { aRadius *= Z; var arcType = aClockwise ? "at" : "wa"; var xStart = aX + (mc(aStartAngle) * aRadius) - Z2; var yStart = aY + (ms(aStartAngle) * aRadius) - Z2; var xEnd = aX + (mc(aEndAngle) * aRadius) - Z2; var yEnd = aY + (ms(aEndAngle) * aRadius) - Z2; // IE won't render arches drawn counter clockwise if xStart == xEnd. if (xStart == xEnd && !aClockwise) { xStart += 0.125; // Offset xStart by 1/80 of a pixel. Use something // that can be represented in binary } this.currentPath_.push({type: arcType, x: aX, y: aY, radius: aRadius, xStart: xStart, yStart: yStart, xEnd: xEnd, yEnd: yEnd}); }; contextPrototype.rect = function(aX, aY, aWidth, aHeight) { this.moveTo(aX, aY); this.lineTo(aX + aWidth, aY); this.lineTo(aX + aWidth, aY + aHeight); this.lineTo(aX, aY + aHeight); this.closePath(); }; contextPrototype.strokeRect = function(aX, aY, aWidth, aHeight) { // Will destroy any existing path (same as FF behaviour) this.beginPath(); this.moveTo(aX, aY); this.lineTo(aX + aWidth, aY); this.lineTo(aX + aWidth, aY + aHeight); this.lineTo(aX, aY + aHeight); this.closePath(); this.stroke(); }; contextPrototype.fillRect = function(aX, aY, aWidth, aHeight) { // Will destroy any existing path (same as FF behaviour) this.beginPath(); this.moveTo(aX, aY); this.lineTo(aX + aWidth, aY); this.lineTo(aX + aWidth, aY + aHeight); this.lineTo(aX, aY + aHeight); this.closePath(); this.fill(); }; contextPrototype.createLinearGradient = function(aX0, aY0, aX1, aY1) { var gradient = new CanvasGradient_("gradient"); return gradient; }; contextPrototype.createRadialGradient = function(aX0, aY0, aR0, aX1, aY1, aR1) { var gradient = new CanvasGradient_("gradientradial"); gradient.radius1_ = aR0; gradient.radius2_ = aR1; gradient.focus_.x = aX0; gradient.focus_.y = aY0; return gradient; }; contextPrototype.drawImage = function (image, var_args) { var dx, dy, dw, dh, sx, sy, sw, sh; // to find the original width we overide the width and height var oldRuntimeWidth = image.runtimeStyle.width; var oldRuntimeHeight = image.runtimeStyle.height; image.runtimeStyle.width = 'auto'; image.runtimeStyle.height = 'auto'; // get the original size var w = image.width; var h = image.height; // and remove overides image.runtimeStyle.width = oldRuntimeWidth; image.runtimeStyle.height = oldRuntimeHeight; if (arguments.length == 3) { dx = arguments[1]; dy = arguments[2]; sx = sy = 0; sw = dw = w; sh = dh = h; } else if (arguments.length == 5) { dx = arguments[1]; dy = arguments[2]; dw = arguments[3]; dh = arguments[4]; sx = sy = 0; sw = w; sh = h; } else if (arguments.length == 9) { sx = arguments[1]; sy = arguments[2]; sw = arguments[3]; sh = arguments[4]; dx = arguments[5]; dy = arguments[6]; dw = arguments[7]; dh = arguments[8]; } else { throw "Invalid number of arguments"; } var d = this.getCoords_(dx, dy); var w2 = sw / 2; var h2 = sh / 2; var vmlStr = []; var W = 10; var H = 10; // For some reason that I've now forgotten, using divs didn't work vmlStr.push(' ' , '', ''); this.element_.insertAdjacentHTML("BeforeEnd", vmlStr.join("")); }; contextPrototype.stroke = function(aFill) { var lineStr = []; var lineOpen = false; var a = processStyle(aFill ? this.fillStyle : this.strokeStyle); var color = a[0]; var opacity = a[1] * this.globalAlpha; var W = 10; var H = 10; lineStr.push(' max.x) { max.x = c.x; } if (min.y == null || c.y < min.y) { min.y = c.y; } if (max.y == null || c.y > max.y) { max.y = c.y; } } } lineStr.push(' ">'); if (typeof this.fillStyle == "object") { var focus = {x: "50%", y: "50%"}; var width = (max.x - min.x); var height = (max.y - min.y); var dimension = (width > height) ? width : height; focus.x = mr((this.fillStyle.focus_.x / width) * 100 + 50) + "%"; focus.y = mr((this.fillStyle.focus_.y / height) * 100 + 50) + "%"; var colors = []; // inside radius (%) if (this.fillStyle.type_ == "gradientradial") { var inside = (this.fillStyle.radius1_ / dimension * 100); // percentage that outside radius exceeds inside radius var expansion = (this.fillStyle.radius2_ / dimension * 100) - inside; } else { var inside = 0; var expansion = 100; } var insidecolor = {offset: null, color: null}; var outsidecolor = {offset: null, color: null}; // We need to sort 'colors' by percentage, from 0 > 100 otherwise ie // won't interpret it correctly this.fillStyle.colors_.sort(function (cs1, cs2) { return cs1.offset - cs2.offset; }); for (var i = 0; i < this.fillStyle.colors_.length; i++) { var fs = this.fillStyle.colors_[i]; colors.push( (fs.offset * expansion) + inside, "% ", fs.color, ","); if (fs.offset > insidecolor.offset || insidecolor.offset == null) { insidecolor.offset = fs.offset; insidecolor.color = fs.color; } if (fs.offset < outsidecolor.offset || outsidecolor.offset == null) { outsidecolor.offset = fs.offset; outsidecolor.color = fs.color; } } colors.pop(); lineStr.push(''); } else if (aFill) { lineStr.push(''); } else { lineStr.push( '' ); } lineStr.push(""); this.element_.insertAdjacentHTML("beforeEnd", lineStr.join("")); this.currentPath_ = []; }; contextPrototype.fill = function() { this.stroke(true); } contextPrototype.closePath = function() { this.currentPath_.push({type: "close"}); }; /** * @private */ contextPrototype.getCoords_ = function(aX, aY) { return { x: Z * (aX * this.m_[0][0] + aY * this.m_[1][0] + this.m_[2][0]) - Z2, y: Z * (aX * this.m_[0][1] + aY * this.m_[1][1] + this.m_[2][1]) - Z2 } }; contextPrototype.save = function() { var o = {}; copyState(this, o); this.aStack_.push(o); this.mStack_.push(this.m_); this.m_ = matrixMultiply(createMatrixIdentity(), this.m_); }; contextPrototype.restore = function() { copyState(this.aStack_.pop(), this); this.m_ = this.mStack_.pop(); }; contextPrototype.translate = function(aX, aY) { var m1 = [ [1, 0, 0], [0, 1, 0], [aX, aY, 1] ]; this.m_ = matrixMultiply(m1, this.m_); }; contextPrototype.rotate = function(aRot) { var c = mc(aRot); var s = ms(aRot); var m1 = [ [c, s, 0], [-s, c, 0], [0, 0, 1] ]; this.m_ = matrixMultiply(m1, this.m_); }; contextPrototype.scale = function(aX, aY) { this.arcScaleX_ *= aX; this.arcScaleY_ *= aY; var m1 = [ [aX, 0, 0], [0, aY, 0], [0, 0, 1] ]; this.m_ = matrixMultiply(m1, this.m_); }; /******** STUBS ********/ contextPrototype.clip = function() { // TODO: Implement }; contextPrototype.arcTo = function() { // TODO: Implement }; contextPrototype.createPattern = function() { return new CanvasPattern_; }; // Gradient / Pattern Stubs function CanvasGradient_(aType) { this.type_ = aType; this.radius1_ = 0; this.radius2_ = 0; this.colors_ = []; this.focus_ = {x: 0, y: 0}; } CanvasGradient_.prototype.addColorStop = function(aOffset, aColor) { aColor = processStyle(aColor); this.colors_.push({offset: 1-aOffset, color: aColor}); }; function CanvasPattern_() {} // set up externs G_vmlCanvasManager = G_vmlCanvasManager_; CanvasRenderingContext2D = CanvasRenderingContext2D_; CanvasGradient = CanvasGradient_; CanvasPattern = CanvasPattern_; })(); } // if ================================================ FILE: input-scripts/coolclock/moreskins.js ================================================ CoolClock.config.skins = { swissRail: { outerBorder: { lineWidth: 2, radius: 95, color: "black", alpha: 1 }, smallIndicator: { lineWidth: 2, startAt: 88, endAt: 92, color: "black", alpha: 1 }, largeIndicator: { lineWidth: 4, startAt: 79, endAt: 92, color: "black", alpha: 1 }, hourHand: { lineWidth: 8, startAt: -15, endAt: 50, color: "black", alpha: 1 }, minuteHand: { lineWidth: 7, startAt: -15, endAt: 75, color: "black", alpha: 1 }, secondHand: { lineWidth: 1, startAt: -20, endAt: 85, color: "red", alpha: 1 }, secondDecoration: { lineWidth: 1, startAt: 70, radius: 4, fillColor: "red", color: "red", alpha: 1 } }, chunkySwiss: { outerBorder: { lineWidth: 4, radius: 97, color: "black", alpha: 1 }, smallIndicator: { lineWidth: 4, startAt: 89, endAt: 93, color: "black", alpha: 1 }, largeIndicator: { lineWidth: 8, startAt: 80, endAt: 93, color: "black", alpha: 1 }, hourHand: { lineWidth: 12, startAt: -15, endAt: 60, color: "black", alpha: 1 }, minuteHand: { lineWidth: 10, startAt: -15, endAt: 85, color: "black", alpha: 1 }, secondHand: { lineWidth: 4, startAt: -20, endAt: 85, color: "red", alpha: 1 }, secondDecoration: { lineWidth: 2, startAt: 70, radius: 8, fillColor: "red", color: "red", alpha: 1 } }, chunkySwissOnBlack: { outerBorder: { lineWidth: 4, radius: 97, color: "white", alpha: 1 }, smallIndicator: { lineWidth: 4, startAt: 89, endAt: 93, color: "white", alpha: 1 }, largeIndicator: { lineWidth: 8, startAt: 80, endAt: 93, color: "white", alpha: 1 }, hourHand: { lineWidth: 12, startAt: -15, endAt: 60, color: "white", alpha: 1 }, minuteHand: { lineWidth: 10, startAt: -15, endAt: 85, color: "white", alpha: 1 }, secondHand: { lineWidth: 4, startAt: -20, endAt: 85, color: "red", alpha: 1 }, secondDecoration: { lineWidth: 2, startAt: 70, radius: 8, fillColor: "red", color: "red", alpha: 1 } }, fancy: { outerBorder: { lineWidth: 5, radius: 95, color: "green", alpha: 0.7 }, smallIndicator: { lineWidth: 1, startAt: 80, endAt: 93, color: "black", alpha: 0.4 }, largeIndicator: { lineWidth: 1, startAt: 30, endAt: 93, color: "black", alpha: 0.5 }, hourHand: { lineWidth: 8, startAt: -15, endAt: 50, color: "blue", alpha: 0.7 }, minuteHand: { lineWidth: 7, startAt: -15, endAt: 92, color: "red", alpha: 0.7 }, secondHand: { lineWidth: 10, startAt: 80, endAt: 85, color: "blue", alpha: 0.3 }, secondDecoration: { lineWidth: 1, startAt: 30, radius: 50, fillColor: "blue", color: "red", alpha: 0.15 } }, machine: { outerBorder: { lineWidth: 60, radius: 55, color: "#dd6655", alpha: 1 }, smallIndicator: { lineWidth: 4, startAt: 80, endAt: 95, color: "white", alpha: 1 }, largeIndicator: { lineWidth: 14, startAt: 77, endAt: 92, color: "#dd6655", alpha: 1 }, hourHand: { lineWidth: 18, startAt: -15, endAt: 40, color: "white", alpha: 1 }, minuteHand: { lineWidth: 14, startAt: 24, endAt: 100, color: "#771100", alpha: 0.5 }, secondHand: { lineWidth: 3, startAt: 22, endAt: 83, color: "green", alpha: 0 }, secondDecoration: { lineWidth: 1, startAt: 52, radius: 26, fillColor: "#ffcccc", color: "red", alpha: 0.5 } }, simonbaird_com: { hourHand: { lineWidth: 80, startAt: -15, endAt: 35, color: 'magenta', alpha: 0.5 }, minuteHand: { lineWidth: 80, startAt: -15, endAt: 65, color: 'cyan', alpha: 0.5 }, secondDecoration: { lineWidth: 1, startAt: 40, radius: 40, color: "#fff", fillColor: 'yellow', alpha: 0.5 } }, // by bonstio, http://bonstio.net classic/*was gIG*/: { outerBorder: { lineWidth: 185, radius: 1, color: "#E5ECF9", alpha: 1 }, smallIndicator: { lineWidth: 2, startAt: 89, endAt: 94, color: "#3366CC", alpha: 1 }, largeIndicator: { lineWidth: 4, startAt: 83, endAt: 94, color: "#3366CC", alpha: 1 }, hourHand: { lineWidth: 5, startAt: 0, endAt: 60, color: "black", alpha: 1 }, minuteHand: { lineWidth: 4, startAt: 0, endAt: 80, color: "black", alpha: 1 }, secondHand: { lineWidth: 1, startAt: -20, endAt: 85, color: "red", alpha: .85 }, secondDecoration: { lineWidth: 3, startAt: 0, radius: 2, fillColor: "black", color: "black", alpha: 1 } }, modern/*was gIG2*/: { outerBorder: { lineWidth: 185, radius: 1, color: "#E5ECF9", alpha: 1 }, smallIndicator: { lineWidth: 5, startAt: 88, endAt: 94, color: "#3366CC", alpha: 1 }, largeIndicator: { lineWidth: 5, startAt: 88, endAt: 94, color: "#3366CC", alpha: 1 }, hourHand: { lineWidth: 8, startAt: 0, endAt: 60, color: "black", alpha: 1 }, minuteHand: { lineWidth: 8, startAt: 0, endAt: 80, color: "black", alpha: 1 }, secondHand: { lineWidth: 5, startAt: 80, endAt: 85, color: "red", alpha: .85 }, secondDecoration: { lineWidth: 3, startAt: 0, radius: 4, fillColor: "black", color: "black", alpha: 1 } }, simple/*was gIG3*/: { outerBorder: { lineWidth: 185, radius: 1, color: "#E5ECF9", alpha: 1 }, smallIndicator: { lineWidth: 10, startAt: 90, endAt: 94, color: "#3366CC", alpha: 1 }, largeIndicator: { lineWidth: 10, startAt: 90, endAt: 94, color: "#3366CC", alpha: 1 }, hourHand: { lineWidth: 8, startAt: 0, endAt: 60, color: "black", alpha: 1 }, minuteHand: { lineWidth: 8, startAt: 0, endAt: 80, color: "black", alpha: 1 }, secondHand: { lineWidth: 5, startAt: 80, endAt: 85, color: "red", alpha: .85 }, secondDecoration: { lineWidth: 3, startAt: 0, radius: 4, fillColor: "black", color: "black", alpha: 1 } }, // by securephp securephp: { outerBorder: { lineWidth: 100, radius: 0.45, color: "#669900", alpha: 0.3 }, smallIndicator: { lineWidth: 2, startAt: 80, endAt: 90 , color: "green", alpha: 1 }, largeIndicator: { lineWidth: 8.5, startAt: 20, endAt: 40 , color: "green", alpha: 0.4 }, hourHand: { lineWidth: 3, startAt: 0, endAt: 60, color: "black", alpha: 1 }, minuteHand: { lineWidth: 2, startAt: 0, endAt: 75, color: "black", alpha: 1 }, secondHand: { lineWidth: 1, startAt: -10, endAt: 80, color: "blue", alpha: 0.8 }, secondDecoration: { lineWidth: 1, startAt: 70, radius: 4, fillColor: "blue", color: "red", alpha: 1 } }, Tes2: { outerBorder: { lineWidth: 4, radius: 95, color: "black", alpha: 0.5 }, smallIndicator: { lineWidth: 1, startAt: 10, endAt: 50 , color: "#66CCFF", alpha: 1 }, largeIndicator: { lineWidth: 8.5, startAt: 60, endAt: 70, color: "#6699FF", alpha: 1 }, hourHand: { lineWidth: 5, startAt: -15, endAt: 60, color: "black", alpha: 0.7 }, minuteHand: { lineWidth: 3, startAt: -25, endAt: 75, color: "black", alpha: 0.7 }, secondHand: { lineWidth: 1.5, startAt: -20, endAt: 88, color: "red", alpha: 1 }, secondDecoration: { lineWidth: 1, startAt: 20, radius: 4, fillColor: "blue", color: "red", alpha: 1 } }, Lev: { outerBorder: { lineWidth: 10, radius: 95, color: "#CCFF33", alpha: 0.65 }, smallIndicator: { lineWidth: 5, startAt: 84, endAt: 90, color: "#996600", alpha: 1 }, largeIndicator: { lineWidth: 40, startAt: 25, endAt: 95, color: "#336600", alpha: 0.55 }, hourHand: { lineWidth: 4, startAt: 0, endAt: 65, color: "black", alpha: 0.9 }, minuteHand: { lineWidth: 3, startAt: 0, endAt: 80, color: "black", alpha: 0.85 }, secondHand: { lineWidth: 1, startAt: 0, endAt: 85, color: "black", alpha: 1 }, secondDecoration: { lineWidth: 2, startAt: 5, radius: 10, fillColor: "black", color: "black", alpha: 1 } }, Sand: { outerBorder: { lineWidth: 1, radius: 70, color: "black", alpha: 0.5 }, smallIndicator: { lineWidth: 3, startAt: 50, endAt: 70, color: "#0066FF", alpha: 0.5 }, largeIndicator: { lineWidth: 200, startAt: 80, endAt: 95, color: "#996600", alpha: 0.75 }, hourHand: { lineWidth: 4, startAt: 0, endAt: 65, color: "black", alpha: 0.9 }, minuteHand: { lineWidth: 3, startAt: 0, endAt: 80, color: "black", alpha: 0.85 }, secondHand: { lineWidth: 1, startAt: 0, endAt: 85, color: "black", alpha: 1 }, secondDecoration: { lineWidth: 2, startAt: 5, radius: 10, fillColor: "black", color: "black", alpha: 1 } }, Sun: { outerBorder: { lineWidth: 100, radius: 140, color: "#99FFFF", alpha: 0.2 }, smallIndicator: { lineWidth: 300, startAt: 50, endAt: 70, color: "black", alpha: 0.1 }, largeIndicator: { lineWidth: 5, startAt: 80, endAt: 95, color: "black", alpha: 0.65 }, hourHand: { lineWidth: 4, startAt: 0, endAt: 65, color: "black", alpha: 0.9 }, minuteHand: { lineWidth: 3, startAt: 0, endAt: 80, color: "black", alpha: 0.85 }, secondHand: { lineWidth: 1, startAt: 0, endAt: 90, color: "black", alpha: 1 }, secondDecoration: { lineWidth: 2, startAt: 5, radius: 10, fillColor: "black", color: "black", alpha: 1 } }, Tor: { outerBorder: { lineWidth: 10, radius: 88, color: "#996600", alpha: 0.9 }, smallIndicator: { lineWidth: 6, startAt: -10, endAt: 73, color: "green", alpha: 0.3 }, largeIndicator: { lineWidth: 6, startAt: 73, endAt: 100, color: "black", alpha: 0.65 }, hourHand: { lineWidth: 4, startAt: 0, endAt: 65, color: "black", alpha: 1 }, minuteHand: { lineWidth: 3, startAt: 0, endAt: 80, color: "black", alpha: 1 }, secondHand: { lineWidth: 1, startAt: -73, endAt: 73, color: "black", alpha: 0.8 }, secondDecoration: { lineWidth: 2, startAt: 5, radius: 10, fillColor: "black", color: "black", alpha: 1 } }, Cold: { outerBorder: { lineWidth: 15, radius: 90, color: "black", alpha: 0.3 }, smallIndicator: { lineWidth: 15, startAt: -10, endAt: 95, color: "blue", alpha: 0.1 }, largeIndicator: { lineWidth: 3, startAt: 80, endAt: 95, color: "blue", alpha: 0.65 }, hourHand: { lineWidth: 4, startAt: 0, endAt: 65, color: "black", alpha: 1 }, minuteHand: { lineWidth: 3, startAt: 0, endAt: 80, color: "black", alpha: 1 }, secondHand: { lineWidth: 1, startAt: 0, endAt: 85, color: "black", alpha: 0.8 }, secondDecoration: { lineWidth: 5, startAt: 30, radius: 10, fillColor: "black", color: "black", alpha: 1 } }, Babosa: { outerBorder: { lineWidth: 100, radius: 25, color: "blue", alpha: 0.25 }, smallIndicator: { lineWidth: 3, startAt: 90, endAt: 95, color: "#3366CC", alpha: 1 }, largeIndicator: { lineWidth: 4, startAt: 75, endAt: 95, color: "#3366CC", alpha: 1 }, hourHand: { lineWidth: 4, startAt: 0, endAt: 60, color: "black", alpha: 1 }, minuteHand: { lineWidth: 3, startAt: 0, endAt: 85, color: "black", alpha: 1 }, secondHand: { lineWidth: 12, startAt: 75, endAt: 90, color: "red", alpha: 0.8 }, secondDecoration: { lineWidth: 3, startAt: 0, radius: 4, fillColor: "black", color: "black", alpha: 1 } }, Tumb: { outerBorder: { lineWidth: 105, radius: 5, color: "green", alpha: 0.4 }, smallIndicator: { lineWidth: 1, startAt: 93, endAt: 98, color: "green", alpha: 1 }, largeIndicator: { lineWidth: 50, startAt: 0, endAt: 89, color: "red", alpha: 0.14 }, hourHand: { lineWidth: 4, startAt: 0, endAt: 65, color: "black", alpha: 1 }, minuteHand: { lineWidth: 3, startAt: 0, endAt: 80, color: "black", alpha: 1 }, secondHand: { lineWidth: 1, startAt: 0, endAt: 85, color: "black", alpha: 0.8 }, secondDecoration: { lineWidth: 5, startAt: 50, radius: 90, fillColor: "black", color: "black", alpha: 0.05 } }, Stone: { outerBorder: { lineWidth: 15, radius: 80, color: "#339933", alpha: 0.5 }, smallIndicator: { lineWidth: 2, startAt: 70, endAt: 90, color: "#FF3300", alpha: 0.7 }, largeIndicator: { lineWidth: 15, startAt: 0, endAt: 29, color: "#FF6600", alpha: 0.3 }, hourHand: { lineWidth: 4, startAt: 0, endAt: 65, color: "black", alpha: 1 }, minuteHand: { lineWidth: 3, startAt: 0, endAt: 75, color: "black", alpha: 1 }, secondHand: { lineWidth: 1, startAt: 0, endAt: 85, color: "black", alpha: 0.8 }, secondDecoration: { lineWidth: 5, startAt: 50, radius: 90, fillColor: "black", color: "black", alpha: 0.05 } }, Disc: { outerBorder: { lineWidth: 105, radius: 1, color: "#666600", alpha: 0.2 }, smallIndicator: { lineWidth: 1, startAt: 58, endAt: 95, color: "#669900", alpha: 0.8 }, largeIndicator: { lineWidth: 6, startAt: 25, endAt: 35, color: "#666600", alpha: 1 }, hourHand: { lineWidth: 4, startAt: 0, endAt: 65, color: "black", alpha: 1 }, minuteHand: { lineWidth: 3, startAt: 0, endAt: 75, color: "black", alpha: 1 }, secondHand: { lineWidth: 1, startAt: -75, endAt: 75, color: "#99CC00", alpha: 0.8 }, secondDecoration: { lineWidth: 5, startAt: 50, radius: 90, fillColor: "#00FF00", color: "green", alpha: 0.05 } }, // By Yoo Nhe watermelon: { outerBorder: { lineWidth: 100, radius: 1.7, color: "#d93d04", alpha: 5 }, smallIndicator: { lineWidth: 2, startAt: 50, endAt: 70, color: "#d93d04", alpha: 5 }, largeIndicator: { lineWidth: 2, startAt: 45, endAt: 94, color: "#a9bf04", alpha: 1 }, hourHand: { lineWidth: 5, startAt: -20, endAt: 80, color: "#8c0d17", alpha: 1 }, minuteHand: { lineWidth: 2, startAt: -20, endAt: 80, color: "#7c8c03", alpha: .9 }, secondHand: { lineWidth: 2, startAt: 70, endAt: 94, color: "#d93d04", alpha: .85 }, secondDecoration: { lineWidth: 1, startAt: 70, radius: 3, fillColor: "red", color: "black", alpha: .7 } } }; ================================================ FILE: input-scripts/flotr/flotr-0.2.0-alpha.js ================================================ //Flotr 0.2.0-alpha Copyright (c) 2009 Bas Wenneker, , MIT License. var Flotr={version:"0.2.0-alpha",author:"Bas Wenneker",website:"http://www.solutoire.com",_registeredTypes:{lines:"drawSeriesLines",points:"drawSeriesPoints",bars:"drawSeriesBars",candles:"drawSeriesCandles",pie:"drawSeriesPie"},register:function(A,B){Flotr._registeredTypes[A]=B+""},draw:function(B,D,A,C){C=C||Flotr.Graph;return new C(B,D,A)},getSeries:function(A){return A.collect(function(C){var B,C=(C.data)?Object.clone(C):{data:C};for(B=C.data.length-1;B>-1;--B){C.data[B][1]=(C.data[B][1]===null?null:parseFloat(C.data[B][1]))}return C})},merge:function(D,B){var A=B||{};for(var C in D){A[C]=(D[C]!=null&&typeof (D[C])=="object"&&!(D[C].constructor==Array||D[C].constructor==RegExp)&&!Object.isElement(D[C]))?Flotr.merge(D[C],B[C]):A[C]=D[C]}return A},getTickSize:function(E,D,A,B){var H=(A-D)/E;var G=Flotr.getMagnitude(H);var C=H/G;var F=10;if(C<1.5){F=1}else{if(C<2.25){F=2}else{if(C<3){F=((B==0)?2:2.5)}else{if(C<7.5){F=5}}}}return F*G},defaultTickFormatter:function(A){return A+""},defaultTrackFormatter:function(A){return"("+A.x+", "+A.y+")"},defaultPieLabelFormatter:function(A){return(A.fraction*100).toFixed(2)+"%"},getMagnitude:function(A){return Math.pow(10,Math.floor(Math.log(A)/Math.LN10))},toPixel:function(A){return Math.floor(A)+0.5},toRad:function(A){return -A*(Math.PI/180)},parseColor:function(D){if(D instanceof Flotr.Color){return D}var A,C=Flotr.Color;if((A=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(D))){return new C(parseInt(A[1]),parseInt(A[2]),parseInt(A[3]))}if((A=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(D))){return new C(parseInt(A[1]),parseInt(A[2]),parseInt(A[3]),parseFloat(A[4]))}if((A=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(D))){return new C(parseFloat(A[1])*2.55,parseFloat(A[2])*2.55,parseFloat(A[3])*2.55)}if((A=/rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(D))){return new C(parseFloat(A[1])*2.55,parseFloat(A[2])*2.55,parseFloat(A[3])*2.55,parseFloat(A[4]))}if((A=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(D))){return new C(parseInt(A[1],16),parseInt(A[2],16),parseInt(A[3],16))}if((A=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(D))){return new C(parseInt(A[1]+A[1],16),parseInt(A[2]+A[2],16),parseInt(A[3]+A[3],16))}var B=D.strip().toLowerCase();if(B=="transparent"){return new C(255,255,255,0)}return((A=C.lookupColors[B]))?new C(A[0],A[1],A[2]):false},extractColor:function(B){var A;do{A=B.getStyle("background-color").toLowerCase();if(!(A==""||A=="transparent")){break}B=B.up(0)}while(!B.nodeName.match(/^body$/i));return(A=="rgba(0, 0, 0, 0)")?"transparent":A}};Flotr.Graph=Class.create({initialize:function(B,C,A){this.el=$(B);if(!this.el){throw"The target container doesn't exist"}this.data=C;this.series=Flotr.getSeries(C);this.setOptions(A);this.lastMousePos={pageX:null,pageY:null};this.selection={first:{x:-1,y:-1},second:{x:-1,y:-1}};this.prevSelection=null;this.selectionInterval=null;this.ignoreClick=false;this.prevHit=null;this.constructCanvas();this.initEvents();this.findDataRanges();this.calculateTicks(this.axes.x);this.calculateTicks(this.axes.x2);this.calculateTicks(this.axes.y);this.calculateTicks(this.axes.y2);this.calculateSpacing();this.draw();this.insertLegend();if(this.options.spreadsheet.show){this.constructTabs()}},setOptions:function(B){var P={colors:["#00A8F0","#C0D800","#CB4B4B","#4DA74D","#9440ED"],title:null,subtitle:null,legend:{show:true,noColumns:1,labelFormatter:Prototype.K,labelBoxBorderColor:"#CCCCCC",labelBoxWidth:14,labelBoxHeight:10,labelBoxMargin:5,container:null,position:"nw",margin:5,backgroundColor:null,backgroundOpacity:0.85},xaxis:{ticks:null,showLabels:true,labelsAngle:0,title:null,titleAngle:0,noTicks:5,tickFormatter:Flotr.defaultTickFormatter,tickDecimals:null,min:null,max:null,autoscaleMargin:0,color:null},x2axis:{},yaxis:{ticks:null,showLabels:true,labelsAngle:0,title:null,titleAngle:90,noTicks:5,tickFormatter:Flotr.defaultTickFormatter,tickDecimals:null,min:null,max:null,autoscaleMargin:0,color:null},y2axis:{titleAngle:270},points:{show:false,radius:3,lineWidth:2,fill:true,fillColor:"#FFFFFF",fillOpacity:0.4},lines:{show:false,lineWidth:2,fill:false,fillColor:null,fillOpacity:0.4},bars:{show:false,lineWidth:2,barWidth:1,fill:true,fillColor:null,fillOpacity:0.4,horizontal:false,stacked:false},candles:{show:false,lineWidth:1,wickLineWidth:1,candleWidth:0.6,fill:true,upFillColor:"#00A8F0",downFillColor:"#CB4B4B",fillOpacity:0.5,barcharts:false},pie:{show:false,lineWidth:1,fill:true,fillColor:null,fillOpacity:0.6,explode:6,sizeRatio:0.6,startAngle:Math.PI/4,labelFormatter:Flotr.defaultPieLabelFormatter,pie3D:false,pie3DviewAngle:(Math.PI/2*0.8),pie3DspliceThickness:20},grid:{color:"#545454",backgroundColor:null,tickColor:"#DDDDDD",labelMargin:3,verticalLines:true,horizontalLines:true,outlineWidth:2},selection:{mode:null,color:"#B6D9FF",fps:20},mouse:{track:false,position:"se",relative:false,trackFormatter:Flotr.defaultTrackFormatter,margin:5,lineColor:"#FF3F19",trackDecimals:1,sensibility:2,radius:3},shadowSize:4,defaultType:"lines",HtmlText:true,fontSize:7.5,spreadsheet:{show:false,tabGraphLabel:"Graph",tabDataLabel:"Data",toolbarDownload:"Download CSV",toolbarSelectAll:"Select all"}};P.x2axis=Object.extend(Object.clone(P.xaxis),P.x2axis);P.y2axis=Object.extend(Object.clone(P.yaxis),P.y2axis);this.options=Flotr.merge((B||{}),P);this.axes={x:{options:this.options.xaxis,n:1},x2:{options:this.options.x2axis,n:2},y:{options:this.options.yaxis,n:1},y2:{options:this.options.y2axis,n:2}};var H=[],C=[],K=this.series.length,N=this.series.length,D=this.options.colors,A=[],G=0,M,J,I,O,E;for(J=N-1;J>-1;--J){M=this.series[J].color;if(M!=null){--N;if(Object.isNumber(M)){H.push(M)}else{A.push(Flotr.parseColor(M))}}}for(J=H.length-1;J>-1;--J){N=Math.max(N,H[J]+1)}for(J=0;C.length=D.length){J=0;++G}}for(J=0,I=0;J'+F+"").select(".flotr-dummy-div")[0];dim=A.getDimensions();A.remove();return dim}},loadDataGrid:function(){if(this.seriesData){return this.seriesData}var A=this.series;var B=[];for(i=0;i"];var F=[''];F.push(" ");for(D=0;D'+(L[D].label||String.fromCharCode(65+D))+"");C.push("")}F.push("");for(B=0;B");for(D=0;D"+G+"")}F.push("")}C.push("");K.update(C.join("")+F.join(""));if(!Prototype.Browser.IE){K.select("td").each(function(N){N.observe("mouseover",function(O){N=O.element();var P=N.previousSiblings();K.select("th[scope=col]")[P.length-1].addClassName("hover");K.select("colgroup col")[P.length].addClassName("hover")});N.observe("mouseout",function(){K.select("colgroup col.hover, th.hover").each(function(O){O.removeClassName("hover")})})})}var H=new Element("div",{className:"flotr-datagrid-toolbar"}).insert(new Element("button",{type:"button",className:"flotr-datagrid-toolbar-button"}).update(this.options.spreadsheet.toolbarDownload).observe("click",this.downloadCSV.bind(this))).insert(new Element("button",{type:"button",className:"flotr-datagrid-toolbar-button"}).update(this.options.spreadsheet.toolbarSelectAll).observe("click",this.selectAllData.bind(this)));var A=new Element("div",{className:"flotr-datagrid-container",style:"left:0px;top:0px;width:"+this.canvasWidth+"px;height:"+this.canvasHeight+"px;overflow:auto;"});A.insert(H);K.wrap(A.hide());this.el.insert(A);return K},selectAllData:function(){if(this.tabs){var B,A,E,D,C=this.constructDataGrid();this.showTab("data");(function(){if((E=C.ownerDocument)&&(D=E.defaultView)&&D.getSelection&&E.createRange&&(B=window.getSelection())&&B.removeAllRanges){A=E.createRange();A.selectNode(C);B.removeAllRanges();B.addRange(A)}else{if(document.body&&document.body.createTextRange&&(A=document.body.createTextRange())){A.moveToElementText(C);A.select()}}}).defer();return true}else{return false}},downloadCSV:function(){var D,A='"x"',C=this.series,E=this.loadDataGrid();for(D=0;D0){var C,A,D,H,F,B,I,E;for(C=0;C0&&!J[C].hide){if(!I.used){I.datamin=I.datamax=B[0][0]}if(!E.used){E.datamin=E.datamax=B[0][1]}I.used=true;E.used=true;for(D=B.length-1;D>-1;--D){H=B[D][0];if(HI.datamax){I.datamax=H}}for(A=1;AE.datamax){E.datamax=F}}}}}}}this.findXAxesValues();this.calculateRange(G.x);this.extendXRangeIfNeededByBar(G.x);if(G.x2.used){this.calculateRange(G.x2);this.extendXRangeIfNeededByBar(G.x2)}this.calculateRange(G.y);this.extendYRangeIfNeededByBar(G.y);if(G.y2.used){this.calculateRange(G.y2);this.extendYRangeIfNeededByBar(G.y2)}},calculateRange:function(D){var F=D.options,C=F.min!=null?F.min:D.datamin,A=F.max!=null?F.max:D.datamax,E;if(A-C==0){var B=(A==0)?1:0.01;C-=B;A+=B}D.tickSize=Flotr.getTickSize(F.noTicks,C,A,F.tickDecimals);if(F.min==null){E=F.autoscaleMargin;if(E!=0){C-=D.tickSize*E;if(C<0&&D.datamin>=0){C=0}C=D.tickSize*Math.floor(C/D.tickSize)}}if(F.max==null){E=F.autoscaleMargin;if(E!=0){A+=D.tickSize*E;if(A>0&&D.datamax<=0){A=0}A=D.tickSize*Math.ceil(A/D.tickSize)}}D.min=C;D.max=A},extendXRangeIfNeededByBar:function(A){if(A.options.max==null){var D=A.max,B,I,F,E,H=[],C=null;for(B=0;BD)||(E.candleWidth+A.datamax>D)){D=A.max+I.bars.barWidth}if(F.stacked&&F.horizontal){for(j=0;jD)||(E.candleWidth+A.datamax>D)){D=A.max+F.barWidth}if(F.stacked&&!F.horizontal){for(j=0;j-1;--i){s=this.series[i];s.xaxis.values=s.xaxis.values||[];for(j=s.data.length-1;j>-1;--j){s.xaxis.values[s.data[j][0]]={}}}},calculateTicks:function(D){var B=D.options,E,H;D.ticks=[];if(B.ticks){var G=B.ticks,I,F;if(Object.isFunction(G)){G=G({min:D.min,max:D.max})}for(E=0;E1)?I[1]:B.tickFormatter(H)}else{H=I;F=B.tickFormatter(H)}D.ticks[E]={v:H,label:F}}}else{var A=D.tickSize*Math.ceil(D.min/D.tickSize),C;for(E=0;A+E*D.tickSize<=D.max;++E){H=A+E*D.tickSize;C=B.tickDecimals;if(C==null){C=1-Math.floor(Math.log(D.tickSize)/Math.LN10)}if(C<0){C=0}H=H.toFixed(C);D.ticks.push({v:H,label:B.tickFormatter(H)})}}},calculateSpacing:function(){var L=this.axes,N=this.options,H=this.series,D=N.grid.labelMargin,M=L.x,A=L.x2,J=L.y,K=L.y2,F=2,G,E,C,I;[M,A,J,K].each(function(P){var O="";if(P.options.showLabels){for(G=0;GO.length){O=P.ticks[G].label}}}P.maxLabel=this.getTextDimensions(O,{size:N.fontSize,angle:Flotr.toRad(P.options.labelsAngle)},"font-size:smaller;","flotr-grid-label");P.titleSize=this.getTextDimensions(P.options.title,{size:N.fontSize*1.2,angle:Flotr.toRad(P.options.titleAngle)},"font-weight:bold;","flotr-axis-title")},this);I=this.getTextDimensions(N.title,{size:N.fontSize*1.5},"font-size:1em;font-weight:bold;","flotr-title");this.titleHeight=I.height;I=this.getTextDimensions(N.subtitle,{size:N.fontSize},"font-size:smaller;","flotr-subtitle");this.subtitleHeight=I.height;if(N.show){F=Math.max(F,N.points.radius+N.points.lineWidth/2)}for(E=0;E'];D=H.x;if(D.options.showLabels){for(E=0;E'+G.label+"")}}D=H.x2;if(D.options.showLabels&&D.used){for(E=0;E'+G.label+"")}}D=H.y;if(D.options.showLabels){for(E=0;E'+G.label+"")}}D=H.y2;if(D.options.showLabels&&D.used){I.save();I.strokeStyle=D.options.color||J.grid.color;I.beginPath();for(E=0;E'+G.label+"");I.moveTo(this.plotOffset.left+this.plotWidth-8,this.plotOffset.top+this.tVert(G.v,D));I.lineTo(this.plotOffset.left+this.plotWidth,this.plotOffset.top+this.tVert(G.v,D))}I.stroke();I.restore()}F.push("");this.el.insert(F.join(""))}}},drawTitles:function(){var D,C=this.options,F=C.grid.labelMargin,B=this.ctx,A=this.axes;if(!C.HtmlText&&this.textEnabled){var E={size:C.fontSize,color:C.grid.color,halign:"c"};if(C.subtitle){B.drawText(C.subtitle,this.plotOffset.left+this.plotWidth/2,this.titleHeight+this.subtitleHeight-2,E)}E.weight=1.5;E.size*=1.5;if(C.title){B.drawText(C.title,this.plotOffset.left+this.plotWidth/2,this.titleHeight-2,E)}E.weight=1.8;E.size*=0.8;E.adjustAlign=true;if(A.x.options.title&&A.x.used){E.halign="c";E.valign="t";E.angle=Flotr.toRad(A.x.options.titleAngle);B.drawText(A.x.options.title,this.plotOffset.left+this.plotWidth/2,this.plotOffset.top+A.x.maxLabel.height+this.plotHeight+2*F,E)}if(A.x2.options.title&&A.x2.used){E.halign="c";E.valign="b";E.angle=Flotr.toRad(A.x2.options.titleAngle);B.drawText(A.x2.options.title,this.plotOffset.left+this.plotWidth/2,this.plotOffset.top-A.x2.maxLabel.height-2*F,E)}if(A.y.options.title&&A.y.used){E.halign="r";E.valign="m";E.angle=Flotr.toRad(A.y.options.titleAngle);B.drawText(A.y.options.title,this.plotOffset.left-A.y.maxLabel.width-2*F,this.plotOffset.top+this.plotHeight/2,E)}if(A.y2.options.title&&A.y2.used){E.halign="l";E.valign="m";E.angle=Flotr.toRad(A.y2.options.titleAngle);B.drawText(A.y2.options.title,this.plotOffset.left+this.plotWidth+A.y2.maxLabel.width+2*F,this.plotOffset.top+this.plotHeight/2,E)}}else{D=['
'];if(C.title){D.push('
'+C.title+"
")}if(C.subtitle){D.push('
'+C.subtitle+"
")}D.push("
");D.push('
');if(A.x.options.title&&A.x.used){D.push('
'+A.x.options.title+"
")}if(A.x2.options.title&&A.x2.used){D.push('
'+A.x2.options.title+"
")}if(A.y.options.title&&A.y.used){D.push('
'+A.y.options.title+"
")}if(A.y2.options.title&&A.y2.used){D.push('
'+A.y2.options.title+"
")}D.push("
");this.el.insert(D.join(""))}},drawSeries:function(A){A=A||this.series;var C=false;for(var B in Flotr._registeredTypes){if(A[B]&&A[B].show){this[Flotr._registeredTypes[B]](A);C=true}}if(!C){this[Flotr._registeredTypes[this.options.defaultType]](A)}},plotLine:function(I,F){var O=this.ctx,A=I.xaxis,K=I.yaxis,J=this.tHoz.bind(this),M=this.tVert.bind(this),H=I.data;if(H.length<2){return }var E=J(H[0][0],A),D=M(H[0][1],K)+F;O.beginPath();O.moveTo(E,D);for(var G=0;G=L&&N>K.max){if(L>K.max){continue}C=(K.max-N)/(L-N)*(B-C)+C;N=K.max}else{if(L>=N&&L>K.max){if(N>K.max){continue}B=(K.max-N)/(L-N)*(B-C)+C;L=K.max}}if(C<=B&&C=B&&C>A.max){if(B>A.max){continue}N=(A.max-C)/(B-C)*(L-N)+N;C=A.max}else{if(B>=C&&B>A.max){if(C>A.max){continue}L=(A.max-C)/(B-C)*(L-N)+N;B=A.max}}if(E!=J(C,A)||D!=M(N,K)+F){O.moveTo(J(C,A),M(N,K)+F)}E=J(B,A);D=M(L,K)+F;O.lineTo(E,D)}O.stroke()},plotLineArea:function(J,D){var S=J.data;if(S.length<2){return }var L,G=0,N=this.ctx,Q=J.xaxis,B=J.yaxis,E=this.tHoz.bind(this),M=this.tVert.bind(this),H=Math.min(Math.max(0,B.min),B.max),F=true;N.beginPath();for(var O=0;O=P&&R>Q.max){if(P>Q.max){continue}C=(Q.max-R)/(P-R)*(A-C)+C;R=Q.max}else{if(P>=R&&P>Q.max){if(R>Q.max){continue}A=(Q.max-R)/(P-R)*(A-C)+C;P=Q.max}}if(F){N.moveTo(E(R,Q),M(H,B)+D);F=false}if(C>=B.max&&A>=B.max){N.lineTo(E(R,Q),M(B.max,B)+D);N.lineTo(E(P,Q),M(B.max,B)+D);continue}else{if(C<=B.min&&A<=B.min){N.lineTo(E(R,Q),M(B.min,B)+D);N.lineTo(E(P,Q),M(B.min,B)+D);continue}}var I=R,K=P;if(C<=A&&C=B.min){R=(B.min-C)/(A-C)*(P-R)+R;C=B.min}else{if(A<=C&&A=B.min){P=(B.min-C)/(A-C)*(P-R)+R;A=B.min}}if(C>=A&&C>B.max&&A<=B.max){R=(B.max-C)/(A-C)*(P-R)+R;C=B.max}else{if(A>=C&&A>B.max&&C<=B.max){P=(B.max-C)/(A-C)*(P-R)+R;A=B.max}}if(R!=I){L=(C<=B.min)?L=B.min:B.max;N.lineTo(E(I,Q),M(L,B)+D);N.lineTo(E(R,Q),M(L,B)+D)}N.lineTo(E(R,Q),M(C,B)+D);N.lineTo(E(P,Q),M(A,B)+D);if(P!=K){L=(A<=B.min)?B.min:B.max;N.lineTo(E(K,Q),M(L,B)+D);N.lineTo(E(P,Q),M(L,B)+D)}G=Math.max(P,K)}N.lineTo(E(G,Q),M(H,B)+D);N.closePath();N.fill()},drawSeriesLines:function(C){C=C||this.series;var B=this.ctx;B.save();B.translate(this.plotOffset.left,this.plotOffset.top);B.lineJoin="round";var D=C.lines.lineWidth;var A=C.shadowSize;if(A>0){B.lineWidth=A/2;var E=D/2+B.lineWidth/2;B.strokeStyle="rgba(0,0,0,0.1)";this.plotLine(C,E+A/2);B.strokeStyle="rgba(0,0,0,0.2)";this.plotLine(C,E);if(C.lines.fill){B.fillStyle="rgba(0,0,0,0.05)";this.plotLineArea(C,E+A/2)}}B.lineWidth=D;B.strokeStyle=C.color;if(C.lines.fill){B.fillStyle=C.lines.fillColor!=null?C.lines.fillColor:Flotr.parseColor(C.color).scale(null,null,null,C.lines.fillOpacity).toString();this.plotLineArea(C,0)}this.plotLine(C,0);B.restore()},drawSeriesPoints:function(C){var B=this.ctx;B.save();B.translate(this.plotOffset.left,this.plotOffset.top);var D=C.lines.lineWidth;var A=C.shadowSize;if(A>0){B.lineWidth=A/2;B.strokeStyle="rgba(0,0,0,0.1)";this.plotPointShadows(C,A/2+B.lineWidth/2,C.points.radius);B.strokeStyle="rgba(0,0,0,0.2)";this.plotPointShadows(C,B.lineWidth/2,C.points.radius)}B.lineWidth=C.points.lineWidth;B.strokeStyle=C.color;B.fillStyle=C.points.fillColor!=null?C.points.fillColor:C.color;this.plotPoints(C,C.points.radius,C.points.fill);B.restore()},plotPoints:function(C,E,I){var A=C.xaxis,F=C.yaxis,J=this.ctx,D,B=C.data;for(D=B.length-1;D>-1;--D){var H=B[D][0],G=B[D][1];if(HA.max||GF.max){continue}J.beginPath();J.arc(this.tHoz(H,A),this.tVert(G,F),E,0,2*Math.PI,true);if(I){J.fill()}J.stroke()}},plotPointShadows:function(D,B,F){var A=D.xaxis,G=D.yaxis,J=this.ctx,E,C=D.data;for(E=C.length-1;E>-1;--E){var I=C[E][0],H=C[E][1];if(IA.max||HG.max){continue}J.beginPath();J.arc(this.tHoz(I,A),this.tVert(H,G)+B,F,0,Math.PI,false);J.stroke()}},drawSeriesBars:function(B){var A=this.ctx,D=B.bars.barWidth,C=Math.min(B.bars.lineWidth,D);A.save();A.translate(this.plotOffset.left,this.plotOffset.top);A.lineJoin="miter";A.lineWidth=C;A.strokeStyle=B.color;this.plotBarsShadows(B,D,0,B.bars.fill);if(B.bars.fill){A.fillStyle=B.bars.fillColor!=null?B.bars.fillColor:Flotr.parseColor(B.color).scale(null,null,null,B.bars.fillOpacity).toString()}this.plotBars(B,D,0,B.bars.fill);A.restore()},plotBars:function(K,N,D,Q){var U=K.data;if(U.length<1){return }var S=K.xaxis,B=K.yaxis,P=this.ctx,F=this.tHoz.bind(this),O=this.tVert.bind(this);for(var R=0;RS.max||MB.max){continue}if(CS.max){T=S.max;if(S.lastSerie!=K&&K.bars.horizontal){L=false}}if(GB.max){M=B.max;if(B.lastSerie!=K&&!K.bars.horizontal){L=false}}if(Q){P.beginPath();P.moveTo(F(C,S),O(G,B)+D);P.lineTo(F(C,S),O(M,B)+D);P.lineTo(F(T,S),O(M,B)+D);P.lineTo(F(T,S),O(G,B)+D);P.fill()}if(K.bars.lineWidth!=0&&(E||A||L)){P.beginPath();P.moveTo(F(C,S),O(G,B)+D);P[E?"lineTo":"moveTo"](F(C,S),O(M,B)+D);P[L?"lineTo":"moveTo"](F(T,S),O(M,B)+D);P[A?"lineTo":"moveTo"](F(T,S),O(G,B)+D);P.stroke()}}},plotBarsShadows:function(I,K,C){var T=I.data;if(T.length<1){return }var R=I.xaxis,A=I.yaxis,P=this.ctx,D=this.tHoz.bind(this),M=this.tVert.bind(this),N=this.options.shadowSize;for(var Q=0;QR.max||JA.max){continue}if(BR.max){S=R.max}if(FA.max){J=A.max}var O=D(S,R)-D(B,R)-((D(S,R)+N<=this.plotWidth)?0:N);var L=Math.max(0,M(F,A)-M(J,A)-((M(F,A)+N<=this.plotHeight)?0:N));P.fillStyle="rgba(0,0,0,0.05)";P.fillRect(Math.min(D(B,R)+N,this.plotWidth),Math.min(M(J,A)+N,this.plotWidth),O,L)}},drawSeriesCandles:function(B){var A=this.ctx,C=B.candles.candleWidth;A.save();A.translate(this.plotOffset.left,this.plotOffset.top);A.lineJoin="miter";A.lineWidth=B.candles.lineWidth;this.plotCandlesShadows(B,C/2);this.plotCandles(B,C/2);A.restore()},plotCandles:function(K,D){var W=K.data;if(W.length<1){return }var T=K.xaxis,B=K.yaxis,P=this.ctx,E=this.tHoz.bind(this),O=this.tVert.bind(this);for(var S=0;ST.max||MB.max){continue}var Q=K.candles[L>N?"downFillColor":"upFillColor"];if(K.candles.fill&&!K.candles.barcharts){P.fillStyle=Flotr.parseColor(Q).scale(null,null,null,K.candles.fillOpacity).toString();P.fillRect(E(C,T),O(R,B)+D,E(V,T)-E(C,T),O(A,B)-O(R,B))}if(K.candles.lineWidth||K.candles.wickLineWidth){var J,H,F=(K.candles.wickLineWidth%2)/2;J=Math.floor(E((C+V)/2),T)+F;P.save();P.strokeStyle=Q;P.lineWidth=K.candles.wickLineWidth;P.lineCap="butt";if(K.candles.barcharts){P.beginPath();P.moveTo(J,Math.floor(O(M,B)+D));P.lineTo(J,Math.floor(O(G,B)+D));H=Math.floor(O(L,B)+D)+0.5;P.moveTo(Math.floor(E(C,T))+F,H);P.lineTo(J,H);H=Math.floor(O(N,B)+D)+0.5;P.moveTo(Math.floor(E(V,T))+F,H);P.lineTo(J,H)}else{P.strokeRect(E(C,T),O(R,B)+D,E(V,T)-E(C,T),O(A,B)-O(R,B));P.beginPath();P.moveTo(J,Math.floor(O(R,B)+D));P.lineTo(J,Math.floor(O(M,B)+D));P.moveTo(J,Math.floor(O(A,B)+D));P.lineTo(J,Math.floor(O(G,B)+D))}P.stroke();P.restore()}}},plotCandlesShadows:function(H,C){var T=H.data;if(T.length<1||H.candles.barcharts){return }var Q=H.xaxis,A=H.yaxis,D=this.tHoz.bind(this),M=this.tVert.bind(this),N=this.options.shadowSize;for(var P=0;PQ.max||JA.max){continue}var O=D(S,Q)-D(B,Q)-((D(S,Q)+N<=this.plotWidth)?0:N);var L=Math.max(0,M(E,A)-M(J,A)-((M(E,A)+N<=this.plotHeight)?0:N));this.ctx.fillStyle="rgba(0,0,0,0.05)";this.ctx.fillRect(Math.min(D(B,Q)+N,this.plotWidth),Math.min(M(J,A)+N,this.plotWidth),O,L)}},drawSeriesPie:function(G){if(!this.options.pie.drawn){var K=this.ctx,C=this.options,E=G.pie.lineWidth,I=G.shadowSize,R=G.data,D=(Math.min(this.canvasWidth,this.canvasHeight)*G.pie.sizeRatio)/2,H=[];var L=1;var P=Math.sin(G.pie.viewAngle)*G.pie.spliceThickness/L;var M={size:C.fontSize*1.2,color:C.grid.color,weight:1.5};var Q={x:(this.canvasWidth+this.plotOffset.left)/2,y:(this.canvasHeight-this.plotOffset.bottom)/2};var O=this.series.collect(function(T,S){if(T.pie.show){return{name:(T.label||T.data[0][1]),value:[S,T.data[0][1]],explode:T.pie.explode}}});var B=O.pluck("value").pluck(1).inject(0,function(S,T){return S+T});var F=0,N=G.pie.startAngle,J=0;var A=O.collect(function(S){N+=F;J=parseFloat(S.value[1]);F=J/B;return{name:S.name,fraction:F,x:S.value[0],y:J,explode:S.explode,startAngle:2*N*Math.PI,endAngle:2*(N+F)*Math.PI}});K.save();if(I>0){A.each(function(V){var S=(V.startAngle+V.endAngle)/2;var T=Q.x+Math.cos(S)*V.explode+I;var U=Q.y+Math.sin(S)*V.explode+I;this.plotSlice(T,U,D,V.startAngle,V.endAngle,false,L);K.fillStyle="rgba(0,0,0,0.1)";K.fill()},this)}if(C.HtmlText){H=['
']}A.each(function(c,X){var W=(c.startAngle+c.endAngle)/2;var V=C.colors[X];var Y=Q.x+Math.cos(W)*c.explode;var U=Q.y+Math.sin(W)*c.explode;this.plotSlice(Y,U,D,c.startAngle,c.endAngle,false,L);if(G.pie.fill){K.fillStyle=Flotr.parseColor(V).scale(null,null,null,G.pie.fillOpacity).toString();K.fill()}K.lineWidth=E;K.strokeStyle=V;K.stroke();var b=C.pie.labelFormatter(c);var S=(Math.cos(W)<0);var a=Y+Math.cos(W)*(G.pie.explode+D);var Z=U+Math.sin(W)*(G.pie.explode+D);if(c.fraction&&b){if(C.HtmlText){var T="position:absolute;top:"+(Z-5)+"px;";if(S){T+="right:"+(this.canvasWidth-a)+"px;text-align:right;"}else{T+="left:"+a+"px;text-align:left;"}H.push('
'+b+"
")}else{M.halign=S?"r":"l";K.drawText(b,a,Z+M.size/2,M)}}},this);if(C.HtmlText){H.push("
");this.el.insert(H.join(""))}K.restore();C.pie.drawn=true}},plotSlice:function(B,H,A,E,D,F,G){var C=this.ctx;G=G||1;C.save();C.scale(1,G);C.beginPath();C.moveTo(B,H);C.arc(B,H,A,E,D,F);C.lineTo(B,H);C.closePath();C.restore()},plotPie:function(){},insertLegend:function(){if(!this.options.legend.show){return }var H=this.series,I=this.plotOffset,B=this.options,b=[],A=false,O=this.ctx,R;var Q=H.findAll(function(c){return(c.label&&!c.hide)}).size();if(Q){if(!B.HtmlText&&this.textEnabled){var T={size:B.fontSize*1.1,color:B.grid.color};var M=B.legend.position,N=B.legend.margin,L=B.legend.labelBoxWidth,Z=B.legend.labelBoxHeight,S=B.legend.labelBoxMargin,W=I.left+N,U=I.top+N;var a=0;for(R=H.length-1;R>-1;--R){if(!H[R].label||H[R].hide){continue}var E=B.legend.labelFormatter(H[R].label);a=Math.max(a,O.measureText(E,T))}var K=Math.round(L+S*3+a),C=Math.round(Q*(S+Z)+S);if(M.charAt(0)=="s"){U=I.top+this.plotHeight-(N+C)}if(M.charAt(1)=="e"){W=I.left+this.plotWidth-(N+K)}var P=Flotr.parseColor(B.legend.backgroundColor||"rgb(240,240,240)").scale(null,null,null,B.legend.backgroundOpacity||0.1).toString();O.fillStyle=P;O.fillRect(W,U,K,C);O.strokeStyle=B.legend.labelBoxBorderColor;O.strokeRect(Flotr.toPixel(W),Flotr.toPixel(U),K,C);var G=W+S;var F=U+S;for(R=0;R":"");A=true}var E=B.legend.labelFormatter(H[R].label);b.push('
'+E+"")}if(A){b.push("")}if(b.length>0){var V=''+b.join("")+"
";if(B.legend.container!=null){$(B.legend.container).update(V)}else{var D="";var M=B.legend.position,N=B.legend.margin;if(M.charAt(0)=="n"){D+="top:"+(N+I.top)+"px;"}else{if(M.charAt(0)=="s"){D+="bottom:"+(N+I.bottom)+"px;"}}if(M.charAt(1)=="e"){D+="right:"+(N+I.right)+"px;"}else{if(M.charAt(1)=="w"){D+="left:"+(N+I.left)+"px;"}}var J=this.el.insert('
'+V+"
").select("div.flotr-legend").first();if(B.legend.backgroundOpacity!=0){var Y=B.legend.backgroundColor;if(Y==null){var X=(B.grid.backgroundColor!=null)?B.grid.backgroundColor:Flotr.extractColor(J);Y=Flotr.parseColor(X).adjust(null,null,null,1).toString()}this.el.insert('
').select("div.flotr-legend-bg").first().setStyle({opacity:B.legend.backgroundOpacity})}}}}}},getEventPosition:function(C){var G=this.overlay.cumulativeOffset(),F=(C.pageX-G.left-this.plotOffset.left),E=(C.pageY-G.top-this.plotOffset.top),D=0,B=0;if(C.pageX==null&&C.clientX!=null){var H=document.documentElement,A=document.body;D=C.clientX+(H&&H.scrollLeft||A.scrollLeft||0);B=C.clientY+(H&&H.scrollTop||A.scrollTop||0)}else{D=C.pageX;B=C.pageY}return{x:this.axes.x.min+F/this.axes.x.scale,x2:this.axes.x2.min+F/this.axes.x2.scale,y:this.axes.y.max-E/this.axes.y.scale,y2:this.axes.y2.max-E/this.axes.y2.scale,relX:F,relY:E,absX:D,absY:B}},clickHandler:function(A){if(this.ignoreClick){this.ignoreClick=false;return }this.el.fire("flotr:click",[this.getEventPosition(A),this])},mouseMoveHandler:function(A){var B=this.getEventPosition(A);this.lastMousePos.pageX=B.absX;this.lastMousePos.pageY=B.absY;if(this.selectionInterval==null&&(this.options.mouse.track||this.series.any(function(C){return C.mouse&&C.mouse.track}))){this.hit(B)}this.el.fire("flotr:mousemove",[A,B,this])},mouseDownHandler:function(C){if(C.isRightClick()){C.stop();var B=this.overlay;B.hide();function A(){B.show();$(document).stopObserving("mousemove",A)}$(document).observe("mousemove",A);return }if(!this.options.selection.mode||!C.isLeftClick()){return }this.setSelectionPos(this.selection.first,C);if(this.selectionInterval!=null){clearInterval(this.selectionInterval)}this.lastMousePos.pageX=null;this.selectionInterval=setInterval(this.updateSelection.bind(this),1000/this.options.selection.fps);this.mouseUpHandler=this.mouseUpHandler.bind(this);$(document).observe("mouseup",this.mouseUpHandler)},fireSelectEvent:function(){var A=this.axes,F=this.selection,C=(F.first.x<=F.second.x)?F.first.x:F.second.x,B=(F.first.x<=F.second.x)?F.second.x:F.first.x,E=(F.first.y>=F.second.y)?F.first.y:F.second.y,D=(F.first.y>=F.second.y)?F.second.y:F.first.y;C=A.x.min+C/A.x.scale;B=A.x.min+B/A.x.scale;E=A.y.max-E/A.y.scale;D=A.y.max-D/A.y.scale;this.el.fire("flotr:select",[{x1:C,y1:E,x2:B,y2:D},this])},mouseUpHandler:function(A){$(document).stopObserving("mouseup",this.mouseUpHandler);A.stop();if(this.selectionInterval!=null){clearInterval(this.selectionInterval);this.selectionInterval=null}this.setSelectionPos(this.selection.second,A);this.clearSelection();if(this.selectionIsSane()){this.drawSelection();this.fireSelectEvent();this.ignoreClick=true}},setSelectionPos:function(D,B){var A=this.options,C=$(this.overlay).cumulativeOffset();if(A.selection.mode.indexOf("x")==-1){D.x=(D==this.selection.first)?0:this.plotWidth}else{D.x=B.pageX-C.left-this.plotOffset.left;D.x=Math.min(Math.max(0,D.x),this.plotWidth)}if(A.selection.mode.indexOf("y")==-1){D.y=(D==this.selection.first)?0:this.plotHeight}else{D.y=B.pageY-C.top-this.plotOffset.top;D.y=Math.min(Math.max(0,D.y),this.plotHeight)}},updateSelection:function(){if(this.lastMousePos.pageX==null){return }this.setSelectionPos(this.selection.second,this.lastMousePos);this.clearSelection();if(this.selectionIsSane()){this.drawSelection()}},clearSelection:function(){if(this.prevSelection==null){return }var G=this.prevSelection,E=this.octx,C=this.plotOffset,A=Math.min(G.first.x,G.second.x),F=Math.min(G.first.y,G.second.y),B=Math.abs(G.second.x-G.first.x),D=Math.abs(G.second.y-G.first.y);E.clearRect(A+C.left-E.lineWidth,F+C.top-E.lineWidth,B+E.lineWidth*2,D+E.lineWidth*2);this.prevSelection=null},setSelection:function(G){var B=this.options,H=this.axes.x,A=this.axes.y,F=yaxis.scale,D=xaxis.scale,E=B.selection.mode.indexOf("x")!=-1,C=B.selection.mode.indexOf("y")!=-1;this.clearSelection();this.selection.first.y=E?0:(A.max-G.y1)*F;this.selection.second.y=E?this.plotHeight:(A.max-G.y2)*F;this.selection.first.x=C?0:(G.x1-H.min)*D;this.selection.second.x=C?this.plotWidth:(G.x2-H.min)*D;this.drawSelection();this.fireSelectEvent()},drawSelection:function(){var C=this.prevSelection,F=this.selection,H=this.octx,I=this.options,A=this.plotOffset;if(C!=null&&F.first.x==C.first.x&&F.first.y==C.first.y&&F.second.x==C.second.x&&F.second.y==C.second.y){return }H.strokeStyle=Flotr.parseColor(I.selection.color).scale(null,null,null,0.8).toString();H.lineWidth=1;H.lineJoin="round";H.fillStyle=Flotr.parseColor(I.selection.color).scale(null,null,null,0.4).toString();this.prevSelection={first:{x:F.first.x,y:F.first.y},second:{x:F.second.x,y:F.second.y}};var E=Math.min(F.first.x,F.second.x),D=Math.min(F.first.y,F.second.y),G=Math.abs(F.second.x-F.first.x),B=Math.abs(F.second.y-F.first.y);H.fillRect(E+A.left,D+A.top,G,B);H.strokeRect(E+A.left,D+A.top,G,B)},selectionIsSane:function(){var A=this.selection;return Math.abs(A.second.x-A.first.x)>=5&&Math.abs(A.second.y-A.first.y)>=5},clearHit:function(){if(this.prevHit){var B=this.options,A=this.plotOffset,C=this.prevHit;this.octx.clearRect(this.tHoz(C.x)+A.left-B.points.radius*2,this.tVert(C.y)+A.top-B.points.radius*2,B.points.radius*3+B.points.lineWidth*3,B.points.radius*3+B.points.lineWidth*3);this.prevHit=null}},hit:function(I){var G=this.series,C=this.options,R=this.prevHit,H=this.plotOffset,D=this.octx,S,A,M,Q,L={dist:Number.MAX_VALUE,x:null,y:null,relX:I.relX,relY:I.relY,absX:I.absX,absY:I.absY,mouse:null};for(Q=0;Q');K=this.mouseTrack=this.el.select(".flotr-mouse-value").first()}else{this.mouseTrack=K.setStyle(O)}if(L.x!==null&&L.y!==null){K.show();this.clearHit();if(L.mouse.lineColor!=null){D.save();D.translate(H.left,H.top);D.lineWidth=C.points.lineWidth;D.strokeStyle=L.mouse.lineColor;D.fillStyle="#ffffff";D.beginPath();D.arc(this.tHoz(L.x),this.tVert(L.y),C.mouse.radius,0,2*Math.PI,true);D.fill();D.stroke();D.restore()}this.prevHit=L;var T=L.mouse.trackDecimals;if(T==null||T<0){T=0}K.innerHTML=L.mouse.trackFormatter({x:L.x.toFixed(T),y:L.y.toFixed(T)});K.fire("flotr:hit",[L,this])}else{if(R){K.hide();this.clearHit()}}}},saveImage:function(D,C,A,B){var E=null;switch(D){case"jpeg":case"jpg":E=Canvas2Image.saveAsJPEG(this.canvas,B,C,A);break;default:case"png":E=Canvas2Image.saveAsPNG(this.canvas,B,C,A);break;case"bmp":E=Canvas2Image.saveAsBMP(this.canvas,B,C,A);break}if(Object.isElement(E)&&B){this.restoreCanvas();this.canvas.hide();this.overlay.hide();this.el.insert(E.setStyle({position:"absolute"}))}},restoreCanvas:function(){this.canvas.show();this.overlay.show();this.el.select("img").invoke("remove")}});Flotr.Color=Class.create({initialize:function(E,D,B,C){this.rgba=["r","g","b","a"];var A=4;while(-1<--A){this[this.rgba[A]]=arguments[A]||((A==3)?1:0)}this.normalize()},adjust:function(D,C,E,B){var A=4;while(-1<--A){if(arguments[A]!=null){this[this.rgba[A]]+=arguments[A]}}return this.normalize()},clone:function(){return new Flotr.Color(this.r,this.b,this.g,this.a)},limit:function(B,A,C){return Math.max(Math.min(B,C),A)},normalize:function(){var A=this.limit;this.r=A(parseInt(this.r),0,255);this.g=A(parseInt(this.g),0,255);this.b=A(parseInt(this.b),0,255);this.a=A(this.a,0,1);return this},scale:function(D,C,E,B){var A=4;while(-1<--A){if(arguments[A]!=null){this[this.rgba[A]]*=arguments[A]}}return this.normalize()},distance:function(B){if(!B){return }B=new Flotr.parseColor(B);var C=0;var A=3;while(-1<--A){C+=Math.abs(this[this.rgba[A]]-B[this.rgba[A]])}return C},toString:function(){return(this.a>=1)?"rgb("+[this.r,this.g,this.b].join(",")+")":"rgba("+[this.r,this.g,this.b,this.a].join(",")+")"}});Flotr.Color.lookupColors={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]};Flotr.Date={format:function(F,E){if(!F){return }var A=function(H){H=H.toString();return H.length==1?"0"+H:H};var D=[];var C=false;for(var B=0;B, MIT License. /* $Id: flotr.js 82 2009-01-12 19:19:31Z fabien.menager $ */ var Flotr = { version: '0.2.0-alpha', author: 'Bas Wenneker', website: 'http://www.solutoire.com', /** * An object of the default registered graph types. Use Flotr.register(type, functionName) * to add your own type. */ _registeredTypes:{ 'lines': 'drawSeriesLines', 'points': 'drawSeriesPoints', 'bars': 'drawSeriesBars', 'candles': 'drawSeriesCandles', 'pie': 'drawSeriesPie' }, /** * Can be used to register your own chart type. Default types are 'lines', 'points' and 'bars'. * This is still experimental. * @todo Test and confirm. * @param {String} type - type of chart, like 'pies', 'bars' etc. * @param {String} functionName - Name of the draw function, like 'drawSeriesPies', 'drawSeriesBars' etc. */ register: function(type, functionName){ Flotr._registeredTypes[type] = functionName+''; }, /** * Draws the graph. This function is here for backwards compatibility with Flotr version 0.1.0alpha. * You could also draw graphs by directly calling Flotr.Graph(element, data, options). * @param {Element} el - element to insert the graph into * @param {Object} data - an array or object of dataseries * @param {Object} options - an object containing options * @param {Class} _GraphKlass_ - (optional) Class to pass the arguments to, defaults to Flotr.Graph * @return {Class} returns a new graph object and of course draws the graph. */ draw: function(el, data, options, _GraphKlass_){ _GraphKlass_ = _GraphKlass_ || Flotr.Graph; return new _GraphKlass_(el, data, options); }, /** * Collects dataseries from input and parses the series into the right format. It returns an Array * of Objects each having at least the 'data' key set. * @param {Array/Object} data - Object or array of dataseries * @return {Array} Array of Objects parsed into the right format ({(...,) data: [[x1,y1], [x2,y2], ...] (, ...)}) */ getSeries: function(data){ return data.collect(function(serie){ var i, serie = (serie.data) ? Object.clone(serie) : {'data': serie}; for (i = serie.data.length-1; i > -1; --i) { serie.data[i][1] = (serie.data[i][1] === null ? null : parseFloat(serie.data[i][1])); } return serie; }); }, /** * Recursively merges two objects. * @param {Object} src - source object (likely the object with the least properties) * @param {Object} dest - destination object (optional, object with the most properties) * @return {Object} recursively merged Object */ merge: function(src, dest){ var result = dest || {}; for(var i in src){ result[i] = (src[i] != null && typeof(src[i]) == 'object' && !(src[i].constructor == Array || src[i].constructor == RegExp) && !Object.isElement(src[i])) ? Flotr.merge(src[i], dest[i]) : result[i] = src[i]; } return result; }, /** * Function calculates the ticksize and returns it. * @param {Integer} noTicks - number of ticks * @param {Integer} min - lower bound integer value for the current axis * @param {Integer} max - upper bound integer value for the current axis * @param {Integer} decimals - number of decimals for the ticks * @return {Integer} returns the ticksize in pixels */ getTickSize: function(noTicks, min, max, decimals){ var delta = (max - min) / noTicks; var magn = Flotr.getMagnitude(delta); // Norm is between 1.0 and 10.0. var norm = delta / magn; var tickSize = 10; if(norm < 1.5) tickSize = 1; else if(norm < 2.25) tickSize = 2; else if(norm < 3) tickSize = ((decimals == 0) ? 2 : 2.5); else if(norm < 7.5) tickSize = 5; return tickSize * magn; }, /** * Default tick formatter. * @param {String/Integer} val - tick value integer * @return {String} formatted tick string */ defaultTickFormatter: function(val){ return val+''; }, /** * Formats the mouse tracker values. * @param {Object} obj - Track value Object {x:..,y:..} * @return {String} Formatted track string */ defaultTrackFormatter: function(obj){ return '('+obj.x+', '+obj.y+')'; }, defaultPieLabelFormatter: function(slice) { return (slice.fraction*100).toFixed(2)+'%'; }, /** * Returns the magnitude of the input value. * @param {Integer/Float} x - integer or float value * @return {Integer/Float} returns the magnitude of the input value */ getMagnitude: function(x){ return Math.pow(10, Math.floor(Math.log(x) / Math.LN10)); }, toPixel: function(val){ return Math.floor(val)+0.5;//((val-Math.round(val) < 0.4) ? (Math.floor(val)-0.5) : val); }, toRad: function(angle){ return -angle * (Math.PI/180); }, /** * Parses a color string and returns a corresponding Color. * @param {String} str - string thats representing a color * @return {Color} returns a Color object or false */ parseColor: function(str){ if (str instanceof Flotr.Color) return str; var result, Color = Flotr.Color; // rgb(num,num,num) if((result = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(str))) return new Color(parseInt(result[1]), parseInt(result[2]), parseInt(result[3])); // rgba(num,num,num,num) if((result = /rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))) return new Color(parseInt(result[1]), parseInt(result[2]), parseInt(result[3]), parseFloat(result[4])); // rgb(num%,num%,num%) if((result = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(str))) return new Color(parseFloat(result[1])*2.55, parseFloat(result[2])*2.55, parseFloat(result[3])*2.55); // rgba(num%,num%,num%,num) if((result = /rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))) return new Color(parseFloat(result[1])*2.55, parseFloat(result[2])*2.55, parseFloat(result[3])*2.55, parseFloat(result[4])); // #a0b1c2 if((result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(str))) return new Color(parseInt(result[1],16), parseInt(result[2],16), parseInt(result[3],16)); // #fff if((result = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(str))) return new Color(parseInt(result[1]+result[1],16), parseInt(result[2]+result[2],16), parseInt(result[3]+result[3],16)); // Otherwise, we're most likely dealing with a named color. var name = str.strip().toLowerCase(); if(name == 'transparent'){ return new Color(255, 255, 255, 0); } return ((result = Color.lookupColors[name])) ? new Color(result[0], result[1], result[2]) : false; }, /** * Extracts the background-color of the passed element. * @param {Element} element * @return {String} color string */ extractColor: function(element){ var color; // Loop until we find an element with a background color and stop when we hit the body element. do { color = element.getStyle('background-color').toLowerCase(); if(!(color == '' || color == 'transparent')) break; element = element.up(0); } while(!element.nodeName.match(/^body$/i)); // Catch Safari's way of signaling transparent. return (color == 'rgba(0, 0, 0, 0)') ? 'transparent' : color; } }; /** * Flotr Graph class that plots a graph on creation. */ Flotr.Graph = Class.create({ /** * Flotr Graph constructor. * @param {Element} el - element to insert the graph into * @param {Object} data - an array or object of dataseries * @param {Object} options - an object containing options */ initialize: function(el, data, options){ this.el = $(el); if (!this.el) throw 'The target container doesn\'t exist'; this.data = data; this.series = Flotr.getSeries(data); this.setOptions(options); // Initialize some variables this.lastMousePos = { pageX: null, pageY: null }; this.selection = { first: { x: -1, y: -1}, second: { x: -1, y: -1} }; this.prevSelection = null; this.selectionInterval = null; this.ignoreClick = false; this.prevHit = null; // Create and prepare canvas. this.constructCanvas(); // Add event handlers for mouse tracking, clicking and selection this.initEvents(); this.findDataRanges(); this.calculateTicks(this.axes.x); this.calculateTicks(this.axes.x2); this.calculateTicks(this.axes.y); this.calculateTicks(this.axes.y2); this.calculateSpacing(); this.draw(); this.insertLegend(); // Graph and Data tabs if (this.options.spreadsheet.show) this.constructTabs(); }, /** * Sets options and initializes some variables and color specific values, used by the constructor. * @param {Object} opts - options object */ setOptions: function(opts){ var options = { colors: ['#00A8F0', '#C0D800', '#CB4B4B', '#4DA74D', '#9440ED'], //=> The default colorscheme. When there are > 5 series, additional colors are generated. title: null, subtitle: null, legend: { show: true, // => setting to true will show the legend, hide otherwise noColumns: 1, // => number of colums in legend table // @todo: doesn't work for HtmlText = false labelFormatter: Prototype.K, // => fn: string -> string labelBoxBorderColor: '#CCCCCC', // => border color for the little label boxes labelBoxWidth: 14, labelBoxHeight: 10, labelBoxMargin: 5, container: null, // => container (as jQuery object) to put legend in, null means default on top of graph position: 'nw', // => position of default legend container within plot margin: 5, // => distance from grid edge to default legend container within plot backgroundColor: null, // => null means auto-detect backgroundOpacity: 0.85// => set to 0 to avoid background, set to 1 for a solid background }, xaxis: { ticks: null, // => format: either [1, 3] or [[1, 'a'], 3] showLabels: true, // => setting to true will show the axis ticks labels, hide otherwise labelsAngle: 0, // => Labels' angle, in degrees title: null, // => axis title titleAngle: 0, // => axis title's angle, in degrees noTicks: 5, // => number of ticks for automagically generated ticks tickFormatter: Flotr.defaultTickFormatter, // => fn: number -> string tickDecimals: null, // => no. of decimals, null means auto min: null, // => min. value to show, null means set automatically max: null, // => max. value to show, null means set automatically autoscaleMargin: 0, // => margin in % to add if auto-setting min/max color: null }, x2axis: {}, yaxis: { ticks: null, // => format: either [1, 3] or [[1, 'a'], 3] showLabels: true, // => setting to true will show the axis ticks labels, hide otherwise labelsAngle: 0, // => Labels' angle, in degrees title: null, // => axis title titleAngle: 90, // => axis title's angle, in degrees noTicks: 5, // => number of ticks for automagically generated ticks tickFormatter: Flotr.defaultTickFormatter, // => fn: number -> string tickDecimals: null, // => no. of decimals, null means auto min: null, // => min. value to show, null means set automatically max: null, // => max. value to show, null means set automatically autoscaleMargin: 0, // => margin in % to add if auto-setting min/max color: null }, y2axis: { titleAngle: 270 }, points: { show: false, // => setting to true will show points, false will hide radius: 3, // => point radius (pixels) lineWidth: 2, // => line width in pixels fill: true, // => true to fill the points with a color, false for (transparent) no fill fillColor: '#FFFFFF', // => fill color fillOpacity: 0.4 }, lines: { show: false, // => setting to true will show lines, false will hide lineWidth: 2, // => line width in pixels fill: false, // => true to fill the area from the line to the x axis, false for (transparent) no fill fillColor: null, // => fill color fillOpacity: 0.4 // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill }, bars: { show: false, // => setting to true will show bars, false will hide lineWidth: 2, // => in pixels barWidth: 1, // => in units of the x axis fill: true, // => true to fill the area from the line to the x axis, false for (transparent) no fill fillColor: null, // => fill color fillOpacity: 0.4, // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill horizontal: false, stacked: false }, candles: { show: false, // => setting to true will show candle sticks, false will hide lineWidth: 1, // => in pixels wickLineWidth: 1, // => in pixels candleWidth: 0.6, // => in units of the x axis fill: true, // => true to fill the area from the line to the x axis, false for (transparent) no fill upFillColor: '#00A8F0',// => up sticks fill color downFillColor: '#CB4B4B',// => down sticks fill color fillOpacity: 0.5, // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill barcharts: false // => draw as barcharts (not standard bars but financial barcharts) }, pie: { show: false, // => setting to true will show bars, false will hide lineWidth: 1, // => in pixels fill: true, // => true to fill the area from the line to the x axis, false for (transparent) no fill fillColor: null, // => fill color fillOpacity: 0.6, // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill explode: 6, sizeRatio: 0.6, startAngle: Math.PI/4, labelFormatter: Flotr.defaultPieLabelFormatter, pie3D: false, pie3DviewAngle: (Math.PI/2 * 0.8), pie3DspliceThickness: 20 }, grid: { color: '#545454', // => primary color used for outline and labels backgroundColor: null, // => null for transparent, else color tickColor: '#DDDDDD', // => color used for the ticks labelMargin: 3, // => margin in pixels verticalLines: true, // => whether to show gridlines in vertical direction horizontalLines: true, // => whether to show gridlines in horizontal direction outlineWidth: 2 // => width of the grid outline/border in pixels }, selection: { mode: null, // => one of null, 'x', 'y' or 'xy' color: '#B6D9FF', // => selection box color fps: 20 // => frames-per-second }, mouse: { track: false, // => true to track the mouse, no tracking otherwise position: 'se', // => position of the value box (default south-east) relative: false, // => next to the mouse cursor trackFormatter: Flotr.defaultTrackFormatter, // => formats the values in the value box margin: 5, // => margin in pixels of the valuebox lineColor: '#FF3F19', // => line color of points that are drawn when mouse comes near a value of a series trackDecimals: 1, // => decimals for the track values sensibility: 2, // => the lower this number, the more precise you have to aim to show a value radius: 3 // => radius of the track point }, shadowSize: 4, // => size of the 'fake' shadow defaultType: 'lines', // => default series type HtmlText: true, // => wether to draw the text using HTML or on the canvas fontSize: 7.5, // => canvas' text font size spreadsheet: { show: false, // => show the data grid using two tabs tabGraphLabel: 'Graph', tabDataLabel: 'Data', toolbarDownload: 'Download CSV', // @todo: add language support toolbarSelectAll: 'Select all' } } options.x2axis = Object.extend(Object.clone(options.xaxis), options.x2axis); options.y2axis = Object.extend(Object.clone(options.yaxis), options.y2axis); this.options = Flotr.merge((opts || {}), options); this.axes = { x: {options: this.options.xaxis, n: 1}, x2: {options: this.options.x2axis, n: 2}, y: {options: this.options.yaxis, n: 1}, y2: {options: this.options.y2axis, n: 2} }; // Initialize some variables used throughout this function. var assignedColors = [], colors = [], ln = this.series.length, neededColors = this.series.length, oc = this.options.colors, usedColors = [], variation = 0, c, i, j, s, tooClose; // Collect user-defined colors from series. for(i = neededColors - 1; i > -1; --i){ c = this.series[i].color; if(c != null){ --neededColors; if(Object.isNumber(c)) assignedColors.push(c); else usedColors.push(Flotr.parseColor(c)); } } // Calculate the number of colors that need to be generated. for(i = assignedColors.length - 1; i > -1; --i) neededColors = Math.max(neededColors, assignedColors[i] + 1); // Generate needed number of colors. for(i = 0; colors.length < neededColors;){ c = (oc.length == i) ? new Flotr.Color(100, 100, 100) : Flotr.parseColor(oc[i]); // Make sure each serie gets a different color. var sign = variation % 2 == 1 ? -1 : 1; var factor = 1 + sign * Math.ceil(variation / 2) * 0.2; c.scale(factor, factor, factor); /** * @todo if we're getting too close to something else, we should probably skip this one */ colors.push(c); if(++i >= oc.length){ i = 0; ++variation; } } // Fill the options with the generated colors. for(i = 0, j = 0; i < ln; ++i){ s = this.series[i]; // Assign the color. if(s.color == null){ s.color = colors[j++].toString(); }else if(Object.isNumber(s.color)){ s.color = colors[s.color].toString(); } if (!s.xaxis) s.xaxis = this.axes.x; if (s.xaxis == 1) s.xaxis = this.axes.x; else if (s.xaxis == 2) s.xaxis = this.axes.x2; if (!s.yaxis) s.yaxis = this.axes.y; if (s.yaxis == 1) s.yaxis = this.axes.y; else if (s.yaxis == 2) s.yaxis = this.axes.y2; // Apply missing options to the series. s.lines = Object.extend(Object.clone(this.options.lines), s.lines); s.points = Object.extend(Object.clone(this.options.points), s.points); s.bars = Object.extend(Object.clone(this.options.bars), s.bars); s.candles = Object.extend(Object.clone(this.options.candles), s.candles); s.pie = Object.extend(Object.clone(this.options.pie), s.pie); s.mouse = Object.extend(Object.clone(this.options.mouse), s.mouse); if(s.shadowSize == null) s.shadowSize = this.options.shadowSize; } }, /** * Initializes the canvas and it's overlay canvas element. When the browser is IE, this makes use * of excanvas. The overlay canvas is inserted for displaying interactions. After the canvas elements * are created, the elements are inserted into the container element. */ constructCanvas: function(){ var el = this.el, size, c, oc; this.canvas = el.select('.flotr-canvas')[0]; this.overlay = el.select('.flotr-overlay')[0]; el.childElements().invoke('remove'); // For positioning labels and overlay. el.setStyle({position:'relative', cursor:'default'}); this.canvasWidth = el.getWidth(); this.canvasHeight = el.getHeight(); size = {'width': this.canvasWidth, 'height': this.canvasHeight}; if(this.canvasWidth <= 0 || this.canvasHeight <= 0){ throw 'Invalid dimensions for plot, width = ' + this.canvasWidth + ', height = ' + this.canvasHeight; } // Insert main canvas. if (!this.canvas) { c = this.canvas = new Element('canvas', size); c.className = 'flotr-canvas'; c = c.writeAttribute('style', 'position:absolute;left:0px;top:0px;'); } else { c = this.canvas.writeAttribute(size); } el.insert(c); if(Prototype.Browser.IE){ c = window.G_vmlCanvasManager.initElement(c); } this.ctx = c.getContext('2d'); // Insert overlay canvas for interactive features. if (!this.overlay) { oc = this.overlay = new Element('canvas', size); oc.className = 'flotr-overlay'; oc = oc.writeAttribute('style', 'position:absolute;left:0px;top:0px;'); } else { oc = this.overlay.writeAttribute(size); } el.insert(oc); if(Prototype.Browser.IE){ oc = window.G_vmlCanvasManager.initElement(oc); } this.octx = oc.getContext('2d'); // Enable text functions if (window.CanvasText) { CanvasText.enable(this.ctx); CanvasText.enable(this.octx); this.textEnabled = true; } }, getTextDimensions: function(text, canvasStyle, HtmlStyle, className) { if (!text) return {width:0, height:0}; if (!this.options.HtmlText && this.textEnabled) { var bounds = this.ctx.getTextBounds(text, canvasStyle); return { width: bounds.width+2, height: bounds.height+6 }; } else { var dummyDiv = this.el.insert('
' + text + '
').select(".flotr-dummy-div")[0]; dim = dummyDiv.getDimensions(); dummyDiv.remove(); return dim; } }, loadDataGrid: function(){ if (this.seriesData) return this.seriesData; var s = this.series; var dg = []; /* The data grid is a 2 dimensions array. There is a row for each X value. * Each row contains the x value and the corresponding y value for each serie ('undefined' if there isn't one) **/ for(i = 0; i < s.length; ++i){ s[i].data.each(function(v) { var x = v[0], y = v[1]; if (r = dg.find(function(row) {return row[0] == x})) { r[i+1] = y; } else { var newRow = []; newRow[0] = x; newRow[i+1] = y dg.push(newRow); } }); } // The data grid is sorted by x value dg = dg.sortBy(function(v) { return v[0]; }); return this.seriesData = dg; }, // @todo: make a tab manager (Flotr.Tabs) showTab: function(tabName, onComplete){ var elementsClassNames = 'canvas, .flotr-labels, .flotr-legend, .flotr-legend-bg, .flotr-title, .flotr-subtitle'; switch(tabName) { case 'graph': this.datagrid.up().hide(); this.el.select(elementsClassNames).invoke('show'); this.tabs.data.removeClassName('selected'); this.tabs.graph.addClassName('selected'); break; case 'data': this.constructDataGrid(); this.datagrid.up().show(); this.el.select(elementsClassNames).invoke('hide'); this.tabs.data.addClassName('selected'); this.tabs.graph.removeClassName('selected'); break; } }, constructTabs: function(){ var tabsContainer = new Element('div', {className:'flotr-tabs-group', style:'position:absolute;left:0px;top:'+this.canvasHeight+'px;width:'+this.canvasWidth+'px;'}); this.el.insert({bottom: tabsContainer}); this.tabs = { graph: new Element('div', {className:'flotr-tab selected', style:'float:left;'}).update(this.options.spreadsheet.tabGraphLabel), data: new Element('div', {className:'flotr-tab', style:'float:left;'}).update(this.options.spreadsheet.tabDataLabel) } tabsContainer.insert(this.tabs.graph).insert(this.tabs.data); this.el.setStyle({height: this.canvasHeight+this.tabs.data.getHeight()+2+'px'}); this.tabs.graph.observe('click', (function() {this.showTab('graph')}).bind(this)); this.tabs.data.observe('click', (function() {this.showTab('data')}).bind(this)); }, // @todo: make a spreadsheet manager (Flotr.Spreadsheet) constructDataGrid: function(){ // If the data grid has already been built, nothing to do here if (this.datagrid) return this.datagrid; var i, j, s = this.series, datagrid = this.loadDataGrid(); var t = this.datagrid = new Element('table', {className:'flotr-datagrid', style:'height:100px;'}); var colgroup = ['']; // First row : series' labels var html = ['']; html.push(' '); for (i = 0; i < s.length; ++i) { html.push(''+(s[i].label || String.fromCharCode(65+i))+''); colgroup.push(''); } html.push(''); // Data rows for (j = 0; j < datagrid.length; ++j) { html.push(''); for (i = 0; i < s.length+1; ++i) { var tag = 'td'; var content = (datagrid[j][i] != null ? Math.round(datagrid[j][i]*100000)/100000 : ''); if (i == 0) { tag = 'th'; var label; if(this.options.xaxis.ticks) { var tick = this.options.xaxis.ticks.find(function (x) { return x[0] == datagrid[j][i] }); if (tick) label = tick[1]; } else { label = this.options.xaxis.tickFormatter(content); } if (label) content = label; } html.push('<'+tag+(tag=='th'?' scope="row"':'')+'>'+content+''); } html.push(''); } colgroup.push(''); t.update(colgroup.join('')+html.join('')); if (!Prototype.Browser.IE) { t.select('td').each(function(td) { td.observe('mouseover', function(e){ td = e.element(); var siblings = td.previousSiblings(); t.select('th[scope=col]')[siblings.length-1].addClassName('hover'); t.select('colgroup col')[siblings.length].addClassName('hover'); }); td.observe('mouseout', function(){ t.select('colgroup col.hover, th.hover').each(function(e){e.removeClassName('hover')}); }); }); } var toolbar = new Element('div', {className: 'flotr-datagrid-toolbar'}). insert(new Element('button', {type:'button', className:'flotr-datagrid-toolbar-button'}).update(this.options.spreadsheet.toolbarDownload).observe('click', this.downloadCSV.bind(this))). insert(new Element('button', {type:'button', className:'flotr-datagrid-toolbar-button'}).update(this.options.spreadsheet.toolbarSelectAll).observe('click', this.selectAllData.bind(this))); var container = new Element('div', {className:'flotr-datagrid-container', style:'left:0px;top:0px;width:'+this.canvasWidth+'px;height:'+this.canvasHeight+'px;overflow:auto;'}); container.insert(toolbar); t.wrap(container.hide()); this.el.insert(container); return t; }, selectAllData: function(){ if (this.tabs) { var selection, range, doc, win, node = this.constructDataGrid(); this.showTab('data'); // deferred to be able to select the table (function () { if ((doc = node.ownerDocument) && (win = doc.defaultView) && win.getSelection && doc.createRange && (selection = window.getSelection()) && selection.removeAllRanges) { range = doc.createRange(); range.selectNode(node); selection.removeAllRanges(); selection.addRange(range); } else if (document.body && document.body.createTextRange && (range = document.body.createTextRange())) { range.moveToElementText(node); range.select(); } }).defer(); return true; } else return false; }, downloadCSV: function(){ var i, csv = '"x"', series = this.series, dg = this.loadDataGrid(); for (i = 0; i < series.length; ++i) { csv += '%09"'+(series[i].label || String.fromCharCode(65+i))+'"'; // \t } csv += "%0D%0A"; // \r\n for (i = 0; i < dg.length; ++i) { if (this.options.xaxis.ticks) { var tick = this.options.xaxis.ticks.find(function (x) { return x[0] == dg[i][0] }); if (tick) dg[i][0] = tick[1]; } else { dg[i][0] = this.options.xaxis.tickFormatter(dg[i][0]); } csv += dg[i].join('%09')+"%0D%0A"; // \t and \r\n } if (Prototype.Browser.IE) { csv = csv.gsub('%09', '\t').gsub('%0A', '\n').gsub('%0D', '\r'); window.open().document.write(csv); } else { window.open('data:text/csv,'+csv); } }, /** * Initializes event some handlers. */ initEvents: function () { //@todo: maybe stopObserving with only flotr functions this.overlay.stopObserving(); this.overlay.observe('mousedown', this.mouseDownHandler.bind(this)); this.overlay.observe('mousemove', this.mouseMoveHandler.bind(this)); this.overlay.observe('click', this.clickHandler.bind(this)); }, /** * Function determines the min and max values for the xaxis and yaxis. */ findDataRanges: function(){ var s = this.series, a = this.axes; a.x.datamin = 0; a.x.datamax = 0; a.x2.datamin = 0; a.x2.datamax = 0; a.y.datamin = 0; a.y.datamax = 0; a.y2.datamin = 0; a.y2.datamax = 0; if(s.length > 0){ var i, j, h, x, y, data, xaxis, yaxis; // Get datamin, datamax start values for(i = 0; i < s.length; ++i) { data = s[i].data, xaxis = s[i].xaxis, yaxis = s[i].yaxis; if (data.length > 0 && !s[i].hide) { if (!xaxis.used) xaxis.datamin = xaxis.datamax = data[0][0]; if (!yaxis.used) yaxis.datamin = yaxis.datamax = data[0][1]; xaxis.used = true; yaxis.used = true; for(h = data.length - 1; h > -1; --h){ x = data[h][0]; if(x < xaxis.datamin) xaxis.datamin = x; else if(x > xaxis.datamax) xaxis.datamax = x; for(j = 1; j < data[h].length; j++){ y = data[h][j]; if(y < yaxis.datamin) yaxis.datamin = y; else if(y > yaxis.datamax) yaxis.datamax = y; } } } } } this.findXAxesValues(); this.calculateRange(a.x); this.extendXRangeIfNeededByBar(a.x); if (a.x2.used) { this.calculateRange(a.x2); this.extendXRangeIfNeededByBar(a.x2); } this.calculateRange(a.y); this.extendYRangeIfNeededByBar(a.y); if (a.y2.used) { this.calculateRange(a.y2); this.extendYRangeIfNeededByBar(a.y2); } }, /** * Calculates the range of an axis to apply autoscaling. */ calculateRange: function(axis){ var o = axis.options, min = o.min != null ? o.min : axis.datamin, max = o.max != null ? o.max : axis.datamax, margin; if(max - min == 0.0){ var widen = (max == 0.0) ? 1.0 : 0.01; min -= widen; max += widen; } axis.tickSize = Flotr.getTickSize(o.noTicks, min, max, o.tickDecimals); // Autoscaling. if(o.min == null){ // Add a margin. margin = o.autoscaleMargin; if(margin != 0){ min -= axis.tickSize * margin; // Make sure we don't go below zero if all values are positive. if(min < 0 && axis.datamin >= 0) min = 0; min = axis.tickSize * Math.floor(min / axis.tickSize); } } if(o.max == null){ margin = o.autoscaleMargin; if(margin != 0){ max += axis.tickSize * margin; if(max > 0 && axis.datamax <= 0) max = 0; max = axis.tickSize * Math.ceil(max / axis.tickSize); } } axis.min = min; axis.max = max; }, /** * Bar series autoscaling in x direction. */ extendXRangeIfNeededByBar: function(axis){ if(axis.options.max == null){ var newmax = axis.max, i, s, b, c, stackedSums = [], lastSerie = null; for(i = 0; i < this.series.length; ++i){ s = this.series[i]; b = s.bars; c = s.candles; if(s.axis == axis && (b.show || c.show)) { if (!b.horizontal && (b.barWidth + axis.datamax > newmax) || (c.candleWidth + axis.datamax > newmax)){ newmax = axis.max + s.bars.barWidth; } if(b.stacked && b.horizontal){ for (j = 0; j < s.data.length; j++) { if (s.bars.show && s.bars.stacked) { var x = s.data[j][0]; stackedSums[x] = (stackedSums[x] || 0) + s.data[j][1]; lastSerie = s; } } for (j = 0; j < stackedSums.length; j++) { newmax = Math.max(stackedSums[j], newmax); } } } } axis.lastSerie = lastSerie; axis.max = newmax; } }, /** * Bar series autoscaling in y direction. */ extendYRangeIfNeededByBar: function(axis){ if(axis.options.max == null){ var newmax = axis.max, i, s, b, c, stackedSums = [], lastSerie = null; for(i = 0; i < this.series.length; ++i){ s = this.series[i]; b = s.bars; c = s.candles; if (s.yaxis == axis && b.show && !s.hide) { if (b.horizontal && (b.barWidth + axis.datamax > newmax) || (c.candleWidth + axis.datamax > newmax)){ newmax = axis.max + b.barWidth; } if(b.stacked && !b.horizontal){ for (j = 0; j < s.data.length; j++) { if (s.bars.show && s.bars.stacked) { var x = s.data[j][0]; stackedSums[x] = (stackedSums[x] || 0) + s.data[j][1]; lastSerie = s; } } for (j = 0; j < stackedSums.length; j++) { newmax = Math.max(stackedSums[j], newmax); } } } } axis.lastSerie = lastSerie; axis.max = newmax; } }, /** * Find every values of the x axes */ findXAxesValues: function(){ for(i = this.series.length-1; i > -1 ; --i){ s = this.series[i]; s.xaxis.values = s.xaxis.values || []; for (j = s.data.length-1; j > -1 ; --j){ s.xaxis.values[s.data[j][0]] = {}; } } }, /** * Calculate axis ticks. * @param {Object} axis - axis object * @param {Object} o - axis options */ calculateTicks: function(axis){ var o = axis.options, i, v; axis.ticks = []; if(o.ticks){ var ticks = o.ticks, t, label; if(Object.isFunction(ticks)){ ticks = ticks({min: axis.min, max: axis.max}); } // Clean up the user-supplied ticks, copy them over. for(i = 0; i < ticks.length; ++i){ t = ticks[i]; if(typeof(t) == 'object'){ v = t[0]; label = (t.length > 1) ? t[1] : o.tickFormatter(v); }else{ v = t; label = o.tickFormatter(v); } axis.ticks[i] = { v: v, label: label }; } } else { // Round to nearest multiple of tick size. var start = axis.tickSize * Math.ceil(axis.min / axis.tickSize), decimals; // Then store all possible ticks. for(i = 0; start + i * axis.tickSize <= axis.max; ++i){ v = start + i * axis.tickSize; // Round (this is always needed to fix numerical instability). decimals = o.tickDecimals; if(decimals == null) decimals = 1 - Math.floor(Math.log(axis.tickSize) / Math.LN10); if(decimals < 0) decimals = 0; v = v.toFixed(decimals); axis.ticks.push({ v: v, label: o.tickFormatter(v) }); } } }, /** * Calculates axis label sizes. */ calculateSpacing: function(){ var a = this.axes, options = this.options, series = this.series, margin = options.grid.labelMargin, x = a.x, x2 = a.x2, y = a.y, y2 = a.y2, maxOutset = 2, i, j, l, dim; // Labels width and height [x, x2, y, y2].each(function(axis) { var maxLabel = ''; if (axis.options.showLabels) { for(i = 0; i < axis.ticks.length; ++i){ l = axis.ticks[i].label.length; if(l > maxLabel.length){ maxLabel = axis.ticks[i].label; } } } axis.maxLabel = this.getTextDimensions(maxLabel, {size:options.fontSize, angle: Flotr.toRad(axis.options.labelsAngle)}, 'font-size:smaller;', 'flotr-grid-label'); axis.titleSize = this.getTextDimensions(axis.options.title, {size: options.fontSize*1.2, angle: Flotr.toRad(axis.options.titleAngle)}, 'font-weight:bold;', 'flotr-axis-title'); }, this); // Title height dim = this.getTextDimensions(options.title, {size: options.fontSize*1.5}, 'font-size:1em;font-weight:bold;', 'flotr-title'); this.titleHeight = dim.height; // Subtitle height dim = this.getTextDimensions(options.subtitle, {size: options.fontSize}, 'font-size:smaller;', 'flotr-subtitle'); this.subtitleHeight = dim.height; // Grid outline line width. if(options.show){ maxOutset = Math.max(maxOutset, options.points.radius + options.points.lineWidth/2); } for(j = 0; j < options.length; ++j){ if (series[j].points.show){ maxOutset = Math.max(maxOutset, series[j].points.radius + series[j].points.lineWidth/2); } } var p = this.plotOffset = {left: 0, right: 0, top: 0, bottom: 0}; p.left = p.right = p.top = p.bottom = maxOutset; p.bottom += (x.options.showLabels ? (x.maxLabel.height + margin) : 0) + (x.options.title ? (x.titleSize.height + margin) : 0); p.top += (x2.options.showLabels ? (x2.maxLabel.height + margin) : 0) + (x2.options.title ? (x2.titleSize.height + margin) : 0) + this.subtitleHeight + this.titleHeight; p.left += (y.options.showLabels ? (y.maxLabel.width + margin) : 0) + (y.options.title ? (y.titleSize.width + margin) : 0); p.right += (y2.options.showLabels ? (y2.maxLabel.width + margin) : 0) + (y2.options.title ? (y2.titleSize.width + margin) : 0); p.top = Math.floor(p.top); // In order the outline not to be blured this.plotWidth = this.canvasWidth - p.left - p.right; this.plotHeight = this.canvasHeight - p.bottom - p.top; x.scale = this.plotWidth / (x.max - x.min); x2.scale = this.plotWidth / (x2.max - x2.min); y.scale = this.plotHeight / (y.max - y.min); y2.scale = this.plotHeight / (y2.max - y2.min); }, /** * Draws grid, labels and series. */ draw: function() { this.drawGrid(); this.drawLabels(); this.drawTitles(); if(this.series.length){ this.el.fire('flotr:beforedraw', [this.series, this]); for(var i = 0; i < this.series.length; i++){ if (!this.series[i].hide) this.drawSeries(this.series[i]); } } this.el.fire('flotr:afterdraw', [this.series, this]); }, /** * Translates absolute horizontal x coordinates to relative coordinates. * @param {Integer} x - absolute integer x coordinate * @return {Integer} translated relative x coordinate */ tHoz: function(x, axis){ axis = axis || this.axes.x; return (x - axis.min) * axis.scale; }, /** * Translates absolute vertical x coordinates to relative coordinates. * @param {Integer} y - absolute integer y coordinate * @return {Integer} translated relative y coordinate */ tVert: function(y, axis){ axis = axis || this.axes.y; return this.plotHeight - (y - axis.min) * axis.scale; }, /** * Draws a grid for the graph. */ drawGrid: function(){ var v, o = this.options, ctx = this.ctx; if(o.grid.verticalLines || o.grid.horizontalLines){ this.el.fire('flotr:beforegrid', [this.axes.x, this.axes.y, o, this]); } ctx.save(); ctx.translate(this.plotOffset.left, this.plotOffset.top); // Draw grid background, if present in options. if(o.grid.backgroundColor != null){ ctx.fillStyle = o.grid.backgroundColor; ctx.fillRect(0, 0, this.plotWidth, this.plotHeight); } // Draw grid lines in vertical direction. ctx.lineWidth = 1; ctx.strokeStyle = o.grid.tickColor; ctx.beginPath(); if(o.grid.verticalLines){ for(var i = 0; i < this.axes.x.ticks.length; ++i){ v = this.axes.x.ticks[i].v; // Don't show lines on upper and lower bounds. if ((v == this.axes.x.min || v == this.axes.x.max) && o.grid.outlineWidth != 0) continue; ctx.moveTo(Math.floor(this.tHoz(v)) + ctx.lineWidth/2, 0); ctx.lineTo(Math.floor(this.tHoz(v)) + ctx.lineWidth/2, this.plotHeight); } } // Draw grid lines in horizontal direction. if(o.grid.horizontalLines){ for(var j = 0; j < this.axes.y.ticks.length; ++j){ v = this.axes.y.ticks[j].v; // Don't show lines on upper and lower bounds. if ((v == this.axes.y.min || v == this.axes.y.max) && o.grid.outlineWidth != 0) continue; ctx.moveTo(0, Math.floor(this.tVert(v)) + ctx.lineWidth/2); ctx.lineTo(this.plotWidth, Math.floor(this.tVert(v)) + ctx.lineWidth/2); } } ctx.stroke(); // Draw axis/grid border. if(o.grid.outlineWidth != 0) { ctx.lineWidth = o.grid.outlineWidth; ctx.strokeStyle = o.grid.color; ctx.lineJoin = 'round'; ctx.strokeRect(0, 0, this.plotWidth, this.plotHeight); } ctx.restore(); if(o.grid.verticalLines || o.grid.horizontalLines){ this.el.fire('flotr:aftergrid', [this.axes.x, this.axes.y, o, this]); } }, /** * Draws labels for x and y axis. */ drawLabels: function(){ // Construct fixed width label boxes, which can be styled easily. var noLabels = 0, axis, xBoxWidth, i, html, tick, options = this.options, ctx = this.ctx, a = this.axes; for(i = 0; i < a.x.ticks.length; ++i){ if (a.x.ticks[i].label) { ++noLabels; } } xBoxWidth = this.plotWidth / noLabels; if (!options.HtmlText && this.textEnabled) { var style = { size: options.fontSize, adjustAlign: true }; // Add x labels. axis = a.x; style.color = axis.options.color || options.grid.color; for(i = 0; i < axis.ticks.length && axis.options.showLabels && axis.used; ++i){ tick = axis.ticks[i]; if(!tick.label || tick.label.length == 0) continue; style.angle = Flotr.toRad(axis.options.labelsAngle); style.halign = 'c'; style.valign = 't'; ctx.drawText( tick.label, this.plotOffset.left + this.tHoz(tick.v, axis), this.plotOffset.top + this.plotHeight + options.grid.labelMargin, style ); } // Add x2 labels. axis = a.x2; style.color = axis.options.color || options.grid.color; for(i = 0; i < axis.ticks.length && axis.options.showLabels && axis.used; ++i){ tick = axis.ticks[i]; if(!tick.label || tick.label.length == 0) continue; style.angle = Flotr.toRad(axis.options.labelsAngle); style.halign = 'c'; style.valign = 'b'; ctx.drawText( tick.label, this.plotOffset.left + this.tHoz(tick.v, axis), this.plotOffset.top + options.grid.labelMargin, style ); } // Add y labels. axis = a.y; style.color = axis.options.color || options.grid.color; for(i = 0; i < axis.ticks.length && axis.options.showLabels && axis.used; ++i){ tick = axis.ticks[i]; if (!tick.label || tick.label.length == 0) continue; style.angle = Flotr.toRad(axis.options.labelsAngle); style.halign = 'r'; style.valign = 'm'; ctx.drawText( tick.label, this.plotOffset.left - options.grid.labelMargin, this.plotOffset.top + this.tVert(tick.v, axis), style ); } // Add y2 labels. axis = a.y2; style.color = axis.options.color || options.grid.color; for(i = 0; i < axis.ticks.length && axis.options.showLabels && axis.used; ++i){ tick = axis.ticks[i]; if (!tick.label || tick.label.length == 0) continue; style.angle = Flotr.toRad(axis.options.labelsAngle); style.halign = 'l'; style.valign = 'm'; ctx.drawText( tick.label, this.plotOffset.left + this.plotWidth + options.grid.labelMargin, this.plotOffset.top + this.tVert(tick.v, axis), style ); ctx.save(); ctx.strokeStyle = style.color; ctx.beginPath(); ctx.moveTo(this.plotOffset.left + this.plotWidth - 8, this.plotOffset.top + this.tVert(tick.v, axis)); ctx.lineTo(this.plotOffset.left + this.plotWidth, this.plotOffset.top + this.tVert(tick.v, axis)); ctx.stroke(); ctx.restore(); } } else if (a.x.options.showLabels || a.x2.options.showLabels || a.y.options.showLabels || a.y2.options.showLabels) { html = ['
']; // Add x labels. axis = a.x; if (axis.options.showLabels){ for(i = 0; i < axis.ticks.length; ++i){ tick = axis.ticks[i]; if(!tick.label || tick.label.length == 0) continue; html.push('
' + tick.label + '
'); } } // Add x2 labels. axis = a.x2; if (axis.options.showLabels && axis.used){ for(i = 0; i < axis.ticks.length; ++i){ tick = axis.ticks[i]; if(!tick.label || tick.label.length == 0) continue; html.push('
' + tick.label + '
'); } } // Add y labels. axis = a.y; if (axis.options.showLabels){ for(i = 0; i < axis.ticks.length; ++i){ tick = axis.ticks[i]; if (!tick.label || tick.label.length == 0) continue; html.push('
' + tick.label + '
'); } } // Add y2 labels. axis = a.y2; if (axis.options.showLabels && axis.used){ ctx.save(); ctx.strokeStyle = axis.options.color || options.grid.color; ctx.beginPath(); for(i = 0; i < axis.ticks.length; ++i){ tick = axis.ticks[i]; if (!tick.label || tick.label.length == 0) continue; html.push('
' + tick.label + '
'); ctx.moveTo(this.plotOffset.left + this.plotWidth - 8, this.plotOffset.top + this.tVert(tick.v, axis)); ctx.lineTo(this.plotOffset.left + this.plotWidth, this.plotOffset.top + this.tVert(tick.v, axis)); } ctx.stroke(); ctx.restore(); } html.push('
'); this.el.insert(html.join('')); } }, /** * Draws the title and the subtitle */ drawTitles: function(){ var html, options = this.options, margin = options.grid.labelMargin, ctx = this.ctx, a = this.axes; if (!options.HtmlText && this.textEnabled) { var style = { size: options.fontSize, color: options.grid.color, halign: 'c' }; // Add subtitle if (options.subtitle){ ctx.drawText( options.subtitle, this.plotOffset.left + this.plotWidth/2, this.titleHeight + this.subtitleHeight - 2, style ); } style.weight = 1.5; style.size *= 1.5; // Add title if (options.title){ ctx.drawText( options.title, this.plotOffset.left + this.plotWidth/2, this.titleHeight - 2, style ); } style.weight = 1.8; style.size *= 0.8; style.adjustAlign = true; // Add x axis title if (a.x.options.title && a.x.used){ style.halign = 'c'; style.valign = 't'; style.angle = Flotr.toRad(a.x.options.titleAngle); ctx.drawText( a.x.options.title, this.plotOffset.left + this.plotWidth/2, this.plotOffset.top + a.x.maxLabel.height + this.plotHeight + 2 * margin, style ); } // Add x2 axis title if (a.x2.options.title && a.x2.used){ style.halign = 'c'; style.valign = 'b'; style.angle = Flotr.toRad(a.x2.options.titleAngle); ctx.drawText( a.x2.options.title, this.plotOffset.left + this.plotWidth/2, this.plotOffset.top - a.x2.maxLabel.height - 2 * margin, style ); } // Add y axis title if (a.y.options.title && a.y.used){ style.halign = 'r'; style.valign = 'm'; style.angle = Flotr.toRad(a.y.options.titleAngle); ctx.drawText( a.y.options.title, this.plotOffset.left - a.y.maxLabel.width - 2 * margin, this.plotOffset.top + this.plotHeight / 2, style ); } // Add y2 axis title if (a.y2.options.title && a.y2.used){ style.halign = 'l'; style.valign = 'm'; style.angle = Flotr.toRad(a.y2.options.titleAngle); ctx.drawText( a.y2.options.title, this.plotOffset.left + this.plotWidth + a.y2.maxLabel.width + 2 * margin, this.plotOffset.top + this.plotHeight / 2, style ); } } else { html = ['
']; // Add title if (options.title){ html.push('
'+options.title+'
'); } // Add subtitle if (options.subtitle){ html.push('
'+options.subtitle+'
'); } html.push('
'); html.push('
'); // Add x axis title if (a.x.options.title && a.x.used){ html.push('
' + a.x.options.title + '
'); } // Add x2 axis title if (a.x2.options.title && a.x2.used){ html.push('
' + a.x2.options.title + '
'); } // Add y axis title if (a.y.options.title && a.y.used){ html.push('
' + a.y.options.title + '
'); } // Add y2 axis title if (a.y2.options.title && a.y2.used){ html.push('
' + a.y2.options.title + '
'); } html.push('
'); this.el.insert(html.join('')); } }, /** * Actually draws the graph. * @param {Object} series - series to draw */ drawSeries: function(series){ series = series || this.series; var drawn = false; for(var type in Flotr._registeredTypes){ if(series[type] && series[type].show){ this[Flotr._registeredTypes[type]](series); drawn = true; } } if(!drawn){ this[Flotr._registeredTypes[this.options.defaultType]](series); } }, plotLine: function(series, offset){ var ctx = this.ctx, xa = series.xaxis, ya = series.yaxis, tHoz = this.tHoz.bind(this), tVert = this.tVert.bind(this), data = series.data; if(data.length < 2) return; var prevx = tHoz(data[0][0], xa), prevy = tVert(data[0][1], ya) + offset; ctx.beginPath(); ctx.moveTo(prevx, prevy); for(var i = 0; i < data.length - 1; ++i){ var x1 = data[i][0], y1 = data[i][1], x2 = data[i+1][0], y2 = data[i+1][1]; // To allow empty values if (y1 === null || y2 === null) continue; /** * Clip with ymin. */ if(y1 <= y2 && y1 < ya.min){ /** * Line segment is outside the drawing area. */ if(y2 < ya.min) continue; /** * Compute new intersection point. */ x1 = (ya.min - y1) / (y2 - y1) * (x2 - x1) + x1; y1 = ya.min; }else if(y2 <= y1 && y2 < ya.min){ if(y1 < ya.min) continue; x2 = (ya.min - y1) / (y2 - y1) * (x2 - x1) + x1; y2 = ya.min; } /** * Clip with ymax. */ if(y1 >= y2 && y1 > ya.max) { if(y2 > ya.max) continue; x1 = (ya.max - y1) / (y2 - y1) * (x2 - x1) + x1; y1 = ya.max; } else if(y2 >= y1 && y2 > ya.max){ if(y1 > ya.max) continue; x2 = (ya.max - y1) / (y2 - y1) * (x2 - x1) + x1; y2 = ya.max; } /** * Clip with xmin. */ if(x1 <= x2 && x1 < xa.min){ if(x2 < xa.min) continue; y1 = (xa.min - x1) / (x2 - x1) * (y2 - y1) + y1; x1 = xa.min; }else if(x2 <= x1 && x2 < xa.min){ if(x1 < xa.min) continue; y2 = (xa.min - x1) / (x2 - x1) * (y2 - y1) + y1; x2 = xa.min; } /** * Clip with xmax. */ if(x1 >= x2 && x1 > xa.max){ if (x2 > xa.max) continue; y1 = (xa.max - x1) / (x2 - x1) * (y2 - y1) + y1; x1 = xa.max; }else if(x2 >= x1 && x2 > xa.max){ if(x1 > xa.max) continue; y2 = (xa.max - x1) / (x2 - x1) * (y2 - y1) + y1; x2 = xa.max; } if(prevx != tHoz(x1, xa) || prevy != tVert(y1, ya) + offset) ctx.moveTo(tHoz(x1, xa), tVert(y1, ya) + offset); prevx = tHoz(x2, xa); prevy = tVert(y2, ya) + offset; ctx.lineTo(prevx, prevy); } ctx.stroke(); }, /** * Function used to fill * @param {Object} data */ plotLineArea: function(series, offset){ var data = series.data; if(data.length < 2) return; var top, lastX = 0, ctx = this.ctx, xa = series.xaxis, ya = series.yaxis, tHoz = this.tHoz.bind(this), tVert = this.tVert.bind(this), bottom = Math.min(Math.max(0, ya.min), ya.max), first = true; ctx.beginPath(); for(var i = 0; i < data.length - 1; ++i){ var x1 = data[i][0], y1 = data[i][1], x2 = data[i+1][0], y2 = data[i+1][1]; if(x1 <= x2 && x1 < xa.min){ if(x2 < xa.min) continue; y1 = (xa.min - x1) / (x2 - x1) * (y2 - y1) + y1; x1 = xa.min; }else if(x2 <= x1 && x2 < xa.min){ if(x1 < xa.min) continue; y2 = (xa.min - x1) / (x2 - x1) * (y2 - y1) + y1; x2 = xa.min; } if(x1 >= x2 && x1 > xa.max){ if(x2 > xa.max) continue; y1 = (xa.max - x1) / (x2 - x1) * (y2 - y1) + y1; x1 = xa.max; }else if(x2 >= x1 && x2 > xa.max){ if (x1 > xa.max) continue; y2 = (xa.max - x1) / (x2 - x1) * (y2 - y1) + y1; x2 = xa.max; } if(first){ ctx.moveTo(tHoz(x1, xa), tVert(bottom, ya) + offset); first = false; } /** * Now check the case where both is outside. */ if(y1 >= ya.max && y2 >= ya.max){ ctx.lineTo(tHoz(x1, xa), tVert(ya.max, ya) + offset); ctx.lineTo(tHoz(x2, xa), tVert(ya.max, ya) + offset); continue; }else if(y1 <= ya.min && y2 <= ya.min){ ctx.lineTo(tHoz(x1, xa), tVert(ya.min, ya) + offset); ctx.lineTo(tHoz(x2, xa), tVert(ya.min, ya) + offset); continue; } /** * Else it's a bit more complicated, there might * be two rectangles and two triangles we need to fill * in; to find these keep track of the current x values. */ var x1old = x1, x2old = x2; /** * And clip the y values, without shortcutting. * Clip with ymin. */ if(y1 <= y2 && y1 < ya.min && y2 >= ya.min){ x1 = (ya.min - y1) / (y2 - y1) * (x2 - x1) + x1; y1 = ya.min; }else if(y2 <= y1 && y2 < ya.min && y1 >= ya.min){ x2 = (ya.min - y1) / (y2 - y1) * (x2 - x1) + x1; y2 = ya.min; } /** * Clip with ymax. */ if(y1 >= y2 && y1 > ya.max && y2 <= ya.max){ x1 = (ya.max - y1) / (y2 - y1) * (x2 - x1) + x1; y1 = ya.max; }else if(y2 >= y1 && y2 > ya.max && y1 <= ya.max){ x2 = (ya.max - y1) / (y2 - y1) * (x2 - x1) + x1; y2 = ya.max; } /** * If the x value was changed we got a rectangle to fill. */ if(x1 != x1old){ top = (y1 <= ya.min) ? top = ya.min : ya.max; ctx.lineTo(tHoz(x1old, xa), tVert(top, ya) + offset); ctx.lineTo(tHoz(x1, xa), tVert(top, ya) + offset); } /** * Fill the triangles. */ ctx.lineTo(tHoz(x1, xa), tVert(y1, ya) + offset); ctx.lineTo(tHoz(x2, xa), tVert(y2, ya) + offset); /** * Fill the other rectangle if it's there. */ if(x2 != x2old){ top = (y2 <= ya.min) ? ya.min : ya.max; ctx.lineTo(tHoz(x2old, xa), tVert(top, ya) + offset); ctx.lineTo(tHoz(x2, xa), tVert(top, ya) + offset); } lastX = Math.max(x2, x2old); } ctx.lineTo(tHoz(lastX, xa), tVert(bottom, ya) + offset); ctx.closePath(); ctx.fill(); }, /** * Function: (private) drawSeriesLines * * Function draws lines series in the canvas element. * * Parameters: * series - Series with options.lines.show = true. * * Returns: * void */ drawSeriesLines: function(series){ series = series || this.series; var ctx = this.ctx; ctx.save(); ctx.translate(this.plotOffset.left, this.plotOffset.top); ctx.lineJoin = 'round'; var lw = series.lines.lineWidth; var sw = series.shadowSize; if(sw > 0){ ctx.lineWidth = sw / 2; var offset = lw/2 + ctx.lineWidth/2; ctx.strokeStyle = "rgba(0,0,0,0.1)"; this.plotLine(series, offset + sw/2); ctx.strokeStyle = "rgba(0,0,0,0.2)"; this.plotLine(series, offset); if(series.lines.fill) { ctx.fillStyle = "rgba(0,0,0,0.05)"; this.plotLineArea(series, offset + sw/2); } } ctx.lineWidth = lw; ctx.strokeStyle = series.color; if(series.lines.fill){ ctx.fillStyle = series.lines.fillColor != null ? series.lines.fillColor : Flotr.parseColor(series.color).scale(null, null, null, series.lines.fillOpacity).toString(); this.plotLineArea(series, 0); } this.plotLine(series, 0); ctx.restore(); }, /** * Function: drawSeriesPoints * * Function draws point series in the canvas element. * * Parameters: * series - Series with options.points.show = true. * * Returns: * void */ drawSeriesPoints: function(series) { var ctx = this.ctx; ctx.save(); ctx.translate(this.plotOffset.left, this.plotOffset.top); var lw = series.lines.lineWidth; var sw = series.shadowSize; if(sw > 0){ ctx.lineWidth = sw / 2; ctx.strokeStyle = 'rgba(0,0,0,0.1)'; this.plotPointShadows(series, sw/2 + ctx.lineWidth/2, series.points.radius); ctx.strokeStyle = 'rgba(0,0,0,0.2)'; this.plotPointShadows(series, ctx.lineWidth/2, series.points.radius); } ctx.lineWidth = series.points.lineWidth; ctx.strokeStyle = series.color; ctx.fillStyle = series.points.fillColor != null ? series.points.fillColor : series.color; this.plotPoints(series, series.points.radius, series.points.fill); ctx.restore(); }, plotPoints: function (series, radius, fill) { var xa = series.xaxis, ya = series.yaxis, ctx = this.ctx, i, data = series.data; for(i = data.length - 1; i > -1; --i){ var x = data[i][0], y = data[i][1]; if(x < xa.min || x > xa.max || y < ya.min || y > ya.max) continue; ctx.beginPath(); ctx.arc(this.tHoz(x, xa), this.tVert(y, ya), radius, 0, 2 * Math.PI, true); if(fill) ctx.fill(); ctx.stroke(); } }, plotPointShadows: function(series, offset, radius){ var xa = series.xaxis, ya = series.yaxis, ctx = this.ctx, i, data = series.data; for(i = data.length - 1; i > -1; --i){ var x = data[i][0], y = data[i][1]; if (x < xa.min || x > xa.max || y < ya.min || y > ya.max) continue; ctx.beginPath(); ctx.arc(this.tHoz(x, xa), this.tVert(y, ya) + offset, radius, 0, Math.PI, false); ctx.stroke(); } }, /** * Function: drawSeriesBars * * Function draws bar series in the canvas element. * * Parameters: * series - Series with options.bars.show = true. * * Returns: * void */ drawSeriesBars: function(series) { var ctx = this.ctx, bw = series.bars.barWidth, lw = Math.min(series.bars.lineWidth, bw); ctx.save(); ctx.translate(this.plotOffset.left, this.plotOffset.top); ctx.lineJoin = 'miter'; /** * @todo linewidth not interpreted the right way. */ ctx.lineWidth = lw; ctx.strokeStyle = series.color; this.plotBarsShadows(series, bw, 0, series.bars.fill); if(series.bars.fill){ ctx.fillStyle = series.bars.fillColor != null ? series.bars.fillColor : Flotr.parseColor(series.color).scale(null, null, null, series.bars.fillOpacity).toString(); } this.plotBars(series, bw, 0, series.bars.fill); ctx.restore(); }, plotBars: function(series, barWidth, offset, fill){ var data = series.data; if(data.length < 1) return; var xa = series.xaxis, ya = series.yaxis, ctx = this.ctx, tHoz = this.tHoz.bind(this), tVert = this.tVert.bind(this); for(var i = 0; i < data.length; i++){ var x = data[i][0], y = data[i][1]; var drawLeft = true, drawTop = true, drawRight = true; // Stacked bars var stackOffset = 0; if(series.bars.stacked) { xa.values.each(function(o, v) { if (v == x) { stackOffset = o.stack || 0; o.stack = stackOffset + y; } }); } // @todo: fix horizontal bars support // Horizontal bars if(series.bars.horizontal) var left = stackOffset, right = x + stackOffset, bottom = y, top = y + barWidth; else var left = x, right = x + barWidth, bottom = stackOffset, top = y + stackOffset; if(right < xa.min || left > xa.max || top < ya.min || bottom > ya.max) continue; if(left < xa.min){ left = xa.min; drawLeft = false; } if(right > xa.max){ right = xa.max; if (xa.lastSerie != series && series.bars.horizontal) drawTop = false; } if(bottom < ya.min) bottom = ya.min; if(top > ya.max){ top = ya.max; if (ya.lastSerie != series && !series.bars.horizontal) drawTop = false; } /** * Fill the bar. */ if(fill){ ctx.beginPath(); ctx.moveTo(tHoz(left, xa), tVert(bottom, ya) + offset); ctx.lineTo(tHoz(left, xa), tVert(top, ya) + offset); ctx.lineTo(tHoz(right, xa), tVert(top, ya) + offset); ctx.lineTo(tHoz(right, xa), tVert(bottom, ya) + offset); ctx.fill(); } /** * Draw bar outline/border. */ if(series.bars.lineWidth != 0 && (drawLeft || drawRight || drawTop)){ ctx.beginPath(); ctx.moveTo(tHoz(left, xa), tVert(bottom, ya) + offset); ctx[drawLeft ?'lineTo':'moveTo'](tHoz(left, xa), tVert(top, ya) + offset); ctx[drawTop ?'lineTo':'moveTo'](tHoz(right, xa), tVert(top, ya) + offset); ctx[drawRight?'lineTo':'moveTo'](tHoz(right, xa), tVert(bottom, ya) + offset); ctx.stroke(); } } }, plotBarsShadows: function(series, barWidth, offset){ var data = series.data; if(data.length < 1) return; var xa = series.xaxis, ya = series.yaxis, ctx = this.ctx, tHoz = this.tHoz.bind(this), tVert = this.tVert.bind(this), sw = this.options.shadowSize; for(var i = 0; i < data.length; i++){ var x = data[i][0], y = data[i][1]; // Stacked bars var stackOffset = 0; if(series.bars.stacked) { xa.values.each(function(o, v) { if (v == x) { stackOffset = o.stackShadow || 0; o.stackShadow = stackOffset + y; } }); } // Horizontal bars if(series.bars.horizontal) var left = stackOffset, right = x + stackOffset, bottom = y, top = y + barWidth; else var left = x, right = x + barWidth, bottom = stackOffset, top = y + stackOffset; if(right < xa.min || left > xa.max || top < ya.min || bottom > ya.max) continue; if(left < xa.min) left = xa.min; if(right > xa.max) right = xa.max; if(bottom < ya.min) bottom = ya.min; if(top > ya.max) top = ya.max; var width = tHoz(right, xa)-tHoz(left, xa)-((tHoz(right, xa)+sw <= this.plotWidth) ? 0 : sw); var height = Math.max(0, tVert(bottom, ya)-tVert(top, ya)-((tVert(bottom, ya)+sw <= this.plotHeight) ? 0 : sw)); ctx.fillStyle = 'rgba(0,0,0,0.05)'; ctx.fillRect(Math.min(tHoz(left, xa)+sw, this.plotWidth), Math.min(tVert(top, ya)+sw, this.plotWidth), width, height); } }, /** * Function: drawSeriesCandles * * Function draws candles series in the canvas element. * * Parameters: * series - Series with options.candles.show = true. * * Returns: * void */ drawSeriesCandles: function(series) { var ctx = this.ctx, bw = series.candles.candleWidth; ctx.save(); ctx.translate(this.plotOffset.left, this.plotOffset.top); ctx.lineJoin = 'miter'; /** * @todo linewidth not interpreted the right way. */ ctx.lineWidth = series.candles.lineWidth; this.plotCandlesShadows(series, bw/2); this.plotCandles(series, bw/2); ctx.restore(); }, plotCandles: function(series, offset){ var data = series.data; if(data.length < 1) return; var xa = series.xaxis, ya = series.yaxis, ctx = this.ctx, tHoz = this.tHoz.bind(this), tVert = this.tVert.bind(this); for(var i = 0; i < data.length; i++){ var d = data[i], x = d[0], open = d[1], high = d[2], low = d[3], close = d[4]; var left = x, right = x + series.candles.candleWidth, bottom = Math.max(ya.min, low), top = Math.min(ya.max, high), bottom2 = Math.max(ya.min, Math.min(open, close)), top2 = Math.min(ya.max, Math.max(open, close)); if(right < xa.min || left > xa.max || top < ya.min || bottom > ya.max) continue; var color = series.candles[open>close?'downFillColor':'upFillColor']; /** * Fill the candle. */ if(series.candles.fill && !series.candles.barcharts){ ctx.fillStyle = Flotr.parseColor(color).scale(null, null, null, series.candles.fillOpacity).toString(); ctx.fillRect(tHoz(left, xa), tVert(top2, ya) + offset, tHoz(right, xa) - tHoz(left, xa), tVert(bottom2, ya) - tVert(top2, ya)); } /** * Draw candle outline/border, high, low. */ if(series.candles.lineWidth || series.candles.wickLineWidth){ var x, y, pixelOffset = (series.candles.wickLineWidth % 2) / 2; x = Math.floor(tHoz((left + right) / 2), xa) + pixelOffset; ctx.save(); ctx.strokeStyle = color; ctx.lineWidth = series.candles.wickLineWidth; ctx.lineCap = 'butt'; if (series.candles.barcharts) { ctx.beginPath(); ctx.moveTo(x, Math.floor(tVert(top, ya) + offset)); ctx.lineTo(x, Math.floor(tVert(bottom, ya) + offset)); y = Math.floor(tVert(open, ya) + offset)+0.5; ctx.moveTo(Math.floor(tHoz(left, xa))+pixelOffset, y); ctx.lineTo(x, y); y = Math.floor(tVert(close, ya) + offset)+0.5; ctx.moveTo(Math.floor(tHoz(right, xa))+pixelOffset, y); ctx.lineTo(x, y); } else { ctx.strokeRect(tHoz(left, xa), tVert(top2, ya) + offset, tHoz(right, xa) - tHoz(left, xa), tVert(bottom2, ya) - tVert(top2, ya)); ctx.beginPath(); ctx.moveTo(x, Math.floor(tVert(top2, ya) + offset)); ctx.lineTo(x, Math.floor(tVert(top, ya) + offset)); ctx.moveTo(x, Math.floor(tVert(bottom2, ya) + offset)); ctx.lineTo(x, Math.floor(tVert(bottom, ya) + offset)); } ctx.stroke(); ctx.restore(); } } }, plotCandlesShadows: function(series, offset){ var data = series.data; if(data.length < 1 || series.candles.barcharts) return; var xa = series.xaxis, ya = series.yaxis, tHoz = this.tHoz.bind(this), tVert = this.tVert.bind(this), sw = this.options.shadowSize; for(var i = 0; i < data.length; i++){ var d = data[i], x = d[0], open = d[1], high = d[2], low = d[3], close = d[4]; var left = x, right = x + series.candles.candleWidth, bottom = Math.max(ya.min, Math.min(open, close)), top = Math.min(ya.max, Math.max(open, close)); if(right < xa.min || left > xa.max || top < ya.min || bottom > ya.max) continue; var width = tHoz(right, xa)-tHoz(left, xa)-((tHoz(right, xa)+sw <= this.plotWidth) ? 0 : sw); var height = Math.max(0, tVert(bottom, ya)-tVert(top, ya)-((tVert(bottom, ya)+sw <= this.plotHeight) ? 0 : sw)); this.ctx.fillStyle = 'rgba(0,0,0,0.05)'; this.ctx.fillRect(Math.min(tHoz(left, xa)+sw, this.plotWidth), Math.min(tVert(top, ya)+sw, this.plotWidth), width, height); } }, /** * Function: drawSeriesPie * * Function draws a pie in the canvas element. * * Parameters: * series - Series with options.pie.show = true. * * Returns: * void */ drawSeriesPie: function(series) { if (!this.options.pie.drawn) { var ctx = this.ctx, options = this.options, lw = series.pie.lineWidth, sw = series.shadowSize, data = series.data, radius = (Math.min(this.canvasWidth, this.canvasHeight) * series.pie.sizeRatio) / 2, html = []; var vScale = 1;//Math.cos(series.pie.viewAngle); var plotTickness = Math.sin(series.pie.viewAngle)*series.pie.spliceThickness / vScale; var style = { size: options.fontSize*1.2, color: options.grid.color, weight: 1.5 }; var center = { x: (this.canvasWidth+this.plotOffset.left)/2, y: (this.canvasHeight-this.plotOffset.bottom)/2 }; // Pie portions var portions = this.series.collect(function(hash, index){ if (hash.pie.show) return { name: (hash.label || hash.data[0][1]), value: [index, hash.data[0][1]], explode: hash.pie.explode }; }); // Sum of the portions' angles var sum = portions.pluck('value').pluck(1).inject(0, function(acc, n) { return acc + n; }); var fraction = 0.0, angle = series.pie.startAngle, value = 0.0; var slices = portions.collect(function(slice){ angle += fraction; value = parseFloat(slice.value[1]); // @warning : won't support null values !! fraction = value/sum; return { name: slice.name, fraction: fraction, x: slice.value[0], y: value, explode: slice.explode, startAngle: 2 * angle * Math.PI, endAngle: 2 * (angle + fraction) * Math.PI }; }); ctx.save(); if(sw > 0){ slices.each(function (slice) { var bisection = (slice.startAngle + slice.endAngle) / 2; var xOffset = center.x + Math.cos(bisection) * slice.explode + sw; var yOffset = center.y + Math.sin(bisection) * slice.explode + sw; this.plotSlice(xOffset, yOffset, radius, slice.startAngle, slice.endAngle, false, vScale); ctx.fillStyle = 'rgba(0,0,0,0.1)'; ctx.fill(); }, this); } if (options.HtmlText) { html = ['
']; } slices.each(function (slice, index) { var bisection = (slice.startAngle + slice.endAngle) / 2; var color = options.colors[index]; var xOffset = center.x + Math.cos(bisection) * slice.explode; var yOffset = center.y + Math.sin(bisection) * slice.explode; this.plotSlice(xOffset, yOffset, radius, slice.startAngle, slice.endAngle, false, vScale); if(series.pie.fill){ ctx.fillStyle = Flotr.parseColor(color).scale(null, null, null, series.pie.fillOpacity).toString(); ctx.fill(); } ctx.lineWidth = lw; ctx.strokeStyle = color; ctx.stroke(); /*ctx.save(); ctx.scale(1, vScale); ctx.moveTo(xOffset, yOffset); ctx.beginPath(); ctx.lineTo(xOffset, yOffset+plotTickness); ctx.lineTo(xOffset+Math.cos(slice.startAngle)*radius, yOffset+Math.sin(slice.startAngle)*radius+plotTickness); ctx.lineTo(xOffset+Math.cos(slice.startAngle)*radius, yOffset+Math.sin(slice.startAngle)*radius); ctx.lineTo(xOffset, yOffset); ctx.closePath(); ctx.fill();ctx.stroke(); ctx.moveTo(xOffset, yOffset); ctx.beginPath(); ctx.lineTo(xOffset, yOffset+plotTickness); ctx.lineTo(xOffset+Math.cos(slice.endAngle)*radius, yOffset+Math.sin(slice.endAngle)*radius+plotTickness); ctx.lineTo(xOffset+Math.cos(slice.endAngle)*radius, yOffset+Math.sin(slice.endAngle)*radius); ctx.lineTo(xOffset, yOffset); ctx.closePath(); ctx.fill();ctx.stroke(); ctx.moveTo(xOffset+Math.cos(slice.startAngle)*radius, yOffset+Math.sin(slice.startAngle)*radius); ctx.beginPath(); ctx.lineTo(xOffset+Math.cos(slice.startAngle)*radius, yOffset+Math.sin(slice.startAngle)*radius+plotTickness); ctx.arc(xOffset, yOffset+plotTickness, radius, slice.startAngle, slice.endAngle, false); ctx.lineTo(xOffset+Math.cos(slice.endAngle)*radius, yOffset+Math.sin(slice.endAngle)*radius); ctx.arc(xOffset, yOffset, radius, slice.endAngle, slice.startAngle, true); ctx.closePath(); ctx.fill();ctx.stroke(); ctx.scale(1, 1/vScale); this.plotSlice(xOffset, yOffset+plotTickness, radius, slice.startAngle, slice.endAngle, false, vScale); ctx.stroke(); if(series.pie.fill){ ctx.fillStyle = Flotr.parseColor(color).scale(null, null, null, series.pie.fillOpacity).toString(); ctx.fill(); } ctx.restore();*/ var label = options.pie.labelFormatter(slice); var textAlignRight = (Math.cos(bisection) < 0); var distX = xOffset + Math.cos(bisection) * (series.pie.explode + radius); var distY = yOffset + Math.sin(bisection) * (series.pie.explode + radius); if (slice.fraction && label) { if (options.HtmlText) { var divStyle = 'position:absolute;top:' + (distY - 5) + 'px;'; //@todo: change if (textAlignRight) { divStyle += 'right:'+(this.canvasWidth - distX)+'px;text-align:right;'; } else { divStyle += 'left:'+distX+'px;text-align:left;'; } html.push('
' + label + '
'); } else { style.halign = textAlignRight ? 'r' : 'l'; ctx.drawText( label, distX, distY + style.size / 2, style ); } } }, this); if (options.HtmlText) { html.push('
'); this.el.insert(html.join('')); } ctx.restore(); options.pie.drawn = true; } }, plotSlice: function(x, y, radius, startAngle, endAngle, fill, vScale) { var ctx = this.ctx; vScale = vScale || 1; ctx.save(); ctx.scale(1, vScale); ctx.beginPath(); ctx.moveTo(x, y); ctx.arc (x, y, radius, startAngle, endAngle, fill); ctx.lineTo(x, y); ctx.closePath(); ctx.restore(); }, plotPie: function() {}, /** * Function: insertLegend * * Function adds a legend div to the canvas container or draws it on the canvas. * * Parameters: * none * * Returns: * void */ insertLegend: function(){ if(!this.options.legend.show) return; var series = this.series, plotOffset = this.plotOffset, options = this.options, fragments = [], rowStarted = false, ctx = this.ctx, i; var noLegendItems = series.findAll(function(s) {return (s.label && !s.hide)}).size(); if (noLegendItems) { if (!options.HtmlText && this.textEnabled) { var style = { size: options.fontSize*1.1, color: options.grid.color }; // @todo: take css into account //var dummyDiv = this.el.insert('
'); var p = options.legend.position, m = options.legend.margin, lbw = options.legend.labelBoxWidth, lbh = options.legend.labelBoxHeight, lbm = options.legend.labelBoxMargin, offsetX = plotOffset.left + m, offsetY = plotOffset.top + m; // We calculate the labels' max width var labelMaxWidth = 0; for(i = series.length - 1; i > -1; --i){ if(!series[i].label || series[i].hide) continue; var label = options.legend.labelFormatter(series[i].label); labelMaxWidth = Math.max(labelMaxWidth, ctx.measureText(label, style)); } var legendWidth = Math.round(lbw + lbm*3 + labelMaxWidth), legendHeight = Math.round(noLegendItems*(lbm+lbh) + lbm); if(p.charAt(0) == 's') offsetY = plotOffset.top + this.plotHeight - (m + legendHeight); if(p.charAt(1) == 'e') offsetX = plotOffset.left + this.plotWidth - (m + legendWidth); // Legend box var color = Flotr.parseColor(options.legend.backgroundColor || 'rgb(240,240,240)').scale(null, null, null, options.legend.backgroundOpacity || 0.1).toString(); ctx.fillStyle = color; ctx.fillRect(offsetX, offsetY, legendWidth, legendHeight); ctx.strokeStyle = options.legend.labelBoxBorderColor; ctx.strokeRect(Flotr.toPixel(offsetX), Flotr.toPixel(offsetY), legendWidth, legendHeight); // Legend labels var x = offsetX + lbm; var y = offsetY + lbm; for(i = 0; i < series.length; i++){ if(!series[i].label || series[i].hide) continue; var label = options.legend.labelFormatter(series[i].label); ctx.fillStyle = series[i].color; ctx.fillRect(x, y, lbw-1, lbh-1); ctx.strokeStyle = options.legend.labelBoxBorderColor; ctx.lineWidth = 1; ctx.strokeRect(Math.ceil(x)-1.5, Math.ceil(y)-1.5, lbw+2, lbh+2); // Legend text ctx.drawText( label, x + lbw + lbm, y + (lbh + style.size - ctx.fontDescent(style))/2, style ); y += lbh + lbm; } } else { for(i = 0; i < series.length; ++i){ if(!series[i].label || series[i].hide) continue; if(i % options.legend.noColumns == 0){ fragments.push(rowStarted ? '' : ''); rowStarted = true; } var label = options.legend.labelFormatter(series[i].label); fragments.push('
' + '' + label + ''); } if(rowStarted) fragments.push(''); if(fragments.length > 0){ var table = '' + fragments.join("") + '
'; if(options.legend.container != null){ $(options.legend.container).update(table); }else{ var pos = ''; var p = options.legend.position, m = options.legend.margin; if(p.charAt(0) == 'n') pos += 'top:' + (m + plotOffset.top) + 'px;'; else if(p.charAt(0) == 's') pos += 'bottom:' + (m + plotOffset.bottom) + 'px;'; if(p.charAt(1) == 'e') pos += 'right:' + (m + plotOffset.right) + 'px;'; else if(p.charAt(1) == 'w') pos += 'left:' + (m + plotOffset.left) + 'px;'; var div = this.el.insert('
' + table + '
').select('div.flotr-legend').first(); if(options.legend.backgroundOpacity != 0.0){ /** * Put in the transparent background separately to avoid blended labels and * label boxes. */ var c = options.legend.backgroundColor; if(c == null){ var tmp = (options.grid.backgroundColor != null) ? options.grid.backgroundColor : Flotr.extractColor(div); c = Flotr.parseColor(tmp).adjust(null, null, null, 1).toString(); } this.el.insert('
').select('div.flotr-legend-bg').first().setStyle({ 'opacity': options.legend.backgroundOpacity }); } } } } } }, /** * Function: getEventPosition * * Calculates the coordinates from a mouse event object. * * Parameters: * event - Mouse Event object. * * Returns: * Object with x and y coordinates of the mouse. */ getEventPosition: function (event){ var offset = this.overlay.cumulativeOffset(), rx = (event.pageX - offset.left - this.plotOffset.left), ry = (event.pageY - offset.top - this.plotOffset.top), ax = 0, ay = 0 if(event.pageX == null && event.clientX != null){ var de = document.documentElement, b = document.body; ax = event.clientX + (de && de.scrollLeft || b.scrollLeft || 0); ay = event.clientY + (de && de.scrollTop || b.scrollTop || 0); }else{ ax = event.pageX; ay = event.pageY; } return { x: this.axes.x.min + rx / this.axes.x.scale, x2: this.axes.x2.min + rx / this.axes.x2.scale, y: this.axes.y.max - ry / this.axes.y.scale, y2: this.axes.y2.max - ry / this.axes.y2.scale, relX: rx, relY: ry, absX: ax, absY: ay }; }, /** * Function: clickHandler * * Handler observes the 'click' event and fires the 'flotr:click' event. * * Parameters: * event - 'click' Event object. * * Returns: * void */ clickHandler: function(event){ if(this.ignoreClick){ this.ignoreClick = false; return; } this.el.fire('flotr:click', [this.getEventPosition(event), this]); }, /** * Function: mouseMoveHandler * * Handler observes mouse movement over the graph area. Fires the * 'flotr:mousemove' event. * * Parameters: * event - 'mousemove' Event object. * * Returns: * void */ mouseMoveHandler: function(event){ var pos = this.getEventPosition(event); this.lastMousePos.pageX = pos.absX; this.lastMousePos.pageY = pos.absY; if(this.selectionInterval == null && (this.options.mouse.track || this.series.any(function(s){return s.mouse && s.mouse.track;}))){ this.hit(pos); } this.el.fire('flotr:mousemove', [event, pos, this]); }, /** * Function: mouseDownHandler * * Handler observes the 'mousedown' event. * * Parameters: * event - 'mousedown' Event object. * * Returns: * void */ mouseDownHandler: function (event){ if(event.isRightClick()) { event.stop(); var overlay = this.overlay; overlay.hide(); function cancelContextMenu () { overlay.show(); $(document).stopObserving('mousemove', cancelContextMenu); } $(document).observe('mousemove', cancelContextMenu); return; } if(!this.options.selection.mode || !event.isLeftClick()) return; this.setSelectionPos(this.selection.first, event); if(this.selectionInterval != null){ clearInterval(this.selectionInterval); } this.lastMousePos.pageX = null; this.selectionInterval = setInterval(this.updateSelection.bind(this), 1000/this.options.selection.fps); this.mouseUpHandler = this.mouseUpHandler.bind(this); $(document).observe('mouseup', this.mouseUpHandler); }, /** * Function: (private) fireSelectEvent * * Fires the 'flotr:select' event when the user made a selection. * * Parameters: * none * * Returns: * void */ fireSelectEvent: function(){ var a = this.axes, selection = this.selection, x1 = (selection.first.x <= selection.second.x) ? selection.first.x : selection.second.x, x2 = (selection.first.x <= selection.second.x) ? selection.second.x : selection.first.x, y1 = (selection.first.y >= selection.second.y) ? selection.first.y : selection.second.y, y2 = (selection.first.y >= selection.second.y) ? selection.second.y : selection.first.y; x1 = a.x.min + x1 / a.x.scale; x2 = a.x.min + x2 / a.x.scale; y1 = a.y.max - y1 / a.y.scale; y2 = a.y.max - y2 / a.y.scale; this.el.fire('flotr:select', [{x1:x1, y1:y1, x2:x2, y2:y2}, this]); }, /** * Function: (private) mouseUpHandler * * Handler observes the mouseup event for the document. * * Parameters: * event - 'mouseup' Event object. * * Returns: * void */ mouseUpHandler: function(event){ $(document).stopObserving('mouseup', this.mouseUpHandler); event.stop(); if(this.selectionInterval != null){ clearInterval(this.selectionInterval); this.selectionInterval = null; } this.setSelectionPos(this.selection.second, event); this.clearSelection(); if(this.selectionIsSane()){ this.drawSelection(); this.fireSelectEvent(); this.ignoreClick = true; } }, /** * Function: setSelectionPos * * Calculates the position of the selection. * * Parameters: * pos - Position object. * event - Event object. * * Returns: * void */ setSelectionPos: function(pos, event) { var options = this.options, offset = $(this.overlay).cumulativeOffset(); if(options.selection.mode.indexOf('x') == -1){ pos.x = (pos == this.selection.first) ? 0 : this.plotWidth; }else{ pos.x = event.pageX - offset.left - this.plotOffset.left; pos.x = Math.min(Math.max(0, pos.x), this.plotWidth); } if (options.selection.mode.indexOf('y') == -1){ pos.y = (pos == this.selection.first) ? 0 : this.plotHeight; }else{ pos.y = event.pageY - offset.top - this.plotOffset.top; pos.y = Math.min(Math.max(0, pos.y), this.plotHeight); } }, /** * Function: updateSelection * * Updates (draws) the selection box. * * Parameters: * none * * Returns: * void */ updateSelection: function(){ if(this.lastMousePos.pageX == null) return; this.setSelectionPos(this.selection.second, this.lastMousePos); this.clearSelection(); if(this.selectionIsSane()) this.drawSelection(); }, /** * Function: clearSelection * * Removes the selection box from the overlay canvas. * * Parameters: * none * * Returns: * void */ clearSelection: function() { if(this.prevSelection == null) return; var prevSelection = this.prevSelection, octx = this.octx, plotOffset = this.plotOffset, x = Math.min(prevSelection.first.x, prevSelection.second.x), y = Math.min(prevSelection.first.y, prevSelection.second.y), w = Math.abs(prevSelection.second.x - prevSelection.first.x), h = Math.abs(prevSelection.second.y - prevSelection.first.y); octx.clearRect(x + plotOffset.left - octx.lineWidth, y + plotOffset.top - octx.lineWidth, w + octx.lineWidth*2, h + octx.lineWidth*2); this.prevSelection = null; }, /** * Function: setSelection * * Allows the user the manually select an area. * * Parameters: * area - Object with coordinates to select. * * Returns: * void */ setSelection: function(area){ var options = this.options, xa = this.axes.x, ya = this.axes.y, vertScale = yaxis.scale, hozScale = xaxis.scale, selX = options.selection.mode.indexOf('x') != -1, selY = options.selection.mode.indexOf('y') != -1; this.clearSelection(); this.selection.first.y = selX ? 0 : (ya.max - area.y1) * vertScale; this.selection.second.y = selX ? this.plotHeight : (ya.max - area.y2) * vertScale; this.selection.first.x = selY ? 0 : (area.x1 - xa.min) * hozScale; this.selection.second.x = selY ? this.plotWidth : (area.x2 - xa.min) * hozScale; this.drawSelection(); this.fireSelectEvent(); }, /** * Function: (private) drawSelection * * Draws the selection box. * * Parameters: * none * * Returns: * void */ drawSelection: function() { var prevSelection = this.prevSelection, selection = this.selection, octx = this.octx, options = this.options, plotOffset = this.plotOffset; if(prevSelection != null && selection.first.x == prevSelection.first.x && selection.first.y == prevSelection.first.y && selection.second.x == prevSelection.second.x && selection.second.y == prevSelection.second.y) return; octx.strokeStyle = Flotr.parseColor(options.selection.color).scale(null, null, null, 0.8).toString(); octx.lineWidth = 1; octx.lineJoin = 'round'; octx.fillStyle = Flotr.parseColor(options.selection.color).scale(null, null, null, 0.4).toString(); this.prevSelection = { first: { x: selection.first.x, y: selection.first.y }, second: { x: selection.second.x, y: selection.second.y } }; var x = Math.min(selection.first.x, selection.second.x), y = Math.min(selection.first.y, selection.second.y), w = Math.abs(selection.second.x - selection.first.x), h = Math.abs(selection.second.y - selection.first.y); octx.fillRect(x + plotOffset.left, y + plotOffset.top, w, h); octx.strokeRect(x + plotOffset.left, y + plotOffset.top, w, h); }, /** * Function: (private) selectionIsSane * * Determines whether or not the selection is sane and should be drawn. * * Parameters: * none * * Returns: * boolean - True when sane, false otherwise. */ selectionIsSane: function(){ var selection = this.selection; return Math.abs(selection.second.x - selection.first.x) >= 5 && Math.abs(selection.second.y - selection.first.y) >= 5; }, /** * Function: clearHit * * Removes the mouse tracking point from the overlay. * * Parameters: * none * * Returns: * void */ clearHit: function(){ if(this.prevHit){ var options = this.options, plotOffset = this.plotOffset, prevHit = this.prevHit; this.octx.clearRect( this.tHoz(prevHit.x) + plotOffset.left - options.points.radius*2, this.tVert(prevHit.y) + plotOffset.top - options.points.radius*2, options.points.radius*3 + options.points.lineWidth*3, options.points.radius*3 + options.points.lineWidth*3 ); this.prevHit = null; } }, /** * Function: hit * * Retrieves the nearest data point from the mouse cursor. If it's within * a certain range, draw a point on the overlay canvas and display the x and y * value of the data. * * Parameters: * mouse - Object that holds the relative x and y coordinates of the cursor. * * Returns: * void */ hit: function(mouse){ var series = this.series, options = this.options, prevHit = this.prevHit, plotOffset = this.plotOffset, octx = this.octx, data, xsens, ysens, /** * Nearest data element. */ i, n = { dist:Number.MAX_VALUE, x:null, y:null, relX:mouse.relX, relY:mouse.relY, absX:mouse.absX, absY:mouse.absY, mouse:null }; for(i = 0; i < series.length; i++){ s = series[i]; if(!s.mouse.track) continue; data = s.data; xsens = (s.xaxis.scale*s.mouse.sensibility); ysens = (s.yaxis.scale*s.mouse.sensibility); for(var j = 0, xpow, ypow; j < data.length; j++){ if (data[j][1] === null) continue; xpow = Math.pow(s.xaxis.scale*(data[j][0] - mouse.x), 2); ypow = Math.pow(s.yaxis.scale*(data[j][1] - mouse.y), 2); if(xpow < xsens && ypow < ysens && Math.sqrt(xpow+ypow) < n.dist){ n.dist = Math.sqrt(xpow+ypow); n.x = data[j][0]; n.y = data[j][1]; n.mouse = s.mouse; } } } if(n.mouse && n.mouse.track && !prevHit || (prevHit && (n.x != prevHit.x || n.y != prevHit.y))){ var mt = this.mouseTrack || this.el.select(".flotr-mouse-value")[0], pos = '', p = options.mouse.position, m = options.mouse.margin, elStyle = 'opacity:0.7;background-color:#000;color:#fff;display:none;position:absolute;padding:2px 8px;-moz-border-radius:4px;border-radius:4px;white-space:nowrap;'; if (!options.mouse.relative) { // absolute to the canvas if(p.charAt(0) == 'n') pos += 'top:' + (m + plotOffset.top) + 'px;'; else if(p.charAt(0) == 's') pos += 'bottom:' + (m + plotOffset.bottom) + 'px;'; if(p.charAt(1) == 'e') pos += 'right:' + (m + plotOffset.right) + 'px;'; else if(p.charAt(1) == 'w') pos += 'left:' + (m + plotOffset.left) + 'px;'; } else { // relative to the mouse if(p.charAt(0) == 'n') pos += 'bottom:' + (m - plotOffset.top - this.tVert(n.y) + this.canvasHeight) + 'px;'; else if(p.charAt(0) == 's') pos += 'top:' + (m + plotOffset.top + this.tVert(n.y)) + 'px;'; if(p.charAt(1) == 'e') pos += 'left:' + (m + plotOffset.left + this.tHoz(n.x)) + 'px;'; else if(p.charAt(1) == 'w') pos += 'right:' + (m - plotOffset.left - this.tHoz(n.x) + this.canvasWidth) + 'px;'; } elStyle += pos; if(!mt){ this.el.insert('
'); mt = this.mouseTrack = this.el.select('.flotr-mouse-value').first(); } else { this.mouseTrack = mt.setStyle(elStyle); } if(n.x !== null && n.y !== null){ mt.show(); this.clearHit(); if(n.mouse.lineColor != null){ octx.save(); octx.translate(plotOffset.left, plotOffset.top); octx.lineWidth = options.points.lineWidth; octx.strokeStyle = n.mouse.lineColor; octx.fillStyle = '#ffffff'; octx.beginPath(); octx.arc(this.tHoz(n.x), this.tVert(n.y), options.mouse.radius, 0, 2 * Math.PI, true); octx.fill(); octx.stroke(); octx.restore(); } this.prevHit = n; var decimals = n.mouse.trackDecimals; if(decimals == null || decimals < 0) decimals = 0; mt.innerHTML = n.mouse.trackFormatter({x: n.x.toFixed(decimals), y: n.y.toFixed(decimals)}); mt.fire('flotr:hit', [n, this]); } else if(prevHit){ mt.hide(); this.clearHit(); } } }, saveImage: function (type, width, height, replaceCanvas) { var image = null; switch (type) { case 'jpeg': case 'jpg': image = Canvas2Image.saveAsJPEG(this.canvas, replaceCanvas, width, height); break; default: case 'png': image = Canvas2Image.saveAsPNG(this.canvas, replaceCanvas, width, height); break; case 'bmp': image = Canvas2Image.saveAsBMP(this.canvas, replaceCanvas, width, height); break; } if (Object.isElement(image) && replaceCanvas) { this.restoreCanvas(); this.canvas.hide(); this.overlay.hide(); this.el.insert(image.setStyle({position: 'absolute'})); } }, restoreCanvas: function() { this.canvas.show(); this.overlay.show(); this.el.select('img').invoke('remove'); } }); Flotr.Color = Class.create({ initialize: function(r, g, b, a){ this.rgba = ['r','g','b','a']; var x = 4; while(-1<--x){ this[this.rgba[x]] = arguments[x] || ((x==3) ? 1.0 : 0); } this.normalize(); }, adjust: function(rd, gd, bd, ad) { var x = 4; while(-1<--x){ if(arguments[x] != null) this[this.rgba[x]] += arguments[x]; } return this.normalize(); }, clone: function(){ return new Flotr.Color(this.r, this.b, this.g, this.a); }, limit: function(val,minVal,maxVal){ return Math.max(Math.min(val, maxVal), minVal); }, normalize: function(){ var limit = this.limit; this.r = limit(parseInt(this.r), 0, 255); this.g = limit(parseInt(this.g), 0, 255); this.b = limit(parseInt(this.b), 0, 255); this.a = limit(this.a, 0, 1); return this; }, scale: function(rf, gf, bf, af){ var x = 4; while(-1<--x){ if(arguments[x] != null) this[this.rgba[x]] *= arguments[x]; } return this.normalize(); }, distance: function(color){ if (!color) return; color = new Flotr.parseColor(color); var dist = 0; var x = 3; while(-1<--x){ dist += Math.abs(this[this.rgba[x]] - color[this.rgba[x]]); } return dist; }, toString: function(){ return (this.a >= 1.0) ? 'rgb('+[this.r,this.g,this.b].join(',')+')' : 'rgba('+[this.r,this.g,this.b,this.a].join(',')+')'; } }); Flotr.Color.lookupColors = { aqua:[0,255,255], azure:[240,255,255], beige:[245,245,220], black:[0,0,0], blue:[0,0,255], brown:[165,42,42], cyan:[0,255,255], darkblue:[0,0,139], darkcyan:[0,139,139], darkgrey:[169,169,169], darkgreen:[0,100,0], darkkhaki:[189,183,107], darkmagenta:[139,0,139], darkolivegreen:[85,107,47], darkorange:[255,140,0], darkorchid:[153,50,204], darkred:[139,0,0], darksalmon:[233,150,122], darkviolet:[148,0,211], fuchsia:[255,0,255], gold:[255,215,0], green:[0,128,0], indigo:[75,0,130], khaki:[240,230,140], lightblue:[173,216,230], lightcyan:[224,255,255], lightgreen:[144,238,144], lightgrey:[211,211,211], lightpink:[255,182,193], lightyellow:[255,255,224], lime:[0,255,0], magenta:[255,0,255], maroon:[128,0,0], navy:[0,0,128], olive:[128,128,0], orange:[255,165,0], pink:[255,192,203], purple:[128,0,128], violet:[128,0,128], red:[255,0,0], silver:[192,192,192], white:[255,255,255], yellow:[255,255,0] }; // not used yet Flotr.Date = { format: function(d, format) { if (!d) return; var leftPad = function(n) { n = n.toString(); return n.length == 1 ? "0" + n : n; }; var r = []; var escape = false; for (var i = 0; i < format.length; ++i) { var c = format.charAt(i); if (escape) { switch (c) { case 'h': c = d.getUTCHours().toString(); break; case 'H': c = leftPad(d.getUTCHours()); break; case 'M': c = leftPad(d.getUTCMinutes()); break; case 'S': c = leftPad(d.getUTCSeconds()); break; case 'd': c = d.getUTCDate().toString(); break; case 'm': c = (d.getUTCMonth() + 1).toString(); break; case 'y': c = d.getUTCFullYear().toString(); break; case 'b': c = Flotr.Date.monthNames[d.getUTCMonth()]; break; } r.push(c); escape = false; } else { if (c == "%") escape = true; else r.push(c); } } return r.join(""); }, timeUnits: { "second": 1000, "minute": 60 * 1000, "hour": 60 * 60 * 1000, "day": 24 * 60 * 60 * 1000, "month": 30 * 24 * 60 * 60 * 1000, "year": 365.2425 * 24 * 60 * 60 * 1000 }, // the allowed tick sizes, after 1 year we use an integer algorithm spec: [ [1, "second"], [2, "second"], [5, "second"], [10, "second"], [30, "second"], [1, "minute"], [2, "minute"], [5, "minute"], [10, "minute"], [30, "minute"], [1, "hour"], [2, "hour"], [4, "hour"], [8, "hour"], [12, "hour"], [1, "day"], [2, "day"], [3, "day"], [0.25, "month"], [0.5, "month"], [1, "month"], [2, "month"], [3, "month"], [6, "month"], [1, "year"] ], monthNames: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] }; ================================================ FILE: input-scripts/flotr/lib/base64.js ================================================ /* Copyright (C) 1999 Masanao Izumo * Version: 1.0 * LastModified: Dec 25 1999 * This library is free. You can redistribute it and/or modify it. */ /* * Interfaces: * b64 = base64encode(data); * data = base64decode(b64); */ (function() { var base64EncodeChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; var base64DecodeChars = [ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1]; function base64encode(str) { var out, i, len; var c1, c2, c3; len = str.length; i = 0; out = ""; while(i < len) { c1 = str.charCodeAt(i++) & 0xff; if(i == len) { out += base64EncodeChars.charAt(c1 >> 2); out += base64EncodeChars.charAt((c1 & 0x3) << 4); out += "=="; break; } c2 = str.charCodeAt(i++); if(i == len) { out += base64EncodeChars.charAt(c1 >> 2); out += base64EncodeChars.charAt(((c1 & 0x3)<< 4) | ((c2 & 0xF0) >> 4)); out += base64EncodeChars.charAt((c2 & 0xF) << 2); out += "="; break; } c3 = str.charCodeAt(i++); out += base64EncodeChars.charAt(c1 >> 2); out += base64EncodeChars.charAt(((c1 & 0x3)<< 4) | ((c2 & 0xF0) >> 4)); out += base64EncodeChars.charAt(((c2 & 0xF) << 2) | ((c3 & 0xC0) >>6)); out += base64EncodeChars.charAt(c3 & 0x3F); } return out; } function base64decode(str) { var c1, c2, c3, c4; var i, len, out; len = str.length; i = 0; out = ""; while(i < len) { /* c1 */ do { c1 = base64DecodeChars[str.charCodeAt(i++) & 0xff]; } while(i < len && c1 == -1); if(c1 == -1) break; /* c2 */ do { c2 = base64DecodeChars[str.charCodeAt(i++) & 0xff]; } while(i < len && c2 == -1); if(c2 == -1) break; out += String.fromCharCode((c1 << 2) | ((c2 & 0x30) >> 4)); /* c3 */ do { c3 = str.charCodeAt(i++) & 0xff; if(c3 == 61) return out; c3 = base64DecodeChars[c3]; } while(i < len && c3 == -1); if(c3 == -1) break; out += String.fromCharCode(((c2 & 0XF) << 4) | ((c3 & 0x3C) >> 2)); /* c4 */ do { c4 = str.charCodeAt(i++) & 0xff; if(c4 == 61) return out; c4 = base64DecodeChars[c4]; } while(i < len && c4 == -1); if(c4 == -1) break; out += String.fromCharCode(((c3 & 0x03) << 6) | c4); } return out; } if (!window.btoa) window.btoa = base64encode; if (!window.atob) window.atob = base64decode; })(); ================================================ FILE: input-scripts/flotr/lib/canvas2image.js ================================================ /* * Canvas2Image v0.1 * Copyright (c) 2008 Jacob Seidelin, cupboy@gmail.com * MIT License [http://www.opensource.org/licenses/mit-license.php] */ var Canvas2Image = (function() { // check if we have canvas support var oCanvas = document.createElement("canvas"); // no canvas, bail out. if (!oCanvas.getContext) { return { saveAsBMP : function(){}, saveAsPNG : function(){}, saveAsJPEG : function(){} } } var bHasImageData = !!(oCanvas.getContext("2d").getImageData); var bHasDataURL = !!(oCanvas.toDataURL); var bHasBase64 = !!(window.btoa); var strDownloadMime = "image/octet-stream"; // ok, we're good var readCanvasData = function(oCanvas) { var iWidth = parseInt(oCanvas.width); var iHeight = parseInt(oCanvas.height); return oCanvas.getContext("2d").getImageData(0,0,iWidth,iHeight); } // base64 encodes either a string or an array of charcodes var encodeData = function(data) { var strData = ""; if (typeof data == "string") { strData = data; } else { var aData = data; for (var i = 0; i < aData.length; i++) { strData += String.fromCharCode(aData[i]); } } return btoa(strData); } // creates a base64 encoded string containing BMP data // takes an imagedata object as argument var createBMP = function(oData) { var aHeader = []; var iWidth = oData.width; var iHeight = oData.height; aHeader.push(0x42); // magic 1 aHeader.push(0x4D); var iFileSize = iWidth*iHeight*3 + 54; // total header size = 54 bytes aHeader.push(iFileSize % 256); iFileSize = Math.floor(iFileSize / 256); aHeader.push(iFileSize % 256); iFileSize = Math.floor(iFileSize / 256); aHeader.push(iFileSize % 256); iFileSize = Math.floor(iFileSize / 256); aHeader.push(iFileSize % 256); aHeader.push(0); // reserved aHeader.push(0); aHeader.push(0); // reserved aHeader.push(0); aHeader.push(54); // data offset aHeader.push(0); aHeader.push(0); aHeader.push(0); var aInfoHeader = []; aInfoHeader.push(40); // info header size aInfoHeader.push(0); aInfoHeader.push(0); aInfoHeader.push(0); var iImageWidth = iWidth; aInfoHeader.push(iImageWidth % 256); iImageWidth = Math.floor(iImageWidth / 256); aInfoHeader.push(iImageWidth % 256); iImageWidth = Math.floor(iImageWidth / 256); aInfoHeader.push(iImageWidth % 256); iImageWidth = Math.floor(iImageWidth / 256); aInfoHeader.push(iImageWidth % 256); var iImageHeight = iHeight; aInfoHeader.push(iImageHeight % 256); iImageHeight = Math.floor(iImageHeight / 256); aInfoHeader.push(iImageHeight % 256); iImageHeight = Math.floor(iImageHeight / 256); aInfoHeader.push(iImageHeight % 256); iImageHeight = Math.floor(iImageHeight / 256); aInfoHeader.push(iImageHeight % 256); aInfoHeader.push(1); // num of planes aInfoHeader.push(0); aInfoHeader.push(24); // num of bits per pixel aInfoHeader.push(0); aInfoHeader.push(0); // compression = none aInfoHeader.push(0); aInfoHeader.push(0); aInfoHeader.push(0); var iDataSize = iWidth*iHeight*3; aInfoHeader.push(iDataSize % 256); iDataSize = Math.floor(iDataSize / 256); aInfoHeader.push(iDataSize % 256); iDataSize = Math.floor(iDataSize / 256); aInfoHeader.push(iDataSize % 256); iDataSize = Math.floor(iDataSize / 256); aInfoHeader.push(iDataSize % 256); for (var i = 0; i < 16; i++) { aInfoHeader.push(0); // these bytes not used } var iPadding = (4 - ((iWidth * 3) % 4)) % 4; var aImgData = oData.data; var strPixelData = ""; var y = iHeight; do { var iOffsetY = iWidth*(y-1)*4; var strPixelRow = ""; for (var x=0;x object containing the imagedata var makeImageObject = function(strSource) { var oImgElement = document.createElement("img"); oImgElement.src = strSource; return oImgElement; } var scaleCanvas = function(oCanvas, iWidth, iHeight) { if (iWidth && iHeight) { var oSaveCanvas = document.createElement("canvas"); oSaveCanvas.width = iWidth; oSaveCanvas.height = iHeight; oSaveCanvas.style.width = iWidth+"px"; oSaveCanvas.style.height = iHeight+"px"; var oSaveCtx = oSaveCanvas.getContext("2d"); oSaveCtx.drawImage(oCanvas, 0, 0, oCanvas.width, oCanvas.height, 0, 0, iWidth, iWidth); return oSaveCanvas; } return oCanvas; } return { saveAsPNG : function(oCanvas, bReturnImg, iWidth, iHeight) { if (!bHasDataURL) { return false; } var oScaledCanvas = scaleCanvas(oCanvas, iWidth, iHeight); var strData = oScaledCanvas.toDataURL("image/png"); if (bReturnImg) { return makeImageObject(strData); } else { saveFile(strData.replace("image/png", strDownloadMime)); } return true; }, saveAsJPEG : function(oCanvas, bReturnImg, iWidth, iHeight) { if (!bHasDataURL) { return false; } var oScaledCanvas = scaleCanvas(oCanvas, iWidth, iHeight); var strMime = "image/jpeg"; var strData = oScaledCanvas.toDataURL(strMime); // check if browser actually supports jpeg by looking for the mime type in the data uri. // if not, return false if (strData.indexOf(strMime) != 5) { return false; } if (bReturnImg) { return makeImageObject(strData); } else { saveFile(strData.replace(strMime, strDownloadMime)); } return true; }, saveAsBMP : function(oCanvas, bReturnImg, iWidth, iHeight) { if (!(bHasImageData && bHasBase64)) { return false; } var oScaledCanvas = scaleCanvas(oCanvas, iWidth, iHeight); var oData = readCanvasData(oScaledCanvas); var strImgData = createBMP(oData); if (bReturnImg) { return makeImageObject(makeDataURI(strImgData, "image/bmp")); } else { saveFile(makeDataURI(strImgData, strDownloadMime)); } return true; } }; })(); ================================================ FILE: input-scripts/flotr/lib/canvastext.js ================================================ /** * This code is released to the public domain by Jim Studt, 2007. * He may keep some sort of up to date copy at http://www.federated.com/~jim/canvastext/ * It as been modified by Fabien Mnager to handle font style like size, weight, color and rotation. * A partial support for accentuated letters as been added too. */ var CanvasText = { /** The letters definition. It is a list of letters, * with their width, and the coordinates of points compositing them. * The syntax for the points is : [x, y], null value means "pen up" */ letters: { '\n':{ width: -1, points: [] }, ' ': { width: 10, points: [] }, '!': { width: 10, points: [[5,21],[5,7],null,[5,2],[4,1],[5,0],[6,1],[5,2]] }, '"': { width: 16, points: [[4,21],[4,14],null,[12,21],[12,14]] }, '#': { width: 21, points: [[11,25],[4,-7],null,[17,25],[10,-7],null,[4,12],[18,12],null,[3,6],[17,6]] }, '$': { width: 20, points: [[8,25],[8,-4],null,[12,25],[12,-4],null,[17,18],[15,20],[12,21],[8,21],[5,20],[3,18],[3,16],[4,14],[5,13],[7,12],[13,10],[15,9],[16,8],[17,6],[17,3],[15,1],[12,0],[8,0],[5,1],[3,3]] }, '%': { width: 24, points: [[21,21],[3,0],null,[8,21],[10,19],[10,17],[9,15],[7,14],[5,14],[3,16],[3,18],[4,20],[6,21],[8,21],null,[17,7],[15,6],[14,4],[14,2],[16,0],[18,0],[20,1],[21,3],[21,5],[19,7],[17,7]] }, '&': { width: 26, points: [[23,12],[23,13],[22,14],[21,14],[20,13],[19,11],[17,6],[15,3],[13,1],[11,0],[7,0],[5,1],[4,2],[3,4],[3,6],[4,8],[5,9],[12,13],[13,14],[14,16],[14,18],[13,20],[11,21],[9,20],[8,18],[8,16],[9,13],[11,10],[16,3],[18,1],[20,0],[22,0],[23,1],[23,2]] }, '\'':{ width: 10, points: [[5,19],[4,20],[5,21],[6,20],[6,18],[5,16],[4,15]] }, '(': { width: 14, points: [[11,25],[9,23],[7,20],[5,16],[4,11],[4,7],[5,2],[7,-2],[9,-5],[11,-7]] }, ')': { width: 14, points: [[3,25],[5,23],[7,20],[9,16],[10,11],[10,7],[9,2],[7,-2],[5,-5],[3,-7]] }, '*': { width: 16, points: [[8,21],[8,9],null,[3,18],[13,12],null,[13,18],[3,12]] }, '+': { width: 26, points: [[13,18],[13,0],null,[4,9],[22,9]] }, ',': { width: 10, points: [[6,1],[5,0],[4,1],[5,2],[6,1],[6,-1],[5,-3],[4,-4]] }, '-': { width: 26, points: [[4,9],[22,9]] }, '.': { width: 10, points: [[5,2],[4,1],[5,0],[6,1],[5,2]] }, '/': { width: 22, points: [[20,25],[2,-7]] }, '0': { width: 20, points: [[9,21],[6,20],[4,17],[3,12],[3,9],[4,4],[6,1],[9,0],[11,0],[14,1],[16,4],[17,9],[17,12],[16,17],[14,20],[11,21],[9,21]] }, '1': { width: 20, points: [[6,17],[8,18],[11,21],[11,0]] }, '2': { width: 20, points: [[4,16],[4,17],[5,19],[6,20],[8,21],[12,21],[14,20],[15,19],[16,17],[16,15],[15,13],[13,10],[3,0],[17,0]] }, '3': { width: 20, points: [[5,21],[16,21],[10,13],[13,13],[15,12],[16,11],[17,8],[17,6],[16,3],[14,1],[11,0],[8,0],[5,1],[4,2],[3,4]] }, '4': { width: 20, points: [[13,21],[3,7],[18,7],null,[13,21],[13,0]] }, '5': { width: 20, points: [[15,21],[5,21],[4,12],[5,13],[8,14],[11,14],[14,13],[16,11],[17,8],[17,6],[16,3],[14,1],[11,0],[8,0],[5,1],[4,2],[3,4]] }, '6': { width: 20, points: [[16,18],[15,20],[12,21],[10,21],[7,20],[5,17],[4,12],[4,7],[5,3],[7,1],[10,0],[11,0],[14,1],[16,3],[17,6],[17,7],[16,10],[14,12],[11,13],[10,13],[7,12],[5,10],[4,7]] }, '7': { width: 20, points: [[17,21],[7,0],null,[3,21],[17,21]] }, '8': { width: 20, points: [[8,21],[5,20],[4,18],[4,16],[5,14],[7,13],[11,12],[14,11],[16,9],[17,7],[17,4],[16,2],[15,1],[12,0],[8,0],[5,1],[4,2],[3,4],[3,7],[4,9],[6,11],[9,12],[13,13],[15,14],[16,16],[16,18],[15,20],[12,21],[8,21]] }, '9': { width: 20, points: [[16,14],[15,11],[13,9],[10,8],[9,8],[6,9],[4,11],[3,14],[3,15],[4,18],[6,20],[9,21],[10,21],[13,20],[15,18],[16,14],[16,9],[15,4],[13,1],[10,0],[8,0],[5,1],[4,3]] }, ':': { width: 10, points: [[5,14],[4,13],[5,12],[6,13],[5,14],null,[5,2],[4,1],[5,0],[6,1],[5,2]] }, ';': { width: 10, points: [[5,14],[4,13],[5,12],[6,13],[5,14],null,[6,1],[5,0],[4,1],[5,2],[6,1],[6,-1],[5,-3],[4,-4]] }, '<': { width: 24, points: [[20,18],[4,9],[20,0]] }, '=': { width: 26, points: [[4,12],[22,12],null,[4,6],[22,6]] }, '>': { width: 24, points: [[4,18],[20,9],[4,0]] }, '?': { width: 18, points: [[3,16],[3,17],[4,19],[5,20],[7,21],[11,21],[13,20],[14,19],[15,17],[15,15],[14,13],[13,12],[9,10],[9,7],null,[9,2],[8,1],[9,0],[10,1],[9,2]] }, '@': { width: 27, points: [[18,13],[17,15],[15,16],[12,16],[10,15],[9,14],[8,11],[8,8],[9,6],[11,5],[14,5],[16,6],[17,8],null,[12,16],[10,14],[9,11],[9,8],[10,6],[11,5],null,[18,16],[17,8],[17,6],[19,5],[21,5],[23,7],[24,10],[24,12],[23,15],[22,17],[20,19],[18,20],[15,21],[12,21],[9,20],[7,19],[5,17],[4,15],[3,12],[3,9],[4,6],[5,4],[7,2],[9,1],[12,0],[15,0],[18,1],[20,2],[21,3],null,[19,16],[18,8],[18,6],[19,5]] }, 'A': { width: 18, points: [[9,21],[1,0],null,[9,21],[17,0],null,[4,7],[14,7]] }, 'B': { width: 21, points: [[4,21],[4,0],null,[4,21],[13,21],[16,20],[17,19],[18,17],[18,15],[17,13],[16,12],[13,11],null,[4,11],[13,11],[16,10],[17,9],[18,7],[18,4],[17,2],[16,1],[13,0],[4,0]] }, 'C': { width: 21, points: [[18,16],[17,18],[15,20],[13,21],[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5]] }, 'D': { width: 21, points: [[4,21],[4,0],null,[4,21],[11,21],[14,20],[16,18],[17,16],[18,13],[18,8],[17,5],[16,3],[14,1],[11,0],[4,0]] }, 'E': { width: 19, points: [[4,21],[4,0],null,[4,21],[17,21],null,[4,11],[12,11],null,[4,0],[17,0]] }, 'F': { width: 18, points: [[4,21],[4,0],null,[4,21],[17,21],null,[4,11],[12,11]] }, 'G': { width: 21, points: [[18,16],[17,18],[15,20],[13,21],[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5],[18,8],null,[13,8],[18,8]] }, 'H': { width: 22, points: [[4,21],[4,0],null,[18,21],[18,0],null,[4,11],[18,11]] }, 'I': { width: 8, points: [[4,21],[4,0]] }, 'J': { width: 16, points: [[12,21],[12,5],[11,2],[10,1],[8,0],[6,0],[4,1],[3,2],[2,5],[2,7]] }, 'K': { width: 21, points: [[4,21],[4,0],null,[18,21],[4,7],null,[9,12],[18,0]] }, 'L': { width: 17, points: [[4,21],[4,0],null,[4,0],[16,0]] }, 'M': { width: 24, points: [[4,21],[4,0],null,[4,21],[12,0],null,[20,21],[12,0],null,[20,21],[20,0]] }, 'N': { width: 22, points: [[4,21],[4,0],null,[4,21],[18,0],null,[18,21],[18,0]] }, 'O': { width: 22, points: [[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5],[19,8],[19,13],[18,16],[17,18],[15,20],[13,21],[9,21]] }, 'P': { width: 21, points: [[4,21],[4,0],null,[4,21],[13,21],[16,20],[17,19],[18,17],[18,14],[17,12],[16,11],[13,10],[4,10]] }, 'Q': { width: 22, points: [[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5],[19,8],[19,13],[18,16],[17,18],[15,20],[13,21],[9,21],null,[12,4],[18,-2]] }, 'R': { width: 21, points: [[4,21],[4,0],null,[4,21],[13,21],[16,20],[17,19],[18,17],[18,15],[17,13],[16,12],[13,11],[4,11],null,[11,11],[18,0]] }, 'S': { width: 20, points: [[17,18],[15,20],[12,21],[8,21],[5,20],[3,18],[3,16],[4,14],[5,13],[7,12],[13,10],[15,9],[16,8],[17,6],[17,3],[15,1],[12,0],[8,0],[5,1],[3,3]] }, 'T': { width: 16, points: [[8,21],[8,0],null,[1,21],[15,21]] }, 'U': { width: 22, points: [[4,21],[4,6],[5,3],[7,1],[10,0],[12,0],[15,1],[17,3],[18,6],[18,21]] }, 'V': { width: 18, points: [[1,21],[9,0],null,[17,21],[9,0]] }, 'W': { width: 24, points: [[2,21],[7,0],null,[12,21],[7,0],null,[12,21],[17,0],null,[22,21],[17,0]] }, 'X': { width: 20, points: [[3,21],[17,0],null,[17,21],[3,0]] }, 'Y': { width: 18, points: [[1,21],[9,11],[9,0],null,[17,21],[9,11]] }, 'Z': { width: 20, points: [[17,21],[3,0],null,[3,21],[17,21],null,[3,0],[17,0]] }, '[': { width: 14, points: [[4,25],[4,-7],null,[5,25],[5,-7],null,[4,25],[11,25],null,[4,-7],[11,-7]] }, '\\':{ width: 14, points: [[0,21],[14,-3]] }, ']': { width: 14, points: [[9,25],[9,-7],null,[10,25],[10,-7],null,[3,25],[10,25],null,[3,-7],[10,-7]] }, '^': { width: 14, points: [[3,10],[8,18],[13,10]] }, '_': { width: 16, points: [[0,-2],[16,-2]] }, '`': { width: 10, points: [[6,21],[5,20],[4,18],[4,16],[5,15],[6,16],[5,17]] }, 'a': { width: 19, points: [[15,14],[15,0],null,[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] }, 'b': { width: 19, points: [[4,21],[4,0],null,[4,11],[6,13],[8,14],[11,14],[13,13],[15,11],[16,8],[16,6],[15,3],[13,1],[11,0],[8,0],[6,1],[4,3]] }, 'c': { width: 18, points: [[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] }, 'd': { width: 19, points: [[15,21],[15,0],null,[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] }, 'e': { width: 18, points: [[3,8],[15,8],[15,10],[14,12],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] }, 'f': { width: 12, points: [[10,21],[8,21],[6,20],[5,17],[5,0],null,[2,14],[9,14]] }, 'g': { width: 19, points: [[15,14],[15,-2],[14,-5],[13,-6],[11,-7],[8,-7],[6,-6],null,[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] }, 'h': { width: 19, points: [[4,21],[4,0],null,[4,10],[7,13],[9,14],[12,14],[14,13],[15,10],[15,0]] }, 'i': { width: 8, points: [[3,21],[4,20],[5,21],[4,22],[3,21],null,[4,14],[4,0]] }, 'j': { width: 10, points: [[5,21],[6,20],[7,21],[6,22],[5,21],null,[6,14],[6,-3],[5,-6],[3,-7],[1,-7]] }, 'k': { width: 17, points: [[4,21],[4,0],null,[14,14],[4,4],null,[8,8],[15,0]] }, 'l': { width: 8, points: [[4,21],[4,0]] }, 'm': { width: 30, points: [[4,14],[4,0],null,[4,10],[7,13],[9,14],[12,14],[14,13],[15,10],[15,0],null,[15,10],[18,13],[20,14],[23,14],[25,13],[26,10],[26,0]] }, 'n': { width: 19, points: [[4,14],[4,0],null,[4,10],[7,13],[9,14],[12,14],[14,13],[15,10],[15,0]] }, 'o': { width: 19, points: [[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3],[16,6],[16,8],[15,11],[13,13],[11,14],[8,14]] }, 'p': { width: 19, points: [[4,14],[4,-7],null,[4,11],[6,13],[8,14],[11,14],[13,13],[15,11],[16,8],[16,6],[15,3],[13,1],[11,0],[8,0],[6,1],[4,3]] }, 'q': { width: 19, points: [[15,14],[15,-7],null,[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] }, 'r': { width: 13, points: [[4,14],[4,0],null,[4,8],[5,11],[7,13],[9,14],[12,14]] }, 's': { width: 17, points: [[14,11],[13,13],[10,14],[7,14],[4,13],[3,11],[4,9],[6,8],[11,7],[13,6],[14,4],[14,3],[13,1],[10,0],[7,0],[4,1],[3,3]] }, 't': { width: 12, points: [[5,21],[5,4],[6,1],[8,0],[10,0],null,[2,14],[9,14]] }, 'u': { width: 19, points: [[4,14],[4,4],[5,1],[7,0],[10,0],[12,1],[15,4],null,[15,14],[15,0]] }, 'v': { width: 16, points: [[2,14],[8,0],null,[14,14],[8,0]] }, 'w': { width: 22, points: [[3,14],[7,0],null,[11,14],[7,0],null,[11,14],[15,0],null,[19,14],[15,0]] }, 'x': { width: 17, points: [[3,14],[14,0],null,[14,14],[3,0]] }, 'y': { width: 16, points: [[2,14],[8,0],null,[14,14],[8,0],[6,-4],[4,-6],[2,-7],[1,-7]] }, 'z': { width: 17, points: [[14,14],[3,0],null,[3,14],[14,14],null,[3,0],[14,0]] }, '{': { width: 14, points: [[9,25],[7,24],[6,23],[5,21],[5,19],[6,17],[7,16],[8,14],[8,12],[6,10],null,[7,24],[6,22],[6,20],[7,18],[8,17],[9,15],[9,13],[8,11],[4,9],[8,7],[9,5],[9,3],[8,1],[7,0],[6,-2],[6,-4],[7,-6],null,[6,8],[8,6],[8,4],[7,2],[6,1],[5,-1],[5,-3],[6,-5],[7,-6],[9,-7]] }, '|': { width: 8, points: [[4,25],[4,-7]] }, '}': { width: 14, points: [[5,25],[7,24],[8,23],[9,21],[9,19],[8,17],[7,16],[6,14],[6,12],[8,10],null,[7,24],[8,22],[8,20],[7,18],[6,17],[5,15],[5,13],[6,11],[10,9],[6,7],[5,5],[5,3],[6,1],[7,0],[8,-2],[8,-4],[7,-6],null,[8,8],[6,6],[6,4],[7,2],[8,1],[9,-1],[9,-3],[8,-5],[7,-6],[5,-7]] }, '~': { width: 24, points: [[3,6],[3,8],[4,11],[6,12],[8,12],[10,11],[14,8],[16,7],[18,7],[20,8],[21,10],null,[3,8],[4,10],[6,11],[8,11],[10,10],[14,7],[16,6],[18,6],[20,7],[21,10],[21,12]] }, '': { diacritic: '', letter: 'e' }, '': { diacritic: '`', letter: 'e' }, '': { diacritic: '^', letter: 'e' }, '': { diacritic: '', letter: 'e' }, '': { diacritic: '`', letter: 'a' }, '': { diacritic: '', letter: 'c' }, '': { diacritic: '~', letter: 'n' }, '': { diacritic: '^', letter: 'o' }, '': { diacritic: '', letter: 'E' }, '': { diacritic: '`', letter: 'E' }, '': { diacritic: '^', letter: 'E' }, '': { diacritic: '', letter: 'E' }, '': { diacritic: '`', letter: 'A' }, '': { diacritic: '', letter: 'C' }, '': { diacritic: '~', letter: 'N' }, '': { diacritic: '^', letter: 'O' } }, specialchars: { 'pi': { width: 19, points: [[6,14],[6,0],null,[14,14],[14,0],null,[2,13],[6,16],[13,13],[17,16]] } }, /** Diacritics, used to draw accentuated letters */ diacritics: { '': { entity: 'cedil', points: [[6,-4],[4,-6],[2,-7],[1,-7]] }, '': { entity: 'acute', points: [[8,19],[13,22]] }, '`': { entity: 'grave', points: [[7,22],[12,19]] }, '^': { entity: 'circ', points: [[5.5,19],[9.5,23],[12.5,19]] }, '': { entity: 'trema', points: [[5,21],[6,20],[7,21],[6,22],[5,21],null,[12,21],[13,20],[14,21],[13,22],[12,21]] }, '~': { entity: 'tilde', points: [[4,18],[7,22],[10,18],[13,22]] } }, /** The default font styling */ style: { size: 8, // font height in pixels font: null, // not yet implemented color: '#000000', // weight: 1, // float, 1 for 'normal' halign: 'l', // l: left, r: right, c: center valign: 'b', // t: top, m: middle, b: bottom adjustAlign: false, // modifies the alignments if the angle is different from 0 to make the spin point always at the good position angle: 0, // in radians, anticlockwise tracking: 1, // space between the letters, float, 1 for 'normal' boundingBoxColor: '#ff0000', //null // color of the bounding box (null to hide), can be used for debug and font drawing originPointColor: '#000000' //null // color of the bounding box (null to hide), can be used for debug and font drawing }, debug: false, _bufferLexemes: {}, /** Get the letter data corresponding to a char * @param {String} ch - The char */ letter: function(ch) { return CanvasText.letters[ch]; }, parseLexemes: function(str) { if (CanvasText._bufferLexemes[str]) return CanvasText._bufferLexemes[str]; var i, c, matches = str.match(/&[A-Za-z]{2,5};|\s|./g); var result = [], chars = []; for (i = 0; i < matches.length; i++) { c = matches[i]; if (c.length == 1) chars.push(c); else { var entity = c.substring(1, c.length-1); if (CanvasText.specialchars[entity]) chars.push(entity); else chars = chars.concat(c.toArray()); } } for (i = 0; i < chars.length; i++) { c = chars[i]; if (c = CanvasText.letters[c] || CanvasText.specialchars[c]) result.push(c); } return CanvasText._bufferLexemes[str] = result.compact(); }, /** Get the font ascent for a given style * @param {Object} style - The reference style */ ascent: function(style) { style = style || {}; return (style.size || CanvasText.style.size); }, /** Get the font descent for a given style * @param {Object} style - The reference style * */ descent: function(style) { style = style || {}; return 7.0*(style.size || CanvasText.style.size)/25.0; }, /** Measure the text horizontal size * @param {String} str - The text * @param {Object} style - Text style * */ measure: function(str, style) { if (!str) return; style = style || {}; var i, width, lexemes = CanvasText.parseLexemes(str), total = 0; for (i = lexemes.length-1; i > -1; --i) { c = lexemes[i]; width = (c.diacritic) ? CanvasText.letter(c.letter).width : c.width; total += width * (style.tracking || CanvasText.style.tracking) * (style.size || CanvasText.style.size) / 25.0; } return total; }, getDimensions: function(str, style) { var width = CanvasText.measure(str, style), height = style.size || CanvasText.style.size, angle = style.angle || CanvasText.style.angle; if (style.angle == 0) return {width: width, height: height}; return { width: Math.abs(Math.cos(angle) * width) + Math.abs(Math.sin(angle) * height), height: Math.abs(Math.sin(angle) * width) + Math.abs(Math.cos(angle) * height) } }, getBestAlign: function(angle, style) { angle += CanvasText.getAngleFromAlign(style.halign, style.valign); var a = {h:'c', v:'m'}; if (Math.round(Math.cos(angle)*1000)/1000 != 0) a.h = (Math.cos(angle) > 0 ? 'r' : 'l'); if (Math.round(Math.sin(angle)*1000)/1000 != 0) a.v = (Math.sin(angle) > 0 ? 't' : 'b'); return a; }, getAngleFromAlign: function(halign, valign) { var pi = Math.PI, table = { 'rm': 0, 'rt': pi/4, 'ct': pi/2, 'lt': 3*(pi/4), 'lm': pi, 'lb': -3*(pi/4), 'cb': -pi/2, 'rb': -pi/4, 'cm': 0 } return table[halign+valign]; }, /** Draws serie of points at given coordinates * @param {Canvas context} ctx - The canvas context * @param {Array} points - The points to draw * @param {Number} x - The X coordinate * @param {Number} y - The Y coordinate * @param {Number} mag - The scale */ drawPoints: function (ctx, points, x, y, mag, offset) { var i, a, penUp = true, needStroke = 0; offset = offset || {x:0, y:0}; ctx.beginPath(); for (i = 0; i < points.length; i++) { a = points[i]; if (!a) { penUp = true; continue; } if (penUp) { ctx.moveTo(x + a[0]*mag + offset.x, y - a[1]*mag + offset.y); penUp = false; } else { ctx.lineTo(x + a[0]*mag + offset.x, y - a[1]*mag + offset.y); } } ctx.stroke(); }, /** Draws a text at given coordinates and with a given style * @param {Canvas context} ctx - The canvas context * @param {String} str - The text to draw * @param {Number} xOrig - The X coordinate * @param {Number} yOrig - The Y coordinate * @param {Object} style - The font style */ draw: function(ctx, str, xOrig, yOrig, style) { if (!str) return; style = style || CanvasText.style; style.halign = style.halign || CanvasText.style.halign; style.valign = style.valign || CanvasText.style.valign; style.angle = style.angle || CanvasText.style.angle; style.size = style.size || CanvasText.style.size; style.adjustAlign = style.adjustAlign || CanvasText.style.adjustAlign; var i, c, total = 0, mag = style.size / 25.0, x = 0, y = 0, lexemes = CanvasText.parseLexemes(str); var offset = {x:0, y:0}, measure = CanvasText.measure(str, style), align; if (style.adjustAlign) { align = CanvasText.getBestAlign(style.angle, style); style.halign = align.h; style.valign = align.v; } switch (style.halign) { case 'l': break; case 'c': offset.x = -measure / 2; break; case 'r': offset.x = -measure; break; } switch (style.valign) { case 'b': break; case 'm': offset.y = style.size / 2; break; case 't': offset.y = style.size; break; } ctx.save(); ctx.translate(xOrig, yOrig); ctx.rotate(style.angle); ctx.lineCap = "round"; ctx.lineWidth = 2.0 * mag * (style.weight || CanvasText.style.weight); ctx.strokeStyle = style.color || CanvasText.style.color; for (i = 0; i < lexemes.length; i++) { c = lexemes[i]; if (c.width == -1) { x = 0; y = style.size * 1.4; continue; } var points = c.points, width = c.width; if (c.diacritic) { var dia = CanvasText.diacritics[c.diacritic]; var char = CanvasText.letter(c.letter); CanvasText.drawPoints(ctx, dia.points, x, y - (c.letter.toUpperCase() == c.letter ? 3 : 0), mag, offset); points = char.points; width = char.width; } CanvasText.drawPoints(ctx, points, x, y, mag, offset); if (CanvasText.debug) { ctx.save(); ctx.lineJoin = "miter"; ctx.lineWidth = 0.5; ctx.strokeStyle = (style.boundingBoxColor || CanvasText.style.boundingBoxColor); ctx.strokeRect(x+offset.x, y+offset.y, width*mag, -style.size); ctx.fillStyle = (style.originPointColor || CanvasText.style.originPointColor); ctx.beginPath(); ctx.arc(0, 0, 1.5, 0, Math.PI*2, true); ctx.fill(); ctx.restore(); } x += width*mag*(style.tracking || CanvasText.style.tracking); } ctx.restore(); return total; }, /** Enables the text function for a Canvas context * @param {Canvas context} ctx - The canvas context */ enable: function(ctx) { ctx.drawText = function(text, x, y, style) { return CanvasText.draw(ctx, text, x, y, style); }; ctx.measureText = function(text, style) { return CanvasText.measure(text, style); }; ctx.getTextBounds = function(text, style) { return CanvasText.getDimensions(text, style); }; ctx.fontAscent = function(style) { return CanvasText.ascent(style); }; ctx.fontDescent = function(style) { return CanvasText.descent(style); }; } }; ================================================ FILE: input-scripts/flotr/lib/excanvas.js ================================================ // Copyright 2006 Google Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Known Issues: // // * Patterns are not implemented. // * Radial gradient are not implemented. The VML version of these look very // different from the canvas one. // * Clipping paths are not implemented. // * Coordsize. The width and height attribute have higher priority than the // width and height style values which isn't correct. // * Painting mode isn't implemented. // * Canvas width/height should is using content-box by default. IE in // Quirks mode will draw the canvas using border-box. Either change your // doctype to HTML5 // (http://www.whatwg.org/specs/web-apps/current-work/#the-doctype) // or use Box Sizing Behavior from WebFX // (http://webfx.eae.net/dhtml/boxsizing/boxsizing.html) // * Non uniform scaling does not correctly scale strokes. // * Optimize. There is always room for speed improvements. // Only add this code if we do not already have a canvas implementation if (!document.createElement('canvas').getContext) { (function() { // alias some functions to make (compiled) code shorter var m = Math; var mr = m.round; var ms = m.sin; var mc = m.cos; var abs = m.abs; var sqrt = m.sqrt; // this is used for sub pixel precision var Z = 10; var Z2 = Z / 2; /** * This funtion is assigned to the elements as element.getContext(). * @this {HTMLElement} * @return {CanvasRenderingContext2D_} */ function getContext() { return this.context_ || (this.context_ = new CanvasRenderingContext2D_(this)); } var slice = Array.prototype.slice; /** * Binds a function to an object. The returned function will always use the * passed in {@code obj} as {@code this}. * * Example: * * g = bind(f, obj, a, b) * g(c, d) // will do f.call(obj, a, b, c, d) * * @param {Function} f The function to bind the object to * @param {Object} obj The object that should act as this when the function * is called * @param {*} var_args Rest arguments that will be used as the initial * arguments when the function is called * @return {Function} A new function that has bound this */ function bind(f, obj, var_args) { var a = slice.call(arguments, 2); return function() { return f.apply(obj, a.concat(slice.call(arguments))); }; } var G_vmlCanvasManager_ = { init: function(opt_doc) { if (/MSIE/.test(navigator.userAgent) && !window.opera) { var doc = opt_doc || document; // Create a dummy element so that IE will allow canvas elements to be // recognized. doc.createElement('canvas'); doc.attachEvent('onreadystatechange', bind(this.init_, this, doc)); } }, init_: function(doc) { // create xmlns if (!doc.namespaces['g_vml_']) { doc.namespaces.add('g_vml_', 'urn:schemas-microsoft-com:vml', '#default#VML'); } if (!doc.namespaces['g_o_']) { doc.namespaces.add('g_o_', 'urn:schemas-microsoft-com:office:office', '#default#VML'); } // Setup default CSS. Only add one style sheet per document if (!doc.styleSheets['ex_canvas_']) { var ss = doc.createStyleSheet(); ss.owningElement.id = 'ex_canvas_'; ss.cssText = 'canvas{display:inline-block;overflow:hidden;' + // default size is 300x150 in Gecko and Opera 'text-align:left;width:300px;height:150px}' + 'g_vml_\\:*{behavior:url(#default#VML)}' + 'g_o_\\:*{behavior:url(#default#VML)}'; } // find all canvas elements var els = doc.getElementsByTagName('canvas'); for (var i = 0; i < els.length; i++) { this.initElement(els[i]); } }, /** * Public initializes a canvas element so that it can be used as canvas * element from now on. This is called automatically before the page is * loaded but if you are creating elements using createElement you need to * make sure this is called on the element. * @param {HTMLElement} el The canvas element to initialize. * @return {HTMLElement} the element that was created. */ initElement: function(el) { if (!el.getContext) { el.getContext = getContext; // Remove fallback content. There is no way to hide text nodes so we // just remove all childNodes. We could hide all elements and remove // text nodes but who really cares about the fallback content. el.innerHTML = ''; // do not use inline function because that will leak memory el.attachEvent('onpropertychange', onPropertyChange); el.attachEvent('onresize', onResize); var attrs = el.attributes; if (attrs.width && attrs.width.specified) { // TODO: use runtimeStyle and coordsize // el.getContext().setWidth_(attrs.width.nodeValue); el.style.width = attrs.width.nodeValue + 'px'; } else { el.width = el.clientWidth; } if (attrs.height && attrs.height.specified) { // TODO: use runtimeStyle and coordsize // el.getContext().setHeight_(attrs.height.nodeValue); el.style.height = attrs.height.nodeValue + 'px'; } else { el.height = el.clientHeight; } //el.getContext().setCoordsize_() } return el; } }; function onPropertyChange(e) { var el = e.srcElement; switch (e.propertyName) { case 'width': el.style.width = el.attributes.width.nodeValue + 'px'; el.getContext().clearRect(); break; case 'height': el.style.height = el.attributes.height.nodeValue + 'px'; el.getContext().clearRect(); break; } } function onResize(e) { var el = e.srcElement; if (el.firstChild) { el.firstChild.style.width = el.clientWidth + 'px'; el.firstChild.style.height = el.clientHeight + 'px'; } } G_vmlCanvasManager_.init(); // precompute "00" to "FF" var dec2hex = []; for (var i = 0; i < 16; i++) { for (var j = 0; j < 16; j++) { dec2hex[i * 16 + j] = i.toString(16) + j.toString(16); } } function createMatrixIdentity() { return [ [1, 0, 0], [0, 1, 0], [0, 0, 1] ]; } function matrixMultiply(m1, m2) { var result = createMatrixIdentity(); for (var x = 0; x < 3; x++) { for (var y = 0; y < 3; y++) { var sum = 0; for (var z = 0; z < 3; z++) { sum += m1[x][z] * m2[z][y]; } result[x][y] = sum; } } return result; } function copyState(o1, o2) { o2.fillStyle = o1.fillStyle; o2.lineCap = o1.lineCap; o2.lineJoin = o1.lineJoin; o2.lineWidth = o1.lineWidth; o2.miterLimit = o1.miterLimit; o2.shadowBlur = o1.shadowBlur; o2.shadowColor = o1.shadowColor; o2.shadowOffsetX = o1.shadowOffsetX; o2.shadowOffsetY = o1.shadowOffsetY; o2.strokeStyle = o1.strokeStyle; o2.globalAlpha = o1.globalAlpha; o2.arcScaleX_ = o1.arcScaleX_; o2.arcScaleY_ = o1.arcScaleY_; o2.lineScale_ = o1.lineScale_; } function processStyle(styleString) { var str, alpha = 1; styleString = String(styleString); if (styleString.substring(0, 3) == 'rgb') { var start = styleString.indexOf('(', 3); var end = styleString.indexOf(')', start + 1); var guts = styleString.substring(start + 1, end).split(','); str = '#'; for (var i = 0; i < 3; i++) { str += dec2hex[Number(guts[i])]; } if (guts.length == 4 && styleString.substr(3, 1) == 'a') { alpha = guts[3]; } } else { str = styleString; } return {color: str, alpha: alpha}; } function processLineCap(lineCap) { switch (lineCap) { case 'butt': return 'flat'; case 'round': return 'round'; case 'square': default: return 'square'; } } /** * This class implements CanvasRenderingContext2D interface as described by * the WHATWG. * @param {HTMLElement} surfaceElement The element that the 2D context should * be associated with */ function CanvasRenderingContext2D_(surfaceElement) { this.m_ = createMatrixIdentity(); this.mStack_ = []; this.aStack_ = []; this.currentPath_ = []; // Canvas context properties this.strokeStyle = '#000'; this.fillStyle = '#000'; this.lineWidth = 1; this.lineJoin = 'miter'; this.lineCap = 'butt'; this.miterLimit = Z * 1; this.globalAlpha = 1; this.canvas = surfaceElement; var el = surfaceElement.ownerDocument.createElement('div'); el.style.width = surfaceElement.clientWidth + 'px'; el.style.height = surfaceElement.clientHeight + 'px'; el.style.overflow = 'hidden'; el.style.position = 'absolute'; surfaceElement.appendChild(el); this.element_ = el; this.arcScaleX_ = 1; this.arcScaleY_ = 1; this.lineScale_ = 1; } var contextPrototype = CanvasRenderingContext2D_.prototype; contextPrototype.clearRect = function() { this.element_.innerHTML = ''; }; contextPrototype.beginPath = function() { // TODO: Branch current matrix so that save/restore has no effect // as per safari docs. this.currentPath_ = []; }; contextPrototype.moveTo = function(aX, aY) { var p = this.getCoords_(aX, aY); this.currentPath_.push({type: 'moveTo', x: p.x, y: p.y}); this.currentX_ = p.x; this.currentY_ = p.y; }; contextPrototype.lineTo = function(aX, aY) { var p = this.getCoords_(aX, aY); this.currentPath_.push({type: 'lineTo', x: p.x, y: p.y}); this.currentX_ = p.x; this.currentY_ = p.y; }; contextPrototype.bezierCurveTo = function(aCP1x, aCP1y, aCP2x, aCP2y, aX, aY) { var p = this.getCoords_(aX, aY); var cp1 = this.getCoords_(aCP1x, aCP1y); var cp2 = this.getCoords_(aCP2x, aCP2y); bezierCurveTo(this, cp1, cp2, p); }; // Helper function that takes the already fixed cordinates. function bezierCurveTo(self, cp1, cp2, p) { self.currentPath_.push({ type: 'bezierCurveTo', cp1x: cp1.x, cp1y: cp1.y, cp2x: cp2.x, cp2y: cp2.y, x: p.x, y: p.y }); self.currentX_ = p.x; self.currentY_ = p.y; } contextPrototype.quadraticCurveTo = function(aCPx, aCPy, aX, aY) { // the following is lifted almost directly from // http://developer.mozilla.org/en/docs/Canvas_tutorial:Drawing_shapes var cp = this.getCoords_(aCPx, aCPy); var p = this.getCoords_(aX, aY); var cp1 = { x: this.currentX_ + 2.0 / 3.0 * (cp.x - this.currentX_), y: this.currentY_ + 2.0 / 3.0 * (cp.y - this.currentY_) }; var cp2 = { x: cp1.x + (p.x - this.currentX_) / 3.0, y: cp1.y + (p.y - this.currentY_) / 3.0 }; bezierCurveTo(this, cp1, cp2, p); }; contextPrototype.arc = function(aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise) { aRadius *= Z; var arcType = aClockwise ? 'at' : 'wa'; var xStart = aX + mc(aStartAngle) * aRadius - Z2; var yStart = aY + ms(aStartAngle) * aRadius - Z2; var xEnd = aX + mc(aEndAngle) * aRadius - Z2; var yEnd = aY + ms(aEndAngle) * aRadius - Z2; // IE won't render arches drawn counter clockwise if xStart == xEnd. if (xStart == xEnd && !aClockwise) { xStart += 0.125; // Offset xStart by 1/80 of a pixel. Use something // that can be represented in binary } var p = this.getCoords_(aX, aY); var pStart = this.getCoords_(xStart, yStart); var pEnd = this.getCoords_(xEnd, yEnd); this.currentPath_.push({type: arcType, x: p.x, y: p.y, radius: aRadius, xStart: pStart.x, yStart: pStart.y, xEnd: pEnd.x, yEnd: pEnd.y}); }; contextPrototype.rect = function(aX, aY, aWidth, aHeight) { this.moveTo(aX, aY); this.lineTo(aX + aWidth, aY); this.lineTo(aX + aWidth, aY + aHeight); this.lineTo(aX, aY + aHeight); this.closePath(); }; contextPrototype.strokeRect = function(aX, aY, aWidth, aHeight) { var oldPath = this.currentPath_; this.beginPath(); this.moveTo(aX, aY); this.lineTo(aX + aWidth, aY); this.lineTo(aX + aWidth, aY + aHeight); this.lineTo(aX, aY + aHeight); this.closePath(); this.stroke(); this.currentPath_ = oldPath; }; contextPrototype.fillRect = function(aX, aY, aWidth, aHeight) { var oldPath = this.currentPath_; this.beginPath(); this.moveTo(aX, aY); this.lineTo(aX + aWidth, aY); this.lineTo(aX + aWidth, aY + aHeight); this.lineTo(aX, aY + aHeight); this.closePath(); this.fill(); this.currentPath_ = oldPath; }; contextPrototype.createLinearGradient = function(aX0, aY0, aX1, aY1) { var gradient = new CanvasGradient_('gradient'); gradient.x0_ = aX0; gradient.y0_ = aY0; gradient.x1_ = aX1; gradient.y1_ = aY1; return gradient; }; contextPrototype.createRadialGradient = function(aX0, aY0, aR0, aX1, aY1, aR1) { var gradient = new CanvasGradient_('gradientradial'); gradient.x0_ = aX0; gradient.y0_ = aY0; gradient.r0_ = aR0; gradient.x1_ = aX1; gradient.y1_ = aY1; gradient.r1_ = aR1; return gradient; }; contextPrototype.drawImage = function(image, var_args) { var dx, dy, dw, dh, sx, sy, sw, sh; // to find the original width we overide the width and height var oldRuntimeWidth = image.runtimeStyle.width; var oldRuntimeHeight = image.runtimeStyle.height; image.runtimeStyle.width = 'auto'; image.runtimeStyle.height = 'auto'; // get the original size var w = image.width; var h = image.height; // and remove overides image.runtimeStyle.width = oldRuntimeWidth; image.runtimeStyle.height = oldRuntimeHeight; if (arguments.length == 3) { dx = arguments[1]; dy = arguments[2]; sx = sy = 0; sw = dw = w; sh = dh = h; } else if (arguments.length == 5) { dx = arguments[1]; dy = arguments[2]; dw = arguments[3]; dh = arguments[4]; sx = sy = 0; sw = w; sh = h; } else if (arguments.length == 9) { sx = arguments[1]; sy = arguments[2]; sw = arguments[3]; sh = arguments[4]; dx = arguments[5]; dy = arguments[6]; dw = arguments[7]; dh = arguments[8]; } else { throw Error('Invalid number of arguments'); } var d = this.getCoords_(dx, dy); var w2 = sw / 2; var h2 = sh / 2; var vmlStr = []; var W = 10; var H = 10; // For some reason that I've now forgotten, using divs didn't work vmlStr.push(' ' , '', ''); this.element_.insertAdjacentHTML('BeforeEnd', vmlStr.join('')); }; contextPrototype.stroke = function(aFill) { var lineStr = []; var lineOpen = false; var a = processStyle(aFill ? this.fillStyle : this.strokeStyle); var color = a.color; var opacity = a.alpha * this.globalAlpha; var W = 10; var H = 10; lineStr.push(''); if (!aFill) { var lineWidth = this.lineScale_ * this.lineWidth; // VML cannot correctly render a line if the width is less than 1px. // In that case, we dilute the color to make the line look thinner. if (lineWidth < 1) { opacity *= lineWidth; } lineStr.push( '' ); } else if (typeof this.fillStyle == 'object') { var fillStyle = this.fillStyle; var angle = 0; var focus = {x: 0, y: 0}; // additional offset var shift = 0; // scale factor for offset var expansion = 1; if (fillStyle.type_ == 'gradient') { var x0 = fillStyle.x0_ / this.arcScaleX_; var y0 = fillStyle.y0_ / this.arcScaleY_; var x1 = fillStyle.x1_ / this.arcScaleX_; var y1 = fillStyle.y1_ / this.arcScaleY_; var p0 = this.getCoords_(x0, y0); var p1 = this.getCoords_(x1, y1); var dx = p1.x - p0.x; var dy = p1.y - p0.y; angle = Math.atan2(dx, dy) * 180 / Math.PI; // The angle should be a non-negative number. if (angle < 0) { angle += 360; } // Very small angles produce an unexpected result because they are // converted to a scientific notation string. if (angle < 1e-6) { angle = 0; } } else { var p0 = this.getCoords_(fillStyle.x0_, fillStyle.y0_); var width = max.x - min.x; var height = max.y - min.y; focus = { x: (p0.x - min.x) / width, y: (p0.y - min.y) / height }; width /= this.arcScaleX_ * Z; height /= this.arcScaleY_ * Z; var dimension = m.max(width, height); shift = 2 * fillStyle.r0_ / dimension; expansion = 2 * fillStyle.r1_ / dimension - shift; } // We need to sort the color stops in ascending order by offset, // otherwise IE won't interpret it correctly. var stops = fillStyle.colors_; stops.sort(function(cs1, cs2) { return cs1.offset - cs2.offset; }); var length = stops.length; var color1 = stops[0].color; var color2 = stops[length - 1].color; var opacity1 = stops[0].alpha * this.globalAlpha; var opacity2 = stops[length - 1].alpha * this.globalAlpha; var colors = []; for (var i = 0; i < length; i++) { var stop = stops[i]; colors.push(stop.offset * expansion + shift + ' ' + stop.color); } // When colors attribute is used, the meanings of opacity and o:opacity2 // are reversed. lineStr.push(''); } else { lineStr.push(''); } lineStr.push(''); this.element_.insertAdjacentHTML('beforeEnd', lineStr.join('')); }; contextPrototype.fill = function() { this.stroke(true); } contextPrototype.closePath = function() { this.currentPath_.push({type: 'close'}); }; /** * @private */ contextPrototype.getCoords_ = function(aX, aY) { var m = this.m_; return { x: Z * (aX * m[0][0] + aY * m[1][0] + m[2][0]) - Z2, y: Z * (aX * m[0][1] + aY * m[1][1] + m[2][1]) - Z2 } }; contextPrototype.save = function() { var o = {}; copyState(this, o); this.aStack_.push(o); this.mStack_.push(this.m_); this.m_ = matrixMultiply(createMatrixIdentity(), this.m_); }; contextPrototype.restore = function() { copyState(this.aStack_.pop(), this); this.m_ = this.mStack_.pop(); }; contextPrototype.translate = function(aX, aY) { var m1 = [ [1, 0, 0], [0, 1, 0], [aX, aY, 1] ]; this.m_ = matrixMultiply(m1, this.m_); }; contextPrototype.rotate = function(aRot) { var c = mc(aRot); var s = ms(aRot); var m1 = [ [c, s, 0], [-s, c, 0], [0, 0, 1] ]; this.m_ = matrixMultiply(m1, this.m_); }; contextPrototype.scale = function(aX, aY) { this.arcScaleX_ *= aX; this.arcScaleY_ *= aY; var m1 = [ [aX, 0, 0], [0, aY, 0], [0, 0, 1] ]; var m = this.m_ = matrixMultiply(m1, this.m_); // Get the line scale. // Determinant of this.m_ means how much the area is enlarged by the // transformation. So its square root can be used as a scale factor // for width. var det = m[0][0] * m[1][1] - m[0][1] * m[1][0]; this.lineScale_ = sqrt(abs(det)); }; /******** STUBS ********/ contextPrototype.clip = function() { // TODO: Implement }; contextPrototype.arcTo = function() { // TODO: Implement }; contextPrototype.createPattern = function() { return new CanvasPattern_; }; // Gradient / Pattern Stubs function CanvasGradient_(aType) { this.type_ = aType; this.x0_ = 0; this.y0_ = 0; this.r0_ = 0; this.x1_ = 0; this.y1_ = 0; this.r1_ = 0; this.colors_ = []; } CanvasGradient_.prototype.addColorStop = function(aOffset, aColor) { aColor = processStyle(aColor); this.colors_.push({offset: aOffset, color: aColor.color, alpha: aColor.alpha}); }; function CanvasPattern_() {} // set up externs G_vmlCanvasManager = G_vmlCanvasManager_; CanvasRenderingContext2D = CanvasRenderingContext2D_; CanvasGradient = CanvasGradient_; CanvasPattern = CanvasPattern_; })(); } // if ================================================ FILE: input-scripts/flotr/lib/prototype-1.6.0.2.js ================================================ /* Prototype JavaScript framework, version 1.6.0.2 * (c) 2005-2008 Sam Stephenson * * Prototype is freely distributable under the terms of an MIT-style license. * For details, see the Prototype web site: http://www.prototypejs.org/ * *--------------------------------------------------------------------------*/ var Prototype = { Version: '1.6.0.2', Browser: { IE: !!(window.attachEvent && !window.opera), Opera: !!window.opera, WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1, Gecko: navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') == -1, MobileSafari: !!navigator.userAgent.match(/Apple.*Mobile.*Safari/) }, BrowserFeatures: { XPath: !!document.evaluate, ElementExtensions: !!window.HTMLElement, SpecificElementExtensions: document.createElement('div').__proto__ && document.createElement('div').__proto__ !== document.createElement('form').__proto__ }, ScriptFragment: ']*>([\\S\\s]*?)<\/script>', JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/, emptyFunction: function() { }, K: function(x) { return x } }; if (Prototype.Browser.MobileSafari) Prototype.BrowserFeatures.SpecificElementExtensions = false; /* Based on Alex Arnell's inheritance implementation. */ var Class = { create: function() { var parent = null, properties = $A(arguments); if (Object.isFunction(properties[0])) parent = properties.shift(); function klass() { this.initialize.apply(this, arguments); } Object.extend(klass, Class.Methods); klass.superclass = parent; klass.subclasses = []; if (parent) { var subclass = function() { }; subclass.prototype = parent.prototype; klass.prototype = new subclass; parent.subclasses.push(klass); } for (var i = 0; i < properties.length; i++) klass.addMethods(properties[i]); if (!klass.prototype.initialize) klass.prototype.initialize = Prototype.emptyFunction; klass.prototype.constructor = klass; return klass; } }; Class.Methods = { addMethods: function(source) { var ancestor = this.superclass && this.superclass.prototype; var properties = Object.keys(source); if (!Object.keys({ toString: true }).length) properties.push("toString", "valueOf"); for (var i = 0, length = properties.length; i < length; i++) { var property = properties[i], value = source[property]; if (ancestor && Object.isFunction(value) && value.argumentNames().first() == "$super") { var method = value, value = Object.extend((function(m) { return function() { return ancestor[m].apply(this, arguments) }; })(property).wrap(method), { valueOf: function() { return method }, toString: function() { return method.toString() } }); } this.prototype[property] = value; } return this; } }; var Abstract = { }; Object.extend = function(destination, source) { for (var property in source) destination[property] = source[property]; return destination; }; Object.extend(Object, { inspect: function(object) { try { if (Object.isUndefined(object)) return 'undefined'; if (object === null) return 'null'; return object.inspect ? object.inspect() : String(object); } catch (e) { if (e instanceof RangeError) return '...'; throw e; } }, toJSON: function(object) { var type = typeof object; switch (type) { case 'undefined': case 'function': case 'unknown': return; case 'boolean': return object.toString(); } if (object === null) return 'null'; if (object.toJSON) return object.toJSON(); if (Object.isElement(object)) return; var results = []; for (var property in object) { var value = Object.toJSON(object[property]); if (!Object.isUndefined(value)) results.push(property.toJSON() + ': ' + value); } return '{' + results.join(', ') + '}'; }, toQueryString: function(object) { return $H(object).toQueryString(); }, toHTML: function(object) { return object && object.toHTML ? object.toHTML() : String.interpret(object); }, keys: function(object) { var keys = []; for (var property in object) keys.push(property); return keys; }, values: function(object) { var values = []; for (var property in object) values.push(object[property]); return values; }, clone: function(object) { return Object.extend({ }, object); }, isElement: function(object) { return object && object.nodeType == 1; }, isArray: function(object) { return object != null && typeof object == "object" && 'splice' in object && 'join' in object; }, isHash: function(object) { return object instanceof Hash; }, isFunction: function(object) { return typeof object == "function"; }, isString: function(object) { return typeof object == "string"; }, isNumber: function(object) { return typeof object == "number"; }, isUndefined: function(object) { return typeof object == "undefined"; } }); Object.extend(Function.prototype, { argumentNames: function() { var names = this.toString().match(/^[\s\(]*function[^(]*\((.*?)\)/)[1].split(",").invoke("strip"); return names.length == 1 && !names[0] ? [] : names; }, bind: function() { if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this; var __method = this, args = $A(arguments), object = args.shift(); return function() { return __method.apply(object, args.concat($A(arguments))); } }, bindAsEventListener: function() { var __method = this, args = $A(arguments), object = args.shift(); return function(event) { return __method.apply(object, [event || window.event].concat(args)); } }, curry: function() { if (!arguments.length) return this; var __method = this, args = $A(arguments); return function() { return __method.apply(this, args.concat($A(arguments))); } }, delay: function() { var __method = this, args = $A(arguments), timeout = args.shift() * 1000; return window.setTimeout(function() { return __method.apply(__method, args); }, timeout); }, wrap: function(wrapper) { var __method = this; return function() { return wrapper.apply(this, [__method.bind(this)].concat($A(arguments))); } }, methodize: function() { if (this._methodized) return this._methodized; var __method = this; return this._methodized = function() { return __method.apply(null, [this].concat($A(arguments))); }; } }); Function.prototype.defer = Function.prototype.delay.curry(0.01); Date.prototype.toJSON = function() { return '"' + this.getUTCFullYear() + '-' + (this.getUTCMonth() + 1).toPaddedString(2) + '-' + this.getUTCDate().toPaddedString(2) + 'T' + this.getUTCHours().toPaddedString(2) + ':' + this.getUTCMinutes().toPaddedString(2) + ':' + this.getUTCSeconds().toPaddedString(2) + 'Z"'; }; var Try = { these: function() { var returnValue; for (var i = 0, length = arguments.length; i < length; i++) { var lambda = arguments[i]; try { returnValue = lambda(); break; } catch (e) { } } return returnValue; } }; RegExp.prototype.match = RegExp.prototype.test; RegExp.escape = function(str) { return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1'); }; /*--------------------------------------------------------------------------*/ var PeriodicalExecuter = Class.create({ initialize: function(callback, frequency) { this.callback = callback; this.frequency = frequency; this.currentlyExecuting = false; this.registerCallback(); }, registerCallback: function() { this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); }, execute: function() { this.callback(this); }, stop: function() { if (!this.timer) return; clearInterval(this.timer); this.timer = null; }, onTimerEvent: function() { if (!this.currentlyExecuting) { try { this.currentlyExecuting = true; this.execute(); } finally { this.currentlyExecuting = false; } } } }); Object.extend(String, { interpret: function(value) { return value == null ? '' : String(value); }, specialChar: { '\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '\\': '\\\\' } }); Object.extend(String.prototype, { gsub: function(pattern, replacement) { var result = '', source = this, match; replacement = arguments.callee.prepareReplacement(replacement); while (source.length > 0) { if (match = source.match(pattern)) { result += source.slice(0, match.index); result += String.interpret(replacement(match)); source = source.slice(match.index + match[0].length); } else { result += source, source = ''; } } return result; }, sub: function(pattern, replacement, count) { replacement = this.gsub.prepareReplacement(replacement); count = Object.isUndefined(count) ? 1 : count; return this.gsub(pattern, function(match) { if (--count < 0) return match[0]; return replacement(match); }); }, scan: function(pattern, iterator) { this.gsub(pattern, iterator); return String(this); }, truncate: function(length, truncation) { length = length || 30; truncation = Object.isUndefined(truncation) ? '...' : truncation; return this.length > length ? this.slice(0, length - truncation.length) + truncation : String(this); }, strip: function() { return this.replace(/^\s+/, '').replace(/\s+$/, ''); }, stripTags: function() { return this.replace(/<\/?[^>]+>/gi, ''); }, stripScripts: function() { return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), ''); }, extractScripts: function() { var matchAll = new RegExp(Prototype.ScriptFragment, 'img'); var matchOne = new RegExp(Prototype.ScriptFragment, 'im'); return (this.match(matchAll) || []).map(function(scriptTag) { return (scriptTag.match(matchOne) || ['', ''])[1]; }); }, evalScripts: function() { return this.extractScripts().map(function(script) { return eval(script) }); }, escapeHTML: function() { var self = arguments.callee; self.text.data = this; return self.div.innerHTML; }, unescapeHTML: function() { var div = new Element('div'); div.innerHTML = this.stripTags(); return div.childNodes[0] ? (div.childNodes.length > 1 ? $A(div.childNodes).inject('', function(memo, node) { return memo+node.nodeValue }) : div.childNodes[0].nodeValue) : ''; }, toQueryParams: function(separator) { var match = this.strip().match(/([^?#]*)(#.*)?$/); if (!match) return { }; return match[1].split(separator || '&').inject({ }, function(hash, pair) { if ((pair = pair.split('='))[0]) { var key = decodeURIComponent(pair.shift()); var value = pair.length > 1 ? pair.join('=') : pair[0]; if (value != undefined) value = decodeURIComponent(value); if (key in hash) { if (!Object.isArray(hash[key])) hash[key] = [hash[key]]; hash[key].push(value); } else hash[key] = value; } return hash; }); }, toArray: function() { return this.split(''); }, succ: function() { return this.slice(0, this.length - 1) + String.fromCharCode(this.charCodeAt(this.length - 1) + 1); }, times: function(count) { return count < 1 ? '' : new Array(count + 1).join(this); }, camelize: function() { var parts = this.split('-'), len = parts.length; if (len == 1) return parts[0]; var camelized = this.charAt(0) == '-' ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1) : parts[0]; for (var i = 1; i < len; i++) camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1); return camelized; }, capitalize: function() { return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase(); }, underscore: function() { return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase(); }, dasherize: function() { return this.gsub(/_/,'-'); }, inspect: function(useDoubleQuotes) { var escapedString = this.gsub(/[\x00-\x1f\\]/, function(match) { var character = String.specialChar[match[0]]; return character ? character : '\\u00' + match[0].charCodeAt().toPaddedString(2, 16); }); if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"'; return "'" + escapedString.replace(/'/g, '\\\'') + "'"; }, toJSON: function() { return this.inspect(true); }, unfilterJSON: function(filter) { return this.sub(filter || Prototype.JSONFilter, '#{1}'); }, isJSON: function() { var str = this; if (str.blank()) return false; str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, ''); return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str); }, evalJSON: function(sanitize) { var json = this.unfilterJSON(); try { if (!sanitize || json.isJSON()) return eval('(' + json + ')'); } catch (e) { } throw new SyntaxError('Badly formed JSON string: ' + this.inspect()); }, include: function(pattern) { return this.indexOf(pattern) > -1; }, startsWith: function(pattern) { return this.indexOf(pattern) === 0; }, endsWith: function(pattern) { var d = this.length - pattern.length; return d >= 0 && this.lastIndexOf(pattern) === d; }, empty: function() { return this == ''; }, blank: function() { return /^\s*$/.test(this); }, interpolate: function(object, pattern) { return new Template(this, pattern).evaluate(object); } }); if (Prototype.Browser.WebKit || Prototype.Browser.IE) Object.extend(String.prototype, { escapeHTML: function() { return this.replace(/&/g,'&').replace(//g,'>'); }, unescapeHTML: function() { return this.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); } }); String.prototype.gsub.prepareReplacement = function(replacement) { if (Object.isFunction(replacement)) return replacement; var template = new Template(replacement); return function(match) { return template.evaluate(match) }; }; String.prototype.parseQuery = String.prototype.toQueryParams; Object.extend(String.prototype.escapeHTML, { div: document.createElement('div'), text: document.createTextNode('') }); with (String.prototype.escapeHTML) div.appendChild(text); var Template = Class.create({ initialize: function(template, pattern) { this.template = template.toString(); this.pattern = pattern || Template.Pattern; }, evaluate: function(object) { if (Object.isFunction(object.toTemplateReplacements)) object = object.toTemplateReplacements(); return this.template.gsub(this.pattern, function(match) { if (object == null) return ''; var before = match[1] || ''; if (before == '\\') return match[2]; var ctx = object, expr = match[3]; var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/; match = pattern.exec(expr); if (match == null) return before; while (match != null) { var comp = match[1].startsWith('[') ? match[2].gsub('\\\\]', ']') : match[1]; ctx = ctx[comp]; if (null == ctx || '' == match[3]) break; expr = expr.substring('[' == match[3] ? match[1].length : match[0].length); match = pattern.exec(expr); } return before + String.interpret(ctx); }); } }); Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; var $break = { }; var Enumerable = { each: function(iterator, context) { var index = 0; iterator = iterator.bind(context); try { this._each(function(value) { iterator(value, index++); }); } catch (e) { if (e != $break) throw e; } return this; }, eachSlice: function(number, iterator, context) { iterator = iterator ? iterator.bind(context) : Prototype.K; var index = -number, slices = [], array = this.toArray(); while ((index += number) < array.length) slices.push(array.slice(index, index+number)); return slices.collect(iterator, context); }, all: function(iterator, context) { iterator = iterator ? iterator.bind(context) : Prototype.K; var result = true; this.each(function(value, index) { result = result && !!iterator(value, index); if (!result) throw $break; }); return result; }, any: function(iterator, context) { iterator = iterator ? iterator.bind(context) : Prototype.K; var result = false; this.each(function(value, index) { if (result = !!iterator(value, index)) throw $break; }); return result; }, collect: function(iterator, context) { iterator = iterator ? iterator.bind(context) : Prototype.K; var results = []; this.each(function(value, index) { results.push(iterator(value, index)); }); return results; }, detect: function(iterator, context) { iterator = iterator.bind(context); var result; this.each(function(value, index) { if (iterator(value, index)) { result = value; throw $break; } }); return result; }, findAll: function(iterator, context) { iterator = iterator.bind(context); var results = []; this.each(function(value, index) { if (iterator(value, index)) results.push(value); }); return results; }, grep: function(filter, iterator, context) { iterator = iterator ? iterator.bind(context) : Prototype.K; var results = []; if (Object.isString(filter)) filter = new RegExp(filter); this.each(function(value, index) { if (filter.match(value)) results.push(iterator(value, index)); }); return results; }, include: function(object) { if (Object.isFunction(this.indexOf)) if (this.indexOf(object) != -1) return true; var found = false; this.each(function(value) { if (value == object) { found = true; throw $break; } }); return found; }, inGroupsOf: function(number, fillWith) { fillWith = Object.isUndefined(fillWith) ? null : fillWith; return this.eachSlice(number, function(slice) { while(slice.length < number) slice.push(fillWith); return slice; }); }, inject: function(memo, iterator, context) { iterator = iterator.bind(context); this.each(function(value, index) { memo = iterator(memo, value, index); }); return memo; }, invoke: function(method) { var args = $A(arguments).slice(1); return this.map(function(value) { return value[method].apply(value, args); }); }, max: function(iterator, context) { iterator = iterator ? iterator.bind(context) : Prototype.K; var result; this.each(function(value, index) { value = iterator(value, index); if (result == null || value >= result) result = value; }); return result; }, min: function(iterator, context) { iterator = iterator ? iterator.bind(context) : Prototype.K; var result; this.each(function(value, index) { value = iterator(value, index); if (result == null || value < result) result = value; }); return result; }, partition: function(iterator, context) { iterator = iterator ? iterator.bind(context) : Prototype.K; var trues = [], falses = []; this.each(function(value, index) { (iterator(value, index) ? trues : falses).push(value); }); return [trues, falses]; }, pluck: function(property) { var results = []; this.each(function(value) { results.push(value[property]); }); return results; }, reject: function(iterator, context) { iterator = iterator.bind(context); var results = []; this.each(function(value, index) { if (!iterator(value, index)) results.push(value); }); return results; }, sortBy: function(iterator, context) { iterator = iterator.bind(context); return this.map(function(value, index) { return {value: value, criteria: iterator(value, index)}; }).sort(function(left, right) { var a = left.criteria, b = right.criteria; return a < b ? -1 : a > b ? 1 : 0; }).pluck('value'); }, toArray: function() { return this.map(); }, zip: function() { var iterator = Prototype.K, args = $A(arguments); if (Object.isFunction(args.last())) iterator = args.pop(); var collections = [this].concat(args).map($A); return this.map(function(value, index) { return iterator(collections.pluck(index)); }); }, size: function() { return this.toArray().length; }, inspect: function() { return '#'; } }; Object.extend(Enumerable, { map: Enumerable.collect, find: Enumerable.detect, select: Enumerable.findAll, filter: Enumerable.findAll, member: Enumerable.include, entries: Enumerable.toArray, every: Enumerable.all, some: Enumerable.any }); function $A(iterable) { if (!iterable) return []; if (iterable.toArray) return iterable.toArray(); var length = iterable.length || 0, results = new Array(length); while (length--) results[length] = iterable[length]; return results; } if (Prototype.Browser.WebKit) { $A = function(iterable) { if (!iterable) return []; if (!(Object.isFunction(iterable) && iterable == '[object NodeList]') && iterable.toArray) return iterable.toArray(); var length = iterable.length || 0, results = new Array(length); while (length--) results[length] = iterable[length]; return results; }; } Array.from = $A; Object.extend(Array.prototype, Enumerable); if (!Array.prototype._reverse) Array.prototype._reverse = Array.prototype.reverse; Object.extend(Array.prototype, { _each: function(iterator) { for (var i = 0, length = this.length; i < length; i++) iterator(this[i]); }, clear: function() { this.length = 0; return this; }, first: function() { return this[0]; }, last: function() { return this[this.length - 1]; }, compact: function() { return this.select(function(value) { return value != null; }); }, flatten: function() { return this.inject([], function(array, value) { return array.concat(Object.isArray(value) ? value.flatten() : [value]); }); }, without: function() { var values = $A(arguments); return this.select(function(value) { return !values.include(value); }); }, reverse: function(inline) { return (inline !== false ? this : this.toArray())._reverse(); }, reduce: function() { return this.length > 1 ? this : this[0]; }, uniq: function(sorted) { return this.inject([], function(array, value, index) { if (0 == index || (sorted ? array.last() != value : !array.include(value))) array.push(value); return array; }); }, intersect: function(array) { return this.uniq().findAll(function(item) { return array.detect(function(value) { return item === value }); }); }, clone: function() { return [].concat(this); }, size: function() { return this.length; }, inspect: function() { return '[' + this.map(Object.inspect).join(', ') + ']'; }, toJSON: function() { var results = []; this.each(function(object) { var value = Object.toJSON(object); if (!Object.isUndefined(value)) results.push(value); }); return '[' + results.join(', ') + ']'; } }); // use native browser JS 1.6 implementation if available if (Object.isFunction(Array.prototype.forEach)) Array.prototype._each = Array.prototype.forEach; if (!Array.prototype.indexOf) Array.prototype.indexOf = function(item, i) { i || (i = 0); var length = this.length; if (i < 0) i = length + i; for (; i < length; i++) if (this[i] === item) return i; return -1; }; if (!Array.prototype.lastIndexOf) Array.prototype.lastIndexOf = function(item, i) { i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1; var n = this.slice(0, i).reverse().indexOf(item); return (n < 0) ? n : i - n - 1; }; Array.prototype.toArray = Array.prototype.clone; function $w(string) { if (!Object.isString(string)) return []; string = string.strip(); return string ? string.split(/\s+/) : []; } if (Prototype.Browser.Opera){ Array.prototype.concat = function() { var array = []; for (var i = 0, length = this.length; i < length; i++) array.push(this[i]); for (var i = 0, length = arguments.length; i < length; i++) { if (Object.isArray(arguments[i])) { for (var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++) array.push(arguments[i][j]); } else { array.push(arguments[i]); } } return array; }; } Object.extend(Number.prototype, { toColorPart: function() { return this.toPaddedString(2, 16); }, succ: function() { return this + 1; }, times: function(iterator) { $R(0, this, true).each(iterator); return this; }, toPaddedString: function(length, radix) { var string = this.toString(radix || 10); return '0'.times(length - string.length) + string; }, toJSON: function() { return isFinite(this) ? this.toString() : 'null'; } }); $w('abs round ceil floor').each(function(method){ Number.prototype[method] = Math[method].methodize(); }); function $H(object) { return new Hash(object); }; var Hash = Class.create(Enumerable, (function() { function toQueryPair(key, value) { if (Object.isUndefined(value)) return key; return key + '=' + encodeURIComponent(String.interpret(value)); } return { initialize: function(object) { this._object = Object.isHash(object) ? object.toObject() : Object.clone(object); }, _each: function(iterator) { for (var key in this._object) { var value = this._object[key], pair = [key, value]; pair.key = key; pair.value = value; iterator(pair); } }, set: function(key, value) { return this._object[key] = value; }, get: function(key) { return this._object[key]; }, unset: function(key) { var value = this._object[key]; delete this._object[key]; return value; }, toObject: function() { return Object.clone(this._object); }, keys: function() { return this.pluck('key'); }, values: function() { return this.pluck('value'); }, index: function(value) { var match = this.detect(function(pair) { return pair.value === value; }); return match && match.key; }, merge: function(object) { return this.clone().update(object); }, update: function(object) { return new Hash(object).inject(this, function(result, pair) { result.set(pair.key, pair.value); return result; }); }, toQueryString: function() { return this.map(function(pair) { var key = encodeURIComponent(pair.key), values = pair.value; if (values && typeof values == 'object') { if (Object.isArray(values)) return values.map(toQueryPair.curry(key)).join('&'); } return toQueryPair(key, values); }).join('&'); }, inspect: function() { return '#'; }, toJSON: function() { return Object.toJSON(this.toObject()); }, clone: function() { return new Hash(this); } } })()); Hash.prototype.toTemplateReplacements = Hash.prototype.toObject; Hash.from = $H; var ObjectRange = Class.create(Enumerable, { initialize: function(start, end, exclusive) { this.start = start; this.end = end; this.exclusive = exclusive; }, _each: function(iterator) { var value = this.start; while (this.include(value)) { iterator(value); value = value.succ(); } }, include: function(value) { if (value < this.start) return false; if (this.exclusive) return value < this.end; return value <= this.end; } }); var $R = function(start, end, exclusive) { return new ObjectRange(start, end, exclusive); }; var Ajax = { getTransport: function() { return Try.these( function() {return new XMLHttpRequest()}, function() {return new ActiveXObject('Msxml2.XMLHTTP')}, function() {return new ActiveXObject('Microsoft.XMLHTTP')} ) || false; }, activeRequestCount: 0 }; Ajax.Responders = { responders: [], _each: function(iterator) { this.responders._each(iterator); }, register: function(responder) { if (!this.include(responder)) this.responders.push(responder); }, unregister: function(responder) { this.responders = this.responders.without(responder); }, dispatch: function(callback, request, transport, json) { this.each(function(responder) { if (Object.isFunction(responder[callback])) { try { responder[callback].apply(responder, [request, transport, json]); } catch (e) { } } }); } }; Object.extend(Ajax.Responders, Enumerable); Ajax.Responders.register({ onCreate: function() { Ajax.activeRequestCount++ }, onComplete: function() { Ajax.activeRequestCount-- } }); Ajax.Base = Class.create({ initialize: function(options) { this.options = { method: 'post', asynchronous: true, contentType: 'application/x-www-form-urlencoded', encoding: 'UTF-8', parameters: '', evalJSON: true, evalJS: true }; Object.extend(this.options, options || { }); this.options.method = this.options.method.toLowerCase(); if (Object.isString(this.options.parameters)) this.options.parameters = this.options.parameters.toQueryParams(); else if (Object.isHash(this.options.parameters)) this.options.parameters = this.options.parameters.toObject(); } }); Ajax.Request = Class.create(Ajax.Base, { _complete: false, initialize: function($super, url, options) { $super(options); this.transport = Ajax.getTransport(); this.request(url); }, request: function(url) { this.url = url; this.method = this.options.method; var params = Object.clone(this.options.parameters); if (!['get', 'post'].include(this.method)) { // simulate other verbs over post params['_method'] = this.method; this.method = 'post'; } this.parameters = params; if (params = Object.toQueryString(params)) { // when GET, append parameters to URL if (this.method == 'get') this.url += (this.url.include('?') ? '&' : '?') + params; else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) params += '&_='; } try { var response = new Ajax.Response(this); if (this.options.onCreate) this.options.onCreate(response); Ajax.Responders.dispatch('onCreate', this, response); this.transport.open(this.method.toUpperCase(), this.url, this.options.asynchronous); if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1); this.transport.onreadystatechange = this.onStateChange.bind(this); this.setRequestHeaders(); this.body = this.method == 'post' ? (this.options.postBody || params) : null; this.transport.send(this.body); /* Force Firefox to handle ready state 4 for synchronous requests */ if (!this.options.asynchronous && this.transport.overrideMimeType) this.onStateChange(); } catch (e) { this.dispatchException(e); } }, onStateChange: function() { var readyState = this.transport.readyState; if (readyState > 1 && !((readyState == 4) && this._complete)) this.respondToReadyState(this.transport.readyState); }, setRequestHeaders: function() { var headers = { 'X-Requested-With': 'XMLHttpRequest', 'X-Prototype-Version': Prototype.Version, 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*' }; if (this.method == 'post') { headers['Content-type'] = this.options.contentType + (this.options.encoding ? '; charset=' + this.options.encoding : ''); /* Force "Connection: close" for older Mozilla browsers to work * around a bug where XMLHttpRequest sends an incorrect * Content-length header. See Mozilla Bugzilla #246651. */ if (this.transport.overrideMimeType && (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005) headers['Connection'] = 'close'; } // user-defined headers if (typeof this.options.requestHeaders == 'object') { var extras = this.options.requestHeaders; if (Object.isFunction(extras.push)) for (var i = 0, length = extras.length; i < length; i += 2) headers[extras[i]] = extras[i+1]; else $H(extras).each(function(pair) { headers[pair.key] = pair.value }); } for (var name in headers) this.transport.setRequestHeader(name, headers[name]); }, success: function() { var status = this.getStatus(); return !status || (status >= 200 && status < 300); }, getStatus: function() { try { return this.transport.status || 0; } catch (e) { return 0 } }, respondToReadyState: function(readyState) { var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this); if (state == 'Complete') { try { this._complete = true; (this.options['on' + response.status] || this.options['on' + (this.success() ? 'Success' : 'Failure')] || Prototype.emptyFunction)(response, response.headerJSON); } catch (e) { this.dispatchException(e); } var contentType = response.getHeader('Content-type'); if (this.options.evalJS == 'force' || (this.options.evalJS && this.isSameOrigin() && contentType && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i))) this.evalResponse(); } try { (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON); Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON); } catch (e) { this.dispatchException(e); } if (state == 'Complete') { // avoid memory leak in MSIE: clean up this.transport.onreadystatechange = Prototype.emptyFunction; } }, isSameOrigin: function() { var m = this.url.match(/^\s*https?:\/\/[^\/]*/); return !m || (m[0] == '#{protocol}//#{domain}#{port}'.interpolate({ protocol: location.protocol, domain: document.domain, port: location.port ? ':' + location.port : '' })); }, getHeader: function(name) { try { return this.transport.getResponseHeader(name) || null; } catch (e) { return null } }, evalResponse: function() { try { return eval((this.transport.responseText || '').unfilterJSON()); } catch (e) { this.dispatchException(e); } }, dispatchException: function(exception) { (this.options.onException || Prototype.emptyFunction)(this, exception); Ajax.Responders.dispatch('onException', this, exception); } }); Ajax.Request.Events = ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; Ajax.Response = Class.create({ initialize: function(request){ this.request = request; var transport = this.transport = request.transport, readyState = this.readyState = transport.readyState; if((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) { this.status = this.getStatus(); this.statusText = this.getStatusText(); this.responseText = String.interpret(transport.responseText); this.headerJSON = this._getHeaderJSON(); } if(readyState == 4) { var xml = transport.responseXML; this.responseXML = Object.isUndefined(xml) ? null : xml; this.responseJSON = this._getResponseJSON(); } }, status: 0, statusText: '', getStatus: Ajax.Request.prototype.getStatus, getStatusText: function() { try { return this.transport.statusText || ''; } catch (e) { return '' } }, getHeader: Ajax.Request.prototype.getHeader, getAllHeaders: function() { try { return this.getAllResponseHeaders(); } catch (e) { return null } }, getResponseHeader: function(name) { return this.transport.getResponseHeader(name); }, getAllResponseHeaders: function() { return this.transport.getAllResponseHeaders(); }, _getHeaderJSON: function() { var json = this.getHeader('X-JSON'); if (!json) return null; json = decodeURIComponent(escape(json)); try { return json.evalJSON(this.request.options.sanitizeJSON || !this.request.isSameOrigin()); } catch (e) { this.request.dispatchException(e); } }, _getResponseJSON: function() { var options = this.request.options; if (!options.evalJSON || (options.evalJSON != 'force' && !(this.getHeader('Content-type') || '').include('application/json')) || this.responseText.blank()) return null; try { return this.responseText.evalJSON(options.sanitizeJSON || !this.request.isSameOrigin()); } catch (e) { this.request.dispatchException(e); } } }); Ajax.Updater = Class.create(Ajax.Request, { initialize: function($super, container, url, options) { this.container = { success: (container.success || container), failure: (container.failure || (container.success ? null : container)) }; options = Object.clone(options); var onComplete = options.onComplete; options.onComplete = (function(response, json) { this.updateContent(response.responseText); if (Object.isFunction(onComplete)) onComplete(response, json); }).bind(this); $super(url, options); }, updateContent: function(responseText) { var receiver = this.container[this.success() ? 'success' : 'failure'], options = this.options; if (!options.evalScripts) responseText = responseText.stripScripts(); if (receiver = $(receiver)) { if (options.insertion) { if (Object.isString(options.insertion)) { var insertion = { }; insertion[options.insertion] = responseText; receiver.insert(insertion); } else options.insertion(receiver, responseText); } else receiver.update(responseText); } } }); Ajax.PeriodicalUpdater = Class.create(Ajax.Base, { initialize: function($super, container, url, options) { $super(options); this.onComplete = this.options.onComplete; this.frequency = (this.options.frequency || 2); this.decay = (this.options.decay || 1); this.updater = { }; this.container = container; this.url = url; this.start(); }, start: function() { this.options.onComplete = this.updateComplete.bind(this); this.onTimerEvent(); }, stop: function() { this.updater.options.onComplete = undefined; clearTimeout(this.timer); (this.onComplete || Prototype.emptyFunction).apply(this, arguments); }, updateComplete: function(response) { if (this.options.decay) { this.decay = (response.responseText == this.lastText ? this.decay * this.options.decay : 1); this.lastText = response.responseText; } this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency); }, onTimerEvent: function() { this.updater = new Ajax.Updater(this.container, this.url, this.options); } }); function $(element) { if (arguments.length > 1) { for (var i = 0, elements = [], length = arguments.length; i < length; i++) elements.push($(arguments[i])); return elements; } if (Object.isString(element)) element = document.getElementById(element); return Element.extend(element); } if (Prototype.BrowserFeatures.XPath) { document._getElementsByXPath = function(expression, parentElement) { var results = []; var query = document.evaluate(expression, $(parentElement) || document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); for (var i = 0, length = query.snapshotLength; i < length; i++) results.push(Element.extend(query.snapshotItem(i))); return results; }; } /*--------------------------------------------------------------------------*/ if (!window.Node) var Node = { }; if (!Node.ELEMENT_NODE) { // DOM level 2 ECMAScript Language Binding Object.extend(Node, { ELEMENT_NODE: 1, ATTRIBUTE_NODE: 2, TEXT_NODE: 3, CDATA_SECTION_NODE: 4, ENTITY_REFERENCE_NODE: 5, ENTITY_NODE: 6, PROCESSING_INSTRUCTION_NODE: 7, COMMENT_NODE: 8, DOCUMENT_NODE: 9, DOCUMENT_TYPE_NODE: 10, DOCUMENT_FRAGMENT_NODE: 11, NOTATION_NODE: 12 }); } (function() { var element = this.Element; this.Element = function(tagName, attributes) { attributes = attributes || { }; tagName = tagName.toLowerCase(); var cache = Element.cache; if (Prototype.Browser.IE && attributes.name) { tagName = '<' + tagName + ' name="' + attributes.name + '">'; delete attributes.name; return Element.writeAttribute(document.createElement(tagName), attributes); } if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName)); return Element.writeAttribute(cache[tagName].cloneNode(false), attributes); }; Object.extend(this.Element, element || { }); }).call(window); Element.cache = { }; Element.Methods = { visible: function(element) { return $(element).style.display != 'none'; }, toggle: function(element) { element = $(element); Element[Element.visible(element) ? 'hide' : 'show'](element); return element; }, hide: function(element) { $(element).style.display = 'none'; return element; }, show: function(element) { $(element).style.display = ''; return element; }, remove: function(element) { element = $(element); element.parentNode.removeChild(element); return element; }, update: function(element, content) { element = $(element); if (content && content.toElement) content = content.toElement(); if (Object.isElement(content)) return element.update().insert(content); content = Object.toHTML(content); element.innerHTML = content.stripScripts(); content.evalScripts.bind(content).defer(); return element; }, replace: function(element, content) { element = $(element); if (content && content.toElement) content = content.toElement(); else if (!Object.isElement(content)) { content = Object.toHTML(content); var range = element.ownerDocument.createRange(); range.selectNode(element); content.evalScripts.bind(content).defer(); content = range.createContextualFragment(content.stripScripts()); } element.parentNode.replaceChild(content, element); return element; }, insert: function(element, insertions) { element = $(element); if (Object.isString(insertions) || Object.isNumber(insertions) || Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML))) insertions = {bottom:insertions}; var content, insert, tagName, childNodes; for (var position in insertions) { content = insertions[position]; position = position.toLowerCase(); insert = Element._insertionTranslations[position]; if (content && content.toElement) content = content.toElement(); if (Object.isElement(content)) { insert(element, content); continue; } content = Object.toHTML(content); tagName = ((position == 'before' || position == 'after') ? element.parentNode : element).tagName.toUpperCase(); childNodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); if (position == 'top' || position == 'after') childNodes.reverse(); childNodes.each(insert.curry(element)); content.evalScripts.bind(content).defer(); } return element; }, wrap: function(element, wrapper, attributes) { element = $(element); if (Object.isElement(wrapper)) $(wrapper).writeAttribute(attributes || { }); else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes); else wrapper = new Element('div', wrapper); if (element.parentNode) element.parentNode.replaceChild(wrapper, element); wrapper.appendChild(element); return wrapper; }, inspect: function(element) { element = $(element); var result = '<' + element.tagName.toLowerCase(); $H({'id': 'id', 'className': 'class'}).each(function(pair) { var property = pair.first(), attribute = pair.last(); var value = (element[property] || '').toString(); if (value) result += ' ' + attribute + '=' + value.inspect(true); }); return result + '>'; }, recursivelyCollect: function(element, property) { element = $(element); var elements = []; while (element = element[property]) if (element.nodeType == 1) elements.push(Element.extend(element)); return elements; }, ancestors: function(element) { return $(element).recursivelyCollect('parentNode'); }, descendants: function(element) { return $(element).select("*"); }, firstDescendant: function(element) { element = $(element).firstChild; while (element && element.nodeType != 1) element = element.nextSibling; return $(element); }, immediateDescendants: function(element) { if (!(element = $(element).firstChild)) return []; while (element && element.nodeType != 1) element = element.nextSibling; if (element) return [element].concat($(element).nextSiblings()); return []; }, previousSiblings: function(element) { return $(element).recursivelyCollect('previousSibling'); }, nextSiblings: function(element) { return $(element).recursivelyCollect('nextSibling'); }, siblings: function(element) { element = $(element); return element.previousSiblings().reverse().concat(element.nextSiblings()); }, match: function(element, selector) { if (Object.isString(selector)) selector = new Selector(selector); return selector.match($(element)); }, up: function(element, expression, index) { element = $(element); if (arguments.length == 1) return $(element.parentNode); var ancestors = element.ancestors(); return Object.isNumber(expression) ? ancestors[expression] : Selector.findElement(ancestors, expression, index); }, down: function(element, expression, index) { element = $(element); if (arguments.length == 1) return element.firstDescendant(); return Object.isNumber(expression) ? element.descendants()[expression] : element.select(expression)[index || 0]; }, previous: function(element, expression, index) { element = $(element); if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element)); var previousSiblings = element.previousSiblings(); return Object.isNumber(expression) ? previousSiblings[expression] : Selector.findElement(previousSiblings, expression, index); }, next: function(element, expression, index) { element = $(element); if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element)); var nextSiblings = element.nextSiblings(); return Object.isNumber(expression) ? nextSiblings[expression] : Selector.findElement(nextSiblings, expression, index); }, select: function() { var args = $A(arguments), element = $(args.shift()); return Selector.findChildElements(element, args); }, adjacent: function() { var args = $A(arguments), element = $(args.shift()); return Selector.findChildElements(element.parentNode, args).without(element); }, identify: function(element) { element = $(element); var id = element.readAttribute('id'), self = arguments.callee; if (id) return id; do { id = 'anonymous_element_' + self.counter++ } while ($(id)); element.writeAttribute('id', id); return id; }, readAttribute: function(element, name) { element = $(element); if (Prototype.Browser.IE) { var t = Element._attributeTranslations.read; if (t.values[name]) return t.values[name](element, name); if (t.names[name]) name = t.names[name]; if (name.include(':')) { return (!element.attributes || !element.attributes[name]) ? null : element.attributes[name].value; } } return element.getAttribute(name); }, writeAttribute: function(element, name, value) { element = $(element); var attributes = { }, t = Element._attributeTranslations.write; if (typeof name == 'object') attributes = name; else attributes[name] = Object.isUndefined(value) ? true : value; for (var attr in attributes) { name = t.names[attr] || attr; value = attributes[attr]; if (t.values[attr]) name = t.values[attr](element, value); if (value === false || value === null) element.removeAttribute(name); else if (value === true) element.setAttribute(name, name); else element.setAttribute(name, value); } return element; }, getHeight: function(element) { return $(element).getDimensions().height; }, getWidth: function(element) { return $(element).getDimensions().width; }, classNames: function(element) { return new Element.ClassNames(element); }, hasClassName: function(element, className) { if (!(element = $(element))) return; var elementClassName = element.className; return (elementClassName.length > 0 && (elementClassName == className || new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName))); }, addClassName: function(element, className) { if (!(element = $(element))) return; if (!element.hasClassName(className)) element.className += (element.className ? ' ' : '') + className; return element; }, removeClassName: function(element, className) { if (!(element = $(element))) return; element.className = element.className.replace( new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip(); return element; }, toggleClassName: function(element, className) { if (!(element = $(element))) return; return element[element.hasClassName(className) ? 'removeClassName' : 'addClassName'](className); }, // removes whitespace-only text node children cleanWhitespace: function(element) { element = $(element); var node = element.firstChild; while (node) { var nextNode = node.nextSibling; if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) element.removeChild(node); node = nextNode; } return element; }, empty: function(element) { return $(element).innerHTML.blank(); }, descendantOf: function(element, ancestor) { element = $(element), ancestor = $(ancestor); var originalAncestor = ancestor; if (element.compareDocumentPosition) return (element.compareDocumentPosition(ancestor) & 8) === 8; if (element.sourceIndex && !Prototype.Browser.Opera) { var e = element.sourceIndex, a = ancestor.sourceIndex, nextAncestor = ancestor.nextSibling; if (!nextAncestor) { do { ancestor = ancestor.parentNode; } while (!(nextAncestor = ancestor.nextSibling) && ancestor.parentNode); } if (nextAncestor && nextAncestor.sourceIndex) return (e > a && e < nextAncestor.sourceIndex); } while (element = element.parentNode) if (element == originalAncestor) return true; return false; }, scrollTo: function(element) { element = $(element); var pos = element.cumulativeOffset(); window.scrollTo(pos[0], pos[1]); return element; }, getStyle: function(element, style) { element = $(element); style = style == 'float' ? 'cssFloat' : style.camelize(); var value = element.style[style]; if (!value) { var css = document.defaultView.getComputedStyle(element, null); value = css ? css[style] : null; } if (style == 'opacity') return value ? parseFloat(value) : 1.0; return value == 'auto' ? null : value; }, getOpacity: function(element) { return $(element).getStyle('opacity'); }, setStyle: function(element, styles) { element = $(element); var elementStyle = element.style, match; if (Object.isString(styles)) { element.style.cssText += ';' + styles; return styles.include('opacity') ? element.setOpacity(styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element; } for (var property in styles) if (property == 'opacity') element.setOpacity(styles[property]); else elementStyle[(property == 'float' || property == 'cssFloat') ? (Object.isUndefined(elementStyle.styleFloat) ? 'cssFloat' : 'styleFloat') : property] = styles[property]; return element; }, setOpacity: function(element, value) { element = $(element); element.style.opacity = (value == 1 || value === '') ? '' : (value < 0.00001) ? 0 : value; return element; }, getDimensions: function(element) { element = $(element); var display = $(element).getStyle('display'); if (display != 'none' && display != null) // Safari bug return {width: element.offsetWidth, height: element.offsetHeight}; // All *Width and *Height properties give 0 on elements with display none, // so enable the element temporarily var els = element.style; var originalVisibility = els.visibility; var originalPosition = els.position; var originalDisplay = els.display; els.visibility = 'hidden'; els.position = 'absolute'; els.display = 'block'; var originalWidth = element.clientWidth; var originalHeight = element.clientHeight; els.display = originalDisplay; els.position = originalPosition; els.visibility = originalVisibility; return {width: originalWidth, height: originalHeight}; }, makePositioned: function(element) { element = $(element); var pos = Element.getStyle(element, 'position'); if (pos == 'static' || !pos) { element._madePositioned = true; element.style.position = 'relative'; // Opera returns the offset relative to the positioning context, when an // element is position relative but top and left have not been defined if (window.opera) { element.style.top = 0; element.style.left = 0; } } return element; }, undoPositioned: function(element) { element = $(element); if (element._madePositioned) { element._madePositioned = undefined; element.style.position = element.style.top = element.style.left = element.style.bottom = element.style.right = ''; } return element; }, makeClipping: function(element) { element = $(element); if (element._overflow) return element; element._overflow = Element.getStyle(element, 'overflow') || 'auto'; if (element._overflow !== 'hidden') element.style.overflow = 'hidden'; return element; }, undoClipping: function(element) { element = $(element); if (!element._overflow) return element; element.style.overflow = element._overflow == 'auto' ? '' : element._overflow; element._overflow = null; return element; }, cumulativeOffset: function(element) { var valueT = 0, valueL = 0; do { valueT += element.offsetTop || 0; valueL += element.offsetLeft || 0; element = element.offsetParent; } while (element); return Element._returnOffset(valueL, valueT); }, positionedOffset: function(element) { var valueT = 0, valueL = 0; do { valueT += element.offsetTop || 0; valueL += element.offsetLeft || 0; element = element.offsetParent; if (element) { if (element.tagName == 'BODY') break; var p = Element.getStyle(element, 'position'); if (p !== 'static') break; } } while (element); return Element._returnOffset(valueL, valueT); }, absolutize: function(element) { element = $(element); if (element.getStyle('position') == 'absolute') return; // Position.prepare(); // To be done manually by Scripty when it needs it. var offsets = element.positionedOffset(); var top = offsets[1]; var left = offsets[0]; var width = element.clientWidth; var height = element.clientHeight; element._originalLeft = left - parseFloat(element.style.left || 0); element._originalTop = top - parseFloat(element.style.top || 0); element._originalWidth = element.style.width; element._originalHeight = element.style.height; element.style.position = 'absolute'; element.style.top = top + 'px'; element.style.left = left + 'px'; element.style.width = width + 'px'; element.style.height = height + 'px'; return element; }, relativize: function(element) { element = $(element); if (element.getStyle('position') == 'relative') return; // Position.prepare(); // To be done manually by Scripty when it needs it. element.style.position = 'relative'; var top = parseFloat(element.style.top || 0) - (element._originalTop || 0); var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0); element.style.top = top + 'px'; element.style.left = left + 'px'; element.style.height = element._originalHeight; element.style.width = element._originalWidth; return element; }, cumulativeScrollOffset: function(element) { var valueT = 0, valueL = 0; do { valueT += element.scrollTop || 0; valueL += element.scrollLeft || 0; element = element.parentNode; } while (element); return Element._returnOffset(valueL, valueT); }, getOffsetParent: function(element) { if (element.offsetParent) return $(element.offsetParent); if (element == document.body) return $(element); while ((element = element.parentNode) && element != document.body) if (Element.getStyle(element, 'position') != 'static') return $(element); return $(document.body); }, viewportOffset: function(forElement) { var valueT = 0, valueL = 0; var element = forElement; do { valueT += element.offsetTop || 0; valueL += element.offsetLeft || 0; // Safari fix if (element.offsetParent == document.body && Element.getStyle(element, 'position') == 'absolute') break; } while (element = element.offsetParent); element = forElement; do { if (!Prototype.Browser.Opera || element.tagName == 'BODY') { valueT -= element.scrollTop || 0; valueL -= element.scrollLeft || 0; } } while (element = element.parentNode); return Element._returnOffset(valueL, valueT); }, clonePosition: function(element, source) { var options = Object.extend({ setLeft: true, setTop: true, setWidth: true, setHeight: true, offsetTop: 0, offsetLeft: 0 }, arguments[2] || { }); // find page position of source source = $(source); var p = source.viewportOffset(); // find coordinate system to use element = $(element); var delta = [0, 0]; var parent = null; // delta [0,0] will do fine with position: fixed elements, // position:absolute needs offsetParent deltas if (Element.getStyle(element, 'position') == 'absolute') { parent = element.getOffsetParent(); delta = parent.viewportOffset(); } // correct by body offsets (fixes Safari) if (parent == document.body) { delta[0] -= document.body.offsetLeft; delta[1] -= document.body.offsetTop; } // set position if (options.setLeft) element.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px'; if (options.setTop) element.style.top = (p[1] - delta[1] + options.offsetTop) + 'px'; if (options.setWidth) element.style.width = source.offsetWidth + 'px'; if (options.setHeight) element.style.height = source.offsetHeight + 'px'; return element; } }; Element.Methods.identify.counter = 1; Object.extend(Element.Methods, { getElementsBySelector: Element.Methods.select, childElements: Element.Methods.immediateDescendants }); Element._attributeTranslations = { write: { names: { className: 'class', htmlFor: 'for' }, values: { } } }; if (Prototype.Browser.Opera) { Element.Methods.getStyle = Element.Methods.getStyle.wrap( function(proceed, element, style) { switch (style) { case 'left': case 'top': case 'right': case 'bottom': if (proceed(element, 'position') === 'static') return null; case 'height': case 'width': // returns '0px' for hidden elements; we want it to return null if (!Element.visible(element)) return null; // returns the border-box dimensions rather than the content-box // dimensions, so we subtract padding and borders from the value var dim = parseInt(proceed(element, style), 10); if (dim !== element['offset' + style.capitalize()]) return dim + 'px'; var properties; if (style === 'height') { properties = ['border-top-width', 'padding-top', 'padding-bottom', 'border-bottom-width']; } else { properties = ['border-left-width', 'padding-left', 'padding-right', 'border-right-width']; } return properties.inject(dim, function(memo, property) { var val = proceed(element, property); return val === null ? memo : memo - parseInt(val, 10); }) + 'px'; default: return proceed(element, style); } } ); Element.Methods.readAttribute = Element.Methods.readAttribute.wrap( function(proceed, element, attribute) { if (attribute === 'title') return element.title; return proceed(element, attribute); } ); } else if (Prototype.Browser.IE) { // IE doesn't report offsets correctly for static elements, so we change them // to "relative" to get the values, then change them back. Element.Methods.getOffsetParent = Element.Methods.getOffsetParent.wrap( function(proceed, element) { element = $(element); var position = element.getStyle('position'); if (position !== 'static') return proceed(element); element.setStyle({ position: 'relative' }); var value = proceed(element); element.setStyle({ position: position }); return value; } ); $w('positionedOffset viewportOffset').each(function(method) { Element.Methods[method] = Element.Methods[method].wrap( function(proceed, element) { element = $(element); var position = element.getStyle('position'); if (position !== 'static') return proceed(element); // Trigger hasLayout on the offset parent so that IE6 reports // accurate offsetTop and offsetLeft values for position: fixed. var offsetParent = element.getOffsetParent(); if (offsetParent && offsetParent.getStyle('position') === 'fixed') offsetParent.setStyle({ zoom: 1 }); element.setStyle({ position: 'relative' }); var value = proceed(element); element.setStyle({ position: position }); return value; } ); }); Element.Methods.getStyle = function(element, style) { element = $(element); style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize(); var value = element.style[style]; if (!value && element.currentStyle) value = element.currentStyle[style]; if (style == 'opacity') { if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/)) if (value[1]) return parseFloat(value[1]) / 100; return 1.0; } if (value == 'auto') { if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none')) return element['offset' + style.capitalize()] + 'px'; return null; } return value; }; Element.Methods.setOpacity = function(element, value) { function stripAlpha(filter){ return filter.replace(/alpha\([^\)]*\)/gi,''); } element = $(element); var currentStyle = element.currentStyle; if ((currentStyle && !currentStyle.hasLayout) || (!currentStyle && element.style.zoom == 'normal')) element.style.zoom = 1; var filter = element.getStyle('filter'), style = element.style; if (value == 1 || value === '') { (filter = stripAlpha(filter)) ? style.filter = filter : style.removeAttribute('filter'); return element; } else if (value < 0.00001) value = 0; style.filter = stripAlpha(filter) + 'alpha(opacity=' + (value * 100) + ')'; return element; }; Element._attributeTranslations = { read: { names: { 'class': 'className', 'for': 'htmlFor' }, values: { _getAttr: function(element, attribute) { return element.getAttribute(attribute, 2); }, _getAttrNode: function(element, attribute) { var node = element.getAttributeNode(attribute); return node ? node.value : ""; }, _getEv: function(element, attribute) { attribute = element.getAttribute(attribute); return attribute ? attribute.toString().slice(23, -2) : null; }, _flag: function(element, attribute) { return $(element).hasAttribute(attribute) ? attribute : null; }, style: function(element) { return element.style.cssText.toLowerCase(); }, title: function(element) { return element.title; } } } }; Element._attributeTranslations.write = { names: Object.extend({ cellpadding: 'cellPadding', cellspacing: 'cellSpacing' }, Element._attributeTranslations.read.names), values: { checked: function(element, value) { element.checked = !!value; }, style: function(element, value) { element.style.cssText = value ? value : ''; } } }; Element._attributeTranslations.has = {}; $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' + 'encType maxLength readOnly longDesc').each(function(attr) { Element._attributeTranslations.write.names[attr.toLowerCase()] = attr; Element._attributeTranslations.has[attr.toLowerCase()] = attr; }); (function(v) { Object.extend(v, { href: v._getAttr, src: v._getAttr, type: v._getAttr, action: v._getAttrNode, disabled: v._flag, checked: v._flag, readonly: v._flag, multiple: v._flag, onload: v._getEv, onunload: v._getEv, onclick: v._getEv, ondblclick: v._getEv, onmousedown: v._getEv, onmouseup: v._getEv, onmouseover: v._getEv, onmousemove: v._getEv, onmouseout: v._getEv, onfocus: v._getEv, onblur: v._getEv, onkeypress: v._getEv, onkeydown: v._getEv, onkeyup: v._getEv, onsubmit: v._getEv, onreset: v._getEv, onselect: v._getEv, onchange: v._getEv }); })(Element._attributeTranslations.read.values); } else if (Prototype.Browser.Gecko && /rv:1\.8\.0/.test(navigator.userAgent)) { Element.Methods.setOpacity = function(element, value) { element = $(element); element.style.opacity = (value == 1) ? 0.999999 : (value === '') ? '' : (value < 0.00001) ? 0 : value; return element; }; } else if (Prototype.Browser.WebKit) { Element.Methods.setOpacity = function(element, value) { element = $(element); element.style.opacity = (value == 1 || value === '') ? '' : (value < 0.00001) ? 0 : value; if (value == 1) if(element.tagName == 'IMG' && element.width) { element.width++; element.width--; } else try { var n = document.createTextNode(' '); element.appendChild(n); element.removeChild(n); } catch (e) { } return element; }; // Safari returns margins on body which is incorrect if the child is absolutely // positioned. For performance reasons, redefine Element#cumulativeOffset for // KHTML/WebKit only. Element.Methods.cumulativeOffset = function(element) { var valueT = 0, valueL = 0; do { valueT += element.offsetTop || 0; valueL += element.offsetLeft || 0; if (element.offsetParent == document.body) if (Element.getStyle(element, 'position') == 'absolute') break; element = element.offsetParent; } while (element); return Element._returnOffset(valueL, valueT); }; } if (Prototype.Browser.IE || Prototype.Browser.Opera) { // IE and Opera are missing .innerHTML support for TABLE-related and SELECT elements Element.Methods.update = function(element, content) { element = $(element); if (content && content.toElement) content = content.toElement(); if (Object.isElement(content)) return element.update().insert(content); content = Object.toHTML(content); var tagName = element.tagName.toUpperCase(); if (tagName in Element._insertionTranslations.tags) { $A(element.childNodes).each(function(node) { element.removeChild(node) }); Element._getContentFromAnonymousElement(tagName, content.stripScripts()) .each(function(node) { element.appendChild(node) }); } else element.innerHTML = content.stripScripts(); content.evalScripts.bind(content).defer(); return element; }; } if ('outerHTML' in document.createElement('div')) { Element.Methods.replace = function(element, content) { element = $(element); if (content && content.toElement) content = content.toElement(); if (Object.isElement(content)) { element.parentNode.replaceChild(content, element); return element; } content = Object.toHTML(content); var parent = element.parentNode, tagName = parent.tagName.toUpperCase(); if (Element._insertionTranslations.tags[tagName]) { var nextSibling = element.next(); var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); parent.removeChild(element); if (nextSibling) fragments.each(function(node) { parent.insertBefore(node, nextSibling) }); else fragments.each(function(node) { parent.appendChild(node) }); } else element.outerHTML = content.stripScripts(); content.evalScripts.bind(content).defer(); return element; }; } Element._returnOffset = function(l, t) { var result = [l, t]; result.left = l; result.top = t; return result; }; Element._getContentFromAnonymousElement = function(tagName, html) { var div = new Element('div'), t = Element._insertionTranslations.tags[tagName]; if (t) { div.innerHTML = t[0] + html + t[1]; t[2].times(function() { div = div.firstChild }); } else div.innerHTML = html; return $A(div.childNodes); }; Element._insertionTranslations = { before: function(element, node) { element.parentNode.insertBefore(node, element); }, top: function(element, node) { element.insertBefore(node, element.firstChild); }, bottom: function(element, node) { element.appendChild(node); }, after: function(element, node) { element.parentNode.insertBefore(node, element.nextSibling); }, tags: { TABLE: ['', '
', 1], TBODY: ['', '
', 2], TR: ['', '
', 3], TD: ['
', '
', 4], SELECT: ['', 1] } }; (function() { Object.extend(this.tags, { THEAD: this.tags.TBODY, TFOOT: this.tags.TBODY, TH: this.tags.TD }); }).call(Element._insertionTranslations); Element.Methods.Simulated = { hasAttribute: function(element, attribute) { attribute = Element._attributeTranslations.has[attribute] || attribute; var node = $(element).getAttributeNode(attribute); return node && node.specified; } }; Element.Methods.ByTag = { }; Object.extend(Element, Element.Methods); if (!Prototype.BrowserFeatures.ElementExtensions && document.createElement('div').__proto__) { window.HTMLElement = { }; window.HTMLElement.prototype = document.createElement('div').__proto__; Prototype.BrowserFeatures.ElementExtensions = true; } Element.extend = (function() { if (Prototype.BrowserFeatures.SpecificElementExtensions) return Prototype.K; var Methods = { }, ByTag = Element.Methods.ByTag; var extend = Object.extend(function(element) { if (!element || element._extendedByPrototype || element.nodeType != 1 || element == window) return element; var methods = Object.clone(Methods), tagName = element.tagName, property, value; // extend methods for specific tags if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]); for (property in methods) { value = methods[property]; if (Object.isFunction(value) && !(property in element)) element[property] = value.methodize(); } element._extendedByPrototype = Prototype.emptyFunction; return element; }, { refresh: function() { // extend methods for all tags (Safari doesn't need this) if (!Prototype.BrowserFeatures.ElementExtensions) { Object.extend(Methods, Element.Methods); Object.extend(Methods, Element.Methods.Simulated); } } }); extend.refresh(); return extend; })(); Element.hasAttribute = function(element, attribute) { if (element.hasAttribute) return element.hasAttribute(attribute); return Element.Methods.Simulated.hasAttribute(element, attribute); }; Element.addMethods = function(methods) { var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag; if (!methods) { Object.extend(Form, Form.Methods); Object.extend(Form.Element, Form.Element.Methods); Object.extend(Element.Methods.ByTag, { "FORM": Object.clone(Form.Methods), "INPUT": Object.clone(Form.Element.Methods), "SELECT": Object.clone(Form.Element.Methods), "TEXTAREA": Object.clone(Form.Element.Methods) }); } if (arguments.length == 2) { var tagName = methods; methods = arguments[1]; } if (!tagName) Object.extend(Element.Methods, methods || { }); else { if (Object.isArray(tagName)) tagName.each(extend); else extend(tagName); } function extend(tagName) { tagName = tagName.toUpperCase(); if (!Element.Methods.ByTag[tagName]) Element.Methods.ByTag[tagName] = { }; Object.extend(Element.Methods.ByTag[tagName], methods); } function copy(methods, destination, onlyIfAbsent) { onlyIfAbsent = onlyIfAbsent || false; for (var property in methods) { var value = methods[property]; if (!Object.isFunction(value)) continue; if (!onlyIfAbsent || !(property in destination)) destination[property] = value.methodize(); } } function findDOMClass(tagName) { var klass; var trans = { "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph", "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList", "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading", "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote", "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION": "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD": "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR": "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET": "FrameSet", "IFRAME": "IFrame" }; if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element'; if (window[klass]) return window[klass]; klass = 'HTML' + tagName + 'Element'; if (window[klass]) return window[klass]; klass = 'HTML' + tagName.capitalize() + 'Element'; if (window[klass]) return window[klass]; window[klass] = { }; window[klass].prototype = document.createElement(tagName).__proto__; return window[klass]; } if (F.ElementExtensions) { copy(Element.Methods, HTMLElement.prototype); copy(Element.Methods.Simulated, HTMLElement.prototype, true); } if (F.SpecificElementExtensions) { for (var tag in Element.Methods.ByTag) { var klass = findDOMClass(tag); if (Object.isUndefined(klass)) continue; copy(T[tag], klass.prototype); } } Object.extend(Element, Element.Methods); delete Element.ByTag; if (Element.extend.refresh) Element.extend.refresh(); Element.cache = { }; }; document.viewport = { getDimensions: function() { var dimensions = { }; var B = Prototype.Browser; $w('width height').each(function(d) { var D = d.capitalize(); dimensions[d] = (B.WebKit && !document.evaluate) ? self['inner' + D] : (B.Opera) ? document.body['client' + D] : document.documentElement['client' + D]; }); return dimensions; }, getWidth: function() { return this.getDimensions().width; }, getHeight: function() { return this.getDimensions().height; }, getScrollOffsets: function() { return Element._returnOffset( window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft, window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop); } }; /* Portions of the Selector class are derived from Jack Slocum’s DomQuery, * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style * license. Please see http://www.yui-ext.com/ for more information. */ var Selector = Class.create({ initialize: function(expression) { this.expression = expression.strip(); this.compileMatcher(); }, shouldUseXPath: function() { if (!Prototype.BrowserFeatures.XPath) return false; var e = this.expression; // Safari 3 chokes on :*-of-type and :empty if (Prototype.Browser.WebKit && (e.include("-of-type") || e.include(":empty"))) return false; // XPath can't do namespaced attributes, nor can it read // the "checked" property from DOM nodes if ((/(\[[\w-]*?:|:checked)/).test(this.expression)) return false; return true; }, compileMatcher: function() { if (this.shouldUseXPath()) return this.compileXPathMatcher(); var e = this.expression, ps = Selector.patterns, h = Selector.handlers, c = Selector.criteria, le, p, m; if (Selector._cache[e]) { this.matcher = Selector._cache[e]; return; } this.matcher = ["this.matcher = function(root) {", "var r = root, h = Selector.handlers, c = false, n;"]; while (e && le != e && (/\S/).test(e)) { le = e; for (var i in ps) { p = ps[i]; if (m = e.match(p)) { this.matcher.push(Object.isFunction(c[i]) ? c[i](m) : new Template(c[i]).evaluate(m)); e = e.replace(m[0], ''); break; } } } this.matcher.push("return h.unique(n);\n}"); eval(this.matcher.join('\n')); Selector._cache[this.expression] = this.matcher; }, compileXPathMatcher: function() { var e = this.expression, ps = Selector.patterns, x = Selector.xpath, le, m; if (Selector._cache[e]) { this.xpath = Selector._cache[e]; return; } this.matcher = ['.//*']; while (e && le != e && (/\S/).test(e)) { le = e; for (var i in ps) { if (m = e.match(ps[i])) { this.matcher.push(Object.isFunction(x[i]) ? x[i](m) : new Template(x[i]).evaluate(m)); e = e.replace(m[0], ''); break; } } } this.xpath = this.matcher.join(''); Selector._cache[this.expression] = this.xpath; }, findElements: function(root) { root = root || document; if (this.xpath) return document._getElementsByXPath(this.xpath, root); return this.matcher(root); }, match: function(element) { this.tokens = []; var e = this.expression, ps = Selector.patterns, as = Selector.assertions; var le, p, m; while (e && le !== e && (/\S/).test(e)) { le = e; for (var i in ps) { p = ps[i]; if (m = e.match(p)) { // use the Selector.assertions methods unless the selector // is too complex. if (as[i]) { this.tokens.push([i, Object.clone(m)]); e = e.replace(m[0], ''); } else { // reluctantly do a document-wide search // and look for a match in the array return this.findElements(document).include(element); } } } } var match = true, name, matches; for (var i = 0, token; token = this.tokens[i]; i++) { name = token[0], matches = token[1]; if (!Selector.assertions[name](element, matches)) { match = false; break; } } return match; }, toString: function() { return this.expression; }, inspect: function() { return "#"; } }); Object.extend(Selector, { _cache: { }, xpath: { descendant: "//*", child: "/*", adjacent: "/following-sibling::*[1]", laterSibling: '/following-sibling::*', tagName: function(m) { if (m[1] == '*') return ''; return "[local-name()='" + m[1].toLowerCase() + "' or local-name()='" + m[1].toUpperCase() + "']"; }, className: "[contains(concat(' ', @class, ' '), ' #{1} ')]", id: "[@id='#{1}']", attrPresence: function(m) { m[1] = m[1].toLowerCase(); return new Template("[@#{1}]").evaluate(m); }, attr: function(m) { m[1] = m[1].toLowerCase(); m[3] = m[5] || m[6]; return new Template(Selector.xpath.operators[m[2]]).evaluate(m); }, pseudo: function(m) { var h = Selector.xpath.pseudos[m[1]]; if (!h) return ''; if (Object.isFunction(h)) return h(m); return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m); }, operators: { '=': "[@#{1}='#{3}']", '!=': "[@#{1}!='#{3}']", '^=': "[starts-with(@#{1}, '#{3}')]", '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']", '*=': "[contains(@#{1}, '#{3}')]", '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]", '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]" }, pseudos: { 'first-child': '[not(preceding-sibling::*)]', 'last-child': '[not(following-sibling::*)]', 'only-child': '[not(preceding-sibling::* or following-sibling::*)]', 'empty': "[count(*) = 0 and (count(text()) = 0 or translate(text(), ' \t\r\n', '') = '')]", 'checked': "[@checked]", 'disabled': "[@disabled]", 'enabled': "[not(@disabled)]", 'not': function(m) { var e = m[6], p = Selector.patterns, x = Selector.xpath, le, v; var exclusion = []; while (e && le != e && (/\S/).test(e)) { le = e; for (var i in p) { if (m = e.match(p[i])) { v = Object.isFunction(x[i]) ? x[i](m) : new Template(x[i]).evaluate(m); exclusion.push("(" + v.substring(1, v.length - 1) + ")"); e = e.replace(m[0], ''); break; } } } return "[not(" + exclusion.join(" and ") + ")]"; }, 'nth-child': function(m) { return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m); }, 'nth-last-child': function(m) { return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m); }, 'nth-of-type': function(m) { return Selector.xpath.pseudos.nth("position() ", m); }, 'nth-last-of-type': function(m) { return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m); }, 'first-of-type': function(m) { m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](m); }, 'last-of-type': function(m) { m[6] = "1"; return Selector.xpath.pseudos['nth-last-of-type'](m); }, 'only-of-type': function(m) { var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m); }, nth: function(fragment, m) { var mm, formula = m[6], predicate; if (formula == 'even') formula = '2n+0'; if (formula == 'odd') formula = '2n+1'; if (mm = formula.match(/^(\d+)$/)) // digit only return '[' + fragment + "= " + mm[1] + ']'; if (mm = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b if (mm[1] == "-") mm[1] = -1; var a = mm[1] ? Number(mm[1]) : 1; var b = mm[2] ? Number(mm[2]) : 0; predicate = "[((#{fragment} - #{b}) mod #{a} = 0) and " + "((#{fragment} - #{b}) div #{a} >= 0)]"; return new Template(predicate).evaluate({ fragment: fragment, a: a, b: b }); } } } }, criteria: { tagName: 'n = h.tagName(n, r, "#{1}", c); c = false;', className: 'n = h.className(n, r, "#{1}", c); c = false;', id: 'n = h.id(n, r, "#{1}", c); c = false;', attrPresence: 'n = h.attrPresence(n, r, "#{1}", c); c = false;', attr: function(m) { m[3] = (m[5] || m[6]); return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}", c); c = false;').evaluate(m); }, pseudo: function(m) { if (m[6]) m[6] = m[6].replace(/"/g, '\\"'); return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m); }, descendant: 'c = "descendant";', child: 'c = "child";', adjacent: 'c = "adjacent";', laterSibling: 'c = "laterSibling";' }, patterns: { // combinators must be listed first // (and descendant needs to be last combinator) laterSibling: /^\s*~\s*/, child: /^\s*>\s*/, adjacent: /^\s*\+\s*/, descendant: /^\s/, // selectors follow tagName: /^\s*(\*|[\w\-]+)(\b|$)?/, id: /^#([\w\-\*]+)(\b|$)/, className: /^\.([\w\-\*]+)(\b|$)/, pseudo: /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/, attrPresence: /^\[([\w]+)\]/, attr: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/ }, // for Selector.match and Element#match assertions: { tagName: function(element, matches) { return matches[1].toUpperCase() == element.tagName.toUpperCase(); }, className: function(element, matches) { return Element.hasClassName(element, matches[1]); }, id: function(element, matches) { return element.id === matches[1]; }, attrPresence: function(element, matches) { return Element.hasAttribute(element, matches[1]); }, attr: function(element, matches) { var nodeValue = Element.readAttribute(element, matches[1]); return nodeValue && Selector.operators[matches[2]](nodeValue, matches[5] || matches[6]); } }, handlers: { // UTILITY FUNCTIONS // joins two collections concat: function(a, b) { for (var i = 0, node; node = b[i]; i++) a.push(node); return a; }, // marks an array of nodes for counting mark: function(nodes) { var _true = Prototype.emptyFunction; for (var i = 0, node; node = nodes[i]; i++) node._countedByPrototype = _true; return nodes; }, unmark: function(nodes) { for (var i = 0, node; node = nodes[i]; i++) node._countedByPrototype = undefined; return nodes; }, // mark each child node with its position (for nth calls) // "ofType" flag indicates whether we're indexing for nth-of-type // rather than nth-child index: function(parentNode, reverse, ofType) { parentNode._countedByPrototype = Prototype.emptyFunction; if (reverse) { for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) { var node = nodes[i]; if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++; } } else { for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++) if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++; } }, // filters out duplicates and extends all nodes unique: function(nodes) { if (nodes.length == 0) return nodes; var results = [], n; for (var i = 0, l = nodes.length; i < l; i++) if (!(n = nodes[i])._countedByPrototype) { n._countedByPrototype = Prototype.emptyFunction; results.push(Element.extend(n)); } return Selector.handlers.unmark(results); }, // COMBINATOR FUNCTIONS descendant: function(nodes) { var h = Selector.handlers; for (var i = 0, results = [], node; node = nodes[i]; i++) h.concat(results, node.getElementsByTagName('*')); return results; }, child: function(nodes) { var h = Selector.handlers; for (var i = 0, results = [], node; node = nodes[i]; i++) { for (var j = 0, child; child = node.childNodes[j]; j++) if (child.nodeType == 1 && child.tagName != '!') results.push(child); } return results; }, adjacent: function(nodes) { for (var i = 0, results = [], node; node = nodes[i]; i++) { var next = this.nextElementSibling(node); if (next) results.push(next); } return results; }, laterSibling: function(nodes) { var h = Selector.handlers; for (var i = 0, results = [], node; node = nodes[i]; i++) h.concat(results, Element.nextSiblings(node)); return results; }, nextElementSibling: function(node) { while (node = node.nextSibling) if (node.nodeType == 1) return node; return null; }, previousElementSibling: function(node) { while (node = node.previousSibling) if (node.nodeType == 1) return node; return null; }, // TOKEN FUNCTIONS tagName: function(nodes, root, tagName, combinator) { var uTagName = tagName.toUpperCase(); var results = [], h = Selector.handlers; if (nodes) { if (combinator) { // fastlane for ordinary descendant combinators if (combinator == "descendant") { for (var i = 0, node; node = nodes[i]; i++) h.concat(results, node.getElementsByTagName(tagName)); return results; } else nodes = this[combinator](nodes); if (tagName == "*") return nodes; } for (var i = 0, node; node = nodes[i]; i++) if (node.tagName.toUpperCase() === uTagName) results.push(node); return results; } else return root.getElementsByTagName(tagName); }, id: function(nodes, root, id, combinator) { var targetNode = $(id), h = Selector.handlers; if (!targetNode) return []; if (!nodes && root == document) return [targetNode]; if (nodes) { if (combinator) { if (combinator == 'child') { for (var i = 0, node; node = nodes[i]; i++) if (targetNode.parentNode == node) return [targetNode]; } else if (combinator == 'descendant') { for (var i = 0, node; node = nodes[i]; i++) if (Element.descendantOf(targetNode, node)) return [targetNode]; } else if (combinator == 'adjacent') { for (var i = 0, node; node = nodes[i]; i++) if (Selector.handlers.previousElementSibling(targetNode) == node) return [targetNode]; } else nodes = h[combinator](nodes); } for (var i = 0, node; node = nodes[i]; i++) if (node == targetNode) return [targetNode]; return []; } return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : []; }, className: function(nodes, root, className, combinator) { if (nodes && combinator) nodes = this[combinator](nodes); return Selector.handlers.byClassName(nodes, root, className); }, byClassName: function(nodes, root, className) { if (!nodes) nodes = Selector.handlers.descendant([root]); var needle = ' ' + className + ' '; for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) { nodeClassName = node.className; if (nodeClassName.length == 0) continue; if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle)) results.push(node); } return results; }, attrPresence: function(nodes, root, attr, combinator) { if (!nodes) nodes = root.getElementsByTagName("*"); if (nodes && combinator) nodes = this[combinator](nodes); var results = []; for (var i = 0, node; node = nodes[i]; i++) if (Element.hasAttribute(node, attr)) results.push(node); return results; }, attr: function(nodes, root, attr, value, operator, combinator) { if (!nodes) nodes = root.getElementsByTagName("*"); if (nodes && combinator) nodes = this[combinator](nodes); var handler = Selector.operators[operator], results = []; for (var i = 0, node; node = nodes[i]; i++) { var nodeValue = Element.readAttribute(node, attr); if (nodeValue === null) continue; if (handler(nodeValue, value)) results.push(node); } return results; }, pseudo: function(nodes, name, value, root, combinator) { if (nodes && combinator) nodes = this[combinator](nodes); if (!nodes) nodes = root.getElementsByTagName("*"); return Selector.pseudos[name](nodes, value, root); } }, pseudos: { 'first-child': function(nodes, value, root) { for (var i = 0, results = [], node; node = nodes[i]; i++) { if (Selector.handlers.previousElementSibling(node)) continue; results.push(node); } return results; }, 'last-child': function(nodes, value, root) { for (var i = 0, results = [], node; node = nodes[i]; i++) { if (Selector.handlers.nextElementSibling(node)) continue; results.push(node); } return results; }, 'only-child': function(nodes, value, root) { var h = Selector.handlers; for (var i = 0, results = [], node; node = nodes[i]; i++) if (!h.previousElementSibling(node) && !h.nextElementSibling(node)) results.push(node); return results; }, 'nth-child': function(nodes, formula, root) { return Selector.pseudos.nth(nodes, formula, root); }, 'nth-last-child': function(nodes, formula, root) { return Selector.pseudos.nth(nodes, formula, root, true); }, 'nth-of-type': function(nodes, formula, root) { return Selector.pseudos.nth(nodes, formula, root, false, true); }, 'nth-last-of-type': function(nodes, formula, root) { return Selector.pseudos.nth(nodes, formula, root, true, true); }, 'first-of-type': function(nodes, formula, root) { return Selector.pseudos.nth(nodes, "1", root, false, true); }, 'last-of-type': function(nodes, formula, root) { return Selector.pseudos.nth(nodes, "1", root, true, true); }, 'only-of-type': function(nodes, formula, root) { var p = Selector.pseudos; return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root); }, // handles the an+b logic getIndices: function(a, b, total) { if (a == 0) return b > 0 ? [b] : []; return $R(1, total).inject([], function(memo, i) { if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i); return memo; }); }, // handles nth(-last)-child, nth(-last)-of-type, and (first|last)-of-type nth: function(nodes, formula, root, reverse, ofType) { if (nodes.length == 0) return []; if (formula == 'even') formula = '2n+0'; if (formula == 'odd') formula = '2n+1'; var h = Selector.handlers, results = [], indexed = [], m; h.mark(nodes); for (var i = 0, node; node = nodes[i]; i++) { if (!node.parentNode._countedByPrototype) { h.index(node.parentNode, reverse, ofType); indexed.push(node.parentNode); } } if (formula.match(/^\d+$/)) { // just a number formula = Number(formula); for (var i = 0, node; node = nodes[i]; i++) if (node.nodeIndex == formula) results.push(node); } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b if (m[1] == "-") m[1] = -1; var a = m[1] ? Number(m[1]) : 1; var b = m[2] ? Number(m[2]) : 0; var indices = Selector.pseudos.getIndices(a, b, nodes.length); for (var i = 0, node, l = indices.length; node = nodes[i]; i++) { for (var j = 0; j < l; j++) if (node.nodeIndex == indices[j]) results.push(node); } } h.unmark(nodes); h.unmark(indexed); return results; }, 'empty': function(nodes, value, root) { for (var i = 0, results = [], node; node = nodes[i]; i++) { // IE treats comments as element nodes if (node.tagName == '!' || (node.firstChild && !node.innerHTML.match(/^\s*$/))) continue; results.push(node); } return results; }, 'not': function(nodes, selector, root) { var h = Selector.handlers, selectorType, m; var exclusions = new Selector(selector).findElements(root); h.mark(exclusions); for (var i = 0, results = [], node; node = nodes[i]; i++) if (!node._countedByPrototype) results.push(node); h.unmark(exclusions); return results; }, 'enabled': function(nodes, value, root) { for (var i = 0, results = [], node; node = nodes[i]; i++) if (!node.disabled) results.push(node); return results; }, 'disabled': function(nodes, value, root) { for (var i = 0, results = [], node; node = nodes[i]; i++) if (node.disabled) results.push(node); return results; }, 'checked': function(nodes, value, root) { for (var i = 0, results = [], node; node = nodes[i]; i++) if (node.checked) results.push(node); return results; } }, operators: { '=': function(nv, v) { return nv == v; }, '!=': function(nv, v) { return nv != v; }, '^=': function(nv, v) { return nv.startsWith(v); }, '$=': function(nv, v) { return nv.endsWith(v); }, '*=': function(nv, v) { return nv.include(v); }, '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); }, '|=': function(nv, v) { return ('-' + nv.toUpperCase() + '-').include('-' + v.toUpperCase() + '-'); } }, split: function(expression) { var expressions = []; expression.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) { expressions.push(m[1].strip()); }); return expressions; }, matchElements: function(elements, expression) { var matches = $$(expression), h = Selector.handlers; h.mark(matches); for (var i = 0, results = [], element; element = elements[i]; i++) if (element._countedByPrototype) results.push(element); h.unmark(matches); return results; }, findElement: function(elements, expression, index) { if (Object.isNumber(expression)) { index = expression; expression = false; } return Selector.matchElements(elements, expression || '*')[index || 0]; }, findChildElements: function(element, expressions) { expressions = Selector.split(expressions.join(',')); var results = [], h = Selector.handlers; for (var i = 0, l = expressions.length, selector; i < l; i++) { selector = new Selector(expressions[i].strip()); h.concat(results, selector.findElements(element)); } return (l > 1) ? h.unique(results) : results; } }); if (Prototype.Browser.IE) { Object.extend(Selector.handlers, { // IE returns comment nodes on getElementsByTagName("*"). // Filter them out. concat: function(a, b) { for (var i = 0, node; node = b[i]; i++) if (node.tagName !== "!") a.push(node); return a; }, // IE improperly serializes _countedByPrototype in (inner|outer)HTML. unmark: function(nodes) { for (var i = 0, node; node = nodes[i]; i++) node.removeAttribute('_countedByPrototype'); return nodes; } }); } function $$() { return Selector.findChildElements(document, $A(arguments)); } var Form = { reset: function(form) { $(form).reset(); return form; }, serializeElements: function(elements, options) { if (typeof options != 'object') options = { hash: !!options }; else if (Object.isUndefined(options.hash)) options.hash = true; var key, value, submitted = false, submit = options.submit; var data = elements.inject({ }, function(result, element) { if (!element.disabled && element.name) { key = element.name; value = $(element).getValue(); if (value != null && (element.type != 'submit' || (!submitted && submit !== false && (!submit || key == submit) && (submitted = true)))) { if (key in result) { // a key is already present; construct an array of values if (!Object.isArray(result[key])) result[key] = [result[key]]; result[key].push(value); } else result[key] = value; } } return result; }); return options.hash ? data : Object.toQueryString(data); } }; Form.Methods = { serialize: function(form, options) { return Form.serializeElements(Form.getElements(form), options); }, getElements: function(form) { return $A($(form).getElementsByTagName('*')).inject([], function(elements, child) { if (Form.Element.Serializers[child.tagName.toLowerCase()]) elements.push(Element.extend(child)); return elements; } ); }, getInputs: function(form, typeName, name) { form = $(form); var inputs = form.getElementsByTagName('input'); if (!typeName && !name) return $A(inputs).map(Element.extend); for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) { var input = inputs[i]; if ((typeName && input.type != typeName) || (name && input.name != name)) continue; matchingInputs.push(Element.extend(input)); } return matchingInputs; }, disable: function(form) { form = $(form); Form.getElements(form).invoke('disable'); return form; }, enable: function(form) { form = $(form); Form.getElements(form).invoke('enable'); return form; }, findFirstElement: function(form) { var elements = $(form).getElements().findAll(function(element) { return 'hidden' != element.type && !element.disabled; }); var firstByIndex = elements.findAll(function(element) { return element.hasAttribute('tabIndex') && element.tabIndex >= 0; }).sortBy(function(element) { return element.tabIndex }).first(); return firstByIndex ? firstByIndex : elements.find(function(element) { return ['input', 'select', 'textarea'].include(element.tagName.toLowerCase()); }); }, focusFirstElement: function(form) { form = $(form); form.findFirstElement().activate(); return form; }, request: function(form, options) { form = $(form), options = Object.clone(options || { }); var params = options.parameters, action = form.readAttribute('action') || ''; if (action.blank()) action = window.location.href; options.parameters = form.serialize(true); if (params) { if (Object.isString(params)) params = params.toQueryParams(); Object.extend(options.parameters, params); } if (form.hasAttribute('method') && !options.method) options.method = form.method; return new Ajax.Request(action, options); } }; /*--------------------------------------------------------------------------*/ Form.Element = { focus: function(element) { $(element).focus(); return element; }, select: function(element) { $(element).select(); return element; } }; Form.Element.Methods = { serialize: function(element) { element = $(element); if (!element.disabled && element.name) { var value = element.getValue(); if (value != undefined) { var pair = { }; pair[element.name] = value; return Object.toQueryString(pair); } } return ''; }, getValue: function(element) { element = $(element); var method = element.tagName.toLowerCase(); return Form.Element.Serializers[method](element); }, setValue: function(element, value) { element = $(element); var method = element.tagName.toLowerCase(); Form.Element.Serializers[method](element, value); return element; }, clear: function(element) { $(element).value = ''; return element; }, present: function(element) { return $(element).value != ''; }, activate: function(element) { element = $(element); try { element.focus(); if (element.select && (element.tagName.toLowerCase() != 'input' || !['button', 'reset', 'submit'].include(element.type))) element.select(); } catch (e) { } return element; }, disable: function(element) { element = $(element); element.blur(); element.disabled = true; return element; }, enable: function(element) { element = $(element); element.disabled = false; return element; } }; /*--------------------------------------------------------------------------*/ var Field = Form.Element; var $F = Form.Element.Methods.getValue; /*--------------------------------------------------------------------------*/ Form.Element.Serializers = { input: function(element, value) { switch (element.type.toLowerCase()) { case 'checkbox': case 'radio': return Form.Element.Serializers.inputSelector(element, value); default: return Form.Element.Serializers.textarea(element, value); } }, inputSelector: function(element, value) { if (Object.isUndefined(value)) return element.checked ? element.value : null; else element.checked = !!value; }, textarea: function(element, value) { if (Object.isUndefined(value)) return element.value; else element.value = value; }, select: function(element, index) { if (Object.isUndefined(index)) return this[element.type == 'select-one' ? 'selectOne' : 'selectMany'](element); else { var opt, value, single = !Object.isArray(index); for (var i = 0, length = element.length; i < length; i++) { opt = element.options[i]; value = this.optionValue(opt); if (single) { if (value == index) { opt.selected = true; return; } } else opt.selected = index.include(value); } } }, selectOne: function(element) { var index = element.selectedIndex; return index >= 0 ? this.optionValue(element.options[index]) : null; }, selectMany: function(element) { var values, length = element.length; if (!length) return null; for (var i = 0, values = []; i < length; i++) { var opt = element.options[i]; if (opt.selected) values.push(this.optionValue(opt)); } return values; }, optionValue: function(opt) { // extend element because hasAttribute may not be native return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text; } }; /*--------------------------------------------------------------------------*/ Abstract.TimedObserver = Class.create(PeriodicalExecuter, { initialize: function($super, element, frequency, callback) { $super(callback, frequency); this.element = $(element); this.lastValue = this.getValue(); }, execute: function() { var value = this.getValue(); if (Object.isString(this.lastValue) && Object.isString(value) ? this.lastValue != value : String(this.lastValue) != String(value)) { this.callback(this.element, value); this.lastValue = value; } } }); Form.Element.Observer = Class.create(Abstract.TimedObserver, { getValue: function() { return Form.Element.getValue(this.element); } }); Form.Observer = Class.create(Abstract.TimedObserver, { getValue: function() { return Form.serialize(this.element); } }); /*--------------------------------------------------------------------------*/ Abstract.EventObserver = Class.create({ initialize: function(element, callback) { this.element = $(element); this.callback = callback; this.lastValue = this.getValue(); if (this.element.tagName.toLowerCase() == 'form') this.registerFormCallbacks(); else this.registerCallback(this.element); }, onElementEvent: function() { var value = this.getValue(); if (this.lastValue != value) { this.callback(this.element, value); this.lastValue = value; } }, registerFormCallbacks: function() { Form.getElements(this.element).each(this.registerCallback, this); }, registerCallback: function(element) { if (element.type) { switch (element.type.toLowerCase()) { case 'checkbox': case 'radio': Event.observe(element, 'click', this.onElementEvent.bind(this)); break; default: Event.observe(element, 'change', this.onElementEvent.bind(this)); break; } } } }); Form.Element.EventObserver = Class.create(Abstract.EventObserver, { getValue: function() { return Form.Element.getValue(this.element); } }); Form.EventObserver = Class.create(Abstract.EventObserver, { getValue: function() { return Form.serialize(this.element); } }); if (!window.Event) var Event = { }; Object.extend(Event, { KEY_BACKSPACE: 8, KEY_TAB: 9, KEY_RETURN: 13, KEY_ESC: 27, KEY_LEFT: 37, KEY_UP: 38, KEY_RIGHT: 39, KEY_DOWN: 40, KEY_DELETE: 46, KEY_HOME: 36, KEY_END: 35, KEY_PAGEUP: 33, KEY_PAGEDOWN: 34, KEY_INSERT: 45, cache: { }, relatedTarget: function(event) { var element; switch(event.type) { case 'mouseover': element = event.fromElement; break; case 'mouseout': element = event.toElement; break; default: return null; } return Element.extend(element); } }); Event.Methods = (function() { var isButton; if (Prototype.Browser.IE) { var buttonMap = { 0: 1, 1: 4, 2: 2 }; isButton = function(event, code) { return event.button == buttonMap[code]; }; } else if (Prototype.Browser.WebKit) { isButton = function(event, code) { switch (code) { case 0: return event.which == 1 && !event.metaKey; case 1: return event.which == 1 && event.metaKey; default: return false; } }; } else { isButton = function(event, code) { return event.which ? (event.which === code + 1) : (event.button === code); }; } return { isLeftClick: function(event) { return isButton(event, 0) }, isMiddleClick: function(event) { return isButton(event, 1) }, isRightClick: function(event) { return isButton(event, 2) }, element: function(event) { var node = Event.extend(event).target; return Element.extend(node.nodeType == Node.TEXT_NODE ? node.parentNode : node); }, findElement: function(event, expression) { var element = Event.element(event); if (!expression) return element; var elements = [element].concat(element.ancestors()); return Selector.findElement(elements, expression, 0); }, pointer: function(event) { return { x: event.pageX || (event.clientX + (document.documentElement.scrollLeft || document.body.scrollLeft)), y: event.pageY || (event.clientY + (document.documentElement.scrollTop || document.body.scrollTop)) }; }, pointerX: function(event) { return Event.pointer(event).x }, pointerY: function(event) { return Event.pointer(event).y }, stop: function(event) { Event.extend(event); event.preventDefault(); event.stopPropagation(); event.stopped = true; } }; })(); Event.extend = (function() { var methods = Object.keys(Event.Methods).inject({ }, function(m, name) { m[name] = Event.Methods[name].methodize(); return m; }); if (Prototype.Browser.IE) { Object.extend(methods, { stopPropagation: function() { this.cancelBubble = true }, preventDefault: function() { this.returnValue = false }, inspect: function() { return "[object Event]" } }); return function(event) { if (!event) return false; if (event._extendedByPrototype) return event; event._extendedByPrototype = Prototype.emptyFunction; var pointer = Event.pointer(event); Object.extend(event, { target: event.srcElement, relatedTarget: Event.relatedTarget(event), pageX: pointer.x, pageY: pointer.y }); return Object.extend(event, methods); }; } else { Event.prototype = Event.prototype || document.createEvent("HTMLEvents").__proto__; Object.extend(Event.prototype, methods); return Prototype.K; } })(); Object.extend(Event, (function() { var cache = Event.cache; function getEventID(element) { if (element._prototypeEventID) return element._prototypeEventID[0]; arguments.callee.id = arguments.callee.id || 1; return element._prototypeEventID = [++arguments.callee.id]; } function getDOMEventName(eventName) { if (eventName && eventName.include(':')) return "dataavailable"; return eventName; } function getCacheForID(id) { return cache[id] = cache[id] || { }; } function getWrappersForEventName(id, eventName) { var c = getCacheForID(id); return c[eventName] = c[eventName] || []; } function createWrapper(element, eventName, handler) { var id = getEventID(element); var c = getWrappersForEventName(id, eventName); if (c.pluck("handler").include(handler)) return false; var wrapper = function(event) { if (!Event || !Event.extend || (event.eventName && event.eventName != eventName)) return false; Event.extend(event); handler.call(element, event); }; wrapper.handler = handler; c.push(wrapper); return wrapper; } function findWrapper(id, eventName, handler) { var c = getWrappersForEventName(id, eventName); return c.find(function(wrapper) { return wrapper.handler == handler }); } function destroyWrapper(id, eventName, handler) { var c = getCacheForID(id); if (!c[eventName]) return false; c[eventName] = c[eventName].without(findWrapper(id, eventName, handler)); } function destroyCache() { for (var id in cache) for (var eventName in cache[id]) cache[id][eventName] = null; } if (window.attachEvent) { window.attachEvent("onunload", destroyCache); } return { observe: function(element, eventName, handler) { element = $(element); var name = getDOMEventName(eventName); var wrapper = createWrapper(element, eventName, handler); if (!wrapper) return element; if (element.addEventListener) { element.addEventListener(name, wrapper, false); } else { element.attachEvent("on" + name, wrapper); } return element; }, stopObserving: function(element, eventName, handler) { element = $(element); var id = getEventID(element), name = getDOMEventName(eventName); if (!handler && eventName) { getWrappersForEventName(id, eventName).each(function(wrapper) { element.stopObserving(eventName, wrapper.handler); }); return element; } else if (!eventName) { Object.keys(getCacheForID(id)).each(function(eventName) { element.stopObserving(eventName); }); return element; } var wrapper = findWrapper(id, eventName, handler); if (!wrapper) return element; if (element.removeEventListener) { element.removeEventListener(name, wrapper, false); } else { element.detachEvent("on" + name, wrapper); } destroyWrapper(id, eventName, handler); return element; }, fire: function(element, eventName, memo) { element = $(element); if (element == document && document.createEvent && !element.dispatchEvent) element = document.documentElement; var event; if (document.createEvent) { event = document.createEvent("HTMLEvents"); event.initEvent("dataavailable", true, true); } else { event = document.createEventObject(); event.eventType = "ondataavailable"; } event.eventName = eventName; event.memo = memo || { }; if (document.createEvent) { element.dispatchEvent(event); } else { element.fireEvent(event.eventType, event); } return Event.extend(event); } }; })()); Object.extend(Event, Event.Methods); Element.addMethods({ fire: Event.fire, observe: Event.observe, stopObserving: Event.stopObserving }); Object.extend(document, { fire: Element.Methods.fire.methodize(), observe: Element.Methods.observe.methodize(), stopObserving: Element.Methods.stopObserving.methodize(), loaded: false }); (function() { /* Support for the DOMContentLoaded event is based on work by Dan Webb, Matthias Miller, Dean Edwards and John Resig. */ var timer; function fireContentLoadedEvent() { if (document.loaded) return; if (timer) window.clearInterval(timer); document.fire("dom:loaded"); document.loaded = true; } if (document.addEventListener) { if (Prototype.Browser.WebKit) { timer = window.setInterval(function() { if (/loaded|complete/.test(document.readyState)) fireContentLoadedEvent(); }, 0); Event.observe(window, "load", fireContentLoadedEvent); } else { document.addEventListener("DOMContentLoaded", fireContentLoadedEvent, false); } } else { document.write(" ================================================ FILE: tests/vue/example.vue ================================================