dygraphs-2.2.1/.browserslistrc010064400000000000000000000000321437353256000133570ustar00last 100 years not ie < 9 dygraphs-2.2.1/.github/CONTRIBUTING.md010064400000000000000000000012661437353256000140720ustar00Filing a bug report? Please include the following: 1. Link to a page which demonstrates the problem, preferably a jsfiddle (use [this jsfiddle](http://dygraphs.com/fiddle) as a template) 2. Browser and Operating System that exhibit the problem It also helps if you include the non-compacted version of the JS on your page. For instance, instead of doing this: ```html ``` do this: ```html ``` This makes error messages and debugging simpler. The jsfiddle does this automatically. Sending a Pull Request? See our [guide to making dygraphs contributions](/DEVELOP.md). dygraphs-2.2.1/.github/ISSUE_TEMPLATE.md010064400000000000000000000011661437353256000143450ustar00Filing a bug report? Please include the following: 1. Link to a page which demonstrates the problem, preferably a jsfiddle (use http://dygraphs.com/fiddle as a template) 2. Browser and Operating System that exhibit the problem 3. Version of dygraphs that you're using It also helps if you include the non-compacted version of the JS on your page. For instance, instead of doing this: ```html ``` do this: ```html ``` This makes error messages and debugging simpler. The jsfiddle does this automatically. dygraphs-2.2.1/.github/PULL_REQUEST_TEMPLATE.md010064400000000000000000000011651437353256000154400ustar00Please read the guide to making dygraphs changes: http://dygraphs.com/changes.html Pull Requests will only be accepted if: - You clearly explain what you're adding and why you believe it's an improvement. For example: "Fixes issue #123". - You adhere to the style of the rest of the dygraphs code base. - You write an `auto_test` for the code that you're adding. Be sure to document any new options you add. Also be aware that PRs which add options are likely to be rejected. dygraphs already has many options. If you can fit your feature into one of those or implement it as a plugin, it will be more likely to get merged. dygraphs-2.2.1/.github/workflows/build.yml010064400000000000000000000025241437353256000155160ustar00{ "concurrency": { "cancel-in-progress": true, "group": "pages" }, "jobs": { "build-and-pages": { "environment": { "name": "github-pages", "url": "${{ steps.deployment.outputs.page_url }}" }, "if": "github.repository == 'mirabilos/dygraphs'", "runs-on": "ubuntu-latest", "steps": [ { "uses": "actions/checkout@v3.3.0" }, { "run": "./.pages.sh" }, { "uses": "actions/upload-pages-artifact@v1.0.7" }, { "id": "deployment", "uses": "actions/deploy-pages@v1.2.3" } ] }, "build-split": { "environment": { "name": "GHA-output", "url": "${{ steps.deployment.outputs.page_url }}" }, "if": "github.repository == 'danvk/dygraphs'", "runs-on": "ubuntu-latest", "steps": [ { "uses": "actions/checkout@v3.3.0" }, { "run": "./.pages.sh" }, { "uses": "actions/upload-pages-artifact@v1.0.7" } ] } }, "name": "build and test", "on": { "push": { "branches": [ "debian", "master" ] }, "workflow_dispatch": null }, "permissions": { "contents": "read", "id-token": "write", "pages": "write" } } dygraphs-2.2.1/.github/workflows/gha-update.yml010064400000000000000000000010521437353256000164310ustar00{ "jobs": { "doit": { "runs-on": "ubuntu-latest", "steps": [ { "uses": "actions/checkout@v3.3.0", "with": { "token": "${{ secrets.WORKFLOW_SECRET }}" } }, { "uses": "saadmk11/github-actions-version-updater@v0.7.3", "with": { "pull_request_user_reviewers": "mirabilos", "token": "${{ secrets.WORKFLOW_SECRET }}" } } ] } }, "name": "GitHub Actions Version Updater", "on": "workflow_dispatch" } dygraphs-2.2.1/.gitignore010064400000000000000000000003211437353256000122600ustar00*.log __pycache__ _site coverage debian/copyright dist disttmp docroot docs/download.html docs/options.html docs/versions.html dygraphs-2.*.tgz env jsdoc jsdoc.tmp node_modules package-lock.json site src-es5/ dygraphs-2.2.1/.jshintrc010064400000000000000000000004201437353256000121150ustar00{ "newcap": true, "noarg": true, "shadow": true, "strict": true, "forin": true, "immed": true, "latedef": true, "nonbsp": true, "undef": true, "sub": true, "browser": true, "devel": true, "globals": { "DEBUG": true }, "maxerr": 100000 } dygraphs-2.2.1/.jslibs012077700000000000000000000000001437353256000140012docs/.jslibsustar00dygraphs-2.2.1/.pages.sh010075500000000000000000000035051437353256000120130ustar00#!/bin/sh sudoapt() { local _sudoapt_e='' if command -v eatmydata >/dev/null 2>&1; then _sudoapt_e=eatmydata fi sudo env DEBIAN_FRONTEND=noninteractive $_sudoapt_e apt-get "$@" } sudoagi='sudoapt --purge install --no-install-recommends -y' set -ex mksh -c true || { $sudoagi mksh exec mksh "$0" "$@" exit 255 } export LC_ALL=C unset LANGUAGE set -o pipefail $sudoagi eatmydata sudoapt clean sudoapt update #sudoapt --purge dist-upgrade -y $sudoagi eatmydata git \ ed jq jsdoc-toolkit \ libjs-bootstrap libjs-jquery libjs-jquery-ui \ mksh pax python3 : drop any pre-installed PhantomJS as they cause breakage bash -c 'set -o noglob; while true; do found=0; for x in $(which -a phantomjs); do test -e "$x" || continue; found=1; rm -f "$x"; done; test $found = 1 || break; done' eatmydata env TMPDIR=/tmp npm install -g phantomjs@1.9.7-15 eatmydata env TMPDIR=/tmp npm install (eatmydata npm run clean || :) eatmydata npm run build eatmydata npm run test eatmydata npm run test-min if [[ $GITHUB_REPOSITORY = danvk/dygraphs ]]; then eatmydata npm run coverage eatmydata scripts/post-coverage.sh eatmydata scripts/weigh-in.sh fi rm -rf _site mv site _site cd _site find . -type d -print0 | sort -z | xargs -0 mksh ../scripts/mkdiridx.sh if [[ $GITHUB_REF = refs/heads/debian && $GITHUB_REPOSITORY = mirabilos/dygraphs ]]; then echo dygraph.github.mirsolutions.de >CNAME imprint_text='Impressum und Datenschutzerklärung' else imprint_text= fi if [[ -n $imprint_text ]]; then grep -FrlZ '@@@PLACE_IMPRINT_LINK_HERE_IF_NECESSARY@@@' . | \ xargs -0 perl -pi -e ' s'\'''\''$1.q{'"$imprint_text"'}.$2'\''eo ' fi cd .. dygraphs-2.2.1/CHANGES.md010064400000000000000000000222571437353256000116760ustar00# v2.2.1 (2023-02-16) ## Future incompatibilities - `window.smoothPlotter` will not be set by `extras/smooth-plotter.js` any more RSN ## New features - Permit initialising with `[]` as “no data yet” indicator (#597) - The modularisation of the test setup also allows stable access to exported functions (e.g. `Dygraph._require('dygraphs/src/dygraph-utils.js').numberValueFormatter`) which makes writing one’s own value formatters easier (as usual, proceed with care, exports with an underscore are internal); see for the exposed surface - Provide Dygraph.onDOMready(cb) as lightweight jQuery(cb)/… alternative, to keep the demos/tests self-contained ## Bugfixes - Fix synchroniser not calling user’s `drawCallback` when blocked (#953) plus do not redraw unnecessarily - Documentation fixes and improvements including self-checks where possible - Fix missing legend for `x == -1` (#1002) - Allow ES6-importing the extras (#848) (#989) (#1009) - Fix broken zoom also in the drawing test (same as #611/#953) - Resetting `panEdgeFraction` now works - Fix tests/unboxed-spark regression caused by PR #936 - Synchroniser doesn’t force x axis range if unzoomed (#956) ## Other user-visible changes - Fix issue #611 in the drawing demo gallery (#953) - `jq` is now also required to build - Fix typos, wordings, apostrophes, etc. - Export `pickDateTickGranularity` so overriding `dateTicker` is easier - Error bars are now properly called high/low bands (#1004), but the options stay the same for compatibility - Document annotations xval for Date better (#970) - Document more strongly that series labels must be unique (#960) - Remove references to nōn-existing `yAxisLabelWidth` option - Fix jsFiddle links from gallery ## Internal refactors/fixes - Shrink `tests.js` source map and make its build reproducible on Debian - `tests/resizable.html` exposes the graph object, for quick F12 in-browser use - Generate `versions.html` and release notes from new top-level [`CHANGES.md`](https://github.com/danvk/dygraphs/blob/master/CHANGES.md) - Modularise dygraphs/tests setup so we can now also test the minified prod css/js (#1028) - Generate stable orig tarballs for releases ourselves (we still use the NPM ones for binary tarballs) - Less fragility with arrow function `this` rebinding check # v2.2.0 (2023-01-25) This is the first “full” release after v2.1.0 with the following news: ## Breaking changes - Stop parsing the deprecated 8-digit ‘YYYYMMDD’ date format (#984) (#985) (#1023) - GWT “support” is no longer pertinent as we cannot recompile the binary JAR; the latter is still available from the website for now ## New features - Add option to colour the range selector’s veil (#856) - Add `strokeStyle` option to crosshair plugin (#955) - Support `DocumentFragment` as a result from a `legendFormatter` function (#958) (#959) - Add data index to `legendFormatter` data (#965) - Implement `#RRGGBB`/`#RRGGBBAA` hex colour parsing - Make `animateBackgroundFade` into an option - Add new `resizable` option using `ResizeObserver` on maindiv + CSS - Add offset config for legend div in follow mode (#946) ## Bugfixes - Fix duplicate annotation when having duplicate points (#826) - Make sure that `canvasx` and `canvasy` properties are initialised from the start (#896) - Fix issue #781 when the y range is zero (#909) - Synchroniser now calls `highlightCallback` (#935) - Fix `drawAxes` to allow one of two y axes to be drawn (#936) - LEGEND: Don’t show y if value is undefined (#853) (#947) - Fixed “Cannot read property 'length' of undefined” issue (#962) (#963) - Two fixes in the calculations of pinch-zoom (#990) - Fix for double paint of grid of x-axis (#1007) - Fix number of days in a year (#1012) - Fix `labelsKMB` and `labelsKMG2` (#571) (#994) (#1022) - Fix label position in `onmouseover` legend mode was never updated - Fix several cases of mispositioning/missizing by enabling elements before using `offsetWidth` etc. - Make it possible to use `logscale` (and `sigma`, `wilsonInterval`) per-axis (#986) ## Other user-visible changes - Documentation is now shipped (mostly self-contained) in the release - Tests that use Google JSAPI are now marked ⚠ and no other external content is loaded anywhere - Calculate legend positioning relative to closest data point (#1013) - Add missing documentation about annotations `xval` option (#992) - source mapping URLs are now correct (#1027) - Ship ES5 versions of extras (#952), use them in examples - Fix documentation/website HTML/CSS/JS bugs (#979) (#1008) - On the website, external links are now clearly labelled - The documentation and website now have browsable subdirectories by manually generated directory index files - Link to UNPKG as CDN as well (to keep neutral; use of CDNs in websites violates the EU-GDPR) - Most issues with right-to-left languages should now be fixed (#1019) - Document that Dygraph will misbehave if the main div has padding ## Internal refactors/fixes - Use link href, not link src (#904) - Add missing semicolon in externs (#964) - Large build system improvements relying in large parts on Debian and GHA - Full licence review as part of uploading to Debian (#1024) (#1025) (#1029); drop embedded code copies - Fix some JSDoc warnings - Update to contemporary versions of Python 3, jQuery, Babel, etc. - Add copies of design documents that were previously only available on Google Docs or part of GitHub issues to offline documentation - Nuke useless semicola after function declaration bodies - Fix closure type annotation in `hairlines.js` - Remove already commented-out obsolete, or redundant, code # v2.1.0 (2017-12-08) ## New features - Double click event can be captured and cancelled by plugins (#840) - `setAnnotations`’ second parameter is now an optional boolean (#851) - Add `pixelRatio` option, which may allow improved performance on smaller screens by controlling the canvas’ pixel ratio (#877) - x axis label and tick logic can now operate at millisecond-level granularity (#893) ## Bug fixes - Repair a bug in “Custom interaction models” demo (#825) ## Internal refactors/fixes - Fix various spelling mistakes (#844) - Fix a couple of type signatures in `dygraph-externs.js` (#859) # v2.0.0 (2017-01-11) ## Breaking changes - JS files were renamed to `dygraph.js` and `dygraph.min.js`. - There’s now a `dygraph.css` file that you must include. - Dropped support for old IE and other non-standard browsers. dygraphs works in IE11. I’m not sure about IE9 and IE10. - Double-click to unzoom zooms all the way out (and ignores `valueRange`). - Dropped old-style per-axis/per-series options. ## New features - Add a `legendFormatter` option (#683) - `this` is the Dygraph object in all callbacks - pass through (row, col) to `valueFormatter` - Option to not sync range in `extras/synchronizer.js` - Additional options for styling the range selector - `getRowForX` method - `setVisibility` can set the visibility of multiple series at once. - crosshair plugin extra - rebase/straw broom plugin (#590) - `highlightSeriesBackgroundColor` option - `yAxisExtremes()` method. - Passing strings in native format now throws. (Previously it kinda sorta worked.) ## Bug fixes - Selections are always cleared with animations - synchronizer calls previously-set callbacks - synchronizer only syncs when graphs are ready - Reset on synchronized graphs failed (#524) - fix to improve synchronizer performance (#658) - binary search bug fix in synchronizer - Fix range selection when chart is located inside fullscreen element (#576) - `fillAlpha` can be set per-series when `fillGraph` is set. - `xRangePad` was ignored on unzoom (#657) - Allow selected points where canvas-y coordinate is 0 (#692) - Using `valueRange` with `logscale` and `yRangePad` has unexpected results (#661) - With `drawGapEdgePoints`, unwanted point often drawn at beginning of chart (#745) ## Other user-visible changes - `legend: follow` positioning changes ## Internal refactors - Code moved into a `src/` directory - Tests use Mocha instead of jstd - dygraphs is split into ES6 modules and uses some ES6 features (e.g. arrows and destructuring). - dygraphs is built using Babel and browserify - Code coverage is tracked continuously - Bundle size is now tracked continuously # v1.1.1 (2015-06-01) - Set `this` to the dygraph in all callbacks. - Minor bug fixes. # v1.1.0 (2014-12-03) ## Highlights - dygraphs is now “retina” compatible. - Dramatically improved performance for filled charts (i.e. `fillGraph`) - More sensible date ticks: “Jan 08” → “Jan 2008”, “29Jan” → “29 Jan” - Using a non-existent option now throws (with `dygraph-combined-dev.js`) - x-axis log scales - The `labelsUTC` option forces UTC formatting for all labels. - The new DataHandler system allows for more flexibility in data loading. - dygraphs has shrunk, because we moved some stuff into “extras” (133 KiB → 122 KiB) This will be the last major release to support browsers without a native implementation. See [blog post](http://blog.dygraphs.com/2014/12/dygraphs-110.html) for more details. # v1.0.1 (2013-08-29) Minor bug fixes and updates to web site. # v1.0.0 (2013-08-14) Initial Release. See [blog post](http://blog.dygraphs.com/2013/08/announcing-dygraphs-100.html). dygraphs-2.2.1/DEVELOP.md010064400000000000000000000046131437353256000117200ustar00## dygraphs developer notes So you've made a change to dygraphs and would like to contribute it back to the open source project. Wonderful! This is a step-by-step guide explaining how to do it. ### How-to To install dependencies, run: # for building apt-get install jq mksh pax python3 # npm dev dependencies npm install To build dygraphs and the tests, run: npm run clean # if necessary npm run build-jsonly To run the tests, run: npm run test # on the unminified bundle npm run test-min # on the .min.{css,js} The prerequisites for a full “npm run build” are: # for building apt-get install ed jsdoc-toolkit mksh pax python3 # for docs apt-get install libjs-bootstrap libjs-jquery libjs-jquery-ui To iterate on the code, install envify and run: npm run watch and open `tests/demo.html` (or one of the other demos) in your browser. To iterate on a unit test, run the `watch` command above and open auto_tests/runner.html in your browser. You can use the Mocha UI to run just a single test or suite. Or you can change `it` to `it.only` to do run just one test in code. To run a single test from the command line, you can use: npm run test -- --grep highlight-series-background (Note the extra `--`.) ### dygraphs style When making a change, please try to follow the style of the existing dygraphs code. This will make the review process go much more smoothly. A few salient points: 1. We try to adhere to Google's [JS style guide][gstyle] and would appreciate it if you try to as well. This means: * No tabs! Indent using two spaces. * Use camelCase for variable and function names. * Limit lines to 80 characters. 1. If you've added a new feature, add a test for it (in the tests/ directory) or a gallery entry. 1. If you've added an option, document it in `dygraph-options-reference.js`. You'll get lots of warnings if you don't. 1. If you've fixed a bug or added a feature, add a unit test (in `auto_tests`) for it. Adding a unit test ensures that we won't inadvertently break your feature in the future. To do this, either add to an existing test in `auto_tests/tests` or create a new one. ### Sending a Pull Request To make a change, you'll need to send a Pull Request. See GitHub's documentation [here][pr]. [gstyle]: https://google.github.io/styleguide/javascriptguide.xml [pr]: http://help.github.com/send-pull-requests/ dygraphs-2.2.1/LICENSE.txt010064400000000000000000000054421437353256000121240ustar00dygraphs is MIT-licenced: Copyright (c) 2006, 2009, 2011, 2012, 2013, 2017 Dan Vanderkam Copyright (c) 2011 Paul Felix Copyright (c) 2011, 2013 Robert Konigsberg Copyright (c) 2013 David Eberlein Copyright (c) 2013 Google, Inc. Copyright (c) 2014 mirabilos Copyright (c) 2015 Petr Shevtsov Copyright (c) 2022, 2023 mirabilos Deutsche Telekom LLCTO and numerous contributors (see git log) Some tests additionally are: Copyright (c) 2011, 2012 Google, Inc. or contributed by: - Benoit Boivin - Paul Felix - Marek Janda - Robert Konigsberg - George Madrid - Anthony Robledo - Fr. Sauter AG - Fr. Sauter AG - Ümit Seren - Sergey Slepian - Dan Vanderkam Parts of the documentation are or make use of code that is: Copyright (c) 2012 Google, Inc. - Robert Konigsberg The automatically added browser-pack shim is: Copyright (c) 2013, 2014 James Halliday Copyright (c) 2013 Roman Shtylman Copyright (c) 2013 Esa-Matti Suuronen Copyright (c) 2018 Philipp Simon Schmidt Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. The documentation and gallery uses Bootstrap and jQuery; see the relevant licence files of those external libraries for details. The icons under common/ are CC0-licenced and adapted by mirabilos. In Debian, /usr/share/common-licenses/CC0-1.0 has the full text. dygraphs-2.2.1/README.md010064400000000000000000000065111437353256000115560ustar00# dygraphs JavaScript charting library The dygraphs JavaScript library produces interactive, zoomable charts of time series: ![sample graph](Screenshot.png) Learn more about it at [dygraphs.com](http://www.dygraphs.com). Get help with dygraphs on [Stack Overflow][] (preferred) and [Google Groups][]. ## Features * Plots time series without using an external server or Flash * Supports [error bands][] around data series * Interactive [pan and zoom][] * Displays values [on mouseover][] * Adjustable [averaging period][] * Extensive set of [options][] for customization. * Compatible with the [Google Visualization API][gviz] ## Minimal Example ```html
``` Learn more by reading [the tutorial][] and seeing demonstrations of what dygraphs can do in the [gallery][]. You can get `dygraph.js` and `dygraph.css` from [UNPKG][], [cdnjs][] or [from NPM][npm] (see below). ## Usage with a module loader Get dygraphs from NPM: npm install dygraphs **Do not install from the git repository!** Always use a tarball install, which contains the prebuilt files; `npm` fails to build the source code on install from github. (The tarball from the GitHub Registry is fine.) You'll find pre-built JS & CSS files in `node_modules/dygraphs/dist/`. If you're using a module bundler like browserify or webpack, you can import dygraphs: ```js import Dygraph from 'dygraphs'; // or: const Dygraph = require('dygraphs'); const g = new Dygraph('graphdiv', data, { /* options */ }); ``` Check out the [dygraphs-es6 repo][] for a fully-worked example. ## Development To get going, install the following Debian packages… - `jq` - `mksh` - `pax` - `python3` … clone the repo and run: npm install npm run build-jsonly Then open `tests/demo.html` in your browser. Read more about the dygraphs development process in the [developer guide](/DEVELOP.md). ## License(s) dygraphs is available under the MIT license, included in [LICENSE.txt](./LICENSE.txt). [UNPKG]: https://unpkg.com/dygraphs/ [cdnjs]: https://cdnjs.com/libraries/dygraph [the tutorial]: http://www.dygraphs.com/tutorial.html [gallery]: http://www.dygraphs.com/gallery [error bands]: http://dygraphs.com/tests/legend-values.html [pan and zoom]: http://dygraphs.com/tests/link-interaction.html [on mouseover]: http://dygraphs.com/tests/legend-values.html [averaging period]: http://dygraphs.com/tests/temperature-sf-ny.html [options]: http://www.dygraphs.com/options.html [Stack Overflow]: https://stackoverflow.com/questions/tagged/dygraphs?sort=votes&pageSize=50 [Google Groups]: http://groups.google.com/group/dygraphs-users [gviz]: http://dygraphs.com/data.html#datatable [npm]: https://www.npmjs.com/package/dygraphs [dygraphs-es6 repo]: https://github.com/danvk/dygraphs-es6 dygraphs-2.2.1/Screenshot.png010064400000000000000000002317461437353256000131340ustar00PNG  IHDR huI8iCCPICCProfilexYP͓ݗG9s Qs摃HPPD@QPQA(IQDT@%Q1 ((uuUw]5 3ӽ ۊWdd(L @Xx Dŕ7 H|#ll,4Bvt{|}l>P$>&f DWw/Aq3@0+xj//J$A! >^!2aa;6EEON/"aPtdWr9g zPڱ 3R} -7'R~F"%ýwYcm!򏌱qOM 4صEtnF0Pot^':8gAf0}PN_ ,@(<ÁS /&! ÐH3/_vY#/Ab/?/ngtA~]-G+ZhmE3فZC5:5dw5ǕE$;3p%؇WZW!Kq DJP@` s$y}%yevάݯb~/s{ZmJOyR~vD@x(@Ygy 4.08v R@:8AeZA' c`Ly` D 6$ yH҆ Kr<(RP>T Cg:*݄GshZ>AaL 3\0,z(8 ΄ 27!x k(B1PR(UGQP.G_@ogЫ#Qǘa1xL6sӌnja1_X,3V5ź`Cؓl/vp8-5 Ɲ]Fq </7ƻRE|7~$k/!PH8K <$6tDсLL'STTTTjTTATiTeTTwfQSSP.~ND" tIRtAf KȾ r y@#DGNDSJs! -Vր֋6)5]!tt8zaz#z_L[s ( g"fW*Qf̡̅MYXXXrYr6~ge3b a+bke{fgeg?ϾȡÑ1 ssq&sp\2:uk[;(w76OQe^&^=P2ۼ||||g6Ewg7 XL"  #.,"$|PUxQUL$I^dJ$#%Z-D +*"vRlDW(K(KIx$T |*E-''U/5#,m)!*^FPUHOY%Pٳrrrrr}+(+)|TPS})|))3IRS ?fv! C68\isY&YlJӃr9A9ù 'r˗/:sae O ?2^St8xĪ(Ѽ_y,U,:N<{|̲#'*+*9+s+O={JWU~A19R-\]Zyssγ?6v݅u*uu9/K\lxԕ3 1q&k׮\^М$N=j7oh!}؝ٽݓԳٻr3\G-[On;`'Cctg>8796{A%Wbf gN̽}zk> MBݢbȲȷ+U}Ϋ)?◾5_þnmm\w [b?:~Zގx PHT.H0wNPH#o)#$۽0OP&'0,&/TԅZ--}. Klhv/..^C<|EdD JIVI}1=,7@RWVTUV)mcipܰިɒ9ۮ 8,b*zVd9:?wy:綸E)aS>}m4A__ օTbڢ͊7KH$&.'OnL)ۗ?*wLfW֫ȼ°#i+s ĻGS/_{9m8#6K;m77n,//{[Uյ?b]fac~? c;&~$@`$@#Q L$mJb>ɁHF{&#!s"Kvi g'7#Em=t-Q`CddM)'Ykijk1i㵿ӝӿgmxָͨ$4,RJd5ͬ#^FSsܝ\\ܰn˽#3[чgַݯ?@3.MЍP0ֈH( /OPb %'&OS姆ߝfv@+49;sf,P+t<]tR7q2aNzMKF]^^v)rwhԻz:٤%N;FFgWח^}nݸ=ֿ4~=poHaׇ#G=~09>t/)B/U_YN.w}pq {niG>+|)Ynq;f_G: v4ЋP ð|D5-UL;Ks}49fC4ʹ rYJX88{xyo5 #+/&[%%xed-fbuJO*nugiXhkNhUkl57X7eThf"bɴ,тeU.Kkfzh; {CnWG^+α.[{Wx|u{τo?À@ K\\Jhmw8gӈ]Qh蕘sq,q&$l$6&%&?O9!-u~[ {2]ܲdo94px`K1Re'\ʽ+"*,9ujjij#EjS/_ϼ4yE!eZZMu()p_d]+#wF>o|vs JvuBr{9k;mi nJ Tj/2A"H" x !9h7:Ih t`o8?8Pz@T;jMVB{"Y5 ǜaX=l jqKxi|,@Ep%\"BDgb*jZ4 O^44$tu$_Ͱ)|E[cxN.<&o? "`ac!Qj5YQےRפed{_*}TA2 Kiijkº&{ s"MMe̸i,PV˻gmfl_۽Ñ,j'y1F{MQu_8#>/L<\:B*R,Moؓ3h_I43۳;t@pq8No>վ[_a6N޸i 9?6/,{W"Jj !M I]n=_M##|!3e]7'lVu] ]}YWٿ0` GbHP詰Gp|' FbMJgb~R[>TҚҗd*~Òw*a"−dK+'r++s&ly z˴Wn5&4\[lm jѝkrkNʠ%=~2<~Bx z1j`~p`ı\"O?lνYg/= C:Ca~΄[hZnEa0q.,k.4pGpxCYI"Z{TKS'ɩ4L4ִJN231XҳNcpq2p>˓' /("#!.+&!&a!%"].%ZHQAUI-]}TST+S^Yc>31fKCg֑vn~rsSܳyWٟ:yPyYRxbVT e>&Z<]%IzʙT i ggw(k,p)|[ZBwTx J͓-UӖgU:+~.|:ȗ}t62]l.d>l[opwfwq'q;Oug~=|oQcNO3]x~ob~rKWJ3suߨ/,-/%]Zi^ʇs.;?eXuv}FVrQS]>۟Eۛ?jdc +~5T^wH_+ __ pHYs  !tEXtSoftwareGraphicConverter (Intel)w'IDATx]L+nbqHAH 56ِl0  6A`Z k33ԣp ԗy^6O 3q{[G Mm3B.4Ͳ,?>6Crn<]-'Cp4c?.4P!H^jk/R~+x1пj|s[0VLJw˒\7x @ H(՝jݟNZwu- @~{PS /_h`*c*F Ws{hG6ӽ~x9Zto>ˬ"2J 7]dEmQF$=B7e^;vWI\oT\ý_q Kp+֪~IzVk.Hq^ Q%@%ҒN~if*&ZgΘST,\6?Q=2$iHCcp0[#~G|u;AUܼWz}KYLƒ9-io+N\EqY!Ca"C=n=ak׎wx@+r{💤ib4k300 0M;)Ycnzm/㵖P%V$ ;liP=_ugi$MQ{$8%IPakÅp6BUɅְj&U!i8/wEp2|Q!IմmC))IJ$ qa}G}1;fGF1JơכQ͞Snyq}L#3w͌2,(Ii -)¢{.;T~RTM)Ir 嫣)a^em:ǣ*)kD @CA2`wT?=sLe~ͽ ṙFSv70dǎ/_w;[be=1ks芄ٜk凴;ۚA{9)^bڝDى/cߛ`%Ijw,v :3gmcZ}aMJv7"Տ7=UJWTMoQsy'KudF% u^@V=]엙%cОMO6t_;L]B/ѽ4$-OۅÄnCaj836;-نO)ۊ _Ow'*٦}L?٪">E:/"+Ic.H|I{s63䴧] ʧ-d @:sDapjisvPJiJ3}FMn??#Io^apጲ3}Fz $PLѼ_, IZmpD':)anCr$t⼛PW̻R9+S$f=Jfh5{lL@[S4 $)2:{*4LvUUJ Y9TbdvFU{$Ci3P}jL$lf(y @4!6l>n?Ub ؟۬ R6dwK= #4RJlÜ7P-w㇫Ixua%b8qfB\k LnɡAWt-Ireii˜\0vXs3ITɜib\Ud2V(ТwC~!&t.-iyJSaV[PEUOE#<HJ|G5;F&4N^89Y7_ưrΣϛi;  7%lȻcg>~nzU,-39I4uњT;KXuvjvS_j?3b4C+s]Kŵ0E8E݉;t|\$Ջu,sraʦ GcV9I!J$3`C9Eh 2(8)!3ܸ%bhRkQqxME"ZV\Sxҫ a:ʰ]ϙoߊ룻)m'rI\͕n7֞jVJ?fVͰ%>Dҝx!3a cyK'Xn\ȆaI/;n|>'<,p]"= T<}_v~oOx$}YP!eAEz̩$MęeA O0“4gK_T'? OҔKI_TH.}YP`s*$gG} _AK@aj:z֒] *Qx{-In)=77VЧ`TA7˷go'W;sG =_:%S0 _r,\LrڠT&]ֹ?U;Su(=$\.˽ϭO,SK)ߦ m@WcQYJ]h1.]_`}R+$}E{O>x6O,gI\c '*O+h~ ȱJR^٪ͣյGfv1Fv,ZRnh;}zqɶ-} F`޹%u>% ǂKt8~,l,? K׏ )UM~zO$ gsO+h~ ȱ2bXl_Ww[@QOoӧ-wyЧ`4oqzy4vO3-IR&Q Tt3=1>z`$)U3%)scЧ`4o1[I|5ߺG$ID1ly܆D$IЧ`TA63%_S0 >4{zqjgh9S]_lT7M//_HАb5LA4hr`@" KnIoA- ,[]Jj ?vwgv93{fKsz97'I& K $,r6ܠ; yc]TB^HgӒXa8I0E{F;沌ntg!oLPyS$ %,x& fr3dř^zƎM%Âq`b|v<il7I[%gg Ej",1Ag ʦJi,x&@Ss'$U2K%izk|$%w87x\|p"tØ X@4}8^WM !{Ba8I!3zcV2y*z @@ {2pNBpd2;ͱfI5/uW3T6eϸQ 7y6uKO< :,')d&,kdnǫY@,31ܢNSi40&-(]NT'{ڸi^*ȂWnªnaZFPo}H,`tȪ0:I3QioT$tØ 770N IAoh ۂ׸oY MX :>;a qD6AXj`^h!1Ne۝;>Md#,=}+ȂWn_Zp VȀqBfm bxGdHxPSw|^ =n`lmC!ArЅ-$n'ʾ*z` bbvxK=˂:U4Tct7aݍD'P$̄C`,Ic5/});U=a&7~: 9 8;gy؋돮k ן 3 *<1 >&IV`pK]^%bWO^{~ǧ[!8Bpsqe55\r叕[vlQG-%M0ƞUQ_kC5|@wV4h) +$rrh*[VsL*alc5NR@ۧ X']\y8g'98BBH <{hל-\{߬}󵓯m +Il;(xx<"N'x+EIngba3z\>7gbE"N7NR0BS9m:IG?pA孺$X>܌e~q(4O@a!MAw"C&uC2O[g[Noq{z7$xH - >q b'=e2 ^B 8{{y>YƩ7ɣT+BDze"?ue+qڇGZ(a8IAt[c5x::~E=pr5{״j`898p`#Fs3d%>RTiҾ 6lnO7C }=t@~S > +F)ƒ iON0Ʒ~wk^]N~}(5-ӏlÕbt7aeu k/?>wo[}s&Oϵ"*?=yXGFcNcvml6MV@Ff/GLqDdx"r +r߇p !r rEgg`jSST{뽪w 8n7d*`Pm1bqoJBN6_NU,BUYffדgzόGr)n,3P!W66](H:nwV;- /ᬻ/#gR'nf)iB$2ϣ2*Cc2&9:СF^R+ɬ&w7| VpJIҩ00)Ưlw^Ŝ !{S2[rkr_?znm.n!i4 2;nd"T#2mn͊F7㶮i]?Oϫ{r~q.w])ݢQS+SgOlMJ=n{М l@qLaTv6&VT>PU_IzxqYn"7ϱ> !_sOs-%m=wvTWQ `j Nu\'[rn^=j;}ߖ^v)v{M3$" @TQA }2:qc0 XqŠ^s~r3Ǣ %ihh K˒ He5I00 ΅AW筙-sN1[^ܧXxbJc06SA䜅696i@xN so{ +@+"0f.ojtF$ n2 Ч`H'`#VP_o//O8:a#i?Y=LKnC*vuL6޹w>[Ȭ΄"* W^\;~7ۼ{GG#9ə>$ kϮ]<6,*}}<@eazDZ03=(疅qR*SR+Sa؝\D[Pi[&\$ Q2=T >Wo:0A"+_٧gmֆe?JR+0yn4\kP$h wHSl=4jE~U'TvAw[xZ$U$p"Tsei@vaY ڢ=8'L56DbBIJfה w)0@# ?RΏ+#W_}yKf 3'CJuiZy;p hEEJePgM#/!2 , ?(RiI In#҂)S]%uh_}n:5 5EK/KvL2dVmVxrkJ۷r ?HV(IПQ>]]M顂QI%}u||L?E³ :t؛wR1T޹:;@ZueR͟Q*G%S`2cǤ;:/1K4H眿#fȚk 9OzMpV(a$`/o.׉sWabE"[eNJ(7?={Y<q8q:_$ICabu)hQ6XD㒇Y"I IucGSG#҂)c_꾿$IS\xGk228_gV_UsFQ֍E{%\ t ֈ qMTy&QHpǏQ*Go39YsLLofk2 F%IM\MϢSҨ8B GeJ cfF,$ۧسI5ai@ص|矙)Lȫ~}llxwK䖯Vgb@m{4rSHn15)+zߧ 4%$ wȑCt?%d}+K{/s|⦎7=T0*IM `ڇ3H0|ww CHp~%d$qf4(LtL5{{iM#҂)959L|:ͻI(_% CX<9+j(VP4$K3zKWĉDew4BGG7z\k!C406`㯿[:AiӒ6pcƭQ[Fuv5 =T0*IM{=8 Y/9Cfa~}۰vw=dBL#cW|8Tqק7= 4L.kd6M?cHZ񿹧`|(s HRïi eӈ -- Q'aJ? ]+yqvծ kd`? o MNe- |/l{~B즚%Xg&8_<ݭXhzDZ00]hQ̢%džd$;oR f"97wE~s:4gE/\t,91s׈ Y7nPM#iz?0(׭ sx.R+yIJ{3L=a?Sw:z4ܔmedWG@hnv<^rC`)s*ي0}\й[A]4UJnՆruu{Yt`a YۥKD(@rs= ,D-`$BTI"$ qL3D .f ?ÙPee ~}@#izu+X|՚ޅi*72=̉Z5Ee匑u JҰR fsK+ }ͦM*yA%ʛj._J#!J(Q\ ٧o OՙOb~֦p z[:ևقB)\>'r{ ղ6u/\@'gT&'wo/LIeKlj#~~*47߃ۺl1q3F]#>& k.3}^#P %f\K"4`s9 OI?8eKJ{S*k B&;O&b#$ac|y3, ōx!?Y>I]` #dX,ptBxqPܦO ʲïK*Ӑrˆ${[ E,[ggsX靑eM0 =1xq~f{wKcDefbWpWzkhCaS$y%ٺpQIBJ" 2eL>4Sր'€rܱ#L?򀂀;yxu H2t/ʲ >f",> K̑tc}zDZ0} =_9eefKN\ď55ZJJlYlQCN Vǣa,) , d>PQqUoJ;l=X}jG^q\pb0EYHM>ee|?uSg^i)'kI٥dmMrМ4sbu@ _VEk{r1grށr[RUU s@HsHZ~Ǐ7 >@[sj$w/9ިe̶:c8xfSB0eJ}EO{: 5:FI粛5 " |r&DL8!ZVRIpɄ (I/{Ggz & 2 Bg{Yzd'Xɒ܇ʖ%t_L+*dŕ`r:w(uKjh:a  @QQ' }ax@S0 GV& JY>x#WvQ-,~WSI%"i;7n$@xv0[1bbEZ9WHZ^y,;FH/KJaA|!i!60Km ^m('Q?v0BAjNpwۂ|z t ~3,X4G[u||?,*ޮm4]{XT׵a{kbZ&6qijZ4jKbLDojw[H EAd~D]3kX=g^<[9sa9{[k=C~@#$~iNʸѬ\իέzӦN,!Te?6;Hl4.]*p͟LU7`e+0mĶ'wVY$^V]Z&'F`!pZ[oqJ)ɬ 0zXi`wIN QAMcpkJ>H2* C D qCkOъ.ȔdUw4t)Wű=OK (^Cs4=z?6aOh`y4 <-iG_Wkl0Q:Ă@p̆gg/KXF O1aQ"2@RɺU;yB_-݃vYP]2syYkくY[E@yaYnW5Z௃@O^-{xh !F-5~ sqie- ܤ5{4in;ic 7.X.PĂ$ JIe4BR%v^u׾f(S8;q[ VfF'W|BCg;&L'W'O ~摈6޺rd й7 HhE.,Y/CF]% ~6n/yXH>?_M&PPi6W]9~s7x||_25 vBuhAS1䖁 ?H˲z0QNZMl7Ё)dm~r@cl{sxh ᫯n :)E.?ȬdfWzAFr^~mwnIGܖ"XʫTUKA HH#$Uh_4VqE3 kX-;t&$9 zhf(jܖ(6Ҝ~kO-nUȚB}mntKT%8@()}_W?9K^}reNA6w B`MٴIuT%aDDI ^HIGa ԟxXZ))ñ@X.3 r &gI&! s@?ٕ/HqKDlYal|0,D(O{х@%sel4/m!LЯ.$Eg"s9 `r`P}[A=aL%I0_Yg:.SL~~7)};7t x?_ Rk1TH]Q >Xgo7YB?> $eTm.9*`Jaۣ]0X|<tq{ ;;_=*w9 4 j:jPռxmyQ=Uʢ|)PT0Y @57\?I|ҪT@LRHCkoc#rHEW\j4Y6_8sOӄ@l<0l" F̎\\uob $]>;EdeUk4)hjISHd@L/|-xў= #>TRU5bTw:Ufl`.1feE,ĔQ\bdB2t$R;V-}aAUh WO)[A4s}z̙ə-3(4xKwKߝ>ǫci3"j%!CS;P8hnbo}m4Y`$RJ^^ ^;`ng).M1 זdݷ='ì(ёJES\L_1ŠlÙ&n/tS£RQ0Rc"C{۬ )}͔JIOI'(ҮQgeQ:|ARWۡ~=xS⤷e(!A3%\Ĥ&a= 4Ȯ35I]))Eѕ㯙nXX[XyHF|Agbŕ@l~@wY'`٬$Q<Ҫi킮(Q zʙ \Tj4] b?d]@rK^jȢ|Hc6RLȊ^!hL zɦ! gIu@p*ff~u#Ӿ$Rj?8l)j-ICn#4PUNcMMGZ=ު1##7/U0 sދ}ouɂ4,rsaٹr 6u 㳬ʢc$7/*ՂE!gr]iQdMjI_a Fz|ٷ@R*;jBv$Jz7$Y"-]==jBy7$j0Á+ׯܼ}zEo ࿘0 ,/^u҇wq ܜn61ɾ17Ka0CP,q6.[,EiJmHJ~]κy_.H( ;b82/t/}~i+ze O*<.()0[0FJ.W^Y[Nr+Y0e%5q.Ю)3ml% 8?LɓddFP*%b"Lnc@R_"L_u-۞>E -il[wn֫  |6g?I\"  $0 82cyCOW &%/6R 4?Ϯ51#2wI m =W^yEӆ x>шj%RͿB{[8AIB5]"%-%c6J.d<)9A Ii@wjcZ[h 0t`k3.X}j G2SQIs9ŻF?Pӌ#l^#O:u|9Ծ0<^xlN,<**+e"&l+A'Y8=CXE8> B9d#yػWi`L<—Xw}t_YQwO.[t}r .P :kp]Uऀϯ:f#JqҰU/+P7x%&~lIzfM 1ck254Eg7$qj,0}pU4 [zI%Qa9n,ЂOnNI@B^WHRHn!8ՙ35Kijmc+2OWG@>aD}P" fswSS9qsn X8~FT-HkD2ܞSXIAlj[=-AV2".4Cɪ+Qíu6vC%r"x8jITTRyծCp5#K豷3㳳)7"ʂ9ȩj?Y>xM>͖.r;NUh>zdeID0p#,HJJTN- g*VmzWAZ,eʼT@R6OI˞rT'7 7o{XZei J]@@`_ nzY²?UA7\PZ: /N Q`WBf_- tMm:`38PcLuNj5y-ID]r34?e_SڸP5#Ayk_)/-RTj:\oS؛4CR&X19(~/QևNs8Q6j$8%WU軹?O/')@Eo5pȇ$Bγ5  Y @*ntjw$UQkP T$C:pJm$.i`uC"7OĕqICTj4#H•TL'%Y˫M` A^&(yQ&b{_?b$ %W"Y Cۍr"a~QfbfZrJ o$S9-bהY=m/y%HEi Wz*KJI'hI; I 3BЯ ?Z5ۇmW2ܵ wEU5O!*qV"K#bBvzBk}Ӻ<܊B{ :*Mw,DNbtZ=q[1DGaIŸB6@.D ZA2 ($轥ץGUcb!I%%`ŃP~AIb!]o\HoVkK 4RwlRrX}S93ꋘM֤q{;Fۮ*.Lj؄tO>,^NA,P. ]bIC*![FraY:;WL.\\Y*R†| NSr>S0eR*=Aݗ/w``Kh4zЉXͽM 5agv;H5X !Q Po!pul qk2b㬠Xeق}\Qk͖J2hImm=\rQpz8N ~}+W໬ӻ]IbIX]ڙ75 g1,q~o/6)ؚ!&8cГm~ޓ}QҴa~Gb-)$K 'DsJ@Xΐ@OXs`zT R%ZDYP e#=ml-2պ}0`8I<1 5;W`2B\.6L"jF0,<Gaf)(IemmJCCh[)U ?WynŽ ؽfXjan%\X^5UGNWAI0Tazø K1uIay.n:j, / &q?9wlqZW5MDbY WQjg=]dݒ87 p1ΰ&N%Va]-}QLnǹci.DGVDj}}i褣yF',nlz*ȥp$DRil?; !CE.@egyfZJZC^4 77pޤ/ʘmD$])zm. T_J|qa`hE~7@CfA74-4R;b]suB),+?8X[p4vb%Kɋgf>^,D1GI皚ĺN)< ϺslDG܏c`$#űvoCގya~љ05ⶉ?7-]N(0\%2ѐ&=xu֟?&e$>;{&.s޵ JԐ3I\ݖmTIZ3XDnݐ U 2}A|7n8} ==9XxI\l9G^i#$qIhN`-ga#h|d[Nx@T .u¨k, ME.)L;1~ D<2vN4!%v,1 /"5KFݔߞ42Vj&5pz c89 x3^>2=>1g)|Dc6ISZN٭X`:ZJ6, -=+ԓzg} TUU>k}kjVf9((" CjΆV8 oF{%_p7YpqZ8o20xנ6f8fh7 .N"$O]Bd+{l5F(ѥMQPƀϽ+~?dحK\.ijt%p02W~(cwQXjL'$au[,|VY&Jkg$wS7=}rm6āg*fĔgW@'Pl쵟Jh aaΞtp(%E9mhmh07ޝ{HRś7FOC8pGq?D|ZΗ+h<^pxSxK(oyssGOO&ÕPX^O?al׬ 42X`^Y@ɟf,W.^ѐBp.u Tp@ 66lU:ZotUli^w~ 7pTPVxFYGII%G:񵉞Ǻg.X+v$m&.nl{ڌVԀBEIʬ99|'m!'Ow}xF 3iӠQ&sٲmmg~pe%g K` @mǿdt<'I&l\%V[ I.E*4KQ\C+]ͻtx?{$,ĶjKpQ~% 5 =-21 p& {:~%#RWfW,d#ZOF8o0,Cl>}?r$ϩK;@U(ijX F!|0juT,P}[x?$! 6PދG?*K@F-ϱI8:a5%v4AZOU|L(U<3;FFa M`m-׮M]D :q$5t6ĴĝM9I0= r.cR?!IlL1 ܸqw`{;Bnho]@\ulۛdUA^PPܡ Q!9n[o吸k:v@ĭLSދR gnmA{?3 \Ms@>K^惛:镃SL**<ɩ{r/ljLPzL zC~@ͷI0=-jqxT^JCR[Ms0:3>cf[B@Xb5hHЇ02#|!Ȫ31ۇr-${իx$;I(%OFεߦ ;ɖ&| {˃ѹ"@~ۢ<ۗ鑒"&$0V-X{ܸq^g$@0>cVI IjIK[_^nTHp@`0J%S:nd!c} p X 5gXܿ?WOOv(Մ- \MM#8NsW" 挵hlO[dɪG*+{2,ldALB4l[ز\HOS;.!1w~pQό@~}M.\]Uş-$յJ_?ybƞ֖]twRZQ¡0詄_&ě)pqP@ፐfym _nP=!w52jAEε3DsCX +Ejŋ#kpA__V[Ó(COJfbUǘDB$kP% ,YKiDvhd8|oa~Y%K7oN53a%K:"#g̐ 9s{l4ěw,M{6=9#/;㥗jЦO~M.)v+eHHPl,@γWOREmZǗҪ(]WĊ^[<B}J!-^ahB7%vgpaAAgў[:~|O,]j`$u4h;@$,' Y@dYyGy'Sɉg8iOWV$Ѿ{ގwY9<@PHȶqn.,\ٶM$ Hj1}ʃ OPR>cJI:Irv]7/Dk H܅#0f!Vb S )^ӓ(;j7G sr.C=vG;[lL!gn Zt.]'xT7<4p'd`IKWZUu;a`k}rK OE?>DwSKzƅE :63w=,J>B$]m[ھ}~(8-4iݺD΅ 33;\\lI-)΁%Rr%i!Ke$)Eǡ PG$0_?TVmYXIrPo}rNc h‚㧏;K0jɋe|)e#KQ_  h\1!g6bbZW0[)u@ގxD)IRQ.o6X^P _$ZM IIp'Hm`|]; 3&#bs^vzhq{i/pž=%KXДC-X/H@|vE㥖"{EenYGDq'Uq!R^-dXo|e }N-%=+RHJߑ;2VdpE቉>s͚D+xs6mJY,:"6P_: 6԰Ėg=tԋXZƜ=mkM-2H'ILH/p:Ž{ks!1:8N&0,uvQC]!Cju/t޾\Y zu G\\esv |0Ob{ z7:I_L1T}$݇A&3>rl*lY~sA5JaCZ<>,8t)Z {VS))mzzA@I jh[$訃R=q0lO<:EK7Ns@dཟƌvq3\ܲ :lͳ[܂8#dhx{LҏJ D }Dk7nLƛ980#S5 zÛ33;zZlOƵ׋A5##øBWqx1L Iɫ l.9֝n@VoܸI."q8)Q<#;~'MP0&1^o85H`]6u@iE(Iawg^CÐSNyAam-77+)f'&N~ zFd>3}F)+H$$+@ Xuut-sC hkЪ|[JAUeyeãf^vݻ3Ύq(,8X˂JW^GExp: yy$c|b#+lՖ|0@SmޚDWU.Y$і-) f7-XM׷ױCQe]$rR^1'OR)ف~~=RSxťZ_?[L1 tgW/YiW1-B;7ɠ[9_""}Tf$IGmqjY0*SDG3'֌I.LdxZV:aXq x\Ǐ!eE HNn7[w%5jiIoPP#)q "*u,C`l`$I$4-_12)G+aIOYBפT&nYKr?j)-Ŝ[IbR'- ^ cdi8]$ޤËe%Gئ_?~ĉޖӪD SRrm0LD.23<ٗ;5 :195]cZ_oѡM[RЂȠիHң{ߵw>V(?zp&I9GX``Q[TFdzꭝS[;at'3=@ gLsqy@<}SuZYq6.^3fgX5"8ss76v.Zȼy<1C_Y.:ҧ$N z%\-rQLC>KjaYs+, v7WڨM[۝U!`$ZZ(*_#s4//* 7aÛ }t0 ?8/I'H cɝE53fHANUltYe%NP8dA{gr@w$W$1zOmC G+{?#61G%ڒaÜ-,[[TA8Wg HкQo*?'.'Mw -$)w$S%D8:* ?++xuKWV\U 3& 1mj.c ],G$ƒhj_H n5^y 鳧h7j{?r]IX/59=vmf,7Ϳ -@@}byBpm[ډ'T]K UPϥT2R$ɳ *:|P "%)I׊*P=C.jKV@# HrҐ([5kDB…I`ήpݺ)~b?p=9dw37ce9=.$ TY4}ɓ`hҒJvBNɅCT,v!0[+ocy%IzGz5R1{aں,CQv3vt>Ϝ)mo{"Zz'VhYM s-ƤBQ\F\EGO= gXCX,6 B1 m!rdT|xstqwcC ;Sl:kihdjm}JTei^y C*xGGWI4]ߒ)v  1 dE64ӡT>@77zm@POgP1@e,۩.*rH 6X`LHͽIbt SA Dޯ %G:Eǡer/I>ʯ |YccӥKw~8kk9(ͳgzz(QFe:*yqoݽf4UuIǏw$Vş8QB !I]5z}{:cIiuAxp,(e&CH0{ v@@N8E yi?SJ; 2˭16{#9)Nu* 5x͚9sdws90d )O\+РP@?>EfB/:aKQ)zk\!f޽9d9W1 *2%Inl!HӼ@fiz"-t@$ nAvՔ' odHBgp]bݿLֈjmҽͨybϞlځw$`3P"l&\ "(?8ܼ9zת?5BiM-tفHWsgj0$:>22z *oq1Oe=ڝ#Y||}fd$)(~&߿{yÌK"&愧Mr~+ B [իݓ#t˽$Tg,]c(5bB=_%c+rLeTG)ӽJikKrr.}CԐT#3&&tmGܓhݗJȩd@I@o9k<]rgkG}}YvvL hM"sY~W&ݻ3U kP ʓI1aҰ;XCd~uƑ\[h3hSx޵"ytKbw\KDmDͨs&^7ѓ(5q66($O):y<7-Eȕذ!"dM\63/p'(e3IbSP$ZlHW '`<%Ι5 f5u.0mg1U\s뛧79zv[lE jj?{ h*WNcɕxuPϾѣl^[{CW7(LH/@8V n`K<'0zt(Kp@CfM#}fͩժzk?gfVek#D3$%Cҫ]jld$G)`:ᙚFƶoQ̥xY50ebp*@YQgf:;[: .oEh*4Ko`9xK kI *Uؑ4$eT_?@15$7eժU爥eAA-emmcVV5oȨs!W566Q|6H$Ԝ+ã%eDeCXar9m櫎Mn$){/g?u M޹3$p mbdfn\[*}X7 ɹ$R3,nP}ҏY|Ahk>wN. ?YdhuuzH_S3 6^Hvm0.flq ;-6z0 \υ;Zm]Ȩ7Ox%my `%!m3?t+V||.%%Cv lm x_]ʜ9МTbx@$Vl#n| VlysTbJ1G7$qQ >|؄C"~I jy|5I\b`J$bIS` ٿyݻ(̷X)%͟>R PНRWۥ #7lfGB\\\~ݽZ"_2O`ĆVž}W2+#0Cݒ[:@#nϨvޏP1{ KH[y]%LtGѺn5 Dc$^ۻ'._N9 h~? %(-'~[O$'BT z]IW鍒B]Y`d@k)N| ~fJ՛c6͗Nť˒ٳ/ٹ'WuM#F@'~I[[K2P%h ?$Ԏh Y[x>"I3z ~B3cHurU^{oAdQ~WЃ03}5R:ؾ૯:߲%E>0RH  8+-+35ΦzyxW:zRb+N4xAkՓƍ?Hю w<#*Ie]S3~@\ED >Q#^J-sm 1'yx,Z}Ǐnܘ+VıLťT(֡+Xaial_ WH70IN!x Gf<%CO?双gM8J $y%Ic}])g+ P|Qc GrWkg|ϫ*u߀Ŝ#Or?\d U $ 1wlLr+RQqJ CnnoJZ= 1KZ֪XSysgt259aj$=}78^[THjO$c *N$9%8,0Dw?pb[lP--cll2~wig2wn_95&@=SfP"Ʌ|/$3}uG3oz<ҝi6M37$mF49E *.[Dgcc3='aɭiFt!woO7/ǧvTk?8@Xmb=۶͟JI{С<+x4J F8dyś|w[9/>%a&.^Ms˂Zb09ǁA|yho‘!՞~N1ىty6ZG?ڈBw+B \Fa}L>КDŽ:? ]=)P)\]tTzZ>PAB$@qA-%:w% X`EpmHx?[;ޣ>goKt~xҡC "ņ8C;|!.1Iғ7Onݒx0PrӋwhO Od>7 .ۘI糵U0gF 7n{K ^}rs67lװt毭}+zѳٷ*clO]bc8cx!5[$⨊{n-jd̩%ek7b& 4luoOgxYtߥ Rn,/€z0k)`ӢA ƍz-;j6g1o#T)㏷*9S***v5mPnsϿRPeKM/גcۉћ6G4e%I"dd(ߥ Rr E~׀?RڌǁY|mmwAaPUuRn[e34K>[+0^iLk qL|gMǕW}ZL,gT;)wC]$e| mV[2գTzbPꂩ^1xt1Z= Og`A3is)YnxzV89ə~o0;ɺxViuɰS|Q"j\B]wsȦk"kx+JcOPxfv%u:53:Y^U`%8Y͝/4)J} =m0}|vZ&NPT1@`ˆ%A\N969T]ݨ;QX"I޽zVLā-/?qr2,V<fA$IY{| eIU' 4Wm.ʼ7?2LP"XY$6͜o^SNa>>G?MG'lnji0Ƃ@ |7ڪ@gs+jV@8^V} ҲU>8ۗ_qo{,0 > #܀*qY9ܙ@ZesK?7SbkLs M.$[虘ygU3  8ev.2$DoQ00v;3_ʹVA哧8bmOhJIhq<% 1utrZ8qSIBA?#%L(ܱllPp wP>_yKLm{CWoDlXOȩ <Ԥ{4ArC8vϏ8">qto&zOpy橨ML*.kG$2uu#ecvIR ;2 'iQA^qꂩC˼{OWF3l^(zMf))_-{[%aVRvۖVoIHBmCF0 s_p,<~&}0 StpUb~)tgn^'K2:vF TX&Jg-JT/:m+a" 1R$0slq.k@ENPD ڗ|tB X5ӆqL%A-YR>|G4,yIU+u %) @}ۊwNIڬ ͽٹ`x$nAt3.Z}Dc{gb߲%Q! 'ڦ)[(0㞴#H 򊀱 #O ؂ߞ5:SMai,yKW%{BFAerCd;"X蟢E0]YBHRϸ#[!fTl0;C-\X\R#I|-%cyJ/tFHa 4OϊKZƝ`@  R'j!9&)S\`46lZc ]G]ybO쮄|arCgPÝ֬ WSs]"[7ەb⧀ijN̒$iq^a Y$>C(PI. H8Τ`sJDAutB+Gh4OLjH`dΜn,dl:!Nx`fPLɧ ?a@^ɬ,Igla.0Y<:x0[JzzY%EA $ `?"dS,ibh3wI64>D|1%g/3p6^+$Sv]T2Oq9Jb+B~}+&Iԑy}Q<ԱGŋwbdӽׯѣWХal$I|pQRO0`]T5bWwAI爄K8JLn, VfTpfJJ|,f߼k|(YX{)4VO>1 M,XwO:[,ʕaHQd=zf<820ŷ?TGOq3#>9ӇQx0zOC%ث;Om ifH\TB8gI; t 66>nwTtozW.,Y^z0Q 4+&Uȧl [ݦ`.7u'ZѧCmUZ2h%$`Bة9mk]56΅~H…{u}.7#IZF-0k1n|nunIyРm#w!I--$-Zq0$08Igd*$ .d(ԗcaP.m9pc[~~yl#X}hO yE?j)o?t3pQ\[݂ 뫙*rO6yp;7 M&,H )uomyF7qmUU](,ʪFMnE;G$l;H/ I*ޣ,fd\,X$8Ԏ ItBWgKϞ])dO+u^Ej`~[T[q]lKC8q,;I l ESsGD_?KHEp@Y'k@;2+S%&IRUH猁C\҃[$1]9T ;޾4ccpww&Tȱ*Wz ^-!IPr*% k7׭ؾ=NnFn!Ih3N7EACMV0Rm`jX(wr""p |0CIiݻO!E,Y P]D!} #s珮bYAm-ǣ a.&ҭ8=?ر4ǻ&4FOp%g`8e?mf{8(#!F/LLr=v˿^ݫ942Kb×/`HS:I?a@36+ fs 0NPD)R͠?J(U)l;QUwO2xإ+$ܹ""|Yɔ‰ L aһp;gt6L2\h v7>ǜ9G W17gЧNe9Kx{98|4SIRId3 dƅ~j'Nv6(Q#;23$Xyѿݛ<#)H=|s~c'+>IeX1Y%{m"Y%EIҩgWH9Kf'0VHj)9coQGp 3fxaMۨzqUV69Rg[mH2$C+'ijz2̟$8b@9ai #a8^)F2o^aӦ WmݽwG7j1KDUjH޽Izz_N$C+dg q Z5OAd)~Y5"A̮Ɵl$kdۊDE!|%BpǧJ]9Q~~Wv0I' 4sSxd*!O0hСܸ1X|Ot5^ ~J/H'WAT/mYů$ȃ)I__3 pQ鯶mX>}C9+{*<hߐ!yy7a@Z&[/Q4$ %bav#{;j`=ԥ"&O!1?:r^P 26o%y=$>kYS2$ .ƝCUt硰Q+/ޝ6ߘֶVF p;vO'ª6DijzPBBc f'MR}sdCh}T:{835 ͌NWP.I?#ݻy.7|JRR!fHo&o٭Л=Kr&߭)PEy@s+J}#7rso%ذ!fOx9sZw$ jgQxfEa9' G @xzV%1qel le@a_ %AYk􂤀$I?pU0 >qׁ4J+Q'e` >8۳ںpٲ`3|BC%TeWQejfswpNuTTk|UFmtm=@dHo3@%$Eb'+ᦀ'u3Ο$IύSSstl aҔSx cĈܼ32j/P[Owc)UK=lY&An.TCʽ9`RRԞ6.rb蟹+wCmT[IW܄R!==Ҝd9ü|9s Lpv.4L$?#ѝɅ~j={S\|)EZ y"&>*G5'GֿS]pދKօS]Lݺuan`}Ƒ͔d*J A`Ԭ55ֱ\tPp}&Eix7Ϡ| IB 5II"!CϝG7<@c3+b/,\DHӦy#TE$I$VШFpt5V`8.,hF_|dJQ\|!-׹Sr}%&9QBҕ?[JUr\3'OqC$`aqNOaڤ 1QԳE\\>>R<(0/lLnS1zG#ɋ rpF55xv~koF뽗'Wo̾˲sJIDm&DVeefEelqɨc jmY_G}aZ0MP]QwŻSqiib;ں<؞_`Og]ils44WUu0i@6$/iwߨS](@yŻL"} jl$so@ĉ[c̙LƠ<OC|ٲ`++oGvv&\`z%`[Xb9z(C3纃)6}D 1nd晿 VJqd?|`rh8XE? :wX!I^y||zBE):Q1F^F|X\d8Q]pCǥKu޿XҲ`@O3ttB9;7H>}ZܷJ1#*-,$GK@ ss=ªF:ْiuG !I[_/A==v/7۳s؆Xxs#g_Ҿ3g>ЧE?׌#Oƚjh۱fΊCԣҋt1nS$:t`(|ECc 55=.\H~:w?Q`9kDpG7c"E˽ I,pꜟTVv31Ƀؼ%*, u1ÐpdV"L6GRzFp蔒O>!YMD$d#F`U!\o6PܷSH264S\|{(g@*-6sv)I ɘ#톏SDHR!ц*]|&Z(eP`@]{D 6o~C>}57ZR@}&b x9hnٿY0Qnr%W:Sz[)7FV8(oٲCغ,%(ܘ y&]R#jl٢d@GxRd=څ^|ws>@75a.Q̻7v禟/ Z ICo*+)48SU gg\>o`?wsy.X5VqhA(%ݢ/r:q"ԩ'OR]#ŋYQ[D 257ڏtMt?)8ɩdX).'Boh{ ¶8ROytkX}S%{ t9 H.bAAAcƸȺH-}[D|}aTWߟ>{8'`|3jf;1YGt]>ȷ[:&FL=P).J)3%xz?}~SW_ ʊDa`=e|k;ܦmP$ ~omξC[ؑ{,̞'@MO/iL1~)Ȉƒ97 d,JUK.(К Đ u3e73J0*QUx6l?~&I„rT/ ?G_2fyCN>b4;(IDD]9PVvL!}éS_m-*$B#qstF1[GTF9P ;Rs/-ЉɄ>}xF`x 6$"V#}TOu_Dq1``ߵ+$0Af->]ș3}UU]77`]Ϙ٨3=y܀弸iYP`WFBiiqny_zHVdɵj$';ǣ g^/6R9m.)r_Y׳ZZ[gP |cNFxg832F@=f݉3$%(GxV. ̺\uP!"w(V1GEFFb[o-]~lZΈ\ SIҋ8V+Z.kӦyd9978F2夙t.6T J!G0a" gp!\j?F6ڧAt(۰$tNhV?xq[JA7l4 )gd9I ^%()Ȋظ#Xw$GaPnҗf=%Fc676h@uEEc8/_$ c_R $Ipu|u 9^&9I淊Jѣ+Wx+^}aIyQ0fKv6ܿ`lJ {l66>ڹ3A~cz:kS2vNov52? 7QT|0n]*sZ*"]CrBVG5j(OҊaxSLKwNhqцSm$[0$PatHLKJqhkR}-{[K'۶m!jTt$dIF؏q2CPUS]Ct SId[ Ib)Ⱦ4EiG:##gI)z?*X1ت͋Hp` {w3-?&t / _~i$:w.T6H'9sCBjʛboŴ QEn7w\?)nIST^ lbQyT`A [X@0 (FJh߇VOڻ7iHGF|QA'N`Q"Дݡ]c..zK_'Ƈ$l :~aI -`݈=?MX$䔂8{6K]ͭ|0Gc3|!0esg<5e$3xBO).㌱Z/݆im|X&0\@_x:c.lկm|"n h׭ihbqCCEp6r":uO|nI͍l SŸ ] xU]UeQ@XVB@A%ܨ(""` ,@#A.0 $d&rAI wI8S奯d~C}==_W+V_YZ,\_]]/9f~v?o71g`!5ߥ~cRY)@/a?췖 o=:R\WZ_J9--WUT4^TCEii=|P-Et8 Dt ɂaxiI5 k ]vVA?>|FZnC$A]d45)c(;8.:gaKZBe#жI,Qݵ:f덎9|! !u]7Zn gཱྀ#GNv|(3TGWe0t_/- oIv*'Z[g턞\.K"0PϸRB4Ώ?siuTR>`nȊ{ Wx傤JK3bvٕ ׮nb"Evdʛ͘uh߸㣽,z}/Ɉi9xƍQ}]j)7^z!S&H;I\'3{41FPŋ#ڿ{L-`Эtcߘj\iR0///,a,]9e8R$o;g#a l8w~8l'vwPvDb*]z嫗k ]E5[` ˟1Gd'_OQ?[B@TYùK瞲|&"No:- Ο+%**̎ua 2k~avOL& M[>M>`|uB\q?9f|@R}2눈`??S6R.1Hr@Y{jynio|ZP%Źs1Pł8`eew4H :{&0*+ 3&PiA>a b#}/niysjSlf:~KY]rY@J֭1 ru3qJ̀Y g t  AŨL0բZ`q^OHA&ugg0ULT޸y]N2'NV7O> )m@Iu.4* 'pS'mVk'B{6f&4~vӄz4]FȪ\%1&s[ΧF8!HjkbCUJ拚VY`L֙<#Ͽ}_#O{w-mڳH G*Wv Z[J${A{Id-gΥG?kIĕ8E[;4,3c)Cfۍ6߭=_| s*s2 Z!* p@/zxXPW74P,')9.W|C-#i9$/ -"] f9 .,,')I=-}v[k*2vlhTqM;zS,h''~2wvQa8myyPXXwZS*럛i$@g-p䖨TEKt~-wVLVLZ1._tӜ9dS\_|X@6ݰ ShdRw7=mRZ靦yw>S%gη[> +WK;*ZK )Bա#CIsC+L=" )4T-R E_ qLK =0ja~qE<_P;*Fh 7o,)yEg23X_qx+ zE,1[oެ;I9LNbE ֕US3iM97Bvj;])2t B"2M'91C\ "+n`/dmjg+R",iV5֚LNi9)s鷔ã=Ķ8jmkoX϶q>=`h`P, ^ka5!!%x=<{Vwrl?jGv:yЧ: ;'훹owa?C;&t!! ?('J$ 1ع.Άs2grG?Mg:SԈ֣Fr\W#{{_ޯ| ގQS:xzHl_}5}7žo#@awkznxeWW`օBCqBBJHu!!:n + 6995..{5k|&Loo쳖7fPssWPq ':FNv5=|?FSǽ#;~2'ë^[b$pR~R!Q磄 ٛ 5a!yJI<2o"I`Kn~wی#1;Cv\jPQNox)$c\F_io^;7 3Q&D:Ei %i4HR'<'FǚBMsCTwZK֣t ~4]^_mmfb$п<ކcgtd56B$}p9 IJ*2Mzpʕ+WaH21V5UϓIb 6 tV8/)!53߼yre "akFْizw^ڗ+0RTV6 ea gLZZ"ҹ&!OzV%Ơ-xOH jUBs 7d`f͡=T:ā/p}Cғ=\=2 ]:Ygh%y1pKӄ"jR=-!{^,V&2Эӊ${2@P.#M}U)_:EDw{Ce00S;zĉ.H.]9GD'eU}vH}=K Vlft$xh莺-Fk QBTR_bu[Ņ[ ZIK %!OwPug4!6nh]]{ޜbm<42p|7&ݖQ ?b$*C+ HB)-:RzH*up.epE3`rتd(sML,+~H1/'g|rSgq{agх% ]п|p< n6wF*sC`kZ: -x̋`5yG!ޝ'oh1{.,ɩʙ6?_\2IEuٰabӠlK|N=W§B>Ie1#1#ܠxE*.H2w[gx_Mq뎊1:BEc }ڱcƑ0"R[{{>:U0r#FW޲%<~Vyytq" Iee(rbSӳ9[<д^<ُ- 4;Bfss!e-(@F//4X N#t"hr[m:ĒIUC& >][ʟ潷fKhv`ex{5/[,Ȋ1p>";eeTU56}95Py#G;s4 wjYOɩT3!=ν²M'7+di.yF7ZY9T*Ǎs?^022*~ݦvkײirO?޵?!-Y($Muif'VZ8T%A.t1Bi}ζP{=2&Bċ_:9ry&k{Y v⼼F_24;[g"5TODY`*HWi6IH 59:M.MLwx#@s22"2&Cƅщ g6Hi,R1%y׼#Og[S{k;zjd'Ǎs4GcH.wn(i|"tŷ uﹽs<{ȵ=H@7mjfE&.fMyvImld0{*͙!S˨)R2Y^tt111.Ywx?^Ɔ֢ZY '.GrBP*+,;$l t̥V֦Gwd!bt6$gҥ~=722Og:k7IQn&IR!t.Abs97=R_T 3g狳H!|,ZkJKJL Eзz/`qwK~q onZagox_P?7QDvIdFڏ "o :떘-k#8V r̽ͱP7)u܄Mf8lϞG;ޗO<31;O#b5T\V6 Y iSv0dv}#aW'H\<C`KPuUx}+-u3ȰeTd 4-5 ֭1ͲYfuUh.s V tr&Fڍ-K&!h6h<^r>В}N=+9z3yW͓wOxAp||f)d/daCEv) $r*/פp^&˛9(z@&)R <RpU"B₤nK0 )0UY+:5x׻@-"l ҋo݆D9CQQj-sy0P;a!dX㸬K4ѢPD槓Tv^: C}V}:%nNQwRX }dՔsk0dEB}t~ ځ\_!ςO bjkdo -X2tƨ'~vvmYCĨAp~އK&uf 4 %V̛'"#ƎupoI"2US{ z8\.ql zfĘm7Ӈ pz,O=lw(D[RSҸӦ lisSXXd0> 0A+[ԙpA{ PZZr]#n)+p.)>kC -"J֭;1{_[ q%js˾JQVRKZ $ۈScf*{oIR4:_sbl@/I/6@W֦$_`L/Ho .i$.2Qb7y&/u R7m t0|<$a I(f|hI:YUz菇yσ= ?.E֮Ɛf*K]}=9[BC b155[U6BȗI)0 }bD3p2N3@7Wd M7l#%gީHÖDX!*RlX`Cn]19_A,v#='Qf4)AZ[{ tIM}Asf l&XVY'Y{L#е Q :[Twp3Eg=JtƑK=vAp!zey 2mBgmPa$Q W'G^&Kz(VptJvU-ԩ\࿸J`URW IZ{H-Fn th+BWy}k\rpВ1 JKl/4x4Q{E %(:y0(lC( z.NZ4ťBN 3D%g6.G %"+8k!:ps=2=8Qs@dX<zi#)mn{UW?@?dIĩԚL~P:IQPQ;ϫR\=$g&e ?%O%Y%.fH w-btdTG*+rU'42`JV%V`e1r[|g}]^x)իm8E$ IRz6c qEfUoifx!a΄ܴ 0eC@ #7Ea/Hzqæ_RBWNzv|7-aqMb5 ԙp8pэ9--`n[軐um- 9rx75>ɾ~b9*8m89 9_A"XCdb ${^ROhsHԩNW^.ZFRBX-X0MH. W6T`zˍeM:I'OXu$n447UT4"Kɴ1$IӨvM㽷N*M`ڣV "Iz#sfH(k3j@x[[oH_k#HiD^y;^:80:lAҏQ?~9nә=A"cL88uRǞ\zڼ?$臞UL?`tU;)[pr=3 ,%+E1^ut#{Ԑ@mԣ!$'dxp: ؊l;ȐA&`:AK8b1;^z-#nlĿ1rC0+n$Q+_ʩd쇣'ϐi߹-Nmi~$wT9"x(Pz`θjHd=4z%[cv!@oB"TևVUYZҲŠb&@Lv$R]2%rI~'J+|WPeu΢IQGG9? - bbw˟$9<(:0l`LOUS.v.sI8Ht;ƍEEŋgR4I )m(Vy*%뮉A?$2S|I:BKQUӻn|]}jQT1O|]g3a8왞3Fn;0jM1I#Xd[SDRB<om|G-t"Ąv'îyMIrot}foywn/ahHt_/ɯ7o̸X$xiMշS60'eHǑNNpsf Ǚ19V*yB Y?Iz(OUNԣ!*z3Z J*"<];ML.03R[.Hb\wƔ,\4UbD6 P[~.\U?<|څ / u5I)uAe|6bxt@™$^bB,IڳǦ~ 7=]Gs@Z/#\78L:=I8Iz$1^wx1 Wr Ib2!aH61Sp?>nn~|Ê R.In:o2eM }NمϾJJ-L I $i(`.`T$P@PIA@Z2T`8ܳf#ɱ6ZCDNcQWh[USfMօIAV%CbIҷ|p&`H#ϒ~#<:{) f}H-^|4I YT9>&4ʳsƩs@x>-4\o {^с1!/y8K 4 .Vi#L]@l؂A_qbԱQ*#[uIAMYNU "jb!D=hf4{l).2o||ᡬCKvaR[FbB,IqHh9{"_mG #:]M _}~5A$I9Ibݽ Yv{v+;1A]sIj$$Y:lQ}Ӡ G07 VTp}ZYut] H-<\q*U6j $RvWkkr }Z$ Γ: q+tr~ { EЊ'Oy)8<ڜe !۾x<_UWp!zc-,2 0l(`]IP9,i@\. i-%d<-0 YKkA%H6,s'ߓUm!H HSw.:*t7ai3 ,gh!aسނZ&AwBu%zG;t_XډcV -{+s?Qbyy,z0ѠFWf&9m}S؂AW`̆ISuve 55+Г<\XS >΃[b|9".0L+0z$ snwK M9Cg3t? W91!B>$= 䣪~f ¨[ɹ]c:iʮ͞:iS#|T>ί:uDMl=~;OX</.z\pԱQqWLu$I3%  - N Q.($$ .oeT`80强<ƣGV]\G $M]WL!VxDW~/| z7us|L:ӞaOniU$1^q?19殏\2e{~Aq3Js*,Z xmg"h^;8jfqxe8*N8(?l9:صMr#PlP_lPE$y^'ތc %_O~A_EgZȾ{622`fdH2 Ijzުڊ}yr%[ak+n(~?EeF:=phL/HG]#qǞ=/N7.nGbߘ]{Mu9]wAW}!HN$)(/r됛Jkobdd9^$6UN9;56X4W]"ᾕոqĜD(duIb3@ódmʷ+CVr/pD]/؛)GW؄ř8$N4РNcc8?꽫 J \e]::AsZ^E*(`(h1P)PyI|t$[y%7I&헧c!H_Iҝt{e@4$%ҙH ~ҵKt)^ӻ{T?edIԻ&Ah.|jX(;~U~tk䑑pq2*1AĻl>et)'Ls&]u'TPlP秓od9m^/\W6i׊>+Y'T7 Q$"ybYxP̌$o8/r"܋P AZhHR}.jV\KZښ5hĿDc:|WL=MO6;U](e9],-=܋'O5H^`"5FEVEyD0n޸9f)fa1#c/9,Y~qW0#2Ěď?f6- zUbHȫNU|<<&Oh?0Fj9>s *G,t Q#..s}l|P AZhVYY[YYmМI2Y)∉Ig!~~yhL"w$tIQw>M8IRUSUXS z cnCh(`(hK6t^oͼڄ[ '?uT[Yb(0O.͎_F}ju 359z0jkqVf?+\~T~fm̓mliv"7Di A\~IWe''$@hhV/3:jn>r= TkeJ.\-[%Foo?T{6IYӉ;vsU- X V5y#ORv[[[s&@6@+ܶ\0KPmgD E: t- T3K4 ]?`Ǹɹ2|4I z[\]hGsn\P_C~pbu!f7/:e?yܘck] \"I^87btA(1=W=){>'Orxy6IYSh葡)7R|4rWUW]{Kh?oP AZt&I(6_iI&ihuz=wm$ w4fd$ߝ@7a?1^; ˛={V{n&AVP_0qzEpu1 6v c:fT'O0F%eeք| @0MDd6.]ͥox9wCZs*G<Cj(K%ӊ(R I _3a疘-X"Q'VKkӶ~yWsvC.֙WGE:q`hK|%p%:f?+3&cLTCtVt{!Hn'I4Gy8@fm2} CI ח64(`[It }^X]qP@uK8<.*eCC+BK|ĝktaWzdIkïELϙr;w-X3a璀vu54dw4ˆ 7'9,yqZ݇wuIKuOA1i$#^uJ"ͮ}#8` D@ԛ"&>#X>0S[`8Ko|x۹YsfBw޷ϫZngy$_=dܓȐQfbm [W-}َ2 z1kAH8 N7tHP o8IC'{xU.Lnv=GQ5|;\OԞAuFts+w1J1XyB愇=$<#Z!7Egؾ  ƌ N[g 68\_=bن.(#v]OTZ7u?sYBÔSl1K[>pG+?_OX NCNu>tp-k):P}ߒb9~XB3 6:#a7/&F꾤Ë~$ 0bNޚwT *Xg=' p4)Vj&GG]Ohff+ GlH)Mo wpw,5X$ Jخ&Fⴲ4.˻`Q$$5:*I]c Y'#ԱNygD[,ut4 g$Dt%;.h!. {]ϭȴNRQS1dag ɓcbk+?鹴q'|w_?)ވ{ L)NƳ(9]`G]OxړO}][)$چZzZ*hoye+6,ܷ?93>c8]!n"tHP o8IfaTh_bww`$̀`eCySI[&}CxI3XH؃#TM.Xt y`vdp2}C1Cm綅PgW|0SQht++6dfAz,YI3rY7pR.9\oj`nzt!_bYb$'#ԱNEWfdVj - 6Es3zhx1qDI+BuFtB:BM<n/ :tgV*鹯[~p9ʴid/RJR*ZPH8cOE73@)2^Qht++ll/\uAOtQ, a)X2wWS^y͏,~q9ed@ m"tHP o8INCPzEc3N\+ܒ\9N|x]>7 , {]"VP0= w 2~m|xk.WZ^QĞ ~*hzhPsWO\xJX3(؆#|edv΀~y$g۽qWa gv=`\8ra9jIwX%'Qo ܊^ri,(T^65…Ğ: {]"Vب!<09;;ay1Ϲ&pgo;+AvcZ豤G-b:EuOV,+2 _Yn+yN)% ,1ɢ&m$Mc0 "C׉]̩D]:VxIbØ6DAqAӶO y {]"VPEaPjpQ]9^H[\BapKゎ;vT{ÕkYML֔[(2 _YxKѓNww $-9Iʔo Y9*A>t^rؓw9+n{ r{#'ӳwp"Wg$AwXaF`&XX~ IbxL69sDV}xTT424qh_GK&4Ч],oB {mtX@fn ߳_?k#zF;YsCNuD\ㆲ +g0fM͚:i XxR>mx,B1j&VqJJx뢮~2ryy譤Tt~멯lg x :ӱ"`N_~£$X_rFجM ,mn:>{Mx5koW+1IwX%'mҺӈ*¸9 {]:VH\7\E 16 tbعbEZG@P\iOn(؃#|hv|22b=uy魄RRQv՜VfɦF{lXJ̠D]:VxIb=(y[c>1{n3u yj4cq#KK Pᾈ9TVj3A_?‡V`(kVhl$4M#Fz-e Nowd%f}"?B+$G7K&ܒ2tVGuFtp_W8?[?M!|Ĉ.i- VObiSQG K7] sIx%Q0J;(HEɡXƉAu :\gᗯ\nrFB"I"fGꌄ=?B+ 0WX,Y#~͔QQSA=Y a-Gbͩ(؃#|kpúr$Eł08 I(B"n˃CwX%'3;2P8֝=)pK;zXQHJi&fGꌄ=?B+hqM7xzd-XSɦ]?0aaGNΨ贠S@laKLj%9;7y˭z3} {]m`&u[ZT#Ғ(' $j3eMJfݲOd''۶w|@bE GcEtr7m5& krҚܥh?j< St BIbطݿb!qq쇸e YGVxjaU)nn(޺ՠ ʉ)NIZd;ݗlS=՜V¯o+bE 톀#Ա]lTE4 c@ m&~BL MM0Q(h6P(􃠈-.+Եt[Y HZPatw9ennw?޻gΙp{̟rC$גbߣ 6MBUV$]ĵ̭<ٶqݲeIC>o󁞀M6 iiiI~n?D%tt+xw:Mo?w9?2 ~ rYƞ=7dCz^kY{hWwJ+2}{uY}?OT:p`PG âW] )!ёž*q/yIcBJ[ЉetהeU9hBBDxc0lh|[nr%(vt*5IuO]Ib ťRA&a>;/A3 ~(0 (gsG :p`PG ¡Pms򃣱h L >=ia[8Hzz!/wwr+< $I 9hBB18V;!uvU+$v {6[(xgWg5W;F7X% ϓVp+reM >dDd7wNF $xqk"$ն 9hBB^Elt| {v䂄 MŮ&qOvUőy! ysY]hntg.n? C21˕O4*ÏOf\Xyʙm ߪ{;InW 9hBB1<;Ca[ FLi"ڎv*<|@k_cu.'uoom1}η w=_w4{p^n` "${P$<׵=hH< XfKI47j c٭yMycnRƈ;!'n? 9 <;tvfѼ v/`@F4 h{:,_ja$/%|)V:*bAR](dnJ//m.%ܜrBWDd!fQۣp}z$; v4P!'<i<2N;v`v TMVyYGɡWW^]+NtKL 9hBZȝ[޺}E0hJ7D&"H%Wז 4Pa*[-79̎g. _0Щ=UV#:*RWmcWayƖx*0%#@vu[f'n?K_&Ƈ?&Z^n? S \B%;䝵sd' ].buT8$EClP?o9Y{f| g˞ml} v4PJ rq0+TT\R*Hr #:* \/lqc*#ӗU/%n|{~wPr`@E*%@Vk`v TJ6G:ϛ*Hܒҳ]~@IqKPOt(%spo0fޓ('n?TJ kk`v TJ WnY5o 6ί?u;zs]~@I$mG/%U9ܫ7nߘQQXjOmy18{l%694P84P!ؙF`[ҳ]p,Hen `U͍Νr4-4P84P!#٦Yo'Q:* `&]|Uے-'n?n?L ZR ± /)3.i}xBHKp+K=:* SU64kyR4P84PWB(9)]~@NI,0fϾ*FOȁ~@i ~@q%p%JXYIQJim6IʗFU4P84PaGBE.xk[ B a!MW/hI='n?n? ;F}\v7y ]~@IQOR#Q4P84PaG2lgJI)?]~@=oY/? ,ܹ,m%r*n, jE!Cl;L\=yd9 }tzf0*IO./c+I` AOD+S]u蓰݊Z~Gc$=yۯYm+$@OP'XlÒq7?Gޒ} >JkHgn'XBp,6TH.|C=?ǂQIыW;d>p=?!B`vM޾$#@O`Tc:wז$*L Uz~Bņ } >J}_n n_<]3 7,T!8 +|~zOӂ>47\08O%v#]dө ɋӺ|&z~B*=?!bs[J5&=?ǂJҰvg*b[|`Ln7P?>ےUoiy3 7,T!8 +G84-#@O𱘖Q5l7*'v&no4^Qn\kI_p&3 7,T!8 'Wݫ}>r>'XLKRp5J[kQ|Z0&WGNbc^K` %)nO&i+bP| |&z~B*=?!A#yӛ|ZGc1Wz1-*Ln$N[]nNmp  7l#@OHJR0{|8áQ5gLܞ0で3 7,T!8  |,$EôjZTʥBқ `ʳ%fKoL Uz~BGy@<ҭY>.&ycN~m-IaAOP'X+h~xm_dwww7[L}I` AO@Wcn/..>ʳ3 7,T!8  |,2+I\__?;c[9L Uz~B -I\&z~B*=?!]As$.p=?!B`9sI#G}DK*  t>JҰS1Gh!1;8$` AO@Wc$Z|ϖBI5NfK1m,ZVAOP'X+h~읭nL}E@@QiX?bd,0b`x6?&#JZ<浽h4|n|QA%X@:K<.!eC+p$>4wn)z~P,+ canHþYAP|`Hz~P,+ cwH:IqBϒ//&aHz~P,+ coH! z/~Nv*x`P/;$~MKȇ+Lw/Y ]0\c /QC: dR"T(,`LzHjXcw[q6̤__o>m_&`~jYAr7~n[LsV5}SrE`z9tg.8:`z e *V{uDaL&Zc"7CR,FH y(]N4|iQÝZV/?tu_N+㰨7X ($=j0ǦU'yG&MNl0kY\o<Hdb 7XRqKu3@Cs_L^Pbв1. Oa 3%e ?56HgILOۺ,Y٥M ~XJ>tgV1 `Xw˨pz@ 3~ix7`>DVUV]3x!m,4 l=I^2.h&&=rAJ)(wO f;)(cN=)HwE!.|W/FA_xõ:fyxi9Yc.gYe4`$I?lđ ٜ7xlz+bo7>DFIC/=20Cʆ}v7ԨT2'.&@4}rS4)PJXbIA siTy&s %飢8ukRh IȦ |J=F_zn'J&(dv_P"Dmsb8Ԇ_OER-)y 2pU ȽDEGU%PЉ v1> E IpIAyt6dLҸMf>837_xtG0_Y"o:6@*:Bs)~uL GE' 6m\„.Ֆbs1_J|yq' 1:141caMǒhsQwP!tF]#Qj^P| 3cJpB&oU'PAqu~/pت. K{tJx5oʲǛb#MUU|~^P ۟ xAE}FP(ag#FP&-Qݭ 6kҋ$Q2,?&,^4^Ɨ^$0kh|ih/hht/z* IENDB`dygraphs-2.2.1/auto_tests/.jshintrc010064400000000000000000000005611437353256000143150ustar00{ "extends": "../.jshintrc", "asi": true, "mocha": true, "strict": false, "laxbreak": true, "globals": { "assert": true, "Dygraph": true, "DygraphOps": true, "Util": true, "CanvasAssertions": true, "PixelSampler": true, "smoothPlotter": true, "$": true, "DygraphCanvasRenderer": true, "DygraphOptions": true } } dygraphs-2.2.1/auto_tests/coverage.html010064400000000000000000000040211437353256000151440ustar00 dygraphs tests
dygraphs-2.2.1/auto_tests/data/data.js010064400000000000000000001564621437353256000146640ustar00// This file is mostly the same as gallery/data.js // but some functions renamed and less functions function temperature_nolabel() { return "" + "2007-01-01,62,39\n" + "2007-01-02,62,44\n" + "2007-01-03,62,42\n" + "2007-01-04,57,45\n" + "2007-01-05,54,44\n" + "2007-01-06,55,36\n" + "2007-01-07,62,45\n" + "2007-01-08,66,48\n" + "2007-01-09,63,39\n" + "2007-01-10,57,37\n" + "2007-01-11,50,37\n" + "2007-01-12,48,35\n" + "2007-01-13,48,30\n" + "2007-01-14,48,28\n" + "2007-01-15,53,28\n" + "2007-01-16,50,30\n" + "2007-01-17,57,37\n" + "2007-01-18,61,33\n" + "2007-01-19,55,35\n" + "2007-01-20,61,35\n" + "2007-01-21,64,43\n" + "2007-01-22,61,36\n" + "2007-01-23,57,35\n" + "2007-01-24,60,35\n" + "2007-01-25,55,39\n" + "2007-01-26,54,44\n" + "2007-01-27,57,48\n" + "2007-01-28,59,45\n" + "2007-01-29,63,45\n" + "2007-01-30,59,41\n" + "2007-01-31,55,48\n" + "2007-02-01,53,46\n" + "2007-02-02,55,44\n" + "2007-02-03,59,37\n" + "2007-02-04,66,39\n" + "2007-02-05,64,43\n" + "2007-02-06,61,46\n" + "2007-02-07,61,51\n" + "2007-02-08,60,51\n" + "2007-02-09,61,55\n" + "2007-02-10,62,55\n" + "2007-02-11,61,46\n" + "2007-02-12,59,43\n" + "2007-02-13,57,46\n" + "2007-02-14,61,39\n" + "2007-02-15,64,44\n" + "2007-02-16,71,46\n" + "2007-02-17,73,51\n" + "2007-02-18,60,46\n" + "2007-02-19,63,44\n" + "2007-02-20,57,45\n" + "2007-02-21,59,48\n" + "2007-02-22,55,44\n" + "2007-02-23,55,42\n" + "2007-02-24,57,39\n" + "2007-02-25,55,48\n" + "2007-02-26,57,44\n" + "2007-02-27,53,39\n" + "2007-02-28,53,37\n" + "2007-03-01,54,37\n" + "2007-03-02,61,39\n" + "2007-03-03,66,43\n" + "2007-03-04,70,48\n" + "2007-03-05,68,53\n" + "2007-03-06,69,46\n" + "2007-03-07,62,51\n" + "2007-03-08,61,46\n" + "2007-03-09,60,45\n" + "2007-03-10,68,46\n" + "2007-03-11,79,48\n" + "2007-03-12,80,52\n" + "2007-03-13,73,53\n" + "2007-03-14,64,48\n" + "2007-03-15,78,46\n" + "2007-03-16,78,50\n" + "2007-03-17,62,51\n" + "2007-03-18,66,46\n" + "2007-03-19,64,48\n" + "2007-03-20,60,48\n" + "2007-03-21,66,46\n" + "2007-03-22,73,43\n" + "2007-03-23,78,48\n" + "2007-03-24,68,48\n" + "2007-03-25,64,53\n" + "2007-03-26,66,48\n" + "2007-03-27,57,46\n" + "2007-03-28,66,42\n" + "2007-03-29,73,42\n" + "2007-03-30,72,46\n" + "2007-03-31,69,46\n" + "2007-04-01,64,46\n" + "2007-04-02,69,46\n" + "2007-04-03,71,46\n" + "2007-04-04,69,50\n" + "2007-04-05,71,52\n" + "2007-04-06,64,52\n" + "2007-04-07,68,51\n" + "2007-04-08,71,51\n" + "2007-04-09,66,50\n" + "2007-04-10,72,46\n" + "2007-04-11,63,50\n" + "2007-04-12,64,46\n" + "2007-04-13,70,44\n" + "2007-04-14,57,51\n" + "2007-04-15,68,46\n" + "2007-04-16,75,46\n" + "2007-04-17,62,48\n" + "2007-04-18,61,45\n" + "2007-04-19,57,42\n" + "2007-04-20,64,46\n" + "2007-04-21,61,43\n" + "2007-04-22,63,48\n" + "2007-04-23,70,44\n" + "2007-04-24,66,46\n" + "2007-04-25,66,48\n" + "2007-04-26,69,48\n" + "2007-04-27,82,50\n" + "2007-04-28,81,55\n" + "2007-04-29,70,53\n" + "2007-04-30,77,51\n" + "2007-05-01,70,48\n" + "2007-05-02,66,52\n" + "2007-05-03,63,48\n" + "2007-05-04,64,51\n" + "2007-05-05,73,46\n" + "2007-05-06,88,54\n" + "2007-05-07,91,57\n" + "2007-05-08,84,60\n" + "2007-05-09,73,55\n" + "2007-05-10,57,52\n" + "2007-05-11,64,51\n" + "2007-05-12,64,50\n" + "2007-05-13,72,46\n" + "2007-05-14,66,50\n" + "2007-05-15,63,51\n" + "2007-05-16,70,48\n" + "2007-05-17,68,50\n" + "2007-05-18,73,50\n" + "2007-05-19,70,52\n" + "2007-05-20,73,51\n" + "2007-05-21,78,54\n" + "2007-05-22,81,51\n" + "2007-05-23,86,55\n" + "2007-05-24,78,55\n" + "2007-05-25,69,54\n" + "2007-05-26,69,55\n" + "2007-05-27,69,54\n" + "2007-05-28,73,52\n" + "2007-05-29,69,53\n" + "2007-05-30,66,55\n" + "2007-05-31,64,54\n" + "2007-06-01,66,54\n" + "2007-06-02,64,54\n" + "2007-06-03,70,55\n" + "2007-06-04,73,59\n" + "2007-06-05,68,55\n" + "2007-06-06,70,53\n" + "2007-06-07,75,51\n" + "2007-06-08,70,50\n" + "2007-06-09,75,53\n" + "2007-06-10,75,55\n" + "2007-06-11,75,53\n" + "2007-06-12,79,52\n" + "2007-06-13,90,59\n" + "2007-06-14,89,60\n" + "2007-06-15,86,59\n" + "2007-06-16,72,55\n" + "2007-06-17,79,53\n" + "2007-06-18,79,57\n" + "2007-06-19,73,55\n" + "2007-06-20,71,55\n" + "2007-06-21,77,55\n" + "2007-06-22,79,54\n" + "2007-06-23,77,54\n" + "2007-06-24,77,53\n" + "2007-06-25,82,53\n" + "2007-06-26,71,54\n" + "2007-06-27,73,55\n" + "2007-06-28,73,57\n" + "2007-06-29,77,60\n" + "2007-06-30,75,54\n" + "2007-07-01,78,54\n" + "2007-07-02,82,57\n" + "2007-07-03,72,57\n" + "2007-07-04,84,59\n" + "2007-07-05,84,61\n" + "2007-07-06,75,60\n" + "2007-07-07,73,55\n" + "2007-07-08,78,55\n" + "2007-07-09,73,57\n" + "2007-07-10,73,59\n" + "2007-07-11,78,62\n" + "2007-07-12,75,59\n" + "2007-07-13,79,60\n" + "2007-07-14,73,60\n" + "2007-07-15,78,62\n" + "2007-07-16,75,59\n" + "2007-07-17,77,60\n" + "2007-07-18,75,63\n" + "2007-07-19,80,59\n" + "2007-07-20,79,59\n" + "2007-07-21,77,61\n" + "2007-07-22,75,63\n" + "2007-07-23,79,64\n" + "2007-07-24,73,61\n" + "2007-07-25,72,57\n" + "2007-07-26,75,60\n" + "2007-07-27,78,60\n" + "2007-07-28,77,57\n" + "2007-07-29,73,57\n" + "2007-07-30,80,59\n" + "2007-07-31,75,59\n" + "2007-08-01,75,59\n" + "2007-08-02,73,60\n" + "2007-08-03,79,60\n" + "2007-08-04,77,59\n" + "2007-08-05,71,57\n" + "2007-08-06,71,59\n" + "2007-08-07,73,57\n" + "2007-08-08,71,55\n" + "2007-08-09,77,60\n" + "2007-08-10,77,57\n" + "2007-08-11,73,57\n" + "2007-08-12,72,55\n" + "2007-08-13,75,55\n" + "2007-08-14,73,55\n" + "2007-08-15,75,57\n" + "2007-08-16,79,60\n" + "2007-08-17,80,55\n" + "2007-08-18,78,57\n" + "2007-08-19,77,55\n" + "2007-08-20,80,64\n" + "2007-08-21,82,62\n" + "2007-08-22,82,60\n" + "2007-08-23,82,57\n" + "2007-08-24,78,59\n" + "2007-08-25,73,61\n" + "2007-08-26,73,61\n" + "2007-08-27,78,59\n" + "2007-08-28,86,62\n" + "2007-08-29,88,68\n" + "2007-08-30,90,68\n" + "2007-08-31,80,66\n" + "2007-09-01,87,62\n" + "2007-09-02,89,61\n" + "2007-09-03,78,61\n" + "2007-09-04,78,63\n" + "2007-09-05,89,57\n" + "2007-09-06,82,64\n" + "2007-09-07,75,61\n" + "2007-09-08,73,62\n" + "2007-09-09,71,61\n" + "2007-09-10,73,59\n" + "2007-09-11,71,59\n" + "2007-09-12,72,60\n" + "2007-09-13,77,57\n" + "2007-09-14,75,60\n" + "2007-09-15,73,57\n" + "2007-09-16,72,61\n" + "2007-09-17,72,55\n" + "2007-09-18,73,55\n" + "2007-09-19,66,55\n" + "2007-09-20,71,52\n" + "2007-09-21,77,57\n" + "2007-09-22,64,57\n" + "2007-09-23,68,55\n" + "2007-09-24,78,52\n" + "2007-09-25,84,53\n" + "2007-09-26,87,57\n" + "2007-09-27,75,55\n" + "2007-09-28,66,54\n" + "2007-09-29,73,52\n" + "2007-09-30,75,48\n" + "2007-10-01,71,57\n" + "2007-10-02,81,53\n" + "2007-10-03,73,54\n" + "2007-10-04,69,55\n" + "2007-10-05,64,50\n" + "2007-10-06,73,45\n" + "2007-10-07,77,46\n" + "2007-10-08,79,53\n" + "2007-10-09,72,53\n" + "2007-10-10,69,54\n" + "2007-10-11,70,48\n" + "2007-10-12,64,54\n" + "2007-10-13,70,53\n" + "2007-10-14,66,51\n" + "2007-10-15,68,52\n" + "2007-10-16,66,52\n" + "2007-10-17,66,50\n" + "2007-10-18,73,50\n" + "2007-10-19,72,57\n" + "2007-10-20,66,54\n" + "2007-10-21,73,51\n" + "2007-10-22,81,51\n" + "2007-10-23,84,53\n" + "2007-10-24,79,55\n" + "2007-10-25,66,53\n" + "2007-10-26,68,46\n" + "2007-10-27,66,52\n" + "2007-10-28,75,52\n" + "2007-10-29,63,55\n" + "2007-10-30,63,53\n" + "2007-10-31,63,54\n" + "2007-11-01,66,53\n" + "2007-11-02,77,50\n" + "2007-11-03,80,48\n" + "2007-11-04,77,48\n" + "2007-11-05,66,48\n" + "2007-11-06,62,52\n" + "2007-11-07,61,48\n" + "2007-11-08,59,53\n" + "2007-11-09,63,48\n" + "2007-11-10,66,48\n" + "2007-11-11,63,48\n" + "2007-11-12,68,44\n" + "2007-11-13,72,51\n" + "2007-11-14,75,55\n" + "2007-11-15,69,51\n" + "2007-11-16,63,55\n" + "2007-11-17,66,51\n" + "2007-11-18,64,53\n" + "2007-11-19,66,48\n" + "2007-11-20,63,46\n" + "2007-11-21,64,43\n" + "2007-11-22,64,37\n" + "2007-11-23,70,37\n" + "2007-11-24,60,37\n" + "2007-11-25,60,46\n" + "2007-11-26,63,42\n" + "2007-11-27,63,45\n" + "2007-11-28,64,46\n" + "2007-11-29,62,41\n" + "2007-11-30,55,42\n" + "2007-12-01,57,37\n" + "2007-12-02,61,45\n" + "2007-12-03,66,50\n" + "2007-12-04,61,54\n" + "2007-12-05,60,50\n" + "2007-12-06,57,48\n" + "2007-12-07,55,45\n" + "2007-12-08,53,42\n" + "2007-12-09,57,39\n" + "2007-12-10,57,39\n" + "2007-12-11,57,41\n" + "2007-12-12,55,35\n" + "2007-12-13,59,34\n" + "2007-12-14,55,34\n" + "2007-12-15,55,39\n" + "2007-12-16,55,43\n" + "2007-12-17,57,48\n" + "2007-12-18,57,43\n" + "2007-12-19,59,41\n" + "2007-12-20,55,43\n" + "2007-12-21,53,39\n" + "2007-12-22,53,32\n" + "2007-12-23,55,37\n" + "2007-12-24,57,45\n" + "2007-12-25,57,37\n" + "2007-12-26,53,43\n" + "2007-12-27,48,37\n" + "2007-12-28,48,43\n" + "2007-12-29,57,44\n" + "2007-12-30,52,43\n" + "2007-12-31,57,42\n"; } function temperature_data() { return "Date,High,Low\n" + temperature_nolabel(); } function NoisyData() { return "" + "Date,A,B\n" + "2006-10-01,3.01953818828,0.7212041046,2.18487394958,0.599318549691\n" + "2006-10-02,3.63321799308,0.778297234566,1.69491525424,0.531417655826\n" + "2006-10-03,2.44328097731,0.644967734352,2.51256281407,0.640539070386\n" + "2006-10-04,3.52733686067,0.774700921683,2.68456375839,0.66207105053\n" + "2006-10-05,3.28719723183,0.741636245748,2.35294117647,0.621407707226\n" + "2006-10-06,1.58450704225,0.523967868159,3.78657487091,0.791868460623\n" + "2006-10-07,5.32859680284,0.946589405904,4.0404040404,0.807910739509\n" + "2006-10-08,2.64084507042,0.672799548916,2.37288135593,0.626609885481\n" + "2006-10-09,2.26480836237,0.620990945917,3.5413153457,0.75897176848\n" + "2006-10-10,3.29289428076,0.74289969528,2.02702702703,0.579191340004\n" + "2006-10-11,2.7633851468,0.681234043829,1.1744966443,0.4413034044\n" + "2006-10-12,3.28719723183,0.741636245748,3.37268128162,0.741327769578\n" + "2006-10-13,1.77304964539,0.55569466381,1.85810810811,0.555011329732\n" + "2006-10-14,3.39892665474,0.7664008338,1.67224080268,0.524368852929\n" + "2006-10-15,2.65017667845,0.675144574777,3.35570469799,0.737661045752\n" + "2006-10-16,3.63951473137,0.779620631266,2.34899328859,0.620377617453\n" + "2006-10-17,2.25694444444,0.618859623032,1.68067226891,0.526990133716\n" + "2006-10-18,4.47504302926,0.857766274964,2.51677852349,0.641599927369\n" + "2006-10-19,2.44755244755,0.646081155692,1.68067226891,0.526990133716\n" + "2006-10-20,3.67775831874,0.787656442774,3.066439523,0.711598843969\n" + "2006-10-21,3.94265232975,0.823839169829,3.85906040268,0.788990618726\n" + "2006-10-22,2.59067357513,0.660187558973,3.71621621622,0.777438794254\n" + "2006-10-23,4.33275563258,0.847570482324,3.85906040268,0.788990618726\n" + "2006-10-24,3.10344827586,0.720049610821,2.84280936455,0.679611549697\n" + "2006-10-25,1.40350877193,0.492720767725,2.7027027027,0.666482380968\n" + "2006-10-26,1.95035460993,0.582291234145,2.36486486486,0.624518599275\n" + "2006-10-27,2.30905861456,0.632980642182,2.03045685279,0.580161203819\n" + "2006-10-28,4.09252669039,0.835706590809,2.87648054146,0.68754192469\n" + "2006-10-29,2.66903914591,0.679883997626,2.02360876897,0.578224712918\n" + "2006-10-30,4.74516695958,0.89127787497,4.36241610738,0.836670992529\n" + "2006-10-31,2.78260869565,0.685905251933,3.20945945946,0.724388507178\n" + "2006-11-01,1.5873015873,0.524884521441,1.51260504202,0.500373860545\n" + "2006-11-02,2.78745644599,0.687083077461,2.0202020202,0.57726130639\n" + "2006-11-03,5.11463844797,0.925157232782,2.68907563025,0.663168401088\n" + "2006-11-04,4.9001814882,0.919644816432,3.07692307692,0.713993047527\n" + "2006-11-05,5.13274336283,0.928343545136,3.55329949239,0.761492892041\n" + "2006-11-06,1.92644483363,0.575222935029,2.35294117647,0.621407707226\n" + "2006-11-07,2.46478873239,0.650573541306,1.52027027027,0.502889967904\n" + "2006-11-08,2.13523131673,0.609772022763,2.6981450253,0.665374048085\n" + "2006-11-09,3.88007054674,0.811026422222,2.72572402044,0.672079879106\n" + "2006-11-10,2.63620386643,0.671633132526,3.71621621622,0.777438794254\n" + "2006-11-11,3.69718309859,0.791736755355,3.0303030303,0.703344064467\n" + "2006-11-12,3.83944153578,0.802703592906,4.05405405405,0.81058250986\n" + "2006-11-13,2.47787610619,0.653984033555,2.20338983051,0.604340313133\n" + "2006-11-14,1.77304964539,0.55569466381,2.22222222222,0.60944692682\n" + "2006-11-15,2.30088495575,0.630766388737,0.843170320405,0.375484163785\n" + "2006-11-16,1.57894736842,0.522144132232,2.19594594595,0.602321544724\n" + "2006-11-18,2.45183887916,0.647198426991,1.69491525424,0.531417655826\n" + "2006-11-19,3.52733686067,0.774700921683,1.85185185185,0.55316023504\n" + "2006-11-20,2.97723292469,0.711254751484,2.6981450253,0.665374048085\n" + "2006-11-21,2.29681978799,0.629665059963,2.01680672269,0.576301104352\n" + "2006-11-22,3.01418439716,0.719945245328,2.5466893039,0.649125445325\n" + "2006-11-23,3.78378378378,0.809917534069,2.6936026936,0.664269394219\n" + "2006-11-24,3.18584070796,0.738851643987,2.01005025126,0.57439025002\n" + "2006-11-25,2.83185840708,0.697868332879,3.066439523,0.711598843969\n" + "2006-11-26,3.01953818828,0.7212041046,2.53378378378,0.645878720149\n" + "2006-11-27,2.81195079086,0.693033387099,1.51006711409,0.499540743312\n" + "2006-11-28,2.97723292469,0.711254751484,2.54237288136,0.648039583782\n" + "2006-11-29,1.41093474427,0.495309102312,3.02013422819,0.701020603129"; } function NoisyDataABC() { return "" + "Date,A,B,C\n" + "2006-10-01,3.01953818828,0.7212041046,2.18487394958,0.599318549691,2.02013422819,0.701020603129\n" + "2006-10-02,3.63321799308,0.778297234566,1.69491525424,0.531417655826,1.54237288136,0.648039583782\n" + "2006-10-03,2.44328097731,0.644967734352,2.51256281407,0.640539070386,0.51006711409,0.499540743312\n" + "2006-10-04,3.52733686067,0.774700921683,2.68456375839,0.66207105053,1.53378378378,0.645878720149\n" + "2006-10-05,3.28719723183,0.741636245748,2.35294117647,0.621407707226,2.066439523,0.711598843969\n" + "2006-10-06,1.58450704225,0.523967868159,3.78657487091,0.791868460623,1.01005025126,0.57439025002\n" + "2006-10-07,5.32859680284,0.946589405904,4.0404040404,0.807910739509,1.6936026936,0.664269394219\n" + "2006-10-08,2.64084507042,0.672799548916,2.37288135593,0.626609885481,1.5466893039,0.649125445325\n" + "2006-10-09,2.26480836237,0.620990945917,3.5413153457,0.75897176848,1.01680672269,0.576301104352\n" + "2006-10-10,3.29289428076,0.74289969528,2.02702702703,0.579191340004,1.6981450253,0.665374048085\n" + "2006-10-11,2.7633851468,0.681234043829,1.1744966443,0.4413034044,0.85185185185,0.55316023504\n" + "2006-10-12,3.28719723183,0.741636245748,3.37268128162,0.741327769578,0.69491525424,0.531417655826\n" + "2006-10-13,1.77304964539,0.55569466381,1.85810810811,0.555011329732,1.19594594595,0.602321544724\n" + "2006-10-14,3.39892665474,0.7664008338,1.67224080268,0.524368852929,0.843170320405,0.375484163785\n" + "2006-10-15,2.65017667845,0.675144574777,3.35570469799,0.737661045752,1.22222222222,0.60944692682\n" + "2006-10-16,3.63951473137,0.779620631266,2.34899328859,0.620377617453,1.20338983051,0.604340313133\n" + "2006-10-17,2.25694444444,0.618859623032,1.68067226891,0.526990133716,3.05405405405,0.81058250986\n" + "2006-10-18,4.47504302926,0.857766274964,2.51677852349,0.641599927369,2.0303030303,0.703344064467\n" + "2006-10-19,2.44755244755,0.646081155692,1.68067226891,0.526990133716,2.71621621622,0.777438794254\n" + "2006-10-20,3.67775831874,0.787656442774,3.066439523,0.711598843969,1.72572402044,0.672079879106\n" + "2006-10-21,3.94265232975,0.823839169829,3.85906040268,0.788990618726,1.6981450253,0.665374048085\n" + "2006-10-22,2.59067357513,0.660187558973,3.71621621622,0.777438794254,0.52027027027,0.502889967904\n" + "2006-10-23,4.33275563258,0.847570482324,3.85906040268,0.788990618726,1.35294117647,0.621407707226\n" + "2006-10-24,3.10344827586,0.720049610821,2.84280936455,0.679611549697,2.55329949239,0.761492892041\n" + "2006-10-25,1.40350877193,0.492720767725,2.7027027027,0.666482380968,2.07692307692,0.713993047527\n" + "2006-10-26,1.95035460993,0.582291234145,2.36486486486,0.624518599275,1.68907563025,0.663168401088\n" + "2006-10-27,2.30905861456,0.632980642182,2.03045685279,0.580161203819,1.0202020202,0.57726130639\n" + "2006-10-28,4.09252669039,0.835706590809,2.87648054146,0.68754192469,0.51260504202,0.500373860545\n" + "2006-10-29,2.66903914591,0.679883997626,2.02360876897,0.578224712918,2.20945945946,0.724388507178\n" + "2006-10-30,4.74516695958,0.89127787497,4.36241610738,0.836670992529,3.36241610738,0.836670992529\n" + "2006-10-31,2.78260869565,0.685905251933,3.20945945946,0.724388507178,1.02360876897,0.578224712918\n" + "2006-11-01,1.5873015873,0.524884521441,1.51260504202,0.500373860545,1.87648054146,0.68754192469\n" + "2006-11-02,2.78745644599,0.687083077461,2.0202020202,0.57726130639,1.03045685279,0.580161203819\n" + "2006-11-03,5.11463844797,0.925157232782,2.68907563025,0.663168401088,1.36486486486,0.624518599275\n" + "2006-11-04,4.9001814882,0.919644816432,3.07692307692,0.713993047527,1.7027027027,0.666482380968\n" + "2006-11-05,5.13274336283,0.928343545136,3.55329949239,0.761492892041,1.84280936455,0.679611549697\n" + "2006-11-06,1.92644483363,0.575222935029,2.35294117647,0.621407707226,2.85906040268,0.788990618726\n" + "2006-11-07,2.46478873239,0.650573541306,1.52027027027,0.502889967904,2.71621621622,0.777438794254\n" + "2006-11-08,2.13523131673,0.609772022763,2.6981450253,0.665374048085,2.85906040268,0.788990618726\n" + "2006-11-09,3.88007054674,0.811026422222,2.72572402044,0.672079879106,2.066439523,0.711598843969\n" + "2006-11-10,2.63620386643,0.671633132526,3.71621621622,0.777438794254,0.68067226891,0.526990133716\n" + "2006-11-11,3.69718309859,0.791736755355,3.0303030303,0.703344064467,1.51677852349,0.641599927369\n" + "2006-11-12,3.83944153578,0.802703592906,4.05405405405,0.81058250986,0.68067226891,0.526990133716\n" + "2006-11-13,2.47787610619,0.653984033555,2.20338983051,0.604340313133,1.34899328859,0.620377617453\n" + "2006-11-14,1.77304964539,0.55569466381,2.22222222222,0.60944692682,2.35570469799,0.737661045752\n" + "2006-11-15,2.30088495575,0.630766388737,0.843170320405,0.375484163785,0.67224080268,0.524368852929\n" + "2006-11-16,1.57894736842,0.522144132232,2.19594594595,0.602321544724,0.85810810811,0.555011329732\n" + "2006-11-18,2.45183887916,0.647198426991,1.69491525424,0.531417655826,2.37268128162,0.741327769578\n" + "2006-11-19,3.52733686067,0.774700921683,1.85185185185,0.55316023504,0.1744966443,0.4413034044\n" + "2006-11-20,2.97723292469,0.711254751484,2.6981450253,0.665374048085,1.02702702703,0.579191340004\n" + "2006-11-21,2.29681978799,0.629665059963,2.01680672269,0.576301104352,2.5413153457,0.75897176848\n" + "2006-11-22,3.01418439716,0.719945245328,2.5466893039,0.649125445325,1.37288135593,0.626609885481\n" + "2006-11-23,3.78378378378,0.809917534069,2.6936026936,0.664269394219,3.0404040404,0.807910739509\n" + "2006-11-24,3.18584070796,0.738851643987,2.01005025126,0.57439025002,2.78657487091,0.791868460623\n" + "2006-11-25,2.83185840708,0.697868332879,3.066439523,0.711598843969,1.35294117647,0.621407707226\n" + "2006-11-26,3.01953818828,0.7212041046,2.53378378378,0.645878720149,1.68456375839,0.66207105053\n" + "2006-11-27,2.81195079086,0.693033387099,1.51006711409,0.499540743312,1.51256281407,0.640539070386\n" + "2006-11-28,2.97723292469,0.711254751484,2.54237288136,0.648039583782,0.69491525424,0.531417655826\n" + "2006-11-29,1.41093474427,0.495309102312,3.02013422819,0.701020603129,1.18487394958,0.599318549691\n"; } function data_showzerovalues() { return "" + "2007-01-01,0,39\n" + "2007-01-02,62,0\n" + "2007-01-03,0,42\n" + "2007-01-04,57,0\n" + "2007-01-05,65,44\n" + "2007-01-06,55,44\n" + "2007-01-07,0,45\n" + "2007-01-08,66,0\n" + "2007-01-09,0,39\n"; } function data_temp_high_low() { return "" + "Date,NY,SF\n" + "2007-01-01,46;51;56,43;45;48\n" + "2007-01-02,43;48;52,48;56;63\n" + "2007-01-03,39;46;53,50;54;62\n" + "2007-01-04,44;51;58,45;52;56\n" + "2007-01-05,51;57;62,44;49;58\n" + "2007-01-06,55;64;72,40;50;60\n" + "2007-01-07,46;51;56,45;53;63\n" + "2007-01-08,40;49;57,43;53;64\n" + "2007-01-09,37;41;45,49;56;66\n" + "2007-01-10,31;35;38,45;49;54\n" + "2007-01-11,29;35;41,41;46;54\n" + "2007-01-12,39;45;50,41;44;49\n" + "2007-01-13,46;52;57,38;44;53\n" + "2007-01-14,42;44;46,36;43;51\n" + "2007-01-15,41;46;51,36;46;55\n" + "2007-01-16,25;41;57,37;45;54\n" + "2007-01-17,21;26;31,41;47;56\n" + "2007-01-18,25;32;38,38;48;61\n" + "2007-01-19,33;38;43,\n" + "2007-01-20,23;29;35,\n" + "2007-01-21,21;26;31,55;60;68\n" + "2007-01-22,28;31;34,44;54;66\n" + "2007-01-23,30;34;38,41;51;64\n" + "2007-01-24,34;37;40,42;51;64\n" + "2007-01-25,17;27;37,45;49;56\n" + "2007-01-26,11;18;24,44;48;52\n" + "2007-01-27,22;32;41,47;52;58\n" + "2007-01-28,32;38;43,48;52;60\n" + "2007-01-29,24;28;32,47;55;64\n" + "2007-01-30,23;30;37,51;54;56\n" + "2007-01-31,27;31;34,49;52;56\n" + "2007-02-01,28;33;37,46;50;52\n" + "2007-02-02,34;37;39,47;51;57\n" + "2007-02-03,25;32;38,42;51;62\n" + "2007-02-04,18;25;31,44;55;69\n" + "2007-02-05,10;15;20,48;55;68\n" + "2007-02-06,13;20;26,48;54;62\n" + "2007-02-07,14;21;27,51;56;62\n" + "2007-02-08,17;24;30,49;54;56\n" + "2007-02-09,20;27;33,55;56;58\n" + "2007-02-10,25;30;34,55;57;60\n" + "2007-02-11,21;28;34,51;55;59\n" + "2007-02-12,30;36;41,48;51;59\n" + "2007-02-13,23;29;34,48;52;58\n" + "2007-02-14,19;25;31,44;52;60\n" + "2007-02-15,17;21;25,49;55;65\n" + "2007-02-16,16;23;30,48;59;72\n" + "2007-02-17,22;29;36,51;62;77\n" + "2007-02-18,20;28;35,48;54;61\n" + "2007-02-19,14;22;29,48;53;61\n" + "2007-02-20,29;39;49,49;53;59\n" + "2007-02-21,39;44;49,50;54;61\n" + "2007-02-22,33;40;46,43;48;54\n" + "2007-02-23,23;32;40,43;48;54\n" + "2007-02-24,22;33;43,46;51;60\n" + "2007-02-25,26;33;39,49;52;55\n" + "2007-02-26,31;34;37,44;49;54\n" + "2007-02-27,35;39;43,40;45;52\n" + "2007-02-28,37;42;46,42;47;53\n" + "2007-03-01,33;39;45,44;49;55\n" + "2007-03-02,36;49;61,45;52;60\n" + "2007-03-03,37;48;59,48;58;68\n" + "2007-03-04,35;39;42,53;60;72\n" + "2007-03-05,23;33;42,52;58;67\n" + "2007-03-06,14;19;24,49;54;66\n" + "2007-03-07,14;20;25,50;53;60\n" + "2007-03-08,20;28;35,48;52;60\n" + "2007-03-09,15;25;34,49;53;58\n" + "2007-03-10,30;43;55,50;57;69\n" + "2007-03-11,40;45;50,53;63;79\n" + "2007-03-12,36;46;56,57;65;78\n" + "2007-03-13,44;49;54,52;58;68\n" + "2007-03-14,46;56;66,50;54;62\n" + "2007-03-15,37;53;69,51;59;75\n" + "2007-03-16,28;33;38,53;62;76\n" + "2007-03-17,27;35;42,50;53;57\n" + "2007-03-18,29;35;41,50;53;61\n" + "2007-03-19,33;38;43,49;53;59\n" + "2007-03-20,35;43;50,50;54;58\n" + "2007-03-21,27;34;41,48;55;62\n" + "2007-03-22,41;54;67,50;58;71\n" + "2007-03-23,46;54;62,49;56;67\n" + "2007-03-24,40;48;55,50;52;56\n" + "2007-03-25,38;46;53,50;53;56\n" + "2007-03-26,41;48;55,48;53;60\n" + "2007-03-27,48;62;75,46;50;56\n" + "2007-03-28,47;55;63,47;52;61\n" + "2007-03-29,40;48;56,49;57;68\n" + "2007-03-30,42;55;67,48;53;64\n" + "2007-03-31,44;51;57,49;55;68\n" + "2007-04-01,42;47;51,49;52;58\n" + "2007-04-02,42;48;54,48;54;68\n" + "2007-04-03,43;52;60,48;54;66\n" + "2007-04-04,41;43;45,50;56;67\n" + "2007-04-05,36;42;47,50;54;63\n" + "2007-04-06,34;39;43,48;51;55\n" + "2007-04-07,34;39;43,51;54;59\n" + "2007-04-08,32;37;42,51;55;61\n" + "2007-04-09,35;42;49,52;55;59\n" + "2007-04-10,36;43;50,50;56;67\n" + "2007-04-11,37;44;51,51;54;57\n" + "2007-04-12,41;43;45,48;53;58\n" + "2007-04-13,44;47;50,49;54;64\n" + "2007-04-14,42;50;57,50;53;56\n" + "2007-04-15,42;49;56,48;55;65\n" + "2007-04-16,40;47;54,52;58;69\n" + "2007-04-17,43;47;50,48;52;56\n" + "2007-04-18,43;48;53,46;50;55\n" + "2007-04-19,46;55;63,46;50;55\n" + "2007-04-20,46;59;71,47;52;60\n" + "2007-04-21,48;63;78,49;52;57\n" + "2007-04-22,52;64;76,51;55;59\n" + "2007-04-23,54;70;85,48;55;65\n" + "2007-04-24,59;67;75,49;54;63\n" + "2007-04-25,49;56;63,51;54;59\n" + "2007-04-26,48;55;62,50;54;63\n" + "2007-04-27,48;52;55,53;62;78\n" + "2007-04-28,53;63;72,52;61;78\n" + "2007-04-29,55;61;66,50;53;61\n" + "2007-04-30,55;68;81,51;57;66\n" + "2007-05-01,52;62;71,50;55;61\n" + "2007-05-02,51;61;71,52;56;62\n" + "2007-05-03,53;63;72,50;53;58\n" + "2007-05-04,52;62;71,50;54;60\n" + "2007-05-05,52;63;73,50;59;70\n" + "2007-05-06,48;55;62,57;72;86\n" + "2007-05-07,48;57;65,69;76;88\n" + "2007-05-08,50;60;70,53;68;79\n" + "2007-05-09,58;70;82,50;55;61\n" + "2007-05-10,62;71;79,50;54;61\n" + "2007-05-11,61;70;78,49;52;56\n" + "2007-05-12,55;64;73,50;54;60\n" + "2007-05-13,53;61;68,47;55;66\n" + "2007-05-14,52;61;69,49;53;60\n" + "2007-05-15,59;73;87,49;52;59\n" + "2007-05-16,63;76;89,48;55;66\n" + "2007-05-17,55;62;68,49;54;61\n" + "2007-05-18,49;52;55,50;55;63\n" + "2007-05-19,52;55;57,52;57;66\n" + "2007-05-20,55;67;78,51;57;67\n" + "2007-05-21,56;65;74,49;59;69\n" + "2007-05-22,55;64;73,52;61;71\n" + "2007-05-23,58;66;74,56;68;83\n" + "2007-05-24,61;75;88,50;56;69\n" + "2007-05-25,69;82;95,50;52;60\n" + "2007-05-26,73;80;86,50;52;60\n" + "2007-05-27,68;77;85,50;52;56\n" + "2007-05-28,67;77;87,50;55;64\n" + "2007-05-29,63;72;81,51;54;59\n" + "2007-05-30,62;73;84,53;54;57\n" + "2007-05-31,67;79;90,52;54;57\n" + "2007-06-01,64;78;92,50;53;59\n" + "2007-06-02,73;82;90,50;52;56\n" + "2007-06-03,65;73;81,51;54;61\n" + "2007-06-04,60;66;71,55;59;65\n" + "2007-06-05,68;77;85,54;57;61\n" + "2007-06-06,58;65;72,52;56;62\n" + "2007-06-07,58;67;76,51;57;68\n" + "2007-06-08,66;76;85,51;54;62\n" + "2007-06-09,64;73;81,50;58;66\n" + "2007-06-10,63;69;74,53;58;64\n" + "2007-06-11,66;76;85,52;58;66\n" + "2007-06-12,68;76;84,52;60;72\n" + "2007-06-13,59;65;70,57;68;80\n" + "2007-06-14,58;63;67,57;67;84\n" + "2007-06-15,62;68;73,54;62;76\n" + "2007-06-16,64;73;82,52;55;59\n" + "2007-06-17,69;80;90,52;59;70\n" + "2007-06-18,70;78;85,52;56;64\n" + "2007-06-19,69;76;83,54;58;65\n" + "2007-06-20,70;75;80,54;56;61\n" + "2007-06-21,65;75;85,53;59;67\n" + "2007-06-22,65;71;77,53;59;67\n" + "2007-06-23,61;69;77,53;58;67\n" + "2007-06-24,63;74;84,52;58;66\n" + "2007-06-25,71;78;84,52;60;75\n" + "2007-06-26,73;84;94,51;57;65\n" + "2007-06-27,73;84;95,53;59;67\n" + "2007-06-28,73;84;94,54;61;72\n" + "2007-06-29,70;75;79,55;61;72\n" + "2007-06-30,68;76;84,53;58;66\n" + "2007-07-01,64;70;75,52;59;68\n" + "2007-07-02,60;68;76,55;62;71\n" + "2007-07-03,65;74;82,54;59;64\n" + "2007-07-04,68;71;73,55;64;75\n" + "2007-07-05,69;76;82,55;62;72\n" + "2007-07-06,70;79;87,53;57;62\n" + "2007-07-07,72;80;88,52;54;58\n" + "2007-07-08,76;85;93,53;57;65\n" + "2007-07-09,71;84;96,55;59;67\n" + "2007-07-10,75;84;93,56;61;70\n" + "2007-07-11,74;81;88,59;65;74\n" + "2007-07-12,69;77;84,58;66;75\n" + "2007-07-13,75;80;85,57;65;78\n" + "2007-07-14,70;78;86,56;60;69\n" + "2007-07-15,75;83;91,57;63;72\n" + "2007-07-16,72;78;83,56;60;67\n" + "2007-07-17,71;80;88,56;62;73\n" + "2007-07-18,71;75;79,61;66;74\n" + "2007-07-19,71;82;92,58;62;72\n" + "2007-07-20,69;75;81,58;64;73\n" + "2007-07-21,67;76;84,60;66;76\n" + "2007-07-22,70;77;84,60;65;74\n" + "2007-07-23,65;71;76,58;64;76\n" + "2007-07-24,63;73;83,56;60;71\n" + "2007-07-25,71;78;85,56;59;64\n" + "2007-07-26,73;80;86,55;58;72\n" + "2007-07-27,75;82;88,55;60;72\n" + "2007-07-28,74;81;88,55;61;70\n" + "2007-07-29,74;77;80,55;60;74\n" + "2007-07-30,72;80;88,55;63;80\n" + "2007-07-31,74;82;90,54;59;68\n" + "2007-08-01,75;84;93,55;60;69\n" + "2007-08-02,76;87;98,55;61;70\n" + "2007-08-03,73;83;93,54;60;71\n" + "2007-08-04,75;84;92,54;58;67\n" + "2007-08-05,72;78;84,55;58;62\n" + "2007-08-06,75;81;86,56;60;68\n" + "2007-08-07,78;84;90,56;61;69\n" + "2007-08-08,75;85;95,56;59;65\n" + "2007-08-09,74;80;85,57;61;72\n" + "2007-08-10,59;67;75,55;62;73\n" + "2007-08-11,60;72;83,56;60;68\n" + "2007-08-12,73;81;88,54;60;70\n" + "2007-08-13,75;82;88,55;61;74\n" + "2007-08-14,71;77;83,55;60;71\n" + "2007-08-15,72;81;89,54;61;70\n" + "2007-08-16,77;82;87,57;62;72\n" + "2007-08-17,66;77;87,53;64;77\n" + "2007-08-18,62;69;76,56;62;71\n" + "2007-08-19,62;68;73,57;64;75\n" + "2007-08-20,61;67;73,59;65;74\n" + "2007-08-21,58;60;62,58;65;80\n" + "2007-08-22,58;64;69,57;65;80\n" + "2007-08-23,63;71;79,56;62;73\n" + "2007-08-24,67;77;86,58;62;70\n" + "2007-08-25,75;83;91,58;62;71\n" + "2007-08-26,75;80;85,57;61;68\n" + "2007-08-27,71;77;83,56;61;71\n" + "2007-08-28,72;78;84,55;66;82\n" + "2007-08-29,72;79;86,61;69;83\n" + "2007-08-30,73;81;88,62;71;82\n" + "2007-08-31,72;77;81,59;64;72\n" + "2007-09-01,66;73;79,58;66;80\n" + "2007-09-02,63;72;80,58;67;86\n" + "2007-09-03,67;77;86,58;63;71\n" + "2007-09-04,73;79;85,60;64;72\n" + "2007-09-05,69;74;79,58;68;84\n" + "2007-09-06,70;77;83,60;64;69\n" + "2007-09-07,72;80;88,59;63;71\n" + "2007-09-08,74;82;90,58;61;66\n" + "2007-09-09,74;80;86,59;61;66\n" + "2007-09-10,73;76;78,60;64;71\n" + "2007-09-11,72;75;77,59;62;70\n" + "2007-09-12,66;72;77,59;62;65\n" + "2007-09-13,65;71;76,59;65;74\n" + "2007-09-14,67;72;77,60;66;74\n" + "2007-09-15,58;65;71,58;62;72\n" + "2007-09-16,55;62;69,59;63;71\n" + "2007-09-17,56;63;70,56;62;71\n" + "2007-09-18,57;65;72,56;60;67\n" + "2007-09-19,59;68;76,54;57;63\n" + "2007-09-20,64;74;83,54;59;72\n" + "2007-09-21,68;76;84,58;62;73\n" + "2007-09-22,68;72;76,58;60;65\n" + "2007-09-23,67;75;82,56;61;67\n" + "2007-09-24,65;73;81,52;62;78\n" + "2007-09-25,66;78;89,54;66;86\n" + "2007-09-26,72;81;90,58;70;92\n" + "2007-09-27,73;79;84,57;61;68\n" + "2007-09-28,64;71;77,55;59;63\n" + "2007-09-29,62;69;76,51;60;74\n" + "2007-09-30,61;67;73,51;60;71\n" + "2007-10-01,62;67;71,56;62;71\n" + "2007-10-02,60;68;75,54;62;76\n" + "2007-10-03,66;73;80,53;59;67\n" + "2007-10-04,69;77;85,53;55;59\n" + "2007-10-05,67;75;83,51;56;62\n" + "2007-10-06,68;77;85,48;57;68\n" + "2007-10-07,67;75;82,51;59;75\n" + "2007-10-08,67;78;89,52;59;78\n" + "2007-10-09,61;72;83,52;59;67\n" + "2007-10-10,62;68;73,\n" + "2007-10-11,59;64;69,\n" + "2007-10-12,51;57;62,\n" + "2007-10-13,49;56;63,\n" + "2007-10-14,52;59;65,\n" + "2007-10-15,53;61;69,\n" + "2007-10-16,58;65;71,\n" + "2007-10-17,60;67;73,\n" + "2007-10-18,64;72;79,\n" + "2007-10-19,66;70;74,\n" + "2007-10-20,64;68;72,\n" + "2007-10-21,59;68;76,\n" + "2007-10-22,62;70;77,\n" + "2007-10-23,67;74;81,\n" + "2007-10-24,54;62;70,\n" + "2007-10-25,52;57;61,\n" + "2007-10-26,53;57;60,\n" + "2007-10-27,59;65;70,\n" + "2007-10-28,46;53;59,\n" + "2007-10-29,43;49;54,\n" + "2007-10-30,49;57;64,\n" + "2007-10-31,51;58;64,53;60;67\n" + "2007-11-01,52;60;67,51;56;72\n" + "2007-11-02,46;51;56,49;60;82\n" + "2007-11-03,47;50;53,50;61;79\n" + "2007-11-04,49;53;56,53;62;80\n" + "2007-11-05,48;53;58,50;54;59\n" + "2007-11-06,45;52;58,50;54;61\n" + "2007-11-07,42;46;50,50;53;59\n" + "2007-11-08,38;43;48,53;56;60\n" + "2007-11-09,40;44;48,53;58;66\n" + "2007-11-10,39;43;47,52;57;62\n" + "2007-11-11,34;41;48,51;56;64\n" + "2007-11-12,40;46;52,47;56;68\n" + "2007-11-13,49;55;61,57;61;73\n" + "2007-11-14,44;53;61,53;61;72\n" + "2007-11-15,45;54;62,56;59;67\n" + "2007-11-16,39;44;48,54;56;61\n" + "2007-11-17,38;43;47,53;56;61\n" + "2007-11-18,41;44;47,53;57;63\n" + "2007-11-19,39;43;46,50;56;60\n" + "2007-11-20,40;45;50,48;54;63\n" + "2007-11-21,44;51;58,44;53;66\n" + "2007-11-22,42;54;66,46;54;68\n" + "2007-11-23,32;37;42,44;57;72\n" + "2007-11-24,28;34;40,44;54;65\n" + "2007-11-25,37;44;50,51;55;62\n" + "2007-11-26,41;52;63,47;54;65\n" + "2007-11-27,46;56;65,50;55;65\n" + "2007-11-28,37;42;47,47;56;66\n" + "2007-11-29,42;47;52,48;53;64\n" + "2007-11-30,37;40;43,45;49;57\n" + "2007-12-01,26;34;42,46;49;56\n" + "2007-12-02,21;30;38,47;54;59\n" + "2007-12-03,34;42;49,52;59;64\n" + "2007-12-04,30;33;35,31;57;69\n" + "2007-12-05,29;32;34,52;56;63\n" + "2007-12-06,23;30;37,51;52;54\n" + "2007-12-07,34;35;36,48;52;57\n" + "2007-12-08,35;40;45,42;49;56\n" + "2007-12-09,36;39;41,44;51;59\n" + "2007-12-10,37;40;43,45;50;59\n" + "2007-12-11,36;41;46,46;52;59\n" + "2007-12-12,37;46;54,42;49;57\n" + "2007-12-13,30;34;38,42;49;59\n" + "2007-12-14,32;39;45,40;48;57\n" + "2007-12-15,30;34;38,43;49;56\n" + "2007-12-16,31;36;40,46;51;57\n" + "2007-12-17,27;31;35,48;52;56\n" + "2007-12-18,31;35;38,49;52;55\n" + "2007-12-19,35;41;47,46;51;57\n" + "2007-12-20,38;42;45,45;51;56\n" + "2007-12-21,36;39;42,43;48;54\n" + "2007-12-22,36;39;42,39;46;53\n" + "2007-12-23,41;52;62,44;51;61\n" + "2007-12-24,39;46;52,49;53;60\n" + "2007-12-25,38;41;44,41;49;57\n" + "2007-12-26,34;38;41,44;48;55\n" + "2007-12-27,37;42;46,41;46;52\n" + "2007-12-28,43;47;50,41;44;45\n" + "2007-12-29,43;48;53,45;48;52\n" + "2007-12-30,37;41;44,46;49;53\n" + "2007-12-31,36;41;46,38;47;56\n" + "2008-01-01,35;42;49,42;50;58\n" + "2008-01-02,19;29;39,43;50;60\n" + "2008-01-03,15;19;23,51;53;58\n" + "2008-01-04,19;28;37,51;53;59\n" + "2008-01-05,33;38;43,46;49;51\n" + "2008-01-06,37;42;47,42;47;51\n" + "2008-01-07,42;52;61,43;48;53\n" + "2008-01-08,52;58;64,44;49;53\n" + "2008-01-09,49;58;66,46;49;52\n" + "2008-01-10,42;47;51,48;50;51\n" + "2008-01-11,41;49;57,48;51;55\n" + "2008-01-12,40;45;49,46;51;58\n" + "2008-01-13,37;42;47,44;51;60\n" + "2008-01-14,34;37;39,46;51;58\n" + "2008-01-15,34;37;40,44;49;57\n" + "2008-01-16,31;36;40,41;50;60\n" + "2008-01-17,30;36;41,44;50;61\n" + "2008-01-18,37;43;49,42;52;63\n" + "2008-01-19,34;36;37,42;50;62\n" + "2008-01-20,19;27;35,46;49;53\n" + "2008-01-21,16;22;28,43;45;46\n" + "2008-01-22,25;33;40,42;44;47\n" + "2008-01-23,32;37;41,42;44;49\n" + "2008-01-24,26;31;35,43;44;45\n" + "2008-01-25,23;29;34,45;49;52\n" + "2008-01-26,27;31;35,52;55;62\n" + "2008-01-27,31;35;39,45;51;54\n" + "2008-01-28,29;36;43,42;46;51\n" + "2008-01-29,33;39;44,41;45;49\n" + "2008-01-30,34;42;50,42;47;54\n" + "2008-01-31,30;35;40,45;48;52\n" + "2008-02-01,33;45;56,40;46;52\n" + "2008-02-02,36;40;44,42;47;53\n" + "2008-02-03,33;42;50,46;49;52\n" + "2008-02-04,34;39;43,43;49;58\n" + "2008-02-05,38;45;52,40;49;58\n" + "2008-02-06,40;55;69,47;50;55\n" + "2008-02-07,39;45;50,43;50;58\n" + "2008-02-08,36;41;46,46;53;65\n" + "2008-02-09,37;41;45,46;57;69\n" + "2008-02-10,17;31;45,49;57;70\n" + "2008-02-11,12;19;25,48;56;69\n" + "2008-02-12,19;25;31,46;54;70\n" + "2008-02-13,31;43;54,48;57;65\n" + "2008-02-14,30;36;41,49;54;64\n" + "2008-02-15,32;40;48,46;52;60\n" + "2008-02-16,25;30;35,45;51;64\n" + "2008-02-17,30;42;53,46;50;55\n" + "2008-02-18,42;53;64,47;50;58\n" + "2008-02-19,29;36;42,48;51;53\n" + "2008-02-20,25;29;33,48;51;57\n" + "2008-02-21,22;28;34,48;51;56\n" + "2008-02-22,26;30;34,46;50;56\n" + "2008-02-23,30;33;36,43;48;54\n" + "2008-02-24,27;34;40,52;54;58\n" + "2008-02-25,33;41;49,49;55;63\n" + "2008-02-26,38;43;48,48;58;70\n" + "2008-02-27,24;36;47,52;58;71\n" + "2008-02-28,20;25;29,48;56;74\n" + "2008-02-29,19;28;36,48;52;62\n" + "2008-03-01,34;40;45,50;53;59\n" + "2008-03-02,30;36;43,49;59;71\n" + "2008-03-03,35;44;52,47;57;72\n" + "2008-03-04,43;53;62,46;55;68\n" + "2008-03-05,39;49;58,46;55;71\n" + "2008-03-06,36;43;49,47;55;65\n" + "2008-03-07,35;40;45,48;56;69\n" + "2008-03-08,38;48;57,49;55;66\n" + "2008-03-09,31;37;43,47;58;76\n" + "2008-03-10,28;37;46,52;58;70\n" + "2008-03-11,36;43;49,50;54;66\n" + "2008-03-12,36;42;48,48;53;63\n" + "2008-03-13,33;39;45,30;55;61\n" + "2008-03-14,39;46;53,49;52;58\n" + "2008-03-15,42;49;55,43;50;57\n" + "2008-03-16,36;42;47,47;55;68\n" + "2008-03-17,32;41;49,49;56;66\n" + "2008-03-18,36;41;46,48;55;64\n" + "2008-03-19,42;47;52,49;52;60\n" + "2008-03-20,37;48;59,46;51;59\n" + "2008-03-21,35;41;47,47;54;67\n" + "2008-03-22,35;43;50,46;55;73\n" + "2008-03-23,32;40;48,46;54;66\n" + "2008-03-24,33;41;49,47;54;64\n" + "2008-03-25,32;39;46,49;53;59\n" + "2008-03-26,43;52;60,48;52;60\n" + "2008-03-27,44;47;50,45;50;59\n" + "2008-03-28,40;45;49,46;52;60\n" + "2008-03-29,34;41;47,48;52;59\n" + "2008-03-30,28;37;46,44;49;56\n" + "2008-03-31,39;48;57,41;50;62\n" + "2008-04-01,55;60;64,49;53;60\n" + "2008-04-02,39;47;54,48;54;62\n" + "2008-04-03,35;42;49,48;53;62\n" + "2008-04-04,42;49;55,45;50;57\n" + "2008-04-05,49;54;59,46;50;57\n" + "2008-04-06,42;46;50,48;52;59\n" + "2008-04-07,41;46;51,45;51;59\n" + "2008-04-08,41;48;55,47;50;55\n" + "2008-04-09,39;48;56,47;52;59\n" + "2008-04-10,48;62;75,46;55;66\n" + "2008-04-11,47;54;61,50;65;84\n" + "2008-04-12,47;61;74,57;71;87\n" + "2008-04-13,44;50;55,51;64;80\n" + "2008-04-14,41;49;56,48;52;58\n" + "2008-04-15,43;51;59,45;51;60\n" + "2008-04-16,46;56;65,46;51;66\n" + "2008-04-17,45;59;72,49;57;75\n" + "2008-04-18,50;67;83,48;52;60\n" + "2008-04-19,51;62;72,45;49;55\n" + "2008-04-20,48;52;56,43;48;55\n" + "2008-04-21,47;52;57,44;49;57\n" + "2008-04-22,48;59;69,48;54;64\n" + "2008-04-23,52;64;76,48;53;60\n" + "2008-04-24,58;68;77,45;53;65\n" + "2008-04-25,55;63;70,48;57;69\n" + "2008-04-26,52;58;63,51;62;78\n" + "2008-04-27,49;52;55,53;62;77\n" + "2008-04-28,48;54;59,49;56;66\n" + "2008-04-29,47;53;58,50;53;61\n" + "2008-04-30,44;51;57,47;52;60\n" + "2008-05-01,45;52;59,47;55;66\n" + "2008-05-02,50;53;55,49;53;60\n" + "2008-05-03,48;52;56,48;54;63\n" + "2008-05-04,49;61;72,49;52;59\n" + "2008-05-05,51;61;71,48;53;61\n" + "2008-05-06,52;65;77,49;56;68\n" + "2008-05-07,58;66;73,49;55;65\n" + "2008-05-08,64;69;74,48;53;62\n" + "2008-05-09,50;57;64,46;54;65\n" + "2008-05-10,50;59;67,47;54;64\n" + "2008-05-11,50;56;61,48;54;64\n" + "2008-05-12,48;53;57,50;56;66\n" + "2008-05-13,52;61;70,49;61;78\n" + "2008-05-14,53;64;74,55;69;85\n" + "2008-05-15,54;64;73,68;81;102\n" + "2008-05-16,51;56;61,65;77;99\n" + "2008-05-17,50;62;74,52;63;73\n" + "2008-05-18,56;62;67,51;54;62\n" + "2008-05-19,51;56;61,50;54;60\n" + "2008-05-20,50;54;58,52;56;63\n" + "2008-05-21,50;60;69,50;57;65\n" + "2008-05-22,50;56;61,52;58;68\n" + "2008-05-23,52;60;68,49;55;65\n" + "2008-05-24,56;63;70,51;54;62\n" + "2008-05-25,58;68;77,50;56;64\n" + "2008-05-26,60;68;76,50;55;62\n" + "2008-05-27,60;73;85,51;56;64\n" + "2008-05-28,53;61;69,52;58;67\n" + "2008-05-29,54;66;78,53;57;63\n" + "2008-05-30,62;72;82,52;56;66\n" + "2008-05-31,63;70;77,52;54;60\n" + "2008-06-01,67;76;84,49;55;63\n" + "2008-06-02,61;71;80,49;54;62\n" + "2008-06-03,65;75;84,51;55;62\n" + "2008-06-04,58;65;71,51;54;62\n" + "2008-06-05,62;67;72,51;58;70\n" + "2008-06-06,59;66;73,51;57;66\n" + "2008-06-07,60;78;96,50;59;70\n" + "2008-06-08,78;87;96,52;61;74\n" + "2008-06-09,76;88;99,54;66;83\n" + "2008-06-10,73;87;100,53;64;77\n" + "2008-06-11,72;80;88,57;65;79\n" + "2008-06-12,71;79;86,55;67;93\n" + "2008-06-13,65;74;82,50;56;65\n" + "2008-06-14,70;80;90,52;56;65\n" + "2008-06-15,65;74;83,50;54;61\n" + "2008-06-16,64;71;78,49;52;58\n" + "2008-06-17,64;71;77,48;60;78\n" + "2008-06-18,60;68;75,54;65;79\n" + "2008-06-19,60;69;77,57;72;92\n" + "2008-06-20,63;71;79,63;80;102\n" + "2008-06-21,65;75;85,56;75;85\n" + "2008-06-22,71;76;80,51;56;63\n" + "2008-06-23,70;77;83,50;52;56\n" + "2008-06-24,70;76;81,50;58;71\n" + "2008-06-25,68;77;85,51;56;64\n" + "2008-06-26,74;80;85,50;57;70\n" + "2008-06-27,76;81;85,52;57;62\n" + "2008-06-28,70;80;90,55;58;64\n" + "2008-06-29,74;82;90,53;57;65\n" + "2008-06-30,73;79;85,52;57;68\n" + "2008-07-01,71;79;86,53;58;67\n" + "2008-07-02,69;78;87,52;56;65\n" + "2008-07-03,73;83;93,54;60;71\n" + "2008-07-04,71;76;80,55;60;69\n" + "2008-07-05,69;72;75,56;62;74\n" + "2008-07-06,68;74;79,56;62;74\n" + "2008-07-07,70;78;85,56;66;80\n" + "2008-07-08,76;84;91,59;69;86\n" + "2008-07-09,75;81;87,62;69;82\n" + "2008-07-10,73;80;87,59;64;72\n" + "2008-07-11,69;79;89,58;64;73\n" + "2008-07-12,73;80;86,60;64;73\n" + "2008-07-13,72;79;85,60;64;71\n" + "2008-07-14,70;77;83,59;62;68\n" + "2008-07-15,72;81;90,59;62;70\n" + "2008-07-16,71;81;90,57;60;66\n" + "2008-07-17,74;83;92,54;58;65\n" + "2008-07-18,76;86;96,52;56;64\n" + "2008-07-19,81;89;97,53;57;63\n" + "2008-07-20,79;87;94,52;55;63\n" + "2008-07-21,75;84;93,54;57;63\n" + "2008-07-22,73;80;87,52;60;73\n" + "2008-07-23,70;76;82,54;61;76\n" + "2008-07-24,70;78;85,52;60;72\n" + "2008-07-25,71;79;87,52;61;72\n" + "2008-07-26,74;81;88,55;65;79\n" + "2008-07-27,70;76;82,55;57;62\n" + "2008-07-28,69;79;88,54;57;65\n" + "2008-07-29,75;83;90,55;59;68\n" + "2008-07-30,73;80;87,56;60;69\n" + "2008-07-31,75;83;90,55;58;69\n" + "2008-08-01,74;81;88,55;62;74\n" + "2008-08-02,69;76;82,55;64;79\n" + "2008-08-03,68;76;83,54;57;62\n" + "2008-08-04,68;77;85,53;56;65\n" + "2008-08-05,73;79;84,52;56;64\n" + "2008-08-06,72;80;88,54;57;68\n" + "2008-08-07,68;77;86,54;57;66\n" + "2008-08-08,68;75;82,54;58;66\n" + "2008-08-09,66;74;82,54;59;67\n" + "2008-08-10,69;76;82,53;64;79\n" + "2008-08-11,60;67;73,56;63;78\n" + "2008-08-12,63;72;81,55;63;79\n" + "2008-08-13,69;75;81,56;64;82\n" + "2008-08-14,68;76;84,55;61;74\n" + "2008-08-15,67;74;81,55;62;75\n" + "2008-08-16,66;74;82,57;60;66\n" + "2008-08-17,69;77;85,57;60;69\n" + "2008-08-18,73;81;88,58;62;68\n" + "2008-08-19,65;74;82,58;62;73\n" + "2008-08-20,61;69;77,58;65;74\n" + "2008-08-21,66;74;81,60;64;73\n" + "2008-08-22,69;76;83,58;62;72\n" + "2008-08-23,67;74;80,57;62;71\n" + "2008-08-24,71;76;81,57;64;73\n" + "2008-08-25,68;77;85,58;62;72\n" + "2008-08-26,62;71;80,55;61;79\n" + "2008-08-27,64;71;77,59;70;88\n" + "2008-08-28,66;75;84,62;72;86\n" + "2008-08-29,68;74;80,59;67;84\n" + "2008-08-30,68;76;84,57;61;70\n" + "2008-08-31,69;77;84,54;61;73\n" + "2008-09-01,68;76;84,57;68;83\n" + "2008-09-02,71;79;86,58;68;85\n" + "2008-09-03,70;76;81,59;69;87\n" + "2008-09-04,72;81;90,60;73;94\n" + "2008-09-05,73;80;86,63;74;92\n" + "2008-09-06,72;78;83,63;72;84\n" + "2008-09-07,70;77;83,56;62;70\n" + "2008-09-08,68;76;83,55;60;67\n" + "2008-09-09,67;73;78,58;60;65\n" + "2008-09-10,63;68;73,57;61;68\n" + "2008-09-11,62;68;73,56;60;66\n" + "2008-09-12,65;69;72,55;57;63\n" + "2008-09-13,68;74;79,55;59;67\n" + "2008-09-14,71;80;89,55;59;67\n" + "2008-09-15,67;76;84,54;58;67\n" + "2008-09-16,63;67;70,54;58;65\n" + "2008-09-17,61;68;75,56;60;67\n" + "2008-09-18,61;68;74,53;60;70\n" + "2008-09-19,55;61;66,55;62;72\n" + "2008-09-20,54;61;68,59;62;69\n" + "2008-09-21,59;70;80,57;61;70\n" + "2008-09-22,62;68;73,55;64;79\n" + "2008-09-23,57;63;69,56;67;85\n" + "2008-09-24,58;64;69,57;64;74\n" + "2008-09-25,56;62;67,57;65;76\n" + "2008-09-26,60;64;68,56;63;75\n" + "2008-09-27,64;67;69,54;62;78\n" + "2008-09-28,67;71;74,55;59;65\n" + "2008-09-29,63;68;73,57;61;67\n" + "2008-09-30,62;68;73,57;63;76\n" + "2008-10-01,61;67;73,59;64;74\n" + "2008-10-02,56;60;64,60;65;73\n" + "2008-10-03,54;60;65,59;63;71\n" + "2008-10-04,53;58;63,58;62;68\n" + "2008-10-05,54;59;63,57;62;69\n" + "2008-10-06,51;57;63,59;64;76\n" + "2008-10-07,48;56;64,56;64;79\n" + "2008-10-08,52;59;66,58;64;75\n" + "2008-10-09,60;69;77,54;61;71\n" + "2008-10-10,61;67;73,54;58;64\n" + "2008-10-11,57;64;71,55;60;67\n" + "2008-10-12,57;64;71,55;62;72\n" + "2008-10-13,60;68;75,58;64;77\n" + "2008-10-14,59;64;68,53;63;80\n" + "2008-10-15,60;66;71,55;65;83\n" + "2008-10-16,57;68;78,58;68;86\n" + "2008-10-17,52;56;60,59;69;86\n" + "2008-10-18,44;51;57,53;58;65\n" + "2008-10-19,43;51;58,52;54;57\n" + "2008-10-20,45;54;62,51;57;65\n" + "2008-10-21,46;55;63,52;62;79\n" + "2008-10-22,42;47;52,55;67;83\n" + "2008-10-23,40;47;54,61;70;85\n" + "2008-10-24,43;50;57,59;66;86\n" + "2008-10-25,54;61;67,57;67;82\n" + "2008-10-26,51;57;63,52;57;66\n" + "2008-10-27,50;57;64,51;55;65\n" + "2008-10-28,41;46;51,50;55;65\n" + "2008-10-29,40;44;48,49;54;64\n" + "2008-10-30,38;44;49,51;56;61\n" + "2008-10-31,42;53;64,58;61;66\n" + "2008-11-01,52;59;65,34;58;63\n" + "2008-11-02,39;46;52,\n" + "2008-11-03,44;52;59,53;56;61\n" + "2008-11-04,50;57;64,51;54;61\n" + "2008-11-05,55;59;63,46;55;66\n" + "2008-11-06,59;62;65,54;60;72\n" + "2008-11-07,58;62;65,52;60;75\n" + "2008-11-08,55;58;60,53;56;62\n" + "2008-11-09,50;53;56,52;56;63\n" + "2008-11-10,44;48;51,49;55;62\n" + "2008-11-11,40;46;51,53;58;64\n" + "2008-11-12,43;47;50,56;59;68\n" + "2008-11-13,47;53;58,52;61;74\n" + "2008-11-14,55;59;63,55;67;80\n" + "2008-11-15,58;62;65,60;70;82\n" + "2008-11-16,42;52;61,58;66;78\n" + "2008-11-17,39;43;47,57;66;77\n" + "2008-11-18,31;36;41,52;56;62\n" + "2008-11-19,28;33;37,52;54;57\n" + "2008-11-20,31;35;39,54;58;66\n" + "2008-11-21,27;33;39,47;55;67\n" + "2008-11-22,25;29;33,47;55;68\n" + "2008-11-23,26;32;37,49;56;69\n" + "2008-11-24,33;43;53,49;56;64\n" + "2008-11-25,38;43;48,52;57;61\n" + "2008-11-26,38;42;45,53;55;58\n" + "2008-11-27,37;41;44,53;55;61\n" + "2008-11-28,38;44;50,50;54;60\n" + "2008-11-29,38;42;45,50;57;68\n" + "2008-11-30,36;40;44,51;58;70\n" + "2008-12-01,43;49;55,53;55;57\n" + "2008-12-02,38;42;46,52;56;64\n" + "2008-12-03,35;39;43,50;54;59\n" + "2008-12-04,39;45;51,47;53;62\n" + "2008-12-05,34;38;42,46;54;66\n" + "2008-12-06,31;34;37,47;53;62\n" + "2008-12-07,22;30;37,44;50;56\n" + "2008-12-08,20;26;31,46;51;55\n" + "2008-12-09,31;44;56,42;50;59\n" + "2008-12-10,44;54;63,47;52;63\n" + "2008-12-11,38;41;44,48;54;63\n" + "2008-12-12,33;40;46,48;53;60\n" + "2008-12-13,28;31;33,45;49;55\n" + "2008-12-14,30;40;49,43;46;50\n" + "2008-12-15,48;58;67,41;46;51\n" + "2008-12-16,31;45;59,39;42;47\n" + "2008-12-17,33;39;44,39;45;53\n" + "2008-12-18,37;40;43,38;47;55\n" + "2008-12-19,30;35;39,45;50;55\n" + "2008-12-20,23;28;32,39;46;54\n" + "2008-12-21,26;33;40,46;48;51\n" + "2008-12-22,14;22;29,44;49;54\n" + "2008-12-23,20;26;31,43;48;53\n" + "2008-12-24,31;45;58,46;50;54\n" + "2008-12-25,35;47;58,45;48;53\n" + "2008-12-26,32;37;41,\n" + "2008-12-27,40;44;48,\n" + "2008-12-28,47;56;65,\n" + "2008-12-29,38;44;49,47;53;63\n" + "2008-12-30,34;39;44,46;51;58\n" + "2008-12-31,19;28;36,45;48;55\n" + "2009-01-01,16;22;27,44;48;51\n" + "2009-01-02,24;30;36,45;50;55\n" + "2009-01-03,30;35;39,40;46;54\n" + "2009-01-04,26;35;43,36;45;54\n" + "2009-01-05,39;42;44,45;46;48\n" + "2009-01-06,31;35;39,44;49;54\n" + "2009-01-07,32;36;40,44;47;52\n" + "2009-01-08,30;35;39,46;49;56\n" + "2009-01-09,27;30;33,41;51;62\n" + "2009-01-10,24;27;30,48;56;66\n" + "2009-01-11,26;29;32,44;55;70\n" + "2009-01-12,23;29;34,49;62;76\n" + "2009-01-13,29;35;41,55;64;75\n" + "2009-01-14,18;28;37,48;59;70\n" + "2009-01-15,17;20;23,48;60;74\n" + "2009-01-16,11;15;18,48;60;72\n" + "2009-01-17,8;15;22,48;57;68\n" + "2009-01-18,22;28;33,49;58;70\n" + "2009-01-19,26;30;33,46;58;71\n" + "2009-01-20,22;26;30,47;58;71\n" + "2009-01-21,19;24;28,52;57;62\n" + "2009-01-22,22;30;37,51;53;55\n" + "2009-01-23,28;37;45,53;55;56\n" + "2009-01-24,18;33;47,49;52;55\n" + "2009-01-25,15;22;28,46;50;55\n" + "2009-01-26,20;25;30,43;49;57\n" + "2009-01-27,26;29;31,43;50;58\n" + "2009-01-28,24;33;42,43;51;61\n" + "2009-01-29,27;32;37,44;56;70\n" + "2009-01-30,28;32;35,47;56;69\n" + "2009-01-31,21;25;28,44;52;69\n" + "2009-02-01,28;41;53,44;54;70\n" + "2009-02-02,33;43;52,47;57;69\n" + "2009-02-03,26;32;37,47;57;71\n" + "2009-02-04,18;25;31,47;55;67\n" + "2009-02-05,14;19;24,51;54;58\n" + "2009-02-06,17;25;32,51;53;55\n" + "2009-02-07,24;37;49,48;54;63\n" + "2009-02-08,38;49;59,45;50;56\n" + "2009-02-09,35;41;46,43;47;56\n" + "2009-02-10,33;42;50,38;47;54\n" + "2009-02-11,38;51;63,43;48;56\n" + "2009-02-12,44;51;57,44;48;55\n" + "2009-02-13,32;38;44,43;46;54\n" + "2009-02-14,29;35;41,43;48;54\n" + "2009-02-15,30;36;42,46;48;51\n" + "2009-02-16,29;35;40,47;49;56\n" + "2009-02-17,26;34;41,47;50;54\n" + "2009-02-18,32;38;43,45;52;60\n" + "2009-02-19,28;39;50,43;54;66\n" + "2009-02-20,24;29;34,49;57;68\n" + "2009-02-21,28;35;42,50;56;63\n" + "2009-02-22,32;38;43,53;55;58\n" + "2009-02-23,25;30;34,52;56;60\n" + "2009-02-24,23;30;37,50;52;53\n" + "2009-02-25,26;34;41,51;55;61\n" + "2009-02-26,35;43;50,48;54;60\n" + "2009-02-27,40;49;58,46;53;62\n" + "2009-02-28,32;43;54,50;56;66\n" + "2009-03-01,28;32;35,54;57;59\n" + "2009-03-02,18;23;28,53;56;61\n" + "2009-03-03,14;21;28,48;52;58\n" + "2009-03-04,19;27;35,44;50;58\n" + "2009-03-05,28;36;43,46;51;60\n" + "2009-03-06,38;46;54,47;52;58\n" + "2009-03-07,46;58;69,43;52;65\n" + "2009-03-08,44;53;62,50;53;60\n" + "2009-03-09,37;42;46,\n" + "2009-03-10,37;42;46,\n" + "2009-03-11,41;50;59,\n" + "2009-03-12,32;38;44,\n" + "2009-03-13,27;33;38,\n" + "2009-03-14,34;42;50,\n" + "2009-03-15,42;48;53,\n" + "2009-03-16,38;42;46,\n" + "2009-03-17,36;44;52,\n" + "2009-03-18,40;51;61,\n" + "2009-03-19,41;46;50,\n" + "2009-03-20,33;39;44,\n" + "2009-03-21,32;40;47,53;55;60\n" + "2009-03-22,36;45;53,45;50;58\n" + "2009-03-23,30;36;42,44;51;59\n" + "2009-03-24,29;38;47,43;54;66\n" + "2009-03-25,36;44;52,47;56;68\n" + "2009-03-26,40;43;45,51;58;68\n" + "2009-03-27,43;53;63,52;61;75\n" + "2009-03-28,42;48;53,51;59;71\n" + "2009-03-29,42;47;52,48;56;64\n" + "2009-03-30,42;47;51,51;57;68\n" + "2009-03-31,40;50;60,47;56;70\n" + "2009-04-01,42;46;50,50;57;68\n" + "2009-04-02,42;51;59,48;52;60\n" + "2009-04-03,44;53;61,47;53;61\n" + "2009-04-04,48;52;55,43;55;70\n" + "2009-04-05,45;56;66,46;61;80\n" + "2009-04-06,44;48;51,51;61;79\n" + "2009-04-07,40;45;50,51;55;63\n" + "2009-04-08,37;43;49,50;55;64\n" + "2009-04-09,38;50;61,50;53;58\n" + "2009-04-10,46;52;58,48;54;63\n" + "2009-04-11,43;47;50,46;53;61\n" + "2009-04-12,38;43;48,49;56;66\n" + "2009-04-13,36;47;57,49;56;65\n" + "2009-04-14,43;48;53,45;50;57\n" + "2009-04-15,43;49;54,46;46;47\n" + "2009-04-16,44;53;62,44;52;61\n" + "2009-04-17,44;57;69,46;54;70\n" + "2009-04-18,56;67;78,52;62;74\n" + "2009-04-19,47;54;61,51;70;92\n" + "2009-04-20,43;47;51,60;77;99\n" + "2009-04-21,47;52;56,64;78;93\n" + "2009-04-22,49;53;57,52;62;71\n" + "2009-04-23,45;51;57,50;54;61\n" + "2009-04-24,45;57;69,48;52;59\n" + "2009-04-25,50;69;88,45;52;60\n" + "2009-04-26,63;77;91,46;53;63\n" + "2009-04-27,53;66;79,48;51;57\n" + "2009-04-28,68;79;89,47;52;60\n" + "2009-04-29,50;59;68,46;53;61\n" + "2009-04-30,46;54;61,47;54;69\n" + "2009-05-01,54;63;72,51;56;62\n" + "2009-05-02,56;62;67,55;59;65\n" + "2009-05-03,53;56;58,53;59;66\n" + "2009-05-04,51;54;56,52;60;71\n" + "2009-05-05,51;54;56,57;61;69\n" + "2009-05-06,49;57;65,55;61;73\n" + "2009-05-07,52;63;73,51;61;72\n" + "2009-05-08,56;65;74,51;60;71\n" + "2009-05-09,57;68;78,50;57;69\n" + "2009-05-10,55;63;70,50;57;68\n" + "2009-05-11,51;59;66,50;56;65\n" + "2009-05-12,52;60;68,47;57;67\n" + "2009-05-13,51;60;68,50;59;70\n" + "2009-05-14,57;62;66,51;58;69\n" + "2009-05-15,58;66;74,51;58;67\n" + "2009-05-16,58;65;71,54;70;93\n" + "2009-05-17,53;59;64,55;70;92\n" + "2009-05-18,50;55;59,51;55;63\n" + "2009-05-19,50;60;69,51;56;64\n" + "2009-05-20,53;67;81,49;55;62\n" + "2009-05-21,64;73;81,49;54;65\n" + "2009-05-22,63;75;86,51;54;63\n" + "2009-05-23,59;68;76,50;52;56\n" + "2009-05-24,57;71;84,49;51;56\n" + "2009-05-25,64;73;81,49;54;63\n" + "2009-05-26,53;60;66,49;59;73\n" + "2009-05-27,52;56;60,52;57;64\n" + "2009-05-28,56;58;59,51;55;62\n" + "2009-05-29,55;65;74,52;57;63\n" + "2009-05-30,63;71;78,54;56;62\n" + "2009-05-31,58;70;82,53;55;60\n" + "2009-06-01,53;61;69,53;56;61\n" + "2009-06-02,63;72;80,52;57;68\n" + "2009-06-03,54;64;73,53;58;67\n" + "2009-06-04,54;62;69,55;59;67\n" + "2009-06-05,55;59;62,57;62;70\n" + "2009-06-06,58;68;78,57;61;68\n" + "2009-06-07,66;74;82,56;61;67\n" + "2009-06-08,63;70;77,55;58;62\n" + "2009-06-09,60;67;73,56;60;66\n" + "2009-06-10,59;62;65,57;61;69\n" + "2009-06-11,59;61;63,56;60;68\n" + "2009-06-12,62;71;80,56;60;67\n" + "2009-06-13,64;68;71,56;62;70\n" + "2009-06-14,61;67;72,57;62;69\n" + "2009-06-15,58;65;71,58;61;68\n" + "2009-06-16,56;62;68,57;59;66\n" + "2009-06-17,57;63;69,57;60;66\n" + "2009-06-18,62;64;66,56;63;75\n" + "2009-06-19,63;71;79,55;62;71\n" + "2009-06-20,63;68;72,53;59;69\n" + "2009-06-21,63;71;78,53;60;70\n" + "2009-06-22,67;71;75,54;62;75\n" + "2009-06-23,67;74;80,54;63;77\n" + "2009-06-24,66;71;75,54;59;69\n" + "2009-06-25,65;73;81,54;58;66\n" + "2009-06-26,65;75;85,53;60;70\n" + "2009-06-27,66;74;81,53;68;85\n" + "2009-06-28,68;75;82,60;68;78\n" + "2009-06-29,68;76;83,56;61;68\n" + "2009-06-30,67;76;84,56;61;68\n" + "2009-07-01,67;73;79,56;61;68\n" + "2009-07-02,66;72;77,56;60;66\n" + "2009-07-03,68;74;80,55;58;65\n" + "2009-07-04,68;75;81,54;60;67\n" + "2009-07-05,64;72;80,55;57;63\n" + "2009-07-06,68;77;85,54;60;69\n" + "2009-07-07,62;73;83,56;58;62\n" + "2009-07-08,61;69;77,52;62;71\n" + "2009-07-09,62;68;73,53;59;68\n" + "2009-07-10,63;69;75,54;59;66\n" + "2009-07-11,65;71;77,53;58;68\n" + "2009-07-12,66;75;83,53;60;70\n" + "2009-07-13,65;72;79,53;66;84\n" + "2009-07-14,66;74;81,58;68;86\n" + "2009-07-15,67;75;83,53;59;66\n" + "2009-07-16,73;79;85,52;57;69\n" + "2009-07-17,69;79;88,52;58;69\n" + "2009-07-18,71;78;84,54;60;74\n" + "2009-07-19,68;76;83,54;61;73\n" + "2009-07-20,68;75;82,54;57;62\n" + "2009-07-21,65;68;71,53;56;61\n" + "2009-07-22,67;75;82,52;56;60\n" + "2009-07-23,64;72;80,52;55;61\n" + "2009-07-24,65;73;80,53;56;62\n" + "2009-07-25,67;77;86,52;56;67\n" + "2009-07-26,71;79;86,53;57;72\n" + "2009-07-27,71;78;85,52;57;70\n" + "2009-07-28,73;80;87,55;58;63\n" + "2009-07-29,73;78;83,58;58;58\n" + "2009-07-30,74;81;87,56;60;65\n" + "2009-07-31,69;79;89,55;58;64\n" + "2009-08-01,69;77;85,55;58;64\n" + "2009-08-02,73;76;78,56;60;66\n" + "2009-08-03,72;79;85,56;62;70\n" + "2009-08-04,72;79;86,57;64;73\n" + "2009-08-05,74;82;89,58;62;72\n" + "2009-08-06,68;74;79,59;63;71\n" + "2009-08-07,67;74;80,58;63;71\n" + "2009-08-08,65;72;78,58;64;76\n" + "2009-08-09,71;76;81,57;66;82\n" + "2009-08-10,71;83;94,59;66;82\n" + "2009-08-11,77;85;92,58;61;66\n" + "2009-08-12,71;76;80,57;66;79\n" + "2009-08-13,70;72;74,57;63;74\n" + "2009-08-14,69;77;85,56;61;70\n" + "2009-08-15,73;81;89,55;62;76\n" + "2009-08-16,75;83;91,55;62;73\n" + "2009-08-17,77;85;93,55;58;64\n" + "2009-08-18,73;82;91,55;59;65\n" + "2009-08-19,75;83;91,57;59;65\n" + "2009-08-20,76;83;89,56;60;67\n" + "2009-08-21,75;84;92,56;62;78\n" + "2009-08-22,72;79;85,56;59;64\n" + "2009-08-23,75;81;86,55;58;65\n" + "2009-08-24,72;78;84,54;58;64\n" + "2009-08-25,71;79;86,55;60;70\n" + "2009-08-26,74;81;87,55;59;68\n" + "2009-08-27,68;74;80,53;66;87\n" + "2009-08-28,66;70;73,63;76;97\n" + "2009-08-29,67;70;73,58;73;87\n" + "2009-08-30,66;75;83,55;57;62\n" + "2009-08-31,64;68;72,54;59;69\n" + "2009-09-01,59;67;74,58;63;72\n" + "2009-09-02,61;69;77,61;70;90\n" + "2009-09-03,64;71;78,58;67;79\n" + "2009-09-04,65;74;83,54;59;66\n" + "2009-09-05,71;78;85,56;62;70\n" + "2009-09-06,64;69;74,57;62;69\n" + "2009-09-07,61;67;72,56;63;73\n" + "2009-09-08,65;70;75,55;61;71\n" + "2009-09-09,65;71;76,54;58;66\n" + "2009-09-10,61;66;71,55;65;82\n" + "2009-09-11,58;64;69,54;62;76\n" + "2009-09-12,65;67;68,58;60;64\n" + "2009-09-13,67;74;81,59;64;73\n" + "2009-09-14,65;72;79,58;63;69\n" + "2009-09-15,68;75;81,57;63;73\n" + "2009-09-16,63;67;71,58;63;72\n" + "2009-09-17,57;61;65,59;67;79\n" + "2009-09-18,58;68;77,62;70;89\n" + "2009-09-19,55;63;71,58;62;73\n" + "2009-09-20,57;66;75,57;63;76\n" + "2009-09-21,61;69;76,56;61;71\n" + "2009-09-22,66;71;75,55;62;77\n" + "2009-09-23,69;76;82,56;58;65\n" + "2009-09-24,68;75;82,55;58;65\n" + "2009-09-25,59;64;69,55;61;73\n" + "2009-09-26,55;61;66,53;66;87\n" + "2009-09-27,60;65;69,59;68;84\n" + "2009-09-28,60;67;74,56;59;63\n" + "2009-09-29,58;63;68,55;58;64\n" + "2009-09-30,51;56;60,51;58;70\n" + "2009-10-01,51;54;57,52;64;79\n" + "2009-10-02,49;58;66,55;63;77\n" + "2009-10-03,65;68;71,52;57;63\n" + "2009-10-04,59;68;76,50;55;62\n" + "2009-10-05,55;62;68,50;57;68\n" + "2009-10-06,54;61;68,50;60;74\n" + "2009-10-07,59;65;71,53;58;68\n" + "2009-10-08,55;60;64,54;57;62\n" + "2009-10-09,61;66;71,52;56;63\n" + "2009-10-10,54;61;68,51;54;59\n" + "2009-10-11,50;58;65,51;54;59\n" + "2009-10-12,46;51;55,56;60;66\n" + "2009-10-13,47;56;64,58;61;64\n" + "2009-10-14,44;49;53,63;66;76\n" + "2009-10-15,39;44;48,64;67;76\n" + "2009-10-16,39;44;49,59;67;79\n" + "2009-10-17,44;47;50,56;64;74\n" + "2009-10-18,43;45;47,56;59;63\n" + "2009-10-19,41;50;58,57;60;68\n" + "2009-10-20,47;58;68,56;59;67\n" + "2009-10-21,53;61;69,54;60;71\n" + "2009-10-22,55;66;76,57;62;72\n" + "2009-10-23,51;59;66,55;63;75\n" + "2009-10-24,53;60;67,55;61;74\n" + "2009-10-25,53;59;65,54;64;81\n" + "2009-10-26,49;55;61,57;62;71\n" + "2009-10-27,50;54;58,54;59;69\n" + "2009-10-28,51;55;58,53;58;67\n" + "2009-10-29,49;54;58,51;58;67\n" + "2009-10-30,52;56;60,52;59;74\n" + "2009-10-31,56;64;71,53;58;72\n" + "2009-11-01,50;55;60,52;63;77\n" + "2009-11-02,48;52;55,56;66;81\n" + "2009-11-03,46;54;62,56;66;80\n" + "2009-11-04,44;48;52,54;58;65\n" + "2009-11-05,44;49;53,57;61;68\n" + "2009-11-06,41;46;50,53;59;63\n" + "2009-11-07,38;46;53,49;56;65\n" + "2009-11-08,48;60;71,50;57;68\n" + "2009-11-09,48;58;67,49;56;64\n" + "2009-11-10,56;60;64,54;58;66\n" + "2009-11-11,49;54;58,54;58;65\n" + "2009-11-12,48;51;53,51;56;62\n" + "2009-11-13,49;52;55,48;53;60\n" + "2009-11-14,52;55;58,50;54;61\n" + "2009-11-15,54;59;63,44;53;65\n" + "2009-11-16,49;54;59,47;54;66\n" + "2009-11-17,46;51;55,45;54;64\n" + "2009-11-18,43;48;53,48;53;62\n" + "2009-11-19,48;55;61,44;53;63\n" + "2009-11-20,50;56;61,46;52;58\n" + "2009-11-21,47;52;57,47;51;59\n" + "2009-11-22,46;51;56,49;53;59\n" + "2009-11-23,45;53;60,44;54;66\n" + "2009-11-24,49;53;57,48;58;69\n" + "2009-11-25,49;51;53,\n" + "2009-11-26,49;52;54,50;55;67\n" + "2009-11-27,44;47;50,51;54;58\n" + "2009-11-28,44;48;51,\n" + "2009-11-29,42;51;59,49;60;71\n" + "2009-11-30,41;49;57,48;55;64\n" + "2009-12-01,38;43;48,46;52;64\n" + "2009-12-02,40;49;57,45;48;55\n" + "2009-12-03,51;59;66,46;50;58\n" + "2009-12-04,46;49;52,44;48;57\n" + "2009-12-05,37;42;47,\n"; } dygraphs-2.2.1/auto_tests/data/out-of-order-dates.csv010064400000000000000000000003521437353256000175340ustar00Date,r17,r82,r83,r9 2013-10-17T21:00:00Z,,,,9 2014-05-29T18:00:00Z,,,,9 2013-10-17T21:00:00Z,17,,, 2014-05-29T17:00:00Z,17,,, 2014-05-29T18:00:00Z,,82,, 2014-05-29T18:00:00Z,,,83, 2014-10-11T04:00:00Z,,,83, 2015-05-22T04:00:00Z,,82,, dygraphs-2.2.1/auto_tests/data/out-of-order.csv010064400000000000000000000000401437353256000164300ustar00X,Y1,Y2 0,0,0 1,1,1 3,3,9 2,2,4 dygraphs-2.2.1/auto_tests/data/sample.csv010064400000000000000000000000401437353256000153670ustar00X,Y1,Y2 0,0,0 1,1,1 2,2,4 3,3,9 dygraphs-2.2.1/auto_tests/runner-min.html010064400000000000000000000046071437353256000154550ustar00 dygraphs tests
dygraphs-2.2.1/auto_tests/runner.html010064400000000000000000000045761437353256000147010ustar00 dygraphs tests
dygraphs-2.2.1/auto_tests/tests/CanvasAssertions.js010064400000000000000000000171251437353256000174620ustar00// Copyright (c) 2011 Google, Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. /** * @fileoverview Assertions and other code used to test a canvas proxy. * * @author konigsberg@google.com (Robert Konigsberg) */ var CanvasAssertions = {}; /** * Updates path attributes to match fill/stroke operations. * * This sets fillStyle to undefined for stroked paths, * and strokeStyle to undefined for filled paths, to simplify * matchers such as numLinesDrawn. * * @private * @param {Array.} List of operations. */ CanvasAssertions.cleanPathAttrs_ = function(calls) { var isStroked = true; for (var i = calls.length - 1; i >= 0; --i) { var call = calls[i]; var name = call.name; if (name == 'stroke') { isStroked = true; } else if (name == 'fill') { isStroked = false; } else if (name == 'lineTo') { if (isStroked) { call.properties.fillStyle = undefined; } else { call.properties.strokeStyle = undefined; } } } }; /** * Assert that a line is drawn between the two points * * This merely looks for one of these four possibilities: * moveTo(p1) -> lineTo(p2) * moveTo(p2) -> lineTo(p1) * lineTo(p1) -> lineTo(p2) * lineTo(p2) -> lineTo(p1) * * predicate is meant to be used when you want to track things like * color and stroke width. It can either be a hash of context properties, * or a function that accepts the current call. */ CanvasAssertions.assertLineDrawn = function(proxy, p1, p2, predicate) { CanvasAssertions.cleanPathAttrs_(proxy.calls__); // found = 1 when prior loop found p1. // found = 2 when prior loop found p2. var priorFound = 0; for (var i = 0; i < proxy.calls__.length; i++) { var call = proxy.calls__[i]; // This disables lineTo -> moveTo pairs. if (call.name == "moveTo" && priorFound > 0) { priorFound = 0; } var found = 0; if (call.name == "moveTo" || call.name == "lineTo") { var matchp1 = CanvasAssertions.matchPixels(p1, call.args); var matchp2 = CanvasAssertions.matchPixels(p2, call.args); if (matchp1 || matchp2) { if (priorFound == 1 && matchp2) { if (CanvasAssertions.match(predicate, call)) { return; } } if (priorFound == 2 && matchp1) { if (CanvasAssertions.match(predicate, call)) { return; } } found = matchp1 ? 1 : 2; } } priorFound = found; } var toString = function(x) { var s = "{"; for (var prop in x) { if (x.hasOwnProperty(prop)) { if (s.length > 1) { s = s + ", "; } s = s + prop + ": " + x[prop]; } } return s + "}"; }; throw "Can't find a line drawn between " + p1 + " and " + p2 + " with attributes " + toString(predicate); }; /** * Return the lines drawn with specific attributes. * * This merely looks for one of these four possibilities: * moveTo(p1) -> lineTo(p2) * moveTo(p2) -> lineTo(p1) * lineTo(p1) -> lineTo(p2) * lineTo(p2) -> lineTo(p1) * * attrs is meant to be used when you want to track things like * color and stroke width. */ CanvasAssertions.getLinesDrawn = function(proxy, predicate) { CanvasAssertions.cleanPathAttrs_(proxy.calls__); var lastCall; var lines = []; for (var i = 0; i < proxy.calls__.length; i++) { var call = proxy.calls__[i]; if (call.name == "lineTo") { if (lastCall != null) { if (CanvasAssertions.match(predicate, call)) { lines.push([lastCall, call]); } } } lastCall = (call.name === "lineTo" || call.name === "moveTo") ? call : null; } return lines; }; /** * Verifies that every call to context.save() has a matching call to * context.restore(). */ CanvasAssertions.assertBalancedSaveRestore = function(proxy) { var depth = 0; for (var i = 0; i < proxy.calls__.length; i++) { var call = proxy.calls__[i]; if (call.name == "save") depth++ if (call.name == "restore") { if (depth == 0) { fail("Too many calls to restore()"); } depth--; } } if (depth > 0) { fail("Missing matching 'context.restore()' calls."); } }; /** * Checks how many lines of the given color have been drawn. * @return {Integer} The number of lines of the given color. */ // TODO(konigsberg): change 'color' to predicate? color is the // common case. Possibly allow predicate to be function, hash, or // string representing color? CanvasAssertions.numLinesDrawn = function(proxy, color) { CanvasAssertions.cleanPathAttrs_(proxy.calls__); var num_lines = 0; var num_potential_calls = 0; for (var i = 0; i < proxy.calls__.length; i++) { var call = proxy.calls__[i]; if (call.name == "beginPath") { num_potential_calls = 0; } else if (call.name == "lineTo") { num_potential_calls++; } else if (call.name == "stroke") { // note: Don't simplify these two conditionals into one. The // separation simplifies debugging tricky tests. if (call.properties.strokeStyle == color) { num_lines += num_potential_calls; } num_potential_calls = 0; } } return num_lines; }; /** * Asserts that a series of lines are connected. For example, * assertConsecutiveLinesDrawn(proxy, [[x1, y1], [x2, y2], [x3, y3]], predicate) * is shorthand for * assertLineDrawn(proxy, [x1, y1], [x2, y2], predicate) * assertLineDrawn(proxy, [x2, y2], [x3, y3], predicate) */ CanvasAssertions.assertConsecutiveLinesDrawn = function(proxy, segments, predicate) { for (var i = 0; i < segments.length - 1; i++) { CanvasAssertions.assertLineDrawn(proxy, segments[i], segments[i+1], predicate); } } CanvasAssertions.matchPixels = function(expected, actual) { // Expect array of two integers. Assuming the values are within one // integer unit of each other. This should be tightened down by someone // who knows what pixel a value of 5.8888 results in. return Math.abs(expected[0] - actual[0]) < 1 && Math.abs(expected[1] - actual[1]) < 1; }; /** * For matching a proxy call against defined conditions. * predicate can either by a hash of items compared against call.properties, * or it can be a function that accepts the call, and returns true or false. * If it's null, this function returns true. */ CanvasAssertions.match = function(predicate, call) { if (predicate === null) { return true; } if (typeof(predicate) === "function") { return predicate(call); } else { for (var attr in predicate) { if (predicate.hasOwnProperty(attr) && predicate[attr] != call.properties[attr]) { return false; } } } return true; }; export default CanvasAssertions; dygraphs-2.2.1/auto_tests/tests/DygraphOps.js010064400000000000000000000134371437353256000162560ustar00// Copyright (c) 2011 Google, Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import * as utils from '../../src/dygraph-utils'; /** * @fileoverview Utility functions for Dygraphs. * * @author konigsberg@google.com (Robert Konigsberg) */ var DygraphOps = {}; DygraphOps.defaultEvent_ = { type : '', canBubble : true, cancelable : true, view : document.defaultView, detail : 0, screenX : 0, screenY : 0, clientX : 0, clientY : 0, ctrlKey : false, altKey : false, shiftKey : false, metaKey : false, button : 0, relatedTarget : null }; /** * Create an event. Sets default event values except for special ones * overridden by the 'custom' parameter. * * @param command the command to create. * @param custom an associative array of event attributes and their new values. */ DygraphOps.createEvent = function(command, custom) { var copy = function(from, to) { if (from != null) { for (var prop in from) { if(from.hasOwnProperty(prop)) { to[prop] = from[prop]; } } } } var e = {}; copy(DygraphOps.defaultEvent_, e); copy(command, e); copy(custom, e); var event = document.createEvent('MouseEvents'); event.initMouseEvent( e.type, e.canBubble, e.cancelable, e.view, e.detail, e.screenX, e.screenY, e.clientX, e.clientY, e.ctrlKey, e.altKey, e.shiftKey, e.metaKey, e.button, e.relatedTarget); return event; }; /** * Dispatch an event onto the graph's canvas. */ DygraphOps.dispatchCanvasEvent = function(g, event) { g.canvas_.dispatchEvent(event); }; DygraphOps.dispatchDoubleClick = function(g, custom) { var opts = { type : 'dblclick', detail : 2 }; var event = DygraphOps.createEvent(opts, custom); DygraphOps.dispatchCanvasEvent(g, event); }; /* * Create an 'opts' argument which can be passed to createEvent that contains * type, screenX, screenY, clientX, clientY. */ DygraphOps.createOptsForPoint_ = function(g, type, x, y) { var pos = utils.findPos(g.canvas_); var pageX = pos.x + x; var pageY = pos.y + y; return { type : type, screenX : pageX, screenY : pageY, clientX : pageX, clientY : pageY, }; }; DygraphOps.dispatchMouseDown_Point = function(g, x, y, custom) { var opts = DygraphOps.createOptsForPoint_(g, 'mousedown', x, y); opts.detail = 1; var event = DygraphOps.createEvent(opts, custom); DygraphOps.dispatchCanvasEvent(g, event); }; DygraphOps.dispatchMouseMove_Point = function(g, x, y, custom) { var opts = DygraphOps.createOptsForPoint_(g, 'mousemove', x, y); var event = DygraphOps.createEvent(opts, custom); DygraphOps.dispatchCanvasEvent(g, event); }; DygraphOps.dispatchMouseUp_Point = function(g, x, y, custom) { var opts = DygraphOps.createOptsForPoint_(g, 'mouseup', x, y); var event = DygraphOps.createEvent(opts, custom); DygraphOps.dispatchCanvasEvent(g, event); }; DygraphOps.dispatchMouseOver_Point = function(g, x, y, custom) { var opts = DygraphOps.createOptsForPoint_(g, 'mouseover', x, y); var event = DygraphOps.createEvent(opts, custom); DygraphOps.dispatchCanvasEvent(g, event); }; DygraphOps.dispatchMouseOut_Point = function(g, x, y, custom) { var opts = DygraphOps.createOptsForPoint_(g, 'mouseout', x, y); var event = DygraphOps.createEvent(opts, custom); DygraphOps.dispatchCanvasEvent(g, event); }; /** * Dispatches a mouse down using the graph's data coordinate system. * (The y value mapped to the first axis.) */ DygraphOps.dispatchMouseDown = function(g, x, y, custom) { DygraphOps.dispatchMouseDown_Point( g, g.toDomXCoord(x), g.toDomYCoord(y), custom); }; /** * Dispatches a mouse move using the graph's data coordinate system. * (The y value mapped to the first axis.) */ DygraphOps.dispatchMouseMove = function(g, x, y, custom) { DygraphOps.dispatchMouseMove_Point( g, g.toDomXCoord(x), g.toDomYCoord(y), custom); }; /** * Dispatches a mouse up using the graph's data coordinate system. * (The y value mapped to the first axis.) */ DygraphOps.dispatchMouseUp = function(g, x, y, custom) { DygraphOps.dispatchMouseUp_Point( g, g.toDomXCoord(x), g.toDomYCoord(y), custom); }; /** * Dispatches a mouse over using the graph's data coordinate system. * (The y value mapped to the first axis.) */ DygraphOps.dispatchMouseOver = function(g, x, y, custom) { DygraphOps.dispatchMouseOver_Point( g, g.toDomXCoord(x), g.toDomYCoord(y), custom); }; /** * Dispatches a mouse out using the graph's data coordinate system. * (The y value mapped to the first axis.) */ DygraphOps.dispatchMouseOut = function(g, x, y, custom) { DygraphOps.dispatchMouseOut_Point( g, g.toDomXCoord(x), g.toDomYCoord(y), custom); }; export default DygraphOps; dygraphs-2.2.1/auto_tests/tests/PixelSampler.js010064400000000000000000000041321437353256000165730ustar00// Copyright 2012 Google Inc. All Rights Reserved. /** * @fileoverview A class to facilitate sampling colors at particular pixels on a * dygraph. * @author danvk@google.com (Dan Vanderkam) * @license MIT */ 'use strict'; /** * @constructor */ var PixelSampler = function(dygraph) { this.dygraph_ = dygraph; var canvas = dygraph.hidden_; var ctx = canvas.getContext("2d"); this.imageData_ = ctx.getImageData(0, 0, canvas.width, canvas.height); this.scale = canvas.width / dygraph.width_; }; /** * @param {number} x The screen x-coordinate at which to sample. * @param {number} y The screen y-coordinate at which to sample. * @return {Array.} a 4D array: [R, G, B, alpha]. All four values * are in [0, 255]. A pixel which has never been touched will be [0,0,0,0]. */ PixelSampler.prototype.colorAtPixel = function(x, y) { var i = 4 * (x * this.scale + this.imageData_.width * y * this.scale); var d = this.imageData_.data; return [d[i], d[i+1], d[i+2], d[i+3]]; }; /** * Convenience wrapper around colorAtPixel if you only care about RGB (not A). * @param {number} x The screen x-coordinate at which to sample. * @param {number} y The screen y-coordinate at which to sample. * @return {Array.} a 3D array: [R, G, B]. All three values * are in [0, 255]. A pixel which has never been touched will be [0,0,0]. */ PixelSampler.prototype.rgbAtPixel = function(x, y) { return this.colorAtPixel(x, y).slice(0, 3); }; /** * The method samples a color using data coordinates (not screen coordinates). * This will round your data coordinates to the nearest screen pixel before * sampling. * @param {number} x The data x-coordinate at which to sample. * @param {number} y The data y-coordinate at which to sample. * @return {Array.} a 4D array: [R, G, B, alpha]. All four values * are in [0, 255]. A pixel which has never been touched will be [0,0,0,0]. */ PixelSampler.prototype.colorAtCoordinate = function(x, y) { var dom_xy = this.dygraph_.toDomCoords(x, y); return this.colorAtPixel(Math.round(dom_xy[0]), Math.round(dom_xy[1])); }; export default PixelSampler; dygraphs-2.2.1/auto_tests/tests/Proxy.js010064400000000000000000000053131437353256000153110ustar00// Copyright (c) 2011 Google, Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. /** * @fileoverview A general purpose object proxy that logs all method calls. * * @author konigsberg@google.com (Robert Konigsberg) */ var Proxy = function(delegate) { this.delegate__ = delegate; this.calls__ = []; this.propertiesToTrack__ = []; for (var propname in delegate) { var type = typeof(delegate[propname]); // Functions are passed through to the delegate, and are logged // prior to the call. if (type == "function") { function makeFunc(name) { return function() { this.log__(name, arguments); return this.delegate__[name].apply(this.delegate__, arguments); } }; this[propname] = makeFunc(propname); } else if (type == "string" || type == "number") { // String and number properties are just passed through to the delegate. this.propertiesToTrack__.push(propname); function makeSetter(name) { return function(x) { this.delegate__[name] = x; } }; this.__defineSetter__(propname, makeSetter(propname)); function makeGetter(name) { return function() { return this.delegate__[name]; } }; this.__defineGetter__(propname, makeGetter(propname)); } } }; Proxy.prototype.log__ = function(name, args) { var properties = {}; for (var propIdx in this.propertiesToTrack__) { var prop = this.propertiesToTrack__[propIdx]; properties[prop] = this.delegate__[prop]; } var call = { name : name, args : args, properties: properties }; this.calls__.push(call); }; Proxy.reset = function(proxy) { proxy.calls__ = []; } export default Proxy; dygraphs-2.2.1/auto_tests/tests/Util.js010064400000000000000000000112511437353256000151030ustar00/** * @fileoverview Utility functions for Dygraphs. * * @author konigsberg@google.com (Robert Konigsberg) */ import * as utils from '../../src/dygraph-utils'; var Util = {}; /** * Get the y-labels for a given axis. * * You can specify a parent if more than one graph is in the document. */ Util.getYLabels = function(axis_num, parent) { axis_num = axis_num || ""; parent = parent || document; var y_labels = parent.getElementsByClassName("dygraph-axis-label-y" + axis_num); var ary = []; for (var i = 0; i < y_labels.length; i++) { ary.push(y_labels[i].innerHTML.replace(/ | /g, ' ')); } return ary; }; /** * Get the x-labels for a given axis. * * You can specify a parent if more than one graph is in the document. */ Util.getXLabels = function(parent) { parent = parent || document; var x_labels = parent.getElementsByClassName("dygraph-axis-label-x"); var ary = []; for (var i = 0; i < x_labels.length; i++) { ary.push(x_labels[i].innerHTML.replace(/ | /g, ' ')); } return ary; }; /** * Returns all text in tags w/ a given css class, sorted. * You can specify a parent if more than one graph is on the document. */ Util.getClassTexts = function(css_class, parent) { parent = parent || document; var texts = []; var els = parent.getElementsByClassName(css_class); for (var i = 0; i < els.length; i++) { texts[i] = els[i].textContent; } texts.sort(); return texts; }; // Convert   to a normal space Util.nbspToSpace = function(str) { return str.replace(/ /g, ' '); }; Util.getLegend = function(parent) { parent = parent || document; var legend = parent.getElementsByClassName("dygraph-legend")[0]; return Util.nbspToSpace(legend.textContent); }; /** * Assert that all elements have a certain style property. */ Util.assertStyleOfChildren = function(selector, property, expectedValue) { assert.isTrue(selector.length > 0); for (var idx = 0; idx < selector.length; idx++) { var child = selector[idx]; assert.equal(expectedValue, window.getComputedStyle(child)[property]); } }; /** * Takes in an array of strings and returns an array of floats. */ Util.makeNumbers = function(ary) { var ret = []; for (var i = 0; i < ary.length; i++) { ret.push(parseFloat(ary[i])); } return ret; }; /** * Sample a pixel from the canvas. * Returns an [r, g, b, a] tuple where each values is in [0, 255]. * This is _very_ slow! If you want to sample many pixels, use PixelSampler. */ Util.samplePixel = function(canvas, x, y) { var ctx = canvas.getContext("2d"); // bypasses Proxy if applied. var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); var scale = utils.getContextPixelRatio(ctx); var i = 4 * (x * scale + imageData.width * y * scale); var d = imageData.data; return [d[i], d[i+1], d[i+2], d[i+3]]; }; /** * Overrides the browser's built-in XMLHttpRequest with a mock. * Usage: * * var mockXhr = Util.overrideXMLHttpRequest(your_data); * ... call code that does an XHR ... * mockXhr.respond(); // restores default behavior. * ... do your assertions ... */ Util.overrideXMLHttpRequest = function(data) { var originalXMLHttpRequest = XMLHttpRequest; var requests = []; var FakeXMLHttpRequest = function () { requests.push(this); }; FakeXMLHttpRequest.prototype.open = function () {}; FakeXMLHttpRequest.prototype.send = function () { this.readyState = 4; this.status = 200; this.responseText = data; }; FakeXMLHttpRequest.restore = function() { window.XMLHttpRequest = originalXMLHttpRequest; }; FakeXMLHttpRequest.respond = function() { for (var i = 0; i < requests.length; i++) { requests[i].onreadystatechange(); } FakeXMLHttpRequest.restore(); }; window.XMLHttpRequest = FakeXMLHttpRequest; return FakeXMLHttpRequest; }; /** * Format a date as 2000/01/23 * @param {number} dateMillis Millis since epoch. * @return {string} The date formatted as YYYY-MM-DD. */ Util.formatDate = function(dateMillis) { return utils.dateString_(dateMillis).slice(0, 10); // 10 == "YYYY/MM/DD".length }; /** * Capture console.{log,warn,error} statements into obj. * obj will look like {log:[], warn:[], error:[]} * This returns a function which will restore the original console. */ Util.captureConsole = function(obj) { obj.log = []; obj.warn = []; obj.error = []; var orig = [console.log, console.warn, console.error]; console.log = function(text) { obj.log.push(text); }; console.warn = function(text) { obj.warn.push(text); }; console.error = function(text) { obj.error.push(text); }; return function() { console.log = orig[0]; console.warn = orig[1]; console.error = orig[2]; }; }; export default Util; dygraphs-2.2.1/auto_tests/tests/annotations.js010064400000000000000000000141461437353256000165310ustar00/** * @fileoverview Tests relating to annotations * * @author danvk@google.com (Dan Vanderkam) */ import Dygraph from '../../src/dygraph'; import Util from './Util'; describe("annotations", function() { cleanupAfterEach(); it('testAnnotationsDrawn', function() { var opts = { width: 480, height: 320 }; var data = "X,Y\n" + "0,-1\n" + "1,0\n" + "2,1\n" + "3,0\n" ; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); g.setAnnotations([ { series: 'Y', x: 1, shortText: 'A', text: 'Long A', cssClass: 'ann1' }, { series: 'Y', x: 2, shortText: 'B', text: 'Long B', cssClass: 'ann2' } ]); assert.equal(2, g.annotations().length); var a1 = document.getElementsByClassName('ann1'); assert.equal(1, a1.length); a1 = a1[0]; assert.equal('A', a1.textContent); var a2 = document.getElementsByClassName('ann2'); assert.equal(1, a2.length); a2 = a2[0]; assert.equal('B', a2.textContent); }); // Some errors that should be flagged: // 1. Invalid series name (e.g. 'X' or 'non-existent') // 2. Passing a string as 'x' instead of a number (e.g. x: '1') it('testAnnotationsDontDisappearOnResize', function() { var opts = { }; var data = "X,Y\n" + "0,-1\n" + "1,0\n" + "2,1\n" + "3,0\n" ; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); g.setAnnotations([ { series: 'Y', x: 1, shortText: 'A', text: 'Long A', cssClass: 'ann1' } ]); // Check that it displays at all assert.equal(1, g.annotations().length); var a1 = document.getElementsByClassName('ann1'); assert.equal(1, a1.length); a1 = a1[0]; assert.equal('A', a1.textContent); // ... and that resizing doesn't kill it. g.resize(400, 300); assert.equal(1, g.annotations().length); var a1 = document.getElementsByClassName('ann1'); assert.equal(1, a1.length); a1 = a1[0]; assert.equal('A', a1.textContent); }); // Verify that annotations outside of the visible x-range are not shown. it('testAnnotationsOutOfRangeX', function() { var opts = { }; var data = "X,Y\n" + "0,-1\n" + "1,0\n" + "2,1\n" + "3,0\n" ; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); g.setAnnotations([ { series: 'Y', x: 1, shortText: 'A', text: 'Long A', cssClass: 'ann1' } ]); // Check that it displays at all assert.equal(1, g.annotations().length); var a1 = document.getElementsByClassName('ann1'); assert.equal(1, a1.length); a1 = a1[0]; assert.equal('A', a1.textContent); // ... and that panning right removes the annotation. g.updateOptions({dateWindow: [2, 6]}); assert.equal(1, g.annotations().length); a1 = document.getElementsByClassName('ann1'); assert.equal(0, a1.length); // ... and that panning left brings it back. g.updateOptions({dateWindow: [0, 4]}); assert.equal(1, g.annotations().length); a1 = document.getElementsByClassName('ann1'); assert.equal(1, a1.length); }); // Verify that annotations outside of the visible y-range are not shown. it('testAnnotationsOutOfRangeY', function() { var opts = { }; var data = "X,Y\n" + "0,-1\n" + "1,0\n" + "2,1\n" + "3,0\n" ; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); g.setAnnotations([ { series: 'Y', x: 1, shortText: 'A', text: 'Long A', cssClass: 'ann1' } ]); // ... check that panning up removes the annotation. g.updateOptions({valueRange: [0.5, 2.5]}); assert.equal(1, g.annotations().length); var a1 = document.getElementsByClassName('ann1'); assert.equal(0, a1.length); // ... and that panning down brings it back. g.updateOptions({valueRange: [-1, 1]}); assert.equal(1, g.annotations().length); a1 = document.getElementsByClassName('ann1'); assert.equal(1, a1.length); }); it('testAnnotationsDrawnInDrawCallback', function() { var data = "X,Y\n" + "0,-1\n" + "1,0\n" + "2,1\n"; var graph = document.getElementById("graph"); var calls = []; var g = new Dygraph(graph, data, { width: 480, height: 320, drawCallback: function(g, initial) { calls.push(initial); if (initial) { g.setAnnotations([ { series: 'Y', x: 1, shortText: 'A', text: 'Long A', }, ]); } } }); assert.deepEqual([true, false], calls); }); // Test that annotations on the same point are stacked. // Regression test for http://code.google.com/p/dygraphs/issues/detail?id=256 it('testAnnotationsStacked', function() { var data = 'X,Y1,Y2\n' + '0,1,2\n' + '1,2,3\n'; var graph = document.getElementById("graph"); var annotations = [ { series: 'Y1', x: 0, shortText: '1', attachAtBottom: true }, { series: 'Y2', x: 0, shortText: '2', attachAtBottom: true } ]; var g = new Dygraph(graph, data, { width: 480, height: 320 }); g.setAnnotations(annotations); var annEls = document.getElementsByClassName('dygraphDefaultAnnotation'); assert.equal(2, annEls.length); assert.equal(annEls[0].offsetLeft, annEls[1].offsetLeft); assert(annEls[1].offsetTop < annEls[0].offsetTop - 10); }); // Test the .ready() method, which is most often used with setAnnotations(). it('testReady', function() { var data = 'X,Y1,Y2\n' + '0,1,2\n' + '1,2,3\n'; var mockXhr = Util.overrideXMLHttpRequest(data); var graph = document.getElementById("graph"); var g = new Dygraph(graph, "data.csv", { width: 480, height: 320 }); var ready_calls = 0; g.ready(function() { ready_calls++; }); assert.equal(0, ready_calls); mockXhr.respond(); assert.equal(1, ready_calls); // Make sure that ready isn't called on redraws. g.updateOptions({}); assert.equal(1, ready_calls); // Or data changes. g.updateOptions({file: data}); assert.equal(1, ready_calls); }); }); dygraphs-2.2.1/auto_tests/tests/axis_labels.js010064400000000000000000001022241437353256000164550ustar00/** * @fileoverview Test cases for how axis labels are chosen and formatted. * * @author dan@dygraphs.com (Dan Vanderkam) */ import Dygraph from '../../src/dygraph'; import * as utils from '../../src/dygraph-utils'; import DEFAULT_ATTRS from '../../src/dygraph-default-attrs'; import Util from './Util'; import {assertDeepCloseTo} from './custom_asserts'; describe("axis-labels", function() { cleanupAfterEach(); var simpleData = "X,Y,Y2\n" + "0,-1,.5\n" + "1,0,.7\n" + "2,1,.4\n" + "3,0,.98\n"; var kCloseFloat = 1.0e-10; it('testMinusOneToOne', function() { var opts = { width: 480, height: 320 }; var data = "X,Y\n" + "0,-1\n" + "1,0\n" + "2,1\n" + "3,0\n" ; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); // TODO(danvk): would ['-1.0','-0.5','0.0','0.5','1.0'] be better? assert.deepEqual(['-1','-0.5','0','0.5','1'], Util.getYLabels()); // Go up to 2 data += "4,2\n"; g.updateOptions({file: data}); assert.deepEqual(['-1','-0.5','0','0.5','1','1.5','2'], Util.getYLabels()); // Now 10 data += "5,10\n"; g.updateOptions({file: data}); assert.deepEqual(['-2','0','2','4','6','8','10'], Util.getYLabels()); // Now 100 data += "6,100\n"; g.updateOptions({file: data}); assert.deepEqual(['0','20','40','60','80','100'], Util.getYLabels()); g.setSelection(0); assert.equal('0: Y: -1', Util.getLegend()); }); it('testSmallRangeNearZero', function() { var opts = { drawAxesAtZero: true, width: 480, height: 320 }; var data = "X,Y\n" + "0,-1\n" + "1,0\n" + "2,1\n" + "3,0\n" ; opts.valueRange = [-0.1, 0.1]; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); assertDeepCloseTo([-0.1,-0.05,0,0.05], Util.makeNumbers(Util.getYLabels()), kCloseFloat); opts.valueRange = [-0.05, 0.05]; g.updateOptions(opts); assert.deepEqual([-0.04,-0.02,0,0.02,0.04], Util.makeNumbers(Util.getYLabels())); opts.valueRange = [-0.01, 0.01]; g.updateOptions(opts); assert.deepEqual([-0.01,-0.005,0,0.005], Util.makeNumbers(Util.getYLabels())); g.setSelection(1); assert.equal('1: Y: 0', Util.getLegend()); }); it('testSmallRangeAwayFromZero', function() { var opts = { width: 480, height: 320 }; var data = "X,Y\n" + "0,-1\n" + "1,0\n" + "2,1\n" + "3,0\n" ; var graph = document.getElementById("graph"); opts.valueRange = [9.9, 10.1]; var g = new Dygraph(graph, data, opts); assert.deepEqual(["9.9","9.92","9.94","9.96","9.98","10","10.02","10.04","10.06","10.08"], Util.getYLabels()); opts.valueRange = [9.99, 10.01]; g.updateOptions(opts); // TODO(danvk): this is bad assert.deepEqual(["9.99","9.99","9.99","10","10","10","10","10","10.01","10.01"], Util.getYLabels()); opts.valueRange = [9.999, 10.001]; g.updateOptions(opts); // TODO(danvk): this is even worse! assert.deepEqual(["10","10","10","10"], Util.getYLabels()); g.setSelection(1); assert.equal('1: Y: 0', Util.getLegend()); }); it('testXAxisTimeLabelFormatter', function() { var opts = { width: 480, height: 320, labels: ['X', 'Y1'] }; var data = [[5.0,0],[5.1,1],[5.2,2],[5.3,3],[5.4,4],[5.5,5],[5.6,6],[5.7,7],[5.8,8],[5.9,9]]; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); g.updateOptions({ axes : { x : { axisLabelFormatter : function (totalMinutes) { var hours = Math.floor( totalMinutes / 60); var minutes = Math.floor((totalMinutes - (hours * 60))); var seconds = Math.round((totalMinutes * 60) - (hours * 3600) - (minutes * 60)); if (hours < 10) hours = "0" + hours; if (minutes < 10) minutes = "0" + minutes; if (seconds < 10) seconds = "0" + seconds; return hours + ':' + minutes + ':' + seconds; } } } }); assert.deepEqual(["00:05:00","00:05:12","00:05:24","00:05:36","00:05:48"], Util.getXLabels()); // The legend does not use the axisLabelFormatter: g.setSelection(1); assert.equal('5.1: Y1: 1', Util.getLegend()); }); it('testAxisLabelFormatter', function() { var opts = { width: 480, height: 320, axes : { x : { axisLabelFormatter : function(x, granularity, opts, dg) { assert.equal('number', typeof(x)); assert.equal('number', typeof(granularity)); assert.equal('function', typeof(opts)); assert.equal('[Dygraph graph]', dg.toString()); return 'x' + x; } }, y : { axisLabelFormatter : function(y, granularity, opts, dg) { assert.equal('number', typeof(y)); assert.equal('number', typeof(granularity)); assert.equal('function', typeof(opts)); assert.equal('[Dygraph graph]', dg.toString()); return 'y' + y; } } }, labels: ['x', 'y'] }; var data = []; for (var i = 0; i < 10; i++) { data.push([i, 2 * i]); } var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); assert.deepEqual(['x0','x2','x4','x6','x8'], Util.getXLabels()); assert.deepEqual(["y0","y5","y10","y15"], Util.getYLabels()); g.setSelection(2); assert.equal("2: y: 4", Util.getLegend()); }); it('testDateAxisLabelFormatter', function() { var opts = { width: 480, height: 320, axes : { x : { pixelsPerLabel: 60, axisLabelFormatter : function(x, granularity, opts, dg) { assert.isTrue(utils.isDateLike(x)); assert.equal('number', typeof(granularity)); assert.equal('function', typeof(opts)); assert.equal('[Dygraph graph]', dg.toString()); return 'x' + Util.formatDate(x); } }, y : { axisLabelFormatter : function(y, granularity, opts, dg) { assert.equal('number', typeof(y)); assert.equal('number', typeof(granularity)); assert.equal('function', typeof(opts)); assert.equal('[Dygraph graph]', dg.toString()); return 'y' + y; } } }, labels: ['x', 'y'] }; var data = []; for (var i = 1; i < 10; i++) { data.push([new Date("2011/01/0" + i), 2 * i]); } var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); assert.deepEqual(["x2011/01/02","x2011/01/04","x2011/01/06","x2011/01/08"], Util.getXLabels()); assert.deepEqual(["y5","y10","y15"], Util.getYLabels()); g.setSelection(0); assert.equal("2011/01/01: y: 2", Util.getLegend()); }); // This test verifies that when a valueFormatter is set (but not an // axisLabelFormatter), then the valueFormatter is used to format the axis // labels. it('testValueFormatter', function() { var opts = { width: 480, height: 320, axes: { x: { valueFormatter: function(x, opts, series_name, dg, row, col) { assert.equal('number', typeof(x)); assert.equal('function', typeof(opts)); assert.equal('string', typeof(series_name)); assert.equal('[Dygraph graph]', dg.toString()); assert.equal('number', typeof(row)); assert.equal('number', typeof(col)); assert.equal(dg, this); return 'x' + x; } }, y: { valueFormatter: function(y, opts, series_name, dg, row, col) { assert.equal('number', typeof(y)); assert.equal('function', typeof(opts)); assert.equal('string', typeof(series_name)); assert.equal('[Dygraph graph]', dg.toString()); assert.equal('number', typeof(row)); assert.equal('number', typeof(col)); assert.equal(dg, this); return 'y' + y; } } }, labels: ['x', 'y'] }; var data = []; for (var i = 0; i < 10; i++) { data.push([i, 2 * i]); } var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); // the valueFormatter options do not affect the ticks. assert.deepEqual(['0','2','4','6','8'], Util.getXLabels()); assert.deepEqual(["0","5","10","15"], Util.getYLabels()); // they do affect the legend, however. g.setSelection(2); assert.equal("x2: y: y4", Util.getLegend()); }); it('testDateValueFormatter', function() { var opts = { width: 480, height: 320, axes : { x : { pixelsPerLabel: 60, valueFormatter: function(x, opts, series_name, dg, row, col) { assert.equal('number', typeof(x)); assert.equal('function', typeof(opts)); assert.equal('string', typeof(series_name)); assert.equal('[Dygraph graph]', dg.toString()); assert.equal('number', typeof(row)); assert.equal('number', typeof(col)); assert.equal(dg, this); return 'x' + Util.formatDate(x); } }, y : { valueFormatter: function(y, opts, series_name, dg, row, col) { assert.equal('number', typeof(y)); assert.equal('function', typeof(opts)); assert.equal('string', typeof(series_name)); assert.equal('[Dygraph graph]', dg.toString()); assert.equal('number', typeof(row)); assert.equal('number', typeof(col)); assert.equal(dg, this); return 'y' + y; } } }, labels: ['x', 'y'] }; var data = []; for (var i = 1; i < 10; i++) { data.push([new Date("2011/01/0" + i), 2 * i]); } var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); // valueFormatters do not affect ticks. assert.deepEqual(["02 Jan","04 Jan","06 Jan","08 Jan"], Util.getXLabels()); assert.deepEqual(["5","10","15"], Util.getYLabels()); // the valueFormatter options also affect the legend. g.setSelection(2); assert.equal('x2011/01/03: y: y6', Util.getLegend()); }); // This test verifies that when both a valueFormatter and an axisLabelFormatter // are specified, the axisLabelFormatter takes precedence. it('testAxisLabelFormatterPrecedence', function() { var opts = { width: 480, height: 320, axes : { x : { valueFormatter: function(x) { assert.equal('[Dygraph graph]', this.toString()); return 'xvf' + x; }, axisLabelFormatter: function(x, granularity) { assert.equal('[Dygraph graph]', this.toString()); return 'x' + x; } }, y : { valueFormatter: function(y) { assert.equal('[Dygraph graph]', this.toString()); return 'yvf' + y; }, axisLabelFormatter: function(y) { assert.equal('[Dygraph graph]', this.toString()); return 'y' + y; } } }, labels: ['x', 'y'] }; var data = []; for (var i = 0; i < 10; i++) { data.push([i, 2 * i]); } var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); assert.deepEqual(['x0','x2','x4','x6','x8'], Util.getXLabels()); assert.deepEqual(["y0","y5","y10","y15"], Util.getYLabels()); g.setSelection(9); assert.equal("xvf9: y: yvf18", Util.getLegend()); }); // This is the same as the previous test, except that options are added // one-by-one. it('testAxisLabelFormatterIncremental', function() { var opts = { width: 480, height: 320, labels: ['x', 'y'] }; var data = []; for (var i = 0; i < 10; i++) { data.push([i, 2 * i]); } var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); g.updateOptions({ axes : { x : { valueFormatter: function(x) { return 'xvf' + x; } } } }); g.updateOptions({ axes : { y : { valueFormatter: function(y) { return 'yvf' + y; } } } }); g.updateOptions({ axes : { x : { axisLabelFormatter: function(x, granularity) { return 'x' + x; } } } }); g.updateOptions({ axes : { y : { axisLabelFormatter: function(y) { return 'y' + y; } } } }); assert.deepEqual(["x0","x2","x4","x6","x8"], Util.getXLabels()); assert.deepEqual(["y0","y5","y10","y15"], Util.getYLabels()); g.setSelection(9); assert.equal("xvf9: y: yvf18", Util.getLegend()); }); it('testGlobalFormatters', function() { var opts = { width: 480, height: 320, labels: ['x', 'y'], valueFormatter: function(x) { assert.equal('[Dygraph graph]', this); return 'vf' + x; }, axisLabelFormatter: function(x) { assert.equal('[Dygraph graph]', this); return 'alf' + x; } }; var data = []; for (var i = 0; i < 10; i++) { data.push([i, 2 * i]); } var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); assert.deepEqual(['alf0','alf2','alf4','alf6','alf8'], Util.getXLabels()); assert.deepEqual(["alf0","alf5","alf10","alf15"], Util.getYLabels()); g.setSelection(9); assert.equal("vf9: y: vf18", Util.getLegend()); }); it('testValueFormatterParameters', function() { var calls = []; // change any functions in list to 'fn' -- functions can't be asserted. var killFunctions = function(list) { var out = []; for (var i = 0; i < list.length; i++) { if (typeof(list[i]) == 'function') { out[i] = 'fn'; } else { out[i] = list[i]; } } return out; }; var taggedRecorder = function(tag) { return function() { calls.push([tag].concat([this], killFunctions(arguments))); return ''; } }; var opts = { axes: { x: { valueFormatter: taggedRecorder('x') }, y: { valueFormatter: taggedRecorder('y') }, y2: { valueFormatter: taggedRecorder('y2') } }, series: { 'y1': { axis: 'y1'}, 'y2': { axis: 'y2'} }, labels: ['x', 'y1', 'y2'] }; var data = [ [0, 1, 2], [1, 3, 4] ]; var graph = document.getElementById('graph'); var g = new Dygraph(graph, data, opts); assert.deepEqual([], calls); g.setSelection(0); assert.deepEqual([ // num or millis, opts, series, dygraph, row, col [ 'x', g, 0, 'fn', 'x', g, 0, 0], [ 'y', g, 1, 'fn', 'y1', g, 0, 1], ['y2', g, 2, 'fn', 'y2', g, 0, 2] ], calls); calls = []; g.setSelection(1); assert.deepEqual([ [ 'x', g, 1, 'fn', 'x', g, 1, 0], [ 'y', g, 3, 'fn', 'y1', g, 1, 1], ['y2', g, 4, 'fn', 'y2', g, 1, 2] ], calls); }); it('testSeriesOrder', function() { var opts = { width: 480, height: 320 }; var data = "x,00,01,10,11\n" + "0,101,201,301,401\n" + "1,102,202,302,402\n" + "2,103,203,303,403\n" + "3,104,204,304,404\n" ; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); g.setSelection(2); assert.equal('2: 00: 103 01: 203 10: 303 11: 403', Util.getLegend()); // Sanity checks for indexFromSetName assert.equal(0, g.indexFromSetName("x")); assert.equal(1, g.indexFromSetName("00")); assert.equal(null, g.indexFromSetName("abcde")); // Verify that we get the label list back in the right order assert.deepEqual(["x", "00", "01", "10", "11"], g.getLabels()); }); it('testLabelKMB', function() { var data = []; data.push([0,0]); data.push([1,2000]); data.push([2,1000]); var g = new Dygraph( document.getElementById("graph"), data, { labels: [ 'X', 'bar' ], axes : { y: { labelsKMB: true } } } ); assert.deepEqual(["0", "500", "1k", "1.5k", "2k"], Util.getYLabels()); }); it('testLabelKMG2', function() { var data = []; data.push([0,0]); data.push([1,2000]); data.push([2,1000]); var g = new Dygraph( document.getElementById("graph"), data, { labels: [ 'X', 'bar' ], axes : { y: { labelsKMG2: true } } } ); assert.deepEqual(["0","256","512","768","1Ki","1.25Ki","1.5Ki","1.75Ki","2Ki"], Util.getYLabels()); }); // Same as testLabelKMG2 but specifies the option at the // top of the option dictionary. it('testLabelKMG2_top', function() { var data = []; data.push([0,0]); data.push([1,2000]); data.push([2,1000]); var g = new Dygraph( document.getElementById("graph"), data, { labels: [ 'X', 'bar' ], labelsKMG2: true } ); assert.deepEqual( ["0","256","512","768","1Ki","1.25Ki","1.5Ki","1.75Ki","2Ki"], Util.getYLabels()); }); it('testSmallLabelKMB', function() { var data = []; data.push([0, 0]); data.push([1, 1e-6]); data.push([2, 2e-6]); var g = new Dygraph( document.getElementById("graph"), data, { labels: [ 'X', 'bar' ], axes : { y: { labelsKMB: true } } } ); assert.deepEqual(['0', '500n', '1µ', '1.5µ', '2µ'], Util.getYLabels()); }); it('testSmallLabelKMG2', function() { var data = []; data.push([0, 0]); data.push([1, 1 * (2**-20)]); data.push([2, 2 * (2**-20)]); var g = new Dygraph( document.getElementById("graph"), data, { labels: [ 'X', 'bar' ], axes : { y: { labelsKMG2: true } } } ); assert.deepEqual(['0', '256p-30', '512p-30', '768p-30', '1p-20', '1.25p-20', '1.5p-20', '1.75p-20', '2p-20'], Util.getYLabels()); }); it('testSmallLabelKMG2legacy', function() { var data = []; data.push([0, 0]); data.push([1, 1 * (2**-20)]); data.push([2, 2 * (2**-20)]); var g = new Dygraph( document.getElementById("graph"), data, { labels: [ 'X', 'bar' ], axes : { y: { labelsKMB: true, labelsKMG2: true } } } ); assert.deepEqual(['0', '256n', '512n', '768n', '1µ', '1.25µ', '1.5µ', '1.75µ', '2µ'], Util.getYLabels()); }); /** * Verify that log scale axis range is properly specified. */ it('testLogScale', function() { var g = new Dygraph("graph", [[0, 5], [1, 1000]], { logscale: true, labels: ['X', 'Y'] }); var nonEmptyLabels = Util.getYLabels().filter(function(x) { return x.length > 0; }); assert.deepEqual(["5","10","20","50","100","200","500","1000"], nonEmptyLabels); g.updateOptions({ logscale : false }); assert.deepEqual(['0','200','400','600','800','1000'], Util.getYLabels()); }); /** * Verify that log scale axis range works with yRangePad. * * This is a regression test for https://github.com/danvk/dygraphs/issues/661 . */ it('testLogScalePad', function() { var g = new Dygraph("graph", [[0, 1e-5], [1, 0.25], [2, 1], [3, 3], [4, 10]], { width: 250, height: 130, logscale: true, yRangePad: 30, axes: {y: {valueRange: [1, 10]}}, labels: ['X', 'Y'] }); var nonEmptyLabels = Util.getYLabels().filter(function(x) { return x.length > 0; }); assert.deepEqual(['1', '7', '30'], nonEmptyLabels); g.updateOptions({ yRangePad: 10, axes: {y: {valueRange: [0.25005, 3]}} }); nonEmptyLabels = Util.getYLabels().filter(function(x) { return x.length > 0; }); assert.deepEqual(['0.4', '1', '3'], nonEmptyLabels); g.updateOptions({ axes: {y: {valueRange: [0.01, 3]}} }); nonEmptyLabels = Util.getYLabels().filter(function(x) { return x.length > 0; }); assert.deepEqual(['0.01','0.1','0.7','5'], nonEmptyLabels); }); /** * Verify that include zero range is properly specified. */ it('testIncludeZero', function() { var g = new Dygraph("graph", [[0, 500], [1, 1000]], { includeZero: true, labels: ['X', 'Y1'] }); assert.deepEqual(['0','200','400','600','800','1000'], Util.getYLabels()); g.updateOptions({ includeZero : false }); assert.deepEqual(['500','600','700','800','900','1000'], Util.getYLabels()); }); it('testAxisLabelFontSize', function() { var graph = document.getElementById("graph"); var g = new Dygraph(graph, simpleData, {}); // Be sure we're dealing with a 14-point default. assert.equal(14, DEFAULT_ATTRS.axisLabelFontSize); var assertFontSize = function(selector, expected) { Util.assertStyleOfChildren(selector, "font-size", expected); } assertFontSize(document.querySelectorAll(".dygraph-axis-label-x"), "14px"); assertFontSize(document.querySelectorAll(".dygraph-axis-label-y"), "14px"); g.updateOptions({axisLabelFontSize : 8}); assertFontSize(document.querySelectorAll(".dygraph-axis-label-x"), "8px"); assertFontSize(document.querySelectorAll(".dygraph-axis-label-y"), "8px"); g.updateOptions({ axisLabelFontSize : null, axes: { x: { axisLabelFontSize : 5 }, } }); assertFontSize(document.querySelectorAll(".dygraph-axis-label-x"), "5px"); assertFontSize(document.querySelectorAll(".dygraph-axis-label-y"), "14px"); g.updateOptions({ axes: { y: { axisLabelFontSize : 20 }, } }); assertFontSize(document.querySelectorAll(".dygraph-axis-label-x"), "5px"); assertFontSize(document.querySelectorAll(".dygraph-axis-label-y"), "20px"); g.updateOptions({ series: { Y2: { axis : "y2" } // copy y2 series to y2 axis. }, axes: { y2: { axisLabelFontSize : 12 }, } }); assertFontSize(document.querySelectorAll(".dygraph-axis-label-x"), "5px"); assertFontSize(document.querySelectorAll(".dygraph-axis-label-y1"), "20px"); assertFontSize(document.querySelectorAll(".dygraph-axis-label-y2"), "12px"); }); it('testAxisLabelFontSizeNull', function() { var graph = document.getElementById("graph"); var g = new Dygraph(graph, simpleData, { axisLabelFontSize: null }); var assertFontSize = function(selector, expected) { Util.assertStyleOfChildren(selector, "font-size", expected); }; // Be sure we're dealing with a 14-point default. assert.equal(14, DEFAULT_ATTRS.axisLabelFontSize); assertFontSize(document.querySelectorAll(".dygraph-axis-label-x"), "14px"); assertFontSize(document.querySelectorAll(".dygraph-axis-label-y"), "14px"); }); /* * This test shows that the label formatter overrides labelsKMB for all values. */ it('testLabelFormatterOverridesLabelsKMB', function() { var g = new Dygraph( document.getElementById("graph"), "X,a,b\n" + "1,0,2000\n" + "2,500,1500\n" + "3,1000,1000\n" + "4,2000,0\n", { labelsKMB: true, axisLabelFormatter: function (v) { return v + ":X"; } }); assert.deepEqual(["0:X","500:X","1000:X","1500:X","2000:X"], Util.getYLabels()); assert.deepEqual(["1:X","2:X","3:X"], Util.getXLabels()); }); /* * This test shows that you can override labelsKMB on the axis level. */ it('testLabelsKMBPerAxis', function() { var g = new Dygraph( document.getElementById("graph"), "x,a,b\n" + "1000,0,2000\n" + "2000,500,1500\n" + "3000,1000,1000\n" + "4000,2000,0\n", { labelsKMB: false, axes: { y2: { labelsKMB: true }, x: { labelsKMB: true } }, series: { b: { axis: "y2" } } }); // labelsKMB doesn't apply to the x axis. This value should be different. // BUG : https://code.google.com/p/dygraphs/issues/detail?id=488 assert.deepEqual(["1000","2000","3000"], Util.getXLabels()); assert.deepEqual(["0","500","1000","1500","2000"], Util.getYLabels(1)); assert.deepEqual(["0","500","1k","1.5k","2k"], Util.getYLabels(2)); }); /* * This test shows that you can override labelsKMG2 on the axis level. */ it('testLabelsKMG2PerAxis', function() { var g = new Dygraph( document.getElementById("graph"), "x,a,b\n" + "1000,0,2000\n" + "2000,500,1500\n" + "3000,1000,1000\n" + "4000,2000,0\n", { labelsKMG2: false, axes: { y2: { labelsKMG2: true }, x: { labelsKMG2: true, pixelsPerLabel: 60 } }, series: { b: { axis: "y2" } } }); // It is weird that labelsKMG2 does something on the x axis but KMB does not. // Plus I can't be sure they're doing the same thing as they're done in different // bits of code. // BUG : https://code.google.com/p/dygraphs/issues/detail?id=488 assert.deepEqual(["1024","2048","3072"], Util.getXLabels()); assert.deepEqual(["0","500","1000","1500","2000"], Util.getYLabels(1)); assert.deepEqual(["0","500","1000","1.46Ki","1.95Ki"], Util.getYLabels(2)); }); /** * This test shows you can override sigFigs on the axis level. */ it('testSigFigsPerAxis', function() { var g = new Dygraph( document.getElementById("graph"), "x,a,b\n" + "1000,0,2000\n" + "2000,500,1500\n" + "3000,1000,1000\n" + "4000,2000,0\n", { sigFigs: 2, axes: { y2: { sigFigs: 6 }, x: { sigFigs: 8 } }, series: { b: { axis: "y2" } } }); // sigFigs doesn't apply to the x axis. This value should be different. // BUG : https://code.google.com/p/dygraphs/issues/detail?id=488 assert.deepEqual(["1000","2000","3000"], Util.getXLabels()); assert.deepEqual(["0.0","5.0e+2","1.0e+3","1.5e+3","2.0e+3"], Util.getYLabels(1)); assert.deepEqual(["0.00000","500.000","1000.00","1500.00","2000.00"], Util.getYLabels(2)); }); /** * This test shows you can override digitsAfterDecimal on the axis level. */ it('testDigitsAfterDecimalPerAxis', function() { var g = new Dygraph( document.getElementById("graph"), "x,a,b\n" + "0.006,0.001,0.008\n" + "0.007,0.002,0.007\n" + "0.008,0.003,0.006\n" + "0.009,0.004,0.005\n", { digitsAfterDecimal: 1, series: { b: { axis: "y2" } } }); g.updateOptions({ axes: { y: { digitsAfterDecimal: 3 }}}); assert.deepEqual(["0.001","0.002","0.002","0.003","0.003","0.004","0.004"], Util.getYLabels(1)); g.updateOptions({ axes: { y: { digitsAfterDecimal: 4 }}}); assert.deepEqual(["0.001","0.0015","0.002","0.0025","0.003","0.0035","0.004"], Util.getYLabels(1)); g.updateOptions({ axes: { y: { digitsAfterDecimal: 5 }}}); assert.deepEqual(["0.001","0.0015","0.002","0.0025","0.003","0.0035","0.004"], Util.getYLabels(1)); g.updateOptions({ axes: { y: { digitsAfterDecimal: null }}}); assert.deepEqual(["1e-3","2e-3","2e-3","3e-3","3e-3","4e-3","4e-3"], Util.getYLabels(1)); g.updateOptions({ axes: { y2: { digitsAfterDecimal: 3 }}}); assert.deepEqual(["0.005","0.006","0.006","0.007","0.007","0.008","0.008"], Util.getYLabels(2)); g.updateOptions({ axes: { y2: { digitsAfterDecimal: 4 }}}); assert.deepEqual(["0.005","0.0055","0.006","0.0065","0.007","0.0075","0.008"], Util.getYLabels(2)); g.updateOptions({ axes: { y2: { digitsAfterDecimal: 5 }}}); assert.deepEqual(["0.005","0.0055","0.006","0.0065","0.007","0.0075","0.008"], Util.getYLabels(2)); g.updateOptions({ axes: { y2: { digitsAfterDecimal: null }}}); assert.deepEqual(["5e-3","6e-3","6e-3","7e-3","7e-3","7e-3","8e-3"], Util.getYLabels(2)); // digitsAfterDecimal is ignored for the x-axis. // BUG : https://code.google.com/p/dygraphs/issues/detail?id=488 g.updateOptions({ axes: { x: { digitsAfterDecimal: 3 }}}); assert.deepEqual(["0.006","0.007","0.008"], Util.getXLabels()); g.updateOptions({ axes: { x: { digitsAfterDecimal: 4 }}}); assert.deepEqual(["0.006","0.007","0.008"], Util.getXLabels()); g.updateOptions({ axes: { x: { digitsAfterDecimal: 5 }}}); assert.deepEqual(["0.006","0.007","0.008"], Util.getXLabels()); g.updateOptions({ axes: { x: { digitsAfterDecimal: null }}}); assert.deepEqual(["0.006","0.007","0.008"], Util.getXLabels()); }); /** * This test shows you can override digitsAfterDecimal on the axis level. */ it('testMaxNumberWidthPerAxis', function() { var g = new Dygraph( document.getElementById("graph"), "x,a,b\n" + "12401,12601,12804\n" + "12402,12602,12803\n" + "12403,12603,12802\n" + "12404,12604,12801\n", { maxNumberWidth: 1, series: { b: { axis: "y2" } } }); g.updateOptions({ axes: { y: { maxNumberWidth: 4 }}}); assert.deepEqual(["1.26e+4","1.26e+4","1.26e+4","1.26e+4","1.26e+4","1.26e+4","1.26e+4"] , Util.getYLabels(1)); g.updateOptions({ axes: { y: { maxNumberWidth: 5 }}}); assert.deepEqual(["12601","12601.5","12602","12602.5","12603","12603.5","12604"] , Util.getYLabels(1)); g.updateOptions({ axes: { y: { maxNumberWidth: null }}}); assert.deepEqual(["1.26e+4","1.26e+4","1.26e+4","1.26e+4","1.26e+4","1.26e+4","1.26e+4"] , Util.getYLabels(1)); g.updateOptions({ axes: { y2: { maxNumberWidth: 4 }}}); assert.deepEqual(["1.28e+4","1.28e+4","1.28e+4","1.28e+4","1.28e+4","1.28e+4","1.28e+4"], Util.getYLabels(2)); g.updateOptions({ axes: { y2: { maxNumberWidth: 5 }}}); assert.deepEqual(["12801","12801.5","12802","12802.5","12803","12803.5","12804"], Util.getYLabels(2)); g.updateOptions({ axes: { y2: { maxNumberWidth: null }}}); assert.deepEqual(["1.28e+4","1.28e+4","1.28e+4","1.28e+4","1.28e+4","1.28e+4","1.28e+4"], Util.getYLabels(2)); // maxNumberWidth is ignored for the x-axis. // BUG : https://code.google.com/p/dygraphs/issues/detail?id=488 g.updateOptions({ axes: { x: { maxNumberWidth: 4 }}}); assert.deepEqual(["12401","12402","12403"], Util.getXLabels()); g.updateOptions({ axes: { x: { maxNumberWidth: 5 }}}); assert.deepEqual(["12401","12402","12403"], Util.getXLabels()); g.updateOptions({ axes: { x: { maxNumberWidth: null }}}); assert.deepEqual(["12401","12402","12403"], Util.getXLabels()); }); /* // Regression test for http://code.google.com/p/dygraphs/issues/detail?id=147 // Checks that axis labels stay sane across a DST change. it('testLabelsCrossDstChange', function() { // (From tests/daylight-savings.html) var g = new Dygraph( document.getElementById("graph"), "Date/Time,Purchases\n" + "2010-11-05 00:00:00,167082\n" + "2010-11-06 00:00:00,168571\n" + "2010-11-07 00:00:00,177796\n" + "2010-11-08 00:00:00,165587\n" + "2010-11-09 00:00:00,164380\n", { width: 1024 } ); // Dates and "nice" hours: 6AM/PM and noon, not 5AM/11AM/... var okLabels = { '05Nov': true, '06Nov': true, '07Nov': true, '08Nov': true, '09Nov': true, '06:00': true, '12:00': true, '18:00': true }; var xLabels = Util.getXLabels(); for (var i = 0; i < xLabels.length; i++) { assert.isTrue(okLabels[xLabels[i]]); } // This range had issues of its own on tests/daylight-savings.html. g.updateOptions({ dateWindow: [1289109997722.8127, 1289261208937.7659] }); xLabels = Util.getXLabels(); for (var i = 0; i < xLabels.length; i++) { assert.isTrue(okLabels[xLabels[i]]); } }); // Tests data which crosses a "fall back" at a high enough frequency that you // can see both 1:00 A.M.s. it('testLabelsCrossDstChangeHighFreq', function() { // Generate data which crosses the EST/EDT boundary. var dst_data = []; var base_ms = 1383454200000; for (var x = base_ms; x < base_ms + 1000 * 60 * 80; x += 1000) { dst_data.push([new Date(x), x]); } var g = new Dygraph( document.getElementById("graph"), dst_data, { width: 1024, labels: ['Date', 'Value'] } ); assert.deepEqual([ '00:50', '00:55', '01:00', '01:05', '01:10', '01:15', '01:20', '01:25', '01:30', '01:35', '01:40', '01:45', '01:50', '01:55', '01:00', '01:05' // 1 AM number two! ], Util.getXLabels()); // Now zoom past the initial 1 AM. This used to cause trouble. g.updateOptions({ dateWindow: [1383454200000 + 15*60*1000, g.xAxisExtremes()[1]]} ); assert.deepEqual([ '01:05', '01:10', '01:15', '01:20', '01:25', '01:30', '01:35', '01:40', '01:45', '01:50', '01:55', '01:00', '01:05' // 1 AM number two! ], Util.getXLabels()); }); // Tests data which crosses a "spring forward" at a low frequency. // Regression test for http://code.google.com/p/dygraphs/issues/detail?id=433 it('testLabelsCrossSpringForward', function() { var g = new Dygraph( document.getElementById("graph"), "Date/Time,Purchases\n" + "2011-03-11 00:00:00,167082\n" + "2011-03-12 00:00:00,168571\n" + "2011-03-13 00:00:00,177796\n" + "2011-03-14 00:00:00,165587\n" + "2011-03-15 00:00:00,164380\n", { width: 1024, dateWindow: [1299989043119.4365, 1300080693627.4866] }); var okLabels = { '13Mar': true, // '02:00': true, // not a real time! '04:00': true, '06:00': true, '08:00': true, '10:00': true, '12:00': true, '14:00': true, '16:00': true, '18:00': true, '20:00': true, '22:00': true, '14Mar': true }; var xLabels = Util.getXLabels(); for (var i = 0; i < xLabels.length; i++) { assert.isTrue(okLabels[xLabels[i]]); } }); it('testLabelsCrossSpringForwardHighFreq', function() { var base_ms_spring = 1299999000000; var dst_data_spring = []; for (var x = base_ms_spring; x < base_ms_spring + 1000 * 60 * 80; x += 1000) { dst_data_spring.push([new Date(x), x]); } var g = new Dygraph( document.getElementById("graph"), dst_data_spring, { width: 1024, labels: ['Date', 'Value'] } ); assert.deepEqual([ '01:50', '01:55', '03:00', '03:05', '03:10', '03:15', '03:20', '03:25', '03:30', '03:35', '03:40', '03:45', '03:50', '03:55', '04:00', '04:05' ], Util.getXLabels()); }); */ }); dygraphs-2.2.1/auto_tests/tests/callback.js010064400000000000000000000452231437353256000157300ustar00/** * @fileoverview Test cases for the callbacks. * * @author uemit.seren@gmail.com (Ümit Seren) */ import Dygraph from '../../src/dygraph'; import * as utils from '../../src/dygraph-utils'; import Util from './Util'; import DygraphOps from './DygraphOps'; describe("callback", function() { cleanupAfterEach(); var xhr, styleSheet; var graph; beforeEach(function() { var container = document.getElementById('graph'); container.innerHTML = "
"; graph = container.querySelector('#inner-graph'); xhr = XMLHttpRequest; styleSheet = document.createElement("style"); styleSheet.type = "text/css"; document.getElementsByTagName("head")[0].appendChild(styleSheet); }); afterEach(function() { window.XMLHttpRequest = xhr; }); var data = "X,a,b,c\n" + "10,-1,1,2\n" + "11,0,3,1\n" + "12,1,4,2\n" + "13,0,2,3\n"; /** * This tests that when the function idxToRow_ returns the proper row and the onHiglightCallback * is properly called when the first series is hidden (setVisibility = false) * */ it('testHighlightCallbackIsCalled', function() { var h_row; var h_pts; var highlightCallback = function(e, x, pts, row) { assert.equal(g, this); h_row = row; h_pts = pts; }; var g = new Dygraph(graph, data, { width: 100, height: 100, visibility: [false, true, true], highlightCallback: highlightCallback }); DygraphOps.dispatchMouseMove(g, 13, 10); //check correct row is returned assert.equal(3, h_row); //check there are only two points (because first series is hidden) assert.equal(2, h_pts.length); }); /** * Test that drawPointCallback isn't called when drawPoints is false */ it('testDrawPointCallback_disabled', function() { var called = false; var callback = function() { assert.equal(g, this); called = true; }; var g = new Dygraph(graph, data, { drawPointCallback: callback, }); assert.isFalse(called); }); /** * Test that drawPointCallback is called when drawPoints is true */ it('testDrawPointCallback_enabled', function() { var called = false; var callbackThis = null; var callback = function() { callbackThis = this; called = true; }; var g = new Dygraph(graph, data, { drawPoints: true, drawPointCallback: callback }); assert.isTrue(called); assert.equal(g, callbackThis); }); /** * Test that drawPointCallback is called when drawPoints is true */ it('testDrawPointCallback_pointSize', function() { var pointSize = 0; var count = 0; var callback = function(g, seriesName, canvasContext, cx, cy, color, pointSizeParam) { assert.equal(g, this); pointSize = pointSizeParam; count++; }; var g = new Dygraph(graph, data, { drawPoints: true, drawPointCallback: callback }); assert.equal(1.5, pointSize); assert.equal(12, count); // one call per data point. var g = new Dygraph(graph, data, { drawPoints: true, drawPointCallback: callback, pointSize: 8 }); assert.equal(8, pointSize); }); /** * Test that drawPointCallback is called for isolated points when * drawPoints is false, and also for gap points if that's enabled. */ it('testDrawPointCallback_isolated', function() { var xvalues = []; var g; var callback = function(g, seriesName, canvasContext, cx, cy, color, pointSizeParam) { assert.equal(g, this); var dx = g.toDataXCoord(cx); xvalues.push(dx); utils.Circles.DEFAULT.apply(this, arguments); }; var testdata = [[10, 2], [11, 3], [12, NaN], [13, 2], [14, NaN], [15, 3]]; var graphOpts = { labels: ['X', 'Y'], valueRange: [0, 4], drawPoints : false, drawPointCallback : callback, pointSize : 8 }; // Test that isolated points get drawn g = new Dygraph(graph, testdata, graphOpts); assert.equal(2, xvalues.length); assert.equal(13, xvalues[0]); assert.equal(15, xvalues[1]); // Test that isolated points + gap points get drawn when // drawGapEdgePoints is set. This should add one point at the right // edge of the segment at x=11, but not at the graph edge at x=10. xvalues = []; // Reset for new test graphOpts.drawGapEdgePoints = true; g = new Dygraph(graph, testdata, graphOpts); assert.equal(3, xvalues.length); assert.equal(11, xvalues[0]); assert.equal(13, xvalues[1]); assert.equal(15, xvalues[2]); }); /** * This tests that when the function idxToRow_ returns the proper row and the onHiglightCallback * is properly called when the first series is hidden (setVisibility = false) * */ it('testDrawHighlightPointCallbackIsCalled', function() { var called = false; var drawHighlightPointCallback = function() { assert.equal(g, this); called = true; }; var g = new Dygraph(graph, data, { width: 100, height: 100, drawHighlightPointCallback: drawHighlightPointCallback }); assert.isFalse(called); DygraphOps.dispatchMouseMove(g, 13, 10); assert.isTrue(called); }); /** * Test the closest-series highlighting methods for normal and stacked modes. * Also pass in line widths for plain and highlighted lines for easier visual * confirmation that the highlighted line is drawn on top of the others. */ var runClosestTest = function(isStacked, widthNormal, widthHighlighted) { var h_row; var h_pts; var h_series; var g = new Dygraph(graph, data, { width: 600, height: 400, visibility: [false, true, true], stackedGraph: isStacked, strokeWidth: widthNormal, strokeBorderWidth: 2, highlightCircleSize: widthNormal * 2, highlightSeriesBackgroundAlpha: 0.3, highlightSeriesOpts: { strokeWidth: widthHighlighted, highlightCircleSize: widthHighlighted * 2 } }); var highlightCallback = function(e, x, pts, row, set) { assert.equal(g, this); h_row = row; h_pts = pts; h_series = set; document.getElementById('selection').innerHTML='row=' + row + ', set=' + set; }; g.updateOptions({highlightCallback: highlightCallback}, true); if (isStacked) { DygraphOps.dispatchMouseMove(g, 11.45, 1.4); assert.equal(1, h_row); assert.equal('c', h_series); //now move up in the same row DygraphOps.dispatchMouseMove(g, 11.45, 1.5); assert.equal(1, h_row); assert.equal('b', h_series); //and a bit to the right DygraphOps.dispatchMouseMove(g, 11.55, 1.5); assert.equal(2, h_row); assert.equal('c', h_series); } else { DygraphOps.dispatchMouseMove(g, 11, 1.5); assert.equal(1, h_row); assert.equal('c', h_series); //now move up in the same row DygraphOps.dispatchMouseMove(g, 11, 2.5); assert.equal(1, h_row); assert.equal('b', h_series); } return g; }; /** * Test basic closest-point highlighting. */ it('testClosestPointCallback', function() { runClosestTest(false, 1, 3); }); /** * Test setSelection() with series name */ it('testSetSelection', function() { var g = runClosestTest(false, 1, 3); assert.equal(1, g.attr_('strokeWidth', 'c')); g.setSelection(false, 'c'); assert.equal(3, g.attr_('strokeWidth', 'c')); }); /** * Test closest-point highlighting for stacked graph */ it('testClosestPointStackedCallback', function() { runClosestTest(true, 1, 3); }); /** * Closest-point highlighting with legend CSS - border around active series. */ it('testClosestPointCallbackCss1', function() { var css = "div.dygraph-legend > span { display: block; }\n" + "div.dygraph-legend > span.highlight { border: 1px solid grey; }\n"; styleSheet.innerHTML = css; runClosestTest(false, 2, 4); styleSheet.innerHTML = ''; }); /** * Closest-point highlighting with legend CSS - show only closest series. */ it('testClosestPointCallbackCss2', function() { var css = "div.dygraph-legend > span { display: none; }\n" + "div.dygraph-legend > span.highlight { display: inline; }\n"; styleSheet.innerHTML = css; runClosestTest(false, 10, 15); styleSheet.innerHTML = ''; // TODO(klausw): verify that the highlighted line is drawn on top? }); /** * Closest-point highlighting with locked series. */ it('testSetSelectionLocking', function() { var g = runClosestTest(false, 2, 4); // Default behavior, 'b' is closest DygraphOps.dispatchMouseMove(g, 11, 4); assert.equal('b', g.getHighlightSeries()); // Now lock selection to 'c' g.setSelection(false, 'c', true); DygraphOps.dispatchMouseMove(g, 11, 4); assert.equal('c', g.getHighlightSeries()); // Unlock, should be back to 'b' g.clearSelection(); DygraphOps.dispatchMouseMove(g, 11, 4); assert.equal('b', g.getHighlightSeries()); }); /** * This tests that closest point searches work for data containing NaNs. * * It's intended to catch a regression where a NaN Y value confuses the * closest-point algorithm, treating it as closer as any previous point. */ it('testNaNData', function() { var dataNaN = [ [9, -1, NaN, NaN], [10, -1, 1, 2], [11, 0, 3, 1], [12, 1, 4, NaN], [13, 0, 2, 3], [14, -1, 1, 4]]; var h_row; var h_pts; var highlightCallback = function(e, x, pts, row) { assert.equal(g, this); h_row = row; h_pts = pts; }; var g = new Dygraph(graph, dataNaN, { width: 600, height: 400, labels: ['x', 'a', 'b', 'c'], visibility: [false, true, true], highlightCallback: highlightCallback }); DygraphOps.dispatchMouseMove(g, 10.1, 0.9); //check correct row is returned assert.equal(1, h_row); // Explicitly test closest point algorithms var dom = g.toDomCoords(10.1, 0.9); assert.equal(1, g.findClosestRow(dom[0])); var res = g.findClosestPoint(dom[0], dom[1]); assert.equal(1, res.row); assert.equal('b', res.seriesName); res = g.findStackedPoint(dom[0], dom[1]); assert.equal(1, res.row); assert.equal('c', res.seriesName); }); /** * This tests that stacked point searches work for data containing NaNs. */ it('testNaNDataStack', function() { var dataNaN = [ [9, -1, NaN, NaN], [10, -1, 1, 2], [11, 0, 3, 1], [12, 1, NaN, 2], [13, 0, 2, 3], [14, -1, 1, 4], [15, 0, 2, NaN], [16, 1, 1, 3], [17, 1, NaN, 3], [18, 0, 2, 5], [19, 0, 1, 4]]; var h_row; var h_pts; var highlightCallback = function(e, x, pts, row) { assert.equal(g, this); h_row = row; h_pts = pts; }; var g = new Dygraph(graph, dataNaN, { width: 600, height: 400, labels: ['x', 'a', 'b', 'c'], visibility: [false, true, true], stackedGraph: true, highlightCallback: highlightCallback }); DygraphOps.dispatchMouseMove(g, 10.1, 0.9); //check correct row is returned assert.equal(1, h_row); // Explicitly test stacked point algorithm. var dom = g.toDomCoords(10.1, 0.9); var res = g.findStackedPoint(dom[0], dom[1]); assert.equal(1, res.row); assert.equal('c', res.seriesName); // All-NaN area at left, should get no points. dom = g.toDomCoords(9.1, 0.9); res = g.findStackedPoint(dom[0], dom[1]); assert.equal(0, res.row); assert.equal(undefined, res.seriesName); // First gap, get 'c' since it's non-NaN. dom = g.toDomCoords(12.1, 0.9); res = g.findStackedPoint(dom[0], dom[1]); assert.equal(3, res.row); assert.equal('c', res.seriesName); // Second gap, get 'b' since 'c' is NaN. dom = g.toDomCoords(15.1, 0.9); res = g.findStackedPoint(dom[0], dom[1]); assert.equal(6, res.row); assert.equal('b', res.seriesName); // Isolated points should work, finding series b in this case. dom = g.toDomCoords(15.9, 3.1); res = g.findStackedPoint(dom[0], dom[1]); assert.equal(7, res.row); assert.equal('b', res.seriesName); }); it('testGapHighlight', function() { var dataGap = [ [1, null, 3], [2, 2, null], [3, null, 5], [4, 4, null], [5, null, 7], [6, NaN, null], [8, 8, null], [10, 10, null]]; var h_row; var h_pts; var highlightCallback = function(e, x, pts, row) { assert.equal(g, this); h_row = row; h_pts = pts; }; var g = new Dygraph(graph, dataGap, { width: 400, height: 300, //stackedGraph: true, connectSeparatedPoints: true, drawPoints: true, labels: ['x', 'A', 'B'], highlightCallback: highlightCallback }); DygraphOps.dispatchMouseMove(g, 1.1, 10); //point from series B assert.equal(0, h_row); assert.equal(1, h_pts.length); assert.equal(3, h_pts[0].yval); assert.equal('B', h_pts[0].name); DygraphOps.dispatchMouseMove(g, 6.1, 10); // A is NaN at x=6 assert.equal(1, h_pts.length); assert(isNaN(h_pts[0].yval)); assert.equal('A', h_pts[0].name); DygraphOps.dispatchMouseMove(g, 8.1, 10); //point from series A assert.equal(6, h_row); assert.equal(1, h_pts.length); assert.equal(8, h_pts[0].yval); assert.equal('A', h_pts[0].name); }); it('testFailedResponse', function() { // Fake out the XMLHttpRequest so it doesn't do anything. XMLHttpRequest = function () {}; XMLHttpRequest.prototype.open = function () {}; XMLHttpRequest.prototype.send = function () {}; var highlightCallback = function(e, x, pts, row) { throw "should not reach here"; }; graph.style.border = "2px solid black"; var g = new Dygraph(graph, "data.csv", { // fake name width: 400, height: 300, highlightCallback : highlightCallback }); DygraphOps.dispatchMouseOver_Point(g, 800, 800); DygraphOps.dispatchMouseMove_Point(g, 100, 100); DygraphOps.dispatchMouseMove_Point(g, 800, 800); var oldOnerror = window.onerror; var failed = false; window.onerror = function() { failed = true; return false; } DygraphOps.dispatchMouseOut_Point(g, 800, 800); // This call should not throw an exception. assert.isFalse(failed, "exception thrown during mouseout"); }); // Regression test for http://code.google.com/p/dygraphs/issues/detail?id=355 it('testHighlightCallbackRow', function() { var highlightRow; var highlightCallback = function(e, x, pts, row) { assert.equal(g, this); highlightRow = row; }; var g = new Dygraph(graph, "X,Y,Z\n" + "0,1,2\n" + // 0 "1,2,3\n" + // 100 "2,3,4\n" + // 200 "3,4,5\n" + // 300 "4,5,6\n", // 400 { // fake name width: 400, height: 300, highlightCallback : highlightCallback }); // Mouse over each of the points DygraphOps.dispatchMouseOver_Point(g, 0, 0); DygraphOps.dispatchMouseMove_Point(g, 0, 0); assert.equal(0, highlightRow); DygraphOps.dispatchMouseMove_Point(g, 100, 0); assert.equal(1, highlightRow); DygraphOps.dispatchMouseMove_Point(g, 200, 0); assert.equal(2, highlightRow); DygraphOps.dispatchMouseMove_Point(g, 300, 0); assert.equal(3, highlightRow); DygraphOps.dispatchMouseMove_Point(g, 400, 0); assert.equal(4, highlightRow); // Now zoom and verify that the row numbers still refer to rows in the data // array. g.updateOptions({dateWindow: [2, 4]}); DygraphOps.dispatchMouseOver_Point(g, 0, 0); DygraphOps.dispatchMouseMove_Point(g, 0, 0); assert.equal(2, highlightRow); assert.equal('2: Y: 3 Z: 4', Util.getLegend()); }); /** * Test that underlay callback is called even when there are no series, * and that the y axis ranges are not NaN. */ it('testUnderlayCallback_noSeries', function() { var called = false; var yMin, yMax; var callback = function(canvas, area, g) { assert.equal(g, this); called = true; yMin = g.yAxisRange(0)[0]; yMax = g.yAxisRange(0)[1]; }; var g = new Dygraph(graph, "\n", { underlayCallback: callback }); assert.isTrue(called); assert.isFalse(isNaN(yMin)); assert.isFalse(isNaN(yMax)); }); /** * Test that underlay callback receives the correct y-axis range. */ it('testUnderlayCallback_yAxisRange', function() { var called = false; var yMin, yMax; var callback = function(canvas, area, g) { assert.equal(g, this); yMin = g.yAxisRange(0)[0]; yMax = g.yAxisRange(0)[1]; }; var g = new Dygraph(graph, "\n", { valueRange: [0,10], underlayCallback: callback }); assert.equal(0, yMin); assert.equal(10, yMax); }); /** * Test that drawPointCallback is called for isolated points and correct idx for the point is returned. */ it('testDrawPointCallback_idx', function() { var indices = []; var g; var callback = function(g, seriesName, canvasContext, cx, cy, color, pointSizeParam,idx) { assert.equal(g, this); indices.push(idx); utils.Circles.DEFAULT.apply(this, arguments); }; var testdata = [[10, 2], [11, 3], [12, NaN], [13, 2], [14, NaN], [15, 3]]; var graphOpts = { labels: ['X', 'Y'], valueRange: [0, 4], drawPoints : false, drawPointCallback : callback, pointSize : 8 }; // Test that correct idx for isolated points are passed to the callback. g = new Dygraph(graph, testdata, graphOpts); assert.equal(2, indices.length); assert.deepEqual([3, 5],indices); // Test that correct indices for isolated points + gap points are passed to the callback when // drawGapEdgePoints is set. This should add one point at the right // edge of the segment at x=11, but not at the graph edge at x=10. indices = []; // Reset for new test graphOpts.drawGapEdgePoints = true; g = new Dygraph(graph, testdata, graphOpts); assert.equal(3, indices.length); assert.deepEqual([1, 3, 5],indices); //Test that correct indices are passed to the callback when zoomed in. indices = []; // Reset for new test graphOpts.dateWindow = [12.5,13.5] graphOpts.drawPoints = true; testdata = [[10, 2], [11, 3], [12, 4], [13, 2], [14, 5], [15, 3]]; g = new Dygraph(graph, testdata, graphOpts); assert.equal(3, indices.length); assert.deepEqual([2, 3, 4],indices); }); /** * Test that the correct idx is returned for the point in the onHiglightCallback. */ it('testDrawHighlightPointCallback_idx', function() { var idxToCheck = null; var drawHighlightPointCallback = function(g, seriesName, canvasContext, cx, cy, color, pointSizeParam,idx) { assert.equal(g, this); idxToCheck = idx; }; var testdata = [[1, 2], [2, 3], [3, NaN], [4, 2], [5, NaN], [6, 3]]; var g = new Dygraph(graph, testdata, { drawHighlightPointCallback: drawHighlightPointCallback, labels: ['X', 'Y'] }); assert.isNull(idxToCheck); DygraphOps.dispatchMouseMove(g, 3, 0); // check that NaN point is not highlighted assert.isNull(idxToCheck); DygraphOps.dispatchMouseMove(g, 1, 2); // check that correct index is returned assert.equal(0,idxToCheck); DygraphOps.dispatchMouseMove(g, 6, 3); assert.equal(5,idxToCheck); }); /** * Test that drawCallback is called with the correct value for `this`. */ it('should set this in drawCallback', function() { var g = new Dygraph('graph', data, { drawCallback: function(g, is_initial) { assert.isTrue(is_initial); assert.equal(g, this); } }); }); }); dygraphs-2.2.1/auto_tests/tests/connect_separated_points.js010064400000000000000000000344601437353256000212520ustar00/** * @fileoverview Test cases for the option "connectSeparatedPoints" especially for the scenario where not every series has a value for each timestamp. * * @author julian.eichstaedt@ch.sauter-bc.com (Fr. Sauter AG) */ import Dygraph from '../../src/dygraph'; import * as utils from '../../src/dygraph-utils'; import CanvasAssertions from './CanvasAssertions'; import Proxy from './Proxy'; describe("connect-separated-points", function() { cleanupAfterEach(); var origFunc = utils.getContext; beforeEach(function() { utils.getContext = function(canvas) { return new Proxy(origFunc(canvas)); }; }); afterEach(function() { Dygraph.getContext = origFunc; }); it('testEdgePointsSimple', function() { var opts = { width: 480, height: 320, labels: ["x", "series1", "series2", "additionalSeries"], connectSeparatedPoints: true, dateWindow: [2.5,7.5] }; var data = [ [0,-1,0,null], [1,null,2,null], [2,null,4,null], [3,0.5,0,null], [4,1,-1,5], [5,2,-2,6], [6,2.5,-2.5,7], [7,3,-3,null], [8,4,null,null], [9,4,-10,null] ]; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); var htx = g.hidden_ctx_; var attrs = {}; // Test if series1 is drawn correctly. // ------------------------------------ // The first point of the first series var x1 = data[0][0]; var y1 = data[0][1]; var xy1 = g.toDomCoords(x1, y1); // The next valid point of this series var x2 = data[3][0]; var y2 = data[3][1]; var xy2 = g.toDomCoords(x2, y2); // Check if both points are connected at the left edge of the canvas and if the option "connectSeparatedPoints" works properly // even if the point is outside the visible range and only one series has a valid value for this point. CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); // Test if series2 is drawn correctly. // ------------------------------------ // The last point of the second series. var x2 = data[9][0]; var y2 = data[9][2]; var xy2 = g.toDomCoords(x2, y2); // The previous valid point of this series var x1 = data[7][0]; var y1 = data[7][2]; var xy1 = g.toDomCoords(x1, y1); // Check if both points are connected at the right edge of the canvas and if the option "connectSeparatedPoints" works properly // even if the point is outside the visible range and only one series has a valid value for this point. CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); }); it('testEdgePointsCustomBars', function() { var opts = { width: 480, height: 320, labels: ["x", "series1", "series2", "additionalSeries"], connectSeparatedPoints: true, dateWindow: [2.5,7.5], customBars: true }; var data = [ [0,[4,5,6], [1,2,3], [null, null, null]], [1,[null,null,null], [2,3,4], [null, null, null]], [2,[null,null,null], [3,4,5], [null, null, null]], [3,[0,1,2], [2,3,4], [null, null, null]], [4,[1,2,3], [2,3,4], [4, 5, 6]], [5,[1,2,3], [3,4,5], [4, 5, 6]], [6,[0,1,2], [4,5,6], [5, 6, 7]], [7,[0,1,2], [4,5,6], [null, null, null]], [8,[2,3,4], [null,null,null], [null, null, null]], [9,[0,1,2], [2,4,9], [null, null, null]] ]; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); var htx = g.hidden_ctx_; var attrs = {}; // Test if values of the series1 are drawn correctly. // ------------------------------------ // The first point of the first series var x1 = data[0][0]; var y1 = data[0][1][1]; var xy1 = g.toDomCoords(x1, y1); // The next valid point of this series var x2 = data[3][0]; var y2 = data[3][1][1]; var xy2 = g.toDomCoords(x2, y2); // Check if both points are connected at the left edge of the canvas and if the option "connectSeparatedPoints" works properly // even if the point is outside the visible range and only one series has a valid value for this point. CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); // Test if the custom bars of the series1 are drawn correctly // -------------------------------------------- // The first min-point of this series x1 = data[0][0]; y1 = data[0][1][0]; xy1 = g.toDomCoords(x1, y1); // The next valid min-point of the second series. x2 = data[3][0]; y2 = data[3][1][0]; xy2 = g.toDomCoords(x2, y2); // Check if both points are connected at the left edge of the canvas and if the option "connectSeparatedPoints" works properly // even if the point is outside the visible range and only one series has a valid value for this point. CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); // The first max-point of this series x1 = data[0][0]; y1 = data[0][1][2]; xy1 = g.toDomCoords(x1, y1); // The next valid max-point of the second series. x2 = data[3][0]; y2 = data[3][1][2]; xy2 = g.toDomCoords(x2, y2); // Check if both points are connected at the left edge of the canvas and if the option "connectSeparatedPoints" works properly // even if the point is outside the visible range and only one series has a valid value for this point. CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); // Test if values of the series2 are drawn correctly. // ------------------------------------ // The last point of the second series. var x2 = data[9][0]; var y2 = data[9][2][1]; var xy2 = g.toDomCoords(x2, y2); // The previous valid point of this series var x1 = data[7][0]; var y1 = data[7][2][1]; var xy1 = g.toDomCoords(x1, y1); // Check if both points are connected at the right edge of the canvas and if the option "connectSeparatedPoints" works properly // even if the point is outside the visible range and only one series has a valid value for this point. CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); // Test if the custom bars of the series2 are drawn correctly // -------------------------------------------- // The last min-point of the second series. x2 = data[9][0]; y2 = data[9][2][0]; xy2 = g.toDomCoords(x2, y2); // The previous valid min-point of this series x1 = data[7][0]; y1 = data[7][2][0]; xy1 = g.toDomCoords(x1, y1); // Check if both points are connected at the right edge of the canvas and if the option "connectSeparatedPoints" works properly // even if the point is outside the visible range and only one series has a valid value for this point. CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); // The last max-point of the second series. x2 = data[9][0]; y2 = data[9][2][2]; xy2 = g.toDomCoords(x2, y2); // The previous valid max-point of this series x1 = data[7][0]; y1 = data[7][2][2]; xy1 = g.toDomCoords(x1, y1); // Check if both points are connected at the right edge of the canvas and if the option "connectSeparatedPoints" works properly // even if the point is outside the visible range and only one series has a valid value for this point. CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); }); it('testEdgePointsErrorBars', function() { var opts = { width: 480, height: 320, labels: ["x", "series1", "series2", "seriesTestHelper"], connectSeparatedPoints: true, dateWindow: [2,7.5], errorBars: true }; var data = [ [0,[5,1], [2,1], [null,null]], [1,[null,null], [3,1], [null,null]], [2,[null,null], [4,1], [null,null]], [3,[1,1], [3,1], [null,null]], [4,[2,1], [3,1], [5,1]], [5,[2,1], [4,1], [5,1]], [6,[1,1], [5,1], [6,1]], [7,[1,1], [5,1], [null,null]], [8,[3,1], [null,null], [null,null]], [9,[1,1], [4,1], [null,null]] ]; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); var htx = g.hidden_ctx_; var attrs = {}; // Test if values of the series1 are drawn correctly. // ------------------------------------ // The first point of the first series var x1 = data[0][0]; var y1 = data[0][1][0]; var xy1 = g.toDomCoords(x1, y1); // The next valid point of this series var x2 = data[3][0]; var y2 = data[3][1][0]; var xy2 = g.toDomCoords(x2, y2); // Check if both points are connected at the left edge of the canvas and if the option "connectSeparatedPoints" works properly // even if the point is outside the visible range and only one series has a valid value for this point. CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); // Test if the upper error bars of series1 are drawn correctly // -------------------------------------------- // The first upper error-point of this series x1 = data[0][0]; var y1error = y1 + (data[0][1][1]*2); xy1 = g.toDomCoords(x1, y1error); // The next valid upper error-point of the second series. x2 = data[3][0]; var y2error = y2 + (data[3][1][1]*2); xy2 = g.toDomCoords(x2, y2error); // Check if both points are connected at the left edge of the canvas and if the option "connectSeparatedPoints" works properly // even if the point is outside the visible range and only one series has a valid value for this point. CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); // Test if the lower error bars of series1 are drawn correctly // -------------------------------------------- // The first lower error-point of this series x1 = data[0][0]; y1error = y1 - (data[0][1][1]*2); xy1 = g.toDomCoords(x1, y1error); //The next valid lower error-point of the second series. x2 = data[3][0]; y2error = y2 - (data[3][1][1]*2); xy2 = g.toDomCoords(x2, y2error); // Check if both points are connected at the left edge of the canvas and if the option "connectSeparatedPoints" works properly // even if the point is outside the visible range and only one series has a valid value for this point. CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); // Test if values of the series2 are drawn correctly. // ------------------------------------ // The last point of this series x2 = data[9][0]; y2 = data[9][2][0]; xy2 = g.toDomCoords(x2, y2); // The previous valid point of the first series x1 = data[7][0]; y1 = data[7][2][0]; xy1 = g.toDomCoords(x1, y1); // Check if both points are connected at the right edge of the canvas and if the option "connectSeparatedPoints" works properly // even if the point is outside the visible range and only one series has a valid value for this point. CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); // Test if the upper error bars of series2 are drawn correctly // -------------------------------------------- // The last upper error-point of the second series. x2 = data[9][0]; var y2error = y2 + (data[9][2][1]*2); xy2 = g.toDomCoords(x2, y2error); // The previous valid upper error-point of this series x1 = data[7][0]; var y1error = y1 + (data[7][2][1]*2); xy1 = g.toDomCoords(x1, y1error); // Check if both points are connected at the right edge of the canvas and if the option "connectSeparatedPoints" works properly // even if the point is outside the visible range and only one series has a valid value for this point. CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); // Test if the lower error bars of series1 are drawn correctly // -------------------------------------------- // The last lower error-point of the second series. x2 = data[9][0]; y2error = y2 - (data[9][2][1]*2); xy2 = g.toDomCoords(x2, y2error); // The previous valid lower error-point of this series x1 = data[7][0]; y1error = y1 - (data[7][2][1]*2); xy1 = g.toDomCoords(x1, y1error); // Check if both points are connected at the right edge of the canvas and if the option "connectSeparatedPoints" works properly // even if the point is outside the visible range and only one series has a valid value for this point. CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); }); it('testConnectSeparatedPointsPerSeries', function() { var assertExpectedLinesDrawnPerSeries = function(htx, expectedSeries1, expectedSeries2, expectedSeries3) { var expected = [expectedSeries1, expectedSeries2, expectedSeries3]; var actual = [ CanvasAssertions.numLinesDrawn(htx, "#ff0000"), CanvasAssertions.numLinesDrawn(htx, "#00ff00"), CanvasAssertions.numLinesDrawn(htx, "#0000ff")]; assert.deepEqual(expected, actual); } var g = new Dygraph(document.getElementById("graph"), [ [1, 10, 10, 10], [2, 15, 11, 12], [3, null, null, 12], [4, 20, 14, null], [5, 15, null, 17], [6, 18, null, null], [7, 12, 14, null] ], { labels: ["Date","Series1","Series2","Series3"], connectSeparatedPoints: false, colors: ["#ff0000", "#00ff00", "#0000ff"] }); var htx = g.hidden_ctx_; assertExpectedLinesDrawnPerSeries(htx, 4, 1, 2); Proxy.reset(htx); g.updateOptions({ connectSeparatedPoints : true, }); assertExpectedLinesDrawnPerSeries(htx, 5, 3, 3); Proxy.reset(htx); g.updateOptions({ connectSeparatedPoints : false, series : { Series1 : { connectSeparatedPoints : true } } }); assertExpectedLinesDrawnPerSeries(htx, 5, 1, 2); Proxy.reset(htx); g.updateOptions({ connectSeparatedPoints : true, series : { Series1 : { connectSeparatedPoints : false } } }); assertExpectedLinesDrawnPerSeries(htx, 4, 3, 3); }); it('testNaNErrorBars', function() { var data = [ [0,[1,2,3]], [1,[2,3,4]], [2,[3,4,5]], [3,[null,null,null]], [4,[2,3,4]], [5,[3,4,5]], [6,[2,3,4]], [7,[NaN,NaN,NaN]], [8,[2,3,4]], [9,[2,3,4]], [10,[2,3,4]], [11,[2,3,4]] ]; var opts = { labels: ["x", "y"], colors: ["#ff0000"], customBars: true, connectSeparatedPoints: true }; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); var htx = g.hidden_ctx_; var attrs = {}; // Line should be drawn across the null gap. CanvasAssertions.assertLineDrawn(htx, g.toDomCoords(data[2][0], data[2][1][1]), g.toDomCoords(data[4][0], data[4][1][1]), attrs); // No line across the NaN gap, and a single line (not two) // across the null gap. assert.equal(8, CanvasAssertions.numLinesDrawn(htx, '#ff0000')); }); }); dygraphs-2.2.1/auto_tests/tests/css.js010064400000000000000000000132451437353256000147630ustar00// Copyright 2011 Google Inc. All Rights Reserved. /** * @fileoverview Regression test based on some strange customBars data. * @author danvk@google.com (Dan Vanderkam) * @license MIT */ import Dygraph from '../../src/dygraph'; describe("css", function() { cleanupAfterEach(); var data = "X,Y,Z\n1,2,3\n4,5,6\n"; var styleSheet; beforeEach(function() { styleSheet = document.createElement("style"); styleSheet.type = "text/css"; document.getElementsByTagName("head")[0].appendChild(styleSheet); }); afterEach(function() { styleSheet.innerHTML = ''; }); // Verifies that an unstyled, unsized dygraph gets a default size. it('testDefaultSize', function() { var opts = { }; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); assert.equal(480, graph.offsetWidth); assert.equal(320, graph.offsetHeight); assert.deepEqual({width: 480, height: 320}, g.size()); }); // Verifies that the width/height parameters work. it('testExplicitParamSize', function() { var opts = { width: 640, height: 480 }; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); assert.equal(640, graph.offsetWidth); assert.equal(480, graph.offsetHeight); assert.deepEqual({width: 640, height: 480}, g.size()); }); // Verifies that setting a style on the div works. it('testExplicitStyleSize', function() { var opts = { }; var graph = document.getElementById("graph"); graph.style.width = '600px'; graph.style.height = '400px'; var g = new Dygraph(graph, data, opts); assert.equal(600, graph.offsetWidth); assert.equal(400, graph.offsetHeight); assert.deepEqual({width: 600, height: 400}, g.size()); }); // Verifies that CSS pixel styles on the div trump explicit parameters. it('testPixelStyleWins', function() { var opts = { width: 987, height: 654 }; var graph = document.getElementById("graph"); graph.style.width = '600px'; graph.style.height = '400px'; var g = new Dygraph(graph, data, opts); assert.equal(600, graph.offsetWidth); assert.equal(400, graph.offsetHeight); assert.deepEqual({width: 600, height: 400}, g.size()); }); // Verifies that a CSS percentage size works. it('testPercentageSize', function() { var testdiv = document.getElementById("graph"); testdiv.innerHTML = '
' + '
'; var opts = { }; var graph = document.getElementById("inner-graph"); graph.style.width = '50%'; graph.style.height = '50%'; var g = new Dygraph(graph, data, opts); assert.equal(300, graph.offsetWidth); assert.equal(200, graph.offsetHeight); assert.deepEqual({width: 300, height: 200}, g.size()); }); // Verifies that a CSS class size works. it('testClassPixelSize', function() { styleSheet.innerHTML = '.chart { width: 456px; height: 345px; }'; var opts = { }; var graph = document.getElementById("graph"); graph.className = "chart"; var g = new Dygraph(graph, data, opts); assert.equal(456, graph.offsetWidth); assert.equal(345, graph.offsetHeight); assert.deepEqual({width: 456, height: 345}, g.size()); }); // An invisible chart div shouldn't produce an error. it('testInvisibleChart', function() { graph.innerHTML = '
' + '
' + '
'; new Dygraph('inner-graph', data, {}); }); // An invisible chart div shouldn't produce an error. it('testInvisibleChartDate', function() { graph.innerHTML = '
' + '
' + '
'; new Dygraph('inner-graph', "Date,Y\n" + "2010/01/01,100\n" + "2010/02/01,200\n" + "2010/03/01,300\n" + "2010/04/01,400\n" + "2010/05/01,300\n" + "2010/06/01,100\n", {}); }); // An invisible chart div that becomes visible. it('testInvisibleThenVisibleChart', function() { var testdiv = document.getElementById("graph"); testdiv.innerHTML = ''; var graph = document.getElementById("inner-graph"); var g = new Dygraph(graph, "Date,Y\n" + "2010/01/01,100\n" + "2010/02/01,200\n" + "2010/03/01,300\n" + "2010/04/01,400\n" + "2010/05/01,300\n" + "2010/06/01,100\n" , {}); // g.size() is undefined here (probably 0x0) document.getElementById("x").style.display = ""; // This resize() call is annoying but essential. // There are no DOM events to inform the dygraph that its div has changed size // or visibility so we need to let it know ourselves. g.resize(); assert.equal(640, graph.offsetWidth); assert.equal(480, graph.offsetHeight); assert.deepEqual({width: 640, height: 480}, g.size()); }); // Verifies that a div resize gets picked up. /* this one isn't quite ready yet. it('testDivResize', function() { var opts = { }; var graph = document.getElementById("graph"); graph.style.width = '640px'; graph.style.height = '480px'; var g = new Dygraph(graph, data, opts); assert.equal(640, graph.offsetWidth); assert.equal(480, graph.offsetHeight); assert.deepEqual({width: 640, height: 480}, g.size()); graph.style.width = '650px'; graph.style.height = '490px'; assert.equal(650, graph.offsetWidth); assert.equal(490, graph.offsetHeight); assert.deepEqual({width: 650, height: 490}, g.size()); }); */ }); dygraphs-2.2.1/auto_tests/tests/custom_asserts.js010064400000000000000000000005171437353256000172470ustar00/** * @fileoverview Assertions that chai doesn't provide out of the box. */ export function assertDeepCloseTo(actualArray, expectedArray, epsilon) { assert.isArray(actualArray); assert.isArray(expectedArray); for (var i = 0; i < actualArray.length; i++) { assert.closeTo(actualArray[i], expectedArray[i], epsilon); } }; dygraphs-2.2.1/auto_tests/tests/custom_bars.js010064400000000000000000000134051437353256000165120ustar00// Copyright 2011 Google Inc. All Rights Reserved. /** * @fileoverview Regression test based on some strange customBars data. * @author danvk@google.com (Dan Vanderkam) * @license MIT */ import Dygraph from '../../src/dygraph'; import * as utils from '../../src/dygraph-utils'; import CanvasAssertions from './CanvasAssertions'; import PixelSampler from './PixelSampler'; import Proxy from './Proxy'; describe("custom-bars", function() { cleanupAfterEach(); var _origFunc = utils.getContext; beforeEach(function() { utils.getContext = function(canvas) { return new Proxy(_origFunc(canvas)); } }); afterEach(function() { utils.getContext = _origFunc; }); // This test used to reliably produce an infinite loop. it('testCustomBarsNoHang', function() { var opts = { width: 480, height: 320, customBars: true }; var data = "X,Y1,Y2\n" + "1,1178.0;1527.5;1856.6,0;22365658;0\n" + "2,1253.0;1303.3;1327.3,0;22368228;0\n" + "3,878.0;1267.0;1357.1,0;22368895;0\n" + "4,1155.0;1273.1;1303.5,0;22369665;0\n" + "5,1089.0;1294.8;1355.3,0;22370160;0\n" + "6,1088.0;1268.6;1336.1,0;22372346;0\n" + "7,1141.0;1269.1;1301.2,0;22373318;0\n" + "8,1072.0;1255.8;1326.2,0;22374310;0\n" + "9,1209.0;1309.2;1351.8,0;22374924;0\n" + "10,1230.0;1303.9;1332.6,0;22380163;0\n" + "11,1014.0;1263.5;1330.8,0;22381117;0\n" + "12,853.0;1215.6;1330.6,0;22381556;0\n" + "13,1134.0;1581.9;1690.1,0;22384631;0\n" + "14,1113.0;1540.1;1676.5,0;22386933;0\n" + "15,1130.0;1542.7;1678.3,0;22393459;0\n" + "18,1582.0;1644.4;1690.2,0;22395914;0\n" + "19,878.0;1558.3;1708.1,0;22397732;0\n" + "20,1076.0;1598.4;1723.8,0;22397886;0\n" + "21,1077.0;1574.0;1685.3,0;22398659;0\n" + "22,1118.0;1590.4;1697.6,0;22399009;0\n" + "23,1031.0;1473.1;1644.9,0;22401969;0\n" + "24,1090.0;1480.7;1640.0,0;22417989;0\n" + "25,1592.0;1681.7;1714.4,0;22422819;0\n" + "26,1251.0;1657.8;1750.6,0;22423681;0\n" + "27,1144.0;1660.9;1776.2,0;22426947;0\n" + "28,1178.0;1642.4;1745.6,0;22428238;0\n" + "29,1169.0;1649.1;1757.5,0;22429524;0\n" + "30,1150.0;1596.1;1746.7,0;22433472;0\n" + "31,1099.0;1586.5;1732.8,0;22434308;0\n" + "32,1120.0;1456.0;1620.3,0;22434821;0\n" + "33,1640.0;1687.7;1709.0,0;22434882;0\n" + "34,1671.0;1712.1;1733.7,0;22435116;0\n" + "35,,0;22437620;0\n"; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); }); // Regression test for http://code.google.com/p/dygraphs/issues/detail?id=201 it('testCustomBarsZero', function() { var opts = { customBars: true }; var data = "X,Y1,Y2\n" + "1,1;2;3,0;0;0\n" + "2,2;3;4,0;0;0\n" + "3,1;3;5,0;0;0\n"; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); var range = g.yAxisRange(); assert.isTrue(range[0] <= 0, 'y-axis must include 0'); assert.isTrue(range[1] >= 5, 'y-axis must include 5'); }); // Regression test for http://code.google.com/p/dygraphs/issues/detail?id=229 it('testCustomBarsAtTop', function() { var g = new Dygraph(document.getElementById("graph"), [ [1, [10, 10, 100]], [1, [10, 10, 100]], [2, [15, 20, 110]], [3, [10, 30, 100]], [4, [15, 40, 110]], [5, [10,120, 100]], [6, [15, 50, 110]], [7, [10, 70, 100]], [8, [15, 90, 110]], [9, [10, 50, 100]] ], { width: 500, height: 350, customBars: true, errorBars: true, axes: { x: { drawGrid: false, drawAxis: false, }, y: { drawGrid: false, drawAxis: false, } }, valueRange: [0, 120], fillAlpha: 0.15, colors: ['#00FF00'], labels: ['X', 'Y'] }); var sampler = new PixelSampler(g); assert.deepEqual([0, 255, 0, 38], sampler.colorAtCoordinate(5, 60)); }); // Tests that custom bars work with log scale. it('testCustomBarsLogScale', function() { var g = new Dygraph(document.getElementById("graph"), [ [1, [10, 10, 100]], [5, [15,120, 80]], [9, [10, 50, 100]] ], { width: 500, height: 350, customBars: true, errorBars: true, valueRange: [1, 120], axes : { x : { drawGrid: false, drawAxis: false, }, y : { drawGrid: false, drawAxis: false, } }, fillAlpha: 1.0, logscale: true, colors: ['#00FF00'], labels: ['X', 'Y'] }); // The following assertions describe the sides of the custom bars, which are // drawn in two halves. CanvasAssertions.assertConsecutiveLinesDrawn( g.hidden_ctx_, [[0, 13.329014086362069], [247.5, 29.64240889852502], [247.5, 152.02209814465604], [0, 181.66450704318103]], { fillStyle: "#00ff00" }); CanvasAssertions.assertConsecutiveLinesDrawn( g.hidden_ctx_, [[247.5, 29.64240889852502], [495, 13.329014086362069], [495, 181.66450704318103], [247.5, 152.02209814465604]], { fillStyle: "#00ff00" }); }); it('testCustomBarsWithNegativeValuesInLogScale', function() { var graph = document.getElementById("graph"); var count = 0; var drawPointCallback = function() { count++; }; var g = new Dygraph(graph, [ [1, [10, 20,30]], [2, [5, 10, 15]], [3, [-1, 5, 10]] ], { drawPoints: true, drawPointCallback: drawPointCallback, customBars: true, labels: ['X', 'Y'] }); // Normally all three points would be drawn. assert.equal(3, count); count = 0; // In log scale, the third point shouldn't be shown. g.updateOptions({ logscale : true }); assert.equal(2, count); }); }); dygraphs-2.2.1/auto_tests/tests/data_api.js010064400000000000000000000054241437353256000157350ustar00/** * @fileoverview Tests for data access methods. * * @author danvdk@gmail.com (Dan Vanderkam) */ import Dygraph from '../../src/dygraph'; import * as utils from '../../src/dygraph-utils'; describe("data-api", function() { cleanupAfterEach(); var opts, graphDiv; beforeEach(function() { opts = { width: 480, height: 320 }; graphDiv = document.getElementById("graph"); }); it('testBasicAccessors', function() { var g = new Dygraph(graphDiv, temperature_data, opts); assert.equal(365, g.numRows()); assert.equal(3, g.numColumns()); // 2007-01-01,62,39 assert.equal(62, g.getValue(0, 1)); assert.equal(39, g.getValue(0, 2)); // 2007-12-31,57,42 assert.equal(57, g.getValue(364, 1)); assert.equal(42, g.getValue(364, 2)); }); it('testAccessorsCustomBars', function() { var g = new Dygraph(graphDiv, data_temp_high_low, { customBars: true }); assert.equal(1070, g.numRows()); assert.equal(3, g.numColumns()); // 2007-01-01,46;51;56,43;45;48 assert.deepEqual([46, 51, 56], g.getValue(0, 1)); assert.deepEqual([43, 45, 48], g.getValue(0, 2)); // 2009-12-05,37;42;47 (i.e. missing second column) assert.deepEqual([37, 42, 47], g.getValue(1069, 1)); assert.deepEqual([null, null, null], g.getValue(1069, 2)); }); // Regression test for #554. it('testGetRowForX', function() { var g = new Dygraph(graphDiv, [ "x,y", "1,2", "3,4", "5,6", "7,8", "9,10" ].join('\n'), opts); assert.equal(null, g.getRowForX(0)); assert.equal(0, g.getRowForX(1)); assert.equal(null, g.getRowForX(2)); assert.equal(1, g.getRowForX(3)); assert.equal(null, g.getRowForX(4)); assert.equal(2, g.getRowForX(5)); assert.equal(null, g.getRowForX(6)); assert.equal(3, g.getRowForX(7)); assert.equal(null, g.getRowForX(8)); assert.equal(4, g.getRowForX(9)); assert.equal(null, g.getRowForX(10)); }); // If there are rows with identical x-values, getRowForX promises that it will // return the first one. it('testGetRowForXDuplicates', function() { var g = new Dygraph(graphDiv, [ "x,y", "1,2", // 0 "1,4", // 1 "1,6", // 2 "1,8", // 3 "1,6", // 4 "9,2", // 5 "9,4", "9,6", "9,8", "9,10" ].join('\n'), opts); assert.equal(0, g.getRowForX(1)); assert.equal(null, g.getRowForX(2)); assert.equal(5, g.getRowForX(9)); }); // indexFromSeriesName should return a value even if the series is invisible // In 1.1.1, if you request the last set and it's invisible, the method returns undefined. it('testIndexFromSetNameOnInvisibleSet', function() { var localOpts = utils.clone(opts); localOpts.visibility = [true, false]; var g = new Dygraph(graphDiv, [ "x,y1,y2", "1,1,1", "2,2,2", "3,3,3" ].join('\n'), localOpts); assert.equal(2, g.indexFromSetName("y2")); }); }); dygraphs-2.2.1/auto_tests/tests/data_types.js010064400000000000000000000012221437353256000163200ustar00/** * @fileoverview Test cases for DygraphOptions. */ import Dygraph from '../../src/dygraph'; describe("dygraph-data-types", function() { cleanupAfterEach(); var graph; beforeEach(function() { graph = document.getElementById("graph"); }); /* * Test to ensure ints are correctly interpreted as ints and not as dates */ it('testNumberOfData', function() { var opts = { width: 480, height: 320 }; var data = "x y\n" + "20033000 1\n" + "20034000 2\n" + "20035000 3\n" + "20036000 4"; var g = new Dygraph(graph, data, opts); assert.deepEqual(4, g.rawData_.length); }); }); dygraphs-2.2.1/auto_tests/tests/date_formats.js010064400000000000000000000027021437353256000166370ustar00/** * @fileoverview Tests that various formats of date are understood by dygraphs. * * @author dan@dygraphs.com (Dan Vanderkam) */ import * as utils from '../../src/dygraph-utils'; describe("date-formats", function() { it('testISO8601', function() { // Format: YYYY-MM-DDTHH:MM:SS.ddddddZ // The "Z" indicates UTC, so this test should pass regardless of the time // zone of the machine on which it is run. // Firefox <4 does not support this format: // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Date/parse if (navigator.userAgent.indexOf("Firefox/3.5") == -1) { assert.equal(946816496789, utils.dateParser("2000-01-02T12:34:56.789012Z")); } }); it('testHyphenatedDate', function() { // Format: YYYY-MM-DD HH:MM // Midnight February 2, 2000, UTC var d = new Date(Date.UTC(2000, 1, 2)); // Convert to a string in the local time zone: YYYY-DD-MM HH:MM var zp = function(x) { return x < 10 ? '0' + x : x; }; var str = d.getFullYear() + '-' + zp(1 + d.getMonth()) + '-' + zp(d.getDate()) + ' ' + zp(d.getHours()) + ':' + zp(d.getMinutes()); assert.equal(Date.UTC(2000, 1, 2), utils.dateParser(str)); }); it('testMillisecondsDate', function() { // Format: YYYY-MM-DD HH:MM:SS.MS // Midnight February 2, 2000 14:25:42.123 UTC var ts = Date.UTC(2000, 1, 2, 14, 25, 42, 123); assert.equal("2000/02/02 14:25:42.123", utils.dateString_(ts, true)); }); }); dygraphs-2.2.1/auto_tests/tests/date_ticker.js010064400000000000000000002061171437353256000164530ustar00/** * @fileoverview Test cases for the tick-generating functions. * These were generated by adding logging code to the old ticker functions. The * tests serve to track existing behavior should it change in the future. * * @author danvdk@gmail.com (Dan Vanderkam) */ import * as DygraphTickers from '../../src/dygraph-tickers'; import DEFAULT_ATTRS from '../../src/dygraph-default-attrs'; describe("date-ticker-tests", function() { cleanupAfterEach(); var createOptionsViewForAxis = function(axis, dict) { return function (x) { if (dict && dict.hasOwnProperty(x)) { return dict[x]; } if (DEFAULT_ATTRS.axes[axis].hasOwnProperty(x)) { return DEFAULT_ATTRS.axes[axis][x]; } if (DEFAULT_ATTRS.hasOwnProperty(x)) { return DEFAULT_ATTRS[x]; } if (x == 'axisLabelFormatter') return null; throw "mysterious " + axis + "-axis option: " + x; }; }; // Change ' ' (non-breaking space) to ' ' in all labels. Cleans up expected values. function changeNbspToSpace(ticks) { for (var i = 0; i < ticks.length; i++) { if (ticks[i].label) { ticks[i].label = ticks[i].label.replace(/ /g, ' '); } } } it('testBasicDateTicker', function() { var opts = {labelsUTC: true}; var options = createOptionsViewForAxis('x', opts); var ticks = DygraphTickers.dateTicker(-1797534000000, 1255579200000, 800, options); var expected_ticks = [ {"v":-1577923200000,"label":"1920"}, {"v":-1262304000000,"label":"1930"}, {"v":-946771200000, "label":"1940"}, {"v":-631152000000, "label":"1950"}, {"v":-315619200000, "label":"1960"}, {"v": 0, "label":"1970"}, {"v": 315532800000, "label":"1980"}, {"v": 631152000000, "label":"1990"}, {"v": 946684800000, "label":"2000"} ]; assert.deepEqual(expected_ticks, ticks); var start = Date.UTC(1999, 11, 31, 14, 0, 0); var end = Date.UTC(2000, 0, 1, 12, 0, 0); var granularity = DygraphTickers.Granularity.TWO_HOURLY; ticks = DygraphTickers.getDateAxis(start, end, granularity, options); changeNbspToSpace(ticks); expected_ticks = [ // months of the year are zero-based. {v: Date.UTC(1999, 11, 31, 14, 0, 0), label: '14:00'}, {v: Date.UTC(1999, 11, 31, 16, 0, 0), label: '16:00'}, {v: Date.UTC(1999, 11, 31, 18, 0, 0), label: '18:00'}, {v: Date.UTC(1999, 11, 31, 20, 0, 0), label: '20:00'}, {v: Date.UTC(1999, 11, 31, 22, 0, 0), label: '22:00'}, {v: Date.UTC(2000, 0, 1, 0, 0, 0), label: '01 Jan'}, {v: Date.UTC(2000, 0, 1, 2, 0, 0), label: '02:00'}, {v: Date.UTC(2000, 0, 1, 4, 0, 0), label: '04:00'}, {v: Date.UTC(2000, 0, 1, 6, 0, 0), label: '06:00'}, {v: Date.UTC(2000, 0, 1, 8, 0, 0), label: '08:00'}, {v: Date.UTC(2000, 0, 1, 10, 0, 0), label: '10:00'}, {v: Date.UTC(2000, 0, 1, 12, 0, 0), label: '12:00'} ]; assert.deepEqual(expected_ticks, ticks); }); it('testAllDateTickers', function() { var opts = {labelsUTC: true, pixelsPerLabel: 60}; var options = createOptionsViewForAxis('x', opts); // For granularities finer than MONTHLY, the first tick returned tick // could lie outside [start_time, end_time] range in the original code. // In these tests, those spurious ticks are removed to test new behavior. var ticker = function() { var ticks = DygraphTickers.dateTicker.apply(null, arguments); changeNbspToSpace(ticks); return ticks; }; assert.deepEqual([{"v":-1577923200000,"label":"1920"},{"v":-1262304000000,"label":"1930"},{"v":-946771200000,"label":"1940"},{"v":-631152000000,"label":"1950"},{"v":-315619200000,"label":"1960"},{"v":0,"label":"1970"},{"v":315532800000,"label":"1980"},{"v":631152000000,"label":"1990"},{"v":946684800000,"label":"2000"}], ticker(-1797552000000, 1255561200000, 800, options)); assert.deepEqual([{"v":-5364662400000,"label":"1800"},{"v":-2208988800000,"label":"1900"}], ticker(-6122044800000, 189302400000, 480, options)); assert.deepEqual([{"v":1041120000000,"label":"29 Dec"},{"v":1041724800000,"label":"05 Jan"},{"v":1042329600000,"label":"12 Jan"},{"v":1042934400000,"label":"19 Jan"},{"v":1043539200000,"label":"26 Jan"},{"v":1044144000000,"label":"02 Feb"},{"v":1044748800000,"label":"09 Feb"},{"v":1045353600000,"label":"16 Feb"}], ticker(1041120000000, 1045353600000, 640, options)); assert.deepEqual([{"v":1041379200000,"label":"Jan 2003"},{"v":1072915200000,"label":"Jan 2004"},{"v":1104537600000,"label":"Jan 2005"},{"v":1136073600000,"label":"Jan 2006"},{"v":1167609600000,"label":"Jan 2007"},{"v":1199145600000,"label":"Jan 2008"},{"v":1230768000000,"label":"Jan 2009"},{"v":1262304000000,"label":"Jan 2010"},{"v":1293840000000,"label":"Jan 2011"}], ticker(1041120000000, 1307833200000, 800, options)); assert.deepEqual([{"v":1159660800000,"label":"01 Oct"},{"v":1160265600000,"label":"08 Oct"},{"v":1160870400000,"label":"15 Oct"},{"v":1161475200000,"label":"22 Oct"},{"v":1162080000000,"label":"29 Oct"}], ticker(1159657200000, 1162252800000, 480, options)); assert.deepEqual([{"v":1159660800000,"label":"01 Oct"},{"v":1160265600000,"label":"08 Oct"},{"v":1160870400000,"label":"15 Oct"},{"v":1161475200000,"label":"22 Oct"},{"v":1162080000000,"label":"29 Oct"}], ticker(1159657200000, 1162252800000, 640, options)); assert.deepEqual([{"v":1159660800000,"label":"01 Oct"},{"v":1160265600000,"label":"08 Oct"},{"v":1160870400000,"label":"15 Oct"},{"v":1161475200000,"label":"22 Oct"},{"v":1162080000000,"label":"29 Oct"},{"v":1162684800000,"label":"05 Nov"},{"v":1163289600000,"label":"12 Nov"},{"v":1163894400000,"label":"19 Nov"},{"v":1164499200000,"label":"26 Nov"}], ticker(1159657200000, 1164758400000, 1150, options)); assert.deepEqual([{"v":1159660800000,"label":"Oct 2006"},{"v":1162339200000,"label":"Nov 2006"}], ticker(1159657200000, 1164758400000, 400, options)); assert.deepEqual([{"v":1159660800000,"label":"01 Oct"},{"v":1160265600000,"label":"08 Oct"},{"v":1160870400000,"label":"15 Oct"},{"v":1161475200000,"label":"22 Oct"},{"v":1162080000000,"label":"29 Oct"},{"v":1162684800000,"label":"05 Nov"},{"v":1163289600000,"label":"12 Nov"},{"v":1163894400000,"label":"19 Nov"},{"v":1164499200000,"label":"26 Nov"}], ticker(1159657200000, 1164758400000, 500, options)); assert.deepEqual([{"v":1159660800000,"label":"01 Oct"},{"v":1160265600000,"label":"08 Oct"},{"v":1160870400000,"label":"15 Oct"},{"v":1161475200000,"label":"22 Oct"},{"v":1162080000000,"label":"29 Oct"},{"v":1162684800000,"label":"05 Nov"},{"v":1163289600000,"label":"12 Nov"},{"v":1163894400000,"label":"19 Nov"},{"v":1164499200000,"label":"26 Nov"}], ticker(1159657200000, 1164758400000, 600, options)); assert.deepEqual([{"v":1160265600000,"label":"08 Oct"},{"v":1160870400000,"label":"15 Oct"},{"v":1161475200000,"label":"22 Oct"},{"v":1162080000000,"label":"29 Oct"},{"v":1162684800000,"label":"05 Nov"},{"v":1163289600000,"label":"12 Nov"}], ticker(1160243979962, 1163887694248, 600, options)); assert.deepEqual([{"v":1160611200000,"label":"12 Oct"},{"v":1160784000000,"label":"14 Oct"},{"v":1160956800000,"label":"16 Oct"},{"v":1161129600000,"label":"18 Oct"}], ticker(1160521200000, 1161298800000, 480, options)); assert.deepEqual([{"v":1161475200000,"label":"22 Oct"},{"v":1161561600000,"label":"23 Oct"},{"v":1161648000000,"label":"24 Oct"},{"v":1161734400000,"label":"25 Oct"},{"v":1161820800000,"label":"26 Oct"},{"v":1161907200000,"label":"27 Oct"},{"v":1161993600000,"label":"28 Oct"}], ticker(1161471164461, 1161994065957, 600, options)); assert.deepEqual([{"v":1161561600000,"label":"23 Oct"},{"v":1161583200000,"label":"06:00"},{"v":1161604800000,"label":"12:00"},{"v":1161626400000,"label":"18:00"}], ticker(1161557878860, 1161642991675, 600, options)); assert.deepEqual([{"v":1161756000000,"label":"06:00"},{"v":1161759600000,"label":"07:00"},{"v":1161763200000,"label":"08:00"},{"v":1161766800000,"label":"09:00"},{"v":1161770400000,"label":"10:00"},{"v":1161774000000,"label":"11:00"},{"v":1161777600000,"label":"12:00"}], ticker(1161752537840, 1161777663332, 600, options)); assert.deepEqual([{"v":1167609600000,"label":"01 Jan"},{"v":1167696000000,"label":"02 Jan"},{"v":1167782400000,"label":"03 Jan"},{"v":1167868800000,"label":"04 Jan"},{"v":1167955200000,"label":"05 Jan"},{"v":1168041600000,"label":"06 Jan"},{"v":1168128000000,"label":"07 Jan"},{"v":1168214400000,"label":"08 Jan"},{"v":1168300800000,"label":"09 Jan"}], ticker(1167609600000, 1168300800000, 480, options)); assert.deepEqual([{"v":1167609600000,"label":"Jan 2007"}], ticker(1167609600000, 1199059200000, 100, options)); assert.deepEqual([{"v":1167609600000,"label":"Jan 2007"},{"v":1175385600000,"label":"Apr 2007"},{"v":1183248000000,"label":"Jul 2007"},{"v":1191196800000,"label":"Oct 2007"}], ticker(1167609600000, 1199059200000, 300, options)); assert.deepEqual([{"v":1167609600000,"label":"Jan 2007"},{"v":1175385600000,"label":"Apr 2007"},{"v":1183248000000,"label":"Jul 2007"},{"v":1191196800000,"label":"Oct 2007"}], ticker(1167609600000, 1199059200000, 480, options)); assert.deepEqual([{"v":1167609600000,"label":"Jan 2007"},{"v":1175385600000,"label":"Apr 2007"},{"v":1183248000000,"label":"Jul 2007"},{"v":1191196800000,"label":"Oct 2007"}], ticker(1167609600000, 1199059200000, 600, options)); assert.deepEqual([{"v":1160611200000,"label":"12 Oct"},{"v":1160784000000,"label":"14 Oct"},{"v":1160956800000,"label":"16 Oct"},{"v":1161129600000,"label":"18 Oct"}], ticker(1160521200000, 1161298800000, 480, options)); assert.deepEqual([{"v":1167609600000,"label":"Jan 2007"},{"v":1170288000000,"label":"Feb 2007"},{"v":1172707200000,"label":"Mar 2007"},{"v":1175385600000,"label":"Apr 2007"},{"v":1177977600000,"label":"May 2007"},{"v":1180656000000,"label":"Jun 2007"},{"v":1183248000000,"label":"Jul 2007"},{"v":1185926400000,"label":"Aug 2007"},{"v":1188604800000,"label":"Sep 2007"},{"v":1191196800000,"label":"Oct 2007"},{"v":1193875200000,"label":"Nov 2007"},{"v":1196467200000,"label":"Dec 2007"}], ticker(1167609600000, 1199059200000, 800, options)); assert.deepEqual([{"v":1293840000000,"label":"Jan 2011"},{"v":1296518400000,"label":"Feb 2011"},{"v":1298937600000,"label":"Mar 2011"},{"v":1301616000000,"label":"Apr 2011"},{"v":1304208000000,"label":"May 2011"},{"v":1306886400000,"label":"Jun 2011"},{"v":1309478400000,"label":"Jul 2011"},{"v":1312156800000,"label":"Aug 2011"}], ticker(1293753600000, 1312844400000, 727, options)); assert.deepEqual([{"v":1201824000000,"label":"01 Feb"},{"v":1201910400000,"label":"02 Feb"},{"v":1201996800000,"label":"03 Feb"},{"v":1202083200000,"label":"04 Feb"},{"v":1202169600000,"label":"05 Feb"},{"v":1202256000000,"label":"06 Feb"}], ticker(1201824000000, 1202256000000, 700, options)); assert.deepEqual([{"v":1210118400000,"label":"07 May"},{"v":1210140000000,"label":"06:00"},{"v":1210161600000,"label":"12:00"},{"v":1210183200000,"label":"18:00"},{"v":1210204800000,"label":"08 May"},{"v":1210226400000,"label":"06:00"},{"v":1210248000000,"label":"12:00"},{"v":1210269600000,"label":"18:00"},{"v":1210291200000,"label":"09 May"}], ticker(1210114800000, 1210291200000, 480, options)); assert.deepEqual([{"v":1210118400000,"label":"07 May"},{"v":1210204800000,"label":"08 May"},{"v":1210291200000,"label":"09 May"},{"v":1210377600000,"label":"10 May"},{"v":1210464000000,"label":"11 May"}], ticker(1210114800000, 1210464000000, 480, options)); assert.deepEqual([{"v":1210118400000,"label":"07 May"},{"v":1210204800000,"label":"08 May"},{"v":1210291200000,"label":"09 May"},{"v":1210377600000,"label":"10 May"},{"v":1210464000000,"label":"11 May"},{"v":1210550400000,"label":"12 May"}], ticker(1210114800000, 1210550400000, 480, options)); assert.deepEqual([{"v":1214870400000,"label":"01 Jul"},{"v":1214872200000,"label":"00:30"},{"v":1214874000000,"label":"01:00"},{"v":1214875800000,"label":"01:30"}], ticker(1214870400000, 1214877599000, 600, options)); assert.deepEqual([{"v":1214870400000,"label":"Jul 2008"},{"v":1217548800000,"label":"Aug 2008"},{"v":1220227200000,"label":"Sep 2008"}], ticker(1214866800000, 1222747200000, 600, options)); assert.deepEqual([{"v":1215820800000,"label":"12 Jul"},{"v":1215842400000,"label":"06:00"},{"v":1215864000000,"label":"12:00"},{"v":1215885600000,"label":"18:00"},{"v":1215907200000,"label":"13 Jul"},{"v":1215928800000,"label":"06:00"},{"v":1215950400000,"label":"12:00"},{"v":1215972000000,"label":"18:00"}], ticker(1215817200000, 1215989940000, 600, options)); assert.deepEqual([{"v":1246752000000,"label":"05 Jul"},{"v":1247356800000,"label":"12 Jul"},{"v":1247961600000,"label":"19 Jul"}], ticker(1246402800000, 1248217200000, 600, options)); assert.deepEqual([{"v":1246752000000,"label":"05 Jul"},{"v":1247356800000,"label":"12 Jul"},{"v":1247961600000,"label":"19 Jul"},{"v":1248566400000,"label":"26 Jul"},{"v":1249171200000,"label":"02 Aug"}], ticker(1246402800000, 1249340400000, 600, options)); assert.deepEqual([{"v":1247356800000,"label":"12 Jul"},{"v":1247360400000,"label":"01:00"},{"v":1247364000000,"label":"02:00"},{"v":1247367600000,"label":"03:00"},{"v":1247371200000,"label":"04:00"},{"v":1247374800000,"label":"05:00"},{"v":1247378400000,"label":"06:00"}], ticker(1247356800000, 1247378400000, 600, options)); assert.deepEqual([{"v":1247356800000,"label":"12 Jul"},{"v":1247360400000,"label":"01:00"},{"v":1247364000000,"label":"02:00"},{"v":1247367600000,"label":"03:00"},{"v":1247371200000,"label":"04:00"},{"v":1247374800000,"label":"05:00"},{"v":1247378400000,"label":"06:00"}], ticker(1247356800000, 1247378400000, 600, options)); assert.deepEqual([{"v":1254268800000,"label":"30 Sep"},{"v":1254355200000,"label":"01 Oct"},{"v":1254441600000,"label":"02 Oct"},{"v":1254528000000,"label":"03 Oct"},{"v":1254614400000,"label":"04 Oct"},{"v":1254700800000,"label":"05 Oct"},{"v":1254787200000,"label":"06 Oct"},{"v":1254873600000,"label":"07 Oct"},{"v":1254960000000,"label":"08 Oct"},{"v":1255046400000,"label":"09 Oct"},{"v":1255132800000,"label":"10 Oct"}], ticker(1254222000000, 1255172400000, 900, options)); assert.deepEqual([{"v":1254441600000,"label":"02 Oct"},{"v":1254528000000,"label":"03 Oct"},{"v":1254614400000,"label":"04 Oct"},{"v":1254700800000,"label":"05 Oct"},{"v":1254787200000,"label":"06 Oct"},{"v":1254873600000,"label":"07 Oct"},{"v":1254960000000,"label":"08 Oct"}], ticker(1254394800000, 1254999600000, 900, options)); assert.deepEqual([{"v":1259625600000,"label":"01 Dec"},{"v":1259712000000,"label":"02 Dec"},{"v":1259798400000,"label":"03 Dec"},{"v":1259884800000,"label":"04 Dec"},{"v":1259971200000,"label":"05 Dec"},{"v":1260057600000,"label":"06 Dec"},{"v":1260144000000,"label":"07 Dec"}], ticker(1259625600000, 1260144000000, 480, options)); assert.deepEqual([{"v":1259625600000,"label":"01 Dec"},{"v":1259712000000,"label":"02 Dec"},{"v":1259798400000,"label":"03 Dec"},{"v":1259884800000,"label":"04 Dec"},{"v":1259971200000,"label":"05 Dec"},{"v":1260057600000,"label":"06 Dec"},{"v":1260144000000,"label":"07 Dec"}], ticker(1259625600000, 1260144000000, 600, options)); assert.deepEqual([{"v":1260057600000,"label":"06 Dec"},{"v":1260662400000,"label":"13 Dec"},{"v":1261267200000,"label":"20 Dec"},{"v":1261872000000,"label":"27 Dec"},{"v":1262476800000,"label":"03 Jan"},{"v":1263081600000,"label":"10 Jan"},{"v":1263686400000,"label":"17 Jan"},{"v":1264291200000,"label":"24 Jan"}], ticker(1260057600000, 1264291200000, 640, options)); assert.deepEqual([{"v":1262304000000,"label":"Jan 2010"},{"v":1264982400000,"label":"Feb 2010"},{"v":1267401600000,"label":"Mar 2010"},{"v":1270080000000,"label":"Apr 2010"}], ticker(1262304000000, 1270857600000, 640, options)); assert.deepEqual([{"v":1288915200000,"label":"05 Nov"},{"v":1288936800000,"label":"06:00"},{"v":1288958400000,"label":"12:00"},{"v":1288980000000,"label":"18:00"},{"v":1289001600000,"label":"06 Nov"},{"v":1289023200000,"label":"06:00"},{"v":1289044800000,"label":"12:00"},{"v":1289066400000,"label":"18:00"},{"v":1289088000000,"label":"07 Nov"},{"v":1289109600000,"label":"06:00"},{"v":1289131200000,"label":"12:00"},{"v":1289152800000,"label":"18:00"},{"v":1289174400000,"label":"08 Nov"},{"v":1289196000000,"label":"06:00"},{"v":1289217600000,"label":"12:00"},{"v":1289239200000,"label":"18:00"},{"v":1289260800000,"label":"09 Nov"}], ticker(1288911600000, 1289260800000, 1024, options)); assert.deepEqual([{"v":1291161600000,"label":"01 Dec"},{"v":1291248000000,"label":"02 Dec"},{"v":1291334400000,"label":"03 Dec"},{"v":1291420800000,"label":"04 Dec"},{"v":1291507200000,"label":"05 Dec"},{"v":1291593600000,"label":"06 Dec"},{"v":1291680000000,"label":"07 Dec"},{"v":1291766400000,"label":"08 Dec"},{"v":1291852800000,"label":"09 Dec"}], ticker(1291161600000, 1291852800000, 600, options)); assert.deepEqual([{"v":1294358400000,"label":"07 Jan"},{"v":1294444800000,"label":"08 Jan"},{"v":1294531200000,"label":"09 Jan"},{"v":1294617600000,"label":"10 Jan"},{"v":1294704000000,"label":"11 Jan"},{"v":1294790400000,"label":"12 Jan"},{"v":1294876800000,"label":"13 Jan"},{"v":1294963200000,"label":"14 Jan"}], ticker(1294358400000, 1294963200000, 480, options)); assert.deepEqual([{"v":1307908005000,"label":"19:46:45"},{"v":1307908010000,"label":"19:46:50"},{"v":1307908015000,"label":"19:46:55"},{"v":1307908020000,"label":"19:47"},{"v":1307908025000,"label":"19:47:05"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908035000,"label":"19:47:15"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908045000,"label":"19:47:25"},{"v":1307908050000,"label":"19:47:30"}], ticker(1307908000112, 1307908050165, 800, options)); assert.deepEqual([{"v":1307908005000,"label":"19:46:45"},{"v":1307908010000,"label":"19:46:50"},{"v":1307908015000,"label":"19:46:55"},{"v":1307908020000,"label":"19:47"},{"v":1307908025000,"label":"19:47:05"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908035000,"label":"19:47:15"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908045000,"label":"19:47:25"},{"v":1307908050000,"label":"19:47:30"}], ticker(1307908000112, 1307908051166, 800, options)); assert.deepEqual([{"v":1307908005000,"label":"19:46:45"},{"v":1307908010000,"label":"19:46:50"},{"v":1307908015000,"label":"19:46:55"},{"v":1307908020000,"label":"19:47"},{"v":1307908025000,"label":"19:47:05"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908035000,"label":"19:47:15"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908045000,"label":"19:47:25"},{"v":1307908050000,"label":"19:47:30"}], ticker(1307908000112, 1307908052167, 800, options)); assert.deepEqual([{"v":1307908005000,"label":"19:46:45"},{"v":1307908010000,"label":"19:46:50"},{"v":1307908015000,"label":"19:46:55"},{"v":1307908020000,"label":"19:47"},{"v":1307908025000,"label":"19:47:05"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908035000,"label":"19:47:15"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908045000,"label":"19:47:25"},{"v":1307908050000,"label":"19:47:30"}], ticker(1307908000112, 1307908053167, 800, options)); assert.deepEqual([{"v":1307908005000,"label":"19:46:45"},{"v":1307908010000,"label":"19:46:50"},{"v":1307908015000,"label":"19:46:55"},{"v":1307908020000,"label":"19:47"},{"v":1307908025000,"label":"19:47:05"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908035000,"label":"19:47:15"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908045000,"label":"19:47:25"},{"v":1307908050000,"label":"19:47:30"}], ticker(1307908000112, 1307908054168, 800, options)); assert.deepEqual([{"v":1307908005000,"label":"19:46:45"},{"v":1307908010000,"label":"19:46:50"},{"v":1307908015000,"label":"19:46:55"},{"v":1307908020000,"label":"19:47"},{"v":1307908025000,"label":"19:47:05"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908035000,"label":"19:47:15"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908045000,"label":"19:47:25"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908055000,"label":"19:47:35"}], ticker(1307908000112, 1307908055169, 800, options)); assert.deepEqual([{"v":1307908005000,"label":"19:46:45"},{"v":1307908010000,"label":"19:46:50"},{"v":1307908015000,"label":"19:46:55"},{"v":1307908020000,"label":"19:47"},{"v":1307908025000,"label":"19:47:05"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908035000,"label":"19:47:15"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908045000,"label":"19:47:25"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908055000,"label":"19:47:35"}], ticker(1307908000112, 1307908056169, 800, options)); assert.deepEqual([{"v":1307908005000,"label":"19:46:45"},{"v":1307908010000,"label":"19:46:50"},{"v":1307908015000,"label":"19:46:55"},{"v":1307908020000,"label":"19:47"},{"v":1307908025000,"label":"19:47:05"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908035000,"label":"19:47:15"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908045000,"label":"19:47:25"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908055000,"label":"19:47:35"}], ticker(1307908000112, 1307908057170, 800, options)); assert.deepEqual([{"v":1307908005000,"label":"19:46:45"},{"v":1307908010000,"label":"19:46:50"},{"v":1307908015000,"label":"19:46:55"},{"v":1307908020000,"label":"19:47"},{"v":1307908025000,"label":"19:47:05"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908035000,"label":"19:47:15"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908045000,"label":"19:47:25"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908055000,"label":"19:47:35"}], ticker(1307908000112, 1307908058171, 800, options)); assert.deepEqual([{"v":1307908005000,"label":"19:46:45"},{"v":1307908010000,"label":"19:46:50"},{"v":1307908015000,"label":"19:46:55"},{"v":1307908020000,"label":"19:47"},{"v":1307908025000,"label":"19:47:05"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908035000,"label":"19:47:15"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908045000,"label":"19:47:25"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908055000,"label":"19:47:35"}], ticker(1307908000112, 1307908059172, 800, options)); assert.deepEqual([{"v":1307908005000,"label":"19:46:45"},{"v":1307908010000,"label":"19:46:50"},{"v":1307908015000,"label":"19:46:55"},{"v":1307908020000,"label":"19:47"},{"v":1307908025000,"label":"19:47:05"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908035000,"label":"19:47:15"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908045000,"label":"19:47:25"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908055000,"label":"19:47:35"},{"v":1307908060000,"label":"19:47:40"}], ticker(1307908000112, 1307908060172, 800, options)); assert.deepEqual([{"v":1307908005000,"label":"19:46:45"},{"v":1307908010000,"label":"19:46:50"},{"v":1307908015000,"label":"19:46:55"},{"v":1307908020000,"label":"19:47"},{"v":1307908025000,"label":"19:47:05"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908035000,"label":"19:47:15"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908045000,"label":"19:47:25"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908055000,"label":"19:47:35"},{"v":1307908060000,"label":"19:47:40"}], ticker(1307908000112, 1307908061174, 800, options)); assert.deepEqual([{"v":1307908005000,"label":"19:46:45"},{"v":1307908010000,"label":"19:46:50"},{"v":1307908015000,"label":"19:46:55"},{"v":1307908020000,"label":"19:47"},{"v":1307908025000,"label":"19:47:05"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908035000,"label":"19:47:15"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908045000,"label":"19:47:25"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908055000,"label":"19:47:35"},{"v":1307908060000,"label":"19:47:40"}], ticker(1307908000112, 1307908062176, 800, options)); assert.deepEqual([{"v":1307908005000,"label":"19:46:45"},{"v":1307908010000,"label":"19:46:50"},{"v":1307908015000,"label":"19:46:55"},{"v":1307908020000,"label":"19:47"},{"v":1307908025000,"label":"19:47:05"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908035000,"label":"19:47:15"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908045000,"label":"19:47:25"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908055000,"label":"19:47:35"},{"v":1307908060000,"label":"19:47:40"}], ticker(1307908000112, 1307908063177, 800, options)); assert.deepEqual([{"v":1307908005000,"label":"19:46:45"},{"v":1307908010000,"label":"19:46:50"},{"v":1307908015000,"label":"19:46:55"},{"v":1307908020000,"label":"19:47"},{"v":1307908025000,"label":"19:47:05"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908035000,"label":"19:47:15"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908045000,"label":"19:47:25"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908055000,"label":"19:47:35"},{"v":1307908060000,"label":"19:47:40"}], ticker(1307908000112, 1307908064178, 800, options)); assert.deepEqual([{"v":1307908005000,"label":"19:46:45"},{"v":1307908010000,"label":"19:46:50"},{"v":1307908015000,"label":"19:46:55"},{"v":1307908020000,"label":"19:47"},{"v":1307908025000,"label":"19:47:05"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908035000,"label":"19:47:15"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908045000,"label":"19:47:25"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908055000,"label":"19:47:35"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908065000,"label":"19:47:45"}], ticker(1307908000112, 1307908065178, 800, options)); assert.deepEqual([{"v":1307908005000,"label":"19:46:45"},{"v":1307908010000,"label":"19:46:50"},{"v":1307908015000,"label":"19:46:55"},{"v":1307908020000,"label":"19:47"},{"v":1307908025000,"label":"19:47:05"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908035000,"label":"19:47:15"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908045000,"label":"19:47:25"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908055000,"label":"19:47:35"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908065000,"label":"19:47:45"}], ticker(1307908000112, 1307908066178, 800, options)); assert.deepEqual([{"v":1307908005000,"label":"19:46:45"},{"v":1307908010000,"label":"19:46:50"},{"v":1307908015000,"label":"19:46:55"},{"v":1307908020000,"label":"19:47"},{"v":1307908025000,"label":"19:47:05"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908035000,"label":"19:47:15"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908045000,"label":"19:47:25"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908055000,"label":"19:47:35"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908065000,"label":"19:47:45"}], ticker(1307908000112, 1307908067179, 800, options)); assert.deepEqual([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"}], ticker(1307908000112, 1307908068179, 800, options)); assert.deepEqual([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"}], ticker(1307908000112, 1307908069179, 800, options)); assert.deepEqual([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"}], ticker(1307908000112, 1307908070180, 800, options)); assert.deepEqual([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"}], ticker(1307908000112, 1307908071180, 800, options)); assert.deepEqual([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"}], ticker(1307908000112, 1307908072181, 800, options)); assert.deepEqual([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"}], ticker(1307908000112, 1307908073181, 800, options)); assert.deepEqual([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"}], ticker(1307908000112, 1307908074182, 800, options)); assert.deepEqual([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"}], ticker(1307908000112, 1307908075182, 800, options)); assert.deepEqual([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"}], ticker(1307908000112, 1307908076183, 800, options)); assert.deepEqual([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"}], ticker(1307908000112, 1307908077183, 800, options)); assert.deepEqual([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"}], ticker(1307908000112, 1307908078184, 800, options)); assert.deepEqual([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"}], ticker(1307908000112, 1307908079185, 800, options)); assert.deepEqual([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"}], ticker(1307908000112, 1307908080186, 800, options)); assert.deepEqual([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"}], ticker(1307908000112, 1307908081187, 800, options)); assert.deepEqual([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"}], ticker(1307908000112, 1307908082188, 800, options)); assert.deepEqual([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"}], ticker(1307908000112, 1307908083188, 800, options)); assert.deepEqual([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"}], ticker(1307908000112, 1307908084189, 800, options)); assert.deepEqual([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"}], ticker(1307908000112, 1307908085190, 800, options)); assert.deepEqual([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"}], ticker(1307908000112, 1307908086191, 800, options)); assert.deepEqual([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"}], ticker(1307908000112, 1307908087192, 800, options)); assert.deepEqual([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"}], ticker(1307908000112, 1307908088192, 800, options)); assert.deepEqual([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"}], ticker(1307908000112, 1307908089193, 800, options)); assert.deepEqual([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"}], ticker(1307908000112, 1307908090194, 800, options)); assert.deepEqual([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"}], ticker(1307908000112, 1307908091194, 800, options)); assert.deepEqual([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"}], ticker(1307908000112, 1307908092196, 800, options)); assert.deepEqual([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"}], ticker(1307908000112, 1307908093196, 800, options)); assert.deepEqual([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"}], ticker(1307908000112, 1307908094197, 800, options)); assert.deepEqual([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"}], ticker(1307908000112, 1307908095197, 800, options)); assert.deepEqual([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"}], ticker(1307908000112, 1307908096198, 800, options)); assert.deepEqual([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"}], ticker(1307908000112, 1307908097199, 800, options)); assert.deepEqual([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"}], ticker(1307908000112, 1307908098200, 800, options)); assert.deepEqual([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"}], ticker(1307908000112, 1307908099200, 800, options)); assert.deepEqual([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"},{"v":1307908100000,"label":"19:48:20"}], ticker(1307908000112, 1307908100201, 800, options)); assert.deepEqual([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"},{"v":1307908100000,"label":"19:48:20"}], ticker(1307908000112, 1307908101201, 800, options)); assert.deepEqual([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"},{"v":1307908100000,"label":"19:48:20"}], ticker(1307908000112, 1307908102202, 800, options)); assert.deepEqual([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"},{"v":1307908100000,"label":"19:48:20"}], ticker(1307908000112, 1307908103203, 800, options)); assert.deepEqual([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"},{"v":1307908100000,"label":"19:48:20"}], ticker(1307908000112, 1307908104204, 800, options)); assert.deepEqual([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"},{"v":1307908100000,"label":"19:48:20"}], ticker(1307908000112, 1307908105205, 800, options)); assert.deepEqual([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"},{"v":1307908100000,"label":"19:48:20"}], ticker(1307908000112, 1307908106205, 800, options)); assert.deepEqual([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"},{"v":1307908100000,"label":"19:48:20"}], ticker(1307908000112, 1307908107206, 800, options)); assert.deepEqual([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"},{"v":1307908100000,"label":"19:48:20"}], ticker(1307908000112, 1307908108209, 800, options)); assert.deepEqual([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"},{"v":1307908100000,"label":"19:48:20"}], ticker(1307908000112, 1307908109209, 800, options)); assert.deepEqual([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"},{"v":1307908100000,"label":"19:48:20"},{"v":1307908110000,"label":"19:48:30"}], ticker(1307908000112, 1307908110209, 800, options)); assert.deepEqual([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"},{"v":1307908100000,"label":"19:48:20"},{"v":1307908110000,"label":"19:48:30"}], ticker(1307908000112, 1307908111210, 800, options)); assert.deepEqual([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"},{"v":1307908100000,"label":"19:48:20"},{"v":1307908110000,"label":"19:48:30"}], ticker(1307908000112, 1307908112211, 800, options)); assert.deepEqual([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"},{"v":1307908100000,"label":"19:48:20"},{"v":1307908110000,"label":"19:48:30"}], ticker(1307908000112, 1307908113211, 800, options)); assert.deepEqual([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"},{"v":1307908100000,"label":"19:48:20"},{"v":1307908110000,"label":"19:48:30"}], ticker(1307908000112, 1307908114212, 800, options)); assert.deepEqual([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"},{"v":1307908100000,"label":"19:48:20"},{"v":1307908110000,"label":"19:48:30"}], ticker(1307908000112, 1307908115213, 800, options)); assert.deepEqual([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"},{"v":1307908100000,"label":"19:48:20"},{"v":1307908110000,"label":"19:48:30"}], ticker(1307908000112, 1307908116214, 800, options)); assert.deepEqual([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"},{"v":1307908100000,"label":"19:48:20"},{"v":1307908110000,"label":"19:48:30"}], ticker(1307908000112, 1307908117214, 800, options)); assert.deepEqual([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"},{"v":1307908100000,"label":"19:48:20"},{"v":1307908110000,"label":"19:48:30"}], ticker(1307908000112, 1307908118215, 800, options)); assert.deepEqual([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"},{"v":1307908100000,"label":"19:48:20"},{"v":1307908110000,"label":"19:48:30"}], ticker(1307908000112, 1307908119215, 800, options)); assert.deepEqual([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"},{"v":1307908100000,"label":"19:48:20"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908120000,"label":"19:48:40"}], ticker(1307908000112, 1307908120217, 800, options)); assert.deepEqual([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"},{"v":1307908100000,"label":"19:48:20"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908120000,"label":"19:48:40"}], ticker(1307908000112, 1307908121218, 800, options)); assert.deepEqual([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"},{"v":1307908100000,"label":"19:48:20"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908120000,"label":"19:48:40"}], ticker(1307908000112, 1307908122219, 800, options)); assert.deepEqual([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"},{"v":1307908100000,"label":"19:48:20"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908120000,"label":"19:48:40"}], ticker(1307908000112, 1307908123219, 800, options)); assert.deepEqual([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"},{"v":1307908100000,"label":"19:48:20"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908120000,"label":"19:48:40"}], ticker(1307908000112, 1307908124220, 800, options)); assert.deepEqual([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"},{"v":1307908100000,"label":"19:48:20"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908120000,"label":"19:48:40"}], ticker(1307908000112, 1307908125221, 800, options)); assert.deepEqual([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"},{"v":1307908100000,"label":"19:48:20"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908120000,"label":"19:48:40"}], ticker(1307908000112, 1307908126222, 800, options)); assert.deepEqual([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"},{"v":1307908100000,"label":"19:48:20"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908120000,"label":"19:48:40"}], ticker(1307908000112, 1307908127222, 800, options)); assert.deepEqual([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"},{"v":1307908100000,"label":"19:48:20"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908120000,"label":"19:48:40"}], ticker(1307908000112, 1307908128223, 800, options)); assert.deepEqual([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"},{"v":1307908100000,"label":"19:48:20"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908120000,"label":"19:48:40"}], ticker(1307908000112, 1307908129223, 800, options)); assert.deepEqual([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"},{"v":1307908100000,"label":"19:48:20"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908120000,"label":"19:48:40"},{"v":1307908130000,"label":"19:48:50"}], ticker(1307908000112, 1307908130224, 800, options)); assert.deepEqual([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"},{"v":1307908100000,"label":"19:48:20"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908120000,"label":"19:48:40"},{"v":1307908130000,"label":"19:48:50"}], ticker(1307908000112, 1307908131225, 800, options)); assert.deepEqual([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"},{"v":1307908100000,"label":"19:48:20"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908120000,"label":"19:48:40"},{"v":1307908130000,"label":"19:48:50"}], ticker(1307908000112, 1307908132226, 800, options)); assert.deepEqual([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"},{"v":1307908100000,"label":"19:48:20"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908120000,"label":"19:48:40"},{"v":1307908130000,"label":"19:48:50"}], ticker(1307908000112, 1307908133227, 800, options)); assert.deepEqual([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"},{"v":1307908100000,"label":"19:48:20"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908120000,"label":"19:48:40"},{"v":1307908130000,"label":"19:48:50"}], ticker(1307908000112, 1307908134227, 800, options)); assert.deepEqual([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"}], ticker(1307908000112, 1307908135227, 800, options)); assert.deepEqual([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"}], ticker(1307908000112, 1307908136228, 800, options)); assert.deepEqual([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"}], ticker(1307908000112, 1307908137230, 800, options)); assert.deepEqual([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"}], ticker(1307908000112, 1307908138231, 800, options)); assert.deepEqual([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"}], ticker(1307908000112, 1307908139232, 800, options)); assert.deepEqual([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908140000,"label":"19:49"}], ticker(1307908000112, 1307908140233, 800, options)); assert.deepEqual([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908140000,"label":"19:49"}], ticker(1307908000112, 1307908141233, 800, options)); assert.deepEqual([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908140000,"label":"19:49"}], ticker(1307908000112, 1307908142234, 800, options)); assert.deepEqual([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908140000,"label":"19:49"}], ticker(1307908000112, 1307908143240, 800, options)); assert.deepEqual([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908140000,"label":"19:49"}], ticker(1307908000112, 1307908144240, 800, options)); assert.deepEqual([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908140000,"label":"19:49"}], ticker(1307908000112, 1307908145240, 800, options)); assert.deepEqual([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908140000,"label":"19:49"}], ticker(1307908000112, 1307908146241, 800, options)); assert.deepEqual([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908140000,"label":"19:49"}], ticker(1307908000112, 1307908147241, 800, options)); assert.deepEqual([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908140000,"label":"19:49"}], ticker(1307908000112, 1307908148242, 800, options)); assert.deepEqual([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908140000,"label":"19:49"}], ticker(1307908000112, 1307908149243, 800, options)); assert.deepEqual([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908140000,"label":"19:49"}], ticker(1307908000112, 1307908150243, 800, options)); assert.deepEqual([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908140000,"label":"19:49"}], ticker(1307908000112, 1307908151244, 800, options)); assert.deepEqual([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908140000,"label":"19:49"}], ticker(1307908000112, 1307908152245, 800, options)); assert.deepEqual([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908140000,"label":"19:49"}], ticker(1307908000112, 1307908153245, 800, options)); assert.deepEqual([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908140000,"label":"19:49"}], ticker(1307908000112, 1307908154246, 800, options)); assert.deepEqual([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908140000,"label":"19:49"}], ticker(1307908000112, 1307908155247, 800, options)); assert.deepEqual([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908140000,"label":"19:49"}], ticker(1307908000112, 1307908156247, 800, options)); assert.deepEqual([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908140000,"label":"19:49"}], ticker(1307908000112, 1307908157248, 800, options)); assert.deepEqual([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908140000,"label":"19:49"}], ticker(1307908000112, 1307908158249, 800, options)); assert.deepEqual([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908140000,"label":"19:49"}], ticker(1307908000112, 1307908159250, 800, options)); assert.deepEqual([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908140000,"label":"19:49"}], ticker(1307908000112, 1307908160251, 800, options)); assert.deepEqual([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908140000,"label":"19:49"}], ticker(1307908000112, 1307908161252, 800, options)); assert.deepEqual([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908140000,"label":"19:49"}], ticker(1307908000112, 1307908162252, 800, options)); assert.deepEqual([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908140000,"label":"19:49"}], ticker(1307908000112, 1307908163253, 800, options)); assert.deepEqual([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908140000,"label":"19:49"}], ticker(1307908000112, 1307908164254, 800, options)); assert.deepEqual([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908140000,"label":"19:49"}], ticker(1307908000112, 1307908165254, 800, options)); assert.deepEqual([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908140000,"label":"19:49"}], ticker(1307908000112, 1307908166255, 800, options)); assert.deepEqual([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908140000,"label":"19:49"}], ticker(1307908000112, 1307908167256, 800, options)); assert.deepEqual([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908140000,"label":"19:49"}], ticker(1307908000112, 1307908168256, 800, options)); assert.deepEqual([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908140000,"label":"19:49"}], ticker(1307908000112, 1307908169257, 800, options)); assert.deepEqual([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908140000,"label":"19:49"},{"v":1307908170000,"label":"19:49:30"}], ticker(1307908000112, 1307908170258, 800, options)); assert.deepEqual([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908140000,"label":"19:49"},{"v":1307908170000,"label":"19:49:30"}], ticker(1307908000112, 1307908171258, 800, options)); assert.deepEqual([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908140000,"label":"19:49"},{"v":1307908170000,"label":"19:49:30"}], ticker(1307908000112, 1307908172259, 800, options)); assert.deepEqual([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908140000,"label":"19:49"},{"v":1307908170000,"label":"19:49:30"}], ticker(1307908000112, 1307908173260, 800, options)); assert.deepEqual([{"v":978307200000,"label":"Jan 2001"},{"v":986083200000,"label":"Apr 2001"},{"v":993945600000,"label":"Jul 2001"},{"v":1001894400000,"label":"Oct 2001"}], ticker(978307200000, 1001894400000, 400, options)); assert.deepEqual([{"v":1307908000110,"label":"40.110"},{"v":1307908000111,"label":"40.111"},{"v":1307908000112,"label":"40.112"},{"v":1307908000113,"label":"40.113"}], ticker(1307908000110, 1307908000113, 200, options)); assert.deepEqual([{"v":1307908000110,"label":"40.110"},{"v":1307908000112,"label":"40.112"},{"v":1307908000114,"label":"40.114"},{"v":1307908000116,"label":"40.116"}], ticker(1307908000110, 1307908000116, 200, options)); assert.deepEqual([{"v":1307908000110,"label":"40.110"},{"v":1307908000115,"label":"40.115"},{"v":1307908000120,"label":"40.120"},{"v":1307908000125,"label":"40.125"}], ticker(1307908000110, 1307908000125, 200, options)); assert.deepEqual([{"v":1307908000110,"label":"40.110"},{"v":1307908000120,"label":"40.120"},{"v":1307908000130,"label":"40.130"},{"v":1307908000140,"label":"40.140"}], ticker(1307908000110, 1307908000140, 200, options)); assert.deepEqual([{"v":1307908000100,"label":"40.100"},{"v":1307908000150,"label":"40.150"},{"v":1307908000200,"label":"40.200"},{"v":1307908000250,"label":"40.250"}], ticker(1307908000100, 1307908000250, 200, options)); assert.deepEqual([{"v":1307908000100,"label":"40.100"},{"v":1307908000150,"label":"40.150"},{"v":1307908000200,"label":"40.200"},{"v":1307908000250,"label":"40.250"}], ticker(1307908000090, 1307908000260, 200, options)); assert.deepEqual([{"v":1307908000100,"label":"40.100"},{"v":1307908000200,"label":"40.200"},{"v":1307908000300,"label":"40.300"},{"v":1307908000400,"label":"40.400"}], ticker(1307908000100, 1307908000400, 200, options)); assert.deepEqual([{"v":1307908000100,"label":"40.100"},{"v":1307908000200,"label":"40.200"},{"v":1307908000300,"label":"40.300"},{"v":1307908000400,"label":"40.400"}], ticker(1307908000100, 1307908000410, 200, options)); assert.deepEqual([{"v":1307908000000,"label":"40.000"},{"v":1307908000500,"label":"40.500"},{"v":1307908001000,"label":"41.000"},{"v":1307908001500,"label":"41.500"}], ticker(1307908000000, 1307908001500, 200, options)); }); }); dygraphs-2.2.1/auto_tests/tests/draw_gap_edge_points.js010064400000000000000000000027371437353256000203430ustar00/** * @fileoverview Test cases for the option "drawGapEdgePoints" */ import Dygraph from '../../src/dygraph'; import * as utils from '../../src/dygraph-utils'; describe("draw-gap-edge-points", function() { cleanupAfterEach(); it("shouldn't draw any points by default", function() { var called = false; var g = new Dygraph(document.getElementById("graph"), [[0, 0], [1, 1], [2, 2], [3, 3], [4, 4], [5, 5]], {labels: ['a', 'b'], drawGapEdgePoints: true, drawPointCallback: function() { called = true; }}); assert.isFalse(called); }); it("shouldn't draw any points by default (no axes)", function() { var called = false; var g = new Dygraph(document.getElementById("graph"), [[0, 0], [1, 1], [2, 2], [3, 3], [4, 4], [5, 5]], {labels: ['a', 'b'], drawGapEdgePoints: true, drawPointCallback: function() { called = true; }, axes: { x: { drawAxis: false }, y: { drawAxis: false } }}); assert.isFalse(called); }); }); dygraphs-2.2.1/auto_tests/tests/dygraph-options-tests.js010064400000000000000000000066121437353256000204620ustar00/** * @fileoverview Test cases for DygraphOptions. */ import Dygraph from '../../src/dygraph'; import DygraphOptions from '../../src/dygraph-options'; import OPTIONS_REFERENCE from '../../src/dygraph-options-reference'; describe("dygraph-options-tests", function() { cleanupAfterEach(); var graph; beforeEach(function() { graph = document.getElementById("graph"); }); /* * Pathalogical test to ensure getSeriesNames works */ it('testGetSeriesNames', function() { var opts = { width: 480, height: 320 }; var data = "X,Y,Y2,Y3\n" + "0,-1,0,0"; // Kind of annoying that you need a DOM to test the object. var g = new Dygraph(graph, data, opts); // We don't need to get at g's attributes_ object just // to test DygraphOptions. var o = new DygraphOptions(g); assert.deepEqual(["Y", "Y2", "Y3"], o.seriesNames()); }); /* * Ensures that even if logscale is set globally, it doesn't impact the * x axis. */ it('testGetLogscaleForX', function() { var opts = { width: 480, height: 320 }; var data = "X,Y,Y2,Y3\n" + "1,-1,2,3"; // Kind of annoying that you need a DOM to test the object. var g = new Dygraph(graph, data, opts); assert.isFalse(!!g.getOptionForAxis('logscale', 'x')); assert.isFalse(!!g.getOptionForAxis('logscale', 'y')); g.updateOptions({ logscale : true }); assert.isFalse(!!g.getOptionForAxis('logscale', 'x')); assert.isTrue(!!g.getOptionForAxis('logscale', 'y')); }); // Helper to gather all warnings emitted by Dygraph constructor. // Removes everything after the first open parenthesis in each warning. // Returns them in a (possibly empty) list. var getWarnings = function(div, data, opts) { var warnings = []; var oldWarn = console.warn; console.warn = function(message) { warnings.push(message.replace(/ \(.*/, '')); }; try { new Dygraph(graph, data, opts); } catch (e) { } console.warn = oldWarn; return warnings; }; it('testLogWarningForNonexistentOption', function() { if (!OPTIONS_REFERENCE) { return; // this test won't pass in non-debug mode. } var data = "X,Y,Y2,Y3\n" + "1,-1,2,3"; var expectWarning = function(opts, badOptionName) { DygraphOptions.resetWarnings_(); var warnings = getWarnings(graph, data, opts); assert.deepEqual(['Unknown option ' + badOptionName], warnings); }; var expectNoWarning = function(opts) { DygraphOptions.resetWarnings_(); var warnings = getWarnings(graph, data, opts); assert.deepEqual([], warnings); }; expectNoWarning({}); expectWarning({nonExistentOption: true}, 'nonExistentOption'); expectWarning({series: {Y: {nonExistentOption: true}}}, 'nonExistentOption'); // expectWarning({Y: {nonExistentOption: true}}); expectWarning({axes: {y: {anotherNonExistentOption: true}}}, 'anotherNonExistentOption'); expectWarning({highlightSeriesOpts: {anotherNonExistentOption: true}}, 'anotherNonExistentOption'); expectNoWarning({highlightSeriesOpts: {strokeWidth: 20}}); expectNoWarning({strokeWidth: 20}); }); it('testOnlyLogsEachWarningOnce', function() { if (!OPTIONS_REFERENCE) { return; // this test won't pass in non-debug mode. } var data = "X,Y,Y2,Y3\n" + "1,-1,2,3"; var warnings1 = getWarnings(graph, data, {nonExistent: true}); var warnings2 = getWarnings(graph, data, {nonExistent: true}); assert.deepEqual(['Unknown option nonExistent'], warnings1); assert.deepEqual([], warnings2); }); }); dygraphs-2.2.1/auto_tests/tests/error_bars.js010064400000000000000000000144771437353256000163430ustar00/** * @fileoverview FILL THIS IN * * @author danvk@google.com (Dan Vanderkam) */ import Dygraph from '../../src/dygraph'; import * as utils from '../../src/dygraph-utils'; import Util from './Util'; import Proxy from './Proxy'; import CanvasAssertions from './CanvasAssertions'; describe("error-bars", function() { cleanupAfterEach(); useProxyCanvas(utils, Proxy); it('testErrorBarsDrawn', function() { var opts = { width: 480, height: 320, axes : { x : { drawGrid: false, drawAxis: false, }, y : { drawGrid: false, drawAxis: false, } }, customBars: true, errorBars: true, labels: ['X', 'Y'] }; var data = [ [1, [10, 10, 100]], [2, [15, 20, 110]], [3, [10, 30, 100]], [4, [15, 40, 110]], [5, [10, 120, 100]], [6, [15, 50, 110]], [7, [10, 70, 100]], [8, [15, 90, 110]], [9, [10, 50, 100]] ]; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); var htx = g.hidden_ctx_; var attrs = {}; // TODO(danvk): fill in for (var i = 0; i < data.length - 1; i++) { // bottom line var xy1 = g.toDomCoords(data[i][0], data[i][1][0]); var xy2 = g.toDomCoords(data[i + 1][0], data[i + 1][1][0]); CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); // top line xy1 = g.toDomCoords(data[i][0], data[i][1][2]); xy2 = g.toDomCoords(data[i + 1][0], data[i + 1][1][2]); CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); // middle line xy1 = g.toDomCoords(data[i][0], data[i][1][1]); xy2 = g.toDomCoords(data[i + 1][0], data[i + 1][1][1]); CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); } g.updateOptions({logscale: true}); for (var i = 0; i < data.length - 1; i++) { // bottom line var xy1 = g.toDomCoords(data[i][0], data[i][1][0]); var xy2 = g.toDomCoords(data[i + 1][0], data[i + 1][1][0]); CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); // top line xy1 = g.toDomCoords(data[i][0], data[i][1][2]); xy2 = g.toDomCoords(data[i + 1][0], data[i + 1][1][2]); CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); // middle line xy1 = g.toDomCoords(data[i][0], data[i][1][1]); xy2 = g.toDomCoords(data[i + 1][0], data[i + 1][1][1]); CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); } g.destroy(); // Restore balanced saves and restores. CanvasAssertions.assertBalancedSaveRestore(htx); }); it('testErrorBarsCorrectColors', function() { // Two constant series with constant error. var data = [ [0, [100, 50], [200, 50]], [1, [100, 50], [200, 50]] ]; var opts = { errorBars: true, sigma: 1.0, fillAlpha: 0.15, colors: ['#00ff00', '#0000ff'], axes : { x : { drawGrid: false, drawAxis: false, }, y : { drawGrid: false, drawAxis: false, } }, width: 400, height: 300, valueRange: [0, 300], labels: ['X', 'Y1', 'Y2'] }; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); // y-pixels (0=top, 299=bottom) // 0- 48: empty (white) // 49- 98: Y2 error bar // 99: Y2 center line // 100-148: Y2 error bar // 149-198: Y1 error bar // 199: Y1 center line // 200-248: Y1 error bar // 249-299: empty (white) // TODO(danvk): test the edges of these regions. assert.deepEqual([0, 0, 255, 38], Util.samplePixel(g.hidden_, 200, 75)); assert.deepEqual([0, 0, 255, 38], Util.samplePixel(g.hidden_, 200, 125)); assert.deepEqual([0, 255, 0, 38], Util.samplePixel(g.hidden_, 200, 175)); assert.deepEqual([0, 255, 0, 38], Util.samplePixel(g.hidden_, 200, 225)); }); // Regression test for https://github.com/danvk/dygraphs/issues/517 // This verifies that the error bars have alpha=fillAlpha, even if the series // color has its own alpha value. it('testErrorBarsForAlphaSeriesCorrectColors', function() { var data = [ [0, [100, 50]], [2, [100, 50]] ]; var opts = { errorBars: true, sigma: 1.0, fillAlpha: 0.15, strokeWidth: 10, colors: ['rgba(0, 255, 0, 0.5)'], axes : { x : { drawGrid: false, drawAxis: false, }, y : { drawGrid: false, drawAxis: false, } }, width: 400, height: 300, valueRange: [0, 300], labels: ['X', 'Y'] }; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); // y-pixels (0=top, 299=bottom) // 0-148: empty (white) // 149-198: Y error bar // 199: Y center line // 200-248: Y error bar // 249-299: empty (white) // 38 = 255 * 0.15 (fillAlpha) // 146 = 255 * (0.15 * 0.5 + 1 * 0.5) (fillAlpha from error bar + alpha from series line) assert.deepEqual([0, 255, 0, 38], Util.samplePixel(g.hidden_, 1, 175)); assert.deepEqual([0, 255, 0, 146], Util.samplePixel(g.hidden_, 200, 199)); assert.deepEqual([0, 255, 0, 38], Util.samplePixel(g.hidden_, 1, 225)); }); // Regression test for http://code.google.com/p/dygraphs/issues/detail?id=392 it('testRollingAveragePreservesNaNs', function() { var graph = document.getElementById("graph"); var data = [ [1, [null, null], [3,1]], [2, [2, 1], [null, null]], [3, [null, null], [5,1]], [4, [4, 0.5], [null, null]], [5, [null, null], [7,1]], [6, [NaN, NaN], [null, null]], [8, [8, 1], [null, null]], [10, [10, 1], [null, null]] ]; var g = new Dygraph(graph, data, { labels: ['x', 'A', 'B' ], connectSeparatedPoints: true, drawPoints: true, errorBars: true } ); var in_series = g.dataHandler_.extractSeries(data, 1, g.attributes_); assert.equal(null, in_series[4][1]); assert.equal(null, in_series[4][2][0]); assert.equal(null, in_series[4][2][1]); assert(isNaN(in_series[5][1])); assert(isNaN(in_series[5][2][0])); assert(isNaN(in_series[5][2][1])); var out_series = g.dataHandler_.rollingAverage(in_series, 1, g.attributes_, 1); assert(isNaN(out_series[5][1])); assert(isNaN(out_series[5][2][0])); assert(isNaN(out_series[5][2][1])); assert.equal(null, out_series[4][1]); assert.equal(null, out_series[4][2][0]); assert.equal(null, out_series[4][2][1]); }); }); dygraphs-2.2.1/auto_tests/tests/fast_canvas_proxy.js010064400000000000000000000106171437353256000177240ustar00/** * @fileoverview Tests for fastCanvasProxy, which drops superfluous segments. * * @author danvdk@gmail.com (Dan Vanderkam) */ import DygraphCanvasRenderer from '../../src/dygraph-canvas'; import Proxy from './Proxy'; describe("fast-canvas-proxy", function() { var fakeCanvasContext = { moveTo: function() {}, lineTo: function() {}, beginPath: function() {}, closePath: function() {}, fill: function() {}, stroke: function() {} } function extractMoveToAndLineToCalls(proxy) { var calls = proxy.calls__; var out = []; for (var i = 0; i < calls.length; i++) { var c = calls[i]; if (c.name == 'moveTo' || c.name == 'lineTo') { out.push([c.name, c.args[0], c.args[1]]); } } return out; } it('testExtraMoveTosElided', function() { var htx = new Proxy(fakeCanvasContext); var fastProxy = DygraphCanvasRenderer._fastCanvasProxy(htx); fastProxy.moveTo(1, 1); fastProxy.lineTo(2, 1); fastProxy.moveTo(2, 1); fastProxy.lineTo(3, 1); fastProxy.moveTo(3, 1); fastProxy.stroke(); assert.deepEqual([['moveTo', 1, 1], ['lineTo', 2, 1], ['lineTo', 3, 1]], extractMoveToAndLineToCalls(htx)); }); it('testConsecutiveMoveTosElided', function() { var htx = new Proxy(fakeCanvasContext); var fastProxy = DygraphCanvasRenderer._fastCanvasProxy(htx); fastProxy.moveTo(1, 1); fastProxy.lineTo(2, 1); fastProxy.moveTo(3, 1); fastProxy.moveTo(3.1, 2); fastProxy.moveTo(3.2, 3); fastProxy.stroke(); assert.deepEqual([['moveTo', 1, 1], ['lineTo', 2, 1], ['moveTo', 3.2, 3]], extractMoveToAndLineToCalls(htx)); }); it('testSuperfluousSegmentsElided', function() { var htx = new Proxy(fakeCanvasContext); var fastProxy = DygraphCanvasRenderer._fastCanvasProxy(htx); fastProxy.moveTo(0.6, 1); fastProxy.lineTo(0.7, 2); fastProxy.lineTo(0.8, 3); fastProxy.lineTo(0.9, 4); fastProxy.lineTo(1.0, 5); // max for Math.round(x) == 1 fastProxy.lineTo(1.1, 3); fastProxy.lineTo(1.2, 0); // min for Math.round(x) == 1 fastProxy.lineTo(1.3, 1); fastProxy.lineTo(1.4, 2); fastProxy.moveTo(1.4, 2); fastProxy.lineTo(1.5, 2); // rounding up to 2 fastProxy.moveTo(1.5, 2); fastProxy.lineTo(1.6, 3); fastProxy.moveTo(1.6, 3); fastProxy.lineTo(1.7, 30); // max for Math.round(x) == 2 fastProxy.moveTo(1.7, 30); fastProxy.lineTo(1.8, -30); // min for Math.round(x) == 2 fastProxy.moveTo(1.8, -30); fastProxy.lineTo(1.9, 0); fastProxy.moveTo(3, 0); // dodge the "don't touch the last pixel" rule. fastProxy.stroke(); assert.deepEqual([['moveTo', 0.6, 1], ['lineTo', 1.0, 5], ['lineTo', 1.2, 0], ['lineTo', 1.7, 30], ['lineTo', 1.8, -30], ['moveTo', 3, 0]], extractMoveToAndLineToCalls(htx)); }); // For a more visual version of this test, see // https://gist.github.com/danvk/e98dbb24253c9b153696 // The drawing commands in the following two tests are taken from there. it('should handle gaps on the left', function() { var htx = new Proxy(fakeCanvasContext); var fastProxy = DygraphCanvasRenderer._fastCanvasProxy(htx); fastProxy.moveTo(0, 320); fastProxy.lineTo(0, 320); fastProxy.lineTo(53.21, 187); fastProxy.lineTo(53.23, 29); fastProxy.lineTo(53.41, 320); fastProxy.lineTo(54.15, 320); fastProxy.lineTo(475, 320); fastProxy.lineTo(475, 320); fastProxy.fill(); assert.deepEqual([ ['moveTo', 0, 320], ['lineTo', 0, 320], ['lineTo', 53.21, 187], ['lineTo', 53.23, 29], ['lineTo', 53.41, 320], ['lineTo', 54.15, 320], ['lineTo', 475, 320], ['lineTo', 475, 320] ], extractMoveToAndLineToCalls(htx)); }); it('should handle gaps on the right', function() { var htx = new Proxy(fakeCanvasContext); var fastProxy = DygraphCanvasRenderer._fastCanvasProxy(htx); fastProxy.moveTo(240.2, 320); fastProxy.lineTo(240.2, 320); fastProxy.lineTo(240.2, 174); fastProxy.lineTo(240.7, 145); fastProxy.lineTo(240.8, 320); fastProxy.lineTo(241.3, 29); fastProxy.lineTo(241.4, 320); fastProxy.lineTo(715.9, 320); fastProxy.lineTo(715.9, 320); fastProxy.fill(); assert.deepEqual([ ['moveTo', 240.2, 320], ['lineTo', 240.2, 320], ['lineTo', 240.2, 174], ['lineTo', 240.7, 145], ['lineTo', 240.8, 320], ['lineTo', 241.3, 29], ['lineTo', 241.4, 320], ['lineTo', 715.9, 320], ['lineTo', 715.9, 320] ], extractMoveToAndLineToCalls(htx)); }); }); dygraphs-2.2.1/auto_tests/tests/fill_step_plot.js010064400000000000000000000024231437353256000172060ustar00/** * @fileoverview Test if you give null values to dygraph with stepPlot * and fillGraph options enabled * * @author benoitboivin.pro@gmail.com (Benoit Boivin) */ import Dygraph from '../../src/dygraph'; import * as utils from '../../src/dygraph-utils'; import CanvasAssertions from './CanvasAssertions'; import Proxy from './Proxy'; describe("fill-step-plot", function() { cleanupAfterEach(); var origFunc = utils.getContext; beforeEach(function() { utils.getContext = function(canvas) { return new Proxy(origFunc(canvas)); }; }); afterEach(function() { utils.getContext = origFunc; }); it('testFillStepPlotNullValues', function() { var opts = { labels: ["x","y"], width: 480, height: 320, fillGraph: true, stepPlot: true }; var data = [ [1,3], [2,0], [3,8], [4,null], [5,9], [6,8], [7,6], [8,3] ]; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); var htx = g.hidden_ctx_; var x1 = data[3][0]; var y1 = data[2][1]; var x2 = data[3][0]; var y2 = 0; var xy1 = g.toDomCoords(x1, y1); var xy2 = g.toDomCoords(x2, y2); // Check if a line is drawn between the previous y and the bottom of the chart CanvasAssertions.assertLineDrawn(htx, xy1, xy2, {}); }); }); dygraphs-2.2.1/auto_tests/tests/formats.js010064400000000000000000000042221437353256000156410ustar00/** * @fileoverview Tests for data formats. * * @author konigsberg@google.com (Robert Konigsberg) */ import Dygraph from '../../src/dygraph'; describe("formats", function() { cleanupAfterEach(); var dataString = "X,Y\n" + "0,-1\n" + "1,0\n" + "2,1\n" + "3,0\n"; var dataArray = [[0,-1], [1,0], [2,1], [3,0]]; var BASE_OPTS = {labels: ['X', 'Y']}; it('testCsv', function() { var data = dataString; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, {}); assertData(g); }); it('testArray', function() { var data = dataArray; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, BASE_OPTS); assertData(g); }); it('testFunctionReturnsCsv', function() { var data = function() { return dataString; }; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, {}); assertData(g); }); it('testFunctionDefinesArray', function() { var array = dataArray; var data = function() { return array; } var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, BASE_OPTS); assertData(g); }); it('testXValueParser', function() { var data = "X,Y\n" + "d,-1\n" + "e,0\n" + "f,1\n" + "g,0\n"; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, { xValueParser : function(str) { assert.equal(1, str.length); return str.charCodeAt(0) - "a".charCodeAt(0); } }); assert.equal(3, g.getValue(0, 0)); assert.equal(4, g.getValue(1, 0)); assert.equal(5, g.getValue(2, 0)); assert.equal(6, g.getValue(3, 0)); }); it('should throw on strings in native format', () => { assert.throws(() => { new Dygraph('graph', [['1', '10'], ['2', '20']]) }, /expected number or date/i); assert.throws(() => { new Dygraph('graph', [[new Date(), '10'], [new Date(), '20']]) }, /expected number or array/i); }); var assertData = function(g) { var expected = dataArray; assert.equal(4, g.numRows()); assert.equal(2, g.numColumns()); for (var i = 0; i < 4; i++) { for (var j = 0; j < 2; j++) { assert.equal(expected[i][j], g.getValue(i, j)); } } }; }); dygraphs-2.2.1/auto_tests/tests/grid_per_axis.js010064400000000000000000000234741437353256000170170ustar00/** * @fileoverview Test cases for the per-axis grid options, including the new * option "gridLinePattern". * * @author david.eberlein@ch.sauter-bc.com (Fr. Sauter AG) */ import Dygraph from '../../src/dygraph'; import * as utils from '../../src/dygraph-utils'; import Proxy from './Proxy'; import CanvasAssertions from './CanvasAssertions'; import Util from './Util'; import PixelSampler from './PixelSampler'; describe("grid-per-axis", function() { cleanupAfterEach(); useProxyCanvas(utils, Proxy); it('testIndependentGrids', function() { var opts = { width : 480, height : 320, errorBars : false, labels : [ "X", "Left", "Right" ], series : { Left : { axis : "y" }, Right : { axis : "y2" } }, axes : { y2 : { drawGrid : true, independentTicks : true } } }; var data = [ [ 1, 0, 0 ], [ 2, 12, 88 ], [ 3, 88, 122 ], [ 4, 63, 273 ], [ 5, 110, 333 ] ]; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); var htx = g.hidden_ctx_; // The expected gridlines var yGridlines = [ 0, 20, 40, 60, 80, 100, 120 ]; var y2Gridlines = [ 0, 50, 100, 150, 200, 250, 300, 350 ]; var gridlines = [ yGridlines, y2Gridlines ]; function halfUp(x) { return Math.round(x) + 0.5; } function halfDown(y) { return Math.round(y) - 0.5; } var attrs = {}, x, y; x = halfUp(g.plotter_.area.x); // Step through y(0) and y2(1) axis for ( var axis = 0; axis < 2; axis++) { // Step through all gridlines of the axis for ( var i = 0; i < gridlines[axis].length; i++) { // Check the labels: var labels = Util.getYLabels(axis + 1); assert.equal(gridlines[axis][i], labels[i], "Expected label not found."); // Check that the grid was drawn. y = halfDown(g.toDomYCoord(gridlines[axis][i], axis)); var p1 = [ x, y ]; var p2 = [ x + g.plotter_.area.w, y ]; CanvasAssertions.assertLineDrawn(htx, p1, p2, attrs); } } }); it('testPerAxisGridColors', function() { var opts = { width : 480, height : 320, errorBars : false, labels : [ "X", "Left", "Right" ], series : { Left : { axis : "y" }, Right : { axis : "y2" } }, axes : { y : { gridLineColor : "#0000ff", gridLineWidth : 2 }, y2 : { drawGrid : true, independentTicks : true, gridLineColor : "#ff0000", gridLineWidth : 2, } } }; var data = [ [ 1, 0, 0 ], [ 2, 12, 88 ], [ 3, 88, 122 ], [ 4, 63, 273 ], [ 5, 110, 333 ] ]; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); var htx = g.hidden_ctx_; // The expected gridlines var yGridlines = [ 20, 40, 60, 80, 100, 120 ]; var y2Gridlines = [ 50, 100, 150, 200, 250, 300, 350 ]; var gridlines = [ yGridlines, y2Gridlines ]; var gridColors = [ [ 0, 0, 255, 255 ], [ 255, 0, 0, 255 ] ]; function halfUp(x) { return Math.round(x) + 1; } function halfDown(y) { return Math.round(y) - 1; } var x, y; x = halfUp(g.plotter_.area.x); var sampler = new PixelSampler(g); // Step through y(0) and y2(1) axis for ( var axis = 0; axis < 2; axis++) { // Step through all gridlines of the axis for ( var i = 0; i < gridlines[axis].length; i++) { y = halfDown(g.toDomYCoord(gridlines[axis][i], axis)); // Check the grid colors. assert.deepEqual(gridColors[axis], sampler.colorAtPixel(x, y), "Unexpected grid color found at pixel: x: " + x + "y: " + y); } } }); it('testPerAxisGridWidth', function() { var opts = { width : 480, height : 320, errorBars : false, gridLineColor : "#ff0000", labels : [ "X", "Left", "Right" ], series : { Left : { axis : "y" }, Right : { axis : "y2" } }, axes : { x : { gridLineWidth : 4 }, y : { gridLineWidth : 2 }, y2 : { drawGrid : true, independentTicks : true, gridLineWidth : 1 } } }; var data = [ [ 1, 0, 0 ], [ 2, 12, 88 ], [ 3, 88, 122 ], [ 4, 63, 273 ], [ 5, 110, 333 ] ]; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); var htx = g.hidden_ctx_; // The expected gridlines var yGridlines = [ 20, 40, 60, 80 ]; var y2Gridlines = [ 50, 100, 150, 200, 250, 350 ]; var gridlines = [ yGridlines, y2Gridlines ]; var xGridlines = [ 2, 3, 4 ]; var gridColor = [ 255, 0, 0 ]; var emptyColor = [ 0, 0, 0 ]; function halfUp(x) { return Math.round(x) + 1; } function halfDown(y) { return Math.round(y) - 1; } var x, y; x = halfUp(g.plotter_.area.x + 10); var sampler = new PixelSampler(g); // Step through y(0) and y2(1) axis for ( var axis = 0; axis < 2; axis++) { // Step through all gridlines of the axis for ( var i = 0; i < gridlines[axis].length; i++) { y = halfDown(g.toDomYCoord(gridlines[axis][i], axis)); // Ignore the alpha value // FIXME(pholden): this test fails with a context pixel ratio of 2. var drawnPixeldown2 = sampler.rgbAtPixel(x, y - 2); var drawnPixeldown1 = sampler.rgbAtPixel(x, y - 1); var drawnPixel = sampler.rgbAtPixel(x, y); var drawnPixelup1 = sampler.rgbAtPixel(x, y + 1); var drawnPixelup2 = sampler.rgbAtPixel(x, y + 2); // Check the grid width. switch (axis) { case 0: // y with 2 pixels width assert.deepEqual(emptyColor, drawnPixeldown2, "Unexpected y-grid color found at pixel: x: " + x + "y: " + y); assert.deepEqual(gridColor, drawnPixeldown1, "Unexpected y-grid color found at pixel: x: " + x + "y: " + y); assert.deepEqual(gridColor, drawnPixel, "Unexpected y-grid color found at pixel: x: " + x + "y: " + y); assert.deepEqual(gridColor, drawnPixelup1, "Unexpected y-grid color found at pixel: x: " + x + "y: " + y); assert.deepEqual(emptyColor, drawnPixelup2, "Unexpected y-grid color found at pixel: x: " + x + "y: " + y); break; case 1: // y2 with 1 pixel width assert.deepEqual(emptyColor, drawnPixeldown1, "Unexpected y2-grid color found at pixel: x: " + x + "y: " + y); assert.deepEqual(gridColor, drawnPixel, "Unexpected y2-grid color found at pixel: x: " + x + "y: " + y); assert.deepEqual(emptyColor, drawnPixelup1, "Unexpected y2-grid color found at pixel: x: " + x + "y: " + y); break; } } } // Check the x axis grid y = halfDown(g.plotter_.area.y) + 10; for ( var i = 0; i < xGridlines.length; i++) { x = halfUp(g.toDomXCoord(xGridlines[i])); assert.deepEqual(emptyColor, sampler.rgbAtPixel(x - 4, y), "Unexpected x-grid color found at pixel: x: " + x + "y: " + y); assert.deepEqual(gridColor, sampler.rgbAtPixel(x - 3, y), "Unexpected x-grid color found at pixel: x: " + x + "y: " + y); assert.deepEqual(gridColor, sampler.rgbAtPixel(x - 2, y), "Unexpected x-grid color found at pixel: x: " + x + "y: " + y); assert.deepEqual(gridColor, sampler.rgbAtPixel(x - 1, y), "Unexpected x-grid color found at pixel: x: " + x + "y: " + y); assert.deepEqual(gridColor, sampler.rgbAtPixel(x, y), "Unexpected x-grid color found at pixel: x: " + x + "y: " + y); assert.deepEqual(gridColor, sampler.rgbAtPixel(x + 1, y), "Unexpected x-grid color found at pixel: x: " + x + "y: " + y); assert.deepEqual(emptyColor, sampler.rgbAtPixel(x + 2, y), "Unexpected x-grid color found at pixel: x: " + x + "y: " + y); } }); // PhantomJS 1.9.x does not support setLineDash // When Travis-CI updates to Phantom2, this can be re-enabled. // See https://github.com/ariya/phantomjs/issues/12948 if (!navigator.userAgent.match(/PhantomJS\/1.9/)) { it('testGridLinePattern', function() { var opts = { width : 480, height : 320, errorBars : false, labels : [ "X", "Left", "Right" ], colors : [ "rgba(0,0,0,0)", "rgba(0,0,0,0)" ], series : { Left : { axis : "y" }, Right : { axis : "y2" } }, axes : { x : { drawGrid: false, drawAxis: false, }, y : { drawAxis : false, gridLineColor : "#0000ff", gridLinePattern : [ 10, 10 ] } } }; var data = [ [ 1, 0, 0 ], [ 2, 12, 88 ], [ 3, 88, 122 ], [ 4, 63, 273 ], [ 5, 110, 333 ] ]; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); var htx = g.hidden_ctx_; // The expected gridlines var yGridlines = [ 0, 20, 40, 60, 80, 100, 120 ]; function halfUp(x) { return Math.round(x) + 1; } function halfDown(y) { return Math.round(y) - 1; } var x, y; var sampler = new PixelSampler(g); // Step through all gridlines of the axis for (var i = 0; i < yGridlines.length; i++) { y = halfDown(g.toDomYCoord(yGridlines[i], 0)); // Step through the pixels of the line and test the pattern. for (x = halfUp(g.plotter_.area.x); x < g.plotter_.area.w; x++) { // avoid checking the edge pixels since they differ depending on the OS. var pixelpos = x % 10; if(pixelpos < 1 || pixelpos > 8) continue; // XXX: check what this looks like at master // Ignore alpha var drawnPixel = sampler.rgbAtPixel(x, y); var pattern = (Math.floor((x) / 10)) % 2; switch (pattern) { case 0: // fill assert.deepEqual([ 0, 0, 255 ], drawnPixel, "Unexpected filled grid-pattern color found at pixel: x: " + x + " y: " + y); break; case 1: // no fill assert.deepEqual([ 0, 0, 0 ], drawnPixel, "Unexpected empty grid-pattern color found at pixel: x: " + x + " y: " + y); break; } } } }); } }); dygraphs-2.2.1/auto_tests/tests/gviz.js010064400000000000000000000125171437353256000151530ustar00/** * Unit tests for GViz data table support. */ import Dygraph from '../../src/dygraph'; import Util from './Util'; describe('gviz', function() { cleanupAfterEach(); // This is a fake version of the gviz DataTable API, which can only be // sourced using the google js loader. // // Their example of the "data" structure is: // cols: [{id: 'task', label: 'Task', type: 'string'}, // {id: 'hours', label: 'Hours per Day', type: 'number'}], // rows: [{c:[{v: 'Work'}, {v: 11}]}, // {c:[{v: 'Eat'}, {v: 2}]}, // {c:[{v: 'Commute'}, {v: 2}]}, // {c:[{v: 'Watch TV'}, {v:2}]}, // {c:[{v: 'Sleep'}, {v:7, f:'7.000'}]} // ] // // https://developers.google.com/chart/interactive/docs/reference#DataTable var FakeDataTable = function(data) { this.data = data; }; FakeDataTable.prototype.getNumberOfColumns = function() { return this.data.cols.length; }; FakeDataTable.prototype.getNumberOfRows = function() { return this.data.rows.length; }; FakeDataTable.prototype.getColumnType = function(idx) { return this.data.cols[idx].type; }; FakeDataTable.prototype.getColumnLabel = function(idx) { return this.data.cols[idx].label; }; FakeDataTable.prototype.getValue = function(row, col) { return this.data.rows[row].c[col].v; }; FakeDataTable.prototype.getColumnRange = function(col) { throw 'Not Implemented'; }; // This mirrors http://dygraphs.com/tests/gviz.html var numericData = new FakeDataTable({ cols: [{id:"",label:"X",type:"number"}, {id:"",label:"A",type:"number"}, {id:"",label:"B",type:"number"}], rows: [{c:[{v:0},{v:1},{v:7}]}, {c:[{v:1},{v:2},{v:4}]}, {c:[{v:2},{v:3},{v:3}]}, {c:[{v:3},{v:4},{v:0}]}] }); it('should parse simple data tables', function() { var g = new Dygraph('graph', numericData); assert.equal(4, g.numRows()); assert.equal(3, g.numColumns()); assert.equal(0, g.getValue(0, 0)); assert.equal(1, g.getValue(0, 1)); assert.equal(7, g.getValue(0, 2)); assert.equal(3, g.getValue(3, 0)); assert.equal(4, g.getValue(3, 1)); assert.equal(0, g.getValue(3, 2)); assert.deepEqual(['X', 'A', 'B'], g.getLabels()); }); it('should parse tables with annotations', function() { // Data from https://developers.google.com/chart/interactive/docs/gallery/annotatedtimeline var data = new FakeDataTable({ cols: [ {label: "Date", type: "date" }, {label: "Sold Pencils", type: "number" }, {label: "title1", type: "string" }, {label: "text1", type: "string" }, {label: "Sold Pens", type: "number" }, {label: "title2", type: "string" }, {label: "text2", type: "string" } ], rows: [ {c: [{v: new Date(2008, 1, 1)}, {v: 30000}, {v: null}, {v: null}, {v: 40645}, {v: null}, {v: null}]}, {c: [{v: new Date(2008, 1, 2)}, {v: 14045}, {v: null}, {v: null}, {v: 20374}, {v: null}, {v: null}]}, {c: [{v: new Date(2008, 1, 3)}, {v: 55022}, {v: null}, {v: null}, {v: 50766}, {v: null}, {v: null}]}, {c: [{v: new Date(2008, 1, 4)}, {v: 75284}, {v: null}, {v: null}, {v: 14334}, {v: "Out of Stock"}, {v: "Ran out of stock"}]}, {c: [{v: new Date(2008, 1, 5)}, {v: 41476}, {v: "Bought Pens"}, {v: "Bought 200k pens" }, {v: 66467}, {v: null}, {v: null}]}, {c: [{v: new Date(2008, 1, 6)}, {v: 33322}, {v: null}, {v: null}, {v: 39463}, {v: null}, {v: null}]} ] }); var g = new Dygraph('graph', data, {displayAnnotations: true}); var annEls = document.getElementsByClassName('dygraphDefaultAnnotation'); assert.equal(2, annEls.length); var annotations = g.annotations(); assert.equal(2, annotations.length); var a0 = annotations[0]; assert.deepEqual({ text: 'Out of Stock\nRan out of stock', series: 'Sold Pens', xval: new Date(2008, 1, 4).getTime(), shortText: 'A' }, annotations[0]); }); it('should parse tables with dates', function() { // This mirrors http://dygraphs.com/tests/gviz.html var data = new FakeDataTable({ cols: [{id:"",label:"Date",type:"datetime"}, {id:"",label:"Column A",type:"number"}, {id:"",label:"Column B",type:"number"}], rows: [{c:[{v:new Date(2009, 6, 1)},{v:1},{v:7}]}, {c:[{v:new Date(2009, 6, 8)},{v:2},{v:4}]}, {c:[{v:new Date(2009, 6, 15)},{v:3},{v:3}]}, {c:[{v:new Date(2009, 6, 22)},{v:4},{v:0}]}] }); var g = new Dygraph('graph', data); assert.equal(4, g.numRows()); assert.equal(3, g.numColumns()); assert.equal(new Date(2009, 6, 1).getTime(), g.getValue(0, 0)); assert.equal(1, g.getValue(0, 1)); assert.equal(7, g.getValue(0, 2)); assert.deepEqual(['Date', 'Column A', 'Column B'], g.getLabels()); }); // it('should parse tables with error bars', function() { // }); it('should implement the gviz API', function() { var g = new Dygraph.GVizChart(document.getElementById('graph')); g.draw(numericData); g.setSelection([{row: 0}]); assert.equal('0: A: 1 B: 7', Util.getLegend()); assert.deepEqual([{row: 0, column: 1}, {row: 0, column: 2}], g.getSelection()); g.setSelection([]); assert.deepEqual([], g.getSelection()); }); }); dygraphs-2.2.1/auto_tests/tests/hidpi.js010064400000000000000000000041451437353256000152670ustar00/** * @fileoverview Tests for window.devicePixelRatio > 1. * * @author danvdk@gmail.com (Dan Vanderkam) */ import Dygraph from '../../src/dygraph'; describe("hidpi", function() { cleanupAfterEach(); var savePixelRatio; beforeEach(function() { savePixelRatio = window.devicePixelRatio; window.devicePixelRatio = 2; }); afterEach(function() { window.devicePixelRatio = savePixelRatio; }); it('testDoesntCreateScrollbars', function() { var sw = document.body.scrollWidth; var cw = document.body.clientWidth; var graph = document.getElementById("graph"); graph.style.width = "70%"; // more than half. graph.style.height = "200px"; var opts = {}; var data = "X,Y\n" + "0,-1\n" + "1,0\n" + "2,1\n" + "3,0\n" ; var g = new Dygraph(graph, data, opts); // Adding the graph shouldn't cause the width of the page to change. // (essentially, we're checking that we don't end up with a scrollbar) // See https://stackoverflow.com/a/2146905/2171120 assert.equal(cw, document.body.clientWidth); assert.equal(sw, document.body.scrollWidth); }); it('should be influenced by options.pixelRatio', function () { var graph = document.getElementById("graph"); // make sure devicePixelRatio is still setup to not 1. assert(devicePixelRatio > 1.5, 'devicePixelRatio is not much greater than 1.'); var data = "X,Y\n" + "0,-1\n" + "1,0\n" + "2,1\n" + "3,0\n"; // first try a default one var g1 = new Dygraph(graph, data, {}); var area1 = g1.getArea(); var g2 = new Dygraph(graph, data, { pixelRatio: 1 }); var area2 = g2.getArea(); var g3 = new Dygraph(graph, data, { pixelRatio: 3 }); var area3 = g3.getArea(); assert.deepEqual(area1, area2, 'areas 1 and 2 are not the same'); assert.deepEqual(area2, area3, 'areas 2 and 3 are not the same'); assert.notEqual(g1.canvas_.width, g2.canvas_.width, 'Expected, for devicePixelRatio != 1, ' + 'that setting options.pixelRatio would change the canvas width'); assert.equal(g2.canvas_.width * 3, g3.canvas_.width, 'Expected that pixelRatio of 3 vs 1 would triple the canvas width.'); }); }); dygraphs-2.2.1/auto_tests/tests/highlight_series_background.js010064400000000000000000000102411437353256000217040ustar00/** * @fileoverview Tests for the highlightSeriesBackgroundAlpha and * highlightSeriesBackgroundColor options. * @author sergeyslepian@gmail.com */ import Dygraph from '../../src/dygraph'; import * as utils from '../../src/dygraph-utils'; import Util from './Util'; describe("highlight-series-background", function() { cleanupAfterEach(); var origRepeatAndCleanup; beforeEach(function() { // A "fast" version of repeatAndCleanup origRepeatAndCleanup = utils.repeatAndCleanup; // utils.repeatAndCleanup = function(repeatFn, maxFrames, framePeriodInMillis, cleanupFn) { // repeatFn(0); // if (maxFrames > 1) repeatFn(maxFrames - 1); // cleanupFn(); // }; }); afterEach(function() { utils.repeatAndCleanup = origRepeatAndCleanup; }); function setupGraph(highlightSeriesBackgroundAlpha, highlightSeriesBackgroundColor) { var opts = { width: 480, height: 320, labels: ['x', 'y'], legend: 'always', highlightSeriesOpts: { strokeWidth: 1, strokeBorderWidth: 1, highlightCircleSize: 1 } }; if (highlightSeriesBackgroundAlpha) utils.update(opts, {highlightSeriesBackgroundAlpha}); if (highlightSeriesBackgroundColor) utils.update(opts, {highlightSeriesBackgroundColor}); var data = []; for (var j = 0; j < 10; j++) { data.push([j, 0]); } return new Dygraph('graph', data, opts); } it('testDefaultHighlight', function(done) { var graph = setupGraph(); assert.deepEqual(Util.samplePixel(graph.canvas_, 100, 100), [0,0,0,0]); graph.setSelection(0, 'y', true); // handle background color fade-in time window.setTimeout(() => { assert.deepEqual(Util.samplePixel(graph.canvas_, 100, 100), [255,255,255,127]); done(); }, 500); }); it('testNoHighlight', function(done) { var graph = setupGraph(1); assert.deepEqual(Util.samplePixel(graph.canvas_, 100, 100), [0,0,0,0]); graph.setSelection(0, 'y', true); // handle background color fade-in time window.setTimeout(() => { assert.deepEqual(Util.samplePixel(graph.canvas_, 100, 100), [0,0,0,0]); done(); }, 500); }); it('testCustomHighlightColor', function(done) { var graph = setupGraph(null, 'rgb(0,255,255)'); assert.deepEqual(Util.samplePixel(graph.canvas_, 100, 100), [0,0,0,0]); graph.setSelection(0, 'y', true); // handle background color fade-in time window.setTimeout(() => { assert.deepEqual(Util.samplePixel(graph.canvas_, 100, 100), [0,255,255,127]); done(); }, 500); }); it('testCustomHighlightAlpha', function(done) { var graph = setupGraph(0.3); assert.deepEqual(Util.samplePixel(graph.canvas_, 100, 100), [0,0,0,0]); graph.setSelection(0, 'y', true); // handle background color fade-in time window.setTimeout(() => { assert.deepEqual(Util.samplePixel(graph.canvas_, 100, 100), [255,255,255,179]); done(); }, 500); }); it('testCustomHighlightColorAndAlpha', function(done) { var graph = setupGraph(0.7,'rgb(255,0,0)'); assert.deepEqual(Util.samplePixel(graph.canvas_, 100, 100), [0,0,0,0]); graph.setSelection(0, 'y', true); // handle background color fade-in time window.setTimeout(() => { assert.deepEqual(Util.samplePixel(graph.canvas_, 100, 100), [255,0,0,76]); done(); }, 500); }); it('testGetSelectionZeroCanvasY', function () { var graph = document.getElementById("graph"); var calls = [] function callback(g, seriesName, canvasContext, cx, cy, color, pointSize, idx) { calls.push(arguments); }; var g = new Dygraph(document.getElementById("graph"), "X,Y\n" + "1,5\n" + "1,10\n" + "1,12\n", { drawHighlightPointCallback: callback, axes: { y: { valueRange: [0, 10] } } }); g.setSelection(1); var args = calls[0]; assert.equal(args[4], 0); }); }); dygraphs-2.2.1/auto_tests/tests/interaction_model.js010064400000000000000000000320771437353256000176760ustar00/** * @fileoverview Test cases for the interaction model. * * @author konigsberg@google.com (Robert Konigsbrg) */ import Dygraph from '../../src/dygraph'; import DygraphInteraction from '../../src/dygraph-interaction-model'; import DygraphOps from './DygraphOps'; describe("interaction-model", function() { cleanupAfterEach(); var data1 = "X,Y\n" + "20,-1\n" + "21,0\n" + "22,1\n" + "23,0\n"; var data2 = "X,Y\n" + "1,10\n" + "2,20\n" + "3,30\n" + "4,40\n" + "5,120\n" + "6,50\n" + "7,70\n" + "8,90\n" + "9,50\n"; function getXLabels() { var x_labels = document.getElementsByClassName("dygraph-axis-label-x"); var ary = []; for (var i = 0; i < x_labels.length; i++) { ary.push(x_labels[i].innerHTML); } return ary; } /* it('testPan', function() { var originalXRange = g.xAxisRange(); var originalYRange = g.yAxisRange(0); DygraphOps.dispatchMouseDown(g, xRange[0], yRange[0]); DygraphOps.dispatchMouseMove(g, xRange[1], yRange[0]); // this is really necessary. DygraphOps.dispatchMouseUp(g, xRange[1], yRange[0]); assert.closeTo(xRange, g.xAxisRange(), 0.2); // assert.closeTo(originalYRange, g.yAxisRange(0), 0.2); // Not true, it's something in the middle. var midX = (xRange[1] - xRange[0]) / 2; DygraphOps.dispatchMouseDown(g, midX, yRange[0]); DygraphOps.dispatchMouseMove(g, midX, yRange[1]); // this is really necessary. DygraphOps.dispatchMouseUp(g, midX, yRange[1]); assert.closeTo(xRange, g.xAxisRange(), 0.2); assert.closeTo(yRange, g.yAxisRange(0), 0.2); }); */ /** * This tests that when changing the interaction model so pan is used instead * of zoom as the default behavior, a standard click method is still called. */ it('testClickCallbackIsCalled', function() { var clicked; var clickCallback = function(event, x) { clicked = x; }; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data1, { width: 100, height : 100, clickCallback : clickCallback }); DygraphOps.dispatchMouseDown_Point(g, 10, 10); DygraphOps.dispatchMouseMove_Point(g, 10, 10); DygraphOps.dispatchMouseUp_Point(g, 10, 10); assert.equal(20, clicked); }); /** * This tests that when changing the interaction model so pan is used instead * of zoom as the default behavior, a standard click method is still called. */ it('testClickCallbackIsCalledOnCustomPan', function() { var clicked; var clickCallback = function(event, x) { clicked = x; }; function customDown(event, g, context) { context.initializeMouseDown(event, g, context); DygraphInteraction.startPan(event, g, context); } function customMove(event, g, context) { DygraphInteraction.movePan(event, g, context); } function customUp(event, g, context) { DygraphInteraction.endPan(event, g, context); } var opts = { width: 100, height : 100, clickCallback : clickCallback, interactionModel : { 'mousedown' : customDown, 'mousemove' : customMove, 'mouseup' : customUp, } }; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data1, opts); DygraphOps.dispatchMouseDown_Point(g, 10, 10); DygraphOps.dispatchMouseMove_Point(g, 10, 10); DygraphOps.dispatchMouseUp_Point(g, 10, 10); assert.equal(20, clicked); }); var clickAt = function(g, x, y) { DygraphOps.dispatchMouseDown(g, x, y); DygraphOps.dispatchMouseMove(g, x, y); DygraphOps.dispatchMouseUp(g, x, y); }; /** * This tests that clickCallback is still called with the nonInteractiveModel. */ it('testClickCallbackIsCalledWithNonInteractiveModel', function() { var clicked; // TODO(danvk): also test pointClickCallback here. var clickCallback = function(event, x) { clicked = x; }; var opts = { width: 100, height : 100, clickCallback : clickCallback, interactionModel : DygraphInteraction.nonInteractiveModel_ }; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data1, opts); DygraphOps.dispatchMouseDown_Point(g, 10, 10); DygraphOps.dispatchMouseMove_Point(g, 10, 10); DygraphOps.dispatchMouseUp_Point(g, 10, 10); assert.equal(20, clicked); }); /** * A sanity test to ensure pointClickCallback is called. */ it('testPointClickCallback', function() { var clicked = null; var g = new Dygraph('graph', data2, { pointClickCallback: function(event, point) { clicked = point; } }); clickAt(g, 4, 40); assert.isNotNull(clicked); assert.equal(4, clicked.xval); assert.equal(40, clicked.yval); }); /** * A sanity test to ensure pointClickCallback is not called when out of range. */ it('testNoPointClickCallbackWhenOffPoint', function() { var clicked; var g = new Dygraph(document.getElementById("graph"), data2, { pointClickCallback : function(event, point) { clicked = point; } }); clickAt(g, 5, 40); assert.isUndefined(clicked); }); /** * Ensures pointClickCallback circle size is taken into account. */ it('testPointClickCallback_circleSize', function() { // TODO(konigsberg): Implement. }); /** * Ensures that pointClickCallback is called prior to clickCallback */ it('testPointClickCallbackCalledPriorToClickCallback', function() { var counter = 0; var pointClicked; var clicked; var g = new Dygraph(document.getElementById("graph"), data2, { pointClickCallback : function(event, point) { counter++; pointClicked = counter; }, clickCallback : function(event, point) { counter++; clicked = counter; } }); clickAt(g, 4, 40); assert.equal(1, pointClicked); assert.equal(2, clicked); }); /** * Ensures that when there's no pointClickCallback, clicking on a point still calls * clickCallback */ it('testClickCallback_clickOnPoint', function() { var clicked; var g = new Dygraph(document.getElementById("graph"), data2, { clickCallback : function(event, point) { clicked = 1; } }); clickAt(g, 4, 40); assert.equal(1, clicked); }); it('testIsZoomed_none', function() { var g = new Dygraph(document.getElementById("graph"), data2, {}); assert.isFalse(g.isZoomed()); assert.isFalse(g.isZoomed("x")); assert.isFalse(g.isZoomed("y")); }); it('testIsZoomed_x', function() { var g = new Dygraph(document.getElementById("graph"), data2, {}); DygraphOps.dispatchMouseDown_Point(g, 100, 100); DygraphOps.dispatchMouseMove_Point(g, 130, 100); DygraphOps.dispatchMouseUp_Point(g, 130, 100); assert.isTrue(g.isZoomed()); assert.isTrue(g.isZoomed("x")); assert.isFalse(g.isZoomed("y")); }); it('testIsZoomed_y', function() { var g = new Dygraph(document.getElementById("graph"), data2, {}); DygraphOps.dispatchMouseDown_Point(g, 10, 10); DygraphOps.dispatchMouseMove_Point(g, 10, 30); DygraphOps.dispatchMouseUp_Point(g, 10, 30); assert.isTrue(g.isZoomed()); assert.isFalse(g.isZoomed("x")); assert.isTrue(g.isZoomed("y")); }); it('testIsZoomed_both', function() { var g = new Dygraph(document.getElementById("graph"), data2, {}); // Zoom x axis DygraphOps.dispatchMouseDown_Point(g, 100, 100); DygraphOps.dispatchMouseMove_Point(g, 130, 100); DygraphOps.dispatchMouseUp_Point(g, 130, 100); // Now zoom y axis DygraphOps.dispatchMouseDown_Point(g, 100, 100); DygraphOps.dispatchMouseMove_Point(g, 100, 130); DygraphOps.dispatchMouseUp_Point(g, 100, 130); assert.isTrue(g.isZoomed()); assert.isTrue(g.isZoomed("x")); assert.isTrue(g.isZoomed("y")); }); it('testIsZoomed_updateOptions_none', function() { var g = new Dygraph(document.getElementById("graph"), data2, {}); g.updateOptions({}); assert.isFalse(g.isZoomed()); assert.isFalse(g.isZoomed("x")); assert.isFalse(g.isZoomed("y")); }); it('testIsZoomed_updateOptions_x', function() { var g = new Dygraph(document.getElementById("graph"), data2, {}); g.updateOptions({dateWindow: [-.5, .3]}); assert.isTrue(g.isZoomed()); assert.isTrue(g.isZoomed("x")); assert.isFalse(g.isZoomed("y")); }); it('testIsZoomed_updateOptions_y', function() { var g = new Dygraph(document.getElementById("graph"), data2, {}); g.updateOptions({valueRange: [1, 10]}); assert.isTrue(g.isZoomed()); assert.isFalse(g.isZoomed("x")); assert.isTrue(g.isZoomed("y")); }); it('testIsZoomed_updateOptions_both', function() { var g = new Dygraph(document.getElementById("graph"), data2, {}); g.updateOptions({dateWindow: [-1, 1], valueRange: [1, 10]}); assert.isTrue(g.isZoomed()); assert.isTrue(g.isZoomed("x")); assert.isTrue(g.isZoomed("y")); }); it('testCorrectAxisValueRangeAfterUnzoom', function() { var g = new Dygraph(document.getElementById("graph"), data2, { valueRange: [1, 50], dateWindow: [1, 9], animatedZooms:false }); // Zoom x axis DygraphOps.dispatchMouseDown_Point(g, 100, 100); DygraphOps.dispatchMouseMove_Point(g, 130, 100); DygraphOps.dispatchMouseUp_Point(g, 130, 100); // Zoom y axis DygraphOps.dispatchMouseDown_Point(g, 100, 100); DygraphOps.dispatchMouseMove_Point(g, 100, 130); DygraphOps.dispatchMouseUp_Point(g, 100, 130); var currentYAxisRange = g.yAxisRange(); var currentXAxisRange = g.xAxisRange(); //check that the range for the axis has changed assert.notEqual(1, currentXAxisRange[0]); assert.notEqual(10, currentXAxisRange[1]); assert.notEqual(1, currentYAxisRange[0]); assert.notEqual(50, currentYAxisRange[1]); // unzoom by doubleclick. This is really the order in which a browser // generates events, and we depend on it. DygraphOps.dispatchMouseDown_Point(g, 10, 10); DygraphOps.dispatchMouseUp_Point(g, 10, 10); DygraphOps.dispatchMouseDown_Point(g, 10, 10); DygraphOps.dispatchMouseUp_Point(g, 10, 10); DygraphOps.dispatchDoubleClick(g, null); // check if the range for both axis was reset to show the full data. assert.deepEqual(g.yAxisExtremes()[0], g.yAxisRange()); assert.deepEqual(g.xAxisExtremes(), g.xAxisRange()); }); /** * Ensures pointClickCallback is called when some points along the y-axis don't * exist. */ it('testPointClickCallback_missingData', function() { // There's a B-value at 2, but no A-value. var data = "X,A,B\n" + "1,,100\n"+ "2,,110\n"+ "3,140,120\n"+ "4,130,110\n"+ ""; var clicked; var g = new Dygraph(document.getElementById("graph"), data, { pointClickCallback : function(event, point) { clicked = point; } }); clickAt(g, 2, 110); assert.equal(2, clicked.xval); assert.equal(110, clicked.yval); }); describe('animated zooms', function() { var oldDuration; before(function() { oldDuration = Dygraph.ANIMATION_DURATION; Dygraph.ANIMATION_DURATION = 100; // speed up the animation for testing }); after(function() { Dygraph.ANIMATION_DURATION = oldDuration; }); it('should support animated zooms', function(done) { var data = "X,A,B\n" + "1,120,100\n"+ "2,110,110\n"+ "3,140,120\n"+ "4,130,110\n"; var ranges = []; var g = new Dygraph('graph', data, { animatedZooms: true, }); // updating the dateWindow does not result in an animation. assert.deepEqual([1, 4], g.xAxisRange()); g.updateOptions({dateWindow: [2, 4]}); assert.deepEqual([2, 4], g.xAxisRange()); g.updateOptions({ // zoomCallback is called once when the animation is complete. zoomCallback: function(xMin, xMax) { assert.equal(1, xMin); assert.equal(4, xMax); assert.deepEqual([1, 4], g.xAxisRange()); done(); } }, false); // Zoom out -- resetZoom() _does_ produce an animation. g.resetZoom(); assert.notDeepEqual([2, 4], g.xAxisRange()); // first frame is synchronous assert.notDeepEqual([1, 4], g.xAxisRange()); // at this point control flow goes up to zoomCallback }); }); //bulk copied from "testCorrectAxisValueRangeAfterUnzoom" //tests if the xRangePad is taken into account after unzoom. it('testCorrectAxisPaddingAfterUnzoom', function() { var g = new Dygraph(document.getElementById("graph"), data2, { valueRange: [1, 50], dateWindow: [1, 9], xRangePad: 10, animatedZooms:false }); var xExtremes = g.xAxisExtremes(); var [ yExtremes ] = g.yAxisExtremes(); // Zoom x axis DygraphOps.dispatchMouseDown_Point(g, 100, 100); DygraphOps.dispatchMouseMove_Point(g, 130, 100); DygraphOps.dispatchMouseUp_Point(g, 130, 100); // Zoom y axis DygraphOps.dispatchMouseDown_Point(g, 100, 100); DygraphOps.dispatchMouseMove_Point(g, 100, 130); DygraphOps.dispatchMouseUp_Point(g, 100, 130); //check that the range for the axis has changed assert.notDeepEqual([1, 10], g.xAxisRange()); assert.notDeepEqual([1, 50], g.yAxisRange()); // unzoom by doubleclick. This is really the order in which a browser // generates events, and we depend on it. DygraphOps.dispatchMouseDown_Point(g, 10, 10); DygraphOps.dispatchMouseUp_Point(g, 10, 10); DygraphOps.dispatchMouseDown_Point(g, 10, 10); DygraphOps.dispatchMouseUp_Point(g, 10, 10); DygraphOps.dispatchDoubleClick(g, null); // check if range for x-axis was reset to original value. assert.deepEqual(xExtremes, g.xAxisRange()); assert.deepEqual(yExtremes, g.yAxisRange()); }); }); dygraphs-2.2.1/auto_tests/tests/missing_points.js010064400000000000000000000274261437353256000172460ustar00// Copyright (c) 2012 Google, Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. /** * @fileoverview Test cases for drawing lines with missing points. * * @author konigsberg@google.com (Robert Konigsberg) */ import Dygraph from '../../src/dygraph'; import * as utils from '../../src/dygraph-utils'; import Proxy from './Proxy'; import CanvasAssertions from './CanvasAssertions'; import Util from './Util'; var ZERO_TO_FIFTY = [[ 10, 0 ] , [ 20, 50 ]]; describe("missing-points", function() { cleanupAfterEach(); useProxyCanvas(utils, Proxy); it('testSeparatedPointsDontDraw', function() { var graph = document.getElementById("graph"); var g = new Dygraph( graph, [[1, 10, 11], [2, 11, null], [3, 12, 13]], { colors: ['red', 'blue'], labels: ['X', 'Y1', 'Y2'] }); var htx = g.hidden_ctx_; assert.equal(2, CanvasAssertions.numLinesDrawn(htx, '#ff0000')); assert.equal(0, CanvasAssertions.numLinesDrawn(htx, '#0000ff')); }); it('testSeparatedPointsDontDraw_expanded', function() { var graph = document.getElementById("graph"); var g = new Dygraph( graph, [[0, 10], [1, 11], [2, null], [3, 13], [4, 14]], { colors: ['blue'], labels: ['X', 'Y']}); var htx = g.hidden_ctx_; assert.equal(2, CanvasAssertions.numLinesDrawn(htx, '#0000ff')); CanvasAssertions.assertLineDrawn(htx, [56, 275], [161, 212], { strokeStyle: '#0000ff', }); CanvasAssertions.assertLineDrawn(htx, [370, 87], [475, 25], { strokeStyle: '#0000ff', }); }); it('testSeparatedPointsDontDraw_expanded_connected', function() { var graph = document.getElementById("graph"); var g = new Dygraph( graph, [[0, 10], [1, 11], [2, null], [3, 13], [4, 14]], { colors: ['blue'], connectSeparatedPoints: true, labels: ['X', 'Y'] }); var htx = g.hidden_ctx_; var num_lines = 0; assert.equal(3, CanvasAssertions.numLinesDrawn(htx, '#0000ff')); CanvasAssertions.assertConsecutiveLinesDrawn(htx, [[56, 275], [161, 212], [370, 87], [475, 25]], { strokeStyle: '#0000ff' }); }); /** * At the time of writing this test, the blue series is only points, and not lines. */ it('testConnectSeparatedPoints', function() { var g = new Dygraph( document.getElementById("graph"), [ [1, null, 3], [2, 2, null], [3, null, 7], [4, 5, null], [5, null, 5], [6, 3, null] ], { connectSeparatedPoints: true, drawPoints: true, colors: ['red', 'blue'], labels: ['X', 'Y1', 'Y2'] } ); var htx = g.hidden_ctx_; assert.equal(2, CanvasAssertions.numLinesDrawn(htx, '#0000ff')); CanvasAssertions.assertConsecutiveLinesDrawn(htx, [[56, 225], [223, 25], [391, 125]], { strokeStyle: '#0000ff' }); assert.equal(2, CanvasAssertions.numLinesDrawn(htx, '#ff0000')); CanvasAssertions.assertConsecutiveLinesDrawn(htx, [[140, 275], [307, 125], [475, 225]], { strokeStyle: '#ff0000' }); }); /** * At the time of writing this test, the blue series is only points, and not lines. */ it('testConnectSeparatedPointsWithNan', function() { var g = new Dygraph( document.getElementById("graph"), "x,A,B \n" + "1,,3 \n" + "2,2, \n" + "3,,5 \n" + "4,4, \n" + "5,,7 \n" + "6,NaN, \n" + "8,8, \n" + "10,10, \n", { connectSeparatedPoints: true, drawPoints: true, colors: ['red', 'blue'] } ); var htx = g.hidden_ctx_; // Red has two disconnected line segments assert.equal(2, CanvasAssertions.numLinesDrawn(htx, '#ff0000')); CanvasAssertions.assertLineDrawn(htx, [102, 275], [195, 212], { strokeStyle: '#ff0000' }); CanvasAssertions.assertLineDrawn(htx, [381, 87], [475, 25], { strokeStyle: '#ff0000' }); // Blue's lines are consecutive, however. assert.equal(2, CanvasAssertions.numLinesDrawn(htx, '#0000ff')); CanvasAssertions.assertConsecutiveLinesDrawn(htx, [[56, 244], [149, 181], [242, 118]], { strokeStyle: '#0000ff' }); }); /* These lines contain awesome powa! var lines = CanvasAssertions.getLinesDrawn(htx, {strokeStyle: "#0000ff"}); for (var idx = 0; idx < lines.length; idx++) { var line = lines[idx]; console.log(line[0].args, line[1].args, line[0].properties.strokeStyle); } */ it('testErrorBarsWithMissingPoints', function() { var data = [ [1, [2,1]], [2, [3,1]], [3, null], [4, [5,1]], [5, [4,1]], [6, [null,null]], ]; var g = new Dygraph( document.getElementById("graph"), data, { errorBars: true, colors: ['red'], labels: ['X', 'Y'] } ); var htx = g.hidden_ctx_; assert.equal(2, CanvasAssertions.numLinesDrawn(htx, '#ff0000')); var p0 = g.toDomCoords(data[0][0], data[0][1][0]); var p1 = g.toDomCoords(data[1][0], data[1][1][0]); var p2 = g.toDomCoords(data[3][0], data[3][1][0]); var p3 = g.toDomCoords(data[4][0], data[4][1][0]); CanvasAssertions.assertConsecutiveLinesDrawn(htx, [p0, p1], { strokeStyle: '#ff0000' }); CanvasAssertions.assertConsecutiveLinesDrawn(htx, [p2, p3], { strokeStyle: '#ff0000' }); }); it('testErrorBarsWithMissingPointsConnected', function() { var data = [ [1, [null,1]], [2, [2,1]], [3, null], [4, [5,1]], [5, [null,null]], [6, [3,1]] ]; var g = new Dygraph( document.getElementById("graph"), data, { connectSeparatedPoints: true, drawPoints: true, errorBars: true, colors: ['red'], labels: ['X', 'Y'] } ); var htx = g.hidden_ctx_; assert.equal(2, CanvasAssertions.numLinesDrawn(htx, '#ff0000')); var p1 = g.toDomCoords(data[1][0], data[1][1][0]); var p2 = g.toDomCoords(data[3][0], data[3][1][0]); var p3 = g.toDomCoords(data[5][0], data[5][1][0]); CanvasAssertions.assertConsecutiveLinesDrawn(htx, [p1, p2, p3], { strokeStyle: '#ff0000' }); }); it('testCustomBarsWithMissingPoints', function() { var data = [ [1, [1,2,3]], [2, [2,3,4]], [3, null], [4, [4,5,6]], [5, [3,4,5]], [6, [null,null,null]], [7, [2,3,4]], [8, [1,2,3]], [9, NaN], [10, [2,3,4]], [11, [3,4,5]], [12, [NaN,NaN,NaN]] ]; var g = new Dygraph( document.getElementById("graph"), data, { customBars: true, colors: ['red'], labels: ['X', 'Y'] } ); var htx = g.hidden_ctx_; assert.equal(4, CanvasAssertions.numLinesDrawn(htx, '#ff0000')); var p0 = g.toDomCoords(data[0][0], data[0][1][1]); var p1 = g.toDomCoords(data[1][0], data[1][1][1]); CanvasAssertions.assertLineDrawn(htx, p0, p1, { strokeStyle: '#ff0000' }); p0 = g.toDomCoords(data[3][0], data[3][1][1]); p1 = g.toDomCoords(data[4][0], data[4][1][1]); CanvasAssertions.assertLineDrawn(htx, p0, p1, { strokeStyle: '#ff0000' }); p0 = g.toDomCoords(data[6][0], data[6][1][1]); p1 = g.toDomCoords(data[7][0], data[7][1][1]); CanvasAssertions.assertLineDrawn(htx, p0, p1, { strokeStyle: '#ff0000' });; p0 = g.toDomCoords(data[9][0], data[9][1][1]); p1 = g.toDomCoords(data[10][0], data[10][1][1]); CanvasAssertions.assertLineDrawn(htx, p0, p1, { strokeStyle: '#ff0000' }); }); it('testCustomBarsWithMissingPointsConnected', function() { var data = [ [1, [1,null,1]], [2, [1,2,3]], [3, null], [4, [4,5,6]], [5, [null,null,null]], [6, [2,3,4]] ]; var g = new Dygraph( document.getElementById("graph"), data, { connectSeparatedPoints: true, drawPoints: true, customBars: true, colors: ['red'], labels: ['X', 'Y'] } ); var htx = g.hidden_ctx_; assert.equal(2, CanvasAssertions.numLinesDrawn(htx, '#ff0000')); var p1 = g.toDomCoords(data[1][0], data[1][1][1]); var p2 = g.toDomCoords(data[3][0], data[3][1][1]); var p3 = g.toDomCoords(data[5][0], data[5][1][1]); CanvasAssertions.assertConsecutiveLinesDrawn(htx, [p1, p2, p3], { strokeStyle: '#ff0000' }); }); it('testLeftBoundaryWithMisingPoints', function() { var data = [ [1, null, 3], [2, 1, null], [3, 0, 5], [4, 2, 1], [5, 4, null], [6, 3, 2] ]; var g = new Dygraph( document.getElementById("graph"), data, { connectSeparatedPoints: true, drawPoints: true, colors: ['red','blue'], labels: ['X', 'Y1', 'Y2'] } ); g.updateOptions({ dateWindow : [ 2.5, 4.5 ] }); assert.equal(1, g.getLeftBoundary_(0)); assert.equal(0, g.getLeftBoundary_(1)); var domX = g.toDomXCoord(1.9); var closestRow = g.findClosestRow(domX); assert.equal(1, closestRow); g.setSelection(closestRow); assert.equal(1, g.selPoints_.length); assert.equal(1, g.selPoints_[0].yval); g.setSelection(3); assert.equal(2, g.selPoints_.length); assert.equal(g.selPoints_[0].xval, g.selPoints_[1].xval); assert.equal(2, g.selPoints_[0].yval); assert.equal(1, g.selPoints_[1].yval); }); // Regression test for issue #411 it('testEmptySeries', function() { var graphDiv = document.getElementById("graph"); var g = new Dygraph( graphDiv, "Time,Empty Series,Series 1,Series 2\n" + "1381134460,,0,100\n" + "1381134461,,1,99\n" + "1381134462,,2,98\n" + "1381134463,,3,97\n" + "1381134464,,4,96\n" + "1381134465,,5,95\n" + "1381134466,,6,94\n" + "1381134467,,7,93\n" + "1381134468,,8,92\n" + "1381134469,,9,91\n", { visibility: [true, false, true], dateWindow: [1381134465, 1381134467] }); g.setSelection(6); assert.equal("1381134466: Series 2: 94", Util.getLegend(graphDiv)); }); // Regression test for issue #485 it('testMissingFill', function() { var graphDiv = document.getElementById("graph"); var N = null; var g = new Dygraph( graphDiv, [ [1, [8, 10, 12]], [2, [3, 5,7] ], [3, N, ], [4, [9, N, 2] ], // Note: nulls in arrays are not technically valid. [5, [N, 2, N] ], // see dygraphs.com/data.html. [6, [2, 3, 6] ] ], { customBars: true, connectSeparatedPoints: false, labels: [ "X", "Series1"] } ); // Make sure there are no 'NaN' line segments. var htx = g.hidden_ctx_; for (var i = 0; i < htx.calls__.length; i++) { var call = htx.calls__[i]; if ((call.name == 'moveTo' || call.name == 'lineTo') && call.args) { for (var j = 0; j < call.args.length; j++) { assert.isFalse(isNaN(call.args[j])); } } } }); }); dygraphs-2.2.1/auto_tests/tests/multi_csv.js010064400000000000000000000022311437353256000161710ustar00/** * @fileoverview Test cases for how axis labels are chosen and formatted. * * @author dan@dygraphs.com (Dan Vanderkam) */ import Dygraph from '../../src/dygraph'; describe("multi-csv", function() { cleanupAfterEach(); function getXLabels() { var x_labels = document.getElementsByClassName("dygraph-axis-label-x"); var ary = []; for (var i = 0; i < x_labels.length; i++) { ary.push(x_labels[i].innerHTML); } return ary; } it('testOneCSV', function() { var opts = { width: 480, height: 320 }; var data = "X,Y\n" + "0,-1\n" + "1,0\n" + "2,1\n" + "3,0\n" ; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); assert.deepEqual(['0', '1', '2'], getXLabels()); }); it('testTwoCSV', function() { var opts = { width: 480, height: 320 }; var data = "X,Y\n" + "0,-1\n" + "1,0\n" + "2,1\n" + "3,0\n" ; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); assert.deepEqual(['0', '1', '2'], getXLabels()); g.updateOptions({file: data}); assert.deepEqual(['0', '1', '2'], getXLabels()); }); }); dygraphs-2.2.1/auto_tests/tests/multiple_axes.js010064400000000000000000000206511437353256000170450ustar00/** * @fileoverview Tests involving multiple y-axes. * * @author danvdk@gmail.com (Dan Vanderkam) */ import Dygraph from '../../src/dygraph'; import * as utils from '../../src/dygraph-utils'; import Util from './Util'; describe("multiple-axes-tests", function() { cleanupAfterEach(); var getData = function() { var data = []; for (var i = 1; i <= 100; i++) { var m = "01", d = i; if (d > 31) { m = "02"; d -= 31; } if (m == "02" && d > 28) { m = "03"; d -= 28; } if (m == "03" && d > 31) { m = "04"; d -= 31; } if (d < 10) d = "0" + d; // two series, one with range 1-100, one with range 1-2M data.push([new Date("2010/" + m + "/" + d), i, 100 - i, 1e6 * (1 + i * (100 - i) / (50 * 50)), 1e6 * (2 - i * (100 - i) / (50 * 50))]); } return data; }; it('testBasicMultipleAxes', function() { var data = getData(); var g = new Dygraph( document.getElementById("graph"), data, { labels: [ 'Date', 'Y1', 'Y2', 'Y3', 'Y4' ], width: 640, height: 350, series : { 'Y3': { axis: 'y2' }, 'Y4': { axis: 'y2' } }, axes : { y2 : { // set axis-related properties here labelsKMB: true } } } ); assert.deepEqual(["0","20","40","60","80","100"], Util.getYLabels("1")); assert.deepEqual(["900k","1.12M","1.34M","1.55M","1.77M","1.99M"], Util.getYLabels("2")); }); it('testTwoAxisVisibility', function() { var data = []; data.push([0,0,0]); data.push([1,2,2000]); data.push([2,4,1000]); var g = new Dygraph( document.getElementById("graph"), data, { labels: [ 'X', 'bar', 'zot' ], series : { zot : { axis : 'y2' } }, axes : { y2: { labelsKMB: true } } } ); assert.isTrue(document.getElementsByClassName("dygraph-axis-label-y").length > 0); assert.isTrue(document.getElementsByClassName("dygraph-axis-label-y2").length > 0); g.setVisibility(0, false); assert.isTrue(document.getElementsByClassName("dygraph-axis-label-y").length > 0); assert.isTrue(document.getElementsByClassName("dygraph-axis-label-y2").length > 0); g.setVisibility(0, true); g.setVisibility(1, false); assert.isTrue(document.getElementsByClassName("dygraph-axis-label-y").length > 0); assert.isTrue(document.getElementsByClassName("dygraph-axis-label-y2").length > 0); }); // verifies that all four chart labels (title, x-, y-, y2-axis label) can be // used simultaneously. it('testMultiChartLabels', function() { var data = getData(); var el = document.getElementById("graph"); el.style.border = '1px solid black'; el.style.marginLeft = '200px'; el.style.marginTop = '200px'; var g = new Dygraph( el, data, { labels: [ 'Date', 'Y1', 'Y2', 'Y3', 'Y4' ], width: 640, height: 350, series : { 'Y3': { axis: 'y2' }, 'Y4': { axis: 'y2' } }, xlabel: 'x-axis', ylabel: 'y-axis', y2label: 'y2-axis', title: 'Chart title' } ); assert.deepEqual(["Chart title", "x-axis", "y-axis", "y2-axis"], Util.getClassTexts("dygraph-label")); assert.deepEqual(["Chart title"], Util.getClassTexts("dygraph-title")); assert.deepEqual(["x-axis"], Util.getClassTexts("dygraph-xlabel")); assert.deepEqual(["y-axis"], Util.getClassTexts("dygraph-ylabel")); assert.deepEqual(["y2-axis"], Util.getClassTexts("dygraph-y2label")); // TODO(danvk): check relative positioning here: title on top, y left of y2. }); // Test option to draw Y2 axis, but not Y1 it('testOnlyDrawingSecondAxis', function() { var data = getData(); var g = new Dygraph( document.getElementById("graph"), data, { labels: [ 'Date', 'Y1', 'Y2', 'Y3', 'Y4' ], drawPoints : true, pointSize : 3, series : { 'Y4': { axis: 'y2' } }, axes: { y: { drawAxis: false }, y2: { drawAxis: true } }, } ); assert.isTrue(document.getElementsByClassName("dygraph-axis-label-y1").length == 0); assert.isTrue(document.getElementsByClassName("dygraph-axis-label-y2").length > 0); }); // Test default(?) Y-axis drawing: Y1 drawn, Y2 not drawn it('testNotDrawingSecondAxis', function() { var data = getData(); var g = new Dygraph( document.getElementById("graph"), data, { labels: [ 'Date', 'Y1', 'Y2', 'Y3', 'Y4' ], drawPoints : true, pointSize : 3, series : { 'Y4': { axis: 'y2' } }, axes: { y: { drawAxis: true }, y2: { drawAxis: false } }, } ); assert.isTrue(document.getElementsByClassName("dygraph-axis-label-y1").length > 0); assert.isTrue(document.getElementsByClassName("dygraph-axis-label-y2").length == 0); }); // Check that a chart w/o a secondary y-axis will not get a y2label, even if one // is specified. it('testNoY2LabelWithoutSecondaryAxis', function() { var g = new Dygraph( document.getElementById("graph"), getData(), { labels: [ 'Date', 'Y1', 'Y2', 'Y3', 'Y4' ], width: 640, height: 350, xlabel: 'x-axis', ylabel: 'y-axis', y2label: 'y2-axis', title: 'Chart title' } ); assert.deepEqual(["Chart title", "x-axis", "y-axis"], Util.getClassTexts("dygraph-label")); assert.deepEqual(["Chart title"], Util.getClassTexts("dygraph-title")); assert.deepEqual(["x-axis"], Util.getClassTexts("dygraph-xlabel")); assert.deepEqual(["y-axis"], Util.getClassTexts("dygraph-ylabel")); assert.deepEqual([], Util.getClassTexts("dygraph-y2label")); }); it('testValueRangePerAxisOptions', function() { var data = getData(); var g = new Dygraph( document.getElementById("graph"), data, { labels: [ 'Date', 'Y1', 'Y2', 'Y3', 'Y4' ], series : { 'Y3': { axis: 'y2' }, 'Y4': { axis: 'y2' } }, axes: { y: { axisLabelWidth: 60, valueRange: [40, 70] }, y2: { // set axis-related properties here labelsKMB: true } }, ylabel: 'Primary y-axis', y2label: 'Secondary y-axis', } ); assert.deepEqual(["40", "45", "50", "55", "60", "65"], Util.getYLabels("1")); assert.deepEqual(["900k","1.1M","1.3M","1.5M","1.7M","1.9M"], Util.getYLabels("2")); g.updateOptions( { axes: { y: { valueRange: [40, 80] }, y2: { valueRange: [1e6, 1.2e6] } } } ); assert.deepEqual(["40", "45", "50", "55", "60", "65", "70", "75"], Util.getYLabels("1")); assert.deepEqual(["1M", "1.02M", "1.05M", "1.08M", "1.1M", "1.13M", "1.15M", "1.18M"], Util.getYLabels("2")); }); it('testDrawPointCallback', function() { var data = getData(); var results = { y : {}, y2 : {}}; var firstCallback = function(g, seriesName, ctx, canvasx, canvasy, color, radius) { results.y[seriesName] = 1; utils.Circles.DEFAULT(g, seriesName, ctx, canvasx, canvasy, color, radius); }; var secondCallback = function(g, seriesName, ctx, canvasx, canvasy, color, radius) { results.y2[seriesName] = 1; utils.Circles.DEFAULT(g, seriesName, ctx, canvasx, canvasy, color, radius); }; var g = new Dygraph( document.getElementById("graph"), data, { labels: [ 'Date', 'Y1', 'Y2', 'Y3', 'Y4' ], drawPoints : true, pointSize : 3, series : { 'Y3': { axis: 'y2' }, 'Y4': { axis: 'y2' } }, axes: { y2: { drawPointCallback: secondCallback } }, drawPointCallback: firstCallback } ); assert.equal(1, results.y["Y1"]); assert.equal(1, results.y["Y2"]); assert.equal(1, results.y2["Y3"]); assert.equal(1, results.y2["Y4"]); }); // Test for http://code.google.com/p/dygraphs/issues/detail?id=436 it('testRemovingSecondAxis', function() { var data = getData(); var results = { y : {}, y2 : {}}; var g = new Dygraph( document.getElementById("graph"), data, { labels: [ 'Date', 'Y1', 'Y2', 'Y3', 'Y4' ], drawPoints : true, pointSize : 3, series : { 'Y4': { axis: 'y2' } }, } ); g.updateOptions({ series : { Y4 : { axis : 'y' } } }); }); }); dygraphs-2.2.1/auto_tests/tests/no_hours.js010064400000000000000000000030571437353256000160270ustar00/** * @fileoverview Tests that we don'show specify hours, minutes or seconds * in your dates if you don't specify them. This can get mixed up becaues of * time zones. * * @author danvk@google.com (Dan Vanderkam) */ import Dygraph from '../../src/dygraph'; import Util from './Util'; describe("no-hours", function() { cleanupAfterEach(); it('testNoHours', function() { var opts = { width: 480, height: 320 }; var data = "Date,Y\n" + "2012/03/13,-1\n" + "2012/03/14,0\n" + "2012/03/15,1\n" + "2012/03/16,0\n" ; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); g.setSelection(0); assert.equal("2012/03/13: Y: -1", Util.getLegend()); g.setSelection(1); assert.equal("2012/03/14: Y: 0", Util.getLegend()); g.setSelection(2); assert.equal("2012/03/15: Y: 1", Util.getLegend()); g.setSelection(3); assert.equal("2012/03/16: Y: 0", Util.getLegend()); }); it('testNoHoursDashed', function() { var opts = { width: 480, height: 320 }; var data = "Date,Y\n" + "2012-03-13,-1\n" + "2012-03-14,0\n" + "2012-03-15,1\n" + "2012-03-16,0\n" ; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); g.setSelection(0); assert.equal("2012/03/13: Y: -1", Util.getLegend()); g.setSelection(1); assert.equal("2012/03/14: Y: 0", Util.getLegend()); g.setSelection(2); assert.equal("2012/03/15: Y: 1", Util.getLegend()); g.setSelection(3); assert.equal("2012/03/16: Y: 0", Util.getLegend()); }); }); dygraphs-2.2.1/auto_tests/tests/numeric_ticker.js010064400000000000000000001211001437353256000171640ustar00/** * @fileoverview Test cases for the numeric tick-generating functions. * These were generated by adding logging code to the old ticker functions. The * tests serve to track existing behavior should it change in the future. * * @author danvdk@gmail.com (Dan Vanderkam) */ import Dygraph from '../../src/dygraph'; import * as DygraphTickers from '../../src/dygraph-tickers'; import DEFAULT_ATTRS from '../../src/dygraph-default-attrs'; describe("numeric-ticker-tests", function() { cleanupAfterEach(); var createOptionsViewForAxis = function(axis, dict) { return function (x) { if (dict && dict.hasOwnProperty(x)) { return dict[x]; } if (DEFAULT_ATTRS.axes[axis].hasOwnProperty(x)) { return DEFAULT_ATTRS.axes[axis][x]; } if (DEFAULT_ATTRS.hasOwnProperty(x)) { return DEFAULT_ATTRS[x]; } if (x == 'axisLabelFormatter') return null; throw "mysterious " + axis + "-axis option: " + x; }; }; it('testBasicNumericTicker', function() { var opts = {"logscale":null,"labelsKMG2":false,"labelsKMB":false}; var options = createOptionsViewForAxis('y', opts); var ticks = DygraphTickers.numericTicks(-0.4, 4.4, 320, options); var expected_ticks = [ {"v":-0.5,"label":"-0.5"}, {"v":0,"label":"0"}, {"v":0.5,"label":"0.5"}, {"v":1,"label":"1"}, {"v":1.5,"label":"1.5"}, {"v":2,"label":"2"}, {"v":2.5,"label":"2.5"}, {"v":3,"label":"3"}, {"v":3.5,"label":"3.5"}, {"v":4,"label":"4"}, {"v":4.5,"label":"4.5"}]; assert.deepEqual(expected_ticks, ticks); ticks = DygraphTickers.numericTicks(1, 84, 540, options); var expected_ticks = [ {"v":0,"label":"0"}, {"v":5,"label":"5"}, {"v":10,"label":"10"}, {"v":15,"label":"15"}, {"v":20,"label":"20"}, {"v":25,"label":"25"}, {"v":30,"label":"30"}, {"v":35,"label":"35"}, {"v":40,"label":"40"}, {"v":45,"label":"45"}, {"v":50,"label":"50"}, {"v":55,"label":"55"}, {"v":60,"label":"60"}, {"v":65,"label":"65"}, {"v":70,"label":"70"}, {"v":75,"label":"75"}, {"v":80,"label":"80"}, {"v":85,"label":"85"} ]; assert.deepEqual(expected_ticks, ticks); }); /* it('testAllNumericTickers', function() { assert.deepEqual([{"v":-0.5,"label":"-0.5"},{"v":0,"label":"0"},{"v":0.5,"label":"0.5"},{"v":1,"label":"1"},{"v":1.5,"label":"1.5"},{"v":2,"label":"2"},{"v":2.5,"label":"2.5"},{"v":3,"label":"3"},{"v":3.5,"label":"3.5"},{"v":4,"label":"4"}], Dygraph.numericTicks(-0.4, 4.4, 320, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":-1.5,"label":"-1.5"},{"v":-1,"label":"-1"},{"v":-0.5,"label":"-0.5"},{"v":0,"label":"0"},{"v":0.5,"label":"0.5"},{"v":1,"label":"1"}], Dygraph.numericTicks(-1.4157430939856124, 1.4157430939856124, 400, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":-10,"label":"-10"},{"v":-8,"label":"-8"},{"v":-6,"label":"-6"},{"v":-4,"label":"-4"},{"v":-2,"label":"-2"},{"v":0,"label":"0"},{"v":2,"label":"2"},{"v":4,"label":"4"},{"v":6,"label":"6"},{"v":8,"label":"8"}], Dygraph.numericTicks(-10, 9.98046875, 400, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":-200,"label":"-200"},{"v":0,"label":"0"},{"v":200,"label":"200"},{"v":400,"label":"400"},{"v":600,"label":"600"},{"v":800,"label":"800"},{"v":-17999000,"label":"1000"}], Dygraph.numericTicks(-101.10000000000001, 1100.1, 300, createOptionsViewForAxis('y',{"logscale":false,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":-20,"label":"-20"},{"v":-10,"label":"-10"},{"v":0,"label":"0"},{"v":10,"label":"10"},{"v":20,"label":"20"},{"v":30,"label":"30"},{"v":40,"label":"40"}], Dygraph.numericTicks(-11.687459005175139, 42.287459005175144, 320, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":-15,"label":"-15"},{"v":-10,"label":"-10"},{"v":-5,"label":"-5"},{"v":0,"label":"0"},{"v":5,"label":"5"},{"v":10,"label":"10"}], Dygraph.numericTicks(-12, 12, 320, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":-15,"label":"-15"},{"v":-10,"label":"-10"},{"v":-5,"label":"-5"},{"v":0,"label":"0"},{"v":5,"label":"5"},{"v":10,"label":"10"}], Dygraph.numericTicks(-13.19792086872138, 13.197062407353386, 300, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":-220,"label":"-220"},{"v":-200,"label":"-200"},{"v":-180,"label":"-180"},{"v":-160,"label":"-160"},{"v":-140,"label":"-140"},{"v":-120,"label":"-120"}], Dygraph.numericTicks(-220, -100, 200, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":-40,"label":"-40"},{"v":-20,"label":"-20"},{"v":0,"label":"0"},{"v":20,"label":"20"},{"v":40,"label":"40"},{"v":60,"label":"60"},{"v":80,"label":"80"},{"v":100,"label":"100"},{"v":120,"label":"120"}], Dygraph.numericTicks(-32.8, 132.8, 300, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":-40,"label":"-40"},{"v":-30,"label":"-30"},{"v":-20,"label":"-20"},{"v":-10,"label":"-10"},{"v":0,"label":"0"},{"v":10,"label":"10"},{"v":20,"label":"20"},{"v":30,"label":"30"},{"v":40,"label":"40"},{"v":50,"label":"50"},{"v":60,"label":"60"},{"v":70,"label":"70"},{"v":80,"label":"80"}], Dygraph.numericTicks(-34.309, 89.279, 400, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":-60,"label":"-60"},{"v":-40,"label":"-40"},{"v":-20,"label":"-20"},{"v":0,"label":"0"},{"v":20,"label":"20"},{"v":40,"label":"40"}], Dygraph.numericTicks(-60, 60, 200, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":-60,"label":"-60"},{"v":-40,"label":"-40"},{"v":-20,"label":"-20"},{"v":0,"label":"0"},{"v":20,"label":"20"},{"v":40,"label":"40"}], Dygraph.numericTicks(-60, 60, 300, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":0,"label":"0"},{"v":0.0001,"label":"1.00e-4"},{"v":0.0002,"label":"2.00e-4"},{"v":-17999999,"label":"3.00e-4"},{"v":0.0004,"label":"4.00e-4"},{"v":0.0005,"label":"5.00e-4"}], Dygraph.numericTicks(0, 0.00055, 320, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":0,"label":0},{"v":0.0001,"label":0.0001},{"v":0.0002,"label":0.0002},{"v":-17999999,"label":0.0003},{"v":0.0004,"label":0.0004},{"v":0.0005,"label":0.0005}], Dygraph.numericTicks(0, 0.00055, 320, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":0,"label":"0"},{"v":0.2,"label":"0.2"},{"v":0.4,"label":"0.4"},{"v":-17999999,"label":"0.6"},{"v":0.8,"label":"0.8"}], Dygraph.numericTicks(0, 1, 200, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":0,"label":"0"},{"v":0.2,"label":"0.2"},{"v":0.4,"label":"0.4"},{"v":-17999999,"label":"0.6"},{"v":0.8,"label":"0.8"}], Dygraph.numericTicks(0, 1, 300, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":0,"label":"0"},{"v":0.1,"label":"0.1"},{"v":0.2,"label":"0.2"},{"v":-17999999,"label":"0.3"},{"v":0.4,"label":"0.4"},{"v":0.5,"label":"0.5"},{"v":-17999999,"label":"0.6"},{"v":-17999999,"label":"0.7"},{"v":0.8,"label":"0.8"},{"v":0.9,"label":"0.9"},{"v":1,"label":"1"},{"v":1.1,"label":"1.1"},{"v":-17999998,"label":"1.2"}], Dygraph.numericTicks(0, 1.2, 400, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":0,"label":"0"},{"v":10,"label":"10"},{"v":20,"label":"20"},{"v":30,"label":"30"},{"v":40,"label":"40"},{"v":50,"label":"50"},{"v":60,"label":"60"},{"v":70,"label":"70"},{"v":80,"label":"80"},{"v":90,"label":"90"}], Dygraph.numericTicks(0, 100, 400, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":0,"label":"0"},{"v":20,"label":"20"},{"v":40,"label":"40"},{"v":60,"label":"60"},{"v":80,"label":"80"},{"v":100,"label":"100"}], Dygraph.numericTicks(0, 104.53192180924043, 300, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":0,"label":"0"},{"v":20,"label":"20"},{"v":40,"label":"40"},{"v":60,"label":"60"},{"v":80,"label":"80"},{"v":100,"label":"100"}], Dygraph.numericTicks(0, 109.9856877755916, 320, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":0,"label":"0"},{"v":2,"label":"2"},{"v":4,"label":"4"},{"v":6,"label":"6"},{"v":8,"label":"8"},{"v":10,"label":"10"}], Dygraph.numericTicks(0, 11, 300, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":0,"label":"0"},{"v":20,"label":"20"},{"v":40,"label":"40"},{"v":60,"label":"60"},{"v":80,"label":"80"},{"v":100,"label":"100"}], Dygraph.numericTicks(0, 110, 300, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":0,"label":"0"},{"v":20,"label":"20"},{"v":40,"label":"40"},{"v":60,"label":"60"},{"v":80,"label":"80"},{"v":100,"label":"100"}], Dygraph.numericTicks(0, 110, 320, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":0,"label":"0"},{"v":10,"label":"10"},{"v":20,"label":"20"},{"v":30,"label":"30"},{"v":40,"label":"40"},{"v":50,"label":"50"},{"v":60,"label":"60"},{"v":70,"label":"70"},{"v":80,"label":"80"},{"v":90,"label":"90"},{"v":100,"label":"100"}], Dygraph.numericTicks(0, 110, 350, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":0,"label":"0"},{"v":200,"label":"200"},{"v":400,"label":"400"},{"v":600,"label":"600"},{"v":800,"label":"800"},{"v":-17999000,"label":"1000"}], Dygraph.numericTicks(0, 1100, 300, createOptionsViewForAxis('y',{"logscale":false,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":0,"label":"0"},{"v":-17000000,"label":"1M"},{"v":-16000000,"label":"2M"},{"v":-15000000,"label":"3M"},{"v":-14000000,"label":"4M"},{"v":-13000000,"label":"5M"},{"v":-12000000,"label":"6M"},{"v":-11000000,"label":"7M"},{"v":-10000000,"label":"8M"},{"v":-9000000,"label":"9M"},{"v":-8000000,"label":"10M"}], Dygraph.numericTicks(0, 11000000, 480, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":true}))); assert.deepEqual([{"v":0,"label":"0"},{"v":20,"label":"20"},{"v":40,"label":"40"},{"v":60,"label":"60"},{"v":80,"label":"80"},{"v":100,"label":"100"}], Dygraph.numericTicks(0, 119, 320, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":0,"label":"0"},{"v":20,"label":"20"},{"v":40,"label":"40"},{"v":60,"label":"60"},{"v":80,"label":"80"},{"v":100,"label":"100"},{"v":120,"label":"120"}], Dygraph.numericTicks(0, 130.9, 300, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":0,"label":"0"},{"v":20,"label":"20"},{"v":40,"label":"40"},{"v":60,"label":"60"},{"v":80,"label":"80"},{"v":100,"label":"100"},{"v":120,"label":"120"}], Dygraph.numericTicks(0, 131, 320, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":0,"label":"0"},{"v":-17998000,"label":"2000"},{"v":-17996000,"label":"4000"},{"v":-17994000,"label":"6000"},{"v":-17992000,"label":"8000"},{"v":-17990000,"label":"10000"},{"v":-17988000,"label":"12000"},{"v":-17986000,"label":"14000"},{"v":-17984000,"label":"16000"}], Dygraph.numericTicks(0, 16977.4, 320, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":0,"label":"0"},{"v":0.5,"label":"0.5"},{"v":1,"label":"1"},{"v":1.5,"label":"1.5"}], Dygraph.numericTicks(0, 2, 300, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":0,"label":"0"},{"v":0.2,"label":"0.2"},{"v":0.4,"label":"0.4"},{"v":-17999999,"label":"0.6"},{"v":0.8,"label":"0.8"},{"v":1,"label":"1"},{"v":-17999998,"label":"1.2"},{"v":-17999998,"label":"1.4"},{"v":1.6,"label":"1.6"},{"v":1.8,"label":"1.8"}], Dygraph.numericTicks(0, 2, 400, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":0,"label":"0"},{"v":0.5,"label":"0.5"},{"v":1,"label":"1"},{"v":1.5,"label":"1.5"},{"v":2,"label":"2"}], Dygraph.numericTicks(0, 2.2, 300, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":0,"label":"0"},{"v":-17800000,"label":"200K"},{"v":-17600000,"label":"400K"},{"v":-17400000,"label":"600K"},{"v":-17200000,"label":"800K"},{"v":-17000000,"label":"1M"},{"v":-16800000,"label":"1.2M"},{"v":-16600000,"label":"1.4M"},{"v":-16400000,"label":"1.6M"},{"v":-16200000,"label":"1.8M"},{"v":-16000000,"label":"2M"}], Dygraph.numericTicks(0, 2200000, 350, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":true}))); assert.deepEqual([{"v":0,"label":"0"},{"v":50,"label":"50"},{"v":100,"label":"100"},{"v":150,"label":"150"},{"v":200,"label":"200"}], Dygraph.numericTicks(0, 249, 300, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":0,"label":"0"},{"v":500,"label":"500"},{"v":-17999000,"label":"1000"},{"v":1500,"label":"1500"},{"v":-17998000,"label":"2000"},{"v":2500,"label":"2500"}], Dygraph.numericTicks(0, 2747.9970998900817, 320, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":0,"label":"0"},{"v":200,"label":"200"},{"v":400,"label":"400"},{"v":600,"label":"600"},{"v":800,"label":"800"},{"v":-17999000,"label":"1K"},{"v":1200,"label":"1.2K"},{"v":1400,"label":"1.4K"},{"v":1600,"label":"1.6K"},{"v":1800,"label":"1.8K"},{"v":-17998000,"label":"2K"},{"v":2200,"label":"2.2K"},{"v":2400,"label":"2.4K"},{"v":2600,"label":"2.6K"}], Dygraph.numericTicks(0, 2747.9970998900817, 480, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":true}))); assert.deepEqual([{"v":0,"label":"0"},{"v":5,"label":"5"},{"v":10,"label":"10"},{"v":15,"label":"15"},{"v":20,"label":"20"},{"v":25,"label":"25"},{"v":30,"label":"30"}], Dygraph.numericTicks(0, 32.698942321287205, 320, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":0,"label":"0"},{"v":-17500000,"label":"500000"},{"v":-17000000,"label":"1.00e+6"},{"v":-16500000,"label":"1.50e+6"},{"v":-16000000,"label":"2.00e+6"},{"v":-15500000,"label":"2.50e+6"},{"v":-15000000,"label":"3.00e+6"}], Dygraph.numericTicks(0, 3263100.6418021005, 480, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":0,"label":"0"},{"v":5,"label":"5"},{"v":10,"label":"10"},{"v":15,"label":"15"},{"v":20,"label":"20"},{"v":25,"label":"25"},{"v":30,"label":"30"}], Dygraph.numericTicks(0, 33.16213467701236, 320, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":0,"label":"0"},{"v":0.5,"label":"0.5"},{"v":1,"label":"1"},{"v":1.5,"label":"1.5"},{"v":2,"label":"2"},{"v":2.5,"label":"2.5"},{"v":3,"label":"3"},{"v":3.5,"label":"3.5"}], Dygraph.numericTicks(0, 4, 300, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":0,"label":"0"},{"v":0.5,"label":"0.5"},{"v":1,"label":"1"},{"v":1.5,"label":"1.5"},{"v":2,"label":"2"},{"v":2.5,"label":"2.5"},{"v":3,"label":"3"},{"v":3.5,"label":"3.5"},{"v":4,"label":"4"}], Dygraph.numericTicks(0, 4.4, 320, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":0,"label":"0"},{"v":5,"label":"5"},{"v":10,"label":"10"},{"v":15,"label":"15"},{"v":20,"label":"20"},{"v":25,"label":"25"},{"v":30,"label":"30"},{"v":35,"label":"35"},{"v":40,"label":"40"}], Dygraph.numericTicks(0, 42, 300, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":true}))); assert.deepEqual([{"v":0,"label":"0"},{"v":8,"label":"8"},{"v":16,"label":"16"},{"v":24,"label":"24"},{"v":32,"label":"32"},{"v":40,"label":"40"}], Dygraph.numericTicks(0, 42, 300, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":true,"labelsKMB":false}))); assert.deepEqual([{"v":0,"label":0},{"v":8,"label":8},{"v":16,"label":16},{"v":24,"label":24},{"v":32,"label":32},{"v":40,"label":40}], Dygraph.numericTicks(0, 42, 300, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":true,"labelsKMB":false}))); assert.deepEqual([{"v":0,"label":"0"},{"v":999982000000,"label":"1T"},{"v":1999982000000,"label":"2T"},{"v":2999982000000,"label":"3T"},{"v":3999982000000,"label":"4T"}], Dygraph.numericTicks(0, 4837851162214.3, 300, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":true}))); assert.deepEqual([{"v":0,"label":"0"},{"v":549755813888,"label":"512G"},{"v":1099511627776,"label":"1T"},{"v":1649267441664,"label":"1.5T"},{"v":2199023255552,"label":"2T"},{"v":2748779069440,"label":"2.5T"},{"v":3298534883328,"label":"3T"},{"v":3848290697216,"label":"3.5T"},{"v":4398046511104,"label":"4T"}], Dygraph.numericTicks(0, 4837851162214.3, 300, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":true,"labelsKMB":false}))); assert.deepEqual([{"v":0,"label":0},{"v":549755813888,"label":"512G"},{"v":1099511627776,"label":"1T"},{"v":1649267441664,"label":"1.5T"},{"v":2199023255552,"label":"2T"},{"v":2748779069440,"label":"2.5T"},{"v":3298534883328,"label":"3T"},{"v":3848290697216,"label":"3.5T"},{"v":4398046511104,"label":"4T"}], Dygraph.numericTicks(0, 4837851162214.3, 300, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":true,"labelsKMB":false}))); assert.deepEqual([{"v":0,"label":"0"},{"v":-17999000,"label":"1000"},{"v":-17998000,"label":"2000"},{"v":-17997000,"label":"3000"},{"v":-17996000,"label":"4000"},{"v":-17995000,"label":"5000"}], Dygraph.numericTicks(0, 5451.6, 300, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":0,"label":"0"},{"v":100,"label":"100"},{"v":200,"label":"200"},{"v":300,"label":"300"},{"v":400,"label":"400"},{"v":500,"label":"500"}], Dygraph.numericTicks(0, 550, 320, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":0,"label":"0"},{"v":10,"label":"10"},{"v":20,"label":"20"},{"v":30,"label":"30"},{"v":40,"label":"40"},{"v":50,"label":"50"},{"v":60,"label":"60"}], Dygraph.numericTicks(0, 64.9, 300, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":0,"label":"0"},{"v":100,"label":"100"},{"v":200,"label":"200"},{"v":300,"label":"300"},{"v":400,"label":"400"},{"v":500,"label":"500"},{"v":600,"label":"600"}], Dygraph.numericTicks(0, 667.9, 300, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":0,"label":"0"},{"v":1,"label":"1"},{"v":2,"label":"2"},{"v":3,"label":"3"},{"v":4,"label":"4"},{"v":5,"label":"5"},{"v":6,"label":"6"},{"v":7,"label":"7"}], Dygraph.numericTicks(0, 7.7, 300, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":0,"label":"0"},{"v":1,"label":"1"},{"v":2,"label":"2"},{"v":3,"label":"3"},{"v":4,"label":"4"},{"v":5,"label":"5"},{"v":6,"label":"6"},{"v":7,"label":"7"}], Dygraph.numericTicks(0, 7.9347329768293005, 300, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":0,"label":"0"},{"v":10,"label":"10"},{"v":20,"label":"20"},{"v":30,"label":"30"},{"v":40,"label":"40"},{"v":50,"label":"50"},{"v":60,"label":"60"},{"v":70,"label":"70"}], Dygraph.numericTicks(0, 72.6, 320, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":0,"label":"0"},{"v":20,"label":"20"},{"v":40,"label":"40"},{"v":60,"label":"60"},{"v":80,"label":"80"}], Dygraph.numericTicks(0, 99, 200, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":0,"label":"0"},{"v":20,"label":"20"},{"v":40,"label":"40"},{"v":60,"label":"60"},{"v":80,"label":"80"}], Dygraph.numericTicks(0, 99, 300, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":0,"label":"0"},{"v":10,"label":"10"},{"v":20,"label":"20"},{"v":30,"label":"30"},{"v":40,"label":"40"},{"v":50,"label":"50"},{"v":60,"label":"60"},{"v":70,"label":"70"},{"v":80,"label":"80"},{"v":90,"label":"90"}], Dygraph.numericTicks(0, 99, 320, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":0,"label":"0"},{"v":200,"label":"200"},{"v":400,"label":"400"},{"v":600,"label":"600"},{"v":800,"label":"800"}], Dygraph.numericTicks(0, 999, 300, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":0.000001,"label":"1.00e-6"},{"v":-17999999,"label":""},{"v":-17999999,"label":""},{"v":-17999999,"label":""},{"v":-17999999,"label":""},{"v":-17999999,"label":""},{"v":-17999999,"label":""},{"v":-17999999,"label":""},{"v":-17999999,"label":""},{"v":-17999999,"label":"1.00e-5"},{"v":-17999999,"label":""},{"v":-17999999,"label":""},{"v":-17999999,"label":""},{"v":-17999999,"label":""},{"v":-17999999,"label":""},{"v":-17999999,"label":""},{"v":-17999999,"label":""},{"v":-17999999,"label":""},{"v":0.0001,"label":"1.00e-4"},{"v":0.0002,"label":""},{"v":-17999999,"label":""},{"v":0.0004,"label":""},{"v":0.0005,"label":""},{"v":-17999999,"label":""},{"v":0.0007,"label":""},{"v":0.0008,"label":""},{"v":-17999999,"label":""},{"v":0.001,"label":"1.00e-3"},{"v":0.002,"label":""},{"v":0.003,"label":""},{"v":0.004,"label":""},{"v":0.005,"label":""},{"v":0.006,"label":""},{"v":0.007,"label":""},{"v":0.008,"label":""},{"v":-17999999,"label":""},{"v":0.01,"label":"0.01"},{"v":0.02,"label":""},{"v":0.03,"label":""},{"v":0.04,"label":""},{"v":0.05,"label":""},{"v":0.06,"label":""},{"v":0.07,"label":""},{"v":0.08,"label":""},{"v":0.09,"label":""},{"v":0.1,"label":"0.1"},{"v":0.2,"label":""},{"v":-17999999,"label":""},{"v":0.4,"label":""},{"v":0.5,"label":""},{"v":-17999999,"label":""},{"v":-17999999,"label":""},{"v":0.8,"label":""},{"v":0.9,"label":""},{"v":1,"label":"1"},{"v":2,"label":""},{"v":3,"label":""},{"v":4,"label":""},{"v":5,"label":""},{"v":6,"label":""},{"v":7,"label":""},{"v":8,"label":""},{"v":9,"label":""},{"v":10,"label":"10"},{"v":20,"label":""},{"v":30,"label":""},{"v":40,"label":""},{"v":50,"label":""},{"v":60,"label":""},{"v":70,"label":""},{"v":80,"label":""},{"v":90,"label":""},{"v":100,"label":"100"},{"v":200,"label":""},{"v":300,"label":""},{"v":400,"label":""},{"v":500,"label":""},{"v":600,"label":""},{"v":700,"label":""},{"v":800,"label":""},{"v":900,"label":""},{"v":-17999000,"label":"1000"}], Dygraph.numericTicks(0.000001, 1099.9999999, 300, createOptionsViewForAxis('y',{"logscale":true,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":0,"label":"0"},{"v":1,"label":"1"},{"v":2,"label":"2"},{"v":3,"label":"3"},{"v":4,"label":"4"},{"v":5,"label":"5"}], Dygraph.numericTicks(0.6, 5.4, 300, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":0.5,"label":"0.5"},{"v":1,"label":"1"},{"v":1.5,"label":"1.5"},{"v":2,"label":"2"},{"v":2.5,"label":"2.5"},{"v":3,"label":"3"},{"v":3.5,"label":"3.5"},{"v":4,"label":"4"},{"v":4.5,"label":"4.5"}], Dygraph.numericTicks(0.6373123361267239, 4.824406504982038, 300, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":0.5,"label":"0.5"},{"v":1,"label":"1"},{"v":1.5,"label":"1.5"},{"v":2,"label":"2"},{"v":2.5,"label":"2.5"},{"v":3,"label":"3"},{"v":3.5,"label":"3.5"},{"v":4,"label":"4"},{"v":4.5,"label":"4.5"}], Dygraph.numericTicks(0.6373123361267239, 4.824406504982038, 353, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":0.6000000000000001,"label":"0.6"},{"v":0.8,"label":"0.8"},{"v":1,"label":"1"},{"v":-17999998,"label":"1.2"},{"v":-17999998,"label":"1.4"},{"v":1.6,"label":"1.6"},{"v":-17999998,"label":"1.8"},{"v":2,"label":"2"},{"v":2.2,"label":"2.2"},{"v":-17999997,"label":"2.4"},{"v":2.6,"label":"2.6"},{"v":-17999997,"label":"2.8"},{"v":-17999997,"label":"3"},{"v":3.2,"label":"3.2"},{"v":-17999996,"label":"3.4"},{"v":3.6,"label":"3.6"},{"v":-17999996,"label":"3.8"},{"v":4,"label":"4"},{"v":4.2,"label":"4.2"},{"v":4.4,"label":"4.4"},{"v":4.6,"label":"4.6"},{"v":-17999995,"label":"4.8"}], Dygraph.numericTicks(0.6373123361267239, 4.824406504982038, 743, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":0.5,"label":"0.5"},{"v":1,"label":"1"},{"v":1.5,"label":"1.5"},{"v":2,"label":"2"},{"v":2.5,"label":"2.5"},{"v":3,"label":"3"},{"v":3.5,"label":"3.5"},{"v":4,"label":"4"},{"v":4.5,"label":"4.5"}], Dygraph.numericTicks(0.6386658954698001, 4.8095173522082, 300, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":0.5,"label":"0.5"},{"v":1,"label":"1"},{"v":1.5,"label":"1.5"},{"v":2,"label":"2"},{"v":2.5,"label":"2.5"},{"v":3,"label":"3"},{"v":3.5,"label":"3.5"},{"v":4,"label":"4"}], Dygraph.numericTicks(0.7101014279158788, 4.023726495301334, 300, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":0,"label":"0"},{"v":20,"label":"20"},{"v":40,"label":"40"},{"v":60,"label":"60"},{"v":80,"label":"80"},{"v":100,"label":"100"}], Dygraph.numericTicks(1, 109, 320, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":1,"label":"1"},{"v":1.5,"label":"1.5"},{"v":2,"label":"2"},{"v":2.5,"label":"2.5"}], Dygraph.numericTicks(1, 3, 300, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":1,"label":"1"},{"v":1.5,"label":"1.5"},{"v":2,"label":"2"},{"v":2.5,"label":"2.5"},{"v":3,"label":"3"},{"v":3.5,"label":"3.5"}], Dygraph.numericTicks(1, 4, 300, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":1,"label":"1"},{"v":1.5,"label":"1.5"},{"v":2,"label":"2"},{"v":2.5,"label":"2.5"},{"v":3,"label":"3"},{"v":3.5,"label":"3.5"}], Dygraph.numericTicks(1, 4, 320, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":1,"label":"1"},{"v":1.5,"label":"1.5"},{"v":2,"label":"2"},{"v":2.5,"label":"2.5"},{"v":3,"label":"3"},{"v":3.5,"label":"3.5"},{"v":4,"label":"4"},{"v":4.5,"label":"4.5"}], Dygraph.numericTicks(1, 5, 320, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":1,"label":1},{"v":1.5,"label":1.5},{"v":2,"label":2},{"v":2.5,"label":2.5},{"v":3,"label":3},{"v":3.5,"label":3.5},{"v":4,"label":4},{"v":4.5,"label":4.5}], Dygraph.numericTicks(1, 5, 320, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":1,"label":"1"},{"v":2,"label":"2"},{"v":3,"label":"3"},{"v":4,"label":"4"},{"v":5,"label":"5"}], Dygraph.numericTicks(1, 6, 300, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":1,"label":"1"},{"v":2,"label":"2"},{"v":3,"label":"3"},{"v":4,"label":"4"},{"v":5,"label":"5"},{"v":6,"label":"6"}], Dygraph.numericTicks(1, 7, 320, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":1,"label":"1"},{"v":2,"label":"2"},{"v":3,"label":"3"},{"v":4,"label":"4"},{"v":5,"label":"5"},{"v":6,"label":"6"},{"v":7,"label":"7"},{"v":8,"label":"8"}], Dygraph.numericTicks(1, 9, 300, createOptionsViewForAxis('y',{"logscale":false,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":1,"label":"1"},{"v":2,"label":"2"},{"v":3,"label":"3"},{"v":4,"label":"4"},{"v":5,"label":"5"},{"v":6,"label":""},{"v":7,"label":"7"},{"v":8,"label":""},{"v":9,"label":"9"}], Dygraph.numericTicks(1, 9, 300, createOptionsViewForAxis('y',{"logscale":true,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":1,"label":"1"},{"v":2,"label":"2"},{"v":3,"label":"3"},{"v":4,"label":"4"},{"v":5,"label":"5"},{"v":6,"label":"6"},{"v":7,"label":"7"},{"v":8,"label":"8"}], Dygraph.numericTicks(1, 9, 320, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":0,"label":"0"},{"v":2,"label":"2"},{"v":4,"label":"4"},{"v":6,"label":"6"},{"v":8,"label":"8"},{"v":10,"label":"10"}], Dygraph.numericTicks(1.2, 10.8, 300, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":1,"label":"1"},{"v":1.5,"label":"1.5"},{"v":2,"label":"2"},{"v":2.5,"label":"2.5"},{"v":3,"label":"3"},{"v":3.5,"label":"3.5"},{"v":4,"label":"4"},{"v":4.5,"label":"4.5"}], Dygraph.numericTicks(1.2872947778969237, 4.765317192093838, 300, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":1,"label":"1"},{"v":2,"label":"2"},{"v":3,"label":"3"},{"v":4,"label":"4"},{"v":5,"label":"5"},{"v":6,"label":"6"},{"v":7,"label":"7"}], Dygraph.numericTicks(1.5, 7.5, 300, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":0,"label":"0"},{"v":5,"label":"5"},{"v":10,"label":"10"},{"v":15,"label":"15"},{"v":20,"label":"20"},{"v":25,"label":"25"}], Dygraph.numericTicks(1.7999999999999998, 28.2, 320, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":10,"label":"10"},{"v":10.1,"label":"10.1"},{"v":10.2,"label":"10.2"},{"v":10.3,"label":"10.3"},{"v":10.4,"label":"10.4"},{"v":10.5,"label":"10.5"},{"v":10.6,"label":"10.6"},{"v":10.7,"label":"10.7"},{"v":10.8,"label":"10.8"},{"v":10.9,"label":"10.9"}], Dygraph.numericTicks(10, 11, 480, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":100,"label":"100"},{"v":120,"label":"120"},{"v":140,"label":"140"},{"v":160,"label":"160"},{"v":180,"label":"180"}], Dygraph.numericTicks(100, 200, 200, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":10000,"label":"10000"},{"v":-17988000,"label":"12000"},{"v":-17986000,"label":"14000"},{"v":-17984000,"label":"16000"},{"v":-17982000,"label":"18000"},{"v":-17980000,"label":"20000"},{"v":-17978000,"label":"22000"},{"v":-17976000,"label":"24000"},{"v":-17974000,"label":"26000"},{"v":-17972000,"label":"28000"},{"v":-17970000,"label":"30000"},{"v":-17968000,"label":"32000"},{"v":-17966000,"label":"34000"},{"v":-17964000,"label":"36000"}], Dygraph.numericTicks(10122.8, 36789.2, 480, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":11000,"label":"11000"},{"v":11200,"label":"11200"},{"v":11400,"label":"11400"},{"v":11600,"label":"11600"},{"v":11800,"label":"11800"},{"v":-17988000,"label":"12000"},{"v":12200,"label":"12200"},{"v":12400,"label":"12400"},{"v":12600,"label":"12600"},{"v":12800,"label":"12800"},{"v":-17987000,"label":"13000"},{"v":13200,"label":"13200"},{"v":13400,"label":"13400"}], Dygraph.numericTicks(11110.5, 13579.5, 480, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":162000,"label":"162000"},{"v":-17836000,"label":"164000"},{"v":-17834000,"label":"166000"},{"v":-17832000,"label":"168000"},{"v":-17830000,"label":"170000"},{"v":-17828000,"label":"172000"},{"v":-17826000,"label":"174000"},{"v":-17824000,"label":"176000"},{"v":-17822000,"label":"178000"}], Dygraph.numericTicks(163038.4, 179137.6, 320, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":2,"label":"2"},{"v":2.5,"label":"2.5"},{"v":3,"label":"3"},{"v":3.5,"label":"3.5"}], Dygraph.numericTicks(2, 4, 300, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":2,"label":"2"},{"v":3,"label":"3"},{"v":4,"label":"4"},{"v":5,"label":"5"},{"v":6,"label":"6"},{"v":7,"label":"7"}], Dygraph.numericTicks(2.6, 7.4, 300, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":20,"label":"20"},{"v":30,"label":"30"},{"v":40,"label":"40"},{"v":50,"label":"50"},{"v":60,"label":"60"},{"v":70,"label":"70"},{"v":80,"label":"80"},{"v":90,"label":"90"}], Dygraph.numericTicks(21.7, 97.3, 300, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":20,"label":"20"},{"v":30,"label":"30"},{"v":40,"label":"40"},{"v":50,"label":"50"},{"v":60,"label":"60"},{"v":70,"label":"70"},{"v":80,"label":"80"},{"v":90,"label":"90"}], Dygraph.numericTicks(21.7, 97.3, 320, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":20,"label":"20"},{"v":30,"label":"30"},{"v":40,"label":"40"},{"v":50,"label":"50"},{"v":60,"label":"60"},{"v":70,"label":"70"},{"v":80,"label":"80"},{"v":90,"label":"90"}], Dygraph.numericTicks(24, 96, 320, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":0,"label":"0"}], Dygraph.numericTicks(26.185714285714287, 90.81428571428572, 20, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":20,"label":"20"},{"v":30,"label":"30"},{"v":40,"label":"40"},{"v":50,"label":"50"},{"v":60,"label":"60"},{"v":70,"label":"70"},{"v":80,"label":"80"},{"v":90,"label":"90"}], Dygraph.numericTicks(26.185714285714287, 90.81428571428572, 200, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false,pixelsPerLabel:20}))); assert.deepEqual([{"v":25,"label":"25"},{"v":30,"label":"30"},{"v":35,"label":"35"},{"v":40,"label":"40"},{"v":45,"label":"45"},{"v":50,"label":"50"},{"v":55,"label":"55"},{"v":60,"label":"60"},{"v":65,"label":"65"},{"v":70,"label":"70"},{"v":75,"label":"75"},{"v":80,"label":"80"},{"v":85,"label":"85"},{"v":90,"label":"90"}], Dygraph.numericTicks(26.185714285714287, 90.81428571428572, 300, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false,pixelsPerLabel:20}))); assert.deepEqual([{"v":20,"label":"20"},{"v":30,"label":"30"},{"v":40,"label":"40"},{"v":50,"label":"50"},{"v":60,"label":"60"},{"v":70,"label":"70"},{"v":80,"label":"80"},{"v":90,"label":"90"}], Dygraph.numericTicks(26.185714285714287, 90.81428571428572, 300, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":20,"label":"20"},{"v":30,"label":"30"},{"v":40,"label":"40"},{"v":50,"label":"50"},{"v":60,"label":"60"},{"v":70,"label":"70"},{"v":80,"label":"80"}], Dygraph.numericTicks(28.33333333333333, 88.33333333333334, 300, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":3,"label":"3"},{"v":3.5,"label":"3.5"},{"v":4,"label":"4"},{"v":4.5,"label":"4.5"}], Dygraph.numericTicks(3, 5, 300, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":3000,"label":"3K"},{"v":2500,"label":"2.5K"},{"v":-17998000,"label":"2K"},{"v":1500,"label":"1.5K"},{"v":-17999000,"label":"1K"},{"v":500,"label":"500"}], Dygraph.numericTicks(3000, 0, 320, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":true}))); assert.deepEqual([{"v":30,"label":"30"},{"v":40,"label":"40"},{"v":50,"label":"50"},{"v":60,"label":"60"},{"v":70,"label":"70"},{"v":80,"label":"80"}], Dygraph.numericTicks(33.11333333333334, 83.75333333333333, 300, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":30,"label":"30"},{"v":40,"label":"40"},{"v":50,"label":"50"},{"v":60,"label":"60"},{"v":70,"label":"70"},{"v":80,"label":"80"}], Dygraph.numericTicks(36.921241050119335, 88.32696897374701, 320, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":50,"label":""},{"v":60,"label":"60"},{"v":70,"label":""},{"v":80,"label":""},{"v":90,"label":""},{"v":100,"label":"100"},{"v":200,"label":""},{"v":300,"label":"300"},{"v":400,"label":""},{"v":500,"label":""},{"v":600,"label":"600"},{"v":700,"label":""},{"v":800,"label":""},{"v":900,"label":""},{"v":-17999000,"label":"1000"},{"v":-17998000,"label":""},{"v":-17997000,"label":"3000"},{"v":-17996000,"label":""},{"v":-17995000,"label":""},{"v":-17994000,"label":"6000"},{"v":-17993000,"label":""},{"v":-17992000,"label":""},{"v":-17991000,"label":""},{"v":-17990000,"label":"10000"}], Dygraph.numericTicks(41.220000000000084, 15576.828000000018, 400, createOptionsViewForAxis('y',{"logscale":true,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":40,"label":"40"},{"v":50,"label":"50"},{"v":60,"label":"60"},{"v":70,"label":"70"},{"v":80,"label":"80"},{"v":90,"label":"90"}], Dygraph.numericTicks(44.5, 98.5, 320, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":5,"label":"5"},{"v":6,"label":""},{"v":7,"label":""},{"v":8,"label":""},{"v":9,"label":""},{"v":10,"label":"10"},{"v":20,"label":"20"},{"v":30,"label":""},{"v":40,"label":""},{"v":50,"label":"50"},{"v":60,"label":""},{"v":70,"label":""},{"v":80,"label":""},{"v":90,"label":""},{"v":100,"label":"100"},{"v":200,"label":"200"},{"v":300,"label":""},{"v":400,"label":""},{"v":500,"label":"500"},{"v":600,"label":""},{"v":700,"label":""},{"v":800,"label":""},{"v":900,"label":""},{"v":-17999000,"label":"1000"}], Dygraph.numericTicks(5, 1099.5, 300, createOptionsViewForAxis('y',{"logscale":true,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":50,"label":"50"},{"v":55,"label":"55"},{"v":60,"label":"60"},{"v":65,"label":"65"},{"v":70,"label":"70"},{"v":75,"label":"75"},{"v":80,"label":"80"}], Dygraph.numericTicks(52.5, 82.5, 320, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":68,"label":"68"},{"v":70,"label":"70"},{"v":72,"label":"72"},{"v":74,"label":"74"},{"v":76,"label":"76"},{"v":78,"label":"78"},{"v":80,"label":"80"}], Dygraph.numericTicks(69, 81, 320, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":0,"label":"0"},{"v":-17980000,"label":"20K"},{"v":-17960000,"label":"40K"},{"v":-17940000,"label":"60K"},{"v":-17920000,"label":"80K"}], Dygraph.numericTicks(7921.099999999999, 81407.9, 240, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":true}))); assert.deepEqual([{"v":8,"label":"8"},{"v":10,"label":"10"},{"v":12,"label":"12"},{"v":14,"label":"14"},{"v":16,"label":"16"},{"v":18,"label":"18"},{"v":20,"label":"20"}], Dygraph.numericTicks(9, 21, 300, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":8,"label":"8"},{"v":10,"label":"10"},{"v":12,"label":"12"},{"v":14,"label":"14"},{"v":16,"label":"16"},{"v":18,"label":"18"},{"v":20,"label":"20"}], Dygraph.numericTicks(9, 21, 320, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":9,"label":"9"},{"v":10,"label":"10"},{"v":11,"label":"11"},{"v":12,"label":"12"},{"v":13,"label":"13"},{"v":14,"label":"14"},{"v":15,"label":"15"},{"v":16,"label":"16"},{"v":17,"label":"17"},{"v":18,"label":"18"}], Dygraph.numericTicks(9.2, 18.8, 320, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":80,"label":"80"},{"v":100,"label":"100"},{"v":120,"label":"120"},{"v":140,"label":"140"},{"v":160,"label":"160"},{"v":180,"label":"180"},{"v":200,"label":"200"}], Dygraph.numericTicks(90, 210, 320, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":true}))); assert.deepEqual([{"v":95,"label":"95"},{"v":96,"label":"96"},{"v":97,"label":"97"},{"v":98,"label":"98"},{"v":99,"label":"99"},{"v":100,"label":"100"},{"v":101,"label":"101"},{"v":102,"label":"102"},{"v":103,"label":"103"},{"v":104,"label":"104"}], Dygraph.numericTicks(95.71121718377088, 104.23150357995226, 320, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); assert.deepEqual([{"v":950,"label":"950"},{"v":-17999000,"label":"1000"},{"v":1050,"label":"1050"},{"v":1100,"label":"1100"},{"v":1150,"label":"1150"},{"v":1200,"label":"1200"}], Dygraph.numericTicks(980.1, 1218.9, 200, createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false}))); }); */ }); dygraphs-2.2.1/auto_tests/tests/parser.js010064400000000000000000000060521437353256000154650ustar00/** * @fileoverview Tests the way that dygraphs parses data. * * @author danvk@google.com (Dan Vanderkam) */ import Dygraph from '../../src/dygraph'; import * as utils from '../../src/dygraph-utils'; describe("parser", function() { cleanupAfterEach(); it('testDetectLineDelimiter', function() { var data = "X,Y\r" + "0,-1\r" + "1,0\r" + "2,1\r" + "3,0\r" ; assert.equal("\r", utils.detectLineDelimiter(data)); data = "X,Y\n" + "0,-1\n" + "1,0\n" + "2,1\n" + "3,0\n" ; assert.equal("\n", utils.detectLineDelimiter(data)); data = "X,Y\n\r" + "0,-1\n\r" + "1,0\n\r" + "2,1\n\r" + "3,0\n\r" ; assert.equal("\n\r", utils.detectLineDelimiter(data)); }); it('testParseDosNewlines', function() { var opts = { width: 480, height: 320 }; var data = "X,Y\r" + "0,-1\r" + "1,0\r" + "2,1\r" + "3,0\r" ; var g = new Dygraph('graph', data, opts); assert.equal(0, g.getValue(0, 0)); assert.equal(-1, g.getValue(0, 1)); assert.equal(1, g.getValue(1, 0)); assert.equal(0, g.getValue(1, 1)); assert.deepEqual(['X', 'Y'], g.getLabels()); }); it('should parse tab-delimited data', function() { var data = "X\tY\n" + "0\t-1\n" + "1\t0\n" + "2\t1\n" + "3\t0\n"; var g = new Dygraph('graph', data); assert.equal(0, g.getValue(0, 0)); assert.equal(-1, g.getValue(0, 1)); assert.equal(1, g.getValue(1, 0)); assert.equal(0, g.getValue(1, 1)); assert.deepEqual(['X', 'Y'], g.getLabels()); }); it('should parse fractions', function() { var data = "X,Y\n" + "0,1/4\n" + "1,2/4\n" + "2,3/4\n" + "3,4/4\n"; var g = new Dygraph('graph', data, {fractions:true}); assert.equal(0, g.getValue(0, 0)); assert.deepEqual([1, 4], g.getValue(0, 1)); assert.equal(1, g.getValue(1, 0)); assert.deepEqual([2, 4], g.getValue(1, 1)); assert.deepEqual(['X', 'Y'], g.getLabels()); }); it('should parse error bars', function() { var data = "X,Y\n" + "0,1,4\n" + "1,2,4\n" + "2,3,4\n" + "3,4,4\n"; var g = new Dygraph('graph', data, {errorBars:true}); assert.equal(0, g.getValue(0, 0)); assert.deepEqual([1, 4], g.getValue(0, 1)); assert.equal(1, g.getValue(1, 0)); assert.deepEqual([2, 4], g.getValue(1, 1)); assert.deepEqual(['X', 'Y'], g.getLabels()); }); it('should parse custom bars', function() { var data = "X,Y1,Y2\n" + "1,10;20;30,20;5;25\n" + "2,10;25;35,20;10;25\n"; var g = new Dygraph('graph', data, {customBars:true}); assert.equal(1, g.getValue(0, 0)); assert.deepEqual([10, 20, 30], g.getValue(0, 1)); assert.deepEqual([20, 5, 25], g.getValue(0, 2)); assert.equal(2, g.getValue(1, 0)); assert.deepEqual([10, 25, 35], g.getValue(1, 1)); assert.deepEqual([20, 10, 25], g.getValue(1, 2)); assert.deepEqual(['X', 'Y1', 'Y2'], g.getLabels()); }); /* it('should warn on unsorted input', function() { }); it('should warn on different length columns', function() { }); it('should detect double-labeled data', function() { }); */ }); dygraphs-2.2.1/auto_tests/tests/pathological_cases.js010064400000000000000000000112071437353256000200130ustar00/** * @fileoverview Tests zero and one-point charts. * These don't have to render nicely, they just have to not crash. * * @author dan@dygraphs.com (Dan Vanderkam) */ import Dygraph from '../../src/dygraph'; import Util from './Util'; describe("pathological-cases", function() { cleanupAfterEach(); var restoreConsole; var logs = {}; beforeEach(function() { restoreConsole = Util.captureConsole(logs); }); afterEach(function() { restoreConsole(); }); var graph = document.getElementById("graph"); it('testZeroPoint', function() { var opts = { width: 480, height: 320 }; var data = "X,Y\n"; var g = new Dygraph(graph, data, opts); }); it('testOnePoint', function() { var opts = { width: 480, height: 320 }; var data = "X,Y\n" + "1,2\n"; var g = new Dygraph(graph, data, opts); }); it('testCombinations', function() { var dataSets = { nil: null, issue597: [], empty: [[]], onePoint: [[10, 2]], nanPoint: [[10, NaN]], nanPoints: [[10, NaN], [20, NaN]], multiNan1: [[10, NaN, 2], [20, 3, NaN]], multiNan2: [[10, NaN, 2], [20, NaN, 4]], multiNan3: [[10, NaN, NaN], [20, 3, 4], [30, NaN, NaN]], atZero: [[0, 0]], atZero2: [[0, 0, 0]], negative: [[-10, -1]], acrossZero: [[-10, 1], [10, 2]], normal: [[0,1,9], [10,3,5], [20,2,7], [30,4,3]] }; var baseOpts = { lines: {}, stacked: { stackedGraph: true } }; var variantOpts = { none: {}, padded: { includeZero: true, drawAxesAtZero: true, xRangePad: 2, yRangePad: 4 } }; for (var baseName in baseOpts) { var base = baseOpts[baseName]; for (var variantName in variantOpts) { var variant = variantOpts[variantName]; var opts = { width: 300, height: 150, pointSize: 10 }; for (var key in base) { if (base.hasOwnProperty(key)) opts[key] = base[key]; } for (var key in variant) { if (variant.hasOwnProperty(key)) opts[key] = variant[key]; } var h = document.createElement('h3'); h.appendChild(document.createTextNode(baseName + ' ' + variantName)); graph.appendChild(h); for (var dataName in dataSets) { var data = dataSets[dataName]; var box = document.createElement('fieldset'); box.style.display = 'inline-block'; var legend = document.createElement('legend'); legend.appendChild(document.createTextNode(dataName)); box.appendChild(legend); var gdiv = document.createElement('div'); gdiv.style.display = 'inline-block'; box.appendChild(gdiv); graph.appendChild(box); var cols = data && data[0] ? data[0].length : (dataName == 'issue597') ? 1 : 0; opts.labels = ['X', 'A', 'B', 'C'].slice(0, cols); var g = new Dygraph(gdiv, data, opts); if (dataName == 'empty') { assert.deepEqual(logs, { log: [], warn: [], error: ["Data set cannot contain an empty row"] }); logs.error = []; // reset } else if (dataName == 'nil') { assert.deepEqual(logs, { log: [], warn: [], error: ["Unknown data format: null"] }); logs.error = []; // reset } else { assert.deepEqual(logs, {log: [], warn: [], error: []}); } } } } }); it('testNullLegend', function() { var opts = { width: 480, height: 320, labelsDiv: null }; var data = "X,Y\n" + "1,2\n"; var g = new Dygraph(graph, data, opts); }); it('testDivAsString', function() { var data = "X,Y\n" + "1,2\n"; var g = new Dygraph('graph', data, {}); }); it('testConstantSeriesNegative', function() { var data = "X,Y\n" + "1,-1\n" + "2,-1\n"; var g = new Dygraph('graph', data, {}); // This check could be loosened to // g.yAxisRange()[0] < g.yAxisRange()[1] if it breaks in the future. assert.deepEqual([-1.1, -0.9], g.yAxisRange()); }); it('testConstantSeriesNegativeIncludeZero', function() { var data = "X,Y\n" + "1,-1\n" + "2,-1\n"; var g = new Dygraph('graph', data, {includeZero: true}); // This check could be loosened to // g.yAxisRange()[0] < g.yAxisRange()[1] if it breaks in the future. assert.deepEqual([-1.1, 0], g.yAxisRange()); }); it('should throw with non-existent divs', function() { var data = "X,Y\n" + "1,-1\n" + "2,1\n"; assert.throws(function() { new Dygraph(null, data); }, /non-existent div/); assert.throws(function() { new Dygraph('non-existent-div-id', data); }, /non-existent div/); }); }); dygraphs-2.2.1/auto_tests/tests/per_axis.js010064400000000000000000000037401437353256000160040ustar00/** * @fileoverview Tests for per-axis options. * * @author konigsberg@google.com (Robert Konigsberg) */ import Dygraph from '../../src/dygraph'; import * as utils from '../../src/dygraph-utils'; import Util from './Util'; import CanvasAssertions from './CanvasAssertions'; import Proxy from './Proxy'; describe("per-axis", function() { cleanupAfterEach(); useProxyCanvas(utils, Proxy); var xAxisLineColor = "#00ffff"; var yAxisLineColor = "#ffff00"; var g, graph; beforeEach(function() { var opts = { axes : { x : { drawAxis : false, drawGrid : false, gridLineColor : xAxisLineColor }, y : { drawAxis : false, drawGrid : false, gridLineColor : yAxisLineColor } }, colors: [ '#ff0000', '#0000ff' ] }; var data = "X,Y,Z\n" + "1,1,0\n" + "8,0,1\n" ; graph = document.getElementById('graph'); g = new Dygraph(graph, data, opts); }); it('testDrawXAxis', function() { g.updateOptions({ axes : { x : { drawAxis: true }} }); assert.isTrue(graph.getElementsByClassName('dygraph-axis-label-x').length > 0); assert.isTrue(graph.getElementsByClassName('dygraph-axis-label-y').length == 0); }); it('testDrawYAxis', function() { g.updateOptions({ axes : { y : { drawAxis: true }} }); assert.isTrue(graph.getElementsByClassName('dygraph-axis-label-x').length ==0); assert.isTrue(graph.getElementsByClassName('dygraph-axis-label-y').length > 0); }); it('testDrawXGrid', function() { g.updateOptions({ axes : { x : { drawGrid : true }}}); var htx = g.hidden_ctx_; assert.isTrue(CanvasAssertions.numLinesDrawn(htx, xAxisLineColor) > 0); assert.isTrue(CanvasAssertions.numLinesDrawn(htx, yAxisLineColor) == 0); }); it('testDrawYGrid', function() { g.updateOptions({ axes : { y : { drawGrid : true }}}); var htx = g.hidden_ctx_; assert.isTrue(CanvasAssertions.numLinesDrawn(htx, xAxisLineColor) == 0); assert.isTrue(CanvasAssertions.numLinesDrawn(htx, yAxisLineColor) > 0); }); }); dygraphs-2.2.1/auto_tests/tests/per_series.js010064400000000000000000000110341437353256000163250ustar00/** * @fileoverview Tests for per-series options. * * @author danvk@google.com (Dan Vanderkam) */ import Dygraph from '../../src/dygraph'; import PixelSampler from './PixelSampler'; describe("per-series", function() { cleanupAfterEach(); it('testPerSeriesFill', function() { var opts = { width: 480, height: 320, axes : { x : { drawGrid: false, drawAxis: false, }, y : { drawGrid: false, drawAxis: false, } }, series: { Y: { fillGraph: true }, }, colors: [ '#FF0000', '#0000FF' ], fillAlpha: 0.15 }; var data = "X,Y,Z\n" + "1,0,0\n" + "2,0,1\n" + "3,0,1\n" + "4,0,0\n" + "5,0,0\n" + "6,1,0\n" + "7,1,0\n" + "8,0,0\n" ; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); var sampler = new PixelSampler(g); // Inside of the "Z" bump -- no fill. assert.deepEqual([0,0,0,0], sampler.colorAtCoordinate(2.5, 0.5)); // Inside of the "Y" bump -- filled in. assert.deepEqual([255,0,0,38], sampler.colorAtCoordinate(6.5, 0.5)); }); it('testPerSeriesAlpha', function() { var opts = { width: 480, height: 320, axes : { x : { drawGrid: false, drawAxis: false, }, y : { drawGrid: false, drawAxis: false, } }, series: { Y: { fillGraph: true, fillAlpha: 0.25 }, Z: { fillGraph: true, fillAlpha: 0.75 } }, colors: [ '#FF0000', '#0000FF' ] }; var data = "X,Y,Z\n" + "1,0,0\n" + "2,0,1\n" + "3,0,1\n" + "4,0,0\n" + "5,0,0\n" + "6,1,0\n" + "7,1,0\n" + "8,0,0\n" ; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); var sampler = new PixelSampler(g); // Inside of the "Y" bump -- 5% alpha. assert.deepEqual([255,0,0,63], sampler.colorAtCoordinate(6.5, 0.5)); // Inside of the "Z" bump -- 95% alpha. assert.deepEqual([0,0,255,191], sampler.colorAtCoordinate(2.5, 0.5)); }); it('testNewStyleSeries', function() { var opts = { pointSize : 5, series : { Y: { pointSize : 4 } }, }; var graph = document.getElementById("graph"); var data = "X,Y,Z\n1,0,0\n"; var g = new Dygraph(graph, data, opts); assert.equal(5, g.getOption("pointSize")); assert.equal(4, g.getOption("pointSize", "Y")); assert.equal(5, g.getOption("pointSize", "Z")); }); // TODO(konigsberg): move to multiple_axes.js it('testAxisInNewSeries', function() { var opts = { series : { D : { axis : 'y2' }, C : { axis : 1 }, B : { axis : 0 }, E : { axis : 'y' } } }; var graph = document.getElementById("graph"); var data = "X,A,B,C,D,E\n0,1,2,3,4,5\n"; var g = new Dygraph(graph, data, opts); assert.deepEqual(["A", "B", "E"], g.attributes_.seriesForAxis(0)); assert.deepEqual(["C", "D"], g.attributes_.seriesForAxis(1)); }); // TODO(konigsberg): move to multiple_axes.js it('testAxisInNewSeries_withAxes', function() { var opts = { series : { D : { axis : 'y2' }, C : { axis : 1 }, B : { axis : 0 }, E : { axis : 'y' } }, axes : { y : { pointSize : 7 }, y2 : { pointSize : 6 } } }; var graph = document.getElementById("graph"); var data = "X,A,B,C,D,E\n0,1,2,3,4,5\n"; var g = new Dygraph(graph, data, opts); assert.deepEqual(["A", "B", "E"], g.attributes_.seriesForAxis(0)); assert.deepEqual(["C", "D"], g.attributes_.seriesForAxis(1)); assert.equal(1.5, g.getOption("pointSize")); assert.equal(7, g.getOption("pointSize", "A")); assert.equal(7, g.getOption("pointSize", "B")); assert.equal(6, g.getOption("pointSize", "C")); assert.equal(6, g.getOption("pointSize", "D")); assert.equal(7, g.getOption("pointSize", "E")); }); // TODO(konigsberg): move to multiple_axes.js it('testOldAxisSpecInNewSeriesThrows', function() { var opts = { series : { D : { axis : {} }, }, }; var graph = document.getElementById("graph"); var data = "X,A,B,C,D,E\n0,1,2,3,4,5\n"; var threw = false; try { new Dygraph(graph, data, opts); } catch(e) { threw = true; } assert.isTrue(threw); }); it('testColorOption', function() { var graph = document.getElementById("graph"); var data = "X,A,B,C\n0,1,2,3\n"; var g = new Dygraph(graph, data, {}); assert.deepEqual(['rgb(64,128,0)', 'rgb(64,0,128)', 'rgb(0,128,128)'], g.getColors()); g.updateOptions({series : { B : { color : 'purple' }}}); assert.deepEqual(['rgb(64,128,0)', 'purple', 'rgb(0,128,128)'], g.getColors()); }); }); dygraphs-2.2.1/auto_tests/tests/plugins.js010064400000000000000000000123301437353256000156460ustar00/** * @fileoverview Tests for the plugins option. * * @author konigsberg@google.com (Robert Konigsberg) */ import Dygraph from '../../src/dygraph'; import DygraphOps from './DygraphOps'; describe("plugins", function() { cleanupAfterEach(); var data; beforeEach(function() { data = "X,Y1,Y2\n" + "0,1,2\n" + "1,2,1\n" + "2,1,2\n" + "3,2,1\n" ; }); it('testWillDrawChart', function() { var draw = 0; var plugin = (function() { var p = function() { }; p.prototype.activate = function(g) { return { willDrawChart: this.willDrawChart }; }; p.prototype.willDrawChart = function(e) { draw++; }; return p; })(); var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, {plugins: [plugin]}); assert.equal(1, draw); }); it('testPassingInstance', function() { // You can also pass an instance of a plugin instead of a Plugin class. var draw = 0; var p = { activate: function(g) { return { willDrawChart: this.willDrawChart } }, willDrawChart: function(g) { draw++; } }; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, {plugins: [p]}); assert.equal(1, draw); }); it('testPreventDefault', function() { var data1 = "X,Y\n" + "20,-1\n" + "21,0\n" + "22,1\n" + "23,0\n"; var events = []; var p = { pointClickPreventDefault: false, clickPreventDefault: false, activate: function(g) { return { pointClick: this.pointClick, click: this.click } }, pointClick: function(e) { events.push(['plugin.pointClick', e.point.xval, e.point.yval]); if (this.pointClickPreventDefault) { e.preventDefault(); } }, click: function(e) { events.push(['plugin.click', e.xval]); if (this.clickPreventDefault) { e.preventDefault(); } } }; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data1, { plugins: [p], clickCallback: function(e, x) { events.push(['clickCallback', x]); }, pointClickCallback: function(e, pt) { events.push(['pointClickCallback', pt.xval, pt.yval]); } }); // Click the point at x=20 function clickOnPoint() { var x = 58, y = 275; DygraphOps.dispatchMouseDown_Point(g, x, y); DygraphOps.dispatchMouseMove_Point(g, x, y); DygraphOps.dispatchMouseUp_Point(g, x, y); } p.pointClickPreventDefault = false; p.clickPreventDefault = false; clickOnPoint(); assert.deepEqual([ ['plugin.pointClick', 20, -1], ['pointClickCallback', 20, -1], ['plugin.click', 20], ['clickCallback', 20] ], events); events = []; p.pointClickPreventDefault = true; p.clickPreventDefault = false; clickOnPoint(); assert.deepEqual([ ['plugin.pointClick', 20, -1] ], events); events = []; p.pointClickPreventDefault = false; p.clickPreventDefault = true; clickOnPoint(); assert.deepEqual([ ['plugin.pointClick', 20, -1], ['pointClickCallback', 20, -1], ['plugin.click', 20] ], events); }); it('testEventSequence', function() { var events = []; var eventLogger = function(name) { return function(e) { events.push(name); }; }; var p = { activate: function(g) { return { clearChart: eventLogger('clearChart'), predraw: eventLogger('predraw'), willDrawChart: eventLogger('willDrawChart'), didDrawChart: eventLogger('didDrawChart'), dataWillUpdate: eventLogger('dataWillUpdate'), dataDidUpdate: eventLogger('dataDidUpdate') } } }; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, {plugins: [p]}); // Initial draw sequence assert.deepEqual([ "dataDidUpdate", // should dataWillUpdate be called here, too? "predraw", "clearChart", "willDrawChart", "didDrawChart" ], events); // An options change triggers a redraw, but doesn't change the data. events = []; g.updateOptions({series: {Y1: {color: 'blue'}}}); assert.deepEqual([ "predraw", "clearChart", "willDrawChart", "didDrawChart" ], events); // A pan shouldn't cause a new "predraw" events = []; DygraphOps.dispatchMouseDown_Point(g, 100, 100, {shiftKey: true}); DygraphOps.dispatchMouseMove_Point(g, 200, 100, {shiftKey: true}); DygraphOps.dispatchMouseUp_Point(g, 200, 100, {shiftKey: true}); assert.deepEqual([ "clearChart", "willDrawChart", "didDrawChart" ], events); // New data triggers the full sequence. events = []; g.updateOptions({file: data + '\n4,1,2'}); assert.deepEqual([ "dataWillUpdate", "dataDidUpdate", "predraw", "clearChart", "willDrawChart", "didDrawChart" ], events); }); it('testDestroyCalledInOrder', function() { var destructions = []; var makePlugin = function(name) { return { activate: function(g) { return {} }, destroy: function() { destructions.push(name); } }; }; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, { plugins: [makePlugin('p'), makePlugin('q')] }); assert.deepEqual([], destructions); g.destroy(); assert.deepEqual(['q', 'p'], destructions); }); }); dygraphs-2.2.1/auto_tests/tests/plugins_legend.js010064400000000000000000000122321437353256000171650ustar00import Dygraph from '../../src/dygraph'; import LegendPlugin from '../../src/plugins/legend'; import Util from './Util'; describe("plugins-legend", function() { var graph; cleanupAfterEach(); beforeEach(function() { var testDiv = document.getElementById('graph'); testDiv.innerHTML = "
"; graph = document.getElementById('inner-graph'); }); it('testLegendEscape', function() { var opts = { width: 480, height: 320 }; var data = "X,\n" + "0,-1\n" + "1,0\n" + "2,1\n" + "3,0\n" ; var g = new Dygraph(graph, data, opts); var legendPlugin = new LegendPlugin(); legendPlugin.activate(g); var e = { selectedX: 'selectedX', selectedPoints: [{ canvasy: 100, name: "", yval: 10, }], dygraph: g } legendPlugin.select(e); var legendSpan = legendPlugin.legend_div_.querySelector("span b span"); assert.equal(legendSpan.innerHTML, "<script>alert('XSS')</script>"); }); it('should let labelsDiv be a string', function() { var labelsDiv = document.getElementById('label'); var g = new Dygraph(graph, 'X,Y\n1,2\n', {labelsDiv: 'label'}); null g.setSelection(0); assert.equal('1: Y: 2', Util.nbspToSpace(labelsDiv.textContent)); }); it('should let labelsDiv be an Element', function() { var labelsDiv = document.getElementById('label'); var g = new Dygraph(graph, 'X,Y\n1,2\n', { labelsDiv: labelsDiv }); assert.isNull(labelsDiv.getAttribute('class')); // dygraph-legend not added. g.setSelection(0); assert.equal('1: Y: 2', Util.nbspToSpace(labelsDiv.textContent)); }); it('should render dashed patterns', function() { var g = new Dygraph(graph, 'X,Y\n1,2\n', { strokePattern: [5, 5], color: 'red', legend: 'always' }); // The legend has a dashed line and a label. var legendEl = document.querySelector('.dygraph-legend > span'); assert.equal(' Y', legendEl.textContent); var dashEl = document.querySelector('.dygraph-legend > span > div'); assert.equal(window.getComputedStyle(dashEl)['border-bottom-color'], 'rgb(255, 0, 0)'); }); it('should use a legendFormatter', function() { var calls = []; var g = new Dygraph(graph, 'X,Y\n1,2\n', { color: 'red', legend: 'always', legendFormatter: function(data) { calls.push(data); // Note: can't check against `g` because it's not defined yet. assert(this.toString().indexOf('Dygraph') >= 0); return ''; } }); assert(calls.length == 1); // legend for no selected points g.setSelection(0); assert(calls.length == 2); // legend with selected points g.clearSelection(); assert(calls.length == 3); assert.equal(calls[0].x, undefined); assert.equal(calls[1].x, 1); assert.equal(calls[1].xHTML, '1'); assert.equal(calls[2].x, undefined); assert.equal(calls[0].series.length, 1); assert.equal(calls[1].series.length, 1); assert.equal(calls[2].series.length, 1); assert.equal(calls[0].series[0].y, undefined); assert.equal(calls[1].series[0].label, 'Y'); assert.equal(calls[1].series[0].labelHTML, 'Y'); assert.equal(calls[1].series[0].y, 2); assert.equal(calls[1].series[0].yHTML, '2'); assert.equal(calls[1].series[0].isVisible, true); assert.equal(calls[2].series[0].y, undefined); }); it('should use a legendFormatter which returns a DocumentFragment', function() { var calls = []; var labelsDiv = document.getElementById('label'); var g = new Dygraph(graph, 'X,Y\n1,2\n', { color: 'red', legend: 'always', labelsDiv: labelsDiv, legendFormatter: function(data) { var fragment = document.createDocumentFragment(); var e = document.createElement('div'); e.innerText='Text Label'; fragment.appendChild(e); calls.push(data); return fragment; } }); assert(calls.length == 1); // legend for no selected points //check that labelsDiv has fragment children attached assert.equal(labelsDiv.children.length, 1); assert.equal(labelsDiv.children[0].nodeName, 'DIV'); assert.equal(labelsDiv.children[0].innerText, 'Text Label'); }); it('should work with highlight series', () => { var calls = []; var g = new Dygraph(graph, 'X,y1,y2\n1,2,3\n', { highlightSeriesOpts: { strokeWidth: 3, } }); g.setSelection(false, 'y2'); assert.equal(Util.getLegend(graph), ''); }); it('should include point drawn where canvas-y is 0', function () { var graph = document.getElementById("graph"); var calls = [] function callback(data) { calls.push(data); }; var g = new Dygraph(document.getElementById("graph"), "X,Y\n" + "1,5\n" + "1,10\n" + "1,12\n", { legendFormatter: callback, axes: { y: { valueRange: [0, 10] } } }); g.setSelection(1); var data = calls[1]; assert.isTrue(data.series[0].isVisible); assert.notEqual(data.series[0].yHTML, ''); }); }); dygraphs-2.2.1/auto_tests/tests/range_selector.js010064400000000000000000000447101437353256000171700ustar00// Copyright 2011 Google Inc. All Rights Reserved. /** * @fileoverview Regression tests for range selector. * @author paul.eric.felix@gmail.com (Paul Felix) * @license MIT */ import Dygraph from '../../src/dygraph'; import * as utils from '../../src/dygraph-utils'; import RangeSelectorPlugin from '../../src/plugins/range-selector'; import Util from './Util'; import DygraphOps from './DygraphOps'; import CanvasAssertions from './CanvasAssertions'; import Proxy from './Proxy'; describe("range-selector", function() { cleanupAfterEach(); var restoreConsole; var logs = {}; beforeEach(function() { restoreConsole = Util.captureConsole(logs); }); afterEach(function() { restoreConsole(); }); it('testRangeSelector', function() { var opts = { width: 480, height: 320, showRangeSelector: true, labels: ['X', 'Y'] }; var data = [ [1, 10], [2, 15], [3, 10], [4, 15], [5, 10], [6, 15], [7, 10], [8, 15], [9, 10] ]; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); assertGraphExistence(g, graph); }); it('testRangeSelectorWithErrorBars', function() { var opts = { width: 480, height: 320, errorBars: true, showRangeSelector: true, labels: ['X', 'Y'] }; var data = [ [1, [10, 10]], [2, [15, 10]], [3, [10, 10]], [4, [15, 10]], [5, [10, 10]], [6, [15, 20]], [7, [10, 20]], [8, [15, 20]], [9, [10, 20]] ]; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); assertGraphExistence(g, graph); }); it('testRangeSelectorWithCustomBars', function() { var opts = { width: 480, height: 320, customBars: true, showRangeSelector: true, labels: ['X', 'Y'] }; var data = [ [1, [10, 10, 100]], [2, [15, 20, 110]], [3, [10, 30, 100]], [4, [15, 40, 110]], [5, [10, 120, 100]], [6, [15, 50, 110]], [7, [10, 70, 100]], [8, [15, 90, 110]], [9, [10, 50, 100]] ]; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); assertGraphExistence(g, graph); }); it('testRangeSelectorWithLogScale', function() { var opts = { width: 480, height: 320, logscale: true, showRangeSelector: true, labels: ['X', 'Y'] }; var data = [ [1, 10], [2, 15], [3, 10], [4, 15], [5, 10], [6, 15], [7, 10], [8, 15], [9, 10] ]; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); assertGraphExistence(g, graph); }); it('testRangeSelectorOptions', function() { var opts = { width: 480, height: 320, showRangeSelector: true, rangeSelectorHeight: 30, rangeSelectorPlotFillColor: 'lightyellow', rangeSelectorPlotFillGradientColor: 'rgba(200, 200, 42, 10)', labels: ['X', 'Y'] }; var data = [ [1, 10], [2, 15], [3, 10], [4, 15], [5, 10], [6, 15], [7, 10], [8, 15], [9, 10] ]; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); assertGraphExistence(g, graph); }); it('testAdditionalRangeSelectorOptions', function() { var opts = { width: 480, height: 320, showRangeSelector: true, rangeSelectorHeight: 30, rangeSelectorBackgroundStrokeColor: 'blue', rangeSelectorBackgroundLineWidth: 3, rangeSelectorPlotLineWidth: 0.5, rangeSelectorForegroundStrokeColor: 'red', rangeSelectorForegroundLineWidth: 2, rangeSelectorAlpha: 0.8, labels: ['X', 'Y'] }; var data = [ [1, 10], [2, 15], [3, 10], [4, 15], [5, 10], [6, 15], [7, 10], [8, 15], [9, 10] ]; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); assertGraphExistence(g, graph); }); it('testRangeSelectorEnablingAfterCreation', function() { var opts = { width: 480, height: 320, labels: ['X', 'Y'] }; var data = [ [1, 10], [2, 15], [3, 10], [4, 15], [5, 10], [6, 15], [7, 10], [8, 15], [9, 10] ]; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); var initialChartHeight = g.getArea().h; g.updateOptions({showRangeSelector: true}); assertGraphExistence(g, graph); assert(g.getArea().h < initialChartHeight); // range selector shown g.updateOptions({showRangeSelector: false}); assert.equal(g.getArea().h, initialChartHeight); // range selector hidden }); // The animatedZooms option does not work with the range selector. Make sure it gets turned off. it('testRangeSelectorWithAnimatedZoomsOption', function() { var opts = { width: 480, height: 320, showRangeSelector: true, animatedZooms: true, labels: ['X', 'Y'] }; var data = [ [1, 10], [2, 15], [3, 10], [4, 15], [5, 10], [6, 15], [7, 10], [8, 15], [9, 10] ]; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); assertGraphExistence(g, graph); assert.isFalse(g.getOption('animatedZooms')); assert.deepEqual(logs, { log: [], error: [], warn: ["Animated zooms and range selector are not compatible; disabling animatedZooms."] }); }); it('testRangeSelectorWithAnimatedZoomsOption2', function() { var opts = { width: 480, height: 320, animatedZooms: true, labels: ['X', 'Y'] }; var data = [ [1, 10], [2, 15], [3, 10], [4, 15], [5, 10], [6, 15], [7, 10], [8, 15], [9, 10] ]; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); g.updateOptions({showRangeSelector: true}); assertGraphExistence(g, graph); assert.isFalse(g.getOption('animatedZooms')); assert.deepEqual(logs, { log: [], error: [], warn: ["Animated zooms and range selector are not compatible; disabling animatedZooms."] }); }); it('testRangeSelectorInteraction', function() { var opts = { width: 480, height: 320, showRangeSelector: true, labels: ['X', 'Y'] }; var data = [ [1, 10], [2, 15], [3, 10], [4, 15], [5, 10], [6, 15], [7, 10], [8, 15], [9, 10] ]; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); assertGraphExistence(g, graph); var zoomhandles = graph.getElementsByClassName('dygraph-rangesel-zoomhandle'); // Move left zoomhandle in var xRange = g.xAxisRange().slice(); var mouseDownEvent = DygraphOps.createEvent({ type : 'dragstart', detail: 1, clientX : 0, clientY : 0 }); zoomhandles[0].dispatchEvent(mouseDownEvent); var mouseMoveEvent = DygraphOps.createEvent({ type : 'mousemove', clientX : 20, clientY : 20 }); zoomhandles[0].dispatchEvent(mouseMoveEvent); var mouseUpEvent = DygraphOps.createEvent({ type : 'mouseup', detail: 1, clientX : 20, clientY : 20 }); zoomhandles[0].dispatchEvent(mouseUpEvent); var newXRange = g.xAxisRange().slice(); assert(newXRange[0] > xRange[0], 'left zoomhandle should have moved: '+newXRange[0]+'>'+xRange[0]); assert.equal(xRange[1], newXRange[1], 'right zoomhandle should not have moved'); // Move right zoomhandle in xRange = newXRange; mouseDownEvent = DygraphOps.createEvent({ type : 'dragstart', detail: 1, clientX : 100, clientY : 100 }); zoomhandles[1].dispatchEvent(mouseDownEvent); mouseMoveEvent = DygraphOps.createEvent({ type : 'mousemove', clientX : 80, clientY : 80 }); zoomhandles[1].dispatchEvent(mouseMoveEvent); mouseUpEvent = DygraphOps.createEvent({ type : 'mouseup', detail: 1, clientX : 80, clientY : 80 }); zoomhandles[1].dispatchEvent(mouseUpEvent); var newXRange = g.xAxisRange().slice(); assert(newXRange[1] < xRange[1], 'right zoomhandle should have moved: '+newXRange[1]+'<'+xRange[1]); assert.equal(xRange[0], newXRange[0], 'left zoomhandle should not have moved'); // Pan left xRange = newXRange; var fgcanvas = graph.getElementsByClassName('dygraph-rangesel-fgcanvas')[0]; var x = parseInt(zoomhandles[0].style.left) + 20; var y = parseInt(zoomhandles[0].style.top); mouseDownEvent = DygraphOps.createEvent({ type : 'mousedown', detail: 1, clientX : x, clientY : y }); fgcanvas.dispatchEvent(mouseDownEvent); x -= 10; mouseMoveEvent = DygraphOps.createEvent({ type : 'mousemove', clientX : x, clientY : y }); fgcanvas.dispatchEvent(mouseMoveEvent); mouseUpEvent = DygraphOps.createEvent({ type : 'mouseup', detail: 1, clientX : x, clientY : y }); fgcanvas.dispatchEvent(mouseUpEvent); var newXRange = g.xAxisRange().slice(); assert(newXRange[0] < xRange[0], newXRange[0]+'<'+xRange[0]); assert(newXRange[1] < xRange[1], newXRange[1]+'<'+xRange[1]); }); it('testRangeSelectorPositionIfXAxisNotDrawn', function() { var opts = { width: 480, height: 100, xAxisHeight: 30, axes : { x : { drawAxis: false }}, showRangeSelector: true, rangeSelectorHeight: 30, labels: ['X', 'Y'] }; var data = [ [0, 1], [10, 1] ]; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); //assert, that the range selector is at top position 70 since the 30px of the // xAxis shouldn't be reserved since it isn't drawn. assertGraphExistence(g, graph); var bgcanvas = graph.getElementsByClassName('dygraph-rangesel-bgcanvas')[0]; assert.equal("70px", bgcanvas.style.top, "Range selector is not at the expected position."); var fgcanvas = graph.getElementsByClassName('dygraph-rangesel-fgcanvas')[0]; assert.equal("70px", fgcanvas.style.top, "Range selector is not at the expected position."); }); it('testMiniPlotDrawn', function() { // Install Proxy to track canvas calls. var origFunc = utils.getContext; var miniHtx; utils.getContext = function(canvas) { if (canvas.className != 'dygraph-rangesel-bgcanvas') { return origFunc(canvas); } miniHtx = new Proxy(origFunc(canvas)); return miniHtx; }; var opts = { width: 480, height: 100, xAxisHeight: 30, axes : { x : { drawAxis: false }}, showRangeSelector: true, rangeSelectorHeight: 30, rangeSelectorPlotStrokeColor: '#ff0000', labels: ['X', 'Y'] }; var data = [ [0, 1], [5, 4], [10, 8] ]; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); // TODO(danvk): more precise tests. assert.isNotNull(miniHtx); assert.isTrue(0 < CanvasAssertions.numLinesDrawn(miniHtx, '#ff0000')); utils.getContext = origFunc; }); // Tests data computation for the mini plot with a single series. it('testSingleCombinedSeries', function() { var opts = { showRangeSelector: true, labels: ['X', 'Y1'] }; var data = [ [0, 1], [5, 4], [10, 8] ]; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); var rangeSelector = g.getPluginInstance_(RangeSelectorPlugin); assert.isNotNull(rangeSelector); var combinedSeries = rangeSelector.computeCombinedSeriesAndLimits_(); assert.deepEqual({ yMin: 1 - 7 * 0.25, // 25% padding yMax: 8 + 7 * 0.25, data: [ [0, 1], [5, 4], [10, 8] ] }, combinedSeries); }); // Tests that multiple series are averaged for the miniplot. it('testCombinedSeries', function() { var opts = { showRangeSelector: true, labels: ['X', 'Y1', 'Y2'] }; var data = [ [0, 1, 3], // average = 2 [5, 4, 6], // average = 5 [10, 7, 9] // average = 8 ]; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); var rangeSelector = g.getPluginInstance_(RangeSelectorPlugin); assert.isNotNull(rangeSelector); var combinedSeries = rangeSelector.computeCombinedSeriesAndLimits_(); assert.deepEqual({ yMin: 2 - 6 * 0.25, // 25% padding on combined series range. yMax: 8 + 6 * 0.25, data: [ [0, 2], [5, 5], [10, 8] ] }, combinedSeries); }); // Tests selection of a specific series to average for the mini plot. it('testSelectedCombinedSeries', function() { var opts = { showRangeSelector: true, labels: ['X', 'Y1', 'Y2', 'Y3', 'Y4'], series: { 'Y1': { showInRangeSelector: true }, 'Y3': { showInRangeSelector: true } } }; var data = [ [0, 5, 8, 13, 21], // average (first and third) = 9 [5, 1, 3, 7, 14], // average (first and third) = 4 [10, 0, 19, 10, 6] // average (first and third) = 5 ]; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); var rangeSelector = g.getPluginInstance_(RangeSelectorPlugin); assert.isNotNull(rangeSelector); var combinedSeries = rangeSelector.computeCombinedSeriesAndLimits_(); assert.deepEqual({ yMin: 4 - 5 * 0.25, // 25% padding on combined series range. yMax: 9 + 5 * 0.25, data: [ [0, 9], [5, 4], [10, 5] ] }, combinedSeries); }); // Tests data computation for the mini plot with a single error bar series. it('testSingleCombinedSeriesCustomBars', function() { var opts = { customBars: true, showRangeSelector: true, labels: ['X', 'Y1'] }; var data = [ [0, [0, 1, 2]], // [low, value, high] [5, [1, 4, 5]], [10, [7, 8, 9]] ]; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); var rangeSelector = g.getPluginInstance_(RangeSelectorPlugin); assert.isNotNull(rangeSelector); var combinedSeries = rangeSelector.computeCombinedSeriesAndLimits_(); assert.deepEqual({ yMin: 1 - 7 * 0.25, // 25% padding yMax: 8 + 7 * 0.25, data: [ [0, 1], [5, 4], [10, 8] ] }, combinedSeries); }); it('testSingleCombinedSeriesErrorBars', function() { var opts = { errorBars: true, showRangeSelector: true, labels: ['X', 'Y1'] }; var data = [ [0, [1, 1]], // [value, standard deviation] [5, [4, 2]], [10, [8, 1]] ]; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); var rangeSelector = g.getPluginInstance_(RangeSelectorPlugin); assert.isNotNull(rangeSelector); var combinedSeries = rangeSelector.computeCombinedSeriesAndLimits_(); assert.deepEqual({ yMin: 1 - 7 * 0.25, // 25% padding yMax: 8 + 7 * 0.25, data: [ [0, 1], [5, 4], [10, 8] ] }, combinedSeries); }); // Tests data computation for the mini plot with two custom bar series. it('testTwoCombinedSeriesCustomBars', function() { var opts = { customBars: true, showRangeSelector: true, labels: ['X', 'Y1', 'Y2'] }; var data = [ [0, [0, 1, 2], [4, 5, 6]], // [low, value, high], avg_val = 3 [5, [1, 4, 5], [5, 8, 9]], // avg_val = 6 [10, [7, 8, 9], [11, 12, 13]] // avg_val = 10 ]; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); var rangeSelector = g.getPluginInstance_(RangeSelectorPlugin); assert.isNotNull(rangeSelector); var combinedSeries = rangeSelector.computeCombinedSeriesAndLimits_(); assert.deepEqual({ yMin: 3 - 7 * 0.25, // 25% padding yMax: 10 + 7 * 0.25, data: [ [0, 3], [5, 6], [10, 10] ] }, combinedSeries); }); it('testHiddenSeriesExcludedFromMiniplot', function() { var opts = { showRangeSelector: true, labels: ['X', 'Y1', 'Y2'], visibility: [true, false] }; var data = [ [0, 1, 3], // average = 2 [5, 4, 6], // average = 5 [10, 7, 9] // average = 8 ]; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); var rangeSelector = g.getPluginInstance_(RangeSelectorPlugin); assert.isNotNull(rangeSelector); // Invisible series (e.g. Y2) are not included in the combined series. var combinedSeries = rangeSelector.computeCombinedSeriesAndLimits_(); assert.deepEqual({ yMin: 1 - 6 * 0.25, // 25% padding on single series range. yMax: 7 + 6 * 0.25, data: [ [0, 1], [5, 4], [10, 7] ] }, combinedSeries); // If Y2 is explicitly marked to be included in the range selector, // then it will be (even if it's not visible). Since we've started being // explicit about marking series for inclusion, this means that Y1 is no // longer included. g.updateOptions({ series: { Y2: { showInRangeSelector: true }, } }); combinedSeries = rangeSelector.computeCombinedSeriesAndLimits_(); assert.deepEqual({ yMin: 3 - 6 * 0.25, // 25% padding on combined series range. yMax: 9 + 6 * 0.25, data: [ [0, 3], [5, 6], [10, 9] ] }, combinedSeries); // If we explicitly mark Y1, too, then it also gets included. g.updateOptions({ series: { Y1: { showInRangeSelector: true }, Y2: { showInRangeSelector: true }, } }); combinedSeries = rangeSelector.computeCombinedSeriesAndLimits_(); assert.deepEqual({ yMin: 2 - 6 * 0.25, // 25% padding on combined series range. yMax: 8 + 6 * 0.25, data: [ [0, 2], [5, 5], [10, 8] ] }, combinedSeries); }); var assertGraphExistence = function(g, graph) { assert.isNotNull(g); var zoomhandles = graph.getElementsByClassName('dygraph-rangesel-zoomhandle'); assert.equal(2, zoomhandles.length); var bgcanvas = graph.getElementsByClassName('dygraph-rangesel-bgcanvas'); assert.equal(1, bgcanvas.length); var fgcanvas = graph.getElementsByClassName('dygraph-rangesel-fgcanvas'); assert.equal(1, fgcanvas.length); }; }); dygraphs-2.2.1/auto_tests/tests/range_tests.js010064400000000000000000000232151437353256000165070ustar00// Copyright (c) 2011 Google, Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. /** * @fileoverview Test valueRange and dateWindow changes. * * @author konigsberg@google.com (Robert Konigsberg) */ import Dygraph from '../../src/dygraph'; import DygraphOps from './DygraphOps'; import {assertDeepCloseTo} from './custom_asserts'; var ZERO_TO_FIFTY = [[ 10, 0 ] , [ 20, 50 ]]; var ZERO_TO_FIFTY_STEPS = (function() { var a = []; var x = 10; var y = 0; var step = 0; for (step = 0; step <= 50; step++) { a.push([x + (step * .2), y + step]); } return a; }()); var FIVE_TO_ONE_THOUSAND = [ [ 1, 10 ], [ 2, 20 ], [ 3, 30 ], [ 4, 40 ] , [ 5, 50 ], [ 6, 60 ], [ 7, 70 ], [ 8, 80 ], [ 9, 90 ] , [ 10, 1000 ]]; describe("range-tests", function() { cleanupAfterEach(); var createGraph = function(opts, data, expectRangeX, expectRangeY) { if (data === undefined) data = ZERO_TO_FIFTY_STEPS; if (expectRangeX === undefined) expectRangeX = [10, 20]; if (expectRangeY === undefined) expectRangeY = [0, 55]; if (!opts) opts = {}; opts['labels'] = ['X', 'Y']; var g = new Dygraph('graph', data, opts); assertDeepCloseTo(expectRangeX, g.xAxisRange(), 0.01); assertDeepCloseTo(expectRangeY, g.yAxisRange(0), 0.01); return g; }; /** * Test that changes to valueRange and dateWindow are reflected * appropriately. */ it('testRangeSetOperations', function() { var g = createGraph({valueRange : [ 0, 55 ]}); g.updateOptions({ dateWindow : [ 12, 18 ] }); assert.deepEqual([12, 18], g.xAxisRange()); assert.deepEqual([0, 55], g.yAxisRange(0)); g.updateOptions({ valueRange : [ 10, 40 ] }); assert.deepEqual([12, 18], g.xAxisRange()); assert.deepEqual([10, 40], g.yAxisRange(0)); g.updateOptions({ valueRange: [10, NaN] }); assert.deepEqual([12, 18], g.xAxisRange()); assert.deepEqual([10, 44.2], g.yAxisRange(0)); g.updateOptions({ valueRange: [10, 40] }); assert.deepEqual([12, 18], g.xAxisRange()); assert.deepEqual([10, 40], g.yAxisRange(0)); g.updateOptions({ valueRange: [10, null] }); assert.deepEqual([12, 18], g.xAxisRange()); assert.deepEqual([10, 44.2], g.yAxisRange(0)); g.updateOptions({ valueRange: [10, 40] }); assert.deepEqual([12, 18], g.xAxisRange()); assert.deepEqual([10, 40], g.yAxisRange(0)); g.updateOptions({ valueRange: [10, undefined] }); assert.deepEqual([12, 18], g.xAxisRange()); assert.deepEqual([10, 44.2], g.yAxisRange(0)); g.updateOptions({ valueRange: [10, 40] }); assert.deepEqual([12, 18], g.xAxisRange()); assert.deepEqual([10, 40], g.yAxisRange(0)); g.updateOptions({ }); assert.deepEqual([12, 18], g.xAxisRange()); assert.deepEqual([10, 40], g.yAxisRange(0)); g.updateOptions({valueRange : null, axes: {y:{valueRange : [15, 20]}}}); assert.deepEqual([12, 18], g.xAxisRange()); assert.deepEqual([15, 20], g.yAxisRange(0)); g.updateOptions({ dateWindow : null, valueRange : null, axes: null }); assert.deepEqual([10, 20], g.xAxisRange()); assert.deepEqual([0, 55], g.yAxisRange(0)); }); /** * Verify that when zoomed in by mouse operations, an empty call to * updateOptions doesn't change the displayed ranges. */ var zoom = function(g, xRange, yRange) { var originalXRange = g.xAxisRange(); var originalYRange = g.yAxisRange(0); DygraphOps.dispatchMouseDown(g, xRange[0], yRange[0]); DygraphOps.dispatchMouseMove(g, xRange[1], yRange[0]); // this is really necessary. DygraphOps.dispatchMouseUp(g, xRange[1], yRange[0]); assertDeepCloseTo(xRange, g.xAxisRange(), 0.2); // assert.closeTo(originalYRange, g.yAxisRange(0), 0.2); // Not true, it's something in the middle. var midX = (xRange[1] - xRange[0]) / 2; DygraphOps.dispatchMouseDown(g, midX, yRange[0]); DygraphOps.dispatchMouseMove(g, midX, yRange[1]); // this is really necessary. DygraphOps.dispatchMouseUp(g, midX, yRange[1]); assertDeepCloseTo(xRange, g.xAxisRange(), 0.2); assertDeepCloseTo(yRange, g.yAxisRange(0), 0.2); } /** * Verify that when zoomed in by mouse operations, an empty call to * updateOptions doesn't change the displayed ranges. */ it('testEmptyUpdateOptions_doesntUnzoom', function() { var g = createGraph(); zoom(g, [ 11, 18 ], [ 35, 40 ]); assertDeepCloseTo([11, 18], g.xAxisRange(), 0.1); assertDeepCloseTo([35, 40], g.yAxisRange(0), 0.2); g.updateOptions({}); assertDeepCloseTo([11, 18], g.xAxisRange(), 0.1); assertDeepCloseTo([35, 40], g.yAxisRange(0), 0.2); }); /** * Verify that when zoomed in by mouse operations, a call to * updateOptions({ dateWindow : null, valueRange : null }) fully * unzooms. */ it('testRestoreOriginalRanges_viaUpdateOptions', function() { var g = createGraph(); zoom(g, [ 11, 18 ], [ 35, 40 ]); g.updateOptions({ dateWindow : null, valueRange : null }); assert.deepEqual([0, 55], g.yAxisRange(0)); assert.deepEqual([10, 20], g.xAxisRange()); }); /** * Verify that log scale axis range is properly specified. */ it('testLogScaleExcludesZero', function() { var g = new Dygraph("graph", FIVE_TO_ONE_THOUSAND, { logscale: true, labels: ['X', 'Y'] }); assert.deepEqual([10, 1099], g.yAxisRange(0)); g.updateOptions({ logscale : false }); assert.deepEqual([0, 1099], g.yAxisRange(0)); }); /** * Verify that includeZero range is properly specified. */ it('testIncludeZeroIncludesZero', function() { var g = new Dygraph("graph", [[0, 500], [500, 1000]], { includeZero: true, labels: ['X', 'Y'] }); assert.deepEqual([0, 1100], g.yAxisRange(0)); g.updateOptions({ includeZero : false }); assert.deepEqual([450, 1050], g.yAxisRange(0)); }); /** * Verify that includeZero range is properly specified per axis. */ it('testIncludeZeroPerAxis', function() { var g = new Dygraph("graph", 'X,A,B\n'+ '0,50,50\n'+ '50,110,110\n', { drawPoints: true, pointSize:5, series:{ A: { axis: 'y', pointSize: 10 }, B: { axis: 'y2' } }, axes: { 'y2': { includeZero: true } } }); assert.deepEqual([44, 116], g.yAxisRange(0)); assert.deepEqual([0, 121], g.yAxisRange(1)); g.updateOptions({ axes: { 'y2': { includeZero: false } } }); assert.deepEqual([44, 116], g.yAxisRange(1)); }); /** * Verify that very large Y ranges don't break things. */ it('testHugeRange', function() { var g = new Dygraph("graph", [[0, -1e120], [1, 1e230]], { includeZero: true, labels: ['X', 'Y'] }); assert.closeTo(1, -1e229 / g.yAxisRange(0)[0], 0.001); assert.closeTo(1, 1.1e230 / g.yAxisRange(0)[1], 0.001); }); /** * Verify ranges with user-specified padding, implicit avoidMinZero. */ it('testPaddingAuto', function() { var g = createGraph({ xRangePad: 42, yRangePad: 30 }, ZERO_TO_FIFTY_STEPS, [9, 21], [-5, 55]); }); /** * Verify auto range with drawAxesAtZero. */ it('testPaddingAutoAxisAtZero', function() { var g = createGraph({ drawAxesAtZero: true, }, ZERO_TO_FIFTY_STEPS, [10, 20], [0, 55]); }); /** * Verify user-specified range with padding and drawAxesAtZero options. * Try explicit range matching the auto range, should have identical results. */ it('testPaddingRange1', function() { var g = createGraph({ valueRange: [0, 50], xRangePad: 42, yRangePad: 30, drawAxesAtZero: true }, ZERO_TO_FIFTY_STEPS, [9, 21], [-5, 55]); }); /** * Verify user-specified range with padding and drawAxesAtZero options. * User-supplied range differs from the auto range. */ it('testPaddingRange2', function() { var g = createGraph({ valueRange: [10, 60], xRangePad: 42, yRangePad: 30, drawAxesAtZero: true, }, ZERO_TO_FIFTY_STEPS, [9, 21], [5, 65]); }); /** * Verify drawAxesAtZero and includeZero. */ it('testPaddingYAtZero', function() { var g = createGraph({ includeZero: true, xRangePad: 42, yRangePad: 30, drawAxesAtZero: true, }, [ [-10, 10], [10, 20], [30, 50] ], [-14, 34], [-5, 55]); }); /** * Verify logscale, compat mode. */ it('testLogscaleCompat', function() { var g = createGraph({ logscale: true }, [[-10, 10], [10, 10], [30, 1000]], [-10, 30], [10, 1099]); }); /** * Verify logscale, new mode. */ it('testLogscalePad', function() { var g = createGraph({ logscale: true, yRangePad: 30 }, [[-10, 10], [10, 10], [30, 1000]], [-10, 30], [5.623, 1778.279]); }); /** * Verify scrolling all-zero region, new-style. */ it('testZeroScroll2', function() { var g = new Dygraph( document.getElementById("graph"), "X,Y\n" + "1,0\n" + "8,0\n" + "9,0.1\n", { animatedZooms: true, drawAxesAtZero: true, xRangePad: 4, yRangePad: 4 }); }); }); dygraphs-2.2.1/auto_tests/tests/resize.js010064400000000000000000000051541437353256000154740ustar00/** * @fileoverview Test cases for resizing. * * @author konigsberg@google.com (Robert Konigsberg) */ import Dygraph from '../../src/dygraph'; import DygraphOps from './DygraphOps'; import Util from './Util'; describe("resize", function() { cleanupAfterEach(); var data = "X,Y\n" + "1,100\n" + "2,200\n" + "3,300\n" + "4,400\n" + "5,300\n" + "6,100\n"; it('testResizeMaintainsMouseOperations', function() { var graph = document.getElementById('graph'); graph.setAttribute('style', 'width: 640px; height: 480px;'); var callbackCount = 0; var callback = function() { callbackCount++; } // Strum the mouse along the y-coordinate y, from 0 to x2. These are DOM values. var strum = function(g, y, x2) { DygraphOps.dispatchMouseDown_Point(g, 0, y); for (var x = 0; x < x2; x++) { DygraphOps.dispatchMouseMove_Point(g, x, y); } DygraphOps.dispatchMouseUp_Point(g, x2 - 1, y); } var g = new Dygraph(graph, data, {highlightCallback: callback}); strum(g, 300, 640); assert.equal(6, callbackCount); graph.style.width = "500px"; g.resize(); callbackCount = 0; strum(g, 300, 500); assert.equal(6, callbackCount); }); /** * Tests that a graph created in a not-displayed div works as expected * if the graph options include height and width. Resize not needed. */ it('testHiddenDivWithSizedGraph', function() { var div = document.getElementById("graph"); div.style.display = 'none'; var g = new Dygraph(div, data, {width: 400, height: 300}); div.style.display = ''; var area = g.getArea(); assert.isTrue(area.w > 0); assert.isTrue(area.h > 0); }); /** * Tests that a graph created in a not-displayed div with * CSS-specified size but no graph height or width options works as * expected. The user needs to call resize() on it after displaying * it. */ it('testHiddenDivWithResize', function() { var div = document.getElementById("graph"); div.style.display = 'none'; div.style.width = '400px'; div.style.height = '300px'; // Setting strokeWidth 3 removes any ambiguitiy from the pixel sampling // request, below. var g = new Dygraph(div, data, {strokeWidth: 3}); div.style.display = ''; g.resize(); var area = g.getArea(); assert.isTrue(area.w > 0); assert.isTrue(area.h > 0); // Regression test: check that graph remains visible after no-op resize. g.resize(); var x = Math.floor(g.toDomXCoord(2)); var y = Math.floor(g.toDomYCoord(200)); assert.deepEqual([0, 128, 128, 255], Util.samplePixel(g.hidden_, x, y), "Unexpected grid color found at pixel: x: " + x + " y: " + y); }); }); dygraphs-2.2.1/auto_tests/tests/rolling_average.js010064400000000000000000000153621437353256000173350ustar00/** * @fileoverview Tests for rolling averages. * * @author danvk@google.com (Dan Vanderkam) */ import Dygraph from '../../src/dygraph'; import Util from './Util'; describe("rolling-average", function() { cleanupAfterEach(); it('testRollingAverage', function() { var opts = { width: 480, height: 320, rollPeriod: 1, showRoller: true }; var data = "X,Y\n" + "0,0\n" + "1,1\n" + "2,2\n" + "3,3\n" ; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); g.setSelection(0); assert.equal("0: Y: 0", Util.getLegend()); g.setSelection(1); assert.equal("1: Y: 1", Util.getLegend()); g.setSelection(2); assert.equal("2: Y: 2", Util.getLegend()); g.setSelection(3); assert.equal("3: Y: 3", Util.getLegend()); assert.equal(1, g.rollPeriod()); g.updateOptions({rollPeriod: 2}); g.setSelection(0); assert.equal("0: Y: 0", Util.getLegend()); g.setSelection(1); assert.equal("1: Y: 0.5", Util.getLegend()); g.setSelection(2); assert.equal("2: Y: 1.5", Util.getLegend()); g.setSelection(3); assert.equal("3: Y: 2.5", Util.getLegend()); assert.equal(2, g.rollPeriod()); g.updateOptions({rollPeriod: 3}); g.setSelection(0); assert.equal("0: Y: 0", Util.getLegend()); g.setSelection(1); assert.equal("1: Y: 0.5", Util.getLegend()); g.setSelection(2); assert.equal("2: Y: 1", Util.getLegend()); g.setSelection(3); assert.equal("3: Y: 2", Util.getLegend()); assert.equal(3, g.rollPeriod()); g.updateOptions({rollPeriod: 4}); g.setSelection(0); assert.equal("0: Y: 0", Util.getLegend()); g.setSelection(1); assert.equal("1: Y: 0.5", Util.getLegend()); g.setSelection(2); assert.equal("2: Y: 1", Util.getLegend()); g.setSelection(3); assert.equal("3: Y: 1.5", Util.getLegend()); assert.equal(4, g.rollPeriod()); }); it('testRollBoxDoesntDisapper', function() { var opts = { showRoller: true }; var data = "X,Y\n" + "0,0\n" + "1,1\n" + "2,2\n" + "3,3\n" ; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); var roll_box = graph.getElementsByTagName("input"); assert.equal(1, roll_box.length); assert.equal("1", roll_box[0].value); graph.style.width = "500px"; g.resize(); assert.equal(1, roll_box.length); assert.equal("1", roll_box[0].value); }); // Regression test for http://code.google.com/p/dygraphs/issues/detail?id=426 it('testRollShortFractions', function() { var opts = { customBars: true, labels: ['x', 'A', 'B'] }; var data1 = [ [1, 10, [1, 20]] ]; var data2 = [ [1, 10, [1, 20]], [2, 20, [1, 30]], ]; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data2, opts); var rolled1 = g.dataHandler_.rollingAverage(data1, 1, g, 1); var rolled2 = g.dataHandler_.rollingAverage(data2, 1, g, 1); assert.deepEqual(rolled1[0], rolled2[0]); }); it('testRollCustomBars', function() { var opts = { customBars: true, rollPeriod: 2, labels: ['x', 'A'] }; var data = [ [1, [1, 10, 20]], [2, [1, 20, 30]], [3, [1, 30, 40]], [4, [1, 40, 50]] ]; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); var rolled = getRolledData(g, data, 1, 2); assert.deepEqual([1, 10, [1, 20]], rolled[0]); assert.deepEqual([2, 15, [1, 25]], rolled[1]); assert.deepEqual([3, 25, [1, 35]], rolled[2]); assert.deepEqual([4, 35, [1, 45]], rolled[3]); }); it('testRollErrorBars', function() { var opts = { errorBars: true, rollPeriod: 2, labels: ['x', 'A'] }; var data = [ [1, [10, 1]], [2, [20, 1]], [3, [30, 1]], [4, [40, 1]] ]; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); var rolled = getRolledData(g, data, 1, 2); assert.deepEqual([1, 10, [8, 12]], rolled[0]); // variance = sqrt( pow(error) * rollPeriod) var variance = Math.sqrt(2); for (var i=1;i"; var testDiv = document.getElementById('graph'); testDiv.innerHTML = "
" + "
" + "
" + LOREM_IPSUM + "
" + "
" + LOREM_IPSUM + "
" + "
"; // The old test runner had an 8px margin on the body // The Mocha test runner does not. We set it on the test div to keep the // coordinates the same. testDiv.style.margin = '8px'; var data = [ [ 10, 1 ], [ 20, 3 ], [ 30, 2 ], [ 40, 4 ], [ 50, 3 ], [ 60, 5 ], [ 70, 4 ], [ 80, 6 ] ]; var graph = document.getElementById("graph-inner"); point = null; g = new Dygraph(graph, data, { labels : ['a', 'b'], drawPoints : true, highlightCircleSize : 6, pointClickCallback : function(evt, p) { point = p; } } ); }); // This is usually something like 15, but for OS X Lion and its auto-hiding // scrollbars, it's 0. This is a large enough difference that we need to // consider it when synthesizing clicks. // Adapted from http://davidwalsh.name/detect-scrollbar-width var detectScrollbarWidth = function() { // Create the measurement node var scrollDiv = document.createElement("div"); scrollDiv.style.width = "100px"; scrollDiv.style.height = "100px"; scrollDiv.style.overflow = "scroll"; scrollDiv.style.position = "absolute"; scrollDiv.style.top = "-9999px"; document.body.appendChild(scrollDiv); // Get the scrollbar width var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth; // Delete the DIV document.body.removeChild(scrollDiv); return scrollbarWidth; }; /** * This tests that when the nested div is unscrolled, things work normally. */ it('testUnscrolledDiv', function() { document.getElementById('scroller').scrollTop = 0; var clickOn4_40 = { clientX: 244, clientY: 131, screenX: 416, screenY: 320 }; DygraphOps.dispatchCanvasEvent(g, DygraphOps.createEvent(clickOn4_40, { type : 'mousemove' })); DygraphOps.dispatchCanvasEvent(g, DygraphOps.createEvent(clickOn4_40, { type : 'mousedown' })); DygraphOps.dispatchCanvasEvent(g, DygraphOps.createEvent(clickOn4_40, { type : 'mouseup' })); assert.equal(40, point.xval); assert.equal(4, point.yval); }); /** * This tests that when the nested div is scrolled, things work normally. */ it('testScrolledDiv', function() { document.getElementById('scroller').scrollTop = 117; var clickOn4_40 = { clientX: 244, clientY: 30 - detectScrollbarWidth(), screenX: 416, screenY: 160 }; DygraphOps.dispatchCanvasEvent(g, DygraphOps.createEvent(clickOn4_40, { type : 'mousemove' })); DygraphOps.dispatchCanvasEvent(g, DygraphOps.createEvent(clickOn4_40, { type : 'mousedown' })); DygraphOps.dispatchCanvasEvent(g, DygraphOps.createEvent(clickOn4_40, { type : 'mouseup' })); assert.equal(40, point.xval); assert.equal(4, point.yval); }); }); dygraphs-2.2.1/auto_tests/tests/selection.js010064400000000000000000000040231437353256000161520ustar00// Copyright 2011 Google Inc. All Rights Reserved. /** * @fileoverview Regression test based on an optimization w/ * unforeseen consequences. * @author danvk@google.com (Dan Vanderkam) * @license MIT */ import Dygraph from '../../src/dygraph'; import DefaultHandler from '../../src/datahandler/default'; describe("selection", function() { cleanupAfterEach(); it('testSetGetSelection', function() { var graph = document.getElementById("graph"); var g = new Dygraph(graph, "X,Y\n" + "1,1\n" + "50,50\n" + "100,100\n" ); g.setSelection(0); assert.equal(0, g.getSelection()); g.setSelection(1); assert.equal(1, g.getSelection()); g.setSelection(2); assert.equal(2, g.getSelection()); }); it('testSetGetSelectionDense', function() { var graph = document.getElementById("graph"); var g = new Dygraph(graph, "X,Y\n" + "1,1\n" + "50,50\n" + "50.0001,50.0001\n" + "100,100\n" ); g.setSelection(0); assert.equal(0, g.getSelection()); g.setSelection(1); assert.equal(1, g.getSelection()); g.setSelection(2); assert.equal(2, g.getSelection()); g.setSelection(3); assert.equal(3, g.getSelection()); }); it('testSetGetSelectionMissingPoints', function() { var dataHandler = function() {}; dataHandler.prototype = new DefaultHandler(); dataHandler.prototype.seriesToPoints = function(series, setName, boundaryIdStart) { var val = null; if (setName == 'A') { val = 1; } else if (setName == 'B') { val = 2; } else if (setName == 'C') { val = 3; } return [{ x: NaN, y: NaN, xval: val, yval: val, name: setName, idx: val - 1 }]; }; var graph = document.getElementById("graph"); var g = new Dygraph(graph, "X,A,B,C\n" + "1,1,,\n" + "2,,2,\n" + "3,,,3\n", { dataHandler: dataHandler } ); g.setSelection(0); assert.equal(0, g.getSelection()); g.setSelection(1); assert.equal(1, g.getSelection()); g.setSelection(2); assert.equal(2, g.getSelection()); }); }); dygraphs-2.2.1/auto_tests/tests/simple_drawing.js010064400000000000000000000125051437353256000171750ustar00// Copyright (c) 2011 Google, Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. /** * @fileoverview Test cases for drawing simple lines. * * @author konigsberg@google.com (Robert Konigsberg) */ import Dygraph from '../../src/dygraph'; import * as utils from '../../src/dygraph-utils'; import CanvasAssertions from './CanvasAssertions'; import Proxy from './Proxy'; import PixelSampler from './PixelSampler'; describe("simple-drawing", function() { cleanupAfterEach(); useProxyCanvas(utils, Proxy); var ZERO_TO_FIFTY = 'X,Y\n10,0\n20,50'; it('testDrawSimpleRangePlusOne', function() { var opts = { axes: { x: { drawGrid: false, drawAxis: false }, y: { drawGrid: false, drawAxis: false } }, valueRange: [0,51] }; var graph = document.getElementById("graph"); var g = new Dygraph(graph, ZERO_TO_FIFTY, opts); var htx = g.hidden_ctx_; CanvasAssertions.assertLineDrawn(htx, [0,320], [475,6.2745], { strokeStyle: "#008080", lineWidth: 1 }); g.destroy(); // to balance context saves and destroys. CanvasAssertions.assertBalancedSaveRestore(htx); }); // See http://code.google.com/p/dygraphs/issues/detail?id=185 it('testDrawSimpleRangeZeroToFifty', function() { var opts = { axes : { x : { drawGrid: false, drawAxis: false, }, y : { drawGrid: false, drawAxis: false, } }, valueRange: [0,50] } var graph = document.getElementById("graph"); var g = new Dygraph(graph, ZERO_TO_FIFTY, opts); var htx = g.hidden_ctx_; var lines = CanvasAssertions.getLinesDrawn(htx, { strokeStyle: "#008080", lineWidth: 1 }); assert.equal(1, lines.length); g.destroy(); // to balance context saves and destroys. CanvasAssertions.assertBalancedSaveRestore(htx); }); it('testDrawWithAxis', function() { var graph = document.getElementById("graph"); var g = new Dygraph(graph, ZERO_TO_FIFTY); var htx = g.hidden_ctx_; g.destroy(); // to balance context saves and destroys. CanvasAssertions.assertBalancedSaveRestore(htx); }); /** * Tests that it is drawing dashes, and it remember the dash history between * points. */ it('testDrawSimpleDash', function() { var opts = { axes: { x: { drawGrid: false, drawAxis: false }, y: { drawGrid: false, drawAxis: false } }, series: { 'Y1': {strokePattern: [25, 7, 7, 7]}, }, colors: ['#ff0000'], labels: ['X', 'Y'] }; var graph = document.getElementById("graph"); // Set the dims so we pass if default changes. graph.style.width='480px'; graph.style.height='320px'; var g = new Dygraph(graph, [[1, 4], [2, 5], [3, 3], [4, 7], [5, 9]], opts); var htx = g.hidden_ctx_; // TODO(danvk): figure out a good way to restore this test. // assert.equal(29, CanvasAssertions.numLinesDrawn(htx, "#ff0000")); g.destroy(); // to balance context saves and destroys. CanvasAssertions.assertBalancedSaveRestore(htx); }); /** * Tests that thick lines are drawn continuously. * Regression test for http://code.google.com/p/dygraphs/issues/detail?id=328 */ it('testDrawThickLine', function() { var opts = { axes: { x: { drawGrid: false, drawAxis: false }, y: { drawGrid: false, drawAxis: false } }, strokeWidth: 15, colors: ['#ff0000'], labels: ['X', 'Y'] }; var graph = document.getElementById("graph"); // Set the dims so we pass if default changes. graph.style.width='480px'; graph.style.height='320px'; var g = new Dygraph(graph, [[1, 2], [2, 5], [3, 2], [4, 7], [5, 0]], opts); var htx = g.hidden_ctx_; // There's a big gap in the line at (2, 5) // If the bug is fixed, then there should be some red going up from here. var xy = g.toDomCoords(2, 5); var x = Math.round(xy[0]), y = Math.round(xy[1]); var sampler = new PixelSampler(g); assert.deepEqual([255,0,0,255], sampler.colorAtPixel(x, y)); assert.deepEqual([255,0,0,255], sampler.colorAtPixel(x, y - 1)); assert.deepEqual([255,0,0,255], sampler.colorAtPixel(x, y - 2)); // TODO(danvk): figure out a good way to restore this test. // assert.equal(29, CanvasAssertions.numLinesDrawn(htx, "#ff0000")); g.destroy(); // to balance context saves and destroys. CanvasAssertions.assertBalancedSaveRestore(htx); }); }); dygraphs-2.2.1/auto_tests/tests/smooth_plotter.js010064400000000000000000000023521437353256000172520ustar00/** * @fileoverview Tests for the smooth (bezier curve) plotter. * * @author danvdk@gmail.com (Dan Vanderkam) */ import Dygraph from '../../src/dygraph'; import '../../src/extras/smooth-plotter'; // defines Dygraph.smoothPlotter describe("smooth-plotter", function() { var smoothPlotter = Dygraph.smoothPlotter; var getControlPoints = smoothPlotter._getControlPoints; beforeEach(function() { }); afterEach(function() { }); it('testNoSmoothing', function() { var lastPt = {x: 10, y: 0}, pt = {x: 11, y: 1}, nextPt = {x: 12, y: 0}, alpha = 0; assert.deepEqual([11, 1, 11, 1], getControlPoints(lastPt, pt, nextPt, alpha)); }); it('testHalfSmoothing', function() { var lastPt = {x: 10, y: 0}, pt = {x: 11, y: 1}, nextPt = {x: 12, y: 0}, alpha = 0.5; assert.deepEqual([10.5, 1, 11.5, 1], getControlPoints(lastPt, pt, nextPt, alpha)); }); it('testExtrema', function() { var lastPt = {x: 10, y: 0}, pt = {x: 11, y: 1}, nextPt = {x: 12, y: 1}, alpha = 0.5; assert.deepEqual([10.5, 0.75, 11.5, 1.25], getControlPoints(lastPt, pt, nextPt, alpha, true)); assert.deepEqual([10.5, 1, 11.5, 1], getControlPoints(lastPt, pt, nextPt, alpha, false)); }); }); dygraphs-2.2.1/auto_tests/tests/stacked.js010064400000000000000000000217661437353256000156200ustar00/** * @fileoverview Tests using the "stackedGraph" option. * * @author dan@dygraphs.com (Dan Vanderkam) */ import Dygraph from '../../src/dygraph'; import * as utils from '../../src/dygraph-utils'; import Proxy from './Proxy'; import CanvasAssertions from './CanvasAssertions'; import Util from './Util'; describe("stacked", function() { cleanupAfterEach(); useProxyCanvas(utils, Proxy); it('testCorrectColors', function() { var opts = { width: 400, height: 300, stackedGraph: true, axes : { x : { drawGrid: false, drawAxis: false, }, y : { drawGrid: false, drawAxis: false, } }, valueRange: [0, 3], colors: ['#00ff00', '#0000ff'], fillAlpha: 0.15 }; var data = "X,Y1,Y2\n" + "0,1,1\n" + "1,1,1\n" + "2,1,1\n" + "3,1,1\n" ; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); // y pixels 299-201 = y2 = transparent blue // y pixel 200 = y2 line (blue) // y pixels 199-101 = y1 = transparent green // y pixel 100 = y1 line (green) // y pixels 0-99 = nothing (white) // 38 = round(0.15 * 255) assert.deepEqual([0, 0, 255, 38], Util.samplePixel(g.hidden_, 200, 250)); assert.deepEqual([0, 255, 0, 38], Util.samplePixel(g.hidden_, 200, 150)); }); // Regression test for http://code.google.com/p/dygraphs/issues/detail?id=358 it('testSelectionValues', function() { var opts = { stackedGraph: true }; var data = "X,Y1,Y2\n" + "0,1,1\n" + "1,1,1\n" + "2,1,1\n" + "3,1,1\n" ; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); g.setSelection(0); assert.equal("0: Y1: 1 Y2: 1", Util.getLegend()); // Verify that the behavior is correct with highlightSeriesOpts as well. g.updateOptions({ highlightSeriesOpts: { strokeWidth: 10 } }); g.setSelection(0); assert.equal("0: Y1: 1 Y2: 1", Util.getLegend()); g.setSelection(1); assert.equal("1: Y1: 1 Y2: 1", Util.getLegend()); g.setSelection(0, 'Y2'); assert.equal("0: Y1: 1 Y2: 1", Util.getLegend()); }); // Regression test for http://code.google.com/p/dygraphs/issues/detail?id=176 it('testDuplicatedXValue', function() { var opts = { stackedGraph: true, fillAlpha: 0.15, colors: ['#00ff00'], width: 400, height: 300 }; var data = "X,Y1\n" + "0,1\n" + "1,1\n" + "2,1\n" + "2,1\n" + // duplicate x-value! "3,1\n" ; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); assert(g.yAxisRange()[1] < 2); assert.deepEqual([0, 255, 0, 38], Util.samplePixel(g.hidden_, 200, 250)); assert.deepEqual([0, 255, 0, 38], Util.samplePixel(g.hidden_, 317, 250)); }); // Validates regression when null values in stacked graphs show up // incorrectly in the legend. it('testNullValues', function() { var opts = { stackedGraph: true, stepPlot:true }; var data = "X,Y1,Y2,Y3\n" + "0,-5,-1,1\n" + "1,1,,1\n" + "2,1,2,3\n" + "3,3,,4\n" + "4,3,2,3\n" ; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); g.setSelection(0); assert.equal("0: Y1: -5 Y2: -1 Y3: 1", Util.getLegend()); g.setSelection(1); assert.equal("1: Y1: 1 Y3: 1", Util.getLegend()); g.setSelection(2); assert.equal("2: Y1: 1 Y2: 2 Y3: 3", Util.getLegend()); g.setSelection(3); assert.equal("3: Y1: 3 Y3: 4", Util.getLegend()); g.setSelection(4); assert.equal("4: Y1: 3 Y2: 2 Y3: 3", Util.getLegend()); }); // Regression test for http://code.google.com/p/dygraphs/issues/detail?id=438 it('testMissingValueAtZero', function() { var opts = { stackedGraph: true }; var data = "X,Y1,Y2\n" + "0,,1\n" + "1,1,2\n" + "2,,3\n" ; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); g.setSelection(0); assert.equal("0: Y2: 1", Util.getLegend()); g.setSelection(1); assert.equal("1: Y1: 1 Y2: 2", Util.getLegend()); g.setSelection(2); assert.equal("2: Y2: 3", Util.getLegend()); }); it('testInterpolation', function() { var opts = { colors: ['#ff0000', '#00ff00', '#0000ff'], stackedGraph: true, labels: ['X', 'Y1', 'Y2', 'Y3', 'Y4'] }; // The last series is all-NaN, it ought to be treated as all zero // for stacking purposes. var N = NaN; var data = [ [100, 1, 2, N, N], [101, 1, 2, 2, N], [102, 1, N, N, N], [103, 1, 2, 4, N], [104, N, N, N, N], [105, 1, 2, N, N], [106, 1, 2, 7, N], [107, 1, 2, 8, N], [108, 1, 2, 9, N], [109, 1, N, N, N]]; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); var htx = g.hidden_ctx_; var attrs = {}; // Check that lines are drawn at the expected positions, using // interpolated values for missing data. CanvasAssertions.assertLineDrawn( htx, g.toDomCoords(100, 4), g.toDomCoords(101, 4), {strokeStyle: '#00ff00'}); CanvasAssertions.assertLineDrawn( htx, g.toDomCoords(102, 6), g.toDomCoords(103, 7), {strokeStyle: '#ff0000'}); CanvasAssertions.assertLineDrawn( htx, g.toDomCoords(107, 8), g.toDomCoords(108, 9), {strokeStyle: '#0000ff'}); CanvasAssertions.assertLineDrawn( htx, g.toDomCoords(108, 12), g.toDomCoords(109, 12), {strokeStyle: '#ff0000'}); // Check that the expected number of line segments gets drawn // for each series. Gaps don't get a line. assert.equal(7, CanvasAssertions.numLinesDrawn(htx, '#ff0000')); assert.equal(4, CanvasAssertions.numLinesDrawn(htx, '#00ff00')); assert.equal(2, CanvasAssertions.numLinesDrawn(htx, '#0000ff')); // Check that the selection returns the original (non-stacked) // values and skips gaps. g.setSelection(1); assert.equal("101: Y1: 1 Y2: 2 Y3: 2", Util.getLegend()); g.setSelection(8); assert.equal("108: Y1: 1 Y2: 2 Y3: 9", Util.getLegend()); g.setSelection(9); assert.equal("109: Y1: 1", Util.getLegend()); }); it('testInterpolationOptions', function() { var opts = { colors: ['#ff0000', '#00ff00', '#0000ff'], stackedGraph: true, labels: ['X', 'Y1', 'Y2', 'Y3'] }; var data = [ [100, 1, NaN, 3], [101, 1, 2, 3], [102, 1, NaN, 3], [103, 1, 2, 3], [104, 1, NaN, 3]]; var choices = ['all', 'inside', 'none']; var stackedY = [ [6, 6, 6, 6, 6], [4, 6, 6, 6, 4], [4, 6, 4, 6, 4]]; for (var i = 0; i < choices.length; ++i) { var graph = document.getElementById("graph"); opts['stackedGraphNaNFill'] = choices[i]; var g = new Dygraph(graph, data, opts); var htx = g.hidden_ctx_; var attrs = {}; // Check top lines get drawn at the expected positions. for (var j = 0; j < stackedY[i].length - 1; ++j) { CanvasAssertions.assertLineDrawn( htx, g.toDomCoords(100 + j, stackedY[i][j]), g.toDomCoords(101 + j, stackedY[i][j + 1]), {strokeStyle: '#ff0000'}); } } }); it('testMultiAxisInterpolation', function() { // Setting 2 axes to test that each axis stacks separately var opts = { colors: ['#ff0000', '#00ff00', '#0000ff'], stackedGraph: true, series: { 'Y1': { axis: 'y', }, 'Y2': { axis: 'y', }, 'Y3': { axis: 'y2', }, 'Y4': { axis: 'y2', } }, labels: ['X', 'Y1', 'Y2', 'Y3', 'Y4'] }; // The last series is all-NaN, it ought to be treated as all zero // for stacking purposes. var N = NaN; var data = [ [100, 1, 2, N, N], [101, 1, 2, 2, N], [102, 1, N, N, N], [103, 1, 2, 4, N], [104, N, N, N, N], [105, 1, 2, N, N], [106, 1, 2, 7, N], [107, 1, 2, 8, N], [108, 1, 2, 9, N], [109, 1, N, N, N]]; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); var htx = g.hidden_ctx_; var attrs = {}; // Check that lines are drawn at the expected positions, using // interpolated values for missing data. CanvasAssertions.assertLineDrawn( htx, g.toDomCoords(100, 2), g.toDomCoords(101, 2), {strokeStyle: '#00ff00'}); CanvasAssertions.assertLineDrawn( htx, g.toDomCoords(102, 3), g.toDomCoords(103, 3), {strokeStyle: '#ff0000'}); CanvasAssertions.assertLineDrawn( htx, g.toDomCoords(107, 2.71), g.toDomCoords(108, 3), {strokeStyle: '#0000ff'}); CanvasAssertions.assertLineDrawn( htx, g.toDomCoords(108, 3), g.toDomCoords(109, 3), {strokeStyle: '#ff0000'}); // Check that the expected number of line segments gets drawn // for each series. Gaps don't get a line. assert.equal(7, CanvasAssertions.numLinesDrawn(htx, '#ff0000')); assert.equal(4, CanvasAssertions.numLinesDrawn(htx, '#00ff00')); assert.equal(2, CanvasAssertions.numLinesDrawn(htx, '#0000ff')); // Check that the selection returns the original (non-stacked) // values and skips gaps. g.setSelection(1); assert.equal("101: Y1: 1 Y2: 2 Y3: 2", Util.getLegend()); g.setSelection(8); assert.equal("108: Y1: 1 Y2: 2 Y3: 9", Util.getLegend()); g.setSelection(9); assert.equal("109: Y1: 1", Util.getLegend()); }); }); dygraphs-2.2.1/auto_tests/tests/step_plot_per_series.js010064400000000000000000000312671437353256000204300ustar00/** * @fileoverview Test cases for the option "stepPlot" especially for the scenario where the option is not set for the whole graph but for single series. * * TODO(danvk): delete this test once dpxdt screenshot tests are part of the * main dygraphs repo. The tests have extremely specific expectations about * how drawing is performed. It's more realistic to test the resulting * pixels. * * @author julian.eichstaedt@ch.sauter-bc.com (Fr. Sauter AG) */ import Dygraph from '../../src/dygraph'; import * as utils from '../../src/dygraph-utils'; import Proxy from './Proxy'; import CanvasAssertions from './CanvasAssertions'; describe("step-plot-per-series", function() { cleanupAfterEach(); useProxyCanvas(utils, Proxy); it('testMixedModeStepAndLineFilled', function() { var opts = { width: 480, height: 320, axes : { x : { drawGrid: false, drawAxis: false, }, y : { drawGrid: false, drawAxis: false, } }, errorBars: false, labels: ["X", "Idle", "Used"], series: { Idle: {stepPlot: false}, Used: {stepPlot: true} }, fillGraph: true, stackedGraph: false, includeZero: true }; var data = [ [1, 70,30], [2, 12,88], [3, 88,12], [4, 63,37], [5, 35,65] ]; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); var htx = g.hidden_ctx_; var attrs = {}; for (var i = 0; i < data.length - 1; i++) { var x1 = data[i][0]; var x2 = data[i + 1][0]; var y1 = data[i][1]; var y2 = data[i + 1][1]; // First series (line) var xy1 = g.toDomCoords(x1, y1); var xy2 = g.toDomCoords(x2, y2); CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); y1 = data[i][2]; y2 = data[i + 1][2]; // Seconds series (step) // Horizontal line xy1 = g.toDomCoords(x1, y1); xy2 = g.toDomCoords(x2, y1); CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); // Vertical line xy1 = g.toDomCoords(x2, y1); xy2 = g.toDomCoords(x2, y2); CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); } }); it('testMixedModeStepAndLineStackedAndFilled', function() { var opts = { width: 480, height: 320, axes : { x : { drawGrid: false, drawAxis: false, }, y : { drawGrid: false, drawAxis: false, } }, errorBars: false, labels: ["X", "Idle", "Used", "NotUsed", "Active"], series: { Idle: {stepPlot: false}, Used: {stepPlot: true}, NotUsed: {stepPlot: false}, Active: {stepPlot: true} }, fillGraph: true, stackedGraph: true, includeZero: true }; var data = [ [1, 60,30,5,5], [2, 12,73,5,10], [3, 38,12,30,20], [4, 50,17,23,10], [5, 35,25,35,5] ]; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); var htx = g.hidden_ctx_; var attrs = {}; for (var i = 0; i < data.length - 1; i++) { var x1 = data[i][0]; var x2 = data[i + 1][0]; var y1base = 0; var y2base = 0; var y1 = data[i][4]; var y2 = data[i + 1][4]; // Fourth series (step) // Test lines // Horizontal line var xy1 = g.toDomCoords(x1, y1); var xy2 = g.toDomCoords(x2, y1); CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); // Vertical line xy1 = g.toDomCoords(x2, y1); xy2 = g.toDomCoords(x2, y2); CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); // Test edges of areas (also drawn by dygraphs as lines) xy1 = g.toDomCoords(x1, y1); xy2 = g.toDomCoords(x2, y1); CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); xy1 = xy2; xy2 = g.toDomCoords(x2, y2base); // CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); xy1 = xy2; xy2 = g.toDomCoords(x1, y1base); CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); // The last edge can not be tested via assertLineDrawn since it wasn't drawn as a line but via clossePath. // But a rectangle is completely tested with three of its four edges. y1base = y1; y2base = y1; y1 += data[i][3]; y2 += data[i + 1][3]; // Third series (line) // Test lines xy1 = g.toDomCoords(x1, y1); xy2 = g.toDomCoords(x2, y2); CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); // Test edges of areas (also drawn by dygraphs as lines) xy1 = g.toDomCoords(x1, y1); xy2 = g.toDomCoords(x2, y2); CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); xy1 = xy2; xy2 = g.toDomCoords(x2, y2base); // CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); xy1 = xy2; xy2 = g.toDomCoords(x1, y1base); CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); // The last edge can not be tested via assertLineDrawn since it wasn't drawn as a line but via clossePath. // But a rectangle is completely tested with three of its four edges. y1base = y1; y2base = y2; y1 += data[i][2]; y2 += data[i + 1][2]; // Second series (step) // Test lines // Horizontal line xy1 = g.toDomCoords(x1, y1); xy2 = g.toDomCoords(x2, y1); CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); // Vertical line xy1 = g.toDomCoords(x2, y1); xy2 = g.toDomCoords(x2, y2); CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); // Test edges of areas (also drawn by dygraphs as lines) xy1 = g.toDomCoords(x1, y1); xy2 = g.toDomCoords(x2, y1); CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); xy1 = xy2; xy2 = g.toDomCoords(x2, y2base); // CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); xy1 = xy2; xy2 = g.toDomCoords(x1, y1base); CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); // The last edge can not be tested via assertLineDrawn since it wasn't drawn as a line but via clossePath. // But a rectangle is completely tested with three of its four edges. y1base = y1; y2base = y1; y1 += data[i][1]; y2 += data[i + 1][1]; // First series (line) // Test lines xy1 = g.toDomCoords(x1, y1); xy2 = g.toDomCoords(x2, y2); CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); // Test edges of areas (also drawn by dygraphs as lines) xy1 = g.toDomCoords(x1, y1); xy2 = g.toDomCoords(x2, y2); CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); xy1 = xy2; xy2 = g.toDomCoords(x2, y2base); // CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); xy1 = xy2; xy2 = g.toDomCoords(x1, y1base); CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); // The last edge can not be tested via assertLineDrawn since it wasn't drawn as a line but via clossePath. // But a rectangle is completely tested with three of its four edges. } }); it('testMixedModeStepAndLineErrorBars', function() { var opts = { width: 480, height: 320, axes : { x : { drawGrid: false, drawAxis: false, }, y : { drawGrid: false, drawAxis: false, } }, errorBars: true, sigma: 1, labels: ["X", "Data1", "Data2"], series: { Data1: {stepPlot: true}, Data2: {stepPlot: false} } }; var data = [ [1, [75, 2], [50, 3]], [2, [70, 5], [90, 4]], [3, [80, 7], [112, 5]], [4, [55, 3], [100, 2]], [5, [69, 4], [85, 6]] ]; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); var htx = g.hidden_ctx_; var attrs = {}; // Test first series (step) for (var i = 0; i < data.length - 1; i++) { var x1 = data[i][0]; var x2 = data[i + 1][0]; var y1_middle = data[i][1][0]; var y2_middle = data[i + 1][1][0]; var y1_top = y1_middle + data[i][1][1]; var y2_top = y2_middle + data[i + 1][1][1]; var y1_bottom = y1_middle - data[i][1][1]; var y2_bottom = y2_middle - data[i + 1][1][1]; // Bottom line var xy1 = g.toDomCoords(x1, y1_bottom); var xy2 = g.toDomCoords(x2, y1_bottom); CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); // Top line xy1 = g.toDomCoords(x1, y1_top); xy2 = g.toDomCoords(x2, y1_top); CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); // Middle line xy1 = g.toDomCoords(x1, y1_middle); xy2 = g.toDomCoords(x2, y1_middle); CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); // Test edges of error bar areas(also drawn by dygraphs as lines) xy1 = g.toDomCoords(x1, y1_top); xy2 = g.toDomCoords(x2, y1_top); CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); xy1 = xy2; xy2 = g.toDomCoords(x2, y1_bottom); CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); xy1 = xy2; xy2 = g.toDomCoords(x1, y1_bottom); CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); // The last edge can not be tested via assertLineDrawn since it wasn't drawn as a line but via clossePath. // But a rectangle is completely tested with three of its four edges. } // Test second series (line) for (var i = 0; i < data.length - 1; i++) { // bottom line var xy1 = g.toDomCoords(data[i][0], (data[i][2][0] - data[i][2][1])); var xy2 = g.toDomCoords(data[i + 1][0], (data[i + 1][2][0] - data[i + 1][2][1])); CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); // top line xy1 = g.toDomCoords(data[i][0], data[i][2][0] + data[i][2][1]); xy2 = g.toDomCoords(data[i + 1][0], data[i + 1][2][0] + data[i + 1][2][1]); CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); // middle line xy1 = g.toDomCoords(data[i][0], data[i][2][0]); xy2 = g.toDomCoords(data[i + 1][0], data[i + 1][2][0]); CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); } }); it('testMixedModeStepAndLineCustomBars', function() { var opts = { width: 480, height: 320, axes : { x : { drawGrid: false, drawAxis: false, }, y : { drawGrid: false, drawAxis: false, } }, customBars: true, labels: ["X", "Data1", "Data2"], series: { Data1: {stepPlot: true}, Data2: {stepPlot: false} } }; var data = [ [1, [73, 75, 78], [50, 55, 70]], [2, [65, 70, 75], [83, 91, 99]], [3, [75, 85, 90], [98, 107, 117]], [4, [55, 58, 61], [93, 102, 105]], [5, [69, 73, 85], [80, 85, 87]] ]; var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); var htx = g.hidden_ctx_; var attrs = {}; // Test first series (step) for (var i = 0; i < data.length - 1; i++) { var x1 = data[i][0]; var x2 = data[i + 1][0]; var y1_middle = data[i][1][1]; var y2_middle = data[i + 1][1][1]; var y1_top = data[i][1][2]; var y2_top = data[i + 1][1][2]; var y1_bottom = data[i][1][0]; var y2_bottom = data[i + 1][1][0]; // Bottom line var xy1 = g.toDomCoords(x1, y1_bottom); var xy2 = g.toDomCoords(x2, y1_bottom); CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); // Top line xy1 = g.toDomCoords(x1, y1_top); xy2 = g.toDomCoords(x2, y1_top); CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); // Middle line xy1 = g.toDomCoords(x1, y1_middle); xy2 = g.toDomCoords(x2, y1_middle); CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); // Test edges of custom bar areas(also drawn by dygraphs as lines) xy1 = g.toDomCoords(x1, y1_top); xy2 = g.toDomCoords(x2, y1_top); CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); xy1 = xy2; xy2 = g.toDomCoords(x2, y1_bottom); CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); xy1 = xy2; xy2 = g.toDomCoords(x1, y1_bottom); CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); // The last edge can not be tested via assertLineDrawn since it wasn't drawn as a line but via clossePath. // But a rectangle is completely tested with three of its four edges. } // Test second series (line) for (var i = 0; i < data.length - 1; i++) { // Bottom line var xy1 = g.toDomCoords(data[i][0], data[i][2][0]); var xy2 = g.toDomCoords(data[i + 1][0], data[i + 1][2][0]); CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); // Top line xy1 = g.toDomCoords(data[i][0], data[i][2][2]); xy2 = g.toDomCoords(data[i + 1][0], data[i + 1][2][2]); CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); // Middle line xy1 = g.toDomCoords(data[i][0], data[i][2][1]); xy2 = g.toDomCoords(data[i + 1][0], data[i + 1][2][1]); CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); } }); }); dygraphs-2.2.1/auto_tests/tests/synchronize.js010064400000000000000000000061331437353256000165440ustar00/** * @fileoverview Tests synchronizer. * * @author nyx@nyx.cz (Marek Janda) */ import Dygraph from '../../src/dygraph'; import '../../src/extras/synchronizer'; // Sets Dygraph.synchronize import DygraphOps from './DygraphOps'; describe("synchronize", function() { var gs; var originalCallbackCalled; var data = "X,a,b,c\n" + "10,-1,1,2\n" + "11,0,3,1\n" + "12,1,4,2\n" + "13,0,2,3\n"; var h_row, h_pts; var graph = document.getElementById('graph'); beforeEach(function() { graph.innerHTML = "
"; originalCallbackCalled = false; h_row = 0, h_pts = []; gs = []; var highlightCallback = function(e, x, pts, row) { originalCallbackCalled = true; h_row = row; h_pts = pts; assert.equal(gs[0], this); }; gs.push(new Dygraph(document.getElementById("graph1"), data, { width: 100, height: 100, visibility: [false, true, true], highlightCallback: highlightCallback })); gs.push(new Dygraph(document.getElementById("graph2"), data, { width: 100, height: 100, visibility: [false, true, true], })); }); afterEach(function() { }); /** * This tests if original highlightCallback is called when synchronizer is attached */ it('testOriginalHighlightCallbackStillWorks', function() { var sync = Dygraph.synchronize(gs); DygraphOps.dispatchMouseMove(gs[1], 5, 5); // check that chart2 doesn't trigger highlightCallback on chart1 assert.equal(originalCallbackCalled, false); DygraphOps.dispatchMouseMove(gs[0], 13, 10); // check that original highlightCallback was called assert.equal(originalCallbackCalled, true); sync.detach(); }); /** * This tests if selection is propagated correctly between charts */ it('testChartsAreSynchronized', function() { DygraphOps.dispatchMouseMove(gs[0], 13, 10); assert.notEqual(gs[0].getSelection(), gs[1].getSelection()); DygraphOps.dispatchMouseMove(gs[0], 0, 0); var sync = Dygraph.synchronize(gs); DygraphOps.dispatchMouseMove(gs[0], 13, 10); //check correct row is highlighted on second chart assert.equal(3, h_row); //check there are only two points (because first series is hidden) assert.equal(2, h_pts.length); //check that selection on both charts is the same assert.equal(gs[0].getSelection(), gs[1].getSelection()); sync.detach(); }); /** * This tests if detach works */ it('testSynchronizerDetach', function() { var sync = Dygraph.synchronize(gs); DygraphOps.dispatchMouseMove(gs[1], 10, 10); sync.detach(); originalCallbackCalled = false; DygraphOps.dispatchMouseMove(gs[1], 0, 0); //check that chart2 doesn't have highlightCallback assert.equal(originalCallbackCalled, false); DygraphOps.dispatchMouseMove(gs[0], 13, 10); //check that original callback was re-attached assert.equal(originalCallbackCalled, true); //check that selection isn't synchronized anymore assert.equal(gs[0].getSelection(), 3); assert.equal(gs[1].getSelection(), 0); }); }); dygraphs-2.2.1/auto_tests/tests/to_dom_coords.js010064400000000000000000000212031437353256000170160ustar00/** * @fileoverview Test cases for toDomCoords/toDataCoords * * @author danvk@google.com (Dan Vanderkam) */ import Dygraph from '../../src/dygraph'; import * as utils from '../../src/dygraph-utils'; import Proxy from './Proxy'; import CanvasAssertions from './CanvasAssertions'; import {assertDeepCloseTo} from './custom_asserts'; describe("to-dom-coords", function() { cleanupAfterEach(); useProxyCanvas(utils, Proxy); // Checks that toDomCoords and toDataCoords are inverses of one another. var checkForInverses = function(g) { var x_range = g.xAxisRange(); var y_range = g.yAxisRange(); for (var i = 0; i <= 10; i++) { var x = x_range[0] + i / 10.0 * (x_range[1] - x_range[0]); for (var j = 0; j <= 10; j++) { var y = y_range[0] + j / 10.0 * (y_range[1] - y_range[0]); assert.equal(x, g.toDataXCoord(g.toDomXCoord(x))); assert.equal(y, g.toDataYCoord(g.toDomYCoord(y))); } } }; it('testPlainChart', function() { var opts = { axes: { x: { drawAxis : false, drawGrid : false }, y: { drawAxis : false, drawGrid : false } }, rightGap: 0, valueRange: [0, 100], dateWindow: [0, 100], width: 400, height: 400, colors: ['#ff0000'], labels: ['X', 'Y'] } var graph = document.getElementById("graph"); var g = new Dygraph(graph, [ [0,0], [100,100] ], opts); assert.deepEqual([0, 100], g.toDataCoords(0, 0)); assert.deepEqual([0, 0], g.toDataCoords(0, 400)); assert.deepEqual([100, 100], g.toDataCoords(400, 0)); assert.deepEqual([100, 0], g.toDataCoords(400, 400)); checkForInverses(g); // TODO(konigsberg): This doesn't really belong here. Move to its own test. var htx = g.hidden_ctx_; assert.equal(1, CanvasAssertions.numLinesDrawn(htx, '#ff0000')); }); it('testChartWithAxes', function() { var opts = { axes: { x: { drawGrid: false, drawAxis: true, }, y: { drawGrid: false, drawAxis: true, axisLabelWidth: 100 } }, xAxisHeight: 50, axisTickSize: 0, rightGap: 0, valueRange: [0, 100], dateWindow: [0, 100], width: 500, height: 450, colors: ['#ff0000'], labels: ['X', 'Y'] } var graph = document.getElementById("graph"); var g = new Dygraph(graph, [ [0,0], [100,100] ], opts); assert.deepEqual([0, 100], g.toDataCoords(100, 0)); assert.deepEqual([0, 0], g.toDataCoords(100, 400)); assert.deepEqual([100, 100], g.toDataCoords(500, 0)); assert.deepEqual([100, 0], g.toDataCoords(500, 400)); checkForInverses(g); }); it('testChartWithAxesAndLabels', function() { var opts = { axes: { x: { drawGrid: false, drawAxis: true, }, y: { drawGrid: false, drawAxis: true, axisLabelWidth: 100 }, }, xAxisHeight: 50, axisTickSize: 0, rightGap: 0, valueRange: [0, 100], dateWindow: [0, 100], width: 500, height: 500, colors: ['#ff0000'], ylabel: 'This is the y-axis', xlabel: 'This is the x-axis', xLabelHeight: 25, title: 'This is the title of the chart', titleHeight: 25, labels: ['X', 'Y'] } var graph = document.getElementById("graph"); var g = new Dygraph(graph, [ [0,0], [100,100] ], opts); assert.deepEqual([0, 100], g.toDataCoords(100, 25)); assert.deepEqual([0, 0], g.toDataCoords(100, 425)); assert.deepEqual([100, 100], g.toDataCoords(500, 25)); assert.deepEqual([100, 0], g.toDataCoords(500, 425)); checkForInverses(g); }); it('testYAxisLabelWidth', function() { var opts = { axes: { y: { axisLabelWidth: 100 } }, axisTickSize: 0, rightGap: 0, valueRange: [0, 100], dateWindow: [0, 100], width: 500, height: 500, labels: ['X', 'Y'] } var graph = document.getElementById("graph"); var g = new Dygraph(graph, [ [0,0], [100,100] ], opts); assert.deepEqual([100, 0], g.toDomCoords(0, 100)); assert.deepEqual([500, 486], g.toDomCoords(100, 0)); g.updateOptions({ axes: { y: { axisLabelWidth: 50 }}, }); assert.deepEqual([50, 0], g.toDomCoords(0, 100)); assert.deepEqual([500, 486], g.toDomCoords(100, 0)); }); it('testAxisTickSize', function() { var opts = { axes: { y: { axisLabelWidth: 100 } }, axisTickSize: 0, rightGap: 0, valueRange: [0, 100], dateWindow: [0, 100], width: 500, height: 500, labels: ['X', 'Y'] } var graph = document.getElementById("graph"); var g = new Dygraph(graph, [ [0,0], [100,100] ], opts); assert.deepEqual([100, 0], g.toDomCoords(0, 100)); assert.deepEqual([500, 486], g.toDomCoords(100, 0)); g.updateOptions({ axisTickSize : 50 }); assert.deepEqual([200, 0], g.toDomCoords(0, 100)); assert.deepEqual([500, 386], g.toDomCoords(100, 0)); }); it('testChartLogarithmic_YAxis', function() { var opts = { rightGap: 0, valueRange: [1, 4], dateWindow: [0, 10], width: 400, height: 400, colors: ['#ff0000'], axes: { x: { drawGrid: false, drawAxis: false }, y: { drawGrid: false, drawAxis: false, logscale: true } }, labels: ['X', 'Y'] } var graph = document.getElementById("graph"); var g = new Dygraph(graph, [ [1,1], [4,4] ], opts); var epsilon = 1e-8; assertDeepCloseTo([0, 4], g.toDataCoords(0, 0), epsilon); assertDeepCloseTo([0, 1], g.toDataCoords(0, 400), epsilon); assertDeepCloseTo([10, 4], g.toDataCoords(400, 0), epsilon); assertDeepCloseTo([10, 1], g.toDataCoords(400, 400), epsilon); assertDeepCloseTo([10, 2], g.toDataCoords(400, 200), epsilon); assert.deepEqual([0, 0], g.toDomCoords(0, 4)); assert.deepEqual([0, 400], g.toDomCoords(0, 1)); assert.deepEqual([400, 0], g.toDomCoords(10, 4)); assert.deepEqual([400, 400], g.toDomCoords(10, 1)); assert.deepEqual([400, 200], g.toDomCoords(10, 2)); // Verify that the margins are adjusted appropriately for yRangePad. g.updateOptions({yRangePad: 40}); assertDeepCloseTo([0, 4], g.toDataCoords(0, 40), epsilon); assertDeepCloseTo([0, 1], g.toDataCoords(0, 360), epsilon); assertDeepCloseTo([10, 4], g.toDataCoords(400, 40), epsilon); assertDeepCloseTo([10, 1], g.toDataCoords(400, 360), epsilon); assertDeepCloseTo([10, 2], g.toDataCoords(400, 200), epsilon); assertDeepCloseTo([0, 40], g.toDomCoords(0, 4), epsilon); assertDeepCloseTo([0, 360], g.toDomCoords(0, 1), epsilon); assertDeepCloseTo([400, 40], g.toDomCoords(10, 4), epsilon); assertDeepCloseTo([400, 360], g.toDomCoords(10, 1), epsilon); assertDeepCloseTo([400, 200], g.toDomCoords(10, 2), epsilon); }); it('testChartLogarithmic_XAxis', function() { var opts = { rightGap: 0, valueRange: [1, 1000], dateWindow: [1, 1000], width: 400, height: 400, colors: ['#ff0000'], axes: { x: { drawGrid: false, drawAxis: false, logscale: true }, y: { drawGrid: false, drawAxis: false } }, labels: ['X', 'Y'] } var graph = document.getElementById("graph"); var g = new Dygraph(graph, [ [1,1], [10, 10], [100,100], [1000,1000] ], opts); var epsilon = 1e-8; assert.closeTo(1, g.toDataXCoord(0), epsilon); assert.closeTo(5.623413251903489, g.toDataXCoord(100), epsilon); assert.closeTo(31.62277660168378, g.toDataXCoord(200), epsilon); assert.closeTo(177.8279410038921, g.toDataXCoord(300), epsilon); assert.closeTo(1000, g.toDataXCoord(400), epsilon); assert.closeTo(0, g.toDomXCoord(1), epsilon); assert.closeTo(3.6036036036036037, g.toDomXCoord(10), epsilon); assert.closeTo(39.63963963963964, g.toDomXCoord(100), epsilon); assert.closeTo(400, g.toDomXCoord(1000), epsilon); assert.closeTo(0, g.toPercentXCoord(1), epsilon); assert.closeTo(0.3333333333, g.toPercentXCoord(10), epsilon); assert.closeTo(0.6666666666, g.toPercentXCoord(100), epsilon); assert.closeTo(1, g.toPercentXCoord(1000), epsilon); // Now zoom in and ensure that the methods return reasonable values. g.updateOptions({dateWindow: [ 10, 100 ]}); assert.closeTo(10, g.toDataXCoord(0), epsilon); assert.closeTo(17.78279410038923, g.toDataXCoord(100), epsilon); assert.closeTo(31.62277660168379, g.toDataXCoord(200), epsilon); assert.closeTo(56.23413251903491, g.toDataXCoord(300), epsilon); assert.closeTo(100, g.toDataXCoord(400), epsilon); assert.closeTo(-40, g.toDomXCoord(1), epsilon); assert.closeTo(0, g.toDomXCoord(10), epsilon); assert.closeTo(400, g.toDomXCoord(100), epsilon); assert.closeTo(4400, g.toDomXCoord(1000), epsilon); assert.closeTo(-1, g.toPercentXCoord(1), epsilon); assert.closeTo(0, g.toPercentXCoord(10), epsilon); assert.closeTo(1, g.toPercentXCoord(100), epsilon); assert.closeTo(2, g.toPercentXCoord(1000), epsilon); }); }); dygraphs-2.2.1/auto_tests/tests/two_digit_years.js010064400000000000000000000024401437353256000173620ustar00/** * @fileoverview Test to check that years < 100 get the correct ticks. * * @author gmadrid@gmail.com (George Madrid) */ import Dygraph from '../../src/dygraph'; import {Granularity, getDateAxis} from '../../src/dygraph-tickers'; import * as utils from '../../src/dygraph-utils'; import DEFAULT_ATTRS from '../../src/dygraph-default-attrs'; describe("two-digit-years", function() { it('testTwoDigitYears', function() { // A date with a one digit year: '9 AD'. var start = new Date(9, 2, 3); // A date with a two digit year: '11 AD'. var end = new Date(11, 3, 5); // Javascript will automatically add 1900 to our years if they are < 100. // Use setFullYear() to get the actual years we desire. start.setFullYear(9); end.setFullYear(11); var ticks = getDateAxis(start, end, Granularity.QUARTERLY, function(x) { return DEFAULT_ATTRS.axes['x'][x]; }); // This breaks in Firefox & Safari: // assert.deepEqual([{"v":-61875345600000,"label":"Apr 9"},{"v":-61867483200000,"label":"Jul 9"},{"v":-61859534400000,"label":"Oct 9"},{"v":-61851582000000,"label":"Jan 10"},{"v":-61843809600000,"label":"Apr 10"},{"v":-61835947200000,"label":"Jul 10"},{"v":-61827998400000,"label":"Oct 10"},{"v":-61820046000000,"label":"Jan 11"},{"v":-61812273600000,"label":"Apr 11"}], ticks); }); }); dygraphs-2.2.1/auto_tests/tests/update_options.js010064400000000000000000000114011437353256000172200ustar00// Copyright 2011 Google Inc. All Rights Reserved. /** * @fileoverview Tests for the updateOptions function. * @author antrob@google.com (Anthony Robledo) * @license MIT */ import Dygraph from '../../src/dygraph'; describe("update-options", function() { cleanupAfterEach(); var opts = { width: 480, height: 320, }; var data = "X,Y1,Y2\n" + "2011-01-01,2,3\n" + "2011-02-02,5,3\n" + "2011-03-03,6,1\n" + "2011-04-04,9,5\n" + "2011-05-05,8,3\n"; /* * Tweaks the dygraph so it sets g._testDrawCalled to true when internal method * drawGraph_ is called. Call unWrapDrawGraph when done with this. */ var wrapDrawGraph = function(g) { g._testDrawCalled = false; g._oldDrawGraph = g.drawGraph_; g.drawGraph_ = function() { g._testDrawCalled = true; g._oldDrawGraph.call(g); } }; /* * See wrapDrawGraph */ var unwrapDrawGraph = function(g) { g.drawGraph_ = g._oldDrawGraph; } it('testStrokeAll', function() { var graphDiv = document.getElementById("graph"); var graph = new Dygraph(graphDiv, data, opts); var updatedOptions = { }; updatedOptions['strokeWidth'] = 3; // These options will allow us to jump to renderGraph_() // drawGraph_() will be skipped. wrapDrawGraph(graph); graph.updateOptions(updatedOptions); unwrapDrawGraph(graph); assert.isFalse(graph._testDrawCalled); }); it('testStrokeSingleSeries', function() { var graphDiv = document.getElementById("graph"); var graph = new Dygraph(graphDiv, data, opts); var updatedOptions = { }; var optionsForY1 = { }; optionsForY1['strokeWidth'] = 3; updatedOptions['series'] = {'Y1': optionsForY1}; // These options will allow us to jump to renderGraph_() // drawGraph_() will be skipped. wrapDrawGraph(graph); graph.updateOptions(updatedOptions); unwrapDrawGraph(graph); assert.isFalse(graph._testDrawCalled); }); it('testSingleSeriesRequiresNewPoints', function() { var graphDiv = document.getElementById("graph"); var graph = new Dygraph(graphDiv, data, opts); var updatedOptions = { series: { Y1: { strokeWidth: 2 }, Y2: { stepPlot: true } } }; // These options will not allow us to jump to renderGraph_() // drawGraph_() must be called wrapDrawGraph(graph); graph.updateOptions(updatedOptions); unwrapDrawGraph(graph); assert.isTrue(graph._testDrawCalled); }); it('testWidthChangeNeedsNewPoints', function() { var graphDiv = document.getElementById("graph"); var graph = new Dygraph(graphDiv, data, opts); var updatedOptions = { }; // This will require new points. updatedOptions['width'] = 600; // These options will not allow us to jump to renderGraph_() // drawGraph_() must be called wrapDrawGraph(graph); graph.updateOptions(updatedOptions); unwrapDrawGraph(graph); assert.isTrue(graph._testDrawCalled); }); // Test https://github.com/danvk/dygraphs/issues/87 it('testUpdateLabelsDivDoesntInfiniteLoop', function() { var graphDiv = document.getElementById("graph"); var labelsDiv = document.getElementById("labels"); var graph = new Dygraph(graphDiv, data, opts); graph.updateOptions({labelsDiv : labelsDiv}); }); // Test https://github.com/danvk/dygraphs/issues/247 it('testUpdateColors', function() { var graphDiv = document.getElementById("graph"); var graph = new Dygraph(graphDiv, data, opts); var defaultColors = ["rgb(0,128,0)", "rgb(0,0,128)"]; assert.deepEqual(["rgb(0,128,0)", "rgb(0,0,128)"], graph.getColors()); var colors1 = [ "#aaa", "#bbb" ]; graph.updateOptions({ colors: colors1 }); assert.deepEqual(colors1, graph.getColors()); // extra colors are ignored until you add additional data series. var colors2 = [ "#ddd", "#eee", "#fff" ]; graph.updateOptions({ colors: colors2 }); assert.deepEqual(["#ddd", "#eee"], graph.getColors()); graph.updateOptions({ file: "X,Y1,Y2,Y3\n" + "2011-01-01,2,3,4\n" + "2011-02-02,5,3,2\n" }); assert.deepEqual(colors2, graph.getColors()); graph.updateOptions({ colors: null, file: data }); assert.deepEqual(defaultColors, graph.getColors()); }); // Regression test for http://code.google.com/p/dygraphs/issues/detail?id=249 // Verifies that setting 'legend: always' via update immediately shows the // legend. it('testUpdateLegendAlways', function() { var graphDiv = document.getElementById("graph"); var graph = new Dygraph(graphDiv, data, opts); var legend = document.getElementsByClassName("dygraph-legend"); assert.equal(1, legend.length); legend = legend[0]; assert.equal("", legend.innerHTML); graph.updateOptions({legend: 'always'}); legend = document.getElementsByClassName("dygraph-legend"); assert.equal(1, legend.length); legend = legend[0]; assert.notEqual(-1, legend.textContent.indexOf("Y1")); assert.notEqual(-1, legend.textContent.indexOf("Y2")); }); }); dygraphs-2.2.1/auto_tests/tests/update_while_panning.js010064400000000000000000000031751437353256000203600ustar00/** * @fileoverview Regression test for a bug involving data update while panning. * * See https://stackoverflow.com/q/9528173/2171120 * * @author dan@dygraphs.com (Dan Vanderkam) */ import Dygraph from '../../src/dygraph'; import DygraphOps from './DygraphOps'; describe("update-while-panning", function() { cleanupAfterEach(); // This tests the following sequence: // 1. Begin dragging a chart (x-panning) // 2. Do a data update (updateOptions({file: ...})) // 3. Verify that the y-axis is still well-defined. it('testUpdateWhilePanning', function() { var sinewave = function(start, limit, step) { var data = []; for (var x = start; x < limit; x += step) { data.push([x, Math.sin(x)]); } return data; }; var opts = { width: 480, height: 320, valueRange: [-2, 2], labels: ['X', 'Y'] }; var graph = document.getElementById("graph"); var g = new Dygraph(graph, sinewave(0, 6, 0.1), opts); assert.deepEqual([-2, 2], g.yAxisRange()); // Start a pan, but don't finish it yet. DygraphOps.dispatchMouseDown_Point(g, 200, 100, {shiftKey: true}); DygraphOps.dispatchMouseMove_Point(g, 100, 100, {shiftKey: true}); assert.deepEqual([-2, 2], g.yAxisRange()); // Now do a data update. y-axis should remain the same. g.updateOptions({file: sinewave(0, 7, 0.1)}); assert.deepEqual([-2, 2], g.yAxisRange()); // Keep the pan going. DygraphOps.dispatchMouseMove_Point(g, 50, 100, {shiftKey: true}); assert.deepEqual([-2, 2], g.yAxisRange()); // Now finish the pan. DygraphOps.dispatchMouseUp_Point(g, 100, 100, {shiftKey: true}); assert.deepEqual([-2, 2], g.yAxisRange()); }); }); dygraphs-2.2.1/auto_tests/tests/utils_test.js010064400000000000000000000123051437353256000163660ustar00/** * @fileoverview Tests for stand-alone functions in dygraph-utils.js * * @author danvdk@gmail.com (Dan Vanderkam) */ import * as utils from '../../src/dygraph-utils'; describe("utils-tests", function() { it('testUpdate', function() { var a = { a: 1, b: [1, 2, 3], c: { x: 1, y: 2}, d: { f: 10, g: 20} }; assert.equal(1, a['a']); assert.deepEqual([1, 2, 3], a['b']); assert.deepEqual({x: 1, y: 2}, a['c']); assert.deepEqual({f: 10, g: 20}, a['d']); utils.update(a, { c: { x: 2 } }); assert.deepEqual({x: 2}, a['c']); utils.update(a, { d: null }); assert.equal(null, a['d']); utils.update(a, { a: 10, b: [1, 2] }); assert.equal(10, a['a']); assert.deepEqual([1, 2], a['b']); assert.deepEqual({x: 2}, a['c']); assert.equal(null, a['d']); }); it('testUpdateDeep', function() { var a = { a: 1, b: [1, 2, 3], c: { x: 1, y: 2}, d: { f: 10, g: 20} }; assert.equal(1, a['a']); assert.deepEqual([1, 2, 3], a['b']); assert.deepEqual({x: 1, y: 2}, a['c']); assert.deepEqual({f: 10, g: 20}, a['d']); utils.updateDeep(a, { c: { x: 2 } }); assert.deepEqual({x: 2, y: 2}, a['c']); utils.updateDeep(a, { d: null }); assert.equal(null, a['d']); utils.updateDeep(a, { a: 10, b: [1, 2] }); assert.equal(10, a['a']); assert.deepEqual([1, 2], a['b']); assert.deepEqual({x: 2, y: 2}, a['c']); assert.equal(null, a['d']); }); it('testUpdateDeepDecoupled', function() { var a = { a: 1, b: [1, 2, 3], c: { x: "original", y: 2}, }; var b = {}; utils.updateDeep(b, a); b.a = 2; assert.equal(1, a.a); b.b[0] = 2; assert.equal(1, a.b[0]); b.c.x = "new value"; assert.equal("original", a.c.x); }); it('testIterator_nopredicate', function() { var array = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']; var iter = utils.createIterator(array, 1, 4); assert.isTrue(iter.hasNext); assert.equal('b', iter.peek); assert.equal('b', iter.next()); assert.isTrue(iter.hasNext); assert.equal('c', iter.peek); assert.equal('c', iter.next()); assert.isTrue(iter.hasNext); assert.equal('d', iter.next()); assert.isTrue(iter.hasNext); assert.equal('e', iter.next()); assert.isFalse(iter.hasNext); }); it('testIterator_predicate', function() { var array = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']; var iter = utils.createIterator(array, 1, 4, function(array, idx) { return array[idx] !== 'd' }); assert.isTrue(iter.hasNext); assert.equal('b', iter.peek); assert.equal('b', iter.next()); assert.isTrue(iter.hasNext); assert.equal('c', iter.peek); assert.equal('c', iter.next()); assert.isTrue(iter.hasNext); assert.equal('e', iter.next()); assert.isFalse(iter.hasNext); }); it('testIterator_empty', function() { var array = []; var iter = utils.createIterator([], 0, 0); assert.isFalse(iter.hasNext); }); it('testIterator_outOfRange', function() { var array = ['a', 'b', 'c']; var iter = utils.createIterator(array, 1, 4, function(array, idx) { return array[idx] !== 'd' }); assert.isTrue(iter.hasNext); assert.equal('b', iter.peek); assert.equal('b', iter.next()); assert.isTrue(iter.hasNext); assert.equal('c', iter.peek); assert.equal('c', iter.next()); assert.isFalse(iter.hasNext); }); // Makes sure full array is tested, and that the predicate isn't called // with invalid boundaries. it('testIterator_whole_array', function() { var array = ['a', 'b', 'c']; var iter = utils.createIterator(array, 0, array.length, function(array, idx) { if (idx < 0 || idx >= array.length) { throw "err"; } else { return true; } }); assert.isTrue(iter.hasNext); assert.equal('a', iter.next()); assert.isTrue(iter.hasNext); assert.equal('b', iter.next()); assert.isTrue(iter.hasNext); assert.equal('c', iter.next()); assert.isFalse(iter.hasNext); assert.isNull(iter.next()); }); it('testIterator_no_args', function() { var array = ['a', 'b', 'c']; var iter = utils.createIterator(array); assert.isTrue(iter.hasNext); assert.equal('a', iter.next()); assert.isTrue(iter.hasNext); assert.equal('b', iter.next()); assert.isTrue(iter.hasNext); assert.equal('c', iter.next()); assert.isFalse(iter.hasNext); assert.isNull(iter.next()); }); it('testToRGB', function() { assert.deepEqual({r: 255, g: 200, b: 150}, utils.toRGB_('rgb(255,200,150)')); assert.deepEqual({r: 255, g: 200, b: 150}, utils.toRGB_('#FFC896')); assert.deepEqual({r: 255, g: 0, b: 0}, utils.toRGB_('red')); assert.deepEqual({r: 255, g: 200, b: 150, a: 0.6}, utils.toRGB_('rgba(255, 200, 150, 0.6)')); }); it('testIsPixelChangingOptionList', function() { var isPx = utils.isPixelChangingOptionList; assert.isTrue(isPx([], { axes: { y: { digitsAfterDecimal: 3 }}})); assert.isFalse(isPx([], { axes: { y: { axisLineColor: 'blue' }}})); }); /* it('testDateSet', function() { var base = new Date(1383455100000); var d = new Date(base); // A one hour shift -- this is surprising behavior! d.setMilliseconds(10); assert.equal(3600010, d.getTime() - base.getTime()); // setDateSameTZ compensates for this surprise. d = new Date(base); Dygraph.setDateSameTZ(d, {ms: 10}); assert.equal(10, d.getTime() - base.getTime()); }); */ }); dygraphs-2.2.1/auto_tests/tests/visibility.js010064400000000000000000000041121437353256000163530ustar00/** * @fileoverview Tests for the setVisibility function. * @author sergeyslepian@gmail.com */ import Dygraph from '../../src/dygraph'; import Util from './Util'; describe("visibility", function() { cleanupAfterEach(); /** * Does a bunch of the shared busywork of setting up a graph and changing its visibility. * @param {boolean} startingVisibility The starting visibility of all series on the graph * @param {*[]} setVisibilityArgs An array of arguments to be passed directly to setVisibility() * @returns {string} The output of Util.getLegend() called after the visibility is set */ var getVisibleSeries = function(startingVisibility, setVisibilityArgs) { var opts = { width: 480, height: 320, labels: ['x', 'A', 'B', 'C', 'D', 'E'], legend: 'always', visibility: [] }; // set the starting visibility var numSeries = opts.labels.length - 1; for(var i = 0; i < numSeries; i++) { opts.visibility[i] = startingVisibility; } var data = []; for (var j = 0; j < 10; j++) { data.push([j, 1, 2, 3, 4, 5]); } var graph = document.getElementById("graph"); var g = new Dygraph(graph, data, opts); g.setVisibility.apply(g, setVisibilityArgs); return Util.getLegend(); }; it('testDefaultCases', function() { assert.equal(' A B C D E', getVisibleSeries(true, [[], true])); assert.equal('', getVisibleSeries(false, [[], true])); }); it('testSingleSeriesHide', function() { assert.equal(' A C D E', getVisibleSeries(true, [1, false])); }); it('testSingleSeriesShow', function() { assert.equal(' E', getVisibleSeries(false, [4, true])); }); it('testMultiSeriesHide', function() { assert.equal(' A E', getVisibleSeries(true, [[1,2,3], false])); }); it('testMultiSeriesShow', function() { assert.equal(' B D', getVisibleSeries(false, [[1,3], true])); }); it('testObjectSeriesShowAndHide', function() { assert.equal(' B D', getVisibleSeries(false, [{1:true, 2:false, 3:true}, null])); }); it('testBooleanArraySeriesShowAndHide', function() { assert.equal(' B D', getVisibleSeries(false, [[false, true, false, true], null])); }); }); dygraphs-2.2.1/auto_tests/tests/xhr.js010064400000000000000000000033341437353256000147720ustar00/** * @fileoverview Tests involving issuing XHRs for data. * * Note that these tests must be run with an HTTP server. * XHRs can't be issued from file:/// URLs. * This can be done with * * npm install http-server * http-server -p 8081 * open http://localhost:8081/auto_tests/runner.html * */ import Dygraph from '../../src/dygraph'; import Util from './Util'; import 'core-js/es/promise'; function dygraphPromise(div, data, opts) { return new Promise((resolve, reject) => { const g = new Dygraph(div, data, opts); g.ready(() => resolve(g)); }); } describe("xhr", () => { it('should issue XHRs for CSV data', () => { return dygraphPromise('graph', 'data/sample.csv').then(g => { assert.isNotNull(g); assert.equal(g.numRows(), 4); assert.equal(g.numColumns(), 3); }); }); it('should warn on out-of-order CSV data', () => { const calls = {}; const restore = Util.captureConsole(calls); return dygraphPromise('graph', 'data/out-of-order.csv').then(g => { restore(); assert.isNotNull(g); assert.equal(g.numRows(), 4); assert.equal(g.numColumns(), 3); assert.equal(calls.warn.length, 1); assert(/out of order/.exec(calls.warn[0])); }, e => { restore(); return Promise.reject(e); }); }); it('should warn on out-of-order CSV data with dates', () => { const calls = {}; const restore = Util.captureConsole(calls); return dygraphPromise('graph', 'data/out-of-order-dates.csv').then(g => { restore(); assert.isNotNull(g); assert.equal(g.numRows(), 8); assert.equal(g.numColumns(), 5); assert.equal(calls.warn.length, 1); assert(/out of order/.exec(calls.warn[0])); }, e => { restore(); return Promise.reject(e); }); }); }); dygraphs-2.2.1/babel.config.json010064400000000000000000000004451437353256000135030ustar00{ "plugins": [ "@babel/plugin-transform-strict-mode", "add-module-exports" ], "presets": [ [ "@babel/env", { "bugfixes": true, "exclude": [ "@babel/plugin-transform-typeof-symbol" ], "modules": "auto" } ] ] } dygraphs-2.2.1/common/cursor-eraser.png010064400000000000000000000011121437353256000150610ustar00PNG  IHDR DPLTE5q6q5r;w:x;x:z={=|>}@}DQTU[[\^ֈ(ֈ)։)׊(׊*׋+؋)،,،-ڍ)܏*ܑ*ٓ4ٔ5ߓ+ܔ2ߔ+˙@͙=+͚=Κ<ۗ8+ݚ9ܛ<,ݝ?-.0-./0-./,--ޠBߡCߥF^abehjjuv݃݅ޅ߅hqEtRNS@fIDAT8˝V0Q "*""(n2nywQr:4`n?9Mb~eh'l:[;Pei능 BVH|m&((]0"by Q*_1uDQĔ'`7<^?xHJ4>rξ.o{/eyCpџ["M )Nnz P}9ssf݃ᇱGkgMIENDB`dygraphs-2.2.1/common/cursor-pencil.png010064400000000000000000000014661437353256000150660ustar00PNG  IHDR DPLTE94+>=!Ξ2@yA ֞Z-[)>0|BrSWUE:7殪̘X.}L6|{4nVv07+k]` 򦺺m0S+ @%7!IKy-eL4<P/<P;{i(X_WC?C?D<<$D4<HW.`dƗru_IENDB`dygraphs-2.2.1/common/dollar.png010064400000000000000000000007131437353256000135500ustar00PNG  IHDR1PLTE  $$$$%%''++--//..115544667788;;AAGGJJIIIINNQQQQRRSSqqrr~~Ŋլٲܺ_tRNS@fIDATӅ0  "NўWwz4<*$uo7яBf@,;rVVEtn\ ZZE[ QB8rgH233 !d&/}>;&?~, vY|t/<- ^?[(gye˖}'<m>ӧ]Y 6[`{&Lcx0 $漵rɆWWʤ1nOPRwE]ٺQT}jCFk6]).PzDŽ@}c +s#d"E<0OO[0Mw{z}r2` +A6ŕ+ .:? P/7 *~q l62ϝ[~}Xhhccm] fۼ 6]i7f(}&%92zw?2=-|0LKM&\AXm6'  /PCӕ<jN ɠU]< *dAS^:22tu^h-|3%yZpH(TDc'( *ey?qD3c y"} L->=u-ۢC m3Q7P vzlA#bSX:t?ќlkkY3 Dh f+zYIRݠ&>@i@`~r?s՛g87cFInܘN C-NBCFC \{jhjvNdzA8O`V!&mJU{`n ע[D62̟ emV|J|LB츀Zs0@H&Sn\>p r-rLW 3߼٥M2HĊ.q+5:=XFW#e;ˠ@*P3r9D7&f`k_ΐLŹ;^j@W\%^ӅD7o6'& RG qdyGL2L Q_!>Lܜ=-QpF/y$Qp,2k: (1UTK$t  bSE u:ⷑٙ~F.%j%=El ;AS =9:@&@(Bqfg6y4LK쵱pPho^,(#H:u0O@w{ʏF/t%{1$lXIS(\0,׽?Gͳ1"s܏ݲWz[ Ue#.b*d?ӡWW81@k[ ̒K`>Y~N0vE>0p#!mJl{9\TyuEC@g n|u1;-@ }xtC# 嫭BG={&g 'Pb0 4\J."0HvEu IMz*)HIh\mP 0)M;Iޫ9I ac~bũAnGE7xMѼϟ"~nBF.c݅oݺDfp @@iH)jOˊ[I/"gA2_c|Ұ@>mht`pdqf=n!~hIIEo޼>#[$CK-$hMMymߡXt)XF(@0ǨU6u5h^Yl\l"1CL pGχWt~_({NnHiqJͥkVU/c`^ Ce2F[Wm-pbٜ|GڞtZ<TG;}/@ߺG.xZo7|XWc2~`cX0IZ1>.f]g G#P߫Qk)5{Zު_dGZp٦E;3A`/g%P[1p%`ܼ%3Y*vy(ptR +h)-92p@>1 f.[ٳ!x:=upyҾ % E*QAP ZTљg95}G۵[&=πXG1l73h£x? S.*u )A#OA9c0FFaXWq9*S?RMEes_@ф.S:Jؼ Zu닌֪R9G ?RHFG|uf,h 珶kca5ADq a47cF0.x(l+7 Z־^SU?b$RgȉI%'v0!8TEQ-Ӥ%p @oQP&{@* (q?>[7iRن&ٌUB1yf+=G d?{L.|Gȇr f7t: BDD5 +dv:xk(1VX2}ڜ6 kKY*˹`텺c LwZgJ^ucz\_âU6bDA+Z6?VsG3W@T3 -+mܭo*d*cF?rQк۩rGiQh9h^҈ΉĠё־#91*b?Hý Zuc?*Hclb<0M2 7L1~>,hىAhQE?jkusb #G%Mr?(0^ (E9S(04x{`jdž'J.1)Қ^iJ

dygraphs Annotations

dygraphs lets you add annotations (markers) to individual points on your chart. These markers have a short bit of text or an icon that's displayed over the chart, plus a longer description that appears when you hover over them.

Demo: Dow Jones Industrial Average

Adding Annotations

There are two methods which are used to add, remove and modify annotations:

setAnnotations(array) Set the list of annotations currently displayed. This overwrites existing annotations and redraws the dygraph.
annotations Returns an array of the currently-displayed annotations.

Calling dygraph.setAnnotations(dygraph.annotations()) is a no-op: it simply causes the chart to refresh.

Let's say we have a simple chart and wish to add an annotation. Here's how:

HTML

<script type="text/javascript">
Dygraph.onDOMready(function onDOMready() {
  g = new Dygraph(
    document.getElementById("graphdiv"),
    "Date,Temperature\n" +
    "2008-05-07,75\n" +
    "2008-05-08,70\n" +
    "2008-05-09,80\n"
  );

  g.ready(function() {
    g.setAnnotations([
    {
      series: "Temperature",
      x: "2008-05-08",
      shortText: "L",
      text: "Coldest Day"
    }
    ]);
  });
});
</script>

OUTPUT

Annotations are JavaScript dictionaries. The series and either x or xval fields are required: they indicate which point the annotation should be attached to. If specified, shortText will appear on the annotation "flag". If you don't specify shortText, you can specify icon instead to display a small picture. The text parameter specifies hovertext. If you highlight the annotation and leave the mouse still, it will appear.

If you are using the native data format, you need to pass in a numeric value for the xval field. For a numeric x axis, simply pass in the x value of the data point on which you wish to attach the annotation. For a Date axis, pass in milliseconds since the epoch, for example from Date.parse('YYYY/MM/DD'), dateObject.getTime() or moment().valueOf(). As an alternative, use the x field, see table below.

Modifying Annotations

To remove or modify existing annotations, call the annotations method to get an array of annotations. Modify that array, then pass it back in to setAnnotations. For example, this code deletes the second annotation and changes the series to which the first is attached:

var annotations = g.annotations();
annotations.splice(1,1);  // remove the second annotation
annotations[0].series = "Series 2";
g.setAnnotations(annotations);  // causes a redraw

For a more real-life example, see the annotations demo

Annotations and Data Sources

When you pass a URL as the data source to dygraphs, it must issue a request for the data before drawing the chart. This means that the chart data is not yet available immediately after you call new Dygraph and so the call to g.setAnnotations will fail. The best way around this is to use the ready() method:

g = new Dygraph(div, "path/to/data.csv");
g.ready(function() {
  // This is called when data.csv comes back and the chart draws.
  g.setAnnotations([
    …
  ]);
});

Annotations property reference

These properties can all be set in the dictionary for an annotation. The use of each one is demonstrated on the annotations demo page.

PropertyDescription
series(required) The name of the series to which the annotated point belongs.
x(required unless xval is given) The x value of the point. This should be the same as the value you specified in your CSV file, e.g. "2010-09-13". As an alternative, use the xval field.
xval(required unless x is given) The numeric x value of the point, milliseconds since the epoch for a Date x axis (see above). As an alternative, use the x field.
shortTextText that will appear on the annotation's flag.
textA longer description of the annotation which will appear when the user hovers over it.
iconSpecify in place of shortText to mark the annotation with an image rather than text. If you specify this, you must specify width and height.
widthWidth (in pixels) of the annotation flag or icon.
heightHeight (in pixels) of the annotation flag or icon.
cssClassCSS class to use for styling the annotation.
tickHeightHeight of the tick mark (in pixels) connecting the point to its flag or icon.
tickWidthWidth of the tick mark connecting the point to its flag or icon.
tickColorColor of the tick mark connecting the point to its flag or icon.
attachAtBottomIf true, attach annotations to the x-axis, rather than to actual points.
clickHandler See Handlers, below
mouseOverHandlerSee Handlers, below
mouseOutHandler See Handlers, below
dblClickHandler See Handlers, below

Annotation event handlers

dygraphs lets you attach event handlers to your annotations. These can be specified globally (for all annotations) or on a per-annotation basis:

Per-point fieldGlobal option
clickHandler annotationClickHandler
mouseOverHandlerannotationMouseOverHandler
mouseOutHandler annotationMouseOutHandler
dblClickHandler annotationDblClickHandler

These event handlers all take the same parameters:

g.updateOptions( {
  annotationClickHandler: function(annotation, point, dygraph, event) {
    // ... access/modify annotation.series, annotation.x, ...
  }
});

Again, check out the annotations demo for concrete examples of usage of all these handlers.

dygraphs-2.2.1/docs/changes.html010064400000000000000000000111741437353256000135260ustar00

Guide to making dygraphs changes

So you've made a change to dygraphs and would like to contribute it back to the open source project. Wonderful!

This is a step-by-step guide explaining how to do it.

dygraphs style

First of all, please try to follow the style of the existing dygraphs code. This will make the review process go much more smoothly.

A few salient points:

  1. We try to adhere to Google's JS style guide and would appreciate it if you try to as well. This means:
    • No tabs! Indent using two spaces.
    • Use camelCase for variable and function names.
    • Limit lines to 80 characters.
  2. Please run 'lint.sh' to see if you've introduced any new violations.
  3. If you've added a new feature, add a test for it (in the tests/ directory) or a gallery entry.
  4. If you've added an option, document it in dygraph-options-reference.js. You'll get lots of warnings if you don't.
  5. If you've fixed a bug or added a feature, add an auto_test for it.
    This ensures that we won't inadvertently break your feature in the future. To do this, either add to an existing auto_test in auto_tests/tests or run auto_tests/misc/new-test.sh your-test-name to create a new one. There are two easy ways to run tests:
    • You can run your auto_test in any browser by visiting auto_tests/misc/local.html. This allows you to debug your test, or test against a specific browser.
    • You can run your auto_test on the command-line by running ./test.sh. (It requires installing phantomjs on your computer.)

Sending a Pull Request

dygraphs is hosted on github, which uses a "pull request" model. They have a good writeup here. These instructions discuss dygraphs more specifically.

The list of steps may look a bit daunting, but it's not too bad, especially if you have any familiarity with git or github. If you run into any problems while following the instructions, feel free to contact dygraphs-users.

Why not just take patches? This process means less work for me (the maintainer) and it also results in your name appearing in the list of dygraphs commits. This lets you take credit for your work.

  1. Create an account on github. This is free, painless and will let you claim credit for your changes.
  2. Install git. github has a good writeup here.
  3. Create a fork of the dygraphs repository on github by clicking this link and then the "Fork" button.
  4. You should see a URL along the lines of git@github.com:yourname/dygraphs.git
    Copy this, open up a terminal and run
    git clone git@github.com:yourname/dygraphs.git
    This pulls the dygraphs code down onto your local disk.
  5. cd into the dygraphs directory and make your changes.
    If you've already got them somewhere else, just copy them over.
  6. Be a good citizen! Make sure your code follows the guidelines above.
    You'll have to do this before we accept your changes, so you may as well do it now
  7. Commit your changes locally: run
    git add .
    git commit
    Type in a description of your change. This will eventually appear in the dygraphs commit list.
  8. Push your changes to github by running
    git push
    This will send your changes to your forked repository on github.
  9. Go to your fork of dygraphs on github (i.e. github.com/yourname/dygraphs).
    Click the "Pull Request" button.
    This will send me an email with a pointer to your changes.
  10. We'll review your changes and (unless your code is perfect!) give you some feedback. Make these suggested changes in your local git client and re-run "git commit" and "git push" so that we can see them.
  11. Once your change is ready, we'll pull it into the main dygraphs repository and publish it to the web.
dygraphs-2.2.1/docs/common012077700000000000000000000000001437353256000140642../commonustar00dygraphs-2.2.1/docs/css.html010064400000000000000000000047261437353256000127130ustar00

CSS Reference

dygraphs charts are a combination of DOM elements and pixels on a <canvas>. The parts that are DOM elements can be styled using CSS.

As a general rule of thumb, all the text on a chart (the legend, the axis labels, the chart labels) can be styled. The data series and gridlines are drawn on the canvas and must by styled using dygraphs options.

This chart shows the CSS classes for chart labels:

The CSS classes for the chart labels are:

  • Title: .dygraph-label.dygraph-title
  • x-axis label: .dygraph-label.dygraph-xlabel
  • y-axis label: .dygraph-label.dygraph-ylabel
  • y2-axis label: .dygraph-label.dygraph-y2label

The axis labels ("2000", "4000", "6000", … for the y-axis and "1920", "1930", "1940", … for the x-axis) also get CSS classes:

  • x-axis: .dygraph-axis-label.dygraph-axis-label-x
  • y-axis: .dygraph-axis-label.dygraph-axis-label-y
  • y2-axis: .dygraph-axis-label.dygraph-axis-label-y.dygraph-axis-label-y2

The legend has the .dygraph-legend class. When using highlightSeriesOpts, the selected series' <span> gets a .highlight class. This can be used to show only a single series in the legend.

For CSS classes and annotations, see the annotations documentation.

dygraphs-2.2.1/docs/data.html010064400000000000000000000274301437353256000130310ustar00

dygraphs Data Format

When you create a Dygraph object, your code looks something like this:

g = new Dygraph(document.getElementById("div"), data, { options });

This document is about what you can put in the data parameter.

There are five types of input that dygraphs will accept:

  1. CSV data
  2. URL
  3. array (native format)
  4. function
  5. DataTable

These are all discussed below. If you're trying to debug why your input won't parse, check the JS error console. dygraphs tries to log informative errors explaining what's wrong with your data, and these can often point you in the right direction.

There are several options which affect how your input data is interpreted. These are:

  • xValueParser affects CSV only.
  • errorBars affects all input types.
  • customBars affects all input types.
  • fractions affects all input types.
  • labels affects all input types.

CSV

Here's an example of what CSV data should look like:

Date,Series1,Series2
2009/07/12,100,200  # comments are OK on data lines
2009/07/19,150,201

"CSV" is actually a bit of a misnomer: the data can be tab-delimited, too. The delimiter is set by the delimiter option. It default to ",". If no delimiter is found in the first row, it switches over to tab.

CSV parsing can be split into three parts: headers, x-value and y-values.

Headers

If you don't specify the labels option, dygraphs will look at the first line of your CSV data to get the labels. If you see numbers for series labels when you hover over the dygraph, it’s likely because your first line contains data but is being parsed as a label. The solution is to either add a header line or specify the labels like this:

new Dygraph(el, "2009/07/12,100,200\n" + "2009/07/19,150,201\n", { labels: [ "Date", "Series1", "Series2" ] });

x-values

Once the headers are parsed, dygraphs needs to determine what the type of the x values is. They're either dates or numbers. To make this determination, it looks at the first column of the first row ("2009/07/12" in the example above). Here's the heuristic: if it contains a '-' or a '/', or otherwise doesn’t parse as a float, then it’s a date. Otherwise, it’s a number.

Once the type is determined, that doesn't mean all the values will parse correctly. The general rule is:

  • For dates, your strings have to be parseable by Date.parse.
  • For numbers, your strings have to be parseable by parseFloat.

You can manually verify this using a JavaScript console. If a value doesn't parse, dygraphs will put a warning about it on your console. But beware: different browsers support different date formats!

Here are some valid date formats:

  • 2009-07-12
  • 2009/07/12
  • 2009/07/12 12
  • 2009/07/12 12:34
  • 2009/07/12 12:34:56

If you specify the xValueParser option, then all this detection is bypassed and your function is called instead. Your parser function takes in a string and needs to return a number. For dates/times, you should return milliseconds since epoch. You may also want to specify a few other options to make sure that everything gets displayed properly.

Here's code which parses a CSV file with unix timestamps in the first column:

new Dygraph(el, "Date,Series1,Series2\n" + "1247382000,100,200\n" + "1247986800,150,201\n", { axis : { x : { valueFormatter: Dygraph.dateString_, valueParser: function(x) { return 1000*parseInt(x); }, ticker: Dygraph.dateTicker } } });

y-values

Dependent (y-axis) values are simpler than x-values because they're always numbers. The complexity here comes from the various ways that you can specify the uncertainty in your measurements.

If your y-values are just numbers, then they need to be parseable by JavaScript's parseFloat function. Acceptable formats include:

  • 12
  • -12
  • 12.
  • 12.3
  • 1.24e+1
  • -1.24e+1

If you have missing data, just leave the column blank (your CSV file will probably contain a ",," in it).

If your numbers have uncertainty associated with them, then there are three basic ways to express this: using fractions, standard deviations or explicit ranges.

Fractions

If you specify the fractions option, then your data will all be interpreted as ratios between zero and one. This is often the case if you're plotting a percentage.

new Dygraph(el, "X,Frac1,Frac2\n" + "1,1/2,3/4\n"+ "2,1/3,2/3\n"+ "3,2/3,17/49\n"+ "4,25/30,100/200", { fractions: true });

Why not just divide the fractions out yourself? There are two attractive reasons not to:

  • If you set both fractions and errorBars, then the denominator is interpreted as a sample size and dygraphs will plot Wilson binomial proportion confidence intervals around each point.
  • If you set showRoller, then dygraphs will combine the values as fractions. If two point are a/b and c/d, it will plot (a+b) / (c+d) rather than (a/b + c/d) / 2, which is what you'd get if you divided the fractions through. This will also shrink the confidence intervals.
Standard Deviations

Often you have a measurement and also a measure of its uncertainty: a standard deviation. If you specify the errorBars option, dygraphs will look for alternating value and standard deviation columns in your CSV data and plot them as high/low bands. Here's what it should look like:

new Dygraph(el, "X,Y1,Y2\n" + "1,10,5,20,5\n" + "2,12,5,22,5\n", { errorBars: true });

The "5" values are standard deviations. When each point is plotted, a 2-standard deviation region around it is shaded, resulting in a 95% confidence interval. If you want more or less confidence, you can set the sigma option to something other than 2.0.

When you roll data with standard deviations, dygraphs will plot the average of your values in each rolling period and the RMS value of your standard deviations: sqrt(std1 + std2 + std3 + ... + stdN)/N.

Note that this function is called errorBars for hysterical raisins; these are drawn as high/low bands of confidence intervals around the graph lines, not as actual error bars vertically bisecting the data points.

Custom high/low bands

Sometimes your data has asymetric uncertainty or you want to specify something else with the high/low bands. One example of this is the "temperatures" demo on the dygraphs home page., where the point is the daily average and the bands denote the low and high temperatures for the day.

To specify this format, set the customBars option. Your CSV values should each be three numbers separated by semicolons ("low;mid;high"). Here's an example:

new Dygraph(el, "X,Y1,Y2\n" + "1,10;20;30,20;5;25\n" + "2,10;25;35,20;10;25\n", { customBars: true });

The middle value need not lie between the low and high values. If you set a rolling period, the three values will all be averaged independently.

URL

If you pass in a URL, dygraphs will issue an XMLHttpRequest for it and attempt to parse the returned data as CSV.

Common problems. Make sure the URL is accessible and returns data in text format (as opposed to a CSV file with an HTML header). You can see what the response looks like by checking your JS console or by requesting the URL yourself.

Array (native format)

If you'll be constructing your data set from a server-side program (or from JavaScript) then you're better off producing an array than CSV data. This saves the cost of parsing the CSV data and also avoids common parser errors.

The downside is that it’s harder to look at your data (you'll need to use a JS debugger) and that the data format is a bit less clear for values with uncertainties.

Here's an example of "native format":

new Dygraph(document.getElementById("graphdiv2"), [ [1,10,100], [2,20,80], [3,50,60], [4,70,80] ], { labels: [ "x", "A", "B" ] });

Headers

Headers for native format must be specified via the labels option. There's no other way to set them.

x-values

If you want your x-values to be dates, you'll need to use specify a Date object in the first column. Otherwise, specify a number. Here's a sample array with dates on the x-axis:

[ [ new Date("2009/07/12"), 100, 200 ], [ new Date("2009/07/19"), 150, 220 ] ]

y-values

You can specify errorBars, fractions or customBars with the array format. If you specify any of these, the values become arrays (rather than numbers). Here's what the format looks like for each one:

errorBars: [x, [value1, std1], [value2, std2], ...] fractions: [x, [num1, den1], [num2, den2], ...] customBars: [x, [low1, val1, high1], [low2, val2, high2], ...]

To specify missing data, set the value to null or NaN. You may not set a value inside an array to null or NaN. Use null or NaN instead of the entire array. The only difference between the two is when the option connectSeparatedPoints true. In that case, the gaps created by nulls are filled in, and gaps created by NaNs are preserved.

Functions

You can specify a function that returns any of the other types. If x is a valid piece of dygraphs input, then so is

function() { return x; } Functions can return strings, arrays, data tables, URLs, or any other data type.

DataTable

You can also specify a Google Visualization Library DataTable object as your input data. This lets you easily switch between dygraphs and other gviz visualizations such as the Annotated Timeline. It also lets you embed a Dygraph in a Google Spreadsheet.

You'll need to set your first column's type to one of "number", "date" or "datetime".

DataTable TODO:
- When to use Dygraph.GvizWrapper
- how to specify fractions
- how to specify missing data
- how to specify value + std. dev.
- how to specify [low, middle, high]
- walkthrough of embedding a gadget in google docs/on a web page
- walkthrough of using std. dev. in a spreadsheet chart
dygraphs-2.2.1/docs/datahandler-proposal.pdf010064400000000000000000005573151437353256000160430ustar00%PDF-1.6 %äüöß 2 0 obj <> stream xK#7}WVCc ryAHdِZjҌEHGR}*=Jդտ+R0]V֬֊VY|UɥbUӪZzwF8(bTu=DdӧQ}-^|?u݇Ͽ{'[VvxZ1Ueew} Ͽ/vC?5erNQVh=QPJ;\+S@/NXb:U]_mڙ6?ղ\?7+f5i_Tw~c/ikحy7fjZB]oAORnnHV5}Q2d[(U|xhI)5jS"QR?p3[vIkw|[2o 3 s֬j k#2Xtv.y82L_M2Qd #7B6 8 pX勈ưWpU"8(# 0AVvO,X-8F&kK; ֽFjۊ,%ƳBܡæs[dםmj!co%/bjT&}Vd3XHcsI:. #7:,Ê5溯bTZcE'"SS@I<`vLXȤ4yk&`;'s 2=87CqC )ܯ8 0!=ME>;洽l틡kzu(eq,q&L eO8k,Aьgff,r[% mB4Ulk0Td- a 3 ϔ{RK׸lJ%E/s$ZѪy^ά>U39'E[k=\dy ұcqxM \tp7dkĺfإf(nDcb4Ŷ o_gD) lXy[yߓBCFuœ,Ӎ՚䉚9+z3t/oAg\dLq\&K1$ R*&<-܃p 75*^0TSN³*)vZ»͜޸Gsyۣ ŴP0 h2RrtyH0ABڸZ 5D4cԋex4v~Ba'DEo)9lcvH4P:O"YȊ!.H36-x bkAo]X5xɮ=GN?d5 JW"߫;P)5 TT]UdW{P@cs_ u ݫ đ}.KMEy{Ј1L.6⳻^L2'Ss5F]FKOE'z\_&Aܪ=\oq;beE|e"# >楹 =Ih7I-ӷiE^H+DFǛYV%]%ltj5o9r<[_akS\<cL߉EUsBcN@`N`ξc(dnt6\;</#V[rg 952߃@b0()JLSE3XGDg\R{=]=/*uyCіKoE@KԚI"*Y,o"<+ RI!х pJ+aq!&܂(ؿ<RX[cZ}dr[97.p黮" Bm(W;OEy:a #ƪW N6X;͙deXk']Jvy-2 P71FeI9PAP& P(0eܔ9@b10=xHb̝_S%k\=,ڏCJ( *#w)& 2쿊u.l=1tɸ0G`wg]%ڂoNd_U_bc21a/>'3,N%$xujDZM@h( oBMUUʎƿ*r|g#׬G9q9;U`$SVOS m7|dC9棓Lzۋ?rr{2xA PJr5 GBOVO\r9PpSA$)q3OEyˑbR RV_Q* endstream endobj 3 0 obj 2447 endobj 5 0 obj <> stream xˮ+mﯘu5`8~EtqE R_(Jۧ )[:ꠇwԠƃav0{=/~a0ax=*}2GeNiozj:QO}fw{}Q$b=$ĀUG+AwAr1AkxRA1KwT!N3v{KO!! c&gLe&khL3ޘ<3%@\"&W޲3 ~ւ" ^"OGnBCūG9J4%zp3}旈a%A/u)w"D Lk<>rjc1Q[;gѤq#OhuAVik= 8<&\qH7fU VTxv9.m!VqEMQ(|IEXmybcj @VqQRWN^y)(N1z;l/[ƇϪ^6L~4yO(&cC%!24ۅ,GNKӌ9#HRd\ixxAإV  Ŝ*QmԵo"%2&9&- _o[pb96Ͼ>klHC\(6M<\Ř rL&b 5CjH9І RP^ׁI 'Jv!R"DwNlWZ[bJMtX'9t LOX8M1`ZA!ƈ?IeP4HEGX It +O VL@Rgŷѥ9̱A[ac#;ΛrRx{bko*#‘3E:d 6(:_49I"8j0n7[պK֕œX &/`%}ע*AgZ8N#⃑z2RKI?[E@<S Kz)3 纘6SքPkmOH :wTƓWx}M.fk[.fFֹR"-7>M莻Z1і+͢%U[P65C/h`E'd[mwkIdA%:wmQM$#\c= M O }f>,7K9(krd٧EYtDdQp,EM.vd]Yb[^6W)q_zzD[%Z^L|BfYRGn/JIviSOݰ(U[·[nEf#7vN ϥk~y{f7lĉ. .-= i k$}V*^WZ;ȫ߫ǝN@=^<|1v,|Ô:o 5YgO .5HYwf`%K,RI홛ߙl4{AAкKn0:B}#Gx߫$ugaIx93>WqZàk? l/s'؛M d@Ht~o*[L-aoun&vHj 4]X) ]JڏeJ$P db6و4~JfM[uP*svl9vnWZuv@|Bo;=gyM"r)H(%B\}2:2%iWM§ς8NF:d3FI)tϋ=!-H.]6Jj!Ka`Q11M]s% ٸj!??|/'r4TacOT+ x答*C>L&{0R* ߝ]pj @v_ӱ3'M{^u.䙃\rԔ\e-vY&w"idQ W#Ψ)w] Shv䰟MW"XVnNp[U(əQ7ծ6ŝi2∓p;Q؍Kdݰ\Cٷ^lܵgM4o2,=:0) tq=.7ut:hJu3$b^ALx7^a- ߂n<A[ ߂\3-BXαrwo~W iw^v9 )Q} *EaOe줻#M'ܒѴ=5T.=#<6 o-ch_oFS<ȌNrV;5(y4W7Fb9kXQ!lK7sYMH=ogJqXoJz ?)J.7FŖsDGEЦD;P PItS%P]> stream xZۋs _q 9eЇv^ .[?|bd.aĒeY~"f$&1 'Y:3y 0?Mь?v痝YN_|xqA(!', }>ND2#R9>f>g]ip:Aș׸|3p9p)RC2tNû >n_p'L$9;#<~J "(uga_oe@Yq4XxH`5#13p48ܤ!8v05#r[Jya4ޢ0R{QZ2{d"OijE-?~6#p)3H-ڷD__N?}\10>j(+E{e6`:j,Jâ,~0k]&{"@Rx'KN씮hylFBۖ=e@wh(0tc9q?n@0QS1^|L/IB۶ӥg'G_>D6P:lSݰƺӦJ"v,h+I| Ɨ5dMFpEBozVpֿN+rzzvN$ PTjA9^Av@*E&־Y_w4d Lh{]owZhxpZT Lh#[xB~F\S>bE≎#zhlI:ndQJ^7LknMVzd(X>'&%pQ |(1+}}mi QՏtjO:$q#IFt*>034?Bn5[!$HbG:-zPay09ې%+JF!7^'ћ[!qK1V:}ddy+ǜɏ{z0O.RPP" P!`W B\Ó Fh-p5߰ D,;RD%>PoT, 0[$ڹJRK*nk.@US7U(nt J7ֵOYrWOT-I"HGk?InBBcibQ "@9ԯxXNbQ`tfTQS hEAu7RGvp9'9}kę̽r79lʀ\\ 1BZz~BjeIXؘdK/hEr7^?+ o OYMvwWpG$YlKp+Ԉ/Guu*v_Δw66:.vl Kc{(y|gh-K3Yte X2pąeU؀N0#}Z һ)WV?&S={XtVOriK+6)ނrVhSΓvpɇ[ UP%/j79,0s:lHmqV XV{MP3ӭQh.l"S4RJΩ+[') 㥘'cbX{V0kuu!_t̙(6e E7E+f?^|dIuI;y5?N6X׋6L_%֚;qЊNVM9Ap-cpM9x|Hk0Nx1n<7 hEA[k%S)ɇ|ckT2L9j9 peo''KaLK#D-RޟvR HrdJߙ<-lo-|Sh:nQՒ~iK_߯3VU1yF[{{mm4VpInM=( L``(I:]3%6Ŕ%r ? vjQLsLQ Fʁ"YXм0Q٬VExag (dP|]ڴcEnHY2iM&RZj}}7^Qr k5 {o -հGul|v~ؚy4(V];-P;{-`P8:p77kg8ꞃxga$Rn,/G1j7rtz@jT jr=GkDyf4 t2Ir׉:Ʉ^ ަ%T> endstream endobj 9 0 obj 2313 endobj 11 0 obj <> stream xˊ+u_uch}MҐ%$32f~?KRf`Ʒ]%d`drk<9`Odo878ޮoq:-9Ӳ-=|l]ٸ;1z9{6 cwocyV~|ayrB[O?Wu_fGha^ $zZ8 `nwS^yzS&9~^G+oGqf~`,?-K謼[3!NKK[= n)Phi'^]{ۻ]U} TB oqsMalKoow")t"$;ٔqgu`G]qOÔi"`l:,1di|qDŽ7V +4(7|si" }k a: #$*NL'd8"dx_g|a튕5˵'*c0-)!`]u Lk:1щ1C~@'dƖ;Ȅ]BeJ߀!tx^op=żl(@5 !x3à5ҹNqY pme F?tW%R*eY`WM)KW? J.W4z)+Bqm'0s}w֢ؗ^f9Ua3b IS+YLėLدk1<8 \`a68}RN:۲LƱ!˔Si2@&4a. ՓID Hwb/;N׼Jj61ŀH=m. ~,` ¹  9/\$uܽz hNn˻^u-Jub$52$}˖=&9 #),1,\[c(gF|xEAM0|M\D~KIfjlM3A=V(6"HEp<PNC7q~-J$|PwEj2[TMՐy zQEx.Zht} o'95ՆO3(eok8h:W.Cht) dU ܘޜ- MZX [t <>QX&_TPȇ'faMDDL[/&ґmˈ14bߢ(b7CkF@OfF /uLfb%'ML (d:v6P,W <]Vi-4qA? mpFXLXDt9oK~^^iw>,M4m'sLqt2 #[Ɩ6Eo`7DZ9+TpڡL\.!Kl!me$E OEorZQ#5?.ݿ{K0mR1%IL#[UIT YX,'`ZďDƦa'{ ҍڤ9l. W4>ŬYi ]ӧhT=d;aoŎMW|߲mmđ b.XŴy''db6 'P\U}ǬHxYɳSGl>?z!V?V'kbbVy Td6qqCC)YZ۴f^X^ @[޵~g C3mȠ/` 'wL!GwFαcRQwjs,Շx+$c[dbIADRiktloݕF^ThW,:AݺIJd%f+PU,9/aMU8n,c:6NTfPylWUc=8gR$TPc>+kⷑhEn9M}̡G%1\s֘|X&_6}`s0zxF'ڲ ]_9D"BRq:m/ЈkQNȀn94|QgLS:3HK,|h`@Yͮ]1^+![Wq%u<9q^YqJfِcÿT,B Kf6]䍮TDJxN)= ťt$]1#-tOHBZ=8>ʈ 7ky(ʀ: f>lIq2C~${Oyt(MI61\rOnky.䢻w1SF8[jt'4]"%]nA8Sõ :Ȯ<}jN:QjlT{[j],Hrͪ\FlR<sgpԏMVZT ͍=W3rײ9jd~ݪ:5a'9 ِأх?îc RCY`cj7U!nyE޹{N|<3 3T]9 r2Ș3n0;sjV|hW)}๷]AYʊm }A"`*w9} @ȂĘS2R7=jrI2 m_5tȆeV+GOI*cFݫ4u}^Tg$C1X^ZkɔFM<줧5H`.ͤz0fS(>C#ڨQwck@;O(!ve[p r52>Z"8t{K:.uew7/9'Rad 700 Ѫm~iG&;#`5wχw1W3Rs#gyTW|q</NDpELoԖ9Q`|[*?? endstream endobj 12 0 obj 3319 endobj 14 0 obj <> stream x\K#W@ZMI3@!pH "dkFz"{Y=(,;.~9u9:#OqDbZq^?M<v=x\EXzzReeo _Vk.v0nʗɥ[+)`aC?eL ff @#vBjrWׇ?>)3|*kP}Mٛ ׳Y x**cϞOH :8x39{6!~Xa aH(UEWd᚞RbD!`6R/i~t|}˿ry{}{"s/ɣZֳ}>]pxepXLux+|+?p5[Dwo2.l0{ )3vIH=JtQw-)~!]3ծ}Lbl-EX0(U>0٪|=#0uCl$1Ě~S(=E$Y9Dv8$Z3ev|X x~Qc*Y k,/JsuYD}{.y/caʷJFb)G)˴8MNɬq +JCLIY1.[Mlr^(_FP!q/%JY9)=K[L=0xC9đ*/:. 2 n̸xͨRb~0G8F WTĘy△R-'d y`uc n\$DL*\ CeMfെdY0˺A;hSӮ ecU&C1u㦰V:kM]gCmZ.{tYff6rHJ0~nXye[Y2\BN1 58kM8ic=~E:b"3D+/1+UQ>{:`^Ĉ䢡_mq<ɳ<~I,TK)7HHuIEQmfK\$čt'WX]X]@tř3µ6K*5lg&p~swmd(lV̘St 6=$MU zʋyR.}D$Dh_HFJJEqv3i#DALNDǠnu&il6G1Yr0 ž2;'mqI>odb1](>6v*#'FXU%Ѓ)0X{ D$?0b!%w~w9IY@Q_CNT/5|ѥ1P1m3K9j7"k+w`#B[}kjtNұm-i~?6W]9ZUZ i9`lPElZ!C M:pigm=[-حrM1mu[ jrEG &aNB>}0TAov)?HW*L0;fGUljΎvE* r>:9Y 5Ӟlpq֓L¯ 8dk"|dʨr*aM8*;BMHCCv ͽxj{6"c'pݬ '|įh=ӖWMYQ(s{dyR43ɛC"f̶jVw͂ t_fNKΜ^@N&@}&]CniBb1p7oL:|f<3D)jS֔ jP5AC^gWgK` ` (Q3*G7Jkeoc/J0E T$ltVgG?1n CN*?ϣZu^yl߮bP5mϫ endstream endobj 15 0 obj 3620 endobj 17 0 obj <> stream xɎ+>_s)dH#lCSU$cgX#IYON`9]`v2ӯzNK3rrYӟ?I/f?]N#Vqp  b̀CnUoFIl׫u5K ‡>;HOp|) \GZȦ#߅Sز F:-.RapĢӺ^T{G{0(P~^{/wBe8d2[x* cb L,MBs~cȆt^ \ĉjZ %0@Q,X8W=oթt*"5v"ف6[Rw̨Eaw;u_Z°NB`CtX&Gy,lR =3;,WJߢB~Ug2z_tJ$ɨ grʹªt!oEF)( 0شZ!+qDӾ'\<\Μ~@{6)s}{gtמ]26 H?X8^.4#越9DR;D?3NDg1[V,Y(܊D!r̾„y6{GDAVJ^j$zXG-_vJrKe P4xWgW<5MIx\&;mIЖD#R=9!(lLf:kgi-[%G{ L8cHfǘ󍆞ģ^`!\v٣uEZ>4Gʭr4~VQLȀDAKX@0A>rRbO2XD,ܥi+ZR#W7`$Bl`4I]a`c!ϳTk!` ϣ+f *HVUdQk1yKp[Ң ǀ-ޚ-QFl)NbJ%ZT̎-f/LzbsY.SiI&{1Kɰh+I`ژ5b8X2&{G7n?kQIǷ7& Û:EV~(pdQYÍ{!k}*sIQU< I'/݈:\YFD*iLb%aa(h}w{9id`Vݱ}!0KqאеPx^ &/~E9%2lROEW^n34ۄ,@}ȮӮj$nep$dވj?9HFUY=* H L'\8:q6qHihqV:a˰v\"urːsC?ՆFM75!ZJ -vYϼrzbv/teɎ9rȉpH`v4 VQ Cz|_ܤQ,70V,L^E_CZ2*'<@q`VdN13DUR埤`02\i͓ZdeJu]Ql͍<:ZW1.X5(k7u#3*!q6Xy9uQ*?ld'l2~\!婮raǵi+*;Y\f#о]8?\]+JL_c}Vta{fQF?us^LB[x";2!Ώ <*q8 б&2ʩr*QrۄE\r3u<:fFs&_m SYcE˴)27eTCw32.JL3]uTbz.I DɸG@3XMRc͐+Ƀ' Ct$`$:ds>j6GwYDɮ"`}j*!s+)=)у.f0eS@xgofAb;(tTxB 8v]pG~@+Cj$7Qk(Sma_`9JjAD][A9FQ~KsȝG)iE?[!7O㤽6v>ࡀyn+/j_ c)yA 5b&lnzI-J{e$7< ?*8-9mEWcnRXAMje]tHAb -(<῝ MG;+ endstream endobj 18 0 obj 3276 endobj 20 0 obj <> stream x[[+7 ~ϯBrl2c&[(B}(A閶?$3,Nvd-xF~ 68 V_2-6:uefFirÇ=H9q [Vl~bdnV~F?bܭ^6$?g߽+1Gm0b__rŨ(Z< ʇ'(WM iP+EHe ޜFX<J $K?*bY`6,R(+B`^5J!o՘")n/$`1_K[zeF GiCRLÄn` K t|`]Cp9]:bt+[zSFȯOI!: Yg+iTGtx`!B/݌z#T`Q;-<%8 d<4kA3#0k>;O(1ttb"WMj"մe9&h ?C |[? ѹ7P*jcK 7s3VF An+\8>)ɒ4X40mLg*+Pg {,4 y$jJld2o6o~xϿܾ_^s[ϫ8'ѭ?-'ı#NVھviRFX|% b-ťfMEQERl#c\emTi,P: ZhNc,/j`AXdRccUԓ(z]$O3%k{$2QWHFpiڃ&?NӪNFOyGU{tܗp:lݵ=kŪq6=-FXtg'6nAn/mYP>r=i+y$rU{W!]2]T2V{.G>#~U{z4 ,) l+)S3&5$/0#`'.9y̏y?@&tx1~ ]Ubgb}H-{x" pi$vMح:#!'DKs&9ғ V0s嶂jKQn+9rSAyP嶂%r"cfj=mFXi4Xi4Xi TjWl>Fwfogu@d,=.Ďaawd dF،F-4uw2ŽF=?1ºq~6Kh%oBj}F9Ob4ˆO/7`_*t` F[-'J8Z-uup8ҵduc*0=U/G%,J`TJ:I!Mps:3Vs:6+VdQnGu El/1+|1i|1i|1Ayb{(.Dž3lt;z'ldq!v]#\%fG%)oh֗=N#Lm珄brH(v,ޓlܓ=?jtu젡~>ane9rt~:ySW`|>c,CixbF+q~ DydNyqPA̯;j^WV\8-GQ[NKh1=/Ԛ|ymDž j|KlX͔ڧCs+ODDѱ/ #subJt@1̤slcA;ߍqօ݇ƥuqj`@f cLѡ͉|< 4j]G]& ͍p,|Î oĉwS5V':t3+>W:qY!ГmP!ŗa4XNva>\6ŋ ;l'hd_`?19{a_(B*߹MJC Zc6Lz>dL 洶 r81U<> stream x[3ݿ;._ >9ЇЇ&/!%\tە9cjFsh<Gc؋PN|'aW13 Ii*|-|p'ASlNJ #nbĩ·$濿{{}!PDKJXRLIݬhYq=4pZ d8$?| ?XމAz|_-_ΡSBE*n3r%*/Wp8|I 8L֫ fMa2HT@9᫾z} 0 Akc@(ԼaLoD[\149;+'B贲^RGd缰{4]K@)+, PrrC"QBB$2O` K43Pi:|j,˴L\gLg8ֵ`\P6aTki``D"q\ϖBh߼}D\Dh^7ʣXu1+K`.{;ׁc{'@n;x~ǹ(Z+ oQZg#!|!JSRbճPk[('E@aŏ3%9*X,^rSՕӿ I_-1j$낥))4 M3@Ur4]^j!bnCѵz֐H ё.d)B;A&݌\fjժj*?`zKPIzZ˕6\%*sR_ˏ Yhg۵Xѵ3/,^0FQ;;|Xѱq(jC@>Dm7Ic~4nW>||v@0nI@\Y6IaPe#ʐ*ZnU>TH8Ap#BиA.U:{\4Ap# KU(ʇ|DT>js?UhpG*ʊ*ua}֋*Ɩ|+2I|t\TxE|4Y|T>hPC=n?Q>ѲY<%7ֿ?pn4jցLX(4EYMn#k jK\_U5܆.oiaYjSA/(|[}QXUP\9RmG\ǀ.aj5|*H mT.v*}l"=h6 \Zl}YPMJ/)0&.l+C©Qr\1,4*-4j+ ~L.=y V(oTq\Q<ž n =BF-Q8{VSNj0WvY*-Tw[j'&%Ӓu{ElsVEA'f7۽V9rV`)lL}ޒṘ'C\խ8M?sAתTrY?* P;N0-uy [&9uO7.isQYnG J>aHK*bߢbtro^Y&I$#09'.ˠl#kJ>3Y;Vis 3, kEkTZ 2WzW Tj7v#d/LLJ XiNBa /zizuTZ}#cd 1ߞ2٤X=SC'zִ)W.> stream xK3_s;z 0^CrzҴ@/hϯHAi$ػE5#R|hgy8P.&{NVy9՛9Jy|OB]ԓX.')d{qOe'o"aODlĉt|t$UX$*D@/%@Eh H`) &eġ슲0~Ƶx+{'؄9(_NF" D箿F?'~<:-IS5AgEN[_U[󦬀=- 3a2z`K{NW͘ Ax٬LK#J CrLH"]E%*kdtկ.hIyfpHfp;dTFr>h !\ݰULPz#:;,R(PY:\ZSz2[K]4 2,_L/P*d>pԧ}.tdbia6z *J*5akQa4K'rm⢸͑W ,)/GG/7U ft~%3Ut3u=^-QyG8B;J.zk`gMBG.2oLM|Vhc٭,*e[YC%5xm5*{81`搜^K7} hcW Ll43|BL8c{`W0FOHPs4rge i:ۧW &ۨsW=*١90όuZV â\1xT$CO3TU;@byα`NW-GCtC\e.y8LԋgmxF.0%68%&SM,f #cy,@r~G_|Ǔ?|_JB_zDd*~ `pǯߟ ZL|9X-=Xd %0l9@)I .YY18 ); {wC/:2(@!$ΎCx*BCY9$ [5h<.\ /+ʴ %8W [oïOO_=~&.rlFj"/χ׿3':Mx`{.\{=p`#MO\ٝJ`}l|ǭ3o.$6oL}3PciG7 XN3LK5.tWIm0vm )<Euy[+ǚcz4GuEklWr̲4F, 9*۫mǏ;A2bM*/*X`j @l cKwa$\gƍ= (#Fi72Qa]DH7>QQ僅ktXMx6i3ŏ)Ա75$H]MM?ִ'k4 Y$uE6^${1۠uyx騡[ !nBSMM$=JWYzݝJ"+ ){ܽJo,&QyG͏ĬųCMWE+bJV"%Y )srNr'',P/nrSr*{yss ϩt9LRNr*ʩdr*]˩ v'Trl(mWNEn |kߑS>ѳYpwq4]MRnzJp~XHƊmBj>$n&hqs)| KPx!˰»p=cX{XMOJ- 3#{rWM[ynXMOʫ+m*7rjH}kU^Qs+r"XeRV"bSg1lYΣFLl8" o]V)4Vd*HUX*Ύ)*KVgS]PŪ*,VZc!73Sx̅i*^j~ Zͬ`,x}|Yn+bKQ|aQ(ձgtT]@IxFj[ =Ӓupjk:Ѩ6m*VUe/5=7RQEmuB_/(B)AjD 䱂<*+A]/̬1/ ncUx.1b]axo+tk*d*^Vl[J6-)u̩K,EK^xWj=VZANmO* 2IV-[kŢ+Wɯ[e915j7*p6Lu}:t{}ᵂ|cWY9T')2nh֩6P ^UP7T]6AlGlnYvC$C[S}dCohR5UFV'$R_n `X';KݍxIbzԜS,šy0^6zd()r ǘwu $j<GYP ŧAn7GO}(-,;j⥿4Es4AI(0ŝZ|<cˌ&)GcdGBe6}4뒳֢4x,\ٷ$s]*ߴN3ݷǓδgo,LrF{~vѠ M+9yT2q!-l X+;4:bg}V,*JZmm2C]Y^Nh K6Ppe!>`#؁ x\~Đ)3{#m) Ѕňc̵ ]#Q[*ΊQNj+5l.ӱ;$+dL 5׍j5VMݼ h̤H qhսNWn;r.XN+ݚ֡iJ=Ld'g`Y endstream endobj 27 0 obj 3451 endobj 29 0 obj <> stream xMO=kC1 +nDdcHI0t(ҏ%$K~tt/&]`bͰH!oy8(<^Hɺ1Oh_ Z_"SA׶CTqVYh2g{s+QpD N4m2oKCNۼ^`K4 endstream endobj 30 0 obj 182 endobj 31 0 obj <> stream x{]uac14:oi26Fq ˩n/SqW>rT~i:( :|1PIK;oQ޻nút7DU^N5~\:ЩrOչȨgJn*s#;jrv*lfL]O~wKT;U+N~ǻݮr;ՠo^OչntTɞ6}=UnIt k)6Pр:] ;wKvr<:DH|1^+YR5S=n˹oTyuFxSUoN _e!N5?=2 >:U: uHjy'Jo?%at*t^ TЩNB*N~Fu`cw_oޢTS-r7lOٙMLu}bu*JSAt*`*㘯7ɵݗR4SѢSUg\YFDj* "iW- Fbd*uT@+H6en:UT+SUTg}EtMy7N٩rOP5 FbNОzvޟv ZhXڸq}ޭSUKZfQy/n9mڴ)ozw+2;nX "4Pڕ:_ggoYS @\fNjtYvTetuS9u\1q3UN%\wkTҩlrOvؑ7;#Sګ)TjN֣ҟ:R}Guh@w:tKS~UZ]ְNENU":USTDգN8oq˖-۶myvYgTIYK>6kNjmdCS@;UsT gtөSuSUO"6gqΝ;֮]>= *y+;iE˦ڇutө*-&_'tQ:UTөөSeݳNS%ZI]S-|#s+S%.ҩ KNU>%N`:UttTD媫[x衇{'Xa?o&ME*T9*1zg٥S@vW%ҩLN՝NU=_˟3̺'X*% S)+]-fHy_?F㹾qs9o_w_rV:UTөөSe~FW搀9:tI'tT>tөSuSUO"*kSufVTPO5:UTөөS;nǎ 3uϮ:]^Z7n )qvlp06mt*ԀN~4}{_ݷk.O:U^Ourx;_gV./MY?O?='ҩS:YA꾗R{7sݺu?mn??i]~i:( :1Td뗦ҩXЩd_JNcb@h~i:( : It*4/t*&뗦ҩXЩd_JNo|?:}P T`SO 3Ϭ{4ܒ%Kv2==]4h8 T@ :!ЩN@t*BS T@ :!ЩN@t*BS ө~=MgzG>=Mq@ :!ЩN@t*BS T@\}=\i# S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, T<șgY4h8N@t*BS T@ :!ЩN@t*BS T@ :!ЩN@t*BS 45넺 @Dt*N@ht*N@ht*N@ht*N@ht*N@ht*N@ht*N@ht*N@ht*N@ht*N@ht*N@ht*N@ht*N@ht*N@ht*N@ht*N@ht*N@ht*BгS=#gyfᬧ :!ЩN@t*BS T@ :!ЩN@t*BS T@ :!ЩN@t*BS T@ :!ЩN@t*BS T@ :!ЩN@t*BS T@ :!ЩN@t*BS T@ :!ЩN@t*BS T@ :!ЩN@t*BS T@ :!ЩN@ >^ wNsX v.Vv. .:ԫTC_O/r0z3[sTuѩ^ÜjN^f z]aDٯR_9wuX*~u*Щ^;UnkzH7q͹7wygo;Yn/zH5t*.) ^%, ^+gV€ѪeCSAw:-u_ȹN՞Z))ߜnFgBSDo94&FNNuEgy8M]Oվ=إSZ55;Cr >!ЩN󟳷i=.䣹!9j6quli6~'J Ty{<Э| >!Щetws zH5t*WXO~*:@TjTPN=:SDENTQCS@:@TjTPN=:SDENTQC1N裏^>TQѩ1N5==}5ל~JJQ;զM.׼5/vWl:@Tčj ߩΙW/k/w%Щ"nTXzos=TQ71h'?ǿ˿]955uO۷g;cm۶ex[nu?-b-]wڵk3p kD;UggK:cTcNF?jΝ3/: |7x7>N? `Ts=զM=bwavT+sG'tRxƿE/:nֆ 2qY7կ~+gqƊ,r)vi7n9眖%^{m怙'-ꫯ. `To۟7,Y] m/^d%7Qo3|ֳ%K*OQjO<|{ы^ r:@#S%?>{キ0lT@lݺub (SOze|O~ݕ4Vg čjS{GmVtv[Z$uA\j~@tjSAߩZjѢeJ0j}@_jTiwxf ܣBCv__YJmjr;3WhI2[%^B%ې\jCidm2_?0zH5t*Ȕ>.цfϞoT [X@"DQl!ud1 _t9p0yitnvv>c]Ɨ[INR҇PNX4#S=#T# S3;j}W46-,Ydzzz jT)LQ{r~m*uެ s,8idmS4R 2jOT*w=UV_Xѕv:fu~SA,nᆫ~S:@#!Щ Sy*S}ӮRMo~GMTTC%K4c=sGoժU#7y8jŋ4HӜwyyϰuO tj ک(œ ҥKG _+sAۯ!5Lk:U5TvX2~#3~F^OҚ|;GFJ^5K/ڳ+r+/z)[nekgLJ<,Om߾G`fppUWmٲu.m:XөTнS=iO{կ~EUt*H!Щ SygvM7ܹ3*:@$jTTN =:|c[|_|=y *zH5t*xN=:SDENTQCS@:@TjTPN=:SDENTQCS@:@TjSmݺK_TQѩ1NW\qk_]q2f ѩSUN\r+_}'yWT4NwK>{ywN@TQѩQS=w~vϻR#<OLLL<׿z/3)mo>m׾)mzg?㯾˲ :~ rK/Y~2~mݹYlRY6o\UV塇h>9o|,?O$seӦMя~9SN9*˿ۿe+^1N8aK.=8kkgǶ^;s|^<'s J>7s˿2y{^#]we_;3s ^[:XөOڹs%Kx)y{Or _=MN :!ЩN@t*BS T߹uOTUV|ƿK&Qө :ө :ө :ө :ө :ө :ө :ө :ө :ө :ө :ө :ө :ө :ө :ө :ө GRN@zvyuOg=\P4h8 T@ :!ЩN@t*BS T@ :!ЩN@t*BS T8c.e}{@tH\z=UV=M ^:U$t* p:U$t* p:U$t* p:U$t* p:U$t* p:U$t* p:U$t* p:U$t* p:U$t* p:U$t* p:U$t* p:U$t* p:U$t* p:U$t* p:U$t* p:U$t* p:U$t* p:U$t* p:U$t* p:U$t* p:U$t* p:U$t* p:U$t* p:U$t* p:U$t* p:U$t* p:U$t* p:U$t* p:U$t* pu(C_WM'Џ t*F:xѩҩƋN4U j֥&7 l3E&J0u*TS$T JNUSҩt* l:N5OjTeZ8#TSA8T۱ti&бM%v=]НN(T̞v7YfLHnJTkMMSw]ya:Tw TmǕܩr+~8cNeS*~ҩߩJ[O>ԯTy"w 1\:P:U; ҩˏRl?aU;l9U꫙=dS%6صN4U*uT[V=˛Q̻wŗIk&wA. Q坊8:T:xѩҩƋN4U5}=(vT@TNWYz9Ï//-S=iO@3 0Bj:29=eC[SmAf?&涐: vߛp޺l?yd"(u $snEY{si0(C;@t Se7=e>tT v__(v;Ug/ONBj:UTu^k΀5WfOվc=U}uL)g S۱.>my|(Nxu.j[Z:+SGQjVҤɢ Tb%z~Jl*@N[Z5,Vg\YfkyRTZyگjv@$uʜTf{Za`L<]!JS5:%xm\hbYP!JS5^*˿˯CLS5K O'Cөo^˴enfDk.өeyRTSXF;zvv狌&5sQ¦SUa͚5Wf_%< Mjt=?U:JqVtZ8Z/ꉹ*3wdթN7G. Nx:ՠ etE*=vTWٱLվ#t $帿.t* 6:UTo=UjDjdMszv>zɴ7=Jsתu3 Nx:ՠ:?/.o:uiaR_c*#Ss\^T#1䎒GeO 9ٍdXҩTS k+E{Fo:mfd9rOuWT鎑ic3jYc&s>g{ά{-e;:tss1^xae˖͛756Nx:&'S1QGu9|͏?xfyRTS 1̇C93θ}Ѻ'ayRTSAź?x~>gutЩoN|]:T-%\rYy>O-_<7֗ꮻ<7n|T;vo>/^|I'?{:T@ŋ.wߧ>~Q)[T+Wr%\R rWg]rXgqZN>y+_y\RqG AygAu@TTPۋ~cS??%[#Ka|(Nx:TCҥK>o|'j]ƫ3@tөby{E]tm]=e Nx:T,'=iŊ'6n*oo^:UTP駟~5lٲ;r C>Bj< *}m]PYNx:PYNx:PYNx:PYNx:PYNx:PYNx:PYNxTӟ7|nCe# L^:Uީ퓟oo/^ h);S5^Yn?xq^_(WOܑ&P/T;w7w{ҥ_֟R# L^:UTo~ԧ?rG@tSoyz._|"OO=[n%s/^{Yp }7|eY~];k䍿e3y߸q,>`?3g?Yz,ynpG}TNx}v>WO ycoO:s9spQYZ{V̍#VNxW~{r?g]5z"M77M͚2_~e?sOկ~5s%\2?|_g꯶jUW]9> :>6̍Y IjbQ׿;xb/qڶm[!9@e3_;<@)tfLOO_s5vZVaf{y.ŐЩoN5u]wCqBt* 6:Uթܹ[ֻKatt* 6:Uީn .=l:FکѩTSҩTSڲe͛W O'CөOz*`h:UT@O%< Mj< 詄TS=t04t*NS5NT tөJx:Nx:5k֬^}t04t*S3l {#L9d>ۖe̹Kc P T#ǒ!+%u;8oشnן~+N{}OcXֳ;U?¤W9g}"eөoԝ;;< kv:+JQsZeVDe-]qN7>2eN9z~漋F:U㍢S=~Rye%}NJm!܇%ZTynޝ*GNLx9t½O1 ^FA:Uة}믿~bb|foK/5aLv,J_, e&cG1ENw1+ok];琾9g}EЩoNe˖kN;{4O?C>BjG?̿{Ņ_*90~|(Nxv?^}޿_|9/_F0ʱf͚իWW!JS5^~~ߩvE]T M2[Y>tSm޼UzUw?8HT@ltK._?_NFjQ߹s緾=GuSѩpJ~cuBet* 6:UҩmܸK.yի^rHT@lt+SۼyW^׿p(T@ltQm۶x\[lټys5*`h:U㍺S P tөJx:Nx:S O'CөOz*`h:UT@O%< Mj< 詄TS=t04t*Sk֬Yzu5*`h:UT0f{y.E O'CөO1SѩO1SѩO1SѩO1SѩO1SѩO1SѩO1e˖͛7WNS5NT tөJx:Z5CoA:_\t0j:SBNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNDNcǎ߭{@tHT7xsܺ KTЩTЩTЩTЩTЩo{jdɒ7N ѩʢSѢS@1:UYt*Zt*(&թ6L.k]lr0ɦ?{[hb+{YEbjj@WբTTPLSt&z{",:0ZjP- M-HGك'')qҩc KTL|Ϗsv]|jK.}4QT3̞v7Yf MHnJTkMM,\;~XMz .{ <];UݾD5T)8=]3_ [j`N5>qtɝ* 3T]>֩d=UPSj- Lvﷄt* ZÜjN^f z]aDٯR_9wuX*TuTۺ!uXd5 l`ݷdͿtNrT%:CquaTeѩSENU S UUT}jSU@Tө*S@t*TЩ&-@Xt*TЩt*o:@t T=;ՆeGؾkML;QlƞN>:4B_*YZj[^wuWש":@t T:tX|2ѩ2/ϨC/s  tCdH^ž'YDk֬Yzuݳ`t*TЩ`|n:.}H]B멒8(~/>,]t.4c¾[ʣ֑G9CR~k'<ӽMoW/^zЩ*T# l㡇*e ~Rִxï*eg>_u).ReZjEboKy)J/}KO13߅,TЩbm۶ko}]wRo _gR?ӫo^_={ue#:Ut*] y}|0.t T[^wuuSONظqGQ,Ȱf͚իW= FNNU إSlo t*TЩ]:UtHTө*StTЩ§SU@vTө"SONҩSEBNU إSLN>:0㡇ZjUݳ N |:UFک~᫮ꫯbQ۲e͛#SO(:տ^r%~[EНN>%v[o /vN8/|ᓟ׽uk;~{eٰaC>G?Q'߲e=YNk:mۖ9E.h$ |:UTw<ܼ=c|<;\tE?wzk֬9*K+,'_Ybd>"Kk,yR93򗿜9k_D72~ƿ}講n)s-,yo˲|;93L.}wvF;v?'qF6:@t /g>s/xA`򗿼p>~33)Oy](o|fߺuu]|l=^x| _8L>)2dǞ>s|[^xq7 zjfNl2cD@S=_gq!s?qFG8ĞC\O2z~ 's>~iNm?*^kƍGqDA׃%ǿmo+܎W*0sS߅Oǎg<ҩ*0ܹgmWQev*B0;GG}tD+p\??ӱ/pu]7dǞq뭷fo_E(J0*n >QTu"U bt թs=g}ZwX>*X:U$t*T(S{.O> .jTҩ"SO:0Ft`TЩ§SU@vTө"SONx衇VZU,ȠSEBNU d[lټysݳ`t*TЩv:@t TP; |:Ut*N>:NߩQFO\4H:24Mݯ w* T@ :!ЩN@t*BS T@ :!ЩN@t*BS T@ :!ЩN@t*BS T@ :!S}{4\N~{4TN@t*BS T@ :!ЩN@t*BS T@HRN{>/_i/ "S8 "S8 "S8 "S8 "S8 "S8 "S8 "S8 "S8 "S8 "S8 "S8 "S8 "S8 "S8 "SjK.{N=ԏRZ?.º @9T@ :!ЩN@t*BS T@ :!ЩN@t*BS T@ :!ЩN@t*BS T@ :!ЩN@t*BS~:{󞺧 @Tׯ?& ?BS T@ :!ЩN@t*BS T@ :!ЩNx_>uOxTK/U&ҩ :ө :ө :ө :ө :ө :ө :ө :ө :ө :ө :ө :ө :ө :ө :ө :ө +z{DgǥK=MT^xa@t*BS T@ :!ЩN@t*BS T@ :!ЩN@t*BS T@ :!ЩN@t*BS T@ :Db @t*N@t*N@t*N@t*N@t*N@t*N@t*N@t*N@t*N@t*N@t*N@t*N@t*N@t*N@t*N@t*N@t*N@t*N@t*N@t*N@t*N@t*DNo~󨣎{KHjbbi/ "?S@$t*S@$t*S@$t*S@$t*S@$t*S@$t*S@$t*S@$t*S@$t*S@$t*S@$t*S@$t*S@$t*Szg>;(TN =/8S3뱢'4:өt٩4Nw7Mn2-Z41=SL6 p:PX{ީjUZ^*PSL6 p:PX=oMaH[IN өʢSL6 p:P{Z0ff.ڎ-,SN3~??cI7BIN 빞jws& ltVSM *ubks_h&r8 (WJXWhf9*s2߱k.Wʣm@t*a:Uq%w ;g,ةl[1L6 p:PXU҇*oW3_naT=frʯQYͤznsS y~A:UzR'uy 9gJ}5amr~vթh&r8 (gJmU&;9wT`.m'kGWvW8{e|h&s8 (yh&r8 (L*NG3)T@a:UYt,\qƲ K#.ͺz0(Utɶ]/\-7;w-gJzIގSC p:PX\yX,;rQgkJ^ھ^{|YҞ!9WO_ QV(ʙ[ޔiz5~ѩS8 ({ެCIzLakKbQ8[v9?6}DS%?kgwnskRϝfL2gYN T@anj)iC z;[PzVnک2/=sPޔz%n"H x:PXT W]vXO1lT]F:U/c_/HF<2ʳ!;|o^~3y衇NU= T@a׃up`M1%KLOOt^-euy+^{\EN@t*0ɹP 4S%}\pΫTөNSE++z׻oرCN@t*qTcNu1L2sAsg`ϹTҩNSTe[o;[ntrTN өFNk6U]Vԉ'{?/y?:Ut*ST#UeF?Yc}=?O?<ѩNSW\}g[qA\rڵ\EN@t*0W^yի_TuѩNSQNU T@a:TuѩNSQNU T@a:TuѩNSgm۶g1:U]t*ST#dɒg1:U]t*ST#S 4EN p:PN5R:@#[tt*ST#S 4EN p:PN5R:@#[tt*ST#S 4EN p:PN5R:@#[tt*ST#uWl߾Y䙚XuTN ө0lTݳN5t*ST S² %ldvRV*tS8 (LJHמ;RG>ڽd>e/:XҩNN; $Pt~$7-LYjRgұ :8ҩNN5l.^Ӫ=ɯZ:Ն;U v[Bb쾕}9WO J?:өšݩrKt/{TzRmg_=7@ڳSvJ\=}tS8 (ٝjQvn*!y]vչSGKcWoeO5t*S5SuYO;+u~m;}tWe[#huTyWjTN kvr~:U<[Ru=Tj_jaWzYWS# T@aT]?oWǗ]:7kۯ:(/u6]'?/f?32>/~7t*S5Slɒ%ls=weͪ:U]t*ST#USmݺwCiSV*}zЩS8 (LA;Ѽ/߼y駟OjÆ 7xMwlΝ?/~s>xTSNTS DmTSNTS Dgʕ+^f͚VXiTmܸ_\s5mjzvnG, t*2j ?c{iO޼yG|*t*өt֩۷%\7aOJA 9 L>;ռ^xᮻ>rʖOJA 9 LAuy/ݻ?Oom|R: :T@e:@ S{Wx[n߾9NE 9 Lut(:T@e:@T٠SuSNTS N5 :Ug:T@e:@T٠SuSNTS N5 :Ug:T@e:@T٠SuSNTS N5 :Ug:T@e:@{oONE 9 LqTGlذo麊NE 9 Lb:ռO?{ᇗZESNTS Dyjޚ5k>J*:U t*2j TVZ599k׮OSNTS n֭[w=׿n;m?;.ϙ+V\tE^xaN5X:T@e:@Tԑ_0 {|5'|~G, t*2j ys1- 󳫴rZWwyg%^W\qڵk{y䑳꫻jt*өtZbų>=zo|?ܹs޽OE 9 L2:ի?Og?۲N5z:T@e:Pu:Sf?RTSNTS u;sι[fffzYE= t*2jƥSp _|{NE 9 LqTTQt*өtڹsg1:U t*2jt(:T@e:TQt*өt*ѩT$STTSEѩHN*өFSNTSQNE 9 L*N@r:PN5TW]u /=ЩT$STCbŊg}6zCSEѩHN*өJZ3 t*2jtelЩ:өHN*өJZ3 t*2jtelЩ:өHN*өJZ3 t*2jtelЩ:өHN*өjΝPTQt*өt*ѩT$STTSEѩHN*өFSNTSQNE 9 L*N@r:PNE5:U t*2jt(:T@?&N;/P[nb93qT$SB3=t*Bh&N@dsөc@r:!4sHN f2z9TLF1 9 9$SB3=t*Bh&N@dsөc@r:!4sHN f2z9TLF1 9 9$Sg=͢T$S@M}{'t*ҩ&lٲw^ t* N5SN5SN5SN5SN5SN5SN5SN5SN5SN5SN5SN5SN5SN5SN5SN5SN5SN5SN5SN5SN5SN5SN5SN5ѵS=c7n&S@MtTO=\=MKpTP:TP:TP:TP:TP:TP:TP:TP:TP:TP:TP:TP:TP:TP:TP:TP:TP:TP:TP:TP:TP:TP:TP:TP:TP:TP:TP:TP:TP:TP];c=qiP_:DNSO]pt* N5SN5SN5SN5SN5SN5SN5SN5SN5SN5SN5SN5SN5SN5SN5SN5SN5SN5SN5S\N?i͚5p\OuGO¹ t*2Щ@  t*2Щ@  t*2Щ@  t*2Щ@  t*2Щ@  t*2Щ@  t*2Щ@  t*2Щ@  t*2Щ@  V\5kDO@ G @t*2Щ@  t*2Щ@  t*2Щe˖9 LbֶmۺSSSJS1S TLT@::NńN$S1S TLT@::NńN$S1S TLT@::NńN$S1S TLT^I0c TS1Sst* NńNѩX: 뻉 0t:KAbT4T@, }v ʠS0t*(NP7ߧS@1t*ѩt*ѩt*ѩt*ѩt*ѩt*ѩt* R, IiR& I:t* ' BTҩfϚܵՆKTB"NS@@O鳚Ǟ5=3N2hmKTB"PNu(.횜h|SçS@@TiAs:P^np i3MOvҙ+:TP BTSki(-}UTg-ѥдt*(N!t*ީ4Kvj`jՙNѩ NEީLuT?id S@@˹_Oİtѩ4:ЩW|٪ZokbkjNѩ NE:} ^i W{DTB"P/XЩ0:ЩS9TB"P;Lө`:Щczw$裏TP0 Bwy93ISMa (NP7:NP7:NP7:NP7:NP7:NP7:NP7:N@:TMT~]ρ$z@t*t*  @B2ЩЩ t*t*  @B2ЩЩ t*t*  @B2ЩЩ t*t*  @B2ЩЩ t*VZ.,z@t*2Щ@  t*2Щ@  t*2Щ@  BtI#N!t+mo{[4`tt*S@ BTB:Щ N-t*S@ BTB:Щ N-t*S@ BTB:Щ N-t*S@ BTB:Щ N-t*S@ BTB:Щ N-t*S@ BTB:Щ N-t*S@ BTB:Щ DN/r-GqD4`tt*z_|qժUѩ N!t*hS@ ZTB:ЩN!t*hS@ ZTB:ЩN!t*hS@ ZTB:ЩN!t*hS@ ZTB:ЩN!t*hS@lW\qCݜyE:Uٶo+ N!tTTB*NPN!tTTB*NPN!tTTB*NPN!tTTB*NPN!tTTB*NPN!tTTB*NPN!tTTB*NPN!tTTB*NPN!tTTB*NPN!tTTB*NPN!tTTB*NPN!T|;71r=~:@:S=]}ٽ|:@:ЩƔN0<:ЩƔN0<:p>陮&&&w|P@ B SMZ@NjבZ>ҩGTAA'[5WS N!t_N0t*1NuIN n;Pg^0$8=ŚroajnZVjvk0R[6im/t* t*1멚!:jsa+.>jռt%v;ײZ90S N!ܩ>XWѲ`:3kߖNs`:TbeA?~w%f;ީ,^#  BT OQS-9SWTT"TTc(@:?jyVY 6=;`髴&.ѩGT nTtU ~5.[fKY}&EoyC ձn{|u* t*1N5zC0 Q BTT4ѩ NuN@ Bߩ~ӟfz|u* t*"3  Bߩ7$Ȇ 6-㎛XNZvRb F- N!;ݩpƊvSUS=ҩ DAS-oS-/22O]=᾿>T@t*ݩ3祗^zxT0Ft*1NuꩧV :T0Ft*1NոOmJgÆ zpqM,S]_L1S@ϧ*S U#:TP* BTҩJTB*N5T:M:Utҩl:ЩJS Neө NU*jm۶ dS:M:Utt*G:ЩJS-S=ҩ NU*j1 N!tRTT@t*SJZLzS@T:b:# BTҩөTB*N5T:M:Utҩl:ЩJS Neө NU*jt*(N!tReT3gML:zt*(N!tRTCS@t*SJ* ʦS@T:PTP6 BT TS\ZdY3MGݩm6MTP6 BT T>k59N$S@T!ꬉmj:STB*UHhש7q? HL:U\Ou C-|*NS@TYOSx>NS@Ti~_}˿'>|g}NNzS@TAjfOƙ׽n]wW_}¦t*G:ЩJU^jz+;|CfS@t*SN5裏޼y7'xbҩl:ЩJULڷo_'󩧞u_t*(N!tRS?A>Neө NUb>ks?wyNI:U TGy{o_W:' BT*SuQ۷x N!tRԩ>[qNI:UJT˥S}ҩ NU*J*ө NU*J*ө NU*J*ө NU*J*ө NU*J*ө NU*J*ө NUaw'TܐT@t*SjHjff榛nZ~Do~ S}ҩ NUv={p ֭ku* JS@TT/vim?zJRTB*U?kwt>ynat*O:ЩJUSܺu'}ݻ9=\۽ٳg_?n׿{nzW_=99y}o|cAsNciY hS@T=W_}/^T@ BT޽㏯vs1w[?h?5{ێ뮻nݺm۶[v裏?V㒭;㡞=#9TP6 BTZ}?} '~LOOS@t*Ss|+_9s?'϶mۆ&ҩl:ЩJkxg~lʕm?zJRTB*Uj/|_s5rJGSTP* BT`j[oݼyQGu7=qS@t*SjHj?Wөl:ЩJ5NUs:M:Utҩl:ЩJS Neө NU*jt*(N!tRTCS@t*SJ* ʦS@T:PTP6 BTҩJTB*N5T:M:Utҩl:ЩJS Neө NU*jt*(N!tRTCS@t*1N5k=#wN,S~S']Ot*#:M"J t*(N!کf7Al2N555=/w=t*#:j"SeSѩ NU** ƈN!tRTT0Ft*SJF1S@T:U6: BTҩѩ`TB*NNcD:Utlt*#:ЩJSeSѩ NU** ƈN!tRTT0Ft*SJF1S@;Չ'|=؉tO9zʅѷ40:SY y:ЩʦSTS@l:@:ЩʦSTS@l:@:ЩʦSTS@l:@:ЩʦSTS@l:@:ЩʦSTS@l:@:ЩʦSTS@l:@:Щ S@ ZTszկqѩ D߿jժiT}B:Щ N-t*S@ BTB:Щ N-t*q&z0R];w߽ziP^ @@:TdSN@:TdSN@:TdSN@:TdSN@:TdSN@:TdSN@:TdSN@:T: өx{=Mp::NNdSSTT@::NNdSSTT@::NNdSSTT@::NNdSSTT@:zݜ| SN@:TdSN@:TdSN@:TdSN@:TdSN@:TdSN@:TdSN@:TdpefժU endstream endobj 85 0 obj 38054 endobj 86 0 obj <> stream x V]t mie)R u@y Y( S݂ .(t*Ҳ(X iay*XD R*R*.?d&q%0Boxf7⾵⌿'ƀS!Htp 39?.{*䭠>7k>t[{C<}pOxһOݗnt[Fڐß7 xny+J|O qхsn:!:zÆ 7m͛7?t˖-uq۷cǎv]v}pgϞ}]_FQa}|O0F [_}=&n4NIs~bn?ޡ!:9OlC|>ٖ$٦ ٶ8/xsudY6m_LItnI/Z.1W ,醯NkN|o9I=/wXB7u)F#zS{M a ݨo9]NѶ+>U3PݰnC}}tj{?']۬Pݰn#?uI7e2n kFmɶ}w3<-` id:Vn3Μ/4U+A7gșuΆ:c5?܍#v̱B7<S_P+wc.7 tށGDIuiѝn}(Zk躹}ߠp@5{urR"A7qtÒ5auC?;9(vXɷy}Vgt o7ԟ-L݈t҄dwjm$cфji/y|p rbD7,U~a8fu;C2lY8f0{JzEl] ܧ6WxF73L kFUն%ڼơ6ՍS*d4s[ۅtXqbf7_ĽhbF s kFEU]}r7nUmfLtG1QpnotB7<XnXC7^qX\{j'|jGPOf[qK݈5SQ͕OXT kF%˜58'ަ0v{S/HqCCPoɟ1v17F[=a0;k>Yx 38so\}%@m8tkO|ZeF77}L7|ǟ{B_m)jP=R;FےWšH}Ilo1tmu~W}ev#qKE76ySkup h`[~3v}Ozr7/Em-~noToAґ;ؿ ~pL783ו6GtNQD}p􅽢:_5CóSB7r7K/ uNrH*@=wc/ra2P8}0n`Itn}{f ,n؍/Gon؍)t_=<6 A7ΈoL~Pc#GK xƙnnA :nW8t6 O8DjdC fɀ?jCǭ> s xnD7<ǣao '^>PtGb(_ ATt?U?T8ݍ~sv\|qkmmMw6lט߸闤C}L ^`߿fo0jf_ތP 4 } ӪmseUOӍ 龪M=oX_?Lw}Zuåi2=F7Ћv~;V~Lmo4᎖P;W=^=fGfUMdFDhsB7 }miՍe?oNt3n) 3 n`HF3-Q(Gf{L r7&w'(m?A7H7bޤo4w50J?.|ӡaTks?Mw|ld _kLo$t8S n$$Yлoo B7n}>q:F?VYPFp܋xt#d @Zݸg&gvc0~?18"0HO5moL*?B7yY؍ϝdV&S yt7ۍ^,n # 8ӍF7<37\;gno}`@nnxfF7'% pRHຆXF߻4':vm ϔq(zK nXC7<3_Wqw E7q|#{ ٍ>PyMWE7 ,cA7F<n`1R獿O`y95n{wuΝ;ܱcǫo?܉ ,n\t{O7kcG !neU2BF7ݕGf@5t>'3BF7~Ka@n~pߔ^ U` D8sݘiF8~FtZw4V^-|Ӽa4SV zጻ'*17G87~T0;oB|0f\ɷT]~ja* ّ4g߼Ц^TmVYdz-t#<uCԚOl:t7]'YY&C$Y?N3Δ!Ad6AVy0u7AIWZN*in;q/2kP[ʶEһdt}lՌ\.:PV.o7ِKqoUs!/.nŬ +tt{c{N3UZcaW,-=[vyk{²~OmTN&;[7K..X-߀=궸bNױͷ|1}\o6fꍜa7 &]Pqygv}m^uB3҃ꪐu aO.Qݍ/xɌ~lTo#o978Qxvc46~/3&AҞaE7QkI7n!ݍOᠷ̻3Lq~*5+]ǛO5 VnD闧G2FT}j5t#(j_ZU[\q`}?o*wFvρ&&+;#wyY T-z7x'7nXC7bVۅ^꼟j]rqȡ ds`ߚPi#6T9}n|=]]Tj7.x3ьS8n]pSE7zo^֧Fn0x+ncN9 {8'CleU{}ARު(9JgJ"}n =ݭ|xW;ء]yq,W7`7!iK;.W7@7l2b3ۏ ' _o}q`_4ӵmtB/~YL34f7̀㗛?fLך6xn{N9: I/=t4c8 n9*torQӍ0 7,l, Mřn|K t A7C7 nthoP5D7 .n@]tD7n`Zz z& |qnX-n`BoFG8s_Hnt` t%t#oXyB7<23݀5tBG G 0@7 GL YE.r!nRݠV﹯j+"'&AL%EPF㙺ۓ$!W6m&B$Z'[*ϔ᳉Ac41b!ՉrVBtD7V(]uLu'nD_*|Hmlz|o?nP Ouc0 m;.^Q^a|G7Vh1ƣ7*{xFH tXB7 XwDmnXnfdGE̽u7?{>@7эU*o[S /VA9J[}'7l=|fg tpD|8sޟ݈un8n8lyui",<m]O1u٫_*۟*{j5r3v}Ou H*wyrqM:vC%atFG8s|G Snth ׍R @ `-&f t֢2qc ΢Z[o:nk4nV+7 n`|NX-n`;ΜG7э6 t8,,6 tڢn3 ݰnv[;O>WpxXF78BfwAqAnӎ6 tZo}qf_~3BOn87yatV wk]D ︎zG8s8|G7:nnt` t#7yB7:™n!t#FxЍB7O @]\D7 3B<Hz 親B8PG_'p}*tSf8Ecg;t[' G7 |G7:™;t%Fo uG} a3` k8BvCoU"5JDV ?Pm0H?ƱnEr^dլ˓܌o'w3݀5tBv#ҟWiU:&|7=͜01<,nn@HnSftC3I}||TV7Olnxb*k|ɇMoS^ VwüZD7Z@7Mk7=6nn'#F ܍mHB6.{/U:!,>\sǫ+xny\qwOkmuF w[8Bָ။#\nn@HGt#[ A:;A\pI0t7Xm^sU.zQ~;$n y{? i>0%jbґt|+݀O m0; '+݀O܍tlQdeo +C7 $no.x~*Ӎq_tmZej  S%5P{[7~*޿Qt#?ҋ \^7:X0s+=n<^// )TͿ_~ sn t[t\w۴7/"^ï,WseKo=[(VKoi]4v nF`r衿>tsk4=G<)p@79VA3z1| axι 9!:0ñG79VA3g_zKqp8BXG8XtBXG8XtBXG8XtBXG8XtBXG=\Z%t<&*sf [8Ve=/MέU`s0]߰n@ yO{ɶCR1J#QzAqp r/Ź>nx*ӟM&.ssf&'E69YEm!CU9b{n:1,t#|gJߠJʎod2FPwCOfJna1;tO7SɞP7VGz }dƲN'_^`nt;ae3pO, @nt;N2/λ?)wcwokJ 4y}}{ )wAoh^Fom${x{#;9kg#M|dO#_g_9swÍҫtLwkU! =^c<8f;ɾ:s[U_lGOn. -7YͶmxɍfiof[gNN7 ʃno`E{rRUK7Zt#h8^=*n`V7K_ΠE7b/n<$yaiFz ` ݀nr+;yvpVC-^o?m^=} S_3Yyڌ3yL}tqqU[j7Xoô6pfy_D7Zf߸~bTm/t ǯ=8]8fL_mglҵW^4>K[}{nPӧW>ñG[?;Vi~8l2㿤 :#R7 ']߇{{W [?_ |݀ut! Ou[q+-tBֺw(XW@F7 KQRtݰn@H1HG8߰cϕ^tTAq0ƨ3G4HnD0^?[z "n8<f$*d|rK \ %vtK"F  }"<ēY, * mHtxTC$ht%v#&G.r7i?0o ڍP.aTnF'(3Ӎ &Fl&oSX7&wnGӍ tS%KۦC~uYN7.X#܍Z>@ F`eOާn7o8 Ź>܍ޜƽkX .Ͽ_jt!U uS%?_RɣV󜸒l;*ӝռk޹@t!n-fW+K;_)G7lTjuw%Uy?vĎ?Y;նrgn,wt5tq߈~Ӎ`Ѝ5d?nf:37J!!ʣy?nx.ĺ˅` [,I2q~uaFx7XA7`ɋ/̠@B70 nah h'2ЍbHh h݀$8׍ot@[F@ px nn@@[F{$ nn@@[F{ynt@[FF nnt@[oG7Жj;2  ]Qz XuUwll#c5ξ, |tĪ8D/\@.߀"C@ID7eүLW5$.~E7 nˤ_u H2_]tLWo@@I2#.~>2#.~D7eү:$ t.It]&7 nˤ_uq]CH2_]tLW݀$.~E7 nˤ_u1$ tStL)qt]&ot@I t@I t@Ib H2_]\GLW݀$.~D7eү!$ t.It]&n@@ID7eүL)qt]&8.~y7:nˤ_Lnˤ_Lnˤ_u1$ t.It]&n@@Ib HzV)KG- :O%[:I/BnX1a ݀ X&  ݰn@.^̯ ` ƅ;Q=[qF=Z"n$~S֏q7z㹷D7w~?ݸ=݉iIe9J7G=]ON%+8WTѰoϓ~?0hB7zca&=Y闧[17Mn N5}|Z>kmş~';_{kR_Szj:zj!T*x| E]E'pܛn[M_4_Sz㭂dViLW QQ)tcdяsPx nȻo-k}lm q n%$?O|Fl+loz#pP;# ~db @@Ftg Qv#71M>g #=o||#f} G>2kx8wGnBB7Jz\!1>lt }4w(;M?Ltun}/ }g͟ttcXC*vcL;顉8κ1!vMMS= ,#Ʒ|?H~kn_̠y˕ :盅t#xvC?7ЍyݍĽ^򱛤(E7n,} |{y[t.qsɎd"/x̷zg]_.cؽTCGnZ'[hs•o/Z•psgL~GNSe^,hn`M%Dz0mՌZ=pQN\'/39~j;޲hCE@.\хWyu~C Zy颕i W>g-Z\n|s.dc+7Z&;f.ieЍL}0n эt&n+̠wcr,ttuϻ mn7xtt׃tXK'uЃͥCé"` dS#wN_D)Z*t%t"R.@D:ܕR뉁GFKqft7.03<=I=%vVэw)t. C%I5ҁ=D~t~n`nB7FnhaW!nLƑ˶7-tR7znHQOȎo  [SeD7ґsOO5ȣ f62t#xtԍ7͗ -t)vc⵿nQ7?H]7\(_qy_Yt7m^7~dU/n#ƍ;ra\2ݍkQx`v M_ e16_eFƜ+̐T/.+LKhX鷨>n?mҚLJuČĕM_dz ̈ϙ~_x> vXMh$ޱ,r7vÊ3̀1^[$ӿhsQ:Nn3Bkq=d42,=mQ.L @RťMOw&7=F[kun^'ig}]_h{2O_lk'jӏ׌)w.`/0=dw>]u;oϤCb S7|w-̐^BOэ Ougo@h_:Tpf#]?mל#-t_?Z0B7lpP7Y @ э [э [э0FxFK8_@_ҋ`(-> M rO}_э<{Kn$}}Eza-a sAnn.hךQImU|CfwުF|:D9R- H?ˌ3oU3(1>،rf4;QSו6-;7Z /^1#2]19G bBը)~[ߣe7\pAΠvǟI2;fGӧ͎/QטRM.[zjninw+̠ {菧qq&n]/̠ԍ-ڍ;7fЍ_>V @s/N}dh&n-ݍޠ%z?Y< Ot]7]JS0wu#Od4ٖYhn zW xT:X1nC}A?I}-WhZ*Rf }?(D9e#sFD?k%۪n0Fx:6A;Έ0NE }UF3u'-.by6эfק\uԧЍfF ?p;XC7ЍНF%n.-qg Һph3 vэЍ0 Pt%t@FK@ݍkZ|`(wKa vэ3la @@E>ht` tT A7 n.\@7Bxt%\@7Zt]t%tnnh ( Pt%t@FKon7F78}Ư*cp,ڕxOpݸߪrAO>Yd=x@7x|'ԫn.Pz5 tSn@x ^ n@x O (O=PzEW\@7htS\@7EW\@7htS\@7EW\@7 t A7 n.-a0gg vэ8Ӎg t%t@FK@э -E7ZB7n7F7Bp 7 S.\a7N]*Xnptpضsʃ+@7x؍7 ho.V7yõ7t0u1F|q\n7fuc: I7z#A7`"nGqooӟ'zz{"_i17֤/d_&MA]&_}mt ^C7)nz8JЫ8YOj%'"37(EHoCs|[7n<SzCAo6/J'~1vٗd"8t@-1L 3H玷7cڍɗ"8t#4hw/S՞]&l*x0MȺ}d{cҍF6;fTYa ݸ}A(OWq߬Cv|COSjno~|7e7&(ܖξ o.hԍOaN7>Gd3O5Pkdխ>WSP]oһԚ)ވF%QT ۸>OzҺ܌vO5 z n.Ս΍u\܍Bn *H <Ɣo퍞~D ͹եGYyUui Wg콨O]z8^Z={o7FvꃢϭΝ=ҷoo`Qq[G8f@.ʍK`0tCPVgJzvՍo=0C݀@C߿zܸ/q޽WI|r슈_ڕxΝOHzEq|"ҍT@3{Zq `k[L936e8sCInt:ntr#_7;fs ).[ n<.nˣ2,no-t]V_:WZ܁[b7 n<.nˣ2,ns yh|ԂO^(7&/9sxHop1QSs{a&g\l7zFArϾ' lb`zT!DÁ CݑH|z`>Qԗod< PKvCoU"5J^'lBa\1u+"3fM~om~U}7uG󍲻;nԒHcjED_DFz;_i7)|@$7<\PT7݈.z/90һV@=zVidT7WOa,͟~Ψ#jW ޭht1yttpnǥ3(t߈ͻmu(xhn=UH7E7!Q8|cw7m^7産3濟o[SR.qzp"Yq40ޅlpKQx?`|wT\pn|m'Q^ks/60>`8q])$ br7N(MG۱cK%7zUcjoLpÚ'nO)P\k_~VJBYJ\(}GrzZ]XwWfDoZ70bvWn,]ñxe-!o+}O=eX]gd#+3t8T>J/8FO7F5H7UzX$|7f_|nn4c.]1N7$T7TԕCo.ͮ(sZ-]q(teF?ph o4pRߛ t%р48X F|x{n>5\P;Zv/{wioi?\v{F}+O7T.[aYwܴn^ö,[xi/۾ʿ9H}]B7܍_yE~3}A|om~Č(tyq*w.`zdԲe׳tŌBB.a @fH/e_Uݯw2 9=n=bfsfc͈kL?z?n6G@XY狀t!3v.wn!t`G܊nE7Z@7,>w -F nnn.-a|<F7Bn`sq6O7n n.\@7ntph6 w7nęy3T8y9 ?μr;ϙU\H(3GϝӅF 9!u x`<۽g\78ϾS/w!nm1L0qx!nN7ztGՉc?7nT](қ">:觇 0ːtcu#-ưKp8ԛыGQUdEy15qS u Cc_5CoQLP 'uT w'**mDT!S;ra \07fsVn.Ӎ? ytpn)GCbv-n.Ʒva36@Xo7qtt-ڱtc9kVd`W먿seR^MyU|q\PyqNzOcf~r_xtߖo`J O?|7FF~>n^ut׊=S ]a.67GZ5eC;aR_k/ieΘ|~1>1^͉+7$ꊏ(}=֕9ot]3=Ѝ쪾ߤnd;Ԫ/S%%(NW(\2)K2; vq]_,\.JoN\7y s䆞ڒH>NP ORܜTtQm%(nqA(xOaSEy{c%$~K f k8_Fu"(NO5̮.ggQz~ Ч7 ^i|\@7Gu@읚\]~0~?kFTH޴w|On=&ğ=S>7#=Vtp݀{N?7<}ߟ̧ sj)yϽ̧ 8y*C+˟~|E,[%lnget'aA oZ8[utY\ɟ/^_c_iǤl%pe5IVe-O(>) h3Cx곷x @h h ĺRA7ЍЍt&of t%t$ nn4:PA7Ѝ\s>vc` @ b 7F78ntp݀ !tC7;rE3H]7\(!qhq$;p|s( nwG3t5ݍ~x"aRÎ?8 8~vڙ3i3fLjU7jO[^:%3eT{‡f 3bx;;> fƁ_p\az-p tڬSri?E7bw7m_~/!b C/h N7`k?nÅ?s菧F7o% ?n݀?hp7FK997 nFKFH t%t?'FK(  Xt̺gF+`э6{#Ua/Ϯ9Uw"Rew_!a oh(GARh#ֳA }a62YW'7q8эt$ H$(wonwZV4C7n4j#BmG#;* 3tC^h_]PO7n44n@o팡>֑F6+!ѐ?5$:f{Cҏ٬Q]x̞O7ЍbJmJjPbJmo 戮yCGN3 t9M=VO7Ѝ0Fg=^nęS8i'~7sunw xKtu }1A7q|%񺆧w|;FKFne,nFKFY&~g t%ל*ݸZa|7F|㴣b 4|tp݀vQ ݨ1 4I/n,peoYn0@7׵nT Ǭ&l{cSFT6gߠztw5o/C,u^vwS}~2gsNO gkFPcG׳AfViv\7TuT$r_&gqˤ&HwH%z$n,n Ғ~Ht%tæfj?[z?Sf nn-qxX|{C `*t#ƈ䢳FKF|8tQn$Б0~4f.L>n>@gэ +oh]ԍo܌yJw-TRf{c|Tj{c0~?:HҬn S~\th ݨpݸ|lj 3*.ghKr_NXMݸ3?6F7POn QQ7\fm云b\|Az{v@vhn{tukht#t߰pޖd &藾>.f7%^6k|]C;[nOěgcON>0d%tF,t#Q<{産 3ZG7nuԭoRO՛|}z|dsߘnC7Z6ηLQ4yU/nNXI7>Uyt%Μ/ށq.q "aӚ=(T@GэЍDկut%tK`B7ZL7VoN7ЍtV;tphIq;tphIo;s)nt7n[6'Z/#/jVntn)|S6sw>wk-;2+{7V @Xn4c+{o v=`"wذJ?nuuF1\sec`p|pXຑݮ0%tpX? 3@7ЍLh7团t7nd|p| hI׺h»tphI"8nęnXnFK5tF7ZB7BJFKF(?@ htϟ=F7Vn'8_ ?42tϟ=A7nLho ?sђ7|n$;?Z^7n%?8+|q@k|>{?̿QԵsO{i| A7Bj7N+mMnFu[ޝnsoNgR~9shvF7Bg+To+:ϻB7Bq7yПga\@7Z+2qأFa|7FK9_+t1x 7@-q>ڏJ3aSFW4wэ 1n[nnT[wtow#"q`et#t>/>N7TP[ joR+t20wuAq7Y7b Oэ cr7c7ЍlVn{c4Ս,oo M gjI t$ YۤOuc򍦏o/F7BG7bwiϹP;:#6`~~T"ߍ7~a?ݰ]'Sthh oش8q]aFXxo,oh38Ɣݸ3?7ht%t#q7kn-6rWthh ݰ܍WfR*aSQ,aS6Xh ݰ܍ 7"Xh o4xT WΣ xUun.tX)N>nC7B~7F7B׉n4:a 4:a 4߰ɍn0э8sxoX=g8nnشu !*aS+F-/5(aRB7H:nnشn~4Ϝ{h ݰI; th oԴq5փ0~ O1zGA4~>8R"sE6K: n.7Fc=+affwWϓ4\!8&F6evcz64/>ՍtT^W0$n?,Y~ 7)ntrcvpHV@E@u@9znYfo7{]![⍽vlo (t#tt#w7QЍlVn{ce7ՍFK# oEXFFFkznGl(O>HxFCq4*|i8 nn7;PVY2OO6SGCF"ndoiT.0ޢЍuߺ<7Q1э0M qg 4g{[>sLޙvэ8Ӎ Ɓ_p܌ ֶ7ht%tæSn-6񪷝|W#nnʸM-61N,aSnDk*߰iqܛVՍZOG7B]狫Rt#tw#񭷝|Vn.n$mKA7nX ht#ttht#ttht%a` @-q|zt%t#tph ݰn-6 !ktorynD7,Wc.a2\a2>tqXoF7Ц xU|q@+waV]e_ڵVfݻW}7/}UNtkݘfz%B݈D~,.W [/غGmQa߽eˊi3}ͫM7s6Ogt ?2gnHrP8eƁ_h7<u ֋xhOu߇nnXC7m_:Ѝ k-8_g?0Qo@TDh|AawcD7ęnD<nPFK@э -E7ZB7n7F7Bn` @ |qaht-|nŕuLHݐ>ϕE7:nc ȢmqeC7q1dэFKnpeC7q1dэ\:7q1dэ8ӍHw7nc @Ѝp]C-E7ZB7nnh oon.ť^ t-W1!uC?WS@h+a+) n{7W1tWS@85z7Z ,"W1tWS@x^/ftnXc r۲il~7GtyL\Y0=&eWUoE Fl,}ee [ͦH{~=IwrQ\gdkGdYU0;Yzq ݍ8V\^I[:H sl_֗[/;;7e6<^NSi>0%jbґt|+oDwxi7>#AOVd'_J!avF2o t= Q<1 -L7Ʒ :m>寯̨׍=Q-f?UR:c?-c N1oCޟXQ+|_+tcHw][7ùS\#n$nu|]YDŽ \yLlmX+wO|f wkZK4;qW\Y {\yLr/a.1RT&;¥;]Nc7p9<[BMtT]#,~k85Jv)ݸog7qV j;AgXz 2u:e8tҀz`\C_#׽o\7kUv~hxVx#.6NwH鷺o7Isn}uSTb|\r+pa*+\i|z(1>ͺ1tGn7n,p!o :~u|goS廡7vv[ߵ0nB*3[yOOOwgţ~t{q 34.w_/- N71n@sn`ŕnA7ˠ 0 t4A7p8 @Hݐ>nޠ $-(?ak:[( {B= n4@7 X@7&ݰ'c@Fm(`ڍl8h2tA7Z@7&QWw#WɆt7n Lt4I7*{}#ݍ ɫn|cO*2\\7Aq^n P^u#~F~^qzA7(QW(NG76Q[Ojnu񻮕^*6y7mta 0􂴊n lt"5]zKߠ[=T{+WBeэרw}opo+VPq:~l>qc/n4G7Ӵjߏ0gΜ/N7tY(0J}~ G?wѸ(i#RgF Eta[f{#IA$ BvAᎆy7>=һa@ؖFC*W?qv|e9 hn xgn$I HHtD0y9c<]3T7ޱ%Y ¶L7QLfQt5R:*KݠpR流~?wCmMdw,tcξnn xcnR|77Dvf?x{cTG/6k#o^n x܍Wv+Kvw/7"6 lt8,[  NGyr|#:_;t"nS^t'SF/xtrRэ wSE O9ލ{4whX=O܍tEtcvWn,Stci7g ;tC7Po•n?DsWMD7)͕ns~D7ҍg΃nxWV!x|c#a7>q8p ;>vNPh8Iz `H7Q0n@Tk r{[nJ~* Ku#ȠF!/.v7>OJ}|'P(N>;~@ޯ_1Ft݈ԦDҋXU"Pѷq1LҌAڍHcoRGtn|iK Wc{#F?{~N'-OnFqKy xnn4׼zHv9=TiAv\\ߩo!|on7]Nh|𻰽H佹t\n4WE?*n#}sQ<!|tCoЧ@utM=ɝRj{c0~?`Q|?H3O xn4--e3柿t\Əvs3)ͩn\gn(dҍt}tX5!> n0FH݈Ëߙn㋟ăn8s}*4f7̀dp݀r7|)5thnzҺ . t9oLTp78 Ѝ/nNk<o2qnn4poYdVnSt\nStC7 ;thnޡ4y <ƍߔ^X7{epp|q4X7~]/!?=.0Que+D9ۍđO<*D7 nܸO^{!g/ܥS++/':츟ڜmN 0Fsn+]^-]F/֞UKG|H2ߤA+:[ȣ t/4"E4/׭y\ H'iu޼sCx\?xn4@7+wӵZZtCotDrt鮩FozEnQO\d՝տO5[v{!g0lta7#$Vq҉aҋ^0,a>91eƤ7wUnS<;狣*ٱd=G# 3>-D?.NB7)t*tc`jUz u@u8_MSL7s斾p&LUh~7 A7 Z7qQ<9?*vc|{T퍸?w0n4U~_ (~| n/aS7&? ` h ť8v})L Ѝmp_t:Y1Lͱxn4@7wFt9xn4@7ݍKv 6[>L 0Fs>l0n@+)n `>!w<;狣9xn@C7 nޡE7Ѝ9xn4R Ft9xn0F8﯀ny wЍhnޡ Ѝhnޡ 0Fst݀F7 8n@tn0FHfwPə@O(bυ& ns8ݍCZL& n@nzUco?L@݀(Go8GaDэj& n4эj& n4RF5G7эj& n0F8m20phnT#3эFstGn4@7<:LtݨFf 0FstGn@nyt46Gn0FHF5a79_эj& n@ݨFaDэj& n@ݨFaFݨFaF!/.nT#0ytݨFa<o&3y wЍF520hn4G7ytFt9Q̣D7эjd`&o4G7yt46Gn@nyt<onT#0yvÝݨFaDэj& n@ݨFaDэj& n4эj& n4RF5G7эj& n0F8m20phnT#3эFstGn4@7<:LtݨFf 0FstGn@nyt46Gn0FHF5a79_эj& n@ݨFaDэj& n@ݨFaFݨFaF!/.nT#0ytݨFa<o&3y wЍF520hn4G7ytFt9Q̣D7эjd`&o4G7yt46Gn@nyt<onT#0yvÝݨFaDэj& n@ݨFaDэj& n4эj& n4RF5G7эj& n0F8m20phnT#3эFstGn4@7<:LtݨFf 0FstGn@nyt46Gn0FHF5a79_эj& n@ݨFaDэj& n@ݨFaFݨFaF!/.nT#0ytݨFa<o&3y wЍF520hn4G7ytFt9Q̣D7эjd`&o4G7yt46Gn@nyt<onT#0yvÝݨFaDэj& n@ݨFaDэj& n4эj& n4RF5G7эj& n0F8m20phnT#3эFstGn4@7<:LtݨFf 0FstGn@nyt46Gn0FHF5a79_эj& n@ݨFaDэj& n@ݨFaFݨFaF!/.nT#0ytݨFa<o&3y wЍF520hn4G7ytFt9Q̣D7⩥;Mh9d`&o478|gK/ n@ ` ݀F7f!w<;狣9xn@C7 nޡ̠o4nEV ` h ť5>0=n ЍfvCtp`6soh;bo?9h/}.Gfm~~It(͕)8}ǎ?>Yt^?y~͛iӦ{.;osLk5#PF7͕1ϕW>)iʦ޼*3m˓tx4iv:twl{CץN{ tܚ8u?ޑ[Wm\?wޛug6эFsS7f,FܬS]WO-F77Y.KHxѩEݸHW8 sD^0nk7;7BT: -yfv࿼|<9_͹~=\SƝ 3\C7BB7UZvvOy&woݘff7E뷈O1,K~cIO7sCv](Κ:ͿthaNqFH ;T%PլS]O ݨnXC79x]oA?nM{=;5tS۫@7 k^ଯF7?ztÚ%,n4:n@*<o At X݈>]SoPkf@U51ƟV9Av%?^`p8=5w;t^LFH ?vC@߽G7T7f?UYUQ4P$i0;F=һMe1F!F/SӍce"x|(D<4**&;{cy4Pn荌Av´5tDzHO507#0yT#֫s5t퍾 L ~_o{ 0=ћO5ښOQ\~?x{#/iwGC:oSXJ)aS7 O,ڳ/̺*rTOVsS/ @7Tp7gǜ{M~>or7^;=Wˬn&KuC9_:܍n!sM3b^xnË_ROz@7.#Oc6lؐMo0MoܴiS6X4}͛7gӛEӽ-[d[E'oݺ5j,~vȦccK+vcP?ݩd;ӻlz`݉lZ}pte٩`L7O l_;--|kܻwo6X4={d{Eӟ,}\{<}rsKn?vYc{gjzg-+GZ t7ٷ%m%ܲG7< endstream endobj 87 0 obj 40778 endobj 89 0 obj <> stream xWPT (9&#&gd  @C#I$ɒ&IHF2HFr6_kUbWswY5kQ;y3Ԝ^.paAa!4𑓵3y;"n(w'p;wE@k4 n;xcm ]ÿ ?hÐp _'; PT|shЮ@S M7sW8 u[ %$b`),{d s&w#q{ C: ϺN6iTol1q l7DL # tp{@3:10d/`  \` 1 r!6ā +W#6JA I 6C vF"oA/n m ު6[upnxn V]ۖ i*ῧturCtnn ˭]rC wk wBݜ=ր7AD_dM *AùF];& Cl*'=C"z U!E]+Yܻ߱}ޚ%]J&^QKBFG,՞ePGRIqd%C*9A=evVu|QeM@ܸR=KL/*b"o?-h]ݍ+f.{{# KþZT hcӫlsE^ l*(}kÒ &u: iZ&ή47Xٶ>+<{([uӦćwOO3vYC1ɦ4! 'ǁwmqVip*082oBo\paq~\R߾9rk5wB9YkF'ְOȬ ߋ)W?KJ],TEWתmB:-l'OiDT8{f~ ֞x 4\6AX\{w X]PyϑKL6k8 UXrtBxm3t~A/ ?|GߚFXkXG» O1#6oKo-[b' Ya$gCϤ1`/=Xww< Cx |?g}>q+ucge+O&#d9 >%;.~_RZOFh+$}% UX_ Do_ut/ "ʼn}|-_Ƃ53ȿ&MXwrhFdhi6_3y|Ccyne_h\%pHX6"[gsu!V)"/',zĿ)9RjmJipgDȇawTa/t>`XRaXT_ sӁ[h2ŻjP>,5#&(\ cj3ɏ>F??gʞ27NL~)>"37HI jAf'IX8zcޓ.DĝN-n#2FNOAn/~,B0z\mM&;5C9Hu MBl(bWzv9<:a$9b4ZVY!hyS2)N,xk:/zzH`[_so 8 mƻ /˄EHRF|*WxL~KpU_mk@O0pX FLƎ9 K}?8ymg\gp?|&sY9w BtKŹ*p&eXձ IҢ1r7[*SDEO&;qtwg^m)&YեHηU}'1lloN ' WLT?pϹEq>`2OŠ))YT~qb;y\BIt8Wg}%@EFDwu|(UQZd:y v= @'~oxf'Z]O23El^O`^i m#Ķ:yLYVx {0yjMN'\w-@6\@UFf'Zz _ʋyXVT_3&}͜hC)ȫT&j^r#rV|܍ haƭ93Ύ9 HxBrzS[1c] ݷ2 s N1w<2b{wn*SˏW{l}rM֠mh >X%yXxKM:CҊ /y4gٱqГJhSe(m_|¶2;K͑H^ˡ1ӹ֑%TzI6BK˧u\ z kt(Њxܰrt/<LgN폝tOl?zm4{|P$N>yK',*[639Vwe+Brlp/䜮P`,4w2Y+MACx&򔐲/G󖸢Vn-0z>wZ&6Ϡ"[hρPH'$r6Ӓ3rls~pxڥ>򗩔J^HѠ wPٶ֡'MȡH'n|Gg]ə)(:)*H9kԵRi|8%|3.//v`W׋YjĽↆ{˭`IZ¢W[kLl=T6I=:QcoVCZTk ᄂ?tCĥ`WobD5u1{A J͘XArgżն;Q}*tnP<b-Yk;3:.Sz[߳G_ zj``G}jo2+/Ǎ^0 ]bYHct{=YmUQ@6 DmCJ9!Sb~k |ۨv.q~E,d"nyKMk"TZxh9,=J)IJ',|}Dz8L~?<ӦB%gI=j1I ;'=CɩSfދ@ 廆8Y>!% vXrh*-k퉹-KʼnkLlM3R[ Xy=gnxdzC!02*i%GӔI6MoŖR }higzl;[E6!} x0L1Qg$NO׭E95i ONz $6OЧqU>G_qC>{9 v{1/Y{ #OV;f3Խ#p<6C- 4XqkFǻ*\E16SV)=m1ڳyV6Pv1紑2ʯ0^{2kO; < "_Oc<Ѧ9|\>faAfėX"s4(}c}d0R͟-=3?ǘ>Jd+@O `D~?(IBN!bWw9*aI qVCY&w+H<;|H$,EO}?Z_{bgɇi8_`NšyAM)gZ?CV^uchZRe n5 a0sAo'["t["V|,vcT,XSv8=P,Yѓj\ ]1~fX={ ;uU,ظ-_ 5d삽ҰG%-id)3yZZ^qmѕ7G8v" uAdPx@7Z)I5x31>.A)𢂱U.½2^\d%a$a<  {SiLv+:űTqP 0ib^Kv`%ʣG)>?]T&ʚO I<{0rγ莗2Ն}ib|iwFͼ?I8# ;}sK?+>INE.oi7Jy`Q-tg2Zj'{cZoOxÉⱷsb{ɜ%ZC U౩@kh|(sѼDw\hXWy5^NIrE!kȟ~cH7Ҁݟ#M +>U7۽| e a)naMْ<¨ἘgxJW=wʎ:xЈW/.c i_wx(_rƚ&=o9anAPy%#|ܱٿl="yn) eN[ثhiHl<%rjMLPjء`nAt`ˎb_֧-/YȢp AU^ZiFq_11^8~`Jی ^.'{%1梥=Sj URO^YXٍp.t ^Oe\~a?@iwCc`<6, 8팄:E endstream endobj 90 0 obj 6332 endobj 91 0 obj <> endobj 92 0 obj <> stream x]n0 > endobj 94 0 obj <> stream x| \Te3g\f@E!PPMk-f b@Dt1]E\3JR+-3s neftuknF 4_pyL'+Iȇ/H9OCrE7؈vVŎk务pmO=(^΢m禣,u &\eW XavpHFAPw-eK 0]bh*=u#nF]=ry'h&R6tX[wiйf E_erp⊁Jt f<݌א@)Bu3TXt+k$-V+ڏѤmH_qOX1oN(UAȎO8R.!}9gւ)=*r0(׮#$A:']NY4_9З;5l*̇&}nϝ:D*NRFF*bIhxzLMrLOZ5֚kQD~v| &oXX84r^G-=!|OS+ҭ$+y/ ΰ:bOG]+g\o=P( caz[bs`)=/X.9'VM&6Cfn jY[m]a¬x!qcґՂ7O?/]/veʘa <iqceĂD]8#={ç>t{uO껊'.|]2*[}%ݻc/jko̘IIȯE;H4~G~s7JBy$qӬ6Cniz:H=E&@5Og؀$$PIS׮Z\߉'|'| r=O]x*3ե:N]p ]mC;@ȁku),1uppCB39[Pn$#Ֆy r.yrT}TWo.E d<!RT}E^o~=#6$;nxRf#Leη4~aޭ^K뺹1swcL 9E(D9Anɢz?yXȇ?r8>sf% Xh*]ߵOPSAB &hEZ h5Q靹HU563*Zu$&񯫇9[yڄw>uԋWlw?yoV2?kq~hzUNnP T>_3\r#VˑBSD `QN?nMWZP:F2rT^eiY鸖r.Kq: iƑ,)N.8y0E,22B( + "@1L{YW]eOIC! 2^r;Bua&f/pڴCA14Q |M*/K؂Ѫ %kuzBzN=q]MwE ƼHASaCw31V"EѣH34j`B: ҙCu,x>z ڲi\ޱ[t/ɾ?wRpOW[]9?S!PY/*HS"nG[1 !^LA@=nc$-+2;;| 1~f9v:;E.Q@rS9p`Kw _)|84@^8L3J:M(6VlZ`Y0dXv21)5xwUjR6a6RpuRکyfn@)N=ã{e0AO8tVaA#` k[5Z[iϷ\2;їE`Cjax=U U ]Έ|?~47ȣtmw>1p굝GѰL6vs\Ns!}øݼ=haqy_oZY=c33Aӓ1 nsk J3b՛_w;_zkkK-$v3DЯN䭄ћc+af^f!Yc^BIT|Fq*lBCCTz-mP}t\6p!'ʷ7stJy ^/5UCN~Ay~߭{\#e ^6ЇIiPLq/ՍgL$O08M <[@n./00ޢۇw~ct͔ zsz ޭލSGՆs8'e} .Do`ON.@TCPۭkMdZۅvth3\HТ^-#,+#b5ˎErѢ|G g^ا̧GOG;|8l)؆'UԯVцLh3HiŤC&`l(aĩ@=af2#@ft\9eZ׮ZcVz ͟}C|X@\@3 '!Gȑc#A/G%!8MٔdZU׎UԳ"+pȕ#FҳA2 TA.[vұ[U3gGe\nofuso/O/O+wT㉘`O,KsĎk6:9dZ-& zFxM+H|v[>Ng00љ_f'g\';;;r\ r ƵmB;M" r'o(7dH.DHzaL@ 8K"|'$p9o"qR1͜M'yT4sbXW$K!pW 7p_,,."܄p ,W6qMTjѷ[%vnѼm[aaML44A{.nv5{Ŀ(t_1^'o -Έp?;g9,.ٻ <<8`%<gvN?Ζ9ٵfj֣N#u◟7.\wZPGqo8FV؍q|U=/ᖓҎq+۸H'J#5r$$;^ϸ߯kYF_{ieVF"9q9'B7Á l <Is B<ioss9/sVpJn 0$OD8$ uD2OP'|0]tJԼ|T*q5 UbԄq+iנ5x=Yϯ֊o嶓m6ax@zUP&LROxVt?XJEckArםl/cFzYIl^ӯ? [;? OO6\koܞ>Ν/Zv=]Yq)pwa?ӓ:gdٱQ;;޿cK;c|G_ϐ}齕t/zq=HH Q{xVtPF"No9s{M ]O8;'&8(14#̑<ϸ͜%4#S=S['KIS6Flsx~\ߥS_U~ @z|XD/8B78QguT$-`D/Gd$On'+jxv.bH0D.ODE2zOƋQ.|aXU2ܸoĕD11ee/f>٫D}*B=B rf8p3T|#gDf*ENʙpYonir7Kȁܷna\&S^youQ:$xQA:(Gz^ ,Œ )r]A1+IDgv2z fpN>Wp 9b.GrJN|4I EJnqBX+z64ZoZ]T/-5kvB gwBPO}ꡬOiuZ2J\ߥgg st$ ?ɏO{F5W@1ޙ6MUs%XbQ =Zlx`aX|şzoq5(1@ AMjWN,NAbX$As@1ctJ~^h)".BйrVE=cw;L#t\pu1 yvNA,˰>Z9DxnX^FxgwO!Ӏ:E=h5'`£aoI =蠯.=-vF5v軭7~˴A_jX͖hA_mN;C"ngRXx  I-.&uA9#sX 1 gz^}җ )9yZYbBQͦ te’00>") [*bU+\=1ߊQzސڻ[`{a{6Mk7wLy'/99MA3X찡&  cN) Iځ&I}|}_Ru&p;n n3sO8OߥޡPww]>A@@%JFVc*]S~٥l$x<9$2{Xdb$Hs(aop>^CИ*$ddЇq)X-zɛWKߪ}đwx|[ >_넄oJ=ቇw~,EH+H!9RBnttØad.QŸ^  BHd12I40̐fR%fTj34 9E[ȁEW{/k9A{ӎiL'p;!qL3ߢ"?'fpߵR ~30 aʌ~6~ };:䎅G ?3YΪ=m 9ED[`vV8:SY baԻxNB$.Y%㠶|\[/Bu [9E$!G*Rf\O@mAܨN'ú7t\"/ ZG/sw_;qDVO.=1oC_I@1=#ͼvE#߲J߭uZ5jO8I4I3t v{ tlBx4:ʿcvΏB^1 4_D>a:$D}w?nfǠCyЂ,t/O>h˞c DivXB#B+ #a# 01j)(vcn|nBU56OLMmnnSSQ qlwI2&P[U{*O-jp5z)JeCVTyRFkQ= >X-otUU-<@8XQ*u@tBc@ԁTGq̭|>oE )noESH马"[x+] dFIn00j`h EirSJM@LmߠI6`>e'E0k}U)pPUj u)q /:[T M uϛ{*i&o3e[箦|&R ܻxlouFPOzmLUr_n@Hu©,A6xnȸRt1׺Z(Zv35zWPTj`_:FȢ*]DU@|tE"4skBsMh@^]MR=ԁH\QQR\vшo6}J\7Q܁%:o_hה{(&ea4FU_N*{}b\J=uClܭ4չ$ -q߯Yz7SUKQ|&0UqkX!?޴Dzj*5*y Kye*%JQySs*q%ܖ_:}Rfg.Pf)م SSEŹ%%b%VQA~.N);5p g*Khl*?[RLH c1v_C\$Q[oэYGL? C*f>f}S)N%fT0xڜvL2h`݌j?=ԿP,F ap\·#Bo{o܀ HmpNa|*Kf4Tu)ŌfE^Fo%oISP˼t QOѨru`8gX45[ͨ:$E0rTkK]̊4^Fips4ӏ`(q#R:a몿'7#U;no>x3D͌+7[w+@捻4).ה3-,H̓2Y=>Z&s_1X1U,+aݶqw8NGجRdA(^(ZI" C!3˾:&\}џ+Hk:efBxfS) *b6} v02BTl6Bgm q6`i0^=žL_\4h%lf1= z GWLLN5|]Gi(-Cg͂b??  zJn~nv1h./a6Ðƨ< g&/7JT)I䆜 ΍#a/I.9S`jvD Na$U $|Ta:O;s'@%ȟVs/?0+3ӄ+șiGw*J~wUrs*ZxG%J\cތ$o*y]%Vk*yU%ec*yQ%GJ_xQV  /H^XUpx9@NrP%ϩJU~7yLޗ <&ڄ} d<D?u<'TG%mdJ,> endobj 97 0 obj <> stream x]M0>nc;ٕ ġ*C#$2oyV=3~l<;ݔKCs9w}m&St}fŴ]3-#6z9Mzj۔i)˿6<>W~2E^6:K}f=ysʿRx&lUkYB`jТ̢\lr%JY k_ސuZsyu1s~+Էف_)W`W0ҿ^>v_o_⏳XWORui)-M.OaD~ endstream endobj 98 0 obj <> endobj 99 0 obj <> stream x| xT9{g>M $$!a $&A@2LL2̄RVIJHQU6)"mE[h՘\{'Ky޳vflC6/25 W]o^{ !t![w+B\e%yZ0BxsOCsGk?C(nVi�Q`ƖM8(Oxk]yWoGs/nEl&8}XIіnoFHah]9 No&j;"Q1q IrJi#?\ztvMf{7F]^ ;jx;3P-zi|Bx0Kf(/B.-t_wŽ6_3{̯+J.Jڄgah X\A/0gMxpJ8. vF휄YxS_/q2&pg@*Y Hh;`t?@_ PѽnpCyadpq $ Ss_.3n'9h Ҽk?w*HLnI&3Ṙ.M@{b=}<]܋(zbn C@1B=|GM z;`-E_"q*1&n-:z<[3Ihr / vDO'9nUXnrI-z@A@<+ <0ʐm{^5 2m:y/k\\4^>m{ߵޘ9 xda&@` PKV`:|x(bp'ǣ"]{Z=G=A^gꋀ8ZpzмEWǁ6;6X'1cl=]=K]ϲ]Fa͊+%8t:8&爌6ž~{9~܈7(eҀ7o(=](= 8Z(*ތfe | Gt <I_D`5#]A@'t'sǢl&|鵏T[_p )k7;z0ύd#I|<KВN)8 7(J;_Moi ./H?_Pm\hZjjZF=RA/u~}H[wn3mKæ,;'N ' ;L$x{S@ÇƏMcq,x4|+o(Q zJZ2]a>$$e(Q#=4K鬈6v &8E棣NN'07_x=sۛnRB[wsW=r#է߁H( = d#cYq]>|*a˰ &4,Ę fdbvNĝcWɻx}Y94+}<=eF\ynyIJܚ'){{RIyJ~*yȜ$WrUREr]RMIkH/y{Τɶ%4ċ(`}:NL:b)&)vޅf_ lũ? Ab_.{˨{_rj$+ C! E\qAo%x~Fgaj'x w?u dA>΍7*^r3¯㗑V62z<~ iO]h?&厡uzH"(- 5gm H=o p c۵؟vBh$3df!I3b#fUVl)T+*Qbm۞.h/ m2pD@I FG: ɢ.|n9/!B,r͉60מi\MxiE1hњ@M#F@dMNNHI/t2MWYg@pRـqQYS6*0ldCll,sB\ o}u'}ٱD,Ϭ>.Y؍JॉIi1)i|w<oIg$u*UI|E`+SlOAkaju` Fh6Xaq8s%ƚ4dJLSyj*M5N1M11̖fgfMCCC!!!sE,z"3F-ں]Qɘt`vVx N?J4:98t K=uoӕϳMi4 ÆaSrܝD*Ma1Th+d3(bF!=dC\GA}6gwv5ϊ_KZ_cA-@'N/prRU.\𕮮Wn4o^M*)PW%j"8@h+J,8yҶς93*VuK]|ja!{MXZKAH+LZe9+ӿD) UM)K/KjZHvڻ͡O0$WdS _D/d!?_Vl$M| ֘BhoVC;y0\7 p6BÿN@׹7tohBR0k/ݹTYō§Qʪئ|NURkY ˘,vYDzAQhg B9ٚͩBCSt$(vebףoN6/ X$Xc]Q=:aam oCHt7|DDyZBVDr 5"'+fL)S05e d1PW\W4cWE>Ř=v~O;q|,\cSGަ7o>SI? &&&ʳf'ϖHK']||]ʲ_/ɗ(֔1d W&W7>dz8iSfySNNN]IwɻRF.L)Q+pS+>n떅m;ϕ{/6mxg-+0/TNL\Z1-?&% tnuYQiRJ;<ʼn,NՊJ7bbź!U.;Tj@1ۜ+)!(OPvtjL@jµ_"1\7Y:_O:xkRJ2EI됟`GgPaJZEu4K܁0^Ohj@?ELxuQV~!+Pp-;B8}Xq,6S8XWžtfZѿq)9~{{,#/C?#\'ZH+uUz߬DIcqq񯦙 FQ_l ?#-}'7 kH Ah}MZ_@&V( :zdAi}#}O kyBIc4BZ9vj}=Z_b/ C5JG No2^f+>G1$%dgk;M`(ud%ur^s\IwJoXd{nk\aZJoc3f[1W8r(x[܁d_PjQc(䟒ޞY^Ȭ(ҕ87K$%T 4MV5dktr'[;d7ڐtg+Z0wtC77y[A#SQ#A_]_][5QzꛚAy$UP;M( xxڶ0@Z桔7}m! ICDTiض d-^Ƶ)ؘ1Gř A/v7CPS :!jo|UC}[zAO2`[2o]Ψ2nnS|&Gp Uh5,k}˽Ֆ } ",Ջ59j܀V=S_+XF@nWd\un@&kzbhyꛨC`~nq`<`SC+#HSI:*4whgtCZ;A?b{i'HEIuv/؝W%>oL r*uTMhRkjOn聲Gܵ^r(Am,@oգ:8<~f%ꨢr3"3~w ` C~i B 6׫d*˪k* *|~IAaW ڒYeQWVP./sJ 2UUry\2JK3\Yy\Z2V* lnae,,)-^!TQE4Oȫ.ɟWW)W̫(*-&P~yʒYp&3ʼ¹ys2(r̶dC.OW+-gTWUWͥ{tR++ȫ.)/g+y3K UڀҼrAܼª~$tN8²ʼ 0v@%l'$Q//*fL0 PȬBȃ2~KTWVrmIUaWYREI(,r>qȓ*L}:`=1XPW (:?D[sr5HF fj03.nUXle 5 2 LX8d%5{{!iHѠdŧ忠]3p,G` 'F BRdwnRr@KYC9X{;2ao5FISk/ТW!@C&G>G(5njD!ӏDuPK(l4zCF3aO@^F-(fKCCdT+F^x{rxz`h>^<[au&P nx?Th,P86001?gG{!6さx 0C?(+ӠI?Ztk~*1O75_ (V=[a`켟A j|5kM0:Ma!FcUؾةBCUڛ]'[5RQQTAzAFWqk3mNѕ|F!i@}BL"^_&tO`ictSajq|7 7f8I;FQɴ٦Jma~ gX~U`twg@ ("Ds&M\%R稝ɣGa{C=`dg1zؓ`o*eS cʯEPa7iNjX/oGV<"8ho_60 <'3ݚj5ۛ*&v=:՘`VӤc5Nt0zY$3%;OC &FiOQr @S93, saRH5jsͬH02 Ocg~< Ǖht5}OThj7<ʰ "^-yIqaSSzg!N\S|76ZXMWdݪy,zN ԽJ{L#2{5̚VTj5XW<@zA65n^(ZI4"`~f7AӚ[|!\jc iB W9*Ujt-Tld*ae> `'TBB,G,F%<)0CalLGs`g U \-wȇy0bDS_f>DQZTJa`J0esaT giyg0I~YEyLF2 WxV-c<K!@ՄJQ>+7Q tU3*(jmgSSsجJYeR~*)4_ 3L7y? 7l; >;cr(gf5*E*ҾEF)/`DI`\:LRlwȱͨXxdT^g]3+TeXv1]WI\y,,y}Q6Xa? 1C=X̞J5 pW!:v |`%_E3܁ޖ!g8;ZPkJ8\1\+ =fWk`___Vz;la; Yvb(,tʁb ^Aߗ,XY?U)6m/y-90C:C0};V0/35˄J zQhSкʠaM>V_dߴD<_-<)4dpx<uhg6>2[OFx Nd (( ql&=c3=3;Fb ZV6}؄933c&lx"XB#`x`l`'C&'·2,2.8 fO亼\( &CQ7[!_U,||ULMTȿrI!0B!$B>풄O%.ǒIX"&%|w0@!BWtwގc[[<7҄7iϯ &z))~&$lkFL^3?Ž?vWi/M¹a+NqӰ|:r/N*嗖/&/_r]CCB~!' 'b9rScO B>'! Ǒ#;9|$C&rT<Iu(dB)3<KFdt`Sd7v"O*dBWc ٩_KdBnUv ⷁupdk-W# aaB޴Dx0yx 4aoT y(\ɃpAD_ pFM#?W:ܣrBT w(4rBnU-9gOV!kbjD!R!+ݤC!w Yⅶn'nXEnTߗ!2Hk7i&,SHBLBciPH}z$D<.VLV"H`PIJz,Qb/VȢE Fœ YMU|.W> endobj 102 0 obj <> stream x]n0E|"W#!$>Դ@`HA,zh+ux|Ey,u7f/4ӍiĕnMW}5z,L}!]_E=Y^Z{S5>W=k[6vm /# ֒Uꡡij2A.9H7\26*m4"8M$G\W;p93㌫|{Vypd>OϜˀ3'0'If#xJ]8HO)?z_˳{|1Q?*W|1]*G_.>?gD}7Ǝb7KNÈ.X endstream endobj 103 0 obj <> endobj 104 0 obj <> stream x{ XT׹{f 80`x< Qaxhb^bbbw6zZJ:&ͣ6Ӧml5}x4D is{3_׿63\b"2{p]*4!\!&1 7BCd݃hL^GX+Y9(LxXbm=hGzĶd/\`'!n_ R9G*rNKv:yi^JbyPM1]ηF!"{ ? DNO_ (t 㙌 LsӝZzrǫ~{_XΓ`Ah$ˏjrMdJ0TM.Y\hamb|~ټҹ%s r]9Yi)Ɏ$5d4uQZ1BV M;F&MR; -U5;ka@RH2 ^F:c˚@t8#]Z\'GGw@*qGq@:dXSw}J)m 8.p*p;:uؼS#k6ct,2r_& -8j|{,CG /ֻ.Ӹ%@SNlr}nliG@l{=d V#-%5@;XtYf=#=3;uM->eay30C@W1mZXRR-8()lȨAz\h[0<Վߖ8D` :C[Taޣ\-R/r&G~2{Z!aSet ̶ю qO+YqZŀJіuk O:[:Z[!2tvelnkr5 `/qXBhPZV4`͋G<) pj<[ X44ȰUwWX&il*VE<{=΢m O#4]\ jlFibc2okqt;Z=Gr {|Sf!+r#s5J}ZݶQi!w|a01ZγۉOrG<v{رu,u4SQeM꠮"; YŘv5y`WS[)]-G)ʎֱdk9eC[RY* 2hx)!#J/4(@6t4fnƇ(Ho,i(ϋn9֔DEzͥ ?"߆?KI qdTCR:R)tV[7X_(GB.p-)̧0pzS"S7v]o>ƽe_}۝;I47nZ黶Hsïwt\I2)"H9O,lxK9~-%ss ;= a'ZY^`3Hz%^d/|\Ā酤9(a̲ttf]}sCoY*@6 Q}tG:]M 2,,j.IIwyD6e/, KポIV1oX뷟z!tfsWŽ NSxDtITLo-ofX3DWK=(k(ܲ@ ^1, Lf2iff(6-j ؽ%%(p߶4\0 Ӝ708c|)d˜M C: KCl"[ylhwj690,)OZP3o-hOur7+n}Y݁jƔ9֒g3k%[l{MYiD{^JlۢyȿBFOEDVƸdڀ7..BKkIg2hFFy/{Ae<0ΫFy ~ Ey@ʱUeO??vG`Z;N(k3*QCry h}y0wXt1:@JrRbBSV(iln\zB3˝lOW5hs\x\ʷpjq' A׀+k&$Hԗ' cbD"d;ZcuDPdr`@rzHn|uҬ1a 1m(ׯmo*м?ƕ70WxH{H4e5Md"=ԹdǾ`QBe_cܸYֻ^}~zs;]|Om92<]˒2[v>r4a eJ:OH*J=q)F٩_yE;̒gb!6lOeC{Gb#VCg@r˧M"!G"d2#^>*?p[Srك!bϭ?2.nk %9QJrO= !lwi:H8&QԜ s>8OjV!(OFb.s \F )(tnv;і+9`8znzssoiT?2C3ZgTKxQ QQ(n$j͍DDj𹼇4WO3u◅4/!kaaiw;>[Lԑ?P +@ʥb GzDATx9*^0b,=FM{ o> f'\cQeaTd#EQ9hV&3 ɴ )`gg^/Ẅ́@g%q1z!BRt ٵe鴢YvtJ0;b*}Ý.w6i49Jf&"CQPd][xf)-Þ;c&{fkFHHxY)^ۀ4g!ÕQ8!$kdɺL4HU\ !Ke*/Wa֢bAqs.ou#LJ&-/[ 6ː8aMz޹ߩ,W(fwyF?2&&>r,㢸ռ`⑃& *XFjʤV{bZ:VMj]>&W$PKRBU'Σ2oj$9=/q![%Ha'%Jp5z^$19J౿JD_Jߖ mRd@f3p8.$<% KRjh)HP ",: su̼=KW$ ! `X!54H/UH4Yy_ ^ޒQ F2iD$(bA#Da-D`v1p($*IY䜿0x@JpLDw10D%°!CRav`"_KPF2Jp&Og쫁LmՍ.}!tv݊40Y~g ۿ:M=۝J,Kv:w:_4LL ;p_G@Cc75y.)dX@4F'm{Tʥέq7L H'G_QG9@&ڼڃJ=[փpUL3/\,q8[E.8qEډ:hc bC"Hb Zְi0)!%&%4IfAQݩ,, GkgWr4xHuI1:YV%$*Q,U\ҏJRxJRH)Jϔ+BGH)$ˌbGcAu!c4܏P&ɠ/$̰Q+?6dNGL3@j#iL+vz 7܂7,Fߜ;8``֡ʶ⸐)A9*'(rS|&O5PY(jN#jUZ怈 9- Z~1j( P빃.E(:EA-r<\-qĻb,sqn KnNk&6{]peqx SiH);'~A$,(}ŎB(#Q`Bc 7 .LJZ8\O~uOumVoOA{q 1|c4a#CUM=E x*}"< PD nF3ͤ Sto`|`d 8W9~՗^|RT0JMQ4|!h& fa %i:-`p}re+|0ի_[ԟ)#NaOE(Dde䷱\Ui#6hE/ bLKL >%-r%;dP87~g%-4|),)T 3!(*?q;_avdN2Z7MӚ~ї[@yeg˺:'4yBP%pN㸈MOiD٦T)Ja|End 0"BQ(dIlvh:Aʳ o gqߎ{D=5d]^oSCL:vȤ )gktn o3]8yʒ3#W5>|+|o 6?@:0Yihzp݃ڹ?M1\ {Qkָt4D=sGf52Oy)=^Jke11 6Zi=Dt*|tDDPeHMQl&C#g%PeYY-^_qfNښ|΍]1SzN,IErA<3&i4"dtd3F2el2h rE`>=𱓠* K!xGRr*F ELҍ d5Ͼ7hkqx)8:fa׺?SPΥ|'M?ȃu? ~vgNXTwI3Y!RQƠu,ɔuJ~8*a NiCYCOM0)+E$KuYUy" ~V|O=$BQ- 쟰O"|pX,{p̫+q{hd=LaExL|KHїs K"wP"zD8"DxU 1"'D)g&6T/Bx>:p,W*҃l\Ďphwg#M#*oO~Ԋ(-:fʅn^sqErU>ĤK,ދISI4II;I;,<J(i]^-@|jNv/+dVrcfг/y=& ?b_l#|G7BDIH( o x@p?0ZVy7OĎv,p5 V]; ~!& l/xg `d)1,sI61QS>pǴhyn7\;YG]Z& G c>/mꜱ&ЍqNp(4999pZis*syI(k .҆vտ`8g1kP#\FkHeXPy \0fnBu,\V+ᲆ`AAt Za/r`[EpYG*m $reJ4ܜp#9O̓tpY weI~.epYC_$Ae-#HJh oc᲎ܩz߿uw}ϰ-}m-?7ضV9e[ط6Ƕ`F0dݕc[zقKmCN`gW u7_ܻ{s7TѿkA[ 6em˻XC^NnqNuAAC݃gsrl }öξ.[uzv+k;ɾe`PWZ6Pj*atۖtw \zkNgx-g}[]Cp9=Û6.FʌH͍&m dنm _݅o^;?uk^W_ozu4 KxhԉOAK֓2y:YK2𙏞k.)2MYK:" K IB`iƗyÐRg7>(cbUA I=aـ^aDn dut8~2E>mZFkO> =lmf.rfh"c!_cC1CX`Vp {2B4)P Hƣae>KfxkXarO۷͊| !$7! JeŨ̹ۢDiVd(5?EnU9s3a9JiBWحz }s0s85xdЫEjiy=/¾^K 2|,,V}ާߦz^߯ߧ_?WZ/yt}F$`579ueuu 4- rʖ1޷w/]oj tn ta #X0HEx2T" CCY+k 9桡a2"!fe` G1p-af0NV OFFF Cqx"w endstream endobj 105 0 obj 11371 endobj 106 0 obj <> endobj 107 0 obj <> stream x]n0y C{$ HaUi $F*Nd_3]07s;4>x SGw|fHC7?K;ey=ܯi\=xtYzVC\n.JgMzwu~k{q9g=zO1}rxmD{w΅֟]ҺQݮɜ I96PC.uklecJ\I"2i/92?K%x#5x+H $FK X+J[8ˈo4 /y)guğſDO||VGR?|H "/R?8ĿFo)9%ſ| B ,MF wB_0<wj'dv۳ endstream endobj 108 0 obj <> endobj 109 0 obj <> stream xս xյ0;ɼsu23g23@2LD @I $\H$$%*jъ7"bՒVxl =c+ӣ~UIh&kk[ֵ! D+ִ؞#F +دz$/!-k !|pGiWWx2Bާ+i{P5$~Źtuϊ #Bk5-ze+ BӨݲ-Կ!?p#k_2>4?1?&/+*FlO&$grJjZzF˝syE%J3fVTfn}/XXSÿͿ"3(=opȄ@h3 - EuNc7EBw7G?CH@2z:ڏ{)\F4Ѝa9C(I৹~vTGx =0Auߒ(t}%h 6s !4@h/[iR9:))؋(ϣ g1{z@R:#Qu5 ̯7wo TV̜1],/+VR\TXzi)$-ƤtQZJx`@Jg><¥:,wЀ)#vf,o)d-bR> Mr+w*s0pG.K9KB˲!)-#*+,wHBB3%##r OGfڲnдCrhk$Y1ZrŰ^0Y"8C_ ZniR/sh PP˹N: VFu4չ WBqGԍ}e NGaC_?SϢZNV=DzFׅv<4bF'b̵,fp"f2yuC#\JU񞖑唟XS8/!^䭗`픪NJr K2$H/ï4T^hJgeso M_S7"VPiS>/MLBjw1ٞ΅uRHԼ"j[YrW5WI`^FKr/Q.`R+ZGl񭴧#b=mzg][=c4ZChv)Ǯ>'pJJb@({ +$3MGE9!h6ϲ 30EAtK|2di L8FZҜ~oZJj\m!˴99qsFjlRrp9䱂'EayZXzş_`Ui(FQlc?8>S'xqInMYUUaYKJr:碭2㎆&ӳV힛ˊui3zq\+BU( ֔-_>Wρ>Y("_Q[KĂ"| I<\MAnnJGkA%$JܹbuR*N\Y` ?*a_x]x_P|$ ?/'B W%gb! =f~iȠ7 p* 5l7g0ne)g ,ax ~_ݚߥo3-1g9ي+ +7wyMFjx uX3*aM|gTp/''@> eaO_PT}.a- d_D]F ~MzZ/scvӰ,MZLZ":'p>(8k31AS(ǕpI 0UJU*MIJJOJ S u-9_и.Z远3D>s oٯ/qӶh3rfC~Y\\ԖrrԇUVrFn7zN9g hl2 a[hޥ<k6 Z!j;9HZTLȚl 7@ $X/8r,\QM8!y@~*_&yb;?7gcx=r֚Vf4W6 bp~r˛\3_^y:tf1 1F('rTSJ  HDDZP4m\e %^F6h5Q57G6#{˺ ɹ+rz$4푐kt|FAQٙ1ot%1F L*όfeqK*(6h j \`,/bZіI'_aJY< 8Ȓ6đY3KʊLeKJr=Mc!(dZKI&8_Eϣ<>C/?coUh8?I2ѣOA"b&'e+PeV~ ׹:"uּh?H4% V#8) "/qiK4n ZWhce62 rIN`vMA(:\J?`L0+[h& ??kz|V]# fywQ{{ս1oX6sT]@ ;1i774Nt6vHl N~/xDzԉ:|AZ9̒0!,E'#2|"RL'J>ⱌj8Q"}EvONr-fB)"# $w\eĂ}f˰er{-d4j8O8&(sE^XxeKMQ#q:+CҢvp?voqբm՝:8j~ ffbAz(bAEpc!ܘw&HLdp^wS%.i '\802 ʧAi6 ƧKp:ItjuIG3*s)}鍇zLJ<݄jL-dmӺy$'mp}%P=/K d*I8.k~GV&5 GYc?ҩ3gvMR`Q d8'Ěa]x,4kS3)@9h6nOTiiKL "EXjXm@T8lhc:bhQ3VF*^IXcOcV@+o6`h5EsǏKh*o+?1UNns͢|q˺]-)%N/߾8gY;;+\*։8$]2!|OzN 䗨V6J=2^| cD-xN^Ae@b5ힴT+:$ ԕun(8H3.~鱕xko@Ah2P %g~?&h>Si } [GΞ2/U9k 5P僚a 5՚ ` JjDȟB;1m #5:V ϙ[&isVvGO-=>}uYMv'/#JCbu}Jg 89dQ|{0/=c^_լy._m= OΜd . $D*;7п텁bV߶peFrxHzc^O ̭{Ahy`U¥3wn۔g8b.XVvۦơ%Y%m攭^T&pʒQXxCpXxF*G2 5fMQ;(3BP 5L^ⷖ<ڲsNؔtLKˑĒv<=37v͓iLV+i5Q#tɃdH&~1bgpaV a2]kمA8Op|֞<|K _Kgn{NgYeL8އ}7OFtNe9~:hg8u$P^ |ʋz![%2/<=^kDzZi-|'/z*RJ)*=Y/)RpJᯥa)^$Ǘ~ktwҗK*ia$,+<]@s]tU)g+eR||)[JoV# 8K9.[1JR()d 6 tW[Kav)3+0=ϔRHYuRʈ!R ?-5 +/ZJh>g )DJ!WJ+WhRrXpH8;;΀YR"P tXh)Gsh,ER^u1#a;B2$Ѧ SPS&:%覛o&-*TF0d,ѓAN)(Z: &pvy_V*.Pe,_u ùj^8vcЎv7?vraETFF0:F 3F{ia0o~g}D[?!V-IP w@M߶`d3)9ɥ)bmә'}|x SK6}˫[^ۼ\3}T[.쯮a˖_]t}clW W2)1Pq[ޔ1H*N`j(ɷ8 VGX`QBU.J6.M#^6ZS4!sEUEa%Б%Jk&9ӝP[.#SdʛʄLḦ́2O2L8 3!?3ٞIb3L8ˢg3qĔ Lj$:ib]ױ|N{K79nv7Ը!pX7|Oκva@)rɝ27+!:#IX7)_v"w{.hݰ_BV 7N=~~ύϸI7lwzFa``q1pݍwneyPlww$3XXDJuZfp wO% O -If 77>ƴ R*XlOI͌d)<~s77(Ul7"zMJvLiux7\*ϸr7`X!g#gz>8CrHEyJz[,NEi-N5@m\Yy]8E-by7NY6U7Dz޵}z?GퟦZx -5M&9 _Pƛ=܁}NWJ¨8}!_G R!^kr N~~(:r.9gSRmXl kfL_řYJ@ztFmEҵЩe&C-R\8{,[,آZP*x"ɪ5LWFi҇JԆLj"yovFqfFmݵ=o/˶*/{mcs249wաeGjcL5A7V}jz` .\ 䄿Yq:ue@ӧJ:ΓO?}ؿ[P?#g٨&x{pyy@J>dQ(xlXNFGX4RAgmb;EExU`yE`tv Wqr]սjSB'6E'lI:c8=!/:qֳi5gtWCBnc*vn[<_H.ϴs{)ȩm(*cu7KۛGb:[SliΉ\37r XB"4rM ekGx_;)^6ҠCv) 4^:NJHJI0j3_v~~4(|,U`0%͒HJJ\γyKFjW3?ePQO|؞ Ԏxq[OX= e ِAjOT6(DH{Fn 6F6`7w6#KȽ7`.)c.mɄL-ҙ\TS(B J%%;=lS&&4YI.0lM:e6{rH5>/涰6EٍGGB_=^TXsk_9ѯF-ڼpWOߺv?) ӓӚfT.*Lhmff,XW'8O6hdM&TYavTDDmawmzyIta;ʆt5 Ԣ/1dfW7tԷ?.2 nlcXIVY9zEUَS {ׇ[gV|d½kYCKpwc:T=ZYyN~n4DŽU:-Tp_Fb8Q{0N?.r/Io'3ɒeegd>29QC ,1 5K[j8cjد*Ь5jUW[՜lZpq5ǩ%0 YP6҂TQTrr)X+2R@^̿bބມ~.PMk'L 7Ü7)¯&{v_=R^zNdž=)+Lޛ+-4ȴvYhȌƼ¬Ҡ/k'a{ .IF6( :&58tاuAݨNө"b'= ~zk' o SY,:^Aʭ[pE#ϯ&4ܢ%'#xfhh}fk\sWM;oWk Cii9K\ZO +R`{fSQi@lT:l-}vn^Š⒂gl͒wP-2q~6kch`7{hI˸HoVa/viު;ֱe鞜u}K/GuqR㑵e+Wkf޹hw6]+qk-_i_JAb ^>5>}q5Q BbddU8EHxu|$jOZʪmYt Ⱦk_e$\@?_cgz(Q.0ןuut +s@tX\}8v,X# oJJ~$j:EMd؂S1yG ʰٰ@ چhl<%yI*]} 7DCzj 'dS]0cbɣH&v0u ^KPsD2lK{ug:fͬV y^3^:t6}6i5[JV֖T w}C3~^2_Wm*ivޒ1-e-cT6fPERit,*T j1 W d$ i?!R<?y|Ó}Q6sDyh4+g&\̺)&&y7u(?E{.xǍ~k*z}ߪ-sa}VloRhU7}(:."iE-Q%-,QT*90"$:9(r卥".؜-ʞMئ\k?>緎=k"iVF/ ڝZR0n ]׺\6-7eVLɇ1pi1N5[*2|Ԍ` &RvBx(?=]HpȄ~65PePddUkMk1ysЍfrwDZĜU=O͌:Nl 3 f>=/zKc}+:Z//Frl g8]5M_3-$BSue6ZLSm%x! OxTGd>b%Vx4j6^?4YƴVXl' 2e.IdߘbdJ1Ԗ蚢9ؠ a٥oTl7vlLўN~/!뷺Jh:5@ ǰ}Gx=(e u Twp 6q`4BrS*imWJBH9D'gddTu@ݮީfaoP)/؂j?誨VP"U AqZh"-Th!_ Zh[iaT ]_{\K_jc-|׵pV a@'oh?~U,4kk Eb s'g)4#B ֳLkZ25o^ʓ/S%*ԄiQ~Z\ & %zuP"-$iAڪ?g9rF a1-BjZkAJd >DUk{ Z&WqVtw,)MavomnEL8lQ[Rz i9ijJJG~ [C Qy+WC؍BK %[Bu1m½J3A} Zyd al Ni!Gl/o7Hޥjّ%aov׹\u=߲!5gO3_q 7C1){(㸭 j5mPUSI>p2:6zchNfX[wZ97 $`K(5kPvΏNYXUgͲ528 FgY08,坅/̺\%TjEY'TȨn\#)>u&A% SF6Oie2#Z IO_57Ɍv1E@*6;jtZ`B8 .;+WL rBSg-7{~ Ϙ]z{.<0% (]ӪҮ˟LXv.> 'AQK )[,l}cQ5K@la26[`bW*2v} {3.=cО0MYTe:/MrA<6_8 9j5+䝊 ,SXRȠ!/0Y?Wa, CTd "UիvRqTW*Ǫ^0*Wm$_eէ*NC *|ZUp@`@T*3*bUUFh cT^F&G޽HGvq^ IM+uj-k'GcEs>aٴ,|tZ1<%{<AR4ւyPIypAU|񶨆(Rrm٫b]KIm#G1u@V" @P D )|tIAy|6;]Qta]P#`X'4 ¨pUol1vjbtaQ JVr=^I]7X`:#7~%j(U^g;3R2u>r)R_򝴾}iöD6e[iilsC\VfVqm,dᬏ<^I>l@x`<>I A:(=Y A+m=;it8;E>Ka |;|?{S@ޤ#|q>Hhs2/|胧|?C>AoW1>aؗ vCovIX*X7/}iߏ|d|wn  4G_Ls>;;JZ6_d g>8Pw``}p|@xu->X&WV|@s"(>x]>D.A}A5\G>x9EYB?۷ć$rb%rDNG%o/K`nm]dVzFYDGDeee<2*h(ֵ.*B4Mͽ {uއ[AæD%R' =tDN1Eu |wvGa F?Ui4Z:Fǡ76VQ:싗tQrABU՛dKJoAQq=V*޶ߙ][93lZA4Ys肒ʕs4e͈^JӸҀԕP"j톄 qjQ/@^xDN0.E(~\ >Az@t y||I'K#[~ j:Ha/ME6IUrFKt" ~~HMǢ؜_gl_:~`vv,rD:~dTNfdzy\DH nŸp֤$Xw@-T8Hˋ[Yc2nћr4@J-*,ŌIL ܘX!36乑mFi/N4y,db.%sñ%Ol>Wlm>|`W u?}v׌TxԖYN~ܜ8ڶd~`{{4aoY]Ͻ8vSi#W,B0E]9ph'@8+Ȫw߽wߕEu)qR~JI^QB^Q[ɕX%R o)񞰿J٩PR gAt|dJUfH8)OPO)bN((,΢Z W)7+(QR^.e WBr6!Jأ}!l)r%%2䄚#ЀE,qT3NQ;UnǴ_sM|X f6qTE8ݾSUI6e8wdc[ _q(Z<#N;_P!fmRqn Uk@5Xnb8HDhi2>fl.֙@fgn.9rӃ^umLM~]y<п~rTBx:~v^ڝv^SV: R j)Au^p^rrN9a Ή 1X@B/RX^bfdїo? ߲^Cibl96y@ [ Da+WV:=5fÿ10M.i:H{6VnΑ5.r!Kd"REHQ*B_K•6hAaUEjbi#A-0&g]$t7v&;+\.OOnB6UVǶvM6[ϸug;KEO?ѓ(o,-]yeU'X۫k; ΄Ez*@Vpl9)]%m[QTbm]K5*o|{^]Y^Yxuc?7Uee֒;}՜9k&&ZI%IJZ2=bzD[Aj\#)o"zi5|O?Czpnwv~;d=b#'# ]}!|n܈12qˍmAԠ՘cb$yTnJ^~'E؉YႩs|ɞU+iʜy2Gw^ʡ {+zB ..Wq2d_9% :[/FY:*b/ 񻅀 Å#\B-y0Xx BU%^/ӫe5g 5vѭDS.54Ϯ42Hed%H5>QnF孙Kcş0/]t1{ՌҖo;Kq Vh\Xsݫ}3RNQ=V96s@jxlx[<&;[&=^.KR:>JuI]"zxԻ=;Oy;y btE-Ǯ3_?ZgN4X .Ԇ7xdC;b QPa|#T$Bjb~"hLPX&ɤ*Q#p*k Otُ*s4a$s {Q{55oqu0w_5 [ZnRWaWh>XK܃89> 0H]K۾'O>U]w1??4XnWm8*g~i\at1%L0` ҵcy~=-^S8ZUֿ{hh7$_h?40 6whP)%&̙+dUy V֭/ݺ5j]|] 7襭R E3 KG`slf7aFJ;l '>9ʏP"I1[.fvs!ឳ^i+Esp.<]xS;EXiVV'Rhu4әYz^x"CWӣ:0ZLRcJj`S!ЌG0;N olB0(yllxeT)Yе}N"FG?u\hcG*id4&pSzXoıIdXZY!Pdl+PAX}D8tl˗s\뒺HބOE7TM2%6fԓs?ẕU/#r=%MoNK1zctj+Fimc f7PD|;ieA҇oR)nܤ22~zz=w|ģeQ P9ZM;Mxeg-\Q M0%>6ѷ>2uY,aKWŚh!mFigTvM[t㰻u_r_u+JxNU`ڼI*pb6d՝+9+evh Y:9?gRɻgp^,qݬrߴ|,ȀJ-[5/kvdY[Ӥ0ĬkNs?g‘@N^fN!@7P&zG9 oRM-T'IY~qCԽsRs]MGon$"MC^1qq5 H;hX6c%}/q;oaE[.hJƧeџR~RFV{:+_Bӳ2G`)4N˷ oLÕ4݃'pA^J߳?؏#+;T?OnEU >F{)7e{U&%]q@;#ʐ[cМw4N!Wh,ƀUY4?k@/8w!>.aiնv"I>Iw Χ7Ĥ"-3Wog\M$~i@֑?<.;~.i/c(ժW%Ij'@:H*9ꉸ C#n Ge`{@o{wxZ"+(gEw lmkmXvJPw?%|#fjziìømmvooktux\ @ðD?KMe}EQDh%@vV A>-';  ᦮*M<5_ {ANS@tTC\)nmh }U4g˥|XL'鈴 e::J7!R}.K0|w")2b[7誯[>⏁nG189h T|Qn{ ߁LՀte2Jӕ}WN^8t,?^ ޽tҕKD\ ~_z"ߑq7`!"&>OiT^qme>{n3l/4丹t/{O;=rN0"^@GGȅ=Y~{n9<܅gg` Oy'='Ο?9l> =8ھ?ڶe<܇ M{{q}[mOK=ȳALm?V'2ZfDKٶ% A[}s <.Бryq~Jz]v[E'p1p%`1AW+j1Z@`uM-:Nzttu:y9 #TU`TBk9#%#k$e!{FdFPmÒS>4:{$gaH~H+u1HͨX3 ;Ѐ\쾳Ȍ H.ph ?O=PiGYp? (e} QS~?f q endstream endobj 110 0 obj 21436 endobj 111 0 obj <> endobj 112 0 obj <> stream x]ˎ@E|bd!1C0 }u+ͭCuQ?ݜ}cjbz׮OMۮu%ͭl==s˰^'}˦S}8u5};-c[4O*meԷI]wu gx16Di6ǺS_c*]Ug!KYy}h+0NtY@{Ajm]2O^cߨeÜzGgax_D;Ě y y &4=4-x x&) !)g =jhZ/7&zZ{*?,xM~'/> endobj 114 0 obj <> endobj 115 0 obj <> /ProcSet[/PDF/Text/ImageC/ImageI/ImageB] >> endobj 1 0 obj <>/Contents 2 0 R>> endobj 4 0 obj <>/Contents 5 0 R>> endobj 7 0 obj <>/Contents 8 0 R>> endobj 10 0 obj <>/Contents 11 0 R>> endobj 13 0 obj <>/Contents 14 0 R>> endobj 16 0 obj <>/Contents 17 0 R>> endobj 19 0 obj <>/Contents 20 0 R>> endobj 22 0 obj <>/Contents 23 0 R>> endobj 25 0 obj <>/Contents 26 0 R>> endobj 28 0 obj <>/Contents 29 0 R>> endobj 88 0 obj <> endobj 32 0 obj <> endobj 33 0 obj <> endobj 34 0 obj <> endobj 35 0 obj <> endobj 36 0 obj <> endobj 37 0 obj <> endobj 38 0 obj <> endobj 39 0 obj <> endobj 40 0 obj <> endobj 41 0 obj <> endobj 42 0 obj <> endobj 43 0 obj <> endobj 44 0 obj <> endobj 45 0 obj <> endobj 46 0 obj <> endobj 47 0 obj <> endobj 48 0 obj <> endobj 49 0 obj <> endobj 50 0 obj <> endobj 51 0 obj <> endobj 52 0 obj <> endobj 53 0 obj <> endobj 54 0 obj <> endobj 55 0 obj <> endobj 56 0 obj <> endobj 57 0 obj <> endobj 58 0 obj <> endobj 59 0 obj <> endobj 60 0 obj <> endobj 61 0 obj <> endobj 62 0 obj <> endobj 63 0 obj <> endobj 64 0 obj <> endobj 65 0 obj <> endobj 66 0 obj <> endobj 67 0 obj <> endobj 68 0 obj <> endobj 69 0 obj <> endobj 70 0 obj <> endobj 71 0 obj <> endobj 72 0 obj <> endobj 73 0 obj <> endobj 74 0 obj <> endobj 75 0 obj <> endobj 76 0 obj <> endobj 77 0 obj <> >> endobj 78 0 obj <> >> endobj 79 0 obj <> >> endobj 80 0 obj <> endobj 81 0 obj <> endobj 82 0 obj <> endobj 83 0 obj <> endobj 84 0 obj <> endobj 116 0 obj <> endobj 117 0 obj < /Producer /CreationDate(D:20220914233756+02'00')>> endobj xref 0 118 0000000000 65535 f 0000175876 00000 n 0000000019 00000 n 0000002537 00000 n 0000176373 00000 n 0000002558 00000 n 0000005845 00000 n 0000176569 00000 n 0000005866 00000 n 0000008250 00000 n 0000176740 00000 n 0000008271 00000 n 0000011663 00000 n 0000176913 00000 n 0000011685 00000 n 0000015378 00000 n 0000177111 00000 n 0000015400 00000 n 0000018749 00000 n 0000177309 00000 n 0000018771 00000 n 0000021094 00000 n 0000177482 00000 n 0000021116 00000 n 0000024413 00000 n 0000177680 00000 n 0000024435 00000 n 0000027959 00000 n 0000177853 00000 n 0000027981 00000 n 0000028236 00000 n 0000028257 00000 n 0000178189 00000 n 0000178319 00000 n 0000178449 00000 n 0000178579 00000 n 0000178709 00000 n 0000178840 00000 n 0000178971 00000 n 0000179102 00000 n 0000179232 00000 n 0000179362 00000 n 0000179492 00000 n 0000179622 00000 n 0000179752 00000 n 0000179882 00000 n 0000180012 00000 n 0000180142 00000 n 0000180272 00000 n 0000180402 00000 n 0000180532 00000 n 0000180662 00000 n 0000180792 00000 n 0000180922 00000 n 0000181052 00000 n 0000181182 00000 n 0000181312 00000 n 0000181442 00000 n 0000181572 00000 n 0000181702 00000 n 0000181832 00000 n 0000181962 00000 n 0000182092 00000 n 0000182222 00000 n 0000182352 00000 n 0000182482 00000 n 0000182612 00000 n 0000182742 00000 n 0000182872 00000 n 0000183002 00000 n 0000183132 00000 n 0000183261 00000 n 0000183390 00000 n 0000183519 00000 n 0000183648 00000 n 0000183777 00000 n 0000183906 00000 n 0000184035 00000 n 0000184246 00000 n 0000184410 00000 n 0000184604 00000 n 0000184733 00000 n 0000184862 00000 n 0000184992 00000 n 0000185122 00000 n 0000066490 00000 n 0000066513 00000 n 0000107473 00000 n 0000178026 00000 n 0000107496 00000 n 0000113938 00000 n 0000113960 00000 n 0000114175 00000 n 0000114586 00000 n 0000114874 00000 n 0000127522 00000 n 0000127545 00000 n 0000127750 00000 n 0000128286 00000 n 0000128677 00000 n 0000139189 00000 n 0000139213 00000 n 0000139418 00000 n 0000139889 00000 n 0000140219 00000 n 0000151679 00000 n 0000151703 00000 n 0000151908 00000 n 0000152417 00000 n 0000152780 00000 n 0000174305 00000 n 0000174329 00000 n 0000174527 00000 n 0000175186 00000 n 0000175696 00000 n 0000175773 00000 n 0000185252 00000 n 0000185364 00000 n trailer < ] /DocChecksum /35A15C954338A63C3A0B49792131908F >> startxref 185540 %%EOF dygraphs-2.2.1/docs/dist012077700000000000000000000000001437353256000132122../distustar00dygraphs-2.2.1/docs/dow.txt010064400000000000000000002764241437353256000125750ustar00Month,Nominal,Real 1913-01-15,59.740;61.330;64.880,609.591836734694;625.816326530612;662.04081632653 1913-02-15,57.670;58.840;61.140,588.469387755102;600.408163265306;623.877551020408 1913-03-15,57.320;59.280;59.840,584.897959183673;604.897959183673;610.612244897959 1913-04-15,57.430;57.540;60.940,586.020408163265;587.142857142857;621.836734693877 1913-05-15,57.420;57.420;58.570,591.958762886598;591.958762886598;603.814432989691 1913-06-15,52.830;54.860;56.610,539.081632653061;559.795918367347;577.65306122449 1913-07-15,55.110;57.490;57.920,556.666666666667;580.707070707071;585.050505050505 1913-08-15,57.290;59.930;59.930,578.686868686869;605.353535353535;605.353535353535 1913-09-15,58.800;58.880;60.830,588;588.8;608.3 1913-10-15,56.470;57.360;59.650,564.7;573.6;596.5 1913-11-15,55.630;55.830;57.220,550.792079207921;552.772277227723;566.534653465347 1913-12-15,55.140;57.710;57.760,551.4;577.1;577.6 1914-01-15,57.570;60.660;60.710,575.7;606.6;607.1 1914-02-15,59.560;60.070;60.940,601.616161616162;606.767676767677;615.555555555556 1914-03-15,59.430;60.360;61.120,600.30303030303;609.69696969697;617.373737373737 1914-04-15,56.790;57.960;60.410,579.489795918367;591.428571428571;616.428571428571 1914-05-15,57.990;59.760;59.820,585.757575757576;603.636363636364;604.242424242424 1914-06-15,58.090;59.090;59.950,586.767676767677;596.868686868687;605.555555555556 1914-07-15,52.320;52.320;59.920,523.2;523.2;599.2 1914-12-15,53.170;54.580;56.760,526.435643564356;540.39603960396;561.980198019802 1915-01-15,55.400;56.540;58.510,548.514851485149;559.80198019802;579.306930693069 1915-02-15,54.220;55.020;57.830,542.2;550.2;578.3 1915-03-15,55.290;60.830;61.300,558.484848484848;614.444444444444;619.191919191919 1915-04-15,61.050;71.780;71.780,610.5;717.8;717.8 1915-05-15,60.380;65.010;69.580,597.821782178218;643.663366336634;688.910891089109 1915-06-15,64.860;70.060;71.900,642.178217821782;693.663366336634;711.881188118812 1915-07-15,67.880;75.530;75.790,672.079207920792;747.821782178218;750.396039603961 1915-08-15,76.460;81.200;81.880,757.029702970297;803.960396039604;810.693069306931 1915-09-15,80.700;90.580;90.580,799.009900990099;896.831683168317;896.831683168317 1915-10-15,88.230;95.340;96.460,865;934.705882352941;945.686274509804 1915-11-15,91.080;96.710;97.560,884.271844660194;938.932038834951;947.184466019417 1915-12-15,94.780;99.150;99.210,920.194174757282;962.621359223301;963.203883495146 1916-01-15,90.580;90.580;98.810,870.961538461538;870.961538461538;950.096153846154 1916-02-15,90.890;91.030;96.150,873.942307692308;875.288461538461;924.519230769231 1916-03-15,90.520;93.250;96.080,862.095238095238;888.095238095238;915.047619047619 1916-04-15,87.000;89.650;94.460,820.754716981132;845.754716981132;891.132075471698 1916-05-15,87.710;91.800;92.620,819.719626168224;857.943925233645;865.607476635514 1916-06-15,87.680;89.580;93.610,811.851851851852;829.444444444444;866.759259259259 1916-07-15,86.420;89.250;90.530,800.185185185185;826.388888888889;838.240740740741 1916-08-15,88.150;92.250;93.830,808.715596330275;846.330275229358;860.825688073395 1916-09-15,91.190;103.730;103.730,821.531531531532;934.504504504505;934.504504504505 1916-10-15,98.940;104.610;105.280,875.575221238938;925.75221238938;931.681415929203 1916-11-15,105.630;105.970;110.150,918.521739130435;921.478260869565;957.826086956522 1916-12-15,90.160;95.000;106.760,777.241379310345;818.965517241379;920.344827586207 1917-01-15,95.430;95.430;99.180,815.641025641026;815.641025641026;847.692307692308 1917-02-15,87.010;91.560;94.910,725.083333333333;763;790.916666666667 1917-03-15,91.100;96.720;98.200,759.166666666667;806;818.333333333333 1917-04-15,90.660;93.230;97.060,719.52380952381;739.920634920635;770.31746031746 1917-05-15,89.080;97.380;97.410,695.9375;760.78125;761.015625 1917-06-15,94.780;95.380;98.580,729.076923076923;733.692307692308;758.307692307692 1917-07-15,90.480;91.750;95.310,706.875;716.796875;744.609375 1917-08-15,83.400;83.400;93.850,641.538461538462;641.538461538462;721.923076923077 1917-09-15,81.200;83.460;86.020,610.526315789474;627.518796992481;646.766917293233 1917-10-15,74.500;74.500;83.580,551.851851851852;551.851851851852;619.111111111111 1917-11-15,68.580;72.650;74.230,508;538.148148148148;549.851851851852 1917-12-15,65.950;74.380;74.380,481.386861313869;542.919708029197;542.919708029197 1918-01-15,73.380;79.800;79.800,524.142857142857;570;570 1918-02-15,77.780;80.390;82.080,551.631205673759;570.141843971631;582.127659574468 1918-03-15,76.410;76.410;79.930,545.785714285714;545.785714285714;570.928571428571 1918-04-15,75.580;77.510;79.420,532.253521126761;545.845070422535;559.295774647887 1918-05-15,78.080;78.080;84.040,538.48275862069;538.48275862069;579.586206896552 1918-06-15,78.530;82.580;83.020,534.21768707483;561.768707482993;564.761904761905 1918-07-15,80.510;81.230;82.960,533.17880794702;537.94701986755;549.403973509934 1918-08-15,80.710;82.460;83.180,524.090909090909;535.454545454545;540.12987012987 1918-09-15,80.290;84.680;84.680,511.40127388535;539.363057324841;539.363057324841 1918-10-15,83.360;85.510;89.070,521;534.4375;556.6875 1918-11-15,79.870;80.930;87.660,490;496.503067484663;537.791411042945 1918-12-15,80.440;82.200;84.500,487.515151515152;498.181818181818;512.121212121212 1919-01-15,79.880;80.610;83.350,484.121212121212;488.545454545455;505.151515151515 1919-02-15,79.350;84.810;85.680,489.814814814815;523.518518518518;528.888888888889 1919-03-15,84.240;88.850;89.050,513.658536585366;541.768292682927;542.987804878049 1919-04-15,88.840;92.880;93.510,531.976047904192;556.167664670659;559.940119760479 1919-05-15,93.260;105.500;105.500,551.834319526627;624.260355029586;624.260355029586 1919-06-15,99.560;106.980;107.550,589.112426035503;633.01775147929;636.390532544379 1919-07-15,107.160;107.160;112.230,615.862068965517;615.862068965517;645 1919-08-15,98.460;104.750;107.990,556.271186440678;591.80790960452;610.112994350283 1919-09-15,105.840;111.420;111.420,594.606741573034;625.955056179775;625.955056179775 1919-10-15,108.900;118.920;118.920,601.657458563536;657.016574585635;657.016574585635 1919-11-15,103.720;103.720;119.620,560.648648648649;560.648648648649;646.594594594595 1919-12-15,103.550;107.230;107.970,547.883597883598;567.354497354497;571.269841269841 1920-01-15,101.900;104.210;108.850,527.979274611399;539.948186528497;563.989637305699 1920-02-15,89.980;91.180;103.010,461.435897435897;467.589743589744;528.25641025641 1920-03-15,91.680;102.810;104.170,465.380710659899;521.878172588833;528.781725888325 1920-04-15,93.160;93.540;105.650,458.916256157635;460.788177339901;520.443349753695 1920-05-15,87.360;91.810;94.410,424.077669902913;445.679611650485;458.300970873786 1920-06-15,90.160;90.760;93.060,431.387559808612;434.258373205742;445.263157894737 1920-07-15,86.860;86.860;94.510,417.596153846154;417.596153846154;454.375 1920-08-15,83.200;86.160;87.290,409.852216748768;424.433497536946;430 1920-09-15,82.950;82.950;89.950,414.75;414.75;449.75 1920-10-15,84.000;85.080;85.730,422.110552763819;427.537688442211;430.804020100503 1920-11-15,73.120;76.040;85.480,369.292929292929;384.040404040404;431.717171717172 1920-12-15,66.750;71.950;77.300,344.072164948454;370.876288659794;398.453608247423 1921-01-15,72.670;76.130;76.760,382.473684210526;400.684210526316;404 1921-02-15,74.340;74.980;77.140,404.021739130435;407.5;419.239130434783 1921-03-15,72.250;75.760;77.780,394.808743169399;413.989071038251;425.027322404372 1921-04-15,75.060;78.570;78.860,414.696132596685;434.088397790055;435.690607734807 1921-05-15,73.440;73.440;80.030,414.915254237288;414.915254237288;452.146892655367 1921-06-15,64.900;68.450;73.510,368.75;388.920454545455;417.670454545455 1921-07-15,67.250;68.370;69.860,379.943502824859;386.271186440678;394.689265536723 1921-08-15,63.900;67.110;69.950,361.016949152542;379.152542372881;395.197740112994 1921-09-15,66.830;71.080;71.720,381.885714285714;406.171428571429;409.828571428571 1921-10-15,69.460;73.210;73.800,396.914285714286;418.342857142857;421.714285714286 1921-11-15,73.440;77.300;78.010,422.068965517241;444.252873563218;448.333333333333 1921-12-15,78.120;80.800;81.500,451.560693641618;467.052023121387;471.098265895954 1922-01-15,78.590;81.300;82.950,465.029585798817;481.065088757396;490.828402366864 1922-02-15,81.680;85.460;85.810,483.313609467456;505.680473372781;507.751479289941 1922-03-15,85.330;89.050;89.050,510.958083832335;533.233532934132;533.233532934132 1922-04-15,89.300;91.930;93.210,534.730538922156;550.479041916168;558.14371257485 1922-05-15,91.500;95.630;96.410,547.904191616767;572.634730538922;577.305389221557 1922-06-15,90.730;92.930;96.360,543.293413173653;556.467065868264;577.005988023952 1922-07-15,92.920;97.050;97.050,553.095238095238;577.678571428571;577.678571428571 1922-08-15,96.210;100.780;100.780,579.578313253012;607.10843373494;607.10843373494 1922-09-15,96.580;97.120;102.050,581.807228915663;585.060240963855;614.759036144578 1922-10-15,96.110;96.110;102.760,575.508982035928;575.508982035928;615.329341317365 1922-11-15,92.030;94.650;99.530,547.797619047619;563.392857142857;592.440476190476 1922-12-15,95.030;98.170;99.220,562.307692307692;580.887573964497;587.100591715976 1923-01-15,96.960;97.430;99.420,577.142857142857;579.940476190476;591.785714285714 1923-02-15,97.710;103.900;103.900,581.607142857143;618.452380952381;618.452380952381 1923-03-15,102.360;102.770;105.380,609.285714285714;611.72619047619;627.261904761905 1923-04-15,98.380;98.380;102.700,582.130177514793;582.130177514793;607.692307692308 1923-05-15,92.770;97.530;98.190,548.934911242604;577.100591715976;581.005917159763 1923-06-15,88.400;88.400;97.240,520;520;572 1923-07-15,86.910;86.910;91.720,505.290697674419;505.290697674419;533.255813953488 1923-08-15,87.960;93.460;93.700,514.385964912281;546.549707602339;547.953216374269 1923-09-15,87.940;87.970;93.610,511.279069767442;511.453488372093;544.244186046512 1923-10-15,85.910;88.530;90.450,496.589595375722;511.734104046243;522.832369942196 1923-11-15,88.410;92.340;92.880,511.040462427746;533.757225433526;536.878612716763 1923-12-15,92.640;95.520;95.610,535.491329479769;552.138728323699;552.658959537572 1924-01-15,94.880;100.660;100.660,548.439306358381;581.849710982659;581.849710982659 1924-02-15,96.330;97.220;101.310,560.058139534884;565.232558139535;589.011627906977 1924-03-15,92.540;93.010;98.860,541.169590643275;543.918128654971;578.12865497076 1924-04-15,89.180;90.630;94.690,524.588235294118;533.117647058823;557 1924-05-15,88.330;89.900;92.470,519.588235294118;528.823529411765;543.941176470588 1924-06-15,89.180;96.370;96.370,524.588235294118;566.882352941176;566.882352941176 1924-07-15,96.380;102.140;102.140,563.625730994152;597.309941520468;597.309941520468 1924-08-15,101.510;104.140;105.570,597.117647058824;612.588235294118;621 1924-09-15,101.070;103.160;104.950,591.052631578947;603.27485380117;613.74269005848 1924-10-15,99.180;104.060;104.080,576.627906976744;605;605.116279069768 1924-11-15,103.890;111.100;111.100,604.011627906977;645.93023255814;645.93023255814 1924-12-15,110.440;120.510;120.510,638.381502890173;696.589595375723;696.589595375723 1925-01-15,119.460;123.490;123.600,690.520231213873;713.815028901734;714.450867052023 1925-02-15,117.960;122.240;122.860,685.813953488372;710.697674418605;714.302325581395 1925-03-15,115.000;116.750;125.680,664.739884393064;674.85549132948;726.473988439306 1925-04-15,117.400;120.010;121.540,682.558139534884;697.732558139535;706.627906976744 1925-05-15,121.100;129.950;129.950,700;751.156069364162;751.156069364162 1925-06-15,126.750;131.010;131.010,724.285714285714;748.628571428571;748.628571428571 1925-07-15,131.330;133.810;136.500,741.977401129944;755.988700564972;771.186440677966 1925-08-15,135.710;141.180;143.180,766.723163841808;797.627118644068;808.926553672316 1925-09-15,137.220;143.460;147.160,775.254237288136;810.508474576271;831.412429378531 1925-10-15,144.770;155.650;155.650,817.909604519774;879.378531073446;879.378531073446 1925-11-15,148.180;151.080;159.390,823.222222222222;839.333333333333;885.5 1925-12-15,152.110;156.660;156.870,849.776536312849;875.195530726257;876.368715083799 1926-01-15,153.200;157.350;158.930,855.865921787709;879.050279329609;887.877094972067 1926-02-15,154.680;154.680;162.310,864.134078212291;864.134078212291;906.759776536313 1926-03-15,135.200;140.460;153.130,759.550561797753;789.101123595506;860.280898876404 1926-04-15,136.270;143.710;144.550,761.284916201117;802.849162011173;807.541899441341 1926-05-15,137.160;143.430;143.430,770.561797752809;805.786516853933;805.786516853933 1926-06-15,142.300;153.040;154.030,803.954802259887;864.632768361582;870.225988700565 1926-07-15,153.010;160.180;160.580,874.342857142857;915.314285714286;917.6 1926-08-15,160.410;162.510;166.140,921.896551724138;933.965517241379;954.827586206897 1926-09-15,156.260;158.190;166.100,892.914285714286;903.942857142857;949.142857142857 1926-10-15,145.660;150.760;159.690,827.613636363636;856.590909090909;907.329545454545 1926-11-15,150.510;156.550;157.370,850.338983050847;884.463276836158;889.09604519774 1926-12-15,156.650;157.200;160.750,885.028248587571;888.135593220339;908.19209039548 1927-01-15,152.730;156.410;156.560,872.742857142857;893.771428571429;894.628571428571 1927-02-15,154.310;161.960;161.960,886.83908045977;930.80459770115;930.80459770115 1927-03-15,158.410;160.080;161.780,915.664739884393;925.317919075144;935.14450867052 1927-04-15,160.710;164.640;167.360,928.959537572254;951.676300578035;967.398843930636 1927-05-15,164.550;172.960;172.960,945.689655172414;994.022988505747;994.022988505747 1927-06-15,165.730;166.230;171.980,941.647727272727;944.488636363636;977.159090909091 1927-07-15,168.060;181.400;181.400,971.445086705202;1048.5549132948;1048.5549132948 1927-08-15,177.130;189.790;190.630,1029.82558139535;1103.43023255814;1108.31395348837 1927-09-15,191.560;197.590;198.970,1107.28323699422;1142.1387283237;1150.11560693642 1927-10-15,181.430;181.730;199.780,1042.70114942529;1044.42528735632;1148.16091954023 1927-11-15,181.650;198.210;198.210,1050;1145.7225433526;1145.7225433526 1927-12-15,193.580;200.700;200.930,1118.95953757225;1160.11560693642;1161.4450867052 1928-01-15,194.500;198.590;203.350,1124.2774566474;1147.91907514451;1175.43352601156 1928-02-15,191.330;194.780;199.350,1118.88888888889;1139.06432748538;1165.78947368421 1928-03-15,194.530;214.450;214.450,1137.60233918129;1254.09356725146;1254.09356725146 1928-04-15,207.940;211.630;216.930,1216.02339181287;1237.60233918129;1268.59649122807 1928-05-15,211.730;219.810;220.880,1230.98837209302;1277.96511627907;1284.18604651163 1928-06-15,201.960;210.370;220.270,1181.05263157895;1230.23391812865;1288.12865497076 1928-07-15,205.100;216.000;216.620,1199.41520467836;1263.15789473684;1266.78362573099 1928-08-15,214.080;240.410;240.410,1251.9298245614;1405.90643274854;1405.90643274854 1928-09-15,236.860;237.380;241.720,1369.13294797688;1372.1387283237;1397.22543352601 1928-10-15,237.750;252.160;257.130,1382.26744186047;1466.04651162791;1494.94186046512 1928-11-15,254.380;293.380;295.620,1478.95348837209;1705.6976744186;1718.72093023256 1928-12-15,263.950;300.000;300.000,1543.56725146199;1754.38596491228;1754.38596491228 1929-01-15,296.980;317.510;317.510,1736.72514619883;1856.78362573099;1856.78362573099 1929-02-15,300.410;317.410;322.060,1756.78362573099;1856.19883040936;1883.3918128655 1929-03-15,296.510;308.850;321.180,1744.17647058824;1816.76470588235;1889.29411764706 1929-04-15,299.130;319.290;319.290,1770;1889.2899408284;1889.2899408284 1929-05-15,293.420;297.410;326.160,1726;1749.47058823529;1918.58823529412 1929-06-15,303.270;331.650;331.650,1773.50877192982;1939.47368421053;1939.47368421053 1929-07-15,335.220;347.700;347.700,1937.68786127168;2009.82658959538;2009.82658959538 1929-08-15,337.990;380.330;380.330,1953.69942196532;2198.43930635838;2198.43930635838 1929-09-15,343.450;343.450;381.170,1985.26011560694;1985.26011560694;2203.29479768786 1929-10-15,230.070;273.510;352.860,1329.88439306358;1580.98265895954;2039.65317919075 1929-11-15,198.690;238.950;257.680,1148.49710982659;1381.21387283237;1489.47976878613 1929-12-15,230.890;248.480;262.200,1342.38372093023;1444.6511627907;1524.41860465116 1930-01-15,244.200;267.140;267.140,1428.0701754386;1562.22222222222;1562.22222222222 1930-02-15,262.470;271.110;272.270,1543.94117647059;1594.76470588235;1601.58823529412 1930-03-15,270.590;286.100;286.100,1601.12426035503;1692.89940828402;1692.89940828402 1930-04-15,276.940;279.230;294.070,1629.05882352941;1642.52941176471;1729.82352941176 1930-05-15,259.680;275.070;275.070,1536.56804733728;1627.63313609467;1627.63313609467 1930-06-15,211.840;226.340;274.450,1260.95238095238;1347.2619047619;1633.63095238095 1930-07-15,218.330;233.990;240.810,1315.24096385542;1409.57831325301;1450.66265060241 1930-08-15,217.240;240.420;240.420,1316.60606060606;1457.09090909091;1457.09090909091 1930-09-15,204.900;204.900;245.090,1234.33734939759;1234.33734939759;1476.44578313253 1930-10-15,183.350;183.350;214.180,1111.21212121212;1111.21212121212;1298.06060606061 1930-11-15,171.600;180.910;190.300,1046.34146341463;1103.10975609756;1160.36585365854 1930-12-15,157.510;164.580;186.820,978.32298136646;1022.23602484472;1160.37267080745 1931-01-15,161.450;169.340;173.040,1015.40880503145;1065.03144654088;1088.30188679245 1931-02-15,168.710;190.340;194.360,1074.58598726115;1212.35668789809;1237.96178343949 1931-03-15,172.360;172.360;187.720,1104.87179487179;1104.87179487179;1203.33333333333 1931-04-15,143.610;151.190;171.070,926.516129032258;975.41935483871;1103.67741935484 1931-05-15,128.460;128.460;154.410,839.607843137255;839.607843137255;1009.21568627451 1931-06-15,121.700;150.180;154.040,805.960264900662;994.569536423841;1020.13245033113 1931-07-15,135.390;135.390;155.260,896.622516556291;896.622516556291;1028.2119205298 1931-08-15,133.770;139.410;144.150,885.894039735099;923.245033112583;954.635761589404 1931-09-15,96.610;96.610;140.130,644.066666666667;644.066666666667;934.2 1931-10-15,86.480;103.970;108.880,580.402684563758;697.785234899329;730.738255033557 1931-11-15,91.550;93.870;116.790,622.789115646259;638.571428571429;794.489795918367 1931-12-15,73.790;77.900;91.170,505.41095890411;533.561643835617;624.452054794521 1932-01-15,71.240;76.550;85.880,498.181818181818;535.314685314685;600.55944055944 1932-02-15,71.800;81.440;85.980,509.219858156028;577.58865248227;609.787234042553 1932-03-15,73.280;73.280;88.780,523.428571428571;523.428571428571;634.142857142857 1932-04-15,55.930;55.930;72.180,402.374100719424;402.374100719424;519.280575539568 1932-05-15,44.740;44.740;59.010,326.569343065693;326.569343065693;430.729927007299 1932-06-15,42.840;42.840;50.620,315;315;372.205882352941 1932-07-15,41.220;53.890;53.890,303.088235294118;396.25;396.25 1932-08-15,53.160;73.160;75.220,393.777777777778;541.925925925926;557.185185185185 1932-09-15,65.060;71.560;79.930,485.522388059701;534.029850746269;596.492537313433 1932-10-15,58.470;61.900;71.210,439.624060150376;465.413533834586;535.413533834586 1932-11-15,56.350;56.350;68.030,426.893939393939;426.893939393939;515.378787878788 1932-12-15,55.910;60.260;61.930,426.793893129771;460;472.748091603053 1933-01-15,59.290;60.900;64.350,459.612403100775;472.093023255814;498.837209302326 1933-02-15,50.160;51.390;60.090,394.96062992126;404.645669291339;473.149606299213 1933-03-15,52.540;55.400;62.950,416.984126984127;439.68253968254;499.603174603175 1933-04-15,55.690;73.100;73.690,441.984126984127;580.15873015873;584.84126984127 1933-05-15,76.630;88.110;90.020,608.174603174603;699.285714285714;714.444444444444 1933-06-15,88.870;98.140;98.740,699.763779527559;772.755905511811;777.48031496063 1933-07-15,88.710;90.770;108.670,677.175572519084;692.900763358779;829.541984732824 1933-08-15,92.550;102.410;105.070,701.136363636364;775.833333333333;795.984848484848 1933-09-15,93.180;94.240;105.740,705.909090909091;713.939393939394;801.060606060606 1933-10-15,84.380;88.160;99.720,639.242424242424;667.878787878788;755.454545454545 1933-11-15,89.620;98.140;101.280,678.939393939394;743.484848484848;767.272727272727 1933-12-15,95.280;98.670;102.040,721.818181818182;747.5;773.030303030303 1934-01-15,96.730;107.220;108.990,732.80303030303;812.272727272727;825.681818181818 1934-02-15,103.120;103.460;110.740,775.338345864662;777.894736842105;832.631578947368 1934-03-15,98.760;100.310;105.790,742.556390977444;754.210526315789;795.413533834586 1934-04-15,100.490;100.490;106.550,755.563909774436;755.563909774436;801.127819548872 1934-05-15,91.810;94.000;100.620,690.300751879699;706.766917293233;756.541353383459 1934-06-15,91.790;95.750;100.420,685;714.55223880597;749.402985074627 1934-07-15,85.510;88.050;98.820,638.134328358209;657.089552238806;737.462686567164 1934-08-15,87.470;92.860;95.480,652.761194029851;692.985074626866;712.537313432836 1934-09-15,86.690;92.490;93.650,637.426470588235;680.073529411765;688.602941176471 1934-10-15,90.410;93.360;95.600,669.703703703704;691.555555555556;708.148148148148 1934-11-15,93.460;102.940;103.080,692.296296296296;762.518518518518;763.555555555556 1934-12-15,99.590;104.040;104.040,743.208955223881;776.417910447761;776.417910447761 1935-01-15,100.490;101.690;105.880,738.897058823529;747.720588235294;778.529411764706 1935-02-15,100.230;102.380;107.170,731.605839416059;747.299270072993;782.262773722628 1935-03-15,96.710;100.780;103.270,705.912408759124;735.620437956204;753.795620437956 1935-04-15,100.390;109.450;110.470,727.463768115942;793.115942028986;800.507246376812 1935-05-15,108.710;110.640;116.810,787.753623188406;801.739130434783;846.449275362319 1935-06-15,111.450;118.360;120.040,813.503649635037;863.941605839416;876.204379562044 1935-07-15,118.690;126.230;126.560,866.350364963504;921.386861313869;923.795620437956 1935-08-15,124.930;127.350;128.990,911.897810218978;929.562043795621;941.532846715329 1935-09-15,127.270;131.920;134.110,928.978102189781;962.919708029197;978.905109489051 1935-10-15,128.060;139.740;140.780,934.744525547445;1020;1027.59124087591 1935-11-15,141.070;142.340;148.440,1022.24637681159;1031.44927536232;1075.65217391304 1935-12-15,138.940;144.130;144.130,1006.8115942029;1044.42028985507;1044.42028985507 1936-01-15,143.110;149.490;149.490,1037.02898550725;1083.26086956522;1083.26086956522 1936-02-15,149.810;152.530;154.430,1085.57971014493;1105.28985507246;1119.05797101449 1936-03-15,150.420;156.340;158.750,1097.95620437956;1141.16788321168;1158.75912408759 1936-04-15,143.650;145.670;161.990,1048.5401459854;1063.28467153285;1182.40875912409 1936-05-15,146.700;152.640;152.640,1070.80291970803;1114.16058394161;1114.16058394161 1936-06-15,149.260;157.690;160.660,1081.59420289855;1142.68115942029;1164.20289855072 1936-07-15,155.600;164.860;167.010,1119.42446043165;1186.04316546763;1201.51079136691 1936-08-15,160.800;166.290;169.050,1148.57142857143;1187.78571428571;1207.5 1936-09-15,165.160;167.820;169.550,1179.71428571429;1198.71428571429;1211.07142857143 1936-10-15,168.260;177.150;177.420,1201.85714285714;1265.35714285714;1267.28571428571 1936-11-15,176.670;183.220;184.900,1261.92857142857;1308.71428571429;1320.71428571429 1936-12-15,175.850;179.900;182.180,1256.07142857143;1285;1301.28571428571 1937-01-15,177.720;184.740;186.900,1260.42553191489;1310.21276595745;1325.53191489362 1937-02-15,186.010;187.170;190.290,1319.21985815603;1327.44680851064;1349.57446808511 1937-03-15,179.820;186.410;194.400,1266.33802816901;1312.74647887324;1369.01408450704 1937-04-15,170.130;174.270;185.190,1189.72027972028;1218.67132867133;1295.03496503496 1937-05-15,167.460;174.710;176.300,1162.91666666667;1213.26388888889;1224.30555555556 1937-06-15,165.510;169.320;175.140,1149.375;1175.83333333333;1216.25 1937-07-15,170.130;184.010;184.420,1173.31034482759;1269.03448275862;1271.86206896552 1937-08-15,175.910;177.410;189.340,1213.1724137931;1223.51724137931;1305.79310344828 1937-09-15,147.380;154.570;173.080,1009.45205479452;1058.69863013699;1185.47945205479 1937-10-15,125.730;138.480;153.890,861.164383561644;948.493150684931;1054.04109589041 1937-11-15,113.640;123.480;135.940,783.724137931035;851.586206896552;937.51724137931 1937-12-15,118.930;120.850;129.980,825.902777777778;839.236111111111;902.638888888889 1938-01-15,120.570;121.870;134.350,849.084507042254;858.239436619718;946.12676056338 1938-02-15,118.490;129.640;132.410,840.354609929078;919.432624113475;939.078014184397 1938-03-15,98.950;98.950;130.470,701.77304964539;701.77304964539;925.31914893617 1938-04-15,103.020;111.660;118.990,725.492957746479;786.338028169014;837.957746478873 1938-05-15,107.740;107.740;119.430,764.113475177305;764.113475177305;847.021276595745 1938-06-15,109.710;133.880;135.870,778.085106382979;949.503546099291;963.617021276596 1938-07-15,134.560;141.200;144.910,954.326241134752;1001.41843971631;1027.7304964539 1938-08-15,136.510;139.270;144.470,968.156028368794;987.730496453901;1024.60992907801 1938-09-15,129.910;141.450;143.080,921.347517730497;1003.1914893617;1014.75177304965 1938-10-15,144.230;151.730;154.170,1030.21428571429;1083.78571428571;1101.21428571429 1938-11-15,146.140;149.820;158.080,1043.85714285714;1070.14285714286;1129.14285714286 1938-12-15,147.390;154.360;154.360,1052.78571428571;1102.57142857143;1102.57142857143 1939-01-15,136.420;143.760;154.850,974.428571428571;1026.85714285714;1106.07142857143 1939-02-15,142.430;147.300;147.300,1024.67625899281;1059.71223021583;1059.71223021583 1939-03-15,131.840;131.840;152.280,948.489208633093;948.489208633093;1095.53956834532 1939-04-15,123.750;128.380;132.250,896.739130434783;930.289855072464;958.333333333333 1939-05-15,127.830;138.180;138.180,926.304347826087;1001.30434782609;1001.30434782609 1939-06-15,130.050;130.630;140.090,942.391304347826;946.594202898551;1015.14492753623 1939-07-15,131.930;143.260;144.510,956.014492753623;1038.11594202899;1047.17391304348 1939-08-15,131.330;134.410;144.260,951.666666666667;973.985507246377;1045.36231884058 1939-09-15,135.250;150.160;155.920,959.219858156028;1064.96453900709;1105.81560283688 1939-10-15,149.890;151.880;155.480,1070.64285714286;1084.85714285714;1110.57142857143 1939-11-15,145.690;145.690;152.640,1040.64285714286;1040.64285714286;1090.28571428571 1939-12-15,146.340;149.990;149.990,1045.28571428571;1071.35714285714;1071.35714285714 1940-01-15,144.650;145.330;152.800,1040.64748201439;1045.53956834532;1099.28057553957 1940-02-15,145.000;146.540;148.940,1035.71428571429;1046.71428571429;1063.85714285714 1940-03-15,145.590;147.540;148.370,1039.92857142857;1053.85714285714;1059.78571428571 1940-04-15,146.800;148.430;151.290,1048.57142857143;1060.21428571429;1080.64285714286 1940-05-15,113.940;116.220;148.170,813.857142857143;830.142857142857;1058.35714285714 1940-06-15,111.840;122.060;123.860,793.191489361702;865.673758865248;878.439716312057 1940-07-15,120.960;126.140;126.140,864;901;901 1940-08-15,121.280;128.880;128.880,866.285714285714;920.571428571428;920.571428571428 1940-09-15,127.740;132.640;135.100,912.428571428571;947.428571428571;965 1940-10-15,130.390;134.610;135.090,931.357142857143;961.5;964.928571428571 1940-11-15,129.780;130.030;137.750,927;928.785714285714;983.928571428571 1940-12-15,128.410;131.130;132.350,910.709219858156;930;938.652482269504 1941-01-15,124.050;124.130;133.590,879.787234042553;880.354609929078;947.446808510638 1941-02-15,117.660;121.970;124.760,834.468085106383;865.035460992908;884.822695035461 1941-03-15,120.300;122.720;123.920,847.183098591549;864.225352112676;872.676056338028 1941-04-15,115.540;115.540;124.650,807.972027972028;807.972027972028;871.678321678322 1941-05-15,115.300;116.230;117.820,800.694444444444;807.152777777778;818.194444444444 1941-06-15,116.180;123.140;123.970,790.340136054422;837.687074829932;843.333333333333 1941-07-15,122.850;128.790;130.060,835.714285714286;876.122448979592;884.761904761905 1941-08-15,124.900;127.430;128.220,838.255033557047;855.234899328859;860.536912751678 1941-09-15,125.810;126.820;129.320,833.17880794702;839.867549668874;856.423841059603 1941-10-15,117.820;117.820;126.850,770.065359477124;770.065359477124;829.084967320261 1941-11-15,114.660;114.660;119.850,744.545454545455;744.545454545455;778.246753246753 1941-12-15,106.340;110.960;116.650,686.064516129032;715.870967741935;752.58064516129 1942-01-15,108.940;109.410;114.220,693.885350318471;696.87898089172;727.515923566879 1942-02-15,105.100;106.580;110.800,665.189873417722;674.556962025316;701.26582278481 1942-03-15,99.210;99.530;106.970,620.0625;622.0625;668.5625 1942-04-15,92.920;95.350;102.500,577.142857142857;592.23602484472;636.645962732919 1942-05-15,95.830;100.880;101.090,587.914110429448;618.895705521472;620.184049079755 1942-06-15,101.300;103.340;106.290,621.472392638037;633.98773006135;652.085889570552 1942-07-15,102.690;105.720;108.910,626.158536585366;644.634146341464;664.085365853659 1942-08-15,104.800;106.330;107.550,635.151515151515;644.424242424242;651.818181818182 1942-09-15,106.030;109.110;109.560,642.606060606061;661.272727272727;664 1942-10-15,109.650;113.500;115.290,656.586826347306;679.640718562874;690.359281437126 1942-11-15,114.100;114.500;117.300,679.166666666667;681.547619047619;698.214285714286 1942-12-15,114.610;119.400;119.560,678.165680473373;706.508875739645;707.455621301775 1943-01-15,119.260;125.410;125.410,705.680473372781;742.07100591716;742.07100591716 1943-02-15,125.070;129.710;130.040,740.059171597633;767.514792899408;769.467455621302 1943-03-15,128.600;136.570;136.820,747.674418604651;794.011627906977;795.46511627907 1943-04-15,131.180;135.480;136.930,753.908045977012;778.620689655172;786.954022988506 1943-05-15,136.820;141.180;141.180,781.828571428571;806.742857142857;806.742857142857 1943-06-15,138.790;143.380;143.380,793.085714285714;819.314285714286;819.314285714286 1943-07-15,137.250;137.250;145.820,788.793103448276;788.793103448276;838.045977011494 1943-08-15,134.000;136.620;138.450,774.566473988439;789.71098265896;800.28901734104 1943-09-15,136.910;140.120;141.750,786.83908045977;805.287356321839;814.655172413793 1943-10-15,136.390;138.290;140.330,783.850574712644;794.770114942529;806.494252873563 1943-11-15,129.570;129.570;138.500,744.655172413793;744.655172413793;795.977011494253 1943-12-15,130.680;135.890;136.240,751.034482758621;780.977011494253;782.988505747127 1944-01-15,135.920;137.400;138.650,781.149425287356;789.655172413793;796.83908045977 1944-02-15,134.220;136.300;137.450,771.379310344828;783.333333333333;789.942528735632 1944-03-15,136.440;138.840;141.000,784.137931034483;797.931034482759;810.344827586207 1944-04-15,135.000;136.210;139.110,771.428571428571;778.342857142857;794.914285714286 1944-05-15,137.060;142.240;142.240,783.2;812.8;812.8 1944-06-15,141.620;148.380;148.630,804.659090909091;843.068181818182;844.488636363636 1944-07-15,145.770;146.110;150.500,823.559322033898;825.480225988701;850.282485875706 1944-08-15,144.900;146.990;148.960,818.64406779661;830.45197740113;841.581920903955 1944-09-15,142.960;146.310;147.160,807.683615819209;826.610169491525;831.412429378531 1944-10-15,145.830;146.530;148.870,823.898305084746;827.853107344633;841.073446327684 1944-11-15,145.600;147.330;148.080,822.598870056497;832.372881355932;836.610169491526 1944-12-15,147.300;151.930;152.280,827.52808988764;853.539325842697;855.505617977528 1945-01-15,151.350;153.670;155.850,850.280898876404;863.314606741573;875.561797752809 1945-02-15,153.790;160.400;160.400,863.988764044944;901.123595505618;901.123595505618 1945-03-15,152.270;154.060;161.520,855.449438202247;865.505617977528;907.415730337079 1945-04-15,154.990;165.440;165.440,870.730337078652;929.438202247191;929.438202247191 1945-05-15,163.090;168.300;169.080,911.117318435754;940.223463687151;944.581005586592 1945-06-15,164.570;164.570;168.920,909.226519337016;909.226519337016;933.259668508287 1945-07-15,160.910;162.880;167.090,889.005524861878;899.889502762431;923.149171270718 1945-08-15,161.550;174.290;174.290,892.541436464088;962.92817679558;962.92817679558 1945-09-15,173.900;180.110;180.220,960.773480662983;995.082872928177;995.690607734806 1945-10-15,183.060;186.600;187.060,1011.38121546961;1030.93922651934;1033.48066298343 1945-11-15,187.820;191.460;192.130,1037.67955801105;1057.79005524862;1061.49171270718 1945-12-15,189.070;192.910;195.820,1038.84615384615;1059.94505494506;1075.93406593407 1946-01-15,190.900;204.670;205.350,1048.9010989011;1124.56043956044;1128.2967032967 1946-02-15,186.020;190.090;206.610,1027.73480662983;1050.22099447514;1141.49171270718 1946-03-15,188.460;199.560;200.560,1029.83606557377;1090.49180327869;1095.95628415301 1946-04-15,199.190;206.770;208.310,1082.55434782609;1123.75;1132.11956521739 1946-05-15,200.650;212.280;212.500,1084.59459459459;1147.45945945946;1148.64864864865 1946-06-15,200.520;205.620;211.470,1072.29946524064;1099.57219251337;1130.85561497326 1946-07-15,195.220;201.560;207.560,985.959595959596;1017.9797979798;1048.28282828283 1946-08-15,189.190;189.190;204.520,936.584158415842;936.584158415842;1012.47524752475 1946-09-15,165.170;172.420;181.180,809.656862745098;845.196078431373;888.137254901961 1946-10-15,163.120;169.150;175.940,784.230769230769;813.221153846154;845.865384615385 1946-11-15,163.550;169.780;174.400,767.840375586855;797.089201877934;818.779342723005 1946-12-15,167.500;177.200;177.850,779.06976744186;824.186046511628;827.209302325581 1947-01-15,171.950;180.440;180.440,799.767441860465;839.255813953488;839.255813953488 1947-02-15,177.220;178.900;184.060,824.279069767442;832.093023255814;856.093023255814 1947-03-15,172.580;177.200;181.880,788.036529680365;809.132420091324;830.502283105023 1947-04-15,166.690;170.640;177.450,761.141552511416;779.178082191781;810.27397260274 1947-05-15,163.550;169.250;174.210,746.803652968037;772.831050228311;795.479452054795 1947-06-15,168.000;177.300;177.440,763.636363636364;805.909090909091;806.545454545455 1947-07-15,179.880;183.180;186.850,810.27027027027;825.135135135135;841.666666666667 1947-08-15,177.570;178.850;183.810,789.2;794.888888888889;816.933333333333 1947-09-15,174.860;177.490;179.810,760.260869565217;771.695652173913;781.782608695652 1947-10-15,178.100;181.810;185.290,774.347826086957;790.478260869565;805.608695652174 1947-11-15,179.510;179.510;183.170,777.099567099567;777.099567099567;792.943722943723 1947-12-15,176.100;181.160;181.160,752.564102564103;774.188034188034;774.188034188034 1948-01-15,171.180;174.760;181.040,722.278481012658;737.383966244726;763.881856540084 1948-02-15,165.650;166.800;174.920,704.893617021277;709.787234042553;744.340425531915 1948-03-15,165.390;177.200;177.200,706.794871794872;757.264957264957;757.264957264957 1948-04-15,177.320;180.510;183.780,745.042016806723;758.44537815126;772.18487394958 1948-05-15,180.940;190.740;191.060,757.071129707113;798.075313807531;799.414225941423 1948-06-15,187.900;189.460;193.160,779.668049792531;786.141078838174;801.49377593361 1948-07-15,181.200;181.330;191.620,742.622950819672;743.155737704918;785.327868852459 1948-08-15,179.270;181.710;183.600,731.714285714286;741.673469387755;749.387755102041 1948-09-15,175.990;178.300;185.360,718.326530612245;727.755102040816;756.571428571429 1948-10-15,179.870;188.280;189.760,737.172131147541;771.639344262295;777.704918032787 1948-11-15,171.200;171.200;189.760,707.438016528926;707.438016528926;784.132231404959 1948-12-15,173.220;177.300;177.920,718.755186721992;735.684647302905;738.257261410788 1949-01-15,175.030;179.120;181.430,729.291666666667;746.333333333333;755.958333333333 1949-02-15,171.100;173.060;180.390,718.90756302521;727.142857142857;757.941176470588 1949-03-15,173.660;177.100;178.450,729.663865546218;744.117647058823;749.789915966387 1949-04-15,173.240;174.060;177.160,724.853556485356;728.284518828452;741.255230125523 1949-05-15,168.360;168.360;176.630,707.394957983193;707.394957983193;742.142857142857 1949-06-15,161.600;167.420;168.150,676.150627615063;700.502092050209;703.556485355649 1949-07-15,168.080;175.920;176.460,709.198312236287;742.278481012658;744.556962025317 1949-08-15,176.840;178.660;182.020,743.025210084034;750.672268907563;764.789915966387 1949-09-15,178.040;182.510;183.290,744.937238493724;763.640167364017;766.903765690377 1949-10-15,182.670;189.540;190.360,770.759493670886;799.746835443038;803.206751054852 1949-11-15,187.980;191.550;193.520,789.831932773109;804.831932773109;813.109243697479 1949-12-15,192.710;200.520;200.520,816.567796610169;849.661016949153;849.661016949153 1950-01-15,196.810;201.790;201.980,837.489361702128;858.68085106383;859.489361702128 1950-02-15,201.690;203.440;204.590,858.255319148936;865.702127659575;870.595744680851 1950-03-15,202.330;206.050;209.780,857.330508474576;873.093220338983;888.898305084746 1950-04-15,208.440;213.560;215.310,883.220338983051;904.915254237288;912.330508474576 1950-05-15,214.870;223.420;223.420,906.62447257384;942.700421940928;942.700421940928 1950-06-15,206.720;209.110;228.380,868.571428571429;878.613445378151;959.579831932773 1950-07-15,197.460;209.400;210.850,819.336099585062;868.879668049792;874.896265560166 1950-08-15,211.260;216.870;221.510,869.382716049383;892.469135802469;911.56378600823 1950-09-15,218.100;226.360;226.780,893.852459016393;927.704918032787;929.426229508197 1950-10-15,225.010;225.010;231.740,914.674796747967;914.674796747967;942.032520325203 1950-11-15,222.520;227.600;235.470,900.890688259109;921.457489878542;953.31983805668 1950-12-15,222.330;235.420;235.420,889.32;941.68;941.68 1951-01-15,238.990;248.830;249.580,940.905511811024;979.645669291339;982.59842519685 1951-02-15,250.760;252.050;255.710,975.719844357977;980.739299610895;994.980544747082 1951-03-15,243.950;248.530;253.610,945.542635658915;963.294573643411;982.984496124031 1951-04-15,246.020;259.130;259.130,953.565891472868;1004.37984496124;1004.37984496124 1951-05-15,245.270;249.650;263.130,946.988416988417;963.899613899614;1015.94594594595 1951-06-15,242.640;242.640;254.030,936.833976833977;936.833976833977;980.810810810811 1951-07-15,243.980;257.860;260.700,942.007722007722;995.598455598456;1006.56370656371 1951-08-15,259.890;270.250;270.250,1003.43629343629;1043.43629343629;1043.43629343629 1951-09-15,270.630;271.160;276.370,1036.89655172414;1038.92720306513;1058.88888888889 1951-10-15,260.430;262.350;275.870,994.007633587786;1001.3358778626;1052.93893129771 1951-11-15,256.950;261.270;264.060,973.295454545455;989.659090909091;1000.22727272727 1951-12-15,263.240;269.230;269.230,993.358490566038;1015.96226415094;1015.96226415094 1952-01-15,268.080;270.690;275.400,1011.62264150943;1021.47169811321;1039.24528301887 1952-02-15,258.490;260.080;271.680,982.851711026616;988.897338403042;1033.00380228137 1952-03-15,260.080;269.460;269.460,988.897338403042;1024.56273764259;1024.56273764259 1952-04-15,257.630;257.630;267.220,975.871212121212;975.871212121212;1012.19696969697 1952-05-15,256.350;262.940;264.220,971.022727272727;995.984848484848;1000.83333333333 1952-06-15,262.090;274.260;274.260,989.018867924528;1034.94339622641;1034.94339622641 1952-07-15,272.580;279.560;279.560,1020.89887640449;1047.04119850187;1047.04119850187 1952-08-15,273.170;275.040;280.290,1023.10861423221;1030.11235955056;1049.77528089888 1952-09-15,268.380;270.610;277.150,1005.16853932584;1013.52059925094;1038.01498127341 1952-10-15,263.060;269.230;271.400,985.243445692884;1008.35205992509;1016.47940074906 1952-11-15,270.230;283.660;283.660,1012.09737827715;1062.39700374532;1062.39700374532 1952-12-15,281.630;291.900;292.000,1054.79400749064;1093.25842696629;1093.6329588015 1953-01-15,285.240;289.770;293.790,1072.33082706767;1089.36090225564;1104.47368421053 1953-02-15,281.140;284.270;290.190,1060.90566037736;1072.71698113208;1095.05660377358 1953-03-15,279.870;279.870;290.640,1052.14285714286;1052.14285714286;1092.63157894737 1953-04-15,270.730;274.750;280.090,1017.78195488722;1032.89473684211;1052.96992481203 1953-05-15,271.480;272.280;278.790,1016.77902621723;1019.77528089888;1044.15730337079 1953-06-15,262.880;268.260;269.840,980.89552238806;1000.97014925373;1006.86567164179 1953-07-15,268.060;275.380;275.380,1000.22388059701;1027.53731343284;1027.53731343284 1953-08-15,261.220;261.220;276.740,971.078066914498;971.078066914498;1028.77323420074 1953-09-15,255.490;264.040;265.480,949.776951672863;981.561338289963;986.914498141264 1953-10-15,264.260;275.810;276.310,978.740740740741;1021.51851851852;1023.37037037037 1953-11-15,273.880;281.370;281.370,1018.14126394052;1045.98513011152;1045.98513011152 1953-12-15,278.300;280.900;283.540,1034.57249070632;1044.23791821561;1054.05204460967 1954-01-15,279.870;292.390;292.850,1040.40892193309;1086.95167286245;1088.66171003717 1954-02-15,289.540;294.540;294.540,1076.35687732342;1094.94423791822;1094.94423791822 1954-03-15,296.400;303.510;303.510,1101.85873605948;1128.28996282528;1128.28996282528 1954-04-15,304.260;319.330;319.330,1135.29850746269;1191.52985074627;1191.52985074627 1954-05-15,317.930;327.490;327.490,1181.89591078067;1217.43494423792;1217.43494423792 1954-06-15,319.270;333.530;336.900,1186.87732342007;1239.88847583643;1252.41635687732 1954-07-15,334.120;347.920;347.920,1242.08178438662;1293.38289962825;1293.38289962825 1954-08-15,335.800;335.800;350.380,1248.32713754647;1248.32713754647;1302.52788104089 1954-09-15,338.130;360.460;363.320,1261.67910447761;1345;1355.67164179104 1954-10-15,352.140;352.140;364.430,1313.9552238806;1313.9552238806;1359.81343283582 1954-11-15,353.960;386.770;388.510,1320.74626865672;1443.17164179104;1449.66417910448 1954-12-15,384.040;404.390;404.390,1438.35205992509;1514.56928838951;1514.56928838951 1955-01-15,388.200;408.830;408.890,1453.93258426966;1531.19850187266;1531.42322097378 1955-02-15,405.700;411.870;413.990,1519.47565543071;1542.58426966292;1550.52434456929 1955-03-15,391.360;409.700;419.680,1465.76779026217;1534.45692883895;1571.83520599251 1955-04-15,412.970;425.650;430.640,1546.70411985019;1594.19475655431;1612.88389513109 1955-05-15,414.120;424.860;426.300,1551.01123595506;1591.23595505618;1596.62921348315 1955-06-15,424.880;451.380;451.380,1591.31086142322;1690.56179775281;1690.56179775281 1955-07-15,453.820;465.850;468.450,1693.35820895522;1738.24626865672;1747.94776119403 1955-08-15,448.840;468.180;468.180,1674.77611940298;1746.94029850746;1746.94029850746 1955-09-15,455.560;466.620;487.450,1693.53159851301;1734.6468401487;1812.08178438662 1955-10-15,438.590;454.870;461.140,1630.44609665428;1690.96654275093;1714.2750929368 1955-11-15,454.890;483.260;487.380,1691.04089219331;1796.50557620818;1811.82156133829 1955-12-15,480.720;488.400;488.400,1793.73134328358;1822.38805970149;1822.38805970149 1956-01-15,462.350;470.740;485.780,1725.18656716418;1756.49253731343;1812.61194029851 1956-02-15,465.720;483.650;485.710,1737.76119402985;1804.66417910448;1812.35074626866 1956-03-15,486.690;511.790;513.030,1816.00746268657;1909.66417910448;1914.29104477612 1956-04-15,503.020;516.120;521.050,1869.96282527881;1918.66171003717;1936.98884758364 1956-05-15,468.810;478.050;516.440,1736.33333333333;1770.55555555556;1912.74074074074 1956-06-15,475.290;492.780;492.780,1747.38970588235;1811.69117647059;1811.69117647059 1956-07-15,491.920;517.810;517.810,1795.32846715328;1889.81751824818;1889.81751824818 1956-08-15,495.960;502.040;520.950,1816.7032967033;1838.97435897436;1908.24175824176 1956-09-15,475.250;475.250;509.820,1734.48905109489;1734.48905109489;1860.65693430657 1956-10-15,468.700;479.850;490.190,1704.36363636364;1744.90909090909;1782.50909090909 1956-11-15,466.100;472.780;495.370,1694.90909090909;1719.2;1801.34545454545 1956-12-15,480.610;499.470;499.470,1741.34057971014;1809.67391304348;1809.67391304348 1957-01-15,474.590;479.160;499.200,1719.52898550725;1736.08695652174;1808.69565217391 1957-02-15,454.820;464.620;477.220,1641.94945848375;1677.3285198556;1722.81588447653 1957-03-15,468.910;474.810;475.010,1686.72661870504;1707.94964028777;1708.6690647482 1957-04-15,474.980;494.360;494.360,1702.43727598566;1771.89964157706;1771.89964157706 1957-05-15,494.680;504.930;506.040,1766.71428571429;1803.32142857143;1807.28571428571 1957-06-15,497.080;503.290;513.190,1768.96797153025;1791.06761565836;1826.29893238434 1957-07-15,503.290;508.520;520.770,1778.40989399293;1796.89045936396;1840.17667844523 1957-08-15,470.140;484.350;506.210,1661.27208480565;1711.48409893993;1788.72791519435 1957-09-15,456.300;456.300;486.130,1612.36749116608;1612.36749116608;1717.77385159011 1957-10-15,419.790;441.040;465.820,1483.35689045936;1558.44522968198;1646.00706713781 1957-11-15,427.940;449.870;449.870,1506.83098591549;1584.04929577465;1584.04929577465 1957-12-15,425.650;435.690;449.550,1498.7676056338;1534.11971830986;1582.92253521127 1958-01-15,438.680;450.020;451.160,1533.84615384615;1573.4965034965;1577.48251748252 1958-02-15,436.890;439.920;458.650,1527.58741258741;1538.18181818182;1603.67132867133 1958-03-15,443.380;446.760;455.920,1539.51388888889;1551.25;1583.05555555556 1958-04-15,440.090;455.860;455.860,1522.80276816609;1577.37024221453;1577.37024221453 1958-05-15,455.450;462.700;463.670,1575.95155709343;1601.03806228374;1604.39446366782 1958-06-15,466.110;478.180;478.970,1612.83737024221;1654.60207612457;1657.33564013841 1958-07-15,476.890;502.990;504.370,1644.44827586207;1734.44827586207;1739.20689655172 1958-08-15,502.670;508.630;512.420,1739.34256055363;1759.96539792388;1773.07958477509 1958-09-15,511.770;532.090;532.090,1770.83044982699;1841.14186851211;1841.14186851211 1958-10-15,530.940;543.220;546.360,1837.16262975779;1879.65397923875;1890.51903114187 1958-11-15,540.520;557.460;567.440,1863.86206896552;1922.27586206897;1956.68965517241 1958-12-15,556.080;583.650;583.650,1924.15224913495;2019.55017301038;2019.55017301038 1959-01-15,583.150;593.960;597.660,2010.86206896552;2048.13793103448;2060.89655172414 1959-02-15,574.460;603.500;603.500,1987.7508650519;2088.23529411765;2088.23529411765 1959-03-15,601.710;601.710;614.690,2082.04152249135;2082.04152249135;2126.95501730104 1959-04-15,602.940;623.750;629.870,2079.10344827586;2150.86206896552;2171.96551724138 1959-05-15,615.640;643.790;643.790,2122.89655172414;2219.96551724138;2219.96551724138 1959-06-15,617.620;643.600;643.600,2122.40549828179;2211.68384879725;2211.68384879725 1959-07-15,650.880;674.880;674.880,2229.04109589041;2311.23287671233;2311.23287671233 1959-08-15,646.530;664.410;678.100,2214.14383561644;2275.37671232877;2322.2602739726 1959-09-15,616.450;631.680;655.900,2103.92491467577;2155.90443686007;2238.56655290102 1959-10-15,625.590;646.600;646.600,2127.85714285714;2199.31972789116;2199.31972789116 1959-11-15,634.460;659.180;659.180,2158.02721088435;2242.10884353741;2242.10884353741 1959-12-15,661.290;679.360;679.360,2249.28571428571;2310.74829931973;2310.74829931973 1960-01-15,622.620;622.620;685.470,2124.98293515358;2124.98293515358;2339.48805460751 1960-02-15,611.330;630.120;636.920,2079.3537414966;2143.26530612245;2166.39455782313 1960-03-15,599.100;616.590;626.870,2037.75510204082;2097.24489795918;2132.21088435374 1960-04-15,601.700;601.700;630.770,2039.66101694915;2039.66101694915;2138.20338983051 1960-05-15,599.610;625.500;625.500,2032.57627118644;2120.33898305085;2120.33898305085 1960-06-15,624.890;640.620;656.420,2111.11486486486;2164.25675675676;2217.63513513513 1960-07-15,601.680;616.730;646.910,2032.7027027027;2083.5472972973;2185.50675675676 1960-08-15,608.690;625.990;641.560,2056.38513513513;2114.83108108108;2167.43243243243 1960-09-15,569.080;580.140;626.100,1922.56756756757;1959.93243243243;2115.2027027027 1960-10-15,566.050;580.360;596.480,1899.4966442953;1947.51677852349;2001.61073825503 1960-11-15,585.240;597.220;612.010,1963.89261744966;2004.09395973154;2053.72483221477 1960-12-15,593.490;615.890;617.780,1991.57718120805;2066.74496644295;2073.08724832215 1961-01-15,610.250;648.200;650.640,2047.81879194631;2175.1677852349;2183.35570469799 1961-02-15,637.040;662.080;662.080,2137.71812080537;2221.74496644295;2221.74496644295 1961-03-15,661.080;676.630;679.380,2218.38926174497;2270.57046979866;2279.79865771812 1961-04-15,672.660;678.710;696.720,2257.24832214765;2277.55033557047;2337.98657718121 1961-05-15,677.050;696.720;705.960,2271.97986577181;2337.98657718121;2368.9932885906 1961-06-15,680.680;683.960;703.790,2284.1610738255;2295.1677852349;2361.71140939597 1961-07-15,679.300;705.370;705.370,2264.33333333333;2351.23333333333;2351.23333333333 1961-08-15,710.460;719.940;725.760,2376.12040133779;2407.82608695652;2427.29096989967 1961-09-15,691.860;701.210;726.530,2306.2;2337.36666666667;2421.76666666667 1961-10-15,697.240;703.920;708.490,2324.13333333333;2346.4;2361.63333333333 1961-11-15,703.840;721.600;734.340,2346.13333333333;2405.33333333333;2447.8 1961-12-15,720.100;731.140;734.910,2400.33333333333;2437.13333333333;2449.7 1962-01-15,689.920;700.000;726.010,2299.73333333333;2333.33333333333;2420.03333333333 1962-02-15,702.540;708.050;717.550,2334.01993355482;2352.32558139535;2383.88704318937 1962-03-15,706.630;706.950;723.540,2347.60797342193;2348.67109634552;2403.78737541528 1962-04-15,665.330;665.330;705.420,2203.07947019868;2203.07947019868;2335.82781456954 1962-05-15,576.930;613.360;675.490,1910.3642384106;2030.99337748344;2236.72185430464 1962-06-15,535.760;561.280;611.050,1774.03973509934;1858.54304635762;2023.34437086093 1962-07-15,571.240;597.930;597.930,1885.28052805281;1973.36633663366;1973.36633663366 1962-08-15,588.350;609.180;616.000,1941.74917491749;2010.49504950495;2033.00330033003 1962-09-15,574.120;578.980;607.630,1888.55263157895;1904.53947368421;1998.78289473684 1962-10-15,558.060;589.770;589.770,1835.72368421053;1940.03289473684;1940.03289473684 1962-11-15,597.130;649.300;652.610,1964.24342105263;2135.85526315789;2146.74342105263 1962-12-15,640.140;652.100;653.990,2105.72368421053;2145.06578947368;2151.28289473684 1963-01-15,646.790;682.850;683.730,2127.59868421053;2246.21710526316;2249.11184210526 1963-02-15,662.940;662.940;688.960,2180.72368421053;2180.72368421053;2266.31578947368 1963-03-15,659.720;682.520;684.730,2163.01639344262;2237.77049180328;2245.01639344262 1963-04-15,684.270;717.700;718.330,2243.50819672131;2353.11475409836;2355.18032786885 1963-05-15,712.550;726.960;726.960,2336.22950819672;2383.47540983607;2383.47540983607 1963-06-15,706.030;706.880;726.870,2307.28758169935;2310.06535947712;2375.39215686275 1963-07-15,687.710;695.430;716.450,2240.09771986971;2265.24429967427;2333.71335504886 1963-08-15,694.870;729.320;729.320,2263.42019543974;2375.63517915309;2375.63517915309 1963-09-15,732.020;732.790;745.960,2384.42996742671;2386.93811074919;2429.83713355049 1963-10-15,737.940;755.230;760.500,2395.90909090909;2452.04545454545;2469.15584415584 1963-11-15,711.490;750.520;753.770,2310.03246753247;2436.75324675325;2447.30519480519 1963-12-15,751.820;762.950;767.210,2433.07443365696;2469.09385113269;2482.88025889968 1964-01-15,766.080;785.340;787.780,2479.22330097087;2541.55339805825;2549.4498381877 1964-02-15,783.040;800.140;800.140,2534.11003236246;2589.4498381877;2589.4498381877 1964-03-15,802.750;813.290;820.250,2597.89644012945;2632.00647249191;2654.53074433657 1964-04-15,810.770;810.770;827.330,2623.85113268608;2623.85113268608;2677.44336569579 1964-05-15,817.100;820.560;830.170,2644.33656957929;2655.53398058252;2686.63430420712 1964-06-15,800.310;831.500;831.500,2581.64516129032;2682.25806451613;2682.25806451613 1964-07-15,837.350;841.100;851.350,2692.44372990354;2704.50160771704;2737.45980707396 1964-08-15,823.400;838.480;842.830,2656.12903225806;2704.77419354839;2718.8064516129 1964-09-15,844.000;875.370;875.740,2713.82636655949;2814.69453376206;2815.88424437299 1964-10-15,868.440;873.080;881.500,2792.4115755627;2807.33118971061;2834.40514469453 1964-11-15,870.640;875.430;891.710,2790.51282051282;2805.86538461538;2858.04487179487 1964-12-15,857.450;874.130;874.130,2748.23717948718;2801.69871794872;2801.69871794872 1965-01-15,869.780;902.860;902.860,2787.75641025641;2893.78205128205;2893.78205128205 1965-02-15,881.350;903.480;906.300,2824.83974358974;2895.76923076923;2904.80769230769 1965-03-15,887.820;889.050;901.910,2836.4856230032;2840.41533546326;2881.50159744409 1965-04-15,890.330;922.310;922.310,2835.44585987261;2937.29299363057;2937.29299363057 1965-05-15,913.220;918.040;939.620,2908.34394904459;2923.69426751592;2992.42038216561 1965-06-15,840.590;868.030;908.530,2660.09493670886;2746.93037974684;2875.09493670886 1965-07-15,861.770;881.740;883.230,2727.12025316456;2790.3164556962;2795.03164556962 1965-08-15,878.890;893.100;896.180,2781.29746835443;2826.26582278481;2836.01265822785 1965-09-15,893.600;930.580;937.880,2827.84810126582;2944.87341772152;2967.9746835443 1965-10-15,929.650;960.820;960.820,2932.64984227129;3030.97791798107;3030.97791798107 1965-11-15,946.380;946.710;961.850,2985.42586750789;2986.46687697161;3034.22712933754 1965-12-15,939.530;969.260;969.260,2954.49685534591;3047.98742138365;3047.98742138365 1966-01-15,968.540;983.510;994.200,3045.72327044025;3092.79874213836;3126.41509433962 1966-02-15,950.660;951.890;995.150,2970.8125;2974.65625;3109.84375 1966-03-15,911.080;924.770;938.190,2838.2554517134;2880.90342679128;2922.71028037383 1966-04-15,931.290;933.680;954.730,2883.25077399381;2890.65015479876;2955.82043343653 1966-05-15,864.140;884.070;931.950,2675.3560371517;2737.05882352941;2885.29411764706 1966-06-15,870.100;870.100;903.170,2685.49382716049;2685.49382716049;2787.56172839506 1966-07-15,847.380;847.380;894.040,2607.32307692308;2607.32307692308;2750.89230769231 1966-08-15,767.030;788.410;852.390,2345.65749235474;2411.03975535168;2606.69724770642 1966-09-15,772.660;774.220;814.300,2362.874617737;2367.64525993884;2490.21406727829 1966-10-15,744.320;807.070;809.570,2262.37082066869;2453.10030395137;2460.6990881459 1966-11-15,791.590;791.590;820.870,2406.04863221885;2406.04863221885;2495.04559270517 1966-12-15,785.690;785.690;820.540,2388.11550151976;2388.11550151976;2494.04255319149 1967-01-15,786.410;849.890;849.890,2390.30395136778;2583.25227963526;2583.25227963526 1967-02-15,836.640;839.370;860.970,2542.97872340426;2551.27659574468;2616.93009118541 1967-03-15,841.760;865.980;876.670,2550.78787878788;2624.18181818182;2656.57575757576 1967-04-15,842.430;897.050;897.050,2545.10574018127;2710.12084592145;2710.12084592145 1967-05-15,852.560;852.560;909.630,2567.95180722892;2567.95180722892;2739.84939759036 1967-06-15,847.770;860.260;886.150,2545.85585585586;2583.36336336336;2661.11111111111 1967-07-15,859.690;904.240;909.560,2573.92215568862;2707.30538922156;2723.23353293413 1967-08-15,893.720;901.290;926.720,2667.82089552239;2690.41791044776;2766.32835820896 1967-09-15,901.180;926.660;943.080,2682.08333333333;2757.91666666667;2806.78571428571 1967-10-15,879.740;879.740;933.310,2610.50445103858;2610.50445103858;2769.46587537092 1967-11-15,849.570;875.810;884.880,2513.52071005917;2591.15384615385;2617.98816568047 1967-12-15,879.160;905.110;905.110,2593.39233038348;2669.94100294985;2669.94100294985 1968-01-15,855.470;855.470;908.920,2508.70967741935;2508.70967741935;2665.45454545455 1968-02-15,831.770;840.500;863.560,2432.07602339181;2457.60233918129;2525.02923976608 1968-03-15,825.130;840.670;843.220,2405.62682215743;2450.93294460641;2458.36734693878 1968-04-15,861.250;912.220;912.220,2503.63372093023;2651.8023255814;2651.8023255814 1968-05-15,891.600;899.000;919.900,2584.34782608696;2605.79710144928;2666.3768115942 1968-06-15,897.800;897.800;917.950,2587.31988472622;2587.31988472622;2645.38904899135 1968-07-15,883.000;883.000;923.720,2530.08595988539;2530.08595988539;2646.76217765043 1968-08-15,869.650;896.010;896.130,2484.71428571429;2560.02857142857;2560.37142857143 1968-09-15,900.360;935.790;938.280,2565.12820512821;2666.06837606838;2673.16239316239 1968-10-15,942.320;952.390;967.490,2669.46175637394;2697.98866855524;2740.76487252125 1968-11-15,946.230;985.080;985.080,2672.96610169492;2782.71186440678;2782.71186440678 1968-12-15,943.750;943.750;985.210,2658.45070422535;2658.45070422535;2775.23943661972 1969-01-15,921.250;946.050;951.890,2587.7808988764;2657.44382022472;2673.84831460674 1969-02-15,899.800;905.210;952.700,2513.40782122905;2528.51955307263;2661.17318435754 1969-03-15,904.030;935.480;935.480,2504.23822714681;2591.35734072022;2591.35734072022 1969-04-15,917.510;950.180;950.180,2527.57575757576;2617.57575757576;2617.57575757576 1969-05-15,936.920;937.560;968.850,2573.95604395604;2575.71428571429;2661.67582417582 1969-06-15,869.760;873.190;933.170,2376.39344262295;2385.7650273224;2549.64480874317 1969-07-15,801.960;815.470;886.120,2179.23913043478;2215.95108695652;2407.9347826087 1969-08-15,809.130;836.720;837.250,2186.83783783784;2261.40540540541;2262.83783783784 1969-09-15,811.840;813.090;837.780,2188.24797843666;2191.61725067385;2258.16711590296 1969-10-15,802.200;855.990;862.260,2150.67024128686;2294.87935656836;2311.6890080429 1969-11-15,807.290;812.300;863.050,2152.77333333333;2166.13333333333;2301.46666666667 1969-12-15,769.930;800.360;805.040,2042.25464190981;2122.97082228117;2135.38461538462 1970-01-15,744.060;744.060;811.310,1968.4126984127;1968.4126984127;2146.32275132275 1970-02-15,746.440;777.590;777.590,1964.31578947368;2046.28947368421;2046.28947368421 1970-03-15,763.600;785.570;791.050,1998.95287958115;2056.46596858639;2070.81151832461 1970-04-15,724.330;736.070;792.500,1881.37662337662;1911.87012987013;2058.44155844156 1970-05-15,631.160;700.440;733.630,1635.12953367876;1814.61139896373;1900.59585492228 1970-06-15,682.910;683.530;720.430,1760.07731958763;1761.67525773196;1856.77835051546 1970-07-15,669.360;734.120;735.560,1716.30769230769;1882.35897435897;1886.05128205128 1970-08-15,707.350;764.580;765.810,1813.71794871795;1960.46153846154;1963.61538461538 1970-09-15,747.470;760.680;773.140,1906.8112244898;1940.51020408163;1972.29591836735 1970-10-15,753.560;755.610;783.680,1912.58883248731;1917.79187817259;1989.03553299492 1970-11-15,754.240;794.090;794.090,1904.64646464646;2005.27777777778;2005.27777777778 1970-12-15,794.290;838.920;842.000,1995.70351758794;2107.8391959799;2115.57788944724 1971-01-15,830.570;868.500;868.500,2086.85929648241;2182.1608040201;2182.1608040201 1971-02-15,868.980;878.830;890.060,2177.89473684211;2202.58145363409;2230.72681704261 1971-03-15,882.390;904.370;916.830,2205.975;2260.925;2292.075 1971-04-15,903.040;941.750;950.820,2251.97007481297;2348.50374064838;2371.12219451372 1971-05-15,905.780;907.810;939.920,2247.59305210918;2252.63027295285;2332.30769230769 1971-06-15,873.100;891.140;923.060,2150.49261083744;2194.92610837438;2273.54679802956 1971-07-15,858.430;858.430;903.400,2109.16461916462;2109.16461916462;2219.65601965602 1971-08-15,839.590;898.070;908.370,2057.81862745098;2201.15196078431;2226.39705882353 1971-09-15,883.470;887.190;920.930,2165.36764705882;2174.48529411765;2257.18137254902 1971-10-15,836.380;839.000;901.800,2044.93887530562;2051.34474327628;2204.88997555012 1971-11-15,797.970;831.340;843.170,1951.02689486553;2032.61613691932;2061.54034229829 1971-12-15,846.010;890.200;893.660,2058.41849148418;2165.93673965937;2174.35523114355 1972-01-15,889.150;902.170;917.220,2163.38199513382;2195.06082725061;2231.67883211679 1972-02-15,901.790;928.130;928.130,2183.51089588378;2247.28813559322;2247.28813559322 1972-03-15,928.660;940.700;950.180,2243.14009661836;2272.22222222222;2295.12077294686 1972-04-15,940.920;954.170;968.920,2267.27710843374;2299.20481927711;2334.74698795181 1972-05-15,925.120;960.720;971.250,2223.84615384615;2309.42307692308;2334.73557692308 1972-06-15,926.250;929.030;961.390,2221.22302158273;2227.8896882494;2305.49160671463 1972-07-15,910.450;924.740;942.130,2172.91169451074;2207.01670644391;2248.52028639618 1972-08-15,930.460;963.730;973.510,2215.38095238095;2294.59523809524;2317.88095238095 1972-09-15,935.730;953.270;970.050,2222.63657957245;2264.29928741093;2304.1567695962 1972-10-15,921.660;955.520;955.520,2178.86524822695;2258.91252955083;2258.91252955083 1972-11-15,968.540;1018.21;1025.21,2284.29245283019;2401.43867924528;2417.94811320755 1972-12-15,1000.00;1020.02;1036.27,2352.94117647059;2400.04705882353;2438.28235294118 1973-01-15,992.930;999.020;1051.70,2330.82159624413;2345.11737089202;2468.779342723 1973-02-15,947.920;955.070;996.760,2209.60372960373;2226.2703962704;2323.44988344988 1973-03-15,922.710;951.010;979.980,2130.96997690531;2196.32794457275;2263.23325635104 1973-04-15,921.430;921.430;967.410,2113.37155963303;2113.37155963303;2218.83027522936 1973-05-15,886.510;901.410;956.580,2019.38496583144;2053.32574031891;2178.99772209567 1973-06-15,869.130;891.710;927.000,1966.35746606335;2017.44343891403;2097.2850678733 1973-07-15,870.110;926.400;936.710,1964.1309255079;2091.19638826185;2114.46952595937 1973-08-15,851.900;887.570;912.780,1888.91352549889;1968.0044345898;2023.90243902439 1973-09-15,880.570;947.100;953.270,1948.16371681416;2095.35398230089;2109.00442477876 1973-10-15,948.830;956.580;987.060,2080.76754385965;2097.76315789474;2164.60526315789 1973-11-15,817.730;822.250;948.830,1781.54684095861;1791.39433551198;2067.16775599129 1973-12-15,788.310;850.860;851.140,1706.2987012987;1841.68831168831;1842.29437229437 1974-01-15,823.110;855.550;880.690,1766.330472103;1835.94420600858;1889.89270386266 1974-02-15,803.900;860.530;863.420,1703.17796610169;1823.15677966102;1829.27966101695 1974-03-15,846.680;846.680;891.660,1771.29707112971;1771.29707112971;1865.39748953975 1974-04-15,827.680;836.750;869.920,1724.33333333333;1743.22916666667;1812.33333333333 1974-05-15,795.370;802.170;865.770,1636.56378600823;1650.55555555556;1781.41975308642 1974-06-15,802.410;802.410;859.670,1637.57142857143;1637.57142857143;1754.42857142857 1974-07-15,757.430;757.430;806.240,1533.25910931174;1533.25910931174;1632.06477732794 1974-08-15,656.840;678.580;797.560,1313.68;1357.16;1595.12 1974-09-15,607.870;607.870;677.880,1201.32411067194;1201.32411067194;1339.6837944664 1974-10-15,584.560;665.520;673.500,1143.9530332681;1302.38747553816;1318.00391389432 1974-11-15,608.570;618.660;674.750,1181.68932038835;1201.28155339806;1310.19417475728 1974-12-15,577.600;616.240;616.240,1112.90944123314;1187.36030828516;1187.36030828516 1975-01-15,632.040;703.690;705.960,1213.12859884837;1350.65259117083;1355.00959692898 1975-02-15,707.600;739.050;749.770,1347.80952380952;1407.71428571429;1428.13333333333 1975-03-15,743.430;768.150;786.530,1410.68311195446;1457.59013282732;1492.46679316888 1975-04-15,742.880;821.340;821.340,1404.31001890359;1552.62759924386;1552.62759924386 1975-05-15,815.000;832.290;858.730,1531.95488721805;1564.45488721804;1614.15413533835 1975-06-15,819.310;878.990;878.990,1528.56343283582;1639.90671641791;1639.90671641791 1975-07-15,824.860;831.510;881.810,1521.88191881919;1534.15129151291;1626.9557195572 1975-08-15,791.690;835.340;835.340,1457.9926335175;1538.37937384899;1538.37937384899 1975-09-15,793.880;793.880;840.110,1453.99267399267;1453.99267399267;1538.663003663 1975-10-15,784.160;836.040;855.160,1428.34244080146;1522.84153005464;1557.66848816029 1975-11-15,825.720;860.670;860.670,1493.16455696203;1556.36528028933;1556.36528028933 1975-12-15,818.800;852.410;859.810,1475.31531531532;1535.87387387387;1549.20720720721 1976-01-15,858.710;975.280;975.280,1544.44244604317;1754.10071942446;1754.10071942446 1976-02-15,950.570;972.610;994.570,1703.53046594982;1743.02867383513;1782.3835125448 1976-03-15,970.640;999.450;1009.21,1736.38640429338;1787.92486583184;1805.38461538462 1976-04-15,968.280;996.850;1011.02,1725.98930481283;1776.91622103387;1802.17468805704 1976-05-15,965.570;975.230;1007.48,1708.97345132743;1726.07079646018;1783.15044247788 1976-06-15,958.090;1002.78;1007.45,1686.77816901408;1765.45774647887;1773.67957746479 1976-07-15,979.290;984.640;1011.21,1715.04378283713;1724.41330998249;1770.94570928196 1976-08-15,960.440;973.740;999.340,1673.24041811847;1696.41114982578;1741.01045296167 1976-09-15,978.640;990.190;1014.79,1699.02777777778;1719.07986111111;1761.78819444444 1976-10-15,932.350;964.930;979.890,1610.27633851468;1666.54576856649;1692.38341968912 1976-11-15,924.040;947.220;966.090,1593.1724137931;1633.13793103448;1665.6724137931 1976-12-15,946.640;1004.65;1004.65,1626.52920962199;1726.20274914089;1726.20274914089 1977-01-15,954.370;954.370;999.750,1631.40170940171;1631.40170940171;1708.97435897436 1977-02-15,931.520;936.420;958.360,1576.17597292724;1584.46700507614;1621.59052453469 1977-03-15,919.130;919.130;968.000,1544.75630252101;1544.75630252101;1626.89075630252 1977-04-15,914.600;926.900;947.760,1524.33333333333;1544.83333333333;1579.6 1977-05-15,898.660;898.660;943.440,1490.31509121061;1490.31509121061;1564.57711442786 1977-06-15,903.040;916.300;929.700,1487.71004942339;1509.55518945634;1531.63097199341 1977-07-15,888.430;890.070;923.420,1456.44262295082;1459.13114754098;1513.80327868852 1977-08-15,854.120;861.490;891.810,1395.62091503268;1407.66339869281;1457.20588235294 1977-09-15,834.720;847.110;876.390,1359.47882736156;1379.65798045603;1427.34527687296 1977-10-15,801.540;818.350;851.960,1301.2012987013;1328.49025974026;1383.05194805195 1977-11-15,800.850;829.700;845.890,1293.7802907916;1340.38772213247;1366.54281098546 1977-12-15,806.220;831.170;831.170,1298.26086956522;1338.43800322061;1338.43800322061 1978-01-15,763.340;769.920;817.740,1221.344;1231.872;1308.384 1978-02-15,742.120;742.120;782.660,1179.84101748808;1179.84101748808;1244.29252782194 1978-03-15,742.720;757.360;773.820,1171.48264984227;1194.57413249211;1220.53627760252 1978-04-15,751.040;837.320;837.320,1175.33646322379;1310.35993740219;1310.35993740219 1978-05-15,822.070;840.610;858.370,1274.52713178295;1303.27131782946;1330.80620155039 1978-06-15,812.280;818.950;866.510,1245.8282208589;1256.05828220859;1329.00306748466 1978-07-15,805.790;862.270;862.270,1226.46879756469;1312.43531202435;1312.43531202435 1978-08-15,860.710;876.820;900.120,1304.10606060606;1328.51515151515;1363.81818181818 1978-09-15,857.160;865.820;907.740,1288.96240601504;1301.98496240602;1365.02255639098 1978-10-15,792.450;792.450;901.420,1180.99850968703;1180.99850968703;1343.39791356185 1978-11-15,785.260;799.030;827.790,1165.07418397626;1185.50445103858;1228.17507418398 1978-12-15,787.510;805.010;821.900,1163.23485967504;1189.08419497784;1214.03249630724 1979-01-15,811.420;839.220;859.750,1188.02342606149;1228.7262079063;1258.78477306003 1979-02-15,807.000;808.820;840.870,1167.87264833575;1170.50651230101;1216.88856729378 1979-03-15,815.750;862.180;871.360,1168.69627507163;1235.21489971347;1248.36676217765 1979-04-15,854.900;854.900;878.720,1210.90651558074;1210.90651558074;1244.64589235127 1979-05-15,822.160;822.330;857.590,1149.87412587413;1150.11188811189;1199.42657342657 1979-06-15,821.210;841.980;849.100,1135.83679114799;1164.5643153527;1174.41217150761 1979-07-15,825.510;846.420;852.990,1129.28864569083;1157.89329685363;1166.88098495212 1979-08-15,846.160;887.630;887.630,1146.55826558266;1202.75067750677;1202.75067750677 1979-09-15,866.130;878.580;893.940,1161.03217158177;1177.72117962466;1198.3109919571 1979-10-15,806.830;815.700;897.610,1072.91223404255;1084.70744680851;1193.63031914894 1979-11-15,796.670;822.350;831.740,1049.63109354414;1083.465085639;1095.83662714097 1979-12-15,819.620;838.740;844.620,1068.60495436767;1093.5332464146;1101.19947848761 1980-01-15,820.310;875.850;881.910,1054.38303341902;1125.77120822622;1133.56041131105 1980-02-15,854.440;863.140;903.840,1082.94043092522;1093.9670468948;1145.55133079848 1980-03-15,759.980;785.750;856.480,948.789013732834;980.961298377029;1069.26342072409 1980-04-15,759.130;817.060;817.060,937.197530864197;1008.71604938272;1008.71604938272 1980-05-15,805.200;850.850;860.320,984.352078239609;1040.15892420538;1051.73594132029 1980-06-15,843.770;867.920;887.540,1020.27811366385;1049.48004836759;1073.20435308343 1980-07-15,872.270;935.320;936.180,1054.7400241838;1130.97944377267;1132.01934703748 1980-08-15,929.780;932.590;966.720,1116.1824729892;1119.55582232893;1160.52821128451 1980-09-15,921.930;932.420;974.570,1097.53571428571;1110.02380952381;1160.20238095238 1980-10-15,917.750;924.490;972.440,1082.25235849057;1090.20047169811;1146.74528301887 1980-11-15,932.420;993.340;1000.17,1090.54970760234;1161.80116959064;1169.78947368421 1980-12-15,908.450;963.990;974.400,1052.6651216686;1117.02201622248;1129.08458864426 1981-01-15,938.910;947.270;1004.69,1079.20689655172;1088.81609195402;1154.81609195402 1981-02-15,931.570;974.580;974.580,1059.80659840728;1108.73720136519;1108.73720136519 1981-03-15,964.620;1003.87;1015.22,1089.96610169492;1134.31638418079;1147.14124293785 1981-04-15,989.100;997.750;1024.05,1110.10101010101;1119.80920314254;1149.3265993266 1981-05-15,963.440;991.750;995.590,1072.87305122494;1104.3986636971;1108.67483296214 1981-06-15,976.880;976.880;1011.99,1078.23399558499;1078.23399558499;1116.98675496689 1981-07-15,924.660;952.340;967.660,1009.45414847162;1039.67248908297;1056.39737991266 1981-08-15,881.470;881.470;953.580,955.005417118093;955.005417118093;1033.13109425786 1981-09-15,824.010;849.980;884.230,884.130901287554;911.995708154506;948.744635193133 1981-10-15,830.960;852.550;878.140,889.678800856531;912.79443254818;940.192719486081 1981-11-15,844.080;888.980;888.980,900.832443970117;948.751334044824;948.751334044824 1981-12-15,868.250;875.000;892.690,923.670212765957;930.851063829787;949.670212765958 1982-01-15,838.950;871.100;882.520,889.66065747614;923.753976670202;935.864262990456 1982-02-15,811.260;824.390;852.550,857.568710359408;871.448202959831;901.215644820296 1982-03-15,795.470;822.770;828.390,841.767195767196;870.656084656085;876.603174603175 1982-04-15,833.240;848.360;865.580,878.018967334036;893.951527924131;912.096944151739 1982-05-15,819.540;819.540;869.200,855.469728601253;855.469728601253;907.306889352818 1982-06-15,788.620;811.930;816.880,813.010309278351;837.041237113402;842.144329896907 1982-07-15,796.990;808.600;833.430,817.425641025641;829.333333333333;854.8 1982-08-15,776.920;901.310;901.310,795.209825997953;922.528147389969;922.528147389969 1982-09-15,895.050;896.250;934.790,914.249233912155;915.474974463738;954.841675178754 1982-10-15,903.610;991.720;1036.98,920.173116089613;1009.89816700611;1055.98778004073 1982-11-15,990.990;1039.28;1065.49,1011.21428571429;1060.48979591837;1087.23469387755 1982-12-15,990.250;1046.54;1070.55,1014.60040983607;1072.27459016393;1096.875 1983-01-15,1027.04;1075.70;1092.35,1050.14314928425;1099.89775051125;1116.92229038855 1983-02-15,1059.79;1112.16;1121.81,1082.52298263534;1136.01634320735;1145.873340143 1983-03-15,1114.45;1130.03;1145.90,1138.35546475996;1154.26966292135;1170.48008171604 1983-04-15,1113.49;1226.20;1226.20,1129.30020283976;1243.61054766734;1243.61054766734 1983-05-15,1190.02;1199.98;1232.59,1199.61693548387;1209.65725806452;1242.53024193548 1983-06-15,1185.50;1221.96;1248.30,1191.45728643216;1228.10050251256;1254.57286432161 1983-07-15,1189.90;1199.22;1243.69,1191.09109109109;1200.42042042042;1244.93493493493 1983-08-15,1163.06;1216.16;1216.16,1160.73852295409;1213.73253493014;1213.73253493014 1983-09-15,1206.81;1233.13;1260.77,1198.42105263158;1224.55809334657;1252.00595829196 1983-10-15,1223.48;1225.20;1284.65,1211.36633663366;1213.06930693069;1271.93069306931 1983-11-15,1214.84;1276.02;1287.19,1200.4347826087;1260.88932806324;1271.92687747036 1983-12-15,1236.79;1258.64;1275.10,1220.91806515301;1242.48766041461;1258.73642645607 1984-01-15,1220.58;1220.58;1286.64,1197.82139352306;1197.82139352306;1262.64965652601 1984-02-15,1134.21;1154.63;1213.88,1107.626953125;1127.568359375;1185.4296875 1984-03-15,1139.76;1164.89;1184.36,1110.87719298246;1135.37037037037;1154.3469785575 1984-04-15,1130.55;1170.75;1175.25,1096.55674102813;1135.54801163919;1139.91270611057 1984-05-15,1101.24;1104.85;1186.56,1065.02901353965;1068.52030947776;1147.54352030948 1984-06-15,1086.90;1132.40;1133.84,1048.11957569913;1091.99614271938;1093.38476374156 1984-07-15,1086.57;1115.28;1134.28,1043.77521613833;1071.35446685879;1089.60614793468 1984-08-15,1134.61;1224.38;1239.73,1085.75119617225;1171.65550239234;1186.34449760766 1984-09-15,1197.99;1206.71;1237.52,1140.94285714286;1149.24761904762;1178.59047619048 1984-10-15,1175.13;1207.38;1225.93,1115.98290598291;1146.60968660969;1164.22602089269 1984-11-15,1185.29;1188.94;1244.15,1125.63152896486;1129.09781576448;1181.5289648623 1984-12-15,1163.21;1211.57;1211.57,1104.6628679962;1150.58879392213;1150.58879392213 1985-01-15,1184.96;1286.77;1292.62,1123.18483412322;1219.68720379147;1225.23222748815 1985-02-15,1275.84;1284.01;1297.92,1203.62264150943;1211.33018867925;1224.45283018868 1985-03-15,1247.35;1266.78;1299.36,1172.32142857143;1190.58270676692;1221.2030075188 1985-04-15,1252.98;1258.06;1284.78,1172.10477081384;1176.85687558466;1201.85219831618 1985-05-15,1242.05;1315.41;1315.41,1157.54892823858;1225.91798695247;1225.91798695247 1985-06-15,1290.10;1335.46;1335.46,1198.97769516729;1241.13382899628;1241.13382899628 1985-07-15,1321.91;1347.45;1359.54,1226.26159554731;1249.95361781076;1261.16883116883 1985-08-15,1312.50;1334.01;1355.62,1215.27777777778;1235.19444444444;1255.2037037037 1985-09-15,1297.94;1328.63;1339.27,1198.46722068329;1226.80517082179;1236.6297322253 1985-10-15,1324.37;1374.31;1375.57,1218.37166513339;1264.3146274149;1265.47378104876 1985-11-15,1389.68;1472.13;1475.69,1274.93577981651;1350.57798165138;1353.84403669725 1985-12-15,1457.91;1546.67;1553.10,1333.86093321135;1415.06861848124;1420.95150960659 1986-01-15,1502.29;1570.99;1570.99,1370.70255474453;1433.38503649635;1433.38503649635 1986-02-15,1593.12;1709.06;1713.99,1457.56633119854;1563.64135407136;1568.15187557182 1986-03-15,1686.42;1818.61;1821.72,1550.01838235294;1671.51654411765;1674.375 1986-04-15,1735.51;1783.98;1855.90,1598.07550644567;1642.70718232044;1708.93186003683 1986-05-15,1758.18;1876.71;1882.35,1614.49035812672;1723.33333333333;1728.51239669421 1986-06-15,1837.19;1892.72;1892.72,1677.79908675799;1728.51141552511;1728.51141552511 1986-07-15,1766.87;1775.31;1909.03,1613.5799086758;1621.28767123288;1743.40639269406 1986-08-15,1763.64;1898.34;1904.53,1607.69371011851;1730.48313582498;1736.1257976299 1986-09-15,1755.20;1767.58;1919.71,1592.74047186933;1603.97459165154;1742.02359346642 1986-10-15,1774.18;1877.71;1878.37,1608.50407978241;1702.36627379873;1702.96464188577 1986-11-15,1817.21;1914.23;1916.76,1646.02355072464;1733.90398550725;1736.19565217391 1986-12-15,1895.95;1895.95;1955.57,1715.79185520362;1715.79185520362;1769.74660633484 1987-01-15,1927.31;2158.04;2163.39,1733.19244604317;1940.68345323741;1945.49460431655 1987-02-15,2158.04;2223.99;2244.09,1933.72759856631;1992.82258064516;2010.83333333333 1987-03-15,2220.47;2304.69;2372.59,1980.79393398751;2055.92328278323;2116.49420160571 1987-04-15,2230.54;2286.36;2405.54,1979.18367346939;2028.71339840284;2134.46317657498 1987-05-15,2215.87;2291.57;2342.19,1959.21308576481;2026.14500442087;2070.90185676393 1987-06-15,2278.22;2418.53;2451.05,2007.2422907489;2130.86343612335;2159.5154185022 1987-07-15,2409.76;2572.07;2572.07,2117.539543058;2260.16695957821;2260.16695957821 1987-08-15,2546.72;2662.95;2722.42,2226.15384615385;2327.7534965035;2379.73776223776 1987-09-15,2492.82;2596.28;2613.04,2167.66956521739;2257.6347826087;2272.20869565217 1987-10-15,1738.74;1993.53;2640.99,1508.01387684302;1728.99392888118;2290.53772766696 1987-11-15,1833.55;1833.55;2014.09,1588.86481802426;1588.86481802426;1745.31195840555 1987-12-15,1766.74;1938.83;2005.64,1530.9705372617;1680.09532062392;1737.98960138648 1988-01-15,1879.14;1958.22;2051.89,1624.14866032844;1692.49783923941;1773.45721694036 1988-02-15,1895.72;2071.62;2071.62,1634.24137931034;1785.87931034483;1785.87931034483 1988-03-15,1978.12;1988.06;2087.37,1697.95708154506;1706.48927038627;1791.7339055794 1988-04-15,1980.60;2032.33;2110.08,1691.37489325363;1735.55081127242;1801.94705380017 1988-05-15,1941.48;2031.12;2058.36,1652.32340425532;1728.61276595745;1751.79574468085 1988-06-15,2052.45;2141.71;2152.20,1739.36440677966;1815.00847457627;1823.89830508475 1988-07-15,2053.70;2128.73;2158.61,1733.08016877637;1796.39662447257;1821.61181434599 1988-08-15,1989.33;2031.65;2134.07,1671.70588235294;1707.26890756303;1793.33613445378 1988-09-15,2002.31;2112.91;2119.31,1671.37729549249;1763.69782971619;1769.04006677796 1988-10-15,2102.06;2148.65;2183.50,1748.80199667221;1787.56239600666;1816.55574043261 1988-11-15,2038.58;2114.51;2170.34,1694.58021612635;1757.69742310889;1804.106400665 1988-12-15,2092.28;2168.57;2182.68,1736.33195020747;1799.64315352697;1811.35269709544 1989-01-15,2144.64;2342.32;2342.32,1770.96614368291;1934.20313790256;1934.20313790256 1989-02-15,2245.54;2258.39;2347.14,1846.66118421053;1857.22861842105;1930.21381578947 1989-03-15,2243.04;2293.62;2340.71,1834.04742436631;1875.40474243663;1913.90842191333 1989-04-15,2291.97;2418.80;2418.99,1861.87652315191;1964.90658001625;1965.06092607636 1989-05-15,2371.33;2480.15;2502.02,1915.45234248788;2003.352180937;2021.01777059774 1989-06-15,2440.06;2440.06;2531.87,1966.20467365028;1966.20467365028;2040.18533440774 1989-07-15,2452.77;2660.66;2660.66,1971.68006430868;2138.79421221865;2138.79421221865 1989-08-15,2641.12;2737.27;2743.36,2119.67897271268;2196.84590690209;2201.73354735152 1989-09-15,2659.19;2692.82;2752.09,2127.352;2154.256;2201.672 1989-10-15,2569.26;2645.08;2791.41,2045.58917197452;2105.95541401274;2222.4601910828 1989-11-15,2582.17;2706.27;2706.27,2050.96902303415;2149.53931691819;2149.53931691819 1989-12-15,2687.93;2753.20;2761.09,2131.58604282316;2183.34655035686;2189.60348929421 1990-01-15,2543.24;2590.54;2810.15,1996.26373626374;2033.39089481947;2205.76923076923 1990-02-15,2564.19;2627.25;2649.55,2003.2734375;2052.5390625;2069.9609375 1990-03-15,2635.59;2707.21;2755.63,2047.85547785548;2103.50427350427;2141.12665112665 1990-04-15,2645.05;2656.76;2765.77,2052.01706749418;2061.1016291699;2145.67106283941 1990-05-15,2668.92;2876.66;2878.56,2065.72755417957;2226.51702786378;2227.98761609907 1990-06-15,2842.33;2880.69;2935.89,2188.09083910701;2217.62124711316;2260.11547344111 1990-07-15,2879.21;2905.20;2999.75,2207.98312883436;2227.91411042945;2300.4217791411 1990-08-15,2483.42;2614.36;2899.26,1887.09726443769;1986.59574468085;2203.08510638298 1990-09-15,2427.48;2452.48;2628.22,1829.29917106255;1848.13865862849;1980.572720422 1990-10-15,2365.10;2442.33;2523.76,1771.61048689139;1829.4606741573;1890.45692883895 1990-11-15,2440.84;2559.65;2565.35,1824.24514200299;1913.04185351271;1917.3019431988 1990-12-15,2565.59;2633.66;2637.13,1917.48131539611;1968.355754858;1970.94917787743 1991-01-15,2470.30;2736.39;2736.39,1835.2897473997;2032.97919762259;2032.97919762259 1991-02-15,2730.69;2882.18;2934.65,2025.73442136499;2138.11572700297;2177.04005934718 1991-03-15,2855.45;2913.86;2973.27,2115.14814814815;2158.41481481481;2202.42222222222 1991-04-15,2873.02;2887.87;3004.46,2125.01479289941;2135.99852071006;2222.23372781065 1991-05-15,2865.38;3027.50;3027.50,2113.11209439528;2232.66961651917;2232.66961651917 1991-06-15,2906.75;2906.75;3035.33,2137.31617647059;2137.31617647059;2231.86029411765 1991-07-15,2932.47;3024.82;3024.82,2153.06167400881;2220.86637298091;2220.86637298091 1991-08-15,2898.03;3043.60;3055.23,2121.54465592972;2228.11127379209;2236.62518301611 1991-09-15,2982.56;3016.77;3029.07,2173.87755102041;2198.81195335277;2207.77696793003 1991-10-15,2942.75;3069.10;3077.15,2141.73944687045;2233.69723435226;2239.55604075691 1991-11-15,2894.68;2894.68;3065.30,2100.63860667634;2100.63860667634;2224.4557329463 1991-12-15,2863.82;3168.83;3168.83,2076.7367657723;2297.91878172589;2297.91878172589 1992-01-15,3172.41;3223.39;3272.14,2297.18320057929;2334.09847936278;2369.39898624185 1992-02-15,3224.73;3267.67;3283.32,2326.64502164502;2357.62626262626;2368.91774891775 1992-03-15,3208.63;3235.47;3290.25,2303.39554917444;2322.66331658291;2361.98851399856 1992-04-15,3181.35;3359.12;3366.50,2280.5376344086;2407.97132616487;2413.26164874552 1992-05-15,3336.09;3396.88;3398.43,2388.03865425913;2431.5533285612;2432.66284896206 1992-06-15,3274.12;3318.52;3413.21,2335.3209700428;2366.99001426534;2434.52924393723 1992-07-15,3277.61;3393.78;3393.78,2332.81850533808;2415.50177935943;2415.50177935943 1992-08-15,3228.17;3257.35;3395.40,2291.1071682044;2311.81689141235;2409.79418026969 1992-09-15,3250.32;3271.66;3376.22,2300.29723991507;2315.39985845718;2389.39844302902 1992-10-15,3136.58;3226.28;3254.37,2211.97461212976;2275.23272214386;2295.04231311707 1992-11-15,3193.32;3305.16;3305.16,2248.81690140845;2327.57746478873;2327.57746478873 1992-12-15,3255.18;3301.11;3333.26,2293.99577167019;2326.36363636364;2349.02043692741 1993-01-15,3241.95;3310.03;3310.03,2273.4572230014;2321.19915848527;2321.19915848527 1993-02-15,3302.19;3370.81;3442.14,2307.61006289308;2355.56254367575;2405.40880503145 1993-03-15,3355.41;3435.11;3478.34,2336.6364902507;2392.13788300836;2422.24233983287 1993-04-15,3370.81;3427.55;3478.61,2340.84027777778;2380.24305555556;2415.70138888889 1993-05-15,3437.19;3527.43;3554.83,2383.62690707351;2446.20665742025;2465.2080443828 1993-06-15,3466.81;3516.08;3553.45,2400.8379501385;2434.95844875346;2460.8379501385 1993-07-15,3449.93;3539.47;3567.70,2389.14819944598;2451.15650969529;2470.70637119114 1993-08-15,3548.97;3651.25;3652.09,2450.94613259668;2521.58149171271;2522.16160220994 1993-09-15,3537.24;3555.12;3645.10,2437.79462439697;2450.11716057891;2512.12956581668 1993-10-15,3577.76;3680.59;3687.86,2455.56623198353;2526.14275909403;2531.13246396706 1993-11-15,3624.98;3683.95;3710.77,2486.26886145405;2526.7146776406;2545.109739369 1993-12-15,3697.08;3754.09;3794.33,2535.72016460905;2574.82167352538;2602.42112482853 1994-01-15,3756.60;3978.36;3978.36,2569.49384404925;2721.17647058824;2721.17647058824 1994-02-15,3832.02;3832.02;3975.54,2612.1472392638;2612.1472392638;2709.97955010225 1994-03-15,3626.75;3635.96;3895.65,2463.82472826087;2470.08152173913;2646.50135869565 1994-04-15,3593.35;3681.69;3705.78,2437.82225237449;2497.75440976934;2514.09769335142 1994-05-15,3629.04;3758.37;3766.35,2460.36610169491;2548.04745762712;2553.45762711864 1994-06-15,3624.96;3624.96;3814.83,2449.2972972973;2449.2972972973;2577.58783783784 1994-07-15,3646.65;3764.50;3764.50,2457.31132075472;2536.72506738544;2536.72506738544 1994-08-15,3747.02;3913.42;3917.30,2514.77852348993;2626.45637583893;2629.06040268456 1994-09-15,3831.75;3843.19;3953.88,2564.75903614458;2572.41633199465;2646.50602409639 1994-10-15,3775.56;3908.12;3936.04,2525.45819397993;2614.127090301;2632.80267558528 1994-11-15,3674.63;3739.23;3863.37,2454.66265865063;2497.81563126253;2580.74148296593 1994-12-15,3685.73;3834.44;3861.69,2462.07748830995;2561.41616566466;2579.61923847695 1995-01-15,3832.08;3843.86;3932.34,2549.62075848303;2557.45841650033;2616.32734530938 1995-02-15,3847.56;4011.05;4011.74,2549.74155069583;2658.08482438701;2658.54208084824 1995-03-15,3962.63;4157.69;4172.56,2617.3249669749;2746.16248348745;2755.98414795244 1995-04-15,4168.41;4321.27;4321.27,2744.18038183015;2844.81237656353;2844.81237656353 1995-05-15,4316.08;4465.14;4465.14,2835.7950065703;2933.73193166886;2933.73193166886 1995-06-15,4423.99;4556.10;4589.64,2900.97704918033;2987.60655737705;3009.6 1995-07-15,4585.15;4708.47;4736.29,3006.65573770492;3087.52131147541;3105.76393442623 1995-08-15,4580.62;4610.56;4701.42,2995.8273381295;3015.40876389797;3074.83322432963 1995-09-15,4647.54;4789.08;4801.80,3033.64229765013;3126.03133159269;3134.33420365535 1995-10-15,4703.82;4755.48;4802.45,3060.39037085231;3094.00130123617;3124.56083279115 1995-11-15,4766.68;5074.49;5105.56,3103.30729166667;3303.70442708333;3323.93229166667 1995-12-15,5059.32;5117.12;5216.47,3295.97394136808;3333.62866449511;3398.35179153094 1996-01-15,5032.94;5395.30;5395.30,3259.67616580311;3494.36528497409;3494.36528497409 1996-02-15,5373.99;5485.62;5630.49,3469.32859909619;3541.39444803099;3634.91930277598 1996-03-15,5470.45;5587.14;5683.60,3513.45536287733;3588.40077071291;3650.35324341683 1996-04-15,5485.98;5569.08;5689.74,3509.90403071017;3563.07101727447;3640.26871401152 1996-05-15,5420.95;5643.18;5778.00,3461.65389527459;3603.56321839081;3689.65517241379 1996-06-15,5624.71;5654.63;5719.27,3589.4767070836;3608.5705169113;3649.82131461391 1996-07-15,5346.55;5528.91;5729.98,3405.44585987261;3521.59872611465;3649.66878980892 1996-08-15,5594.75;5616.21;5733.47,3556.73871582962;3570.38143674507;3644.92689129053 1996-09-15,5606.96;5882.17;5894.74,3553.20659062104;3727.61089987326;3735.57667934094 1996-10-15,5904.90;6029.38;6094.23,3730.1958307012;3808.83133291219;3849.79785217941 1996-11-15,6021.93;6521.70;6547.79,3796.92938209332;4112.04287515763;4128.49306431274 1996-12-15,6268.35;6448.27;6560.91,3952.30138713745;4065.74401008827;4136.76544766709 1997-01-15,6442.49;6813.09;6883.90,4049.33375235701;4282.26901319925;4326.77561282212 1997-02-15,6746.90;6877.74;7067.46,4227.38095238095;4309.36090225564;4428.23308270677 1997-03-15,6583.48;6583.48;7085.16,4114.675;4114.675;4428.225 1997-04-15,6391.69;7008.99;7008.99,3989.81897627965;4375.14981273408;4375.14981273408 1997-05-15,6976.48;7331.04;7383.41,4357.57651467833;4579.03810118676;4611.74890693317 1997-06-15,7269.66;7672.79;7796.51,4535.0343106675;4786.5190268247;4863.69931378665 1997-07-15,7722.33;8222.61;8254.89,4811.42056074766;5123.1214953271;5143.23364485981 1997-08-15,7622.42;7622.42;8259.31,4740.31094527363;4740.31094527363;5136.3868159204 1997-09-15,7660.98;7945.26;7996.83,4752.46898263027;4928.82133995037;4960.81265508685 1997-10-15,7161.15;7442.08;8178.31,4431.4047029703;4605.24752475248;5060.8353960396 1997-11-15,7401.32;7823.13;7881.07,4582.86068111455;4844.04334365325;4879.91950464396 1997-12-15,7660.13;7908.25;8149.13,4748.99566026038;4902.82083075015;5052.15747055177 1998-01-15,7580.42;7906.50;7978.99,4690.85396039604;4892.63613861386;4937.49381188119 1998-02-15,8107.78;8545.72;8545.72,5007.89376158122;5278.39407041383;5278.39407041383 1998-03-15,8444.33;8799.81;8906.43,5206.12207151665;5425.28360049322;5491.01726263872 1998-04-15,8868.32;9063.37;9184.94,5457.42769230769;5577.45846153846;5652.27076923077 1998-05-15,8899.95;8899.95;9211.84,5466.79975429975;5466.79975429975;5658.37837837838 1998-06-15,8627.93;8952.02;9069.60,5293.20858895706;5492.03680981595;5564.1717791411 1998-07-15,8883.29;8883.29;9337.97,5443.19240196078;5443.19240196078;5721.79534313726 1998-08-15,7539.07;7539.07;8786.74,4613.87392900857;4613.87392900857;5377.44186046512 1998-09-15,7615.54;7842.62;8154.41,4654.97555012225;4793.77750611247;4984.35819070905 1998-10-15,7632.53;8592.10;8592.10,4653.98170731707;5239.08536585366;5239.08536585366 1998-11-15,8706.15;9116.55;9374.27,5308.62804878049;5558.87195121951;5716.01829268293 1998-12-15,8695.60;9181.43;9320.98,5305.43014032947;5601.84868822453;5686.99206833435 1999-01-15,9120.67;9358.83;9643.32,5551.22945830797;5696.18381010347;5869.33657942788 1999-02-15,9133.03;9306.58;9552.68,5551.99392097264;5657.49544072948;5807.10030395137 1999-03-15,9275.88;9786.16;10006.8,5621.74545454545;5931.00606060606;6064.72727272727 1999-04-15,9832.51;10789.0;10878.4,5916.07099879663;6491.57641395909;6545.3670276775 1999-05-15,10466.9;10559.7;11107.2,6297.77376654633;6353.61010830325;6683.03249097473 1999-06-15,10490.5;10970.8;10970.8,6311.97352587244;6600.96269554753;6600.96269554753 1999-07-15,10655.2;10655.2;11209.8,6391.84163167367;6391.84163167367;6724.5350929814 1999-08-15,10646.0;10829.3;11326.0,6371.03530819868;6480.73010173549;6777.97725912627 1999-09-15,10213.5;10337.0;11079.4,6083.08516974389;6156.64085765336;6598.8088147707 1999-10-15,10019.7;10729.9;10729.9,5957.01545778835;6379.25089179548;6379.25089179548 1999-11-15,10581.8;10877.8;11089.5,6287.46286393345;6463.33927510398;6589.12655971479 1999-12-15,10998.4;11497.1;11497.1,6534.99702911468;6831.31313131313;6831.31313131313 2000-01-15,10738.9;10940.5;11723.0,6361.90758293839;6481.33886255924;6944.90521327014 2000-02-15,9862.12;10128.3;11041.1,5808.0800942285;5964.84098939929;6502.41460541814 2000-03-15,9796.03;10921.9;11119.9,5721.98014018692;6379.61448598131;6495.26869158879 2000-04-15,10305.8;10733.9;11287.1,6016.22883829539;6266.14127262113;6589.08347927612 2000-05-15,10299.2;10522.3;10934.6,6005.36443148688;6135.45189504373;6375.86005830904 2000-06-15,10376.1;10447.9;10815.3,6018.61948955917;6060.26682134571;6273.37587006961 2000-07-15,10481.5;10522.0;10843.9,6065.68287037037;6089.12037037037;6275.40509259259 2000-08-15,10607.0;11215.1;11252.8,6138.31018518518;6490.21990740741;6512.03703703704 2000-09-15,10628.4;10650.9;11310.6,6118.82556131261;6131.77892918826;6511.57167530225 2000-10-15,9975.02;10971.1;10971.1,5732.77011494253;6305.22988505747;6305.22988505747 2000-11-15,10399.3;10414.5;10977.2,5973.1763354394;5981.90695002872;6305.11200459506 2000-12-15,10318.9;10788.0;10898.7,5930.40229885057;6200;6263.62068965517 2001-01-15,10525.4;10887.4;10945.8,6011.0793832096;6217.81838949172;6251.17075956596 2001-02-15,10441.9;10495.3;10983.6,5939.64732650739;5970.02275312855;6247.78156996587 2001-03-15,9389.48;9878.78;10858.3,5328.876276958;5606.57207718502;6162.48581157775 2001-04-15,9485.71;10735.0;10810.1,5362.18767665348;6068.40022611645;6110.85358959864 2001-05-15,10796.7;10911.9;11337.9,6075.80191333709;6140.63027574564;6380.36015756894 2001-06-15,10434.8;10502.4;11175.8,5862.24719101124;5900.22471910112;6278.5393258427 2001-07-15,10175.6;10522.8;10610.0,5732.7323943662;5928.33802816901;5977.46478873239 2001-08-15,9919.58;9949.75;10551.2,5588.49577464789;5605.49295774648;5944.33802816902 2001-09-15,8235.81;8847.56;10033.3,4619.07459338194;4962.17610768368;5627.20134604599 2001-10-15,8836.83;9075.14;9545.17,4972.89251547552;5107.0005627462;5371.50815981992 2001-11-15,9263.90;9851.56;9982.75,5222.04058624577;5553.30326944758;5627.25479143179 2001-12-15,9763.96;10021.6;10137.0,5525.727221279;5671.53367289191;5736.84210526316 2002-01-15,9618.24;9920.00;10259.7,5430.96555618295;5601.35516657256;5793.16770186335 2002-02-15,9625.44;10106.1;10145.7,5413.63329583802;5683.97075365579;5706.2429696288 2002-03-15,10281.7;10403.9;10635.3,5750.39149888143;5818.73601789709;5948.15436241611 2002-04-15,9819.87;9946.22;10381.7,5461.55172413793;5531.82424916574;5774.02669632926 2002-05-15,9808.04;9925.25;10353.1,5454.97219132369;5520.16129032258;5758.12013348165 2002-06-15,9120.11;9243.26;9796.80,5069.54419121734;5137.99888827126;5445.69205113952 2002-07-15,7702.34;8736.59;9379.50,4276.70183231538;4850.96612992782;5207.94003331483 2002-08-15,8043.63;8663.50;9053.64,4451.37244050913;4794.41062534588;5010.31543995573 2002-09-15,7591.93;7591.93;8602.61,4194.4364640884;4194.4364640884;4752.82320441989 2002-10-15,7286.27;8397.03;8538.24,4018.90237175951;4631.56646442361;4709.45394373966 2002-11-15,8358.95;8896.09;8931.68,4610.56260341975;4906.83397683398;4926.46442360728 2002-12-15,8303.78;8341.63;8862.57,4590.25981205086;4611.18297401879;4899.15422885572 2003-01-15,7945.13;8053.81;8842.62,4372.66373142543;4432.47660979637;4866.60429279031 2003-02-15,7749.87;7891.08;8109.82,4232.58874931731;4309.71054068815;4429.17531403605 2003-03-15,7524.06;7992.13;8521.97,4084.72312703583;4338.83279044517;4626.4766558089 2003-04-15,8069.86;8480.09;8515.66,4390.56583242655;4613.75952121872;4633.11207834603 2003-05-15,8454.25;8850.26;8850.26,4607.22070844687;4823.02997275204;4823.02997275204 2003-06-15,8897.81;8985.44;9323.02,4843.66358192706;4891.36635819271;5075.13336962439 2003-07-15,9036.04;9233.80;9284.57,4913.56171832518;5021.09842305601;5048.70581837955 2003-08-15,9036.32;9415.82;9428.90,4895.0812567714;5100.66088840737;5107.74647887324 2003-09-15,9275.06;9275.06;9659.13,5008.13174946004;5008.13174946004;5215.51295896328 2003-10-15,9469.20;9801.12;9812.98,5118.48648648649;5297.9027027027;5304.31351351351 2003-11-15,9619.42;9782.46;9858.46,5213.77777777778;5302.14634146341;5343.33875338753 2003-12-15,9853.64;10453.9;10453.9,5346.52197504069;5672.21920781335;5672.21920781335 2004-01-15,10409.9;10488.1;10702.5,5620.89632829374;5663.12095032397;5778.88768898488 2004-02-15,10470.7;10583.9;10737.7,5623.3619763695;5684.15682062299;5766.75617615467 2004-03-15,10048.2;10357.7;10678.1,5361.89967982924;5527.05442902882;5698.02561366062 2004-04-15,10225.6;10225.6;10570.8,5439.14893617021;5439.14893617021;5622.76595744681 2004-05-15,9906.91;10188.5;10317.2,5238.97937599154;5387.89000528821;5455.94923320994 2004-06-15,10195.9;10435.5;10479.6,5374.7496046389;5501.05429625725;5524.30152872957 2004-07-15,9961.92;10139.7;10334.2,5259.72544878564;5353.59028511088;5456.28299894403 2004-08-15,9814.59;10173.9;10195.0,5179.20316622691;5368.81266490765;5379.94722955145 2004-09-15,9988.54;10080.3;10341.2,5259.89468141127;5308.21484992101;5445.60294892048 2004-10-15,9749.99;10027.5;10239.9,5107.38082765846;5252.75013095862;5364.01257202724 2004-11-15,10035.7;10428.0;10572.6,5254.29319371728;5459.68586387435;5535.39267015707 2004-12-15,10440.6;10783.0;10854.5,5486.38991066737;5666.31634261692;5703.88859695218 2005-01-15,10368.6;10489.9;10729.4,5437.1263765076;5500.73413738857;5626.32406921867 2005-02-15,10551.9;10766.2;10841.6,5501.51199165798;5613.24296141814;5652.55474452555 2005-03-15,10405.7;10503.8;10940.6,5383.1867563373;5433.93688566994;5659.90688049664 2005-04-15,10012.4;10192.5;10546.3,5145.11819116136;5237.66700924974;5419.47584789311 2005-05-15,10140.1;10467.5;10542.6,5216.10082304527;5384.51646090535;5423.14814814815 2005-06-15,10275.0;10275.0;10623.1,5282.7763496144;5282.7763496144;5461.74807197943 2005-07-15,10270.7;10640.9;10705.6,5256.24360286592;5445.7011258956;5478.81269191402 2005-08-15,10397.3;10481.6;10697.6,5293.94093686354;5336.86354378819;5446.84317718941 2005-09-15,10378.0;10568.7;10682.9,5220.32193158954;5316.24748490946;5373.6921529175 2005-10-15,10215.2;10440.1;10535.5,5128.1124497992;5241.0140562249;5288.90562248996 2005-11-15,10406.8;10805.9;10931.6,5266.5991902834;5468.57287449393;5532.18623481781 2005-12-15,10717.5;10717.5;10912.6,5445.88414634146;5445.88414634146;5545.02032520325 2006-01-15,10667.4;10864.9;11043.4,5379.42511346445;5479.02168431669;5569.03681290973 2006-02-15,10749.8;10993.4;11137.2,5410.06542526422;5532.66230498239;5605.03271263211 2006-03-15,10958.6;11109.3;11317.4,5484.78478478478;5560.21021021021;5664.36436436436 2006-04-15,11073.8;11367.1;11382.5,5495.68238213399;5641.24069478908;5648.88337468983 2006-05-15,11094.4;11168.3;11642.7,5478.71604938272;5515.20987654321;5749.48148148148 2006-06-15,10706.1;11150.2;11260.3,5276.54016757023;5495.41646131099;5549.67964514539 2006-07-15,10739.4;11185.7;11228.0,5277.34643734644;5496.65847665848;5517.44471744472 2006-08-15,11076.2;11381.2;11382.9,5432.17263364394;5581.75576262874;5582.58950465915 2006-09-15,11331.4;11679.1;11718.5,5584.7215377033;5756.08674223756;5775.50517496304 2006-10-15,11670.4;12080.7;12163.7,5783.15163528246;5986.47175421209;6027.60158572844 2006-11-15,11986.0;12221.9;12342.6,5948.38709677419;6065.45905707196;6125.35980148883 2006-12-15,12194.1;12463.2;12510.6,6042.66600594648;6176.01585728444;6199.50445986125 2007-01-15,12398.0;12621.7;12621.8,6125.00988064185;6235.52485969489;6235.57426290412 2007-02-15,12216.2;12268.6;12786.6,6003.07618219254;6028.82569447516;6283.37239986437 2007-03-15,12050.4;12354.4;12481.0,5868.16782889867;6016.20631890607;6077.85655849468 2007-04-15,12382.3;13062.9;13120.9,5990.87504717301;6320.16682310364;6348.22871408804 2007-05-15,13136.1;13627.6;13633.1,6316.9815675959;6553.33759719931;6555.9824764726 2007-06-15,13266.7;13408.6;13676.3,6367.44547688527;6435.55137459684;6564.03586238673 2007-07-15,13212.0;13212.0;14000.4,6342.8052943125;6342.8052943125;6721.29967018565 2007-08-15,12845.8;13357.7;13657.9,6178.3307762232;6424.53479032499;6568.91932838585 2007-09-15,13113.4;13895.6;13912.9,6289.70214398772;6664.87601323804;6673.17377332246 2007-10-15,13522.0;13930.0;14164.5,6471.83826626335;6667.11337443045;6779.34870008041 2007-11-15,12743.4;13371.7;13660.9,6063.17532365578;6362.11383738468;6499.71214738054 2007-12-15,13167.2;13264.8;13727.0,6269.02054885829;6315.48877335314;6535.54628730313 2008-01-15,11971.2;12650.4;13056.7,5671.40420693576;5993.17794201251;6185.66420314573 2008-02-15,12182.1;12266.4;12743.2,5754.60690717218;5794.42872461536;6019.66054616827 2008-03-15,11740.2;12262.9;12548.6,5498.20164100259;5742.99389307257;5876.79367577086 2008-04-15,12302.1;12820.1;12891.9,5726.62145114815;5967.75019434604;6001.17305875069 2008-05-15,12479.6;12638.3;13058.2,5760.73710255179;5833.99497765796;6027.82599062004 2008-06-15,11346.5;11350.0;12604.5,5185.430614903;5187.03013961566;5760.34549733793 2008-07-15,10962.5;11378.0;11632.4,4983.77007146624;5172.66461784656;5288.31990689385 2008-08-15,11284.2;11543.6;11782.4,5150.58013748026;5268.98113069753;5377.97942360534 2008-09-15,10365.5;10850.7;11532.9,4737.79955481002;4959.57181316647;5271.387630666 2008-10-15,8175.77;9325.01;10831.1,3775.06429702687;4305.71216171914;5001.13125828243 2008-11-15,7552.29;8829.04;9625.28,3555.27362598564;4156.30928563022;4531.14275626692 2008-12-15,8149.09;8776.39;8934.18,3876.31048195293;4174.70080103507;4249.75740624465 2009-01-15,7949.09;8000.86;9034.69,3764.78973965512;3789.30866758548;4278.94365430064 2009-02-15,7062.93;7062.93;8280.59,3328.54052678458;3328.54052678458;3902.38603535461 2009-03-15,6547.05;7608.92;7924.56,3077.93746385908;3577.15000305582;3725.54052719913 2009-04-15,7761.60;8168.12;8185.73,3639.84243106359;3830.48208591259;3838.74038641906 2009-05-15,8212.41;8500.33;8574.65,3840.15879844381;3974.79144845129;4009.54380517732 2009-06-15,8299.86;8447.00;8799.26,3847.99692155054;3916.21424895569;4079.52970193748 2009-07-15,8146.52;9171.61;9171.61,3782.90326025883;4258.91219451036;4258.91219451036 2009-08-15,9135.34;9496.28;9580.63,4232.5768877934;4399.8072592826;4438.88821965029 2009-09-15,9280.67;9712.28;9829.87,4297.2232125907;4497.07133894216;4551.51896800004 2009-10-15,9487.67;9712.73;10092.2,4388.84340147194;4492.9525342659;4668.48924723722 dygraphs-2.2.1/docs/extras012077700000000000000000000000001437353256000146502dist/extrasustar00dygraphs-2.2.1/docs/favicon.ico010064400000000000000000000042761437353256000133560ustar00 ( @IS]gr~Bh7 M=X>FV:DWd =fs`T.ISKOM_{_/{RhValwj5jMxu1hTai]ekeluKyM?9o4*8o=AJz'pywi4,uw8RjfhA!C}z2IRm{zJNV~LjSWv_ldpcaʃhoՍwaq~uϑmvorauqpܗolʡޢѧtzߪȩ۪`ŀ˯魷ᱱ۰ص跷ţŬǧɢƨɢՋϜܾؾDZͳѪլקҳܡٲڱٷ߹Ưÿ 뺛u^ҽƗ@ElbO:輁yaoI%7>]pLGSR~n2B;#=hʫ4,8sQ̓[d_}jq1FDt̩̆d 2$\9'f؊wskTUz!x5&o  rN?CX⪜$Yi/*רr?3APPj 6ʦO[-K|~HxJDb=ZV|m)"ace0<.{ ({@E`vgWM+gܿƽlиvȟҥÿdygraphs-2.2.1/docs/footer.html010064400000000000000000000004711437353256000134120ustar00 dygraphs-2.2.1/docs/hairlines-annotations.pdf010064400000000000000000003670111437353256000162400ustar00%PDF-1.6 %äüöß 2 0 obj <> stream xXK6Wx]F݅@p[wcNzc˒f曙oF# bn4J; N ~;߷?0 ֨@ : 8O'!Gy|T'pYg6ϏsWB]9d@G7ن;p/e|> p[dҤ<`(e9&@T*HYm<|'h ".=Ga5fK._p{-?+-lU %NEfkM\]Z*Oɺ ܝM R"洓)0W`zVOqܾ 0يh!*4UˮŮFXٖuLW_p?f5 &dNq@zʺc"PBIZDZ _M  H =>ǟ=e F4%< ZnT}XE xn.jW)TYIjYOԠvW2 ܕwB]aRzKWސ8-0gr%=\t9JRȬͱ&6R/ ؊h;zR' O1gp6aE OمhRDR#;230̪TTWC%=kjC1ZbU#6H4a5+BOѪ0Xzh+`wOx&V]h8+$\P38;fIDٝ,Ue|Md̽M!dSCQtѨ+&%>8oVK]8 /WB7Ţw }VƐ{K!l/IZl&z Rvc72 QXL(]z"?q0B$쮌3yԶSh!k1zFEՌJBCNW~nl<[nJ4̸74UZgN90%19keZՅتjĥHkJUfEցؗ?V`ľgHr'{d/C*>3|_0T NLZfN5HDrf^\ȳeY`U1Yi54mJ5 3Ui6Y*=e k~MAբԍ1S;k skLKQqm4Ed!X)p|SWv,74'IVY_KGO~^"#jγM-15V|:!Z@Ek[`cA[z+csTEddl 7ΠNYR endstream endobj 3 0 obj 1371 endobj 4 0 obj <> stream x |}ŢU_R-֪U.-ڪU d#BBBCldf2w{Ιy~̝9s~y̝9GAAAuuh@sְX䫺o߾N"2Iɂu/0OBkXhr“'Or:)ϩpC~y XBc=cLT!L[ְ>oְWu``uF_H' _̙3z V@3 `x~QTT{]ǎS}~ hrl_B< ̞=`?@ D~1888k,kpsso111F`?@ i@chOȿp Q ~`Ke/X߰Z[[gΜy1::[7cƌKjooW˅2h6T2Ҕ8[8yd&&&)I&Niu}~ hrTilVDDwR줤$-e Y_UU%0{l\xXR`` gqz0 ;<{lnn.~zllJ#SQQhѢ;v Ⱦa/AU&ǺSm۶qy'ɓ'Io/ݕ-6䲅Qgg;Ii=CCFbݺurrr2qV+$hkkSLZ AT߻M_;g xzz}}},יoŋJ$rrrO%9 ˤ?yg}݉ Ft47 :IWZ&=jCC1//XMv Q6x?Ϥ'x"22RQ@.\Hiiiȷ -*7(ME+%q@[[I)nj_` MM Ι3T^NLL\?I`` )aɒ%۷ogoݤ8{c1c`~A]]}FG_Ȓ&Buu59IR6yL$ƔcF/A&/lE.5L~ѣG_}ՁH~urrH$YK'+7lGI#˗/_f 'n#.}/._-^tΎR:y<)** S]d3yh[/XM*#w*䋑SN*RJJH_@֐D{/yʃ[: ? |DJy{Q$##C~ܹqqqW%`y` +Аw,!'G"70…Ϭ\V^s}y>J 6G@z)OlA`49`_O6 0fo=/ B/kXhrqF;vڵWu(/A&JlwMJҧ2_BkXhrDK>޽:T P_` Mhi:?a~ }a~ }+@%'$靽{sw'neb"9+s_x8Y0%ej\Pݻɧt ysmRׁO'&r%¤ybs'ɸ<Xļ g;7b%VVhxQMd=?:~2!!~\Ny` +QqN]|(@'OF?noҫ@i7MY_!x~\NF ~-&GtkWOcc&&ș}9 !""M~~m`JZZy[/6oՖ-o#ߝ<9%-ٽ{ptT7']7mY}үsN_+OdT@؂~ hrǎɷ/twW]ͮ%9d̀˩_@Vn*)!_s 瓖;0PȩNL,$!EE&Kpp{lll*']U?md sG/L' s9_޳G;?֭GsEU? )/$'寭^{PѣO~F&>>^tysO/>q~[iM' 6rB+ɱn/9ídͯl!bVg J?pext&B;eeZZZ/_L}8Oְ`?@COO}Ɵ>=oF&y` +~gy6^ph[/XM.CN뮼v~.? &G+Ɂ6lȨ} ^hNܱj{_@F09*/XMp8X_0PKVlIS_@F09*/XMpmzBPKUTwr &G+Ɂ ==7yy ;%BЂzۣ_@F09*/XMdps.~嫀}{ &G+Ɂ_]ds3U@ <=\Ѹ=dӧs*&Bc͟wx &G+Ɂܱûm$>x &g``ś&_@L|%(6@ |9yyy3fXtYۭ7pssneSS+ſIMeXS@ c>Gv / \NMM xψgUmSQQhѢ;v$%%Z~~ hr@CѻB/82帻ϟ?ll,777XMB.=l=ڍ~ hrűo*fJZZjS xbOOOyypp\_Xn 믿{=d V@/޳gy^\Zh$D{_@y]}LQ0JKK,YBr㭷ޚP>rsso111FXP-+W&ͪ4)ٿf/D;Vec8'/fΜ٩ V@""Zh]pV^_rp7eHKK}[['O&$$=ˤ(-tP_@珏nbg==== V@/;:n/hDEe;g2֒dyڣGEEE}}}TU&_|Ҭ,r(mz Cj8c fjhhWo=eddG͝;7j,_wua~͐/hspݻT/ c 9ݪ=>'[n3hX@c_dOTe!L,JM8vL2Q1g5Grh5GRhNfeĨV_@F09*~G?6$P&Xf-4NqgPkD`rT|ᇳOqY&X>y[iJ`8Ż\;dſ5,49ORiJ`8֬\~ ~QzW?` M~Q% 8=55ʟL 7mʨЂ5,49<](@ =tO &GǏkXhr CChq<={?/ #_ ]90kXhrܺ[Gsh@ uℼ~PfWkXhr?0͡-4.RV/ #_WT<kXhr[}|.tu9BN,~䨰opf@@uGna1/JKBٹӧ@B`rTX[wۛ5,49ůnxqy-4"#% &G_xkvhFOZh\/ #/[ŋZ&G+.9_@ |޽L _x翽g>a/oܸ栜)kmʪUL _T}YkXhrt;V9"?l蓚ʰ@ &G|dVVphby^2HcX-44=GF &G_uZ&Gg^/_-449<6ƪ@ &G_rŊA[E&GO(t>sh䡐3gX~䨰 ¯l+)}h=bYV{~>-4̖-JC``rTӧ.!!XB_Tz휡JrrnjgR[¦_ }˂5,49EֹsEG7BC JZZF_E09*lmŠZ&G7}ffБ#4 PBdTV>i/Mn0<5hr񋉉[||.7BC,vyZJ/M22f/_9\-}" *Y_v=t4 3>1Yoo:7w?hrO9@ %3-$>@~w'Ogh:ww?́tZh(Q >LY[@y~ LO(!a&G Br2QZh(Q伛!4 tα_0IH{iX&@_|-gEENQCýaa_N e: & i8 hrxEK_ߧWa7?K9^DS[q:/$ i8 hrxEXQ[z:!tZh(q^߳iJC` 9tn|mSr^Ұ&_/Dӹ)BNH{iXV@/4 P{_vt4 @tN_kCChX@/FG=w|=JTrj::f\[ ,,|cv%k ʄe49+P Pb-gƍ;]+ -;UUl aM?:po{3BCG]V-)omw:/V983 oX@/yuZh(sJC`rV<ݜ {z>І7,[gӔ?BCM9mp4 .s;bc]JH{ްl&_|?^hĦǎBil1V閖oYï|~^\p"M-4ؔ,l1VrnWhϬ\961И'\ōkBC=9O%$$:[[ݵk<'X+<⽜srh5hĞ'8[[ snΧsN'^r>!y~A$M-4ؓ98xgȈS_@9\=ΧsNE{Ly~As)(rIJ?}ک/b{BC)

r/69Kg+ U*ڨkdBCɔr>bEk/آum5ӳ][B+"ܹGEa@ %Z&551-_E9_]UcGjwPENJYقx-_Eg9uuۢ~tS 4< +R@@u{;PEͽSn~u.=[t/F?b>y&~unF-4hmk5ܒB`rXR9rZZ.y&~;fT#ÀJ4I+/<.n/آڹѺN Hmy&~ENF52 hD>>W8-zEʕ-ޒ< _쮩ƍc BCv9MO+.v ['ᕴ4-HmyzxcǎUc BCv9?[tUS7$jH+/B=R`p/WgQP├[}}/8--Rń$}ۻIc{`pIMFu1h)9gd9-əp\^^ތ3.]:k֬k ۶mS# W(GF>rUeZh(qJNfe壛69-SS3σ555$%rDDٳG-&m?uaGP\yUeZh(qV|}-yc@>-ϟ?ll,777(/4L؞5'\WU=f  P⬜řk)[s;/^)/닜S77,x+>ٽbbb hrH?OFD°2-48+gej*q{ yt\'5uڷn### OEFecߒrJ|ɡ9ooҞ= +c,BC rnww/آ?l~bpE'owcp~ MɡA$-Kr~dv7[t]]Frww̙3Ϟ=|/WU= O/@CHZ$W_g-8~zyZyZ;|l_}:D"*m~r>T7DŒ3g͚`C ^'\qPF&Hʱ7z$[ɬt6k wr%~ک$0OB?011YooylhrhID!l*g]x/_= W\,G&HrlP~rnq+Z5 ZVUZܒB` '9-s4ByҟolD"QY{wA'[xʕ.Mc~;}Gj8uhrhID-'(O \Z >y./"!y荞+ɡA$- 9_ *km/CoRSYh <𕠠6`pE{?̙_&&VBCHZ$r޳y- }˫m/޽A`pE{?BrST+ɡA$- 9.`:y-l9{Vۢph)<~odz!zhrhIH7֬9s~rD<ɶL^]zM0O;}-[CCHZ$FreeKa+g`t _mϫHmys[ƖX&H?)_3gm@Ł 7<ኖ~{hza돠ɡA$-;9 9܌~r~uko/O57+Z 'Ol#hrhIN?wn.[9~apEK?͛-&sD"s;aala(\YsUiL{Šm~y•)jShrhITޏb+97yy 93 ~An{996?<ʔS} M "i߻Jκ?3 ~q!!6?<~ʕz{OD"C򤠾a΂ѱɂ2&E1__~a3!1O/ڰ! _T)0aNQt(<=(omJPДCCHZ$~Aax(::9&a>~Aƍ-`pŞ_x8=SM "iŚGX`t,x:guBbpŞ_|/2 SM "iť[||%OB7NHMM߲ hrhI/SxA c9/$(0Ob/>׾}Zv&H¨(0: OBOP%$ Wl=aa Zv&HF/(h?3 =B'\C/oxy _ٲ%ikt( 3IIJBbp/fekD" $Pk#ӹa~2}9 WΠVCCHZ$~18:zWk_#={<4~14:+.O&$ WT~hCCHZ$~Ax%--aSёcժrͧsF_^ݶ-l2!1Ogf9}whrhI/2*+eX`tkjmȼ2/2++0O8cLLLsG _n?~ڙ9/3j::0Obl--] _eNp Fg|b Μ!~ݻ 0Ob}SCCHZ$~_$>21/Ο~d$ W~{h:w!v&H0+8ds3ã8֭OPo:ƻk@K{:qdgw&Hſ{?'Q0ͣ58x'O(  94'8rΏ#M "it:d4ͣĉRR8U9Fڽ꜃-~VƩ&HyVs(`G8QXc_]j_mQGgÇc CCHZ$С?@ѩ?0@s\ Z׭˭sawhrhI_uv~ח1]P]*b=OLU@K{܅&HzቸRDztc~A-gU@K{AnkCCHZ$"Ymt^tOh(0XTR'sZړ~k֜niqmwhrhI_ãc`ᬙygޏxօ='Wkv=:3 hrhI_^NK[{Y3mU/c"hiO̠ɡA$-~ʏ?1À* UUxqTڏ~@ɡD$-~Ar``e{;#q۶-ԄɆ4y]Vj2k@}S%H߲6 309Cdgx*y3HZ$ȥK1Yex Ht]1yʖᱱϬ\x办d``EAŲ, !NL;}Z^FPId_xp>TR̖-zV&yyy3fXtYm?11v$d˦&#P~qzBw->>c "i 81}x\\rYD,GDD̞={ttTMEEŢEvؑ{ԠPtI-:pr. HE`aLGMtھseqww?$%VK6[? @ǎ-JMտ2tIBA$-~wާIt>GvΕfŞ ɱ@6_1KDLL]}#}^CM9៖vUkHi 4V[DҲge734Luۏ̛7/h`_Y WZZdoքŔЮ/gdQL`۶mGZ H^_WV`:D'MUA_}h_(DEE͜9SY/rJi90_Vf_(E2/wxfn5edI'/\0ல_۱ _Yz LW~  _|,V>:W|zŊAǐt>>>|ٳgL6Q ?ءge10'**9J HFX>:~Yikkkrrr<ѣG_}ՁоɹUw`<qFцCCף_(Eg0ڵ.\026!51cYf5\x|)##C5wܸ8վ0B~pU;wBA$- H<{X l wwwV+ S$~h*C!i?2+lH~  _{ժV B s49P| h1$Ë_JIHZ$0~~NNsoW [ wA!iEPI/*n)(K7 ESoM^^&qg`}lㅢ_(E_&&O'pt ;|񢁕ad &55$wOřBA$-$H)+{eQj~p{=]`ӾkpWiBA$-$ |m4%#M4r>sگ>rm6_(E彽gM BFfoo|,,|-#we9DD9BA$-0=49kZLF# {hFOO'*6 "i԰bw2:wM~AΝ\+Ci\RDPI/~/2ŋN\Iɓ FW MM_[g][Wb_(EhJċÛ6]./?O`4'tKdgk߾)7CPI/"~V,: 10_$=bUZ'G?[1 "i@}}55EɈC/o<>iq ZDPI/fgSe5"EzwwL"a")(}f&УOp:멑l~  _T769qS>m]D~Gso/PC響>>HZ$~A֭ksv/ի>D~A]q tH_m٢}x Hb_mk:0x7oFM_ QRC''j/D" O6nLsQRa;cs49O+ˤ'ؾ漫@PI/69դώ#:QŐcEn]}'}0ypʍ^^N=w~  _RP_}{1{bȱ11)eel+C-=:~g^OqIBaUe'Q'NbfvDPId&_ 7W˖Nk_ߍ^^S˙Eï8o8w׮ͮuaG H&WrKSG߹o^;Lybxlv3/%<$䧮~ $~A}fSnf\wU{Jтyuyɏ֯^Uھ "igZZ7<[?散Sŕaﮍy\V6owGPId mÎ1itFo?ҢZoR91_HzC`',)^KDPId*88g&OM*G#f־OXm8: H ”B1:ˁ Qv};h`'_8*/D"/76 +ߦȌr#_ Wbu#QgIlIɣ_(E2_^p=4tO'3M') CN qr SSCYHZ$Eykg;t 7;wtrB 3(%V+9ǎ"1 H& ›vc#sEOϺN{KR&%1,YXɹk&C)_(E2_\ãYYEYc.9"_ um &0XXCBA$-9Bݻכ(:U7yy:Dr\@$;}7J{ HojR7Qt^ٶkNL$D Occ?μX-!g_g  H šGejY][)73/\pgP8󒧄R9ϯ3$.~ ~Auy6ĝ>=ffaqf濵 J9O'&/V/,IdrHaYƑM!eOxx?BԲGF2~aHZ$٤U#؝AA縄/yS)!g\伋ae$ D"/N57 "i wkjydӦcǴo\%;v0|D\#Yɠ_(E/ɷ ֧+uLJ,TLOx2![BJII1Rкe˖X;vfME%JSˑ1c?`OIhDz%-O=եKH;ʟ˶8䐴!W60r.G-X &&e?)/3:r.j*վx"p-95B{r!99Y 7o޵kfƌMMMw}795SCN!HVǓM97L-ą!Z9BT1͕ݻmVɎM!Gӌ=9fHD)'o>r٩lo'4,各3goooʑ&O}]lii4y{Dr6n"-+Z9f$ 9p=˗wBHuV5/HΧoܸѤrIEnnn=s綷+:׿lBlV\,\iH5,G"'x#x͛Gx㍜UΚ5k``ZRN?ɓ, "Ȥro6Y8tʕ+eQCʏ/6]RRb-ҵrl4Qhkk.@?`)G~=RyyXҞ[lADDO?zrFGG|0ҤrH%-Z(Doll4Ç/]T^rʙ3gl -Rs65BM444CLL͆ȐC_ZreG\Xy *9w)׬Y/љT9{we"G>/|7wر짞zfkѮ6k|j~ ֭#H7 d5kOX> {re˖+nrw%K,XE/ԙ|@9E#)Wss䘴0/===*O1 CCCCjeҌrlVۤZr X?          endstream endobj 5 0 obj 20920 endobj 7 0 obj <> stream xZK+' ޟ_ut)I*uH]T!uQwӿ_HNM?>Q'8AI0,IOapG8|퇯60Mws8|EYw.(jP#~NjFRjq nZpֆf7=gyijQ~wepReRFss/{p ~Ehز{}HhGy[WT0W?'Dh㻾;A]vGolp_* J~,g/c;?+QJ _qBq%00\5gz DG/tad!u+5ҴPRjL#ɦnK-Q;[6o3. iL_JZ7ct6kLENk@W+6"[MvC!g%Dbe/dD4N4/ښrfsPb\#O-~Z7EdHM)Կ XBxc X֋Hp 5rˡIØ[P$CkryXB %a4'[PH5q#W}ѝ'YNܮadA'>)0R(hAq߲zg!-MuØ>9|A5fԅy͜ժfR..$H帘:;N~A3'_fCfỡdaD6#D(d^II$llXϗZyE2:!$֑䤆W2'7׿b5\NB٣̱]h"q)%m4s@48{stSx25UtJm2*-Ȭ<;6Ao<Ҕ^)dR Ռ`W^knUCDl ,L.u)1i>h>K7cC^Q,kϩ%mv):k'!DksUcI7,)L,y *O6dh^ACSO{ c%g#mJ4:C%IEӌl1'%Tu59wMNOiN˦6'RALs޻ H)z~?l&ۏN[({*|4"߼eq6#w6?QHj0V[M39QQk_ /( URK#Zҟ A6{' mX|ME.UuS@3#`5sR[t'/O4h4-=.y)Hֶq:X(92&HdQՃ^;:j{١D q>NkڑpLgL{{XIR"g^;n8+O rbX={O Sk}Uc 9åX f<Ͳ+]hNZl=8pj<:'SJa޳4SEÒWs$'(d 9fɩL&=l=F*-;} /j%;ePtNt10MuvR70G@jpYm|N/\d}5ަh7j ?웃Fm:l4uըc5x1m|خTc'- /h7$^NC.4 ^lEQ6VHԓk^[H&M-guǨ$fY~pתLmGǢNFBKWȄ F7YMm4VL᷒U?f^7-^Opĥ|ӽWmQ'H\ȵQgM&llS=<;qmN MVw 1yZ r&j#(Iq^齿 l9̠[;pĠNay恼zWЩ^%P~orËj&z4.!P@ql endstream endobj 8 0 obj 2351 endobj 10 0 obj <> stream xZ˪6߯u:zۆG Y2~JR,ٞ;3iڒ,ԩE\ th//?w;?X7Lq44͗.?E|*??0W*,!ER_M3oO:p!Êw]\LWUp9ْΑۏ/xΟ~xVO`!?׬|~ZaQLb"\CLrF׷3ZE[FGN߮=)Yl8v5s%yKCu!LcAeKz)fɣ R'O$tn FӒŰä!G˃I(2>Vy9U <\p4C\?*ܴ qJ#Fz^L̞آ4yNsGF7 !"4G]> ow~[_@v6߮jp0҂$\ygUȐ:krK3$B98xiJ١( =z?:.xߑߑX+G;3 Ƃr%Jʼ) c ڔ,)'drz`wP"nc / ƫvsUaNZe 0cB>1j3T ok w]S7PW(Uxb۫ ֕9i.dڍ-X<<$a 9ҊmsBfxX@ZKY]u ˜#/R%N-q8>ڸ9j10{Y+ŗmd8N/8)_ 8qxɾiw 8aҎahVcy}$Ok;ZZS(8S\j50r#/aCH5J%F#p030y0^ސNO} 90oW.̟}5q:pze/mKrC0WmgUTF*xQ ò@j-Y V-Wh'{ z_s_%WczJ+Rbj$]v$ĨIRf7,w%qVG's aֻ(f.$+ܷe CYQm$\K0.VSUkgpq&}+C ujE[u1H"F]'`@K*m;iJg3.e_ƢmVwX^$OG}u҇b@*q,Ş5f*m2g1/ֱ676NjAwIP=׫p-{~5<2^8;a]R \%HTMxEWGyD*~ςD@QU.Ӫg+OSM;|+TI\=vnG$-6B)%륵ôfA@ D@4fK"k;k 9 #=[ ׅi{9@q3)=Mv(fq\WlxE1IQ-A-C*R(yvVM]Vi aF<`F>k5kwؾeb]I_}uCo W}P-T|FY˺,[SA533%a.%厾cPVIfD+1Pڧwӂ8n,2\N;G=[3ƛ<^y<, d3d2y)C~#vWUNs/#\ endstream endobj 11 0 obj 2227 endobj 13 0 obj <> stream xXK6W\ /!ZE{@AOMZH.#Vt "9fHnH}=|VZFNz9j@˧_BϗCө6؆ջ)ۇQOd=n{:iÐVBHV5j*]w8j6쳣jWH!<\\EqEq议E`@lU]ƃ?@ endstream endobj 14 0 obj 1102 endobj 15 0 obj <> stream xxTǹIrMMB7q|\cmvb`l&! uԐPA %Ԑ@]HBꪬzGdپgOf̼39g           \G._,v~P&IԈ.4֠L5>]i"Aj$}P#L MN \)&IF5bPmKww hkP&Ih&+D޼@H`i"Aj$}P#c}ܡſZi>,XMa $}P#АENƿ)v~P&I4>i>\+X}hkP&IZv/YV-N³h"ANN!EChlGa%jɒ i"N&fff&''',LCr( e&uH*L|zɒ(! 4%tȳKrLm$%%@Di@ MڑdhqX&-T"@dd!9@&EH2 Eәn ௷[Ma E2A?<33蜗XY? i@ MM'K-eZ4M5ԔL&A b4 ȄH4M54???99SYY)A@@,E2A?<11} 4M 4 ȄH4M5Ʉ)q4Z@DXCLhM4M5Ʉ)q4Z@DXCLݻwu癞gIh-i"H&YІCr>4M(&kGV" 1͊ &effF4eR;K8::`%ikkGԨTtڵǏ;88C8<55522B JLF^~HY ÇIQ̕dADd(_M!Eʨ0i"H&CL3:: ̮ <1RiOa)iggW\\bŊ-[(0$$~>} ׷o߆c׬YF pr?pfooo'N ѻ&b,Cl:D}lK0i"H&CL,E=)_MHH EF R򚤃y0Z\r%$*Wm۶{I0{iii0$ċCCC`sssȑ#4IgΜ4Mhf,힭 Q hEEE*hk(˳dܰa M1xٻw/*gniia&&1.,& ҡj42)MaAK31ͧ|>@eMa E2;h||\ih+P=2* 0l}}}* hEHr<{mZ&!dYe\bťKRRR^C˶ 4e2ԛ/ A'IR7MryV4z)MaGqs<Z4s4MC(IiFDDI0)ĉ]uy%%%=== KH~+++_ᣣ{UB`P dtrwAa`d Ni={*++/\×'hKZբQf6h3]v]b?.dd߃/_ ILdW`NNNff&"##ULU@Ν#YFK&aG\xUf2GA-yĦ> Br666䵷7S> LJq!I@d=$ӎ\.^|)(G..䜇ϜM5@!0ܙZjm#Mso|_.+KmhR_S]]]n`0QL r\NOiGׯ?d`˗ZZ9˱s9tnԟb4\W~ĉ32d7 Չugj0dM>Xr连㏯\bȱ;v8v)%kYMM?vus--T9.nG͛Efhp+Ӯ 8T7.EEpvN3xM\% ixl~~>ZVVfQ֠4MkW@C2_niYB166MUI4&djx7SR:*+^dv?AZǯ1tr'>>~zz0 4dfmx;o7AGMMͮ]' BDXÉLڲPݎkqޜ ı4ܟQ'cED4M54<5S&94briJ2u3݋E;z-)qq\Dž&esGL,󫎎qj[(ciNMM' 077yjK眦&ï,4 ~?(1v獽p&ez5!V9[[25M0??)-a|t+y6ly;t0ϐ^:>>$vuumܸyTぁdD9ϋ2׮] N8v۶mw^u,4co~ݽ_לQADXcL>O6i@3ӧ#ba`7+>|-](Ik5ZWjK<<!6> -TK3o,'WRs&&2uv~Q[ oS_di&$$ԘNn[tC„!伨PD(=1+ TE4?fPPdHIIȀ()_ٷE'6d_Ԥ5oҊ喖ﺺrL .+#ﹹe55)'bghKnݪn1iii/i ~z\٧ 4Ʉ˗b0҄1333L)EN2w~TY4=JJ6Iww -b87hktiϝ)e";sF9ik6kKgZr%daa&~">Cz`FF̥Tc߿ٝ0}q6"K*>55566FfϪO2W҉7t⤻a& 6/k_ 4M5F?>S(f[LcA';?ڰa3 GOav$%ɴ?ǵ1UQB"1;V3I.4+%_f97mDL9Jݑ, B7yTm4w]xL_3᪻ۚ€(8(ʞ8:2hlV`,= .hK_P\RAgxyy-(MИ|ؒ%/`!^~:y8%55Fy@Ɂ˗/\ڪ*@ߒ Lu鈜Orm|!0wfU0K,hk[:pm xMa25ֶfjk6M={q͛V>(s.ԖxFe27n#+Ԗ^VVFQ_>C$(2ݝ8---w}ng$,Ξh:"W O!Lɪ<`\D!w9~UiF4.ӫ :?cmݤ3l]B\q=bT ̥].,R*[߯RYݑ+jR%jGuN6?L[kHNn??n @4(SIGǷNG/)]NUb93K4 RZBvw׻䄏&eQRw0ӵN&@D(DLsM^p59b 4"Sa{C ǂ7M LQ-Шyw80e{a\4"Ӌn^r/G۽hRƜLO;ݽh p&2Q87d4M)cN ix~>ʞo8;RMJ4M5zez5!ᄾ-<b ===hDi@IM3Q᩻s\ N4M5e_jo/Y|efZY(;(߀id{b_0߄TTuO]qGEN`i"-SS))|mCC_kiI_[[\.3f^k سFQQ~_ݙ{[:t{nn"b' HJ; !7O56R05~q%{Iɮ>SLo_%u|vL 6%^)Xeb!i5** a2c4''?em=01!v,[:u'1bG;ؔxe_Jɛ Q\M3!!eƐ4aYZ*v,[:(up1u0߻m&?+Fuqal@L3: c7ҩ@YQQ!7o 2D޺Rw32)å@&[:02u~Ύ%G&,k)BoZ#BjTCOO3KGft ƖNL%%{ Ё>%v,,iOy@)AEOσm#FãnRA4R@SiǍnca"""ͷpCQxB**Xo t*._@L"$eQ|mPQuz[I_Mꜯ&v܃M[RM|5黴XM3À-]IH{_>qn`SKwJWMfwW&\ddtu0`K29͏+~rL**-v\b~ ʞ>JW/ 0Mk^pA`K͚LSS{I1?D>><.F;bc޸!n 4GG2"DyxT)3K~iN[ FAvќ ]*)pt&144V`D?m3[ZT,e4bH`nvwӓіݦ48S A g0?UgBBĎU#!zK/\. mcg䵶  "D}}WZo\e:UTfJQpj$$Of55W4;PFRgr{zUZ*b/ P4Ꟙ++n0Њ80uvvdn6Wab"q,(8n25 |E(L5V%5/88Q(xd޽666$43'%ٳgIvl>:;D1ussSOV8;{'&{,A#>x;"Y__a꒚Fl RsfffݺuOV6MOox>::\эG4ݎ1%׆f ˖-TDmi "K&B SOYF?hg?1!Lu!N 3رÃILmd2)WS9"u#bK|)"W;Ң ш[V{*?$ћ))Ď/֯_???r9x[e LQ963)+ ٹ#bY24- س7nK,D#n9n/Xu(ٰ0 Ia3+^^^ccc *o2M`SxZ׌"$M"S͛;bcŎ%2RѭSLeXߒ݊Ag^UBL #SnKbGшP$Þ+N#,L3ww.}-Y#Cr݌ qcE7nn ˌlBYdN %d؝!XFLHH}c&s'y(=ݱrh9 ?#-Yw'vd213jrvV0(蓋Q]]bG!2fB]oL/lxcoXXL_sr+CptR_14T(h@Z1NLsdzE,248h!.sbG H7fd]*v4itvxJNLX:E.aYL>{ FD,\#ϯPwPy{XHLcc_ Egyj GE5A<)v@F낂ŎBL2+з^:̀""XLG; ]FڈzA ӥG/; 14/8?>IQtEf'&= ez=)ɫT(tiC:ѥQNsSgϊph0Zh|"a}e 6a6~yKK{>oggۄqh΅h|"ڱ{?+Ł(SK㾘6P#4 Q 4PW'vi^7'EQwQ͆FLj_ߒ jkv](v: JO; 4o89妗CYD/,2oqFs\\͛bG/ӈx[ZQaz9ԝELHcQ& &0FuTKyɓ_'5M2N,23Y琉2)vHLF/ĄK~9,4keM/ƳR֜9sݱ(>Z\)@F^'%8pkC52xшl|{{ևL99ǤUF!**Z( QjBOyxHۿd}8DȼsfP#uq 'P6 pn;bcM,ҳ:z(av~?llFD ;T]/1r3Asl˗7guĴ-eQ&_44P#^}eyR޸҅ bG!bwMYDrםL)ebrbGHgϝK;T;ߕ̪JB‡iKIq-.6J"󦔀21\mm}jgmlF$v!^~Y%{ӌCT)%{QkL)eR'N)oP#eCYR"vBÇi~є=(]P&e8CP#eIr/Hz5.?|i+2akz"ZhXnIƒiibG j;wĎBz5;25i<,7^6yʤLYg||ĎBԈa|fssb ǧS('Lp"*xBTaԈ!achQhj9dZu>`kɫyL*QU%v1zo^Z#WLXX"SL*x!UQ#i? ;\vR}Iﲲ׸Q&*{zVzy}F/ىfhbG!fzcX숍=^x(:ߖ "R+v8qQ91;)++vsh?$7OjM/eRgg\\͛bGoP#KL+kXW}R _ppfSi? ֣LIH;L|rWhH>9y;;>)WWnnf9EH@4=7~p-~, 4:e%yWӜ̌Y$YΑNB4"yQ7[3(>W69E҄ۥP&pD!WF _ 4"Kmyyge{EĹwLd1|@K|Qq3tillXY=onmy--O{EosUʤ==9/j!h(S̿<)mo`g749i!EeCWL؛m\AF玿&%ysX Ii++Qn P/s| `:N sX ʤЊĎ(z gY[WAff!/ZY)yJ 8gML>?K:N, ʤo:%v@FBg(e2YOOO7hlɓIIIb`7o~ʊkgWߕ -p#o `03FBKK߆; ʤG*Ŏz\p:1ob`*))ϻ?nk9j38[3G <`o/3f62-:xL:krgiQСtw3330j>ps841m(¥1i d0vnPpj8s13t*,w.2Q&zW1Ptw7n@D 8ǵ[]tÇWXR˜&UGQ3S)bn~66?q2f_Jkq1P&Lqɒ%QUfggw3ͭ1fS喖'_I(NFEi"4֯_?77p0Њ ]Lӥhjhqnٜ2{t@Fh;x2ͽ{ؐד0V?wuYtv60spp0XxŢLzy)DB̤Hm[VV)*ѫoaaD933nݺӧO?i2.8tװ/>mm=tM+P&LKs*,1*4;҄N>++bppP FH'Zu "0qpIooo򧸦BLLDU!9h鴐Դ>(Q&<!bTh4-S}WkE)Ӽwގ;<<<ȟ4/(+iz59ِTtZgNG|(^Ɩ:85XI IۻRVSO4֯_O ˖-u󮐦Y󈗗!9h鴰>88Q&Cx߿]کHiqq$33r:Bp&}555ł?W*:>rҤ6ϟ?/#'o:;zQҩ`|fӊӜ2ۗ.9U;5@;*^z"{mhhжM9QAgE [L0*0?E4KZYYXݻo l;bcCx-  pkk.VTh4oi6U*._u蚛sӟ4ǂ h|WFijvzzgyꩧ:::0 =yUs)?@/!]VZRlTt* ;*/Q&CXS!992CCC=PAAO>nooկ~1Aʶm` /z[[[y{12;{,d8wzdcݦ1j(Q%L....IVRΝ;|LHYn|?QS MVo<=fSy*e2R5b 4 p= {Ǝ۷/(([/"~:.P€`th 6p1p08WW׶6ʦ&_"d=|l0ٻwDnTa |^w*Z{2gml L1MLA߿x= hkkc^?~l^eUUcgiiiPȭ[uGGIHIIݰ~ DGG{ݻWi7`049DxӬtwם.}֜94p>"|Thdi&ǡC0‚%I'/{%omڴ \W_? /y~*abP+ zY ˳NNN<1Ν;!ΐryVg7*~Vw*P%ėN p>87Wz0Ej|CJ 󺫫 x 6MтQk+_920ԑJfOcvMG*ZĹX[Q&Iol:B# .=t @!i8BF{a2"ϝ;Ia9yƍֿU.qJOw,( p&ff>em=L:B#WZZ:222`P$Hg!dMMM0*~Ӽ38]h;:xe2 θsGJHi@W9"*XV:ѪcHa9 ݨhRgt+|ׂ2ű˗\)5f,@̬ j:Oe%&.+.-]TW!*ZP&njZ'Th4Ny`$8V2sϟ.-]OMuY(fftzZJDu,4eޥKx{_X~ dThâL7tTt1ӌχ%x(ıL\д9/-]jӧeboyA.Sk$EfgggJJJ]]݃> w^b?OM^?~mhh])ֆ)q,4~BEKRRebML}77*B#MsppP2;w^y0V'''x P4J^^H\w}WɁ+at O唗_~Y4!4M)`Ҳӏ.5DFTW Y#d d|BFЀ(ۙ2$TN!r DiŚ&; O*ZX7>.d()ML.+*4k]]]`U01@$D%w(cɦ5-]R䷵=vLpr|BFzM\_R0($S{VK6MUhʕdr{S^lmmMII"xI%fYg||?hbchhjCL&S2Mp5f,b1@$D%w(cɦ |Ɏa򚊖.fg?mm=>3#p(LKs*, *4k ߄<0Г cJ 7??VYI^Sҥå;w^D.N.?BF&cylob"yMEKG>^D>Ύ*MaTtD@@sP&rv*4BDa |ݽN$-]" LL|IӞ2ټ^%B#X6ph{|(i!x/ t2Y_Th4ϟ?ځX*yM5i~wl%-]"OMu)*jtf?kk;2=STh4 >h_;yr.~t.QF87aa uu<NFzMŋ.H Pt4M~~%wRҥ@w\\Īeǂ.]p*4kh z:&p%|*Z~ ĪeOp*4kT__Y`pp{TnjjjEZʀ$* $9*Zs\7Īeo8;/.-ThdiNNN^=i3,QlkժUsss̚`;ݥ4M|&=m|ԩ!jCB**( fRRch\.? )?!!a˖-Gooe˖UUU-(ƛojhh_k~)Q2 7h5MBTٹ[萩ڵhec:Th4\bEf볳"DfWhf#vzҥ u.,%`ׯ>R֬YsW~KK˾}RRR=zR}ٕ+WOH22ݱc0 vڞHIKKLJDׯ_'ᅆAt4Mu^ދ%8HK1*:d*hXGTh4Gk]p!::L J#L&]P(z{{WYollO@:1,0;+++r<0ܽ{l7okkks///֭[$8L3BH5d{OUj%SP!| CCC677WcZhpiV ~FiᇞAP^LMMU6Mpp̰00MH"&y(&WY]٩tMa¸X Vn۶HU633->4sNpsPhȉǁD{{{qڡ(L P'Ulr;vdʂa / y7G@}4lg36&vf޽ vO'ionA.ϒ#$n߾Օبz6x ͑r` ᜜[nX>#ykWIJ ^ ? v(Ν;*4W-222Biɰp:,&v`P!O!R^Ӽt钲i+$}< An;o^e`+0@S*++|(e7R=OJ=`TX]]x5,1X #R7Yu2jll~~>N @o v,i 2M0&{n-[Xy$W~__E) 6rjP !˳$")Gc62lݺXDCEgd`Jܳgǁ3nH..(9> T ())a>/ MS/))ss; IK6qcCugf55q[&5M򌉶axEUgNehmUy:*O1wI\F4vqޅϮpi2@Ki^y5:UGGC㹹p25ͬ,*#srȨuf߭z:&}))bm!}UVIF9CELW;s2lL 4M@KHDJsAEL_qpV=V|{˗/'Cpm q޽R^MirN7IӧwuſC߄%qR6M7|>>d/D ]ǁgϞո1'A-///'''K__|8P]f d6BdPnJ`UP'q;Di m/NF\=x Y|-ܓά-2AAA`j]?>vm:"'kpfOLmx2M? F!08Z]]GɠҏdeWlUxML;CEL#==?(*4ba€HeEan:fmZWjK<ۛ,Pȭ[ Y6m3b. =zڵkcbb> َ9B†w5Rt`Ӻ74䃐bkr`Ò-c`,K 4M喞d`H?DEEK'2<~[i*424kz󓛡Wn67 FC=%2o1#Mb^d0ݻwCsssd [RRBWuQ}@vʕ${o.---̎fMA?`o߫}; OP$%:TtȔFR{IPrmVG~rOS\]]506/^666Jf/(0~4 )))0EIA N`$3 TG7H{gi2t䤑;w}P!Sذ L1Mrm6))uVuOIKKӘqcr>K0$;e/_1333ߛ(OLJMMU\YYKaL WNieqFnF͕+bGqTtȔ"͌'& L1MpK)rJBrmzA.裏:tH-{n4kLgmyyy>,%⃄3WٳPŅiih6o~׮]7JLyppXHx{-NiTt144YqYј1?Y >>zŽHK&iK'ޛ lt*F dj\\HV`L\MUiBM.2C<ȹi&b^zA4~%A9 K1{!̍Z4/L <0uvvV]F4?%%ݵ*:dz}BHiٳ6o -!QA' ???x 07}njjK/++#_fWlɓITTg΄H|wvޝ8`$ 1M1cXd!-`Bdd`g{!qqRK k׮#88X%=>~S#:^(TQ <dJ ThDNJJ4jv1cN \.xtHt _#0v-K!‹b/W)u4~KJ3 W&$ ׭[Q_ f8pOZFםrOYYIdge4ʄpȁ-5LF\...;> 4M=3̼f *Rd/_࿸Eq7aabG*:dIihx&$ĔHIv ; DM3,, c45'd2zM3ŵ,%'J'.2nЄm&HHM{=דr A4M`ls8]FozzZ6 /]ӡ2MvLr.fk_sR\̀5g.>Fg,Tht޽)/TWWjFA!Y[[+QYΈ?2MGO_`&̈́QP!Vyydw,Aw. Y__%++o kknRޑ޺S^KXQ!坝?f!Aw777VV[[ݰ"##>*++a ?11r2}dvV>3pP@iv~ o ƃl1l%**Rv H#v"@F *44N`}Q\ ,iۻtql"MӣdobϑhhGwlK'NsntuS(ā24u j ?:}؅$ТB4Ξdvk^Z%v]xQN333SSS|/qV?+X fC[KCɾ"vb"}eh7o:SЫBW/e2YOOߛĭTQw-\b@w3'N7$;dWH_#DMXg-!cLjkk+++y݋˛O na㼈{ )~~y:o }e4]^7@g>33c"XY?n -|h,;<ސ+rw; F f`-FN7̧d2|KOVễoMϞ;WS7;㹹Z Q L:󓓓===h'|7 ߳Pߵk/]7;utH\#D0MDww nn *| io|ssIC.|i)kc5:'v Fh%22Rw)wefZ^`*HY#D0MQQu Fh^U\D7h.mJCyh ц9&Ok;m`%0(>xtժU„!GsrӾ4;MaabG! 33p!Y^^ؑ?pҥjXquk׮|w}Yx >|xٲe ʅ\vP򻺺_ܹ3** @) TDR_ Bmk֬)T\wtt 8s V=n49{nnޕ`24UGGсfLMggNb`m`@0VX1:: kpo|| #Fs!B ;`P}yL^אa˖-O8144|+ʄx ]qq1b%%%y߾}ӓw^( |V%Oՙ4鐐ZoIC;&vB!:03~u16ʹ4u-^%ʂ\e^l;vVZ÷{IKx{{(?!!&WS,pbdDyF/I3%uʕpRɥ]KM[ovP<=K6K$34zZZZk똘(좣4o:5SGϟW1MbIa4O]bPl߾099933 >rM[foߒZ\_g 5ݘi_wr7Cv :f8x<¸\ڼyCTTTL|MIgٿ?|! ] זGh?5tu;bc}X4w22L_Ӓ+W V#P@$WSNCfh{R4e2dNJJQiR1M&t2҄r !eӦMPʵ_yt|hSrJ//tIu]_;i!)i1@ yN399yvvg`` ;;x8κu r( bA3:: {+V@H:'I&ӂ2@58vzz={.(m,533C"ZYYrl[(933/3ḳE䃵O^XXPӎ4Bba[@NJ@Z˗jll ϒ?7ovvvڞ[YP%49s͚5666䵷wDDy V6C*qM&?!?:!_W'>55*ݢiOyθ8DIuȫĎBrHJ#D/j&N2}NE/]꾌 E\mez{׽А!y4`bv{!DtI0; )"C0W0au l7ܱcDZcDŽo4y파͗N%2b Dhu^>ֆ4uԲ23s4Mɾq鐫z{%v!1c vkOOO i\)`Z1iD"A$b fl ' +HӌAԆ5JIC]Ȃ44B ǼME4M^_?̦&Z KQѮŎBHA#p4gBB"XhiVTT,Vm43u4yŻ션Z J/vb8foO-cfCCó>tRww^fe]rTKKˊ+6l;vq mquu%C$kƍdPs@Q.o,~~~ׯL &,wu-\@toX ڌ]#(40Mm'@i'%/_^dGͶ\\\Ukm: +'Bu`:<{L7߄*vܩ4ƩQ;K !b`UU(v2 ݦ-:4Y^RY| ((Hyc00E[*/3iCyZP^]4M'퐣/8XM.,4dܹc!:LLjvvv͚5ijݻwݺu9`W^Ֆ;He0[Jۉ`7m\(& ѣk׮g?|0d;r,mȢ|@鑑C@4Mq{#)Ign1*@Ӥ 1̀ן4YZZJ'߿矇;vPYAQ,|UF-NFGGV5'y7GD/3T |sd2o>(nAQ4ehr'NDl5Xhta! >{"AӤ 2ͤYivtt(_dg0G/d$%hK'lcU1Y0JH$1S3I.4+%J97mD||mhRÞ[ZԫtS,4M(1`bbbǐk*8%ܐ1Ǐ/,Q൶O8^6܃)Ԩ<Hn^~< ߈b˗@ldlmmmUUYJ o\ p5)ДoxV }ťx4M4,ho=b!McNgټy3Hcc#VCC8 Gpqƍ`d1c"?g A~(Eٺa)$'?}]pݻw/, );hP={Z`0M)b,8'!A4M4\PRT YFo|||``@e, 3N9\.טMc:~cXTnAҮ`!Ԩ|~b <4M M)_mZW=ɋ4MslzzkrBi ӔR\Cgϝ*-3M.,4=v-$[i Ӕ_]>ee_AӤ 4Mh|c`(77 䩢Α::޽Sf &]Xi6 ei4MS0TθdfQіH<>J6{4bMuӧG̤E4MPiJxzć\b&]Xio_DT:ɞJ4MnUnF_m ظٹCn4Mp_PWܻwojj FChgmmL&oX[46%01񨿿ߜi&p~xG+ݼ0͹[Y]~.]z:*3S[$22C*++a ?110&hJ/ƾ?On)i&!?llT&H@+nM\(+{ĉb1IIIߧL&1&8,:&nJ gyx;\hҤbEEE*he뉫W;;;޽9+Vjx|BU[fOffLNNNMMSt79o>101ax7ީ)iJ>@hʴo ٛؿ؇tuuo~ʊcSSg"aqԽED0)y+װ_;;9k4 _j!=Y/HSǣ[N4etK1P7tw…wҹеzmFۑ4\biڵ7_wp}4<|U })//DqeBbF~1;矔 .^|%(h˷ߋ/TeIlf8A~Em,-QrZ2 сMO'TY 8Kv$:sss0h옹Ñґ j$}P# [`ҥei"2IHF311Qĉ@L5>4YEEJ:&IF5 555vZd _(Ma $}P#&IF5 4M5(AjDhkP&IԈ.4֠L5>]i"Aj$}P#@DX2IHFte>AMa $}P#&IF5 4M5(AjDhkP&IԈ.4֠L5>]i"Aj$}P#@DX2IHFte>AMa $}P#&IF5 4M5(AjDhkP&IԈ.4֠L5>]i"Aj$}P#@DX2IHFte>AMa $}P#&IF5$=v%v~P&I#IAAAAAAAAAcbbbddD(Ĭ;A8޽{vZ`ҥ]]] >bŊ~c\֯_?77GR&''nݺ{UV;wNG"" 7nXȱcP#ܼy3zlG ܹ3999:::''R쪯׾pΊ%_ӆ l2??s7xcAW/[,//Ocȡ[ -(dڿTTT||<ۋ|^^^o6l؎SᤳL&cRpUTT}2;::@"l]=Pܪ*&Ep5 $aa Dž={FGG!q޽666$9ͲE Ӣ9M%'"<rVV axDODTLsjj/!!fРQ8H`d2ֆᄀ8I:;;%I"(w111:((&Cww7i>Cȑ#QQQ ؎a$ٱcI$yqcd;ZvbŊ ĉ "<&`kkQ8HH4lGGb8g֯_Oz\n;:EC&[[lјH# 4= KaaÇ둑*lG;JKKfeXISSӂ7GN%88XgDD5V^ m i>(/Ė(5a\.WKNOOC(!!zf&NDgnn pT@$#AAAAAAAAAAAAAAAAAAAAAk4K endstream endobj 16 0 obj 30990 endobj 18 0 obj <> stream xZɎ6W7Q0 Yv&h ,@2T)hlK(qݷ:щPcOY }7yw4>_|Y^z{a0qߺ望{W!/U( *i|Z)p)y3^QNCxqˌq /M 7~9LʴaL}9Q ;rR4L˯o?^r@ t+rP͜L%JӗɺS3ؙINR “5,"EJ%3v3L1L9 W%y!>y&;ȧOD B4q:?ܳޏGEY tN7-uA.zv'W;Ww=R_(&(k b\g@-t\5G,mByO"T*)G{K8_'/ސ; pDEi HtziNjxdheGN8J ΑË{ny14Yf:Ϸq|4ϴ8)94c{B" =T|g p iU< wT6'WLcDZq\+#JͺX?8 O2JK%>hb w__4c=M%Z> stream xV˪0+.e!NEw]z7jf8!\ ¶F3gf$[ VuVxw ~"<`]Eי^\%׷AG3ql 003'uc@ 8%i)LáQ]2 C9q+=,0(L|.`n'VG32@OV4,B)(D^fT1q*.9 DKbLp, cëL_'|~9fm8Ygfܠ+9IKiǘyQd[8濧h\S9#`SjKee"\] 0H-YRG|p$ JDEjy ! t!W&+Z 6Ŕ2XlU FiiY7݅!(hDQSΫ:S"7+"S[ y- M )y=dt|W4hUkx5ItN҄6/ ١26;sJ0sKSK,C[-0DcBHw4U+ S">R⚺őOYQ)}B.Rl,5I/A2 q ֣Zp;u苗$R_<^>RnPUzuxz56=wtT|ѫn s~0G[+h>Wȱ&u,='@q@ad1)!'gDo?P) endstream endobj 22 0 obj 841 endobj 26 0 obj <> stream xePAhSSC;7544N9g0ޏ޿*֮;/#DUь eo``kjmŦnlK䦧lffk `b 02qYX#lfT4g odg5{+'73)Q3Nj峌'@C9:z}v099L.Vv0){sdϾ@q7[zGl3{ >*Z[X9FF^pLaXٛyCD>L>C>\?C3INn!%gC>7f8[YX[s5X>L?X>L?CsY>D??eF.ILyX;?rYYO]VNtaki 89G^f&ь'}4bI yjyI,~&gOPO{7;c3g+O-SS\& q,ۇ'qs'}H{~҇'}j~҇'}҇'}ȩ|҇'}H~>.BbfkkbwJp|x҇'}xy҇s|y9sW_w}?lnyfff&p B{tZE3k 5OδnHE*h=B7[:YҼMmB gSݸMyJ~ԍ(S1W^;r ģUboPpZX9Mh͒!9Դ~9  ]ncgM ao-˸ܒ/zCZ+_5(m5&$܁p`RK˱c̹xFd f&AN2=E==GR ݗt$'Q?W4LTx~V~QHSO݄HHQX34Z(!:lyKsedPo+~x:˰ / Ѩ=YwCxaźun5{Q.D'=BwV_{{j%'<; Y Iuլs{pQ*7't!Z #شM~} ]IQV$T<3Аlۡ kȞPfwu6y t{*O,Ce_4f%IA  |MLHz`662Ϸ>- -SˣD7 *Zm|J[X50q=ZNϹ4Tb%qo B;9Ez/D[6)mټT bh zËuvpcg;_vUfiz]~-wxY"M1s(m("mW0xߌI59V,Mh=f_? //_"&ֿ4na ."TH 8?"B:l=MޖTr{ Y|$aF% ݂JEҡ3@m*Gn Kn }Azj-3hh0coi~ LV*% z:$FΨrVlaپ gNX@Ea_@6 tȾ:#j}YP0]pzAWY$PpbR`Hw=X^?h!qztti8m;wx'f_j R%ƪ5|Xk $`?CMAK*A'+_Kրޥ= .X&!}N'G6w{z(z[Tw( eI8Y|tкRb7F; ~ vQjdop"[ϫףc z*gw҅p8f ̫b`ZaB$Q2-z ŧU QOyNG>fBpe}H*5ݰX?@8n3_Jǎ(p /(Ѽ+LѩG+tU)7>wZ :Mcy LK)W&kD>%~d)CD$ `2VVɹXMXR!qȬeB}E8MGo*V-Q56|P:}޻"6G7`qhp{E]U4p؋Gf|?PddG>ldlHGSJP}p3D]2T;f5q$ Μ9t4sF h}y!M^><0 po).U^L5~ee˝cuk1ץY|"u+;gg t f/,C:h+ a0@ūCq&O*._țH*%VuԶ ma(Hlf-5j4a(Mp/Sk C? pȩbuW≃@2Dm|+Ju.ZG8u,;0ı۫WA4Z 4{rf ՈgǍ˴Jθnv8p/(҃cY%oЇLS`6abiޞXǾ? JgibD8Җ Zw& ໴6wm2-fKQ qUs;jU%ܩ+M >osE$#>@/P(Բw0`QДVV њm.sIҌYޭjӮ-މJlG:ɖA`X\8C(2&)r[1K3 ||ʒ]pLU^+&09q-&q-$@٪{8]'nd j ؙ 6xb^9;D bDVs3)ϼ[rz俀SW0n鉮% cȩsonQ[AA$'Z*eeu'ϑ>Z3ԙ/Pf )ylQw{Kwmo@dU'Sӑlh"So އϳʋͥ5N rG/r)g ']AKJZՌqq1\~l`?H"gV۱htSrƎK=*"jPy)Z{t4I@HHx2$8v6-i\a(sO)5bM~f'y9?$UtMMZ E"l^_^CEli}54;5e!£a)Nc]&,ofMwB-ohZO7h8c].# Z2-W"@D)[ҋR庙$oO!B;†at%,gudZIzGkX{2 s>y8B@M2% I lvppC'&<fE C:XrGCnڹ,=`85˳T#DEU=Ȣ%2tmZaW`'Ur$*ܞFC2m]D| w6b:g~ w=K#8- ~.L\zpy*?ewQt` 0+8/T,[Z}c ؁n=o2v\_vX2=:E Šut)DG7,Z/uM7v!YU"^鬒P2)|e1+qXZ(3-vYPw((h:ܤ+ݦF 3v͕;H"y xdP<2ޫaryhj:ύU5wC;(*KVGmqOZF 'ߣ&jct{PaO7jڿ*u!Z cxr#Ūƙszk{4KRSv\100zdlgk@ԺQ@uĭB),*SE?b|5tP#OټC0G"ӽΰjh Lm 2}&sbX1y@X"?(]z/+ 6u`EhqC8&Y֚䜑X!,̥Z\$i}`.Q:ʊ:9Iwڅl&Gbv̫&o{@RJp^N!,|liA,3x ~U,q߼C2>Dx!<.'nWL|9ueN)x ?R>J;ٵpJw]<]^{ !:M+z}6KnGՍKHxͣG'ULX} i*_G|*aY.nG񬜰T*ij}2%7Y9|G/{[RN2HۧU GಌPBܘװ@_dyq3yf?ĩq`or{d_S"aE;V}zk,ۨjLPZN?O'Ynqe=Y~Rש٭vvTwlJE. 0Қ. e\Nw՛/@{KWUw$3b9=hMqmD:<%MQd2Y%,[ުљ@ UO7ΰ pLo0=l~ՄDfgn'mғ->/S>*2^C7PQ> ?77D.G;[7U2#u|'b6.9HÆ$}HL,7tOX! =ъMTNq7JcؼKݺM@ڌKFg\b[zKTǽ5Mb֙(_ >_>w>elRAΨ|_~\GG"Gn7rImwK#:TA}9{@MQovSb3F]1@Q3'@LJ\ĕ7^[9S 0`E,W+Y& x pF/~\e5_z){&;tM?"K֍=Óm CKVEƄp@ͻ&RME?UA!s71:T]0k{>S]8v;/ӜڕmB+RޜU.~J76/0eF/{sp^+֞@E|I,[aUǬ֏9`m1EMǛ;zB"#JM*Wc&=QRqKeYc3N؛BzdV0lz`>m~^x/tdoɛ}><2؍;Npa et KJedD#0n@c B\]cnTM(>sN$#9ց3箈0{FC 3.C& )͟2ٿo(mngw (){#m/:uo :Dg  WѓT*R<j BOm 9Al *ibj;(r)}==;ْd'W$=5M Y1K dIH&1\!.^NZ'XM\)sFr O$hmFӨz{/iIs>X驞뫧-Pq7C'w_ 07tLCQ¿,\OG;aLHiaX9m.S-6" VZ__1%Y-Վ1j"S}G@m8x}[Jߒ_&6-=\pBv0fWWXßAF -SmI/cEjcjZ~r:!։c$?Ior i{8lQcWrZn%CgЂ϶õYWݥ~=I[Ӆ2G ;N.1gckgYp+da]U$ #EF9넰qл2X)K*~'/tX'VVEFU,xH$;/EDso;}Ïz6qv^WWRovM$QE %Jhk eQ/$k[!L!&x.DՐPJ~':a+@{٣ ԌEofg C]GX/U0e{,B\P\"rT;/یX 55s*_H4_}CW/%kח)hŨvI|DݷԮsܶ<9 IDyto- bZ7 }}-ǭhN+s#\X?oZYeV8V;pЌ9S27hI%JD ~ S^IXc:Blマ :ZěX2E"4hHDE-s%x; .F-ZaaG lQF|0W)@ .>2ǭ`NR)V}}m 7?ި#uCL\@B͡62ta~e r.ژkzӪ[O~:Ƴ`ꦀp wCsGo;ݎa,32(CShR83W\#+xhEdwnrٕB?nʯNR<{ $TRM}yWJ=9b9NLLj_&0Ө-n:+gA8F^S0SnSAgE[.I3*`8 0U"L89"9>p=X]` xzt! V2eR9,KF{9WkX0ĬS<~!z 3@`uCɫ/zkIE454H`$2`WD'i%Rv܆WaX?xiዷiz_0R 1^nxzЛN(ސT 4PԱqb2fޣrԐ:-.ԁ+e؃: ?ɡQ݉DϯY$+LpՍR9@.oׇhwI8X8@9Ge"=Oʔ Gqջf/VzY9C\/ a0(@g~ Fj!z{.B0{}_d_swOFV `bOG87f{\Ŀt=o3ȴ2qvjs# jl#ڌqus4x*@hnZ ?%9ܤkkhÚr+\fGkTO=!ZTFg)ǧ,v'$2\hPO*6@M'ny fPWv{CO1r&jG%Jvgap jjl,M7om[ J#U*Q̧F:~Lvd =)9fxJC>s 2C P8E3}8!޵6~" %47l-C((19MH8AMrRf-"i,b#vEEլv ^$|Tz>=I џ+a}Dr'nz/%*A`%}~a#쯺kk,UZ9u$X(AXvq{9AhpLJPѥ>ZM?^k5sh[՝hFĵpY(QEh$VB$oJDr鶵> 6Ց̍=;LoI4WS@(; vJ@[bm?729S͏^aU/\7qqnc3ߑ}ejir!+i)ESSqWul0Q0-ESējt:i*qqR4rvf |X,SC' (2یe- 90<™yeBr1(O= kB_U ;42̂y*aCv/7z!mYȺV9rMO++UeNo<6128_dzPYSl6' 4Y)h.D,cۨX%cB_M\XQ8Gjݱs4IU&/t "}фIeJ jh|+\grɘN9U1amyr/;bjWbS=L2XeUkm@6k r>攅ˤq!-owT CoRh*AuZQ4 B]7RP5fIpshAN4?㞰!QMuWCFkY`bW!7[Z3Y=eM}_+UZq3}7(yCPGm[h '1 rP΋{%/*DJ މ}S`1UR::iR{ϧڤ&;_Oa_zfX; ERྮf(%mD{T M}auX;HQ~x#LgsK~^]-6RYšX ZYV;JCfro@x*>Ƣf `xaQ^  4GZu$2\yCYD+?ڝkj 2ʶ}g&x(Iun jieX_,&܋!)/!06AvW-ʔ-v30 ҷUnvt}}AQ)zif ٝgT&cMg8:̊ 6wŵ"9^7&Op]pfJ[>7s<>`'3ݣ| h$3eC֑y@K5*Α5Gt%?Oz ) ;hl;ěmg$*"xU2:h(aO?[_#M-S |gN_3-3WH5*RgRo|acوBE}4Q)5; -$> ӹ 8zf/¥0#%Z}X}%?[Hx;GRR-0l19> )B{T>`eJ s_S>/8gŪUknOnp*)J ojT}jBCz(X q&*-2PŋGS$,+76~ݠPDYqf+q>"*:nrݯ_LM3Ymϥ6/ѲݛDH%?̴MWf7CN`ӛªNvǜtڪ+l{4jv,Cr  u-]J ҁ6D`ĸfx i P"Ő *WWNF ҪDǤfSSp%]sЧh'kD֮"0ȩ@z1>hPvM2sJJ+ 94<6ߒ;-ֲ4> endobj 29 0 obj <> stream x]͎0<baF"eEL"5YsJ]rm>v_oÒmmO%=LY%\y\{|w[GS_.pɟ~lx_%/:9󹙾4PC_9.B.lҎ]MMflU|߯0tj.9۟[Ml-K)בEz[;++X߂k;݁X[e;w={iJ:,Cu8.Ljs 70+]/ݟvG#L=%kOWN#[IM?_пRr=/_J -zaྖkΖNΖ^ϢC>n6[;'k;d?K,GxU QϘ}ꏂ&ߍiJ~ endstream endobj 30 0 obj <> endobj 31 0 obj <> stream x{ |T9w2{f2I I H$3IqfB۪jRD*ZWPu\֪moQS9do/{OHoz8_- I9Q|؏:6@t)zdXSm4i9@ V|"Ӂ콝z!Zw0p}xYt2i#cX| ȇ{-K']ɨ 30D*G%׊WגVŕJ!u |ZD6h5͡q?n3QrMH F%t|Fq4UJO?!uX"r7MrqH>%Q%IrO~,y0Q:'->C ۄiIhk* ގh ɐsQH'D񾮃#{Vᰰu|ztjaGw}-tؚ& rن.G{4q{||_7|&'PG[GHAA|]YG|5!0$%sTz]Y' J؍B%$J8't%NٶS(t-\"`sP;INK%bt4f'wE {xwZ-KF8,ub0Vt,bѱOPt #'&ۉe{qC$g{Ce]O# v D cD.w ψ..$"`(?1P>^v3eTHST]jJj [6YtY:H<ZRVY}Eet BEm,g@|HO]t$'Dn5|,ME/כI(ȶ,Y`ɘ993P^n@<Μ=3`ӟHUJ͒gW/,`qwwTiccOOd;GS~5ǚ{?؜˱3߶.̌fb{T+-*;uEN[TK_N'ˢ5|U wЀy{x7oA?EW3sku,;c5P:@4]U->$H"v2nf|C61N; Wlf].z9N\YvAB֗ S: lgpΜ:{ HL,yH#i^j~RZ2LT&׽{U4+;+ A"*M+Ǔ!ZMD2U"RH"\ ]N) я"S]hj|'̗mTŒjfyq 5`1Q5gM~Cg٧72畄0 zIx}qme8" hj8?!m2_goJ{ټ~Lnϴj:s :3p툉w)r4*͛Pe8Ŵv $m@&I~;xje+R=l䎛oV&ѩtʡ{;VcF!q]bi|HqZ~hr䋙=!f<.n*$0sSHKn5#~<˲PfLӶNUQ(f>B{--ܥ7Qu`0L&bHFG[F[SCTb.L+3 33M33, M,}>>>>>>>KUgMbn= eݪ:4#+YbAqvnSLRҩSQѷh_MӟQ>W⡏W]~,g_9[qIY䩗 E{OQM2Ԣwlٮ^1cmɮQv tк@Jg+X}4`1m;({•NߔV9L}da2I$3s4ճ`s&x x>a\R<,_larL!FjsHNJ$c($ y"P./h ܼ^~ykM$_?Y=u6,u 7/G9=:;t,!S]ۃrjr@f7!$WZMHɽH}kȟ6%B6<I/E:t㔟㈭fQk$nfh}z$,"Z_"fVHK말2+ySpZbH'_ g*p3>%ck@mZHq;/lU4V말VSד1]Z߄0^WBH+&o UGnbXqʵ[nfTQGV}%Iy?[P7ި_T2,IH Dex}o:91 =ub}}}%mPϷRb,$blGzh d]uF;"~?tr,${rP[(3X_ #9#u!@?F_8QN@f>^'B=`cx:ZF{\po[w "d"rԏ@B!Caff:.+  󍾐 9hor{ͨ61CA_Eo[h `(/PhjkvC i({Bca$` _3 =!_#|\{|(< no M,J$v$EGJRΧ}aڞAn//:?}sZ։2S2$RďqW"\B&; -ԌީӲ͏ +B$4 1cyۺlAypL7&wyn7>7 -_(VIn:(ͪL0mۉa>CCk(,\ݡš66xƹ+*rmܸ]-V)_Q׸##Eseg8WlX*/mv%MYou\mCUݢچykhudi[5VƬ\5sjj=KZO9VM͞ڪEurӢ7F sQލJ Ʀ͵5T<,!U$D¤DIHxn']22{mH!9H#Q?ZDUn|dҜ#?g>2,^`9LusJR&$|\<&~K.b|Ƈ+=p.D:ѶX?A 1~k2Lm<{8(!\Ok~MN.)JƫqЉsN-Q0$k 8b>r\ǫ'/E)~$Ӂn79qP?FE\MLJ81sm _-Ƽۍ|I8B^Y (?2,6U܆!aτHyGq+p&,ENEyNhV:a9m8ѱQ7QGϷȆ!£5 Jp7rhTq qj}q@C:#mxк4h/W ǴMˠՆցdWVfxS-_SYE!㌦.n?KRӉǴΨh]}C|?4T\j^Z8Zt"ӨK ^Ej 'dQuZ|" 3|G zMDQț_waZ IG`􉓷pDS7iWˇVjVZFQO2/䎡Wq}tj/wTձjw:V{9p+_Ȳ NFiȺDFDw.v }1;Z_U+1|fye51Y8+jqNƻ]3,Q5VsT^3 3dhƖ^3l&K 7rk͜w=ۭѱU8Ǭ_CT׀<<>EEAQr d8jFJ]1Nn)oH✫!6b*DTGlv۳묢m:uU7GzBET&(j`<k|VE֨yhTq0/NJn#s=8ᾩD ةqW%k̞̊uI!^b~cȫJn j6;[SH_Q㱖ZVƽuC[ud]RZLUr BP Z[5foм[u#/[ nNU}ݒ\EC",EZ|6& o"tߦv{S%io;XxƵX~?ɇ$oC!5w@5gZkh]&o‰ۈZo7awh򖢞#MarUz#yL/90i XN_a~Rx?R~-qWrdW7 7?־~YڠM:ZF62tA<ߴ<*\_]a Kر^D8*"Yjs|=ցr1$\v4m6oG64Xk&>tOm*UGBN9 9HDj&'_?"5Q#sluj;M!f޲]?)*ө%VT5 -q_/EtJg |y*gS' x(}\G1J__q=YҟS3CN)T}]{7[zf;;>ov1b7q[ү୓6 8io2i78_/~l^+W_ɐ^ O^ɀip x 2E.bڤ# xNΐrgg3O)إGÁ;g>3<žR`Ofi Q`F93 ~|~ǐ\؎ <:~# \m +V[%nACmfܲ)*FذLz`?L7.sXpQPdBb P .Q΀|#87&G+1Ҙk=bF,Fe,L, !M; |jIzPF%r0j,00DzRIFId%qKPzY$jbn^ _G˄? endstream endobj 32 0 obj 7819 endobj 33 0 obj <> endobj 34 0 obj <> stream x]Ko0 |CEBKBh8족}HLi(ۤ@??bUsn髛d (tsXWm1[/&imؘa*$} ٻNj!I_͕>6/xƓd P穳iZ7*_!OX`Y"'$\!)8/YQeF\ |{(gwyC|DFg9+yO#HA>FΣDbGث" G>FP诉^4Ϳ/~V͹xȸOܤ6{k;Y̊7Ӭa endstream endobj 35 0 obj <> endobj 36 0 obj <> stream xX{tי$?%b#BLm<~H $H !' 8!n8݆vstSƄismڞݤ6 & lJi\ݐvd(ilcGsw݇f B2jxiJ iޭW|n /Bؿ}O_N$0n WұVsAԗ *ޗ>z%O'v [PGp΢¡H!K6p0|)Է6~!'1r h2'K|S| أ߯ k v~̵[w0wEr2ꚆoE=Og~yikrd19}u'(*O@??)DžLhfFiǑ3fp:7wh#/N_G{[kKsM7j՚U[SzʊWqQa~^n2eCLY-)I fQ4@[ZnfU"+~41h2&U؆62lho9=ג[FZ{6ߘfYPܘ6\OwPZv2f+]:VƨeMŴ׵R˘UWR?USv ז&W*Hy܊'5rQuƦݧ ('J\)lЧOs)a-]O{ͧݴz zz^{c= 8;&Vat6p0u4'M T|N0C;ޣF}mJcx Ngq@1\r9,]D  )njŊ ׭|U>b94`CHEslF"OtĮBrc0z的< FF7\f5/ҩ ȚcѳO\_],L8yN.('+X\'~czp :aF!ِ08O=̈́fلQ=ƾÌ4pO :5i`0ڝBR 0 jj+]i]Se.݅N~ w_v]stZv9ul{{߶>mo3JiCq$b!/U0 Î{|xO)%SdB[!ɏ/CysT]NS7qXqߤBl>ɳS] rK=}n$ݴE6J'jÛnZН 05Si.gn C$=*_ml*rZ*Z{b{~s=4RY]-m/ PDk߉ٟr g?㙛A4b IЪVXb'$)%)j &l-(^(&ͻsյ3.mS,/ bn83|4܁oFȆ fBhh&f~&D-MʒZA,$wf6s/)pᅢg?66#K4,Z\9WASAwAqmhk]IV, w }T ]\ZL+V8K#=TAXJMbB7|aOd+2WRak:IHqRJ3~hM{ #5u n&<=؋@)f/f ʴB9shXRJC}yYj9ٛel^b]`I|< jʉӳs~T1dt1ϑ-d%|6acz*̇D=L xTm{;riw`򢶈+WP_\잰>VjXj9OHݸS=DIB R&cl6ZlM|Jj]rT,θT*-qKs+lJE Ŗd=ƧVU͢VD:375_w⚑0ןC`Gj"+NNjGZ)xZ=YW* 3Y rYI*5h~Xݾ]y8  ܭ6˃/Grzۂ`hHnEBP 8,7ʼf[n(-.YU*np3(#0b EeP@nwl pԏPtþcdx0EGSaE&4k8Ž?!}@tP{h͠'4=)## `hhqhߵ{AWopu˼A0 B? @| .XR+!(Di= !Z؎bKȍUndv`3ZuK~ap'B'߈~x ⇰ !C};z֢܋!}Ez<Wn1yD)W +n1Pgz2~%sǐ:w"tTs{Q7 {Cѫ^;z9@"~sc`<7|M ݐ-.M=16kaXF.حs=soXv /9wlnghHO'0Cbگqpk3\,׏o!4&kLLCGA]G\!]!+${^>xeߦ+$Ӵq]_^_\>'˅j|:ޯ:~6IVI$VXT,ig-'J.gCK?asg^i(\8_5_>ă)$aj~֣~sig8TDdd' O'{NO:x$/9NWItBŜ5X8 Fx##5G=b!T恗/$\xi%+sSN1E'b; YHӠD2ԽUҗ'5/a"9ɗ}3<ii\'r$I?+Yƥq=|vܠ/X $Dz_O}"u?BV>y FDD=L0$L 2L¤3LE4I y!Odvdgvʅ#Ύ}{ˤnnJ{۶%mJ :D"tʄ@,B@ۈږ_Q۲mA%_jiZ,5cj*hMMti'Kz:<2:la-tPfIb;jOʪVcmvj{n,5n4YB--Szbn dhos:'M6r]m٢iб6!|d|4jem>gIg@A( % :#Ht$u.`p+79 9Fs8#8#A"qs;#ΨNŁ(8 t#/!{Aԟ endstream endobj 37 0 obj 5244 endobj 38 0 obj <> endobj 39 0 obj <> stream x]n E|t۩%R6}n>AaߗGJ]<.r;%(rLR,Brwgf~[̝u k= ="Vj»˩qs-0w^yc3w—^y4 8X&@ -njuR #2;3Ii% |H:p \F>VU~L=E:!r5LZ46Z 椂m*o; endstream endobj 40 0 obj <> endobj 41 0 obj <> stream xz{|q IH I_(/H=Hd˲-*,l˖'R"ч$Hh6$]m6\&M*EvEPd%wGٙcDGf K=CS{wn'C =B:!LԎ=ENG߱Oo њ :q=<qeJα=?P vO9^BJ^g.9ź81'xŠ?$DL4@Hj>5zeGΰPNoTs%=#3˚MxwG$w=b&Hc{a])T /XvDulJ$7 ҖK-i=evNAf+2FChO;R8qGVc.$ֺ{eZj|{쀖BM+P`JkȆiu&?2XVKK-HC5jXCD-ݎ|g=X]K[쏓d, $D7wU5/YEz[(ŏvU:#Fňzq{h/54Pu.gݑP#Ա%:)H[aXl|al/!p4;Wa1e,5z1VDbċhKbSs)hV{=2,&*Rq QTň#uq)sꑵTZuU[,TcR*3pYW*7Vߩin[jTPeH2$(y[PVM;Zs%1mm#sHLCtPwsY):xOtmi4ȫ 0ދNl)"Xb)VDZ:oJʭ@dj dxIK8q\'8*e#t}6N,@GE`mkx_x%ZX9cÌ29|=+IE W\MEWYArh_U*̟Կ 5MvWZwev >ߑν1f󋗥'خڭ>^T+Щ qy#AfIKSK#]@"p] WpY q5Wô۵wvꏹ\oٷ׵jJH`{=oa_7:?I[$yQ-srLfrM K&)nlcT XPlRVsH 6%(-Y5 f-(|BHɛRkIw^M-\X3wxH8v2d}&)]Y7#jUuMƋ*.G*z4dݬ>©J*r80-e2<_=vy\G"m Mĵx5t/^` X :BfOE%tV<^5bQ{==|m?9?*6d=P? b~];~H燏liKC$hIN.$( UTTG/]tT-kx˜p:C}[,~R;?KpWeL6+d(uy2"+5뼬rS[ܒ/.K>{׷udiQju{})&&er2/wY ::>rS٪U<dX6^%]~;t̩9_vGgakkB\i}_݁ bKWT:vR:]C jU+/Pufz}[ nܡl;qKXQx+6tRWitVtЦXS\)쮵_,8^M o@AUfI47wXT.7J ì`!>MwӋDguS4^R[~feT;tXG^9S1B\FV겛T4s.425-/ppN۸/\s=%__Mm}fw\VW6')ZmO+m*mUjRa0Tu<]k;wWufn ޜK7nm.\xRnwr,|Nk6ֈ ;V-={zWl։͍ij͎=*Ct5NlֻLf-rrbgqK|A*Tٌj5(ilߌiV`d&,n=џ_sz}~&"s +:sBVsv5h7֜li:8^eeui0o\uҽ:, B=_,f+% uw?Xe 7ONGUi&@|= =L}ڒ7;Zؑ=q; ڛk;BrMg SP+r7vr\ìg:=tCl#ՅABOaјfD""&4E>!7FY7̻+&`&O*(93C5\zp]ڝlvZjۚUS-f+ZQ3t,\Ò5ᘃ+.$3A6-U{5V+CE1_)V̒b(.fVA^.lE\T99V!({ǎ[sWbjǜ\ԑrnyvU-jjwYW.4R0Lmitwl6Rr,^~ [RҠm\0nOӮa5`j<|YS,y))qVmg0Ѧ)dRUSƛFycò}G¡TQtɥI9@Myz.oyɾwOڵU:дj/m|bYC;C롡PwذnueI+zj]i9g7 f55%ԔQ[dFZ6v.v)Ղ/nvâRЩifVkĢ]ZX56:ԓt˵w$&р ɝ%nh'^Rհ/jl ͡2m5Cɾ̫M?xhXk͍[rXg.J[1񑿐:ؤײſ{Mo Sk:qf)yk+Jõ4@CCSAh+JV5Щ iuuٴnO{I jtsx :L|N":ӻV_px<8I~8Ju:1YbJyNUt*cx9C=wuPzjqђogpW,.XWV9<wKYgc|Ogؿ-ۥa^|8a_DIvI-DaT0pI ƭP0ncڹk)85kpKѮO 7vht0Αq*QOO?jM_8}ON?ggUw~ÙjݟO|oo}|G>?um.]U{dKՉx5:kd VrT=jj8 fX3\3gӏ3%o/=m R[j71'%'u \qSdt=BM\dF@|*28X{iyGϾW LUm6>+pzOE\3"sߘ@C'!2 Q$ #gA@(^zqJ/ñ|\ u'/\"½B@!I3dvC lmi[)S h,M؝ ts^^:~҅<[;o2RG3='[S7r2IȒUahH$򙑚k>ҍyyA^6&_~)f ~aU$S/=u\ 0UzPvղhv4ˮa5kʽjǩzYEh~`H枫PgNx%-ڝZ6g~I|MMh͵@%cN1bIr4NtK1ɑR&(O'n-3/JL [ϋ,oЫQ]W;V}sWڱUsͅޜU*3g,䢴 P)2EbLB2,FiBo$;+fRgʃ;NJV^c9[$ts895UYS$%qֺ7LNVށ.Cp_ 5n~*8tgi¯.}olrq\iS{BQǵ>.ͥ13lTƝ VA̤== '$uvPt@D^~'-?Eݓav;g͑K`.]9X z䖉 n?|왦mKя1y8l~#_z1ZAIP+p}<y+~02$f#3ȉEe)~IHp$;'۵!x. `V&@sꞩcPUSeiU|Fΰᶼ6~uJ+`S坏#E"FX)dǨRH^VVK$մ7k [ P0,o`u7ں :.P+d STBZP?zlݖc=EޘhmA`]}̪;kw왪TjVeׯ{%'岭%95B( 9O4#ja:<baU[ՌZDߠV3F1EJ ̦p e`z|˨%!h @ $o^ ۵Z>ce?DYgvf'Nk}Op ˿OIM,yL;6͟/E^AN+n@ ̒@V"G;nK?\iz p׫_ӿ4nCbK䥬;HPPZ$1eF?#gsw%}Xe͐r9rKK+ۄm<#.+j$CI~~Sv3EqKOka>g GV:櫚_u:GO ("lrv`$nomљgæ2Ђ^ 2џ}I#r: N#r|B)*CDLtltl'cc(}3#t;sONOM&EG CXtfrb,Zv?CIa-mmSё Xl( OY.M8 hSӓ;ñw'vP.qsGMb9@8AH c/:I}Hd)Jj#HUPٍ 3r-Q/W3iEnMv2R!mi qdoǡ-H?]X@mBƑ H9!0TeLxM2J겜/%.e #4X2;A9.A.*,ɣMTG卮yӹ$8O" ~(p:wqߚuօ[Wnt#|c k6g ,`+ׯ]g@5\is ؞6 `ovOޞ}YzRϧx·|b!y7<65k^cՃ8l|<ggי_9 92~i%Fxs/]{Ӟ?IԦy sc_eHMzXe,WI6 Xb m=]=}4Ce|Zo zNMr)N8e;u3O?WoK Hϩu$ }"`8!`fO۠N(N*4\sHvxϢmuyeߵ}AlgA%^Ӂ*^,TAi@b&6% 2z2=J/ۣ@nPTd+ z: ΃J,( oض*m[Q[;ՓrgӑeNBrNu Pm؉*ձHURG~QJčj-/ސ nl m7@zgY c}xqgB[[@q2k\3'B#0@z& [h&gk¢!b>kً].Wh^+[D<-qqҳek"ӽGO 9+ G(01碅4fb\@\3. \23d6Hkƕ@(n&f\P3"31A8t1#⢴$)?졟bA>w&0 endstream endobj 42 0 obj 9663 endobj 43 0 obj <> endobj 44 0 obj <> stream x]n0 09#s\1'I33c2bF7):KOd;"gzRO sտ _oH9I 1;Y endstream endobj 45 0 obj <> endobj 46 0 obj <> stream xռ xSǵ0:kfoI[-Y-۲$oۖ[!c^6!` ; H- y!)>mNҴIIӞ=p4P8=I&?%J{ߕ-53k֬Yf͚=4šaD|mgޞEBo!f=N a9;Bjgͦq !2ݝ]5f"1(^I#LܭF53VݚY )5};㏷%]4}tmsT Buk|P!78tM"(q@؇*ƄUj є`Xl)4ӕx}~iZA2XU-O16BwېmR~opȌnChuB}F I]΢0ȾA!t?[t}:h&0- '*(~zR&z=A1D/T࿡نFiB/846YQMDQ=«~ t^OAg JܶDu/b< j_6ޓ'>+ &aU 6&!M^{}O/ K3 >E6!WqH8AD 8!(.t\-l O  /|\ a6E!Sx~x.`I=a0N ,]Y}QpIP#J. 0, 9 P/_pN 'l(8YI.p젙 \N 5xh2WSGĩVc1ِV;"'`B m9/m~ֳπY mѰja_Ŝ9M*rYNr;?|HDSv/# .C Br}qass_pscn0a nV gBb85)4fdmؒMgAz^DZՁj/m[Vn@I"4l;=^FsG)H,0֮un=~7]je,KTV˾*7nI[I7%u2KvaL4a=}.d% 33ygU㭴6Me2?x.jSjCmm`NÁxN*̪hl p1cpš5u{kK}F_Pj)[oC$?MݜGl]vC=]"kFQ\98 U&3Vĉ5Q_Pc4jQ!}Ug"vaPzE\YY| mi֫>H۽MGߖ 6wqO'I0TK*L >tkH))9lea-azffLEhX Br ~͆dGVE.m덬/sQI+U6->=vQ5Pdj1-|8O pbc=>'Oj†,=׽ CZ|̬@/K6mk>z4U\ZbȞ0ʫz^95᫟ϑ~&d~qaCeCE Z:XcAYOtO{S∆c-gI sF*D1_Ca38 Zv5P3iXz.LB{~ӧyOq+EE܁"YsƘCD.#^l9 ְ-$ tm_]]x FeӤف4b (ݐh~BKחYst&)jqI2J5<1\ZaW|Ǚ[VT)'ҹ).#N.y|1塟汁w24gZhSX=)ګ*4pU&`sX6d:6}c3EpMF͍e!!{JBcz@zQ/1}Yk6UCL0tzzC2K m``IrՂUVMOos}da왒UٽKGlc"7W!vlXj+dC^ X ui)i8FaZ/)&K$a]VZoxݎT1w2(S$<;/\]b$xz'ʀь8#)~ցNS-4x10.RҦZX%iÝ)l)*,fQPƓ+ۻPo?}F!Ϝ5=-K]{c|1{4:ϑ`̫]1g܆[f{˲h{'svnBh,n*ŽyΌg$%:Gשx'9@Wx4K>FrxZ!DQ"#P] Q 0VyzE)磺T,fG(}@jcM:hYn*W_1)b9mݳtT&/?ԖjRŲ[*Weխ ^^ӷ;ԅR}We+ (O.[x{[ב5'N[UIkEFwQ$` X136E'ivN)~ƹUT.WxqX'XǧfTӎ1,e@bR[|f941kŴXjҤDe)]_:L?҆Wg훛[U?7M|8}Rh߻:,;w-w}oYNY^uՉ̆,3!G9,s2Gcd#:T]S~9p W~s[rPy䔜D(Ͻ%oݓH.)kEA!~K$ӹe٪7RRqY*4PBO$hHc= h ȠxLE[j@74vVbX uyPYyyQ>`SV[|/yyLbUhDbR04z #g^r  +boȋm^ y5/C^! foY*xm^LsEޝ^BsxX&Y>¯~ ^bP)CyGwv/ldvy Vos^o^^˃ F/MIeV)Ȍ^Rmr{?c4Ὡʑ!v! ş*|ǽ֡W@ K-|R*B!EJ{_ +: RYTSfKE$ k^ yE/֨C3Pt5SsΉ3L)O+M⩱`GLy]֯I3勹!>改11nl, ~S0%~c f[;](mwUxP7 њX\R% 7ƨ VyĿ>:^Mg%A#^W"5A}6R{8o+q33;+![ےg443gsș/`š/#nKtkJGz8n4qq82w,DZpZq'aHUQH}O׾ja6jan ߴ5-FxХY f-#Kz?~״dX;Ŕzmhș~!VzץVVE,fh(v8JY"+ǴpZ \,aÓ%ƋAbD*l@ESY11uU,uiA{]bꫀ֏> <$iꑹ! @?ݻ <.aR6މFm˭ ơ—)1-:pm <5].Z alf;m4 69$хδi.9q 1r* ΢<>!LI2]6qS+ .PlD[cGkB}Rԁ*$2X[e4[lsxRkpO'J]τi%ueStŠozlyL'ɟO,"! @ sh.dC"Z©-$ۨ /llxކ 6gc7ñjIY;6 "19ͣi%P>|1b}(F٘L4[>oF9b#~mrQi6L8Mvg)bɬ 6aAR<N?:ҙVk2>:|bI̍-̂F31IQd >m)n OOg賦jn֖Us9~/̞>{l<4}kd]ddI8J$#s82fV T!CA]E:hqWfCQ->%U3D)ҧUe3+t*2V ',SfZB%!e/XXhH8VM;ΐY G ǂ-] ?HN&>GQczuilj>~sLlS7[_cG1˜E(eNyj}ͦ(Af%eK*L*)oI%I]nxn V_sG< w-1?@o|@haգHtg%Fh/:d/٭6%N$$x:R=["B"r[ p& EA2,D"jUBUׄΌmtbT66L̘. 0߽̗7]Hxoloo({u\Nd^h?1q~y*|{L9;rCpH}Q7^;<i"m`g $ Ad$%i1!rN4; T8LFѨrʕ*v\+\];]^t+ ]l>|O\a~JBE4%Du?q v1zЅ\.\ >qa +]!V3\.xMP&(b {1_<+?N)V.wW ;] s. _p]v\7 1J:F.Fջ]1wr.=iFC\=6VOLa|ŎjhWGnr.e~[rT%0ż1*$VK-}nz3A20N,Pp$ahOӾeUp{ ٸ?tUtw1u[^LWz0=`sj>5 <˱ Ck-d]␈E+;A j@]#av|kxN|^|y<OS}JlZzkrZ^ZyOiSt99>s`G:'e"DT>BQ eoɆ!v+19?/<M>18W|p}0->ANO>ɇ >|?DBDpaa* ˓6o)B"$JVIP"HI$$!$Mj)QoAK"KKOHg$WTj%x_'H{$hVJ$'S4#](.*_h $kHOJߗ^ ;$ȓs S H[:$` e\EjHIr\%oI O*SҽC,`x$H@' "o$\` 'V J. R$%\RJxGJJ0pp]!A)%(R| "3ߢ-JLaǦov?~~P!nJGyiRR!55Vɖ -ֽTB| t. lZ/)+/Vd]oi兵3S*K+%TT ծTH2AM@SAĭr<l]D.7b6"RKr:4mtplگ՞6""bN4Gjq_|M|O| µ0"'F$XْVΔbR~fZM{MM?3}`L5{&|MM5v3M XKd)%RX"|P0D~P˻U~zgaiQhhYPUʼq &`^6`D~l#YY:=Mb3wЯYk]_N6Wu٨R%-r㲵bQm?ʎ \EKe:< ,b#L>`HtQ9ևE1י vv Ko6`׶[/P%Ps$$ ҢYT(9 twʵ^ٰ޵\G3ZO:fnn ϝl!@JwʂP^G})7܁a6aB)0ͨmBykI@ihq$@:A7ۯ#=݇Ot*IXqa/ss'A2pQC??#ϳg0=&i4AA4qɾ2:`{F Iחh×euZ9_Dxp/&׵[tb`'<фmfE3{pMfh`L7y#DQ5*؁?']׊)P:,"+IW}nq=Je.=`?3iE * o#:ЉИ_/0]`׈oo>fb21ս=tQ}-xGݙ(r]ͮ^uClؐ#R ۾NYyaY# % em YLnى1ّFMĴthM&l&!+˨~11 5Tdxq:(o[2}:c;m/2,[=ݻTYT%rBES?ϞM/Y.7:ud?f PF`@5(hM,`@E7t _XA:P՞` X\pP`7oΟ479ͼ)y06נy]2'/vN8Q`;cϗ3_R/0v3<ʉ UtFq,MqNJze= +I\TщvWX4\He?&< / "jDI~qLOō|{.ϊvp|p#]L8`f>jLLu霸7:dRy}f2 Vڡ;qdCK>I#a_INhI9-9mb)KAcV^oAlȄaɔ)ؙG Nqv'W[FX`ml=q1=a,+S.M--nzU14qn_][60[~6_, ʉY wRt~sxSK~<}íӷe-oo0-e)~Yg + ?[.N5.:M,L4uijU?a?@uz){ct=.9؜؛I/[+B,-l734jl"#&bRv䔱s)RLy2%#Q=>,D'%(xP \fcZQ 1`ܣKΖػpvUl2[K=ގ}kWqNeN^w j Y ۧ8+qvqv{prrx{9tC6!%7jLZ4h [Ctv$ N  Pl[?ƒ y{_4lj^!jBO<cf>ATN](֎3#jLݑbdsr|W4޿z౎vr揾6p$ hіvwW~=z`G3fLϰWϨ]Te;X1Q~z٦oߛk.ʼt\yW{8: N4%ؼJ,Ј1ʤ?ts pw1ʓF)[/4A] %)`>N@wA>0k>0> />c mKaJCyχG},wG%E` >%(FQ5>bE)l14,'>aC>D^R9#Jdx6cv>8ðH5k,Ar*V,Q3Db'33HI9R,c j7:아[R\`T>m2(>upY8+%b-I&yq8B56,h`V Ga[ͼyLfY.r]?z9vr@YM+dץ93 ܟ^4,;~|b%G7Ԍo =ڹs{N܄r{ jj !r[ kz^hDzдoЎLSW_T.(7l=:.+}˿ )srvd Vv"B\9_vbOoݪUx hoT-l .xDjhAq * ^tbL9H"xQG@҅t+t;u,u!h$GX7^u}J5ܰPPF~i)- vՇnA9{,qMvހx{s ?^? d27b=FxVypΉ)k;N[N!٭ۚx>'2g\)T,-rKapRl(skQԫD{;7_2Axvg)+.cO&>ؿ9g~Ц\|a\٫g. S!vK V I,&ѝ_KɥT{`D\XGn[ޜ' ,LQa+)5j^Iշ$tNLLdG?Lti85q9SNTHMMiWܩss$P,b>M6uE*nskSFE#+v.ڹ: `km83<5{f=M?q7 -V?q4}{m Z*V6 e3\E?2uKw~V<*s.fUNN:awpڬ&:rQ۾LMMXwQ"u vn&Rs.u#aCܼq8?H\JԂ/9ir=9so]SMx+N-mU=m# .GW߽Jr OklZKיu H9s`c㒃SAK3U5aBAq{MIU0KquPbQ.a3yYP}^k{vK/X>}į׮@t*rCtY&T]r5*/L((j0πvA9)rY܉޻f~AV#1L8-j胓phgy”'U7M2RuMɯiDi]1?i&ÎE(;>oA-h" ҩp9 P ~y_GE vx5b@ 6SNQ2QecNb%>"n+w'Fl͆گ唾T~+c)kU@&;]3nѿ{nu^pL\7d健3^3W6X^Z{nxYtuG0$RU/#~ײd n< b^zOM~1VmUJ1OUs}Q琍.vm'(3I(n+\6|Z} Op=O*'{K'ʩW8a7I|%dmhq2.蕲w/ْmy6"fr3]u}-sL${x!-O$zC1,bf:)WQ{w}s`Y@-. br;Ǜe4GSa, թ8q{#SE%/RKLghQF~l4mxBlYwtmOʾb):]A"ZV9ܫQFe1[R i7<(zlӸnKK l η?^*Z5Y:{@Tѕ:}hQ-gΫ)L+{GֻЕ>g\iYU57,xM){u:nSf'b[iryWh}VnBZ.F`vC}/ɫoe74j~bUs̫tgpG>nO17 7n$E{hxP}n 8}aa~ʧ2|xc4ޮx>-F}SMqиCJFq4~Rx6rO.JUFP'+6+~FLYtF!(yqSa>TQrQ_ &(mv GUԜ&8 (`7THc0Gq`}1XE'b}B^ÝJ.`*8/zh Gw4۳rș{]W3yME J|oB`r;8=@#{9} }ΡuCu]ΆXѻ[\=0IVR>Wm]J]ef@_9CC݃}V v3SЦޞu⾕CkJZ7HߠHB}hǬxNF~_EY[ս|7㿭wu?Jw]F%VYnP?ڄP/A+rg_ P>èbx)TQ,5yA%MQ0ofZJm:j<4*;D;)n7ZKh5C+[ieJ9,⯣ ih ?Fo:YNc)e P|oͩ(G|ks~3yBeHUh7R VR:kJGK\A6\=DQ}^kU|Su% P뻩w2(C,JәƏnS|Fct} b?7DGFҎJ/Phkdkup]m,<#f tXϲ';`J9V맿}ݻ)=J{iRzXnfe$fKK= |?q@㠝%8Os 8;8/m ]j4r%^]aȿ Yy!x .B8t!m|Iy !/ʏӤ;?W*\s/C3gr1 xy//|Iܱ"s0D^ 1<_pd4#ȹ>=y=si|){ܓxމ'#3=BvǷ':?Hdxȹ4>;R)ao颀̀a SV4yhph Q y< bPt{<4  4A:h , ~=Q%?CQ҃LZJ_Yo endstream endobj 47 0 obj 18156 endobj 48 0 obj <> endobj 49 0 obj <> stream x]n@<,EsgD,80 }9L[D3\;ݜfsv >5¥+9^ZI9\yXr6OiSߧ6L]1Or}g~6Y^6TrK˿赥J36MUj_'oWfl9zZJRe_/, ؑsW +{)Xc%\ W eWdߑu7֗=6c -=n^`_h/s8[Kf_F͌L,ӿL {8-8cBBn ; fnGAd͡SC} =<se9xv9uxF.?X_wxп|οu?R<]]~K9^<;0 =hD(>M1_ܮ-0K~+ endstream endobj 50 0 obj <> endobj 51 0 obj <> endobj 52 0 obj <> /ProcSet[/PDF/Text/ImageC/ImageI/ImageB] >> endobj 1 0 obj <>/Contents 2 0 R>> endobj 6 0 obj <>/Contents 7 0 R>> endobj 9 0 obj <>/Contents 10 0 R>> endobj 12 0 obj <>/Contents 13 0 R>> endobj 17 0 obj <>/Contents 18 0 R>> endobj 20 0 obj <>/Contents 21 0 R>> endobj 25 0 obj <> endobj 23 0 obj <> >> endobj 24 0 obj <> >> endobj 53 0 obj <> endobj 54 0 obj < /Producer /CreationDate(D:20220914231852+02'00')>> endobj xref 0 55 0000000000 65535 f 0000123369 00000 n 0000000019 00000 n 0000001461 00000 n 0000001482 00000 n 0000022563 00000 n 0000123539 00000 n 0000022585 00000 n 0000025007 00000 n 0000123709 00000 n 0000025028 00000 n 0000027328 00000 n 0000123880 00000 n 0000027350 00000 n 0000028525 00000 n 0000028547 00000 n 0000059700 00000 n 0000124077 00000 n 0000059723 00000 n 0000062135 00000 n 0000124249 00000 n 0000062157 00000 n 0000063071 00000 n 0000124554 00000 n 0000124717 00000 n 0000124421 00000 n 0000063092 00000 n 0000076883 00000 n 0000076906 00000 n 0000077121 00000 n 0000077707 00000 n 0000078159 00000 n 0000086065 00000 n 0000086087 00000 n 0000086291 00000 n 0000086667 00000 n 0000086909 00000 n 0000092239 00000 n 0000092261 00000 n 0000092464 00000 n 0000092798 00000 n 0000093000 00000 n 0000102750 00000 n 0000102772 00000 n 0000102980 00000 n 0000103396 00000 n 0000103677 00000 n 0000121920 00000 n 0000121943 00000 n 0000122139 00000 n 0000122741 00000 n 0000123185 00000 n 0000123258 00000 n 0000124882 00000 n 0000124993 00000 n trailer < <7B1525C058B226039AFC335666C5F4A6> ] /DocChecksum /60085FA2C15917D3C387CBF5518FD148 >> startxref 125168 %%EOF dygraphs-2.2.1/docs/header.html010064400000000000000000000131161437353256000133440ustar00 Dygraphs — <!--#echo var="pagetitle" -->

dygraphs-2.2.1/docs/ie.html010064400000000000000000000015331437353256000125110ustar00

Internet Explorer Compatibility

The dygraphs library relies heavily on the HTML5 <canvas> tag, which Microsoft Internet Explorer did not traditionally support. To use Microsoft's native canvas implementation in IE9, you need to set an HTML5 doctype on your page:

<!DOCTYPE html>

When IE9 is in HTML5 mode, dygraphs works just like in other modern browsers.

If you need to support older versions of IE, which do not have native <canvas> implementations, you'll need to use the 1.x series of dygraphs and follow its instructions for IE.

dygraphs-2.2.1/docs/index.html010064400000000000000000000101631437353256000132220ustar00

Please visit the official Dygraphs homepage https://dygraphs.com/ instead. You are reading this on a mirror, which may have outdated, incomplete and/or locally patched information, or as part of the Debian package; links may not work.

dygraphs is a fast, flexible open source JavaScript charting library.

It allows users to explore and interpret dense data sets. Here's how it works:

This JavaScript…
g = new Dygraph(div, "ny-vs-sf.txt", {
  legend: 'always',
  title: 'NYC vs. SF',
  showRoller: true,
  rollPeriod: 14,
  customBars: true,
  ylabel: 'Temperature (F)',
});
… makes this chart!

The chart is interactive: you can mouse over to highlight individual values. You can click and drag to zoom. Double-clicking will zoom you back out. Shift-drag will pan. You can change the number and hit enter to adjust the averaging period.

Features

  • Handles huge data sets: dygraphs plots millions of points without getting bogged down.
  • Interactive out of the box: zoom, pan and mouseover are on by default.
  • Strong support for high/low bands / confidence intervals.
  • Highly customizable: using options and custom callbacks, you can make dygraphs do almost anything.
  • dygraphs is works in all recent browsers. You can even pinch to zoom on mobile/tablet devices!
  • There's an active community developing and supporting dygraphs.

Getting Started

Start by downloading dygraphs. Then read the Tutorial to learn how to use it, or just play with dygraphs on jsFiddle.

Once you've got your feet wet, look for inspiration in the demo gallery or check out our list of users.

If you're using npm and a bundler like webpack, browserify or rollup, you can install dygraphs via:

npm install --save dygraphs
and use it via:
import Dygraph from 'dygraphs';
// or: const Dygraph = require('dygraphs');
const g = new Dygraph(div, data, {});

Check out the dygraphs ES6 sample project for more details on this approach.

dygraphs-2.2.1/docs/legal.html010064400000000000000000000007111437353256000131750ustar00

Legal Information

dygraphs is purely client-side JavaScript. It does not send your data to any servers — the data is processed entirely in the client's browser.

dygraphs is available under the MIT license, included in LICENSE.txt.

dygraphs-2.2.1/docs/legend-formatter.md010064400000000000000000000072761437353256000150210ustar00This is intended to be a catch-all for legend formatting needs. This [comes](https://stackoverflow.com/a/25090552/2171120) [up](https://stackoverflow.com/a/24175648/2171120) [often](https://stackoverflow.com/a/18211338/2171120) in Stack Overflow [questions](https://stackoverflow.com/a/23354308/2171120) which can’t be easily answered with `valueFormatter`. Nowadays I wave my hands and say "write your own legend, either as a plugin or using `highlightCallback` and `unhighlightCallback`." This is a simpler option. The `legendFormatter` is repeatedly called with a `data` object describing the selection or lack of selection. It contains all the information you need to generate a standard legend (e.g. formatted values), but there’s nothing preventing you from doing something crazier on your own. For example, here’s what a simple `legendFormatter` might look like: ```js function legendFormatter(data) { if (data.x == null) return ''; // no selection return data.xHTML + data.series.map(v => v.labelHTML + ': ' + v.yHTML).join(' '); } ``` Here’s what the `data` object looks like when nothing is selected: ```json { "dygraph": "(dygraph)", "series": [ { "dashHTML": "
", "label": "Y1", "labelHTML": "Y1", "visible": true, "color": "rgb(0,128,0)" }, { "dashHTML": "
", "label": "Y2", "labelHTML": "Y2", "visible": true, "color": "rgb(0,0,128)" } ] } ``` The `dashHTML` properties help you render lines which match each series’ line on the chart. When `strokePattern` is set, they become dotted or dashed lines as needed. Each value has a corresponding `HTML` variant, which is properly formatted and escaped according to all the relevant options which have been set for the chart. Here’s what it looks like when a row is selected: ```json { "dygraph": "(dygraph)", "x": 93, "xHTML": 93, "series": [ { "dashHTML": "
", "label": "Y1", "labelHTML": "Y1", "visible": true, "color": "rgb(0,128,0)", "y": 93, "yHTML": "93" }, { "dashHTML": "
", "label": "Y2", "labelHTML": "Y2", "visible": true, "color": "rgb(0,0,128)", "y": 26.04, "yHTML": "26.04" } ] } ``` Here’s what it looks like when a single series is selected (e.g. with `highlightSeriesOpts`): ```json { "dygraph": "(dygraph)", "x": 94, "xHTML": 94, "series": [ { "dashHTML": "
", "label": "Y1", "labelHTML": "Y1", "visible": true, "color": "rgb(0,128,0)", "y": 94, "yHTML": "94", "isHighlighted": true }, { "dashHTML": "
", "label": "Y2", "labelHTML": "Y2", "visible": true, "color": "rgb(0,0,128)", "y": 22.56, "yHTML": "22.56" } ] } ``` (Note the `isHighlighted` property set on `Y1`.) dygraphs-2.2.1/docs/ny-vs-sf.txt010064400000000000000000000737631437353256000134670ustar00Date,NY,SF 2007-01-01,46;51;56,43;45;48 2007-01-02,43;48;52,48;56;63 2007-01-03,39;46;53,50;54;62 2007-01-04,44;51;58,45;52;56 2007-01-05,51;57;62,44;49;58 2007-01-06,55;64;72,40;50;60 2007-01-07,46;51;56,45;53;63 2007-01-08,40;49;57,43;53;64 2007-01-09,37;41;45,49;56;66 2007-01-10,31;35;38,45;49;54 2007-01-11,29;35;41,41;46;54 2007-01-12,39;45;50,41;44;49 2007-01-13,46;52;57,38;44;53 2007-01-14,42;44;46,36;43;51 2007-01-15,41;46;51,36;46;55 2007-01-16,25;41;57,37;45;54 2007-01-17,21;26;31,41;47;56 2007-01-18,25;32;38,38;48;61 2007-01-19,33;38;43, 2007-01-20,23;29;35, 2007-01-21,21;26;31,55;60;68 2007-01-22,28;31;34,44;54;66 2007-01-23,30;34;38,41;51;64 2007-01-24,34;37;40,42;51;64 2007-01-25,17;27;37,45;49;56 2007-01-26,11;18;24,44;48;52 2007-01-27,22;32;41,47;52;58 2007-01-28,32;38;43,48;52;60 2007-01-29,24;28;32,47;55;64 2007-01-30,23;30;37,51;54;56 2007-01-31,27;31;34,49;52;56 2007-02-01,28;33;37,46;50;52 2007-02-02,34;37;39,47;51;57 2007-02-03,25;32;38,42;51;62 2007-02-04,18;25;31,44;55;69 2007-02-05,10;15;20,48;55;68 2007-02-06,13;20;26,48;54;62 2007-02-07,14;21;27,51;56;62 2007-02-08,17;24;30,49;54;56 2007-02-09,20;27;33,55;56;58 2007-02-10,25;30;34,55;57;60 2007-02-11,21;28;34,51;55;59 2007-02-12,30;36;41,48;51;59 2007-02-13,23;29;34,48;52;58 2007-02-14,19;25;31,44;52;60 2007-02-15,17;21;25,49;55;65 2007-02-16,16;23;30,48;59;72 2007-02-17,22;29;36,51;62;77 2007-02-18,20;28;35,48;54;61 2007-02-19,14;22;29,48;53;61 2007-02-20,29;39;49,49;53;59 2007-02-21,39;44;49,50;54;61 2007-02-22,33;40;46,43;48;54 2007-02-23,23;32;40,43;48;54 2007-02-24,22;33;43,46;51;60 2007-02-25,26;33;39,49;52;55 2007-02-26,31;34;37,44;49;54 2007-02-27,35;39;43,40;45;52 2007-02-28,37;42;46,42;47;53 2007-03-01,33;39;45,44;49;55 2007-03-02,36;49;61,45;52;60 2007-03-03,37;48;59,48;58;68 2007-03-04,35;39;42,53;60;72 2007-03-05,23;33;42,52;58;67 2007-03-06,14;19;24,49;54;66 2007-03-07,14;20;25,50;53;60 2007-03-08,20;28;35,48;52;60 2007-03-09,15;25;34,49;53;58 2007-03-10,30;43;55,50;57;69 2007-03-11,40;45;50,53;63;79 2007-03-12,36;46;56,57;65;78 2007-03-13,44;49;54,52;58;68 2007-03-14,46;56;66,50;54;62 2007-03-15,37;53;69,51;59;75 2007-03-16,28;33;38,53;62;76 2007-03-17,27;35;42,50;53;57 2007-03-18,29;35;41,50;53;61 2007-03-19,33;38;43,49;53;59 2007-03-20,35;43;50,50;54;58 2007-03-21,27;34;41,48;55;62 2007-03-22,41;54;67,50;58;71 2007-03-23,46;54;62,49;56;67 2007-03-24,40;48;55,50;52;56 2007-03-25,38;46;53,50;53;56 2007-03-26,41;48;55,48;53;60 2007-03-27,48;62;75,46;50;56 2007-03-28,47;55;63,47;52;61 2007-03-29,40;48;56,49;57;68 2007-03-30,42;55;67,48;53;64 2007-03-31,44;51;57,49;55;68 2007-04-01,42;47;51,49;52;58 2007-04-02,42;48;54,48;54;68 2007-04-03,43;52;60,48;54;66 2007-04-04,41;43;45,50;56;67 2007-04-05,36;42;47,50;54;63 2007-04-06,34;39;43,48;51;55 2007-04-07,34;39;43,51;54;59 2007-04-08,32;37;42,51;55;61 2007-04-09,35;42;49,52;55;59 2007-04-10,36;43;50,50;56;67 2007-04-11,37;44;51,51;54;57 2007-04-12,41;43;45,48;53;58 2007-04-13,44;47;50,49;54;64 2007-04-14,42;50;57,50;53;56 2007-04-15,42;49;56,48;55;65 2007-04-16,40;47;54,52;58;69 2007-04-17,43;47;50,48;52;56 2007-04-18,43;48;53,46;50;55 2007-04-19,46;55;63,46;50;55 2007-04-20,46;59;71,47;52;60 2007-04-21,48;63;78,49;52;57 2007-04-22,52;64;76,51;55;59 2007-04-23,54;70;85,48;55;65 2007-04-24,59;67;75,49;54;63 2007-04-25,49;56;63,51;54;59 2007-04-26,48;55;62,50;54;63 2007-04-27,48;52;55,53;62;78 2007-04-28,53;63;72,52;61;78 2007-04-29,55;61;66,50;53;61 2007-04-30,55;68;81,51;57;66 2007-05-01,52;62;71,50;55;61 2007-05-02,51;61;71,52;56;62 2007-05-03,53;63;72,50;53;58 2007-05-04,52;62;71,50;54;60 2007-05-05,52;63;73,50;59;70 2007-05-06,48;55;62,57;72;86 2007-05-07,48;57;65,69;76;88 2007-05-08,50;60;70,53;68;79 2007-05-09,58;70;82,50;55;61 2007-05-10,62;71;79,50;54;61 2007-05-11,61;70;78,49;52;56 2007-05-12,55;64;73,50;54;60 2007-05-13,53;61;68,47;55;66 2007-05-14,52;61;69,49;53;60 2007-05-15,59;73;87,49;52;59 2007-05-16,63;76;89,48;55;66 2007-05-17,55;62;68,49;54;61 2007-05-18,49;52;55,50;55;63 2007-05-19,52;55;57,52;57;66 2007-05-20,55;67;78,51;57;67 2007-05-21,56;65;74,49;59;69 2007-05-22,55;64;73,52;61;71 2007-05-23,58;66;74,56;68;83 2007-05-24,61;75;88,50;56;69 2007-05-25,69;82;95,50;52;60 2007-05-26,73;80;86,50;52;60 2007-05-27,68;77;85,50;52;56 2007-05-28,67;77;87,50;55;64 2007-05-29,63;72;81,51;54;59 2007-05-30,62;73;84,53;54;57 2007-05-31,67;79;90,52;54;57 2007-06-01,64;78;92,50;53;59 2007-06-02,73;82;90,50;52;56 2007-06-03,65;73;81,51;54;61 2007-06-04,60;66;71,55;59;65 2007-06-05,68;77;85,54;57;61 2007-06-06,58;65;72,52;56;62 2007-06-07,58;67;76,51;57;68 2007-06-08,66;76;85,51;54;62 2007-06-09,64;73;81,50;58;66 2007-06-10,63;69;74,53;58;64 2007-06-11,66;76;85,52;58;66 2007-06-12,68;76;84,52;60;72 2007-06-13,59;65;70,57;68;80 2007-06-14,58;63;67,57;67;84 2007-06-15,62;68;73,54;62;76 2007-06-16,64;73;82,52;55;59 2007-06-17,69;80;90,52;59;70 2007-06-18,70;78;85,52;56;64 2007-06-19,69;76;83,54;58;65 2007-06-20,70;75;80,54;56;61 2007-06-21,65;75;85,53;59;67 2007-06-22,65;71;77,53;59;67 2007-06-23,61;69;77,53;58;67 2007-06-24,63;74;84,52;58;66 2007-06-25,71;78;84,52;60;75 2007-06-26,73;84;94,51;57;65 2007-06-27,73;84;95,53;59;67 2007-06-28,73;84;94,54;61;72 2007-06-29,70;75;79,55;61;72 2007-06-30,68;76;84,53;58;66 2007-07-01,64;70;75,52;59;68 2007-07-02,60;68;76,55;62;71 2007-07-03,65;74;82,54;59;64 2007-07-04,68;71;73,55;64;75 2007-07-05,69;76;82,55;62;72 2007-07-06,70;79;87,53;57;62 2007-07-07,72;80;88,52;54;58 2007-07-08,76;85;93,53;57;65 2007-07-09,71;84;96,55;59;67 2007-07-10,75;84;93,56;61;70 2007-07-11,74;81;88,59;65;74 2007-07-12,69;77;84,58;66;75 2007-07-13,75;80;85,57;65;78 2007-07-14,70;78;86,56;60;69 2007-07-15,75;83;91,57;63;72 2007-07-16,72;78;83,56;60;67 2007-07-17,71;80;88,56;62;73 2007-07-18,71;75;79,61;66;74 2007-07-19,71;82;92,58;62;72 2007-07-20,69;75;81,58;64;73 2007-07-21,67;76;84,60;66;76 2007-07-22,70;77;84,60;65;74 2007-07-23,65;71;76,58;64;76 2007-07-24,63;73;83,56;60;71 2007-07-25,71;78;85,56;59;64 2007-07-26,73;80;86,55;58;72 2007-07-27,75;82;88,55;60;72 2007-07-28,74;81;88,55;61;70 2007-07-29,74;77;80,55;60;74 2007-07-30,72;80;88,55;63;80 2007-07-31,74;82;90,54;59;68 2007-08-01,75;84;93,55;60;69 2007-08-02,76;87;98,55;61;70 2007-08-03,73;83;93,54;60;71 2007-08-04,75;84;92,54;58;67 2007-08-05,72;78;84,55;58;62 2007-08-06,75;81;86,56;60;68 2007-08-07,78;84;90,56;61;69 2007-08-08,75;85;95,56;59;65 2007-08-09,74;80;85,57;61;72 2007-08-10,59;67;75,55;62;73 2007-08-11,60;72;83,56;60;68 2007-08-12,73;81;88,54;60;70 2007-08-13,75;82;88,55;61;74 2007-08-14,71;77;83,55;60;71 2007-08-15,72;81;89,54;61;70 2007-08-16,77;82;87,57;62;72 2007-08-17,66;77;87,53;64;77 2007-08-18,62;69;76,56;62;71 2007-08-19,62;68;73,57;64;75 2007-08-20,61;67;73,59;65;74 2007-08-21,58;60;62,58;65;80 2007-08-22,58;64;69,57;65;80 2007-08-23,63;71;79,56;62;73 2007-08-24,67;77;86,58;62;70 2007-08-25,75;83;91,58;62;71 2007-08-26,75;80;85,57;61;68 2007-08-27,71;77;83,56;61;71 2007-08-28,72;78;84,55;66;82 2007-08-29,72;79;86,61;69;83 2007-08-30,73;81;88,62;71;82 2007-08-31,72;77;81,59;64;72 2007-09-01,66;73;79,58;66;80 2007-09-02,63;72;80,58;67;86 2007-09-03,67;77;86,58;63;71 2007-09-04,73;79;85,60;64;72 2007-09-05,69;74;79,58;68;84 2007-09-06,70;77;83,60;64;69 2007-09-07,72;80;88,59;63;71 2007-09-08,74;82;90,58;61;66 2007-09-09,74;80;86,59;61;66 2007-09-10,73;76;78,60;64;71 2007-09-11,72;75;77,59;62;70 2007-09-12,66;72;77,59;62;65 2007-09-13,65;71;76,59;65;74 2007-09-14,67;72;77,60;66;74 2007-09-15,58;65;71,58;62;72 2007-09-16,55;62;69,59;63;71 2007-09-17,56;63;70,56;62;71 2007-09-18,57;65;72,56;60;67 2007-09-19,59;68;76,54;57;63 2007-09-20,64;74;83,54;59;72 2007-09-21,68;76;84,58;62;73 2007-09-22,68;72;76,58;60;65 2007-09-23,67;75;82,56;61;67 2007-09-24,65;73;81,52;62;78 2007-09-25,66;78;89,54;66;86 2007-09-26,72;81;90,58;70;92 2007-09-27,73;79;84,57;61;68 2007-09-28,64;71;77,55;59;63 2007-09-29,62;69;76,51;60;74 2007-09-30,61;67;73,51;60;71 2007-10-01,62;67;71,56;62;71 2007-10-02,60;68;75,54;62;76 2007-10-03,66;73;80,53;59;67 2007-10-04,69;77;85,53;55;59 2007-10-05,67;75;83,51;56;62 2007-10-06,68;77;85,48;57;68 2007-10-07,67;75;82,51;59;75 2007-10-08,67;78;89,52;59;78 2007-10-09,61;72;83,52;59;67 2007-10-10,62;68;73, 2007-10-11,59;64;69, 2007-10-12,51;57;62, 2007-10-13,49;56;63, 2007-10-14,52;59;65, 2007-10-15,53;61;69, 2007-10-16,58;65;71, 2007-10-17,60;67;73, 2007-10-18,64;72;79, 2007-10-19,66;70;74, 2007-10-20,64;68;72, 2007-10-21,59;68;76, 2007-10-22,62;70;77, 2007-10-23,67;74;81, 2007-10-24,54;62;70, 2007-10-25,52;57;61, 2007-10-26,53;57;60, 2007-10-27,59;65;70, 2007-10-28,46;53;59, 2007-10-29,43;49;54, 2007-10-30,49;57;64, 2007-10-31,51;58;64,53;60;67 2007-11-01,52;60;67,51;56;72 2007-11-02,46;51;56,49;60;82 2007-11-03,47;50;53,50;61;79 2007-11-04,49;53;56,53;62;80 2007-11-05,48;53;58,50;54;59 2007-11-06,45;52;58,50;54;61 2007-11-07,42;46;50,50;53;59 2007-11-08,38;43;48,53;56;60 2007-11-09,40;44;48,53;58;66 2007-11-10,39;43;47,52;57;62 2007-11-11,34;41;48,51;56;64 2007-11-12,40;46;52,47;56;68 2007-11-13,49;55;61,57;61;73 2007-11-14,44;53;61,53;61;72 2007-11-15,45;54;62,56;59;67 2007-11-16,39;44;48,54;56;61 2007-11-17,38;43;47,53;56;61 2007-11-18,41;44;47,53;57;63 2007-11-19,39;43;46,50;56;60 2007-11-20,40;45;50,48;54;63 2007-11-21,44;51;58,44;53;66 2007-11-22,42;54;66,46;54;68 2007-11-23,32;37;42,44;57;72 2007-11-24,28;34;40,44;54;65 2007-11-25,37;44;50,51;55;62 2007-11-26,41;52;63,47;54;65 2007-11-27,46;56;65,50;55;65 2007-11-28,37;42;47,47;56;66 2007-11-29,42;47;52,48;53;64 2007-11-30,37;40;43,45;49;57 2007-12-01,26;34;42,46;49;56 2007-12-02,21;30;38,47;54;59 2007-12-03,34;42;49,52;59;64 2007-12-04,30;33;35,31;57;69 2007-12-05,29;32;34,52;56;63 2007-12-06,23;30;37,51;52;54 2007-12-07,34;35;36,48;52;57 2007-12-08,35;40;45,42;49;56 2007-12-09,36;39;41,44;51;59 2007-12-10,37;40;43,45;50;59 2007-12-11,36;41;46,46;52;59 2007-12-12,37;46;54,42;49;57 2007-12-13,30;34;38,42;49;59 2007-12-14,32;39;45,40;48;57 2007-12-15,30;34;38,43;49;56 2007-12-16,31;36;40,46;51;57 2007-12-17,27;31;35,48;52;56 2007-12-18,31;35;38,49;52;55 2007-12-19,35;41;47,46;51;57 2007-12-20,38;42;45,45;51;56 2007-12-21,36;39;42,43;48;54 2007-12-22,36;39;42,39;46;53 2007-12-23,41;52;62,44;51;61 2007-12-24,39;46;52,49;53;60 2007-12-25,38;41;44,41;49;57 2007-12-26,34;38;41,44;48;55 2007-12-27,37;42;46,41;46;52 2007-12-28,43;47;50,41;44;45 2007-12-29,43;48;53,45;48;52 2007-12-30,37;41;44,46;49;53 2007-12-31,36;41;46,38;47;56 2008-01-01,35;42;49,42;50;58 2008-01-02,19;29;39,43;50;60 2008-01-03,15;19;23,51;53;58 2008-01-04,19;28;37,51;53;59 2008-01-05,33;38;43,46;49;51 2008-01-06,37;42;47,42;47;51 2008-01-07,42;52;61,43;48;53 2008-01-08,52;58;64,44;49;53 2008-01-09,49;58;66,46;49;52 2008-01-10,42;47;51,48;50;51 2008-01-11,41;49;57,48;51;55 2008-01-12,40;45;49,46;51;58 2008-01-13,37;42;47,44;51;60 2008-01-14,34;37;39,46;51;58 2008-01-15,34;37;40,44;49;57 2008-01-16,31;36;40,41;50;60 2008-01-17,30;36;41,44;50;61 2008-01-18,37;43;49,42;52;63 2008-01-19,34;36;37,42;50;62 2008-01-20,19;27;35,46;49;53 2008-01-21,16;22;28,43;45;46 2008-01-22,25;33;40,42;44;47 2008-01-23,32;37;41,42;44;49 2008-01-24,26;31;35,43;44;45 2008-01-25,23;29;34,45;49;52 2008-01-26,27;31;35,52;55;62 2008-01-27,31;35;39,45;51;54 2008-01-28,29;36;43,42;46;51 2008-01-29,33;39;44,41;45;49 2008-01-30,34;42;50,42;47;54 2008-01-31,30;35;40,45;48;52 2008-02-01,33;45;56,40;46;52 2008-02-02,36;40;44,42;47;53 2008-02-03,33;42;50,46;49;52 2008-02-04,34;39;43,43;49;58 2008-02-05,38;45;52,40;49;58 2008-02-06,40;55;69,47;50;55 2008-02-07,39;45;50,43;50;58 2008-02-08,36;41;46,46;53;65 2008-02-09,37;41;45,46;57;69 2008-02-10,17;31;45,49;57;70 2008-02-11,12;19;25,48;56;69 2008-02-12,19;25;31,46;54;70 2008-02-13,31;43;54,48;57;65 2008-02-14,30;36;41,49;54;64 2008-02-15,32;40;48,46;52;60 2008-02-16,25;30;35,45;51;64 2008-02-17,30;42;53,46;50;55 2008-02-18,42;53;64,47;50;58 2008-02-19,29;36;42,48;51;53 2008-02-20,25;29;33,48;51;57 2008-02-21,22;28;34,48;51;56 2008-02-22,26;30;34,46;50;56 2008-02-23,30;33;36,43;48;54 2008-02-24,27;34;40,52;54;58 2008-02-25,33;41;49,49;55;63 2008-02-26,38;43;48,48;58;70 2008-02-27,24;36;47,52;58;71 2008-02-28,20;25;29,48;56;74 2008-02-29,19;28;36,48;52;62 2008-03-01,34;40;45,50;53;59 2008-03-02,30;36;43,49;59;71 2008-03-03,35;44;52,47;57;72 2008-03-04,43;53;62,46;55;68 2008-03-05,39;49;58,46;55;71 2008-03-06,36;43;49,47;55;65 2008-03-07,35;40;45,48;56;69 2008-03-08,38;48;57,49;55;66 2008-03-09,31;37;43,47;58;76 2008-03-10,28;37;46,52;58;70 2008-03-11,36;43;49,50;54;66 2008-03-12,36;42;48,48;53;63 2008-03-13,33;39;45,30;55;61 2008-03-14,39;46;53,49;52;58 2008-03-15,42;49;55,43;50;57 2008-03-16,36;42;47,47;55;68 2008-03-17,32;41;49,49;56;66 2008-03-18,36;41;46,48;55;64 2008-03-19,42;47;52,49;52;60 2008-03-20,37;48;59,46;51;59 2008-03-21,35;41;47,47;54;67 2008-03-22,35;43;50,46;55;73 2008-03-23,32;40;48,46;54;66 2008-03-24,33;41;49,47;54;64 2008-03-25,32;39;46,49;53;59 2008-03-26,43;52;60,48;52;60 2008-03-27,44;47;50,45;50;59 2008-03-28,40;45;49,46;52;60 2008-03-29,34;41;47,48;52;59 2008-03-30,28;37;46,44;49;56 2008-03-31,39;48;57,41;50;62 2008-04-01,55;60;64,49;53;60 2008-04-02,39;47;54,48;54;62 2008-04-03,35;42;49,48;53;62 2008-04-04,42;49;55,45;50;57 2008-04-05,49;54;59,46;50;57 2008-04-06,42;46;50,48;52;59 2008-04-07,41;46;51,45;51;59 2008-04-08,41;48;55,47;50;55 2008-04-09,39;48;56,47;52;59 2008-04-10,48;62;75,46;55;66 2008-04-11,47;54;61,50;65;84 2008-04-12,47;61;74,57;71;87 2008-04-13,44;50;55,51;64;80 2008-04-14,41;49;56,48;52;58 2008-04-15,43;51;59,45;51;60 2008-04-16,46;56;65,46;51;66 2008-04-17,45;59;72,49;57;75 2008-04-18,50;67;83,48;52;60 2008-04-19,51;62;72,45;49;55 2008-04-20,48;52;56,43;48;55 2008-04-21,47;52;57,44;49;57 2008-04-22,48;59;69,48;54;64 2008-04-23,52;64;76,48;53;60 2008-04-24,58;68;77,45;53;65 2008-04-25,55;63;70,48;57;69 2008-04-26,52;58;63,51;62;78 2008-04-27,49;52;55,53;62;77 2008-04-28,48;54;59,49;56;66 2008-04-29,47;53;58,50;53;61 2008-04-30,44;51;57,47;52;60 2008-05-01,45;52;59,47;55;66 2008-05-02,50;53;55,49;53;60 2008-05-03,48;52;56,48;54;63 2008-05-04,49;61;72,49;52;59 2008-05-05,51;61;71,48;53;61 2008-05-06,52;65;77,49;56;68 2008-05-07,58;66;73,49;55;65 2008-05-08,64;69;74,48;53;62 2008-05-09,50;57;64,46;54;65 2008-05-10,50;59;67,47;54;64 2008-05-11,50;56;61,48;54;64 2008-05-12,48;53;57,50;56;66 2008-05-13,52;61;70,49;61;78 2008-05-14,53;64;74,55;69;85 2008-05-15,54;64;73,68;81;102 2008-05-16,51;56;61,65;77;99 2008-05-17,50;62;74,52;63;73 2008-05-18,56;62;67,51;54;62 2008-05-19,51;56;61,50;54;60 2008-05-20,50;54;58,52;56;63 2008-05-21,50;60;69,50;57;65 2008-05-22,50;56;61,52;58;68 2008-05-23,52;60;68,49;55;65 2008-05-24,56;63;70,51;54;62 2008-05-25,58;68;77,50;56;64 2008-05-26,60;68;76,50;55;62 2008-05-27,60;73;85,51;56;64 2008-05-28,53;61;69,52;58;67 2008-05-29,54;66;78,53;57;63 2008-05-30,62;72;82,52;56;66 2008-05-31,63;70;77,52;54;60 2008-06-01,67;76;84,49;55;63 2008-06-02,61;71;80,49;54;62 2008-06-03,65;75;84,51;55;62 2008-06-04,58;65;71,51;54;62 2008-06-05,62;67;72,51;58;70 2008-06-06,59;66;73,51;57;66 2008-06-07,60;78;96,50;59;70 2008-06-08,78;87;96,52;61;74 2008-06-09,76;88;99,54;66;83 2008-06-10,73;87;100,53;64;77 2008-06-11,72;80;88,57;65;79 2008-06-12,71;79;86,55;67;93 2008-06-13,65;74;82,50;56;65 2008-06-14,70;80;90,52;56;65 2008-06-15,65;74;83,50;54;61 2008-06-16,64;71;78,49;52;58 2008-06-17,64;71;77,48;60;78 2008-06-18,60;68;75,54;65;79 2008-06-19,60;69;77,57;72;92 2008-06-20,63;71;79,63;80;102 2008-06-21,65;75;85,56;75;85 2008-06-22,71;76;80,51;56;63 2008-06-23,70;77;83,50;52;56 2008-06-24,70;76;81,50;58;71 2008-06-25,68;77;85,51;56;64 2008-06-26,74;80;85,50;57;70 2008-06-27,76;81;85,52;57;62 2008-06-28,70;80;90,55;58;64 2008-06-29,74;82;90,53;57;65 2008-06-30,73;79;85,52;57;68 2008-07-01,71;79;86,53;58;67 2008-07-02,69;78;87,52;56;65 2008-07-03,73;83;93,54;60;71 2008-07-04,71;76;80,55;60;69 2008-07-05,69;72;75,56;62;74 2008-07-06,68;74;79,56;62;74 2008-07-07,70;78;85,56;66;80 2008-07-08,76;84;91,59;69;86 2008-07-09,75;81;87,62;69;82 2008-07-10,73;80;87,59;64;72 2008-07-11,69;79;89,58;64;73 2008-07-12,73;80;86,60;64;73 2008-07-13,72;79;85,60;64;71 2008-07-14,70;77;83,59;62;68 2008-07-15,72;81;90,59;62;70 2008-07-16,71;81;90,57;60;66 2008-07-17,74;83;92,54;58;65 2008-07-18,76;86;96,52;56;64 2008-07-19,81;89;97,53;57;63 2008-07-20,79;87;94,52;55;63 2008-07-21,75;84;93,54;57;63 2008-07-22,73;80;87,52;60;73 2008-07-23,70;76;82,54;61;76 2008-07-24,70;78;85,52;60;72 2008-07-25,71;79;87,52;61;72 2008-07-26,74;81;88,55;65;79 2008-07-27,70;76;82,55;57;62 2008-07-28,69;79;88,54;57;65 2008-07-29,75;83;90,55;59;68 2008-07-30,73;80;87,56;60;69 2008-07-31,75;83;90,55;58;69 2008-08-01,74;81;88,55;62;74 2008-08-02,69;76;82,55;64;79 2008-08-03,68;76;83,54;57;62 2008-08-04,68;77;85,53;56;65 2008-08-05,73;79;84,52;56;64 2008-08-06,72;80;88,54;57;68 2008-08-07,68;77;86,54;57;66 2008-08-08,68;75;82,54;58;66 2008-08-09,66;74;82,54;59;67 2008-08-10,69;76;82,53;64;79 2008-08-11,60;67;73,56;63;78 2008-08-12,63;72;81,55;63;79 2008-08-13,69;75;81,56;64;82 2008-08-14,68;76;84,55;61;74 2008-08-15,67;74;81,55;62;75 2008-08-16,66;74;82,57;60;66 2008-08-17,69;77;85,57;60;69 2008-08-18,73;81;88,58;62;68 2008-08-19,65;74;82,58;62;73 2008-08-20,61;69;77,58;65;74 2008-08-21,66;74;81,60;64;73 2008-08-22,69;76;83,58;62;72 2008-08-23,67;74;80,57;62;71 2008-08-24,71;76;81,57;64;73 2008-08-25,68;77;85,58;62;72 2008-08-26,62;71;80,55;61;79 2008-08-27,64;71;77,59;70;88 2008-08-28,66;75;84,62;72;86 2008-08-29,68;74;80,59;67;84 2008-08-30,68;76;84,57;61;70 2008-08-31,69;77;84,54;61;73 2008-09-01,68;76;84,57;68;83 2008-09-02,71;79;86,58;68;85 2008-09-03,70;76;81,59;69;87 2008-09-04,72;81;90,60;73;94 2008-09-05,73;80;86,63;74;92 2008-09-06,72;78;83,63;72;84 2008-09-07,70;77;83,56;62;70 2008-09-08,68;76;83,55;60;67 2008-09-09,67;73;78,58;60;65 2008-09-10,63;68;73,57;61;68 2008-09-11,62;68;73,56;60;66 2008-09-12,65;69;72,55;57;63 2008-09-13,68;74;79,55;59;67 2008-09-14,71;80;89,55;59;67 2008-09-15,67;76;84,54;58;67 2008-09-16,63;67;70,54;58;65 2008-09-17,61;68;75,56;60;67 2008-09-18,61;68;74,53;60;70 2008-09-19,55;61;66,55;62;72 2008-09-20,54;61;68,59;62;69 2008-09-21,59;70;80,57;61;70 2008-09-22,62;68;73,55;64;79 2008-09-23,57;63;69,56;67;85 2008-09-24,58;64;69,57;64;74 2008-09-25,56;62;67,57;65;76 2008-09-26,60;64;68,56;63;75 2008-09-27,64;67;69,54;62;78 2008-09-28,67;71;74,55;59;65 2008-09-29,63;68;73,57;61;67 2008-09-30,62;68;73,57;63;76 2008-10-01,61;67;73,59;64;74 2008-10-02,56;60;64,60;65;73 2008-10-03,54;60;65,59;63;71 2008-10-04,53;58;63,58;62;68 2008-10-05,54;59;63,57;62;69 2008-10-06,51;57;63,59;64;76 2008-10-07,48;56;64,56;64;79 2008-10-08,52;59;66,58;64;75 2008-10-09,60;69;77,54;61;71 2008-10-10,61;67;73,54;58;64 2008-10-11,57;64;71,55;60;67 2008-10-12,57;64;71,55;62;72 2008-10-13,60;68;75,58;64;77 2008-10-14,59;64;68,53;63;80 2008-10-15,60;66;71,55;65;83 2008-10-16,57;68;78,58;68;86 2008-10-17,52;56;60,59;69;86 2008-10-18,44;51;57,53;58;65 2008-10-19,43;51;58,52;54;57 2008-10-20,45;54;62,51;57;65 2008-10-21,46;55;63,52;62;79 2008-10-22,42;47;52,55;67;83 2008-10-23,40;47;54,61;70;85 2008-10-24,43;50;57,59;66;86 2008-10-25,54;61;67,57;67;82 2008-10-26,51;57;63,52;57;66 2008-10-27,50;57;64,51;55;65 2008-10-28,41;46;51,50;55;65 2008-10-29,40;44;48,49;54;64 2008-10-30,38;44;49,51;56;61 2008-10-31,42;53;64,58;61;66 2008-11-01,52;59;65,34;58;63 2008-11-02,39;46;52, 2008-11-03,44;52;59,53;56;61 2008-11-04,50;57;64,51;54;61 2008-11-05,55;59;63,46;55;66 2008-11-06,59;62;65,54;60;72 2008-11-07,58;62;65,52;60;75 2008-11-08,55;58;60,53;56;62 2008-11-09,50;53;56,52;56;63 2008-11-10,44;48;51,49;55;62 2008-11-11,40;46;51,53;58;64 2008-11-12,43;47;50,56;59;68 2008-11-13,47;53;58,52;61;74 2008-11-14,55;59;63,55;67;80 2008-11-15,58;62;65,60;70;82 2008-11-16,42;52;61,58;66;78 2008-11-17,39;43;47,57;66;77 2008-11-18,31;36;41,52;56;62 2008-11-19,28;33;37,52;54;57 2008-11-20,31;35;39,54;58;66 2008-11-21,27;33;39,47;55;67 2008-11-22,25;29;33,47;55;68 2008-11-23,26;32;37,49;56;69 2008-11-24,33;43;53,49;56;64 2008-11-25,38;43;48,52;57;61 2008-11-26,38;42;45,53;55;58 2008-11-27,37;41;44,53;55;61 2008-11-28,38;44;50,50;54;60 2008-11-29,38;42;45,50;57;68 2008-11-30,36;40;44,51;58;70 2008-12-01,43;49;55,53;55;57 2008-12-02,38;42;46,52;56;64 2008-12-03,35;39;43,50;54;59 2008-12-04,39;45;51,47;53;62 2008-12-05,34;38;42,46;54;66 2008-12-06,31;34;37,47;53;62 2008-12-07,22;30;37,44;50;56 2008-12-08,20;26;31,46;51;55 2008-12-09,31;44;56,42;50;59 2008-12-10,44;54;63,47;52;63 2008-12-11,38;41;44,48;54;63 2008-12-12,33;40;46,48;53;60 2008-12-13,28;31;33,45;49;55 2008-12-14,30;40;49,43;46;50 2008-12-15,48;58;67,41;46;51 2008-12-16,31;45;59,39;42;47 2008-12-17,33;39;44,39;45;53 2008-12-18,37;40;43,38;47;55 2008-12-19,30;35;39,45;50;55 2008-12-20,23;28;32,39;46;54 2008-12-21,26;33;40,46;48;51 2008-12-22,14;22;29,44;49;54 2008-12-23,20;26;31,43;48;53 2008-12-24,31;45;58,46;50;54 2008-12-25,35;47;58,45;48;53 2008-12-26,32;37;41, 2008-12-27,40;44;48, 2008-12-28,47;56;65, 2008-12-29,38;44;49,47;53;63 2008-12-30,34;39;44,46;51;58 2008-12-31,19;28;36,45;48;55 2009-01-01,16;22;27,44;48;51 2009-01-02,24;30;36,45;50;55 2009-01-03,30;35;39,40;46;54 2009-01-04,26;35;43,36;45;54 2009-01-05,39;42;44,45;46;48 2009-01-06,31;35;39,44;49;54 2009-01-07,32;36;40,44;47;52 2009-01-08,30;35;39,46;49;56 2009-01-09,27;30;33,41;51;62 2009-01-10,24;27;30,48;56;66 2009-01-11,26;29;32,44;55;70 2009-01-12,23;29;34,49;62;76 2009-01-13,29;35;41,55;64;75 2009-01-14,18;28;37,48;59;70 2009-01-15,17;20;23,48;60;74 2009-01-16,11;15;18,48;60;72 2009-01-17,8;15;22,48;57;68 2009-01-18,22;28;33,49;58;70 2009-01-19,26;30;33,46;58;71 2009-01-20,22;26;30,47;58;71 2009-01-21,19;24;28,52;57;62 2009-01-22,22;30;37,51;53;55 2009-01-23,28;37;45,53;55;56 2009-01-24,18;33;47,49;52;55 2009-01-25,15;22;28,46;50;55 2009-01-26,20;25;30,43;49;57 2009-01-27,26;29;31,43;50;58 2009-01-28,24;33;42,43;51;61 2009-01-29,27;32;37,44;56;70 2009-01-30,28;32;35,47;56;69 2009-01-31,21;25;28,44;52;69 2009-02-01,28;41;53,44;54;70 2009-02-02,33;43;52,47;57;69 2009-02-03,26;32;37,47;57;71 2009-02-04,18;25;31,47;55;67 2009-02-05,14;19;24,51;54;58 2009-02-06,17;25;32,51;53;55 2009-02-07,24;37;49,48;54;63 2009-02-08,38;49;59,45;50;56 2009-02-09,35;41;46,43;47;56 2009-02-10,33;42;50,38;47;54 2009-02-11,38;51;63,43;48;56 2009-02-12,44;51;57,44;48;55 2009-02-13,32;38;44,43;46;54 2009-02-14,29;35;41,43;48;54 2009-02-15,30;36;42,46;48;51 2009-02-16,29;35;40,47;49;56 2009-02-17,26;34;41,47;50;54 2009-02-18,32;38;43,45;52;60 2009-02-19,28;39;50,43;54;66 2009-02-20,24;29;34,49;57;68 2009-02-21,28;35;42,50;56;63 2009-02-22,32;38;43,53;55;58 2009-02-23,25;30;34,52;56;60 2009-02-24,23;30;37,50;52;53 2009-02-25,26;34;41,51;55;61 2009-02-26,35;43;50,48;54;60 2009-02-27,40;49;58,46;53;62 2009-02-28,32;43;54,50;56;66 2009-03-01,28;32;35,54;57;59 2009-03-02,18;23;28,53;56;61 2009-03-03,14;21;28,48;52;58 2009-03-04,19;27;35,44;50;58 2009-03-05,28;36;43,46;51;60 2009-03-06,38;46;54,47;52;58 2009-03-07,46;58;69,43;52;65 2009-03-08,44;53;62,50;53;60 2009-03-09,37;42;46, 2009-03-10,37;42;46, 2009-03-11,41;50;59, 2009-03-12,32;38;44, 2009-03-13,27;33;38, 2009-03-14,34;42;50, 2009-03-15,42;48;53, 2009-03-16,38;42;46, 2009-03-17,36;44;52, 2009-03-18,40;51;61, 2009-03-19,41;46;50, 2009-03-20,33;39;44, 2009-03-21,32;40;47,53;55;60 2009-03-22,36;45;53,45;50;58 2009-03-23,30;36;42,44;51;59 2009-03-24,29;38;47,43;54;66 2009-03-25,36;44;52,47;56;68 2009-03-26,40;43;45,51;58;68 2009-03-27,43;53;63,52;61;75 2009-03-28,42;48;53,51;59;71 2009-03-29,42;47;52,48;56;64 2009-03-30,42;47;51,51;57;68 2009-03-31,40;50;60,47;56;70 2009-04-01,42;46;50,50;57;68 2009-04-02,42;51;59,48;52;60 2009-04-03,44;53;61,47;53;61 2009-04-04,48;52;55,43;55;70 2009-04-05,45;56;66,46;61;80 2009-04-06,44;48;51,51;61;79 2009-04-07,40;45;50,51;55;63 2009-04-08,37;43;49,50;55;64 2009-04-09,38;50;61,50;53;58 2009-04-10,46;52;58,48;54;63 2009-04-11,43;47;50,46;53;61 2009-04-12,38;43;48,49;56;66 2009-04-13,36;47;57,49;56;65 2009-04-14,43;48;53,45;50;57 2009-04-15,43;49;54,46;46;47 2009-04-16,44;53;62,44;52;61 2009-04-17,44;57;69,46;54;70 2009-04-18,56;67;78,52;62;74 2009-04-19,47;54;61,51;70;92 2009-04-20,43;47;51,60;77;99 2009-04-21,47;52;56,64;78;93 2009-04-22,49;53;57,52;62;71 2009-04-23,45;51;57,50;54;61 2009-04-24,45;57;69,48;52;59 2009-04-25,50;69;88,45;52;60 2009-04-26,63;77;91,46;53;63 2009-04-27,53;66;79,48;51;57 2009-04-28,68;79;89,47;52;60 2009-04-29,50;59;68,46;53;61 2009-04-30,46;54;61,47;54;69 2009-05-01,54;63;72,51;56;62 2009-05-02,56;62;67,55;59;65 2009-05-03,53;56;58,53;59;66 2009-05-04,51;54;56,52;60;71 2009-05-05,51;54;56,57;61;69 2009-05-06,49;57;65,55;61;73 2009-05-07,52;63;73,51;61;72 2009-05-08,56;65;74,51;60;71 2009-05-09,57;68;78,50;57;69 2009-05-10,55;63;70,50;57;68 2009-05-11,51;59;66,50;56;65 2009-05-12,52;60;68,47;57;67 2009-05-13,51;60;68,50;59;70 2009-05-14,57;62;66,51;58;69 2009-05-15,58;66;74,51;58;67 2009-05-16,58;65;71,54;70;93 2009-05-17,53;59;64,55;70;92 2009-05-18,50;55;59,51;55;63 2009-05-19,50;60;69,51;56;64 2009-05-20,53;67;81,49;55;62 2009-05-21,64;73;81,49;54;65 2009-05-22,63;75;86,51;54;63 2009-05-23,59;68;76,50;52;56 2009-05-24,57;71;84,49;51;56 2009-05-25,64;73;81,49;54;63 2009-05-26,53;60;66,49;59;73 2009-05-27,52;56;60,52;57;64 2009-05-28,56;58;59,51;55;62 2009-05-29,55;65;74,52;57;63 2009-05-30,63;71;78,54;56;62 2009-05-31,58;70;82,53;55;60 2009-06-01,53;61;69,53;56;61 2009-06-02,63;72;80,52;57;68 2009-06-03,54;64;73,53;58;67 2009-06-04,54;62;69,55;59;67 2009-06-05,55;59;62,57;62;70 2009-06-06,58;68;78,57;61;68 2009-06-07,66;74;82,56;61;67 2009-06-08,63;70;77,55;58;62 2009-06-09,60;67;73,56;60;66 2009-06-10,59;62;65,57;61;69 2009-06-11,59;61;63,56;60;68 2009-06-12,62;71;80,56;60;67 2009-06-13,64;68;71,56;62;70 2009-06-14,61;67;72,57;62;69 2009-06-15,58;65;71,58;61;68 2009-06-16,56;62;68,57;59;66 2009-06-17,57;63;69,57;60;66 2009-06-18,62;64;66,56;63;75 2009-06-19,63;71;79,55;62;71 2009-06-20,63;68;72,53;59;69 2009-06-21,63;71;78,53;60;70 2009-06-22,67;71;75,54;62;75 2009-06-23,67;74;80,54;63;77 2009-06-24,66;71;75,54;59;69 2009-06-25,65;73;81,54;58;66 2009-06-26,65;75;85,53;60;70 2009-06-27,66;74;81,53;68;85 2009-06-28,68;75;82,60;68;78 2009-06-29,68;76;83,56;61;68 2009-06-30,67;76;84,56;61;68 2009-07-01,67;73;79,56;61;68 2009-07-02,66;72;77,56;60;66 2009-07-03,68;74;80,55;58;65 2009-07-04,68;75;81,54;60;67 2009-07-05,64;72;80,55;57;63 2009-07-06,68;77;85,54;60;69 2009-07-07,62;73;83,56;58;62 2009-07-08,61;69;77,52;62;71 2009-07-09,62;68;73,53;59;68 2009-07-10,63;69;75,54;59;66 2009-07-11,65;71;77,53;58;68 2009-07-12,66;75;83,53;60;70 2009-07-13,65;72;79,53;66;84 2009-07-14,66;74;81,58;68;86 2009-07-15,67;75;83,53;59;66 2009-07-16,73;79;85,52;57;69 2009-07-17,69;79;88,52;58;69 2009-07-18,71;78;84,54;60;74 2009-07-19,68;76;83,54;61;73 2009-07-20,68;75;82,54;57;62 2009-07-21,65;68;71,53;56;61 2009-07-22,67;75;82,52;56;60 2009-07-23,64;72;80,52;55;61 2009-07-24,65;73;80,53;56;62 2009-07-25,67;77;86,52;56;67 2009-07-26,71;79;86,53;57;72 2009-07-27,71;78;85,52;57;70 2009-07-28,73;80;87,55;58;63 2009-07-29,73;78;83,58;58;58 2009-07-30,74;81;87,56;60;65 2009-07-31,69;79;89,55;58;64 2009-08-01,69;77;85,55;58;64 2009-08-02,73;76;78,56;60;66 2009-08-03,72;79;85,56;62;70 2009-08-04,72;79;86,57;64;73 2009-08-05,74;82;89,58;62;72 2009-08-06,68;74;79,59;63;71 2009-08-07,67;74;80,58;63;71 2009-08-08,65;72;78,58;64;76 2009-08-09,71;76;81,57;66;82 2009-08-10,71;83;94,59;66;82 2009-08-11,77;85;92,58;61;66 2009-08-12,71;76;80,57;66;79 2009-08-13,70;72;74,57;63;74 2009-08-14,69;77;85,56;61;70 2009-08-15,73;81;89,55;62;76 2009-08-16,75;83;91,55;62;73 2009-08-17,77;85;93,55;58;64 2009-08-18,73;82;91,55;59;65 2009-08-19,75;83;91,57;59;65 2009-08-20,76;83;89,56;60;67 2009-08-21,75;84;92,56;62;78 2009-08-22,72;79;85,56;59;64 2009-08-23,75;81;86,55;58;65 2009-08-24,72;78;84,54;58;64 2009-08-25,71;79;86,55;60;70 2009-08-26,74;81;87,55;59;68 2009-08-27,68;74;80,53;66;87 2009-08-28,66;70;73,63;76;97 2009-08-29,67;70;73,58;73;87 2009-08-30,66;75;83,55;57;62 2009-08-31,64;68;72,54;59;69 2009-09-01,59;67;74,58;63;72 2009-09-02,61;69;77,61;70;90 2009-09-03,64;71;78,58;67;79 2009-09-04,65;74;83,54;59;66 2009-09-05,71;78;85,56;62;70 2009-09-06,64;69;74,57;62;69 2009-09-07,61;67;72,56;63;73 2009-09-08,65;70;75,55;61;71 2009-09-09,65;71;76,54;58;66 2009-09-10,61;66;71,55;65;82 2009-09-11,58;64;69,54;62;76 2009-09-12,65;67;68,58;60;64 2009-09-13,67;74;81,59;64;73 2009-09-14,65;72;79,58;63;69 2009-09-15,68;75;81,57;63;73 2009-09-16,63;67;71,58;63;72 2009-09-17,57;61;65,59;67;79 2009-09-18,58;68;77,62;70;89 2009-09-19,55;63;71,58;62;73 2009-09-20,57;66;75,57;63;76 2009-09-21,61;69;76,56;61;71 2009-09-22,66;71;75,55;62;77 2009-09-23,69;76;82,56;58;65 2009-09-24,68;75;82,55;58;65 2009-09-25,59;64;69,55;61;73 2009-09-26,55;61;66,53;66;87 2009-09-27,60;65;69,59;68;84 2009-09-28,60;67;74,56;59;63 2009-09-29,58;63;68,55;58;64 2009-09-30,51;56;60,51;58;70 2009-10-01,51;54;57,52;64;79 2009-10-02,49;58;66,55;63;77 2009-10-03,65;68;71,52;57;63 2009-10-04,59;68;76,50;55;62 2009-10-05,55;62;68,50;57;68 2009-10-06,54;61;68,50;60;74 2009-10-07,59;65;71,53;58;68 2009-10-08,55;60;64,54;57;62 2009-10-09,61;66;71,52;56;63 2009-10-10,54;61;68,51;54;59 2009-10-11,50;58;65,51;54;59 2009-10-12,46;51;55,56;60;66 2009-10-13,47;56;64,58;61;64 2009-10-14,44;49;53,63;66;76 2009-10-15,39;44;48,64;67;76 2009-10-16,39;44;49,59;67;79 2009-10-17,44;47;50,56;64;74 2009-10-18,43;45;47,56;59;63 2009-10-19,41;50;58,57;60;68 2009-10-20,47;58;68,56;59;67 2009-10-21,53;61;69,54;60;71 2009-10-22,55;66;76,57;62;72 2009-10-23,51;59;66,55;63;75 2009-10-24,53;60;67,55;61;74 2009-10-25,53;59;65,54;64;81 2009-10-26,49;55;61,57;62;71 2009-10-27,50;54;58,54;59;69 2009-10-28,51;55;58,53;58;67 2009-10-29,49;54;58,51;58;67 2009-10-30,52;56;60,52;59;74 2009-10-31,56;64;71,53;58;72 2009-11-01,50;55;60,52;63;77 2009-11-02,48;52;55,56;66;81 2009-11-03,46;54;62,56;66;80 2009-11-04,44;48;52,54;58;65 2009-11-05,44;49;53,57;61;68 2009-11-06,41;46;50,53;59;63 2009-11-07,38;46;53,49;56;65 2009-11-08,48;60;71,50;57;68 2009-11-09,48;58;67,49;56;64 2009-11-10,56;60;64,54;58;66 2009-11-11,49;54;58,54;58;65 2009-11-12,48;51;53,51;56;62 2009-11-13,49;52;55,48;53;60 2009-11-14,52;55;58,50;54;61 2009-11-15,54;59;63,44;53;65 2009-11-16,49;54;59,47;54;66 2009-11-17,46;51;55,45;54;64 2009-11-18,43;48;53,48;53;62 2009-11-19,48;55;61,44;53;63 2009-11-20,50;56;61,46;52;58 2009-11-21,47;52;57,47;51;59 2009-11-22,46;51;56,49;53;59 2009-11-23,45;53;60,44;54;66 2009-11-24,49;53;57,48;58;69 2009-11-25,49;51;53, 2009-11-26,49;52;54,50;55;67 2009-11-27,44;47;50,51;54;58 2009-11-28,44;48;51, 2009-11-29,42;51;59,49;60;71 2009-11-30,41;49;57,48;55;64 2009-12-01,38;43;48,46;52;64 2009-12-02,40;49;57,45;48;55 2009-12-03,51;59;66,46;50;58 2009-12-04,46;49;52,44;48;57 2009-12-05,37;42;47, dygraphs-2.2.1/docs/options.css010064400000000000000000000025171437353256000134360ustar00/* * CSS for the options reference page. * Putting it in its own file allows for iteration w/o re-running * generate-documentation.py */ p.option { padding-left: 25px; } div.parameters { padding-left: 15px; margin-bottom: 10px; } div.parameters table { width: 100%; table-layout: auto; } div.parameters th { font-style: italic; font-weight: normal; text-align: right; vertical-align: top; width: 2em; /* more due to nowrap and table-layout=auto */ white-space: nowrap; padding-right: 0.5em; } table.gallerylinks { width: 100%; table-layout: auto; margin-bottom: 12px; } table.gallerylinks th { font-style: normal; font-weight: normal; text-align: right; vertical-align: top; width: 2em; /* more due to nowrap and table-layout=auto */ white-space: nowrap; padding-right: 0.5em; } #main { margin-top: 0; } .row { margin-top: 0; } .dygraphs-side-nav .nav>li>a { padding-top: 2px; padding-bottom: 2px; } .dygraphs-side-nav { background: #f9f9f9; border-radius: 5px; display: table; overflow-y: scroll; min-width: 200px; } /* This prevents the sidenav from floating on top of the content on small * screens, tablet and mobile. * The 1199px are the -lg- grid size. */ @media screen and (max-width:1199px) { .dygraphs-side-nav.affix { position: relative; } } .extlink { color: #3300FF; } dygraphs-2.2.1/docs/per-axis.html010064400000000000000000000057721437353256000136550ustar00

dygraphs per-series and per-axis options

When you create a Dygraph object, your code looks something like this:

  g = new Dygraph(document.getElementById("div"),
                  data,
                  { options });

This document is about some of the values you can put in the options parameter.

per-series options

Typically, an option applies to the whole chart: if you set the strokeWidth option, it will apply to all data-series equally:

  g = new Dygraph(document.getElementById("div"),
                  "X,Y1,Y2,Y3\n" +
                  "1,2,3,4\n" +
                  ...,
                  {
                    strokeWidth: 5
                  });

Some options, however, can be applied on a per-series or a per-axis basis. For instance, to set three different strokeWidths, you could write:

  g = new Dygraph(document.getElementById("div"),
                  "X,Y1,Y2,Y3\n" +
                  "1,2,3,4\n" +
                  ...,
                  {
                    strokeWidth: 5,  // default stroke width
                    series: {
                      Y1: {
                        strokeWidth: 3  // Y1 gets a special value.
                      },
                      Y3: {
                        strokeWidth: 1  // so does Y3.
                      }
                    }
                  });

The result of these options is that Y1 will have a strokeWidth of 1, Y2 will have a strokeWidth of 5 and Y3 will have a strokeWidth of 1. You can see a demonstration of this here.

per-axis options

Some options make more sense when applied to an entire axis, rather than to individual series. For instance, the axisLabelFormatter option lets you specify a function for format the labels on axis tick marks for display. You might want one function for the x-axis and another one for the y-axis.

Here's how you can do that:

  g = new Dygraph(document.getElementById("div"),
                  "X,Y1,Y2,Y3\n" +
                  "1,2,3,4\n" +
                  ...,
                  {
                    axes: {
                      x: {
                        axisLabelFormatter: function(x) {
                          return 'x' + x;
                        }
                      },
                      y: {
                        axisLabelFormatter: function(y) {
                          return 'y' + y;
                        }
                      }
                    }
                  });

The keys in the 'axes' option are always 'x', 'y' and, if you have a secondary y-axis, 'y2'. If you set the "axisLabelFormatter" option at the top level, it will apply to all axes.

To see this in practice, check out the two-axes test.

dygraphs-2.2.1/docs/site.css010064400000000000000000000051411437353256000127030ustar00body { /* required for fixed header */ padding-top: 50px; } #header { background: #F9F9F9; margin-bottom: 10px; border-bottom: 1px solid lightgray; } #main { padding-top: 1ex; } code { white-space: pre-wrap; color: black; background: transparent; } #demodiv { width: 100%; height: 320px; margin-bottom: 10px; } @media screen and (max-width: 768px) { /* aspect ratio is weird on phones */ #demodiv { height: 200px; } } .dygraph-axis-label { font-size: 12px; } .dygraph-title { font-size: 16px; } h1{ margin-top: 20px; margin-bottom: 20px; } pre{ background: #F9F9F9; } /* #main { margin-top: 30px; } .row { margin-top: 30px; } */ .row.smalltop { margin-top: 10px; } .thinborder { border-width: 1px; border-spacing: 0px; border-style: solid; border-color: black; border-collapse: collapse; } .thinborder td, .thinborder th { border-width: 1px; padding: 5px; border-style: solid; border-color: black; } #current-release { font-size: 18px; background: #F9F9F9; /* padding: 10px 15px; border: 1px dashed black; */ display: table; } #current-release p:last-child { margin-bottom: 0; } /* * When you visit dygraphs.com/options.html#errorBars, the fixed top nav * obscures the top 50px of content for that anchor. This is a workaround. * See https://stackoverflow.com/a/55683966/2171120 */ :target { scroll-margin-top: 50px; } a.link { visibility: hidden; text-decoration: none; color: #777777; } .option:hover a.link { visibility: visible; } /* stuff for specific pages */ /* annotations.html */ .annotationpage code, .annotationpage pre { white-space: pre; } .annotationpage .annotation { font-size: 12px !important; } .annotationpage .dygraph-legend { width: 350px; } .annotationpage table { margin-bottom: 10px; } .annotationpage table i { margin-right: 1ex; } /* index.html */ pre.prettyprint { margin-top: 28px; } /* data.html */ code.sitedata { white-space: pre; border: 1px dashed black; display: block; } pre.sitedata { white-space: pre; border: 1px dashed black; } /* tutorial.html */ .tutannotation { font-size: 12px !important; } /* versions.html */ .versions { margin-top: 20px; } .versions td { vertical-align: top; } .versions td.notes { padding-bottom: 20px; } .versions td:first-child { text-align: right; padding-right: 20px; font-weight: bold; } .versions .date { font-size: small; color: darkgray; font-weight: normal; } .moveup { /* match margin-bottom on p/ul in bootstrap */ margin-top: -10px; } .versions ul { padding-left: 1.2em; } .versions td.notes h4:first-child { margin-top: 0; } dygraphs-2.2.1/docs/smooth-plotter-p1-recording.gif010064400000000000000000013557521437353256000172250ustar00GIF87a/?Xs///=[?W__/o/psW  !!"""''+++,,..?.////?2/t//W///1s_12[22547455?6/\6/66/9];<MA p]j[tҕB"OyRlGl\[p{ϟ#+fO53 'Nؐ >$\S:g@ ȆҕnLB`z` F}!O%N2 >"ȉUxB=b\53x% PeD?(@enXv|tp jb@Lղeb#R9HB0&#]]29\sEW]6hMiRI@#z2`\P x1m5*sZj{ଧ#;3@swwZ *s;&9ȱ@tݴpc xМsWKĺqo KzD[" }#V K^m\Õ rk4ߺ{PMJ9AWvu 1CX`>`N_0zSYZ#X@%+JX16_m+m(9,]Ɂ=^F5~p&r%TN?tוXU2JZR(N=юUd(LD)6K򕰌,gIZ̥.w^ 0IbL2f:Ќ4IjZ̦6nz 8IrL:v~ @JЂ)UV~R(B2PT,rQt4e!D9=x xCCKc,4(MJ~4aUKӉ4 ՈXS\G"BgbL!hQUlBYQ@vReZ[GaV"@h(!uMFPD )-Āe}8Cl0o`JZ_65]di ^b@e# la[<=gԣ=Gb hP{C iWXf9bæX;կ,qnPb'!&5]oJ9vP dv+YV!Z ;؝aX۞a80g;bkc~f ػ~.Rצ7n+gamGpl\IE%XU-,3,SU~/;@ĚX#?FDYgbŸm|̕@May hõ-D1լ-bJ-d>RhDC8S`@*{$9Ue*7k63_ L`92^m~ed8XwvY]E~q7=S jmBـw.k0³4|24g Fz{"y`!YAu6'4tW~Gb<}އ*0?KEWTwsbb4@ e~yN1z8H\qU;y w8 `8H ؀MD/J ~ 1s-l/WaY1#wU9 XX ~z8À o\_cAnMbh>@/ 7<%pvӈv 4Ay0GGR(w.`+ԉ8|00ѱ^8Sp>j `!xx鴄xw\1>AoE\Y\zw;e8|$i.m  y `7-A rme  "[ hxC= 05yfvՒ؉6 5p rP0%Ȓ+^"n8YxvL9w =W{{:69@BӳϨ$CJLNPԍ0 ŃAIW*ԧaϱ]=[`}.x/S?mmaŠt]v}ׯJl`x׀؂=ةŽ)rk[@@ySM֛t9;OiJL#F }A8Gp?/I&}m9; At<æ頔0Hl޲8TTPۊuR+  Pd] <|(3`; '@1 ظ=nv0 bdϝ0 MzOPE=)4BrM `*msz T0L06 |4 =.KW@LKd P~k C^Q~5XQ?Ad^^`bkmogiqwny^{AQT }>. LڰNL8R* ߲_L~Jn惮MaY Pn,L! LPaHH>;W~0 a*@ ~8M!ʅ1-P3ӟ °nn ?P @P*PIP~#M10G`Kɞ7j%>0n@/mL1JIIL>_~@?p`G` Wp0`c_pP鹠~.0~ }o9ʄ3LN I0GO>NVV#~`0o# h0@|oZAQQpK NlF_pn.*J1x*PON_aK5?<$X n7=z%5Æ?2iIp_ U`0!#%N>o eLD%S&JQNZUYGa[6*Yi UV ۫>*^;fFmR6| gW@H('D'f6c6 $ ;Zni$ r٣ mܹu;-\]:Uǫ&*Ź^0qE..}+&/ {9?@asSjB+T9t,NB /LHñ8$HvBD )C{KEbDc1g< EqPG yqƍF"4|Kɵ \RƻFG)r$/ S1L4Ӵ#,=TTjֳVi%pN0b)Z֌Ԡ ;0IX*U  uC7+b N*OF()+ fkWGR*qZL*H _vթhC뛄UT Ij)jnk]z=`O[YtG[<6{Mz [W&*,\KI^w,hRC551/U*u?7+}-Cܯ'M`J-2'60FVMz* OEy>svS _#6-t A -?\\x8o#S޺1{j8Zp|S·r2Cph.&mnZU[KM9ˊfmfulLjEW&`,dQOw\2jJXG\BmH1o;@Ԯ[5"{φ>0 C#%mRMh 6W-+u08k5w}o|[w#F7'nQt[\;3g@[}cn9G;8|9.;h=m7V"d^B=uFcnt7?uyX.b*v>Dx7 MK/kvG ~&/OKu| ý( TQ驺t/ *vYJ,Zг}y7KO,>RwxZ|ԅ|;bF5z޴_/t6Bi| ܵW oGtC}ľG粸w }Z6 D|՛_8-K꧟D[<+?;?[@J-"Kik4@˫ H 0 峾R4?َ? :[ :,>Ĵ lA` bk:94)A?r' &4 0+BHZCd@ $=9| >\+4 ">ߓ( &۠ݒDOJ"JJ7 DA$1$CɳBG4A#RJCU\E: P.6T+[K-E C7iAF|hSD9^C k0f;W4E"i2h2GiT?ìBҤD$&ndG *IE[h$D`;jWԠ\0s9g zq qSaȲȳ3H0 IdȆIDd3Dkɞ h2퓑t;41JSʥJG,7jP!ʬʮ]4rKȠ42@HK{t1-  /P3 _mNk*eQ4Ҷö6􊿘˥40a?( |@, }DXy:X8NK@ U00N-US=UTM҅AMUх9N(}U{PO] 8A&œ/ yf=% >޼9] m7ۚkė:]'!X%}HЪ8cOH $R ={% Nx:P N@0[MX;N X&2"@3Qk9?uQ@ }S]c֎Z\WQ="ͼ W^hxOl1ц^H.$V΅ ȪLlԖ~;mӖ1vJik2"hncF+O@.mn3~U&mkj!<^[F\5F+%Em}uLkny5_+`va8>j{x?քA&nr*0 f0iAd+l偠9iq7F0 RpXa U=ogr.<-trq`Fm/W#r}p?1/mV1P݃l5/7qp87r&?]0Fs]E,/&)۲\E%=7RF7\>s3gCcNu ,RGeCp' *%uHZ1RU{Ƅ=0ԓcYaomٚFZSF۞`EmV}[:@?+rYzI /{JiG o~[+J TVKwuw*wysUz8 h~kVJ_md6HyyQRCa%Wo9L~Ѵy,P}G(TywZ׬eה;V^ߌ՚zw:e>H6@cpx4POdע:2VrpVެ_@HUh^JS"s@o?{8: BE^)7EoxbKrXֱJnɧ':>0zm ڹoErP  %?'n/x%m(5E[ {iظz[5g+?p?[n5@T-nWqz$A)'Rh"Ɗ3rHP$JSl9q˘2gҴX&Ό0Ob@-9/8$ʱS=%CxTB "$0bTP\yD1Ǫuɶ-ܸoʝ6 ~ޥW Yc^?f X٭v|^eТ3E?dBb [j[dɷQ lLeKKoRD^a+PhcI(By%qYaH"@Fzs~, pZv{Zڨ+ӣyr"i6&KkZӬ_T%GRK,.NS}6U%ԒB؉'h?,Х>ԥ 0.fvXt Цߊvu")[8B0lGqtL2hw? j'OǮ޲|3CQ`[rJG㭣G_JIx&Z]PHPF-yBܙCZ"cÓ2{U1]u@leXb.^2\d"%S?\wWcQHa=wDU٧WǚpmޟD=tk-2Q>{@㸯K ݒ2$W<mc7?/Z3rFlIHP~}U"O]Qna ¥y{_ggX'ۻ]Yn0}fšֹ ݈D,؝ \4 rhh9j]x"F *&QEm)gn:wY. F@cKИF%/jA<)ODh$7LjL!F0U*Ar8[AyaPDy&'&E#>\9$dj!*$V^J X,K];{xA drsl2UYFjl"1%j{k]n@^e+%l_*)fn 0LzAgv4*H.umY5#m8&Sq];yI֤k&j).m#L#*:ҩ㉿TMځӢj^lqqI6"}EkTXUѯeP* p^PWE) YjX$֮D`Lm6&G󵓉E-dmYJ$2%`JُA1:p`%< ҆gđZ  Ȩ͉xF>-sw{;(APIx`" c/8.$bHL=uע^$b}9+^p n lK'z5߲tA# ^3BL}Wh]>{!YQ>1,!C"3N~2EVvrd`U4^2/ȷ*1m e~3,9 aIܘϠ2h5yG "0?5\ 쑪(&"[VTC+80~Jq?:vUr$:%)ThY:.!(k>Eni!J gh#dd!aYW븘*6ZrPܝ>/E8+0%^AeSZo[=\vd5_n0r ^ܢYLj"&X@TZqqh E]s·@Ab_^IShHG7'Fa!ѳv0y~9: myF E@gv_M`KjY&qم0!qp^3<ԇ2sacBmā]"UhtH@g%#wBCu$M\QD!D"I,s:kn'w"'d^ABmhN`N^znj^y(5PfseR5҆2~rhJ [bhF$UxG8|L (|gi,pPw5Mń](>(9iU_PX"gnn'IGŊ(\N&hG ^SUbKjhiKSf(N`E㔲QpB c$P/"e5Q(J .(JN?6Q5HEjd/9jb*FʟTZ2~i***ƩJkhnihUXfM/`G ǟT~v"JFN됂kNPG;A*AkKzFX)`;xqWVBaxBg ElGdS^ܨl6R8ĭ!D؃DkiTPIF+U]Wkny@[zMXM>F ĺYClk=p@؍b.,BPˊښmYI5A-.6}9!Cgfn.v~^Ĩ.BaʅәBLe֭!#DdП!GLUmn .R-BX=D,o[ٌLzbd^^RozDi֬\i+GҮf2^(kmk/\0V<$acxm3/d+=n`Q6p:c!yB[j j0 p0oPxˮ.? /q+o\9g0n pc/\hDjv::mp~IV1/{ZĀh K;qMunRq o dkODqұj-V0rrڰS$/)mo*8^fOwk*4iFL6:%hHok$~#{~xs k~~s‡4BCL spl ;[? 4xaBJ)tbDa&Ix1=u\cHmy2= `eLALʴF/hhPkN*"HN%L7'5'uyU拯`COT"qPcۚJµ{׌|߈.hCvK`L aZȖN-/Kҫ^,ll|,{ݺU+}C]Z|FA 7Bev^e%uwH{xhG<}O~|׷~ <\|% - 5ܐ=E !,i6- ho*\Ȱa#JpŋګHS2@^(1$_$PY2 $FM=E,rŃTa%B`w+hBd~"[K8uoqDA ~:;[$D:U](h&L u?]} yނ|)w8d z@mJ2n(H Ĩ]S9a#b}9"yP?HV޸qr wvv!3HSA28%bmgO]AWqDHMu#)`pC$QWi`Ou.G8RTGt${>xe^!TG]u$@RNgm|_/[ޒ-u8ɝDhwuf-f~H?H ?L矁ڃ.S`UtC?Ihvg=A=ȤyK{!icVV!meP\z?p^&qUj*X=xV)6vum9LuE9CTz=ɨB,I/Zqf'^S]^ }9꥘$Pe -6IѩftNˢ!SYJ$1I;ڽ$sk '@Ou M8¾>l0 Üʒt<ܱ:Jr.hٵEZ24I# 4x;ςæPAy \c]pn(A|e)A)1ؐa@fgSTFvH:2)dյbEm9Rg\*lSp y_D.' W 28=D9J6Y}Rv^Ӫ EHTiq+?`TBlK"y8"$H8JRLQsl#L*WV򕰌,gIZ̥.wJYxbl=6, @<HaK2r%g4XMGu*љ {b8r`^}w 7s4JI/@N;oU!xaCMHHH_HP! S謂bɡȩ*/)Iٙ DY9Fwڴ5ua4+J|@9ԢVIͪOuz%388P `Px%֘:AzzCPv#t<{= ]N2o&W9vF+M0PttPlIjQq]YgWBdRO1[RA?ֵ UgKͣcnJ^ˮl'Llԍ6w_@$X/ k;HڲӦp(>&E|+pd3\vKg+>t8ML*Q-2>EZ&fG!1CwĘw Fprc$qtE|"!U3Mujn/u=>]9Fz hpN}`*vWKe)Ä'JD c/yW|! y:XA/S)QK/?]ٷ_ Rl&=8|l 2b`z@z= zgj}oɏG#8gn/n=3c[ 05,Rumg~k'2zg$Wq §z| f{kQ)` /Nym'n#( -X~k(`Wgc>hi'Egt hvq5^*(nRqJxǃo!z)hn@dm'} pTu$p}aHT lmN.QWpn(0~ցnq({V$lF(`JV f\tpix@(HqcsdH|Kxʒf؈rɘmgg&F.(tghm6uhrdaX}Uҋ^rkȍ(qY'i(h,?UOtZˡ`K T-iFf? E@?Kv,:m 0,QsT%_PVEl`"3Of,9{6B9̸<׋G2> u 'ȗj>PD9pJ#v ('D Vz*soj5}a QTi$`8ŪKD=U h*xEUr "r`I`W YHF{ȷ@v,>,wu |=|{Q ȳ{m{#xބ%r48¸p},S W'xƻIו#O`2RXlfPim! %˨ɒ!vάlxd^eaOz˷ A|j3د*8KԀ˼0AuХ,T| ¯Q8r1X}R}t(@"E? n'P oI\{"%ݚH _pV{@1p  @l}[!Aw ׽H3]δd7}tYm!Џ0.'h_vNqq [YG¼C>ӹ '0ޝ?XWa#ܛ^޽, (?8d? 8ݭ 9q0._ jʦ'oidKw _L*H1M o&l0{]B<y'}M`loGuB 2e Ņ;DP 4_IOd/_=Ι]â8 |n=вp/rg?O/88?/pͯbKt~g]J&oc 'O_v h? 3(? tIA .dhC%NE-fqF!w _DRĖ/e~YsM1/jN\8*hѕG\Q)ĒISk׊y2WѦɶڳoM*\ שߊ&\`dzcȑ%O\e̙5oٳ^n+2.`{˺qյis]shT"bl'_aJ.uFP6ࡊh%C2ʤ "EpJ%{*~"K0r+CsI:4N'־SM&m6æ4[6Π BOE J)!`Q A=(0k5?UUbCi_}u@/\XWQ%/CiZ]V؃vԨiM"lM/Y8DhȃBԏ[yw:zav%8nC,='K3U9 Ɍ`-nd=bctx)d(σ^8]d pЃ~܏A^eD+O K_u瞊\+hoVRb#~ڊQXm[cl-_[knA'>3k;/i3G='' 0 +\0xpr` [wuc}vkvs}w{wI7xW~ywy!, 'T8aB{T)Hb :h/7*1Fy!ɕ&]RL C'MpE[4*hOB2lw*BL[qO9iU^֫|n$^]:4;O ՛oC9;a@!,V= J:Ȱ[IHqÆTUHbuHS d@sF$) *.6)D>h Nm㒎=AkT)FI:(΃85} lT U:kYhڛv*d@'d9i6T'3F=+ǩeNYEF HR=mf s:"Fn9kҰMM`N{]#Tbr%?N=zʯ=;w޿#I?qUz?_~x{]o>` H%}ǠoVay]Jh^@!,V ԦJ)‡ rB ñLj EdɅfnL˗/퍳g-mr)&z4*)Bi͋ua;M>U*0P#Lxa#„hNP2 -:!ޤqԖ*ƅzktE9ۍlX ș0H]!,X ʞ Tho\:#YȰѭo $2m C6ǁ#'mR ^/lHaBY&™[ ;=$u*I*)䪫zS㬲BFhIꈤ˘ GjJbT $P #4ۜ;AЮjϮGG մ.Bpr`~ߴBVw|yJ@v..a8Ip?sySAʚKȴo6kp mNS4e_1ZBin ͝;یks1?߫ͭ)>-b-q-J*(#b.ӭ*s8Ůx* $ҭ>z91mNa"1*H$w2j 𥻝 `l`P?oVWw%rax%{bz֩h}lڀEFIG>ƱhÆ8+BvIBȅ=\ƶpANEe.ҖI0iK8 sN1_L4V5MeV=ϴH6]xFXvOf8'oFhf&WɴhӜRaI'DI'9ilPi@JЂMBІ:D* 0| BD%<}ˣŊ0xQJ9( a 4&єӦS)觊᪀vr+:-@nxmWB%*N?:ULh@&zkP lZU+hخ#\I`[֠QR`1fqy} kb'*YsuW +NrIJ+M !dTP~IJ|>2#lK2+]]. Lfdڌ׸ɭ$S͋!Hc?[V%ck0jOYhT@;X&xB]u㲥!M-ͫ_w!n+`t5%hz{yEװ僥Wl۝C@` ~gWZp `S*`ot+R16a`r1D_yū1TV IR;ZKdJMn$-7}|:XI+>PUx?]ĉ/LBwR@O$n4eʦQUYΖ3O"Vg\NH^ 3wJzzN}CH>j ׽Kiu3w] 2bpX.Ƨt`m}_6~~mA}Ax-z|WqU}/&~.~62}Ƿ 8mw>{L#+P4e3`,؂`4Tu-H@6~ |lp-2P*pXi+6v68grs(p]zQz1genCƅtsxbJex+7痄m8'.S q8fb_Mf^0oA׈z#@&+8`|PzƆUkD|aeyvV(~x"+ ƊL+e*gO(8m v1qsbhgXkdq-aYve}(qV \6H G*FhMG2qsyԒEG@Qp;ߨ[JY|qT##\,w6[ I-H~ȏtK66aA*Χ0}8?2EKY`Ef7"n?o3 o7~5ZRwMm#h4ɧ^d~Q+~q/hxIP! ט B9I)t\F9>hI\Ȑnc C29"+y_)v6Yf7>Pyi+e@%'{i2/9ɝi؝Cv]bڹɖ`tؙhiByhhީ Y@;g]I.wPjٞN\ʖy)jRDd OR ڟL  .Q-z ]2=>hUy?yG*O2 wE)YL)-SZ\:ဟpZV @DrdZ@zZFHꀫU'ꨦuk(]@j| dZwoʦLi }٩z {g:{yd8Ǫ顯x97:ZjɊ`j'j F: @z'JѤ{fڠyTWt r w`P &j:,9Q#:q {P0 {Z.chaz"} lI%E8d`bzC+GT 5"V9p!,;TC? H*\ȰÇSը*{3jȱǏ CIɓ(S\ɒIk8sɳϟ@ 薽Hݚ4ӧPJJjGUuM`ÊKY[˶mvgʝKF{u˷߿ Lq*eǐ#KLˍh3I>|TMӨS^ͺ׬UqB:N1[m|`ͻ N*><+@Cl`^ȴOӫ_Ͼ.Zv;>Ͽ(L/,=>0A42· ($h()jcfl 8<@yc#L)ha?SPNӏ=UqXfh/13hlwGbTvP%A!`'b i衈(L>ϟ"'tG駠 ֢H@0F䩨'Yir2jOH?v4p7juL ZFsX9 ϰ҂t,jci覫B`m T/܂LKjK %߅qA/wnW =`X E '(aό{̈́:lhO#%`LţH\90F5ڣ(Lr!|ij )χy$'$e8K.dF*ъ7Z$ C>Rr#PU>}Ӝ 9 sfG 2&A =QQ^ HI.d*ؔgI@%R=QT ^o 12}¡TxC_ПLqH$c7N)(10xQc 5>6lTl`IxH2$U3bJ'cޔo % #Hg,bcMHݳr <= H ` S)|G$2q+hcr 4p5 3x̻3^.&J YShBR~w\`'u.4YҗE)l OܺB Ķ$ 6vhIϖR5CYp{}]nh n+v[[FŻW3oyɻ^^׽}.t]wm z_/|+_V$.z_Ve^p}, Smp56 7o4 #Iŕ=fL8αw@L"HNC B8ĒL*)ۉLSb;2N8' S-ysP$GCl!Bg-zfp}brBD62h<OR4x?Hlss|TYBoCW|=E>SC̎r>I{pNJh2>Y^|SXvu{^~uG'cR zH&l?yʗ79N /7N!o}G&yrz:ayqv|W~xXgn? E8'|gtk}14|!4%h~痀4}FaDEZGgVwtQWoH]wJhַKpy4X~Ǘn;fV/UG_ȄkȆCwP oȅ~:Zȇ[wwsF kWYȉnHXZ~zŊ&ՀH aDpk8ft9!9c@xG4{X(y*QA3X;0.Pr(m؅8~-1X~A@c8 F>:ADe uXX8aD׍i89M*?֤a8^pX)# ~*cX/ X%9Is @X2ihCY$A:MvxiUR9TYVyXZIc G ųfc5DI}tƨ1j (nk&9at<`DٖI @O`i}!1XL"k ) 闯 >Tɚz陟Y>wXM(xdis9 у7$a  jɗQ$ ڝ zA9 СoBɠAǡ(ȣ8C>G#!9@) ⩤l[yi,c Lڤ[ڥZ$9H<:z\^E>"%aRVzl,ڢoqIv_D׹k쉧z`&!9L5Sje췂ڠ;^F&@w:g:퐦KꪯaJ%>p<ڨO 霭Ap:᫝ڦ?ʧ}ʟ(aHJH*n*ڬ)Op#!3(9FЭ*9ܰ[⺧犮ĊXz&5V`(ڣa걄*` pN#z&Xʨ'ag} !Y[[_a;ID'ApIbZ)Fԓio{8jL7^ w̤ ][Kck{[t kR;@ q+iʁǘiF44˹۪ s[?;39мK.r xXC3i[:Y;̺D&wӱ њɻ6(d _4K5Ӫ˸:,qz7_@0Y3,uKE'̄ PA0|[ kGy*[@};xoD249BL1\Y AO<)1di2YW`3 9{p\*}&PA-R8q߫k rWY(68M2|FH o=/뫝J3"S |G79 *Qtϼ|G F)ad3˂j=J$9֋[#=:ELf,޴(R(G ڷU، njs̬:U;6 m׵ Qj]dS):mڊ:}[k: ,aۯ7zWw)Mn8 IMSf8E w8T~1y:ArG!Cܱ Hm.YtRMQsȬAS$94#ޠ9V;ԹqMXmtT/ N=q=:}P/HGA2 ԉ;טN L+Y<Xn:kcFmG/7R*:1bW;|O}^~8G:[b sFgloESWG|~aT;EP=GBE>۷TKH>./DDs9HlFn9ۜ!-ѾVCȣ~<`T[ao07L*Ҏ~@85gCE!՛c;zn:vwo>@ւ OnFG1`e竈AYVq]#MbjB>l՞ v0 &y 1X0t4/aomk1 t^סZm1{?z;vΞsL_݋9'Xjs?\HNu_{ GH -*oElE,&yDVU7`6/)=ڜn3ģJWn 6آ8=oF{Hz>pM$@J7S)+rÖ&x;4N0 r I!*Pg([;"tQ?cDX2ٰ"UI),P0+o4FĎLyS"|XH!=,P.2/HಇlL7=V: "sbXдQFg46Czd FDzWEV>mslXVϔHHXR:AdCYo6g3?MZ [Ȃ/L`y7NAC7˝e¦kkqNx'_[ Ո6'@$נS|Amw )1)sdueM]ƅB+cevo[v-f쥕RQ`_d_SYm _WXֽnLTk'f%4Y+YF5tQ`Do^((' Ը*B; $!Fd\߸GvlLdvi}/\5Ύu؂d1jH$fx75SP+O1s$~{N0v4yjFg ZrLϐ]I~g-;3 UͰ YV l=ٲ!q8䵜# eθ>ŋ, ;Psñg\nm_ 6BGi1մ1&zvnr;*cX&8fyr GɯE?2<^V~)+QthwO*0ym|,S\}y'~-vsW849J ^lV9ބ5{2IN߸ȫ* _[S'HT|ݓl(*O'-.2=QRު S :[4 HC +{?Z `ȅ6b J"I}s {p# ;Pu[B&Y :+!A8(V& Zr2*50h5C/|wæ Rd\4#] <=gD|{ѓED8 Dyˉe=Da$ȑXABĀV<Q[ )L8/8!h2p?a̲Hl9sVrSvTB:Z{C=0 ú54Y 0CT&ĈR1yG”9(`F#lé= {[≽Ld $FʘAH0 8$GLU7+IPJH(DDp8p? \iFWtAwagƝ̈x0C!%X!H,|M=OؔxēϞhGQHD /ȅCP $ÉqP͞G Ǔ0&!Ȁ /Ʉu QN±OS̉AiЌ "G.C S`3%REK?n`~`` N_ѹm_m^(h] VRr^Pڡa8ޱ%[(ǴUaMaOa M߯pu<U)w_- 1=MbbF .b0 50>c* Ϋ |&a/^͵b:c4&b12_b^mؒD5Bb1 S͋Ui@<^I6cJ0^0,.e6dS^3Dc%?aPcBdZW.V]*fQU9[)>Z]?nK@Xm`Zf\{M Gfo&fvfmeut[$W_gfĸcLdmc2hgfn܍dV\6h%Rf[#fF1S YVFۆ.^]}u]iePY}ifv e~Va c"&a6f扤g扙ar@jjijwg@#}4ʭ#^.Vi2k4>(0 )]fk F\ Hfdb6l.^HlNluy驶kl֎pxPe!"j߭NikqF=ܝ(m>mf !9P&mF f"(nЮm^.y 0 "np. >.>lߴmӆ. 1~r"wXd@ o.r?9:g :0FH $tt  ؀3tMws0_uVXuYuZu[u\u]u^u__7ZQ+vc?vdOve_vfovgvhvi_!,;TC? H*\Ȱ!A{ITeϡŋ3jȱǏ CIɓ(SnMӨSخi> G۸sͻ NjU|u^/v 7٘;`sFiOӫ7WvėGHv@Ͽ(h&^{0B=>0A42Έ$h(ڸWVڜ2+ 5<@)DiB6vQ/3HI?أAfpc cZm5ȗlp)lRd#=h!l)Q6@QH裐FJzVDT3djݡ3 '*r2wꪬD4LQXe:'klANҝ?v4l7_݊+Ț4"f#+2 (@bݻ3mFd2G nl\`Q〰PY."gqUY?P2/\@f 3r/ c",+ń:Suԋz@Z<P7TǠ 号xlMEGLQ-5 5xh4L]ԶSo7b׽amT0q6M4PUb{mC+ITyOcxњ6Z/\zd*!ظ=CnjU{lߔNM $EX#ԞAzR`;HW$~X^4dV+OC/ut:IKH?fۥ#Hv@y[h->ӻCg- G.~HT@$BC@9r0 @9x ~![@A#|z(mdMʹ^‹8ū}b%$@t ` :EG@"W ۞=l.|Tb&$)$瞇(ˆ"\DLBHѕ,gq1?^Cuaޯ"; vɼaњ̦@1Ŕg=1~DdBm8թKD5WֶrZē.mq kܼ'G͔% HkRVwustĒj7 E<y9~S)xhP[nyUt{wp8tNѿ:vOo W]j ҆[:`nwBپ%AxS~0!h+~=BW]Li yC>{pהm/]Gw?|ϯ{r K>&=׉pK(;Ga=S+,${7{K7^·?!Ccg}ׇ~h|Dkwes  Xq'{h@AW:!Gtᇀ@F0 nW|*h;ȃ,h~G\qi7l'z><T:2x xgMȅ1 xUSk^׃odžqt G :WIzJhsz؇M/}dXHpmn{tN91Yͣw|yȄ4QxPGh؈ȇah&@6cȉ燊q8(؋mdeIQw؊˸~Ȋ=ChihNj~;8;`xXh x91x8x&=7 y8蘎 yh&Smd)H"Y縍t޸U W$Y&'B-;4I7(i)!P 96&gR9TYVyXZSG o\YVAH9Eق(f[xx39Gi'!G_f:?ɗw_`ǓY J3 QJ׹ $@`E786^1*AJSʔ'aBw!Z%*OJcڹY*[0*R:r )/&|ڦSqZ oH y)(Q0bZdZD A;ep]\wMB*:y(깟U!駃:Jxӥ q J* =+rɫS( tthM YR$[OK8@. &DǢs ;M c G6Q f?>֓tLv{7z+k+ <5+'rK'i9cu7J빑*AWw0 @+9  n;mB* \ w`H _ʻKA;FptxqEݫkۼ+[hۓ-qVѹ;@`狾};*1s3tA+U`|k;:# ,[ d[*G>됽E1Z/FLр1E̙*Wfr&TG!;6ANܩ =wp8y^XT-Gu StʒoTZ0xmz?ִ} QY =p=⽾B~v }c]3l 63C]3eؑ?D?:o M%j=S1$"mS?\m&\^=> @@1`.f|:An gj\a&H>Y=dp:io-{-N:=l-Deݶ #vxr|E8Ck9:*E]?.Hܖg{S32YZTxL*uwA*3Θ$I 34E>0aIh>} lOƘ:^O.A̫cK%#Y1ڃ=Ώn3d s.c Snп>`qm2w _|B-/w$ 'N^ =&o8-b'}JwBpZ J&$@,hJbGGE]K _&ĤO_;18͝,A"N_b5/ *M!Gv.p_=1HܢT|~3 >A)g#~p }ҧZzrWWֽVuRnf% ;58բQN"^ْ?j~rnHď2yROx/:>k@G_rq,AZ$XA SC #NaB,TbG ?$NIGh@\i- ʱXO?y? xiHO9:i ?n[Tuؕ׳4MIe4;!UM@֭r=$AC^+V@ODSlɾHEc1EdM!qG{qH"4#/?l`c: 2 П4)% $" 1L 1* kn4 6Pn A<bRQ3Q*CzQ0)%P봻eS5S%h TaJPT 8h:*6[ȀOd-%UoI$TIQ%\G9UvVr[J*|RCZU?!+^ծi{zFS[^ K$W_.5YH!7X;׷h{Afo$_>V6Hm80D:YM&%w玁'5\mH9xe8.3kX- ɍKcp5-B9'zM> 4ЦW{[߹"[;k!p0@J Y\r\%2%:%o(SAtx LɊ{f~]JζhPMRxI& 2h*3 V<hp%~ 4H,J Kdc 4ґlzyU IĤ,ɅH/uLi8(8lB LҒ u#*ҎI$"tq /i{y pJ_)Q@Vҥ9ld035Dw"AyA3 & -OŬ̞ 1t|wlEPgPunLJԥ6A}%1F*J5Ա/N/Hε1CI&xTʢ@zF̘ 3%˥@2(͌yn eqi=e3iy *k\Hi |JMݷt(뎃}*:?R I+ 9ɤ/,g=[YbR!-HxZy: {,܄+x|rT#B V1gGn$N)2! q-T1^#n^6\dn%qhuP0fH\H׬6ʄh zG*)Bdg~qKs.HO EYdQ( "gL&~Ō) -$xul_n巧`e/r4j;ng%""2jGYXSZd :NkAZw'/ޜgL@Xδ_0՘lf/\|1Qr. 9E)ǘpbe+ aPLhfگV@]HaD_(=ȓ^ts̰^Tz\aG, G'CY fQv1H<VŮSHdI%gHs6_H"28G =hOfH FG2]>PO0&\RC6ݡjvae@I2n3x1zS3f _,rP{y;'ߢk57,(, )K0wM;rf`MgHJჼʵ6__4awG&dL`O:a{~=dݣxwzt^(19h <[/+y>Yi:= І?(>ۜ؀(S*62#=^')cC11,ô̈A`7>=jhJ@"̳ 9@D:t\H.Cd1r#c " i6Is  =IjCQ>a\ :Dd;:0# {H:?x 3"#dң s?@ _"GCΈ0l6'd; j6x>{) U\ ْ5PtC %|&e쉾༲ @Fκ,ľC `2zFcZK uiEs m@þkhG$ɉ g3H2[@ 0PDTƩl dA"<ʜ4e Ac‚:üIdtCF68K<@¦0RTq@ULLt̹M tdA夈1)8$2LqDuaݜ? +ŠMfԲOh0CL Nh!Nτ@,C&t"db QaLxͷt\<LP QLOP+м {0DP7Px͈,KJPA 4ЦXv-$UR mC mG;I]RR,ҐHHfZD"Œl-%Sq (EDP6#)R8@ϬT wK!?ITH9R ?0Ӽ! zHT&}0T%UFS4]6N:Dȉ;Q d`PMUQ@ (TOTH8]PY]XV@ϑ\=>X8R+umCcE?{Pc-՟L_U= \Wweb nmW7}g ІQ? ͍LTcTW{f%VȒ8MمLq`'b)& 665fcۅ&c;zN=8FV YK`u_5`@?N`aReWdMFmeFU FKaMd^^N>ݹP,:>MS\e[dk6/v95U^kfVnc mT\T-fnyhZ ,.gsgeF]VEi^hOfnb>弸`)[hDx%F ,h?8RL&hi^ ]4#S0;㘎藖fyѴi~snGNaE.ui|aqS T[&n a>~樆&@# 6e^k[pݻ뼶쳨iEꪈdȎ5iFl/fl΋A6Nm6^۝30;BW%Fe mʶh^nΌnnnnnnnn* H>oNo^ono~oo!,<TB? HSը*{ #JHŋ3jȱǏ CIɓ(SJT58{zMR͛8sɳϟ@U6eQ{&]ʴӧPJSvj]Hׯ`ÊKlvک]m;pʝKnX{u[Wۿ LÈ+^̸ǐ#KL{ԁhH>|TMz4mjSװc˞M۸sͻ O [`~pc8l !㓺80˟OϿ( Hr  @dK+<M8vzv zrZ(,0(4h8qk(jӋ?6سp\v`)dCBO=ӏ cGPr4N UYb柀*!A&D@8 8=p6zJeQf馜ih4 Qi=pbyiQ*무F`vr4pf ldO#܂LLJ*6Ў45 =(+,4‡^*{Ѧ 5PQZk.￝gBIIY \G,rS-&0>%|B)lP:$0WأD'8笳<2!lcXFMпe~`.s _مv[w[$|m5b¤͜W0gL8αw@68aTAN sMW(e dylav9e`"Yd9o_~es.s{g~= zygH#2TQFH/Hq6٥ zO&VbX˙ee=h>.00jBP>5,HH "txpy"vMiOkΘ*}mgp7X>"BFۓ˿>Gyq?<b8ym( <"j4YDx1Z"5M^vZRfxNSv桎Pȃh.9Ӛ7iU 8 $ݽ&O>uL=?t(u81 tݶUvuw<Hxxz~_Mx##QSqwyLnQ~yGgvRM 6;ի)1]Ul[O?|ژ]{?'rr'_"htuG@xԥO_;e+z_wWek}'\gSeCmb Q}I77__}~"8'i{!hG ^ՂStgz~J 8B)|ItwMF6s~G'<؃/G5ov* 0(1yq(ZC t"wU8 1E@61Te2pXtH%Ȅ(Q@{xW;(х1C}Vk(vciZa}|2(JGA@bK6XxȘʸbAЌb>dH_;!`Hx[h(m(Hmg$ъ8}HGG` Qe{V8){6AGY9ܘH 19 Y x}xh Lj 35 ~%YB)_#1ǔ8Yy$XAig()'#lI,Gu)!o(iei[C{y"ٔAe YJ+Ș]nIv邘)$٘z98`ɕYB9yi雛Ԏݥ>V) '1E`g44 I'UF)qY E9&Iov꩝(F~ M牓y$ՙX8P ɟzYK &eT$,ڜ40Ad ៯G*_88DQҠUKԩ? UFN*I١uى>sir?ڣd/ꕊYO8=7p*Z!#ʨ;l u%AZ |k祡'Ju#*qG zlZEo*1'*Axj<ᧀJ~R0hКg~ j٪lO8X7R:KtmGY"ajdZz7% mBb:ڍ8$' yz9JXR-5{c&!THa3;,{ڰ6NkhZ )X犲AZ ۱<>+[E2՘I V r;t[@6hA&^Ɍac*iPeW:]`#q ZBu f]KG h#Cp깋*=X=S;+vG^w۹ꫴ6eKg+&V|;K}U{/",Eچx[&L˳w:A,flp7Vˡk_{AeriNiF \SYwRi#FF A8[GЦgQ19Ӫ0WTS[{Ô.6X ţklg{쫲0M|) N \AV0s'Vƹ;_;j7d-G뛮ᾘl_v! Ƽf8Z C &5풷i|>n'>?);{,QM.YNK7l;66i%>k'MoNP4xG[oE<!|mѬ!_nB_WޱAw$ >jpP VW]`n_zLp^ nV Xqnc˦h>~h٥~2><C:kU2TeEM >˞.$V6pz Av0G<ʼn@=̔ӖlH;qJn r>4ޝnȧA1)(˖$_|*a }f hmuP!KGH9k4pE~9= \OL.mA26{Ͻ$C:_0/a-J_Y-2ov-/61 G˷Fn`|h<h-6wHUʿQehֽ%䄶lkՏ߬QlF퀔}m<_2SmoC,pop~#XA & NȰEHb=f$pdI) “ Jc$M91t.;S$ɣ*QpGʜ(8yfWO)%,BѵLY~$P֠.F8 ;aY qa%$[;&1in/@销$:[=o{4[2H@r6'Onw2c vyP 0g.ZB"28sW/ Xݫ!aW7I[}#޸ ʯ ᤗ JQ1[Tpr.C:\ F[:$F챧WJIj ""R>?D >;Lֶ6c>h [d&t(Iϒƾ bK 9@@2V*^5hK#)r 5ic :d{dʌE9S zO)X})J;j&=4h%Vho  -#&i ZwSLN6ٯ춓:)Â[#{FVlcrēG64{JMpB)@ S~ў>YrW?MS^ "9+lj#nU,)Z+6zq"jT3_[Nw<wZK,:%+)}S(8]~Mp1"pXX$5@ +oAif ԕ}3R MxBP+d ha mxC搅Wa}õH`?$C"&D(FQSbCHc Gw٨_l&5: gQ?ԇSLKwƯĈ#]ZDQscp Nt11qB`&yaS$mQXu‡pTb K}pv )RQˋI}wI $%bq.YJ_>I4x| ԬI_,I$0UaaI) @S)-y+qNlų R<|9pI36jk!/]2=& kl4 ( bQLpW&GxzXSL=YtGPU.bϮjʬbĞQOgš:(M S5X5jWu,,RAYT QA_Y:#4FJH7M* Џ5)L[jPdJGFA%vGG%mAg11[kwOʶ1ːYμU5:Q&%0t.M4'Vd',Z4}hF:$XҫV"آ\OHff[Jv Q`2#95vH?v'>;Ϭd'y J,ɜZ/})^n=SXɵM-qÉ ^]is=*=$Zo 8)1\aⶼ8@yxZcVξj2gE.pJe)7-iL|hD3ed19%d%%nq x|:[>GLZ=N$Dsm iӒZ,{b(,KnqeI:s6r,6(T8aF;02մ w۾I:"g̚&e=w촛$]6%g&zmuاž c,mfmpZ /J\ o_.8M4]6}N<xk7/7<FX֪6 ^rl]9MRD6i&aLrP7m-$6֯^t d%ے2LcN$^DLKtO\0kd6t)b>iJykp{sͧw;F,E۝gzԴW+|JDO=^_7wmt?ĦzE^ =u,4P~< }G𾱼1[ ^~Ԓ ahÔ,]7ԁJ"@,@#3t0\@l@|@@[=i2=ۿlIj?sA[؏w) j=A/# @[ Y9@ <̉n s!,'|9nMӢhV԰c˞M۸sͻW'P%*dcBË׆k|_Ͼ˟O_?^0Dh+s$cf}($h(,0(6vIˁLA>=O?sWڈPF)TVi啿Ec#=hq?v`$O2ITԖp)rID@8 8=p!'`foҩ袌6ʓc@0DdMI;p)'\zћAꪣcud4l7;H 2҈J@2:5 =@g-,k4 CoAͦs9FtQe叵eK %8kG=`sp + <LȨpwܰ,r\'ܦX13/@!LsB aЁa(ci#M5CoN/ \5Nos׎hOkSAsrG`HȦIE_>xpYh-lݶ@; !pN #DHE*x"KwKy`}WιwS@gRP$d C#nmB_ >GzhEY8:L/K׹UG08S4,?$* I`0?ʡ `1Q;Bi`pHj:ً$·hq?q^‡x? E">,"Vn9#~(P=`L1ŢHH\9\F1ڣ^rAAzG ŹS)ަ>Gm n[xȢT*&R7ɂH \A(uѓH(w8.0e߲u@9g dwcECЗd8'13HzC$h*j$ؔ@֪gI@%9Qܞ V) A.{̣lj+c{b5hVSos#cҩMB@ FqJ<ؐQ5j|♽ ?OR#B!E?H|1oMd&rFRdPF=@q֑ "6U=G#؃ ЀraV1'iKӷRo; %&MEB䇅|IiBe"њ*7\ (rFM +ڭ΃9AN?P+Z֨$ESӧm udikeKڒ#1Hpnn-~i͝r]=[j]Jcx{[`mxޢw)wM{Cַ/o _oߞw#~;6d V0;/0F4lN_Ly%/PjsfL8αw@L"HN NXĒLe*1qm!p8ĉ+grOa ycNH2g2'u3s= ͳpB3dͲSq"p };kJqj#|S,4!;L'Z*b%Ûn%׾ΉEϒ4$PCZ5kǩ\ oٌ6>XԹ"v:RR=i`Cפv8$;@~j;v0Ullsd]fqZںIdy;@1z `v'n`[ owS[R_5 !Jwhx[γO:oi=]t='6(ueybRBY?qo:N ɾsjS hӻmwliuJ>BZ]w7w?&l%]W-Y!Vwc 4{Yz'o%h]}h73O# ypXhE(sPhzP TXa LJZWdž5(^`u0wXQGG0v؄Hx3^#H8(h*mi(׈` ؉ XUex蘎긎؎6; ecx\Hۨ6q`Og7N 8|sg yy }y˗ Yҍ10YI/ $Y۳d h{X7yn8Ȑ&qla"sD擋Y0tzOGPr@)`#ъ..ٕ2)#QqhAp! hɕ&Q7g#Q0WjtYA  1)7|9ff酟 lnIm7v?M|5j;9ٚ$aXC7'aeX‰ɹT$ ^ɜJ難scsZ. ^Yi1`YGjHrG)qEF`㉞)Y!tn8۠꠼妘 ]ꔽ :_G8`S!Y :JAr**8^q Т*zA:OA@^Q mJ=hd9$Ş)iQ~)[* ڈEh6:1a _88Xpx)_R:d`ff du`Juڙ Q5j*A@d^եpV:iz)GgmyK+sʔ0jymyXsbE0cz!:vhʨ*ampҊE-ɊA@OuEH0A1ysQڪ* E5amp:uB-^Jv'rnz{jb:3pZ2kO bHy:EGg 1tjk8^% گr٭d77" U?S_e% )ptApF Aʭ:8 e 8EDk4[FKr[89[pH%VʷXE8kh&'6cjXS3ʓV0(,gApa7%gU4XS wV cIAb&KvEBix Mpdia 8[[aX𶓫é"j# Zڮיj;8@`epڱACV)GKTc))ہs fk X!zCkl[*vk8bCOt|{{$+k M[S8צl+j~G!tޫ=5HgAX9:]Qn5fFSe:+m Sm{3찅8z:aXC쳣 ZRgd{Is< ArgjHhA2g3t`gyܷ ~ %P =LgXܖyy $l3s3_P(m\np24LZ:K2fW4a':<1LAy,?E`{+#sbQ>{sZǷl#5 ^yVlsFL˗e\ v\>:.Ӽ1fbT'w,={@~G,(|&c,i,llR(Y vQ;"]p_ږ]٤<Ȧ(_&E祤[tHnARL>p@}ǚ'QGF~<Pխ]ԟ W[bfm A %]+[M4fr*pk5 9ǜx|i~Ykͼ.^8.x؎\8: A1%~!|;sXXNnZ?.1@s諜FNy0^}8f=~>N9P6EӍT>0:_[ Ur{ 8m^*in҃m星Lv:\-:)^饎}D X@^wݞ^A~M+GnF 5<=#n&qz 5V>VZ i>S/juәG+2J>Sto`|9a7:^ƹ.#pVL匨"xfT%qX8Pn?_A`JGp}q7RD [Z\ʌ K[_c8 ؊NAx5ohm/fqi^]{!+_^?0XhA&e?V|N(oz.|*>۪  6og/|o@ok~oa/XY.fS=c<*rFs?1 :8F?mO  $XA SP NPbE)A@F)TٲEl\ȒM6qٳΚ$B{$*M{^dOj-*Ĭ\Y ]`,Ϸ큑p$ڻ9~%W!2*2~XAǏ%<i#?بS*s(ڳ !Wꉧˆ]`rލtgIu;ŰiJ;^]Jnkt  k/ʭK{KGyyʜ7>Bq:O&-{K+MKfL)4O2ڹC @WB$kKʱZ- ;&2 BY]Ŧ >3fAC?׀|-ڰ)j2r $K#d& $ĖzE)%@#$;,0).6$ STJlMz9P[:@8/ RSrS3 lA `Q1FT"X̲'ASP6 DYyV0nVǤ\T=FT8#\2= ӦG9]|wvե6Qm:$+dJ&Jio]8:ʖ^zLNߙ,hO|kU)M>Ԇ48!L\E9D)Hv!V9 A&fz2֍sIJ>[Jkm- V,#ڝ))$ V e^wjQ 4yyra,=nt:ɺޔ7EMT$bd9]}A{˞3p$r6+ЕܯO=Gwb̦,oyI0J ax͉6TCd$r:эN[NtXo)nA7 q8o2W^ ]X2@JtxΛ'߭`۽wB dS$^% ˔%|ɵvyDAݜ8|Ɓ1|ה%K͋?O=M~| 1HY |͉_xSzd/ǟ<{֠"?J}w&7ݗ~\CH X/|s .ߪƻ{(a3 P{C@뀇t5x? L 1 t3@7c?t'~Ҷh:?{0"|0Ap9K@X;@Ԉ=r9IAT%4AB0A*T x =.\,>Mх8BBǠ ,ld0ڀ4C?ĉ)+^m>-l 2|JDGt# 9ČC 5;FlDx)!R 0A42Άv ($h("Ȑ *(4h8xb#Mx`?SPFӏ=ThE)TR\v嗿e#=h!49six(L3 '"r2 w6 = sABz= bjivرB6ӄ٪ߠj=hb/(sX9 uŽe2^ϯv`NR$8-+YY?P2/Y\+Bs,&V3,+ф:S\\ Q;CLUP,,N)|0W<;5)D +2TMAC\j׽8=`3F C_vPWO{pc0ַJQ;nKg\]д} RI"жy,SjN/چ4Qг;2u6IKH@?H07ݧ] UDЫK4㹗Ԇo6D-?-tۦ?@R$?C@9r.0 l@9w~WuӀQ#sJΨ2"ǥ&KP#-Bul(1<) u+GܮE{T@c[.#Ota7ԵPo+ R&mi4CȕW!88V" F:mbb`y6E?lF77b#n(GLAҍ3^͔ά`c|EPQ ׾&ȥȥR8Ă<!: "R20S(H:SmlEKXT>11"s(`.l^$e`"]KaG6s^1%<6L4lgٶs.π|I҆)v)AKngKQ..&wdf-ER0P>=,+u.iM?PiryvpVh7\Z;~-llA֠d 㐋[ַ{`nԂIKgRWP.]NwSK譭z+5ozZ:ŽAѬk]m/[7׿-pU5WN'D#|D} َx-# 0gL8αw@L" 2 ;P- 6H,00\bX(͓ pvl3f~Jܢn@-}_B_ʅJ`} %q3U|3D7cGelilCc:ѧSxEЄBb0P'6mMk*?jh D?TnRW׾3w7ʥ3aH7I;&Kneﶝy-6~kd[wznvg<4Dž9;^'^} Vuɟug "sR\u'ܽ"^ו/P$`?ywg^rGukv~|/H#f_閿Cȅ~p}w Damtp5@_ևxh{H(3v%?]HH&UL 5ghH'A&T X(bXH5/ ЈhzhhH'?}ݸ8xXN؏AC-;(u8'Y.b+4@xhg׋y 4쇓99<:ďq8/$kyØ IKNg6.hx{QvhjlٖnpyRtI |Ic6Y)vLjؒJ:DFIXGiy6՘hxZyqRP ਒$(i_ * 1YII"a:^EL9v)[ys`ZU9 IIi{z8hk%{Ƀx#B#a iII u{@ps9Ď) YHj Ak РI) y>H ࢟)I*Zt 4Z:0:0 JdM2JE:S:os%Q VkNdD19ZCETs*:a&<h@ Czp*ق)tŧ P:o єWkڨJ9BSgz6DzI !gZNzCꣶ׊zYj搼ZQ]ZzA:wzTq C ڡ韢:"$ӇZHD4'l*iM**P *d*` Q7sì uS ˱ Jɦ$O8'F`ٱP_&:ڳ> WB+,jtJ;a`399a۲K,EC^b;EZ۴7@W[as +eA)h2'+ A2u~˪n{34t$Nd q * 6He[RÔ .{kCȯ+73??_0&ʼPhCl9ˊ[C[ۼ]'XHZ)q?7۷;Ơ Ὧ{KZ*" u6Ali?øʛ\g֖F",8 U@$ݶBZPYD& ah6,6S (R:t Ռz-1Ǝވ 0}}==cyH=,Ǔ 1ȹp=؟Y)g:ύ- a:wǝ 7; % NtM븦Z'sGmozi3wlc*!kQVL8uSݥ-}у5 e:;X3GLq3 MA,9D +hۄ)~7?hm]Y^:ylܡ7ۖk1WɃN&XEv}ZVˑq3 <1` Z$(mA> &g5kX!Tz,6z2%<3No|~=~9SѸv?Hݙ2SF%9auF1ڏQn뿞S(/>A7*Dzp~%P9fn3{McA(Tg5.,8tuSW lr,Np=N_Shgp̤a hV=q.!6D5ʦB kãCpKBJT`gä(J^׽1@<Et!۪<|-+L_~I(N 7i#᤭*]e Ae919pb߽%9YqF65KC-_zǀP &uB7ZOŽ`;n܁u,b3kD y?Twu󺽋??ql]ʹğfo> )jxXgPMܳt ?z*HYdTHصY[lj/*z {#mH`yCh,!s =I vK؊I}C Ȑ$So:-rkS#Iд2>CP'| ֲ6MJ4q3u, R>R QH(;0d LL1<L2jʭ L8)Ҧ5Ōh H,1J>Hs$/Mry86KT Cި[͋3|;ȈQUXcuVZkV\su#AP]vF"UvYf MgZl [pw[|93_0ˋ,S.w 2VAKt?=wU-MI'6\, 1 7ꐸD0H( V۷qQ?4d">$%di^GwSU饾 LrwI޸N=̄g&j|u w mkYՔZ2j8#((ΞQN%N2;{[ɶ;)UJ2 7.@Q |.z $ky~ڏ40H>'-^LU 9ҿT rzd|8 Ihr31ղJp&s߸U9*'!Y쫋%ɥw\00>nj'?z˟>g`!;b-Ʊ,/9.jѯ ݜ:#L8Fi'D!$p!Y6<cydI g(5-`>!mNy" g"mX Nhb?#| o@BdLQLHu>3] C* Ca$Qg:\*є:q|+!&( A*נ8Jнq% CVAdl猨cSÝ5nx][.̈ |nq1D"yjr|?R/)L֔zP%X%DٍΕ,x=b p=/$ӓIZ艘~ JC(8݈H#gG)IKz xXlq9-Uw"g%@F2%Q[ף!tЅt(t@4)E}"f9w؋5Rš|ڔ3r(FTaTҪ˛$$TAtHjƒHh$ZQuH˖V:B_x;,YX<(؛S|4+La`h- Q P֞ 0F[A[!q*CEFg1lck[Ǘ} n嬧9[WxTnMI}(Z|94yGBtAJ״^(ճYw$^+N;Q25&m5ՋkH2n53RD=X R>=D& }Czi29#alj[pH_]50K*sLJ|8/f)O_Tߢhr EY3^P Ei1KLZ0lT53D1惓',X.[1CrJ%ebX%街69,ѕ)ph)sh !U1*3NM]X(nݤ3׭lIڎ ԵǽDe7FpjV&xw.;%hw)JCfnжav:"?y$@"߶\fZUF0DcJU2rg;|牸ĝS6Djnru,kO~nrƑYӞx<9_8;)FÑ]Tiq%D ^voiDqʥԫRr'Q.c4Ncem ֊w[Nt~cR2e7u; c`OE(=V(2(KducGe∀W>dM=HNNeẝ(VdU&N 6C-geT6f^vemdevPfZ `k)D^=bk^w]Q^ nc*lJXv `dNf\>gtMfgvzb}gsXzK=L ZZ ,+gEN!X牾@cf聾Wc#L Ee痆i8Mh~ȎQ3i鑖iuz J|Rh]i(.h=0LeK~I8~jF 6[^h昵fg^f-j@^c(䋶j;fe[ 9ͦJj|k ǵJ?*շv\.Ж 潮ܙvjЎ졦>N=vmNy$0/ؘ~n֋࠾%e)fl~mmm ]w[v숮vo#fok莉Y~o'.w ;Z_Wf #Kp g w]dP3_qFnxfYqkq&i뻰6/r#j'mv_ h a r)nW (Y 's1q8o$~)@%4jF8 89)nGr3.(fzG2#_"{p@wW73s3؎ uXXQnWWV79oar{HYltQov9 'a_ k] tIugm$F3htP®t^}Yw020X?[fvn8xxxxxxx??F3y?yOy_yoyyyW!,;TC? H*\ȰÇ#JHŋjQ= CIɓ(S\ɲ˗"UM5޸^Tɳϟ@ JF)]j$PJJիPUjg׮tbKٳhӾlר۷pٶSK]˷߿ LÈ+^̸Jˍx3I>|MӨS^ͺװc˞MۨhW O %.P&3}p.ب2mËOӫ_Ͼ˟O<@%^!K#y0G,h+s$v ($h(,b| 3"2|h8S޵?,T3dc>i$pPF)TVi%lxK Wv%kG`O?ZhGL#ȈsiXG@8 8砄2d$$ꨣzhO$EPf =$8~I jj?ڢ?v4p7ڣ=p)#2[6R(@2ݲ3ke2G kֵ ,5LnU1eK %kVd0,>,ۄ: YX CqQU gX]IDMA95kh}|C{8UfI3I rihnqn;/)M/ȕ[)ͳ& ?(a.wSP)8?VɐL\?`ckk^}^A^SG"ŏX0 TҚ;2hLc B.}@haͅ~?عkÆ4P dDF/vsNHl4l@0][G/-zm$W)̅ H*E ry: d9H\]Ǹ9Q?euQ$`IJ ͋ 6F fs0eXK1}X;C6t0K=d*uτji. f™`)ژ nǔ !CRO=X:[ !6NRЄD]2#Q@..S$(ɃNreB7/фP4kb4Rd[u .QX%J.LG( p{S!_Cb~{QSfΙB}$QyP>nd[qw'=YSri3TdJFA,Ŋ@h5MUC @ ϾFwҕ ϴj0ZZl./p 'imAy+X E@kGYVQ.ŘR̋߱p[]X.Ʈ^Lw}aNEn{}k1x%{[>W=w;NؾR1p[w7{| lvp4K8 19~&spQ<2 7F%(QL"HN&;PL*[XQ!dHLf2'[F[e@R.1q|$L! !gM3Gǂg92}K @#؋Na 9~4LۜW\Q(TvٽsjDZ*@MvCN<9H9bIS\9wB-а9&Q7L;0+ Njs]{}ӵ"B8xܟ>d?zw 3;>-^ W=HO!dhH+ֻ_aR㛳 =s?KVǮ#!ڟ/onY?Mrx[ZBNP'x|?Q-}&U(rH~Hx'm~wog7~gg}|g| @2L 5}g,t |1Lr w֗|zG4)XPsp?Gt F}{&{[|%SwA(lV#it7\ȄM"a~eA6C 6W^8ZwvX9@7׆#9ȃ(cس-G{ a4-(7eCV 108OXh'oHw9c |$Xg|Ƹ`$xԇ،Yx#@yH|8GȌȎh8x4Txȍ]"SwIGMՈX؍(9%rS9񨍵؏ I?h(- I){6F 鸏%Ȓ0ZH5(r=ɐC!y1tpDHʨiX؁Ii)ȓ Yr8Ϩv/@5ӎM)w )t⸗QoUwVf9YyJ1B_0d yEd)IxSi!RJW)@ tY9I$qF)2;98xhQ9GԊvC# kqɚ ? #dD84y6)If 93iW}s ^@V޹YB)SS(px y`yX2`rYIj5>w;({:y #{tTsĔizщgxv-C6㠠IFGKa61 0E9g:,Gi$d*jWUcnt^L4%h:fj_ڝb3C8P_ci6*ʈ yEPc6ez*zXC(`%qD_ZyYѣJ#=@J{ʧZʚè"5`@Y/xj؊s:.jʫ׺ 1B-Z|*@Q.Ppzy B|*) 3zۊ J#kiH麪 Q#*F';`ʯڰ qߪf(C9sQ5"kϪ'i3CB[D2;E)j))>K1"*0W+ꊴJClʤ* FIePB䳶z\Gde[/j1Fs CSXKZ ȴ'[#Aۀ=i"xѱl뺁鸢39 DB=$YYXԉ{~),#AJA8[٫8tK6>Ip>T׻ËwA2cfʰkK)̻y˖K'5;ۋK+Dܫvۂ+kܺޫl;26,6۵-̮jѼ \+-I5;>[@z`8k(۠a<1V 1+VK+X{̾*\ 0,)!nz1|[> (,LlL G*Q.'j}U1Sʀ<ɦܬJ|ɲ9L+bY*PCh,$Qnμ1yY ~e<\X5{ eQ5 LKJ=Ʃ S-01bʿRnѲTLo@hK V\̦j5],oĪ~1!l<̾<Sti5 ufv+$K5 ,C?@mT7@\vH}x>xx(^4:V}_ Ji?ԥKĿ"{c >h0B)o++9u)wbm H)MnؓjGou-Jٖڨ%-@x|<ے 3]0AG9 oYX{z->j =!,uQsFq(#ԕClw "EAm3}):2ǺW=U 1L$?=UEMr]Q ' ۱6+1yȺ-N-$q-Zi|\!In#71E*tP-mscʠ e)D=zZcSδ䄾qv*\Fueh.ۡ}_<~άT =I$l^exG:~갞/謱^Һ}Z?nS7Ѯx>#iq7Ujdhn(qDqޥ(e.n2Zgx؝l]"Ϭ\6iϘT37$NRxoa47?o!,Q6gwfpKpIQUCq#1|]iTh53;QBt6Ԍ)K잿{0_6l .~ ?@bS]O@1T;+[.].zwQ~ }h}# &TJ: rnrn $XA%dC !NXџD5- :n8I&QDXK*Yj@`[,vdH0# %ʱыH-J=JʔjEWWZHJn!+y6GjjU%WM8 ~׬ߌYS[͂{ς^e<~ ˠ3S\NGFz5{Ixxz9=@T$~IG-߬?qk?X܏,Y߇ VeaiKbSfL8%2@R*]~pf%{.OEgU AĢ*aO*-\ nÝhbyR 3)V`ZҠ@ԑ,#Ʊl2)r=gR) 28X3($: CTtĝ8-as盈RS'%+"LGD@Xf!Fs\ʩ>hCQ˟ݳ6 XXVh14%,{t ?8X̤!c3W՛tŤv*T릫 M湰za}k1H6&,ml 2" "&U2 ~DFI^'[W ̳@7p6˺qQ8K%S>dhp |꽥JŠ ay2s߄WZ !Āl\^ ֜F_eLl4~ H'(QBZs)+n J1nIHr n\F:7=A9!?=^A`T˕dOѹ!Kd24H qHf % Z0$|!)3e -y*F{Y2Xkez%}so2P NwۀʹKK ¬ 9:<2*L6Dij5jC%~M[7{O>{ӻ@lʛ䒀G3lõџpC3tU9:U÷U8@4lolLǜ7x0 8¬l|Ѕk ML/ -i7μˑ̫:{| ˛ht耥mÉ̈́$t7|hoT'L$ ϯJ:ℽExO\Xr9lВIRDOKJ_Ux 5 l(q3o\{r S=Ոo*U8;6I?%%]X5?ײ H8jR_,c=TcX AؓpUT`YY 6|)YuقI֐O5Z3vdR`XZ6X?X!ڎ ; T x=ٍp|[H5eVM[ d #}b9q@ܣ==WwLC6US5e ,]  {]ZU;]WxW6=[ &uЍ 4$5] R>]]uZpWbZaL\}A` #Vt+M>x-+6hg5j0szŴlsi_.gԨ U6N4<賾hoj޵bFFI9c&HpuSBdlɆ 6``lFhmF>> 9>'7Vjl]~ s侘L쨶&l.J&.^k>hk`nQӎmNF`lzێn¦Fd> @vjf`kmn6f}NA%4vo~fWo 5mw6iFpf_okP:+OqnW 搑oW o.DNb&?r_q@g.j9IFWk#rFPNʄ87N4/m/sprM@5o47tp /+P#Cr$q_W?#]k <*GrM>rIk?9K+r,WrElu"?sRS'&nUqfj6 {tSsJiwodqidm_$67jZkvP19:lw[x=*jƓlQ/vywjw ^O tpqxL'-/hwgpyw&!R)xv0{Ywyx);wWexvyxxG\YbAy 2xyw(xFU)gTw%ӫ))(dalX)?zy{{ 4]^Y ׵xoo/y?N!pu7wg {XIQyۊw}P}U{{} {;g 1FzV}/rP738@׀ ~g{o}x)?O_o3Ȁ0:,h „ 2l!Ĉ'Rh"Ɖ!,<TB? H*\ȰÇ#JHŋ3.GU4Iɓ(S\ɲ˗0Skq&ɳϟ@ J(F)]j$PJJիFUjg׮vbKٳhӺlר۷pٶSKnL{u˷߿ LÈ+^̸{ŝ fꄴ˹$>ZoMӨS^ͺװc˞M۸I696>V'P-K`?TL1 6LËOӫ_Ͼ˟O<@'^|d4r!ق=>0A42΅fv ($h(,!Ȁ .b#O YH Tcc>qpL6PF)唸-Ph 鸣?`O?Z ! lv6N dfH@8 8C矀be$x$(QviO$EPVjKRh;p)'Pz騘c vg4lv7*+I4e`وZ(@ݱ 6C H|2>mF`R dۖ,SKnFY?PkC ́Ͻ,7!' W0T1u6lq]S,X qP#TO'LR2o,u׎h'k#A7E`HH!S4$}Q/2OSTu7hr/aPhP}A@!I"`tR|.ݷ-Sjv41 >=5Y4IF?Htw㽒y\,MR +؋ԅS6Ȉ}MsgSH2z cER=$H 6H`?娀T'Q4d~_Gv9J3IJfh3νEv wI1?@kMEP4 P`rs#‚Jr(m%GxG;fO{JMo9L /uMqvf6If!9q;5EvW,HŢ˕DcFq!Ӽ iN3.GGU Qӵ5x퀪n3(6e.dX8N&gI@ rFP̜ BѱEc$$TvΏ&!IYb aY"yDπLd'}v(J '  I9 U 98zic9[ZfΖF>3Ȥ1ϳdTJ()ڈtfg|`"BRhO=xU 6>RԜ/F#Q@.= 󘂬i+N5WPH05`mRUREN-EV% JE( nmA\R/zES7Įo˩N̟T@ۊ[Hjmb waS*xHd%S$x7 SedשqfpugkGwpڅac'" 졍-rۍD`+Bk ȝd*s]u [[H6bٝHz)]+7tXFe:/D JKJBa[^}{Iq*[0ѫ I |#l` +0aW(-G,b8K(5HQ&,I‚BL"HN&;PL*[Xu$| @@С2;&YMbX{Ih^(5^ n5iqł! pF=qtA$8x~،*"<sq(]i> TcQٹ~1O-5{dlS5vm5eci,ɘ (iv VYxJ6FM~g7Vy2;1A(u7\2# !f@R^эd>dwr3 Jh;mNm1Н Ʌ>juÜE8L2dfR&7dJkc]cgGaӊ[ܛ-3%;iwv+kCyTn]vv!ã#-BNIC} O5^ַ,%GfO)UxkoJ䩐9@o;]*DҹA(Xm?_W>w︢e?x{F'mv|_a0!4_j.=f'~FwG!{y6yTXxh[VWvWGT| ^p˷p}vw}|qgNsn0ւ~'XL.S6Up>ǁ(灸y$'%uh&8|Ȁ٧5 zd7 S*w7ֆ)bWz @&x[|'a7=XW#`(|oEA03nHc5X)!> g?4eXgLJQ7dcy(0чxh$t8 utgK芩86/Qh}Ηl،xD#恴X9Si؆nL}OVȉǎH+xX~ɨݘ茼(_XP3y莎kR1o &Ww$ؑS|Hr탑 ɐȑȒӄ7yV='Dr{(9)cцUS)}KُF7$9U4@L? ɕM)'YyĈ/1w8:glٖnpr9tiD 0DuEƊȓHF)2)?0I{ L7#ɘ@#LYYiy)yH{8Iy!\):}x_s I99Zl)%YyY^I,HOW⹓ "a]" `pԩْ/09su螣88I{)? zʡ)_(tC=A<*1ONڀj+A4+#gDcA/.+a7܂Y@<Vmކ.}u 5h_T\ҷq^>킷P67 R(Idgc~,dmA,k5vjі2$-T ةr33i>oR#kA>n,c٣bL1uNf쿮3ؠQE?3a("3-|(.~ ]>/$ xNK?H 3-=~/o?3co2A%6ݾtOxξzLhN-i_/ƧH5#̻1/N@0H<(uIUlA-%i#a/]K]Ywڃoȿy;"Q6 0mϐ*5Lx嚘9>*'ʚCk&-uf IB3à ]"O񴊵mo ڨ.v!Q7,:^%=}u<2d[]ʟ^zm4H%̩:98SK4O@+ BF 0 !J>?$ɫC S(?4Eo?lJKĂ~OiGcdh% K ! )rk>8?&JjK{?*.= ZMM o??I2NlJ)QH#tRJ+RL3*tELMCv@QSUuUV)e㤫bJ6$iV\s5[8igvvXb5RD=N^^4 Ab6{ X+G%C o;q) rP\P1{@"\R!({SOTwݕH9"d\">#0GFzQLVQ-GƉ\נ Z3(ןƉh1cE dSf-BMjtyfƊT誡 +kMfS1XѶB4ۚZBk'֚xANg0k(-x2\(PCdZ#8䌀&6ifݻ%ʷg( ݢ sBALu^-N6 HR&#v ;y׏tJ,uz7*c#ޢ!"^6eU}HGHgz>B2twkAV0B$ VRv9E !q%ЅmI{F`B7HY sk5,Cy !)#)FdЊ.?-(դhCTĻ n0k10IAXZZ,%RutH>8jKiP$Ȣ$+`ANE4Z$1('8Eb'CBY=^KEG"R$ȅ0iI+M ә"S82c~ r3j?Q+I6*BјV~AB\jG\5 [l/'9L |ř@ΰnCdU81)kSFcMMeEsA\,cřį HNZF9K'ZV"ZWO`1\=}y˭uHnbN*ًUհ-sJgKچqF@L7:ENxY6+#WByvrCJ\X0*MzCBDKN(gKj!Չ>MiՍit!nI\x%;!lI' "oc;1}BCfc77$i cE!K-Nk֊d!嫔-srѬf/F=G A(?\Ot zw}dHY]ѭ)E-j+##b j`RMK(8dl?.t̂;jh}3HH"8-7BGf}gi Rӳy0k`&un[phֱA m͇@Rz]Cs;@Fj KR|LbgϤ/PVL_8նcݎ{+E-KaAeVT [ԺL9 }&Fﺭ-V!ds“]G׏;4Q2u\ܢ=Knӄ>bofR~4IC vC#^s:>eX >& *>ȴxeB2<<`2=Au+2 P.(bp 4ˋܳS@3Ask/(2ؐ4!  3&=Gےe:bk=L ̉,ܷ$Bp5o 4T`{‡0O)lö{84<*\ -&ꚰ8&)= *t-iDGTď( j=t=x hz2C1EĹij灹Dl.u[F(%ŸE_B|S{^b4F7\#: h:Bhl O#4jBXx{17$ҷؙC: #̾ J>SB$F0pQj ޣH RF >u,p.TDq(õKc\@_0Nj h|D0{6{AĉT >Xʯ " #şlJqS34 ԡJ!9Y< ԸDH(1A8Jɗ6yr8 0GXw ʏ<3F,L>7CKT0-Kh* JJR50 |̭+M/8L#0$|ތߌġE6T͏$匉 đ,ұ=(ˍ@MtM ? #z+L0nIINȬTMD4̕ØxCѰ}!Nےytl$ X}Q2QФ5 l(aH%p%]::?/X ijNlZ3 HSYTLT?@e&E]cO{LlUO?` \U]=<RԨSc·%ԠEP:GUCڀ+,G,'IȵX ї&DVdf%V0Ąni=TQ2PϞѷIPBNe=w뫼+ӧeUmW,לDx0R+WՈhUWQVlMMa݈"~عXxXȵ`Y'O8ժVKYnYLlѕ̬4"٩ZMܳQ=٨= iYBMُ%ӂ}۳Љ;Z{N5KsQS [@ڼm\Ҥ9I[nبz:ځ0O݈=Ƶ\Atdٝ]\gt̝ԣ5٥] ESZ6p%J9/osTfmԭ]ǽ؅y{Yݘ*UCe tY ]IL (գ[h;ʮQ KZ^}u5 ԭ\UUᭈY^ӅҲ_'ܿ U]x0@H` ]HKGX7 f`Ua J0 NaD`9 ^a[-@Y_ }ucϥFb Y*,Y[Zalj(a &2ƌZ\c6.p,I\{#a6c=c>c?؁J[^Mb*3 ~ (k } /'!, 䏕GndGn UmJ7 eV6VAףPK_ǠR^eW.WeeM[.0PEbOd-.Ee0YͅG^¶䚂AvgV挸LbڇVmTnnfoV . bPqQe}fb&>P̊weMR0Yh%a%^e >EdJi |^)FhgfhF瑾vv犎*n%ifgp暖嘖˂oF`i~6Fh{\icᨦus^ݤѢ.ee^fV ̊;S&tFihMJlZjFrQOxXnkf*찀>\k맆k.iq+Hlņ귶hn @d l̮mV VL knjkӞmhmHJ ,>jnVmAmFgå$ ԶVʀNE^enl5'd~o&x;1_pinh+pP(ͶooOV a%&оp /q6w@ cﲮDcvg( oqop>$@Xr/g#?CTXr0&wp&s$)A%1>OI{c(r sFGqfv\IZ4*Et7sn t00t߰6!/>7O`1#VuN?DOu`ku(pSP4t\rssyy(vcO[?j?nMӨS^ͺװc˞M|EV'P-K`?TL1 6LËOӫ_Ͼw|Ͽxd4\g ق=>0A42΅fv ($h()b},(6Ԁv LA>=O?sX%L6I-Aiᘣ?`O?Z !h4N p&R (L|y=p!'d8)= A"*'팓馜) vc4lf ߄j4-ȴ-4髼^55 =Dg,,jji2|G#T9Kb+YL-5TfB +,&0>ބ:l'\QP1\DA%P1Ps\ձO#Tɔ*uȈ̓03c];ͣˑbHH S4$}Q,DKSTJWc}Q;f>tE_hpPg4HHC*"uJ{}Qw%sC*#F5Ydl&I.!٣B"M}zwU(|ԅ$n6Ȅ}MrgHNZAѦTjO.?IH O9*`A91fh3ϽR׷#c7RWnѵ "~((9`@L@HpZ9A ڣ׫\rqqGO%3I I2CQDe⚂4tT W)&DPb BABzcHH4Q]G91zP4SPrh|gF @Vj.)G/~揲5,')@q G*E+ Bi9$Q46@q(C]Ŏnã?ͱ")5g8 1q)B6ETJ vq,m *J-/qe-X\bd]%/KL\Ig=jgJI/G2"$E$#H d bc5h;= H @^IC[֑zCq'ԧhÚ6)MWrfE Ā斋}B%@Q VObUR7Yύ`+XZөS3:1&toACR,=ZVUq= h*  +},BAa]evCrDXϾQ,ˁIpJbsVaHp2\•:m}K#;]!Uqۮ<mguv]FwbKw\O~=/z)׺x\[o+' d岉*+Xkl#f책%ͻ|mi aI}^]8ˆ=aҮX"L9ߋ銫ʱw@L"HN&;P66 d HDe.'I6f⁔LJ+϶/3Jra& K3Z4rxn<MXU^vbHUlkV]Ŝ@nlNu1}miNj W: *G}jUc~#@^AKj\Ȯ}h? [ld>IIe3;S15GhlZgF‡;$A,W,TaXml̤sQ3ġ7p]u;>!Z *rpwٝս}pOX)]=ۿY0 z7se<0 RmkV;܆htFB yI2uX/zOLqL;'H ]3Xn՛JA]{I^u_%u껝> e;a)i!sP;aY=돷6t1gH7 cyz'ZuOt}IFѸ&@&l]ȥ7Yˀ |"!UXI8<#-$|'} M6gnrz8{Iu~WwfzxvhW̗w{z Fivu-aRn+g6 yJSr$( y7ӧ4y9w|aWFg;*DS|g_ Q?&J5H&8mZh, 3GV\}iJh=ȁ@ 47tHkpYȆ'ٷ?M5qG{hmz_xcCehGDŽM؉ox$dX`!wvxy航}h(!Wf^E؈֊"Q788188ȣCȋ|':S$GtG~x"=cxȍQ|X<}a (wh\Qp|ʨ،g|s2  J7R(*W)X~騍lǎWx1(&(|9Ѹ>?Ȓh(M~}uJ0eVyXZ\ٕ^#7:  `<hEccRZXf:yy"fcŖxdM`eǂ<Ȅ\O7? eIM a̢J&a9 }1Id6|3m2J3z W E[ĄrUH&cڗzd(&gl-a3 <;}}P+qY="ͳ 4`>-GS%R~2XD)B? ; .aԢ= f6&kup}+ >*BGةڋ} 4ԦA 6V$10ȸaX}ڨMQb/x$+'#~V3cRbH ތ=@P*q\xB! |NMȃ7μbHg' 3wo ߝ py.db;?~0 .Q7},.~@ >/1km)O@tPB  ' uQ>iOD!RL3tn@GSRK5TGo3PuUXc5P9[2 {p,N+7 S29{ <&5L: D 8VFP7=sB:gZ+%\K[nA$nUŵ!!eցy7MB̵CQI쩓M-Ly39m¦}GNrG:sL m'j>#/7[1ڭ.eqzϟ4 " FI:|zRN oS{h`ڶJ/7(Kkt-fӸ.:[,\kj7{qп2ǧO՜c!!hG9$EYotz@bn/ rF=7t1$0x3 ˥PT ,' 6ng޼(KaL-*Sd~wk.vגnP,!Ina@#e`Trՠ)T!v#%1`Cf@d(ϋVMd3/<Žqd'H>b$$3 2&e(@H2fHll "ن@#\$EȋzǃC6PJ^c/"qM ?$ q %m k6,xMް4i/7\dd-UQc3iM1@Nd JN6)ԿsO1 SR| ˿t I;c(4+Td(!3BC/p#7{E(F Ot'Ў2 AK%!smHx diNcj HyOӬTɌ[)K:3Z<Ȩ*1ԪDH$`ґd6+W@|Sz:܈hܨ\J0QkHV7Ut=Dps64g 8!VtU$kVFi#3Z80ljW Y|2 U,/H\RwJ$3;ƜDžg9TvuSkR& ඏ,Iz+U凎&UK%=enWoQ3+AoBӒ(WFm7py($cn2]ɝ&U?[e}˅qa1A*B=*c=[dTP,PRX+1arƶ%IòK0H_Hn1tt ko*? Ȫm|[!8B_c#Yqn*ͱtleKQ& S nH9H_8VV= EӘ u,6ժf9C ʤ&Ig0bd(sMvF2.s"17+I?Ÿ!frVV#5@?&|qx3^e9\q?Z pm^(rJ5b/r'e2dmxNn{`܃I;\=v?GxRm>j+vFNvrDڸ'w&qeВN9S*G\`˕_+\839sQMz1BH1SݻBBBmA];X <^󹷤$'Ùb끫O{/5+Nw/Yi,0W:`;$ M쾮 3x {Ë0, D i ,'X#ij{:Nj%W2h  w;ҋ :'B0=(>y\L",B$)ɭjˊd@7(A@$\s*( r I }c=иrZ240*@OA#'&`C; DATCx77|C[H4>҈@=XB3YŘC K )!`Ep?,+;T$<:-LhQ=OdĚ`R4ņRd̶ƫ1NJ0 LD7˷g4]jԕgzƒX'Kx/\F.i\5|~7> )" )LrP6ȜG05ڋ3U ?!yɓt= Io əɃ@@JsG*kH{JS`@RK 6* J0?!QGIH軰L 6 Ґ`# P31 ?%@%]7}S7?$ `,HrKbNlX ۬ 3 CNxUA(/OTJTPR>L *PR*qʄ4T (RcZZ=9\#NONǰ1]Mk;$Ԑ P/TVa\T'Q, p``2* .>(O*X0].:e%Z]6`X` & ̞΂)F` *a P;c4.N?cR$Ic:c;c<\`[Yu!b}b"/LdEY#Z$AE]dLa0㐠 +F/n L>bC EXN\fQvRa؉:>fUvb^T5 ^J5 -cNf(Ffk P!ۈmjNlvd]m XnDGebl^yg^NX2YK&gr:|> [V&0[nfs^f-BMgheq;hXRTh~@ @aOZebvgD_ j,Mze(]0FfjfjhfjFkLƩPh`v~>B=FA >j~NkflŖin. {J06ii.4{[ Z&Qk®F;Hå.ؖmkXT #mmھ{&nMlVjvjfe^0acgk[Y輞nЧljNfFgAn^k?lc v3/CgjO3nFepm*Nn~qmo(p_qӖqSړ/2qm"x-gw"onO-/p+l(Mfآ"2G,F.O fɅ n3?s“I s;%GDKCnHtPC:? 0 Q7VGr-ooO.A=YnϠ$sWt%t y=RuXW󰠟ad|ZmW/tm4qI3h1[r,o+Zv#S,x:03VnowvwPx/x?xOx_xoxx$?8xxxxyy/!,;TC? H*\ȰÇSը*{3jȱǏ CIɓ(S\ɒIk8sɳϟ@ 薽Hݚ4ӧPJJjGUuM`ÊKY[˶mvgʝKF{u˷߿ L:*uǐ#KL{h3I>|TMӨS^ͺ׮hSVظsͻ O %.P&3}p.ب2mËOӫ_^=2>޾Ͽ!K#y0F,h+s$v ($hމ,0(4h#0U] ˂LA>AO?sW5䓥6-AiXf喿]##=hѣ!(P2k)r(&F@8 8C'PLILݟ&(XvhOEСdO 3r*9hvرB6ӄ٩ߐ=r 2 sK *sX9t–d2G#vk>08 (%˭-p֏/k滐=`+{;2[ 7Yb) NQܔ;qBP2cqeO+2O/?h3Ļw2a$`zkGLm T $P #cQ3TmGnxIw!M1d-vW@E)9r )$@̅` EGʁH_首U=ltp{ Rpuy,a+k 0irYbBcRvłl`ZAJAНO| ]!5m9i|5SPtxL )%bfQ[r9hTT \z4D%AHJ `tT@(k U Kө3156@s&lꢕ?B(6!84SZ!@(v2L+Z%EMM 82ϧ5ӱ3d.MKt f WH!"s:48%F唢iI iGyv>i &2"0$$#H{ bc% ; j;%  1 O8h 46Q&ي.AQ+r1/pU_РHZiT0@7ф0e75 g)fTJNT{j`/v |gV `ː[z1Lf)gBlfr'LLІzJbR-kA{V8ѻ\-(m7fی(f(*8PM/s-p\d°[lxk eڊBWx[^l+wތwsѤ/y[W]h ]S1"!f &|[ c]/Fk P0JC"M`'"&'`Ol4RHN&;PL*[Xβe,k4Ȁ27vY5X*F#I%t5 `*k ^TFCs_ShE9σ~SZ[؆cBїFu<1AmZꨤYfVYi`U T Ǎ˭wt׻S|N(5]hK 6RS.AVpxռvAcϻ6t-=qWz+S@y4 6u >qhWO%X3;ʲyfn{gh,R.g[4tq@Huoβ{RLvDeKGzֱ.ht9Aԝmb[:Ϯ^8'e4Is~;%$)v4\i-:/|IMs$Ӟ?)yuC|?QxJ3^WesKtǞ}wγ`{ry:Ӽ;IMKqyg[/O޻^{nJ!嵿@{7|G'} D'}G(7r{ pW'GxZIĀ !Ag'%HyS~b1}sy>suEhBŖBրq@Ax#H~hB5A ;6IzGHc( 4`}d#yG|X|Âyc(uxz87 hkfjz7x _whxWsV WC5xā q@`lXX6˸8y؇$!5gU a2xH4 3Ǎ3h78؈g\ezlj5ͣShuhh:H9Q@eX(RoXq@؏ M!X8>$d EfY8x|3P Ȍ`0يh7VAys @0VC2FYV9&!_V"g6dYfyhjlidͣ G ӖxdBh}DdԸ1| yTy5s.`1IU؄3@ Ɉi\JODcБ)S)%5I#7H%ٛ 5 l!! ?I9YHع7O9g ٜ(Qu ډOٚI I2dwavItĞ !2dc ɟ93٘) __%*ڠQ9.JiE Т"ʣ ?*>YV= *ʤӹ:g#!5@=ڤ 9JDN#Sc ޙ,ʦmj5(%j"5H 6{ə.@]H:f" oEZ&Bd43\ !5$UZ\e`j s5B!T(4qr;|j˴avc ,s@OA+ q~i*qș?qI0Zv-c=pyr>ʹ+Y]730JUۼc35AHs Vsڻ!K{ɕ+j>@J ;+hd L +S{,a{rct7 z@35 \1<\ 9Je+y )j VgVLR1c,ʼn;9a:d Lh\#Ȕ;"(1&{H;l1Sdy9]@pB˼˾Zv* X79Ŗ g_peRQC`{+ 5eC {ƢAL0K6+D LWl P`qoC| Ql-1;kƛJw% Aa oܴ<5?WT0hIGk'ͿCwU@4G a=1Pj[5A%19*J?s Oja:N\ul|mt+}SW,2cL[ю uXyQ;_4Pi=Z856\ؔ#\B)<-\̦97؟4nχ DRK J?BQl2ژCRxыۡ<M 3;9"mLm1kQvچƣ]-!5ē`=^˭ǀb=UwIlpz 2g%N:Bg.g4T nڣ։2k $" ]nnA<9-<ߕ^9t $57n1k^̍})m2=CaQ-];4= 4>x=(G=1mP!54…r>Cu9w6pjZM- 85(8򄜔# A'i||\>a:1갮c N|(M- 8(w~cp q6a۝Tqń>,8BL]p1ݠ)Ld]}X@ls5 }F,ܜ{Xc>.bh@̣A~0n>C4-=3l5gCtEdpG^՘s#3 F*j[ ہ۹QZ;31P$o MH5?+@v{>L4Ԝ3ĄcOvfc ^Y/äDt}wo^l1Wqouo?{v]t6( C>)ƗW&9T@IE \^/б a/ AJ1BoÿńHBQtu]r_7_D? DnBB. .dC EXE1nQcG!~hA*)T"K1֑Y!L$PlJoI&uiKOEF%ԍQԺu#M1r vCFu˞= F;b]thޛSla1ß[nBLKlkN̹N,ZӅ˴ |S-c6Yͻ^$``q7DniKt/̽HSyOߐηCo{k? ˿nϿ @@{*{)*Ȉ¬H 5\ T؍HJAd1"I84Q ʞ04FQοbK:rl A{qҦ)|Ș&G>\ο؀L2++̟#@8!*_t -N92x-=;s[7s QxRL3tSN;SPC2IqSTTSmMtKUXcUL"2fuW^{W}vXb5S/Ϗ*4q BCCzϪ LKk%sKoSJv\`FB/fsr|x)MhҔ%7HCz \l[BFzOӠ! c/IS҉|8r1zƙhLx et\i.<,?"s.ڹ0h3'5 H CnRhGz#fU6=J'J뾏rBIKs$: g)nK 뾶mKBRvq"<$ǓP~nr%ɱ3?K萝RYO;bIW]|9a=COޭ{hD*ˋRȧm$.#nɌ㪽oP޳Ԍ@-ﳲa} )%0 7HDmXs?qA:a*to7{ 2/ 6L:]sȅ>`m'hLAS@5) ,G~B= lu?,I#YNQkH[;zt Z)DDy!@,[x!ԌQ372_UX6)IMB)KI/p7>qj,9 sy )lLP~~S]20:!s]tzN M,dҧu*c򜳴խ"HnD؆Qՠ1B -mi]Mm[&@&WB>`9}Ƴva RD;ڔh#,ӈ|bPŦP"t@K`TJV$P^hʫHg"ц_ZGDŮJnaZzba^'1c+Ub%h{qe| Ji0OV` e$1 k(&&"YU3kl $z푒% Ʉnx6X#Zk  v.OɁ=iۦ`]C9&6Ƥa&4pG f gz*~AS-)ـuSp~FiAt" R]zdeA:XH4leej9%PmOI'i$d^vF5vNlVTeFֶT*8L=95©@+cR+ee^$GC)|Α)v)K>tD>SaJ7+ 屆e!R2|w%8U*8!]=PQu LSϡ$<zԧ^]S !P;{XԷbO~WD0)GY,xOlDJL=T5TɯXRYpS!BSf80 @s`bsx2=4Q @GS!>S>ۍ1&K s@@;kű;S\p ;Ag1+A]A2PU" "I#| {#hddK-˜0 06(;h)kY»И(c[.j2l01*`E: ƨw&|Lf D>򫵌3= +c 17|üxɬ1ABBC 1QL;0"Fl1$ JKaLM,åDK (S&üxB-;[G̳hF>L `4!ZDDH2hhBذF8#BCC2 |i?FSHt AM;~\d[?447 HHJ` #@ˣcLrIT{\=U }H E ~1PTXTDC!%X!Hk8}P DHI?ؘ[\0 QI˄ʘHhD{P Ȁ /# ^2PӉ XeTǔ "S@Oθ|/}9 4RIq@[LC HER,Ӳ K`LԶEFɇܜT`2[K] a%,yY?V h*2U1@T R05bT WVlTYuKm)U>UךxDD,(S=veo[kv=mJ Z<׍iY}]*dɝWd*X xпhZeڥ۝Q Ӧ4!Kr.UPcDňyeً]WuXؑXؤDLAXdA۾%M[V\=Iz3 [ }]1{(^(ڇ ^߅^{=[5٤F[P4e][xȹ>Ŋp h^}=^,_Ŋx/`f=_ ȀGX= `3,<3r```aX=.׼[U̅X"תeJ3EgZΝa!f ~ f!>݈%E_᨜[Z X+)a*.bܯ[ Yah *v3c: &݇pc2)+n.vbH=d;!&bI~ %bBcC_1&JeQƊ hYp%Ps^Hdx0^14dR_YV -}_Ӣ߆,avfaeĸ6ap܍9)hIfo6cKed\g6f`gq6&If pnYngw.dXp Xev~.fا(Weghf茦d j>pIgbe[$h K9Pjlc@ ^U5$~qfq1ap IZd&gVf#[ "'x.6볎خj&ׅx.kn,>4kk>i^k,‡kF.l~}OD&llFFUlA-캞lTe]"mזmZV8k4S#nx~hm+TjE׆~>gʣX^&fNo+" /خnVfk67!9+oVoonvoPE޴no>p/Npn G>b3ophkCgߋ&_F= Q>5orv' <-qGor ;̼`P/&s-D,]"r qqrG IwL{HZn.otGgo sE/Er7'rָ>3h )Mt.?Os 3BTN_u`vavb/vc?vdOve_vfof7mR+pvjvkvlvmvnvovp!,;TC? H*nMӢhV԰c˞M۸sͻW'P[`~pc6l ËO^2>^˟OϿr@80 - s 4I#dv衆}y"h(,0(4h㇍(E] ㋂LA>AO?ÐUTViXf喿E##=hѣ!(8p)tԣ(OL‰L]&T٣= CAdO 3rH*ꨤԣ-c Lf~=r 2 sK Z+Aa`O+5 ?d2|G#:v+>0c8 (MGK-l֏/k滐=`B+{ 2[ 7Yb) NQܔ;qB%QQ` h$u N%rP/̣2$˞#{<y׎h@k3YG`HHW IE_T}:{r |jSDKk@YmMM #DHE*XmT @>WC{7/M]>Pt5IKH@?H5׊MrQH9K=wܟVTl0rw/I`zkGTmJT $P #諟Dw\mOG$sjg;$Iq@Bm48֋5 )TpXې jB|G0E "#k qh ďuUEv6]Y3`ƄF* 6A$4,0\;(9 !E6j! O>a`uwն&C?}DZaCp(GLAB4]k\A9mBv4)oEml|,?w:{N*#}X<&*eT H ΐ a2v T 6B / LNr ^4:hbTL6531kHLt|Z} l1Օϙ]$pP-7N- +ˉmLkRJ+ !)'Av\/4zBfW( H  FxAΊjFi1+)a6A%+QJAWӇrQ/U_8j*@71r=TfW5&EbqYU٦UM^kNJXaPǼ%ىvGG!+ֶ;@6mƦqYNM.r%\'\ҋ+pBn܍Ew qYmw5׹L/{+J^f+C"ֿܵg_Wp}`f6;a&Xp)\n^y%{SD /V1{c=It CX0`h&HN&;PL*[Xβ.{Y:~hF3of6P5 "H֜8=1, Bo@ρ B 6I,`B1c@#*8Q5=*V`QV9*p饤ځmqui _jT͞pI1dw@j֖Z5bdxf:󹽭SefQ &nftyodCkHvƁ ݙgbUļ ";Vuս7j@(AA hAGɌg{xwNt s};?wSZ[P*t1s=2:P~ϛD@0Χ򚨼My[%Z@sVTi?Kn!.׵jERBRew{|y\xGH/h8[I= ?ohnb>!6\]{#|r{Sx{3]8gO{w ~t`]^9mzW '|s'E-vpww _CƗ7q ?~|w8QPG aΧh/1( ]c FVׂ$1)X07Pr z'ChSHXDW&@h,؁~XORі}xdX"yw:oZ 9tp}s}(w7xxDX~~RgVxXWc'(VS W8 1z8ppWxH7aTw@d(Yg?o}hhu(*4Pjȁ،fh85q)(wN{bHv8 tרƐ(؎H56^qH)1/1wa H$(2먏؏((aG(:^V3#IvS 9bi64Y6y8:fy))9٘Ge tz9 ঄8 9i9i9)y$YiI=֝)ɜ $az\c ʞ zAWg$1ePʟx/%j": 'yڟ,*8ڢV5&hɡ)zIzq1@oivIL9Xy :ڡL()F ڠڥfwy1:U %)6p&gp)(>@0.#vZx|zW;V5R!pdꝹiAtjXm5A5^q  JzJ@zT` v9@hZo+0h'T)P J ::7u 38P5XPњJY4G7q0Q! F2J: )Q=d 2ӬFjʥikjM6iYK~ ڪ:*8^@4LI.t kHf cK򲢚=2Q=Y*BH438|CEZ{ƙj& 9c >B{Xk˶ /J[_R@03hgpc{&k Cu>;a Sshk} ZNz_kh@eB|~;:sňzX5Cu"LzɊ:w0?3k7_gK)Ar>+y)'>n9e YWCDý齅۶JD;wCI_+k[(68l;۔_1\ )ij <̄`+⻮r<xx"PKrF9h wV BLN^&>w_BB0[:y Ke-,bSrUk[kƦ{t:L q`:93:Xڼq=5@Pipf, Q3}L|("Һ @H1kk_ ?=@BKșQyZP8 Vk n <+ 2 KlʐToʞ#LfƁ i*gRqW577̼̯cfP;gfľ|Y=`Bh U#tǜR@ʏu|L8OEʜAtN13j˸5щTsvgG](=X =r<' Ӭ9AF ||ͣ:ZPl@51s4_K8s1%K%=< rn (\֎ ܲ9qB@~3U='dkx~2smBrlWw{}pLitF'}؈}n}] [L7`T8x.ڱz0ְ/U#-d+Laى]Ø[FZ4˧gކ+x< =}8:߬Ճ-pj7+ |m C)H[Þav_Wpj%ڐ  my4=C `1`L}YZ ڌ}:6Rōߞh5-8׃=Nz޹#>0+fsIr5=]U=޺Bg(TA5mHhD|.3p@gf~Kވl{NS~,.* G -HO7kT.,*>r幣 %N>u4ؑװ]U~B뭭֏+ϴn2+8n(nkNdJ^򙝃V-&y=u;zǾ$X8y3.qi]^X%xB8 0k 86ըEF$n>a.n{Jh B2 #'H/LN~0? 1Uf/D X`Vo-@Fp|smolﶶe t 2%p8xzVCy ހ.ZM5Bn]/-GcXACoX LKF>J,oK{{@.pV`J N8Rؓ&.Ƞڅ?U@۫ $H .dС?{%N )HĈE, &I\K0eJ3&C+Q&̟ mfǠGWhCFRJo&S DudWk:3ڪh/dq务g/mo%D+m_9-H8aGUDHom}|0NJH0.=2P]ѥ% J]m5EƯ)N描,%?B .qHcƖ>b-7q`{_6%t>?&whYq{* 8'=$jP"n +vJ O 4"r .[ BI.%#Nq@(E~pUR0F'3F.dy0%mѺ1$_뒲dI(ϬjJE<4͗$0ʂ1RlՀ͟&U UQRQG5N\ʞCx4kG7_?Bt,9"W[ !#_P Mu $;#I 0;l7Ld@HȠvF2:_[QHP#{e|o\8nqPgdD I`d㴪bG3I\ʯQ2=b̢0@.\!:a!;x 5 a:O"NDÌ\hm 2^ L~\# M |+ic"鲾mr(M t%J!: vM G k݉'R)D^xr$}MH-4,HiȐJ"S 0J!3qNDS/2)I>5JECTY(Tk3?.QDUG0] ,N-\s.ܰI(1=j_0jM e+pGєӊPEmW["t)sCr$_( *]hc-4l#k7Ҷ*mToV/5A*Wo"4!`'X f[`G¶- a-8a=rX#&qM|T.xmn[Z$gVm^\0c8Zn2tA XaxIy 0}ی@I0nfWZsf VaPgb; T1d&b53fЉx;"%Vk8ϯ")V:ߧz] ,DĔ dh6UKTdپ%x•92ٌR3~kGYZs+=>VʍtU  Nv]:JP#eb0+u5]#:iwRƱ-/FYd%*~t^F_c=WI;Yy3iv|B[/ԯ[ j섗)OtQ?ʱd;÷7ý㿙pi* ,4*27-p'@ 8$-8ԑu@p' ' ! d`=OBɂ)#"pL@?;>*\@XA{ Ժ/@ـB{ #?[6d "C(,2 >B;aćBc qd>K qk<ƑD*c],FU/s G!Ɨ`vK@Xjlr|)g іJFQdFF(`AwtgDe<&󺉀 1oܸx_Gs$Gl |EG\ z)Hd, ryHxOƑ+ēLHtc?c@dAdB.dC>dDND."PdHdIdJdKdLdMdN!,;TC? H*\ȰÇSը*{3jȱǏ CIɓ(S\ɒIk8sɳϟ@ 薽Hݚ4ӧPJJjGUu뭛VÊKٳR5jǶ[ʝK]˷߿ L᾽*uǐ#KL˘x3I>|MӨS^ͺ׷hSVظsͻ O %.P&3}p.ب2mËOӫ_^@޾Ͽ!K#y0F,h+s$v ($h| 3"2|0(4h8S޵?,T3dc>M$pPFm#Xf\vi#F`O?ZhGL8ti X&F@8 8JLILݠ6(ZzhOEТzdO 3rj@hvرB6ӄ٪ߠڑ=\"2V6B+Ha`O+5 ?=4Ru4̯v 9H܂dRGBgBI؃,&l3!N7h1PQMa1$XrP#Ua=̓3n*IJU4sN?}h.kcAGҤ`3FP C_Pm;W5tKcT6d܎6 g>+DG_h6_5FHC*TmT6QmctC7D1݊IKH?H5׆Baͭ(㷼-iGi8L2L ނLGPmJT $P #SvXmOG޷O%IG.ROc6XZ/jW;.3R0jlS:I> <#k,t=*>Q-ͳ88{3 ,HHD&5J, F (+1'@9́2jA?w>m#oP9x v>ldM)(0>Q4=1$VmD]smTu4e(YG//b0l#W)#pP? Iã6T.K4 p a06uT5%ٺENgs&Mpq&e)V7 ޔBhN9Swn[K[T!!zo\'[fnK7a$x5uE%R j48{zG;-qxoQKծnWr6j$ྟ/o7Pr it{.wkNwdŧOJn w7~g:ÁvC;@*&]}=;OW@䲉y|n|_9,?Gm~ ǻt(mz7}w~?7~{bS@y$Rx؇~x;1Bl|zG(h'h}dLSA7fG|f;S~X;"W~旁As 1 1(HGhǧ MAv(GxrUWwMb8(x)YHA}>肣588(sH~T8,U~}C8HmHX*׆@y7|rx8xXa%K%S@ڣv]thVxX)ARぜ؉P@p8xvhy(l3XH(Lj($~s4XsHHhꨍ%STxUP Wxؘ츎iXTy $d DXbQI3qx5*ņ@&7X*&Xӌ_Gc(؏/[ȋ%L I $uzXr (zfxCٔQ+Ȍ= {_s`b9dYfydA7e f p=h9V֓ *YQ8dHI&RA騌 OLj5NwȘ|1)+ HBSPi!VI:@pP)ّ$|U"! Yyْ٩)#I :1Ti i, Q#a D驞E~0թi?D}:iʙx'Ar,ԅIq h%a]y>y艝4"j z扢z؞. :ڳ !*lS8zEZ#эG3351N92JZzTc N&jiWڠ&5@P6"$Z6TZvآɥ" Dz%:t:<&ARH06:uZz ?Fu ?yz5J!ZZ'1Bd040P*_HZtib'Q: sd@rZJ ޹)yZYÌzZ( [P=$+X(Kܚ,k|XC5 :Z)Vѳڮ:+R:,5^J:`wER4ڲlw>@a J1? i[*> Tv|#۰&@7qkzQ1*5ۋ>q4f+A+{fJ9T4(Q8" 4BW Qu{*_ bz;# p[*Js  M뻅ڷ/k `kǫB` t qq[B;ݨͣ ໼+I p\;* +f<!K:U+A۾f= ܹJO5hE|,lэ 8pg50B;;l( #k_hD ۰S5& *|)<wg `=;e a1c{ƨn0`[Lf 45IOb(|><ŗ; QGCTLItqv@4pʲ<˴\e  df&ý UЖڰ C0f{RQL>@)SCk,hyNh0iLSɞL68q.++8< Q ufIR+hjsN=t]Cl9MhSc\\54I xQcL JD\mV~99u=qu:JSzpzͬT]cJMhPML1znܛV]=YP B9DFxHޔ 5`"3؞(8Nj:Z-AC-۴-g՟ 3l b,/ l5Ղ^>:.;גs@I{D'WN$YVl 8Qdd80J?q7㉸y)M@QuEGN ;h 6X:E5?0?S7m [Nek<<ȫxn^G+l%g .$EݵTCgCyM@~(ߵSWW5Od(12܂4} o>l0-[FN؞܂(?;B,$<;/?XįlaNn5 @CjW[nWA SV/*$jXŊ 1nرF!E^9ɒ ?Ipe”/eb9f:7u~y=Y`[ I3QEF:E XXH_=;3Il$w+ԊCەPlƻp<oF rWaa&~UH =LZg9s㊀$ll3ӆt){nil<ޘڰUcHxpW'^ :qi5f0+S[lx .Wg GݾuPGh" ?4m(G H { n?PCg H)pCdO=5 )E0DОv Jǩ2F OD@?hl7r˟r'8a B)!I'#$,9"D)o+/;̲̂H{O@tPB 5PD` +uQ?۱R RL3}Tb MCuTR/}DJUuUV[u03oF yǾzG6+'0 2S & 2=3DX!ϩIJ"z%i0:hE:DȘ"})Id[B&ɦmOQTin)C/ÄQZx3YZ("9BjbS/I %W LuNEX^a6I/r0m۹L?£K]lhI].CYVo2"Vp]խ2! ͋iecq8j[%_fش-5e~J2nAZFuJҖ:mXVj\f9;ʭeU"Lo{9@Hc^${cNeM"z#c2&%Jbp, jg,?YKMQ"f/$<NymSEIiHWK;mjB$ăBWtWמQq,*vHqˋ^toFlq8/<G#nd?͏~)3a@40_K=KtƑ!XY^c2mR숢`@Œ-"k4$Hf#K"wCLj]l<с;b NȊ 3H7"$x=χYJPd3֠w#YPn Axl$H$3\BhX[)MC|4+0]qK+|%orث(# Cf4"Œ@w[G$)#nBcT.󑷐x[S eP L HmG/ڻh< 20-2i[MV+mԷ[+Op[)vMb!6a5Ubkq~8[ aI( ?ˮ4XY㼨(h=lSf/kd=mh6:p-O`|ڊu$ IӷZ|99!هjp㬐˚ 99XƦXmuo¦ rNq!y@l\pOA%EMJ.K9 8uZ^" S>4؄$v6WTd ^[8_CE0IXҩȀ\ >ZbI j 5U6}\@'N9,q#YbqSS!ƞ;!6ܑ^jUZ#^T LN^T@%TG݂Gg^-dT$s1W$=s%gNIy nsz(tw-wȠX(0FXjsdF=o}WW-dhTEb!#'t@eo ՛])-:Ec KY Ȅ58r>;]Kv #9kȴ0P;vC 39>wi?8п E!@# 82B8S7(7ˌy; A5!V:o 6$ ClP xA>k2B44 3 3L/ܿ:AI$c2 3@F B$,BèmCC7CS΢Đ;8;; T6P; ÏDP XlAzBh@H EKt\;X CTFTD,Ď $DD 2=a{QB݋TD~Bs+4), i1dJ ? h@#4E1F8ǑBxǞp8gd >E*'+ 81/TJDI4>8rPa-aܽ,UER2JY $H6,9KV?8tJ F KD֨16̒P ㊱9FzĽ4pHv2Lz)l 1DTtI<+K d\8Yh>ь&S8$Cł FLI iI9,r >>O,QJR7,Jj <9Oq@5B-Tq./EBt+DR]4 `SljS7%PZӐ\Ȼԉc2E2TC S85Rɼ}{Q&MSP]ԶBNEFX՝@T-A)g uTNhI1u˂5Lv=Ou|CV^-Y}YӠ*8hڐO8@͗Jo׎}Y%P{aХ%ZX/x$XɊ\עeZGZBc_Y mKȯ^X%YpUXLQڿ݉EZȋ@բTyU ]C0!mʕ[TBz#ؽ/L5\buQx΍ å\{ЅD][Pwhd}t[[e ;-]ـ^}] Oݩ8YPx%G<_+`K;߶- na1[X] "_׸muW/ F- Xa'ok`=Cګ- ZmUyXᬵUm܈F [}b&Wd- DY,b-VaMM^ch!.[A_6&V% Qc>~a niIE؍* c.Vc8n7F])eDEa7vbO ؉m8VZLa/dՋI 2TN\>avN6e;<49de&Gf]axv[/v[^e?F^~ K*q6hfiN -x aeigVewދygq ҂-q!~㇆uc M r*a[ngrh&d$6(6=f ٍ*!zvh\V8Y0"Պgdvi 烸 yjnif6EV,cNk Nc\AK2 djVkykdْj~i.@e(B6>lH(/SlѶ&mxmkȏHllڮoN܀l kh弈aH2$Nv8* ߮;lnKL+)Kzn&h$ +nnoooE#Xoo^poka쾋͡q{V _k&h 'bcoc#6of'oͶĸ R# .kp/ ]UReo<WppXoq9r$1's^ۤCWqq W/' cPUs:q9sAߋ{:0>wpIq> 80(:ykrH*xr)uS?uTOuU_uVouWuXuYuZuSLu]u^u_u`vavb/vc!,;TC? H*\ȰÇ#JHŋ3jDhOUmIɓ(S\ɲ˗0cNT58{zMR%ϟ@ JQn[ޭIGJJիX}^ׯ^oJٳhӪ=ٮQpm]˷߿ LÈ+^̸ǐ#%r/ɘy3I>|MӨS^ͺװc˞M۸sG[eޭO %.P&3}p.ب2mËOӫ_Ͼ˟Oڐ]9w!K#y0H,h+s$v ($h(,0Ƹ!Ȍ 2T޵?,T3dc>u$pPF)TViX&܈el!H`O?ZhGLp*4N #-XH@8 8砄&g$$hFzhO(EPfTh; *'`hvرB6ӄ٪ߠj>ӈ5"e#*,Aa`O+5 ?+&كL#uQG#K`SߦnCM1Y?PB -NȨ 7gudQU1Qc EUC@\2(U&rO/̛.%)U8ԳK?}h-kcAGE`HH"S5$}Q\,)Ehss/u h}"I"`bS6JW+1Y8}sK;cEQS$hd C\w}x؏β[+ܙ=uDrRDh-XAզnO.?IH DO9*`A9/>ITMA97{x}~ T;1<ihq,@ n,%ÉH(_unsJ,⇪I> \Ck,t=*>U-ϻ<'8{86"ryMAb.n6E \P$;.ă` c B.}@hq ~p?9kÆ4PgDNҐQbn60nNѐ;8.3\!8 RCSAmH0㕄@Y$Ծ/}\]%D 1>x2a Xl)xN~r6f|UAJUϓXL2Myr: `+GQ`%^EW($1.q rh&/2m N?MuAӁI2 g@Xv> '2l1 bIb@pFi.*@YoZ֝CbOwd=)iڭkN[&#d!B0—2D}mmo=far: x=/l.I#2qT-|]e6h,$$a_!7&^BL- #ʁw@oЪ.&^jһ&H+7$FW]BE]hOFtavƗ_ؗ7KOۜ2\ϵB?~;>?IF.z\']g{5d&~hg~Wqu/FG77 x7ud|rp[wz='f}ׁǀ Xww{n'w^oey؃ǁ2&3W&ww{A(!($5m<+Hfy&6T8s~U|A5He/wh֗J,xv8t`)=wg9(Z17|?(9+Dt/AoA@Hfx3؀H#loxjEl}hqn5txȌu(Lj7Hh>׋ͨxG}oEqnSA5:WxuvبȉO$qg9V]c\1x`DXjHHp5ؑ  H8 P(lsȐ撺8#8pW?)C /)Xr9w,DYPixqK@g`W]8pr9tYvyxIJKea w ! yaxؖa):x䘓a@ӔSR93)A9ĕ>ق_I!U1HglAolyؘ`Tٚ$y&@ gwvٛə1r6)h3)q7 䔨9ɞ$  ]`ZnQɟ9H.# dB ~$_'70ْ ڄQsø`^Px)(T%,ʏٙ9a8_H'{ @УN) jJ#qK6 RYH뇣%Q5١Izs+9SPc#t :dj1*D^^%AH`ʡ2:ezS: G8 jZtj׉*:UijYڟajʩ nq:*ZEoڪAa0ZzkȦ%>j[c3j#7ګ{(T1l֏ z/WE8'L3_Ho`z骮z7kj%aS*ڰj"z'ëyG Z&AvZ-{@YR c K^(T40[SK ?ۊcK)u96XJ#W:_a";z)yκyZZ2 's 7tɶw$tPDAe8w*[!x;5Ht[+*ض03g{ aY[(27IrJKekUk(2G oeK_U*ʸLI 5`/<J}Y BPWP`ojg|˶Pc q {&3 +3 ~,[hqKJCM[ic;ԍ܉CHA8r-v}-P!3Ow|77Z\ki|&D[>|]A, <}U}W5oTtMr M ;I(Wi=]^TLOP%ѡo,W;ѦٸV&=sFܙ,mUsDN'* ~%s$3U7jCMzqTޔ=ǧj ھԍ~Gyr7L[W,7=MTz|+#m} @Q$N^S ! =>#1֓OM]NagR҈jveU4,ak= g <)^G3nI'2NB%,lKl-١yY|JU]k.]5!el~&A58~pDxZT|}g~7E.J;hŖ(y4JԎa֧m=)q6GMX ANLa+}K)7HXé'K!w6z 4WWœ}A_ZN9Ƴ$@U5Mb \/'mADƩ#>Xj>ܣ6ݙZ{ [HBd"FB..CQ'2 {Mo22_X>R!Dـ.aLNIK#⧵S!xR}%vc<!%,!̍PB)ܳP$ (OP?ˍ {b?u$($jU%1d(J}R`Qd E1x((':W4!/y?AL<D Q qkOO= *[2?rI:AQ^8dGM+DFSWnrйefEg9\n%_je8r]ma YgVH WVR(m V koH k U@T&TuARa\ӵ-a $@_U^I"E*_?h]M㮈^B\^^pmoiBX &?*@CwT0bv#B!d}CY@`e X{pGW X.!,jԸ=fC$ %{ GBͥc>Rc^AAؚ5ܑ\Ɍ Gd(wD骒s \v&mk0X~3vJ)n(/SB@ 'PDb++.v@8S{ W!ْ-1>Yg[KSʖɁ21kFy!ag;7im O_ `"Mֵ)^[;&Br2fU>AlmT)jeHWu9LJyoG̨ (wD唉t Omdz'02~ךeqsc2̻.'DFEDA>4q2 J)GY,~fFXwG[ z͚ "T~e֪'m$qv-u!.cn',->^aۍۋ D!sK09~t~74PϢ#DQ.1L{xI;*+%L㋉,'Nu`ݯRT&CyCq2e71$~+8˓3HClaSHϙ@" s:: x4+30EB4 ]?[0᛽SN賝A@£𴠊'!1T(wK?D*ܺ<ʾE zB3< cr@[-)0 xtC>Ԛ>D:%QNĥi;@NPDQqtH 6xDLDMD;@3ʜp=qQTSsE dn}HrI?+X];lX YSX?H P#],TIj,׏UXȅәZZ ?h OM\YMT.edzOe\6ɀp8[Ml(%_UdIPuY$Kԉo[7UF[Ι`bsLb [d҄5RP 9`QOR yZTq0BkؖMk$@Ӎݙ> uO>ݕέ jJ,^u}z5VΜ[T!8ت^4(\U-^Q(H hۮ}_ [ֽl4;j2_UQ- ^Gޚ*[?*͗= ^59FU)4ՕXZ%t >)]lbH^@c<*I.` @#y$~>eȕ(b%e5@Jћ8S=4)3efRn U]"d`1Uቀ˂c>eqNf0xȄlmq&  lgw\e(@80x\@dPEVnfWD>HUh8,> ~MFv.gЛ/XNLi>*FßA]]iiii i %,]]2M&i|"SJ~f׊T>\ggތs[SV + ffcvh,0FF]3&ekxڶPRv꯾bNhbn&'evfȎkNakEhag&l9m$HFVWv벞R\ڦkF|V1naf޾Քg+G?&ln16Xmm~jvچcyN{VoonoF=4 1Fjmlnvnyo> ΎpPmmhv> 6oĩe Npa htL9Nq_n^Nbn,ʬn)6q ݹ~g{( o)rѮq dOr)p%"*!PjT\Ў,7s4o:sЯ(pݞ2(02g U<y#0t"wttАtk6B!8sKOu9F< ;CuRGrRoe8Pc}NrWGt5t`h;hl tBXOreW 8o JuhWp^wvQu^h6:upu_WhouWc6U(m#gYOw:nW?lv{9r!wfov|w/xV6oVD Ga|}^sxᓼ„q_wm Jk-O2yx~_vsd1s~wkO$MyW'{O/)A!yzvH4|BL|y"F{rӍ3vMy/'ǟtʞ˶2Tdȟ||> (%}z{/~}Qj )h/y T3hI҅}~{ꯋn@38KalwO藌_o?.,Zg,h „ 2l!Ĉ'Rh"ƌ7r!,;TC? H*\ȰÇ#JHŋ3j8ОF UHɓ(S\ɲ˗0cjRqjϟ@ Jѣ%ݲǴ[JJիXTu=`ꩵٳhӪ]]vpuێݳ˷߿ LÈ+^̸ǐ#Kk^3g|pEӨS^ͺװc˞M۸sޝ6smZ@K l]M6f\Q/d‹Oӫ_Ͼ˟O# r L/(# =>0A42Άv ($h(,0(| C"2|a#Qx`?SPFӏ=hE)TViXfYp$"H`O?ZiGMp4N $YH@8 8砄=p""'pg = AB馞j)xivرB6ӄ٪ߠjx5xȩ (@BݲCkF+=4Ru4 ƹ 45MrTSjK %kBd0,>,愌: XGV qQU gX]:. S<ɳG I˵Kk5i˒E!I59O}Jy$A={s4]T 1) 뷬u@#!sAzk8+ʹ 1F}2pGvr_9Jia$2[3R[]{MBdkUc+38)+}ÔKI(hSRz4;5e8W{&QHS'Zyw g=?,~~p'?F֏U^< Ň)q3R)qJR/n5Ǟf=fǣƆ ͞A+ly5bM۸^l ,*!]0Q'1~'js> jWCI`3]G34D=A`.!nr KLiHǎ.eRS^g AO5`OkM.潶*ҏup0 ~>?f}ϋ]{̍^%]V F*eo}ƻu>ğ;0Imϣ@Sv~tS4刺? v_.l| wo_S3#XA .dNC%F8q ~ڻuď9ZHJ)7daH1S”Y!M{'A͙@ZCIy& '^کĩcNJ>kW_R8&Q=(Eo#"h. *E'Gm UHVwᰈ*+1@eret1K|Qd˷zuS6&p)kF96Ѣ>ntՄ͹Zbޓ7mQzZ \<'9!${|vD[,!:BR j-C˶[ / S踩C53prv˰7|BF60[J,L!TF:hکDžB<''-krH"/kNJ>!ܮæi봚2D~ LH4+sO ՌAC5zK ЄHT?P K˂Ot3SR4MDkɳ,TTVo)ϤS+[4m DU[um!TvY5)5* ڱ ~$.{5i,_F٢%چ!:F1o.U$F>@40Sb!S^`~sҟP 2U06V^2'R@w|%u[J{Thi| L cɅղ`ϳCVX&1\,`T\y"<ȉBBĩ__ B0%кΎ vWgB-1 Rla`.Y79dRK9ƉrQl$E]%$M _œR&)-)-"9pbAb☲ zfȔ$g~T̈֝ҸB,ҍ V ȴDkMw\%c[hlȻ60NV,+`TؔP%sXbOs6*H1<ȎwRF≀0J;V3LQJ'~ӓ"ъR9 0mdGc(ђ(ЫVFėC?:BOBL}Vq$NGQ3NK,!ROSn)1̈́bdN{J5>#_Xp '(m7-r!lAB@t&k`CĶljoH A,Hj0D`14$HXEGSؗvzZ^oiM (~.ݩҲʡ[LpiU. ͠JDP"U+:O>7FLRHGh  וGRl[2?:6zU 'q$ZZce,Y2YJ|QRf0nhܔ !=rlqc m<[]ܮx8]rKT>+=zao_ڈV)o1ZrB` ,RtY*ɲ$OpٖCOOI5NEkez̹aQLAZ)YtP+s6W2kSvaS3& eg (/v`Dؕ.1p@α_p,K-֚Pv%{uQM!w03G&ac8[T]wҪsDEh!Nog=6KhEoB L5$H9ݢۖ$6tK/7Ӣ NzA0F;amQj~72\U=6(ݘO&I8 8NL6-f1l-EЍ{55O]`ZKH7 h"m <2YɄ˺3Ԁ3? T "H k@ |^c>02> *@ @Ԟ 4jx th.T ,{54 > KYB+B1Nذ$T°@$!,+HaP//돎"”Э$PC ó) à0 ̉.4C/&A)GLĽ P\URt Xű UE]E^Ŭ齡@2": 2R ,uɦE BDc>3=L {Bx,7D"2g̍,\ȖGƦKA0;buZ<`S M*FCh *CD\^Э 7t ;Aћ~Թ#@p۷)HK0 $I0HscȢHITpH($ܸIGQɑɖ8$ *s $zIcmt ȽJLt͡ȝJռ dtJT4]:'L+Nh;|GPZHa??TkONK@T7 J+˂TTCVU eD Ra?+X];lYYl?ٺQ(E%_;DTJO~Xm [H Z ۰ŀ#Ԋ%l([SEإ oT0?8 (Z-\¥١*;mU[ִ >MFCq@[m&eMZ]x6,=4ԫm\GuSlh 9ccERԝZ]^u܈@p\慈K[=m _GQe-ٕ_i\JQVe~\0 p_ǺQuM7^N " : '@iݍ? >'pHޱ(_v$%KmPa]SD{(4>JWMh;-^ҜpHSh "_Hbbb΍b$a.v^U'8V h(`c#^_{V{p! a5cbJJcAU]F䏥P0 6yr@,>dNbWa]ȅ|9#{1/]ebb]"4lY p7_9Δx^d]]dY&_0fvf@cCTr|drWe0Ld|qnec|5JLh5|&wv~0vۆf6^1b%/Xq>fhg~"N,p9Rh6tPh@(F$~W< ,3k&Mf8/X_|j>tNL .jjjkIbLj?Sa蚎k"Ⓠkb n%~dH0biN-^liPI^(j.l=>k +dRdviCiFS'<5FUBcib>qd۶B&ۦnmw¼?J.ޛU#nnMlͮ^eSex6omaV`TghomV ppluNzMX﷦o /oV;k lq7mFׂ|pqV6q1eqx&w:N j$O> p"q&crp,4O _s)7 VkT]9r6p7p̮87 NaLM?6_tF0WeCh)FG?:C]?~3XqTO `lUO h qm$DOt(sh.tnYTuEs^ _/U_|z NR7\ov1v WbnuJtPlW\u_LHQR N20vvxr|gNl*OnoGmOHrؚJNz7NMwxuWxgwO #)ΫRw}xw<__) de:y_hw m ;T! * .yh]Oy.l#g8nyr77W4 n7'(zxfGzwpOYxc'{/8B_oHQY+U?ɧ|NOz7k**R߱7|ӏr8Խx1L/{տn̿($jAT|?}W{0}.=4XEl-|/~Oї V̂ a'~dUzdȞq!qx?S{wOO\Py1Ȟ@{*HG Ĉ'Rh"‹7r1ǐ"9iG*Q\rc27i3b͛&hX :X2tTӧ+JeYU'ի#_+ذbǒ-k,ڴjײMkBørZ.޼z/ !,;TC? H*\ȰÇ#JОFU٣ȱǏ CIɓ(S\ɲ%EUz7$U.sɳϟ@ -{Hڻ5ӧPJJUn+[8KٳhkԮ۷kۥK]˷߿ LÈ+ ^#KL˘x3I>|MӨS^ͺװc˞}6pͻ O %.P&3}p.ب2mËOӫ_Ͼ! qϿ!K#y0F,h+s$v ($h(| 3"2|(4h8HS޵?,T3dc>Y$pPF)6-P\vF=Q=ӏ?a8ti'H?9P1)hCLILݠ6h?@0Gȡir)܏h+d3M86`ȨkYa`O+% ?j=4Ru4 fmU`NR$zTR妫nTY?Pk/G s/MȨC'0uu0T;1QS 5,VCq@2$'U"rO+Բ˥"%%0Y4申K;w팖6t/Mzd*!،=OCOGVܒ+c{Ԏ63ٵ劭AL Q$ PouUdTI=EJ#SGKS$d CV_7wڕ=ML{7FZ{ Or=$H 6H?娀'4~_̌QۃT-^R޵Ҥ8E-[;R6"nh[:⇧I.Dj$4=*@>=-ȋ47{ȚG8N#k hpYc.CRl7łXX|ZA@hΉ{]4e3YM|ؠʁ5SP tppCQ$bf6!P ]ЄĥȥHG!x;J$@:gI@# 2P BOc$8P.[!xm; #YI&EVv@`IBQjؐMs%`:2:})#l$2"Ib.EЪѐL2lDVH IGC7ĆC! bpeԐw2JBȅ=A\2*V강NĥqLBe,LXę+7eFbk~ӾT |x(WP5Jg0ثT%~|'fbT'݉#-c誜@{N-]hEZVu,Pj2OՌ.bXXþUq Wû%O1+6VY"V(ŗg= fc5mj2ۗcJk-vۏV]g;ܫ o\µumr?FZ{px]v׻ƝP $\ҽqQ[̈́[rZvo}2_es[қƷpU <>e+^ם]\, lr|(G%Fx*9w@L"D^"&;P,ec.{Y)&aVv0Z[|Lf+1ph' a 4Ӓb!afYWozS'L@83pδqByr^ ZVtMlz>4'p}5ݮAiv]K p @ݢx[yt 0'Hu){+vD,FUD/BJ>}*O|N6Xj HN7x|=ri_ی7|z 귈B.QǚG\wi:2z ĬV}_w/%XAz"^] s?[^WH8G}Ȉ9ȉ~8TnCA{ϖ||L؊P  2Cz؅:cXAtI؈oN(' Ql6c~؋n؉$8hIh؎uAXCt(hut(4P׏ɊR`hn2 7X8) /H(@ٸy$) NT!sؐ9A n1Ř Zב(09C ?IāaE5@ HU.9@9Ns8bDS#er9tYvyxz<۠ z 4c`aWiu6or Dm,ٓcP q'J wД9]_:C*q !64&CkȆ)&*)j`#*Q TH$QA@xcJ258Oꥥjs Z!BiJ ٪*t:Ix1ʏZĚ։Yzs3zoъ(4DpJqy{ȯaԧxqASq V ꡁjZj6*C7*A`RC4 뤒 ʪ͚@!ۦ#JJ(Z*(Q=*1 J׊9ˮa5c.P4H+5T*av:xGC4[˵K_у˴)A#@@4 صyPk` Y*q2F4)Ag۴[ Gvz jE2$>q븆kImW )Q}w;SXA:+t^;6'_۠r;+KPkhE@B׺Z;_M);ZEb;[T6+|zK;6;bÜ+LKj"8I,5+Nc{ak'k=gYpX),<\j0,ѕrȀTǷUDh< b(V1mG4OS3Dmع ,_à( I %Lڠ9lOtț]ŭ4F ڣmȖru rRM}{b Rz>7iSc90.i lAY~+8UנJtf[B[漭zcS=2b [A@~T΀* PdST$h+Q=gsuN,gjyߛT#19>ctD3Pt`]Mxz^TgIW<>:d~lv=6n~nXe3գq};T>63 =A-\X=:Q^L2 ,zǪ2~iYHg?_}MXKgO=??1}g` 4#fHWs)8jםa# J2wIG:#zJ}) Kx(/ KZnWfX !XDw{9S++Xc <tG9`cv; 2 `D/YW|7ӤE|^ɷ=`HF@*q*5@0\O; ^|=uV49?O{zݲA To$0lXŋ1n1G!AjYҤAHHr%”/eb9f:7uV93 v[~igȞOeFjUWm kǫ_E1gYdKj@ ^.{dHJw_poa<jUH0+v(%Lt㒞_# hؘ5M*Hv٭̖yފkGϯ7-|8M^QGkㆥ{+|xtOQa |6Cy b`s˯= JnLc/!(7v $y0ˊj b-D;l<ؐDQC;D7=AǠTq}4R/#$`* Šd-ct# *G D p(Nl UE"b|eg, k(2d+$NM dg]vu]xw^zW"]A&X{v)?5`U7 6Qxb+[bb;cܗ ɪ̮#$`f6㚅6cmYq {Ό OɳK#c@taJ $a]JkgKll9F+9BJ)Tm=7Ue(14y#9>*6Jlү+QھrHB 3[)ZT 3\4=g2uR+NR{pCc6 Hqz=$')"{r=~c:G~*S zҝl!EyH TL?uAz$PxbaEؒ5qy@(FDOBmf hJ8P%*P 7*!SH9%EW9 $A=,2yE$Ȯz5H F 4 HH*{"  DM!)DZĔ㴣4/%GhɌeGreFU:F22'MzM;U/JTI’5"Glܐ$$/K%3z$Ea3թvΑ&9D-dx2!A<Ф.Ґ<5ҠOB]v,(*d҄y"yLR[ HҰiOP@ɱ1Fxz7Hp(BÛz´9C-6NJ}He806#iBgvTbVhGd* )&@*z5>6!"Έ*1n-%}: xJYAc58XC&h򳠅ZIUXa3+[kz5# C2/"5$IkJ,LP"$tE1:m< __3~+5.y܇Jn3xeVxYi-&2_|{,Eyt$t7"M0bܦ:x9s=cdGݭc)D^TK ]Q`K8REx䳇D>1 S<- Odd'gEf3P̆!~K.oMSa,srh~˓g?#@V9T*:4rT#&yQ!rGLJr+E/H&ƣc sMK1N/ C'ߜ_cNWW-T[|adzi~ [Z "5$Jj_Hr|.)|, "&c7Ǧgى}8Xmf6Nc$pl?p Scc\,6:Ͳe !!D[W^z 8r}oF_aKmptRِ(zhUٰ/iW\rj2^UIzHI$,WU|\od ۶F"`{|pHy_-9( Fr$b&$p$_qӦZ7!;5_y \ _2ۺ,?LGCRqʠ-~z ޢu//7@m/Y"{㽳ȴ~HoSEϾ=&_'s{9 ٗ!،G \@b\SN1U@NPˑI@F|B+3҉ D|r& 0A k)TCG=Ɔӑʺ TLŲ*3aW9nFN<8ŭ ;F Y 7Ė@U,S/O%[P9lLLd"ؑ= pCQ!X+X%O?*}K PQ8]Q};J+F.qUVj-/+<=uۆ|[d8h _|Y<$+`Ŵ纅]Dz q^L] lJM^гL^%^ F (݌} P#[.=`k?(6 > PDRQ{mb'6 я툋a^F\΍0^1 %Bވ^c5~"S:c`R Ѹ(%^>`cpaC1=bŊ /+Fd;c  eQ@{@d@eW~eXeYU Iӝ4!B~DfbMd(1I܀E2dL&f"aef2Y^en̑c CfkFY5gp^(`fc~br.gd$LA,g.ō\+"~^'ZJ[^Gg{hfuF kl)9~a!&t(deFi`}}hYƁ@e=i8-7^5,i.iV.f_b鄨 8Tyjꯖsꧨs 扮kF(~jHIJVcd ۨck A'7˫H!kfneT3FclNvlvj~k(dиjφkmgh>m"NǞf졎~ͮCDj\m6iNn>pchmim|cϢ9kvp^uۇ֭nevnig%Ĺ~l=@Anon onnn >p_pܼp^1qlqNF _] Wo_olऺè #HZ?o! d뀓3i'r*r`@v[0+p)"w7'.(p-:0=s |V ^6XrEoEgt1/ Nsl>AF9/qnn1%9@r!8trAm 7{tV[/@ X32]wTOvew^G_׉mgxB s>WUguU,JN ϊ]vNGsGwOgpMGmꦂx:6tw.l_>Qnsvuu?rF C-̱rv3'x|tKe,zkhw Y?wogvWuПrh:0Fόy?v,z0'0 30+zxzzzz{{/{?{GV X{{{{{{!,;TC? H*nMӢhV԰c˞M۸sͻW'P[`~pc6l ËO^2>^˟OϿr@80 - s 4I#dv衆}y"h(,0(4h㇍(E] ㋂LA>AO?ÐUTViXf喿E##=hѣ!(8p)tԣ(OL‰L]&T٣= CAdO 3rH*ꨤԣ-c Lf~=r 2 sK Z+Aa`O+5 ?d2|G#:v+>0c8 (MGK-l֏/k滐=`B+{ 2[ 7Yb) NQܔ;qB1S2P#u V?ܓҎ]E 43\ϖF,G޵#Lz-0=`3DP IE_Pm:_@hsԎ66Ym=#L e-$a UsHFs5LX+MQM>=.u5IKH?H5ׅ}Q(*юݑno>ZQr>?7L/KyTB=$H 6H`?娀T'A5~_ܵV-+ bGne{F;Ci`Ӑ26(HH/[ )v& ?(a.wS(8?VɐPG\PjG>?`k{(;.wgR wTj,JĂt FAbx!Hs_0p::cj[s=~N"ۚ5 kDY@Lrgl&YP #юEM=hF8 aQXDSy(Ajl^ }[t"KĚ%=B3VP4cӌIdSo#9 2%辭 !bV혧#3:GJsM69E,t9!EӚTҊA\FrH IGO##ĆKaZPkըw;JBȅ=wg*͂dB-оiS*M7CNNRT4!K&(&+GYYMbkվV |ax W>J!ޗ T[ E1:bXwR5^γ5o!i -^ y =Pd Y\N빢A-fpɶr.upy%l5);>aѭs)"Hzcܩ7aMmoe2Kxk,+_o䲒!׻04,2 Keya9'pĎ+^aw?ime8kc@EULKXIYdV.{`L2hN6pfmp x'>H]2BD1c4z!.-hJW@7htc[i| 2-QS%TtKmPׄz`!>R;ֵu=쩤FYyx.T2^ݬlQOڲj2m&`bKB2e3[mwGߜ}vrD Ljl@"T7<TWjgHK9'FJ >‡<{"dTpd>ntgGqauѢH/j:2_n3#/T.6wꘄՂ U-vP=ꅗ1.WWH!j-wSF[!7t7>.?=E)v׳^>'g{'zPM'$߁{~e?{C˯1D^y?T59}ׯo@3TdGw{i|^=2h~>quƇ{l_׀߷Cwzq ?8Y)] ~?P(qȀ(|]c'#>uՁ|ڇxS (sdǁFȄ9ȃ0 V|;x]؃\u:!3LnmW[xl8tYXkzi7Aa~u~ffSrL#cRey8hx* ~q TcX8nvhzw@uYh#exȋɈ'ч2ktxxǸ~;^48#ՂREx؍a %tfٸژ_~8؄Hhy؏|ِ%33Tȏ7IYܸ`4 Y87Pm~v8:<ٓ>@ f{WAbv5Y*)!QLpNRy+9(AUYMؔ&yّ_ ! _ Xr iɒkm4.9 whlI`ii! qx\ٕVflGY{ITB!vg^Iǂ!.@y9(iYG"7 IYY(ɚ"QЛ)QyQoQ)Н ) ԙp)iIyZ#Az!iwiI) @3c;c *zVxw#qe  %ɠi)k$*0.9ɡ0I5)'yڣ>$1PpiW6(L)8[3BF8ʢ-fIwgz3z5T P퐥٥i<Aq(>@P%x.Yw;C5 P!ͩLquo(15 _q  j+ʦZZH6>PS ƪ|꤯PR*)1B LЪ8ڤ񃁚>$TE!(*qtVzhi *j^:9uC 2i˯mʩ~8ji?v \^y5A8^23V*3 K *"$@0g 9+-hKbG갳2ۨA=j5BH`A1}+! "ʙz' :+>{i/;ʶcX)(q#fjynZY@9a SthkɷkzPz_]{R%d JGY˦scYl12k3;jd^K58" GS1븛۶ cYCU{y8> [^ӚڻNS;v{뽵kKWɛ1dp@ K};{Qָ$1`>*kA=< WRB<8A8OKcWs Oj"(A47IՇ(ZTJY| wV bNPs>&tBB`>y gKg.lsT ¹j8qL`r8$ƭ # p[Ȇggw w`iK>C4^T3ƒ| }s S&_X!ڽ\gM {|5(W!TLœL . lKʓS3~ m\˴qwo?R*I*L$mt6ny+}ZMk`fB8-h|5=C5b7V,L [X7=VdwL qu hN{#k5Swg |̋lrLR/Pٛ8]M1)'+A- P缷Aq1F_`XQ- У59wK)C=l[+c u-i-?BH~@nW̴>91B0]iM1BkGs-,-Muj `-Pch\г]26]\AK2@R[6-LAh+\,v]Ø,l(՛<}'/C5|yL ƭ=чܤm֭ԓmqR SMQK=+ } GYjLpwhIVZc,ysW]i>F>Xs i_MZ M) =RŃ5N}rKr@>-2C`6 P9A8ݸ8C qjҒh$D15H|~LP8p@G$ ߜ`-p^S_ K^gؑTH Ml'^>Gn>=Xb+n jۭj{F 'z:i3|1qU5=)16k"/&Ki)B#;{b]_mPx~E?=Q?oD XRO`gnLmtO}8Qν@s_Oqwk!{ J._ PqZ~P^oPB}ü9)bao p9_ISѯQOoyjo)pV@J[f< G$|eA .dذ _NXE";dAC4$YɌ1$BK1eWM9us=Aq@P7 U4ӤKNz Y)HڵTaDXgѮNoT `E:n\s\TV3*̖p9H cS1gNsI V8?{|y5N{QP]nӣ.>wWJ|gB*zG†q@Y\7-Iyԉ#GV] wԇp1Xs{ pQKubAAځ̵"B>'B ,{J!Y9,- K@@gq P*]MI`L+v5"44"r23}5才ΏM󴹽G% N[9!u6rؿR sB p)x]{AU6~as]I`v;YV*Aary/rJZ]z \D+c ps ^zD|H1,2*ڭ~=%_^p'a$Υ.j-TBܴP9I,\'_aGrAaa (t` BQkpEp]9[/$35x)()>28ULvwGhHȊDG_a@«27s' fK&o ;lC7 ͘ ;!dC!=󓡜 U#:/.t9"J.R,^aZ+g#~-KeH.ނ@#?>&(b|WYݯZk^!X@*:gRf 'дNi U[ $yRsZtATVp`Ɂq-h!%J b*oa 4%TdWbv9`IC觘,K˪ԛb%l3H2 a$OD_NٮCL›sTz dul,eZ3bV X Xk xgd ڥJYLr3hk|EjA^ѬA^+NZ7)%W?>$H/3 LXj+Ps[)+nc[,Υ;RQ/^ObR&#28A+ vYℬϿ0ntB܂m%}c %1wE~B\}0 sQ@0ׯV29kEVUfCY2ᴔ7+?Sz[&(9Ď,εAٵed/8f}xnRL|3t/Wb  e87\y=pLf>obUɹ).zA r o/2>bBO53/~zYTV$v9}iC% iG[F Od>S'KmX bBrimi ׃Xldl[-hSz.2&q.lO(^lޑV^ZC v ^|"J1W~۶1mg =, 'gdaWrb_br`dK=+wk)ɢL:;{~/twL]P;ȣ^g{]$cs!V/X>YSNUrW\Bæœ}.Ԗy:!{Azo-b9_c*'~Kj.+}T}okE, $yѽ{olo]ٳ ~kCkP?"V"8˳L[v;>䓀㺿A*@)Y;b+8;P᷸s 3?8 7{9[5C@WS@Ǡ8LC 0b3бJ@;@MBP`=s0( H!@ӓ 284TA*cA#d" "Ms86qp9?l 6. JL4-?K4 .C "Cy-?ÿ< 2ӴSäȵP >Dd` DV4~#=STED 4bqg>Ɋěk= k8cƮ—;$h Z@Ēzo_Gjf|KDuPxdy|kp9F| G`qǀ,ȁL2ض CɩIL xp?FŽ@s4ljHi MNLXIU.+@\]®E4HUFpI8 dtʪɪ;?JqL;ʡB) x { JMNl V TR3M6UJQ} K` M:wf UQ-aG8(UUʞBBKVIS%%h\QqVg}ֳ:p匪B W}W[=heiC@3S H,NToe}>)Gx3XcTy[UԨޓ ޝ`Z ᒥY%_ u!U =a ~`X_/G-ab5`ι( T[X#>v84h!M"6뀓`}(n'N|u_K-:F^^-%(b/vcZ=ccNEx:dKdLdMdNdOdPeQeR.R.#"0eVneW~eXeYeZe[e\!,<TB? HT5Z#JHŋ3jȱǏ CIɓ(Kkq&JI͛8sɳϟ@5eQ{&]ʴӧPJSխvjzk&կ`ÊKق[˶mvgʝK],U߿ LÈ+^̸ǐ#KLef8y$mzjsӨS^ͺװc˞M۸sͻӪLܟv 7٘;`3A{|B LËOӫ_Ͼ˟OߑjT3q0 -s T2D( fv ($h(,b#JwCTscc> -fH&L6䓷5$#=h!?vdDҡSihyCV:DT33 'r2uj矀4 QAdO x3rVj饘5`u\4lf (H"Cd#f꫰(@ܮ3j"H|2ʪ6>0c8 lAGv@Y?P覫=`sKȨsu/T?U'N1̔KA lXƗ%O#TN'Qw6r/yd*!،=;CR;rM)4NKy1Ghq/!kg=xS`4H@;$~XG#4XMi1SCvdOKLf=* )=4m459M)Jߦc6XKt;"=$H 6H`?娀T'30~_|L=Ck9G{Sz䖗o>m81rT:aCRR#%;G~ؙ(`8`L1H Z9A ڣ[rAapg}fYq Ƈą%a l!< ~[h LwV pK7E{Lh E9qBs6rPO8;A~{Wd390+r菾e.[Q^ЬDE%:o$E5M2N^`Xk%!FxcH:Ė!f-!`%҈ RJ^rp^tij d(yVJOkl+X;hQYA$5D(e䂤Pz HQq,!@l(< QٸUvG$4 \#x'F^sjKsDiJ;dc.>O~4 (Im#Җ}B%P:/\S6= )IK*˰~ĬSi-vt|'H\o:b%LWU*}kS`2G[ckY+ذ֕ kGwPwnUB &keGO\ W<7שajp @vVQ8nX97|o|:(Ŷ؝W /x ~h!Q5 0zZ8Tq"/7,&~t "k/Anj&;7;y,ϹCޓw ЮuPu|O^gID;[비{:x 7zǑn2}:q~wV9--G)nxz~?k5v`A/A+ufqٯI?'=s?𰗈eo>si}9'8 <|/T?k﵈ǿt}W؈WT~ڧ~9^yRw}Fx9 0uv}؁~!Hk xt8 hr<`i-8,(ws0Xj7gx V藃:<؃z |yd+#lCh⸏荣hY!rhhH&q׉" ɏ!AǑG+َoi AzxtXXx ّ))!)[g{A.9 $Mٍ, SC1'#a$1Oɕ[yi9$sb)uiD_9!tsfq7uْ)wyg3'ѓ)FYIU'Amrɘtygaxٙili6&mYG)oYa;i]Y$!~dsĚU7iIy!!PQМ R&3؝Sq  ܙQɍk #q|kțQnL$9I&EyО蝐Y) ۠a !*"BMmwҙ9y7 ywk  *4jAYK CZa):9w8@وofQS*U*X:q`ePgZTK)/zԷ3Oʥ<cA'@6231)rjtJvMHÔ{<"CjJ˧kD`j7hQJsDr ꠏZYȡ@:ę٪ ح9HJj*y5x%=J>iʫju{(!N&jDJ"_nACjf>:h9A'Xm:@&f*`pY({)@1;Hdp0]IR;T dhUdfAfqvb'{؊ q֧!B@ffvum6#Oi{˲<j;( MJ2a`eX@H$'&kv7i[kṠkZᵓ( pgY|{l{:R2֢򚹮<@g{뭳F;#2;*lu)z0Mqr3ܫ…{A2\ t,Q {nɾS{KLY1^]nv'\\?V;]KPf1ƌ jr[X%ŋǣ3f`4Iɼ[gKt}Ž\IK|.*Vvpkeы gLil{a['3y$,̎| @hs`1ʱ ̂V&gvpKʹlB@1fq& 3ϑ:$Cq L&* v LeS'q* ]"yr0@}Fz %-^;< 5*RJùv@l̏MS-huk0/ ֱd}bV֢ez 2m_m [aQ}C.6!xtaѕMZZ\8%/ׇ5|v- +ڌ m٤=71ir q}Dʭr ܸ]Mx~= ۩MLL죨-ݱ;X^ Minޚ1#n2> %1~0siׁ9хClA9pX *+1Ypö-ZU 4 Xd~cVf ej-vnML34*e27h^LCˮ >!{ 8Ql刮 4"r^p9:3^-. K]4$ƶjR۴>5u֡\ƨ痭C7,C+`'F͞.®c FؼqNS@E-"~LÁmCL%!ɢoP>! $Ab ٜ?Wj4ml[>`mn}'[:/?oV>64 뵋Mɣ<NV̽.`o[E\-?Uko  }-4!x10|H旌-ݡ 7X_8\ٓoH_/v6!z^8ވ^Pd| O.݀MnE1}mX%SOi"B'~?ʧ|.~tUHР,^ZǎO:q) $XA CFXE'.$"aCF AZ+dč'Cڣ ]”;RئMVA hQIO-IMAb@$@W AaV3kj,Lمo:;׮P,%k.wOU$Lbu%Oe1H@˗5KUBeCnj\~ tT ۸uWV)Z3ÈC~m6ʈ}P5gݦ%dHd" rGzyְ}{;T4c.#Tj#jp%$B2 ! =[ɽ 7ǾoQEB#f2LЦ?B: ,$sjI3$J/p3/*eM=K5h"nP[|p 7pGԪ#|r+G|h3\VśxJK\30}u[wuc|Lؖ[$.4=9wl) jffφkZQҪZn[kf~|癍r$^V+A-5=[%y O#_T=u`r|;﹫ZT$@`I {X $[V &LOAhP:m)ia+\d` 1B fM>L:a10bP|hAH1vVyLaD݉|TMGg6cq~Wy"DhQsl$Li4^R<\,>_ C?$C+eIm8I:<&V e(/Q"bL*x*1a (r,MɂFJjY5W9#iˋH8C8 WxmY<6/%'2D/vI5rE R>B7snS:$lYF6/,bd>=%UDIpғh{4j(dSi說 RdSz͡uB`[cxح^kHRL8ە]7/۟DB<}=G| U>!>nWxy5Cz;EO0?}}Sm#&;=p 3 d;닥c@6c |,A9u*[T@ Sk<@;C$tA,"A* A&B ĉBh>+%B/tBx$"|0$C 14B:r9{C8$=C9C4 B6lC%CD?@یٻAD<*TDAFDI@<1DLx* >KDJ,CVRl KlEC|EPC@Ch J \tb4F&( DF,DFLd)@][n\@eE@iU Ƕ \@?'.P&3}t.ب2mËOӫ_ϾGH7@Ͽ!K#y0G,h+s$v ($h(̈4h8؈S޵?,T3dc>]4qPF)TۈR\v%k#G`O?ZhGLO2oiX&G@8 8*3 '!r2v6(ZzhO Ef*= 8~I jjEhvرB6ӄ٪ߠj+C4dH+,Ka`O+5 ?+@ H|2N#9J݂kRJ@gB y! U)&ӥ?ơ^€n(<0#l Wn6 "~(9`LHZ9Bڣr<as u[()F(# |4ΊѠHP#0 :2cj[#5j#5 kC" sAH-fm pg=)sED Β&%@-pP?äq2T*I7Pps,Εp⿒j dG.DRrlѠh)mUT%oW D($H ݔ!\I:XVM-:?quDlLrLF,8p; )7|8$0FѕlGQVepi5ጒЀrauPeJ)&Hyg'ię49 Xdj)"@ }B%QmSB/%|,w=<.Vo+e)ƶy"k6\1 kZR`-]gE+Ql2P -z}J`?jc1֎bMb5K(AGl)fX.K >KBr6d-VJk1%jd meWYiY\6./U\ҥYvw2\.W%nu z݂ngߕJwo{ޗwmn^gz͎L81oK]P>ۼ ;x%Mߢ0'~H' EcǕ##~p]8αw@L"Gr W+xHL*[9ș2 0@`? 5ɴGf33W*)0NVڅ1qS:V\T@I! a 4*pR8Ђ1e%wu4J\L1"|B ^y'^HgZޅJ8XDURKj_ X#8d {_d0'S&CRΚtDA*"A}-i;#钉et^ysvP>nHS( x95zK7|'!]:/i)z* t3!^f.t;,s^z㛹Fw퓮 |?r. MTQ{A^ۘ;i]h Mp ixCAM߹zX _=gUr/,t}-nt}=zAFM DԺɽ֐e9.#QYTpɛY1$3ms G?N鏄0 hH{Xa48Qْ!iٜ!Rw@); hXp= )15(8 Ƞh :ٝ*鞷Hhhs`ɝڛZVAyy! |'%j#ZJA9Hk!5j3:A'E נAڞ7$ T:uؤ!Q j'y!> #aʐ@ɢ̉VJASKTSxfUsڧDvwiȥ!P`R:WJw=Dr7Y$QT0%9uJBXS cjUAHy35F@ (zf"ȩ$C*:jh(<*Eg PPDZJ?DwʚŊ+Ȫlz?qqfZʯ1b3C8+  cڰ eiiA1jH`E9zyJ7?Tc d5T㲆zڱ{ Qi'AQ)d L+EƊ N)۩k1࡟f%گ\۶ JStˡBa4p=xʶ4;Y${&~8~4} 1kjoָŧeǣ=W[3{'2#4 ӉZD`{)qX=@hǻk _IJ/[(ɤ Kp+QıMKjd ft)y[vc~:,m15ll#i .>Nim- ;oKwFU_'&A>n]3yf|H l0=??A/&Qy48՗.iT,Tf `PJ>aig"Um kn᥀P}\$5|o86 8$%^Tֽg5jOp.+> 4ݻ \_ 4 aBKbo9@}kUap3.vGi/O^p(r :$XA %dC !NXџD5?E8Ò'Ueė5H V(޺61)zhQGnTJK/J}ĖW"#C^=5'V 6 \;jۆomH5WA6nic2Oe)eάSh6{< C(zQILӥ,*I׬m7#3ĝ{:uU9 [͕;^-О?޸˟`sڹ)) r? $I©B(p)۰过 <ÍjRVcrg7Ųi4DAϛ"v'bn6Mj)u?^.J=#AǢkclirrN 4`ziNL@^ ZSr:R]qơm.xtQe/?zW$a^ZLA7MPp"H@Dv8HS m#yJbNĞj_fMP|NE+J`i˸rTU ML 9 B| +짤PS<C`t"VCBiS0=vJ먣}"eW<99x "OST<7-WNiUAFPˊʓ/3VjF8l6Xu/<%,Bt j5'fhX'yt^tCgoH{!n)*.ꗈBE·'AOL ϱ7ژIGv.9% /ī_*`J@ vB72̐"aAG)ۖOSJ]OjHfb  T0K-ULMblN}ъ|{ݵ"]> (E;&ݝ 4xlk1 lLJt Ά p&CWC*r[*HvU$k&|[[{@ m6-6I)ͬEC3lZc0v=ƞf2."R]=ryAp_  ZٞKskVq$Xl%1ݥ0En})L _HƑK -7;.]r8,lF`yU$nU)d*WMwr)K'u~ŮA,s(0Q!bw8o0M>v\ävfʑsk95GW|Jkvv X| rԒ{ #;&ю GEOE"H# QYU#쓧H;<+̜J',%ӎct]XuJlJ"ȫ2wn3Ohq[:;λ=H飾蓀$X#k؈q(xb@د{PN(AA U`NPفAAl d@ 뺑x8w b<@ h5Sk9!U kB]8x.)Qؾ@?kU22Ș*5, ;+,CC=(C=tL㨉?@ X57|C@K&HԈ{J30hOē ť`CE\D`$@šI4_Cżg|AB  @)lDB)ª؆CPE1Dž {-|Ƃ Crak $9w=샋Jr\;PD fL<4Lƭ<2ƍBq|`Hơ* ɜSFNHZ60ʑSIp`BI9lD땵A:IʳED0G`,K>HHˉ4r ILɱ[ʹ(E4̍ 0I( !ɵH)hIX̶>D׻0ؐ4Kx8Y 1obY bF iܹxYʩ|L,LXܑQ;|I0 L|RC JlxJ<𤈧4xV&zl9` I0<.㠿Pˌ5VtT?=IPAU洇\41FKOҍxE (͙aR C l !X+X%]?TQ6 ˨SPHrTUڀ94U OVVWW ??R1d8Eh6}?0ppp`=>y*bT`8#oΑ bs3A|X-0t΁AWUpa|Ф<&q@=MQTYxUaWX YZ/Y$SàJ})c?%Y%ᬈ#{<`ډHĝ, GۨPeB]xU\@H6.iXDRUzmOѶu!Q9ۊ:7m]\uHn` YݍI";K˽]^USMu!eߩMbX]iҮ*! []罉2H[͈%ތ02`̋_U[?G_:@J^ë#n+` a<_6 eʇDHn^_^I42/(XL*k 0p7a%F?HyE0Ub2NNPJE D.|LӻbC-nc@.7e ޑ=V279IeUNe(W1] [+I]ef 0Aj̱Ę>fpgqgr.獹d^լ!ݨe#g{"a^ex۽$>RyuL~GK rMj,fcgvc}~  ~p.!߂e#Ni(+3Z r^gzgf~ `nH@>8脦 i4 (iFi>^h|#m_i6Yu ܻM1.k>kTk馦 VEi\j62xjFNꓮac:l^(ʀ.pl^iöl~x݌o. ։i;".&^2Jg>vk oloN0氺p6q&W OHvqkKHZp^^ZW OM!r&7Wp.g6OlmqfDmr'?r= noI=s7/n.ܖsHnOdM-얋(NtSGuaFn9 EgtU_sSGr"+hRG`Z\w8utQWNrJub?@v8/a ƵS7hivHWb/ VO\Ow %x#*ws)os7%o Io7ˠ^p'wvqr{uxVi*] L/xcU_|uOy7tw[n{ZWnkϋ$PCdNot_vp$w_zx]Ov BqPHozuGvlziAù2}j{_)u3h?{]|x{s]5+0l{w xȏ|ɟ|ʯ|˿|||||?WW}?}O}_}o}}؏}W!,;TC? H*\ȰÇ#JHET5ڨŏ CIɓ(S\ɲ˗UM5޸^Tɳϟ@ JF)]j$PJJիPUjg׮tbKٳhӾlר۷pٶSKݕ˷߿ LÈ+^̸^q#rLˍx3I>|MӨS^ͺװc˞Mm|E O %.P&3}p.ب2mËOӫ_Ͼ˟_Yݑ}Ͽ!K#y0G,h+s$v ($h(,"Ȍ .h8S޵?,T3dc>e$pPF)TV9o#\veG=Q=ӏ?aA2oiN?9P1)hLILݠ6Q?@0IbeO 3rȥj*B?ڢ?v4p7*=l#2Z6Bf (@2ݲ3k e2G k: ,5Ln*1:jK %kBd0,>,ڄ: XX CqQU gÚu{q| wʎ*%$;dU0UO7}h7kSA?E`HH4$}\=4Y4IKH?H@ug'ⷠ=9iJiH8أ 2jTS䑀-XAӦnO.?IH $O9*`A9a=I4MA9/[X}vT#։Wh3нh]B.)MHȕ[)ų& l?`.wSTP)8?V@״\iG>?`jSt( 574 2D?B*Zi 7>as_ӨA?eGBGaCj(LAϹ1_Cqr/ (gC\V È٨xJNnsЛT;P)a! U Щ@,46@Bsd5[{^W":2F+uOR)RĜGб,d*CJs׋MK[Sbڢ8̮&(z0|D*M|t` [y=5M?]jī'I:Vm-Pv ykUtԜ%]gȪF&TRWjw5;lZ Ktuqr J6fXbׄ6FkXF%C,X[zH[Wb*M9[;\Kv:l?\* Js[\>Uhk޾գw߆6x]=5?pjWՍ{ޟLrCN[_p@ mE`c8oxE*6\_id. y` &u9hSZe3ʱw@L"HNsL͌nL*[XV Ȳ<71h'XhnI*) U1N`;ضڐ6|X>G !&% AǫGI{80SjR~ߊUE'`Q}K={yۡC[X74H4-3cj3l.n SNMe9`zدƴz1ek/~s@@M[mbpDɠD`w4p uErFh  7:᩸zlv'瞰^8_W/ lSFZMr;?}9u~iY`yt.m\gЋ9".q_ROR΅to8ONY*+ \îX ?[&\ X }wI~r>HAn+ot_ O) {v/O ״_A<$3]?rǣIۛpG|2=.p=[,^~~x;QRHD~x#u|tdWwAs{8yTxpuR|('|Wz%!5G?r<6g78&L8W~{k}ꇄHz8} !|RC| 8.kgmj-qChw4qz7i]"X:؄["a5,}p747XZX=򃦕Yx/Ԉ!H~؅H^ 1BLOHyYXȅ'8h7v8w%rosȆ~6z')芎H6ySmFX(x(q? z8JHxkVHǍ84Vv҈(X#x'sSeՇƈyWShY؇2H,莇6DW8x7I8sS;䨐K LS(BG=c∏[ɕ)Iu0)VG4&x*)w&Js5 DiɇmZJYNx}?ygX9}M ٘7EHDphɗEfٙ9YK{9 @ `c4oL y壍dY?0Yk)ؕёяi9Mlyɜٙђ 3I^X)b%W@ DȩIɝi!1APCR Hs @0QYɩ۩! g w@{Y ʘE:z j>CƉ$a|i# I!y; _yaq":1%:[쩥wx+zhr/H#aY_jʦ 6{V{ 8nڡ?;xU<ڣ\J],Q:~虥J `$ឞ#k@| ڪz>8ʞ4Lڤڢ~AFe~s`کʬ]JmZLZ˺ʢګ*=9ٺq 'ʭpy LovW%q -qxe}1m>A <>-2l5e (LHx!q! 3'!Ӄ%m@^NB4HGu0nM)fV3lK%~#nR+\ 7üM~M7-4ɺqh~/n-R890 +F?G^"k1\Zu(Huj*p%!ld) sFN~6,d-1P斢Υ9c n܌AB5]ޮ(St޿)yx/7e|B\>?`OQ3 /Vc̮`~SWٻQʙt-)!PJ߈@9 6PGojЂ=0j ޣQx9ppTv`uhwljTDancs)i~=]@O8Og)Iʣ.Xqϳ Pa# 1p.0To\+H?z %c/c<{/<0Z{ &uCKl<=A.7T֥S $XA%dCu NXBHc-d1wK[AHddipdK_ƤpfMo"#AEN@ZTDCw&̒6\zK{$$u8+K_V= ؐMh-Do"2ō ḾUHUw].]<6 5H"bt-,iAm&YŘ)^yY{w$`̺Ě]3Gzeӟ{]|7㌠ny+u8ܱv'ڰɵٷ?P|HR٭SH~#Eʏ ˞Cf۬A³J>.&Đ¯PL*,d[K> +>(ܯìjΫqIsV(TYX:1 [ `sX[OYJEf'Lõ SQZͰ=0 *\^m#lb'Ȱ)Bv׈VjQ{$"q%Y .)YTlQ^t_mYnOiݧJ-O[8EF?q;EohBc4EKN><)xlL.I;oI WҤ5k( @S{^t2 t<! :#{;R?K2Z?,ڼث+ 03  sS$:+!ЊGB2"«QN@B({UpBUX@*B/B0, # dA d?x㹢u.9ؠ˓0 4@7;3LHt's{@@L@5԰A(@hDK.ʷ;D l6=LD3y$8 þH$LY0HĤH٪TEpU#TtEu FexQ@+ jEWL,C Zlr C B|!olG7qg =h>Tx\ֻ CAF@H~ȚJeT s<PHz {XK; i󭌴ƚ؈;:ѓdtGMJF̝K AaB9 <@; JٛIh쬥1hcJSpL+O ,3ktw|FaGA\>6xK| n)A_*=+K M\ۆi0dJ Zʷ(9ˑ KPL\ȴA9h\8M͘؈?MhG t)ͥ > ތL]<Ժs̉ (ؔ͜ 8Ϥ 6"4 JĊDz|-d | DNIR=؏6"6ˇK1{z, I7Ь BvDϺ 5$HQ(#N|Ia,ZY Qkhl :a${5JJ{S  `@R,cR09LM#*јH ""5 OS l@͌N S$SI b'SS RA=JUK;%4D@ @Ĭ R`(E\Rr,GBH%R/c<IX[BK` qFFH95_ѫI͈M3U Rpy+Xx];lW|I?`֢ӯO7ٺՊYT )ΚؘOXW_XX?qSǁ✍^Sm͈4Yd< l`WY6WH Ԇ5X >{ Od؉2UR|ZS:\х=WNѭɺt+Վq@(UZ5ڽRڇЦUK[µغr ؖ/ c]eJho֕uqBaU:aɾG[\6e"і[H[M9-.P[ᤶ]1|ݲE\j\ŋZ1]d^\\@8{]^[8 UMS}=\Zoߝ>MX0]Զ@8 ׎1=5 V `O{=M`x@ ;>Z5SqW 8`ᘀ`]]B;Fn^EjH>ۑC'a a# i@p-(-ʒR^7?P ׵ޚ"܇Е3vc2.e?V>^/xc3&d(@(bq }Q B1хAcL~FvU&A kݭch63}df"QY:J@d 2[Ka]v fpgcVDnLog)>g\F0g}$+$˜9^g.h>hN虹E_Y(e z֎"qneuag3b`eyٝvg~&hi#Uc~袘iM@6iVfꞦg0YvxXJ Mv feŎiy.YǾl,icfllΆ F|YVNm܎d&^QHt slf썶۾>nf<жӃ( r;d#Qy/Vn~j3Pn6.ﯠml&~l.nծoEk>䰘ZnFmfm׶ nOjaqIQ=kpN N ;L'p qGN8Lr r4N"/4-nɦ)f7qr&o 밾?gd6p&s/ k5דoz@'r!j -6͊X D8r0tA'na>9&s@8s2 ɦRtKwW7n/P0k*t-!\IOg ';7j.X_vO.H7GB'Lulf)lloAtIug{_whCBʨJz?w~W!ot' ]m28?xsw_}w&Aw/x/y'ragxQc "v7yeyè*yh(pVOy`eJDpnO?ˑX8Ϙ/zzH ;BZI^uojWzTW'|@{7{AAJգL[{{83h(s}ɟ|g¿L +38/S,qӯv7|x;}}}~~/~?~O~G~3_~~~~~~~!,;TC? H*\ȰÇ#JH1=U2WǏ CIɓ(S\ɲK&go\I^ɳϟ@ Jc[*wkRѧPJJիUjg׮rbKٳhӾlר۷pٶSKݻ˷߿ LÈ+^8p{5L˘-g|hEӨS^ͺװc˞Mi> Ƕ \@?'.P&3}t.ب2mËOӫ_ϾGH7@Ͽ!K#y0G,h+s$v ($h(̈4h8؈S޵?,T3dc>]4qPF)TۈR\v%k#G`O?ZhGLO2oiX&G@8 8*3 '!r2v6(ZzhO Ef*= 8~I jjEhvرB6ӄ٪ߠj+C4dH+,Ka`O+5 ?+@ H|2N#9J݂kRJ@gB y! U)&ӥ?ơ^€n(<0#l Wn6 "~(9`LHZ9Bڣr<as u[()F(# |4ΊѠHP#0 :2cj[#5j#5 kC" sAH-fm pg=)sED Β&%@-pP?äq2T*I7Pps,Εp⿒j dG.DRrlѠh)mUT%oW D($H ݔ!\I:XVM-:?quDlLrLF,8p; )7|8$0FѕlGQVepi5ጒЀrauPeJ)&Hyg'ię49 Xdj)"@ }B%QmSB/%|,w=<.Vo+e)ƶy"k6\1 kZV>l`b ghSPNMb-֎b%@vrf͆sIgVJRZʶUgZSj*e-Bz[P - q ɭXu-BUȺ?fnzOޟѽyϛBľmOYwG0p b-d|m;*uyq+ (am_uر6˂axSIQ HsG_})i . F9ٖN{ZϨ $au<`kNs>OY;q>x¯'b/ѨsZw?WN)wRx_}_ȋ((%1=B@GAowV3ZFx|)QTsH~suU؊؋()]36guxC8$8'(Xxxu;%ֵ Haj' rVXoݨXW?8؃A7鏉XyCI' z91ucSibIRO 33y.9Byq{\idrI%aUI# Poyng6YyidC. @8Й9dA yhg9YxiH[;y ept9q 9#j]Oc@I)Qifɜ9hHzia48QHU隯 !!R@IYT@??Iـtɚ)SYDh)Y#iMht`J:)#A֨ P{3IZ& ar5Hk %ZC:"A'D/S6׀?ڞ2j)Sf OJ!J)>9#_4Yq:X$&qy]G5P:d*ej|j: EJ6CwzJEX@JY::Y#q=062uja*UJAx35FPZ*$C Zj:3<Pŝj**!PPDJzb ZZI?4K19JӚ AZGTA@9 Jz*`9ʱV: {5Tc t3Tòo*1/+Q%qQ)djk*ǃz XF1*q=_LYcl!ǜK0<R+!%lSU|P+"꿞\  Ll$Hp>Jce p`` Tv־\:!h=h -]-ͬs>A&ut٤F~[*a.+Vn]X 5/nTjc鍎FsuY.NܺJsSX#̹@>&()A>Ž<|:w5v|Hi$WI#v 3KU$m% eС$} R ڶNm/\ ҾI^gP1x^Dڿ\O;ɹ= H~@R`СPb_ޛnY?j?lꦉ KKLۻ\u#USN饻3 an^a/;^A~ ѯU<N5N]s/  ^Kwq2u niW .dC EXE1nQcG!%^"HSKu`|蒦? HIşoF&eJOEFHAUfmLMa/!K[ akʅJݩIPVsw;X)S{.60CZ႗1k챢0kbaI_$g/N~"AtոS[8ʥwS!ړp5ٶ3HJ‘jߎ|y?|7sKq*o-k|CxO3R ~  A LD2JH$h7 AJFTCcPDdιZQ`+* jspGg#H ChI1o0 +L*- 4ȧhj-*\&2pOyD> O˻kk͙(φ6,S <85zo:+zO`P'S3P,Ӕ-A#?̱TXO ۩A\ 'PIi%MXQ*Uij(R;㤭X_] tJXd-u7tGH6+jL\$pÊ?*AՂ (`YuQxava#xb'֥`b;^cK6䉷9)bcyf=Ekyg{YueI9e B )6=i X(jN6;]- fR҈,,QX h dǹѬNkgSi'-,2 nՄUAaU饱HAFMo$̚sF uq$|ƙHiEhOtC2+. N%JjŪ0^Po}yo1jm;rvϏl^֫tW1H2Y tK1{KB'8z8÷}oHǕ0Ow{-X6y$8|s=Ꞁ nl5HL+HmI 3cO 2q"+W4*uɞ4;J^ E#an$$aK339J3& RL]`JH(SCY T@ 6uEj0tbpG=^?bgZFE&{# I #AX(&k#K{+a;TBǩ+E`-4!fniy]&h!3X"j g'Έ9&G*f&'HCςX蚠GMC3g\*n4Qg 0Kr #>36@L/2m%403Q5;;!{B0zSi Ջ rǢ܀;]FמnS;,ݮyV,>Y:cp>Q{,*1Jڱj-Pֶ,,F YD咇nR(53R~"[S]Ԇw>${> H#02s?cј!hG0 @ًA )IUDt0Q[UqmAA@ ;=z[w(̊{xʽ8>;BQ0Ѕ{?h+RȾj Sj2*4 =q:ó@B?(ŸC<<4N;l=܈ Ű6lChL%G4𱆐5"N)ҿ{ $Pzy: $"W9HT `B \/{Eȃ栮['#E$EXZ`<gt2T"^3 ) 0)l4?;ȉmFb T>0=g$h|Z )Gi\ @cǤ}>)"Ǜ3E`J+\H/ܠZ EB"I `@,IĚDɭDYP _Rtm42K` SSy`{BCHŨė?HὓH;$# r pɺ@F8ŵĔ 0|!rʎ$?tC #dTK[`q E\š!4Gc, nL,w`sJpmTJ$RL:IP LLMZDʧqlNJp̸ 눁ؔ #i@#GlzT<))2xOhd\Oބ|ɕrB@(OЬ`\5͋h,d Ű T {0Ͷ6$K+˂PЯ 2P xP R@"OM lȤBIhL@?I 3 2U\?ȉKO %¿4{Mv C@L`NOE$Y el`, Hd͏"ecRf7:9e>.eW/VcI v[Ea:4eW |fhA[A3hfnfofp_dNfd8,ײZr4(ueS=$$㈘e b1^ 7rD hx~^xda ~XH.$_whyV{ >E] aX*i.ѿdx,F^``yS陶[?Fhj]b<:FdC6g]j8i bmt"6 jFkf\H` ;Ni&鰞kZk8dOfh6챖liFNӲGɉV~dFl>h Fd p.mǞhlͿG&j[xl,3n.jߎѮֆ 0Gݦinn&l҆.*vY)nnc =6U)nmXg ;>p8oYsk~ l gpFga'mCk/idNo_qp@ե@@ZpF7vSw׎"Wo0kt"/ )Grs: 7z!'r8?( JdN3s; s2 `e /h:@Gq,#\jm?8's(G|s"6 oONtQbBp+>*;+Ts\U/Ps5oqR]7[uWu rT7:douVok7rfrajl0 +/s_Co"˂qvd/wsov)&[%s?H'xhu/t!z7xFp@!UywAxP?x $j΀#q* iA@X_Ou/諉0rHHoz}IWIپ(lLy{ϊt7߇E(}3h@c^my # :+(֭?czr7/|?|O|_|o||ȏ|ɟ|?UW||||}}/}!,6TH? HjP=#JHŋ3jȱǏ CIɓ(SBT58{zMR͛8sɳϟ@u6eQ{&]ʴӧPJz(IUu-TÊKٳ@']vpuݻx+- UÈ+^̸ǐ#KL˘3k̹gfDT33 '.r2 yf馜vZP4 QAdO 3rj뭸+d3M8p9"2[6BkF+:qxa`O+܅ K*2G ,+"9F[tQ,CdK %7'-&dOg10!N,ȤQQN20Gd2Y.7UR71lc >ݓGj?H,b59QW^; qARۃ lH 6#=dڐTl uXXߔwM{Է@21/ !NA $a k |7UIT1N`SAT8Mh=*)-wvl$!bELk 0t/#Z`3{a)Lۓ?@R`CS `P@j@9w~ps1#prT<AJkqsRh() !|$@4` EG Ԗ Ü=lM^䑠T((.VA+HnP J/rv dx̣ Àiŭ~0?i/n4P$ Ŕ1ς @(;µfpgGƵu(HK/ƦJ9…,w) $T\ <4rd9H·$r Q'>1CظfFTU%A dA,  \" hm65ɱ^HD?2ډsA۹ZBOP?VX'l0DRO.=;cZ !6nӵ! ^6f#R@.1?n(xM*f[$F v($]"ּ6^+A8s\.J 2N{@.R}&KԴԦXoPj7kfIJUjVmGtdRi2JZodSb2W9.]d]66kzw[Vo'[`9.rK6vŮr{rPN7斗nz5AQRYf׫ߔ9yf\R_P"y \f 6{:xm3 >=p-bNz(lT-7Ʊvbx(f 'JbFbR?5V*b%3I>s[#\^*QԽhN6pL:xγ>Y* ?Јa2*WYˏ2%픯Y@4ht9晓Waqu2CuzLiAr&z6h jЩMė|ԲN=A8ǵ-l?Xv=\Aeh$pO8Ob;*P;Zq lEW\6) 7`QQ^rל*.X.9 wc(*' _,qmb]Nѹo,9 ?y~$Jrp<,}χ'Zz) _ M7S\|$.@B9DYFfj C 4Gki(69!qmGqpmxhT׈Xz>bj %Xakuibi6ɳ4y5?؁slxvZq@x:ň)eyi \q5㘚i蘎imٙᅤY稖k!ї ؛񘙚y){YZ xxy){x gٝ9#q|l)̉ii)x8VXe!Q9 ه)z iy)虞BwU&pXI(ʡʢhI%+j9.z/yKÏڢHS*$aʈ*Y*;ʅ.$1LkSbZGIZ&1v2QQJr:8N*(gq|V q x'ȚRq KI8qJ<5]*JGy *L"Z2(jm JXu*Ap[W_}*ƊF: is*' (Zr)}  ZZZ)n0?z稺Gگ |ICzu2ڠ: ۰;`ɱ7*Fi yzjIZ1תw(()9Ϫ64-KA맢 z{28:c4[멞 :jH77KD3Vwk-W$ɵ6Q4$A5;y[fYj~ӫгb]W֊ɶ8ۉMuaʹ{4Z'akxۺYkK9K;Xźڻ(W@{ ɽˣUḱmsp(AS;+h2 :$ )z#,`pͫM+;kS |hp0."<$gLi%|h gEY^gRk[k^Ƽ =w/R>}.3nb~,(Y[}ĽɍUx ڌ4 ֪階#( [.t.;^nX;|~nN±>mf ^@<1ž >ֱ|1Ż3?=Q>֥NȾ͇ՇKnTlhqm}ﳉg}-Ѯƾ ٮ$q~ONow1d+-UC+Ax=ʲ _c`(B;Nn`mN>s wi'>w{mh)}l ޷KrCbmoǷU['wa= 0!M6j6›9$N=? P D䫌B ZCD>":Ģ(EڀE"DѾ. DYq@(챠- iȪr%LFP(r̭L4 %1$39d!6$RD/fN$HHP>/EqÊ쌔q:UvM\O,; 0$Z[2[tݑקFN/P%r 4`Yմi%ZxԶ0:ɕw"^PnQO}ZH5~ѯ-eQwٽmp^t'v۔5ЁN`^ s 463𤄚<"ÀnG۔hY˫ht}e-¹aqSl/z"%EL|h#"V'jK¥@8fҶPpY8/3js6.*B8 ,QEҋ6#Bmf*=:qLu^b'VM^fJ,|JpX;4r6|8hI+cu| 6GjQԨY^2DetSdg;NxI*юxSg?IT*zP!#@hCשhE-zQfDvᬠLe ZN͛pbgzQh(|Ļ>2f-`'NtcMj=D3BFԑdi]7tTI䄠Pz]B2BܪWZ!ZRjǖ9m7yؗh/Y+#/wxV.!_FWPLJ'Y<d<6{0χK*fcFse k4eD|~}{Nh`Oq--co j4vDk?4>3?ۿw=?p+ˣ4es ;@\Ʋ3AS>kASi0alp9 42ScS,{ D/;?HDTBf hXB#(B#-,s3 dAH;+t+C;C$C)9J(C7C?L'$ZC\LS &,DŭPD)[cE[5E8>3DWktFc6\`->j,_Gr=t>P, ӋpG@tF\ #YPxKƄ"BX D>$>4GƅȘ1v<|{GcǶ ,ɶ(|lȌlșIF̋{Ʉpɜ4ɟG Կ0!IHG$L; I JJ\lf ]ý6~tʴʵGɼF4FFʁʕl_Rʂ:K,LDFŹ2bI|ȔLLN΍M4 ||Ί؜NN lKH!+5ʻUP UNtQеPP -޴| Q eRP䌿@KmR(e-"0 =[3EX/7L|SR'}BQ|Q[ԤS|TZͺװWѶx6>sͻ Nȓ+WO =jޣ%*dcB׆O|Ͽ(h& *L/Dg:Tw¡=>0A42Ί,0_,G8<@)DiHR5Ex8/3X?ÖA_-n8ނhlp"'QNB #=h!|8X(&袌$[wND@8 8=pB#'訤j*Uix@0E$MI;+'z+IjyرB6ӄCڱdO#܂L;H#{xq(@޻D @"j|4L9G[ڄQ7æiB g٣.&$l21!N'tAPM3&2} =L$~XMvIDI}A8Mi=*)7}ε-ΚQ+}SC4уGa ) ٓ?@R`CS `P@r@9w~Aqz졷#Ns3TP0MycM1zK :,VnP#~(;`L1CH[98D!ڣra>aw΂T($) $gňq"$πMBqF;ѐ'MJ['ЁGӠu=4˙*Cj_E㎑2`1:d+0?L8[UF8QIց"8R(!67F`WXdžp0؇o(>Pg8Qkǂ( 9AWe lz(cOljThoPqq牟؈+4gww{؅u`:AvX NȌ͸peuhEc(SHwиh 1@ 7 挹TwpgԘx8|)(3N# 禊 P4 ɇ9m $Ơ?*A%^{ȑ0<ɒ+A1Nj*ѷaK QxTn>"ٕ&!`hSqf֍. xx8 (wg!jz|ٗ~99i|E)hv옏h@)a ԔUYjX) IL3d;؃S}jɅlٖ=9` ئm91 X9VijI)/"A<۠ܘoiչ%1rAI"yD*9ْ)xQ" i8ɕiM %h Q. Y٠yyǖU ')#QI 9ʢ)d`9 )*{c;=*#)#J#Ö8٢)97J–ҹ+ : ~TIcWڟ[ʥgJY&QW$e fhjjʤTY3Enp:Л㙧zJ!7Cq'Kᩞ?:Jډ'11 @J4:II{ꨙ8uCF`XlkvY}xxr MpY꤆NJAt4'@@PAz5jڡ|trs *qzJ٥܊GzqpؚzAQM%R –UDə1D&*P 㪭Ztج{x+48 7XR֊뢸$y'71Q! F5zMK PxC3[l W+JˮO[1rYF𰜠KtPxDC Sq)קIuCH@1(ǀ>en_{4' ;50?sۤ+ѧZ3Gc}JW4;7˻`kBAʫj0Khk771Pp0;C[؎{9ۜ+wI8?6rsT++L{[CL ltM V(v?Y2?;+e C&kN x8Q:cQpIx@|W;!qV YŖ>ݻB9C;1:u l+Pa-Ҩkćavel"POzkɡ wV ɢ<ʤ\ʗ6?fʘvCBdy IKPi +Uk'7×[ QW#$Wkv Kƅ;# {rb97@˨ w`[3}.lq #7uſZ`o>lvt[!m;LGLR~P|7(ƨ;:B88 !mKҢpM@ƾtqc yp"2z,<*5IǕxwSqDU:8n勑f|HF>`pg"%l͘־ &r#&uE:ϐ_}P1:E[ ̿\xZkESn:qzg8Y9=\,Cǜ &*==: 8;ٱ CrjGͽ5X$uE]Ǽ˔ޗM#߫m=lO5JU GS^Pl#7|M-QzA߃q}k8rʷeNAׇaW,&s>ktR Ra-`` ?zx:V_5mg`Cvjm>$C ;ڰ;:5wۢ"=on8>1SˎSA=}}u> -0EH5v?.؉鲜5&CċTqipNmOZFs w`4~z QEp\pӭ֎٢ nTHkǽN>5 sR-|>肮>Kq] }mElo~~N~fp|VM9+_ķ]4?4_Nkњ#醬yhNGZ/-?{)OMV_;ϛdjD?][#>Ǿ*1;j_@(C16yz#PtO{ 8@|o~&_†o=$O<ݝis/~_dnc&OV쟍i$XA%dÇu NXBiD[Ƅ" !HEd9qeK _ƤYpw(lygM?a(ThД_$l@{`$|1겪vOZԺO,xJfW=,6%ZtV k% [n0H$l;p㋐+h7䇏ګ Le"ѣEuٵ_GvȂ n'_1$B{{dYr)NNK$0IKA B@b Ͼ PCؐB!ʹ !@3:0?4k&/ĺKȃ[0$( ^J*e'|,C!)Ǫoh4ʏY2Vtɗ,ΫұšO 'Ѥ2Tߠj  $*#*?A|Ԯ ƱԟvmKL+ŴՔN=՟qTuGʐqXbeh7QOGVEmTh# ,dnPY-cMi̽ T (]g{hq[=kUlRbv`l5Fdyu-"_F_ -җ ir=4/]݂ѷk]uH 8eXAR9TĝpGVWbF"%hi^^[ۦXZ1+Rku;&1;+ꝕܟ:ƍ?ŨlpKMдTΙ, :т !AAwuKn ,7ur T v8cL"y!xM.L][ӯ~)2)2fZ'hS@Sq^B' "q@ƻ/AM#4 H A ۪#yziXgM!u*45.ۦ KiA0mK>!=ԃe5*tG'6]8BŒRBZfބT1b5B ۜpe=suІ N@?$& PL1F` m, e:Rn3@drF<6 PTHID)p>9iPi-C&y27P!ɐl`,f) {mdMS$`@6߂JbBu$J NwPӤzϒ@3"36Ac9|5r` }Y ZG/~2փZ6nSv^UF^ƃ~)IogH`nmJdm. \:/qg*/!+`o9\6ˎEp岕su2[7>O&M[}08 4sùϻcʚe\c ۊ)Cws{;fdQ IS_;2rISfD5mjc`Eun_^'q an לAS _# O˿uP.jS|y?}q% )J>rS,qۃ8?(?7 #cGK Y{:k />䉥K:ܰ .ӺâbښK[.҉ Aº nк j=\ԫxB(0i&84B葽rA߁\[f(ܱ7( nC!# Ũåp{A*kSD cqFt DIZ;s kEA'qi9G3J@?'4D A)N

To use dygraphs, include the dygraph.js JavaScript file and dygraph.css CSS file. Then instantiate a Dygraph object.

Here's a basic example to get things started:

HTML

<html><head>
<script type="text/javascript"
  src="dygraph.js"></script>
<link rel="stylesheet" type="text/css"
  src="dygraph.css" />
</head><body>
<div id="graphdiv"></div>
<script type="text/javascript">
Dygraph.onDOMready(function onDOMready() {
  g = new Dygraph(

    // containing div
    document.getElementById("graphdiv"),

    // CSV or path to a CSV file.
    "Date,Temperature\n" +
    "2008-05-07,75\n" +
    "2008-05-08,70\n" +
    "2008-05-09,80\n"

  );
});
</script>
</body></html>

OUTPUT

In order to keep this example self-contained, the second parameter is raw CSV data. You can use your favourite “defer until DOM is ready” method (e.g. Prototype or jQuery); dygraphs includes a small self-contained one that can be used if nothing else is available. The dygraphs library parses this data (including column headers), resizes its container to a reasonable default, calculates appropriate axis ranges and tick marks and draws the graph. Make sure the container does not have any padding.

In most applications, it makes more sense to include a CSV file instead. If the second parameter to the constructor doesn't contain a newline, it will be interpreted as the path to a CSV file. The Dygraph will perform an XMLHttpRequest to retrieve this file and display the data when it becomes available. Make sure your CSV file is readable and serving from a place that understands XMLHttpRequests! In particular, you cannot specify a CSV file using "file:///". Here's an example: (data from Weather Underground)

HTML

<html><head>
<script type="text/javascript"
  src="dygraph.js"></script>
<link rel="stylesheet" type="text/css"
  src="dygraph.css" />
</head><body>
<div id="graphdiv2"
  style="width:500px; height:300px;"></div>
<script type="text/javascript">
Dygraph.onDOMready(function onDOMready() {
  g2 = new Dygraph(
    document.getElementById("graphdiv2"),
    "temperatures.csv", // path to CSV file
    {}                  // options
  );
});
</script>
</body></html>

OUTPUT

The file used is temperatures.csv.

There are a few things to note here:

  • The Dygraph sent off an XHR to get the temperatures.csv file.
  • The labels were taken from the first line of temperatures.csv, which is Date,High,Low. Make sure they are unique!
  • The Dygraph automatically chose two different, easily-distinguishable colors for the two data series.
  • The labels on the x-axis have switched from days to months. If you zoom in, they'll switch to weeks and then days.
  • Some heuristics are used to determine a good vertical range for the data. The idea is to make all the data visible and have human-friendly values on the axis (i.e. 200 instead of 193.4). Generally this works well.
  • The data is very spiky. A moving average would be easier to interpret.

This problem can be fixed by specifying the appropriate options in the "additional options" parameter to the Dygraph constructor. To set the number of days for a moving average, use the rollPeriod option. Here’s how it’s done:

HTML

<html><head>
<script type="text/javascript"
  src="dygraph.js"></script>
<link rel="stylesheet" type="text/css"
  src="dygraph.css" />
</head><body>
<div id="graphdiv3"
  style="width:500px; height:300px;"></div>
<script type="text/javascript">
Dygraph.onDOMready(function onDOMready() {
  g3 = new Dygraph(
    document.getElementById("graphdiv3"),
    "temperatures.csv",
    {
      rollPeriod: 7,
      showRoller: true
    }
  );
});
</script>
</body></html>

OUTPUT

A rolling average can be set using the text box in the lower left-hand corner of the graph (the showRoller attribute is what makes this appear). Also note that we've explicitly set the size of the chart div.

Bands

Another significant feature of the dygraphs library is the ability to display high/low bands around data series. One standard deviation must be specified for each data point. A ±n sigma band will be drawn around the data series at that point. If a moving average is being displayed, dygraphs will compute the standard deviation of the average at each point. I.E. σ = sqrt( (σ12 + σ22 + ... + σn2) / n )

Here's a demonstration. There are two data series. One is N(100,10) with a standard deviation of 10 specified at each point. The other is N(80,20) with a standard deviation of 20 specified at each point. The CSV file was generated using Octave and can be viewed at twonormals.csv.

HTML

<html><head>
<script type="text/javascript"
  src="dygraph.js"></script>
<link rel="stylesheet" type="text/css"
  src="dygraph.css" />
</head><body>
<div id="graphdiv4"
  style="width:480px; height:320px;"></div>
<script type="text/javascript">
Dygraph.onDOMready(function onDOMready() {
  g4 = new Dygraph(
    document.getElementById("graphdiv4"),
    "twonormals.csv",
    {
      rollPeriod: 7,
      showRoller: true,
      errorBars: true,
      valueRange: [50,125]
    }
  );
});
</script>
</body></html>

OUTPUT

Things to note here:

  • The option is named errorBars for historical reasons.
  • The errorBars option affects both the interpretation of the CSV file and the display of the graph. When errorBars is set to true, each line is interpreted as YYYY-MM-DD, A, sigma_A, B, sigma_B, …
  • The first line of the CSV file doesn’t mention the extra columns. In this case, it’s just "Date,Series1,Series2". (Which breaks the CSV file format. This is an unfortunate historic accident.)
  • The averaging visibly affects the high/low bands. This is most clear if you crank up the rolling period to something like 100 days. For the earliest dates, there won’t be 100 data points to average so the signal will be noisier. The high/low bands get smaller like sqrt(N) going forward in time until there’s a full 100 points to average.
  • The high/low bands are partially transparent. This can be seen when they overlap one another.

GViz Data

The Google Visualization API provides a standard interface for describing data. Once you've specified your data using this API, you can plug in any GViz-compatible visualization. dygraphs is such a visualization. In particular, it can be used as a drop-in replacement for the AnnotatedTimeline visualization used on Google Finance and other sites. To see how this works, check out the gviz annotation demo.

Here is another demonstration of how to use dygraphs a GViz visualization.

Charting Fractions

Situations often arise where you want to plot fractions, e.g. the fraction of respondents in a poll who said they'd vote for candidate X or the number of hits divided by at bats (baseball's batting average). Fractions require special treatment for two main reasons:

  • The average of a1/b1 and a2/b2 is (a1+a2)/(b1+b2), not (a1/b1 + a2/b2)/2.
  • The normal approximation is not always applicable and more sophisticated confidence intervals (e.g. the Wilson confidence interval) must be employed to avoid ratios that exceed 100% or go below 0%.

Fortunately, dygraphs handles both of these for you! Here's a chart and the command that generated it:

Batting Average for Ichiro Suzuki vs. Mariners (2004)
Code:
new Dygraph(
  document.getElementById("baseballdiv"),
  "suzuki-mariners.txt",
  {
    fractions: true,
    errorBars: true,
    showRoller: true,
    rollPeriod: 15
  }
);

The fractions option indicates that the values in each column should be parsed as fractions (e.g. "1/2" instead of "0.5"). The errorBars option indicates that we'd like to see a confidence interval around each data point. By default, when fractions is set, you get a Wilson confidence interval. If you look carefully at the chart, you can see that the high/low bands are asymmetric.

A couple things to notice about this chart:

  • The high/low bands for Ichiro's batting average are larger than for the Mariners', since he has far fewer at bats than his team.
  • dygraphs makes it easy to see "batting average over the last 30 games". This is ordinarily quite difficult to compute. It makes it clear where the "hot" and "cold" part of Suzuki's season were.
  • If you set the averaging period to something large, like 200, you'll see the team's and player's batting average through that game. The final number is the overall batting average for the season.
  • Where the high/low bands do not overlap, we can say with 95% confidence that the series differ. There is a better than 95% chance that Ichiro was a better hitter than his team as a whole in 2004, the year he won the batting title.

One last demo

This chart shows monthly closes of the Dow Jones Industrial Average, both in nominal and real (i.e. adjusted for inflation) dollars. The shaded areas show its monthly high and low. CPI values with a base from 1982-84 are used to adjust for inflation.

Display:

Common Gotchas

Here are a few problems that I've frequently run into while using the dygraphs library.

  • If your chart doesn't display, be sure to check your browser's JavaScript error console. dygraphs makes every attempt to log errors and warnings, and these can often guide you in the right direction.
  • Make sure your CSV files are readable! If your graph isn't showing up, the XMLHttpRequest for the CSV file may be failing. You can determine whether this is the case using tools like Firebug.
  • Make sure your CSV files are in the correct format. They must be of the form YYYY-MM-DD, series1, series2, … — the older YYYYMMDD as first column is not recognised as date any more but as number. And if you set the errorBars property, make sure you alternate data series and standard deviations.
  • dygraphs are not happy when placed inside a <center> tag. This applies to the CSS text-align property as well. If you want to center a Dygraph, put it inside a table with align = center set.
  • Don't set the dateWindow property to a date. It expects milliseconds since epoch, which can be obtained from a JavaScript Date object's valueOf method.
  • Make sure you don't have any trailing commas in your call to the Dygraph constructor or in the options parameter. Firefox, Chrome and Safari ignore these but they can cause a graph to not display in Internet Explorer.

What next?

If you need to support Internet Explorer, check out our notes on IE.

To get some inspiration, look at how the charts in our gallery are built.

dygraphs-2.2.1/docs/twonormals.csv010064400000000000000000000441621437353256000141550ustar00Date,Series1,Series2 2007-01-01,94.0946841239929,10,92.7865064144135,20 2007-01-02,113.201811313629,10,48.7444639205933,20 2007-01-03,95.592303276062,10,79.2437474429607,20 2007-01-04,97.5064766407013,10,77.9290872812271,20 2007-01-05,106.963732838631,10,95.9084284305573,20 2007-01-06,80.2781760692596,10,61.6853272914886,20 2007-01-07,100.051359818317,10,73.4595620632172,20 2007-01-08,89.2442333698273,10,56.9360947608948,20 2007-01-09,106.051690578461,10,107.259395122528,20 2007-01-10,101.531482040882,10,86.664856672287,20 2007-01-11,89.1888439655304,10,77.707946896553,20 2007-01-12,107.355877161026,10,77.3741406202316,20 2007-01-13,94.1611880064011,10,121.865606307983,20 2007-01-14,96.1331555247307,10,93.0631613731384,20 2007-01-15,101.575378775597,10,82.4605470895767,20 2007-01-16,102.771860063076,10,76.7487442493439,20 2007-01-17,94.0878880023956,10,80.7623960077763,20 2007-01-18,109.288640022278,10,47.7631306648254,20 2007-01-19,100.911911055446,10,64.3824207782745,20 2007-01-20,112.278221845627,10,68.3479058742523,20 2007-01-21,78.9384078979492,10,103.021440505981,20 2007-01-22,107.03338265419,10,45.7433104515076,20 2007-01-23,120.598442554474,10,120.512318611145,20 2007-01-24,91.6525495052338,10,73.7835818529129,20 2007-01-25,86.6101157665253,10,61.0721278190613,20 2007-01-26,76.7657494544983,10,49.9800586700439,20 2007-01-27,104.709110856056,10,64.9826431274414,20 2007-01-28,87.2436583042145,10,98.4307944774628,20 2007-01-29,86.5187978744507,10,56.8577694892883,20 2007-01-30,96.5837013721466,10,91.8027782440186,20 2007-01-31,104.319975674152,10,53.7843894958496,20 2007-02-01,93.6525058746338,10,114.006254673004,20 2007-02-02,132.866880893707,10,91.8379282951355,20 2007-02-03,93.4353011846542,10,77.8351998329163,20 2007-02-04,106.085510253906,10,130.814952850342,20 2007-02-05,93.2852751016617,10,81.5491008758545,20 2007-02-06,92.3503077030182,10,92.9888260364532,20 2007-02-07,92.8799241781235,10,67.8397727012634,20 2007-02-08,90.887468457222,10,106.050927639008,20 2007-02-09,97.4124673008919,10,61.2136566638947,20 2007-02-10,93.2560741901398,10,70.8249318599701,20 2007-02-11,109.902315735817,10,60.8251547813416,20 2007-02-12,94.9087244272232,10,64.1621708869934,20 2007-02-13,95.3440955281258,10,75.9353131055832,20 2007-02-14,102.835389971733,10,67.8751254081726,20 2007-02-15,84.5214533805847,10,49.9142575263977,20 2007-02-16,103.06267619133,10,115.240833759308,20 2007-02-17,97.7182766795158,10,83.3081617951393,20 2007-02-18,116.737879514694,10,101.066656112671,20 2007-02-19,120.076403617859,10,90.3911316394806,20 2007-02-20,95.6556394696236,10,113.495917320251,20 2007-02-21,94.5879065990448,10,96.415947675705,20 2007-02-22,101.539226770401,10,61.9145536422729,20 2007-02-23,98.8678465783596,10,77.8282150626183,20 2007-02-24,97.6890955865383,10,67.0508527755737,20 2007-02-25,99.6496134251356,10,86.52967274189,20 2007-02-26,100.740707740188,10,98.5435569286346,20 2007-02-27,91.4698106050491,10,69.810516834259,20 2007-02-28,98.8102301210165,10,47.4838805198669,20 2007-03-01,99.2288841307163,10,94.5062613487244,20 2007-03-02,92.5863647460938,10,103.611235618591,20 2007-03-03,114.908181428909,10,74.837434887886,20 2007-03-04,109.287412166595,10,91.3202583789825,20 2007-03-05,95.3682035207748,10,79.3710635602474,20 2007-03-06,95.1182308793068,10,98.0750358104706,20 2007-03-07,103.946290016174,10,50.4906249046326,20 2007-03-08,109.746969342232,10,68.1199038028717,20 2007-03-09,114.140131473541,10,92.4589133262634,20 2007-03-10,85.407782793045,10,91.6265773773193,20 2007-03-11,90.8102822303772,10,55.8610534667969,20 2007-03-12,96.6755110025406,10,118.259813785553,20 2007-03-13,83.8195776939392,10,123.414402008057,20 2007-03-14,108.24079990387,10,88.1427580118179,20 2007-03-15,104.835974276066,10,113.903331756592,20 2007-03-16,93.1726837158203,10,64.5078217983246,20 2007-03-17,103.685465157032,10,110.236563682556,20 2007-03-18,90.1124656200409,10,83.8830429315567,20 2007-03-19,97.3299288749695,10,87.7998399734497,20 2007-03-20,107.909459471703,10,42.5621438026428,20 2007-03-21,95.2882331609726,10,82.12317019701,20 2007-03-22,108.571609854698,10,102.018263339996,20 2007-03-23,99.6926294267178,10,59.2778038978577,20 2007-03-24,112.102061510086,10,94.2071461677551,20 2007-03-25,94.4191563129425,10,77.509905397892,20 2007-03-26,100.833547562361,10,94.6505665779114,20 2007-03-27,106.16162955761,10,61.8702232837677,20 2007-03-28,98.9218898117542,10,112.216217517853,20 2007-03-29,96.4712545275688,10,55.2638268470764,20 2007-03-30,96.4686763286591,10,89.237694144249,20 2007-03-31,101.455799043179,10,88.7331908941269,20 2007-04-01,102.23684206605,10,111.971719264984,20 2007-04-02,114.709233045578,10,66.1184501647949,20 2007-04-03,107.03797519207,10,124.90466594696,20 2007-04-04,101.396441310644,10,107.248797416687,20 2007-04-05,103.902289569378,10,64.0629303455353,20 2007-04-06,92.3808485269547,10,76.2916377186775,20 2007-04-07,105.23556470871,10,43.2561206817627,20 2007-04-08,87.1553158760071,10,82.2280541062355,20 2007-04-09,119.241544008255,10,53.4055876731873,20 2007-04-10,111.096258163452,10,79.0462526679039,20 2007-04-11,97.4128222465515,10,55.7952237129211,20 2007-04-12,110.409885644913,10,70.3491985797882,20 2007-04-13,95.4885852336884,10,58.0946588516235,20 2007-04-14,103.764507174492,10,61.7675912380219,20 2007-04-15,114.021297693253,10,94.2548739910126,20 2007-04-16,98.9073173701763,10,43.8510704040527,20 2007-04-17,115.810360908508,10,68.9345467090607,20 2007-04-18,101.422971785069,10,100.504853725433,20 2007-04-19,119.795391559601,10,100.85517168045,20 2007-04-20,92.0360016822815,10,82.4440509080887,20 2007-04-21,112.404752969742,10,81.8624790012836,20 2007-04-22,104.059817194939,10,51.2524080276489,20 2007-04-23,93.1676262617111,10,86.5015059709549,20 2007-04-24,93.2092815637589,10,90.7923364639282,20 2007-04-25,104.13601398468,10,101.198389530182,20 2007-04-26,98.1847678124905,10,42.97687292099,20 2007-04-27,121.308670043945,10,45.9942388534546,20 2007-04-28,115.053536891937,10,71.6148275136948,20 2007-04-29,94.0280091762543,10,56.2359380722046,20 2007-04-30,88.1207823753357,10,99.3783688545227,20 2007-05-01,111.701385974884,10,80.2669881284237,20 2007-05-02,98.714727461338,10,56.6372489929199,20 2007-05-03,103.363722264767,10,100.052332878113,20 2007-05-04,111.602603197098,10,77.2077092528343,20 2007-05-05,99.4656148552895,10,82.2163315117359,20 2007-05-06,77.7958679199219,10,105.606355667114,20 2007-05-07,104.113802611828,10,50.6052374839783,20 2007-05-08,123.41313123703,10,77.7422100305557,20 2007-05-09,94.3239623308182,10,81.5457877516747,20 2007-05-10,88.1108486652374,10,57.1634531021118,20 2007-05-11,105.014639496803,10,99.3462133407593,20 2007-05-12,100.885331854224,10,84.4843065738678,20 2007-05-13,100.741365477443,10,66.0745120048523,20 2007-05-14,91.6865712404251,10,96.2191820144653,20 2007-05-15,114.093443155289,10,102.257599830627,20 2007-05-16,97.6279026269913,10,130.860342979431,20 2007-05-17,112.286994457245,10,78.5536044836044,20 2007-05-18,95.2370589971542,10,44.7373557090759,20 2007-05-19,97.7498243749142,10,93.4921395778656,20 2007-05-20,108.320077061653,10,69.7792756557465,20 2007-05-21,99.222916662693,10,67.9618048667908,20 2007-05-22,96.8812572956085,10,102.4937748909,20 2007-05-23,69.8594975471497,10,70.4179978370667,20 2007-05-24,89.9046337604523,10,108.971984386444,20 2007-05-25,96.7731782793999,10,71.2224543094635,20 2007-05-26,112.635122537613,10,122.293920516968,20 2007-05-27,104.880233108997,10,75.9474596381187,20 2007-05-28,108.829988837242,10,66.4318740367889,20 2007-05-29,113.387352228165,10,85.2988499403,20 2007-05-30,94.149631857872,10,73.1878679990768,20 2007-05-31,106.634711623192,10,93.1930267810822,20 2007-06-01,99.3034096062183,10,57.9911375045776,20 2007-06-02,103.288851976395,10,46.1840200424194,20 2007-06-03,107.427718043327,10,94.0104794502258,20 2007-06-04,86.3590371608734,10,77.0007252693176,20 2007-06-05,101.470438838005,10,77.6732224225998,20 2007-06-06,95.8705332875252,10,102.285735607147,20 2007-06-07,116.72345161438,10,82.1839436888695,20 2007-06-08,88.9263653755188,10,29.0613269805908,20 2007-06-09,99.8003915697336,10,75.9601619839668,20 2007-06-10,99.5304548367858,10,79.3176300823689,20 2007-06-11,96.5316081047058,10,95.626939535141,20 2007-06-12,84.9738717079163,10,63.4740471839905,20 2007-06-13,103.224220573902,10,76.5348345041275,20 2007-06-14,90.2293705940247,10,58.5764074325562,20 2007-06-15,82.8239297866821,10,88.4199869632721,20 2007-06-16,108.036482334137,10,79.1778791695833,20 2007-06-17,111.484116315842,10,59.3552279472351,20 2007-06-18,107.548137307167,10,84.4000813364983,20 2007-06-19,108.109716176987,10,48.3497023582458,20 2007-06-20,115.827913284302,10,90.9793400764465,20 2007-06-21,100.512249879539,10,105.234756469727,20 2007-06-22,105.511372685432,10,93.8042879104614,20 2007-06-23,96.7094963788986,10,117.535014152527,20 2007-06-24,94.7944843769073,10,96.3854289054871,20 2007-06-25,98.8138699531555,10,67.9375243186951,20 2007-06-26,87.5452423095703,10,110.896701812744,20 2007-06-27,95.6386211514473,10,87.176525592804,20 2007-06-28,105.971124768257,10,104.18493270874,20 2007-06-29,105.616582632065,10,100.755131244659,20 2007-06-30,87.1863389015198,10,87.1826964616776,20 2007-07-01,122.550580501556,10,29.0275621414185,20 2007-07-02,97.9201781749725,10,75.8811420202255,20 2007-07-03,101.693934798241,10,26.1782026290894,20 2007-07-04,106.984695196152,10,87.9097819328308,20 2007-07-05,101.652606278658,10,80.0788712035865,20 2007-07-06,103.062781989574,10,75.9296423196793,20 2007-07-07,85.9849941730499,10,111.249039173126,20 2007-07-08,111.554799079895,10,84.0449270606041,20 2007-07-09,108.148396611214,10,78.3289317786694,20 2007-07-10,120.08163690567,10,73.9953505992889,20 2007-07-11,95.8106634020805,10,77.5311101973057,20 2007-07-12,109.306951761246,10,98.9882755279541,20 2007-07-13,109.748197793961,10,77.1462577581406,20 2007-07-14,107.401624917984,10,70.9187364578247,20 2007-07-15,115.204228162766,10,51.7829537391663,20 2007-07-16,101.363894045353,10,62.3194003105164,20 2007-07-17,101.237911954522,10,82.4289344251156,20 2007-07-18,101.364123821259,10,112.487089633942,20 2007-07-19,82.0579624176025,10,84.9630063772202,20 2007-07-20,87.840758562088,10,113.736288547516,20 2007-07-21,96.2999013066292,10,90.8173656463623,20 2007-07-22,74.482958316803,10,96.8275988101959,20 2007-07-23,92.1898084878922,10,81.5540917217731,20 2007-07-24,111.657168865204,10,99.6730208396912,20 2007-07-25,95.6442388892174,10,84.8401191830635,20 2007-07-26,126.834321022034,10,49.5946288108826,20 2007-07-27,104.035118818283,10,106.899120807648,20 2007-07-28,114.844251871109,10,71.1432409286499,20 2007-07-29,93.8618439435959,10,72.0631432533264,20 2007-07-30,110.952197313309,10,86.6521227359772,20 2007-07-31,94.7132760286331,10,80.2921519987285,20 2007-08-01,108.074281215668,10,83.1143301725388,20 2007-08-02,102.066531479359,10,81.2765935063362,20 2007-08-03,96.064564883709,10,68.3822906017303,20 2007-08-04,94.8778116703033,10,105.685148239136,20 2007-08-05,96.9068056344986,10,83.0777508020401,20 2007-08-06,72.676248550415,10,102.971677780151,20 2007-08-07,102.892329394817,10,95.9137535095215,20 2007-08-08,100.392684154212,10,67.2186517715454,20 2007-08-09,102.075793892145,10,98.5560536384583,20 2007-08-10,97.244453728199,10,67.8452551364899,20 2007-08-11,95.4441091418266,10,102.870435714722,20 2007-08-12,98.565482199192,10,76.2988811731339,20 2007-08-13,104.287070035934,10,82.662740945816,20 2007-08-14,109.793092012405,10,39.3169212341309,20 2007-08-15,112.275532484055,10,77.8041803836823,20 2007-08-16,77.7145385742188,10,72.9709446430206,20 2007-08-17,109.886486530304,10,38.7261343002319,20 2007-08-18,108.64046394825,10,67.3095226287842,20 2007-08-19,85.4451429843903,10,62.3904550075531,20 2007-08-20,83.6442470550537,10,106.529610157013,20 2007-08-21,109.308217763901,10,60.1290965080261,20 2007-08-22,108.235449790955,10,42.5594019889832,20 2007-08-23,123.438725471497,10,70.7120442390442,20 2007-08-24,96.0279887914658,10,98.3958852291107,20 2007-08-25,116.927028894424,10,64.0689778327942,20 2007-08-26,98.9606958627701,10,91.1650800704956,20 2007-08-27,95.2501532435417,10,67.9479813575745,20 2007-08-28,127.378940582275,10,81.7891944944859,20 2007-08-29,101.324769854546,10,70.4397636651993,20 2007-08-30,103.480939865112,10,99.0965926647186,20 2007-08-31,106.017985343933,10,38.3763265609741,20 2007-09-01,77.277090549469,10,97.051066160202,20 2007-09-02,97.553293555975,10,53.2996654510498,20 2007-09-03,112.12229013443,10,48.2950472831726,20 2007-09-04,88.9848005771637,10,64.006495475769,20 2007-09-05,113.623507022858,10,90.6919419765472,20 2007-09-06,117.776914834976,10,65.5359637737274,20 2007-09-07,95.3629437088966,10,65.0978481769562,20 2007-09-08,107.617252469063,10,132.306122779846,20 2007-09-09,84.3433558940887,10,76.6177946329117,20 2007-09-10,103.260976672173,10,100.815682411194,20 2007-09-11,88.3913385868073,10,85.6291252374649,20 2007-09-12,102.712529301643,10,125.389094352722,20 2007-09-13,101.77043363452,10,58.5648274421692,20 2007-09-14,111.676977872849,10,107.442679405212,20 2007-09-15,116.648640632629,10,67.8087985515594,20 2007-09-16,93.7750494480133,10,73.3145958185196,20 2007-09-17,103.814198076725,10,59.816951751709,20 2007-09-18,102.28799149394,10,63.2355391979218,20 2007-09-19,102.797355353832,10,78.1814505159855,20 2007-09-20,90.6087863445282,10,72.3672294616699,20 2007-09-21,95.8696460723877,10,101.906864643097,20 2007-09-22,101.489384919405,10,105.947561264038,20 2007-09-23,88.5241162776947,10,72.0068687200546,20 2007-09-24,103.184829056263,10,73.6309725046158,20 2007-09-25,108.286160826683,10,87.141484618187,20 2007-09-26,99.1833540052176,10,92.3960912227631,20 2007-09-27,105.875065922737,10,97.6024556159973,20 2007-09-28,109.003535509109,10,116.824145317078,20 2007-09-29,119.159464836121,10,84.8063838481903,20 2007-09-30,88.9144706726074,10,42.9914784431458,20 2007-10-01,107.298363447189,10,91.3336789608002,20 2007-10-02,108.407359719276,10,66.9903635978699,20 2007-10-03,89.8164367675781,10,54.9865126609802,20 2007-10-04,114.692898988724,10,70.2726489305496,20 2007-10-05,99.0317782759666,10,121.484136581421,20 2007-10-06,95.2851551771164,10,77.5017619132996,20 2007-10-07,75.3657913208008,10,91.8574166297913,20 2007-10-08,93.4729248285294,10,43.2036662101746,20 2007-10-09,118.223353624344,10,75.5406692624092,20 2007-10-10,103.33395242691,10,97.8111124038696,20 2007-10-11,85.7886075973511,10,59.4538998603821,20 2007-10-12,92.8499019145966,10,86.2665349245071,20 2007-10-13,90.1979243755341,10,60.1760244369507,20 2007-10-14,113.305931091309,10,74.9672168493271,20 2007-10-15,98.8471588492393,10,46.5943837165833,20 2007-10-16,105.021520853043,10,75.8990100026131,20 2007-10-17,103.107974529266,10,48.1224822998047,20 2007-10-18,94.6753841638565,10,101.704847812653,20 2007-10-19,92.0168948173523,10,63.6530780792236,20 2007-10-20,95.3590556979179,10,83.1053757667542,20 2007-10-21,103.06764870882,10,93.262699842453,20 2007-10-22,107.505331039429,10,135.405459403992,20 2007-10-23,114.560452699661,10,91.0899269580841,20 2007-10-24,90.034704208374,10,47.4418616294861,20 2007-10-25,93.8096088171005,10,64.0784442424774,20 2007-10-26,88.0993688106537,10,61.059547662735,20 2007-10-27,110.720720291138,10,93.6888122558594,20 2007-10-28,102.795536518097,10,82.3270231485367,20 2007-10-29,97.5641116499901,10,89.780347943306,20 2007-10-30,105.768427848816,10,56.7267251014709,20 2007-10-31,91.7177611589432,10,121.892156600952,20 2007-11-01,111.892886161804,10,89.5366156101227,20 2007-11-02,100.893505290151,10,82.8624200820923,20 2007-11-03,106.685945391655,10,80.6789672374725,20 2007-11-04,91.3279163837433,10,67.0352756977081,20 2007-11-05,101.440892368555,10,56.3839888572693,20 2007-11-06,96.4535877108574,10,74.6495789289474,20 2007-11-07,82.3770368099213,10,68.8839828968048,20 2007-11-08,116.052260398865,10,43.0678629875183,20 2007-11-09,125.71759223938,10,71.6680705547333,20 2007-11-10,103.291112780571,10,98.5829889774323,20 2007-11-11,109.987460970879,10,54.6437668800354,20 2007-11-12,125.61119556427,10,83.1547689437866,20 2007-11-13,81.9022762775421,10,76.0121485590935,20 2007-11-14,101.001473367214,10,52.9020714759827,20 2007-11-15,97.4931824207306,10,94.7068226337433,20 2007-11-16,100.898016765714,10,71.1474251747131,20 2007-11-17,87.1413099765778,10,87.4912250041962,20 2007-11-18,99.8441741894931,10,75.5515825748444,20 2007-11-19,101.680787652731,10,74.8838800191879,20 2007-11-20,101.4102037251,10,38.1694889068604,20 2007-11-21,104.962818622589,10,79.2502553761005,20 2007-11-22,100.41438985616,10,63.105149269104,20 2007-11-23,96.3839226961136,10,93.790431022644,20 2007-11-24,88.1726503372192,10,104.002554416656,20 2007-11-25,99.3147452920675,10,75.3712517023087,20 2007-11-26,92.6553928852081,10,83.6958381533623,20 2007-11-27,126.858286857605,10,74.9949443340302,20 2007-11-28,109.922057986259,10,82.4879696965218,20 2007-11-29,104.839740395546,10,74.3334722518921,20 2007-11-30,91.77410364151,10,84.5260953903198,20 2007-12-01,104.208002388477,10,70.7656121253967,20 2007-12-02,84.3402886390686,10,67.908148765564,20 2007-12-03,112.135351896286,10,80.6186366081238,20 2007-12-04,88.3082449436188,10,79.4679778069258,20 2007-12-05,108.096032738686,10,96.3960409164429,20 2007-12-06,102.557981014252,10,74.2482161521912,20 2007-12-07,89.1109883785248,10,84.9787417054176,20 2007-12-08,107.703627347946,10,40.3948593139648,20 2007-12-09,103.8067689538,10,100.3515625,20 2007-12-10,98.8902306556702,10,70.5492150783539,20 2007-12-11,113.535689115524,10,73.7627375125885,20 2007-12-12,110.228134393692,10,75.3778123855591,20 2007-12-13,104.585482180119,10,100.361814498901,20 2007-12-14,106.728765964508,10,85.6635415554047,20 2007-12-15,103.84180277586,10,88.3290749788284,20 2007-12-16,113.178231716156,10,89.592170715332,20 2007-12-17,114.0374147892,10,85.6432050466537,20 2007-12-18,103.519011437893,10,63.2945621013641,20 2007-12-19,95.9166973829269,10,72.5889933109283,20 2007-12-20,98.520376086235,10,68.655389547348,20 2007-12-21,112.214257717133,10,97.4554777145386,20 2007-12-22,94.7806811332703,10,68.030036687851,20 2007-12-23,97.4172702431679,10,61.5741550922394,20 2007-12-24,111.523776054382,10,91.1805129051208,20 2007-12-25,98.2731455564499,10,115.216774940491,20 2007-12-26,106.441938281059,10,90.604395866394,20 2007-12-27,84.2323613166809,10,66.9695138931274,20 2007-12-28,91.1851263046265,10,92.4789094924927,20 2007-12-29,126.743235588074,10,87.0600241422653,20 2007-12-30,100.71555621922,10,101.07501745224,20 2007-12-31,114.120811223984,10,49.1957330703735,20 dygraphs-2.2.1/docs/users.html010064400000000000000000000162051437353256000132570ustar00

Known Users

Since its public release in late 2009, dygraphs has found many users across the web. This is a small collection of the uses that we know about. If you're using dygraphs, please send Dan a link and he'll add it to this list.

dygraphs was originally developed at Google and has found wide use on internal dashboards and servers there. There are also a few uses of dygraphs on public Google products:

dygraphs has also found use in other organizations:

  • Integrated Space Weather Analysis System (NASA)
    “We use [dygraphs] in the Integrated Space Weather Analysis System available from the Space Weather Laboratory at NASA Goddard Space Flight Center. It works quite well for time series data from various missions and simulations that we store.”
  • Eutelsat
    “Eutelsat uses dygraphs for charting spacecraft telemetry for a fleet of 25 geostationary satellites. The spacecraft engineers are very happy with it. All satellite combined are producing about 200 millions unique data points per day so we really appreciate the excellent performance of dygraphs.”
  • 10gen MongoDB Monitoring Service
    A free monitoring service for MongoDB from 10gen (the creators of MongoDB). Used by thousands of servers and users. Makes use of synchronized charts to display many quantities simultaneously.
  • Duck Duck Go Traffic Dashboard
    DDG uses dygraphs to display a public chart of their daily traffic. They use annotations and the moving average features.
  • Wikimedia Foundation - Moodbar data dashboard
    dygraphs is used internally at Wikimedia as a handy solution to monitor the results of a bunch of small experiments.
  • quadrant-framework (MySQL Load Testing Framework)
    A user friendly framework for creating and visualizing MySQL database load test jobs. For more information on its use of dygraphs, see this post.
  • Spinwave Systems (Home energy monitoring)
    dygraphs is used to chart energy usage over time.
  • Jwebchart
    jWebChart is a stand-alone and Thredds' embedded plotting system for netCDF files. NetCDF is a common standard for the storage and distribution of scientific data.
  • n-gramas - Explore las tendencias en los artículos periodísticos de Colombia.
    (English: "Explore trends in newspaper articles of Colombia"). dygraphs is used for displaying the results of this n-grams viewer. Uses an extension for exporting the plots as PNG images ([1], [2]).
  • NOAA Great Lakes Dashboard
    The Great Lakes Dashboard provides user-friendly access to aggregated time series data, model output, and forecasts for multiple variables describing the Great Lakes along with background information. First developed in Adobe Flash, a HTML 5 compatible version has been in the works and a functional draft is available here.
  • HowManyDiapers.com
    HowManyDiapers.com is a free tool for estimating diaper use and sizes for any infant, in any time period. Using the baby’s percentile by weight (from the CDC growth chart), it plots out how much the baby is likely to weigh in the future, then checks with the manufacturer’s sizing charts to determine best diaper fit. Dygraphs are used in the results page, to display the growth curve and sizing changes (using annotations).
  • USGS CIDA/GCMRC River Sediment and Discharge
    The Center for Integrated Data Analytics (CIDA) is using Dygraphs to display timeseries data collected at USGS gage stations, most prominently on the Colorado River through the Grand Canyon. Dygraphs performance was essential for interacting with years of dense timeseries data in the browser. (View the code!)
  • Ghanastocks.net
    Ghanastocks.net is a financial platform offering information on the stocks traded on the Ghana Stock Exchange. Dygraphs is used to display the stock charts. On the front page a tiny preview graph with no interaction model and labels is used, the quote pages uses a standard version of dygraphs and the detailed chart is displayed using two dygraphs instances, one for the volume bars and one for the chart, which are synchronized through a custom highlight callback.

Are you using dygraphs? Please let Dan know and he'll add your link here!

dygraphs-2.2.1/dygraph-exports.js010064400000000000000000000072621437353256000140010ustar00// This is the complete set of symbols that dygraphs exports for use by // clients. Exporting a symbols forces the Closure Compiler to preserve it in // the minified JS (symbols not in this list are mangled). /*global Dygraph:false, goog:false */ goog.exportSymbol('Dygraph', Dygraph); goog.exportSymbol('Dygraph.prototype.adjustRoll', Dygraph.prototype.adjustRoll); goog.exportSymbol('Dygraph.prototype.annotations', Dygraph.prototype.annotations); goog.exportSymbol('Dygraph.prototype.clearSelection', Dygraph.prototype.clearSelection); goog.exportSymbol('Dygraph.prototype.destroy', Dygraph.prototype.destroy); goog.exportSymbol('Dygraph.prototype.eventToDomCoords', Dygraph.prototype.eventToDomCoords); goog.exportSymbol('Dygraph.prototype.getArea', Dygraph.prototype.getArea); goog.exportSymbol('Dygraph.prototype.getColors', Dygraph.prototype.getColors); goog.exportSymbol('Dygraph.prototype.getHighlightSeries', Dygraph.prototype.getHighlightSeries); goog.exportSymbol('Dygraph.prototype.getLabels', Dygraph.prototype.getLabels); goog.exportSymbol('Dygraph.prototype.getOption', Dygraph.prototype.getOption); goog.exportSymbol('Dygraph.prototype.getPropertiesForSeries', Dygraph.prototype.getPropertiesForSeries); goog.exportSymbol('Dygraph.prototype.getSelection', Dygraph.prototype.getSelection); goog.exportSymbol('Dygraph.prototype.getValue', Dygraph.prototype.getValue); goog.exportSymbol('Dygraph.prototype.indexFromSetName', Dygraph.prototype.indexFromSetName); goog.exportSymbol('Dygraph.prototype.isSeriesLocked', Dygraph.prototype.isSeriesLocked); goog.exportSymbol('Dygraph.prototype.isZoomed', Dygraph.prototype.isZoomed); goog.exportSymbol('Dygraph.prototype.numAxes', Dygraph.prototype.numAxes); goog.exportSymbol('Dygraph.prototype.numColumns', Dygraph.prototype.numColumns); goog.exportSymbol('Dygraph.prototype.numRows', Dygraph.prototype.numRows); goog.exportSymbol('Dygraph.prototype.ready', Dygraph.prototype.ready); goog.exportSymbol('Dygraph.prototype.resetZoom', Dygraph.prototype.resetZoom); goog.exportSymbol('Dygraph.prototype.resize', Dygraph.prototype.resize); goog.exportSymbol('Dygraph.prototype.rollPeriod', Dygraph.prototype.rollPeriod); goog.exportSymbol('Dygraph.prototype.setAnnotations', Dygraph.prototype.setAnnotations); goog.exportSymbol('Dygraph.prototype.setSelection', Dygraph.prototype.setSelection); goog.exportSymbol('Dygraph.prototype.setVisibility', Dygraph.prototype.setVisibility); goog.exportSymbol('Dygraph.prototype.toDataCoords', Dygraph.prototype.toDataCoords); goog.exportSymbol('Dygraph.prototype.toDataXCoord', Dygraph.prototype.toDataXCoord); goog.exportSymbol('Dygraph.prototype.toDataYCoord', Dygraph.prototype.toDataYCoord); goog.exportSymbol('Dygraph.prototype.toDomCoords', Dygraph.prototype.toDomCoords); goog.exportSymbol('Dygraph.prototype.toDomXCoord', Dygraph.prototype.toDomXCoord); goog.exportSymbol('Dygraph.prototype.toDomYCoord', Dygraph.prototype.toDomYCoord); goog.exportSymbol('Dygraph.prototype.toPercentXCoord', Dygraph.prototype.toPercentXCoord); goog.exportSymbol('Dygraph.prototype.toPercentYCoord', Dygraph.prototype.toPercentYCoord); goog.exportSymbol('Dygraph.prototype.toString', Dygraph.prototype.toString); goog.exportSymbol('Dygraph.prototype.updateOptions', Dygraph.prototype.updateOptions); goog.exportSymbol('Dygraph.prototype.visibility', Dygraph.prototype.visibility); goog.exportSymbol('Dygraph.prototype.xAxisExtremes', Dygraph.prototype.xAxisExtremes); goog.exportSymbol('Dygraph.prototype.xAxisRange', Dygraph.prototype.xAxisRange); goog.exportSymbol('Dygraph.prototype.yAxisRange', Dygraph.prototype.yAxisRange); goog.exportSymbol('Dygraph.prototype.yAxisRanges', Dygraph.prototype.yAxisRanges); goog.exportSymbol('Dygraph.Plotters', Dygraph.Plotters); dygraphs-2.2.1/dygraph-externs.js010064400000000000000000000074331437353256000137650ustar00/** * @license * Copyright 2006 Dan Vanderkam (danvdk@gmail.com) * MIT-licenced: https://opensource.org/licenses/MIT */ // TODO(danvk): move the Dygraph definitions out of here once I closure-ify dygraphs.js /** * @param {!HTMLDivElement|string} div * @param {DygraphDataArray| * GVizDataTable| * string| * function():(DygraphDataArray|GVizDataTable|string)} file * @param {Object} attrs * @constructor */ function Dygraph(div, file, attrs) {} /** @type {string} */ Dygraph.NAME; /** @type {string} */ Dygraph.VERSION; /** @type {function(): string} */ Dygraph.toString; /** @type {function(Event, Dygraph, DygraphInteractionContext)} */ Dygraph.startPan; /** @type {function(Event, Dygraph, DygraphInteractionContext)} */ Dygraph.movePan; /** @type {function(Event, Dygraph, DygraphInteractionContext)} */ Dygraph.endPan; /** @type {function(?string): boolean} */ Dygraph.prototype.isZoomed; /** @type {function(): string} */ Dygraph.prototype.toString; /** @type {function(string, string=)} */ Dygraph.prototype.getOption; /** @type {function(): number} */ Dygraph.prototype.rollPeriod; /** @type {function(): ?Array.} */ Dygraph.prototype.xAxisRange; /** @type {function(): Array.} */ Dygraph.prototype.xAxisExtremes; /** @type {function(number): ?Array.} */ Dygraph.prototype.yAxisRange; /** @type {function(): Array.>} */ Dygraph.prototype.yAxisRanges; /** @type {function(?number, ?number, ?number): Array.} */ Dygraph.prototype.toDomCoords; /** @type {function(?number): ?number} */ Dygraph.prototype.toDomXCoord; /** @type {function(?number, ?number): ?number} */ Dygraph.prototype.toDomYCoord; /** @type {function(?number, ?number, ?number): Array.} */ Dygraph.prototype.toDataCoords; /** @type {function(?number): ?number} */ Dygraph.prototype.toDataXCoord; /** @type {function(?number, ?number): ?number} */ Dygraph.prototype.toDataYCoord; /** @type {function(?number, ?number): ?number} */ Dygraph.prototype.toPercentYCoord; /** @type {function(?number): ?number} */ Dygraph.prototype.toPercentXCoord; /** @type {function(): number} */ Dygraph.prototype.numColumns; /** @type {function(): number} */ Dygraph.prototype.numRows; /** @type {function(number, number)} */ Dygraph.prototype.getValue; /** @type {function()} */ Dygraph.prototype.destroy; /** @type {function()} */ Dygraph.prototype.getColors; /** @type {function(string)} */ Dygraph.prototype.getPropertiesForSeries; /** @type {function()} */ Dygraph.prototype.resetZoom; /** @type {function(): {x, y, w, h}} */ Dygraph.prototype.getArea; /** @type {function(Object): Array.} */ Dygraph.prototype.eventToDomCoords; /** @type {function(number, string, boolean): boolean} */ Dygraph.prototype.setSelection; /** @type {function()} */ Dygraph.prototype.clearSelection; /** @type {function(): number} */ Dygraph.prototype.getSelection; /** @type {function(): string} */ Dygraph.prototype.getHighlightSeries; /** @type {function(): boolean} */ Dygraph.prototype.isSeriesLocked; /** @type {function(): number} */ Dygraph.prototype.numAxes; /** @type {function(Object, boolean=)} */ Dygraph.prototype.updateOptions; /** @type {function(number, number)} */ Dygraph.prototype.resize; /** @type {function(number)} */ Dygraph.prototype.adjustRoll; /** @type {function(): Array.} */ Dygraph.prototype.visibility; /** @type {function(number, boolean)} */ Dygraph.prototype.setVisibility; /** @type {function(Array., boolean=)} */ Dygraph.prototype.setAnnotations; /** @type {function(): Array.} */ Dygraph.prototype.annotations; /** @type {function(): ?Array.} */ Dygraph.prototype.getLabels; /** @type {function(string): ?number} */ Dygraph.prototype.indexFromSetName; /** @type {function(function(!Dygraph))} */ Dygraph.prototype.ready; dygraphs-2.2.1/extras012077700000000000000000000000001437353256000135442src/extrasustar00dygraphs-2.2.1/favicon.ico012077700000000000000000000000001437353256000154652docs/favicon.icoustar00dygraphs-2.2.1/gallery/README010064400000000000000000000020321437353256000126100ustar00Tips on adding entries to the gallery * You can use the id in conjunction with specialized CSS. * Most files don't have a lot of HTML, but some have tons. See independent-series for tips on adding a lot of HTML. * You can also define a function called cleanup, which is the opposite of setup. It's perfect for stopping timers, for instance. * Callbacks in HTML widgets. You might assume you can use ", "", "
", "
", "
" ].join("\n"); }, run: function() { var eventDiv = document.getElementById("events"); function nameAnnotation(ann) { return "(" + ann.series + ", " + ann.x + ")"; } var g = new Dygraph( document.getElementById("g_div"), function() { var zp = function(x) { if (x < 10) return "0"+x; else return x; }; var r = "date,parabola,line,another line,sine wave\n"; for (var i=1; i<=31; i++) { r += "2006-10-" + zp(i); r += "," + 10*(i*(31-i)); r += "," + 10*(8*i); r += "," + 10*(250 - 8*i); r += "," + 10*(125 + 125 * Math.sin(0.3*i)); r += "\n"; } return r; }, { rollPeriod: 1, showRoller: true, width: 480, height: 320, drawCallback: function(g) { var ann = g.annotations(); var html = ""; for (var i = 0; i < ann.length; i++) { var name = nameAnnotation(ann[i]); html += ""; html += name + ": " + (ann[i].shortText || '(icon)'); html += " -> " + ann[i].text + "
"; } document.getElementById("list").innerHTML = html; } } ); var last_ann = 0; var annotations = []; for (var x = 10; x < 15; x += 2) { annotations.push( { series: 'sine wave', x: "2006-10-" + x, shortText: x, text: 'Stock Market Crash ' + x } ); last_ann = x; } annotations.push( { series: 'another line', x: "2006-10-13", icon: '../common/dollar.png', width: 18, height: 23, tickHeight: 4, text: 'Another one', cssClass: 'annotation', clickHandler: function() { eventDiv.innerHTML += "special handler
"; } } ); g.setAnnotations(annotations); document.getElementById('add').onclick = function() { var x = last_ann + 2; annotations.push( { series: 'line', x: "2006-10-" + x, shortText: x, text: 'Line ' + x, tickHeight: 10 } ); last_ann = x; g.setAnnotations(annotations); }; var bottom = document.getElementById('bottom'); bottom.onclick = function() { var to_bottom = bottom.textContent == 'Shove to bottom'; var anns = g.annotations(); for (var i = 0; i < anns.length; i++) { anns[i].attachAtBottom = to_bottom; } g.setAnnotations(anns); if (to_bottom) { bottom.textContent = 'Lift back up'; } else { bottom.textContent = 'Shove to bottom'; } }; var saveBg = ''; var num = 0; g.updateOptions( { annotationClickHandler: function(ann, point, dg, event) { eventDiv.innerHTML += "click: " + nameAnnotation(ann) + "
"; }, annotationDblClickHandler: function(ann, point, dg, event) { eventDiv.innerHTML += "dblclick: " + nameAnnotation(ann) + "
"; }, annotationMouseOverHandler: function(ann, point, dg, event) { document.getElementById(nameAnnotation(ann)).style.fontWeight = 'bold'; saveBg = ann.div.style.backgroundColor; ann.div.style.backgroundColor = '#ddd'; }, annotationMouseOutHandler: function(ann, point, dg, event) { document.getElementById(nameAnnotation(ann)).style.fontWeight = 'normal'; ann.div.style.backgroundColor = saveBg; }, pointClickCallback: function(event, p) { // Check if the point is already annotated. if (p.annotation) return; // If not, add one. var ann = { series: p.name, xval: p.xval, shortText: num, text: "Annotation #" + num }; var anns = g.annotations(); anns.push(ann); g.setAnnotations(anns); num++; } }); } }); dygraphs-2.2.1/gallery/border.js010064400000000000000000000011201437353256000135400ustar00/*global Gallery,Dygraph,data */ //galleryActive=false Gallery.register( 'border', { name: "Border test", title: 'Graph stays within the border', setup: function(parent) { parent.innerHTML = "" + "
"; }, run: function() { new Dygraph(document.getElementById('bordered'), data, { title: 'Chart Title', xlabel: 'Date', ylabel: 'Temperature (F)' }); } }); dygraphs-2.2.1/gallery/callbacks.js010064400000000000000000000056521437353256000142200ustar00/*global Gallery,Dygraph,data */ /*global NoisyData */ //galleryActive=false Gallery.register( 'callbacks', { name: "Callbacks", title: "Hover, click and zoom to test the callbacks.", setup: function(parent) { parent.innerHTML = "
" + "" + "" + "" + "

"; }, run: function() { var colorSets = [ ['#284785', '#EE1111', '#8AE234'], ['#444444', '#888888', '#DDDDDD'], null ]; var chart = new Dygraph(document.getElementById("blah"), "X,a,b,c\n" + "10,12345,23456,34567\n" + "11,12345,20123,31345\n", { width: 640, height: 480, colors: colorSets[0] }); function change(event) { chart.updateOptions({colors: colorSets[event.target.id]}); } document.getElementById("0").onclick = change; document.getElementById("1").onclick = change; document.getElementById("2").onclick = change; } }); dygraphs-2.2.1/gallery/color-visibility.js010064400000000000000000000023031437353256000155720ustar00/*global Gallery,Dygraph,data,$ */ /*jshint unused:false */ //galleryActive=false Gallery.register( 'color-visibility', { name: "Color visibility", title: 'The lines should maintain their colors as their visibility is toggled.', setup: function(parent) { parent.innerHTML = "
" + "

Display: " + "" + "" + "" + "" + "" + "" + "

"; }, run: function() { var g = new Dygraph(document.getElementById("blah"), "X,a,b,c\n" + "10,12345,23456,34567\n" + "11,12345,20123,31345\n", { width: 640, height: 480, colors: ['#284785', '#EE1111', '#8AE234'], visibility: [true, true, true] }); $('input[type=checkbox]').click(function() { var el = this; g.setVisibility(el.id, el.checked); }); } }); dygraphs-2.2.1/gallery/data.js010064400000000000000000005102051437353256000132050ustar00// This file is a superset of auto_tests/data/data.js // and only contains test data function data_nolabel() { return "" + "2007-01-01,62,39\n" + "2007-01-02,62,44\n" + "2007-01-03,62,42\n" + "2007-01-04,57,45\n" + "2007-01-05,54,44\n" + "2007-01-06,55,36\n" + "2007-01-07,62,45\n" + "2007-01-08,66,48\n" + "2007-01-09,63,39\n" + "2007-01-10,57,37\n" + "2007-01-11,50,37\n" + "2007-01-12,48,35\n" + "2007-01-13,48,30\n" + "2007-01-14,48,28\n" + "2007-01-15,53,28\n" + "2007-01-16,50,30\n" + "2007-01-17,57,37\n" + "2007-01-18,61,33\n" + "2007-01-19,55,35\n" + "2007-01-20,61,35\n" + "2007-01-21,64,43\n" + "2007-01-22,61,36\n" + "2007-01-23,57,35\n" + "2007-01-24,60,35\n" + "2007-01-25,55,39\n" + "2007-01-26,54,44\n" + "2007-01-27,57,48\n" + "2007-01-28,59,45\n" + "2007-01-29,63,45\n" + "2007-01-30,59,41\n" + "2007-01-31,55,48\n" + "2007-02-01,53,46\n" + "2007-02-02,55,44\n" + "2007-02-03,59,37\n" + "2007-02-04,66,39\n" + "2007-02-05,64,43\n" + "2007-02-06,61,46\n" + "2007-02-07,61,51\n" + "2007-02-08,60,51\n" + "2007-02-09,61,55\n" + "2007-02-10,62,55\n" + "2007-02-11,61,46\n" + "2007-02-12,59,43\n" + "2007-02-13,57,46\n" + "2007-02-14,61,39\n" + "2007-02-15,64,44\n" + "2007-02-16,71,46\n" + "2007-02-17,73,51\n" + "2007-02-18,60,46\n" + "2007-02-19,63,44\n" + "2007-02-20,57,45\n" + "2007-02-21,59,48\n" + "2007-02-22,55,44\n" + "2007-02-23,55,42\n" + "2007-02-24,57,39\n" + "2007-02-25,55,48\n" + "2007-02-26,57,44\n" + "2007-02-27,53,39\n" + "2007-02-28,53,37\n" + "2007-03-01,54,37\n" + "2007-03-02,61,39\n" + "2007-03-03,66,43\n" + "2007-03-04,70,48\n" + "2007-03-05,68,53\n" + "2007-03-06,69,46\n" + "2007-03-07,62,51\n" + "2007-03-08,61,46\n" + "2007-03-09,60,45\n" + "2007-03-10,68,46\n" + "2007-03-11,79,48\n" + "2007-03-12,80,52\n" + "2007-03-13,73,53\n" + "2007-03-14,64,48\n" + "2007-03-15,78,46\n" + "2007-03-16,78,50\n" + "2007-03-17,62,51\n" + "2007-03-18,66,46\n" + "2007-03-19,64,48\n" + "2007-03-20,60,48\n" + "2007-03-21,66,46\n" + "2007-03-22,73,43\n" + "2007-03-23,78,48\n" + "2007-03-24,68,48\n" + "2007-03-25,64,53\n" + "2007-03-26,66,48\n" + "2007-03-27,57,46\n" + "2007-03-28,66,42\n" + "2007-03-29,73,42\n" + "2007-03-30,72,46\n" + "2007-03-31,69,46\n" + "2007-04-01,64,46\n" + "2007-04-02,69,46\n" + "2007-04-03,71,46\n" + "2007-04-04,69,50\n" + "2007-04-05,71,52\n" + "2007-04-06,64,52\n" + "2007-04-07,68,51\n" + "2007-04-08,71,51\n" + "2007-04-09,66,50\n" + "2007-04-10,72,46\n" + "2007-04-11,63,50\n" + "2007-04-12,64,46\n" + "2007-04-13,70,44\n" + "2007-04-14,57,51\n" + "2007-04-15,68,46\n" + "2007-04-16,75,46\n" + "2007-04-17,62,48\n" + "2007-04-18,61,45\n" + "2007-04-19,57,42\n" + "2007-04-20,64,46\n" + "2007-04-21,61,43\n" + "2007-04-22,63,48\n" + "2007-04-23,70,44\n" + "2007-04-24,66,46\n" + "2007-04-25,66,48\n" + "2007-04-26,69,48\n" + "2007-04-27,82,50\n" + "2007-04-28,81,55\n" + "2007-04-29,70,53\n" + "2007-04-30,77,51\n" + "2007-05-01,70,48\n" + "2007-05-02,66,52\n" + "2007-05-03,63,48\n" + "2007-05-04,64,51\n" + "2007-05-05,73,46\n" + "2007-05-06,88,54\n" + "2007-05-07,91,57\n" + "2007-05-08,84,60\n" + "2007-05-09,73,55\n" + "2007-05-10,57,52\n" + "2007-05-11,64,51\n" + "2007-05-12,64,50\n" + "2007-05-13,72,46\n" + "2007-05-14,66,50\n" + "2007-05-15,63,51\n" + "2007-05-16,70,48\n" + "2007-05-17,68,50\n" + "2007-05-18,73,50\n" + "2007-05-19,70,52\n" + "2007-05-20,73,51\n" + "2007-05-21,78,54\n" + "2007-05-22,81,51\n" + "2007-05-23,86,55\n" + "2007-05-24,78,55\n" + "2007-05-25,69,54\n" + "2007-05-26,69,55\n" + "2007-05-27,69,54\n" + "2007-05-28,73,52\n" + "2007-05-29,69,53\n" + "2007-05-30,66,55\n" + "2007-05-31,64,54\n" + "2007-06-01,66,54\n" + "2007-06-02,64,54\n" + "2007-06-03,70,55\n" + "2007-06-04,73,59\n" + "2007-06-05,68,55\n" + "2007-06-06,70,53\n" + "2007-06-07,75,51\n" + "2007-06-08,70,50\n" + "2007-06-09,75,53\n" + "2007-06-10,75,55\n" + "2007-06-11,75,53\n" + "2007-06-12,79,52\n" + "2007-06-13,90,59\n" + "2007-06-14,89,60\n" + "2007-06-15,86,59\n" + "2007-06-16,72,55\n" + "2007-06-17,79,53\n" + "2007-06-18,79,57\n" + "2007-06-19,73,55\n" + "2007-06-20,71,55\n" + "2007-06-21,77,55\n" + "2007-06-22,79,54\n" + "2007-06-23,77,54\n" + "2007-06-24,77,53\n" + "2007-06-25,82,53\n" + "2007-06-26,71,54\n" + "2007-06-27,73,55\n" + "2007-06-28,73,57\n" + "2007-06-29,77,60\n" + "2007-06-30,75,54\n" + "2007-07-01,78,54\n" + "2007-07-02,82,57\n" + "2007-07-03,72,57\n" + "2007-07-04,84,59\n" + "2007-07-05,84,61\n" + "2007-07-06,75,60\n" + "2007-07-07,73,55\n" + "2007-07-08,78,55\n" + "2007-07-09,73,57\n" + "2007-07-10,73,59\n" + "2007-07-11,78,62\n" + "2007-07-12,75,59\n" + "2007-07-13,79,60\n" + "2007-07-14,73,60\n" + "2007-07-15,78,62\n" + "2007-07-16,75,59\n" + "2007-07-17,77,60\n" + "2007-07-18,75,63\n" + "2007-07-19,80,59\n" + "2007-07-20,79,59\n" + "2007-07-21,77,61\n" + "2007-07-22,75,63\n" + "2007-07-23,79,64\n" + "2007-07-24,73,61\n" + "2007-07-25,72,57\n" + "2007-07-26,75,60\n" + "2007-07-27,78,60\n" + "2007-07-28,77,57\n" + "2007-07-29,73,57\n" + "2007-07-30,80,59\n" + "2007-07-31,75,59\n" + "2007-08-01,75,59\n" + "2007-08-02,73,60\n" + "2007-08-03,79,60\n" + "2007-08-04,77,59\n" + "2007-08-05,71,57\n" + "2007-08-06,71,59\n" + "2007-08-07,73,57\n" + "2007-08-08,71,55\n" + "2007-08-09,77,60\n" + "2007-08-10,77,57\n" + "2007-08-11,73,57\n" + "2007-08-12,72,55\n" + "2007-08-13,75,55\n" + "2007-08-14,73,55\n" + "2007-08-15,75,57\n" + "2007-08-16,79,60\n" + "2007-08-17,80,55\n" + "2007-08-18,78,57\n" + "2007-08-19,77,55\n" + "2007-08-20,80,64\n" + "2007-08-21,82,62\n" + "2007-08-22,82,60\n" + "2007-08-23,82,57\n" + "2007-08-24,78,59\n" + "2007-08-25,73,61\n" + "2007-08-26,73,61\n" + "2007-08-27,78,59\n" + "2007-08-28,86,62\n" + "2007-08-29,88,68\n" + "2007-08-30,90,68\n" + "2007-08-31,80,66\n" + "2007-09-01,87,62\n" + "2007-09-02,89,61\n" + "2007-09-03,78,61\n" + "2007-09-04,78,63\n" + "2007-09-05,89,57\n" + "2007-09-06,82,64\n" + "2007-09-07,75,61\n" + "2007-09-08,73,62\n" + "2007-09-09,71,61\n" + "2007-09-10,73,59\n" + "2007-09-11,71,59\n" + "2007-09-12,72,60\n" + "2007-09-13,77,57\n" + "2007-09-14,75,60\n" + "2007-09-15,73,57\n" + "2007-09-16,72,61\n" + "2007-09-17,72,55\n" + "2007-09-18,73,55\n" + "2007-09-19,66,55\n" + "2007-09-20,71,52\n" + "2007-09-21,77,57\n" + "2007-09-22,64,57\n" + "2007-09-23,68,55\n" + "2007-09-24,78,52\n" + "2007-09-25,84,53\n" + "2007-09-26,87,57\n" + "2007-09-27,75,55\n" + "2007-09-28,66,54\n" + "2007-09-29,73,52\n" + "2007-09-30,75,48\n" + "2007-10-01,71,57\n" + "2007-10-02,81,53\n" + "2007-10-03,73,54\n" + "2007-10-04,69,55\n" + "2007-10-05,64,50\n" + "2007-10-06,73,45\n" + "2007-10-07,77,46\n" + "2007-10-08,79,53\n" + "2007-10-09,72,53\n" + "2007-10-10,69,54\n" + "2007-10-11,70,48\n" + "2007-10-12,64,54\n" + "2007-10-13,70,53\n" + "2007-10-14,66,51\n" + "2007-10-15,68,52\n" + "2007-10-16,66,52\n" + "2007-10-17,66,50\n" + "2007-10-18,73,50\n" + "2007-10-19,72,57\n" + "2007-10-20,66,54\n" + "2007-10-21,73,51\n" + "2007-10-22,81,51\n" + "2007-10-23,84,53\n" + "2007-10-24,79,55\n" + "2007-10-25,66,53\n" + "2007-10-26,68,46\n" + "2007-10-27,66,52\n" + "2007-10-28,75,52\n" + "2007-10-29,63,55\n" + "2007-10-30,63,53\n" + "2007-10-31,63,54\n" + "2007-11-01,66,53\n" + "2007-11-02,77,50\n" + "2007-11-03,80,48\n" + "2007-11-04,77,48\n" + "2007-11-05,66,48\n" + "2007-11-06,62,52\n" + "2007-11-07,61,48\n" + "2007-11-08,59,53\n" + "2007-11-09,63,48\n" + "2007-11-10,66,48\n" + "2007-11-11,63,48\n" + "2007-11-12,68,44\n" + "2007-11-13,72,51\n" + "2007-11-14,75,55\n" + "2007-11-15,69,51\n" + "2007-11-16,63,55\n" + "2007-11-17,66,51\n" + "2007-11-18,64,53\n" + "2007-11-19,66,48\n" + "2007-11-20,63,46\n" + "2007-11-21,64,43\n" + "2007-11-22,64,37\n" + "2007-11-23,70,37\n" + "2007-11-24,60,37\n" + "2007-11-25,60,46\n" + "2007-11-26,63,42\n" + "2007-11-27,63,45\n" + "2007-11-28,64,46\n" + "2007-11-29,62,41\n" + "2007-11-30,55,42\n" + "2007-12-01,57,37\n" + "2007-12-02,61,45\n" + "2007-12-03,66,50\n" + "2007-12-04,61,54\n" + "2007-12-05,60,50\n" + "2007-12-06,57,48\n" + "2007-12-07,55,45\n" + "2007-12-08,53,42\n" + "2007-12-09,57,39\n" + "2007-12-10,57,39\n" + "2007-12-11,57,41\n" + "2007-12-12,55,35\n" + "2007-12-13,59,34\n" + "2007-12-14,55,34\n" + "2007-12-15,55,39\n" + "2007-12-16,55,43\n" + "2007-12-17,57,48\n" + "2007-12-18,57,43\n" + "2007-12-19,59,41\n" + "2007-12-20,55,43\n" + "2007-12-21,53,39\n" + "2007-12-22,53,32\n" + "2007-12-23,55,37\n" + "2007-12-24,57,45\n" + "2007-12-25,57,37\n" + "2007-12-26,53,43\n" + "2007-12-27,48,37\n" + "2007-12-28,48,43\n" + "2007-12-29,57,44\n" + "2007-12-30,52,43\n" + "2007-12-31,57,42\n"; } function data() { return "Date,High,Low\n" + data_nolabel(); } function NoisyData() { return "" + "Date,A,B\n" + "2006-10-01,3.01953818828,0.7212041046,2.18487394958,0.599318549691\n" + "2006-10-02,3.63321799308,0.778297234566,1.69491525424,0.531417655826\n" + "2006-10-03,2.44328097731,0.644967734352,2.51256281407,0.640539070386\n" + "2006-10-04,3.52733686067,0.774700921683,2.68456375839,0.66207105053\n" + "2006-10-05,3.28719723183,0.741636245748,2.35294117647,0.621407707226\n" + "2006-10-06,1.58450704225,0.523967868159,3.78657487091,0.791868460623\n" + "2006-10-07,5.32859680284,0.946589405904,4.0404040404,0.807910739509\n" + "2006-10-08,2.64084507042,0.672799548916,2.37288135593,0.626609885481\n" + "2006-10-09,2.26480836237,0.620990945917,3.5413153457,0.75897176848\n" + "2006-10-10,3.29289428076,0.74289969528,2.02702702703,0.579191340004\n" + "2006-10-11,2.7633851468,0.681234043829,1.1744966443,0.4413034044\n" + "2006-10-12,3.28719723183,0.741636245748,3.37268128162,0.741327769578\n" + "2006-10-13,1.77304964539,0.55569466381,1.85810810811,0.555011329732\n" + "2006-10-14,3.39892665474,0.7664008338,1.67224080268,0.524368852929\n" + "2006-10-15,2.65017667845,0.675144574777,3.35570469799,0.737661045752\n" + "2006-10-16,3.63951473137,0.779620631266,2.34899328859,0.620377617453\n" + "2006-10-17,2.25694444444,0.618859623032,1.68067226891,0.526990133716\n" + "2006-10-18,4.47504302926,0.857766274964,2.51677852349,0.641599927369\n" + "2006-10-19,2.44755244755,0.646081155692,1.68067226891,0.526990133716\n" + "2006-10-20,3.67775831874,0.787656442774,3.066439523,0.711598843969\n" + "2006-10-21,3.94265232975,0.823839169829,3.85906040268,0.788990618726\n" + "2006-10-22,2.59067357513,0.660187558973,3.71621621622,0.777438794254\n" + "2006-10-23,4.33275563258,0.847570482324,3.85906040268,0.788990618726\n" + "2006-10-24,3.10344827586,0.720049610821,2.84280936455,0.679611549697\n" + "2006-10-25,1.40350877193,0.492720767725,2.7027027027,0.666482380968\n" + "2006-10-26,1.95035460993,0.582291234145,2.36486486486,0.624518599275\n" + "2006-10-27,2.30905861456,0.632980642182,2.03045685279,0.580161203819\n" + "2006-10-28,4.09252669039,0.835706590809,2.87648054146,0.68754192469\n" + "2006-10-29,2.66903914591,0.679883997626,2.02360876897,0.578224712918\n" + "2006-10-30,4.74516695958,0.89127787497,4.36241610738,0.836670992529\n" + "2006-10-31,2.78260869565,0.685905251933,3.20945945946,0.724388507178\n" + "2006-11-01,1.5873015873,0.524884521441,1.51260504202,0.500373860545\n" + "2006-11-02,2.78745644599,0.687083077461,2.0202020202,0.57726130639\n" + "2006-11-03,5.11463844797,0.925157232782,2.68907563025,0.663168401088\n" + "2006-11-04,4.9001814882,0.919644816432,3.07692307692,0.713993047527\n" + "2006-11-05,5.13274336283,0.928343545136,3.55329949239,0.761492892041\n" + "2006-11-06,1.92644483363,0.575222935029,2.35294117647,0.621407707226\n" + "2006-11-07,2.46478873239,0.650573541306,1.52027027027,0.502889967904\n" + "2006-11-08,2.13523131673,0.609772022763,2.6981450253,0.665374048085\n" + "2006-11-09,3.88007054674,0.811026422222,2.72572402044,0.672079879106\n" + "2006-11-10,2.63620386643,0.671633132526,3.71621621622,0.777438794254\n" + "2006-11-11,3.69718309859,0.791736755355,3.0303030303,0.703344064467\n" + "2006-11-12,3.83944153578,0.802703592906,4.05405405405,0.81058250986\n" + "2006-11-13,2.47787610619,0.653984033555,2.20338983051,0.604340313133\n" + "2006-11-14,1.77304964539,0.55569466381,2.22222222222,0.60944692682\n" + "2006-11-15,2.30088495575,0.630766388737,0.843170320405,0.375484163785\n" + "2006-11-16,1.57894736842,0.522144132232,2.19594594595,0.602321544724\n" + "2006-11-18,2.45183887916,0.647198426991,1.69491525424,0.531417655826\n" + "2006-11-19,3.52733686067,0.774700921683,1.85185185185,0.55316023504\n" + "2006-11-20,2.97723292469,0.711254751484,2.6981450253,0.665374048085\n" + "2006-11-21,2.29681978799,0.629665059963,2.01680672269,0.576301104352\n" + "2006-11-22,3.01418439716,0.719945245328,2.5466893039,0.649125445325\n" + "2006-11-23,3.78378378378,0.809917534069,2.6936026936,0.664269394219\n" + "2006-11-24,3.18584070796,0.738851643987,2.01005025126,0.57439025002\n" + "2006-11-25,2.83185840708,0.697868332879,3.066439523,0.711598843969\n" + "2006-11-26,3.01953818828,0.7212041046,2.53378378378,0.645878720149\n" + "2006-11-27,2.81195079086,0.693033387099,1.51006711409,0.499540743312\n" + "2006-11-28,2.97723292469,0.711254751484,2.54237288136,0.648039583782\n" + "2006-11-29,1.41093474427,0.495309102312,3.02013422819,0.701020603129"; } function NoisyDataABC() { return "" + "Date,A,B,C\n" + "2006-10-01,3.01953818828,0.7212041046,2.18487394958,0.599318549691,2.02013422819,0.701020603129\n" + "2006-10-02,3.63321799308,0.778297234566,1.69491525424,0.531417655826,1.54237288136,0.648039583782\n" + "2006-10-03,2.44328097731,0.644967734352,2.51256281407,0.640539070386,0.51006711409,0.499540743312\n" + "2006-10-04,3.52733686067,0.774700921683,2.68456375839,0.66207105053,1.53378378378,0.645878720149\n" + "2006-10-05,3.28719723183,0.741636245748,2.35294117647,0.621407707226,2.066439523,0.711598843969\n" + "2006-10-06,1.58450704225,0.523967868159,3.78657487091,0.791868460623,1.01005025126,0.57439025002\n" + "2006-10-07,5.32859680284,0.946589405904,4.0404040404,0.807910739509,1.6936026936,0.664269394219\n" + "2006-10-08,2.64084507042,0.672799548916,2.37288135593,0.626609885481,1.5466893039,0.649125445325\n" + "2006-10-09,2.26480836237,0.620990945917,3.5413153457,0.75897176848,1.01680672269,0.576301104352\n" + "2006-10-10,3.29289428076,0.74289969528,2.02702702703,0.579191340004,1.6981450253,0.665374048085\n" + "2006-10-11,2.7633851468,0.681234043829,1.1744966443,0.4413034044,0.85185185185,0.55316023504\n" + "2006-10-12,3.28719723183,0.741636245748,3.37268128162,0.741327769578,0.69491525424,0.531417655826\n" + "2006-10-13,1.77304964539,0.55569466381,1.85810810811,0.555011329732,1.19594594595,0.602321544724\n" + "2006-10-14,3.39892665474,0.7664008338,1.67224080268,0.524368852929,0.843170320405,0.375484163785\n" + "2006-10-15,2.65017667845,0.675144574777,3.35570469799,0.737661045752,1.22222222222,0.60944692682\n" + "2006-10-16,3.63951473137,0.779620631266,2.34899328859,0.620377617453,1.20338983051,0.604340313133\n" + "2006-10-17,2.25694444444,0.618859623032,1.68067226891,0.526990133716,3.05405405405,0.81058250986\n" + "2006-10-18,4.47504302926,0.857766274964,2.51677852349,0.641599927369,2.0303030303,0.703344064467\n" + "2006-10-19,2.44755244755,0.646081155692,1.68067226891,0.526990133716,2.71621621622,0.777438794254\n" + "2006-10-20,3.67775831874,0.787656442774,3.066439523,0.711598843969,1.72572402044,0.672079879106\n" + "2006-10-21,3.94265232975,0.823839169829,3.85906040268,0.788990618726,1.6981450253,0.665374048085\n" + "2006-10-22,2.59067357513,0.660187558973,3.71621621622,0.777438794254,0.52027027027,0.502889967904\n" + "2006-10-23,4.33275563258,0.847570482324,3.85906040268,0.788990618726,1.35294117647,0.621407707226\n" + "2006-10-24,3.10344827586,0.720049610821,2.84280936455,0.679611549697,2.55329949239,0.761492892041\n" + "2006-10-25,1.40350877193,0.492720767725,2.7027027027,0.666482380968,2.07692307692,0.713993047527\n" + "2006-10-26,1.95035460993,0.582291234145,2.36486486486,0.624518599275,1.68907563025,0.663168401088\n" + "2006-10-27,2.30905861456,0.632980642182,2.03045685279,0.580161203819,1.0202020202,0.57726130639\n" + "2006-10-28,4.09252669039,0.835706590809,2.87648054146,0.68754192469,0.51260504202,0.500373860545\n" + "2006-10-29,2.66903914591,0.679883997626,2.02360876897,0.578224712918,2.20945945946,0.724388507178\n" + "2006-10-30,4.74516695958,0.89127787497,4.36241610738,0.836670992529,3.36241610738,0.836670992529\n" + "2006-10-31,2.78260869565,0.685905251933,3.20945945946,0.724388507178,1.02360876897,0.578224712918\n" + "2006-11-01,1.5873015873,0.524884521441,1.51260504202,0.500373860545,1.87648054146,0.68754192469\n" + "2006-11-02,2.78745644599,0.687083077461,2.0202020202,0.57726130639,1.03045685279,0.580161203819\n" + "2006-11-03,5.11463844797,0.925157232782,2.68907563025,0.663168401088,1.36486486486,0.624518599275\n" + "2006-11-04,4.9001814882,0.919644816432,3.07692307692,0.713993047527,1.7027027027,0.666482380968\n" + "2006-11-05,5.13274336283,0.928343545136,3.55329949239,0.761492892041,1.84280936455,0.679611549697\n" + "2006-11-06,1.92644483363,0.575222935029,2.35294117647,0.621407707226,2.85906040268,0.788990618726\n" + "2006-11-07,2.46478873239,0.650573541306,1.52027027027,0.502889967904,2.71621621622,0.777438794254\n" + "2006-11-08,2.13523131673,0.609772022763,2.6981450253,0.665374048085,2.85906040268,0.788990618726\n" + "2006-11-09,3.88007054674,0.811026422222,2.72572402044,0.672079879106,2.066439523,0.711598843969\n" + "2006-11-10,2.63620386643,0.671633132526,3.71621621622,0.777438794254,0.68067226891,0.526990133716\n" + "2006-11-11,3.69718309859,0.791736755355,3.0303030303,0.703344064467,1.51677852349,0.641599927369\n" + "2006-11-12,3.83944153578,0.802703592906,4.05405405405,0.81058250986,0.68067226891,0.526990133716\n" + "2006-11-13,2.47787610619,0.653984033555,2.20338983051,0.604340313133,1.34899328859,0.620377617453\n" + "2006-11-14,1.77304964539,0.55569466381,2.22222222222,0.60944692682,2.35570469799,0.737661045752\n" + "2006-11-15,2.30088495575,0.630766388737,0.843170320405,0.375484163785,0.67224080268,0.524368852929\n" + "2006-11-16,1.57894736842,0.522144132232,2.19594594595,0.602321544724,0.85810810811,0.555011329732\n" + "2006-11-18,2.45183887916,0.647198426991,1.69491525424,0.531417655826,2.37268128162,0.741327769578\n" + "2006-11-19,3.52733686067,0.774700921683,1.85185185185,0.55316023504,0.1744966443,0.4413034044\n" + "2006-11-20,2.97723292469,0.711254751484,2.6981450253,0.665374048085,1.02702702703,0.579191340004\n" + "2006-11-21,2.29681978799,0.629665059963,2.01680672269,0.576301104352,2.5413153457,0.75897176848\n" + "2006-11-22,3.01418439716,0.719945245328,2.5466893039,0.649125445325,1.37288135593,0.626609885481\n" + "2006-11-23,3.78378378378,0.809917534069,2.6936026936,0.664269394219,3.0404040404,0.807910739509\n" + "2006-11-24,3.18584070796,0.738851643987,2.01005025126,0.57439025002,2.78657487091,0.791868460623\n" + "2006-11-25,2.83185840708,0.697868332879,3.066439523,0.711598843969,1.35294117647,0.621407707226\n" + "2006-11-26,3.01953818828,0.7212041046,2.53378378378,0.645878720149,1.68456375839,0.66207105053\n" + "2006-11-27,2.81195079086,0.693033387099,1.51006711409,0.499540743312,1.51256281407,0.640539070386\n" + "2006-11-28,2.97723292469,0.711254751484,2.54237288136,0.648039583782,0.69491525424,0.531417655826\n" + "2006-11-29,1.41093474427,0.495309102312,3.02013422819,0.701020603129,1.18487394958,0.599318549691\n"; } function data_showzerovalues() { return "" + "2007-01-01,0,39\n" + "2007-01-02,62,0\n" + "2007-01-03,0,42\n" + "2007-01-04,57,0\n" + "2007-01-05,65,44\n" + "2007-01-06,55,44\n" + "2007-01-07,0,45\n" + "2007-01-08,66,0\n" + "2007-01-09,0,39\n"; } function data_temp() { return "" + "Date,NY,SF\n" + "2007-01-01,46;51;56,43;45;48\n" + "2007-01-02,43;48;52,48;56;63\n" + "2007-01-03,39;46;53,50;54;62\n" + "2007-01-04,44;51;58,45;52;56\n" + "2007-01-05,51;57;62,44;49;58\n" + "2007-01-06,55;64;72,40;50;60\n" + "2007-01-07,46;51;56,45;53;63\n" + "2007-01-08,40;49;57,43;53;64\n" + "2007-01-09,37;41;45,49;56;66\n" + "2007-01-10,31;35;38,45;49;54\n" + "2007-01-11,29;35;41,41;46;54\n" + "2007-01-12,39;45;50,41;44;49\n" + "2007-01-13,46;52;57,38;44;53\n" + "2007-01-14,42;44;46,36;43;51\n" + "2007-01-15,41;46;51,36;46;55\n" + "2007-01-16,25;41;57,37;45;54\n" + "2007-01-17,21;26;31,41;47;56\n" + "2007-01-18,25;32;38,38;48;61\n" + "2007-01-19,33;38;43,\n" + "2007-01-20,23;29;35,\n" + "2007-01-21,21;26;31,55;60;68\n" + "2007-01-22,28;31;34,44;54;66\n" + "2007-01-23,30;34;38,41;51;64\n" + "2007-01-24,34;37;40,42;51;64\n" + "2007-01-25,17;27;37,45;49;56\n" + "2007-01-26,11;18;24,44;48;52\n" + "2007-01-27,22;32;41,47;52;58\n" + "2007-01-28,32;38;43,48;52;60\n" + "2007-01-29,24;28;32,47;55;64\n" + "2007-01-30,23;30;37,51;54;56\n" + "2007-01-31,27;31;34,49;52;56\n" + "2007-02-01,28;33;37,46;50;52\n" + "2007-02-02,34;37;39,47;51;57\n" + "2007-02-03,25;32;38,42;51;62\n" + "2007-02-04,18;25;31,44;55;69\n" + "2007-02-05,10;15;20,48;55;68\n" + "2007-02-06,13;20;26,48;54;62\n" + "2007-02-07,14;21;27,51;56;62\n" + "2007-02-08,17;24;30,49;54;56\n" + "2007-02-09,20;27;33,55;56;58\n" + "2007-02-10,25;30;34,55;57;60\n" + "2007-02-11,21;28;34,51;55;59\n" + "2007-02-12,30;36;41,48;51;59\n" + "2007-02-13,23;29;34,48;52;58\n" + "2007-02-14,19;25;31,44;52;60\n" + "2007-02-15,17;21;25,49;55;65\n" + "2007-02-16,16;23;30,48;59;72\n" + "2007-02-17,22;29;36,51;62;77\n" + "2007-02-18,20;28;35,48;54;61\n" + "2007-02-19,14;22;29,48;53;61\n" + "2007-02-20,29;39;49,49;53;59\n" + "2007-02-21,39;44;49,50;54;61\n" + "2007-02-22,33;40;46,43;48;54\n" + "2007-02-23,23;32;40,43;48;54\n" + "2007-02-24,22;33;43,46;51;60\n" + "2007-02-25,26;33;39,49;52;55\n" + "2007-02-26,31;34;37,44;49;54\n" + "2007-02-27,35;39;43,40;45;52\n" + "2007-02-28,37;42;46,42;47;53\n" + "2007-03-01,33;39;45,44;49;55\n" + "2007-03-02,36;49;61,45;52;60\n" + "2007-03-03,37;48;59,48;58;68\n" + "2007-03-04,35;39;42,53;60;72\n" + "2007-03-05,23;33;42,52;58;67\n" + "2007-03-06,14;19;24,49;54;66\n" + "2007-03-07,14;20;25,50;53;60\n" + "2007-03-08,20;28;35,48;52;60\n" + "2007-03-09,15;25;34,49;53;58\n" + "2007-03-10,30;43;55,50;57;69\n" + "2007-03-11,40;45;50,53;63;79\n" + "2007-03-12,36;46;56,57;65;78\n" + "2007-03-13,44;49;54,52;58;68\n" + "2007-03-14,46;56;66,50;54;62\n" + "2007-03-15,37;53;69,51;59;75\n" + "2007-03-16,28;33;38,53;62;76\n" + "2007-03-17,27;35;42,50;53;57\n" + "2007-03-18,29;35;41,50;53;61\n" + "2007-03-19,33;38;43,49;53;59\n" + "2007-03-20,35;43;50,50;54;58\n" + "2007-03-21,27;34;41,48;55;62\n" + "2007-03-22,41;54;67,50;58;71\n" + "2007-03-23,46;54;62,49;56;67\n" + "2007-03-24,40;48;55,50;52;56\n" + "2007-03-25,38;46;53,50;53;56\n" + "2007-03-26,41;48;55,48;53;60\n" + "2007-03-27,48;62;75,46;50;56\n" + "2007-03-28,47;55;63,47;52;61\n" + "2007-03-29,40;48;56,49;57;68\n" + "2007-03-30,42;55;67,48;53;64\n" + "2007-03-31,44;51;57,49;55;68\n" + "2007-04-01,42;47;51,49;52;58\n" + "2007-04-02,42;48;54,48;54;68\n" + "2007-04-03,43;52;60,48;54;66\n" + "2007-04-04,41;43;45,50;56;67\n" + "2007-04-05,36;42;47,50;54;63\n" + "2007-04-06,34;39;43,48;51;55\n" + "2007-04-07,34;39;43,51;54;59\n" + "2007-04-08,32;37;42,51;55;61\n" + "2007-04-09,35;42;49,52;55;59\n" + "2007-04-10,36;43;50,50;56;67\n" + "2007-04-11,37;44;51,51;54;57\n" + "2007-04-12,41;43;45,48;53;58\n" + "2007-04-13,44;47;50,49;54;64\n" + "2007-04-14,42;50;57,50;53;56\n" + "2007-04-15,42;49;56,48;55;65\n" + "2007-04-16,40;47;54,52;58;69\n" + "2007-04-17,43;47;50,48;52;56\n" + "2007-04-18,43;48;53,46;50;55\n" + "2007-04-19,46;55;63,46;50;55\n" + "2007-04-20,46;59;71,47;52;60\n" + "2007-04-21,48;63;78,49;52;57\n" + "2007-04-22,52;64;76,51;55;59\n" + "2007-04-23,54;70;85,48;55;65\n" + "2007-04-24,59;67;75,49;54;63\n" + "2007-04-25,49;56;63,51;54;59\n" + "2007-04-26,48;55;62,50;54;63\n" + "2007-04-27,48;52;55,53;62;78\n" + "2007-04-28,53;63;72,52;61;78\n" + "2007-04-29,55;61;66,50;53;61\n" + "2007-04-30,55;68;81,51;57;66\n" + "2007-05-01,52;62;71,50;55;61\n" + "2007-05-02,51;61;71,52;56;62\n" + "2007-05-03,53;63;72,50;53;58\n" + "2007-05-04,52;62;71,50;54;60\n" + "2007-05-05,52;63;73,50;59;70\n" + "2007-05-06,48;55;62,57;72;86\n" + "2007-05-07,48;57;65,69;76;88\n" + "2007-05-08,50;60;70,53;68;79\n" + "2007-05-09,58;70;82,50;55;61\n" + "2007-05-10,62;71;79,50;54;61\n" + "2007-05-11,61;70;78,49;52;56\n" + "2007-05-12,55;64;73,50;54;60\n" + "2007-05-13,53;61;68,47;55;66\n" + "2007-05-14,52;61;69,49;53;60\n" + "2007-05-15,59;73;87,49;52;59\n" + "2007-05-16,63;76;89,48;55;66\n" + "2007-05-17,55;62;68,49;54;61\n" + "2007-05-18,49;52;55,50;55;63\n" + "2007-05-19,52;55;57,52;57;66\n" + "2007-05-20,55;67;78,51;57;67\n" + "2007-05-21,56;65;74,49;59;69\n" + "2007-05-22,55;64;73,52;61;71\n" + "2007-05-23,58;66;74,56;68;83\n" + "2007-05-24,61;75;88,50;56;69\n" + "2007-05-25,69;82;95,50;52;60\n" + "2007-05-26,73;80;86,50;52;60\n" + "2007-05-27,68;77;85,50;52;56\n" + "2007-05-28,67;77;87,50;55;64\n" + "2007-05-29,63;72;81,51;54;59\n" + "2007-05-30,62;73;84,53;54;57\n" + "2007-05-31,67;79;90,52;54;57\n" + "2007-06-01,64;78;92,50;53;59\n" + "2007-06-02,73;82;90,50;52;56\n" + "2007-06-03,65;73;81,51;54;61\n" + "2007-06-04,60;66;71,55;59;65\n" + "2007-06-05,68;77;85,54;57;61\n" + "2007-06-06,58;65;72,52;56;62\n" + "2007-06-07,58;67;76,51;57;68\n" + "2007-06-08,66;76;85,51;54;62\n" + "2007-06-09,64;73;81,50;58;66\n" + "2007-06-10,63;69;74,53;58;64\n" + "2007-06-11,66;76;85,52;58;66\n" + "2007-06-12,68;76;84,52;60;72\n" + "2007-06-13,59;65;70,57;68;80\n" + "2007-06-14,58;63;67,57;67;84\n" + "2007-06-15,62;68;73,54;62;76\n" + "2007-06-16,64;73;82,52;55;59\n" + "2007-06-17,69;80;90,52;59;70\n" + "2007-06-18,70;78;85,52;56;64\n" + "2007-06-19,69;76;83,54;58;65\n" + "2007-06-20,70;75;80,54;56;61\n" + "2007-06-21,65;75;85,53;59;67\n" + "2007-06-22,65;71;77,53;59;67\n" + "2007-06-23,61;69;77,53;58;67\n" + "2007-06-24,63;74;84,52;58;66\n" + "2007-06-25,71;78;84,52;60;75\n" + "2007-06-26,73;84;94,51;57;65\n" + "2007-06-27,73;84;95,53;59;67\n" + "2007-06-28,73;84;94,54;61;72\n" + "2007-06-29,70;75;79,55;61;72\n" + "2007-06-30,68;76;84,53;58;66\n" + "2007-07-01,64;70;75,52;59;68\n" + "2007-07-02,60;68;76,55;62;71\n" + "2007-07-03,65;74;82,54;59;64\n" + "2007-07-04,68;71;73,55;64;75\n" + "2007-07-05,69;76;82,55;62;72\n" + "2007-07-06,70;79;87,53;57;62\n" + "2007-07-07,72;80;88,52;54;58\n" + "2007-07-08,76;85;93,53;57;65\n" + "2007-07-09,71;84;96,55;59;67\n" + "2007-07-10,75;84;93,56;61;70\n" + "2007-07-11,74;81;88,59;65;74\n" + "2007-07-12,69;77;84,58;66;75\n" + "2007-07-13,75;80;85,57;65;78\n" + "2007-07-14,70;78;86,56;60;69\n" + "2007-07-15,75;83;91,57;63;72\n" + "2007-07-16,72;78;83,56;60;67\n" + "2007-07-17,71;80;88,56;62;73\n" + "2007-07-18,71;75;79,61;66;74\n" + "2007-07-19,71;82;92,58;62;72\n" + "2007-07-20,69;75;81,58;64;73\n" + "2007-07-21,67;76;84,60;66;76\n" + "2007-07-22,70;77;84,60;65;74\n" + "2007-07-23,65;71;76,58;64;76\n" + "2007-07-24,63;73;83,56;60;71\n" + "2007-07-25,71;78;85,56;59;64\n" + "2007-07-26,73;80;86,55;58;72\n" + "2007-07-27,75;82;88,55;60;72\n" + "2007-07-28,74;81;88,55;61;70\n" + "2007-07-29,74;77;80,55;60;74\n" + "2007-07-30,72;80;88,55;63;80\n" + "2007-07-31,74;82;90,54;59;68\n" + "2007-08-01,75;84;93,55;60;69\n" + "2007-08-02,76;87;98,55;61;70\n" + "2007-08-03,73;83;93,54;60;71\n" + "2007-08-04,75;84;92,54;58;67\n" + "2007-08-05,72;78;84,55;58;62\n" + "2007-08-06,75;81;86,56;60;68\n" + "2007-08-07,78;84;90,56;61;69\n" + "2007-08-08,75;85;95,56;59;65\n" + "2007-08-09,74;80;85,57;61;72\n" + "2007-08-10,59;67;75,55;62;73\n" + "2007-08-11,60;72;83,56;60;68\n" + "2007-08-12,73;81;88,54;60;70\n" + "2007-08-13,75;82;88,55;61;74\n" + "2007-08-14,71;77;83,55;60;71\n" + "2007-08-15,72;81;89,54;61;70\n" + "2007-08-16,77;82;87,57;62;72\n" + "2007-08-17,66;77;87,53;64;77\n" + "2007-08-18,62;69;76,56;62;71\n" + "2007-08-19,62;68;73,57;64;75\n" + "2007-08-20,61;67;73,59;65;74\n" + "2007-08-21,58;60;62,58;65;80\n" + "2007-08-22,58;64;69,57;65;80\n" + "2007-08-23,63;71;79,56;62;73\n" + "2007-08-24,67;77;86,58;62;70\n" + "2007-08-25,75;83;91,58;62;71\n" + "2007-08-26,75;80;85,57;61;68\n" + "2007-08-27,71;77;83,56;61;71\n" + "2007-08-28,72;78;84,55;66;82\n" + "2007-08-29,72;79;86,61;69;83\n" + "2007-08-30,73;81;88,62;71;82\n" + "2007-08-31,72;77;81,59;64;72\n" + "2007-09-01,66;73;79,58;66;80\n" + "2007-09-02,63;72;80,58;67;86\n" + "2007-09-03,67;77;86,58;63;71\n" + "2007-09-04,73;79;85,60;64;72\n" + "2007-09-05,69;74;79,58;68;84\n" + "2007-09-06,70;77;83,60;64;69\n" + "2007-09-07,72;80;88,59;63;71\n" + "2007-09-08,74;82;90,58;61;66\n" + "2007-09-09,74;80;86,59;61;66\n" + "2007-09-10,73;76;78,60;64;71\n" + "2007-09-11,72;75;77,59;62;70\n" + "2007-09-12,66;72;77,59;62;65\n" + "2007-09-13,65;71;76,59;65;74\n" + "2007-09-14,67;72;77,60;66;74\n" + "2007-09-15,58;65;71,58;62;72\n" + "2007-09-16,55;62;69,59;63;71\n" + "2007-09-17,56;63;70,56;62;71\n" + "2007-09-18,57;65;72,56;60;67\n" + "2007-09-19,59;68;76,54;57;63\n" + "2007-09-20,64;74;83,54;59;72\n" + "2007-09-21,68;76;84,58;62;73\n" + "2007-09-22,68;72;76,58;60;65\n" + "2007-09-23,67;75;82,56;61;67\n" + "2007-09-24,65;73;81,52;62;78\n" + "2007-09-25,66;78;89,54;66;86\n" + "2007-09-26,72;81;90,58;70;92\n" + "2007-09-27,73;79;84,57;61;68\n" + "2007-09-28,64;71;77,55;59;63\n" + "2007-09-29,62;69;76,51;60;74\n" + "2007-09-30,61;67;73,51;60;71\n" + "2007-10-01,62;67;71,56;62;71\n" + "2007-10-02,60;68;75,54;62;76\n" + "2007-10-03,66;73;80,53;59;67\n" + "2007-10-04,69;77;85,53;55;59\n" + "2007-10-05,67;75;83,51;56;62\n" + "2007-10-06,68;77;85,48;57;68\n" + "2007-10-07,67;75;82,51;59;75\n" + "2007-10-08,67;78;89,52;59;78\n" + "2007-10-09,61;72;83,52;59;67\n" + "2007-10-10,62;68;73,\n" + "2007-10-11,59;64;69,\n" + "2007-10-12,51;57;62,\n" + "2007-10-13,49;56;63,\n" + "2007-10-14,52;59;65,\n" + "2007-10-15,53;61;69,\n" + "2007-10-16,58;65;71,\n" + "2007-10-17,60;67;73,\n" + "2007-10-18,64;72;79,\n" + "2007-10-19,66;70;74,\n" + "2007-10-20,64;68;72,\n" + "2007-10-21,59;68;76,\n" + "2007-10-22,62;70;77,\n" + "2007-10-23,67;74;81,\n" + "2007-10-24,54;62;70,\n" + "2007-10-25,52;57;61,\n" + "2007-10-26,53;57;60,\n" + "2007-10-27,59;65;70,\n" + "2007-10-28,46;53;59,\n" + "2007-10-29,43;49;54,\n" + "2007-10-30,49;57;64,\n" + "2007-10-31,51;58;64,53;60;67\n" + "2007-11-01,52;60;67,51;56;72\n" + "2007-11-02,46;51;56,49;60;82\n" + "2007-11-03,47;50;53,50;61;79\n" + "2007-11-04,49;53;56,53;62;80\n" + "2007-11-05,48;53;58,50;54;59\n" + "2007-11-06,45;52;58,50;54;61\n" + "2007-11-07,42;46;50,50;53;59\n" + "2007-11-08,38;43;48,53;56;60\n" + "2007-11-09,40;44;48,53;58;66\n" + "2007-11-10,39;43;47,52;57;62\n" + "2007-11-11,34;41;48,51;56;64\n" + "2007-11-12,40;46;52,47;56;68\n" + "2007-11-13,49;55;61,57;61;73\n" + "2007-11-14,44;53;61,53;61;72\n" + "2007-11-15,45;54;62,56;59;67\n" + "2007-11-16,39;44;48,54;56;61\n" + "2007-11-17,38;43;47,53;56;61\n" + "2007-11-18,41;44;47,53;57;63\n" + "2007-11-19,39;43;46,50;56;60\n" + "2007-11-20,40;45;50,48;54;63\n" + "2007-11-21,44;51;58,44;53;66\n" + "2007-11-22,42;54;66,46;54;68\n" + "2007-11-23,32;37;42,44;57;72\n" + "2007-11-24,28;34;40,44;54;65\n" + "2007-11-25,37;44;50,51;55;62\n" + "2007-11-26,41;52;63,47;54;65\n" + "2007-11-27,46;56;65,50;55;65\n" + "2007-11-28,37;42;47,47;56;66\n" + "2007-11-29,42;47;52,48;53;64\n" + "2007-11-30,37;40;43,45;49;57\n" + "2007-12-01,26;34;42,46;49;56\n" + "2007-12-02,21;30;38,47;54;59\n" + "2007-12-03,34;42;49,52;59;64\n" + "2007-12-04,30;33;35,31;57;69\n" + "2007-12-05,29;32;34,52;56;63\n" + "2007-12-06,23;30;37,51;52;54\n" + "2007-12-07,34;35;36,48;52;57\n" + "2007-12-08,35;40;45,42;49;56\n" + "2007-12-09,36;39;41,44;51;59\n" + "2007-12-10,37;40;43,45;50;59\n" + "2007-12-11,36;41;46,46;52;59\n" + "2007-12-12,37;46;54,42;49;57\n" + "2007-12-13,30;34;38,42;49;59\n" + "2007-12-14,32;39;45,40;48;57\n" + "2007-12-15,30;34;38,43;49;56\n" + "2007-12-16,31;36;40,46;51;57\n" + "2007-12-17,27;31;35,48;52;56\n" + "2007-12-18,31;35;38,49;52;55\n" + "2007-12-19,35;41;47,46;51;57\n" + "2007-12-20,38;42;45,45;51;56\n" + "2007-12-21,36;39;42,43;48;54\n" + "2007-12-22,36;39;42,39;46;53\n" + "2007-12-23,41;52;62,44;51;61\n" + "2007-12-24,39;46;52,49;53;60\n" + "2007-12-25,38;41;44,41;49;57\n" + "2007-12-26,34;38;41,44;48;55\n" + "2007-12-27,37;42;46,41;46;52\n" + "2007-12-28,43;47;50,41;44;45\n" + "2007-12-29,43;48;53,45;48;52\n" + "2007-12-30,37;41;44,46;49;53\n" + "2007-12-31,36;41;46,38;47;56\n" + "2008-01-01,35;42;49,42;50;58\n" + "2008-01-02,19;29;39,43;50;60\n" + "2008-01-03,15;19;23,51;53;58\n" + "2008-01-04,19;28;37,51;53;59\n" + "2008-01-05,33;38;43,46;49;51\n" + "2008-01-06,37;42;47,42;47;51\n" + "2008-01-07,42;52;61,43;48;53\n" + "2008-01-08,52;58;64,44;49;53\n" + "2008-01-09,49;58;66,46;49;52\n" + "2008-01-10,42;47;51,48;50;51\n" + "2008-01-11,41;49;57,48;51;55\n" + "2008-01-12,40;45;49,46;51;58\n" + "2008-01-13,37;42;47,44;51;60\n" + "2008-01-14,34;37;39,46;51;58\n" + "2008-01-15,34;37;40,44;49;57\n" + "2008-01-16,31;36;40,41;50;60\n" + "2008-01-17,30;36;41,44;50;61\n" + "2008-01-18,37;43;49,42;52;63\n" + "2008-01-19,34;36;37,42;50;62\n" + "2008-01-20,19;27;35,46;49;53\n" + "2008-01-21,16;22;28,43;45;46\n" + "2008-01-22,25;33;40,42;44;47\n" + "2008-01-23,32;37;41,42;44;49\n" + "2008-01-24,26;31;35,43;44;45\n" + "2008-01-25,23;29;34,45;49;52\n" + "2008-01-26,27;31;35,52;55;62\n" + "2008-01-27,31;35;39,45;51;54\n" + "2008-01-28,29;36;43,42;46;51\n" + "2008-01-29,33;39;44,41;45;49\n" + "2008-01-30,34;42;50,42;47;54\n" + "2008-01-31,30;35;40,45;48;52\n" + "2008-02-01,33;45;56,40;46;52\n" + "2008-02-02,36;40;44,42;47;53\n" + "2008-02-03,33;42;50,46;49;52\n" + "2008-02-04,34;39;43,43;49;58\n" + "2008-02-05,38;45;52,40;49;58\n" + "2008-02-06,40;55;69,47;50;55\n" + "2008-02-07,39;45;50,43;50;58\n" + "2008-02-08,36;41;46,46;53;65\n" + "2008-02-09,37;41;45,46;57;69\n" + "2008-02-10,17;31;45,49;57;70\n" + "2008-02-11,12;19;25,48;56;69\n" + "2008-02-12,19;25;31,46;54;70\n" + "2008-02-13,31;43;54,48;57;65\n" + "2008-02-14,30;36;41,49;54;64\n" + "2008-02-15,32;40;48,46;52;60\n" + "2008-02-16,25;30;35,45;51;64\n" + "2008-02-17,30;42;53,46;50;55\n" + "2008-02-18,42;53;64,47;50;58\n" + "2008-02-19,29;36;42,48;51;53\n" + "2008-02-20,25;29;33,48;51;57\n" + "2008-02-21,22;28;34,48;51;56\n" + "2008-02-22,26;30;34,46;50;56\n" + "2008-02-23,30;33;36,43;48;54\n" + "2008-02-24,27;34;40,52;54;58\n" + "2008-02-25,33;41;49,49;55;63\n" + "2008-02-26,38;43;48,48;58;70\n" + "2008-02-27,24;36;47,52;58;71\n" + "2008-02-28,20;25;29,48;56;74\n" + "2008-02-29,19;28;36,48;52;62\n" + "2008-03-01,34;40;45,50;53;59\n" + "2008-03-02,30;36;43,49;59;71\n" + "2008-03-03,35;44;52,47;57;72\n" + "2008-03-04,43;53;62,46;55;68\n" + "2008-03-05,39;49;58,46;55;71\n" + "2008-03-06,36;43;49,47;55;65\n" + "2008-03-07,35;40;45,48;56;69\n" + "2008-03-08,38;48;57,49;55;66\n" + "2008-03-09,31;37;43,47;58;76\n" + "2008-03-10,28;37;46,52;58;70\n" + "2008-03-11,36;43;49,50;54;66\n" + "2008-03-12,36;42;48,48;53;63\n" + "2008-03-13,33;39;45,30;55;61\n" + "2008-03-14,39;46;53,49;52;58\n" + "2008-03-15,42;49;55,43;50;57\n" + "2008-03-16,36;42;47,47;55;68\n" + "2008-03-17,32;41;49,49;56;66\n" + "2008-03-18,36;41;46,48;55;64\n" + "2008-03-19,42;47;52,49;52;60\n" + "2008-03-20,37;48;59,46;51;59\n" + "2008-03-21,35;41;47,47;54;67\n" + "2008-03-22,35;43;50,46;55;73\n" + "2008-03-23,32;40;48,46;54;66\n" + "2008-03-24,33;41;49,47;54;64\n" + "2008-03-25,32;39;46,49;53;59\n" + "2008-03-26,43;52;60,48;52;60\n" + "2008-03-27,44;47;50,45;50;59\n" + "2008-03-28,40;45;49,46;52;60\n" + "2008-03-29,34;41;47,48;52;59\n" + "2008-03-30,28;37;46,44;49;56\n" + "2008-03-31,39;48;57,41;50;62\n" + "2008-04-01,55;60;64,49;53;60\n" + "2008-04-02,39;47;54,48;54;62\n" + "2008-04-03,35;42;49,48;53;62\n" + "2008-04-04,42;49;55,45;50;57\n" + "2008-04-05,49;54;59,46;50;57\n" + "2008-04-06,42;46;50,48;52;59\n" + "2008-04-07,41;46;51,45;51;59\n" + "2008-04-08,41;48;55,47;50;55\n" + "2008-04-09,39;48;56,47;52;59\n" + "2008-04-10,48;62;75,46;55;66\n" + "2008-04-11,47;54;61,50;65;84\n" + "2008-04-12,47;61;74,57;71;87\n" + "2008-04-13,44;50;55,51;64;80\n" + "2008-04-14,41;49;56,48;52;58\n" + "2008-04-15,43;51;59,45;51;60\n" + "2008-04-16,46;56;65,46;51;66\n" + "2008-04-17,45;59;72,49;57;75\n" + "2008-04-18,50;67;83,48;52;60\n" + "2008-04-19,51;62;72,45;49;55\n" + "2008-04-20,48;52;56,43;48;55\n" + "2008-04-21,47;52;57,44;49;57\n" + "2008-04-22,48;59;69,48;54;64\n" + "2008-04-23,52;64;76,48;53;60\n" + "2008-04-24,58;68;77,45;53;65\n" + "2008-04-25,55;63;70,48;57;69\n" + "2008-04-26,52;58;63,51;62;78\n" + "2008-04-27,49;52;55,53;62;77\n" + "2008-04-28,48;54;59,49;56;66\n" + "2008-04-29,47;53;58,50;53;61\n" + "2008-04-30,44;51;57,47;52;60\n" + "2008-05-01,45;52;59,47;55;66\n" + "2008-05-02,50;53;55,49;53;60\n" + "2008-05-03,48;52;56,48;54;63\n" + "2008-05-04,49;61;72,49;52;59\n" + "2008-05-05,51;61;71,48;53;61\n" + "2008-05-06,52;65;77,49;56;68\n" + "2008-05-07,58;66;73,49;55;65\n" + "2008-05-08,64;69;74,48;53;62\n" + "2008-05-09,50;57;64,46;54;65\n" + "2008-05-10,50;59;67,47;54;64\n" + "2008-05-11,50;56;61,48;54;64\n" + "2008-05-12,48;53;57,50;56;66\n" + "2008-05-13,52;61;70,49;61;78\n" + "2008-05-14,53;64;74,55;69;85\n" + "2008-05-15,54;64;73,68;81;102\n" + "2008-05-16,51;56;61,65;77;99\n" + "2008-05-17,50;62;74,52;63;73\n" + "2008-05-18,56;62;67,51;54;62\n" + "2008-05-19,51;56;61,50;54;60\n" + "2008-05-20,50;54;58,52;56;63\n" + "2008-05-21,50;60;69,50;57;65\n" + "2008-05-22,50;56;61,52;58;68\n" + "2008-05-23,52;60;68,49;55;65\n" + "2008-05-24,56;63;70,51;54;62\n" + "2008-05-25,58;68;77,50;56;64\n" + "2008-05-26,60;68;76,50;55;62\n" + "2008-05-27,60;73;85,51;56;64\n" + "2008-05-28,53;61;69,52;58;67\n" + "2008-05-29,54;66;78,53;57;63\n" + "2008-05-30,62;72;82,52;56;66\n" + "2008-05-31,63;70;77,52;54;60\n" + "2008-06-01,67;76;84,49;55;63\n" + "2008-06-02,61;71;80,49;54;62\n" + "2008-06-03,65;75;84,51;55;62\n" + "2008-06-04,58;65;71,51;54;62\n" + "2008-06-05,62;67;72,51;58;70\n" + "2008-06-06,59;66;73,51;57;66\n" + "2008-06-07,60;78;96,50;59;70\n" + "2008-06-08,78;87;96,52;61;74\n" + "2008-06-09,76;88;99,54;66;83\n" + "2008-06-10,73;87;100,53;64;77\n" + "2008-06-11,72;80;88,57;65;79\n" + "2008-06-12,71;79;86,55;67;93\n" + "2008-06-13,65;74;82,50;56;65\n" + "2008-06-14,70;80;90,52;56;65\n" + "2008-06-15,65;74;83,50;54;61\n" + "2008-06-16,64;71;78,49;52;58\n" + "2008-06-17,64;71;77,48;60;78\n" + "2008-06-18,60;68;75,54;65;79\n" + "2008-06-19,60;69;77,57;72;92\n" + "2008-06-20,63;71;79,63;80;102\n" + "2008-06-21,65;75;85,56;75;85\n" + "2008-06-22,71;76;80,51;56;63\n" + "2008-06-23,70;77;83,50;52;56\n" + "2008-06-24,70;76;81,50;58;71\n" + "2008-06-25,68;77;85,51;56;64\n" + "2008-06-26,74;80;85,50;57;70\n" + "2008-06-27,76;81;85,52;57;62\n" + "2008-06-28,70;80;90,55;58;64\n" + "2008-06-29,74;82;90,53;57;65\n" + "2008-06-30,73;79;85,52;57;68\n" + "2008-07-01,71;79;86,53;58;67\n" + "2008-07-02,69;78;87,52;56;65\n" + "2008-07-03,73;83;93,54;60;71\n" + "2008-07-04,71;76;80,55;60;69\n" + "2008-07-05,69;72;75,56;62;74\n" + "2008-07-06,68;74;79,56;62;74\n" + "2008-07-07,70;78;85,56;66;80\n" + "2008-07-08,76;84;91,59;69;86\n" + "2008-07-09,75;81;87,62;69;82\n" + "2008-07-10,73;80;87,59;64;72\n" + "2008-07-11,69;79;89,58;64;73\n" + "2008-07-12,73;80;86,60;64;73\n" + "2008-07-13,72;79;85,60;64;71\n" + "2008-07-14,70;77;83,59;62;68\n" + "2008-07-15,72;81;90,59;62;70\n" + "2008-07-16,71;81;90,57;60;66\n" + "2008-07-17,74;83;92,54;58;65\n" + "2008-07-18,76;86;96,52;56;64\n" + "2008-07-19,81;89;97,53;57;63\n" + "2008-07-20,79;87;94,52;55;63\n" + "2008-07-21,75;84;93,54;57;63\n" + "2008-07-22,73;80;87,52;60;73\n" + "2008-07-23,70;76;82,54;61;76\n" + "2008-07-24,70;78;85,52;60;72\n" + "2008-07-25,71;79;87,52;61;72\n" + "2008-07-26,74;81;88,55;65;79\n" + "2008-07-27,70;76;82,55;57;62\n" + "2008-07-28,69;79;88,54;57;65\n" + "2008-07-29,75;83;90,55;59;68\n" + "2008-07-30,73;80;87,56;60;69\n" + "2008-07-31,75;83;90,55;58;69\n" + "2008-08-01,74;81;88,55;62;74\n" + "2008-08-02,69;76;82,55;64;79\n" + "2008-08-03,68;76;83,54;57;62\n" + "2008-08-04,68;77;85,53;56;65\n" + "2008-08-05,73;79;84,52;56;64\n" + "2008-08-06,72;80;88,54;57;68\n" + "2008-08-07,68;77;86,54;57;66\n" + "2008-08-08,68;75;82,54;58;66\n" + "2008-08-09,66;74;82,54;59;67\n" + "2008-08-10,69;76;82,53;64;79\n" + "2008-08-11,60;67;73,56;63;78\n" + "2008-08-12,63;72;81,55;63;79\n" + "2008-08-13,69;75;81,56;64;82\n" + "2008-08-14,68;76;84,55;61;74\n" + "2008-08-15,67;74;81,55;62;75\n" + "2008-08-16,66;74;82,57;60;66\n" + "2008-08-17,69;77;85,57;60;69\n" + "2008-08-18,73;81;88,58;62;68\n" + "2008-08-19,65;74;82,58;62;73\n" + "2008-08-20,61;69;77,58;65;74\n" + "2008-08-21,66;74;81,60;64;73\n" + "2008-08-22,69;76;83,58;62;72\n" + "2008-08-23,67;74;80,57;62;71\n" + "2008-08-24,71;76;81,57;64;73\n" + "2008-08-25,68;77;85,58;62;72\n" + "2008-08-26,62;71;80,55;61;79\n" + "2008-08-27,64;71;77,59;70;88\n" + "2008-08-28,66;75;84,62;72;86\n" + "2008-08-29,68;74;80,59;67;84\n" + "2008-08-30,68;76;84,57;61;70\n" + "2008-08-31,69;77;84,54;61;73\n" + "2008-09-01,68;76;84,57;68;83\n" + "2008-09-02,71;79;86,58;68;85\n" + "2008-09-03,70;76;81,59;69;87\n" + "2008-09-04,72;81;90,60;73;94\n" + "2008-09-05,73;80;86,63;74;92\n" + "2008-09-06,72;78;83,63;72;84\n" + "2008-09-07,70;77;83,56;62;70\n" + "2008-09-08,68;76;83,55;60;67\n" + "2008-09-09,67;73;78,58;60;65\n" + "2008-09-10,63;68;73,57;61;68\n" + "2008-09-11,62;68;73,56;60;66\n" + "2008-09-12,65;69;72,55;57;63\n" + "2008-09-13,68;74;79,55;59;67\n" + "2008-09-14,71;80;89,55;59;67\n" + "2008-09-15,67;76;84,54;58;67\n" + "2008-09-16,63;67;70,54;58;65\n" + "2008-09-17,61;68;75,56;60;67\n" + "2008-09-18,61;68;74,53;60;70\n" + "2008-09-19,55;61;66,55;62;72\n" + "2008-09-20,54;61;68,59;62;69\n" + "2008-09-21,59;70;80,57;61;70\n" + "2008-09-22,62;68;73,55;64;79\n" + "2008-09-23,57;63;69,56;67;85\n" + "2008-09-24,58;64;69,57;64;74\n" + "2008-09-25,56;62;67,57;65;76\n" + "2008-09-26,60;64;68,56;63;75\n" + "2008-09-27,64;67;69,54;62;78\n" + "2008-09-28,67;71;74,55;59;65\n" + "2008-09-29,63;68;73,57;61;67\n" + "2008-09-30,62;68;73,57;63;76\n" + "2008-10-01,61;67;73,59;64;74\n" + "2008-10-02,56;60;64,60;65;73\n" + "2008-10-03,54;60;65,59;63;71\n" + "2008-10-04,53;58;63,58;62;68\n" + "2008-10-05,54;59;63,57;62;69\n" + "2008-10-06,51;57;63,59;64;76\n" + "2008-10-07,48;56;64,56;64;79\n" + "2008-10-08,52;59;66,58;64;75\n" + "2008-10-09,60;69;77,54;61;71\n" + "2008-10-10,61;67;73,54;58;64\n" + "2008-10-11,57;64;71,55;60;67\n" + "2008-10-12,57;64;71,55;62;72\n" + "2008-10-13,60;68;75,58;64;77\n" + "2008-10-14,59;64;68,53;63;80\n" + "2008-10-15,60;66;71,55;65;83\n" + "2008-10-16,57;68;78,58;68;86\n" + "2008-10-17,52;56;60,59;69;86\n" + "2008-10-18,44;51;57,53;58;65\n" + "2008-10-19,43;51;58,52;54;57\n" + "2008-10-20,45;54;62,51;57;65\n" + "2008-10-21,46;55;63,52;62;79\n" + "2008-10-22,42;47;52,55;67;83\n" + "2008-10-23,40;47;54,61;70;85\n" + "2008-10-24,43;50;57,59;66;86\n" + "2008-10-25,54;61;67,57;67;82\n" + "2008-10-26,51;57;63,52;57;66\n" + "2008-10-27,50;57;64,51;55;65\n" + "2008-10-28,41;46;51,50;55;65\n" + "2008-10-29,40;44;48,49;54;64\n" + "2008-10-30,38;44;49,51;56;61\n" + "2008-10-31,42;53;64,58;61;66\n" + "2008-11-01,52;59;65,34;58;63\n" + "2008-11-02,39;46;52,\n" + "2008-11-03,44;52;59,53;56;61\n" + "2008-11-04,50;57;64,51;54;61\n" + "2008-11-05,55;59;63,46;55;66\n" + "2008-11-06,59;62;65,54;60;72\n" + "2008-11-07,58;62;65,52;60;75\n" + "2008-11-08,55;58;60,53;56;62\n" + "2008-11-09,50;53;56,52;56;63\n" + "2008-11-10,44;48;51,49;55;62\n" + "2008-11-11,40;46;51,53;58;64\n" + "2008-11-12,43;47;50,56;59;68\n" + "2008-11-13,47;53;58,52;61;74\n" + "2008-11-14,55;59;63,55;67;80\n" + "2008-11-15,58;62;65,60;70;82\n" + "2008-11-16,42;52;61,58;66;78\n" + "2008-11-17,39;43;47,57;66;77\n" + "2008-11-18,31;36;41,52;56;62\n" + "2008-11-19,28;33;37,52;54;57\n" + "2008-11-20,31;35;39,54;58;66\n" + "2008-11-21,27;33;39,47;55;67\n" + "2008-11-22,25;29;33,47;55;68\n" + "2008-11-23,26;32;37,49;56;69\n" + "2008-11-24,33;43;53,49;56;64\n" + "2008-11-25,38;43;48,52;57;61\n" + "2008-11-26,38;42;45,53;55;58\n" + "2008-11-27,37;41;44,53;55;61\n" + "2008-11-28,38;44;50,50;54;60\n" + "2008-11-29,38;42;45,50;57;68\n" + "2008-11-30,36;40;44,51;58;70\n" + "2008-12-01,43;49;55,53;55;57\n" + "2008-12-02,38;42;46,52;56;64\n" + "2008-12-03,35;39;43,50;54;59\n" + "2008-12-04,39;45;51,47;53;62\n" + "2008-12-05,34;38;42,46;54;66\n" + "2008-12-06,31;34;37,47;53;62\n" + "2008-12-07,22;30;37,44;50;56\n" + "2008-12-08,20;26;31,46;51;55\n" + "2008-12-09,31;44;56,42;50;59\n" + "2008-12-10,44;54;63,47;52;63\n" + "2008-12-11,38;41;44,48;54;63\n" + "2008-12-12,33;40;46,48;53;60\n" + "2008-12-13,28;31;33,45;49;55\n" + "2008-12-14,30;40;49,43;46;50\n" + "2008-12-15,48;58;67,41;46;51\n" + "2008-12-16,31;45;59,39;42;47\n" + "2008-12-17,33;39;44,39;45;53\n" + "2008-12-18,37;40;43,38;47;55\n" + "2008-12-19,30;35;39,45;50;55\n" + "2008-12-20,23;28;32,39;46;54\n" + "2008-12-21,26;33;40,46;48;51\n" + "2008-12-22,14;22;29,44;49;54\n" + "2008-12-23,20;26;31,43;48;53\n" + "2008-12-24,31;45;58,46;50;54\n" + "2008-12-25,35;47;58,45;48;53\n" + "2008-12-26,32;37;41,\n" + "2008-12-27,40;44;48,\n" + "2008-12-28,47;56;65,\n" + "2008-12-29,38;44;49,47;53;63\n" + "2008-12-30,34;39;44,46;51;58\n" + "2008-12-31,19;28;36,45;48;55\n" + "2009-01-01,16;22;27,44;48;51\n" + "2009-01-02,24;30;36,45;50;55\n" + "2009-01-03,30;35;39,40;46;54\n" + "2009-01-04,26;35;43,36;45;54\n" + "2009-01-05,39;42;44,45;46;48\n" + "2009-01-06,31;35;39,44;49;54\n" + "2009-01-07,32;36;40,44;47;52\n" + "2009-01-08,30;35;39,46;49;56\n" + "2009-01-09,27;30;33,41;51;62\n" + "2009-01-10,24;27;30,48;56;66\n" + "2009-01-11,26;29;32,44;55;70\n" + "2009-01-12,23;29;34,49;62;76\n" + "2009-01-13,29;35;41,55;64;75\n" + "2009-01-14,18;28;37,48;59;70\n" + "2009-01-15,17;20;23,48;60;74\n" + "2009-01-16,11;15;18,48;60;72\n" + "2009-01-17,8;15;22,48;57;68\n" + "2009-01-18,22;28;33,49;58;70\n" + "2009-01-19,26;30;33,46;58;71\n" + "2009-01-20,22;26;30,47;58;71\n" + "2009-01-21,19;24;28,52;57;62\n" + "2009-01-22,22;30;37,51;53;55\n" + "2009-01-23,28;37;45,53;55;56\n" + "2009-01-24,18;33;47,49;52;55\n" + "2009-01-25,15;22;28,46;50;55\n" + "2009-01-26,20;25;30,43;49;57\n" + "2009-01-27,26;29;31,43;50;58\n" + "2009-01-28,24;33;42,43;51;61\n" + "2009-01-29,27;32;37,44;56;70\n" + "2009-01-30,28;32;35,47;56;69\n" + "2009-01-31,21;25;28,44;52;69\n" + "2009-02-01,28;41;53,44;54;70\n" + "2009-02-02,33;43;52,47;57;69\n" + "2009-02-03,26;32;37,47;57;71\n" + "2009-02-04,18;25;31,47;55;67\n" + "2009-02-05,14;19;24,51;54;58\n" + "2009-02-06,17;25;32,51;53;55\n" + "2009-02-07,24;37;49,48;54;63\n" + "2009-02-08,38;49;59,45;50;56\n" + "2009-02-09,35;41;46,43;47;56\n" + "2009-02-10,33;42;50,38;47;54\n" + "2009-02-11,38;51;63,43;48;56\n" + "2009-02-12,44;51;57,44;48;55\n" + "2009-02-13,32;38;44,43;46;54\n" + "2009-02-14,29;35;41,43;48;54\n" + "2009-02-15,30;36;42,46;48;51\n" + "2009-02-16,29;35;40,47;49;56\n" + "2009-02-17,26;34;41,47;50;54\n" + "2009-02-18,32;38;43,45;52;60\n" + "2009-02-19,28;39;50,43;54;66\n" + "2009-02-20,24;29;34,49;57;68\n" + "2009-02-21,28;35;42,50;56;63\n" + "2009-02-22,32;38;43,53;55;58\n" + "2009-02-23,25;30;34,52;56;60\n" + "2009-02-24,23;30;37,50;52;53\n" + "2009-02-25,26;34;41,51;55;61\n" + "2009-02-26,35;43;50,48;54;60\n" + "2009-02-27,40;49;58,46;53;62\n" + "2009-02-28,32;43;54,50;56;66\n" + "2009-03-01,28;32;35,54;57;59\n" + "2009-03-02,18;23;28,53;56;61\n" + "2009-03-03,14;21;28,48;52;58\n" + "2009-03-04,19;27;35,44;50;58\n" + "2009-03-05,28;36;43,46;51;60\n" + "2009-03-06,38;46;54,47;52;58\n" + "2009-03-07,46;58;69,43;52;65\n" + "2009-03-08,44;53;62,50;53;60\n" + "2009-03-09,37;42;46,\n" + "2009-03-10,37;42;46,\n" + "2009-03-11,41;50;59,\n" + "2009-03-12,32;38;44,\n" + "2009-03-13,27;33;38,\n" + "2009-03-14,34;42;50,\n" + "2009-03-15,42;48;53,\n" + "2009-03-16,38;42;46,\n" + "2009-03-17,36;44;52,\n" + "2009-03-18,40;51;61,\n" + "2009-03-19,41;46;50,\n" + "2009-03-20,33;39;44,\n" + "2009-03-21,32;40;47,53;55;60\n" + "2009-03-22,36;45;53,45;50;58\n" + "2009-03-23,30;36;42,44;51;59\n" + "2009-03-24,29;38;47,43;54;66\n" + "2009-03-25,36;44;52,47;56;68\n" + "2009-03-26,40;43;45,51;58;68\n" + "2009-03-27,43;53;63,52;61;75\n" + "2009-03-28,42;48;53,51;59;71\n" + "2009-03-29,42;47;52,48;56;64\n" + "2009-03-30,42;47;51,51;57;68\n" + "2009-03-31,40;50;60,47;56;70\n" + "2009-04-01,42;46;50,50;57;68\n" + "2009-04-02,42;51;59,48;52;60\n" + "2009-04-03,44;53;61,47;53;61\n" + "2009-04-04,48;52;55,43;55;70\n" + "2009-04-05,45;56;66,46;61;80\n" + "2009-04-06,44;48;51,51;61;79\n" + "2009-04-07,40;45;50,51;55;63\n" + "2009-04-08,37;43;49,50;55;64\n" + "2009-04-09,38;50;61,50;53;58\n" + "2009-04-10,46;52;58,48;54;63\n" + "2009-04-11,43;47;50,46;53;61\n" + "2009-04-12,38;43;48,49;56;66\n" + "2009-04-13,36;47;57,49;56;65\n" + "2009-04-14,43;48;53,45;50;57\n" + "2009-04-15,43;49;54,46;46;47\n" + "2009-04-16,44;53;62,44;52;61\n" + "2009-04-17,44;57;69,46;54;70\n" + "2009-04-18,56;67;78,52;62;74\n" + "2009-04-19,47;54;61,51;70;92\n" + "2009-04-20,43;47;51,60;77;99\n" + "2009-04-21,47;52;56,64;78;93\n" + "2009-04-22,49;53;57,52;62;71\n" + "2009-04-23,45;51;57,50;54;61\n" + "2009-04-24,45;57;69,48;52;59\n" + "2009-04-25,50;69;88,45;52;60\n" + "2009-04-26,63;77;91,46;53;63\n" + "2009-04-27,53;66;79,48;51;57\n" + "2009-04-28,68;79;89,47;52;60\n" + "2009-04-29,50;59;68,46;53;61\n" + "2009-04-30,46;54;61,47;54;69\n" + "2009-05-01,54;63;72,51;56;62\n" + "2009-05-02,56;62;67,55;59;65\n" + "2009-05-03,53;56;58,53;59;66\n" + "2009-05-04,51;54;56,52;60;71\n" + "2009-05-05,51;54;56,57;61;69\n" + "2009-05-06,49;57;65,55;61;73\n" + "2009-05-07,52;63;73,51;61;72\n" + "2009-05-08,56;65;74,51;60;71\n" + "2009-05-09,57;68;78,50;57;69\n" + "2009-05-10,55;63;70,50;57;68\n" + "2009-05-11,51;59;66,50;56;65\n" + "2009-05-12,52;60;68,47;57;67\n" + "2009-05-13,51;60;68,50;59;70\n" + "2009-05-14,57;62;66,51;58;69\n" + "2009-05-15,58;66;74,51;58;67\n" + "2009-05-16,58;65;71,54;70;93\n" + "2009-05-17,53;59;64,55;70;92\n" + "2009-05-18,50;55;59,51;55;63\n" + "2009-05-19,50;60;69,51;56;64\n" + "2009-05-20,53;67;81,49;55;62\n" + "2009-05-21,64;73;81,49;54;65\n" + "2009-05-22,63;75;86,51;54;63\n" + "2009-05-23,59;68;76,50;52;56\n" + "2009-05-24,57;71;84,49;51;56\n" + "2009-05-25,64;73;81,49;54;63\n" + "2009-05-26,53;60;66,49;59;73\n" + "2009-05-27,52;56;60,52;57;64\n" + "2009-05-28,56;58;59,51;55;62\n" + "2009-05-29,55;65;74,52;57;63\n" + "2009-05-30,63;71;78,54;56;62\n" + "2009-05-31,58;70;82,53;55;60\n" + "2009-06-01,53;61;69,53;56;61\n" + "2009-06-02,63;72;80,52;57;68\n" + "2009-06-03,54;64;73,53;58;67\n" + "2009-06-04,54;62;69,55;59;67\n" + "2009-06-05,55;59;62,57;62;70\n" + "2009-06-06,58;68;78,57;61;68\n" + "2009-06-07,66;74;82,56;61;67\n" + "2009-06-08,63;70;77,55;58;62\n" + "2009-06-09,60;67;73,56;60;66\n" + "2009-06-10,59;62;65,57;61;69\n" + "2009-06-11,59;61;63,56;60;68\n" + "2009-06-12,62;71;80,56;60;67\n" + "2009-06-13,64;68;71,56;62;70\n" + "2009-06-14,61;67;72,57;62;69\n" + "2009-06-15,58;65;71,58;61;68\n" + "2009-06-16,56;62;68,57;59;66\n" + "2009-06-17,57;63;69,57;60;66\n" + "2009-06-18,62;64;66,56;63;75\n" + "2009-06-19,63;71;79,55;62;71\n" + "2009-06-20,63;68;72,53;59;69\n" + "2009-06-21,63;71;78,53;60;70\n" + "2009-06-22,67;71;75,54;62;75\n" + "2009-06-23,67;74;80,54;63;77\n" + "2009-06-24,66;71;75,54;59;69\n" + "2009-06-25,65;73;81,54;58;66\n" + "2009-06-26,65;75;85,53;60;70\n" + "2009-06-27,66;74;81,53;68;85\n" + "2009-06-28,68;75;82,60;68;78\n" + "2009-06-29,68;76;83,56;61;68\n" + "2009-06-30,67;76;84,56;61;68\n" + "2009-07-01,67;73;79,56;61;68\n" + "2009-07-02,66;72;77,56;60;66\n" + "2009-07-03,68;74;80,55;58;65\n" + "2009-07-04,68;75;81,54;60;67\n" + "2009-07-05,64;72;80,55;57;63\n" + "2009-07-06,68;77;85,54;60;69\n" + "2009-07-07,62;73;83,56;58;62\n" + "2009-07-08,61;69;77,52;62;71\n" + "2009-07-09,62;68;73,53;59;68\n" + "2009-07-10,63;69;75,54;59;66\n" + "2009-07-11,65;71;77,53;58;68\n" + "2009-07-12,66;75;83,53;60;70\n" + "2009-07-13,65;72;79,53;66;84\n" + "2009-07-14,66;74;81,58;68;86\n" + "2009-07-15,67;75;83,53;59;66\n" + "2009-07-16,73;79;85,52;57;69\n" + "2009-07-17,69;79;88,52;58;69\n" + "2009-07-18,71;78;84,54;60;74\n" + "2009-07-19,68;76;83,54;61;73\n" + "2009-07-20,68;75;82,54;57;62\n" + "2009-07-21,65;68;71,53;56;61\n" + "2009-07-22,67;75;82,52;56;60\n" + "2009-07-23,64;72;80,52;55;61\n" + "2009-07-24,65;73;80,53;56;62\n" + "2009-07-25,67;77;86,52;56;67\n" + "2009-07-26,71;79;86,53;57;72\n" + "2009-07-27,71;78;85,52;57;70\n" + "2009-07-28,73;80;87,55;58;63\n" + "2009-07-29,73;78;83,58;58;58\n" + "2009-07-30,74;81;87,56;60;65\n" + "2009-07-31,69;79;89,55;58;64\n" + "2009-08-01,69;77;85,55;58;64\n" + "2009-08-02,73;76;78,56;60;66\n" + "2009-08-03,72;79;85,56;62;70\n" + "2009-08-04,72;79;86,57;64;73\n" + "2009-08-05,74;82;89,58;62;72\n" + "2009-08-06,68;74;79,59;63;71\n" + "2009-08-07,67;74;80,58;63;71\n" + "2009-08-08,65;72;78,58;64;76\n" + "2009-08-09,71;76;81,57;66;82\n" + "2009-08-10,71;83;94,59;66;82\n" + "2009-08-11,77;85;92,58;61;66\n" + "2009-08-12,71;76;80,57;66;79\n" + "2009-08-13,70;72;74,57;63;74\n" + "2009-08-14,69;77;85,56;61;70\n" + "2009-08-15,73;81;89,55;62;76\n" + "2009-08-16,75;83;91,55;62;73\n" + "2009-08-17,77;85;93,55;58;64\n" + "2009-08-18,73;82;91,55;59;65\n" + "2009-08-19,75;83;91,57;59;65\n" + "2009-08-20,76;83;89,56;60;67\n" + "2009-08-21,75;84;92,56;62;78\n" + "2009-08-22,72;79;85,56;59;64\n" + "2009-08-23,75;81;86,55;58;65\n" + "2009-08-24,72;78;84,54;58;64\n" + "2009-08-25,71;79;86,55;60;70\n" + "2009-08-26,74;81;87,55;59;68\n" + "2009-08-27,68;74;80,53;66;87\n" + "2009-08-28,66;70;73,63;76;97\n" + "2009-08-29,67;70;73,58;73;87\n" + "2009-08-30,66;75;83,55;57;62\n" + "2009-08-31,64;68;72,54;59;69\n" + "2009-09-01,59;67;74,58;63;72\n" + "2009-09-02,61;69;77,61;70;90\n" + "2009-09-03,64;71;78,58;67;79\n" + "2009-09-04,65;74;83,54;59;66\n" + "2009-09-05,71;78;85,56;62;70\n" + "2009-09-06,64;69;74,57;62;69\n" + "2009-09-07,61;67;72,56;63;73\n" + "2009-09-08,65;70;75,55;61;71\n" + "2009-09-09,65;71;76,54;58;66\n" + "2009-09-10,61;66;71,55;65;82\n" + "2009-09-11,58;64;69,54;62;76\n" + "2009-09-12,65;67;68,58;60;64\n" + "2009-09-13,67;74;81,59;64;73\n" + "2009-09-14,65;72;79,58;63;69\n" + "2009-09-15,68;75;81,57;63;73\n" + "2009-09-16,63;67;71,58;63;72\n" + "2009-09-17,57;61;65,59;67;79\n" + "2009-09-18,58;68;77,62;70;89\n" + "2009-09-19,55;63;71,58;62;73\n" + "2009-09-20,57;66;75,57;63;76\n" + "2009-09-21,61;69;76,56;61;71\n" + "2009-09-22,66;71;75,55;62;77\n" + "2009-09-23,69;76;82,56;58;65\n" + "2009-09-24,68;75;82,55;58;65\n" + "2009-09-25,59;64;69,55;61;73\n" + "2009-09-26,55;61;66,53;66;87\n" + "2009-09-27,60;65;69,59;68;84\n" + "2009-09-28,60;67;74,56;59;63\n" + "2009-09-29,58;63;68,55;58;64\n" + "2009-09-30,51;56;60,51;58;70\n" + "2009-10-01,51;54;57,52;64;79\n" + "2009-10-02,49;58;66,55;63;77\n" + "2009-10-03,65;68;71,52;57;63\n" + "2009-10-04,59;68;76,50;55;62\n" + "2009-10-05,55;62;68,50;57;68\n" + "2009-10-06,54;61;68,50;60;74\n" + "2009-10-07,59;65;71,53;58;68\n" + "2009-10-08,55;60;64,54;57;62\n" + "2009-10-09,61;66;71,52;56;63\n" + "2009-10-10,54;61;68,51;54;59\n" + "2009-10-11,50;58;65,51;54;59\n" + "2009-10-12,46;51;55,56;60;66\n" + "2009-10-13,47;56;64,58;61;64\n" + "2009-10-14,44;49;53,63;66;76\n" + "2009-10-15,39;44;48,64;67;76\n" + "2009-10-16,39;44;49,59;67;79\n" + "2009-10-17,44;47;50,56;64;74\n" + "2009-10-18,43;45;47,56;59;63\n" + "2009-10-19,41;50;58,57;60;68\n" + "2009-10-20,47;58;68,56;59;67\n" + "2009-10-21,53;61;69,54;60;71\n" + "2009-10-22,55;66;76,57;62;72\n" + "2009-10-23,51;59;66,55;63;75\n" + "2009-10-24,53;60;67,55;61;74\n" + "2009-10-25,53;59;65,54;64;81\n" + "2009-10-26,49;55;61,57;62;71\n" + "2009-10-27,50;54;58,54;59;69\n" + "2009-10-28,51;55;58,53;58;67\n" + "2009-10-29,49;54;58,51;58;67\n" + "2009-10-30,52;56;60,52;59;74\n" + "2009-10-31,56;64;71,53;58;72\n" + "2009-11-01,50;55;60,52;63;77\n" + "2009-11-02,48;52;55,56;66;81\n" + "2009-11-03,46;54;62,56;66;80\n" + "2009-11-04,44;48;52,54;58;65\n" + "2009-11-05,44;49;53,57;61;68\n" + "2009-11-06,41;46;50,53;59;63\n" + "2009-11-07,38;46;53,49;56;65\n" + "2009-11-08,48;60;71,50;57;68\n" + "2009-11-09,48;58;67,49;56;64\n" + "2009-11-10,56;60;64,54;58;66\n" + "2009-11-11,49;54;58,54;58;65\n" + "2009-11-12,48;51;53,51;56;62\n" + "2009-11-13,49;52;55,48;53;60\n" + "2009-11-14,52;55;58,50;54;61\n" + "2009-11-15,54;59;63,44;53;65\n" + "2009-11-16,49;54;59,47;54;66\n" + "2009-11-17,46;51;55,45;54;64\n" + "2009-11-18,43;48;53,48;53;62\n" + "2009-11-19,48;55;61,44;53;63\n" + "2009-11-20,50;56;61,46;52;58\n" + "2009-11-21,47;52;57,47;51;59\n" + "2009-11-22,46;51;56,49;53;59\n" + "2009-11-23,45;53;60,44;54;66\n" + "2009-11-24,49;53;57,48;58;69\n" + "2009-11-25,49;51;53,\n" + "2009-11-26,49;52;54,50;55;67\n" + "2009-11-27,44;47;50,51;54;58\n" + "2009-11-28,44;48;51,\n" + "2009-11-29,42;51;59,49;60;71\n" + "2009-11-30,41;49;57,48;55;64\n" + "2009-12-01,38;43;48,46;52;64\n" + "2009-12-02,40;49;57,45;48;55\n" + "2009-12-03,51;59;66,46;50;58\n" + "2009-12-04,46;49;52,44;48;57\n" + "2009-12-05,37;42;47,\n"; } function stockData() { return "Month,Nominal,Real\n" + "1913-01-15,59.740;61.330;64.880,609.591836734694;625.816326530612;662.04081632653\n" + "1913-02-15,57.670;58.840;61.140,588.469387755102;600.408163265306;623.877551020408\n" + "1913-03-15,57.320;59.280;59.840,584.897959183673;604.897959183673;610.612244897959\n" + "1913-04-15,57.430;57.540;60.940,586.020408163265;587.142857142857;621.836734693877\n" + "1913-05-15,57.420;57.420;58.570,591.958762886598;591.958762886598;603.814432989691\n" + "1913-06-15,52.830;54.860;56.610,539.081632653061;559.795918367347;577.65306122449\n" + "1913-07-15,55.110;57.490;57.920,556.666666666667;580.707070707071;585.050505050505\n" + "1913-08-15,57.290;59.930;59.930,578.686868686869;605.353535353535;605.353535353535\n" + "1913-09-15,58.800;58.880;60.830,588;588.8;608.3\n" + "1913-10-15,56.470;57.360;59.650,564.7;573.6;596.5\n" + "1913-11-15,55.630;55.830;57.220,550.792079207921;552.772277227723;566.534653465347\n" + "1913-12-15,55.140;57.710;57.760,551.4;577.1;577.6\n" + "1914-01-15,57.570;60.660;60.710,575.7;606.6;607.1\n" + "1914-02-15,59.560;60.070;60.940,601.616161616162;606.767676767677;615.555555555556\n" + "1914-03-15,59.430;60.360;61.120,600.30303030303;609.69696969697;617.373737373737\n" + "1914-04-15,56.790;57.960;60.410,579.489795918367;591.428571428571;616.428571428571\n" + "1914-05-15,57.990;59.760;59.820,585.757575757576;603.636363636364;604.242424242424\n" + "1914-06-15,58.090;59.090;59.950,586.767676767677;596.868686868687;605.555555555556\n" + "1914-07-15,52.320;52.320;59.920,523.2;523.2;599.2\n" + "1914-12-15,53.170;54.580;56.760,526.435643564356;540.39603960396;561.980198019802\n" + "1915-01-15,55.400;56.540;58.510,548.514851485149;559.80198019802;579.306930693069\n" + "1915-02-15,54.220;55.020;57.830,542.2;550.2;578.3\n" + "1915-03-15,55.290;60.830;61.300,558.484848484848;614.444444444444;619.191919191919\n" + "1915-04-15,61.050;71.780;71.780,610.5;717.8;717.8\n" + "1915-05-15,60.380;65.010;69.580,597.821782178218;643.663366336634;688.910891089109\n" + "1915-06-15,64.860;70.060;71.900,642.178217821782;693.663366336634;711.881188118812\n" + "1915-07-15,67.880;75.530;75.790,672.079207920792;747.821782178218;750.396039603961\n" + "1915-08-15,76.460;81.200;81.880,757.029702970297;803.960396039604;810.693069306931\n" + "1915-09-15,80.700;90.580;90.580,799.009900990099;896.831683168317;896.831683168317\n" + "1915-10-15,88.230;95.340;96.460,865;934.705882352941;945.686274509804\n" + "1915-11-15,91.080;96.710;97.560,884.271844660194;938.932038834951;947.184466019417\n" + "1915-12-15,94.780;99.150;99.210,920.194174757282;962.621359223301;963.203883495146\n" + "1916-01-15,90.580;90.580;98.810,870.961538461538;870.961538461538;950.096153846154\n" + "1916-02-15,90.890;91.030;96.150,873.942307692308;875.288461538461;924.519230769231\n" + "1916-03-15,90.520;93.250;96.080,862.095238095238;888.095238095238;915.047619047619\n" + "1916-04-15,87.000;89.650;94.460,820.754716981132;845.754716981132;891.132075471698\n" + "1916-05-15,87.710;91.800;92.620,819.719626168224;857.943925233645;865.607476635514\n" + "1916-06-15,87.680;89.580;93.610,811.851851851852;829.444444444444;866.759259259259\n" + "1916-07-15,86.420;89.250;90.530,800.185185185185;826.388888888889;838.240740740741\n" + "1916-08-15,88.150;92.250;93.830,808.715596330275;846.330275229358;860.825688073395\n" + "1916-09-15,91.190;103.730;103.730,821.531531531532;934.504504504505;934.504504504505\n" + "1916-10-15,98.940;104.610;105.280,875.575221238938;925.75221238938;931.681415929203\n" + "1916-11-15,105.630;105.970;110.150,918.521739130435;921.478260869565;957.826086956522\n" + "1916-12-15,90.160;95.000;106.760,777.241379310345;818.965517241379;920.344827586207\n" + "1917-01-15,95.430;95.430;99.180,815.641025641026;815.641025641026;847.692307692308\n" + "1917-02-15,87.010;91.560;94.910,725.083333333333;763;790.916666666667\n" + "1917-03-15,91.100;96.720;98.200,759.166666666667;806;818.333333333333\n" + "1917-04-15,90.660;93.230;97.060,719.52380952381;739.920634920635;770.31746031746\n" + "1917-05-15,89.080;97.380;97.410,695.9375;760.78125;761.015625\n" + "1917-06-15,94.780;95.380;98.580,729.076923076923;733.692307692308;758.307692307692\n" + "1917-07-15,90.480;91.750;95.310,706.875;716.796875;744.609375\n" + "1917-08-15,83.400;83.400;93.850,641.538461538462;641.538461538462;721.923076923077\n" + "1917-09-15,81.200;83.460;86.020,610.526315789474;627.518796992481;646.766917293233\n" + "1917-10-15,74.500;74.500;83.580,551.851851851852;551.851851851852;619.111111111111\n" + "1917-11-15,68.580;72.650;74.230,508;538.148148148148;549.851851851852\n" + "1917-12-15,65.950;74.380;74.380,481.386861313869;542.919708029197;542.919708029197\n" + "1918-01-15,73.380;79.800;79.800,524.142857142857;570;570\n" + "1918-02-15,77.780;80.390;82.080,551.631205673759;570.141843971631;582.127659574468\n" + "1918-03-15,76.410;76.410;79.930,545.785714285714;545.785714285714;570.928571428571\n" + "1918-04-15,75.580;77.510;79.420,532.253521126761;545.845070422535;559.295774647887\n" + "1918-05-15,78.080;78.080;84.040,538.48275862069;538.48275862069;579.586206896552\n" + "1918-06-15,78.530;82.580;83.020,534.21768707483;561.768707482993;564.761904761905\n" + "1918-07-15,80.510;81.230;82.960,533.17880794702;537.94701986755;549.403973509934\n" + "1918-08-15,80.710;82.460;83.180,524.090909090909;535.454545454545;540.12987012987\n" + "1918-09-15,80.290;84.680;84.680,511.40127388535;539.363057324841;539.363057324841\n" + "1918-10-15,83.360;85.510;89.070,521;534.4375;556.6875\n" + "1918-11-15,79.870;80.930;87.660,490;496.503067484663;537.791411042945\n" + "1918-12-15,80.440;82.200;84.500,487.515151515152;498.181818181818;512.121212121212\n" + "1919-01-15,79.880;80.610;83.350,484.121212121212;488.545454545455;505.151515151515\n" + "1919-02-15,79.350;84.810;85.680,489.814814814815;523.518518518518;528.888888888889\n" + "1919-03-15,84.240;88.850;89.050,513.658536585366;541.768292682927;542.987804878049\n" + "1919-04-15,88.840;92.880;93.510,531.976047904192;556.167664670659;559.940119760479\n" + "1919-05-15,93.260;105.500;105.500,551.834319526627;624.260355029586;624.260355029586\n" + "1919-06-15,99.560;106.980;107.550,589.112426035503;633.01775147929;636.390532544379\n" + "1919-07-15,107.160;107.160;112.230,615.862068965517;615.862068965517;645\n" + "1919-08-15,98.460;104.750;107.990,556.271186440678;591.80790960452;610.112994350283\n" + "1919-09-15,105.840;111.420;111.420,594.606741573034;625.955056179775;625.955056179775\n" + "1919-10-15,108.900;118.920;118.920,601.657458563536;657.016574585635;657.016574585635\n" + "1919-11-15,103.720;103.720;119.620,560.648648648649;560.648648648649;646.594594594595\n" + "1919-12-15,103.550;107.230;107.970,547.883597883598;567.354497354497;571.269841269841\n" + "1920-01-15,101.900;104.210;108.850,527.979274611399;539.948186528497;563.989637305699\n" + "1920-02-15,89.980;91.180;103.010,461.435897435897;467.589743589744;528.25641025641\n" + "1920-03-15,91.680;102.810;104.170,465.380710659899;521.878172588833;528.781725888325\n" + "1920-04-15,93.160;93.540;105.650,458.916256157635;460.788177339901;520.443349753695\n" + "1920-05-15,87.360;91.810;94.410,424.077669902913;445.679611650485;458.300970873786\n" + "1920-06-15,90.160;90.760;93.060,431.387559808612;434.258373205742;445.263157894737\n" + "1920-07-15,86.860;86.860;94.510,417.596153846154;417.596153846154;454.375\n" + "1920-08-15,83.200;86.160;87.290,409.852216748768;424.433497536946;430\n" + "1920-09-15,82.950;82.950;89.950,414.75;414.75;449.75\n" + "1920-10-15,84.000;85.080;85.730,422.110552763819;427.537688442211;430.804020100503\n" + "1920-11-15,73.120;76.040;85.480,369.292929292929;384.040404040404;431.717171717172\n" + "1920-12-15,66.750;71.950;77.300,344.072164948454;370.876288659794;398.453608247423\n" + "1921-01-15,72.670;76.130;76.760,382.473684210526;400.684210526316;404\n" + "1921-02-15,74.340;74.980;77.140,404.021739130435;407.5;419.239130434783\n" + "1921-03-15,72.250;75.760;77.780,394.808743169399;413.989071038251;425.027322404372\n" + "1921-04-15,75.060;78.570;78.860,414.696132596685;434.088397790055;435.690607734807\n" + "1921-05-15,73.440;73.440;80.030,414.915254237288;414.915254237288;452.146892655367\n" + "1921-06-15,64.900;68.450;73.510,368.75;388.920454545455;417.670454545455\n" + "1921-07-15,67.250;68.370;69.860,379.943502824859;386.271186440678;394.689265536723\n" + "1921-08-15,63.900;67.110;69.950,361.016949152542;379.152542372881;395.197740112994\n" + "1921-09-15,66.830;71.080;71.720,381.885714285714;406.171428571429;409.828571428571\n" + "1921-10-15,69.460;73.210;73.800,396.914285714286;418.342857142857;421.714285714286\n" + "1921-11-15,73.440;77.300;78.010,422.068965517241;444.252873563218;448.333333333333\n" + "1921-12-15,78.120;80.800;81.500,451.560693641618;467.052023121387;471.098265895954\n" + "1922-01-15,78.590;81.300;82.950,465.029585798817;481.065088757396;490.828402366864\n" + "1922-02-15,81.680;85.460;85.810,483.313609467456;505.680473372781;507.751479289941\n" + "1922-03-15,85.330;89.050;89.050,510.958083832335;533.233532934132;533.233532934132\n" + "1922-04-15,89.300;91.930;93.210,534.730538922156;550.479041916168;558.14371257485\n" + "1922-05-15,91.500;95.630;96.410,547.904191616767;572.634730538922;577.305389221557\n" + "1922-06-15,90.730;92.930;96.360,543.293413173653;556.467065868264;577.005988023952\n" + "1922-07-15,92.920;97.050;97.050,553.095238095238;577.678571428571;577.678571428571\n" + "1922-08-15,96.210;100.780;100.780,579.578313253012;607.10843373494;607.10843373494\n" + "1922-09-15,96.580;97.120;102.050,581.807228915663;585.060240963855;614.759036144578\n" + "1922-10-15,96.110;96.110;102.760,575.508982035928;575.508982035928;615.329341317365\n" + "1922-11-15,92.030;94.650;99.530,547.797619047619;563.392857142857;592.440476190476\n" + "1922-12-15,95.030;98.170;99.220,562.307692307692;580.887573964497;587.100591715976\n" + "1923-01-15,96.960;97.430;99.420,577.142857142857;579.940476190476;591.785714285714\n" + "1923-02-15,97.710;103.900;103.900,581.607142857143;618.452380952381;618.452380952381\n" + "1923-03-15,102.360;102.770;105.380,609.285714285714;611.72619047619;627.261904761905\n" + "1923-04-15,98.380;98.380;102.700,582.130177514793;582.130177514793;607.692307692308\n" + "1923-05-15,92.770;97.530;98.190,548.934911242604;577.100591715976;581.005917159763\n" + "1923-06-15,88.400;88.400;97.240,520;520;572\n" + "1923-07-15,86.910;86.910;91.720,505.290697674419;505.290697674419;533.255813953488\n" + "1923-08-15,87.960;93.460;93.700,514.385964912281;546.549707602339;547.953216374269\n" + "1923-09-15,87.940;87.970;93.610,511.279069767442;511.453488372093;544.244186046512\n" + "1923-10-15,85.910;88.530;90.450,496.589595375722;511.734104046243;522.832369942196\n" + "1923-11-15,88.410;92.340;92.880,511.040462427746;533.757225433526;536.878612716763\n" + "1923-12-15,92.640;95.520;95.610,535.491329479769;552.138728323699;552.658959537572\n" + "1924-01-15,94.880;100.660;100.660,548.439306358381;581.849710982659;581.849710982659\n" + "1924-02-15,96.330;97.220;101.310,560.058139534884;565.232558139535;589.011627906977\n" + "1924-03-15,92.540;93.010;98.860,541.169590643275;543.918128654971;578.12865497076\n" + "1924-04-15,89.180;90.630;94.690,524.588235294118;533.117647058823;557\n" + "1924-05-15,88.330;89.900;92.470,519.588235294118;528.823529411765;543.941176470588\n" + "1924-06-15,89.180;96.370;96.370,524.588235294118;566.882352941176;566.882352941176\n" + "1924-07-15,96.380;102.140;102.140,563.625730994152;597.309941520468;597.309941520468\n" + "1924-08-15,101.510;104.140;105.570,597.117647058824;612.588235294118;621\n" + "1924-09-15,101.070;103.160;104.950,591.052631578947;603.27485380117;613.74269005848\n" + "1924-10-15,99.180;104.060;104.080,576.627906976744;605;605.116279069768\n" + "1924-11-15,103.890;111.100;111.100,604.011627906977;645.93023255814;645.93023255814\n" + "1924-12-15,110.440;120.510;120.510,638.381502890173;696.589595375723;696.589595375723\n" + "1925-01-15,119.460;123.490;123.600,690.520231213873;713.815028901734;714.450867052023\n" + "1925-02-15,117.960;122.240;122.860,685.813953488372;710.697674418605;714.302325581395\n" + "1925-03-15,115.000;116.750;125.680,664.739884393064;674.85549132948;726.473988439306\n" + "1925-04-15,117.400;120.010;121.540,682.558139534884;697.732558139535;706.627906976744\n" + "1925-05-15,121.100;129.950;129.950,700;751.156069364162;751.156069364162\n" + "1925-06-15,126.750;131.010;131.010,724.285714285714;748.628571428571;748.628571428571\n" + "1925-07-15,131.330;133.810;136.500,741.977401129944;755.988700564972;771.186440677966\n" + "1925-08-15,135.710;141.180;143.180,766.723163841808;797.627118644068;808.926553672316\n" + "1925-09-15,137.220;143.460;147.160,775.254237288136;810.508474576271;831.412429378531\n" + "1925-10-15,144.770;155.650;155.650,817.909604519774;879.378531073446;879.378531073446\n" + "1925-11-15,148.180;151.080;159.390,823.222222222222;839.333333333333;885.5\n" + "1925-12-15,152.110;156.660;156.870,849.776536312849;875.195530726257;876.368715083799\n" + "1926-01-15,153.200;157.350;158.930,855.865921787709;879.050279329609;887.877094972067\n" + "1926-02-15,154.680;154.680;162.310,864.134078212291;864.134078212291;906.759776536313\n" + "1926-03-15,135.200;140.460;153.130,759.550561797753;789.101123595506;860.280898876404\n" + "1926-04-15,136.270;143.710;144.550,761.284916201117;802.849162011173;807.541899441341\n" + "1926-05-15,137.160;143.430;143.430,770.561797752809;805.786516853933;805.786516853933\n" + "1926-06-15,142.300;153.040;154.030,803.954802259887;864.632768361582;870.225988700565\n" + "1926-07-15,153.010;160.180;160.580,874.342857142857;915.314285714286;917.6\n" + "1926-08-15,160.410;162.510;166.140,921.896551724138;933.965517241379;954.827586206897\n" + "1926-09-15,156.260;158.190;166.100,892.914285714286;903.942857142857;949.142857142857\n" + "1926-10-15,145.660;150.760;159.690,827.613636363636;856.590909090909;907.329545454545\n" + "1926-11-15,150.510;156.550;157.370,850.338983050847;884.463276836158;889.09604519774\n" + "1926-12-15,156.650;157.200;160.750,885.028248587571;888.135593220339;908.19209039548\n" + "1927-01-15,152.730;156.410;156.560,872.742857142857;893.771428571429;894.628571428571\n" + "1927-02-15,154.310;161.960;161.960,886.83908045977;930.80459770115;930.80459770115\n" + "1927-03-15,158.410;160.080;161.780,915.664739884393;925.317919075144;935.14450867052\n" + "1927-04-15,160.710;164.640;167.360,928.959537572254;951.676300578035;967.398843930636\n" + "1927-05-15,164.550;172.960;172.960,945.689655172414;994.022988505747;994.022988505747\n" + "1927-06-15,165.730;166.230;171.980,941.647727272727;944.488636363636;977.159090909091\n" + "1927-07-15,168.060;181.400;181.400,971.445086705202;1048.5549132948;1048.5549132948\n" + "1927-08-15,177.130;189.790;190.630,1029.82558139535;1103.43023255814;1108.31395348837\n" + "1927-09-15,191.560;197.590;198.970,1107.28323699422;1142.1387283237;1150.11560693642\n" + "1927-10-15,181.430;181.730;199.780,1042.70114942529;1044.42528735632;1148.16091954023\n" + "1927-11-15,181.650;198.210;198.210,1050;1145.7225433526;1145.7225433526\n" + "1927-12-15,193.580;200.700;200.930,1118.95953757225;1160.11560693642;1161.4450867052\n" + "1928-01-15,194.500;198.590;203.350,1124.2774566474;1147.91907514451;1175.43352601156\n" + "1928-02-15,191.330;194.780;199.350,1118.88888888889;1139.06432748538;1165.78947368421\n" + "1928-03-15,194.530;214.450;214.450,1137.60233918129;1254.09356725146;1254.09356725146\n" + "1928-04-15,207.940;211.630;216.930,1216.02339181287;1237.60233918129;1268.59649122807\n" + "1928-05-15,211.730;219.810;220.880,1230.98837209302;1277.96511627907;1284.18604651163\n" + "1928-06-15,201.960;210.370;220.270,1181.05263157895;1230.23391812865;1288.12865497076\n" + "1928-07-15,205.100;216.000;216.620,1199.41520467836;1263.15789473684;1266.78362573099\n" + "1928-08-15,214.080;240.410;240.410,1251.9298245614;1405.90643274854;1405.90643274854\n" + "1928-09-15,236.860;237.380;241.720,1369.13294797688;1372.1387283237;1397.22543352601\n" + "1928-10-15,237.750;252.160;257.130,1382.26744186047;1466.04651162791;1494.94186046512\n" + "1928-11-15,254.380;293.380;295.620,1478.95348837209;1705.6976744186;1718.72093023256\n" + "1928-12-15,263.950;300.000;300.000,1543.56725146199;1754.38596491228;1754.38596491228\n" + "1929-01-15,296.980;317.510;317.510,1736.72514619883;1856.78362573099;1856.78362573099\n" + "1929-02-15,300.410;317.410;322.060,1756.78362573099;1856.19883040936;1883.3918128655\n" + "1929-03-15,296.510;308.850;321.180,1744.17647058824;1816.76470588235;1889.29411764706\n" + "1929-04-15,299.130;319.290;319.290,1770;1889.2899408284;1889.2899408284\n" + "1929-05-15,293.420;297.410;326.160,1726;1749.47058823529;1918.58823529412\n" + "1929-06-15,303.270;331.650;331.650,1773.50877192982;1939.47368421053;1939.47368421053\n" + "1929-07-15,335.220;347.700;347.700,1937.68786127168;2009.82658959538;2009.82658959538\n" + "1929-08-15,337.990;380.330;380.330,1953.69942196532;2198.43930635838;2198.43930635838\n" + "1929-09-15,343.450;343.450;381.170,1985.26011560694;1985.26011560694;2203.29479768786\n" + "1929-10-15,230.070;273.510;352.860,1329.88439306358;1580.98265895954;2039.65317919075\n" + "1929-11-15,198.690;238.950;257.680,1148.49710982659;1381.21387283237;1489.47976878613\n" + "1929-12-15,230.890;248.480;262.200,1342.38372093023;1444.6511627907;1524.41860465116\n" + "1930-01-15,244.200;267.140;267.140,1428.0701754386;1562.22222222222;1562.22222222222\n" + "1930-02-15,262.470;271.110;272.270,1543.94117647059;1594.76470588235;1601.58823529412\n" + "1930-03-15,270.590;286.100;286.100,1601.12426035503;1692.89940828402;1692.89940828402\n" + "1930-04-15,276.940;279.230;294.070,1629.05882352941;1642.52941176471;1729.82352941176\n" + "1930-05-15,259.680;275.070;275.070,1536.56804733728;1627.63313609467;1627.63313609467\n" + "1930-06-15,211.840;226.340;274.450,1260.95238095238;1347.2619047619;1633.63095238095\n" + "1930-07-15,218.330;233.990;240.810,1315.24096385542;1409.57831325301;1450.66265060241\n" + "1930-08-15,217.240;240.420;240.420,1316.60606060606;1457.09090909091;1457.09090909091\n" + "1930-09-15,204.900;204.900;245.090,1234.33734939759;1234.33734939759;1476.44578313253\n" + "1930-10-15,183.350;183.350;214.180,1111.21212121212;1111.21212121212;1298.06060606061\n" + "1930-11-15,171.600;180.910;190.300,1046.34146341463;1103.10975609756;1160.36585365854\n" + "1930-12-15,157.510;164.580;186.820,978.32298136646;1022.23602484472;1160.37267080745\n" + "1931-01-15,161.450;169.340;173.040,1015.40880503145;1065.03144654088;1088.30188679245\n" + "1931-02-15,168.710;190.340;194.360,1074.58598726115;1212.35668789809;1237.96178343949\n" + "1931-03-15,172.360;172.360;187.720,1104.87179487179;1104.87179487179;1203.33333333333\n" + "1931-04-15,143.610;151.190;171.070,926.516129032258;975.41935483871;1103.67741935484\n" + "1931-05-15,128.460;128.460;154.410,839.607843137255;839.607843137255;1009.21568627451\n" + "1931-06-15,121.700;150.180;154.040,805.960264900662;994.569536423841;1020.13245033113\n" + "1931-07-15,135.390;135.390;155.260,896.622516556291;896.622516556291;1028.2119205298\n" + "1931-08-15,133.770;139.410;144.150,885.894039735099;923.245033112583;954.635761589404\n" + "1931-09-15,96.610;96.610;140.130,644.066666666667;644.066666666667;934.2\n" + "1931-10-15,86.480;103.970;108.880,580.402684563758;697.785234899329;730.738255033557\n" + "1931-11-15,91.550;93.870;116.790,622.789115646259;638.571428571429;794.489795918367\n" + "1931-12-15,73.790;77.900;91.170,505.41095890411;533.561643835617;624.452054794521\n" + "1932-01-15,71.240;76.550;85.880,498.181818181818;535.314685314685;600.55944055944\n" + "1932-02-15,71.800;81.440;85.980,509.219858156028;577.58865248227;609.787234042553\n" + "1932-03-15,73.280;73.280;88.780,523.428571428571;523.428571428571;634.142857142857\n" + "1932-04-15,55.930;55.930;72.180,402.374100719424;402.374100719424;519.280575539568\n" + "1932-05-15,44.740;44.740;59.010,326.569343065693;326.569343065693;430.729927007299\n" + "1932-06-15,42.840;42.840;50.620,315;315;372.205882352941\n" + "1932-07-15,41.220;53.890;53.890,303.088235294118;396.25;396.25\n" + "1932-08-15,53.160;73.160;75.220,393.777777777778;541.925925925926;557.185185185185\n" + "1932-09-15,65.060;71.560;79.930,485.522388059701;534.029850746269;596.492537313433\n" + "1932-10-15,58.470;61.900;71.210,439.624060150376;465.413533834586;535.413533834586\n" + "1932-11-15,56.350;56.350;68.030,426.893939393939;426.893939393939;515.378787878788\n" + "1932-12-15,55.910;60.260;61.930,426.793893129771;460;472.748091603053\n" + "1933-01-15,59.290;60.900;64.350,459.612403100775;472.093023255814;498.837209302326\n" + "1933-02-15,50.160;51.390;60.090,394.96062992126;404.645669291339;473.149606299213\n" + "1933-03-15,52.540;55.400;62.950,416.984126984127;439.68253968254;499.603174603175\n" + "1933-04-15,55.690;73.100;73.690,441.984126984127;580.15873015873;584.84126984127\n" + "1933-05-15,76.630;88.110;90.020,608.174603174603;699.285714285714;714.444444444444\n" + "1933-06-15,88.870;98.140;98.740,699.763779527559;772.755905511811;777.48031496063\n" + "1933-07-15,88.710;90.770;108.670,677.175572519084;692.900763358779;829.541984732824\n" + "1933-08-15,92.550;102.410;105.070,701.136363636364;775.833333333333;795.984848484848\n" + "1933-09-15,93.180;94.240;105.740,705.909090909091;713.939393939394;801.060606060606\n" + "1933-10-15,84.380;88.160;99.720,639.242424242424;667.878787878788;755.454545454545\n" + "1933-11-15,89.620;98.140;101.280,678.939393939394;743.484848484848;767.272727272727\n" + "1933-12-15,95.280;98.670;102.040,721.818181818182;747.5;773.030303030303\n" + "1934-01-15,96.730;107.220;108.990,732.80303030303;812.272727272727;825.681818181818\n" + "1934-02-15,103.120;103.460;110.740,775.338345864662;777.894736842105;832.631578947368\n" + "1934-03-15,98.760;100.310;105.790,742.556390977444;754.210526315789;795.413533834586\n" + "1934-04-15,100.490;100.490;106.550,755.563909774436;755.563909774436;801.127819548872\n" + "1934-05-15,91.810;94.000;100.620,690.300751879699;706.766917293233;756.541353383459\n" + "1934-06-15,91.790;95.750;100.420,685;714.55223880597;749.402985074627\n" + "1934-07-15,85.510;88.050;98.820,638.134328358209;657.089552238806;737.462686567164\n" + "1934-08-15,87.470;92.860;95.480,652.761194029851;692.985074626866;712.537313432836\n" + "1934-09-15,86.690;92.490;93.650,637.426470588235;680.073529411765;688.602941176471\n" + "1934-10-15,90.410;93.360;95.600,669.703703703704;691.555555555556;708.148148148148\n" + "1934-11-15,93.460;102.940;103.080,692.296296296296;762.518518518518;763.555555555556\n" + "1934-12-15,99.590;104.040;104.040,743.208955223881;776.417910447761;776.417910447761\n" + "1935-01-15,100.490;101.690;105.880,738.897058823529;747.720588235294;778.529411764706\n" + "1935-02-15,100.230;102.380;107.170,731.605839416059;747.299270072993;782.262773722628\n" + "1935-03-15,96.710;100.780;103.270,705.912408759124;735.620437956204;753.795620437956\n" + "1935-04-15,100.390;109.450;110.470,727.463768115942;793.115942028986;800.507246376812\n" + "1935-05-15,108.710;110.640;116.810,787.753623188406;801.739130434783;846.449275362319\n" + "1935-06-15,111.450;118.360;120.040,813.503649635037;863.941605839416;876.204379562044\n" + "1935-07-15,118.690;126.230;126.560,866.350364963504;921.386861313869;923.795620437956\n" + "1935-08-15,124.930;127.350;128.990,911.897810218978;929.562043795621;941.532846715329\n" + "1935-09-15,127.270;131.920;134.110,928.978102189781;962.919708029197;978.905109489051\n" + "1935-10-15,128.060;139.740;140.780,934.744525547445;1020;1027.59124087591\n" + "1935-11-15,141.070;142.340;148.440,1022.24637681159;1031.44927536232;1075.65217391304\n" + "1935-12-15,138.940;144.130;144.130,1006.8115942029;1044.42028985507;1044.42028985507\n" + "1936-01-15,143.110;149.490;149.490,1037.02898550725;1083.26086956522;1083.26086956522\n" + "1936-02-15,149.810;152.530;154.430,1085.57971014493;1105.28985507246;1119.05797101449\n" + "1936-03-15,150.420;156.340;158.750,1097.95620437956;1141.16788321168;1158.75912408759\n" + "1936-04-15,143.650;145.670;161.990,1048.5401459854;1063.28467153285;1182.40875912409\n" + "1936-05-15,146.700;152.640;152.640,1070.80291970803;1114.16058394161;1114.16058394161\n" + "1936-06-15,149.260;157.690;160.660,1081.59420289855;1142.68115942029;1164.20289855072\n" + "1936-07-15,155.600;164.860;167.010,1119.42446043165;1186.04316546763;1201.51079136691\n" + "1936-08-15,160.800;166.290;169.050,1148.57142857143;1187.78571428571;1207.5\n" + "1936-09-15,165.160;167.820;169.550,1179.71428571429;1198.71428571429;1211.07142857143\n" + "1936-10-15,168.260;177.150;177.420,1201.85714285714;1265.35714285714;1267.28571428571\n" + "1936-11-15,176.670;183.220;184.900,1261.92857142857;1308.71428571429;1320.71428571429\n" + "1936-12-15,175.850;179.900;182.180,1256.07142857143;1285;1301.28571428571\n" + "1937-01-15,177.720;184.740;186.900,1260.42553191489;1310.21276595745;1325.53191489362\n" + "1937-02-15,186.010;187.170;190.290,1319.21985815603;1327.44680851064;1349.57446808511\n" + "1937-03-15,179.820;186.410;194.400,1266.33802816901;1312.74647887324;1369.01408450704\n" + "1937-04-15,170.130;174.270;185.190,1189.72027972028;1218.67132867133;1295.03496503496\n" + "1937-05-15,167.460;174.710;176.300,1162.91666666667;1213.26388888889;1224.30555555556\n" + "1937-06-15,165.510;169.320;175.140,1149.375;1175.83333333333;1216.25\n" + "1937-07-15,170.130;184.010;184.420,1173.31034482759;1269.03448275862;1271.86206896552\n" + "1937-08-15,175.910;177.410;189.340,1213.1724137931;1223.51724137931;1305.79310344828\n" + "1937-09-15,147.380;154.570;173.080,1009.45205479452;1058.69863013699;1185.47945205479\n" + "1937-10-15,125.730;138.480;153.890,861.164383561644;948.493150684931;1054.04109589041\n" + "1937-11-15,113.640;123.480;135.940,783.724137931035;851.586206896552;937.51724137931\n" + "1937-12-15,118.930;120.850;129.980,825.902777777778;839.236111111111;902.638888888889\n" + "1938-01-15,120.570;121.870;134.350,849.084507042254;858.239436619718;946.12676056338\n" + "1938-02-15,118.490;129.640;132.410,840.354609929078;919.432624113475;939.078014184397\n" + "1938-03-15,98.950;98.950;130.470,701.77304964539;701.77304964539;925.31914893617\n" + "1938-04-15,103.020;111.660;118.990,725.492957746479;786.338028169014;837.957746478873\n" + "1938-05-15,107.740;107.740;119.430,764.113475177305;764.113475177305;847.021276595745\n" + "1938-06-15,109.710;133.880;135.870,778.085106382979;949.503546099291;963.617021276596\n" + "1938-07-15,134.560;141.200;144.910,954.326241134752;1001.41843971631;1027.7304964539\n" + "1938-08-15,136.510;139.270;144.470,968.156028368794;987.730496453901;1024.60992907801\n" + "1938-09-15,129.910;141.450;143.080,921.347517730497;1003.1914893617;1014.75177304965\n" + "1938-10-15,144.230;151.730;154.170,1030.21428571429;1083.78571428571;1101.21428571429\n" + "1938-11-15,146.140;149.820;158.080,1043.85714285714;1070.14285714286;1129.14285714286\n" + "1938-12-15,147.390;154.360;154.360,1052.78571428571;1102.57142857143;1102.57142857143\n" + "1939-01-15,136.420;143.760;154.850,974.428571428571;1026.85714285714;1106.07142857143\n" + "1939-02-15,142.430;147.300;147.300,1024.67625899281;1059.71223021583;1059.71223021583\n" + "1939-03-15,131.840;131.840;152.280,948.489208633093;948.489208633093;1095.53956834532\n" + "1939-04-15,123.750;128.380;132.250,896.739130434783;930.289855072464;958.333333333333\n" + "1939-05-15,127.830;138.180;138.180,926.304347826087;1001.30434782609;1001.30434782609\n" + "1939-06-15,130.050;130.630;140.090,942.391304347826;946.594202898551;1015.14492753623\n" + "1939-07-15,131.930;143.260;144.510,956.014492753623;1038.11594202899;1047.17391304348\n" + "1939-08-15,131.330;134.410;144.260,951.666666666667;973.985507246377;1045.36231884058\n" + "1939-09-15,135.250;150.160;155.920,959.219858156028;1064.96453900709;1105.81560283688\n" + "1939-10-15,149.890;151.880;155.480,1070.64285714286;1084.85714285714;1110.57142857143\n" + "1939-11-15,145.690;145.690;152.640,1040.64285714286;1040.64285714286;1090.28571428571\n" + "1939-12-15,146.340;149.990;149.990,1045.28571428571;1071.35714285714;1071.35714285714\n" + "1940-01-15,144.650;145.330;152.800,1040.64748201439;1045.53956834532;1099.28057553957\n" + "1940-02-15,145.000;146.540;148.940,1035.71428571429;1046.71428571429;1063.85714285714\n" + "1940-03-15,145.590;147.540;148.370,1039.92857142857;1053.85714285714;1059.78571428571\n" + "1940-04-15,146.800;148.430;151.290,1048.57142857143;1060.21428571429;1080.64285714286\n" + "1940-05-15,113.940;116.220;148.170,813.857142857143;830.142857142857;1058.35714285714\n" + "1940-06-15,111.840;122.060;123.860,793.191489361702;865.673758865248;878.439716312057\n" + "1940-07-15,120.960;126.140;126.140,864;901;901\n" + "1940-08-15,121.280;128.880;128.880,866.285714285714;920.571428571428;920.571428571428\n" + "1940-09-15,127.740;132.640;135.100,912.428571428571;947.428571428571;965\n" + "1940-10-15,130.390;134.610;135.090,931.357142857143;961.5;964.928571428571\n" + "1940-11-15,129.780;130.030;137.750,927;928.785714285714;983.928571428571\n" + "1940-12-15,128.410;131.130;132.350,910.709219858156;930;938.652482269504\n" + "1941-01-15,124.050;124.130;133.590,879.787234042553;880.354609929078;947.446808510638\n" + "1941-02-15,117.660;121.970;124.760,834.468085106383;865.035460992908;884.822695035461\n" + "1941-03-15,120.300;122.720;123.920,847.183098591549;864.225352112676;872.676056338028\n" + "1941-04-15,115.540;115.540;124.650,807.972027972028;807.972027972028;871.678321678322\n" + "1941-05-15,115.300;116.230;117.820,800.694444444444;807.152777777778;818.194444444444\n" + "1941-06-15,116.180;123.140;123.970,790.340136054422;837.687074829932;843.333333333333\n" + "1941-07-15,122.850;128.790;130.060,835.714285714286;876.122448979592;884.761904761905\n" + "1941-08-15,124.900;127.430;128.220,838.255033557047;855.234899328859;860.536912751678\n" + "1941-09-15,125.810;126.820;129.320,833.17880794702;839.867549668874;856.423841059603\n" + "1941-10-15,117.820;117.820;126.850,770.065359477124;770.065359477124;829.084967320261\n" + "1941-11-15,114.660;114.660;119.850,744.545454545455;744.545454545455;778.246753246753\n" + "1941-12-15,106.340;110.960;116.650,686.064516129032;715.870967741935;752.58064516129\n" + "1942-01-15,108.940;109.410;114.220,693.885350318471;696.87898089172;727.515923566879\n" + "1942-02-15,105.100;106.580;110.800,665.189873417722;674.556962025316;701.26582278481\n" + "1942-03-15,99.210;99.530;106.970,620.0625;622.0625;668.5625\n" + "1942-04-15,92.920;95.350;102.500,577.142857142857;592.23602484472;636.645962732919\n" + "1942-05-15,95.830;100.880;101.090,587.914110429448;618.895705521472;620.184049079755\n" + "1942-06-15,101.300;103.340;106.290,621.472392638037;633.98773006135;652.085889570552\n" + "1942-07-15,102.690;105.720;108.910,626.158536585366;644.634146341464;664.085365853659\n" + "1942-08-15,104.800;106.330;107.550,635.151515151515;644.424242424242;651.818181818182\n" + "1942-09-15,106.030;109.110;109.560,642.606060606061;661.272727272727;664\n" + "1942-10-15,109.650;113.500;115.290,656.586826347306;679.640718562874;690.359281437126\n" + "1942-11-15,114.100;114.500;117.300,679.166666666667;681.547619047619;698.214285714286\n" + "1942-12-15,114.610;119.400;119.560,678.165680473373;706.508875739645;707.455621301775\n" + "1943-01-15,119.260;125.410;125.410,705.680473372781;742.07100591716;742.07100591716\n" + "1943-02-15,125.070;129.710;130.040,740.059171597633;767.514792899408;769.467455621302\n" + "1943-03-15,128.600;136.570;136.820,747.674418604651;794.011627906977;795.46511627907\n" + "1943-04-15,131.180;135.480;136.930,753.908045977012;778.620689655172;786.954022988506\n" + "1943-05-15,136.820;141.180;141.180,781.828571428571;806.742857142857;806.742857142857\n" + "1943-06-15,138.790;143.380;143.380,793.085714285714;819.314285714286;819.314285714286\n" + "1943-07-15,137.250;137.250;145.820,788.793103448276;788.793103448276;838.045977011494\n" + "1943-08-15,134.000;136.620;138.450,774.566473988439;789.71098265896;800.28901734104\n" + "1943-09-15,136.910;140.120;141.750,786.83908045977;805.287356321839;814.655172413793\n" + "1943-10-15,136.390;138.290;140.330,783.850574712644;794.770114942529;806.494252873563\n" + "1943-11-15,129.570;129.570;138.500,744.655172413793;744.655172413793;795.977011494253\n" + "1943-12-15,130.680;135.890;136.240,751.034482758621;780.977011494253;782.988505747127\n" + "1944-01-15,135.920;137.400;138.650,781.149425287356;789.655172413793;796.83908045977\n" + "1944-02-15,134.220;136.300;137.450,771.379310344828;783.333333333333;789.942528735632\n" + "1944-03-15,136.440;138.840;141.000,784.137931034483;797.931034482759;810.344827586207\n" + "1944-04-15,135.000;136.210;139.110,771.428571428571;778.342857142857;794.914285714286\n" + "1944-05-15,137.060;142.240;142.240,783.2;812.8;812.8\n" + "1944-06-15,141.620;148.380;148.630,804.659090909091;843.068181818182;844.488636363636\n" + "1944-07-15,145.770;146.110;150.500,823.559322033898;825.480225988701;850.282485875706\n" + "1944-08-15,144.900;146.990;148.960,818.64406779661;830.45197740113;841.581920903955\n" + "1944-09-15,142.960;146.310;147.160,807.683615819209;826.610169491525;831.412429378531\n" + "1944-10-15,145.830;146.530;148.870,823.898305084746;827.853107344633;841.073446327684\n" + "1944-11-15,145.600;147.330;148.080,822.598870056497;832.372881355932;836.610169491526\n" + "1944-12-15,147.300;151.930;152.280,827.52808988764;853.539325842697;855.505617977528\n" + "1945-01-15,151.350;153.670;155.850,850.280898876404;863.314606741573;875.561797752809\n" + "1945-02-15,153.790;160.400;160.400,863.988764044944;901.123595505618;901.123595505618\n" + "1945-03-15,152.270;154.060;161.520,855.449438202247;865.505617977528;907.415730337079\n" + "1945-04-15,154.990;165.440;165.440,870.730337078652;929.438202247191;929.438202247191\n" + "1945-05-15,163.090;168.300;169.080,911.117318435754;940.223463687151;944.581005586592\n" + "1945-06-15,164.570;164.570;168.920,909.226519337016;909.226519337016;933.259668508287\n" + "1945-07-15,160.910;162.880;167.090,889.005524861878;899.889502762431;923.149171270718\n" + "1945-08-15,161.550;174.290;174.290,892.541436464088;962.92817679558;962.92817679558\n" + "1945-09-15,173.900;180.110;180.220,960.773480662983;995.082872928177;995.690607734806\n" + "1945-10-15,183.060;186.600;187.060,1011.38121546961;1030.93922651934;1033.48066298343\n" + "1945-11-15,187.820;191.460;192.130,1037.67955801105;1057.79005524862;1061.49171270718\n" + "1945-12-15,189.070;192.910;195.820,1038.84615384615;1059.94505494506;1075.93406593407\n" + "1946-01-15,190.900;204.670;205.350,1048.9010989011;1124.56043956044;1128.2967032967\n" + "1946-02-15,186.020;190.090;206.610,1027.73480662983;1050.22099447514;1141.49171270718\n" + "1946-03-15,188.460;199.560;200.560,1029.83606557377;1090.49180327869;1095.95628415301\n" + "1946-04-15,199.190;206.770;208.310,1082.55434782609;1123.75;1132.11956521739\n" + "1946-05-15,200.650;212.280;212.500,1084.59459459459;1147.45945945946;1148.64864864865\n" + "1946-06-15,200.520;205.620;211.470,1072.29946524064;1099.57219251337;1130.85561497326\n" + "1946-07-15,195.220;201.560;207.560,985.959595959596;1017.9797979798;1048.28282828283\n" + "1946-08-15,189.190;189.190;204.520,936.584158415842;936.584158415842;1012.47524752475\n" + "1946-09-15,165.170;172.420;181.180,809.656862745098;845.196078431373;888.137254901961\n" + "1946-10-15,163.120;169.150;175.940,784.230769230769;813.221153846154;845.865384615385\n" + "1946-11-15,163.550;169.780;174.400,767.840375586855;797.089201877934;818.779342723005\n" + "1946-12-15,167.500;177.200;177.850,779.06976744186;824.186046511628;827.209302325581\n" + "1947-01-15,171.950;180.440;180.440,799.767441860465;839.255813953488;839.255813953488\n" + "1947-02-15,177.220;178.900;184.060,824.279069767442;832.093023255814;856.093023255814\n" + "1947-03-15,172.580;177.200;181.880,788.036529680365;809.132420091324;830.502283105023\n" + "1947-04-15,166.690;170.640;177.450,761.141552511416;779.178082191781;810.27397260274\n" + "1947-05-15,163.550;169.250;174.210,746.803652968037;772.831050228311;795.479452054795\n" + "1947-06-15,168.000;177.300;177.440,763.636363636364;805.909090909091;806.545454545455\n" + "1947-07-15,179.880;183.180;186.850,810.27027027027;825.135135135135;841.666666666667\n" + "1947-08-15,177.570;178.850;183.810,789.2;794.888888888889;816.933333333333\n" + "1947-09-15,174.860;177.490;179.810,760.260869565217;771.695652173913;781.782608695652\n" + "1947-10-15,178.100;181.810;185.290,774.347826086957;790.478260869565;805.608695652174\n" + "1947-11-15,179.510;179.510;183.170,777.099567099567;777.099567099567;792.943722943723\n" + "1947-12-15,176.100;181.160;181.160,752.564102564103;774.188034188034;774.188034188034\n" + "1948-01-15,171.180;174.760;181.040,722.278481012658;737.383966244726;763.881856540084\n" + "1948-02-15,165.650;166.800;174.920,704.893617021277;709.787234042553;744.340425531915\n" + "1948-03-15,165.390;177.200;177.200,706.794871794872;757.264957264957;757.264957264957\n" + "1948-04-15,177.320;180.510;183.780,745.042016806723;758.44537815126;772.18487394958\n" + "1948-05-15,180.940;190.740;191.060,757.071129707113;798.075313807531;799.414225941423\n" + "1948-06-15,187.900;189.460;193.160,779.668049792531;786.141078838174;801.49377593361\n" + "1948-07-15,181.200;181.330;191.620,742.622950819672;743.155737704918;785.327868852459\n" + "1948-08-15,179.270;181.710;183.600,731.714285714286;741.673469387755;749.387755102041\n" + "1948-09-15,175.990;178.300;185.360,718.326530612245;727.755102040816;756.571428571429\n" + "1948-10-15,179.870;188.280;189.760,737.172131147541;771.639344262295;777.704918032787\n" + "1948-11-15,171.200;171.200;189.760,707.438016528926;707.438016528926;784.132231404959\n" + "1948-12-15,173.220;177.300;177.920,718.755186721992;735.684647302905;738.257261410788\n" + "1949-01-15,175.030;179.120;181.430,729.291666666667;746.333333333333;755.958333333333\n" + "1949-02-15,171.100;173.060;180.390,718.90756302521;727.142857142857;757.941176470588\n" + "1949-03-15,173.660;177.100;178.450,729.663865546218;744.117647058823;749.789915966387\n" + "1949-04-15,173.240;174.060;177.160,724.853556485356;728.284518828452;741.255230125523\n" + "1949-05-15,168.360;168.360;176.630,707.394957983193;707.394957983193;742.142857142857\n" + "1949-06-15,161.600;167.420;168.150,676.150627615063;700.502092050209;703.556485355649\n" + "1949-07-15,168.080;175.920;176.460,709.198312236287;742.278481012658;744.556962025317\n" + "1949-08-15,176.840;178.660;182.020,743.025210084034;750.672268907563;764.789915966387\n" + "1949-09-15,178.040;182.510;183.290,744.937238493724;763.640167364017;766.903765690377\n" + "1949-10-15,182.670;189.540;190.360,770.759493670886;799.746835443038;803.206751054852\n" + "1949-11-15,187.980;191.550;193.520,789.831932773109;804.831932773109;813.109243697479\n" + "1949-12-15,192.710;200.520;200.520,816.567796610169;849.661016949153;849.661016949153\n" + "1950-01-15,196.810;201.790;201.980,837.489361702128;858.68085106383;859.489361702128\n" + "1950-02-15,201.690;203.440;204.590,858.255319148936;865.702127659575;870.595744680851\n" + "1950-03-15,202.330;206.050;209.780,857.330508474576;873.093220338983;888.898305084746\n" + "1950-04-15,208.440;213.560;215.310,883.220338983051;904.915254237288;912.330508474576\n" + "1950-05-15,214.870;223.420;223.420,906.62447257384;942.700421940928;942.700421940928\n" + "1950-06-15,206.720;209.110;228.380,868.571428571429;878.613445378151;959.579831932773\n" + "1950-07-15,197.460;209.400;210.850,819.336099585062;868.879668049792;874.896265560166\n" + "1950-08-15,211.260;216.870;221.510,869.382716049383;892.469135802469;911.56378600823\n" + "1950-09-15,218.100;226.360;226.780,893.852459016393;927.704918032787;929.426229508197\n" + "1950-10-15,225.010;225.010;231.740,914.674796747967;914.674796747967;942.032520325203\n" + "1950-11-15,222.520;227.600;235.470,900.890688259109;921.457489878542;953.31983805668\n" + "1950-12-15,222.330;235.420;235.420,889.32;941.68;941.68\n" + "1951-01-15,238.990;248.830;249.580,940.905511811024;979.645669291339;982.59842519685\n" + "1951-02-15,250.760;252.050;255.710,975.719844357977;980.739299610895;994.980544747082\n" + "1951-03-15,243.950;248.530;253.610,945.542635658915;963.294573643411;982.984496124031\n" + "1951-04-15,246.020;259.130;259.130,953.565891472868;1004.37984496124;1004.37984496124\n" + "1951-05-15,245.270;249.650;263.130,946.988416988417;963.899613899614;1015.94594594595\n" + "1951-06-15,242.640;242.640;254.030,936.833976833977;936.833976833977;980.810810810811\n" + "1951-07-15,243.980;257.860;260.700,942.007722007722;995.598455598456;1006.56370656371\n" + "1951-08-15,259.890;270.250;270.250,1003.43629343629;1043.43629343629;1043.43629343629\n" + "1951-09-15,270.630;271.160;276.370,1036.89655172414;1038.92720306513;1058.88888888889\n" + "1951-10-15,260.430;262.350;275.870,994.007633587786;1001.3358778626;1052.93893129771\n" + "1951-11-15,256.950;261.270;264.060,973.295454545455;989.659090909091;1000.22727272727\n" + "1951-12-15,263.240;269.230;269.230,993.358490566038;1015.96226415094;1015.96226415094\n" + "1952-01-15,268.080;270.690;275.400,1011.62264150943;1021.47169811321;1039.24528301887\n" + "1952-02-15,258.490;260.080;271.680,982.851711026616;988.897338403042;1033.00380228137\n" + "1952-03-15,260.080;269.460;269.460,988.897338403042;1024.56273764259;1024.56273764259\n" + "1952-04-15,257.630;257.630;267.220,975.871212121212;975.871212121212;1012.19696969697\n" + "1952-05-15,256.350;262.940;264.220,971.022727272727;995.984848484848;1000.83333333333\n" + "1952-06-15,262.090;274.260;274.260,989.018867924528;1034.94339622641;1034.94339622641\n" + "1952-07-15,272.580;279.560;279.560,1020.89887640449;1047.04119850187;1047.04119850187\n" + "1952-08-15,273.170;275.040;280.290,1023.10861423221;1030.11235955056;1049.77528089888\n" + "1952-09-15,268.380;270.610;277.150,1005.16853932584;1013.52059925094;1038.01498127341\n" + "1952-10-15,263.060;269.230;271.400,985.243445692884;1008.35205992509;1016.47940074906\n" + "1952-11-15,270.230;283.660;283.660,1012.09737827715;1062.39700374532;1062.39700374532\n" + "1952-12-15,281.630;291.900;292.000,1054.79400749064;1093.25842696629;1093.6329588015\n" + "1953-01-15,285.240;289.770;293.790,1072.33082706767;1089.36090225564;1104.47368421053\n" + "1953-02-15,281.140;284.270;290.190,1060.90566037736;1072.71698113208;1095.05660377358\n" + "1953-03-15,279.870;279.870;290.640,1052.14285714286;1052.14285714286;1092.63157894737\n" + "1953-04-15,270.730;274.750;280.090,1017.78195488722;1032.89473684211;1052.96992481203\n" + "1953-05-15,271.480;272.280;278.790,1016.77902621723;1019.77528089888;1044.15730337079\n" + "1953-06-15,262.880;268.260;269.840,980.89552238806;1000.97014925373;1006.86567164179\n" + "1953-07-15,268.060;275.380;275.380,1000.22388059701;1027.53731343284;1027.53731343284\n" + "1953-08-15,261.220;261.220;276.740,971.078066914498;971.078066914498;1028.77323420074\n" + "1953-09-15,255.490;264.040;265.480,949.776951672863;981.561338289963;986.914498141264\n" + "1953-10-15,264.260;275.810;276.310,978.740740740741;1021.51851851852;1023.37037037037\n" + "1953-11-15,273.880;281.370;281.370,1018.14126394052;1045.98513011152;1045.98513011152\n" + "1953-12-15,278.300;280.900;283.540,1034.57249070632;1044.23791821561;1054.05204460967\n" + "1954-01-15,279.870;292.390;292.850,1040.40892193309;1086.95167286245;1088.66171003717\n" + "1954-02-15,289.540;294.540;294.540,1076.35687732342;1094.94423791822;1094.94423791822\n" + "1954-03-15,296.400;303.510;303.510,1101.85873605948;1128.28996282528;1128.28996282528\n" + "1954-04-15,304.260;319.330;319.330,1135.29850746269;1191.52985074627;1191.52985074627\n" + "1954-05-15,317.930;327.490;327.490,1181.89591078067;1217.43494423792;1217.43494423792\n" + "1954-06-15,319.270;333.530;336.900,1186.87732342007;1239.88847583643;1252.41635687732\n" + "1954-07-15,334.120;347.920;347.920,1242.08178438662;1293.38289962825;1293.38289962825\n" + "1954-08-15,335.800;335.800;350.380,1248.32713754647;1248.32713754647;1302.52788104089\n" + "1954-09-15,338.130;360.460;363.320,1261.67910447761;1345;1355.67164179104\n" + "1954-10-15,352.140;352.140;364.430,1313.9552238806;1313.9552238806;1359.81343283582\n" + "1954-11-15,353.960;386.770;388.510,1320.74626865672;1443.17164179104;1449.66417910448\n" + "1954-12-15,384.040;404.390;404.390,1438.35205992509;1514.56928838951;1514.56928838951\n" + "1955-01-15,388.200;408.830;408.890,1453.93258426966;1531.19850187266;1531.42322097378\n" + "1955-02-15,405.700;411.870;413.990,1519.47565543071;1542.58426966292;1550.52434456929\n" + "1955-03-15,391.360;409.700;419.680,1465.76779026217;1534.45692883895;1571.83520599251\n" + "1955-04-15,412.970;425.650;430.640,1546.70411985019;1594.19475655431;1612.88389513109\n" + "1955-05-15,414.120;424.860;426.300,1551.01123595506;1591.23595505618;1596.62921348315\n" + "1955-06-15,424.880;451.380;451.380,1591.31086142322;1690.56179775281;1690.56179775281\n" + "1955-07-15,453.820;465.850;468.450,1693.35820895522;1738.24626865672;1747.94776119403\n" + "1955-08-15,448.840;468.180;468.180,1674.77611940298;1746.94029850746;1746.94029850746\n" + "1955-09-15,455.560;466.620;487.450,1693.53159851301;1734.6468401487;1812.08178438662\n" + "1955-10-15,438.590;454.870;461.140,1630.44609665428;1690.96654275093;1714.2750929368\n" + "1955-11-15,454.890;483.260;487.380,1691.04089219331;1796.50557620818;1811.82156133829\n" + "1955-12-15,480.720;488.400;488.400,1793.73134328358;1822.38805970149;1822.38805970149\n" + "1956-01-15,462.350;470.740;485.780,1725.18656716418;1756.49253731343;1812.61194029851\n" + "1956-02-15,465.720;483.650;485.710,1737.76119402985;1804.66417910448;1812.35074626866\n" + "1956-03-15,486.690;511.790;513.030,1816.00746268657;1909.66417910448;1914.29104477612\n" + "1956-04-15,503.020;516.120;521.050,1869.96282527881;1918.66171003717;1936.98884758364\n" + "1956-05-15,468.810;478.050;516.440,1736.33333333333;1770.55555555556;1912.74074074074\n" + "1956-06-15,475.290;492.780;492.780,1747.38970588235;1811.69117647059;1811.69117647059\n" + "1956-07-15,491.920;517.810;517.810,1795.32846715328;1889.81751824818;1889.81751824818\n" + "1956-08-15,495.960;502.040;520.950,1816.7032967033;1838.97435897436;1908.24175824176\n" + "1956-09-15,475.250;475.250;509.820,1734.48905109489;1734.48905109489;1860.65693430657\n" + "1956-10-15,468.700;479.850;490.190,1704.36363636364;1744.90909090909;1782.50909090909\n" + "1956-11-15,466.100;472.780;495.370,1694.90909090909;1719.2;1801.34545454545\n" + "1956-12-15,480.610;499.470;499.470,1741.34057971014;1809.67391304348;1809.67391304348\n" + "1957-01-15,474.590;479.160;499.200,1719.52898550725;1736.08695652174;1808.69565217391\n" + "1957-02-15,454.820;464.620;477.220,1641.94945848375;1677.3285198556;1722.81588447653\n" + "1957-03-15,468.910;474.810;475.010,1686.72661870504;1707.94964028777;1708.6690647482\n" + "1957-04-15,474.980;494.360;494.360,1702.43727598566;1771.89964157706;1771.89964157706\n" + "1957-05-15,494.680;504.930;506.040,1766.71428571429;1803.32142857143;1807.28571428571\n" + "1957-06-15,497.080;503.290;513.190,1768.96797153025;1791.06761565836;1826.29893238434\n" + "1957-07-15,503.290;508.520;520.770,1778.40989399293;1796.89045936396;1840.17667844523\n" + "1957-08-15,470.140;484.350;506.210,1661.27208480565;1711.48409893993;1788.72791519435\n" + "1957-09-15,456.300;456.300;486.130,1612.36749116608;1612.36749116608;1717.77385159011\n" + "1957-10-15,419.790;441.040;465.820,1483.35689045936;1558.44522968198;1646.00706713781\n" + "1957-11-15,427.940;449.870;449.870,1506.83098591549;1584.04929577465;1584.04929577465\n" + "1957-12-15,425.650;435.690;449.550,1498.7676056338;1534.11971830986;1582.92253521127\n" + "1958-01-15,438.680;450.020;451.160,1533.84615384615;1573.4965034965;1577.48251748252\n" + "1958-02-15,436.890;439.920;458.650,1527.58741258741;1538.18181818182;1603.67132867133\n" + "1958-03-15,443.380;446.760;455.920,1539.51388888889;1551.25;1583.05555555556\n" + "1958-04-15,440.090;455.860;455.860,1522.80276816609;1577.37024221453;1577.37024221453\n" + "1958-05-15,455.450;462.700;463.670,1575.95155709343;1601.03806228374;1604.39446366782\n" + "1958-06-15,466.110;478.180;478.970,1612.83737024221;1654.60207612457;1657.33564013841\n" + "1958-07-15,476.890;502.990;504.370,1644.44827586207;1734.44827586207;1739.20689655172\n" + "1958-08-15,502.670;508.630;512.420,1739.34256055363;1759.96539792388;1773.07958477509\n" + "1958-09-15,511.770;532.090;532.090,1770.83044982699;1841.14186851211;1841.14186851211\n" + "1958-10-15,530.940;543.220;546.360,1837.16262975779;1879.65397923875;1890.51903114187\n" + "1958-11-15,540.520;557.460;567.440,1863.86206896552;1922.27586206897;1956.68965517241\n" + "1958-12-15,556.080;583.650;583.650,1924.15224913495;2019.55017301038;2019.55017301038\n" + "1959-01-15,583.150;593.960;597.660,2010.86206896552;2048.13793103448;2060.89655172414\n" + "1959-02-15,574.460;603.500;603.500,1987.7508650519;2088.23529411765;2088.23529411765\n" + "1959-03-15,601.710;601.710;614.690,2082.04152249135;2082.04152249135;2126.95501730104\n" + "1959-04-15,602.940;623.750;629.870,2079.10344827586;2150.86206896552;2171.96551724138\n" + "1959-05-15,615.640;643.790;643.790,2122.89655172414;2219.96551724138;2219.96551724138\n" + "1959-06-15,617.620;643.600;643.600,2122.40549828179;2211.68384879725;2211.68384879725\n" + "1959-07-15,650.880;674.880;674.880,2229.04109589041;2311.23287671233;2311.23287671233\n" + "1959-08-15,646.530;664.410;678.100,2214.14383561644;2275.37671232877;2322.2602739726\n" + "1959-09-15,616.450;631.680;655.900,2103.92491467577;2155.90443686007;2238.56655290102\n" + "1959-10-15,625.590;646.600;646.600,2127.85714285714;2199.31972789116;2199.31972789116\n" + "1959-11-15,634.460;659.180;659.180,2158.02721088435;2242.10884353741;2242.10884353741\n" + "1959-12-15,661.290;679.360;679.360,2249.28571428571;2310.74829931973;2310.74829931973\n" + "1960-01-15,622.620;622.620;685.470,2124.98293515358;2124.98293515358;2339.48805460751\n" + "1960-02-15,611.330;630.120;636.920,2079.3537414966;2143.26530612245;2166.39455782313\n" + "1960-03-15,599.100;616.590;626.870,2037.75510204082;2097.24489795918;2132.21088435374\n" + "1960-04-15,601.700;601.700;630.770,2039.66101694915;2039.66101694915;2138.20338983051\n" + "1960-05-15,599.610;625.500;625.500,2032.57627118644;2120.33898305085;2120.33898305085\n" + "1960-06-15,624.890;640.620;656.420,2111.11486486486;2164.25675675676;2217.63513513513\n" + "1960-07-15,601.680;616.730;646.910,2032.7027027027;2083.5472972973;2185.50675675676\n" + "1960-08-15,608.690;625.990;641.560,2056.38513513513;2114.83108108108;2167.43243243243\n" + "1960-09-15,569.080;580.140;626.100,1922.56756756757;1959.93243243243;2115.2027027027\n" + "1960-10-15,566.050;580.360;596.480,1899.4966442953;1947.51677852349;2001.61073825503\n" + "1960-11-15,585.240;597.220;612.010,1963.89261744966;2004.09395973154;2053.72483221477\n" + "1960-12-15,593.490;615.890;617.780,1991.57718120805;2066.74496644295;2073.08724832215\n" + "1961-01-15,610.250;648.200;650.640,2047.81879194631;2175.1677852349;2183.35570469799\n" + "1961-02-15,637.040;662.080;662.080,2137.71812080537;2221.74496644295;2221.74496644295\n" + "1961-03-15,661.080;676.630;679.380,2218.38926174497;2270.57046979866;2279.79865771812\n" + "1961-04-15,672.660;678.710;696.720,2257.24832214765;2277.55033557047;2337.98657718121\n" + "1961-05-15,677.050;696.720;705.960,2271.97986577181;2337.98657718121;2368.9932885906\n" + "1961-06-15,680.680;683.960;703.790,2284.1610738255;2295.1677852349;2361.71140939597\n" + "1961-07-15,679.300;705.370;705.370,2264.33333333333;2351.23333333333;2351.23333333333\n" + "1961-08-15,710.460;719.940;725.760,2376.12040133779;2407.82608695652;2427.29096989967\n" + "1961-09-15,691.860;701.210;726.530,2306.2;2337.36666666667;2421.76666666667\n" + "1961-10-15,697.240;703.920;708.490,2324.13333333333;2346.4;2361.63333333333\n" + "1961-11-15,703.840;721.600;734.340,2346.13333333333;2405.33333333333;2447.8\n" + "1961-12-15,720.100;731.140;734.910,2400.33333333333;2437.13333333333;2449.7\n" + "1962-01-15,689.920;700.000;726.010,2299.73333333333;2333.33333333333;2420.03333333333\n" + "1962-02-15,702.540;708.050;717.550,2334.01993355482;2352.32558139535;2383.88704318937\n" + "1962-03-15,706.630;706.950;723.540,2347.60797342193;2348.67109634552;2403.78737541528\n" + "1962-04-15,665.330;665.330;705.420,2203.07947019868;2203.07947019868;2335.82781456954\n" + "1962-05-15,576.930;613.360;675.490,1910.3642384106;2030.99337748344;2236.72185430464\n" + "1962-06-15,535.760;561.280;611.050,1774.03973509934;1858.54304635762;2023.34437086093\n" + "1962-07-15,571.240;597.930;597.930,1885.28052805281;1973.36633663366;1973.36633663366\n" + "1962-08-15,588.350;609.180;616.000,1941.74917491749;2010.49504950495;2033.00330033003\n" + "1962-09-15,574.120;578.980;607.630,1888.55263157895;1904.53947368421;1998.78289473684\n" + "1962-10-15,558.060;589.770;589.770,1835.72368421053;1940.03289473684;1940.03289473684\n" + "1962-11-15,597.130;649.300;652.610,1964.24342105263;2135.85526315789;2146.74342105263\n" + "1962-12-15,640.140;652.100;653.990,2105.72368421053;2145.06578947368;2151.28289473684\n" + "1963-01-15,646.790;682.850;683.730,2127.59868421053;2246.21710526316;2249.11184210526\n" + "1963-02-15,662.940;662.940;688.960,2180.72368421053;2180.72368421053;2266.31578947368\n" + "1963-03-15,659.720;682.520;684.730,2163.01639344262;2237.77049180328;2245.01639344262\n" + "1963-04-15,684.270;717.700;718.330,2243.50819672131;2353.11475409836;2355.18032786885\n" + "1963-05-15,712.550;726.960;726.960,2336.22950819672;2383.47540983607;2383.47540983607\n" + "1963-06-15,706.030;706.880;726.870,2307.28758169935;2310.06535947712;2375.39215686275\n" + "1963-07-15,687.710;695.430;716.450,2240.09771986971;2265.24429967427;2333.71335504886\n" + "1963-08-15,694.870;729.320;729.320,2263.42019543974;2375.63517915309;2375.63517915309\n" + "1963-09-15,732.020;732.790;745.960,2384.42996742671;2386.93811074919;2429.83713355049\n" + "1963-10-15,737.940;755.230;760.500,2395.90909090909;2452.04545454545;2469.15584415584\n" + "1963-11-15,711.490;750.520;753.770,2310.03246753247;2436.75324675325;2447.30519480519\n" + "1963-12-15,751.820;762.950;767.210,2433.07443365696;2469.09385113269;2482.88025889968\n" + "1964-01-15,766.080;785.340;787.780,2479.22330097087;2541.55339805825;2549.4498381877\n" + "1964-02-15,783.040;800.140;800.140,2534.11003236246;2589.4498381877;2589.4498381877\n" + "1964-03-15,802.750;813.290;820.250,2597.89644012945;2632.00647249191;2654.53074433657\n" + "1964-04-15,810.770;810.770;827.330,2623.85113268608;2623.85113268608;2677.44336569579\n" + "1964-05-15,817.100;820.560;830.170,2644.33656957929;2655.53398058252;2686.63430420712\n" + "1964-06-15,800.310;831.500;831.500,2581.64516129032;2682.25806451613;2682.25806451613\n" + "1964-07-15,837.350;841.100;851.350,2692.44372990354;2704.50160771704;2737.45980707396\n" + "1964-08-15,823.400;838.480;842.830,2656.12903225806;2704.77419354839;2718.8064516129\n" + "1964-09-15,844.000;875.370;875.740,2713.82636655949;2814.69453376206;2815.88424437299\n" + "1964-10-15,868.440;873.080;881.500,2792.4115755627;2807.33118971061;2834.40514469453\n" + "1964-11-15,870.640;875.430;891.710,2790.51282051282;2805.86538461538;2858.04487179487\n" + "1964-12-15,857.450;874.130;874.130,2748.23717948718;2801.69871794872;2801.69871794872\n" + "1965-01-15,869.780;902.860;902.860,2787.75641025641;2893.78205128205;2893.78205128205\n" + "1965-02-15,881.350;903.480;906.300,2824.83974358974;2895.76923076923;2904.80769230769\n" + "1965-03-15,887.820;889.050;901.910,2836.4856230032;2840.41533546326;2881.50159744409\n" + "1965-04-15,890.330;922.310;922.310,2835.44585987261;2937.29299363057;2937.29299363057\n" + "1965-05-15,913.220;918.040;939.620,2908.34394904459;2923.69426751592;2992.42038216561\n" + "1965-06-15,840.590;868.030;908.530,2660.09493670886;2746.93037974684;2875.09493670886\n" + "1965-07-15,861.770;881.740;883.230,2727.12025316456;2790.3164556962;2795.03164556962\n" + "1965-08-15,878.890;893.100;896.180,2781.29746835443;2826.26582278481;2836.01265822785\n" + "1965-09-15,893.600;930.580;937.880,2827.84810126582;2944.87341772152;2967.9746835443\n" + "1965-10-15,929.650;960.820;960.820,2932.64984227129;3030.97791798107;3030.97791798107\n" + "1965-11-15,946.380;946.710;961.850,2985.42586750789;2986.46687697161;3034.22712933754\n" + "1965-12-15,939.530;969.260;969.260,2954.49685534591;3047.98742138365;3047.98742138365\n" + "1966-01-15,968.540;983.510;994.200,3045.72327044025;3092.79874213836;3126.41509433962\n" + "1966-02-15,950.660;951.890;995.150,2970.8125;2974.65625;3109.84375\n" + "1966-03-15,911.080;924.770;938.190,2838.2554517134;2880.90342679128;2922.71028037383\n" + "1966-04-15,931.290;933.680;954.730,2883.25077399381;2890.65015479876;2955.82043343653\n" + "1966-05-15,864.140;884.070;931.950,2675.3560371517;2737.05882352941;2885.29411764706\n" + "1966-06-15,870.100;870.100;903.170,2685.49382716049;2685.49382716049;2787.56172839506\n" + "1966-07-15,847.380;847.380;894.040,2607.32307692308;2607.32307692308;2750.89230769231\n" + "1966-08-15,767.030;788.410;852.390,2345.65749235474;2411.03975535168;2606.69724770642\n" + "1966-09-15,772.660;774.220;814.300,2362.874617737;2367.64525993884;2490.21406727829\n" + "1966-10-15,744.320;807.070;809.570,2262.37082066869;2453.10030395137;2460.6990881459\n" + "1966-11-15,791.590;791.590;820.870,2406.04863221885;2406.04863221885;2495.04559270517\n" + "1966-12-15,785.690;785.690;820.540,2388.11550151976;2388.11550151976;2494.04255319149\n" + "1967-01-15,786.410;849.890;849.890,2390.30395136778;2583.25227963526;2583.25227963526\n" + "1967-02-15,836.640;839.370;860.970,2542.97872340426;2551.27659574468;2616.93009118541\n" + "1967-03-15,841.760;865.980;876.670,2550.78787878788;2624.18181818182;2656.57575757576\n" + "1967-04-15,842.430;897.050;897.050,2545.10574018127;2710.12084592145;2710.12084592145\n" + "1967-05-15,852.560;852.560;909.630,2567.95180722892;2567.95180722892;2739.84939759036\n" + "1967-06-15,847.770;860.260;886.150,2545.85585585586;2583.36336336336;2661.11111111111\n" + "1967-07-15,859.690;904.240;909.560,2573.92215568862;2707.30538922156;2723.23353293413\n" + "1967-08-15,893.720;901.290;926.720,2667.82089552239;2690.41791044776;2766.32835820896\n" + "1967-09-15,901.180;926.660;943.080,2682.08333333333;2757.91666666667;2806.78571428571\n" + "1967-10-15,879.740;879.740;933.310,2610.50445103858;2610.50445103858;2769.46587537092\n" + "1967-11-15,849.570;875.810;884.880,2513.52071005917;2591.15384615385;2617.98816568047\n" + "1967-12-15,879.160;905.110;905.110,2593.39233038348;2669.94100294985;2669.94100294985\n" + "1968-01-15,855.470;855.470;908.920,2508.70967741935;2508.70967741935;2665.45454545455\n" + "1968-02-15,831.770;840.500;863.560,2432.07602339181;2457.60233918129;2525.02923976608\n" + "1968-03-15,825.130;840.670;843.220,2405.62682215743;2450.93294460641;2458.36734693878\n" + "1968-04-15,861.250;912.220;912.220,2503.63372093023;2651.8023255814;2651.8023255814\n" + "1968-05-15,891.600;899.000;919.900,2584.34782608696;2605.79710144928;2666.3768115942\n" + "1968-06-15,897.800;897.800;917.950,2587.31988472622;2587.31988472622;2645.38904899135\n" + "1968-07-15,883.000;883.000;923.720,2530.08595988539;2530.08595988539;2646.76217765043\n" + "1968-08-15,869.650;896.010;896.130,2484.71428571429;2560.02857142857;2560.37142857143\n" + "1968-09-15,900.360;935.790;938.280,2565.12820512821;2666.06837606838;2673.16239316239\n" + "1968-10-15,942.320;952.390;967.490,2669.46175637394;2697.98866855524;2740.76487252125\n" + "1968-11-15,946.230;985.080;985.080,2672.96610169492;2782.71186440678;2782.71186440678\n" + "1968-12-15,943.750;943.750;985.210,2658.45070422535;2658.45070422535;2775.23943661972\n" + "1969-01-15,921.250;946.050;951.890,2587.7808988764;2657.44382022472;2673.84831460674\n" + "1969-02-15,899.800;905.210;952.700,2513.40782122905;2528.51955307263;2661.17318435754\n" + "1969-03-15,904.030;935.480;935.480,2504.23822714681;2591.35734072022;2591.35734072022\n" + "1969-04-15,917.510;950.180;950.180,2527.57575757576;2617.57575757576;2617.57575757576\n" + "1969-05-15,936.920;937.560;968.850,2573.95604395604;2575.71428571429;2661.67582417582\n" + "1969-06-15,869.760;873.190;933.170,2376.39344262295;2385.7650273224;2549.64480874317\n" + "1969-07-15,801.960;815.470;886.120,2179.23913043478;2215.95108695652;2407.9347826087\n" + "1969-08-15,809.130;836.720;837.250,2186.83783783784;2261.40540540541;2262.83783783784\n" + "1969-09-15,811.840;813.090;837.780,2188.24797843666;2191.61725067385;2258.16711590296\n" + "1969-10-15,802.200;855.990;862.260,2150.67024128686;2294.87935656836;2311.6890080429\n" + "1969-11-15,807.290;812.300;863.050,2152.77333333333;2166.13333333333;2301.46666666667\n" + "1969-12-15,769.930;800.360;805.040,2042.25464190981;2122.97082228117;2135.38461538462\n" + "1970-01-15,744.060;744.060;811.310,1968.4126984127;1968.4126984127;2146.32275132275\n" + "1970-02-15,746.440;777.590;777.590,1964.31578947368;2046.28947368421;2046.28947368421\n" + "1970-03-15,763.600;785.570;791.050,1998.95287958115;2056.46596858639;2070.81151832461\n" + "1970-04-15,724.330;736.070;792.500,1881.37662337662;1911.87012987013;2058.44155844156\n" + "1970-05-15,631.160;700.440;733.630,1635.12953367876;1814.61139896373;1900.59585492228\n" + "1970-06-15,682.910;683.530;720.430,1760.07731958763;1761.67525773196;1856.77835051546\n" + "1970-07-15,669.360;734.120;735.560,1716.30769230769;1882.35897435897;1886.05128205128\n" + "1970-08-15,707.350;764.580;765.810,1813.71794871795;1960.46153846154;1963.61538461538\n" + "1970-09-15,747.470;760.680;773.140,1906.8112244898;1940.51020408163;1972.29591836735\n" + "1970-10-15,753.560;755.610;783.680,1912.58883248731;1917.79187817259;1989.03553299492\n" + "1970-11-15,754.240;794.090;794.090,1904.64646464646;2005.27777777778;2005.27777777778\n" + "1970-12-15,794.290;838.920;842.000,1995.70351758794;2107.8391959799;2115.57788944724\n" + "1971-01-15,830.570;868.500;868.500,2086.85929648241;2182.1608040201;2182.1608040201\n" + "1971-02-15,868.980;878.830;890.060,2177.89473684211;2202.58145363409;2230.72681704261\n" + "1971-03-15,882.390;904.370;916.830,2205.975;2260.925;2292.075\n" + "1971-04-15,903.040;941.750;950.820,2251.97007481297;2348.50374064838;2371.12219451372\n" + "1971-05-15,905.780;907.810;939.920,2247.59305210918;2252.63027295285;2332.30769230769\n" + "1971-06-15,873.100;891.140;923.060,2150.49261083744;2194.92610837438;2273.54679802956\n" + "1971-07-15,858.430;858.430;903.400,2109.16461916462;2109.16461916462;2219.65601965602\n" + "1971-08-15,839.590;898.070;908.370,2057.81862745098;2201.15196078431;2226.39705882353\n" + "1971-09-15,883.470;887.190;920.930,2165.36764705882;2174.48529411765;2257.18137254902\n" + "1971-10-15,836.380;839.000;901.800,2044.93887530562;2051.34474327628;2204.88997555012\n" + "1971-11-15,797.970;831.340;843.170,1951.02689486553;2032.61613691932;2061.54034229829\n" + "1971-12-15,846.010;890.200;893.660,2058.41849148418;2165.93673965937;2174.35523114355\n" + "1972-01-15,889.150;902.170;917.220,2163.38199513382;2195.06082725061;2231.67883211679\n" + "1972-02-15,901.790;928.130;928.130,2183.51089588378;2247.28813559322;2247.28813559322\n" + "1972-03-15,928.660;940.700;950.180,2243.14009661836;2272.22222222222;2295.12077294686\n" + "1972-04-15,940.920;954.170;968.920,2267.27710843374;2299.20481927711;2334.74698795181\n" + "1972-05-15,925.120;960.720;971.250,2223.84615384615;2309.42307692308;2334.73557692308\n" + "1972-06-15,926.250;929.030;961.390,2221.22302158273;2227.8896882494;2305.49160671463\n" + "1972-07-15,910.450;924.740;942.130,2172.91169451074;2207.01670644391;2248.52028639618\n" + "1972-08-15,930.460;963.730;973.510,2215.38095238095;2294.59523809524;2317.88095238095\n" + "1972-09-15,935.730;953.270;970.050,2222.63657957245;2264.29928741093;2304.1567695962\n" + "1972-10-15,921.660;955.520;955.520,2178.86524822695;2258.91252955083;2258.91252955083\n" + "1972-11-15,968.540;1018.21;1025.21,2284.29245283019;2401.43867924528;2417.94811320755\n" + "1972-12-15,1000.00;1020.02;1036.27,2352.94117647059;2400.04705882353;2438.28235294118\n" + "1973-01-15,992.930;999.020;1051.70,2330.82159624413;2345.11737089202;2468.779342723\n" + "1973-02-15,947.920;955.070;996.760,2209.60372960373;2226.2703962704;2323.44988344988\n" + "1973-03-15,922.710;951.010;979.980,2130.96997690531;2196.32794457275;2263.23325635104\n" + "1973-04-15,921.430;921.430;967.410,2113.37155963303;2113.37155963303;2218.83027522936\n" + "1973-05-15,886.510;901.410;956.580,2019.38496583144;2053.32574031891;2178.99772209567\n" + "1973-06-15,869.130;891.710;927.000,1966.35746606335;2017.44343891403;2097.2850678733\n" + "1973-07-15,870.110;926.400;936.710,1964.1309255079;2091.19638826185;2114.46952595937\n" + "1973-08-15,851.900;887.570;912.780,1888.91352549889;1968.0044345898;2023.90243902439\n" + "1973-09-15,880.570;947.100;953.270,1948.16371681416;2095.35398230089;2109.00442477876\n" + "1973-10-15,948.830;956.580;987.060,2080.76754385965;2097.76315789474;2164.60526315789\n" + "1973-11-15,817.730;822.250;948.830,1781.54684095861;1791.39433551198;2067.16775599129\n" + "1973-12-15,788.310;850.860;851.140,1706.2987012987;1841.68831168831;1842.29437229437\n" + "1974-01-15,823.110;855.550;880.690,1766.330472103;1835.94420600858;1889.89270386266\n" + "1974-02-15,803.900;860.530;863.420,1703.17796610169;1823.15677966102;1829.27966101695\n" + "1974-03-15,846.680;846.680;891.660,1771.29707112971;1771.29707112971;1865.39748953975\n" + "1974-04-15,827.680;836.750;869.920,1724.33333333333;1743.22916666667;1812.33333333333\n" + "1974-05-15,795.370;802.170;865.770,1636.56378600823;1650.55555555556;1781.41975308642\n" + "1974-06-15,802.410;802.410;859.670,1637.57142857143;1637.57142857143;1754.42857142857\n" + "1974-07-15,757.430;757.430;806.240,1533.25910931174;1533.25910931174;1632.06477732794\n" + "1974-08-15,656.840;678.580;797.560,1313.68;1357.16;1595.12\n" + "1974-09-15,607.870;607.870;677.880,1201.32411067194;1201.32411067194;1339.6837944664\n" + "1974-10-15,584.560;665.520;673.500,1143.9530332681;1302.38747553816;1318.00391389432\n" + "1974-11-15,608.570;618.660;674.750,1181.68932038835;1201.28155339806;1310.19417475728\n" + "1974-12-15,577.600;616.240;616.240,1112.90944123314;1187.36030828516;1187.36030828516\n" + "1975-01-15,632.040;703.690;705.960,1213.12859884837;1350.65259117083;1355.00959692898\n" + "1975-02-15,707.600;739.050;749.770,1347.80952380952;1407.71428571429;1428.13333333333\n" + "1975-03-15,743.430;768.150;786.530,1410.68311195446;1457.59013282732;1492.46679316888\n" + "1975-04-15,742.880;821.340;821.340,1404.31001890359;1552.62759924386;1552.62759924386\n" + "1975-05-15,815.000;832.290;858.730,1531.95488721805;1564.45488721804;1614.15413533835\n" + "1975-06-15,819.310;878.990;878.990,1528.56343283582;1639.90671641791;1639.90671641791\n" + "1975-07-15,824.860;831.510;881.810,1521.88191881919;1534.15129151291;1626.9557195572\n" + "1975-08-15,791.690;835.340;835.340,1457.9926335175;1538.37937384899;1538.37937384899\n" + "1975-09-15,793.880;793.880;840.110,1453.99267399267;1453.99267399267;1538.663003663\n" + "1975-10-15,784.160;836.040;855.160,1428.34244080146;1522.84153005464;1557.66848816029\n" + "1975-11-15,825.720;860.670;860.670,1493.16455696203;1556.36528028933;1556.36528028933\n" + "1975-12-15,818.800;852.410;859.810,1475.31531531532;1535.87387387387;1549.20720720721\n" + "1976-01-15,858.710;975.280;975.280,1544.44244604317;1754.10071942446;1754.10071942446\n" + "1976-02-15,950.570;972.610;994.570,1703.53046594982;1743.02867383513;1782.3835125448\n" + "1976-03-15,970.640;999.450;1009.21,1736.38640429338;1787.92486583184;1805.38461538462\n" + "1976-04-15,968.280;996.850;1011.02,1725.98930481283;1776.91622103387;1802.17468805704\n" + "1976-05-15,965.570;975.230;1007.48,1708.97345132743;1726.07079646018;1783.15044247788\n" + "1976-06-15,958.090;1002.78;1007.45,1686.77816901408;1765.45774647887;1773.67957746479\n" + "1976-07-15,979.290;984.640;1011.21,1715.04378283713;1724.41330998249;1770.94570928196\n" + "1976-08-15,960.440;973.740;999.340,1673.24041811847;1696.41114982578;1741.01045296167\n" + "1976-09-15,978.640;990.190;1014.79,1699.02777777778;1719.07986111111;1761.78819444444\n" + "1976-10-15,932.350;964.930;979.890,1610.27633851468;1666.54576856649;1692.38341968912\n" + "1976-11-15,924.040;947.220;966.090,1593.1724137931;1633.13793103448;1665.6724137931\n" + "1976-12-15,946.640;1004.65;1004.65,1626.52920962199;1726.20274914089;1726.20274914089\n" + "1977-01-15,954.370;954.370;999.750,1631.40170940171;1631.40170940171;1708.97435897436\n" + "1977-02-15,931.520;936.420;958.360,1576.17597292724;1584.46700507614;1621.59052453469\n" + "1977-03-15,919.130;919.130;968.000,1544.75630252101;1544.75630252101;1626.89075630252\n" + "1977-04-15,914.600;926.900;947.760,1524.33333333333;1544.83333333333;1579.6\n" + "1977-05-15,898.660;898.660;943.440,1490.31509121061;1490.31509121061;1564.57711442786\n" + "1977-06-15,903.040;916.300;929.700,1487.71004942339;1509.55518945634;1531.63097199341\n" + "1977-07-15,888.430;890.070;923.420,1456.44262295082;1459.13114754098;1513.80327868852\n" + "1977-08-15,854.120;861.490;891.810,1395.62091503268;1407.66339869281;1457.20588235294\n" + "1977-09-15,834.720;847.110;876.390,1359.47882736156;1379.65798045603;1427.34527687296\n" + "1977-10-15,801.540;818.350;851.960,1301.2012987013;1328.49025974026;1383.05194805195\n" + "1977-11-15,800.850;829.700;845.890,1293.7802907916;1340.38772213247;1366.54281098546\n" + "1977-12-15,806.220;831.170;831.170,1298.26086956522;1338.43800322061;1338.43800322061\n" + "1978-01-15,763.340;769.920;817.740,1221.344;1231.872;1308.384\n" + "1978-02-15,742.120;742.120;782.660,1179.84101748808;1179.84101748808;1244.29252782194\n" + "1978-03-15,742.720;757.360;773.820,1171.48264984227;1194.57413249211;1220.53627760252\n" + "1978-04-15,751.040;837.320;837.320,1175.33646322379;1310.35993740219;1310.35993740219\n" + "1978-05-15,822.070;840.610;858.370,1274.52713178295;1303.27131782946;1330.80620155039\n" + "1978-06-15,812.280;818.950;866.510,1245.8282208589;1256.05828220859;1329.00306748466\n" + "1978-07-15,805.790;862.270;862.270,1226.46879756469;1312.43531202435;1312.43531202435\n" + "1978-08-15,860.710;876.820;900.120,1304.10606060606;1328.51515151515;1363.81818181818\n" + "1978-09-15,857.160;865.820;907.740,1288.96240601504;1301.98496240602;1365.02255639098\n" + "1978-10-15,792.450;792.450;901.420,1180.99850968703;1180.99850968703;1343.39791356185\n" + "1978-11-15,785.260;799.030;827.790,1165.07418397626;1185.50445103858;1228.17507418398\n" + "1978-12-15,787.510;805.010;821.900,1163.23485967504;1189.08419497784;1214.03249630724\n" + "1979-01-15,811.420;839.220;859.750,1188.02342606149;1228.7262079063;1258.78477306003\n" + "1979-02-15,807.000;808.820;840.870,1167.87264833575;1170.50651230101;1216.88856729378\n" + "1979-03-15,815.750;862.180;871.360,1168.69627507163;1235.21489971347;1248.36676217765\n" + "1979-04-15,854.900;854.900;878.720,1210.90651558074;1210.90651558074;1244.64589235127\n" + "1979-05-15,822.160;822.330;857.590,1149.87412587413;1150.11188811189;1199.42657342657\n" + "1979-06-15,821.210;841.980;849.100,1135.83679114799;1164.5643153527;1174.41217150761\n" + "1979-07-15,825.510;846.420;852.990,1129.28864569083;1157.89329685363;1166.88098495212\n" + "1979-08-15,846.160;887.630;887.630,1146.55826558266;1202.75067750677;1202.75067750677\n" + "1979-09-15,866.130;878.580;893.940,1161.03217158177;1177.72117962466;1198.3109919571\n" + "1979-10-15,806.830;815.700;897.610,1072.91223404255;1084.70744680851;1193.63031914894\n" + "1979-11-15,796.670;822.350;831.740,1049.63109354414;1083.465085639;1095.83662714097\n" + "1979-12-15,819.620;838.740;844.620,1068.60495436767;1093.5332464146;1101.19947848761\n" + "1980-01-15,820.310;875.850;881.910,1054.38303341902;1125.77120822622;1133.56041131105\n" + "1980-02-15,854.440;863.140;903.840,1082.94043092522;1093.9670468948;1145.55133079848\n" + "1980-03-15,759.980;785.750;856.480,948.789013732834;980.961298377029;1069.26342072409\n" + "1980-04-15,759.130;817.060;817.060,937.197530864197;1008.71604938272;1008.71604938272\n" + "1980-05-15,805.200;850.850;860.320,984.352078239609;1040.15892420538;1051.73594132029\n" + "1980-06-15,843.770;867.920;887.540,1020.27811366385;1049.48004836759;1073.20435308343\n" + "1980-07-15,872.270;935.320;936.180,1054.7400241838;1130.97944377267;1132.01934703748\n" + "1980-08-15,929.780;932.590;966.720,1116.1824729892;1119.55582232893;1160.52821128451\n" + "1980-09-15,921.930;932.420;974.570,1097.53571428571;1110.02380952381;1160.20238095238\n" + "1980-10-15,917.750;924.490;972.440,1082.25235849057;1090.20047169811;1146.74528301887\n" + "1980-11-15,932.420;993.340;1000.17,1090.54970760234;1161.80116959064;1169.78947368421\n" + "1980-12-15,908.450;963.990;974.400,1052.6651216686;1117.02201622248;1129.08458864426\n" + "1981-01-15,938.910;947.270;1004.69,1079.20689655172;1088.81609195402;1154.81609195402\n" + "1981-02-15,931.570;974.580;974.580,1059.80659840728;1108.73720136519;1108.73720136519\n" + "1981-03-15,964.620;1003.87;1015.22,1089.96610169492;1134.31638418079;1147.14124293785\n" + "1981-04-15,989.100;997.750;1024.05,1110.10101010101;1119.80920314254;1149.3265993266\n" + "1981-05-15,963.440;991.750;995.590,1072.87305122494;1104.3986636971;1108.67483296214\n" + "1981-06-15,976.880;976.880;1011.99,1078.23399558499;1078.23399558499;1116.98675496689\n" + "1981-07-15,924.660;952.340;967.660,1009.45414847162;1039.67248908297;1056.39737991266\n" + "1981-08-15,881.470;881.470;953.580,955.005417118093;955.005417118093;1033.13109425786\n" + "1981-09-15,824.010;849.980;884.230,884.130901287554;911.995708154506;948.744635193133\n" + "1981-10-15,830.960;852.550;878.140,889.678800856531;912.79443254818;940.192719486081\n" + "1981-11-15,844.080;888.980;888.980,900.832443970117;948.751334044824;948.751334044824\n" + "1981-12-15,868.250;875.000;892.690,923.670212765957;930.851063829787;949.670212765958\n" + "1982-01-15,838.950;871.100;882.520,889.66065747614;923.753976670202;935.864262990456\n" + "1982-02-15,811.260;824.390;852.550,857.568710359408;871.448202959831;901.215644820296\n" + "1982-03-15,795.470;822.770;828.390,841.767195767196;870.656084656085;876.603174603175\n" + "1982-04-15,833.240;848.360;865.580,878.018967334036;893.951527924131;912.096944151739\n" + "1982-05-15,819.540;819.540;869.200,855.469728601253;855.469728601253;907.306889352818\n" + "1982-06-15,788.620;811.930;816.880,813.010309278351;837.041237113402;842.144329896907\n" + "1982-07-15,796.990;808.600;833.430,817.425641025641;829.333333333333;854.8\n" + "1982-08-15,776.920;901.310;901.310,795.209825997953;922.528147389969;922.528147389969\n" + "1982-09-15,895.050;896.250;934.790,914.249233912155;915.474974463738;954.841675178754\n" + "1982-10-15,903.610;991.720;1036.98,920.173116089613;1009.89816700611;1055.98778004073\n" + "1982-11-15,990.990;1039.28;1065.49,1011.21428571429;1060.48979591837;1087.23469387755\n" + "1982-12-15,990.250;1046.54;1070.55,1014.60040983607;1072.27459016393;1096.875\n" + "1983-01-15,1027.04;1075.70;1092.35,1050.14314928425;1099.89775051125;1116.92229038855\n" + "1983-02-15,1059.79;1112.16;1121.81,1082.52298263534;1136.01634320735;1145.873340143\n" + "1983-03-15,1114.45;1130.03;1145.90,1138.35546475996;1154.26966292135;1170.48008171604\n" + "1983-04-15,1113.49;1226.20;1226.20,1129.30020283976;1243.61054766734;1243.61054766734\n" + "1983-05-15,1190.02;1199.98;1232.59,1199.61693548387;1209.65725806452;1242.53024193548\n" + "1983-06-15,1185.50;1221.96;1248.30,1191.45728643216;1228.10050251256;1254.57286432161\n" + "1983-07-15,1189.90;1199.22;1243.69,1191.09109109109;1200.42042042042;1244.93493493493\n" + "1983-08-15,1163.06;1216.16;1216.16,1160.73852295409;1213.73253493014;1213.73253493014\n" + "1983-09-15,1206.81;1233.13;1260.77,1198.42105263158;1224.55809334657;1252.00595829196\n" + "1983-10-15,1223.48;1225.20;1284.65,1211.36633663366;1213.06930693069;1271.93069306931\n" + "1983-11-15,1214.84;1276.02;1287.19,1200.4347826087;1260.88932806324;1271.92687747036\n" + "1983-12-15,1236.79;1258.64;1275.10,1220.91806515301;1242.48766041461;1258.73642645607\n" + "1984-01-15,1220.58;1220.58;1286.64,1197.82139352306;1197.82139352306;1262.64965652601\n" + "1984-02-15,1134.21;1154.63;1213.88,1107.626953125;1127.568359375;1185.4296875\n" + "1984-03-15,1139.76;1164.89;1184.36,1110.87719298246;1135.37037037037;1154.3469785575\n" + "1984-04-15,1130.55;1170.75;1175.25,1096.55674102813;1135.54801163919;1139.91270611057\n" + "1984-05-15,1101.24;1104.85;1186.56,1065.02901353965;1068.52030947776;1147.54352030948\n" + "1984-06-15,1086.90;1132.40;1133.84,1048.11957569913;1091.99614271938;1093.38476374156\n" + "1984-07-15,1086.57;1115.28;1134.28,1043.77521613833;1071.35446685879;1089.60614793468\n" + "1984-08-15,1134.61;1224.38;1239.73,1085.75119617225;1171.65550239234;1186.34449760766\n" + "1984-09-15,1197.99;1206.71;1237.52,1140.94285714286;1149.24761904762;1178.59047619048\n" + "1984-10-15,1175.13;1207.38;1225.93,1115.98290598291;1146.60968660969;1164.22602089269\n" + "1984-11-15,1185.29;1188.94;1244.15,1125.63152896486;1129.09781576448;1181.5289648623\n" + "1984-12-15,1163.21;1211.57;1211.57,1104.6628679962;1150.58879392213;1150.58879392213\n" + "1985-01-15,1184.96;1286.77;1292.62,1123.18483412322;1219.68720379147;1225.23222748815\n" + "1985-02-15,1275.84;1284.01;1297.92,1203.62264150943;1211.33018867925;1224.45283018868\n" + "1985-03-15,1247.35;1266.78;1299.36,1172.32142857143;1190.58270676692;1221.2030075188\n" + "1985-04-15,1252.98;1258.06;1284.78,1172.10477081384;1176.85687558466;1201.85219831618\n" + "1985-05-15,1242.05;1315.41;1315.41,1157.54892823858;1225.91798695247;1225.91798695247\n" + "1985-06-15,1290.10;1335.46;1335.46,1198.97769516729;1241.13382899628;1241.13382899628\n" + "1985-07-15,1321.91;1347.45;1359.54,1226.26159554731;1249.95361781076;1261.16883116883\n" + "1985-08-15,1312.50;1334.01;1355.62,1215.27777777778;1235.19444444444;1255.2037037037\n" + "1985-09-15,1297.94;1328.63;1339.27,1198.46722068329;1226.80517082179;1236.6297322253\n" + "1985-10-15,1324.37;1374.31;1375.57,1218.37166513339;1264.3146274149;1265.47378104876\n" + "1985-11-15,1389.68;1472.13;1475.69,1274.93577981651;1350.57798165138;1353.84403669725\n" + "1985-12-15,1457.91;1546.67;1553.10,1333.86093321135;1415.06861848124;1420.95150960659\n" + "1986-01-15,1502.29;1570.99;1570.99,1370.70255474453;1433.38503649635;1433.38503649635\n" + "1986-02-15,1593.12;1709.06;1713.99,1457.56633119854;1563.64135407136;1568.15187557182\n" + "1986-03-15,1686.42;1818.61;1821.72,1550.01838235294;1671.51654411765;1674.375\n" + "1986-04-15,1735.51;1783.98;1855.90,1598.07550644567;1642.70718232044;1708.93186003683\n" + "1986-05-15,1758.18;1876.71;1882.35,1614.49035812672;1723.33333333333;1728.51239669421\n" + "1986-06-15,1837.19;1892.72;1892.72,1677.79908675799;1728.51141552511;1728.51141552511\n" + "1986-07-15,1766.87;1775.31;1909.03,1613.5799086758;1621.28767123288;1743.40639269406\n" + "1986-08-15,1763.64;1898.34;1904.53,1607.69371011851;1730.48313582498;1736.1257976299\n" + "1986-09-15,1755.20;1767.58;1919.71,1592.74047186933;1603.97459165154;1742.02359346642\n" + "1986-10-15,1774.18;1877.71;1878.37,1608.50407978241;1702.36627379873;1702.96464188577\n" + "1986-11-15,1817.21;1914.23;1916.76,1646.02355072464;1733.90398550725;1736.19565217391\n" + "1986-12-15,1895.95;1895.95;1955.57,1715.79185520362;1715.79185520362;1769.74660633484\n" + "1987-01-15,1927.31;2158.04;2163.39,1733.19244604317;1940.68345323741;1945.49460431655\n" + "1987-02-15,2158.04;2223.99;2244.09,1933.72759856631;1992.82258064516;2010.83333333333\n" + "1987-03-15,2220.47;2304.69;2372.59,1980.79393398751;2055.92328278323;2116.49420160571\n" + "1987-04-15,2230.54;2286.36;2405.54,1979.18367346939;2028.71339840284;2134.46317657498\n" + "1987-05-15,2215.87;2291.57;2342.19,1959.21308576481;2026.14500442087;2070.90185676393\n" + "1987-06-15,2278.22;2418.53;2451.05,2007.2422907489;2130.86343612335;2159.5154185022\n" + "1987-07-15,2409.76;2572.07;2572.07,2117.539543058;2260.16695957821;2260.16695957821\n" + "1987-08-15,2546.72;2662.95;2722.42,2226.15384615385;2327.7534965035;2379.73776223776\n" + "1987-09-15,2492.82;2596.28;2613.04,2167.66956521739;2257.6347826087;2272.20869565217\n" + "1987-10-15,1738.74;1993.53;2640.99,1508.01387684302;1728.99392888118;2290.53772766696\n" + "1987-11-15,1833.55;1833.55;2014.09,1588.86481802426;1588.86481802426;1745.31195840555\n" + "1987-12-15,1766.74;1938.83;2005.64,1530.9705372617;1680.09532062392;1737.98960138648\n" + "1988-01-15,1879.14;1958.22;2051.89,1624.14866032844;1692.49783923941;1773.45721694036\n" + "1988-02-15,1895.72;2071.62;2071.62,1634.24137931034;1785.87931034483;1785.87931034483\n" + "1988-03-15,1978.12;1988.06;2087.37,1697.95708154506;1706.48927038627;1791.7339055794\n" + "1988-04-15,1980.60;2032.33;2110.08,1691.37489325363;1735.55081127242;1801.94705380017\n" + "1988-05-15,1941.48;2031.12;2058.36,1652.32340425532;1728.61276595745;1751.79574468085\n" + "1988-06-15,2052.45;2141.71;2152.20,1739.36440677966;1815.00847457627;1823.89830508475\n" + "1988-07-15,2053.70;2128.73;2158.61,1733.08016877637;1796.39662447257;1821.61181434599\n" + "1988-08-15,1989.33;2031.65;2134.07,1671.70588235294;1707.26890756303;1793.33613445378\n" + "1988-09-15,2002.31;2112.91;2119.31,1671.37729549249;1763.69782971619;1769.04006677796\n" + "1988-10-15,2102.06;2148.65;2183.50,1748.80199667221;1787.56239600666;1816.55574043261\n" + "1988-11-15,2038.58;2114.51;2170.34,1694.58021612635;1757.69742310889;1804.106400665\n" + "1988-12-15,2092.28;2168.57;2182.68,1736.33195020747;1799.64315352697;1811.35269709544\n" + "1989-01-15,2144.64;2342.32;2342.32,1770.96614368291;1934.20313790256;1934.20313790256\n" + "1989-02-15,2245.54;2258.39;2347.14,1846.66118421053;1857.22861842105;1930.21381578947\n" + "1989-03-15,2243.04;2293.62;2340.71,1834.04742436631;1875.40474243663;1913.90842191333\n" + "1989-04-15,2291.97;2418.80;2418.99,1861.87652315191;1964.90658001625;1965.06092607636\n" + "1989-05-15,2371.33;2480.15;2502.02,1915.45234248788;2003.352180937;2021.01777059774\n" + "1989-06-15,2440.06;2440.06;2531.87,1966.20467365028;1966.20467365028;2040.18533440774\n" + "1989-07-15,2452.77;2660.66;2660.66,1971.68006430868;2138.79421221865;2138.79421221865\n" + "1989-08-15,2641.12;2737.27;2743.36,2119.67897271268;2196.84590690209;2201.73354735152\n" + "1989-09-15,2659.19;2692.82;2752.09,2127.352;2154.256;2201.672\n" + "1989-10-15,2569.26;2645.08;2791.41,2045.58917197452;2105.95541401274;2222.4601910828\n" + "1989-11-15,2582.17;2706.27;2706.27,2050.96902303415;2149.53931691819;2149.53931691819\n" + "1989-12-15,2687.93;2753.20;2761.09,2131.58604282316;2183.34655035686;2189.60348929421\n" + "1990-01-15,2543.24;2590.54;2810.15,1996.26373626374;2033.39089481947;2205.76923076923\n" + "1990-02-15,2564.19;2627.25;2649.55,2003.2734375;2052.5390625;2069.9609375\n" + "1990-03-15,2635.59;2707.21;2755.63,2047.85547785548;2103.50427350427;2141.12665112665\n" + "1990-04-15,2645.05;2656.76;2765.77,2052.01706749418;2061.1016291699;2145.67106283941\n" + "1990-05-15,2668.92;2876.66;2878.56,2065.72755417957;2226.51702786378;2227.98761609907\n" + "1990-06-15,2842.33;2880.69;2935.89,2188.09083910701;2217.62124711316;2260.11547344111\n" + "1990-07-15,2879.21;2905.20;2999.75,2207.98312883436;2227.91411042945;2300.4217791411\n" + "1990-08-15,2483.42;2614.36;2899.26,1887.09726443769;1986.59574468085;2203.08510638298\n" + "1990-09-15,2427.48;2452.48;2628.22,1829.29917106255;1848.13865862849;1980.572720422\n" + "1990-10-15,2365.10;2442.33;2523.76,1771.61048689139;1829.4606741573;1890.45692883895\n" + "1990-11-15,2440.84;2559.65;2565.35,1824.24514200299;1913.04185351271;1917.3019431988\n" + "1990-12-15,2565.59;2633.66;2637.13,1917.48131539611;1968.355754858;1970.94917787743\n" + "1991-01-15,2470.30;2736.39;2736.39,1835.2897473997;2032.97919762259;2032.97919762259\n" + "1991-02-15,2730.69;2882.18;2934.65,2025.73442136499;2138.11572700297;2177.04005934718\n" + "1991-03-15,2855.45;2913.86;2973.27,2115.14814814815;2158.41481481481;2202.42222222222\n" + "1991-04-15,2873.02;2887.87;3004.46,2125.01479289941;2135.99852071006;2222.23372781065\n" + "1991-05-15,2865.38;3027.50;3027.50,2113.11209439528;2232.66961651917;2232.66961651917\n" + "1991-06-15,2906.75;2906.75;3035.33,2137.31617647059;2137.31617647059;2231.86029411765\n" + "1991-07-15,2932.47;3024.82;3024.82,2153.06167400881;2220.86637298091;2220.86637298091\n" + "1991-08-15,2898.03;3043.60;3055.23,2121.54465592972;2228.11127379209;2236.62518301611\n" + "1991-09-15,2982.56;3016.77;3029.07,2173.87755102041;2198.81195335277;2207.77696793003\n" + "1991-10-15,2942.75;3069.10;3077.15,2141.73944687045;2233.69723435226;2239.55604075691\n" + "1991-11-15,2894.68;2894.68;3065.30,2100.63860667634;2100.63860667634;2224.4557329463\n" + "1991-12-15,2863.82;3168.83;3168.83,2076.7367657723;2297.91878172589;2297.91878172589\n" + "1992-01-15,3172.41;3223.39;3272.14,2297.18320057929;2334.09847936278;2369.39898624185\n" + "1992-02-15,3224.73;3267.67;3283.32,2326.64502164502;2357.62626262626;2368.91774891775\n" + "1992-03-15,3208.63;3235.47;3290.25,2303.39554917444;2322.66331658291;2361.98851399856\n" + "1992-04-15,3181.35;3359.12;3366.50,2280.5376344086;2407.97132616487;2413.26164874552\n" + "1992-05-15,3336.09;3396.88;3398.43,2388.03865425913;2431.5533285612;2432.66284896206\n" + "1992-06-15,3274.12;3318.52;3413.21,2335.3209700428;2366.99001426534;2434.52924393723\n" + "1992-07-15,3277.61;3393.78;3393.78,2332.81850533808;2415.50177935943;2415.50177935943\n" + "1992-08-15,3228.17;3257.35;3395.40,2291.1071682044;2311.81689141235;2409.79418026969\n" + "1992-09-15,3250.32;3271.66;3376.22,2300.29723991507;2315.39985845718;2389.39844302902\n" + "1992-10-15,3136.58;3226.28;3254.37,2211.97461212976;2275.23272214386;2295.04231311707\n" + "1992-11-15,3193.32;3305.16;3305.16,2248.81690140845;2327.57746478873;2327.57746478873\n" + "1992-12-15,3255.18;3301.11;3333.26,2293.99577167019;2326.36363636364;2349.02043692741\n" + "1993-01-15,3241.95;3310.03;3310.03,2273.4572230014;2321.19915848527;2321.19915848527\n" + "1993-02-15,3302.19;3370.81;3442.14,2307.61006289308;2355.56254367575;2405.40880503145\n" + "1993-03-15,3355.41;3435.11;3478.34,2336.6364902507;2392.13788300836;2422.24233983287\n" + "1993-04-15,3370.81;3427.55;3478.61,2340.84027777778;2380.24305555556;2415.70138888889\n" + "1993-05-15,3437.19;3527.43;3554.83,2383.62690707351;2446.20665742025;2465.2080443828\n" + "1993-06-15,3466.81;3516.08;3553.45,2400.8379501385;2434.95844875346;2460.8379501385\n" + "1993-07-15,3449.93;3539.47;3567.70,2389.14819944598;2451.15650969529;2470.70637119114\n" + "1993-08-15,3548.97;3651.25;3652.09,2450.94613259668;2521.58149171271;2522.16160220994\n" + "1993-09-15,3537.24;3555.12;3645.10,2437.79462439697;2450.11716057891;2512.12956581668\n" + "1993-10-15,3577.76;3680.59;3687.86,2455.56623198353;2526.14275909403;2531.13246396706\n" + "1993-11-15,3624.98;3683.95;3710.77,2486.26886145405;2526.7146776406;2545.109739369\n" + "1993-12-15,3697.08;3754.09;3794.33,2535.72016460905;2574.82167352538;2602.42112482853\n" + "1994-01-15,3756.60;3978.36;3978.36,2569.49384404925;2721.17647058824;2721.17647058824\n" + "1994-02-15,3832.02;3832.02;3975.54,2612.1472392638;2612.1472392638;2709.97955010225\n" + "1994-03-15,3626.75;3635.96;3895.65,2463.82472826087;2470.08152173913;2646.50135869565\n" + "1994-04-15,3593.35;3681.69;3705.78,2437.82225237449;2497.75440976934;2514.09769335142\n" + "1994-05-15,3629.04;3758.37;3766.35,2460.36610169491;2548.04745762712;2553.45762711864\n" + "1994-06-15,3624.96;3624.96;3814.83,2449.2972972973;2449.2972972973;2577.58783783784\n" + "1994-07-15,3646.65;3764.50;3764.50,2457.31132075472;2536.72506738544;2536.72506738544\n" + "1994-08-15,3747.02;3913.42;3917.30,2514.77852348993;2626.45637583893;2629.06040268456\n" + "1994-09-15,3831.75;3843.19;3953.88,2564.75903614458;2572.41633199465;2646.50602409639\n" + "1994-10-15,3775.56;3908.12;3936.04,2525.45819397993;2614.127090301;2632.80267558528\n" + "1994-11-15,3674.63;3739.23;3863.37,2454.66265865063;2497.81563126253;2580.74148296593\n" + "1994-12-15,3685.73;3834.44;3861.69,2462.07748830995;2561.41616566466;2579.61923847695\n" + "1995-01-15,3832.08;3843.86;3932.34,2549.62075848303;2557.45841650033;2616.32734530938\n" + "1995-02-15,3847.56;4011.05;4011.74,2549.74155069583;2658.08482438701;2658.54208084824\n" + "1995-03-15,3962.63;4157.69;4172.56,2617.3249669749;2746.16248348745;2755.98414795244\n" + "1995-04-15,4168.41;4321.27;4321.27,2744.18038183015;2844.81237656353;2844.81237656353\n" + "1995-05-15,4316.08;4465.14;4465.14,2835.7950065703;2933.73193166886;2933.73193166886\n" + "1995-06-15,4423.99;4556.10;4589.64,2900.97704918033;2987.60655737705;3009.6\n" + "1995-07-15,4585.15;4708.47;4736.29,3006.65573770492;3087.52131147541;3105.76393442623\n" + "1995-08-15,4580.62;4610.56;4701.42,2995.8273381295;3015.40876389797;3074.83322432963\n" + "1995-09-15,4647.54;4789.08;4801.80,3033.64229765013;3126.03133159269;3134.33420365535\n" + "1995-10-15,4703.82;4755.48;4802.45,3060.39037085231;3094.00130123617;3124.56083279115\n" + "1995-11-15,4766.68;5074.49;5105.56,3103.30729166667;3303.70442708333;3323.93229166667\n" + "1995-12-15,5059.32;5117.12;5216.47,3295.97394136808;3333.62866449511;3398.35179153094\n" + "1996-01-15,5032.94;5395.30;5395.30,3259.67616580311;3494.36528497409;3494.36528497409\n" + "1996-02-15,5373.99;5485.62;5630.49,3469.32859909619;3541.39444803099;3634.91930277598\n" + "1996-03-15,5470.45;5587.14;5683.60,3513.45536287733;3588.40077071291;3650.35324341683\n" + "1996-04-15,5485.98;5569.08;5689.74,3509.90403071017;3563.07101727447;3640.26871401152\n" + "1996-05-15,5420.95;5643.18;5778.00,3461.65389527459;3603.56321839081;3689.65517241379\n" + "1996-06-15,5624.71;5654.63;5719.27,3589.4767070836;3608.5705169113;3649.82131461391\n" + "1996-07-15,5346.55;5528.91;5729.98,3405.44585987261;3521.59872611465;3649.66878980892\n" + "1996-08-15,5594.75;5616.21;5733.47,3556.73871582962;3570.38143674507;3644.92689129053\n" + "1996-09-15,5606.96;5882.17;5894.74,3553.20659062104;3727.61089987326;3735.57667934094\n" + "1996-10-15,5904.90;6029.38;6094.23,3730.1958307012;3808.83133291219;3849.79785217941\n" + "1996-11-15,6021.93;6521.70;6547.79,3796.92938209332;4112.04287515763;4128.49306431274\n" + "1996-12-15,6268.35;6448.27;6560.91,3952.30138713745;4065.74401008827;4136.76544766709\n" + "1997-01-15,6442.49;6813.09;6883.90,4049.33375235701;4282.26901319925;4326.77561282212\n" + "1997-02-15,6746.90;6877.74;7067.46,4227.38095238095;4309.36090225564;4428.23308270677\n" + "1997-03-15,6583.48;6583.48;7085.16,4114.675;4114.675;4428.225\n" + "1997-04-15,6391.69;7008.99;7008.99,3989.81897627965;4375.14981273408;4375.14981273408\n" + "1997-05-15,6976.48;7331.04;7383.41,4357.57651467833;4579.03810118676;4611.74890693317\n" + "1997-06-15,7269.66;7672.79;7796.51,4535.0343106675;4786.5190268247;4863.69931378665\n" + "1997-07-15,7722.33;8222.61;8254.89,4811.42056074766;5123.1214953271;5143.23364485981\n" + "1997-08-15,7622.42;7622.42;8259.31,4740.31094527363;4740.31094527363;5136.3868159204\n" + "1997-09-15,7660.98;7945.26;7996.83,4752.46898263027;4928.82133995037;4960.81265508685\n" + "1997-10-15,7161.15;7442.08;8178.31,4431.4047029703;4605.24752475248;5060.8353960396\n" + "1997-11-15,7401.32;7823.13;7881.07,4582.86068111455;4844.04334365325;4879.91950464396\n" + "1997-12-15,7660.13;7908.25;8149.13,4748.99566026038;4902.82083075015;5052.15747055177\n" + "1998-01-15,7580.42;7906.50;7978.99,4690.85396039604;4892.63613861386;4937.49381188119\n" + "1998-02-15,8107.78;8545.72;8545.72,5007.89376158122;5278.39407041383;5278.39407041383\n" + "1998-03-15,8444.33;8799.81;8906.43,5206.12207151665;5425.28360049322;5491.01726263872\n" + "1998-04-15,8868.32;9063.37;9184.94,5457.42769230769;5577.45846153846;5652.27076923077\n" + "1998-05-15,8899.95;8899.95;9211.84,5466.79975429975;5466.79975429975;5658.37837837838\n" + "1998-06-15,8627.93;8952.02;9069.60,5293.20858895706;5492.03680981595;5564.1717791411\n" + "1998-07-15,8883.29;8883.29;9337.97,5443.19240196078;5443.19240196078;5721.79534313726\n" + "1998-08-15,7539.07;7539.07;8786.74,4613.87392900857;4613.87392900857;5377.44186046512\n" + "1998-09-15,7615.54;7842.62;8154.41,4654.97555012225;4793.77750611247;4984.35819070905\n" + "1998-10-15,7632.53;8592.10;8592.10,4653.98170731707;5239.08536585366;5239.08536585366\n" + "1998-11-15,8706.15;9116.55;9374.27,5308.62804878049;5558.87195121951;5716.01829268293\n" + "1998-12-15,8695.60;9181.43;9320.98,5305.43014032947;5601.84868822453;5686.99206833435\n" + "1999-01-15,9120.67;9358.83;9643.32,5551.22945830797;5696.18381010347;5869.33657942788\n" + "1999-02-15,9133.03;9306.58;9552.68,5551.99392097264;5657.49544072948;5807.10030395137\n" + "1999-03-15,9275.88;9786.16;10006.8,5621.74545454545;5931.00606060606;6064.72727272727\n" + "1999-04-15,9832.51;10789.0;10878.4,5916.07099879663;6491.57641395909;6545.3670276775\n" + "1999-05-15,10466.9;10559.7;11107.2,6297.77376654633;6353.61010830325;6683.03249097473\n" + "1999-06-15,10490.5;10970.8;10970.8,6311.97352587244;6600.96269554753;6600.96269554753\n" + "1999-07-15,10655.2;10655.2;11209.8,6391.84163167367;6391.84163167367;6724.5350929814\n" + "1999-08-15,10646.0;10829.3;11326.0,6371.03530819868;6480.73010173549;6777.97725912627\n" + "1999-09-15,10213.5;10337.0;11079.4,6083.08516974389;6156.64085765336;6598.8088147707\n" + "1999-10-15,10019.7;10729.9;10729.9,5957.01545778835;6379.25089179548;6379.25089179548\n" + "1999-11-15,10581.8;10877.8;11089.5,6287.46286393345;6463.33927510398;6589.12655971479\n" + "1999-12-15,10998.4;11497.1;11497.1,6534.99702911468;6831.31313131313;6831.31313131313\n" + "2000-01-15,10738.9;10940.5;11723.0,6361.90758293839;6481.33886255924;6944.90521327014\n" + "2000-02-15,9862.12;10128.3;11041.1,5808.0800942285;5964.84098939929;6502.41460541814\n" + "2000-03-15,9796.03;10921.9;11119.9,5721.98014018692;6379.61448598131;6495.26869158879\n" + "2000-04-15,10305.8;10733.9;11287.1,6016.22883829539;6266.14127262113;6589.08347927612\n" + "2000-05-15,10299.2;10522.3;10934.6,6005.36443148688;6135.45189504373;6375.86005830904\n" + "2000-06-15,10376.1;10447.9;10815.3,6018.61948955917;6060.26682134571;6273.37587006961\n" + "2000-07-15,10481.5;10522.0;10843.9,6065.68287037037;6089.12037037037;6275.40509259259\n" + "2000-08-15,10607.0;11215.1;11252.8,6138.31018518518;6490.21990740741;6512.03703703704\n" + "2000-09-15,10628.4;10650.9;11310.6,6118.82556131261;6131.77892918826;6511.57167530225\n" + "2000-10-15,9975.02;10971.1;10971.1,5732.77011494253;6305.22988505747;6305.22988505747\n" + "2000-11-15,10399.3;10414.5;10977.2,5973.1763354394;5981.90695002872;6305.11200459506\n" + "2000-12-15,10318.9;10788.0;10898.7,5930.40229885057;6200;6263.62068965517\n" + "2001-01-15,10525.4;10887.4;10945.8,6011.0793832096;6217.81838949172;6251.17075956596\n" + "2001-02-15,10441.9;10495.3;10983.6,5939.64732650739;5970.02275312855;6247.78156996587\n" + "2001-03-15,9389.48;9878.78;10858.3,5328.876276958;5606.57207718502;6162.48581157775\n" + "2001-04-15,9485.71;10735.0;10810.1,5362.18767665348;6068.40022611645;6110.85358959864\n" + "2001-05-15,10796.7;10911.9;11337.9,6075.80191333709;6140.63027574564;6380.36015756894\n" + "2001-06-15,10434.8;10502.4;11175.8,5862.24719101124;5900.22471910112;6278.5393258427\n" + "2001-07-15,10175.6;10522.8;10610.0,5732.7323943662;5928.33802816901;5977.46478873239\n" + "2001-08-15,9919.58;9949.75;10551.2,5588.49577464789;5605.49295774648;5944.33802816902\n" + "2001-09-15,8235.81;8847.56;10033.3,4619.07459338194;4962.17610768368;5627.20134604599\n" + "2001-10-15,8836.83;9075.14;9545.17,4972.89251547552;5107.0005627462;5371.50815981992\n" + "2001-11-15,9263.90;9851.56;9982.75,5222.04058624577;5553.30326944758;5627.25479143179\n" + "2001-12-15,9763.96;10021.6;10137.0,5525.727221279;5671.53367289191;5736.84210526316\n" + "2002-01-15,9618.24;9920.00;10259.7,5430.96555618295;5601.35516657256;5793.16770186335\n" + "2002-02-15,9625.44;10106.1;10145.7,5413.63329583802;5683.97075365579;5706.2429696288\n" + "2002-03-15,10281.7;10403.9;10635.3,5750.39149888143;5818.73601789709;5948.15436241611\n" + "2002-04-15,9819.87;9946.22;10381.7,5461.55172413793;5531.82424916574;5774.02669632926\n" + "2002-05-15,9808.04;9925.25;10353.1,5454.97219132369;5520.16129032258;5758.12013348165\n" + "2002-06-15,9120.11;9243.26;9796.80,5069.54419121734;5137.99888827126;5445.69205113952\n" + "2002-07-15,7702.34;8736.59;9379.50,4276.70183231538;4850.96612992782;5207.94003331483\n" + "2002-08-15,8043.63;8663.50;9053.64,4451.37244050913;4794.41062534588;5010.31543995573\n" + "2002-09-15,7591.93;7591.93;8602.61,4194.4364640884;4194.4364640884;4752.82320441989\n" + "2002-10-15,7286.27;8397.03;8538.24,4018.90237175951;4631.56646442361;4709.45394373966\n" + "2002-11-15,8358.95;8896.09;8931.68,4610.56260341975;4906.83397683398;4926.46442360728\n" + "2002-12-15,8303.78;8341.63;8862.57,4590.25981205086;4611.18297401879;4899.15422885572\n" + "2003-01-15,7945.13;8053.81;8842.62,4372.66373142543;4432.47660979637;4866.60429279031\n" + "2003-02-15,7749.87;7891.08;8109.82,4232.58874931731;4309.71054068815;4429.17531403605\n" + "2003-03-15,7524.06;7992.13;8521.97,4084.72312703583;4338.83279044517;4626.4766558089\n" + "2003-04-15,8069.86;8480.09;8515.66,4390.56583242655;4613.75952121872;4633.11207834603\n" + "2003-05-15,8454.25;8850.26;8850.26,4607.22070844687;4823.02997275204;4823.02997275204\n" + "2003-06-15,8897.81;8985.44;9323.02,4843.66358192706;4891.36635819271;5075.13336962439\n" + "2003-07-15,9036.04;9233.80;9284.57,4913.56171832518;5021.09842305601;5048.70581837955\n" + "2003-08-15,9036.32;9415.82;9428.90,4895.0812567714;5100.66088840737;5107.74647887324\n" + "2003-09-15,9275.06;9275.06;9659.13,5008.13174946004;5008.13174946004;5215.51295896328\n" + "2003-10-15,9469.20;9801.12;9812.98,5118.48648648649;5297.9027027027;5304.31351351351\n" + "2003-11-15,9619.42;9782.46;9858.46,5213.77777777778;5302.14634146341;5343.33875338753\n" + "2003-12-15,9853.64;10453.9;10453.9,5346.52197504069;5672.21920781335;5672.21920781335\n" + "2004-01-15,10409.9;10488.1;10702.5,5620.89632829374;5663.12095032397;5778.88768898488\n" + "2004-02-15,10470.7;10583.9;10737.7,5623.3619763695;5684.15682062299;5766.75617615467\n" + "2004-03-15,10048.2;10357.7;10678.1,5361.89967982924;5527.05442902882;5698.02561366062\n" + "2004-04-15,10225.6;10225.6;10570.8,5439.14893617021;5439.14893617021;5622.76595744681\n" + "2004-05-15,9906.91;10188.5;10317.2,5238.97937599154;5387.89000528821;5455.94923320994\n" + "2004-06-15,10195.9;10435.5;10479.6,5374.7496046389;5501.05429625725;5524.30152872957\n" + "2004-07-15,9961.92;10139.7;10334.2,5259.72544878564;5353.59028511088;5456.28299894403\n" + "2004-08-15,9814.59;10173.9;10195.0,5179.20316622691;5368.81266490765;5379.94722955145\n" + "2004-09-15,9988.54;10080.3;10341.2,5259.89468141127;5308.21484992101;5445.60294892048\n" + "2004-10-15,9749.99;10027.5;10239.9,5107.38082765846;5252.75013095862;5364.01257202724\n" + "2004-11-15,10035.7;10428.0;10572.6,5254.29319371728;5459.68586387435;5535.39267015707\n" + "2004-12-15,10440.6;10783.0;10854.5,5486.38991066737;5666.31634261692;5703.88859695218\n" + "2005-01-15,10368.6;10489.9;10729.4,5437.1263765076;5500.73413738857;5626.32406921867\n" + "2005-02-15,10551.9;10766.2;10841.6,5501.51199165798;5613.24296141814;5652.55474452555\n" + "2005-03-15,10405.7;10503.8;10940.6,5383.1867563373;5433.93688566994;5659.90688049664\n" + "2005-04-15,10012.4;10192.5;10546.3,5145.11819116136;5237.66700924974;5419.47584789311\n" + "2005-05-15,10140.1;10467.5;10542.6,5216.10082304527;5384.51646090535;5423.14814814815\n" + "2005-06-15,10275.0;10275.0;10623.1,5282.7763496144;5282.7763496144;5461.74807197943\n" + "2005-07-15,10270.7;10640.9;10705.6,5256.24360286592;5445.7011258956;5478.81269191402\n" + "2005-08-15,10397.3;10481.6;10697.6,5293.94093686354;5336.86354378819;5446.84317718941\n" + "2005-09-15,10378.0;10568.7;10682.9,5220.32193158954;5316.24748490946;5373.6921529175\n" + "2005-10-15,10215.2;10440.1;10535.5,5128.1124497992;5241.0140562249;5288.90562248996\n" + "2005-11-15,10406.8;10805.9;10931.6,5266.5991902834;5468.57287449393;5532.18623481781\n" + "2005-12-15,10717.5;10717.5;10912.6,5445.88414634146;5445.88414634146;5545.02032520325\n" + "2006-01-15,10667.4;10864.9;11043.4,5379.42511346445;5479.02168431669;5569.03681290973\n" + "2006-02-15,10749.8;10993.4;11137.2,5410.06542526422;5532.66230498239;5605.03271263211\n" + "2006-03-15,10958.6;11109.3;11317.4,5484.78478478478;5560.21021021021;5664.36436436436\n" + "2006-04-15,11073.8;11367.1;11382.5,5495.68238213399;5641.24069478908;5648.88337468983\n" + "2006-05-15,11094.4;11168.3;11642.7,5478.71604938272;5515.20987654321;5749.48148148148\n" + "2006-06-15,10706.1;11150.2;11260.3,5276.54016757023;5495.41646131099;5549.67964514539\n" + "2006-07-15,10739.4;11185.7;11228.0,5277.34643734644;5496.65847665848;5517.44471744472\n" + "2006-08-15,11076.2;11381.2;11382.9,5432.17263364394;5581.75576262874;5582.58950465915\n" + "2006-09-15,11331.4;11679.1;11718.5,5584.7215377033;5756.08674223756;5775.50517496304\n" + "2006-10-15,11670.4;12080.7;12163.7,5783.15163528246;5986.47175421209;6027.60158572844\n" + "2006-11-15,11986.0;12221.9;12342.6,5948.38709677419;6065.45905707196;6125.35980148883\n" + "2006-12-15,12194.1;12463.2;12510.6,6042.66600594648;6176.01585728444;6199.50445986125\n" + "2007-01-15,12398.0;12621.7;12621.8,6125.00988064185;6235.52485969489;6235.57426290412\n" + "2007-02-15,12216.2;12268.6;12786.6,6003.07618219254;6028.82569447516;6283.37239986437\n" + "2007-03-15,12050.4;12354.4;12481.0,5868.16782889867;6016.20631890607;6077.85655849468\n" + "2007-04-15,12382.3;13062.9;13120.9,5990.87504717301;6320.16682310364;6348.22871408804\n" + "2007-05-15,13136.1;13627.6;13633.1,6316.9815675959;6553.33759719931;6555.9824764726\n" + "2007-06-15,13266.7;13408.6;13676.3,6367.44547688527;6435.55137459684;6564.03586238673\n" + "2007-07-15,13212.0;13212.0;14000.4,6342.8052943125;6342.8052943125;6721.29967018565\n" + "2007-08-15,12845.8;13357.7;13657.9,6178.3307762232;6424.53479032499;6568.91932838585\n" + "2007-09-15,13113.4;13895.6;13912.9,6289.70214398772;6664.87601323804;6673.17377332246\n" + "2007-10-15,13522.0;13930.0;14164.5,6471.83826626335;6667.11337443045;6779.34870008041\n" + "2007-11-15,12743.4;13371.7;13660.9,6063.17532365578;6362.11383738468;6499.71214738054\n" + "2007-12-15,13167.2;13264.8;13727.0,6269.02054885829;6315.48877335314;6535.54628730313\n" + "2008-01-15,11971.2;12650.4;13056.7,5671.40420693576;5993.17794201251;6185.66420314573\n" + "2008-02-15,12182.1;12266.4;12743.2,5754.60690717218;5794.42872461536;6019.66054616827\n" + "2008-03-15,11740.2;12262.9;12548.6,5498.20164100259;5742.99389307257;5876.79367577086\n" + "2008-04-15,12302.1;12820.1;12891.9,5726.62145114815;5967.75019434604;6001.17305875069\n" + "2008-05-15,12479.6;12638.3;13058.2,5760.73710255179;5833.99497765796;6027.82599062004\n" + "2008-06-15,11346.5;11350.0;12604.5,5185.430614903;5187.03013961566;5760.34549733793\n" + "2008-07-15,10962.5;11378.0;11632.4,4983.77007146624;5172.66461784656;5288.31990689385\n" + "2008-08-15,11284.2;11543.6;11782.4,5150.58013748026;5268.98113069753;5377.97942360534\n" + "2008-09-15,10365.5;10850.7;11532.9,4737.79955481002;4959.57181316647;5271.387630666\n" + "2008-10-15,8175.77;9325.01;10831.1,3775.06429702687;4305.71216171914;5001.13125828243\n" + "2008-11-15,7552.29;8829.04;9625.28,3555.27362598564;4156.30928563022;4531.14275626692\n" + "2008-12-15,8149.09;8776.39;8934.18,3876.31048195293;4174.70080103507;4249.75740624465\n" + "2009-01-15,7949.09;8000.86;9034.69,3764.78973965512;3789.30866758548;4278.94365430064\n" + "2009-02-15,7062.93;7062.93;8280.59,3328.54052678458;3328.54052678458;3902.38603535461\n" + "2009-03-15,6547.05;7608.92;7924.56,3077.93746385908;3577.15000305582;3725.54052719913\n" + "2009-04-15,7761.60;8168.12;8185.73,3639.84243106359;3830.48208591259;3838.74038641906\n" + "2009-05-15,8212.41;8500.33;8574.65,3840.15879844381;3974.79144845129;4009.54380517732\n" + "2009-06-15,8299.86;8447.00;8799.26,3847.99692155054;3916.21424895569;4079.52970193748\n" + "2009-07-15,8146.52;9171.61;9171.61,3782.90326025883;4258.91219451036;4258.91219451036\n" + "2009-08-15,9135.34;9496.28;9580.63,4232.5768877934;4399.8072592826;4438.88821965029\n" + "2009-09-15,9280.67;9712.28;9829.87,4297.2232125907;4497.07133894216;4551.51896800004\n" + "2009-10-15,9487.67;9712.73;10092.2,4388.84340147194;4492.9525342659;4668.48924723722\n"; }; dygraphs-2.2.1/gallery/demo.js010064400000000000000000000035131437353256000132170ustar00/*global Gallery,Dygraph,data */ //galleryActive=false Gallery.register( 'demo', { name: 'Interesting Shapes', title: 'The original demo!', setup: function(parent) { parent.innerHTML = "(Mouse over to highlight individual values. Click and drag to zoom. Double-click to zoom out.)
" + "" + "
" + "
" + "
" + "
" + "
"; }, run: function() { new Dygraph( document.getElementById("demodiv"), function() { var zp = function(x) { if (x < 10) return "0"+x; else return x; }; var r = "date,parabola,line,another line,sine wave\n"; for (var i=1; i<=31; i++) { r += "2006-10-" + zp(i); r += "," + 10*(i*(31-i)); r += "," + 10*(8*i); r += "," + 10*(250 - 8*i); r += "," + 10*(125 + 125 * Math.sin(0.3*i)); r += "\n"; } return r; }, { labelsDiv: document.getElementById('status'), labelsSeparateLines: true, labelsKMB: true, legend: 'always', colors: ["rgb(51,204,204)", "rgb(255,100,100)", "#00DD55", "rgba(50,50,200,0.4)"], width: 640, height: 480, title: 'Interesting Shapes', xlabel: 'Date', ylabel: 'Count', axisLineColor: 'white', drawXGrid: false } ); } }); dygraphs-2.2.1/gallery/drawing.js010064400000000000000000000165641437353256000137400ustar00/*global Gallery,Dygraph,data */ //galleryActive=true Gallery.register( 'drawing', { name: 'Time Series Drawing Demo', title: 'Time Series Drawing Demo', setup: function(parent) { parent.innerHTML = [ "
", "
", "
", "
", "
", "
"].join("\n"); }, run: function() { var change_tool; // defined below. var zoom = document.getElementById('tool_zoom'); zoom.onclick = function() { change_tool(zoom); }; var pencil = document.getElementById('tool_pencil'); pencil.onclick = function() { change_tool(pencil); }; var eraser = document.getElementById('tool_eraser'); eraser.onclick = function() { change_tool(eraser); }; var start_date = new Date("2002/12/29").getTime(); var end_date = new Date().getTime(); var data = []; for (var d = start_date; d < end_date; d += 604800 * 1000) { var millis = d + 2 * 3600 * 1000; data.push( [ new Date(Dygraph.dateString_(millis)), 50 ]); } var isDrawing = false; var lastDrawRow = null, lastDrawValue = null; var tool = 'pencil'; var valueRange = [0, 100]; function setPoint(event, g, context) { var graphPos = Dygraph.findPos(g.graphDiv); var canvasx = Dygraph.pageX(event) - graphPos.x; var canvasy = Dygraph.pageY(event) - graphPos.y; var xy = g.toDataCoords(canvasx, canvasy); var x = xy[0], value = xy[1]; var rows = g.numRows(); var closest_row = -1; var smallest_diff = -1; // TODO(danvk): binary search for (var row = 0; row < rows; row++) { var date = g.getValue(row, 0); // millis var diff = Math.abs(date - x); if (smallest_diff < 0 || diff < smallest_diff) { smallest_diff = diff; closest_row = row; } } if (closest_row != -1) { if (lastDrawRow === null) { lastDrawRow = closest_row; lastDrawValue = value; } var coeff = (value - lastDrawValue) / (closest_row - lastDrawRow); if (closest_row == lastDrawRow) coeff = 0.0; var minRow = Math.min(lastDrawRow, closest_row); var maxRow = Math.max(lastDrawRow, closest_row); for (var row = minRow; row <= maxRow; row++) { if (tool == 'pencil') { var val = lastDrawValue + coeff * (row - lastDrawRow); val = Math.max(valueRange[0], Math.min(val, valueRange[1])); data[row][1] = val; if (val === null || value === undefined || isNaN(val)) { console.log(val); } } else if (tool == 'eraser') { data[row][1] = null; } } lastDrawRow = closest_row; lastDrawValue = value; g.updateOptions({ file: data }); g.setSelection(closest_row); // prevents the dot from being finnicky. } } function finishDraw() { isDrawing = false; lastDrawRow = null; lastDrawValue = null; } change_tool = function(tool_div) { var ids = ['tool_zoom', 'tool_pencil', 'tool_eraser']; for (var i = 0; i < ids.length; i++) { var div = document.getElementById(ids[i]); if (div == tool_div) { div.style.backgroundPosition = -(i * 32) + 'px -32px'; } else { div.style.backgroundPosition = -(i * 32) + 'px 0px'; } } tool = tool_div.id.replace('tool_', ''); var dg_div = document.getElementById("draw_div"); if (tool == 'pencil') { dg_div.style.cursor = 'url(../common/cursor-pencil.png) 2 30, auto'; } else if (tool == 'eraser') { dg_div.style.cursor = 'url(../common/cursor-eraser.png) 10 30, auto'; } else if (tool == 'zoom') { dg_div.style.cursor = 'crosshair'; } }; change_tool(document.getElementById("tool_pencil")); new Dygraph(document.getElementById("draw_div"), data, { valueRange: valueRange, labels: [ 'Date', 'Value' ], interactionModel: { // the next line is required when using the // defaultInteractionModel mousedown function. willDestroyContextMyself: true, mousedown: function (event, g, context) { if (tool == 'zoom') { Dygraph.defaultInteractionModel.mousedown(event, g, context); } else { // prevents mouse drags from selecting page text. if (event.preventDefault) { event.preventDefault(); // Firefox, Chrome, etc. } else { event.returnValue = false; // IE event.cancelBubble = true; } isDrawing = true; setPoint(event, g, context); } }, mousemove: function (event, g, context) { // note that the defaultInteractionModel dynamically binds // its own mousemove event inside the mousedown handler if (tool != 'zoom') { if (!isDrawing) return; setPoint(event, g, context); } }, mouseup: function(event, g, context) { // note that the defaultInteractionModel dynamically binds // its own mouseup event inside the mousedown handler if (tool != 'zoom') { finishDraw(); } }, mouseout: function(event, g, context) { // note that the defaultInteractionModel does not use // the mouseout event, instead it detects when the mouse // is outside the chart using a dynamically bound // mousemove event }, dblclick: function(event, g, context) { Dygraph.defaultInteractionModel.dblclick(event, g, context); }, mousewheel: function(event, g, context) { var normal = event.detail ? event.detail * -1 : event.wheelDelta / 40; var percentage = normal / 50; var axis = g.xAxisRange(); var xOffset = g.toDomCoords(axis[0], null)[0]; var x = event.offsetX - xOffset; var w = g.toDomCoords(axis[1], null)[0] - xOffset; var xPct = w === 0 ? 0 : (x / w); var delta = axis[1] - axis[0]; var increment = delta * percentage; var foo = [increment * xPct, increment * (1 - xPct)]; var dateWindow = [ axis[0] + foo[0], axis[1] - foo[1] ]; g.updateOptions({ dateWindow: dateWindow }); event.preventDefault(); } }, strokeWidth: 1.5, gridLineColor: 'rgb(196, 196, 196)', axes: { x: { drawAxis: false }, y: { drawGrid: false } } }); window.onmouseup = finishDraw; } }); dygraphs-2.2.1/gallery/dygraph-simple.js010064400000000000000000000014641437353256000152230ustar00/*global Gallery,Dygraph,data */ //galleryActive=false Gallery.register( 'dygraph-simple', { name: 'Minimal Example', setup: function(parent) { parent.innerHTML = "

Minimal example of a dygraph chart:

" + "

Same data, specified in a parsed format:

"; }, run: function() { new Dygraph(document.getElementById("graphdiv"), "Date,Temperature\n" + "2008-05-07,75\n" + "2008-05-08,70\n" + "2008-05-09,80\n"); new Dygraph(document.getElementById("graphdiv2"), [ [ new Date("2008/05/07"), 75], [ new Date("2008/05/08"), 70], [ new Date("2008/05/09"), 80] ], { labels: [ "Date", "Temperature" ] }); } }); dygraphs-2.2.1/gallery/dynamic-update.js010064400000000000000000000027601437353256000152020ustar00/*global Gallery,Dygraph,data */ //galleryActive=true Gallery.register( 'dynamic-update', { name: 'Dynamic Update', title: 'Live random data', setup: function(parent) { parent.innerHTML = [ "
", "

This test is modeled after a ", "highcharts", "test. New points should appear once per second. Try zooming and ", "panning over to the right edge to watch them show up.

"].join("\n"); }, run: function() { var data = []; var t = new Date(); for (var i = 10; i >= 0; i--) { var x = new Date(t.getTime() - i * 1000); data.push([x, Math.random()]); } var g = new Dygraph(document.getElementById("div_g"), data, { drawPoints: true, showRoller: true, valueRange: [0.0, 1.2], labels: ['Time', 'Random'] }); // It sucks that these things aren't objects, and we need to store state in window. window.intervalId = setInterval(function() { var x = new Date(); // current time var y = Math.random(); data.push([x, y]); g.updateOptions( { 'file': data } ); }, 1000); }, clean: function() { clearInterval(window.intervalId); } }); dygraphs-2.2.1/gallery/gallery-template.js010064400000000000000000000006641437353256000155470ustar00// Use this as a template for new Gallery entries. /*global Gallery,Dygraph,data */ // Set this to true upon adding to index.html //galleryActive=false Gallery.register( 'id', { name: 'name', title: 'title', setup: function(parent) { parent.innerHTML = "
"; }, run: function() { new Dygraph(document.getElementById("blah"), "X,Y\n10,12345\n11,12345\n", {}); } }); dygraphs-2.2.1/gallery/gallery.css010064400000000000000000000062051437353256000141070ustar00body { font-family: Helvetica Neue, Arial, Helvetica, sans-serif; font-size: 90%; } aside { font-size: 90%; } #toc { vertical-align: top; width: 200px; } #gap { style="width:0.5em;" } #rhs, #lhs { vertical-align: top; } #subtitle a:visited { color: blue; } #workarea { border-style: solid; border-color: #ddd; padding: 4px; } #toc .entry { background-color: #eee; padding: .7em; /* These two lines result in indenting wrapped lines forward a little bit. */ /* text-indent: -1em; padding-left: 1em; */ } #toc .entry:hover { color: blue; cursor: pointer; } #toc .entry .selected { color: #090; } #code a:hover { color: blue; cursor: pointer; } #demotitle { text-align: center; font-size: 1.5em; vertical-align: bottom; } a { text-decoration: none; } /* CSS for drawing tool */ #workarea #drawing #tool_zoom { background: url('../common/tool-palette.png'); background-position: 0px 0px; width: 32px; height: 33px; margin-left: 50px; display: inline-block; } #workarea #drawing #tool_pencil { background: url('../common/tool-palette.png'); background-position: -32px 0px; width: 32px; height: 33px; display: inline-block; } #workarea #drawing #tool_eraser { background: url('../common/tool-palette.png'); background-position: -64px 0px; width: 33px; height: 33px; display: inline-block; } #workarea #drawing #toolbar { display: inline-block; } /* CSS for independent series */ #workarea #independent-series .thinborder { border-width: 1px; border-spacing: 0px; border-style: solid; border-color: black; border-collapse: collapse; } #workarea #independent-series .thinborder td, #workarea #independent-series .thinborder th { border-width: 1px; padding: 5px; border-style: solid; border-color: black; } /* CSS for resize */ #workarea #resize #div_g { /* The left and top are just guesses, this needs a proper run-through */ position: absolute; left: 200px; right: 10px; top: 150px; bottom: 10px; } /* CSS for styled-chart-label */ #workarea #styled-chart-labels .infotext { } #workarea #styled-chart-labels #div_g .dygraph-label { /* This applies to the title, x-axis label and y-axis label */ font-family: Georgia, Verdana, serif; } #workarea #styled-chart-labels #div_g .dygraph-title { /* This rule only applies to the chart title */ font-size: 36px; text-shadow: gray 2px 2px 2px; /* color, delta-x, delta-y, blur radius */ } #workarea #styled-chart-labels #div_g .dygraph-ylabel { /* This rule only applies to the y-axis label */ font-size: 18px; text-shadow: gray -2px 2px 2px; /* (offsets are in a rotated frame) */ } #workarea #styled-chart-labels .chart { border: 1px dashed black; margin: 5px 5px 5px 50px; padding: 2px; } #workarea #temperature-sf-ny #bordered { border: 1px solid red; } #workarea #highlighted-series .few .dygraph-legend > span.highlight { border: 1px solid grey; } #workarea #highlighted-series .many .dygraph-legend > span { display: none; } #workarea #highlighted-series .many .dygraph-legend > span.highlight { display: inline; } #workarea #edge-padding fieldset { display: inline-block; vertical-align: top; } dygraphs-2.2.1/gallery/gallery.js010064400000000000000000000131261437353256000137330ustar00/*jshint loopfunc:true */ /*global Dygraph,$,TextArea,jQuery */ var Gallery = {}; Gallery.entries = {}; Gallery.entryOrder = []; Gallery.runningDemo = null; /* * Shortcut for creating HTML associated with a parent. */ Gallery.create = function(type, parent, className) { var elem = document.createElement(type); parent.appendChild(elem); if (className) { elem.className = className; } return elem; }; Gallery.start = function() { Gallery.toc = document.getElementById("toc"); Gallery.workarea = document.getElementById("workarea"); Gallery.subtitle = Gallery.create("div", Gallery.workarea); Gallery.subtitle.id = "subtitle"; Gallery.workareaChild = Gallery.create("div", Gallery.workarea); Gallery.demotitle = document.getElementById("demotitle"); Gallery.textarea = new TextArea(); Gallery.textarea.cancel.style.display = "none"; Gallery.textarea.width = 600; Gallery.textarea.height = 400; for (var idx in Gallery.entryOrder) { var id = Gallery.entryOrder[idx]; var demo = Gallery.entries[id]; var div = Gallery.create("div", Gallery.toc, "entry"); div.id = id + "-toc"; var innerDiv = Gallery.create("div", div, ""); // Storing extra data in the demo object. demo.div = div; demo.innerDiv = innerDiv; innerDiv.textContent = demo.name; div.onclick = function(demo, id) { return function() { if (Gallery.runningDemo !== null) { Gallery.runningDemo.innerDiv.className = ""; if (Gallery.runningDemo.clean) { Gallery.runningDemo.clean(Gallery.workareaChild); } } Gallery.subtitle.innerHTML = ""; Gallery.workareaChild.id = id; location.hash = "g/" + id; Gallery.workareaChild.innerHTML=''; if (demo.setup) { demo.setup(Gallery.workareaChild); } Gallery.demotitle.textContent = demo.title ? demo.title : ""; demo.innerDiv.className = "selected"; var codeSpan = Gallery.create("span", Gallery.subtitle); codeSpan.id = "code"; var htmlLink = Gallery.create("a", codeSpan); htmlLink.textContent = "HTML"; Gallery.create("span", codeSpan).textContent = " | "; var javascriptLink = Gallery.create("a", codeSpan); javascriptLink.textContent = "Javascript"; var css = getCss(id); var cssLink; if (css) { Gallery.create("span", codeSpan).textContent = " | "; cssLink = Gallery.create("a", codeSpan); cssLink.textContent = "CSS"; } var jsFiddleForm = Gallery.create("form", codeSpan); var jsfs = $(jsFiddleForm); jsFiddleForm.method = "post"; jsFiddleForm.action = "http://jsfiddle.net/api/post/jquery/1.4/"; jsFiddleForm.target = "_blank"; jsfs.html("\n" + "\n" + "\n" + "\n"); var javascript = demo.run.toString(); var html = Gallery.workareaChild.innerHTML; // tweak for use in jsfiddle javascript = " $(document).ready(" + javascript + "\n);"; jQuery('', { name: 'html' }) .val(html) .hide() .appendTo(jsfs); jQuery('', { name: 'js' }) .val(javascript) .hide() .appendTo(jsfs); if (css) { jQuery('', { name: 'css' }) .val(css) .hide() .appendTo(jsfs); } jQuery('', { type: 'text', name: 'title', value: 'title tbd' }) .hide() .appendTo(jsfs); jQuery('', { type: 'text', name: 'description', value: 'desc tbd' }) .hide() .appendTo(jsfs); htmlLink.onclick = function() { Gallery.textarea.show("HTML", html); }; javascriptLink.onclick = function() { Gallery.textarea.show("Javascript", javascript); }; if (css) { cssLink.onclick = function() { Gallery.textarea.show("CSS", css); }; } demo.run(Gallery.workareaChild); Gallery.runningDemo = demo; }; }(demo, id); } Gallery.hashChange(); window.onhashchange = Gallery.setHash; }; var getCss = function(id) { for (var i = 0; i < document.styleSheets.length; i++) { var ss = document.styleSheets[i]; if (ss.title == "gallery") { try { var rules = ss.rules || ss.cssRules; if (rules) { var arry = []; for (var j = 0; j < rules.length; j++) { var rule = rules[j]; var cssText = rule.cssText; var key = "#workarea #" + id + " "; if (cssText.indexOf(key) === 0) { arry.push(cssText.substr(key.length)); } } return arry.join("\n\n"); } } catch(e) { // security error console.log(e); } } } return "not found"; }; Gallery.register = function(id, demo) { if (Gallery.entries[id]) { throw id + " already registered"; } Gallery.entries[id] = demo; Gallery.entryOrder.push(id); }; Gallery.hashChange = function(event) { if (location.hash) { if (location.hash.indexOf("#g/") === 0) { var id = location.hash.substring(3) + "-toc"; var elem = document.getElementById(id); elem.onclick(); return; } } Gallery.workareaChild.innerHTML = "

Select a demo from the gallery on the left

"; }; dygraphs-2.2.1/gallery/highlighted-region.js010064400000000000000000000027021437353256000160330ustar00/*global Gallery,Dygraph,data */ //galleryActive=true Gallery.register( 'highlighted-region', { name: 'Highlighted Region', title: 'Draws a time series with an unusual region highlighted', setup: function(parent) { parent.innerHTML = [ "
", "

When you zoom and pan, the region remains highlighted.

"].join("\n"); }, run: function() { // A basic sinusoidal data series. var data = []; for (var i = 0; i < 1000; i++) { var base = 10 * Math.sin(i / 90.0); data.push([i, base, base + Math.sin(i / 2.0)]); } // Shift one portion out of line. var highlight_start = 450; var highlight_end = 500; for (var i = highlight_start; i <= highlight_end; i++) { data[i][2] += 5.0; } new Dygraph( document.getElementById("div_g"), data, { labels: ['X', 'Est.', 'Actual'], animatedZooms: true, underlayCallback: function(canvas, area, g) { var bottom_left = g.toDomCoords(highlight_start, -20); var top_right = g.toDomCoords(highlight_end, +20); var left = bottom_left[0]; var right = top_right[0]; canvas.fillStyle = "rgba(255, 255, 102, 1.0)"; canvas.fillRect(left, area.y, right - left, area.h); } } ); } }); dygraphs-2.2.1/gallery/highlighted-series.js010064400000000000000000000037531437353256000160510ustar00/*global Gallery,Dygraph,data */ //galleryActive=true Gallery.register( 'highlighted-series', { name: 'Highlight Closest Series', title: 'Interactive closest-series highlighting', setup: function(parent) { parent.innerHTML = "
"; }, run: function() { var getData = function(numSeries, numRows, isStacked) { var data = []; for (var j = 0; j < numRows; ++j) { data[j] = [j]; } for (var i = 0; i < numSeries; ++i) { var val = 0; for (var j = 0; j < numRows; ++j) { if (isStacked) { val = Math.random(); } else { val += Math.random() - 0.5; } data[j][i + 1] = val; } } return data; }; var makeGraph = function(className, numSeries, numRows, isStacked) { var demo = document.getElementById('demo'); var div = document.createElement('div'); div.className = className; div.style.display = 'inline-block'; div.style.margin = '4px'; demo.appendChild(div); var labels = ['x']; for (var i = 0; i < numSeries; ++i) { var label = '' + i; label = 's' + '000'.substr(label.length) + label; labels[i + 1] = label; } var g = new Dygraph( div, getData(numSeries, numRows, isStacked), { width: 480, height: 320, labels: labels.slice(), stackedGraph: isStacked, highlightCircleSize: 2, strokeWidth: 1, strokeBorderWidth: isStacked ? null : 1, highlightSeriesOpts: { strokeWidth: 3, strokeBorderWidth: 1, highlightCircleSize: 5 } }); var onclick = function(ev) { if (g.isSeriesLocked()) { g.clearSelection(); } else { g.setSelection(g.getSelection(), g.getHighlightSeries(), true); } }; g.updateOptions({clickCallback: onclick}, true); g.setSelection(false, 's005'); //console.log(g); }; makeGraph("few", 20, 50, false); makeGraph("few", 10, 20, true); makeGraph("many", 75, 50, false); makeGraph("many", 40, 50, true); } }); dygraphs-2.2.1/gallery/highlighted-weekends.js010064400000000000000000000075631437353256000163670ustar00/*global Gallery,Dygraph,data */ //galleryActive=true Gallery.register( 'highlighted-weekends', { name: 'Highlighted Weekends', title: 'Draws a time series with weekends highlighted', setup: function(parent) { parent.innerHTML = [ "
", "

When you zoom and pan, the weekend regions remain highlighted.

"].join("\n"); }, run: function() { // Some sample data var data = "2011-01-01," + Math.random()*100 + "\n" + "2011-01-02," + Math.random()*100 + "\n" + "2011-01-03," + Math.random()*100 + "\n" + "2011-01-04," + Math.random()*100 + "\n" + "2011-01-05," + Math.random()*100 + "\n" + "2011-01-06," + Math.random()*100 + "\n" + "2011-01-07," + Math.random()*100 + "\n" + "2011-01-08," + Math.random()*100 + "\n" + "2011-01-09," + Math.random()*100 + "\n" + "2011-01-10," + Math.random()*100 + "\n" + "2011-01-11," + Math.random()*100 + "\n" + "2011-01-12," + Math.random()*100 + "\n" + "2011-01-13," + Math.random()*100 + "\n" + "2011-01-14," + Math.random()*100 + "\n" + "2011-01-15," + Math.random()*100 + "\n" + "2011-01-16," + Math.random()*100 + "\n" + "2011-01-17," + Math.random()*100 + "\n" + "2011-01-18," + Math.random()*100 + "\n" + "2011-01-19," + Math.random()*100 + "\n" + "2011-01-20," + Math.random()*100 + "\n" + "2011-01-21," + Math.random()*100 + "\n" + "2011-01-22," + Math.random()*100 + "\n" + "2011-01-23," + Math.random()*100 + "\n" + "2011-01-24," + Math.random()*100 + "\n" + "2011-01-25," + Math.random()*100 + "\n" + "2011-01-26," + Math.random()*100 + "\n" + "2011-01-27," + Math.random()*100 + "\n" + "2011-01-28," + Math.random()*100 + "\n" + "2011-01-29," + Math.random()*100 + "\n" + "2011-01-30," + Math.random()*100 + "\n" + "2011-01-31," + Math.random()*100 + "\n"; new Dygraph( document.getElementById("div_g"), data, { labels: ['Date','Value'], underlayCallback: function(canvas, area, g) { canvas.fillStyle = "rgba(255, 255, 102, 1.0)"; function highlight_period(x_start, x_end) { var canvas_left_x = g.toDomXCoord(x_start); var canvas_right_x = g.toDomXCoord(x_end); var canvas_width = canvas_right_x - canvas_left_x; canvas.fillRect(canvas_left_x, area.y, canvas_width, area.h); } var min_data_x = g.getValue(0,0); var max_data_x = g.getValue(g.numRows()-1,0); // get day of week var d = new Date(min_data_x); var dow = d.getUTCDay(); var w = min_data_x; // starting on Sunday is a special case if (dow === 0) { highlight_period(w,w+12*3600*1000); } // find first saturday while (dow != 6) { w += 24*3600*1000; d = new Date(w); dow = d.getUTCDay(); } // shift back 1/2 day to center highlight around the point for the day w -= 12*3600*1000; while (w < max_data_x) { var start_x_highlight = w; var end_x_highlight = w + 2*24*3600*1000; // make sure we don't try to plot outside the graph if (start_x_highlight < min_data_x) { start_x_highlight = min_data_x; } if (end_x_highlight > max_data_x) { end_x_highlight = max_data_x; } highlight_period(start_x_highlight,end_x_highlight); // calculate start of highlight for next Saturday w += 7*24*3600*1000; } } }); } }); dygraphs-2.2.1/gallery/independent-series.js010064400000000000000000000115241437353256000160610ustar00/*global Gallery,Dygraph,data */ //galleryActive=true Gallery.register( 'independent-series', { name: 'Independent Series', title: 'Independent Series', setup: function(parent) { parent.innerHTML = [ "

By using the connectSeparated attribute, it’s possible to display a chart of several time series with completely independent x-values.

", "", "

The trick is to specify values for the series at the union of the x-values of all series. For one series' x values, specify null for each of the other series.

", "", "
", "

For example, say you had two series:

", " ", " ", " ", "
", " ", "
", " ", " ", " ", " ", "
xA
22
46
64
", "
", " ", " ", " ", " ", " ", "
xB
13
37
55
", "
", "", "

Then you would specify the following CSV or native data:

", " ", " ", " ", "
", "(CSV) ", "
 ",
    "
", "(native) ", "
 ",
    "
", "", "

Encoding a gap

", "

There's one extra wrinkle. What if one of the series has a missing ", "value, i.e. what if your series are something like

", "", " ", " ", " ", "
", " ", "
", " ", " ", " ", " ", " ", " ", "
xA
22
44
6(gap)
88
1010
", "
", " ", " ", " ", " ", " ", "
xB
13
35
57
", "
", "", "
", "

The gap would normally be encoded as a null, or missing value. But when you use connectSeparatedPoints, that has a special meaning. Instead, you have to use NaN. This is a bit of a hack, but it gets the job done.

", "", " ", " ", " ", "
", "(CSV) ", "
 ",
    "
", "(native) ", "
 ",
    "
"].join("\n"); }, run: function() { document.getElementById("csv1").textContent = "X,A,B\n" + "1,,3\n" + "2,2,\n" + "3,,7\n" + "4,6,\n" + "5,,5\n" + "6,4,"; document.getElementById("native1").textContent = "[\n" + " [1, null, 3],\n" + " [2, 2, null],\n" + " [3, null, 7],\n" + " [4, 6, null],\n" + " [5, null, 5],\n" + " [6, 4, null]\n" + "]"; document.getElementById("csv2").textContent = "X,A,B\n" + "1,,3\n" + "2,2,\n" + "3,,5\n" + "4,4,\n" + "6,NaN,\n" + "8,8,\n" + "10,10,"; document.getElementById("native2").textContent = "[\n" + " [1, null, 3],\n" + " [2, 2, null],\n" + " [3, null, 5],\n" + " [4, 4, null],\n" + " [5, null, 7],\n" + " [6, NaN, null],\n" + " [8, 8, null]\n" + " [10, 10, null]\n" + "]"; new Dygraph( document.getElementById('graph'), [ [1, null, 3], [2, 2, null], [3, null, 7], [4, 5, null], [5, null, 5], [6, 3, null] ], { labels: ['x', 'A', 'B' ], connectSeparatedPoints: true, drawPoints: true } ); new Dygraph( document.getElementById('graph2'), 'x,A,B \n' + '1,,3 \n' + '2,2, \n' + '3,,5 \n' + '4,4, \n' + '5,,7 \n' + '6,NaN, \n' + '8,8, \n' + '10,10, \n', { connectSeparatedPoints: true, drawPoints: true } ); } }); dygraphs-2.2.1/gallery/index.html010064400000000000000000000071511437353256000137340ustar00 Dygraphs Gallery

Dygraphs Gallery


Back to Dygraphs.
dygraphs-2.2.1/gallery/interaction-api.js010064400000000000000000000142641437353256000153660ustar00/*global Dygraph */ // Code for a variety of interaction models. Used in interaction.html, but split out from // that file so they can be tested in isolation. // function downV3(event, g, context) { context.initializeMouseDown(event, g, context); if (event.altKey || event.shiftKey) { Dygraph.startZoom(event, g, context); } else { Dygraph.startPan(event, g, context); } } function moveV3(event, g, context) { if (context.isPanning) { Dygraph.movePan(event, g, context); } else if (context.isZooming) { Dygraph.moveZoom(event, g, context); } } function upV3(event, g, context) { if (context.isPanning) { Dygraph.endPan(event, g, context); } else if (context.isZooming) { Dygraph.endZoom(event, g, context); } } // Take the offset of a mouse event on the dygraph canvas and // convert it to a pair of percentages from the bottom left. // (Not top left, bottom is where the lower value is.) function offsetToPercentage(g, offsetX, offsetY) { // This is calculating the pixel offset of the leftmost date. var xOffset = g.toDomCoords(g.xAxisRange()[0], null)[0]; var yar0 = g.yAxisRange(0); // This is calculating the pixel of the higest value. (Top pixel) var yOffset = g.toDomCoords(null, yar0[1])[1]; // x y w and h are relative to the corner of the drawing area, // so that the upper corner of the drawing area is (0, 0). var x = offsetX - xOffset; var y = offsetY - yOffset; // This is computing the rightmost pixel, effectively defining the // width. var w = g.toDomCoords(g.xAxisRange()[1], null)[0] - xOffset; // This is computing the lowest pixel, effectively defining the height. var h = g.toDomCoords(null, yar0[0])[1] - yOffset; // Percentage from the left. var xPct = w === 0 ? 0 : (x / w); // Percentage from the top. var yPct = h === 0 ? 0 : (y / h); // The (1-) part below changes it from "% distance down from the top" // to "% distance up from the bottom". return [xPct, (1-yPct)]; } function dblClickV3(event, g, context) { // Reducing by 20% makes it 80% the original size, which means // to restore to original size it must grow by 25% if (!(event.offsetX && event.offsetY)){ event.offsetX = event.layerX - event.target.offsetLeft; event.offsetY = event.layerY - event.target.offsetTop; } var percentages = offsetToPercentage(g, event.offsetX, event.offsetY); var xPct = percentages[0]; var yPct = percentages[1]; if (event.ctrlKey) { zoom(g, -0.25, xPct, yPct); } else { zoom(g, +0.2, xPct, yPct); } } var lastClickedGraph = null; function clickV3(event, g, context) { lastClickedGraph = g; event.preventDefault();; } function scrollV3(event, g, context) { if (lastClickedGraph != g) { return; } var normal = event.detail ? event.detail * -1 : event.wheelDelta / 40; // For me the normalized value shows 0.075 for one click. If I took // that verbatim, it would be a 7.5%. var percentage = normal / 50; if (!(event.offsetX && event.offsetY)){ event.offsetX = event.layerX - event.target.offsetLeft; event.offsetY = event.layerY - event.target.offsetTop; } var percentages = offsetToPercentage(g, event.offsetX, event.offsetY); var xPct = percentages[0]; var yPct = percentages[1]; zoom(g, percentage, xPct, yPct); event.preventDefault(); } // Adjusts [x, y] toward each other by zoomInPercentage% // Split it so the left/bottom axis gets xBias/yBias of that change and // tight/top gets (1-xBias)/(1-yBias) of that change. // // If a bias is missing it splits it down the middle. function zoom(g, zoomInPercentage, xBias, yBias) { xBias = xBias || 0.5; yBias = yBias || 0.5; function adjustAxis(axis, zoomInPercentage, bias) { var delta = axis[1] - axis[0]; var increment = delta * zoomInPercentage; var foo = [increment * bias, increment * (1-bias)]; return [ axis[0] + foo[0], axis[1] - foo[1] ]; } var yAxes = g.yAxisRanges(); var newYAxes = []; for (var i = 0; i < yAxes.length; i++) { newYAxes[i] = adjustAxis(yAxes[i], zoomInPercentage, yBias); } g.updateOptions({ dateWindow: adjustAxis(g.xAxisRange(), zoomInPercentage, xBias), valueRange: newYAxes[0] }); } var v4Active = false; var v4Canvas = null; function downV4(event, g, context) { context.initializeMouseDown(event, g, context); v4Active = true; moveV4(event, g, context); // in case the mouse went down on a data point. } var processed = []; function moveV4(event, g, context) { var RANGE = 7; if (v4Active) { var graphPos = Dygraph.findPos(g.graphDiv); var canvasx = Dygraph.pageX(event) - graphPos.x; var canvasy = Dygraph.pageY(event) - graphPos.y; var rows = g.numRows(); // Row layout: // [date, [val1, stdev1], [val2, stdev2]] for (var row = 0; row < rows; row++) { var date = g.getValue(row, 0); var x = g.toDomCoords(date, null)[0]; var diff = Math.abs(canvasx - x); if (diff < RANGE) { for (var col = 1; col < 3; col++) { // TODO(konigsberg): these will throw exceptions as data is removed. var vals = g.getValue(row, col); if (vals === null || vals === undefined) { continue; } var val = vals[0]; var y = g.toDomCoords(null, val)[1]; var diff2 = Math.abs(canvasy - y); if (diff2 < RANGE) { var found = false; for (var i in processed) { var stored = processed[i]; if(stored[0] == row && stored[1] == col) { found = true; break; } } if (!found) { processed.push([row, col]); drawV4(x, y); } return; } } } } } } function upV4(event, g, context) { if (v4Active) { v4Active = false; } } function dblClickV4(event, g, context) { restorePositioning(g); } function drawV4(x, y) { var ctx = v4Canvas; ctx.strokeStyle = "#000000"; ctx.fillStyle = "#FFFF00"; ctx.beginPath(); ctx.arc(x,y,5,0,Math.PI*2,true); ctx.closePath(); ctx.stroke(); ctx.fill(); } function captureCanvas(canvas, area, g) { v4Canvas = canvas; } function restorePositioning(g) { g.updateOptions({ dateWindow: null, valueRange: null }); } dygraphs-2.2.1/gallery/interaction.js010064400000000000000000000061331437353256000146130ustar00/*global Gallery,Dygraph,data */ /*global NoisyData,downV3,moveV3,upV3,clickV3,dblClickV3,scrollV3,restorePositioning,downV4,moveV4,upV4,dblClickV4,captureCanvas */ //galleryActive=true Gallery.register( 'interaction', { name: 'Custom interaction models', title: 'title', setup: function(parent) { parent.innerHTML = [ "

Default interaction model

", "
", "

", " Zoom: click-drag, Pan: shift-click-drag, Restore: double-click", "

", "
", "
", "", "

Empty interaction model

", "
", "

", " Click and drag all you like, it won't do anything!", "

", "
", "
", "
", // what is this? "", "

Custom interaction model

", "
", "

", " Zoom in: double-click, scroll wheel
", " Zoom out: ctrl-double-click, scroll wheel
", " Standard Zoom: shift-click-drag", " Standard Pan: click-drag
", " Restore zoom level: press button
", "

", " ", "
", "
", "

Fun model!

", "
", "

", " Keep the mouse button pressed, and hover over all points", " to mark them.", "

", "
", "
" ].join('\n'); }, run: function() { new Dygraph(document.getElementById("div_g"), NoisyData, { errorBars : true }); new Dygraph(document.getElementById("div_g2"), NoisyData, { errorBars : true, interactionModel: {} }); var g3 = new Dygraph(document.getElementById("div_g3"), NoisyData, { errorBars : true, interactionModel : { 'mousedown' : downV3, 'mousemove' : moveV3, 'mouseup' : upV3, 'click' : clickV3, 'dblclick' : dblClickV3, 'mousewheel' : scrollV3 }}); document.getElementById("restore3").onclick = function() { restorePositioning(g3); }; new Dygraph(document.getElementById("div_g4"), NoisyData, { errorBars : true, drawPoints : true, interactionModel : { 'mousedown' : downV4, 'mousemove' : moveV4, 'mouseup' : upV4, 'dblclick' : dblClickV4 }, underlayCallback : captureCanvas }); } }); dygraphs-2.2.1/gallery/linear-regression.js010064400000000000000000000102331437353256000157200ustar00/*global Gallery,Dygraph,data */ //galleryActive=true Gallery.register( 'linear-regression', { name: 'Linear Regressions', title: 'Linear Regression Demo', setup: function(parent) { parent.innerHTML = [ "

Click the buttons to generate linear regressions over either data ", "series. If you zoom in and then click the regression button, the regression ", "will only be run over visible points. Zoom back out to see what the local ", "regression looks like over the full data.

", "
", "
", " ", " ", "", "
"].join("\n"); }, run: function() { var g, regression, clearLines; // defined below document.getElementById("ry1").onclick = function() { regression(1); }; document.getElementById("ry2").onclick = function() { regression(2); }; document.getElementById("clear").onclick = function() { clearLines(); }; var data = []; for (var i = 0; i < 120; i++) { data.push([i, i / 5.0 + 10.0 * Math.sin(i / 3.0), 30.0 - i / 5.0 - 10.0 * Math.sin(i / 3.0 + 1.0)]); } // coefficients of regression for each series. // if coeffs = [ null, [1, 2], null ] then we draw a regression for series 1 // only. The regression line is y = 1 + 2 * x. var coeffs = [ null, null, null ]; regression = function(series) { // Only run the regression over visible points. var range = g.xAxisRange(); var sum_xy = 0.0, sum_x = 0.0, sum_y = 0.0, sum_x2 = 0.0, num = 0; for (var i = 0; i < g.numRows(); i++) { var x = g.getValue(i, 0); if (x < range[0] || x > range[1]) continue; var y = g.getValue(i, series); if (y === null || y === undefined) continue; if (y.length == 2) { // using fractions y = y[0] / y[1]; } num++; sum_x += x; sum_y += y; sum_xy += x * y; sum_x2 += x * x; } var a = (sum_xy - sum_x * sum_y / num) / (sum_x2 - sum_x * sum_x / num); var b = (sum_y - a * sum_x) / num; coeffs[series] = [b, a]; if (typeof(console) != 'undefined') { console.log("coeffs(" + series + "): [" + b + ", " + a + "]"); } g.updateOptions({}); // forces a redraw. }; clearLines = function() { for (var i = 0; i < coeffs.length; i++) coeffs[i] = null; g.updateOptions({}); }; function drawLines(ctx, area, layout) { if (typeof(g) == 'undefined') return; // won't be set on the initial draw. var range = g.xAxisRange(); for (var i = 0; i < coeffs.length; i++) { if (!coeffs[i]) continue; var a = coeffs[i][1]; var b = coeffs[i][0]; var x1 = range[0]; var y1 = a * x1 + b; var x2 = range[1]; var y2 = a * x2 + b; var p1 = g.toDomCoords(x1, y1); var p2 = g.toDomCoords(x2, y2); var c = Dygraph.toRGB_(g.getColors()[i - 1]); c.r = Math.floor(255 - 0.5 * (255 - c.r)); c.g = Math.floor(255 - 0.5 * (255 - c.g)); c.b = Math.floor(255 - 0.5 * (255 - c.b)); var color = 'rgb(' + c.r + ',' + c.g + ',' + c.b + ')'; ctx.save(); ctx.strokeStyle = color; ctx.lineWidth = 1.0; ctx.beginPath(); ctx.moveTo(p1[0], p1[1]); ctx.lineTo(p2[0], p2[1]); ctx.closePath(); ctx.stroke(); ctx.restore(); } } g = new Dygraph( document.getElementById("demodiv"), data, { labels: ['X', 'Y1', 'Y2'], underlayCallback: drawLines, drawPoints: true, drawAxesAtZero: true, strokeWidth: 0.0 } ); } }); dygraphs-2.2.1/gallery/link-interaction.js010064400000000000000000000062221437353256000155450ustar00/*global Gallery,Dygraph,data */ //galleryActive=true Gallery.register( 'link-interaction', { name: 'Link Interaction', setup: function(parent) { parent.innerHTML = [ ""].join("\n"); }, run: function() { var r = [ ]; var base_time = Date.parse("2008/07/01"); var num = 24 * 0.25 * 365; for (var i = 0; i < num; i++) { r.push([ new Date(base_time + i * 3600 * 1000), i + 50 * (i % 60), // line i * (num - i) * 4.0 / num // parabola ]); } var orig_range = [ r[0][0].valueOf(), r[r.length - 1][0].valueOf() ]; var g = new Dygraph( document.getElementById("div_g"), r, { rollPeriod: 7, animatedZooms: true, // errorBars: true, width: 600, height: 300, labels: ["Date", "a", "b"] } ); var desired_range = null, animate; function approach_range() { if (!desired_range) return; // go halfway there var range = g.xAxisRange(); if (Math.abs(desired_range[0] - range[0]) < 60 && Math.abs(desired_range[1] - range[1]) < 60) { g.updateOptions({dateWindow: desired_range}); // (do not set another timeout.) } else { var new_range; new_range = [0.5 * (desired_range[0] + range[0]), 0.5 * (desired_range[1] + range[1])]; g.updateOptions({dateWindow: new_range}); animate(); } } animate = function() { setTimeout(approach_range, 50); }; var zoom = function(res) { var w = g.xAxisRange(); desired_range = [ w[0], w[0] + res * 1000 ]; animate(); }; var reset = function() { desired_range = orig_range; animate(); }; var pan = function(dir) { var w = g.xAxisRange(); var scale = w[1] - w[0]; var amount = scale * 0.25 * dir; desired_range = [ w[0] + amount, w[1] + amount ]; animate(); }; document.getElementById('hour').onclick = function() { zoom(3600); }; document.getElementById('day').onclick = function() { zoom(86400); }; document.getElementById('week').onclick = function() { zoom(604800); }; document.getElementById('month').onclick = function() { zoom(30 * 86400); }; document.getElementById('full').onclick = function() { reset(); }; document.getElementById('left').onclick = function() { pan(-1); }; document.getElementById('right').onclick = function() { pan(+1); }; } }); dygraphs-2.2.1/gallery/negative.js010064400000000000000000000022121437353256000140700ustar00/*global Gallery,Dygraph,data */ //galleryActive=false Gallery.register( 'negative', { name: 'Negative values', setup: function(parent) { parent.innerHTML = "

All negatives (x-axis on top):

" + "
" + "

Mixed (x-axis in middle):

" + "
" + "

All positives (x-axis on bottom):

" + "
"; }, run: function() { var negs = []; var mixed = []; var pos = []; for (var i = 0; i < 100; i++) { negs.push([i, -210 + i, -110 - i]); mixed.push([i, -50 + i, 50 - i]); pos.push([i, 1000 + 2 * i, 1100 + i]); } new Dygraph( document.getElementById("g1"), negs, { labels: [ 'x', 'y1', 'y2' ] } ); new Dygraph( document.getElementById("g2"), mixed, { labels: [ 'x', 'y1', 'y2' ] } ); new Dygraph( document.getElementById("g3"), pos, { labels: [ 'x', 'y1', 'y2' ] } ); } }); dygraphs-2.2.1/gallery/no-range.js010064400000000000000000000020441437353256000137770ustar00/*global Gallery,Dygraph,data */ //galleryActive=false Gallery.register( 'no-range', { name: 'No Range', setup: function(parent) { parent.innerHTML = "

Line should be visible in the middle of the chart:

" + "
" + "

Line should be visible ~90% up the chart:

" + "
"; }, run: function() { new Dygraph(document.getElementById("blah"), "X,Y\n10,12345\n11,12345\n", { width: 640, height: 480 }); new Dygraph(document.getElementById("blah2"), "date,10M\n" + "2002-12-29,10000000.000000\n" + "2003-01-05,10000000.000000\n" + "2003-01-12,10000000.000000\n" + "2003-01-19,10000000.000000\n" + "2003-01-26,10000000.000000\n" + "2003-02-02,10000000.000000\n" + "2003-02-09,10000000.000000\n" + "2003-02-16,10000000.000000\n", { width: 640, height: 480, includeZero: true, labelsKMB: true }); } }); dygraphs-2.2.1/gallery/number-format.js010064400000000000000000000057601437353256000150570ustar00/*global Gallery,Dygraph,data */ //galleryActive=false Gallery.register( 'number-format', { name: 'Number formatting', setup: function(parent) { parent.innerHTML = "

The default formatting mimics printf with %.pg where p is" + " the precision to use. It turns out that JavaScript's toPrecision()" + " method is almost but not exactly equal to %g; they differ for values" + " with small absolute values (10^-1 to 10^-5 or so), with toPrecision()" + " yielding strings that are longer than they should be (i.e. using fixed" + " point where %g would use exponential).

" + "

This test is intended to check that our formatting works properly for a" + " variety of precisions.

" + "

Precision to use (1 to 21):" + "

" + "

" + "

"; }, run: function() { // Helper functions for generating an HTML table for holding the test // results. var createRow = function(columnType, columns) { var row = document.createElement('tr'); for (var i = 0; i < columns.length; i ++) { var th = document.createElement(columnType); var text = document.createTextNode(columns[i]); th.appendChild(text); row.appendChild(th); } return row; }; var createHeaderRow = function(columns) { return createRow('th', columns); }; var createDataRow = function(columns) { return createRow('td', columns); }; var createTable = function(headerColumns, dataColumnsList) { var table = document.createElement('table'); table.appendChild(createHeaderRow(headerColumns)); for (var i = 0; i < dataColumnsList.length; i++) { table.appendChild(createDataRow(dataColumnsList[i])); } return table; }; var updateTable = function() { var headers = ['Dygraph.floatFormat()', 'toPrecision()', 'Dygraph.floatFormat()', 'toPrecision()']; var numbers = []; var p = parseInt(document.getElementById('p_input').value, 10); for (var i = -10; i <= 10; i++) { var n = Math.pow(10, i); numbers.push([Dygraph.floatFormat(n, p), n.toPrecision(p), Dygraph.floatFormat(Math.PI * n, p), (Math.PI * n).toPrecision(p)]); } // Check exact values of 0. numbers.push([Dygraph.floatFormat(0.0, p), (0.0).toPrecision(p)]); var elem = document.getElementById('content'); elem.innerHTML = ''; elem.appendChild(createTable(headers, numbers)); }; document.getElementById('p_input').value = '4'; document.getElementById('p_input').onchange = updateTable; updateTable(); } }); dygraphs-2.2.1/gallery/per-series.js010064400000000000000000000027561437353256000143610ustar00/*global Gallery,Dygraph,data */ //galleryActive=true Gallery.register( 'per-series', { name: 'Per-series properties', title: 'Chart with per-series properties', setup: function(parent) { parent.innerHTML = "
"; }, run: function() { new Dygraph( document.getElementById("demodiv"), function() { var zp = function(x) { if (x < 10) return "0"+x; else return x; }; var r = "date,parabola,line,another line,sine wave\n"; for (var i=1; i<=31; i++) { r += "2006-10-" + zp(i); r += "," + 10*(i*(31-i)); r += "," + 10*(8*i); r += "," + 10*(250 - 8*i); r += "," + 10*(125 + 125 * Math.sin(0.3*i)); r += "\n"; } return r; }, { strokeWidth: 2, series: { 'parabola': { strokeWidth: 0.0, drawPoints: true, pointSize: 4, highlightCircleSize: 6 }, 'line': { strokeWidth: 1.0, drawPoints: true, pointSize: 1.5 }, 'sine wave': { strokeWidth: 3, highlightCircleSize: 10 } } } ); } }); dygraphs-2.2.1/gallery/plotter.js010064400000000000000000000057171437353256000137740ustar00/*global Gallery,Dygraph,data */ /*jshint evil:true */ /*global fn */ //galleryActive=true Gallery.register( 'plotter', { name: 'Function Plotter', title: 'Define your data with functions', setup: function(parent) { parent.innerHTML = [ "

Equation:
", "
", "Preset functions: ", "

", "", "

x range: ", "to

", "

", "", "
"].join("\n"); }, run: function() { var plot; // defined below var select = document.getElementById("presets"); var presets = { 'id': [ -10, 10, 'function(x) {\n return x;\n}' ], 'sine': [ -10, 10, 'function(x) {\n return Math.sin(x);\n}' ], 'taylor': [ -3, 3, 'function(x) {\n return [Math.cos(x), 1 - x*x/2 + x*x*x*x/24];\n}' ], 'sawtooth': [-10, 10, 'function(x) {\n var y = 0;\n for (var i = 1; i < 20; i+=2) {\n y += Math.sin(i * x)/i;\n }\n var final = 1 - 2*(Math.abs(Math.floor(x / Math.PI)) % 2);\n return [4/Math.PI * y, final];\n}' ] }; select.onchange = function() { var sel = select.selectedIndex; var id = select.options[sel].id; if (id == "custom") { return; } document.getElementById("x1").value = presets[id][0]; document.getElementById("x2").value = presets[id][1]; document.getElementById("eq").value = presets[id][2]; plot(); }; var plotButton = document.getElementById("plot"); plot = function() { var eq = document.getElementById("eq").value; eval("fn = " + eq); var graph = document.getElementById("graph_div"); var width = parseInt(graph.style.width, 10); var x1 = parseFloat(document.getElementById("x1").value); var x2 = parseFloat(document.getElementById("x2").value); var xs = 1.0 * (x2 - x1) / width; var data = []; for (var i = 0; i < width; i++) { var x = x1 + i * xs; var y = fn(x); var row = [x]; if (y.length > 0) { for (var j = 0; j < y.length; j++) { row.push(y[j]); } } else { row.push(y); } data.push(row); } new Dygraph(graph, data); }; plotButton.onclick = plot; plot(); } }); dygraphs-2.2.1/gallery/range-selector.js010064400000000000000000000047151437353256000152120ustar00/*global Gallery,Dygraph,data */ /*global data_temp */ //galleryActive=true Gallery.register( 'range-selector', { name: 'Range Selector', title: 'Demo of the Range Selector', setup: function(parent) { parent.innerHTML = [ "", "

No roll period.

", "
", "", "

Roll period of 14 timesteps, custom range selector height and plot color.

", "
", "", "
", "

Dark background, custom range selector gradient color.

", "
", "
"].join("\n"); }, run: function() { new Dygraph( document.getElementById("noroll"), data_temp, { customBars: true, title: 'Daily Temperatures in New York vs. San Francisco', ylabel: 'Temperature (F)', legend: 'always', showRangeSelector: true } ); new Dygraph( document.getElementById("roll14"), data_temp, { rollPeriod: 14, showRoller: true, customBars: true, title: 'Daily Temperatures in New York vs. San Francisco', ylabel: 'Temperature (F)', legend: 'always', showRangeSelector: true, rangeSelectorHeight: 30, rangeSelectorPlotStrokeColor: 'yellow', rangeSelectorPlotFillColor: 'lightyellow' } ); new Dygraph( document.getElementById("darkbg"), data_temp, { rollPeriod: 14, showRoller: true, customBars: true, title: 'Nightly Temperatures in New York vs. San Francisco', ylabel: 'Temperature (F)', legend: 'always', showRangeSelector: true, rangeSelectorPlotFillColor: 'MediumSlateBlue', rangeSelectorPlotFillGradientColor: 'rgba(123, 104, 238, 0)', colorValue: 0.9, fillAlpha: 0.4 } ); } }); dygraphs-2.2.1/gallery/resizable.js010064400000000000000000000010341437353256000142470ustar00/*global Gallery,Dygraph,data */ /*global NoisyData */ //galleryActive=true Gallery.register( 'resizable', { name: 'resizable graph div', title: 'Drag the bottom-right handle and see the graph resize.', setup: function(parent) { parent.innerHTML = "
"; }, run: function() { new Dygraph( document.getElementById("div_g"), NoisyData, { resizable: "both", rollPeriod: 7, errorBars: true } ); } }); dygraphs-2.2.1/gallery/resize.js010064400000000000000000000007621437353256000135770ustar00/*global Gallery,Dygraph,data */ /*global NoisyData */ //galleryActive=true Gallery.register( 'resize', { name: 'resizable window', title: 'Resize the window. The dygraph will resize with it.', setup: function(parent) { parent.innerHTML = "
"; }, run: function() { new Dygraph( document.getElementById("div_g"), NoisyData, { rollPeriod: 7, errorBars: true } ); } }); dygraphs-2.2.1/gallery/stock.js010064400000000000000000000020731437353256000134160ustar00/*global Gallery,Dygraph,data */ /*global stockData */ //galleryActive=true Gallery.register( 'stock', { name: 'Stock Chart Demo', title: 'Stock Chart Demo', setup: function(parent) { parent.innerHTML = [ "

", "
", "  ", " ", "
"].join("\n"); }, run: function() { var g = new Dygraph(document.getElementById("stock_div"), stockData, { customBars: true, logscale: true }); var linear = document.getElementById("linear"); var log = document.getElementById("log"); var setLog = function(val) { g.updateOptions({ logscale: val }); linear.disabled = !val; log.disabled = val; }; linear.onclick = function() { setLog(false); }; log.onclick = function() { setLog(true); }; } }); dygraphs-2.2.1/gallery/styled-chart-labels.js010064400000000000000000000031231437353256000161330ustar00/*global Gallery,Dygraph,data */ //galleryActive=true Gallery.register( 'styled-chart-labels', { name: 'CSS label styling', title: 'Each chart label is styled independently with CSS', setup: function(parent) { parent.innerHTML = [ "", "

This chart's labels are styled

", "
", "

This version of the chart uses the default styles:

", "
"].join("\n"); }, run: function() { new Dygraph( document.getElementById("div_g"), data, { rollPeriod: 7, legend: 'always', title: 'High and Low Temperatures', titleHeight: 32, ylabel: 'Temperature (F)', xlabel: 'Date (Ticks indicate the start of the indicated time period)', strokeWidth: 1.5 } ); new Dygraph( document.getElementById("div_g2"), data, { rollPeriod: 30, legend: 'always', title: 'High and Low Temperatures (30-day average)', ylabel: 'Temperature (F)', xlabel: 'Date (Ticks indicate the start of the indicated time period)', strokeWidth: 1.5 } ); } }); dygraphs-2.2.1/gallery/synchronize.js010064400000000000000000000044161437353256000146510ustar00/*global Gallery,Dygraph,data */ /*jshint loopfunc:true */ /*global NoisyData */ //galleryActive=true Gallery.register( 'synchronize', { name: 'Synchronization', title: 'Multiple graphs in sync', setup: function(parent) { parent.innerHTML = [ '', '

Zooming and panning on any of the charts will zoom and pan all the', 'others. Selecting points on one will select points on the others.

', '

To use this, source extras/synchronizer.js on your page.', 'See the comments in that file for usage. However, for better browser', 'compatibility, use the file from src-es5/extras/ (dist/extras/) instead.

', '
', '
', '
', '
', '
', '
', '

', ' Synchronize what?', ' ', ' ', '

' ].join("\n"); }, run: function() { var gs = []; var blockRedraw = false; for (var i = 1; i <= 4; i++) { gs.push( new Dygraph( document.getElementById("div" + i), NoisyData, { rollPeriod: 7, errorBars: true, } ) ); } var sync = Dygraph.synchronize(gs); function update() { var zoom = document.getElementById('chk-zoom').checked; var selection = document.getElementById('chk-selection').checked; sync.detach(); sync = Dygraph.synchronize(gs, { zoom: zoom, selection: selection }); } $('#chk-zoom, #chk-selection').change(update); } }); dygraphs-2.2.1/gallery/temperature-sf-ny.js010064400000000000000000000024501437353256000156610ustar00/*global Gallery,Dygraph,data */ /*global data_temp */ //galleryActive=true Gallery.register( // Get a better name. 'temperature-sf-ny', { name: 'roll periods and high/low bands', title: 'Demo of a graph with many data points and custom high/low bands.', setup: function(parent) { parent.innerHTML = [ "", "

Roll period of 14 timesteps.

", "
", "

No roll period.

", "
"] .join("\n"); }, run: function() { new Dygraph( document.getElementById("noroll"), data_temp, { customBars: true, title: 'Daily Temperatures in New York vs. San Francisco', ylabel: 'Temperature (F)', legend: 'always', } ); new Dygraph( document.getElementById("roll14"), data_temp, { rollPeriod: 14, showRoller: true, customBars: true, title: 'Daily Temperatures in New York vs. San Francisco', ylabel: 'Temperature (F)', legend: 'always', } ); } }); dygraphs-2.2.1/gallery/two-axes.js010064400000000000000000000044571437353256000140520ustar00/*global Gallery,Dygraph,data */ //galleryActive=false Gallery.register( 'two-axes', { name: "Multiple y-axes", setup: function(parent) { parent.innerHTML = "

The same data with both one and two y-axes. Two y-axes:

" + "
" + "

A single y-axis:

" + "
" + ""; }, run: function() { var g, g2; document.getElementById('check').onchange = function(el) { g.updateOptions( { fillGraph: el.checked } ); g2.updateOptions( { fillGraph: el.checked } ); }; var data = []; for (var i = 1; i <= 100; i++) { var m = "01", d = i; if (d > 31) { m = "02"; d -= 31; } if (m == "02" && d > 28) { m = "03"; d -= 28; } if (m == "03" && d > 31) { m = "04"; d -= 31; } if (d < 10) d = "0" + d; // two series, one with range 1-100, one with range 1-2M data.push([new Date("2010/" + m + "/" + d), i, 100 - i, 1e6 * (1 + i * (100 - i) / (50 * 50)), 1e6 * (2 - i * (100 - i) / (50 * 50))]); } g = new Dygraph( document.getElementById("demodiv"), data, { labels: [ 'Date', 'Y1', 'Y2', 'Y3', 'Y4' ], 'Y3': { axis: { } }, 'Y4': { axis: 'Y3' // use the same y-axis as series Y3 }, axes: { y: { axisLabelWidth: 60 }, y2: { // set axis-related properties here labelsKMB: true, axisLabelWidth: 60 } }, ylabel: 'Primary y-axis', y2label: 'Secondary y-axis' } ); g2 = new Dygraph( document.getElementById("demodiv_one"), data, { labels: [ 'Date', 'Y1', 'Y2', 'Y3', 'Y4' ], labelsKMB: true, ylabel: 'Primary y-axis', y2label: 'Secondary y-axis' } ); } }); dygraphs-2.2.1/index.es5.js010064400000000000000000000000571437353256000124360ustar00module.exports = require('./src-es5/dygraph'); dygraphs-2.2.1/index.js010064400000000000000000000000511437353256000117350ustar00export { default } from './src/dygraph'; dygraphs-2.2.1/package.json010064400000000000000000000044471437353256000125730ustar00{ "name": "dygraphs", "version": "2.2.1", "description": "dygraphs is a fast, flexible open source JavaScript charting library.", "main": "index.es5", "module": "index", "jsnext:main": "index", "files": [ "_site", "dist", "index.es5.js", "index.js", "src", "src-es5" ], "repository": { "type": "git", "url": "git://github.com/danvk/dygraphs.git" }, "keywords": [ "dygraphs", "javascript", "visualization", "canvas", "chart", "timeseries" ], "author": "Dan Vanderkam (danvdk@gmail.com)", "license": "MIT", "bugs": { "url": "https://github.com/danvk/dygraphs/issues" }, "homepage": "https://github.com/danvk/dygraphs", "devDependencies": { "@babel/cli": "^7.19.3", "@babel/core": "^7.20.2", "@babel/plugin-transform-strict-mode": "^7.18.6", "@babel/preset-env": "^7.20.2", "babel-plugin-add-module-exports": "^1.0.4", "browser-pack": "^6.1.0", "browserify": "^17.0.0", "chai": "^3.3.0", "clean-css": "^5.3.1", "clean-css-cli": "^5.6.1", "core-js": "^3.25.1", "coveralls": "^3.1.1", "globals": "^13.17.0", "http-server": "^13.0.2", "istanbul": "^0.4.0", "js-yaml": "^3.14.1", "jshint": "^2.5.10", "lcov-parse": "0.0.9", "mocha": "^5.2.0", "mocha-phantomjs": "^3.6.0", "mocha-phantomjs-istanbul": "0.0.2", "parse-data-uri": "^0.2.0", "phantomjs": "1.9.7-15", "phantomjs-function-bind-polyfill": "^1.0.0", "pre-commit": "^1.0.6", "source-map": "^0.6.1", "uglify-js": "^3.17.3", "watchify": "^4.0.0" }, "scripts": { "build": "./scripts/build.sh", "build-jsonly": "./scripts/build-js.sh", "clean": "./scripts/clean.sh", "coverage": "./scripts/generate-coverage.sh", "site": "./scripts/run-site.sh", "prepack": "rm -rf _site && cp -Lr site _site && pax -rw -l LICENSE.txt README.md _site/ && jdupes -rL dist _site src src-es5 && cd _site && mksh ../scripts/mkdiridx.sh --ah 'dygraphs manual integration tests' ./tests", "postpack": "rm -rf _site", "watch": "./scripts/watch.sh", "test": "./scripts/run-tests.sh", "test-min": "./scripts/run-tests.sh min", "tests-ok": "./scripts/check-no-only.sh" }, "pre-commit": [ "tests-ok" ] } dygraphs-2.2.1/releases.json010064400000000000000000000015231437353256000127730ustar00[ { "files": [ "dygraph.min.js", "dygraph.js", "dygraph.css" ], "version": "2.2.0" }, { "files": [ "dygraph.min.js", "dygraph.js", "dygraph.css" ], "version": "2.1.0" }, { "files": [ "dygraph.min.js", "dygraph.js", "dygraph.css" ], "version": "2.0.0" }, { "distsubdir": false, "files": [ "dygraph-combined.js", "dygraph-combined-dev.js" ], "version": "1.1.1" }, { "distsubdir": false, "files": [ "dygraph-combined.js", "dygraph-combined-dev.js" ], "version": "1.1.0" }, { "distsubdir": false, "files": [ "dygraph-combined.js" ], "version": "1.0.1" }, { "distsubdir": false, "files": [ "dygraph-combined.js" ], "version": "1.0.0" } ] dygraphs-2.2.1/scripts/build-docs.sh010075500000000000000000000020541437353256000143500ustar00#!/bin/mksh set -e v=$(sed -n '/^Dygraph.VERSION = "\(.*\)";$/s//\1/p' docs/download.html scripts/generate-documentation.py >docs/options.html scripts/generate-versions.sh >docs/versions.html chmod a+r docs/download.html docs/options.html docs/versions.html for file in docs/download.html docs/options.html docs/versions.html; do test -s "$file" || { echo >&2 "E: generating $file failed" exit 1 } done scripts/generate-jsdoc.sh rm -rf docroot mkdir docroot cd docs pax -rw . ../docroot/ ./ssi_expander.py ../docroot/ cd ../docroot rm -f NOTES TODO common footer.html header.html *.py mv README README-docs.txt cd .. rm -f docs/download.html docs/options.html docs/versions.html pax -rw -l \ common \ gallery \ jsdoc \ tests \ README.md \ Screenshot.png \ thumbnail.png \ docroot/ rm -rf */__pycache__ dygraphs-2.2.1/scripts/build-js.sh010075500000000000000000000063261437353256000140420ustar00#!/bin/mksh # initialisation set -e set -o pipefail if [[ -e node_modules ]]; then # NPM setup babel_js=babel else # Debian packaging babel_js=babeljs fi babelrc=$PWD/babel.config.json set -x # obtain dygraphs version… v=$(sed -n '/^Dygraph.VERSION = "\(.*\)";$/s//\1/p' &2 'E: could not determine version' exit 1 fi # … as well as the version of the last release (for a snapshot) if [[ $v = +([0-9]).+([0-9]).+([0-9])-* ]]; then relv=${v%%-*} IFS=. set -- $relv IFS=$' \t\n' relv=$1.$2.$(($3 - 1)) else relv=$v fi # build ES5- and browser-compatible code in a subdirectory # and the code and test bundles as well as the minified CSS+JS # copy/transform sources and licence info rm -rf dist disttmp src-es5 mkdir disttmp if [[ -e node_modules ]]; then pax -rw -l node_modules disttmp/ fi pax -rw -l auto_tests src disttmp/ # licence headers for unminified and minified js, respectively scripts/txt2js.sh LICENSE.txt disttmp/LICENCE.js header="/*! @license https://github.com/danvk/dygraphs/blob/v$relv/LICENSE.txt (MIT) */" # prepare for building; avoid bad relative paths cd disttmp if [[ -e node_modules ]]; then PATH=$PWD/node_modules/.bin:$PATH export PATH fi # ES5-compatible source $babel_js \ --config-file "$babelrc" \ --compact false \ --source-maps inline \ -d tests5 \ auto_tests $babel_js \ --config-file "$babelrc" \ --compact false \ --source-maps inline \ -d es5 \ src rm -rf auto_tests src # get core-js from Debian, if needed, for XHR test [[ -e node_modules ]] || ln -s /usr/share/nodejs/core-js . # bundle dygraph.js{,.map} and tests.js with dev env cp -r es5 src ../scripts/env-patcher.sh development src browserify \ -v \ --debug \ -p ../scripts/xfrmmodmap-dy.js \ --full-paths \ LICENCE.js \ src/dygraph.js \ >dygraph.tmp.js browserify \ -v \ --debug \ -p ../scripts/xfrmmodmap-t.js \ --full-paths \ tests5/tests/*.js \ >tests.tmp.js rm -rf src ../scripts/smap-out.py dygraph.tmp.js dygraph.js dygraph.js.map ../scripts/smap-out.py tests.tmp.js tests.tmp2.js tests.tmp.map jq . tests.tmp2.map ../scripts/smap-in.py tests.tmp2.js tests.tmp2.map tests.js #--nonl # bundle and minify dygraph.min.js{,.map} with prod env cp -r es5 src ../scripts/env-patcher.sh production src browserify \ -v \ --debug \ -p ../scripts/xfrmmodmap-dy.js \ --full-paths \ src/dygraph.js \ >dygraph.min.tmp.js rm -rf src ../scripts/smap-out.py dygraph.min.tmp.js /dev/null dygraph.min.tmp.js.map uglifyjs \ --compress \ --mangle \ --output-opts "preamble='$header'" \ --source-map "content='dygraph.min.tmp.js.map',includeSources=true,url='dygraph.min.js.map'" \ -o dygraph.min.js \ dygraph.min.tmp.js # copy out results mkdir ../dist mv dygraph.js dygraph.js.map dygraph.min.js dygraph.min.js.map tests.js ../dist/ mv es5 ../src-es5 cd .. rm -rf disttmp # minify CSS cp css/dygraph.css dist/ cleancss css/dygraph.css -o dist/dygraph.min.css --source-map --source-map-inline-sources # add (ES5-compatible) extras to dist cd src-es5 pax -rw -l extras ../dist/ cd .. dygraphs-2.2.1/scripts/build.sh010075500000000000000000000006311437353256000134210ustar00#!/bin/mksh set -ex # Build code, tests, browser bundles. scripts/build-js.sh # Build documentation. scripts/build-docs.sh # This is for on the webserver rm -rf site _site mkdir site cd docroot pax -rw . ../site/ rm ../site/.jslibs/* ../site/LICENSE.txt ../site/dist cp -L .jslibs/* ../site/.jslibs/ cd .. pax -rw LICENSE.txt dist site/ rm -f site/dist/tests.js find site -print0 | xargs -0r chmod a+rX -- dygraphs-2.2.1/scripts/casperscript.js010064400000000000000000000012061437353256000150220ustar00// This script can be used to check for errors/console warnings in _all_ the // files in tests/*.html. // // Suggested usage: // // npm install casperjs // for x in tests/*.html; casperjs scripts/casperscript.js $x var casper = require('casper').create({ verbose: true }); var filename = casper.cli.args[0]; casper.echo(filename); casper.on('remote.message', function(message) { this.echo('page console: ' + message); }); casper.on('resource.error', function(e) { this.echo('resource.error: ' + e); }); casper.on('page.error', function(e) { this.echo('error! ' + e); }); casper.start(filename, function() { }); casper.run(); dygraphs-2.2.1/scripts/check-no-only.sh010075500000000000000000000004061437353256000147700ustar00#!/bin/mksh # Checks that no ".only" has made it into tests. This should never be committed, # since it will disable the vast majority of tests. if grep -R '\.only(' auto_tests/tests; then echo 'Remove .only from tests before committing.' exit 1 fi exit 0 dygraphs-2.2.1/scripts/clean.sh010075500000000000000000000001551437353256000134050ustar00#!/bin/sh set -ex cd "$(dirname "$0")/.." rm -rf disttmp git clean -dfx -e node_modules -e package-lock.json dygraphs-2.2.1/scripts/env-patcher.js010064400000000000000000000042441437353256000145410ustar00#!/usr/bin/env nodejs // © 2022 mirabilos Ⓕ MIT const fs = require('fs'); const { SourceMapConsumer, SourceMapGenerator, } = require('source-map'); const { relative } = require('source-map/lib/util'); const inScript = fs.readFileSync('env-patcher.tmp.js', 'UTF-8'); const inMap = fs.readFileSync('env-patcher.tmp.map', 'UTF-8'); const thefile = inScript.split('\n'); const ostr = "typeof process !== 'undefined' && process.env.NODE_ENV != 'production'"; const nstr = process.argv[2]; const olen = ostr.length; const nlen = nstr.length; const dlen = nlen - olen; const rpl = {}; var lno; for (lno = 0; lno < thefile.length; ++lno) { const str = thefile[lno]; const col = str.indexOf(ostr); if (col === -1) continue; if (str.indexOf(ostr, col + olen) !== -1) { console.log("E: more than one occurrence in line " + lno); console.log("N: for simplicity only one replacement is supported"); console.log("N: hint: uglify after transforming only"); process.exit(1); } rpl[lno + 1] = col; thefile[lno] = str.substring(0, col) + nstr + str.substring(col + olen); } const outScript = thefile.join('\n'); fs.writeFileSync('env-patcher.tmp.js', outScript, 'UTF-8'); const smc = new SourceMapConsumer(JSON.parse(inMap)); // like SourceMapGenerator.fromSourceMap() const sr = smc.sourceRoot; const smg = new SourceMapGenerator({ "file": smc.file, "sourceRoot": sr, }); smc.eachMapping(function (omap) { const l = omap.generatedLine; var c = omap.generatedColumn; const r = rpl[l]; if (r !== undefined) { if (c >= r + olen) c += dlen; else if (c > r) return; } var nmap = { "generated": { "line": l, "column": c, }, }; if (omap.source != null) { if (sr == null) nmap.source = omap.source; else nmap.source = relative(sr, omap.source); nmap.original = { "line": omap.originalLine, "column": omap.originalColumn, }; if (omap.name != null) nmap.name = omap.name; } smg.addMapping(nmap); }); smc.sources.forEach(function (sourceFile) { const content = smc.sourceContentFor(sourceFile); if (content != null) smg.setSourceContent(sourceFile, content); }); fs.writeFileSync('env-patcher.tmp.map', smg.toString(), 'UTF-8'); dygraphs-2.2.1/scripts/env-patcher.sh010075500000000000000000000013011437353256000145310ustar00#!/bin/mksh # © 2022 mirabilos Ⓕ MIT set -eo pipefail mydir=$(realpath "$0/..") if [[ $1 = development ]]; then rpl=true elif [[ $1 = production ]]; then rpl=false else print -ru2 "$0: syntax error" exit 1 fi shift if command -v nodejs >/dev/null 2>&1; then node_js=nodejs else node_js=node fi grep -FrlZ process.env.NODE_ENV "$@" | while IFS= read -d '' -r fn; do print -ru2 "I: patching $fn for !prod=$rpl" "$mydir"/smap-out.py "$fn" env-patcher.tmp.js env-patcher.tmp.map $node_js "$mydir"/env-patcher.js "$rpl" "$mydir"/smap-in.py env-patcher.tmp.js env-patcher.tmp.map "$fn" --nonl done rm -f env-patcher.tmp.js env-patcher.tmp.map print -ru2 "I: done patching" dygraphs-2.2.1/scripts/generate-coverage.sh010075500000000000000000000036631437353256000157150ustar00#!/bin/mksh # Generate code coverage data for posting to Coveralls. # This requires dist/*.js to be in place. # Output is coverage/lcov.info set -o errexit if [[ -e node_modules ]]; then babel_js=babel else babel_js=babeljs fi babelrc=$PWD/babel.config.json set -x rm -rf disttmp mkdir disttmp # Generate per-file ES6 --> ES5 transpilations $babel_js \ --config-file "$babelrc" \ --retain-lines \ --out-dir disttmp/src \ src $babel_js \ --config-file "$babelrc" \ --retain-lines \ --out-dir disttmp/auto_tests/tests \ auto_tests/tests # Instrument the source code with Istanbul's __coverage__ variable. rm -rf coverage # Clear out everything to ensure a hermetic run. mkdir coverage istanbul instrument --output coverage/src disttmp/src cp -r disttmp/auto_tests coverage/ # Build a combined file for running the tests in-browser browserify coverage/auto_tests/tests/*.js -o coverage/tests.js # Run http-server and save its PID for cleanup http-server -p 8082 > /dev/null & SERVER_PID=$! function finish { kill -TERM $SERVER_PID } trap finish EXIT # Give the server a chance to start up sleep 1 # Run the tests using mocha-phantomjs & mocha-phantomjs-istanbul # This produces coverage/coverage.json phantomjs \ ./node_modules/mocha-phantomjs/lib/mocha-phantomjs.coffee \ http://localhost:8082/auto_tests/coverage.html \ spec '{"hooks": "mocha-phantomjs-istanbul", "coverageFile": "coverage/coverage.json"}' if [ $CI ]; then # Convert the JSON coverage to LCOV for coveralls. istanbul report --include coverage/*.json lcovonly # Monkey patch in the untransformed source paths. perl -i -pe 's,dist(?:tmp)?/,,' coverage/lcov.info echo '' # reset exit code -- failure to post coverage shouldn't be an error. else # Convert the JSON coverage to HTML for viewing istanbul report --include coverage/*.json html set +x echo 'To browse coverage, run:' echo echo ' open coverage/index.html' echo fi rm -rf disttmp dygraphs-2.2.1/scripts/generate-documentation.py010075500000000000000000000220201437353256000167750ustar00#!/usr/bin/env python3 # Generate docs/options.html import glob import json import os import re import sys # Set this to the path to a test file to get debug output for just that test # file. Can be helpful to figure out why a test is not being shown for a # particular option. debug_tests = [] # [ 'tests/zoom.html' ] # Pull options reference JSON out of dygraph.js js = [] in_json = False numjson = 0 with open('src/dygraph-options-reference.js', 'rt', encoding='UTF-8', errors='strict', newline=None) as infile: for line in infile: if '' in line: in_json = True js.append('') elif '' in line: in_json = False numjson += 1 elif in_json: js[numjson] += line # TODO(danvk): better errors here. assert in_json == False assert len(js) == 2 assert numjson == 2 assert js[0] assert js[1] docs = json.loads(js[0]) cats = json.loads(js[1]) # Go through the tests and find uses of each option. for opt in docs: docs[opt]['tests'] = [] docs[opt]['gallery'] = [] encode_re = re.compile('[^A-Za-z0-9.-]') def encode_cb(match): return ''.join(["_%02X" % x for x in match.group(0).encode('UTF-8')]) def encode_anchor(name): return encode_re.sub(encode_cb, name) # This is helpful for differentiating uses of options like 'width' and 'height' # from appearances of identically-named options in CSS. def find_braces(txt): """Really primitive method to find text inside of {..} braces. Doesn't work if there's an unmatched brace in a string, e.g. '{'. """ out = '' level = 0 for char in txt: if char == '{': level += 1 if level >= 1: out += char if char == '}': level -= 1 return out ext_tests = [] gallery_files = {} def search_files(type, files): # Find text followed by a colon. These won't all be options, but those that # have the same name as a Dygraph option probably will be. prop_re = re.compile(r'\b([a-zA-Z0-9]+) *:') gf_re = re.compile(r'//galleryActive=(true|false)$', re.MULTILINE) for test_file in files: if os.path.isfile(test_file): # Basically skips directories with open(test_file, 'rt', encoding='UTF-8', errors='strict', newline=None) as infile: text = infile.read() if type == "tests": if text.find("src=\"http") >= 0: ext_tests.append(test_file) # Hack for slipping past gallery demos that have title in their attributes # so they don't appear as reasons for the demo to have 'title' options. if type == "gallery": ga = re.findall(gf_re, text) if ga: ga = ga[0] == "true" else: # not annotated ga = None gallery_files[test_file] = ga idx = text.find("function(") if idx >= 0: text = text[idx:] braced_html = find_braces(text) if test_file in debug_tests: print("\033[0m" + re.sub(prop_re, lambda m: "\033[1;31m" + m.group(0) + "\033[0m", braced_html)) print("\033[1;34m==================================================================\033[0m") ms = re.findall(prop_re, braced_html) if test_file in debug_tests: print('\n'.join(sorted(set(ms)))) for opt in ms: if opt in docs and test_file not in docs[opt][type]: docs[opt][type].append(test_file) search_files("tests", glob.glob("tests/*.html")) search_files("gallery", glob.glob("gallery/*.js")) #TODO add grep "Gallery.register\(" if debug_tests: sys.exit(0) # Extract a labels list. labels = [] for _, opt in docs.items(): for label in opt['labels']: if label not in labels: labels.append(label) for label in labels: assert label in cats, "unknown label: " + label for label in cats: assert label in labels, "unused label: " + label print("""
') print("""

Options Reference

Dygraphs tries to do a good job of displaying your data without any further configuration. But inevitably, you’re going to want to tinker. Dygraphs provides a rich set of options for configuring its display and behaviour.

Usage

You specify options in the third parameter to the dygraphs constructor:

g = new Dygraph(div,
                data,
                {
                  option1: value1,
                  option2: value2,
                  ...
                });

After you've created a Dygraph, you can change an option by calling the updateOptions method:

g.updateOptions({
                  new_option1: value1,
                  new_option2: value2
                });

Some options can be set on a per-axis and per-series basis. See the docs on per-axis and per-series options to learn how to do this. The options which may be set in this way are marked as such on this page.

For options which are functions (e.g. callbacks and formatters), the value of this is set to the Dygraph object.

Note: tests marked with ⚠ access external resources, such as Google’s jsapi.

And, without further ado, here's the complete list of options:

""") def test_name(f): """Takes 'tests/demo.html' -> 'demo'""" return f.replace('tests/', '').replace('.html', '') def gallery_name(f): """Takes 'gallery/demo.js' -> 'demo'""" return f.replace('gallery/', '').replace('.js', '') def urlify_gallery(f): """Takes 'gallery/demo.js' -> 'demo'""" return f.replace('gallery/', 'gallery/#g/').replace('.js', '') def test_fmt(f): res = '%s' % (f, test_name(f)) if f in ext_tests: res += '' return res def gallery_fmt(f): if gallery_files[f]: res = '%s' % (urlify_gallery(f), gallery_name(f)) else: res = '%s' % gallery_name(f) return res for label in sorted(labels): print('

%s

' % (encode_anchor(label), label)) if cats[label]: print('

%s

' % cats[label]) for opt_name in sorted(docs.keys()): opt = docs[opt_name] if label not in opt['labels']: continue tests = opt['tests'] if not tests: examples_html = 'NONE' else: examples_html = '; '.join(test_fmt(f) for f in sorted(tests, key=test_name)) gallery = opt['gallery'] if not gallery: gallery_html = 'NONE' else: gallery_html = '; '.join(gallery_fmt(f) for f in sorted(gallery, key=gallery_name)) if 'parameters' in opt: parameters = opt['parameters'] type_want = 'function(%s)' % ', '.join(p[0] for p in parameters) if opt['type'] == type_want: pass else: assert opt['type'].startswith(type_want + ' → '), \ "%s type does not match %s" % (opt_name, type_want) parameters_html = '\n'.join("%s:%s" % (p[0], p[1]) for p in parameters) parameters_html = "\n

\n%s\n

" % (parameters_html); else: parameters_html = '' if not opt['type']: opt['type'] = '(missing)' if not opt['default']: opt['default'] = '(missing)' if not opt['description']: opt['description'] = '(missing)' print("""

%(name)s #

%(desc)s

Type: %(type)s
%(parameters)s Default: %(default)s

""".rstrip() % { 'name': opt_name, 'namenc': encode_anchor(opt_name), 'type': opt['type'], 'parameters': parameters_html, 'default': opt['default'], 'desc': opt['description'], 'examples_html': examples_html, 'gallery_html': gallery_html}) print("""

Point Properties

Some callbacks take a point argument. Its properties are:
  • xval/yval: The data coordinates of the point (with dates/times as millis since epoch)
  • canvasx/canvasy: The canvas coordinates at which the point is drawn.
  • name: The name of the data series to which the point belongs
  • idx: The row number of the point in the data set
""") # This page was super-helpful: # http://jsbeautifier.org/ dygraphs-2.2.1/scripts/generate-download.py010075500000000000000000000110421437353256000157350ustar00#!/usr/bin/env python3 # Generates docs/download.html # Run: # ./generate-download.py > docs/download.html import json import sys if len(sys.argv) > 1: debian_text = '''

The Debian version %s, which this documentation was built for, corresponds to the subsequently mentioned release (plus patches as applicable). Note that, in the Debian package, most of the following links will not work for hopefully obvious reasons.

'''.strip() % sys.argv[1] else: debian_text = '' with open('releases.json', 'rt', encoding='UTF-8', errors='strict', newline=None) as infile: releases = json.load(infile) def file_links(release): v = release['version'] if 'distsubdir' in release and release['distsubdir'] == False: sd = '' else: sd = 'dist/' return ['%(f)s' % { 'f': f, 'sd': sd, 'v': v} for f in release['files']] # Validation of releases.json for idx, release in enumerate(releases): if idx == 0: continue assert 'version' in release, 'Release missing version: %s' % release assert 'files' in release, 'Release missing files: %s' % release assert release['version'] < releases[idx - 1]['version'], ( 'Releases should be in reverse chronological order in releases.json') current_html = '

' + ('

'.join(file_links(releases[0]))) + '

' current_link = 'https://github.com/danvk/dygraphs/releases/tag/v' + releases[0]['version'] previous_lis = [] for release in releases[1:]: previous_lis.append('
  • %(v)s: %(files)s (%(v)s docs)' % { 'v': release['version'], 'files': ', '.join(file_links(release)) }) print(''' %(debian_text)s

    The current version of dygraphs is %(version)s. Most users will want to download minified files for this version:

    %(current_html)s

    For recent releases, GitHub Releases hosts both source tarball (dygraphs_%(version)s.orig.tar.gz, basically the git repository packaged up) and binary release (dygraphs-%(version)s.tgz, identical with what was uploaded to NPM). The latter contains the precompiled CSS and JS files in both readable and minified forms as well as a copy of the documentation site, ideal for end users. The former only contains the source code needed to build all of it; distro packagers will want that file.

    There's a hosted version of dygraphs on UNPKG:

    <script type="text/javascript" src="https://unpkg.com/dygraphs@%(version)s/dist/dygraph.min.js"></script>
    <link rel="stylesheet" type="text/css" href="https://unpkg.com/dygraphs@%(version)s/dist/dygraph.min.css" />
    

    There's a hosted version of dygraphs on cdnjs.com:

    <script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/dygraph/%(version)s/dygraph.min.js"></script>
    <link rel="stylesheet" type="text/css" href="//cdnjs.cloudflare.com/ajax/libs/dygraph/%(version)s/dygraph.min.css" />
    

    But note that use of CDNs violates the EU-GDPR. Besides the tarballs (see above) you can also install dygraphs locally into your project instead using NPM:

    $ npm install dygraphs
      # dygraphs is now in node_modules/dygraphs/dist/dygraph.{css,js} for
      # the browser, and node_modules/dygraphs/index{,.es5}.js for nodejs

    Most distributions include a source map to facilitate debugging.

    To generate your own minified JS, install the prerequisites…

    • mksh
    • pax
    • python3

    … and run:

    git clone https://github.com/danvk/dygraphs.git
    cd dygraphs
    npm install
    npm run build-jsonly
    

    This will create a dygraph.min.css and a dygraph.min.js file in the dist directory.

    You may also download files for previously-released versions:

      %(previous_lis)s

    See Version History for more information on each release.

    ''' % { 'version': releases[0]['version'], 'current_html': current_html, 'current_link': current_link, 'debian_text': debian_text, 'previous_lis': '\n'.join(previous_lis) }) dygraphs-2.2.1/scripts/generate-jsdoc.sh010075500000000000000000000041511437353256000152150ustar00#!/bin/mksh # # Generates JSDoc in the /jsdoc dir. Clears any existing jsdoc there. set -e v=$(sed -n '/^Dygraph.VERSION = "\(.*\)";$/s//\1/p' &1 | tee jsdoc.tmp/.errs ed -s jsdoc.tmp/.errs <<-\EOF 1g/java .*jsrun.jar/d w q EOF if test -s jsdoc.tmp/.errs; then errs=true; else errs=false; fi rm -rf jsdoc.tmp if $errs; then echo Please fix any jsdoc errors/warnings exit 1 fi chmod -R a+rX jsdoc echo Done dygraphs-2.2.1/scripts/generate-versions.sh010075500000000000000000000050071437353256000157640ustar00#!/bin/mksh # © 2023 mirabilos Ⓕ MIT set -eo pipefail cat <<\EOF

    Version History

    For links to download each release, see the Downloads page.

    EOF # could be a function converting */_ to b/i but we do not use it anywhere alias out='print -rn --' sed \ -e 's!&!\&!g' \ -e 's!!\>!g' \ -e 's!>!\>!g' \ -e 's!"!\"!g' \ -e 's!\[\([^]]*\)\](\(https*://[!-~]*\))!\1!g' \ -e 's!\([ >]\)`\([^`]*\)`!\1\2!g' \ ' out " ${line##+( )}" continue fi print -r '' in_li=0 fi if (( in_ul )); then if [[ $line = '- '* ]]; then out "
  • ${line##-+( )}" in_li=1 continue fi print -r ' ' in_ul=0 fi if [[ $line = '#'* ]]; then if [[ $line = '# '* ]]; then if (( in_tr )); then print -r ' ' print -r ' ' fi line=${line#'# '} line=${line%')'} title=${line%' ('*} tag=$title if [[ $tag = v+([0-9.]) ]]; then title="🔗"$title else tag= fi print -r " " print -r "
  • " print -r '
    $title

    ${line##*' ('}

    ' in_tr=1 continue fi if [[ $line = '## '* ]]; then print -r "

    ${line#'## '}

      " in_ul=1 continue fi print -ru2 "E: unsupported headline level" print -ru2 "N: $line" exit 1 fi if [[ -z $line ]]; then if (( in_p )); then print -r '

      ' in_p=0 fi # in_li/in_ul already handled above continue fi if [[ $line = '- '* ]]; then if (( in_p )); then print -r '

      ' in_p=0 print -r '
        ' else print -r '
          ' fi in_ul=1 out "
        • ${line##-+( )}" in_li=1 continue fi if (( in_p )); then print -r '
          ' print -nr ' ' elif (( was_in_ul )); then print -nr '

          ' in_p=1 else print -nr '

          ' in_p=1 fi out "$line" done if (( in_p )); then print -r '

          ' fi if (( in_li )); then print -r '
        • ' fi if (( in_ul )); then print -r '
        ' fi cat <<\EOF
    EOF dygraphs-2.2.1/scripts/ghp-publish.sh010064400000000000000000000014361437353256000145450ustar00#!/bin/mksh set -exo pipefail cd "$(dirname "$0")/.." gitstatus=$(git status --porcelain) if [[ -n $gitstatus ]]; then : repo unclean false fi curbranch=$(git branch --show-current) [[ -n $curbranch ]] curremote=$(git config --get "branch.$curbranch.remote") [[ -n $curremote ]] curremurl=$(git config --get "remote.$curremote.url") [[ $curremurl = git@github.com:+([!\\\"])/+([!\\\"]).git ]] shortrem=${curremurl%.git} shortrem=${shortrem#git@github.com:} curpkg=$(package.json set +e npm_config_registry=https://npm.pkg.github.com npm publish "$@" rv=$? git checkout package.json || rv=255 exit $rv dygraphs-2.2.1/scripts/mkdiridx.sh010064400000000000000000000107631437353256000141410ustar00#!/bin/mksh # From MirOS: www/mk/common,v 1.12 2021/12/11 20:10:49 tg Exp $' #- # Copyright © 2022 # mirabilos # Copyright © 2007, 2008, 2012, 2013, 2014, 2018, 2021 # mirabilos # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation # files (the "Software"), to deal in the Software without # restriction, including without limitation the rights to use, # copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the # Software is furnished to do so, subject to the following # conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR # OTHER DEALINGS IN THE SOFTWARE. export LC_ALL=C unset LANGUAGE fw=' ' ah='dygraphs JavaScript charting library Pages' # RFC 2396 and some optional characters _plus_ apostrophe # -> escapes all shell meta-characters as well function uri_escape { if (( $# )); then print -nr -- "$@" else cat fi | sed -e ' s.%.%25.g s.;.%3B.g s./.%2F.g s.?.%3F.g s.:.%3A.g s.@.%40.g s.&.%26.g s.=.%3D.g s.+.%2B.g s.\$.%24.g s.,.%2C.g s. .%09.g s. .%20.g s.<.%3C.g s.>.%3E.g s.#.%23.g s.".%22.g s.{.%7B.g s.}.%7D.g s.|.%7C.g s.\\.%5C.g s.\^.%5E.g s.\[.%5B.g s.\].%5D.g s.`.%60.g s.'\''.%27.g ' } # escape XHTML characters (three mandatory XML ones plus double quotes, # the latter in an XML safe fashion numerically though) function xhtml_fesc { REPLY=${1//'&'/'&'} REPLY=${REPLY//'<'/'<'} REPLY=${REPLY//'>'/'>'} REPLY=${REPLY//'"'/'"'} } set -eo pipefail print -ru2 "I: mkdiridx.sh beginning" while [[ $1 = --* ]]; do if [[ $1 = --ah && -n $2 ]]; then ah=$2 shift 2 else break fi done trap 'print -ru2 "E: could not grep for extjs tests"; exit 1' USR1 (grep -FrlZ 'src="http' . || kill -USR1 $$) |& set -A exttests nexttests=0 while IFS= read -d '' -pr fn; do exttests[nexttests++]=$(realpath "$fn") done trap - USR1 owd=$PWD for dir in "$@"; do cd "$owd" print -ru2 "I: processing $dir" cd "$dir" if [[ -s index.html ]]; then print -ru2 "I: already present, skipping" continue fi set -A subdirs nsubdirs=0 set -A files set -A fwarns nfiles=0 for de in *; do if [[ $de = index.html ]]; then print -ru2 "W: huh, index.html present but unusable?" elif [[ -d $de ]]; then subdirs[nsubdirs++]=$de elif [[ -e $de ]]; then da=$(realpath "$de") i=-1 while (( ++i < nexttests )); do if [[ $da = "${exttests[i]}" ]]; then fwarns[nfiles]=1 break fi done files[nfiles++]=$de else print -ru2 "W: does not exist: $de" fi done exec >index.html if [[ $dir = . ]]; then t='Index of /' else t="Index of /${dir#./}" t=${|xhtml_fesc "${t%%+(/)}/";} fi cat <<-EOF $t

    $t

    • Parent Directory
    • EOF i=-1 while (( ++i < nsubdirs )); do d=${subdirs[i]} dl=$(uri_escape "$d") dh=${|xhtml_fesc "$d";} print -r -- "
    • ${dh}/
    • " done i=-1 while (( ++i < nfiles )); do f=${files[i]} fl=$(uri_escape "$f") fh=${|xhtml_fesc "$f";} print -r -- "
    • ${fh}${fwarns[i]:+$fw}
    • " done print -r -- '
    ' print -r -- '' print -r -- "
    $ah
    " print -r -- '' exec >&2 done print -ru2 "I: mkdiridx.sh finished" dygraphs-2.2.1/scripts/mkorigtgz.sh010064400000000000000000000031211437353256000143310ustar00#!/bin/mksh # © 2019, 2023 mirabilos Ⓕ CC0 or MirBSD or MIT thisprojecttopdir=dygraphs- thisprojectprefix=dygraphs_ thisprojectsuffix=.orig.tar.gz set -A usecompressor -- gzip -n9 function thisprojectversionmapping { REPLY=${1#v} } export LC_ALL=C unset LANGUAGE set -e set -o pipefail r=$(git rev-parse --show-toplevel) [[ $r != /* ]] || r=$(realpath "$r") if [[ $r != /* || "$(realpath .)" != "$r"?(/*) ]]; then print -ru2 -- "E: could not determine repository root" exit 1 fi w=$PWD cd "$r" x=$(git status --porcelain) || x='<>' if [[ -n $x ]]; then print -ru2 -- "N: git status info follows" print -r -- "$x" | sed 's/^/N: /' >&2 print -ru2 -- "E: source tree not clean" exit 1 fi v=$(git describe --always --dirty --tags) || x='<>' if [[ $v != [0-9A-Za-z]+([0-9A-Za-z.-]) ]]; then print -ru2 -- "E: cannot describe version" print -ru2 -- "N: $v" exit 1 fi set -x v=${|thisprojectversionmapping "$v";} d=${thisprojecttopdir}$v f=${thisprojectprefix}$v${thisprojectsuffix} T=$(mktemp -d "$r/.tmp.XXXXXXXXXX") trap 'set +e; cd "$r"; rm -rf "$T"' EXIT mkdir -p "$T/$d" x=$(git show --no-notes -s --pretty=tformat:%cd --date=format:%Y%m%d%H%M.%S) [[ $x = 2[01][0-9][0-9][0-1][0-9][0-3][0-9][0-2][0-9][0-5][0-9].[0-6][0-9] ]] git ls-tree -r --name-only -z HEAD | sort -z >"$T/.flst" <"$T/.flst" xargs -0 touch -h -t "$x" -- <"$T/.flst" pax -rw -0 -l -pe "$T/$d/" cd "$T" jdupes -rL . export d <"$T/.flst" perl -0nle 'print "$ENV{d}/$_";' | \ pax -w -0 -P -x ustar -o write_opt=nodir -b 512 -M dist | \ "${usecompressor[@]}" >"$w/${f}~" mv "$w/${f}~" "$w/$f" dygraphs-2.2.1/scripts/post-coverage.sh010075500000000000000000000003721437353256000151020ustar00#!/bin/mksh # uncomment this to skip posting to coveralls.io # comment the line out to post #exit 0 if [ $CI ]; then /dev/null & SERVER_PID=$! function finish { kill -TERM $SERVER_PID } trap finish EXIT # the following sleep step is not really necessary # as http-server starts almost instantenously; # but letting the server settle might help prevent # possible racing conditions sleep 1 if [[ $1 = min ]]; then shift fn=runner-min.html else fn=runner.html fi # Start the tests mocha-phantomjs http://localhost:8081/auto_tests/$fn "$@" dygraphs-2.2.1/scripts/smap-in.py010075500000000000000000000023451437353256000137100ustar00#!/usr/bin/python3 # © 2022 mirabilos Ⓕ MIT import base64 import json import sys _b64leader = 'sourceMappingURL=data:application/json;charset=UTF-8;base64,' _linefmt = '//# %s%s\n' if len(sys.argv) == 4: donl = True elif len(sys.argv) == 5 and sys.argv[4] == '--nonl': # evil hack for browserify donl = False else: sys.stderr.write('E: syntax: smap-in.py in.js in.map out.js\n') sys.exit(1) with open(sys.argv[1], 'r') as f: lines = f.readlines() if lines[-1].startswith('//# sourceMappingURL='): lines.pop() elif lines[-1].startswith('/*# sourceMappingURL='): _linefmt = '/*# %s%s */\n' lines.pop() with open(sys.argv[2], 'r') as f: smap = json.load(f) # clear "file" key as it’s inappropriate for embedded maps smap.pop('file', None) smap = json.dumps(smap, ensure_ascii=False, allow_nan=False, indent=None, separators=(',', ':')) smap = base64.b64encode(smap.encode('UTF-8')).decode('UTF-8') while lines[-1] == '\n': lines.pop() if not lines[-1].endswith('\n'): lines.append('\n') if not donl: # evil hack for browserify _linefmt = _linefmt.rstrip('\n') lines.append(_linefmt % (_b64leader, smap)) with open(sys.argv[3], 'w') as f: f.writelines(lines) dygraphs-2.2.1/scripts/smap-out.py010075500000000000000000000101421437353256000141030ustar00#!/usr/bin/python3 # © 2022 mirabilos Ⓕ MIT import base64 import json import re import sys _linefmt = '//# %s%s\n' _smap = re.compile('^(?://# |(?P/\\*# ))sourceMappingURL=data:application/json(?:;charset[=:](?i:iso-ir-6|ANSI_X3\\.4-19[68][86]|ISO_646\\.irv:1991|ISO646-US|(?:US-|cs)?ASCII|us|(?:IBM|cp)367|(?:cs)?utf-?8))?;base64,(?P(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}[A-Za-z0-9+/=]=)?)(?(iscss) \\*/)\n?$', re.ASCII) if len(sys.argv) == 2 and sys.argv[1] == '--test': rv = 0 def t(s, css=False): global rv m = _smap.match(s) if m is None: print('no match', s) rv = 1 return hascss = m.group('iscss') is not None if hascss != css: print('bogus css', hascss, s) rv = 1 print('got b64', m.group('b64')) def f(s): global rv if _smap.match(s) is not None: print('bogus match', s) rv = 1 t('//# sourceMappingURL=data:application/json;base64,') t('//# sourceMappingURL=data:application/json;base64,eh==') t('//# sourceMappingURL=data:application/json;base64,foo=') t('//# sourceMappingURL=data:application/json;base64,Zm9v') t('//# sourceMappingURL=data:application/json;base64,Zm9veh==') t('//# sourceMappingURL=data:application/json;base64,Zm9vfoo=') t('//# sourceMappingURL=data:application/json;base64,Zm9vYmFy\n') t('//# sourceMappingURL=data:application/json;charset=iso-ir-6;base64,foo=') t('//# sourceMappingURL=data:application/json;charset=ANSI_X3.4-1968;base64,foo=') t('//# sourceMappingURL=data:application/json;charset=ANSI_X3.4-1986;base64,foo=') t('//# sourceMappingURL=data:application/json;charset=ISO_646.irv:1991;base64,foo=') t('//# sourceMappingURL=data:application/json;charset=ISO646-US;base64,foo=') t('//# sourceMappingURL=data:application/json;charset=US-ASCII;base64,foo=') t('//# sourceMappingURL=data:application/json;charset=us;base64,foo=') t('//# sourceMappingURL=data:application/json;charset=IBM367;base64,foo=') t('//# sourceMappingURL=data:application/json;charset=cp367;base64,foo=') t('//# sourceMappingURL=data:application/json;charset=csASCII;base64,foo=') t('//# sourceMappingURL=data:application/json;charset=UTF-8;base64,foo=') t('//# sourceMappingURL=data:application/json;charset=csUTF8;base64,foo=') t('//# sourceMappingURL=data:application/json;charset:utf8;base64,foo=') t('//# sourceMappingURL=data:application/json;charset:ascii;base64,foo=') t('/*# sourceMappingURL=data:application/json;base64,css= */', css=True) f('//# sourceMappingURL=data:application/json;charset:latin1;base64,foo=') f('//# sourceMappingURL=data:application/json;charset:ascii;base64,foo') f('//# sourceMappingURL=data:application/json;charset:ascii;base64,foo-') f('//# sourceMappingURL=data:application/json;base64,Zm9vYmFy\r\n') print('test finished') sys.exit(rv) if len(sys.argv) == 4: smapname = sys.argv[3] elif len(sys.argv) == 5: smapname = sys.argv[4] else: sys.stderr.write('E: syntax: smap-out.py in.js out.js out.map [maplink]\n') sys.exit(1) with open(sys.argv[1], 'r') as f: lines = f.readlines() if lines[-1].find('sourceMappingURL=') == -1: sys.stderr.write('E: file does not contain a source map\n') sys.exit(1) smap = _smap.match(lines[-1]) if smap is None: sys.stderr.write('E: incomprehensible source map\n') sys.exit(1) lines.pop() if smap.group('iscss') is not None: _linefmt = '/*# %s%s */\n' smap = smap.group('b64') smap = base64.b64decode(smap.rstrip('\n'), validate=True).decode('UTF-8') smap = json.loads(smap) # clear "file" key as browserify writes the wrong one and it’s optional anyway smap.pop('file', None) with open(sys.argv[3], 'w') as f: json.dump(smap, f, ensure_ascii=False, allow_nan=False, indent=2, separators=(',', ': ')) f.write('\n') while lines[-1] == '\n': lines.pop() if not lines[-1].endswith('\n'): lines.append('\n') lines.append(_linefmt % ('sourceMappingURL=', smapname)) with open(sys.argv[2], 'w') as f: f.writelines(lines) dygraphs-2.2.1/scripts/txt2js.js010064400000000000000000000024651437353256000135660ustar00#!/usr/bin/env nodejs // © 2022 mirabilos Ⓕ MIT const fs = require('fs'); const { SourceMapGenerator } = require('source-map'); const srcFN = process.argv[2]; const dstFN = process.argv[3]; const inScript = fs.readFileSync(srcFN, 'UTF-8'); const thefile = inScript.split('\n'); var outlines = []; const smg = new SourceMapGenerator({ "file": dstFN, "sourceRoot": "" }); smg.setSourceContent(srcFN, inScript); outlines[0] = '/*'; var lno; for (lno = 0; lno < thefile.length; ++lno) { const str = thefile[lno]; const outln = lno + 1; if (str === '') { outlines[outln] = ' *'; continue; } outlines[outln] = ' * ' + str; smg.addMapping({ "source": srcFN, "original": { "line": /* 1-based */ (lno + 1), "column": /* 0-based */ 0 }, "generated": { "line": /* 1-based */ (outln + 1), "column": /* 0-based */ 3 } }); } outlines[thefile.length + 0] = ' */'; outlines[thefile.length + 1] = '"use strict";'; // the appending of “"use strict";“ is the same as running through // babel currently is; for some reason, babel drops the source map // from this output file so just use this i̲n̲s̲t̲e̲a̲d̲ ̲o̲f̲ babel… atm… const outScript = outlines.join('\n'); fs.writeFileSync(dstFN, outScript, 'UTF-8'); fs.writeFileSync(dstFN + '.map', smg.toString(), 'UTF-8'); dygraphs-2.2.1/scripts/txt2js.sh010075500000000000000000000011201437353256000135520ustar00#!/bin/mksh # © 2022 mirabilos Ⓕ MIT set -eo pipefail mydir=$(realpath "$0/..") infile=$1 outfile=$2 if [[ -z $infile || ! -s $infile || -z $outfile ]]; then print -ru2 "E: syntax error" exit 1 fi if command -v nodejs >/dev/null 2>&1; then node_js=nodejs else node_js=node fi rm -f "$outfile" "$outfile.tmp.js" "$outfile.tmp.js.map" print -ru2 "I: converting $infile to $outfile" $node_js "$mydir"/txt2js.js "$infile" "$outfile.tmp.js" "$mydir"/smap-in.py "$outfile.tmp.js" "$outfile.tmp.js.map" "$outfile" --nonl rm -f "$outfile.tmp.js" "$outfile.tmp.js.map" dygraphs-2.2.1/scripts/watch.sh010075500000000000000000000011511437353256000134260ustar00#!/bin/bash # Continually regenerate these two files: # - dist/dygraph.js # - dist/tests.js # As source/test files change. # Clean background processes after quit trap "trap - SIGTERM && kill -- -$$" SIGINT SIGTERM EXIT mkdir -p dist # in case it doesn't exist; watchify needs it watchify \ -v \ -t babelify \ -t [ envify --NODE_ENV development ] \ --debug \ --standalone Dygraph \ -o dist/dygraph.js \ src/dygraph.js & watchify \ -v \ -t babelify \ -t [ envify --NODE_ENV development ] \ --debug \ -o dist/tests.js \ auto_tests/tests/*.js & # Wait until background processes end wait dygraphs-2.2.1/scripts/weigh-in.sh010075500000000000000000000010071437353256000140270ustar00#!/bin/mksh # This tracks the effect of pull requests on the size of dygraphs. # See https://github.com/danvk/travis-weigh-in set -o errexit if [ -z "$GITHUB_TOKEN" ]; then echo "GITHUB_TOKEN not set. Skipping size checks." exit 0 else curl -O https://raw.githubusercontent.com/danvk/travis-weigh-in/master/weigh_in.py python weigh_in.py dist/dygraph.min.js mkdir -p disttmp gzip -cn9 disttmp/dygraph.min.js.gz python weigh_in.py disttmp/dygraph.min.js.gz rm -r disttmp fi dygraphs-2.2.1/scripts/xfrmmodmap-dy.js010064400000000000000000000047061437353256000151140ustar00// © 2023 mirabilos Ⓕ MIT const assert = require('assert'); const through = require('through2'); const umd = require('umd'); module.exports = function (b) { const nmap = {}; const rows = []; const basedir = process.cwd() + '/'; const skip_lic = basedir + 'LICENCE.js'; const npmbase = basedir + 'node_modules/'; const testbase = basedir + 'tests5/'; const rplbase = 'dygraphs/'; const norpl = function norpl(s) { return (!s.startsWith(basedir) || s == skip_lic || s.startsWith(npmbase) || s.startsWith(testbase)); }; const mainmodule = basedir + 'src/dygraph.js'; const scan = function scan(s) { if (norpl(s)) nmap[s] = true; }; const xfrm = function xfrm(s) { if (norpl(s)) return (nmap[s]); return (rplbase + s.substr(basedir.length)); }; b.pipeline.get('label').push(through.obj(function transform(row, enc, next) { scan(row.id); for (let dep in row.deps) if (row.deps.hasOwnProperty(dep)) scan(row.deps[dep]); rows.push(row); next(); }, function end(next) { const keys = Object.keys(nmap).slice().sort(); for (let i = 0; i < keys.length; ++i) nmap[keys[i]] = i + 1; for (let i = 0; i < rows.length; ++i) { let row = rows[i]; row.id = xfrm(row.id); for (let dep in row.deps) if (row.deps.hasOwnProperty(dep)) row.deps[dep] = xfrm(row.deps[dep]); if (row.dedupe) row.source = 'arguments[4][' + JSON.stringify(xfrm(row.dedupe)) + '][0].apply(exports,arguments)'; this.push(row); } if (typeof(b._bpack.standaloneModule) === 'string') b._bpack.standaloneModule = xfrm(b._bpack.standaloneModule); next(); })); // use this instead of --standalone so we can require for the tests var first = true, hadsm = false; b.pipeline.get('pack').push(through.obj(function transform(row, enc, next) { if (first) { var pre = umd.prelude('Dygraph').trim() + 'var r='; assert(pre.match(/\n/g) === null, 'UMD prelude contains newlines'); this.push(Buffer.from(pre, 'utf8')); first = false; } else if (row.toString().startsWith('\n//# sourceMappingURL')) { assert(!hadsm, 'multiple source maps in file‽'); var post = ';var x=r(' + JSON.stringify(xfrm(mainmodule)) + ');x._require._b=r;return x' + umd.postlude('Dygraph').trim(); assert(post.match(/\n/g) === null, 'UMD postlude contains newlines'); this.push(Buffer.from(post, 'utf8')); hadsm = true; } next(null, row); })); }; dygraphs-2.2.1/scripts/xfrmmodmap-t.js010064400000000000000000000031071437353256000147350ustar00// © 2023 mirabilos Ⓕ MIT const through = require('through2'); module.exports = function (b) { const nmap = {}; const rows = []; const basedir = process.cwd() + '/'; const npmbase = basedir + 'node_modules/'; const testbase = basedir + 'tests5/'; const rplbase = 'dygraphs/'; const norpl = function norpl(s) { return (!s.startsWith(basedir) || s.startsWith(npmbase) || s.startsWith(testbase)); }; const scan = function scan(s) { if (norpl(s)) nmap[s] = true; }; const xfrm = function xfrm(s) { if (norpl(s)) return (nmap[s]); return (rplbase + s.substr(basedir.length)); }; b.pipeline.get('label').push(through.obj(function transform(row, enc, next) { scan(row.id); for (let dep in row.deps) if (row.deps.hasOwnProperty(dep)) scan(row.deps[dep]); rows.push(row); next(); }, function end(next) { const keys = Object.keys(nmap).slice().sort(); for (let i = 0; i < keys.length; ++i) nmap[keys[i]] = i + 1; for (let i = 0; i < rows.length; ++i) { let row = rows[i]; // use this instead of -x/-u so we can map pathnames if (!norpl(row.id)) continue; row.id = xfrm(row.id); for (let dep in row.deps) if (row.deps.hasOwnProperty(dep)) row.deps[dep] = xfrm(row.deps[dep]); if (row.dedupe) row.source = 'arguments[4][' + JSON.stringify(xfrm(row.dedupe)) + '][0].apply(exports,arguments)'; this.push(row); } if (typeof(b._bpack.standaloneModule) === 'string') b._bpack.standaloneModule = xfrm(b._bpack.standaloneModule); next(); })); }; dygraphs-2.2.1/src/datahandler/bars-custom.js010064400000000000000000000050661437353256000161360ustar00/** * @license * Copyright 2013 David Eberlein (david.eberlein@ch.sauter-bc.com) * MIT-licenced: https://opensource.org/licenses/MIT */ /** * @fileoverview DataHandler implementation for the custom bars option. * @author David Eberlein (david.eberlein@ch.sauter-bc.com) */ /*global Dygraph:false */ "use strict"; import BarsHandler from './bars'; /** * @constructor * @extends Dygraph.DataHandlers.BarsHandler */ var CustomBarsHandler = function() { }; CustomBarsHandler.prototype = new BarsHandler(); /** @inheritDoc */ CustomBarsHandler.prototype.extractSeries = function(rawData, i, options) { // TODO(danvk): pre-allocate series here. var series = []; var x, y, point; const seriesLabel = options.get("labels")[i]; const logScale = options.getForSeries("logscale", seriesLabel); for ( var j = 0; j < rawData.length; j++) { x = rawData[j][0]; point = rawData[j][i]; if (logScale && point !== null) { // On the log scale, points less than zero do not exist. // This will create a gap in the chart. if (point[0] <= 0 || point[1] <= 0 || point[2] <= 0) { point = null; } } // Extract to the unified data format. if (point !== null) { y = point[1]; if (y !== null && !isNaN(y)) { series.push([ x, y, [ point[0], point[2] ] ]); } else { series.push([ x, y, [ y, y ] ]); } } else { series.push([ x, null, [ null, null ] ]); } } return series; }; /** @inheritDoc */ CustomBarsHandler.prototype.rollingAverage = function(originalData, rollPeriod, options, i) { rollPeriod = Math.min(rollPeriod, originalData.length); var rollingData = []; var y, low, high, mid,count, i, extremes; low = 0; mid = 0; high = 0; count = 0; for (i = 0; i < originalData.length; i++) { y = originalData[i][1]; extremes = originalData[i][2]; rollingData[i] = originalData[i]; if (y !== null && !isNaN(y)) { low += extremes[0]; mid += y; high += extremes[1]; count += 1; } if (i - rollPeriod >= 0) { var prev = originalData[i - rollPeriod]; if (prev[1] !== null && !isNaN(prev[1])) { low -= prev[2][0]; mid -= prev[1]; high -= prev[2][1]; count -= 1; } } if (count) { rollingData[i] = [ originalData[i][0], 1.0 * mid / count, [ 1.0 * low / count, 1.0 * high / count ] ]; } else { rollingData[i] = [ originalData[i][0], null, [ null, null ] ]; } } return rollingData; }; export default CustomBarsHandler; dygraphs-2.2.1/src/datahandler/bars-error.js010064400000000000000000000057571437353256000157640ustar00/** * @license * Copyright 2013 David Eberlein (david.eberlein@ch.sauter-bc.com) * MIT-licenced: https://opensource.org/licenses/MIT */ /** * @fileoverview DataHandler implementation for the errorBars option. * @author David Eberlein (david.eberlein@ch.sauter-bc.com) */ /*global Dygraph:false */ "use strict"; import BarsHandler from './bars'; /** * @constructor * @extends BarsHandler */ var ErrorBarsHandler = function() { }; ErrorBarsHandler.prototype = new BarsHandler(); /** @inheritDoc */ ErrorBarsHandler.prototype.extractSeries = function(rawData, i, options) { // TODO(danvk): pre-allocate series here. var series = []; var x, y, variance, point; const seriesLabel = options.get("labels")[i]; const logScale = options.getForSeries("logscale", seriesLabel); const sigma = options.getForSeries("sigma", seriesLabel); for ( var j = 0; j < rawData.length; j++) { x = rawData[j][0]; point = rawData[j][i]; if (logScale && point !== null) { // On the log scale, points less than zero do not exist. // This will create a gap in the chart. if (point[0] <= 0 || point[0] - sigma * point[1] <= 0) { point = null; } } // Extract to the unified data format. if (point !== null) { y = point[0]; if (y !== null && !isNaN(y)) { variance = sigma * point[1]; // preserve original error value in extras for further // filtering series.push([ x, y, [ y - variance, y + variance, point[1] ] ]); } else { series.push([ x, y, [ y, y, y ] ]); } } else { series.push([ x, null, [ null, null, null ] ]); } } return series; }; /** @inheritDoc */ ErrorBarsHandler.prototype.rollingAverage = function(originalData, rollPeriod, options, i) { rollPeriod = Math.min(rollPeriod, originalData.length); var rollingData = []; const seriesLabel = options.get("labels")[i]; const sigma = options.getForSeries("sigma", seriesLabel); var i, j, y, v, sum, num_ok, stddev, variance, value; // Calculate the rolling average for the first rollPeriod - 1 points // where there is not enough data to roll over the full number of points for (i = 0; i < originalData.length; i++) { sum = 0; variance = 0; num_ok = 0; for (j = Math.max(0, i - rollPeriod + 1); j < i + 1; j++) { y = originalData[j][1]; if (y === null || isNaN(y)) continue; num_ok++; sum += y; variance += Math.pow(originalData[j][2][2], 2); } if (num_ok) { stddev = Math.sqrt(variance) / num_ok; value = sum / num_ok; rollingData[i] = [ originalData[i][0], value, [value - sigma * stddev, value + sigma * stddev] ]; } else { // This explicitly preserves NaNs to aid with "independent // series". // See testRollingAveragePreservesNaNs. v = (rollPeriod == 1) ? originalData[i][1] : null; rollingData[i] = [ originalData[i][0], v, [ v, v ] ]; } } return rollingData; }; export default ErrorBarsHandler; dygraphs-2.2.1/src/datahandler/bars-fractions.js010064400000000000000000000071371437353256000166150ustar00/** * @license * Copyright 2013 David Eberlein (david.eberlein@ch.sauter-bc.com) * MIT-licenced: https://opensource.org/licenses/MIT */ /** * @fileoverview DataHandler implementation for the combination * of error bars and fractions options. * @author David Eberlein (david.eberlein@ch.sauter-bc.com) */ /*global Dygraph:false */ "use strict"; import BarsHandler from './bars'; /** * @constructor * @extends Dygraph.DataHandlers.BarsHandler */ var FractionsBarsHandler = function() { }; FractionsBarsHandler.prototype = new BarsHandler(); /** @inheritDoc */ FractionsBarsHandler.prototype.extractSeries = function(rawData, i, options) { // TODO(danvk): pre-allocate series here. var series = []; var x, y, point, num, den, value, stddev, variance; var mult = 100.0; const seriesLabel = options.get("labels")[i]; const logScale = options.getForSeries("logscale", seriesLabel); const sigma = options.getForSeries("sigma", seriesLabel); for ( var j = 0; j < rawData.length; j++) { x = rawData[j][0]; point = rawData[j][i]; if (logScale && point !== null) { // On the log scale, points less than zero do not exist. // This will create a gap in the chart. if (point[0] <= 0 || point[1] <= 0) { point = null; } } // Extract to the unified data format. if (point !== null) { num = point[0]; den = point[1]; if (num !== null && !isNaN(num)) { value = den ? num / den : 0.0; stddev = den ? sigma * Math.sqrt(value * (1 - value) / den) : 1.0; variance = mult * stddev; y = mult * value; // preserve original values in extras for further filtering series.push([ x, y, [ y - variance, y + variance, num, den ] ]); } else { series.push([ x, num, [ num, num, num, den ] ]); } } else { series.push([ x, null, [ null, null, null, null ] ]); } } return series; }; /** @inheritDoc */ FractionsBarsHandler.prototype.rollingAverage = function(originalData, rollPeriod, options, i) { rollPeriod = Math.min(rollPeriod, originalData.length); var rollingData = []; const seriesLabel = options.get("labels")[i]; const sigma = options.getForSeries("sigma", seriesLabel); const wilsonInterval = options.getForSeries("wilsonInterval", seriesLabel); var low, high, i, stddev; var num = 0; var den = 0; // numerator/denominator var mult = 100.0; for (i = 0; i < originalData.length; i++) { num += originalData[i][2][2]; den += originalData[i][2][3]; if (i - rollPeriod >= 0) { num -= originalData[i - rollPeriod][2][2]; den -= originalData[i - rollPeriod][2][3]; } var date = originalData[i][0]; var value = den ? num / den : 0.0; if (wilsonInterval) { // For more details on this confidence interval, see: // https://en.wikipedia.org/wiki/Binomial_proportion_confidence_interval if (den) { var p = value < 0 ? 0 : value, n = den; var pm = sigma * Math.sqrt(p * (1 - p) / n + sigma * sigma / (4 * n * n)); var denom = 1 + sigma * sigma / den; low = (p + sigma * sigma / (2 * den) - pm) / denom; high = (p + sigma * sigma / (2 * den) + pm) / denom; rollingData[i] = [ date, p * mult, [ low * mult, high * mult ] ]; } else { rollingData[i] = [ date, 0, [ 0, 0 ] ]; } } else { stddev = den ? sigma * Math.sqrt(value * (1 - value) / den) : 1.0; rollingData[i] = [ date, mult * value, [ mult * (value - stddev), mult * (value + stddev) ] ]; } } return rollingData; }; export default FractionsBarsHandler; dygraphs-2.2.1/src/datahandler/bars.js010064400000000000000000000066031437353256000146240ustar00/** * @license * Copyright 2013 David Eberlein (david.eberlein@ch.sauter-bc.com) * MIT-licenced: https://opensource.org/licenses/MIT */ /** * @fileoverview DataHandler base implementation for the "bar" * data formats. This implementation must be extended and the * extractSeries and rollingAverage must be implemented. * @author David Eberlein (david.eberlein@ch.sauter-bc.com) */ /*global Dygraph:false */ /*global DygraphLayout:false */ "use strict"; import DygraphDataHandler from './datahandler'; import DygraphLayout from '../dygraph-layout'; /** * @constructor * @extends {Dygraph.DataHandler} */ var BarsHandler = function() { DygraphDataHandler.call(this); }; BarsHandler.prototype = new DygraphDataHandler(); // TODO(danvk): figure out why the jsdoc has to be copy/pasted from superclass. // (I get closure compiler errors if this isn't here.) /** * @override * @param {!Array.} rawData The raw data passed into dygraphs where * rawData[i] = [x,ySeries1,...,ySeriesN]. * @param {!number} seriesIndex Index of the series to extract. All other * series should be ignored. * @param {!DygraphOptions} options Dygraph options. * @return {Array.<[!number,?number,?]>} The series in the unified data format * where series[i] = [x,y,{extras}]. */ BarsHandler.prototype.extractSeries = function(rawData, seriesIndex, options) { // Not implemented here must be extended }; /** * @override * @param {!Array.<[!number,?number,?]>} series The series in the unified * data format where series[i] = [x,y,{extras}]. * @param {!number} rollPeriod The number of points over which to average the data * @param {!DygraphOptions} options The dygraph options. * @param {!number} seriesIndex Index of the series this was extracted from. * TODO(danvk): be more specific than "Array" here. * @return {!Array.<[!number,?number,?]>} the rolled series. */ BarsHandler.prototype.rollingAverage = function(series, rollPeriod, options, seriesIndex) { // Not implemented here, must be extended. }; /** @inheritDoc */ BarsHandler.prototype.onPointsCreated_ = function(series, points) { for (var i = 0; i < series.length; ++i) { var item = series[i]; var point = points[i]; point.y_top = NaN; point.y_bottom = NaN; point.yval_minus = DygraphDataHandler.parseFloat(item[2][0]); point.yval_plus = DygraphDataHandler.parseFloat(item[2][1]); } }; /** @inheritDoc */ BarsHandler.prototype.getExtremeYValues = function(series, dateWindow, stepPlot) { var minY = null, maxY = null, y; var firstIdx = 0; var lastIdx = series.length - 1; for ( var j = firstIdx; j <= lastIdx; j++) { y = series[j][1]; if (y === null || isNaN(y)) continue; var low = series[j][2][0]; var high = series[j][2][1]; if (low > y) low = y; // this can happen with custom bars, if (high < y) high = y; // e.g. in tests/custom-bars.html if (maxY === null || high > maxY) maxY = high; if (minY === null || low < minY) minY = low; } return [ minY, maxY ]; }; /** @inheritDoc */ BarsHandler.prototype.onLineEvaluated = function(points, axis, logscale) { var point; for (var j = 0; j < points.length; j++) { // Copy over the error terms point = points[j]; point.y_top = DygraphLayout.calcYNormal_(axis, point.yval_minus, logscale); point.y_bottom = DygraphLayout.calcYNormal_(axis, point.yval_plus, logscale); } }; export default BarsHandler; dygraphs-2.2.1/src/datahandler/datahandler.js010064400000000000000000000165411437353256000161460ustar00/** * @license * Copyright 2013 David Eberlein (david.eberlein@ch.sauter-bc.com) * MIT-licenced: https://opensource.org/licenses/MIT */ /** * @fileoverview This file contains the managment of data handlers * @author David Eberlein (david.eberlein@ch.sauter-bc.com) * * The idea is to define a common, generic data format that works for all data * structures supported by dygraphs. To make this possible, the DataHandler * interface is introduced. This makes it possible, that dygraph itself can work * with the same logic for every data type independent of the actual format and * the DataHandler takes care of the data format specific jobs. * DataHandlers are implemented for all data types supported by Dygraphs and * return Dygraphs compliant formats. * By default the correct DataHandler is chosen based on the options set. * Optionally the user may use his own DataHandler (similar to the plugin * system). * * * The unified data format returend by each handler is defined as so: * series[n][point] = [x,y,(extras)] * * This format contains the common basis that is needed to draw a simple line * series extended by optional extras for more complex graphing types. It * contains a primitive x value as first array entry, a primitive y value as * second array entry and an optional extras object for additional data needed. * * x must always be a number. * y must always be a number, NaN of type number or null. * extras is optional and must be interpreted by the DataHandler. It may be of * any type. * * In practice this might look something like this: * default: [x, yVal] * errorBar / customBar: [x, yVal, [yTopVariance, yBottomVariance] ] * */ /*global Dygraph:false */ /*global DygraphLayout:false */ "use strict"; /** * * The data handler is responsible for all data specific operations. All of the * series data it receives and returns is always in the unified data format. * Initially the unified data is created by the extractSeries method * @constructor */ var DygraphDataHandler = function () { }; var handler = DygraphDataHandler; /** * X-value array index constant for unified data samples. * @const * @type {number} */ handler.X = 0; /** * Y-value array index constant for unified data samples. * @const * @type {number} */ handler.Y = 1; /** * Extras-value array index constant for unified data samples. * @const * @type {number} */ handler.EXTRAS = 2; /** * Extracts one series from the raw data (a 2D array) into an array of the * unified data format. * This is where undesirable points (i.e. negative values on log scales and * missing values through which we wish to connect lines) are dropped. * TODO(danvk): the "missing values" bit above doesn't seem right. * * @param {!Array.} rawData The raw data passed into dygraphs where * rawData[i] = [x,ySeries1,...,ySeriesN]. * @param {!number} seriesIndex Index of the series to extract. All other * series should be ignored. * @param {!DygraphOptions} options Dygraph options. * @return {Array.<[!number,?number,?]>} The series in the unified data format * where series[i] = [x,y,{extras}]. */ handler.prototype.extractSeries = function(rawData, seriesIndex, options) { }; /** * Converts a series to a Point array. The resulting point array must be * returned in increasing order of idx property. * * @param {!Array.<[!number,?number,?]>} series The series in the unified * data format where series[i] = [x,y,{extras}]. * @param {!string} setName Name of the series. * @param {!number} boundaryIdStart Index offset of the first point, equal to the * number of skipped points left of the date window minimum (if any). * @return {!Array.} List of points for this series. */ handler.prototype.seriesToPoints = function(series, setName, boundaryIdStart) { // TODO(bhs): these loops are a hot-spot for high-point-count charts. In // fact, // on chrome+linux, they are 6 times more expensive than iterating through // the // points and drawing the lines. The brunt of the cost comes from allocating // the |point| structures. var points = []; for ( var i = 0; i < series.length; ++i) { var item = series[i]; var yraw = item[1]; var yval = yraw === null ? null : handler.parseFloat(yraw); var point = { x : NaN, y : NaN, xval : handler.parseFloat(item[0]), yval : yval, name : setName, // TODO(danvk): is this really necessary? idx : i + boundaryIdStart, canvasx: NaN, // add these so we do not alter the structure later, which slows Chrome canvasy: NaN, }; points.push(point); } this.onPointsCreated_(series, points); return points; }; /** * Callback called for each series after the series points have been generated * which will later be used by the plotters to draw the graph. * Here data may be added to the seriesPoints which is needed by the plotters. * The indexes of series and points are in sync meaning the original data * sample for series[i] is points[i]. * * @param {!Array.<[!number,?number,?]>} series The series in the unified * data format where series[i] = [x,y,{extras}]. * @param {!Array.} points The corresponding points passed * to the plotter. * @protected */ handler.prototype.onPointsCreated_ = function(series, points) { }; /** * Calculates the rolling average of a data set. * * @param {!Array.<[!number,?number,?]>} series The series in the unified * data format where series[i] = [x,y,{extras}]. * @param {!number} rollPeriod The number of points over which to average the data * @param {!DygraphOptions} options The dygraph options. * @param {!number} seriesIndex Index of the series this was extracted from. * @return {!Array.<[!number,?number,?]>} the rolled series. */ handler.prototype.rollingAverage = function(series, rollPeriod, options, seriesIndex) { }; /** * Computes the range of the data series (including confidence intervals). * * @param {!Array.<[!number,?number,?]>} series The series in the unified * data format where series[i] = [x, y, {extras}]. * @param {!Array.} dateWindow The x-value range to display with * the format: [min, max]. * @param {boolean} stepPlot Whether the stepPlot option is set. * @return {Array.} The low and high extremes of the series in the * given window with the format: [low, high]. */ handler.prototype.getExtremeYValues = function(series, dateWindow, stepPlot) { }; /** * Callback called for each series after the layouting data has been * calculated before the series is drawn. Here normalized positioning data * should be calculated for the extras of each point. * * @param {!Array.} points The points passed to * the plotter. * @param {!Object} axis The axis on which the series will be plotted. * @param {!boolean} logscale Whether or not to use a logscale. */ handler.prototype.onLineEvaluated = function(points, axis, logscale) { }; /** * Optimized replacement for parseFloat, which was way too slow when almost * all values were type number, with few edge cases, none of which were strings. * @param {?number} val * @return {number} * @protected */ handler.parseFloat = function(val) { // parseFloat(null) is NaN if (val === null) { return NaN; } // Assume it's a number or NaN. If it's something else, I'll be shocked. return val; }; export default DygraphDataHandler; dygraphs-2.2.1/src/datahandler/default-fractions.js010064400000000000000000000045451437353256000173120ustar00/** * @license * Copyright 2013 David Eberlein (david.eberlein@ch.sauter-bc.com) * MIT-licenced: https://opensource.org/licenses/MIT */ /** * @fileoverview DataHandler implementation for the fractions option. * @author David Eberlein (david.eberlein@ch.sauter-bc.com) */ /*global Dygraph:false */ "use strict"; import DygraphDataHandler from './datahandler'; import DefaultHandler from './default'; /** * @extends DefaultHandler * @constructor */ var DefaultFractionHandler = function() { }; DefaultFractionHandler.prototype = new DefaultHandler(); DefaultFractionHandler.prototype.extractSeries = function(rawData, i, options) { // TODO(danvk): pre-allocate series here. var series = []; var x, y, point, num, den, value; var mult = 100.0; const seriesLabel = options.get("labels")[i]; const logScale = options.getForSeries("logscale", seriesLabel); for ( var j = 0; j < rawData.length; j++) { x = rawData[j][0]; point = rawData[j][i]; if (logScale && point !== null) { // On the log scale, points less than zero do not exist. // This will create a gap in the chart. if (point[0] <= 0 || point[1] <= 0) { point = null; } } // Extract to the unified data format. if (point !== null) { num = point[0]; den = point[1]; if (num !== null && !isNaN(num)) { value = den ? num / den : 0.0; y = mult * value; // preserve original values in extras for further filtering series.push([ x, y, [ num, den ] ]); } else { series.push([ x, num, [ num, den ] ]); } } else { series.push([ x, null, [ null, null ] ]); } } return series; }; DefaultFractionHandler.prototype.rollingAverage = function(originalData, rollPeriod, options, i) { rollPeriod = Math.min(rollPeriod, originalData.length); var rollingData = []; var i; var num = 0; var den = 0; // numerator/denominator var mult = 100.0; for (i = 0; i < originalData.length; i++) { num += originalData[i][2][0]; den += originalData[i][2][1]; if (i - rollPeriod >= 0) { num -= originalData[i - rollPeriod][2][0]; den -= originalData[i - rollPeriod][2][1]; } var date = originalData[i][0]; var value = den ? num / den : 0.0; rollingData[i] = [ date, mult * value ]; } return rollingData; }; export default DefaultFractionHandler; dygraphs-2.2.1/src/datahandler/default.js010064400000000000000000000047561437353256000153300ustar00/** * @license * Copyright 2013 David Eberlein (david.eberlein@ch.sauter-bc.com) * MIT-licenced: https://opensource.org/licenses/MIT */ /** * @fileoverview DataHandler default implementation used for simple line charts. * @author David Eberlein (david.eberlein@ch.sauter-bc.com) */ /*global Dygraph:false */ "use strict"; import DygraphDataHandler from './datahandler'; /** * @constructor * @extends Dygraph.DataHandler */ var DefaultHandler = function() { }; DefaultHandler.prototype = new DygraphDataHandler(); /** @inheritDoc */ DefaultHandler.prototype.extractSeries = function(rawData, i, options) { // TODO(danvk): pre-allocate series here. var series = []; const seriesLabel = options.get("labels")[i]; const logScale = options.getForSeries("logscale", seriesLabel); for ( var j = 0; j < rawData.length; j++) { var x = rawData[j][0]; var point = rawData[j][i]; if (logScale) { // On the log scale, points less than zero do not exist. // This will create a gap in the chart. if (point <= 0) { point = null; } } series.push([ x, point ]); } return series; }; /** @inheritDoc */ DefaultHandler.prototype.rollingAverage = function(originalData, rollPeriod, options, i) { rollPeriod = Math.min(rollPeriod, originalData.length); var rollingData = []; var i, j, y, sum, num_ok; // Calculate the rolling average for the first rollPeriod - 1 points // where // there is not enough data to roll over the full number of points if (rollPeriod == 1) { return originalData; } for (i = 0; i < originalData.length; i++) { sum = 0; num_ok = 0; for (j = Math.max(0, i - rollPeriod + 1); j < i + 1; j++) { y = originalData[j][1]; if (y === null || isNaN(y)) continue; num_ok++; sum += originalData[j][1]; } if (num_ok) { rollingData[i] = [ originalData[i][0], sum / num_ok ]; } else { rollingData[i] = [ originalData[i][0], null ]; } } return rollingData; }; /** @inheritDoc */ DefaultHandler.prototype.getExtremeYValues = function getExtremeYValues(series, dateWindow, stepPlot) { var minY = null, maxY = null, y; var firstIdx = 0, lastIdx = series.length - 1; for ( var j = firstIdx; j <= lastIdx; j++) { y = series[j][1]; if (y === null || isNaN(y)) continue; if (maxY === null || y > maxY) { maxY = y; } if (minY === null || y < minY) { minY = y; } } return [ minY, maxY ]; }; export default DefaultHandler; dygraphs-2.2.1/src/dygraph-canvas.js010064400000000000000000000662311437353256000143400ustar00/** * @license * Copyright 2006 Dan Vanderkam (danvdk@gmail.com) * MIT-licenced: https://opensource.org/licenses/MIT */ /** * @fileoverview Based on PlotKit.CanvasRenderer, but modified to meet the * needs of dygraphs. * * In particular, support for: * - grid overlays * - high/low bands * - dygraphs attribute system */ /** * The DygraphCanvasRenderer class does the actual rendering of the chart onto * a canvas. It's based on PlotKit.CanvasRenderer. * @param {Object} element The canvas to attach to * @param {Object} elementContext The 2d context of the canvas (injected so it * can be mocked for testing.) * @param {Layout} layout The DygraphLayout object for this graph. * @constructor */ /*global Dygraph:false */ "use strict"; import * as utils from './dygraph-utils'; import Dygraph from './dygraph'; /** * @constructor * * This gets called when there are "new points" to chart. This is generally the * case when the underlying data being charted has changed. It is _not_ called * in the common case that the user has zoomed or is panning the view. * * The chart canvas has already been created by the Dygraph object. The * renderer simply gets a drawing context. * * @param {Dygraph} dygraph The chart to which this renderer belongs. * @param {HTMLCanvasElement} element The <canvas> DOM element on which to draw. * @param {CanvasRenderingContext2D} elementContext The drawing context. * @param {DygraphLayout} layout The chart's DygraphLayout object. * * TODO(danvk): remove the elementContext property. */ var DygraphCanvasRenderer = function(dygraph, element, elementContext, layout) { this.dygraph_ = dygraph; this.layout = layout; this.element = element; this.elementContext = elementContext; this.height = dygraph.height_; this.width = dygraph.width_; // --- check whether everything is ok before we return if (!utils.isCanvasSupported(this.element)) { throw "Canvas is not supported."; } // internal state this.area = layout.getPlotArea(); // Set up a clipping area for the canvas (and the interaction canvas). // This ensures that we don't overdraw. var ctx = this.dygraph_.canvas_ctx_; ctx.beginPath(); ctx.rect(this.area.x, this.area.y, this.area.w, this.area.h); ctx.clip(); ctx = this.dygraph_.hidden_ctx_; ctx.beginPath(); ctx.rect(this.area.x, this.area.y, this.area.w, this.area.h); ctx.clip(); }; /** * Clears out all chart content and DOM elements. * This is called immediately before render() on every frame, including * during zooms and pans. * @private */ DygraphCanvasRenderer.prototype.clear = function() { this.elementContext.clearRect(0, 0, this.width, this.height); }; /** * This method is responsible for drawing everything on the chart, including * lines, high/low bands, fills and axes. * It is called immediately after clear() on every frame, including during pans * and zooms. * @private */ DygraphCanvasRenderer.prototype.render = function() { // attaches point.canvas{x,y} this._updatePoints(); // actually draws the chart. this._renderLineChart(); }; /** * Returns a predicate to be used with an iterator, which will * iterate over points appropriately, depending on whether * connectSeparatedPoints is true. When it's false, the predicate will * skip over points with missing yVals. */ DygraphCanvasRenderer._getIteratorPredicate = function(connectSeparatedPoints) { return connectSeparatedPoints ? DygraphCanvasRenderer._predicateThatSkipsEmptyPoints : null; }; DygraphCanvasRenderer._predicateThatSkipsEmptyPoints = function(array, idx) { return array[idx].yval !== null; }; /** * Draws a line with the styles passed in and calls all the drawPointCallbacks. * @param {Object} e The dictionary passed to the plotter function. * @private */ DygraphCanvasRenderer._drawStyledLine = function(e, color, strokeWidth, strokePattern, drawPoints, drawPointCallback, pointSize) { var g = e.dygraph; // TODO(konigsberg): Compute attributes outside this method call. var stepPlot = g.getBooleanOption("stepPlot", e.setName); if (!utils.isArrayLike(strokePattern)) { strokePattern = null; } var drawGapPoints = g.getBooleanOption('drawGapEdgePoints', e.setName); var points = e.points; var setName = e.setName; var iter = utils.createIterator(points, 0, points.length, DygraphCanvasRenderer._getIteratorPredicate( g.getBooleanOption("connectSeparatedPoints", setName))); var stroking = strokePattern && (strokePattern.length >= 2); var ctx = e.drawingContext; ctx.save(); if (stroking) { if (ctx.setLineDash) ctx.setLineDash(strokePattern); } var pointsOnLine = DygraphCanvasRenderer._drawSeries( e, iter, strokeWidth, pointSize, drawPoints, drawGapPoints, stepPlot, color); DygraphCanvasRenderer._drawPointsOnLine( e, pointsOnLine, drawPointCallback, color, pointSize); if (stroking) { if (ctx.setLineDash) ctx.setLineDash([]); } ctx.restore(); }; /** * This does the actual drawing of lines on the canvas, for just one series. * Returns a list of [canvasx, canvasy] pairs for points for which a * drawPointCallback should be fired. These include isolated points, or all * points if drawPoints=true. * @param {Object} e The dictionary passed to the plotter function. * @private */ DygraphCanvasRenderer._drawSeries = function(e, iter, strokeWidth, pointSize, drawPoints, drawGapPoints, stepPlot, color) { var prevCanvasX = null; var prevCanvasY = null; var nextCanvasY = null; var isIsolated; // true if this point is isolated (no line segments) var point; // the point being processed in the while loop var pointsOnLine = []; // Array of [canvasx, canvasy] pairs. var first = true; // the first cycle through the while loop var ctx = e.drawingContext; ctx.beginPath(); ctx.strokeStyle = color; ctx.lineWidth = strokeWidth; // NOTE: we break the iterator's encapsulation here for about a 25% speedup. var arr = iter.array_; var limit = iter.end_; var predicate = iter.predicate_; for (var i = iter.start_; i < limit; i++) { point = arr[i]; if (predicate) { while (i < limit && !predicate(arr, i)) { i++; } if (i == limit) break; point = arr[i]; } // FIXME: The 'canvasy != canvasy' test here catches NaN values but the test // doesn't catch Infinity values. Could change this to // !isFinite(point.canvasy), but I assume it avoids isNaN for performance? if (point.canvasy === null || point.canvasy != point.canvasy) { if (stepPlot && prevCanvasX !== null) { // Draw a horizontal line to the start of the missing data ctx.moveTo(prevCanvasX, prevCanvasY); ctx.lineTo(point.canvasx, prevCanvasY); } prevCanvasX = prevCanvasY = null; } else { isIsolated = false; if (drawGapPoints || prevCanvasX === null) { iter.nextIdx_ = i; iter.next(); nextCanvasY = iter.hasNext ? iter.peek.canvasy : null; var isNextCanvasYNullOrNaN = nextCanvasY === null || nextCanvasY != nextCanvasY; isIsolated = (prevCanvasX === null && isNextCanvasYNullOrNaN); if (drawGapPoints) { // Also consider a point to be "isolated" if it's adjacent to a // null point, excluding the graph edges. if ((!first && prevCanvasX === null) || (iter.hasNext && isNextCanvasYNullOrNaN)) { isIsolated = true; } } } if (prevCanvasX !== null) { if (strokeWidth) { if (stepPlot) { ctx.moveTo(prevCanvasX, prevCanvasY); ctx.lineTo(point.canvasx, prevCanvasY); } ctx.lineTo(point.canvasx, point.canvasy); } } else { ctx.moveTo(point.canvasx, point.canvasy); } if (drawPoints || isIsolated) { pointsOnLine.push([point.canvasx, point.canvasy, point.idx]); } prevCanvasX = point.canvasx; prevCanvasY = point.canvasy; } first = false; } ctx.stroke(); return pointsOnLine; }; /** * This fires the drawPointCallback functions, which draw dots on the points by * default. This gets used when the "drawPoints" option is set, or when there * are isolated points. * @param {Object} e The dictionary passed to the plotter function. * @private */ DygraphCanvasRenderer._drawPointsOnLine = function( e, pointsOnLine, drawPointCallback, color, pointSize) { var ctx = e.drawingContext; for (var idx = 0; idx < pointsOnLine.length; idx++) { var cb = pointsOnLine[idx]; ctx.save(); drawPointCallback.call(e.dygraph, e.dygraph, e.setName, ctx, cb[0], cb[1], color, pointSize, cb[2]); ctx.restore(); } }; /** * Attaches canvas coordinates to the points array. * @private */ DygraphCanvasRenderer.prototype._updatePoints = function() { // Update Points // TODO(danvk): here // // TODO(bhs): this loop is a hot-spot for high-point-count charts. These // transformations can be pushed into the canvas via linear transformation // matrices. // NOTE(danvk): this is trickier than it sounds at first. The transformation // needs to be done before the .moveTo() and .lineTo() calls, but must be // undone before the .stroke() call to ensure that the stroke width is // unaffected. An alternative is to reduce the stroke width in the // transformed coordinate space, but you can't specify different values for // each dimension (as you can with .scale()). The speedup here is ~12%. var sets = this.layout.points; for (var i = sets.length; i--;) { var points = sets[i]; for (var j = points.length; j--;) { var point = points[j]; point.canvasx = this.area.w * point.x + this.area.x; point.canvasy = this.area.h * point.y + this.area.y; } } }; /** * Add canvas Actually draw the lines chart, including high/low bands. * * This function can only be called if DygraphLayout's points array has been * updated with canvas{x,y} attributes, i.e. by * DygraphCanvasRenderer._updatePoints. * * @param {string=} opt_seriesName when specified, only that series will * be drawn. (This is used for expedited redrawing with highlightSeriesOpts) * @param {CanvasRenderingContext2D} opt_ctx when specified, the drawing * context. However, lines are typically drawn on the object's * elementContext. * @private */ DygraphCanvasRenderer.prototype._renderLineChart = function(opt_seriesName, opt_ctx) { var ctx = opt_ctx || this.elementContext; var i; var sets = this.layout.points; var setNames = this.layout.setNames; var setName; this.colors = this.dygraph_.colorsMap_; // Determine which series have specialized plotters. var plotter_attr = this.dygraph_.getOption("plotter"); var plotters = plotter_attr; if (!utils.isArrayLike(plotters)) { plotters = [plotters]; } var setPlotters = {}; // series name -> plotter fn. for (i = 0; i < setNames.length; i++) { setName = setNames[i]; var setPlotter = this.dygraph_.getOption("plotter", setName); if (setPlotter == plotter_attr) continue; // not specialized. setPlotters[setName] = setPlotter; } for (i = 0; i < plotters.length; i++) { var plotter = plotters[i]; var is_last = (i == plotters.length - 1); for (var j = 0; j < sets.length; j++) { setName = setNames[j]; if (opt_seriesName && setName != opt_seriesName) continue; var points = sets[j]; // Only throw in the specialized plotters on the last iteration. var p = plotter; if (setName in setPlotters) { if (is_last) { p = setPlotters[setName]; } else { // Don't use the standard plotters in this case. continue; } } var color = this.colors[setName]; var strokeWidth = this.dygraph_.getOption("strokeWidth", setName); ctx.save(); ctx.strokeStyle = color; ctx.lineWidth = strokeWidth; p({ points: points, setName: setName, drawingContext: ctx, color: color, strokeWidth: strokeWidth, dygraph: this.dygraph_, axis: this.dygraph_.axisPropertiesForSeries(setName), plotArea: this.area, seriesIndex: j, seriesCount: sets.length, singleSeriesName: opt_seriesName, allSeriesPoints: sets }); ctx.restore(); } } }; /** * Standard plotters. These may be used by clients via Dygraph.Plotters. * See comments there for more details. */ DygraphCanvasRenderer._Plotters = { linePlotter: function(e) { DygraphCanvasRenderer._linePlotter(e); }, fillPlotter: function(e) { DygraphCanvasRenderer._fillPlotter(e); }, errorPlotter: function(e) { DygraphCanvasRenderer._errorPlotter(e); } }; /** * Plotter which draws the central lines for a series. * @private */ DygraphCanvasRenderer._linePlotter = function(e) { var g = e.dygraph; var setName = e.setName; var strokeWidth = e.strokeWidth; // TODO(danvk): Check if there's any performance impact of just calling // getOption() inside of _drawStyledLine. Passing in so many parameters makes // this code a bit nasty. var borderWidth = g.getNumericOption("strokeBorderWidth", setName); var drawPointCallback = g.getOption("drawPointCallback", setName) || utils.Circles.DEFAULT; var strokePattern = g.getOption("strokePattern", setName); var drawPoints = g.getBooleanOption("drawPoints", setName); var pointSize = g.getNumericOption("pointSize", setName); if (borderWidth && strokeWidth) { DygraphCanvasRenderer._drawStyledLine(e, g.getOption("strokeBorderColor", setName), strokeWidth + 2 * borderWidth, strokePattern, drawPoints, drawPointCallback, pointSize ); } DygraphCanvasRenderer._drawStyledLine(e, e.color, strokeWidth, strokePattern, drawPoints, drawPointCallback, pointSize ); }; /** * Draws the shaded high/low bands (confidence intervals) for each series. * This happens before the center lines are drawn, since the center lines * need to be drawn on top of the high/low bands for all series. * @private */ DygraphCanvasRenderer._errorPlotter = function(e) { var g = e.dygraph; var setName = e.setName; var errorBars = g.getBooleanOption("errorBars") || g.getBooleanOption("customBars"); if (!errorBars) return; var fillGraph = g.getBooleanOption("fillGraph", setName); if (fillGraph) { console.warn("Can't use fillGraph option with customBars or errorBars option"); } var ctx = e.drawingContext; var color = e.color; var fillAlpha = g.getNumericOption('fillAlpha', setName); var stepPlot = g.getBooleanOption("stepPlot", setName); var points = e.points; var iter = utils.createIterator(points, 0, points.length, DygraphCanvasRenderer._getIteratorPredicate( g.getBooleanOption("connectSeparatedPoints", setName))); var newYs; // setup graphics context var prevX = NaN; var prevY = NaN; var prevYs = [-1, -1]; // should be same color as the lines but only 15% opaque. var rgb = utils.toRGB_(color); var err_color = 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + fillAlpha + ')'; ctx.fillStyle = err_color; ctx.beginPath(); var isNullUndefinedOrNaN = function(x) { return (x === null || x === undefined || isNaN(x)); }; while (iter.hasNext) { var point = iter.next(); if ((!stepPlot && isNullUndefinedOrNaN(point.y)) || (stepPlot && !isNaN(prevY) && isNullUndefinedOrNaN(prevY))) { prevX = NaN; continue; } newYs = [ point.y_bottom, point.y_top ]; if (stepPlot) { prevY = point.y; } // The documentation specifically disallows nulls inside the point arrays, // but in case it happens we should do something sensible. if (isNaN(newYs[0])) newYs[0] = point.y; if (isNaN(newYs[1])) newYs[1] = point.y; newYs[0] = e.plotArea.h * newYs[0] + e.plotArea.y; newYs[1] = e.plotArea.h * newYs[1] + e.plotArea.y; if (!isNaN(prevX)) { if (stepPlot) { ctx.moveTo(prevX, prevYs[0]); ctx.lineTo(point.canvasx, prevYs[0]); ctx.lineTo(point.canvasx, prevYs[1]); } else { ctx.moveTo(prevX, prevYs[0]); ctx.lineTo(point.canvasx, newYs[0]); ctx.lineTo(point.canvasx, newYs[1]); } ctx.lineTo(prevX, prevYs[1]); ctx.closePath(); } prevYs = newYs; prevX = point.canvasx; } ctx.fill(); }; /** * Proxy for CanvasRenderingContext2D which drops moveTo/lineTo calls which are * superfluous. It accumulates all movements which haven't changed the x-value * and only applies the two with the most extreme y-values. * * Calls to lineTo/moveTo must have non-decreasing x-values. */ DygraphCanvasRenderer._fastCanvasProxy = function(context) { var pendingActions = []; // array of [type, x, y] tuples var lastRoundedX = null; var lastFlushedX = null; var LINE_TO = 1, MOVE_TO = 2; var actionCount = 0; // number of moveTos and lineTos passed to context. // Drop superfluous motions // Assumes all pendingActions have the same (rounded) x-value. var compressActions = function(opt_losslessOnly) { if (pendingActions.length <= 1) return; // Lossless compression: drop inconsequential moveTos. for (var i = pendingActions.length - 1; i > 0; i--) { var action = pendingActions[i]; if (action[0] == MOVE_TO) { var prevAction = pendingActions[i - 1]; if (prevAction[1] == action[1] && prevAction[2] == action[2]) { pendingActions.splice(i, 1); } } } // Lossless compression: ... drop consecutive moveTos ... for (var i = 0; i < pendingActions.length - 1; /* incremented internally */) { var action = pendingActions[i]; if (action[0] == MOVE_TO && pendingActions[i + 1][0] == MOVE_TO) { pendingActions.splice(i, 1); } else { i++; } } // Lossy compression: ... drop all but the extreme y-values ... if (pendingActions.length > 2 && !opt_losslessOnly) { // keep an initial moveTo, but drop all others. var startIdx = 0; if (pendingActions[0][0] == MOVE_TO) startIdx++; var minIdx = null, maxIdx = null; for (var i = startIdx; i < pendingActions.length; i++) { var action = pendingActions[i]; if (action[0] != LINE_TO) continue; if (minIdx === null && maxIdx === null) { minIdx = i; maxIdx = i; } else { var y = action[2]; if (y < pendingActions[minIdx][2]) { minIdx = i; } else if (y > pendingActions[maxIdx][2]) { maxIdx = i; } } } var minAction = pendingActions[minIdx], maxAction = pendingActions[maxIdx]; pendingActions.splice(startIdx, pendingActions.length - startIdx); if (minIdx < maxIdx) { pendingActions.push(minAction); pendingActions.push(maxAction); } else if (minIdx > maxIdx) { pendingActions.push(maxAction); pendingActions.push(minAction); } else { pendingActions.push(minAction); } } }; var flushActions = function(opt_noLossyCompression) { compressActions(opt_noLossyCompression); for (var i = 0, len = pendingActions.length; i < len; i++) { var action = pendingActions[i]; if (action[0] == LINE_TO) { context.lineTo(action[1], action[2]); } else if (action[0] == MOVE_TO) { context.moveTo(action[1], action[2]); } } if (pendingActions.length) { lastFlushedX = pendingActions[pendingActions.length - 1][1]; } actionCount += pendingActions.length; pendingActions = []; }; var addAction = function(action, x, y) { var rx = Math.round(x); if (lastRoundedX === null || rx != lastRoundedX) { // if there are large gaps on the x-axis, it's essential to keep the // first and last point as well. var hasGapOnLeft = (lastRoundedX - lastFlushedX > 1), hasGapOnRight = (rx - lastRoundedX > 1), hasGap = hasGapOnLeft || hasGapOnRight; flushActions(hasGap); lastRoundedX = rx; } pendingActions.push([action, x, y]); }; return { moveTo: function(x, y) { addAction(MOVE_TO, x, y); }, lineTo: function(x, y) { addAction(LINE_TO, x, y); }, // for major operations like stroke/fill, we skip compression to ensure // that there are no artifacts at the right edge. stroke: function() { flushActions(true); context.stroke(); }, fill: function() { flushActions(true); context.fill(); }, beginPath: function() { flushActions(true); context.beginPath(); }, closePath: function() { flushActions(true); context.closePath(); }, _count: function() { return actionCount; } }; }; /** * Draws the shaded regions when "fillGraph" is set. * Not to be confused with high/low bands (historically misnamed errorBars). * * For stacked charts, it's more convenient to handle all the series * simultaneously. So this plotter plots all the points on the first series * it's asked to draw, then ignores all the other series. * * @private */ DygraphCanvasRenderer._fillPlotter = function(e) { // Skip if we're drawing a single series for interactive highlight overlay. if (e.singleSeriesName) return; // We'll handle all the series at once, not one-by-one. if (e.seriesIndex !== 0) return; var g = e.dygraph; var setNames = g.getLabels().slice(1); // remove x-axis // getLabels() includes names for invisible series, which are not included in // allSeriesPoints. We remove those to make the two match. // TODO(danvk): provide a simpler way to get this information. for (var i = setNames.length; i >= 0; i--) { if (!g.visibility()[i]) setNames.splice(i, 1); } var anySeriesFilled = (function() { for (var i = 0; i < setNames.length; i++) { if (g.getBooleanOption("fillGraph", setNames[i])) return true; } return false; })(); if (!anySeriesFilled) return; var area = e.plotArea; var sets = e.allSeriesPoints; var setCount = sets.length; var stackedGraph = g.getBooleanOption("stackedGraph"); var colors = g.getColors(); // For stacked graphs, track the baseline for filling. // // The filled areas below graph lines are trapezoids with two // vertical edges. The top edge is the line segment being drawn, and // the baseline is the bottom edge. Each baseline corresponds to the // top line segment from the previous stacked line. In the case of // step plots, the trapezoids are rectangles. var baseline = {}; var currBaseline; var prevStepPlot; // for different line drawing modes (line/step) per series // Helper function to trace a line back along the baseline. var traceBackPath = function(ctx, baselineX, baselineY, pathBack) { ctx.lineTo(baselineX, baselineY); if (stackedGraph) { for (var i = pathBack.length - 1; i >= 0; i--) { var pt = pathBack[i]; ctx.lineTo(pt[0], pt[1]); } } }; // process sets in reverse order (needed for stacked graphs) for (var setIdx = setCount - 1; setIdx >= 0; setIdx--) { var ctx = e.drawingContext; var setName = setNames[setIdx]; if (!g.getBooleanOption('fillGraph', setName)) continue; var fillAlpha = g.getNumericOption('fillAlpha', setName); var stepPlot = g.getBooleanOption('stepPlot', setName); var color = colors[setIdx]; var axis = g.axisPropertiesForSeries(setName); var axisY = 1.0 + axis.minyval * axis.yscale; if (axisY < 0.0) axisY = 0.0; else if (axisY > 1.0) axisY = 1.0; axisY = area.h * axisY + area.y; var points = sets[setIdx]; var iter = utils.createIterator(points, 0, points.length, DygraphCanvasRenderer._getIteratorPredicate( g.getBooleanOption("connectSeparatedPoints", setName))); // setup graphics context var prevX = NaN; var prevYs = [-1, -1]; var newYs; // should be same color as the lines but only 15% opaque. var rgb = utils.toRGB_(color); var err_color = 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + fillAlpha + ')'; ctx.fillStyle = err_color; ctx.beginPath(); var last_x, is_first = true; // If the point density is high enough, dropping segments on their way to // the canvas justifies the overhead of doing so. if (points.length > 2 * g.width_ || Dygraph.FORCE_FAST_PROXY) { ctx = DygraphCanvasRenderer._fastCanvasProxy(ctx); } // For filled charts, we draw points from left to right, then back along // the x-axis to complete a shape for filling. // For stacked plots, this "back path" is a more complex shape. This array // stores the [x, y] values needed to trace that shape. var pathBack = []; // TODO(danvk): there are a lot of options at play in this loop. // The logic would be much clearer if some (e.g. stackGraph and // stepPlot) were split off into separate sub-plotters. var point; while (iter.hasNext) { point = iter.next(); if (!utils.isOK(point.y) && !stepPlot) { traceBackPath(ctx, prevX, prevYs[1], pathBack); pathBack = []; prevX = NaN; if (point.y_stacked !== null && !isNaN(point.y_stacked)) { baseline[point.canvasx] = area.h * point.y_stacked + area.y; } continue; } if (stackedGraph) { if (!is_first && last_x == point.xval) { continue; } else { is_first = false; last_x = point.xval; } currBaseline = baseline[point.canvasx]; var lastY; if (currBaseline === undefined) { lastY = axisY; } else { if(prevStepPlot) { lastY = currBaseline[0]; } else { lastY = currBaseline; } } newYs = [ point.canvasy, lastY ]; if (stepPlot) { // Step plots must keep track of the top and bottom of // the baseline at each point. if (prevYs[0] === -1) { baseline[point.canvasx] = [ point.canvasy, axisY ]; } else { baseline[point.canvasx] = [ point.canvasy, prevYs[0] ]; } } else { baseline[point.canvasx] = point.canvasy; } } else { if (isNaN(point.canvasy) && stepPlot) { newYs = [ area.y + area.h, axisY ]; } else { newYs = [ point.canvasy, axisY ]; } } if (!isNaN(prevX)) { // Move to top fill point if (stepPlot) { ctx.lineTo(point.canvasx, prevYs[0]); ctx.lineTo(point.canvasx, newYs[0]); } else { ctx.lineTo(point.canvasx, newYs[0]); } // Record the baseline for the reverse path. if (stackedGraph) { pathBack.push([prevX, prevYs[1]]); if (prevStepPlot && currBaseline) { // Draw to the bottom of the baseline pathBack.push([point.canvasx, currBaseline[1]]); } else { pathBack.push([point.canvasx, newYs[1]]); } } } else { ctx.moveTo(point.canvasx, newYs[1]); ctx.lineTo(point.canvasx, newYs[0]); } prevYs = newYs; prevX = point.canvasx; } prevStepPlot = stepPlot; if (newYs && point) { traceBackPath(ctx, point.canvasx, newYs[1], pathBack); pathBack = []; } ctx.fill(); } }; export default DygraphCanvasRenderer; dygraphs-2.2.1/src/dygraph-default-attrs.js010064400000000000000000000064641437353256000156460ustar00'use strict' import * as DygraphTickers from './dygraph-tickers'; import DygraphInteraction from './dygraph-interaction-model'; import DygraphCanvasRenderer from './dygraph-canvas'; import * as utils from './dygraph-utils'; // Default attribute values. var DEFAULT_ATTRS = { highlightCircleSize: 3, highlightSeriesOpts: null, highlightSeriesBackgroundAlpha: 0.5, highlightSeriesBackgroundColor: 'rgb(255, 255, 255)', labelsSeparateLines: false, labelsShowZeroValues: true, labelsKMB: false, labelsKMG2: false, showLabelsOnHighlight: true, digitsAfterDecimal: 2, maxNumberWidth: 6, sigFigs: null, strokeWidth: 1.0, strokeBorderWidth: 0, strokeBorderColor: "white", axisTickSize: 3, axisLabelFontSize: 14, rightGap: 5, showRoller: false, xValueParser: undefined, delimiter: ',', sigma: 2.0, errorBars: false, fractions: false, wilsonInterval: true, // only relevant if fractions is true customBars: false, fillGraph: false, fillAlpha: 0.15, connectSeparatedPoints: false, stackedGraph: false, stackedGraphNaNFill: 'all', hideOverlayOnMouseOut: true, resizable: 'no', legend: 'onmouseover', legendFollowOffsetX: 50, legendFollowOffsetY: -50, stepPlot: false, xRangePad: 0, yRangePad: null, drawAxesAtZero: false, // Sizes of the various chart labels. titleHeight: 28, xLabelHeight: 18, yLabelWidth: 18, axisLineColor: "black", axisLineWidth: 0.3, gridLineWidth: 0.3, axisLabelWidth: 50, gridLineColor: "rgb(128,128,128)", interactionModel: DygraphInteraction.defaultModel, animatedZooms: false, // (for now) animateBackgroundFade: true, // Range selector options showRangeSelector: false, rangeSelectorHeight: 40, rangeSelectorPlotStrokeColor: "#808FAB", rangeSelectorPlotFillGradientColor: "white", rangeSelectorPlotFillColor: "#A7B1C4", rangeSelectorBackgroundStrokeColor: "gray", rangeSelectorBackgroundLineWidth: 1, rangeSelectorPlotLineWidth:1.5, rangeSelectorForegroundStrokeColor: "black", rangeSelectorForegroundLineWidth: 1, rangeSelectorAlpha: 0.6, showInRangeSelector: null, // The ordering here ensures that central lines always appear above any // fill bars/error bars. plotter: [ DygraphCanvasRenderer._fillPlotter, DygraphCanvasRenderer._errorPlotter, DygraphCanvasRenderer._linePlotter ], plugins: [ ], // per-axis options axes: { x: { pixelsPerLabel: 70, axisLabelWidth: 60, axisLabelFormatter: utils.dateAxisLabelFormatter, valueFormatter: utils.dateValueFormatter, drawGrid: true, drawAxis: true, independentTicks: true, ticker: DygraphTickers.dateTicker }, y: { axisLabelWidth: 50, pixelsPerLabel: 30, valueFormatter: utils.numberValueFormatter, axisLabelFormatter: utils.numberAxisLabelFormatter, drawGrid: true, drawAxis: true, independentTicks: true, ticker: DygraphTickers.numericTicks }, y2: { axisLabelWidth: 50, pixelsPerLabel: 30, valueFormatter: utils.numberValueFormatter, axisLabelFormatter: utils.numberAxisLabelFormatter, drawAxis: true, // only applies when there are two axes of data. drawGrid: false, independentTicks: false, ticker: DygraphTickers.numericTicks } } }; export default DEFAULT_ATTRS; dygraphs-2.2.1/src/dygraph-gviz.js010064400000000000000000000044051437353256000140370ustar00/** * @license * Copyright 2011 Dan Vanderkam (danvdk@gmail.com) * MIT-licenced: https://opensource.org/licenses/MIT */ /** * @fileoverview A wrapper around the Dygraph class which implements the * interface for a GViz (aka Google Visualization API) visualization. * It is designed to be a drop-in replacement for Google's AnnotatedTimeline, * so the documentation at * http://code.google.com/apis/chart/interactive/docs/gallery/annotatedtimeline.html * translates over directly. * * For a full demo, see: * - http://dygraphs.com/tests/gviz.html * - http://dygraphs.com/tests/annotation-gviz.html */ /*global Dygraph:false */ "use strict"; import Dygraph from './dygraph'; /** * A wrapper around Dygraph that implements the gviz API. * @param {!HTMLDivElement} container The DOM object the visualization should * live in. * @constructor */ var GVizChart = function(container) { this.container = container; }; /** * @param {GVizDataTable} data * @param {Object.<*>} options */ GVizChart.prototype.draw = function(data, options) { // Clear out any existing dygraph. // TODO(danvk): would it make more sense to simply redraw using the current // date_graph object? this.container.innerHTML = ''; if (typeof(this.date_graph) != 'undefined') { this.date_graph.destroy(); } this.date_graph = new Dygraph(this.container, data, options); }; /** * Google charts compatible setSelection * Only row selection is supported, all points in the row will be highlighted * @param {Array.<{row:number}>} selection_array array of the selected cells * @public */ GVizChart.prototype.setSelection = function(selection_array) { var row = false; if (selection_array.length) { row = selection_array[0].row; } this.date_graph.setSelection(row); }; /** * Google charts compatible getSelection implementation * @return {Array.<{row:number,column:number}>} array of the selected cells * @public */ GVizChart.prototype.getSelection = function() { var selection = []; var row = this.date_graph.getSelection(); if (row < 0) return selection; var points = this.date_graph.layout_.points; for (var setIdx = 0; setIdx < points.length; ++setIdx) { selection.push({row: row, column: setIdx + 1}); } return selection; }; export default GVizChart; dygraphs-2.2.1/src/dygraph-interaction-model.js010064400000000000000000000614671437353256000165100ustar00/** * @license * Copyright 2011 Robert Konigsberg (konigsberg@google.com) * MIT-licenced: https://opensource.org/licenses/MIT */ /** * @fileoverview The default interaction model for Dygraphs. This is kept out * of dygraph.js for better navigability. * @author Robert Konigsberg (konigsberg@google.com) */ /*global Dygraph:false */ "use strict"; import * as utils from './dygraph-utils'; /** * You can drag this many pixels past the edge of the chart and still have it * be considered a zoom. This makes it easier to zoom to the exact edge of the * chart, a fairly common operation. */ var DRAG_EDGE_MARGIN = 100; /** * A collection of functions to facilitate build custom interaction models. * @class */ var DygraphInteraction = {}; /** * Checks whether the beginning & ending of an event were close enough that it * should be considered a click. If it should, dispatch appropriate events. * Returns true if the event was treated as a click. * * @param {Event} event * @param {Dygraph} g * @param {Object} context */ DygraphInteraction.maybeTreatMouseOpAsClick = function(event, g, context) { context.dragEndX = utils.dragGetX_(event, context); context.dragEndY = utils.dragGetY_(event, context); var regionWidth = Math.abs(context.dragEndX - context.dragStartX); var regionHeight = Math.abs(context.dragEndY - context.dragStartY); if (regionWidth < 2 && regionHeight < 2 && g.lastx_ !== undefined && g.lastx_ !== null) { DygraphInteraction.treatMouseOpAsClick(g, event, context); } context.regionWidth = regionWidth; context.regionHeight = regionHeight; }; /** * Called in response to an interaction model operation that * should start the default panning behavior. * * It's used in the default callback for "mousedown" operations. * Custom interaction model builders can use it to provide the default * panning behavior. * * @param {Event} event the event object which led to the startPan call. * @param {Dygraph} g The dygraph on which to act. * @param {Object} context The dragging context object (with * dragStartX/dragStartY/etc. properties). This function modifies the * context. */ DygraphInteraction.startPan = function(event, g, context) { var i, axis; context.isPanning = true; var xRange = g.xAxisRange(); if (g.getOptionForAxis("logscale", "x")) { context.initialLeftmostDate = utils.log10(xRange[0]); context.dateRange = utils.log10(xRange[1]) - utils.log10(xRange[0]); } else { context.initialLeftmostDate = xRange[0]; context.dateRange = xRange[1] - xRange[0]; } context.xUnitsPerPixel = context.dateRange / (g.plotter_.area.w - 1); if (g.getNumericOption("panEdgeFraction")) { var maxXPixelsToDraw = g.width_ * g.getNumericOption("panEdgeFraction"); var xExtremes = g.xAxisExtremes(); // I REALLY WANT TO CALL THIS xTremes! var boundedLeftX = g.toDomXCoord(xExtremes[0]) - maxXPixelsToDraw; var boundedRightX = g.toDomXCoord(xExtremes[1]) + maxXPixelsToDraw; var boundedLeftDate = g.toDataXCoord(boundedLeftX); var boundedRightDate = g.toDataXCoord(boundedRightX); context.boundedDates = [boundedLeftDate, boundedRightDate]; var boundedValues = []; var maxYPixelsToDraw = g.height_ * g.getNumericOption("panEdgeFraction"); for (i = 0; i < g.axes_.length; i++) { axis = g.axes_[i]; var yExtremes = axis.extremeRange; var boundedTopY = g.toDomYCoord(yExtremes[0], i) + maxYPixelsToDraw; var boundedBottomY = g.toDomYCoord(yExtremes[1], i) - maxYPixelsToDraw; var boundedTopValue = g.toDataYCoord(boundedTopY, i); var boundedBottomValue = g.toDataYCoord(boundedBottomY, i); boundedValues[i] = [boundedTopValue, boundedBottomValue]; } context.boundedValues = boundedValues; } else { // undo effect if it was once set context.boundedDates = null; context.boundedValues = null; } // Record the range of each y-axis at the start of the drag. // If any axis has a valueRange, then we want a 2D pan. // We can't store data directly in g.axes_, because it does not belong to us // and could change out from under us during a pan (say if there's a data // update). context.is2DPan = false; context.axes = []; for (i = 0; i < g.axes_.length; i++) { axis = g.axes_[i]; var axis_data = {}; var yRange = g.yAxisRange(i); // TODO(konigsberg): These values should be in |context|. // In log scale, initialTopValue, dragValueRange and unitsPerPixel are log scale. var logscale = g.attributes_.getForAxis("logscale", i); if (logscale) { axis_data.initialTopValue = utils.log10(yRange[1]); axis_data.dragValueRange = utils.log10(yRange[1]) - utils.log10(yRange[0]); } else { axis_data.initialTopValue = yRange[1]; axis_data.dragValueRange = yRange[1] - yRange[0]; } axis_data.unitsPerPixel = axis_data.dragValueRange / (g.plotter_.area.h - 1); context.axes.push(axis_data); // While calculating axes, set 2dpan. if (axis.valueRange) context.is2DPan = true; } }; /** * Called in response to an interaction model operation that * responds to an event that pans the view. * * It's used in the default callback for "mousemove" operations. * Custom interaction model builders can use it to provide the default * panning behavior. * * @param {Event} event the event object which led to the movePan call. * @param {Dygraph} g The dygraph on which to act. * @param {Object} context The dragging context object (with * dragStartX/dragStartY/etc. properties). This function modifies the * context. */ DygraphInteraction.movePan = function(event, g, context) { context.dragEndX = utils.dragGetX_(event, context); context.dragEndY = utils.dragGetY_(event, context); var minDate = context.initialLeftmostDate - (context.dragEndX - context.dragStartX) * context.xUnitsPerPixel; if (context.boundedDates) { minDate = Math.max(minDate, context.boundedDates[0]); } var maxDate = minDate + context.dateRange; if (context.boundedDates) { if (maxDate > context.boundedDates[1]) { // Adjust minDate, and recompute maxDate. minDate = minDate - (maxDate - context.boundedDates[1]); maxDate = minDate + context.dateRange; } } if (g.getOptionForAxis("logscale", "x")) { g.dateWindow_ = [ Math.pow(utils.LOG_SCALE, minDate), Math.pow(utils.LOG_SCALE, maxDate) ]; } else { g.dateWindow_ = [minDate, maxDate]; } // y-axis scaling is automatic unless this is a full 2D pan. if (context.is2DPan) { var pixelsDragged = context.dragEndY - context.dragStartY; // Adjust each axis appropriately. for (var i = 0; i < g.axes_.length; i++) { var axis = g.axes_[i]; var axis_data = context.axes[i]; var unitsDragged = pixelsDragged * axis_data.unitsPerPixel; var boundedValue = context.boundedValues ? context.boundedValues[i] : null; // In log scale, maxValue and minValue are the logs of those values. var maxValue = axis_data.initialTopValue + unitsDragged; if (boundedValue) { maxValue = Math.min(maxValue, boundedValue[1]); } var minValue = maxValue - axis_data.dragValueRange; if (boundedValue) { if (minValue < boundedValue[0]) { // Adjust maxValue, and recompute minValue. maxValue = maxValue - (minValue - boundedValue[0]); minValue = maxValue - axis_data.dragValueRange; } } if (g.attributes_.getForAxis("logscale", i)) { axis.valueRange = [ Math.pow(utils.LOG_SCALE, minValue), Math.pow(utils.LOG_SCALE, maxValue) ]; } else { axis.valueRange = [ minValue, maxValue ]; } } } g.drawGraph_(false); }; /** * Called in response to an interaction model operation that * responds to an event that ends panning. * * It's used in the default callback for "mouseup" operations. * Custom interaction model builders can use it to provide the default * panning behavior. * * @param {Event} event the event object which led to the endPan call. * @param {Dygraph} g The dygraph on which to act. * @param {Object} context The dragging context object (with * dragStartX/dragStartY/etc. properties). This function modifies the * context. */ DygraphInteraction.endPan = DygraphInteraction.maybeTreatMouseOpAsClick; /** * Called in response to an interaction model operation that * responds to an event that starts zooming. * * It's used in the default callback for "mousedown" operations. * Custom interaction model builders can use it to provide the default * zooming behavior. * * @param {Event} event the event object which led to the startZoom call. * @param {Dygraph} g The dygraph on which to act. * @param {Object} context The dragging context object (with * dragStartX/dragStartY/etc. properties). This function modifies the * context. */ DygraphInteraction.startZoom = function(event, g, context) { context.isZooming = true; context.zoomMoved = false; }; /** * Called in response to an interaction model operation that * responds to an event that defines zoom boundaries. * * It's used in the default callback for "mousemove" operations. * Custom interaction model builders can use it to provide the default * zooming behavior. * * @param {Event} event the event object which led to the moveZoom call. * @param {Dygraph} g The dygraph on which to act. * @param {Object} context The dragging context object (with * dragStartX/dragStartY/etc. properties). This function modifies the * context. */ DygraphInteraction.moveZoom = function(event, g, context) { context.zoomMoved = true; context.dragEndX = utils.dragGetX_(event, context); context.dragEndY = utils.dragGetY_(event, context); var xDelta = Math.abs(context.dragStartX - context.dragEndX); var yDelta = Math.abs(context.dragStartY - context.dragEndY); // drag direction threshold for y axis is twice as large as x axis context.dragDirection = (xDelta < yDelta / 2) ? utils.VERTICAL : utils.HORIZONTAL; g.drawZoomRect_( context.dragDirection, context.dragStartX, context.dragEndX, context.dragStartY, context.dragEndY, context.prevDragDirection, context.prevEndX, context.prevEndY); context.prevEndX = context.dragEndX; context.prevEndY = context.dragEndY; context.prevDragDirection = context.dragDirection; }; /** * TODO(danvk): move this logic into dygraph.js * @param {Dygraph} g * @param {Event} event * @param {Object} context */ DygraphInteraction.treatMouseOpAsClick = function(g, event, context) { var clickCallback = g.getFunctionOption('clickCallback'); var pointClickCallback = g.getFunctionOption('pointClickCallback'); var selectedPoint = null; // Find out if the click occurs on a point. var closestIdx = -1; var closestDistance = Number.MAX_VALUE; // check if the click was on a particular point. for (var i = 0; i < g.selPoints_.length; i++) { var p = g.selPoints_[i]; var distance = Math.pow(p.canvasx - context.dragEndX, 2) + Math.pow(p.canvasy - context.dragEndY, 2); if (!isNaN(distance) && (closestIdx == -1 || distance < closestDistance)) { closestDistance = distance; closestIdx = i; } } // Allow any click within two pixels of the dot. var radius = g.getNumericOption('highlightCircleSize') + 2; if (closestDistance <= radius * radius) { selectedPoint = g.selPoints_[closestIdx]; } if (selectedPoint) { var e = { cancelable: true, point: selectedPoint, canvasx: context.dragEndX, canvasy: context.dragEndY }; var defaultPrevented = g.cascadeEvents_('pointClick', e); if (defaultPrevented) { // Note: this also prevents click / clickCallback from firing. return; } if (pointClickCallback) { pointClickCallback.call(g, event, selectedPoint); } } var e = { cancelable: true, xval: g.lastx_, // closest point by x value pts: g.selPoints_, canvasx: context.dragEndX, canvasy: context.dragEndY }; if (!g.cascadeEvents_('click', e)) { if (clickCallback) { // TODO(danvk): pass along more info about the points, e.g. 'x' clickCallback.call(g, event, g.lastx_, g.selPoints_); } } }; /** * Called in response to an interaction model operation that * responds to an event that performs a zoom based on previously defined * bounds.. * * It's used in the default callback for "mouseup" operations. * Custom interaction model builders can use it to provide the default * zooming behavior. * * @param {Event} event the event object which led to the endZoom call. * @param {Dygraph} g The dygraph on which to end the zoom. * @param {Object} context The dragging context object (with * dragStartX/dragStartY/etc. properties). This function modifies the * context. */ DygraphInteraction.endZoom = function(event, g, context) { g.clearZoomRect_(); context.isZooming = false; DygraphInteraction.maybeTreatMouseOpAsClick(event, g, context); // The zoom rectangle is visibly clipped to the plot area, so its behavior // should be as well. // See http://code.google.com/p/dygraphs/issues/detail?id=280 var plotArea = g.getArea(); if (context.regionWidth >= 10 && context.dragDirection == utils.HORIZONTAL) { var left = Math.min(context.dragStartX, context.dragEndX), right = Math.max(context.dragStartX, context.dragEndX); left = Math.max(left, plotArea.x); right = Math.min(right, plotArea.x + plotArea.w); if (left < right) { g.doZoomX_(left, right); } context.cancelNextDblclick = true; } else if (context.regionHeight >= 10 && context.dragDirection == utils.VERTICAL) { var top = Math.min(context.dragStartY, context.dragEndY), bottom = Math.max(context.dragStartY, context.dragEndY); top = Math.max(top, plotArea.y); bottom = Math.min(bottom, plotArea.y + plotArea.h); if (top < bottom) { g.doZoomY_(top, bottom); } context.cancelNextDblclick = true; } context.dragStartX = null; context.dragStartY = null; }; /** * @private */ DygraphInteraction.startTouch = function(event, g, context) { event.preventDefault(); // touch browsers are all nice. if (event.touches.length > 1) { // If the user ever puts two fingers down, it's not a double tap. context.startTimeForDoubleTapMs = null; } var touches = []; for (var i = 0; i < event.touches.length; i++) { var t = event.touches[i]; var rect = t.target.getBoundingClientRect() // we dispense with 'dragGetX_' because all touchBrowsers support pageX touches.push({ pageX: t.pageX, pageY: t.pageY, dataX: g.toDataXCoord(t.clientX - rect.left), dataY: g.toDataYCoord(t.clientY - rect.top) // identifier: t.identifier }); } context.initialTouches = touches; if (touches.length == 1) { // This is just a swipe. context.initialPinchCenter = touches[0]; context.touchDirections = { x: true, y: true }; } else if (touches.length >= 2) { // It's become a pinch! // In case there are 3+ touches, we ignore all but the "first" two. // only screen coordinates can be averaged (data coords could be log scale). context.initialPinchCenter = { pageX: 0.5 * (touches[0].pageX + touches[1].pageX), pageY: 0.5 * (touches[0].pageY + touches[1].pageY), // TODO(danvk): remove dataX: 0.5 * (touches[0].dataX + touches[1].dataX), dataY: 0.5 * (touches[0].dataY + touches[1].dataY) }; // Make pinches in a 45-degree swath around either axis 1-dimensional zooms. var initialAngle = 180 / Math.PI * Math.atan2( context.initialPinchCenter.pageY - touches[0].pageY, touches[0].pageX - context.initialPinchCenter.pageX); // use symmetry to get it into the first quadrant. initialAngle = Math.abs(initialAngle); if (initialAngle > 90) initialAngle = 90 - initialAngle; context.touchDirections = { x: (initialAngle < (90 - 45/2)), y: (initialAngle > 45/2) }; } // save the full x & y ranges. context.initialRange = { x: g.xAxisRange(), y: g.yAxisRange() }; }; /** * @private */ DygraphInteraction.moveTouch = function(event, g, context) { // If the tap moves, then it's definitely not part of a double-tap. context.startTimeForDoubleTapMs = null; var i, touches = []; for (i = 0; i < event.touches.length; i++) { var t = event.touches[i]; touches.push({ pageX: t.pageX, pageY: t.pageY }); } var initialTouches = context.initialTouches; var c_now; // old and new centers. var c_init = context.initialPinchCenter; if (touches.length == 1) { c_now = touches[0]; } else { c_now = { pageX: 0.5 * (touches[0].pageX + touches[1].pageX), pageY: 0.5 * (touches[0].pageY + touches[1].pageY) }; } // this is the "swipe" component // we toss it out for now, but could use it in the future. var swipe = { pageX: c_now.pageX - c_init.pageX, pageY: c_now.pageY - c_init.pageY }; var dataWidth = context.initialRange.x[1] - context.initialRange.x[0]; var dataHeight = context.initialRange.y[0] - context.initialRange.y[1]; swipe.dataX = (swipe.pageX / g.plotter_.area.w) * dataWidth; swipe.dataY = (swipe.pageY / g.plotter_.area.h) * dataHeight; var xScale, yScale; // The residual bits are usually split into scale & rotate bits, but we split // them into x-scale and y-scale bits. if (touches.length == 1) { xScale = 1.0; yScale = 1.0; } else if (touches.length >= 2) { var initHalfWidth = (initialTouches[1].pageX - c_init.pageX); xScale = (touches[1].pageX - c_now.pageX) / initHalfWidth; var initHalfHeight = (initialTouches[1].pageY - c_init.pageY); yScale = (touches[1].pageY - c_now.pageY) / initHalfHeight; } // Clip scaling to [1/8, 8] to prevent too much blowup. xScale = Math.min(8, Math.max(0.125, xScale)); yScale = Math.min(8, Math.max(0.125, yScale)); var didZoom = false; if (context.touchDirections.x) { var cFactor = c_init.dataX - swipe.dataX / xScale; g.dateWindow_ = [ cFactor + (context.initialRange.x[0] - c_init.dataX) / xScale, cFactor + (context.initialRange.x[1] - c_init.dataX) / xScale ]; didZoom = true; } if (context.touchDirections.y) { for (i = 0; i < 1 /*g.axes_.length*/; i++) { var axis = g.axes_[i]; var logscale = g.attributes_.getForAxis("logscale", i); if (logscale) { // TODO(danvk): implement } else { var cFactor = c_init.dataY - swipe.dataY / yScale; axis.valueRange = [ cFactor + (context.initialRange.y[0] - c_init.dataY) / yScale, cFactor + (context.initialRange.y[1] - c_init.dataY) / yScale ]; didZoom = true; } } } g.drawGraph_(false); // We only call zoomCallback on zooms, not pans, to mirror desktop behavior. if (didZoom && touches.length > 1 && g.getFunctionOption('zoomCallback')) { var viewWindow = g.xAxisRange(); g.getFunctionOption("zoomCallback").call(g, viewWindow[0], viewWindow[1], g.yAxisRanges()); } }; /** * @private */ DygraphInteraction.endTouch = function(event, g, context) { if (event.touches.length !== 0) { // this is effectively a "reset" DygraphInteraction.startTouch(event, g, context); } else if (event.changedTouches.length == 1) { // Could be part of a "double tap" // The heuristic here is that it's a double-tap if the two touchend events // occur within 500ms and within a 50x50 pixel box. var now = new Date().getTime(); var t = event.changedTouches[0]; if (context.startTimeForDoubleTapMs && now - context.startTimeForDoubleTapMs < 500 && context.doubleTapX && Math.abs(context.doubleTapX - t.screenX) < 50 && context.doubleTapY && Math.abs(context.doubleTapY - t.screenY) < 50) { g.resetZoom(); } else { context.startTimeForDoubleTapMs = now; context.doubleTapX = t.screenX; context.doubleTapY = t.screenY; } } }; // Determine the distance from x to [left, right]. var distanceFromInterval = function(x, left, right) { if (x < left) { return left - x; } else if (x > right) { return x - right; } else { return 0; } }; /** * Returns the number of pixels by which the event happens from the nearest * edge of the chart. For events in the interior of the chart, this returns zero. */ var distanceFromChart = function(event, g) { var chartPos = utils.findPos(g.canvas_); var box = { left: chartPos.x, right: chartPos.x + g.canvas_.offsetWidth, top: chartPos.y, bottom: chartPos.y + g.canvas_.offsetHeight }; var pt = { x: utils.pageX(event), y: utils.pageY(event) }; var dx = distanceFromInterval(pt.x, box.left, box.right), dy = distanceFromInterval(pt.y, box.top, box.bottom); return Math.max(dx, dy); }; /** * Default interation model for dygraphs. You can refer to specific elements of * this when constructing your own interaction model, e.g.: * g.updateOptions( { * interactionModel: { * mousedown: DygraphInteraction.defaultInteractionModel.mousedown * } * } ); */ DygraphInteraction.defaultModel = { // Track the beginning of drag events mousedown: function(event, g, context) { // Right-click should not initiate a zoom. if (event.button && event.button == 2) return; context.initializeMouseDown(event, g, context); if (event.altKey || event.shiftKey) { DygraphInteraction.startPan(event, g, context); } else { DygraphInteraction.startZoom(event, g, context); } // Note: we register mousemove/mouseup on document to allow some leeway for // events to move outside of the chart. Interaction model events get // registered on the canvas, which is too small to allow this. var mousemove = function(event) { if (context.isZooming) { // When the mouse moves >200px from the chart edge, cancel the zoom. var d = distanceFromChart(event, g); if (d < DRAG_EDGE_MARGIN) { DygraphInteraction.moveZoom(event, g, context); } else { if (context.dragEndX !== null) { context.dragEndX = null; context.dragEndY = null; g.clearZoomRect_(); } } } else if (context.isPanning) { DygraphInteraction.movePan(event, g, context); } }; var mouseup = function(event) { if (context.isZooming) { if (context.dragEndX !== null) { DygraphInteraction.endZoom(event, g, context); } else { DygraphInteraction.maybeTreatMouseOpAsClick(event, g, context); } } else if (context.isPanning) { DygraphInteraction.endPan(event, g, context); } utils.removeEvent(document, 'mousemove', mousemove); utils.removeEvent(document, 'mouseup', mouseup); context.destroy(); }; g.addAndTrackEvent(document, 'mousemove', mousemove); g.addAndTrackEvent(document, 'mouseup', mouseup); }, willDestroyContextMyself: true, touchstart: function(event, g, context) { DygraphInteraction.startTouch(event, g, context); }, touchmove: function(event, g, context) { DygraphInteraction.moveTouch(event, g, context); }, touchend: function(event, g, context) { DygraphInteraction.endTouch(event, g, context); }, // Disable zooming out if panning. dblclick: function(event, g, context) { if (context.cancelNextDblclick) { context.cancelNextDblclick = false; return; } // Give plugins a chance to grab this event. var e = { canvasx: context.dragEndX, canvasy: context.dragEndY, cancelable: true, }; if (g.cascadeEvents_('dblclick', e)) { return; } if (event.altKey || event.shiftKey) { return; } g.resetZoom(); } }; /* Dygraph.DEFAULT_ATTRS.interactionModel = DygraphInteraction.defaultModel; // old ways of accessing these methods/properties Dygraph.defaultInteractionModel = DygraphInteraction.defaultModel; Dygraph.endZoom = DygraphInteraction.endZoom; Dygraph.moveZoom = DygraphInteraction.moveZoom; Dygraph.startZoom = DygraphInteraction.startZoom; Dygraph.endPan = DygraphInteraction.endPan; Dygraph.movePan = DygraphInteraction.movePan; Dygraph.startPan = DygraphInteraction.startPan; */ DygraphInteraction.nonInteractiveModel_ = { mousedown: function(event, g, context) { context.initializeMouseDown(event, g, context); }, mouseup: DygraphInteraction.maybeTreatMouseOpAsClick }; // Default interaction model when using the range selector. DygraphInteraction.dragIsPanInteractionModel = { mousedown: function(event, g, context) { context.initializeMouseDown(event, g, context); DygraphInteraction.startPan(event, g, context); }, mousemove: function(event, g, context) { if (context.isPanning) { DygraphInteraction.movePan(event, g, context); } }, mouseup: function(event, g, context) { if (context.isPanning) { DygraphInteraction.endPan(event, g, context); } } }; export default DygraphInteraction; dygraphs-2.2.1/src/dygraph-internal.externs.js010064400000000000000000000007671437353256000163720ustar00// This file: // - declares symbols that are provided outisde of dygraphs // - defines custom types used internally /** * @typedef {function( * (number|Date), * number, * function(string):*, * (Dygraph|undefined) * ):string} */ var AxisLabelFormatter; /** * @typedef {function(number,function(string),Dygraph):string} */ var ValueFormatter; /** * @typedef {Array.>>} */ var DygraphDataArray; /** * @constructor */ function GVizDataTable() {} dygraphs-2.2.1/src/dygraph-layout.js010064400000000000000000000247371437353256000144070ustar00/** * @license * Copyright 2011 Dan Vanderkam (danvdk@gmail.com) * MIT-licenced: https://opensource.org/licenses/MIT */ /** * @fileoverview Based on PlotKitLayout, but modified to meet the needs of * dygraphs. */ /*global Dygraph:false */ "use strict"; import * as utils from './dygraph-utils'; /** * Creates a new DygraphLayout object. * * This class contains all the data to be charted. * It uses data coordinates, but also records the chart range (in data * coordinates) and hence is able to calculate percentage positions ('In this * view, Point A lies 25% down the x-axis.') * * Two things that it does not do are: * 1. Record pixel coordinates for anything. * 2. (oddly) determine anything about the layout of chart elements. * * The naming is a vestige of Dygraph's original PlotKit roots. * * @constructor */ var DygraphLayout = function(dygraph) { this.dygraph_ = dygraph; /** * Array of points for each series. * * [series index][row index in series] = |Point| structure, * where series index refers to visible series only, and the * point index is for the reduced set of points for the current * zoom region (including one point just outside the window). * All points in the same row index share the same X value. * * @type {Array.>} */ this.points = []; this.setNames = []; this.annotations = []; this.yAxes_ = null; // TODO(danvk): it's odd that xTicks_ and yTicks_ are inputs, // but xticks and yticks are outputs. Clean this up. this.xTicks_ = null; this.yTicks_ = null; }; /** * Add points for a single series. * * @param {string} setname Name of the series. * @param {Array.} set_xy Points for the series. */ DygraphLayout.prototype.addDataset = function(setname, set_xy) { this.points.push(set_xy); this.setNames.push(setname); }; /** * Returns the box which the chart should be drawn in. This is the canvas's * box, less space needed for the axis and chart labels. * * @return {{x: number, y: number, w: number, h: number}} */ DygraphLayout.prototype.getPlotArea = function() { return this.area_; }; // Compute the box which the chart should be drawn in. This is the canvas's // box, less space needed for axis, chart labels, and other plug-ins. // NOTE: This should only be called by Dygraph.predraw_(). DygraphLayout.prototype.computePlotArea = function() { var area = { // TODO(danvk): per-axis setting. x: 0, y: 0 }; area.w = this.dygraph_.width_ - area.x - this.dygraph_.getOption('rightGap'); area.h = this.dygraph_.height_; // Let plugins reserve space. var e = { chart_div: this.dygraph_.graphDiv, reserveSpaceLeft: function(px) { var r = { x: area.x, y: area.y, w: px, h: area.h }; area.x += px; area.w -= px; return r; }, reserveSpaceRight: function(px) { var r = { x: area.x + area.w - px, y: area.y, w: px, h: area.h }; area.w -= px; return r; }, reserveSpaceTop: function(px) { var r = { x: area.x, y: area.y, w: area.w, h: px }; area.y += px; area.h -= px; return r; }, reserveSpaceBottom: function(px) { var r = { x: area.x, y: area.y + area.h - px, w: area.w, h: px }; area.h -= px; return r; }, chartRect: function() { return {x:area.x, y:area.y, w:area.w, h:area.h}; } }; this.dygraph_.cascadeEvents_('layout', e); this.area_ = area; }; DygraphLayout.prototype.setAnnotations = function(ann) { // The Dygraph object's annotations aren't parsed. We parse them here and // save a copy. If there is no parser, then the user must be using raw format. this.annotations = []; var parse = this.dygraph_.getOption('xValueParser') || function(x) { return x; }; for (var i = 0; i < ann.length; i++) { var a = {}; if (!ann[i].xval && ann[i].x === undefined) { console.error("Annotations must have an 'x' property"); return; } if (ann[i].icon && !(ann[i].hasOwnProperty('width') && ann[i].hasOwnProperty('height'))) { console.error("Must set width and height when setting " + "annotation.icon property"); return; } utils.update(a, ann[i]); if (!a.xval) a.xval = parse(a.x); this.annotations.push(a); } }; DygraphLayout.prototype.setXTicks = function(xTicks) { this.xTicks_ = xTicks; }; // TODO(danvk): add this to the Dygraph object's API or move it into Layout. DygraphLayout.prototype.setYAxes = function (yAxes) { this.yAxes_ = yAxes; }; DygraphLayout.prototype.evaluate = function() { this._xAxis = {}; this._evaluateLimits(); this._evaluateLineCharts(); this._evaluateLineTicks(); this._evaluateAnnotations(); }; DygraphLayout.prototype._evaluateLimits = function() { var xlimits = this.dygraph_.xAxisRange(); this._xAxis.minval = xlimits[0]; this._xAxis.maxval = xlimits[1]; var xrange = xlimits[1] - xlimits[0]; this._xAxis.scale = (xrange !== 0 ? 1 / xrange : 1.0); if (this.dygraph_.getOptionForAxis("logscale", 'x')) { this._xAxis.xlogrange = utils.log10(this._xAxis.maxval) - utils.log10(this._xAxis.minval); this._xAxis.xlogscale = (this._xAxis.xlogrange !== 0 ? 1.0 / this._xAxis.xlogrange : 1.0); } for (var i = 0; i < this.yAxes_.length; i++) { var axis = this.yAxes_[i]; axis.minyval = axis.computedValueRange[0]; axis.maxyval = axis.computedValueRange[1]; axis.yrange = axis.maxyval - axis.minyval; axis.yscale = (axis.yrange !== 0 ? 1.0 / axis.yrange : 1.0); if (this.dygraph_.getOption("logscale") || axis.logscale) { axis.ylogrange = utils.log10(axis.maxyval) - utils.log10(axis.minyval); axis.ylogscale = (axis.ylogrange !== 0 ? 1.0 / axis.ylogrange : 1.0); if (!isFinite(axis.ylogrange) || isNaN(axis.ylogrange)) { console.error('axis ' + i + ' of graph at ' + axis.g + ' can\'t be displayed in log scale for range [' + axis.minyval + ' - ' + axis.maxyval + ']'); } } } }; DygraphLayout.calcXNormal_ = function(value, xAxis, logscale) { if (logscale) { return ((utils.log10(value) - utils.log10(xAxis.minval)) * xAxis.xlogscale); } else { return (value - xAxis.minval) * xAxis.scale; } }; /** * @param {DygraphAxisType} axis * @param {number} value * @param {boolean} logscale * @return {number} */ DygraphLayout.calcYNormal_ = function(axis, value, logscale) { if (logscale) { var x = 1.0 - ((utils.log10(value) - utils.log10(axis.minyval)) * axis.ylogscale); return isFinite(x) ? x : NaN; // shim for v8 issue; see pull request 276 } else { return 1.0 - ((value - axis.minyval) * axis.yscale); } }; DygraphLayout.prototype._evaluateLineCharts = function() { var isStacked = this.dygraph_.getOption("stackedGraph"); var isLogscaleForX = this.dygraph_.getOptionForAxis("logscale", 'x'); for (var setIdx = 0; setIdx < this.points.length; setIdx++) { var points = this.points[setIdx]; var setName = this.setNames[setIdx]; var connectSeparated = this.dygraph_.getOption('connectSeparatedPoints', setName); var axis = this.dygraph_.axisPropertiesForSeries(setName); // TODO (konigsberg): use optionsForAxis instead. var logscale = this.dygraph_.attributes_.getForSeries("logscale", setName); for (var j = 0; j < points.length; j++) { var point = points[j]; // Range from 0-1 where 0 represents left and 1 represents right. point.x = DygraphLayout.calcXNormal_(point.xval, this._xAxis, isLogscaleForX); // Range from 0-1 where 0 represents top and 1 represents bottom var yval = point.yval; if (isStacked) { point.y_stacked = DygraphLayout.calcYNormal_( axis, point.yval_stacked, logscale); if (yval !== null && !isNaN(yval)) { yval = point.yval_stacked; } } if (yval === null) { yval = NaN; if (!connectSeparated) { point.yval = NaN; } } point.y = DygraphLayout.calcYNormal_(axis, yval, logscale); } this.dygraph_.dataHandler_.onLineEvaluated(points, axis, logscale); } }; DygraphLayout.prototype._evaluateLineTicks = function() { var i, tick, label, pos, v, has_tick; this.xticks = []; for (i = 0; i < this.xTicks_.length; i++) { tick = this.xTicks_[i]; label = tick.label; has_tick = !('label_v' in tick); v = has_tick ? tick.v : tick.label_v; pos = this.dygraph_.toPercentXCoord(v); if ((pos >= 0.0) && (pos < 1.0)) { this.xticks.push({pos, label, has_tick}); } } this.yticks = []; for (i = 0; i < this.yAxes_.length; i++ ) { var axis = this.yAxes_[i]; for (var j = 0; j < axis.ticks.length; j++) { tick = axis.ticks[j]; label = tick.label; has_tick = !('label_v' in tick); v = has_tick ? tick.v : tick.label_v; pos = this.dygraph_.toPercentYCoord(v, i); if ((pos > 0.0) && (pos <= 1.0)) { this.yticks.push({axis: i, pos, label, has_tick}); } } } }; DygraphLayout.prototype._evaluateAnnotations = function() { // Add the annotations to the point to which they belong. // Make a map from (setName, xval) to annotation for quick lookups. var i; var annotations = {}; for (i = 0; i < this.annotations.length; i++) { var a = this.annotations[i]; annotations[a.xval + "," + a.series] = a; } this.annotated_points = []; // Exit the function early if there are no annotations. if (!this.annotations || !this.annotations.length) { return; } // TODO(antrob): loop through annotations not points. for (var setIdx = 0; setIdx < this.points.length; setIdx++) { var points = this.points[setIdx]; for (i = 0; i < points.length; i++) { var p = points[i]; var k = p.xval + "," + p.name; if (k in annotations) { p.annotation = annotations[k]; this.annotated_points.push(p); //if there are multiple same x-valued points, the annotation would be rendered multiple times //remove already rendered annotation delete annotations[k]; } } } }; /** * Convenience function to remove all the data sets from a graph */ DygraphLayout.prototype.removeAllDatasets = function() { delete this.points; delete this.setNames; delete this.setPointsLengths; delete this.setPointsOffsets; this.points = []; this.setNames = []; this.setPointsLengths = []; this.setPointsOffsets = []; }; export default DygraphLayout; dygraphs-2.2.1/src/dygraph-options-reference.js010064400000000000000000001342741437353256000165170ustar00/** * @license * Copyright 2011 Dan Vanderkam (danvdk@gmail.com) * MIT-licenced: https://opensource.org/licenses/MIT */ "use strict"; var OPTIONS_REFERENCE = null; if (typeof process !== 'undefined' && process.env.NODE_ENV != 'production') { // For "production" code, this gets removed by uglifyjs. // NOTE: in addition to parsing as JS, this snippet is expected to be valid // JSON. This assumption cannot be checked in JS, but it will be checked when // documentation is generated by the generate-documentation.py script. For the // most part, this just means that you should always use double quotes. OPTIONS_REFERENCE = // { "animateBackgroundFade": { "default": "true", "labels": ["Overall display"], "type": "boolean", "description": "Activate an animation effect for a gradual fade." }, "xValueParser": { "default": "parseFloat() or Date.parse()*", "labels": ["CSV parsing"], "type": "function(str) -> number", "description": "A function which parses x-values (i.e. the dependent series). Must return a number, even when the values are dates. In this case, millis since epoch are used. This is used primarily for parsing CSV data. *=Dygraphs is slightly more accepting in the dates which it will parse. See code for details." }, "stackedGraph": { "default": "false", "labels": ["Data Line display"], "type": "boolean", "description": "If set, stack series on top of one another rather than drawing them independently. The first series specified in the input data will wind up on top of the chart and the last will be on bottom. NaN values are drawn as white areas without a line on top, see stackedGraphNaNFill for details." }, "stackedGraphNaNFill": { "default": "all", "labels": ["Data Line display"], "type": "string", "description": "Controls handling of NaN values inside a stacked graph. NaN values are interpolated/extended for stacking purposes, but the actual point value remains NaN in the legend display. Valid option values are \"all\" (interpolate internally, repeat leftmost and rightmost value as needed), \"inside\" (interpolate internally only, use zero outside leftmost and rightmost value), and \"none\" (treat NaN as zero everywhere)." }, "pointSize": { "default": "1", "labels": ["Data Line display"], "type": "integer", "description": "The size of the dot to draw on each point in pixels (see drawPoints). A dot is always drawn when a point is \"isolated\", i.e. there is a missing point on either side of it. This also controls the size of those dots." }, "drawPoints": { "default": "false", "labels": ["Data Line display"], "type": "boolean", "description": "Draw a small dot at each point, in addition to a line going through the point. This makes the individual data points easier to see, but can increase visual clutter in the chart. The small dot can be replaced with a custom rendering by supplying a drawPointCallback." }, "drawGapEdgePoints": { "default": "false", "labels": ["Data Line display"], "type": "boolean", "description": "Draw points at the edges of gaps in the data. This improves visibility of small data segments or other data irregularities." }, "drawPointCallback": { "default": "null", "labels": ["Data Line display"], "type": "function(g, seriesName, canvasContext, cx, cy, color, pointSize, idx)", "parameters": [ ["g", "the reference graph"], ["seriesName", "the name of the series"], ["canvasContext", "the canvas to draw on"], ["cx", "center x coordinate"], ["cy", "center y coordinate"], ["color", "series color"], ["pointSize", "the radius of the image."], ["idx", "the row-index of the point in the data."] ], "description": "Draw a custom item when drawPoints is enabled. Default is a small dot matching the series color. This method should constrain drawing to within pointSize pixels from (cx, cy). Also see drawHighlightPointCallback" }, "height": { "default": "320", "labels": ["Overall display"], "type": "integer", "description": "Height, in pixels, of the chart. If the container div has been explicitly sized, this will be ignored." }, "resizable": { "default": "no", "labels": ["Overall display"], "type": "string", "description": "Whether to add a ResizeObserver to the container div (\"passive\") and additionally make it resizable (\"horizontal\", \"vertical\", \"both\"). In any case, if the container div has CSS \"overflow:visible;\" it will be changed to \"overflow:hidden;\" to make CSS resizing possible. Note that this is distinct from resizing the graph when the window size changes, which is always active; this feature adds user-resizable “handles” to the container div." }, "zoomCallback": { "default": "null", "labels": ["Callbacks"], "type": "function(minDate, maxDate, yRanges)", "parameters": [ ["minDate", "milliseconds since epoch"], ["maxDate", "milliseconds since epoch."], ["yRanges", "is an array of [bottom, top] pairs, one for each y-axis."] ], "description": "A function to call when the zoom window is changed (either by zooming in or out). When animatedZooms is set, zoomCallback is called once at the end of the transition (it will not be called for intermediate frames)." }, "pointClickCallback": { "snippet": "function(e, point){
      alert(point);
    }", "default": "null", "labels": ["Callbacks", "Interactive Elements"], "type": "function(e, point)", "parameters": [ ["e", "the event object for the click"], ["point", "the point that was clicked See Point properties for details"] ], "description": "A function to call when a data point is clicked. and the point that was clicked." }, "color": { "default": "(see description)", "labels": ["Data Series Colors"], "type": "string", "example": "red", "description": "A per-series color definition. Used in conjunction with, and overrides, the colors option." }, "colors": { "default": "(see description)", "labels": ["Data Series Colors"], "type": "Array of strings", "example": "['red', '#00FF00']", "description": "List of colors for the data series. These can be of the form \"#AABBCC\" or \"rgb(255,100,200)\" or \"yellow\", etc. If not specified, equally-spaced points around a color wheel are used. Overridden by the “color” option." }, "connectSeparatedPoints": { "default": "false", "labels": ["Data Line display"], "type": "boolean", "description": "Usually, when Dygraphs encounters a missing value in a data series, it interprets this as a gap and draws it as such. If, instead, the missing values represents an x-value for which only a different series has data, then you’ll want to connect the dots by setting this to true. To explicitly include a gap with this option set, use a value of NaN." }, "highlightCallback": { "default": "null", "labels": ["Callbacks"], "type": "function(event, x, points, row, seriesName)", "description": "When set, this callback gets called every time a new point is highlighted.", "parameters": [ ["event", "the JavaScript mousemove event"], ["x", "the x-coordinate of the highlighted points"], ["points", "an array of highlighted points: [ {name: 'series', yval: y-value}, … ]"], ["row", "integer index of the highlighted row in the data table, starting from 0"], ["seriesName", "name of the highlighted series, only present if highlightSeriesOpts is set."] ] }, "drawHighlightPointCallback": { "default": "null", "labels": ["Data Line display"], "type": "function(g, seriesName, canvasContext, cx, cy, color, pointSize, idx)", "parameters": [ ["g", "the reference graph"], ["seriesName", "the name of the series"], ["canvasContext", "the canvas to draw on"], ["cx", "center x coordinate"], ["cy", "center y coordinate"], ["color", "series color"], ["pointSize", "the radius of the image."], ["idx", "the row-index of the point in the data."] ], "description": "Draw a custom item when a point is highlighted. Default is a small dot matching the series color. This method should constrain drawing to within pointSize pixels from (cx, cy) Also see drawPointCallback" }, "highlightSeriesOpts": { "default": "null", "labels": ["Interactive Elements"], "type": "Object", "description": "When set, the options from this object are applied to the timeseries closest to the mouse pointer for interactive highlighting. See also “highlightCallback”. Example: highlightSeriesOpts: { strokeWidth: 3 }." }, "highlightSeriesBackgroundAlpha": { "default": "0.5", "labels": ["Interactive Elements"], "type": "float", "description": "Fade the background while highlighting series. 1=fully visible background (disable fading), 0=hiddden background (show highlighted series only)." }, "highlightSeriesBackgroundColor": { "default": "rgb(255, 255, 255)", "labels": ["Interactive Elements"], "type": "string", "description": "Sets the background color used to fade out the series in conjunction with “highlightSeriesBackgroundAlpha”." }, "includeZero": { "default": "false", "labels": ["Axis display"], "type": "boolean", "description": "Usually, dygraphs will use the range of the data plus some padding to set the range of the y-axis. If this option is set, the y-axis will always include zero, typically as the lowest value. This can be used to avoid exaggerating the variance in the data" }, "rollPeriod": { "default": "1", "labels": ["Error Bars", "Rolling Averages"], "type": "integer >= 1", "description": "Number of days over which to average data. Discussed extensively above." }, "unhighlightCallback": { "default": "null", "labels": ["Callbacks"], "type": "function(event)", "parameters": [ ["event", "the mouse event"] ], "description": "When set, this callback gets called every time the user stops highlighting any point by mousing out of the graph." }, "axisTickSize": { "default": "3.0", "labels": ["Axis display"], "type": "number", "description": "The size of the line to display next to each tick mark on x- or y-axes." }, "labelsSeparateLines": { "default": "false", "labels": ["Legend"], "type": "boolean", "description": "Put <br/> between lines in the label string. Often used in conjunction with labelsDiv." }, "valueFormatter": { "default": "Depends on the type of your data.", "labels": ["Legend", "Value display/formatting"], "type": "function(num_or_millis, opts, seriesName, dygraph, row, col)", "description": "Function to provide a custom display format for the values displayed on mouseover. This does not affect the values that appear on tick marks next to the axes. To format those, see axisLabelFormatter. This is usually set on a per-axis basis. .", "parameters": [ ["num_or_millis", "The value to be formatted. This is always a number. For date axes, it’s millis since epoch. You can call new Date(millis) to get a Date object."], ["opts", "This is a function you can call to access various options (e.g. opts('labelsKMB')). It returns per-axis values for the option when available."], ["seriesName", "The name of the series from which the point came, e.g. 'X', 'Y', 'A', etc."], ["dygraph", "The dygraph object for which the formatting is being done"], ["row", "The row of the data from which this point comes. g.getValue(row, 0) will return the x-value for this point."], ["col", "The column of the data from which this point comes. g.getValue(row, col) will return the original y-value for this point. This can be used to get the full confidence interval for the point, or access un-rolled values for the point."] ] }, "annotationMouseOverHandler": { "default": "null", "labels": ["Annotations"], "type": "function(annotation, point, dygraph, event)", "description": "If provided, this function is called whenever the user mouses over an annotation." }, "annotationMouseOutHandler": { "default": "null", "labels": ["Annotations"], "type": "function(annotation, point, dygraph, event)", "parameters": [ ["annotation", "the annotation left"], ["point", "the point associated with the annotation"], ["dygraph", "the reference graph"], ["event", "the mouse event"] ], "description": "If provided, this function is called whenever the user mouses out of an annotation." }, "annotationClickHandler": { "default": "null", "labels": ["Annotations"], "type": "function(annotation, point, dygraph, event)", "parameters": [ ["annotation", "the annotation left"], ["point", "the point associated with the annotation"], ["dygraph", "the reference graph"], ["event", "the mouse event"] ], "description": "If provided, this function is called whenever the user clicks on an annotation." }, "annotationDblClickHandler": { "default": "null", "labels": ["Annotations"], "type": "function(annotation, point, dygraph, event)", "parameters": [ ["annotation", "the annotation left"], ["point", "the point associated with the annotation"], ["dygraph", "the reference graph"], ["event", "the mouse event"] ], "description": "If provided, this function is called whenever the user double-clicks on an annotation." }, "drawCallback": { "default": "null", "labels": ["Callbacks"], "type": "function(dygraph, is_initial)", "parameters": [ ["dygraph", "The graph being drawn"], ["is_initial", "True if this is the initial draw, false for subsequent draws."] ], "description": "When set, this callback gets called every time the dygraph is drawn. This includes the initial draw, after zooming and repeatedly while panning." }, "labelsKMG2": { "default": "false", "labels": ["Value display/formatting"], "type": "boolean", "description": "Show Ki/Mi/Gi for powers of 1024 on y-axis. If used together with labelsKMB (deprecated), K/M/G are used instead." }, "delimiter": { "default": ",", "labels": ["CSV parsing"], "type": "string", "description": "The delimiter to look for when separating fields of a CSV file. Setting this to a tab is not usually necessary, since tab-delimited data is auto-detected." }, "axisLabelFontSize": { "default": "14", "labels": ["Axis display"], "type": "integer", "description": "Size of the font (in pixels) to use in the axis labels, both x- and y-axis." }, "underlayCallback": { "default": "null", "labels": ["Callbacks"], "type": "function(context, area, dygraph)", "parameters": [ ["context", "the canvas drawing context on which to draw"], ["area", "An object with {x,y,w,h} properties describing the drawing area."], ["dygraph", "the reference graph"] ], "description": "When set, this callback gets called before the chart is drawn. It details on how to use this." }, "width": { "default": "480", "labels": ["Overall display"], "type": "integer", "description": "Width, in pixels, of the chart. If the container div has been explicitly sized, this will be ignored." }, "pixelRatio": { "default": "(devicePixelRatio / context.backingStoreRatio)", "labels": ["Overall display"], "type": "float", "description": "Overrides the pixel ratio scaling factor for the canvas’ 2d context. Ordinarily, this is set to the devicePixelRatio / (context.backingStoreRatio || 1), so on mobile devices, where the devicePixelRatio can be somewhere around 3, performance can be improved by overriding this value to something less precise, like 1, at the expense of resolution." }, "interactionModel": { "default": "...", "labels": ["Interactive Elements"], "type": "Object", "description": "TODO(konigsberg): document this" }, "ticker": { "default": "Dygraph.dateTicker or Dygraph.numericTicks", "labels": ["Axis display"], "type": "function(min, max, pixels, opts, dygraph, vals) → [{v: …, label: …}, …]", "parameters": [ ["min", ""], ["max", ""], ["pixels", ""], ["opts", ""], ["dygraph", "the reference graph"], ["vals", ""] ], "description": "This lets you specify an arbitrary function to generate tick marks on an axis. The tick marks are an array of (value, label) pairs. The built-in functions go to great lengths to choose good tick marks so, if you set this option, you’ll most likely want to call one of them and modify the result. See dygraph-tickers.js for an extensive discussion. This is set on a per-axis basis." }, "xAxisHeight": { "default": "(null)", "labels": ["Axis display"], "type": "integer", "description": "Height, in pixels, of the x-axis. If not set explicitly, this is computed based on axisLabelFontSize and axisTickSize." }, "showLabelsOnHighlight": { "default": "true", "labels": ["Interactive Elements", "Legend"], "type": "boolean", "description": "Whether to show the legend upon mouseover." }, "axis": { "default": "(none)", "labels": ["Axis display"], "type": "string", "description": "Set to either 'y1' or 'y2' to assign a series to a y-axis (primary or secondary). Must be set per-series." }, "pixelsPerLabel": { "default": "70 (x-axis) or 30 (y-axes)", "labels": ["Axis display", "Grid"], "type": "integer", "description": "Number of pixels to require between each x- and y-label. Larger values will yield a sparser axis with fewer ticks. This is set on a per-axis basis." }, "labelsDiv": { "default": "null", "labels": ["Legend"], "type": "DOM element or string", "example": "document.getElementById('foo') or 'foo'", "description": "Show data labels in an external div, rather than on the graph. This value can either be a div element or a div id." }, "fractions": { "default": "false", "labels": ["CSV parsing", "Error Bars"], "type": "boolean", "description": "When set, attempt to parse each cell in the CSV file as \"a/b\", where a and b are integers. The ratio will be plotted. This allows computation of Wilson confidence intervals (see below)." }, "logscale": { "default": "false", "labels": ["Axis display"], "type": "boolean", "description": "When set for the y-axis or x-axis, the graph shows that axis in log scale. Any values less than or equal to zero are not displayed. Showing log scale with ranges that go below zero will result in an unviewable graph.\n\n Not compatible with showZero. connectSeparatedPoints is ignored. This is ignored for date-based x-axes." }, "strokeWidth": { "default": "1.0", "labels": ["Data Line display"], "type": "float", "example": "0.5, 2.0", "description": "The width of the lines connecting data points. This can be used to increase the contrast or some graphs." }, "strokePattern": { "default": "null", "labels": ["Data Line display"], "type": "Array of integers", "example": "[10, 2, 5, 2]", "description": "A custom pattern array where the even index is a draw and odd is a space in pixels. If null then it draws a solid line. The array should have a even length as any odd lengthed array could be expressed as a smaller even length array. This is used to create dashed lines." }, "strokeBorderWidth": { "default": "null", "labels": ["Data Line display"], "type": "float", "example": "1.0", "description": "Draw a border around graph lines to make crossing lines more easily distinguishable. Useful for graphs with many lines." }, "strokeBorderColor": { "default": "white", "labels": ["Data Line display"], "type": "string", "example": "red, #ccffdd", "description": "Color for the line border used if strokeBorderWidth is set." }, "wilsonInterval": { "default": "true", "labels": ["Error Bars"], "type": "boolean", "description": "Use in conjunction with the \"fractions\" option. Instead of plotting +/- N standard deviations, dygraphs will compute a Wilson confidence interval and plot that. This has more reasonable behavior for ratios close to 0 or 1." }, "fillGraph": { "default": "false", "labels": ["Data Line display"], "type": "boolean", "description": "Should the area underneath the graph be filled? This option is not compatible with customBars nor errorBars. This may be set on a per-series basis." }, "highlightCircleSize": { "default": "3", "labels": ["Interactive Elements"], "type": "integer", "description": "The size in pixels of the dot drawn over highlighted points." }, "gridLineColor": { "default": "rgb(128,128,128)", "labels": ["Grid"], "type": "red, blue", "description": "The color of the gridlines. This may be set on a per-axis basis to define each axis’ grid separately." }, "gridLinePattern": { "default": "null", "labels": ["Grid"], "type": "Array of integers", "example": "[10, 2, 5, 2]", "description": "A custom pattern array where the even index is a draw and odd is a space in pixels. If null then it draws a solid line. The array should have a even length as any odd lengthed array could be expressed as a smaller even length array. This is used to create dashed gridlines." }, "visibility": { "default": "[true, true, ...]", "labels": ["Data Line display"], "type": "Array of booleans", "description": "Which series should initially be visible? Once the Dygraph has been constructed, you can access and modify the visibility of each series using the visibility and setVisibility methods." }, "valueRange": { "default": "Full range of the input is shown", "labels": ["Axis display"], "type": "Array of two numbers", "example": "[10, 110]", "description": "Explicitly set the vertical range of the graph to [low, high]. This may be set on a per-axis basis to define each y-axis separately. If either limit is unspecified, it will be calculated automatically (e.g. [null, 30] to automatically calculate just the lower bound)" }, "colorSaturation": { "default": "1.0", "labels": ["Data Series Colors"], "type": "float (0.0 - 1.0)", "description": "If colors is not specified, saturation of the automatically-generated data series colors." }, "hideOverlayOnMouseOut": { "default": "true", "labels": ["Interactive Elements", "Legend"], "type": "boolean", "description": "Whether to hide the legend when the mouse leaves the chart area." }, "legend": { "default": "onmouseover", "labels": ["Legend"], "type": "string", "description": "When to display the legend. By default, it only appears when a user mouses over the chart. Set it to \"always\" to always display a legend of some sort, \"never\" to hide it. When set to \"follow\", legend follows highlighted points." }, "legendFollowOffsetX": { "default": "50", "labels": ["Legend"], "type": "integer", "description": "Number of pixels to use as horizontal offset from the point for a “floating” legend (\"follow\" mode). This should be positive (to the right) because the legend flips over to the left side if it’s too wide." }, "legendFollowOffsetY": { "default": "-50", "labels": ["Legend"], "type": "integer", "description": "Number of pixels to use as vertical offset from the point for a “floating” legend (\"follow\" mode)." }, "legendFormatter": { "default": "null", "labels": ["Legend"], "type": "function(data): string or DocumentFragment node", "params": [ ["data", "An object containing information about the selection (or lack of a selection). This includes formatted values and series information. See docs/legend-formatter.md (online) for sample values."] ], "description": "Set this to supply a custom formatter for the legend. See docs/legend-formatter.md (online) and the legendFormatter demo for usage." }, "labelsShowZeroValues": { "default": "true", "labels": ["Legend"], "type": "boolean", "description": "Show zero value labels in the labelsDiv." }, "stepPlot": { "default": "false", "labels": ["Data Line display"], "type": "boolean", "description": "When set, display the graph as a step plot instead of a line plot. This option may either be set for the whole graph or for single series." }, "labelsUTC": { "default": "false", "labels": ["Value display/formatting", "Axis display"], "type": "boolean", "description": "Show date/time labels according to UTC (instead of local time)." }, "labelsKMB": { "default": "false", "labels": ["Value display/formatting"], "type": "boolean", "description": "Show k/M/B for thousands/millions/billions on y-axis." }, "rightGap": { "default": "5", "labels": ["Overall display"], "type": "integer", "description": "Number of pixels to leave blank at the right edge of the Dygraph. This makes it easier to highlight the right-most data point." }, "drawAxesAtZero": { "default": "false", "labels": ["Axis display"], "type": "boolean", "description": "When set, draw the X axis at the Y=0 position and the Y axis at the X=0 position if those positions are inside the graph’s visible area. Otherwise, draw the axes at the bottom or left graph edge as usual." }, "xRangePad": { "default": "0", "labels": ["Axis display"], "type": "float", "description": "Add the specified amount of extra space (in pixels) around the X-axis value range to ensure points at the edges remain visible." }, "yRangePad": { "default": "null", "labels": ["Axis display"], "type": "float", "description": "If set, add the specified amount of extra space (in pixels) around the Y-axis value range to ensure points at the edges remain visible. If unset, use the traditional Y padding algorithm." }, "axisLabelFormatter": { "default": "Depends on the data type", "labels": ["Axis display"], "type": "function(number_or_Date, granularity, opts, dygraph)", "parameters": [ ["number_or_Date", "Either a number (for a numeric axis) or a Date object (for a date axis)"], ["granularity", "specifies how fine-grained the axis is. For date axes, this is a reference to the time granularity enumeration, defined in dygraph-tickers.js, e.g. Dygraph.WEEKLY."], ["opts", "a function which provides access to various options on the dygraph, e.g. opts('labelsKMB')."], ["dygraph", "the referenced graph"] ], "description": "Function to call to format the tick values that appear along an axis. This is usually set on a per-axis basis." }, "clickCallback": { "snippet": "function(e, date_millis){
      alert(new Date(date_millis));
    }", "default": "null", "labels": ["Callbacks"], "type": "function(e, x, points)", "parameters": [ ["e", "The event object for the click"], ["x", "The x value that was clicked (for dates, this is milliseconds since epoch)"], ["points", "The closest points along that date. See Point properties for details."] ], "description": "A function to call when the canvas is clicked." }, "labels": { "default": "[\"X\", \"Y1\", \"Y2\", ...]*", "labels": ["Legend"], "type": "Array of strings", "description": "A distinct name for each data series, including the independent (X) series. For CSV files and DataTable objects, this is determined by context. For raw data, this must be specified. If it is not, default values are supplied and a warning is logged. Make sure no two names are the same!" }, "dateWindow": { "default": "Full range of the input is shown", "labels": ["Axis display"], "type": "Array of two numbers", "example": "[
      Date.parse('2006-01-01'),
      (new Date()).valueOf()
    ]", "description": "Initially zoom in on a section of the graph. Is of the form [earliest, latest], where earliest/latest are milliseconds since epoch. If the data for the x-axis is numeric, the values in dateWindow must also be numbers." }, "showRoller": { "default": "false", "labels": ["Interactive Elements", "Rolling Averages"], "type": "boolean", "description": "If the rolling average period text box should be shown." }, "sigma": { "default": "2.0", "labels": ["Error Bars"], "type": "float", "description": "When errorBars is set, shade this many standard deviations above/below each point." }, "customBars": { "default": "false", "labels": ["CSV parsing", "Error Bars"], "type": "boolean", "description": "When set, parse each CSV cell as \"low;middle;high\". Custom high/low bands will be drawn for each point between low and high, with the series itself going through middle." }, "colorValue": { "default": "1.0", "labels": ["Data Series Colors"], "type": "float (0.0 - 1.0)", "description": "If colors is not specified, value of the data series colors, as in hue/saturation/value. (0.0-1.0, default 0.5)" }, "errorBars": { "default": "false", "labels": ["CSV parsing", "Error Bars"], "type": "boolean", "description": "Does the data contain standard deviations? Setting this to true alters the input format (see above)." }, "displayAnnotations": { "default": "false", "labels": ["Annotations"], "type": "boolean", "description": "Only applies when Dygraphs is used as a GViz chart. Causes string columns following a data series to be interpreted as annotations on points in that series. This is the same format used by Google’s AnnotatedTimeLine chart." }, "panEdgeFraction": { "default": "null", "labels": ["Axis display", "Interactive Elements"], "type": "float", "description": "A value representing the farthest a graph may be panned, in percent of the display. For example, a value of 0.1 means that the graph can only be panned 10% passed the edges of the displayed values. null means no bounds." }, "title": { "labels": ["Chart labels"], "type": "string", "default": "null", "description": "Text to display above the chart. You can supply any HTML for this value, not just text. If you wish to style it using CSS, use the “dygraph-label” or “dygraph-title” classes." }, "titleHeight": { "default": "18", "labels": ["Chart labels"], "type": "integer", "description": "Height of the chart title, in pixels. This also controls the default font size of the title. If you style the title on your own, this controls how much space is set aside above the chart for the title’s div." }, "xlabel": { "labels": ["Chart labels"], "type": "string", "default": "null", "description": "Text to display below the chart’s x-axis. You can supply any HTML for this value, not just text. If you wish to style it using CSS, use the “dygraph-label” or “dygraph-xlabel” classes." }, "xLabelHeight": { "labels": ["Chart labels"], "type": "integer", "default": "18", "description": "Height of the x-axis label, in pixels. This also controls the default font size of the x-axis label. If you style the label on your own, this controls how much space is set aside below the chart for the x-axis label’s div." }, "ylabel": { "labels": ["Chart labels"], "type": "string", "default": "null", "description": "Text to display to the left of the chart’s y-axis. You can supply any HTML for this value, not just text. If you wish to style it using CSS, use the “dygraph-label” or “dygraph-ylabel” classes. The text will be rotated 90 degrees by default, so CSS rules may behave in unintuitive ways. No additional space is set aside for a y-axis label. If you need more space, increase the width of the y-axis tick labels using the per-axis axisLabelWidth option on the y axis. If you need a wider div for the y-axis label, either style it that way with CSS (but remember that it’s rotated, so width is controlled by the “height” property) or set the yLabelWidth option." }, "y2label": { "labels": ["Chart labels"], "type": "string", "default": "null", "description": "Text to display to the right of the chart’s secondary y-axis. This label is only displayed if a secondary y-axis is present. See this test for an example of how to do this. The comments for the “ylabel” option generally apply here as well. This label gets a “dygraph-y2label” instead of a “dygraph-ylabel” class." }, "yLabelWidth": { "labels": ["Chart labels"], "type": "integer", "default": "18", "description": "Width of the div which contains the y-axis label. Since the y-axis label appears rotated 90 degrees, this actually affects the height of its div." }, "drawGrid": { "default": "true for x and y, false for y2", "labels": ["Grid"], "type": "boolean", "description": "Whether to display gridlines in the chart. This may be set on a per-axis basis to define the visibility of each axis’ grid separately." }, "independentTicks": { "default": "true for y, false for y2", "labels": ["Axis display", "Grid"], "type": "boolean", "description": "Only valid for y and y2, has no effect on x: This option defines whether the y axes should align their ticks or if they should be independent. Possible combinations: [1.] y=true, y2=false (default): y is the primary axis and the y2 ticks are aligned to the the ones of y. (only 1 grid) [2.] y=false, y2=true: y2 is the primary axis and the y ticks are aligned to the the ones of y2. (only 1 grid) [3.] y=true, y2=true: Both axis are independent and have their own ticks. (2 grids) [4.] y=false, y2=false: Invalid configuration causes an error." }, "drawAxis": { "default": "true for x and y, false for y2", "labels": ["Axis display"], "type": "boolean", "description": "Whether to draw the specified axis. This may be set on a per-axis basis to define the visibility of each axis separately. Setting this to false also prevents axis ticks from being drawn and reclaims the space for the chart grid/lines." }, "gridLineWidth": { "default": "0.3", "labels": ["Grid"], "type": "float", "description": "Thickness (in pixels) of the gridlines drawn under the chart. The vertical/horizontal gridlines can be turned off entirely by using the drawGrid option. This may be set on a per-axis basis to define each axis’ grid separately." }, "axisLineWidth": { "default": "0.3", "labels": ["Axis display"], "type": "float", "description": "Thickness (in pixels) of the x- and y-axis lines." }, "axisLineColor": { "default": "black", "labels": ["Axis display"], "type": "string", "description": "Color of the x- and y-axis lines. Accepts any value which the HTML canvas strokeStyle attribute understands, e.g. 'black' or 'rgb(0, 100, 255)'." }, "fillAlpha": { "default": "0.15", "labels": ["Error Bars", "Data Series Colors"], "type": "float (0.0 - 1.0)", "description": "Custom or sigma-based high/low bands for each series are drawn in the same colour as the series, but with partial transparency. This sets the transparency. A value of 0.0 means that the bands will not be drawn, whereas a value of 1.0 means that the bands will be as dark as the line for the series itself. This can be used to produce chart lines whose thickness varies at each point." }, "axisLabelWidth": { "default": "50 (y-axis), 60 (x-axis)", "labels": ["Axis display", "Chart labels"], "type": "integer", "description": "Width (in pixels) of the containing divs for x- and y-axis labels. For the y-axis, this also controls the width of the y-axis. Note that for the x-axis, this is independent from pixelsPerLabel, which controls the spacing between labels." }, "sigFigs": { "default": "null", "labels": ["Value display/formatting"], "type": "integer", "description": "By default, dygraphs displays numbers with a fixed number of digits after the decimal point. If you’d prefer to have a fixed number of significant figures, set this option to that number of sig figs. A value of 2, for instance, would cause 1 to be display as 1.0 and 1234 to be displayed as 1.23e+3." }, "digitsAfterDecimal": { "default": "2", "labels": ["Value display/formatting"], "type": "integer", "description": "Unless it’s run in scientific mode (see the sigFigs option), dygraphs displays numbers with digitsAfterDecimal digits after the decimal point. Trailing zeros are not displayed, so with a value of 2 you’ll get '0', '0.1', '0.12', '123.45' but not '123.456' (it will be rounded to '123.46'). Numbers with absolute value less than 0.1^digitsAfterDecimal (i.e. those which would show up as '0.00') will be displayed in scientific notation." }, "maxNumberWidth": { "default": "6", "labels": ["Value display/formatting"], "type": "integer", "description": "When displaying numbers in normal (not scientific) mode, large numbers will be displayed with many trailing zeros (e.g. 100000000 instead of 1e9). This can lead to unwieldy y-axis labels. If there are more than maxNumberWidth digits to the left of the decimal in a number, dygraphs will switch to scientific notation, even when not operating in scientific mode. If you’d like to see all those digits, set this to something large, like 20 or 30." }, "file": { "default": "(set when constructed)", "labels": ["Data"], "type": "string (URL of CSV or CSV), GViz DataTable or 2D Array", "description": "Sets the data being displayed in the chart. This can only be set when calling updateOptions; it cannot be set from the constructor. For a full description of valid data formats, see the Data Formats page." }, "timingName": { "default": "null", "labels": ["Debugging", "Deprecated"], "type": "string", "description": "Set this option to log timing information. The value of the option will be logged along with the timimg, so that you can distinguish multiple dygraphs on the same page." }, "showRangeSelector": { "default": "false", "labels": ["Range Selector"], "type": "boolean", "description": "Show or hide the range selector widget." }, "rangeSelectorHeight": { "default": "40", "labels": ["Range Selector"], "type": "integer", "description": "Height, in pixels, of the range selector widget. This option can only be specified at Dygraph creation time." }, "rangeSelectorPlotStrokeColor": { "default": "#808FAB", "labels": ["Range Selector"], "type": "string", "description": "The range selector mini plot stroke color. This can be of the form \"#AABBCC\" or \"rgb(255,100,200)\" or \"yellow\". You can also specify null or \"\" to turn off stroke." }, "rangeSelectorPlotFillColor": { "default": "#A7B1C4", "labels": ["Range Selector"], "type": "string", "description": "The range selector mini plot fill color. This can be of the form \"#AABBCC\" or \"rgb(255,100,200)\" or \"yellow\". You can also specify null or \"\" to turn off fill." }, "rangeSelectorPlotFillGradientColor": { "default": "white", "labels": ["Range Selector"], "type": "string", "description": "The top color for the range selector mini plot fill color gradient. This can be of the form \"#AABBCC\" or \"rgb(255,100,200)\" or \"rgba(255,100,200,42)\" or \"yellow\". You can also specify null or \"\" to disable the gradient and fill with one single color." }, "rangeSelectorBackgroundStrokeColor": { "default": "gray", "labels": ["Range Selector"], "type": "string", "description": "The color of the lines below and on both sides of the range selector mini plot. This can be of the form \"#AABBCC\" or \"rgb(255,100,200)\" or \"yellow\"." }, "rangeSelectorBackgroundLineWidth": { "default": "1", "labels": ["Range Selector"], "type": "float", "description": "The width of the lines below and on both sides of the range selector mini plot." }, "rangeSelectorPlotLineWidth": { "default": "1.5", "labels": ["Range Selector"], "type": "float", "description": "The width of the range selector mini plot line." }, "rangeSelectorForegroundStrokeColor": { "default": "black", "labels": ["Range Selector"], "type": "string", "description": "The color of the lines in the interactive layer of the range selector. This can be of the form \"#AABBCC\" or \"rgb(255,100,200)\" or \"yellow\"." }, "rangeSelectorForegroundLineWidth": { "default": "1", "labels": ["Range Selector"], "type": "float", "description": "The width the lines in the interactive layer of the range selector." }, "rangeSelectorAlpha": { "default": "0.6", "labels": ["Range Selector"], "type": "float (0.0 - 1.0)", "description": "The transparency of the veil that is drawn over the unselected portions of the range selector mini plot. A value of 0 represents full transparency and the unselected portions of the mini plot will appear as normal. A value of 1 represents full opacity and the unselected portions of the mini plot will be hidden." }, "rangeSelectorVeilColour": { "default": "null", "labels": ["Range Selector"], "type": "string", "description": "The fillStyle for the veil of the range selector (e.g. \"rgba(240, 240, 240, 0.6)\"); if set, the rangeSelectorAlpha option is ignored." }, "showInRangeSelector": { "default": "null", "labels": ["Range Selector"], "type": "boolean", "description": "Mark this series for inclusion in the range selector. The mini plot curve will be an average of all such series. If this is not specified for any series, the default behavior is to average all the visible series. Setting it for one series will result in that series being charted alone in the range selector. Once it’s set for a single series, it needs to be set for all series which should be included (regardless of visibility)." }, "animatedZooms": { "default": "false", "labels": ["Interactive Elements"], "type": "boolean", "description": "Set this option to animate the transition between zoom windows. Applies to programmatic and interactive zooms. Note that if you also set a drawCallback, it will be called several times on each zoom. If you set a zoomCallback, it will only be called after the animation is complete." }, "plotter": { "default": "[DygraphCanvasRenderer.Plotters.fillPlotter, DygraphCanvasRenderer.Plotters.errorPlotter, DygraphCanvasRenderer.Plotters.linePlotter]", "labels": ["Data Line display"], "type": "array or function", "description": "A function (or array of functions) which plot each data series on the chart. TODO(danvk): more details! May be set per-series." }, "axes": { "default": "null", "labels": ["Configuration"], "type": "Object", "description": "Defines per-axis options. Valid keys are 'x', 'y' and 'y2'. Only some options may be set on a per-axis basis. If an option may be set in this way, it will be noted on this page. See also documentation on per-series and per-axis options." }, "series": { "default": "null", "labels": ["Series"], "type": "Object", "description": "Defines per-series options. Its keys match the y-axis label names, and the values are dictionaries themselves that contain options specific to that series." }, "plugins": { "default": "[]", "labels": ["Configuration"], "type": "Array of plugins", "description": "Defines per-graph plugins. Useful for per-graph customization" }, "dataHandler": { "default": "(depends on data)", "labels": ["Data"], "type": "Dygraph.DataHandler", "description": "Custom DataHandler. This is an advanced customisation. See docs/datahandler-proposal.pdf." } } ; //
    // NOTE: in addition to parsing as JS, this snippet is expected to be valid // JSON. This assumption cannot be checked in JS, but it will be checked when // documentation is generated by the generate-documentation.py script. For the // most part, this just means that you should always use double quotes. // Do a quick sanity check on the options reference. const warn = function warn(msg) { if (window.console) window.console.warn(msg); }; const flds = ['type', 'default', 'description']; const valid_cats = // { "Annotations": "", "Axis display": "", "CSV parsing": "", "Callbacks": "", "Chart labels": "", "Configuration": "", "Data Line display": "", "Data Series Colors": "", "Data": "", "Debugging": "", "Deprecated": "", "Error Bars": "These are actually high/low bands, not error bars; the misnomer is historic.", "Grid": "", "Interactive Elements": "", "Legend": "", "Overall display": "", "Range Selector": "", "Rolling Averages": "", "Series": "", "Value display/formatting": "" } ; // for (let k in OPTIONS_REFERENCE) { if (!OPTIONS_REFERENCE.hasOwnProperty(k)) continue; var op = OPTIONS_REFERENCE[k]; for (let i = 0; i < flds.length; i++) { if (!op.hasOwnProperty(flds[i])) { warn('Option ' + k + ' missing "' + flds[i] + '" property'); } else if (typeof(op[flds[i]]) != 'string') { warn(k + '.' + flds[i] + ' must be of type string'); } } let labels = op.labels; if (!Array.isArray(labels)) { warn('Option "' + k + '" is missing a "labels": [...] option'); } else { for (let i = 0; i < labels.length; i++) { if (!valid_cats.hasOwnProperty(labels[i])) { warn('Option "' + k + '" has label "' + labels[i] + '", which is invalid.'); } } } } } export default OPTIONS_REFERENCE; dygraphs-2.2.1/src/dygraph-options.js010064400000000000000000000257611437353256000145630ustar00/** * @license * Copyright 2011 Dan Vanderkam (danvdk@gmail.com) * MIT-licenced: https://opensource.org/licenses/MIT */ /** * @fileoverview DygraphOptions is responsible for parsing and returning * information about options. */ // TODO: remove this jshint directive & fix the warnings. /*jshint sub:true */ "use strict"; import * as utils from './dygraph-utils'; import DEFAULT_ATTRS from './dygraph-default-attrs'; import OPTIONS_REFERENCE from './dygraph-options-reference'; /* * Interesting member variables: (REMOVING THIS LIST AS I CLOSURIZE) * global_ - global attributes (common among all graphs, AIUI) * user - attributes set by the user * series_ - { seriesName -> { idx, yAxis, options }} */ /** * This parses attributes into an object that can be easily queried. * * It doesn't necessarily mean that all options are available, specifically * if labels are not yet available, since those drive details of the per-series * and per-axis options. * * @param {Dygraph} dygraph The chart to which these options belong. * @constructor */ var DygraphOptions = function(dygraph) { /** * The dygraph. * @type {!Dygraph} */ this.dygraph_ = dygraph; /** * Array of axis index to { series : [ series names ] , options : { axis-specific options. } } * @type {Array.<{series : Array., options : Object}>} @private */ this.yAxes_ = []; /** * Contains x-axis specific options, which are stored in the options key. * This matches the yAxes_ object structure (by being a dictionary with an * options element) allowing for shared code. * @type {options: Object} @private */ this.xAxis_ = {}; this.series_ = {}; // Once these two objects are initialized, you can call get(); this.global_ = this.dygraph_.attrs_; this.user_ = this.dygraph_.user_attrs_ || {}; /** * A list of series in columnar order. * @type {Array.} */ this.labels_ = []; this.highlightSeries_ = this.get("highlightSeriesOpts") || {}; this.reparseSeries(); }; /** * Not optimal, but does the trick when you're only using two axes. * If we move to more axes, this can just become a function. * * @type {Object.} * @private */ DygraphOptions.AXIS_STRING_MAPPINGS_ = { 'y' : 0, 'Y' : 0, 'y1' : 0, 'Y1' : 0, 'y2' : 1, 'Y2' : 1 }; /** * @param {string|number} axis * @private */ DygraphOptions.axisToIndex_ = function(axis) { if (typeof(axis) == "string") { if (DygraphOptions.AXIS_STRING_MAPPINGS_.hasOwnProperty(axis)) { return DygraphOptions.AXIS_STRING_MAPPINGS_[axis]; } throw "Unknown axis : " + axis; } if (typeof(axis) == "number") { if (axis === 0 || axis === 1) { return axis; } throw "Dygraphs only supports two y-axes, indexed from 0-1."; } if (axis) { throw "Unknown axis : " + axis; } // No axis specification means axis 0. return 0; }; /** * Reparses options that are all related to series. This typically occurs when * options are either updated, or source data has been made available. * * TODO(konigsberg): The method name is kind of weak; fix. */ DygraphOptions.prototype.reparseSeries = function() { var labels = this.get("labels"); if (!labels) { return; // -- can't do more for now, will parse after getting the labels. } this.labels_ = labels.slice(1); this.yAxes_ = [ { series : [], options : {}} ]; // Always one axis at least. this.xAxis_ = { options : {} }; this.series_ = {}; // Series are specified in the series element: // // { // labels: [ "X", "foo", "bar" ], // pointSize: 3, // series : { // foo : {}, // options for foo // bar : {} // options for bar // } // } // // So, if series is found, it's expected to contain per-series data, // otherwise set a default. var seriesDict = this.user_.series || {}; for (var idx = 0; idx < this.labels_.length; idx++) { var seriesName = this.labels_[idx]; var optionsForSeries = seriesDict[seriesName] || {}; var yAxis = DygraphOptions.axisToIndex_(optionsForSeries["axis"]); this.series_[seriesName] = { idx: idx, yAxis: yAxis, options : optionsForSeries }; if (!this.yAxes_[yAxis]) { this.yAxes_[yAxis] = { series : [ seriesName ], options : {} }; } else { this.yAxes_[yAxis].series.push(seriesName); } } var axis_opts = this.user_["axes"] || {}; utils.update(this.yAxes_[0].options, axis_opts["y"] || {}); if (this.yAxes_.length > 1) { utils.update(this.yAxes_[1].options, axis_opts["y2"] || {}); } utils.update(this.xAxis_.options, axis_opts["x"] || {}); if (typeof process !== 'undefined' && process.env.NODE_ENV != 'production') { // For "production" code, this gets removed by uglifyjs. this.validateOptions_(); } }; /** * Get a global value. * * @param {string} name the name of the option. */ DygraphOptions.prototype.get = function(name) { var result = this.getGlobalUser_(name); if (result !== null) { return result; } return this.getGlobalDefault_(name); }; DygraphOptions.prototype.getGlobalUser_ = function(name) { if (this.user_.hasOwnProperty(name)) { return this.user_[name]; } return null; }; DygraphOptions.prototype.getGlobalDefault_ = function(name) { if (this.global_.hasOwnProperty(name)) { return this.global_[name]; } if (DEFAULT_ATTRS.hasOwnProperty(name)) { return DEFAULT_ATTRS[name]; } return null; }; /** * Get a value for a specific axis. If there is no specific value for the axis, * the global value is returned. * * @param {string} name the name of the option. * @param {string|number} axis the axis to search. Can be the string representation * ("y", "y2") or the axis number (0, 1). */ DygraphOptions.prototype.getForAxis = function(name, axis) { var axisIdx; var axisString; // Since axis can be a number or a string, straighten everything out here. if (typeof(axis) == 'number') { axisIdx = axis; axisString = axisIdx === 0 ? "y" : "y2"; } else { if (axis == "y1") { axis = "y"; } // Standardize on 'y'. Is this bad? I think so. if (axis == "y") { axisIdx = 0; } else if (axis == "y2") { axisIdx = 1; } else if (axis == "x") { axisIdx = -1; // simply a placeholder for below. } else { throw "Unknown axis " + axis; } axisString = axis; } var userAxis = (axisIdx == -1) ? this.xAxis_ : this.yAxes_[axisIdx]; // Search the user-specified axis option first. if (userAxis) { // This condition could be removed if we always set up this.yAxes_ for y2. var axisOptions = userAxis.options; if (axisOptions.hasOwnProperty(name)) { return axisOptions[name]; } } // User-specified global options second. // But, hack, ignore globally-specified 'logscale' for 'x' axis declaration. if (!(axis === 'x' && name === 'logscale')) { var result = this.getGlobalUser_(name); if (result !== null) { return result; } } // Default axis options third. var defaultAxisOptions = DEFAULT_ATTRS.axes[axisString]; if (defaultAxisOptions.hasOwnProperty(name)) { return defaultAxisOptions[name]; } // Default global options last. return this.getGlobalDefault_(name); }; /** * Get a value for a specific series. If there is no specific value for the series, * the value for the axis is returned (and afterwards, the global value.) * * @param {string} name the name of the option. * @param {string} series the series to search. */ DygraphOptions.prototype.getForSeries = function(name, series) { // Honors indexes as series. if (series === this.dygraph_.getHighlightSeries()) { if (this.highlightSeries_.hasOwnProperty(name)) { return this.highlightSeries_[name]; } } if (!this.series_.hasOwnProperty(series)) { throw "Unknown series: " + series; } var seriesObj = this.series_[series]; var seriesOptions = seriesObj["options"]; if (seriesOptions.hasOwnProperty(name)) { return seriesOptions[name]; } return this.getForAxis(name, seriesObj["yAxis"]); }; /** * Returns the number of y-axes on the chart. * @return {number} the number of axes. */ DygraphOptions.prototype.numAxes = function() { return this.yAxes_.length; }; /** * Return the y-axis for a given series, specified by name. */ DygraphOptions.prototype.axisForSeries = function(series) { return this.series_[series].yAxis; }; /** * Returns the options for the specified axis. */ // TODO(konigsberg): this is y-axis specific. Support the x axis. DygraphOptions.prototype.axisOptions = function(yAxis) { return this.yAxes_[yAxis].options; }; /** * Return the series associated with an axis. */ DygraphOptions.prototype.seriesForAxis = function(yAxis) { return this.yAxes_[yAxis].series; }; /** * Return the list of all series, in their columnar order. */ DygraphOptions.prototype.seriesNames = function() { return this.labels_; }; if (typeof process !== 'undefined' && process.env.NODE_ENV != 'production') { // For "production" code, this gets removed by uglifyjs. /** * Validate all options. * This requires OPTIONS_REFERENCE, which is only available in debug builds. * @private */ DygraphOptions.prototype.validateOptions_ = function() { if (typeof OPTIONS_REFERENCE === 'undefined') { throw 'Called validateOptions_ in prod build.'; } var that = this; var validateOption = function(optionName) { if (!OPTIONS_REFERENCE[optionName]) { that.warnInvalidOption_(optionName); } }; var optionsDicts = [this.xAxis_.options, this.yAxes_[0].options, this.yAxes_[1] && this.yAxes_[1].options, this.global_, this.user_, this.highlightSeries_]; var names = this.seriesNames(); for (var i = 0; i < names.length; i++) { var name = names[i]; if (this.series_.hasOwnProperty(name)) { optionsDicts.push(this.series_[name].options); } } for (var i = 0; i < optionsDicts.length; i++) { var dict = optionsDicts[i]; if (!dict) continue; for (var optionName in dict) { if (dict.hasOwnProperty(optionName)) { validateOption(optionName); } } } }; var WARNINGS = {}; // Only show any particular warning once. /** * Logs a warning about invalid options. * TODO: make this throw for testing * @private */ DygraphOptions.prototype.warnInvalidOption_ = function(optionName) { if (!WARNINGS[optionName]) { WARNINGS[optionName] = true; var isSeries = (this.labels_.indexOf(optionName) >= 0); if (isSeries) { console.warn('Use new-style per-series options (saw ' + optionName + ' as top-level options key). See http://blog.dygraphs.com/2012/12/the-new-and-better-way-to-specify.html (The New and Better Way to Specify Series and Axis Options).'); } else { console.warn('Unknown option ' + optionName + ' (see https://dygraphs.com/options.html for the full list of options)'); } throw "invalid option " + optionName; } }; // Reset list of previously-shown warnings. Used for testing. DygraphOptions.resetWarnings_ = function() { WARNINGS = {}; }; } export default DygraphOptions; dygraphs-2.2.1/src/dygraph-plugin-install.js010064400000000000000000000012171437353256000160200ustar00/*global Dygraph:false */ // This file defines the ordering of the plugins. // // The ordering is from most-general to most-specific. // This means that, in an event cascade, plugins which have registered for that // event will be called in reverse order. // // This is most relevant for plugins which register a layout event, e.g. // Axes, Legend and ChartLabels. Dygraph.PLUGINS.push( Dygraph.Plugins.Legend, Dygraph.Plugins.Axes, Dygraph.Plugins.RangeSelector, // Has to be before ChartLabels so that its callbacks are called after ChartLabels' callbacks. Dygraph.Plugins.ChartLabels, Dygraph.Plugins.Annotations, Dygraph.Plugins.Grid ); dygraphs-2.2.1/src/dygraph-tickers.js010064400000000000000000000447501437353256000145330ustar00/** * @license * Copyright 2011 Dan Vanderkam (danvdk@gmail.com) * MIT-licenced: https://opensource.org/licenses/MIT */ /** * @fileoverview Description of this file. * @author danvk@google.com (Dan Vanderkam) */ /* * A ticker is a function with the following interface: * * function(a, b, pixels, options_view, dygraph, forced_values); * -> [ { v: tick1_v, label: tick1_label[, label_v: label_v1] }, * { v: tick2_v, label: tick2_label[, label_v: label_v2] }, * ... * ] * * The returned value is called a "tick list". * * Arguments * --------- * * [a, b] is the range of the axis for which ticks are being generated. For a * numeric axis, these will simply be numbers. For a date axis, these will be * millis since epoch (convertable to Date objects using "new Date(a)" and "new * Date(b)"). * * opts provides access to chart- and axis-specific options. It can be used to * access number/date formatting code/options, check for a log scale, etc. * * pixels is the length of the axis in pixels. opts('pixelsPerLabel') is the * minimum amount of space to be allotted to each label. For instance, if * pixels=400 and opts('pixelsPerLabel')=40 then the ticker should return * between zero and ten (400/40) ticks. * * dygraph is the Dygraph object for which an axis is being constructed. * * forced_values is used for secondary y-axes. The tick positions are typically * set by the primary y-axis, so the secondary y-axis has no choice in where to * put these. It simply has to generate labels for these data values. * * Tick lists * ---------- * Typically a tick will have both a grid/tick line and a label at one end of * that line (at the bottom for an x-axis, at left or right for the y-axis). * * A tick may be missing one of these two components: * - If "label_v" is specified instead of "v", then there will be no tick or * gridline, just a label. * - Similarly, if "label" is not specified, then there will be a gridline * without a label. * * This flexibility is useful in a few situations: * - For log scales, some of the tick lines may be too close to all have labels. * - For date scales where years are being displayed, it is desirable to display * tick marks at the beginnings of years but labels (e.g. "2006") in the * middle of the years. */ /*jshint sub:true */ /*global Dygraph:false */ "use strict"; import * as utils from './dygraph-utils'; /** @typedef {Array.<{v:number, label:string, label_v:(string|undefined)}>} */ var TickList = undefined; // the ' = undefined' keeps jshint happy. /** @typedef {function( * number, * number, * number, * function(string):*, * Dygraph=, * Array.= * ): TickList} */ var Ticker = undefined; // the ' = undefined' keeps jshint happy. /** @type {Ticker} */ export var numericLinearTicks = function(a, b, pixels, opts, dygraph, vals) { var nonLogscaleOpts = function(opt) { if (opt === 'logscale') return false; return opts(opt); }; return numericTicks(a, b, pixels, nonLogscaleOpts, dygraph, vals); }; /** @type {Ticker} */ export var numericTicks = function(a, b, pixels, opts, dygraph, vals) { var pixels_per_tick = /** @type{number} */(opts('pixelsPerLabel')); var ticks = []; var i, j, tickV, nTicks; if (vals) { for (i = 0; i < vals.length; i++) { ticks.push({v: vals[i]}); } } else { // TODO(danvk): factor this log-scale block out into a separate function. if (opts("logscale")) { nTicks = Math.floor(pixels / pixels_per_tick); var minIdx = utils.binarySearch(a, PREFERRED_LOG_TICK_VALUES, 1); var maxIdx = utils.binarySearch(b, PREFERRED_LOG_TICK_VALUES, -1); if (minIdx == -1) { minIdx = 0; } if (maxIdx == -1) { maxIdx = PREFERRED_LOG_TICK_VALUES.length - 1; } // Count the number of tick values would appear, if we can get at least // nTicks / 4 accept them. var lastDisplayed = null; if (maxIdx - minIdx >= nTicks / 4) { for (var idx = maxIdx; idx >= minIdx; idx--) { var tickValue = PREFERRED_LOG_TICK_VALUES[idx]; var pixel_coord = Math.log(tickValue / a) / Math.log(b / a) * pixels; var tick = { v: tickValue }; if (lastDisplayed === null) { lastDisplayed = { tickValue : tickValue, pixel_coord : pixel_coord }; } else { if (Math.abs(pixel_coord - lastDisplayed.pixel_coord) >= pixels_per_tick) { lastDisplayed = { tickValue : tickValue, pixel_coord : pixel_coord }; } else { tick.label = ""; } } ticks.push(tick); } // Since we went in backwards order. ticks.reverse(); } } // ticks.length won't be 0 if the log scale function finds values to insert. if (ticks.length === 0) { // Basic idea: // Try labels every 1, 2, 5, 10, 20, 50, 100, etc. // Calculate the resulting tick spacing (i.e. this.height_ / nTicks). // The first spacing greater than pixelsPerYLabel is what we use. // TODO(danvk): version that works on a log scale. var kmg2 = opts("labelsKMG2"); var mults, base; if (kmg2) { mults = [1, 2, 4, 8, 16, 32, 64, 128, 256]; base = 16; } else { mults = [1, 2, 5, 10, 20, 50, 100]; base = 10; } // Get the maximum number of permitted ticks based on the // graph's pixel size and pixels_per_tick setting. var max_ticks = Math.ceil(pixels / pixels_per_tick); // Now calculate the data unit equivalent of this tick spacing. // Use abs() since graphs may have a reversed Y axis. var units_per_tick = Math.abs(b - a) / max_ticks; // Based on this, get a starting scale which is the largest // integer power of the chosen base (10 or 16) that still remains // below the requested pixels_per_tick spacing. var base_power = Math.floor(Math.log(units_per_tick) / Math.log(base)); var base_scale = Math.pow(base, base_power); // Now try multiples of the starting scale until we find one // that results in tick marks spaced sufficiently far apart. // The "mults" array should cover the range 1 .. base^2 to // adjust for rounding and edge effects. var scale, low_val, high_val, spacing; for (j = 0; j < mults.length; j++) { scale = base_scale * mults[j]; low_val = Math.floor(a / scale) * scale; high_val = Math.ceil(b / scale) * scale; nTicks = Math.abs(high_val - low_val) / scale; spacing = pixels / nTicks; if (spacing > pixels_per_tick) break; } // Construct the set of ticks. // Allow reverse y-axis if it's explicitly requested. if (low_val > high_val) scale *= -1; for (i = 0; i <= nTicks; i++) { tickV = low_val + i * scale; ticks.push( {v: tickV} ); } } } var formatter = /**@type{AxisLabelFormatter}*/(opts('axisLabelFormatter')); // Add labels to the ticks. for (i = 0; i < ticks.length; i++) { if (ticks[i].label !== undefined) continue; // Use current label. // TODO(danvk): set granularity to something appropriate here. ticks[i].label = formatter.call(dygraph, ticks[i].v, 0, opts, dygraph); } return ticks; }; /** @type {Ticker} */ export var dateTicker = function(a, b, pixels, opts, dygraph, vals) { var chosen = pickDateTickGranularity(a, b, pixels, opts); if (chosen >= 0) { return getDateAxis(a, b, chosen, opts, dygraph); } else { // this can happen if self.width_ is zero. return []; } }; // Time granularity enumeration export var Granularity = { MILLISECONDLY: 0, TWO_MILLISECONDLY: 1, FIVE_MILLISECONDLY: 2, TEN_MILLISECONDLY: 3, FIFTY_MILLISECONDLY: 4, HUNDRED_MILLISECONDLY: 5, FIVE_HUNDRED_MILLISECONDLY: 6, SECONDLY: 7, TWO_SECONDLY: 8, FIVE_SECONDLY: 9, TEN_SECONDLY: 10, THIRTY_SECONDLY: 11, MINUTELY: 12, TWO_MINUTELY: 13, FIVE_MINUTELY: 14, TEN_MINUTELY: 15, THIRTY_MINUTELY: 16, HOURLY: 17, TWO_HOURLY: 18, SIX_HOURLY: 19, DAILY: 20, TWO_DAILY: 21, WEEKLY: 22, MONTHLY: 23, QUARTERLY: 24, BIANNUAL: 25, ANNUAL: 26, DECADAL: 27, CENTENNIAL: 28, NUM_GRANULARITIES: 29 } // Date components enumeration (in the order of the arguments in Date) // TODO: make this an @enum var DateField = { DATEFIELD_Y: 0, DATEFIELD_M: 1, DATEFIELD_D: 2, DATEFIELD_HH: 3, DATEFIELD_MM: 4, DATEFIELD_SS: 5, DATEFIELD_MS: 6, NUM_DATEFIELDS: 7 }; /** * The value of datefield will start at an even multiple of "step", i.e. * if datefield=SS and step=5 then the first tick will be on a multiple of 5s. * * For granularities <= HOURLY, ticks are generated every `spacing` ms. * * At coarser granularities, ticks are generated by incrementing `datefield` by * `step`. In this case, the `spacing` value is only used to estimate the * number of ticks. It should roughly correspond to the spacing between * adjacent ticks. * * @type {Array.<{datefield:number, step:number, spacing:number}>} */ var TICK_PLACEMENT = []; TICK_PLACEMENT[Granularity.MILLISECONDLY] = {datefield: DateField.DATEFIELD_MS, step: 1, spacing: 1}; TICK_PLACEMENT[Granularity.TWO_MILLISECONDLY] = {datefield: DateField.DATEFIELD_MS, step: 2, spacing: 2}; TICK_PLACEMENT[Granularity.FIVE_MILLISECONDLY] = {datefield: DateField.DATEFIELD_MS, step: 5, spacing: 5}; TICK_PLACEMENT[Granularity.TEN_MILLISECONDLY] = {datefield: DateField.DATEFIELD_MS, step: 10, spacing: 10}; TICK_PLACEMENT[Granularity.FIFTY_MILLISECONDLY] = {datefield: DateField.DATEFIELD_MS, step: 50, spacing: 50}; TICK_PLACEMENT[Granularity.HUNDRED_MILLISECONDLY] = {datefield: DateField.DATEFIELD_MS, step: 100, spacing: 100}; TICK_PLACEMENT[Granularity.FIVE_HUNDRED_MILLISECONDLY] = {datefield: DateField.DATEFIELD_MS, step: 500, spacing: 500}; TICK_PLACEMENT[Granularity.SECONDLY] = {datefield: DateField.DATEFIELD_SS, step: 1, spacing: 1000 * 1}; TICK_PLACEMENT[Granularity.TWO_SECONDLY] = {datefield: DateField.DATEFIELD_SS, step: 2, spacing: 1000 * 2}; TICK_PLACEMENT[Granularity.FIVE_SECONDLY] = {datefield: DateField.DATEFIELD_SS, step: 5, spacing: 1000 * 5}; TICK_PLACEMENT[Granularity.TEN_SECONDLY] = {datefield: DateField.DATEFIELD_SS, step: 10, spacing: 1000 * 10}; TICK_PLACEMENT[Granularity.THIRTY_SECONDLY] = {datefield: DateField.DATEFIELD_SS, step: 30, spacing: 1000 * 30}; TICK_PLACEMENT[Granularity.MINUTELY] = {datefield: DateField.DATEFIELD_MM, step: 1, spacing: 1000 * 60}; TICK_PLACEMENT[Granularity.TWO_MINUTELY] = {datefield: DateField.DATEFIELD_MM, step: 2, spacing: 1000 * 60 * 2}; TICK_PLACEMENT[Granularity.FIVE_MINUTELY] = {datefield: DateField.DATEFIELD_MM, step: 5, spacing: 1000 * 60 * 5}; TICK_PLACEMENT[Granularity.TEN_MINUTELY] = {datefield: DateField.DATEFIELD_MM, step: 10, spacing: 1000 * 60 * 10}; TICK_PLACEMENT[Granularity.THIRTY_MINUTELY] = {datefield: DateField.DATEFIELD_MM, step: 30, spacing: 1000 * 60 * 30}; TICK_PLACEMENT[Granularity.HOURLY] = {datefield: DateField.DATEFIELD_HH, step: 1, spacing: 1000 * 3600}; TICK_PLACEMENT[Granularity.TWO_HOURLY] = {datefield: DateField.DATEFIELD_HH, step: 2, spacing: 1000 * 3600 * 2}; TICK_PLACEMENT[Granularity.SIX_HOURLY] = {datefield: DateField.DATEFIELD_HH, step: 6, spacing: 1000 * 3600 * 6}; TICK_PLACEMENT[Granularity.DAILY] = {datefield: DateField.DATEFIELD_D, step: 1, spacing: 1000 * 86400}; TICK_PLACEMENT[Granularity.TWO_DAILY] = {datefield: DateField.DATEFIELD_D, step: 2, spacing: 1000 * 86400 * 2}; TICK_PLACEMENT[Granularity.WEEKLY] = {datefield: DateField.DATEFIELD_D, step: 7, spacing: 1000 * 604800}; TICK_PLACEMENT[Granularity.MONTHLY] = {datefield: DateField.DATEFIELD_M, step: 1, spacing: 1000 * 7200 * 365.2425}; // 1e3 * 60 * 60 * 24 * 365.2425 / 12 TICK_PLACEMENT[Granularity.QUARTERLY] = {datefield: DateField.DATEFIELD_M, step: 3, spacing: 1000 * 21600 * 365.2425}; // 1e3 * 60 * 60 * 24 * 365.2425 / 4 TICK_PLACEMENT[Granularity.BIANNUAL] = {datefield: DateField.DATEFIELD_M, step: 6, spacing: 1000 * 43200 * 365.2425}; // 1e3 * 60 * 60 * 24 * 365.2425 / 2 TICK_PLACEMENT[Granularity.ANNUAL] = {datefield: DateField.DATEFIELD_Y, step: 1, spacing: 1000 * 86400 * 365.2425}; // 1e3 * 60 * 60 * 24 * 365.2425 * 1 TICK_PLACEMENT[Granularity.DECADAL] = {datefield: DateField.DATEFIELD_Y, step: 10, spacing: 1000 * 864000 * 365.2425}; // 1e3 * 60 * 60 * 24 * 365.2425 * 10 TICK_PLACEMENT[Granularity.CENTENNIAL] = {datefield: DateField.DATEFIELD_Y, step: 100, spacing: 1000 * 8640000 * 365.2425}; // 1e3 * 60 * 60 * 24 * 365.2425 * 100 /** * This is a list of human-friendly values at which to show tick marks on a log * scale. It is k * 10^n, where k=1..9 and n=-39..+39, so: * ..., 1, 2, 3, 4, 5, ..., 9, 10, 20, 30, ..., 90, 100, 200, 300, ... * NOTE: this assumes that utils.LOG_SCALE = 10. * @type {Array.} */ var PREFERRED_LOG_TICK_VALUES = (function() { var vals = []; for (var power = -39; power <= 39; power++) { var range = Math.pow(10, power); for (var mult = 1; mult <= 9; mult++) { var val = range * mult; vals.push(val); } } return vals; })(); /** * Determine the correct granularity of ticks on a date axis. * * @param {number} a Left edge of the chart (ms) * @param {number} b Right edge of the chart (ms) * @param {number} pixels Size of the chart in the relevant dimension (width). * @param {function(string):*} opts Function mapping from option name -> value. * @return {number} The appropriate axis granularity for this chart. See the * enumeration of possible values in dygraph-tickers.js. */ export var pickDateTickGranularity = function(a, b, pixels, opts) { var pixels_per_tick = /** @type{number} */(opts('pixelsPerLabel')); for (var i = 0; i < Granularity.NUM_GRANULARITIES; i++) { var num_ticks = numDateTicks(a, b, i); if (pixels / num_ticks >= pixels_per_tick) { return i; } } return -1; }; /** * Compute the number of ticks on a date axis for a given granularity. * @param {number} start_time * @param {number} end_time * @param {number} granularity (one of the granularities enumerated above) * @return {number} (Approximate) number of ticks that would result. */ var numDateTicks = function(start_time, end_time, granularity) { var spacing = TICK_PLACEMENT[granularity].spacing; return Math.round(1.0 * (end_time - start_time) / spacing); }; /** * Compute the positions and labels of ticks on a date axis for a given granularity. * @param {number} start_time * @param {number} end_time * @param {number} granularity (one of the granularities enumerated above) * @param {function(string):*} opts Function mapping from option name -> value. * @param {Dygraph=} dg * @return {!TickList} */ export var getDateAxis = function(start_time, end_time, granularity, opts, dg) { var formatter = /** @type{AxisLabelFormatter} */( opts("axisLabelFormatter")); var utc = opts("labelsUTC"); var accessors = utc ? utils.DateAccessorsUTC : utils.DateAccessorsLocal; var datefield = TICK_PLACEMENT[granularity].datefield; var step = TICK_PLACEMENT[granularity].step; var spacing = TICK_PLACEMENT[granularity].spacing; // Choose a nice tick position before the initial instant. // Currently, this code deals properly with the existent daily granularities: // DAILY (with step of 1) and WEEKLY (with step of 7 but specially handled). // Other daily granularities (say TWO_DAILY) should also be handled specially // by setting the start_date_offset to 0. var start_date = new Date(start_time); var date_array = []; date_array[DateField.DATEFIELD_Y] = accessors.getFullYear(start_date); date_array[DateField.DATEFIELD_M] = accessors.getMonth(start_date); date_array[DateField.DATEFIELD_D] = accessors.getDate(start_date); date_array[DateField.DATEFIELD_HH] = accessors.getHours(start_date); date_array[DateField.DATEFIELD_MM] = accessors.getMinutes(start_date); date_array[DateField.DATEFIELD_SS] = accessors.getSeconds(start_date); date_array[DateField.DATEFIELD_MS] = accessors.getMilliseconds(start_date); var start_date_offset = date_array[datefield] % step; if (granularity == Granularity.WEEKLY) { // This will put the ticks on Sundays. start_date_offset = accessors.getDay(start_date); } date_array[datefield] -= start_date_offset; for (var df = datefield + 1; df < DateField.NUM_DATEFIELDS; df++) { // The minimum value is 1 for the day of month, and 0 for all other fields. date_array[df] = (df === DateField.DATEFIELD_D) ? 1 : 0; } // Generate the ticks. // For granularities not coarser than HOURLY we use the fact that: // the number of milliseconds between ticks is constant // and equal to the defined spacing. // Otherwise we rely on the 'roll over' property of the Date functions: // when some date field is set to a value outside of its logical range, // the excess 'rolls over' the next (more significant) field. // However, when using local time with DST transitions, // there are dates that do not represent any time value at all // (those in the hour skipped at the 'spring forward'), // and the JavaScript engines usually return an equivalent value. // Hence we have to check that the date is properly increased at each step, // returning a date at a nice tick position. var ticks = []; var tick_date = accessors.makeDate.apply(null, date_array); var tick_time = tick_date.getTime(); if (granularity <= Granularity.HOURLY) { if (tick_time < start_time) { tick_time += spacing; tick_date = new Date(tick_time); } while (tick_time <= end_time) { ticks.push({ v: tick_time, label: formatter.call(dg, tick_date, granularity, opts, dg) }); tick_time += spacing; tick_date = new Date(tick_time); } } else { if (tick_time < start_time) { date_array[datefield] += step; tick_date = accessors.makeDate.apply(null, date_array); tick_time = tick_date.getTime(); } while (tick_time <= end_time) { if (granularity >= Granularity.DAILY || accessors.getHours(tick_date) % step === 0) { ticks.push({ v: tick_time, label: formatter.call(dg, tick_date, granularity, opts, dg) }); } date_array[datefield] += step; tick_date = accessors.makeDate.apply(null, date_array); tick_time = tick_date.getTime(); } } return ticks; }; dygraphs-2.2.1/src/dygraph-types.js010064400000000000000000000021231437353256000142170ustar00/** * @license * Copyright 2006 Dan Vanderkam (danvdk@gmail.com) * MIT-licenced: https://opensource.org/licenses/MIT */ // This file contains typedefs and externs that are needed by the Closure Compiler. /** * @typedef {{ * px: number, * py: number, * isZooming: boolean, * isPanning: boolean, * is2DPan: boolean, * cancelNextDblclick: boolean, * initializeMouseDown: * function(!Event, !Dygraph, !DygraphInteractionContext) * }} */ var DygraphInteractionContext; /** * Point structure. * * xval_* and yval_* are the original unscaled data values, * while x_* and y_* are scaled to the range (0.0-1.0) for plotting. * yval_stacked is the cumulative Y value used for stacking graphs, * and bottom/top/minus/plus are used for error bar graphs. * * @typedef {{ * idx: number, * name: string, * x: ?number, * xval: ?number, * y_bottom: ?number, * y: ?number, * y_stacked: ?number, * y_top: ?number, * yval_minus: ?number, * yval: ?number, * yval_plus: ?number, * yval_stacked * }} */ Dygraph.PointType; dygraphs-2.2.1/src/dygraph-utils.js010064400000000000000000001233271437353256000142250ustar00/** * @license * Copyright 2011 Dan Vanderkam (danvdk@gmail.com) * MIT-licenced: https://opensource.org/licenses/MIT */ /** * @fileoverview This file contains utility functions used by dygraphs. These * are typically static (i.e. not related to any particular dygraph). Examples * include date/time formatting functions, basic algorithms (e.g. binary * search) and generic DOM-manipulation functions. */ /*global Dygraph:false, Node:false */ "use strict"; import * as DygraphTickers from './dygraph-tickers'; /** * @param {*} o * @return {string} * @private */ export function type(o) { return (o === null ? 'null' : typeof(o)); } export var LOG_SCALE = 10; export var LN_TEN = Math.log(LOG_SCALE); /** * @private * @param {number} x * @return {number} */ export var log10 = function(x) { return Math.log(x) / LN_TEN; }; /** * @private * @param {number} r0 * @param {number} r1 * @param {number} pct * @return {number} */ export var logRangeFraction = function(r0, r1, pct) { // Computing the inverse of toPercentXCoord. The function was arrived at with // the following steps: // // Original calcuation: // pct = (log(x) - log(xRange[0])) / (log(xRange[1]) - log(xRange[0])); // // Multiply both sides by the right-side denominator. // pct * (log(xRange[1] - log(xRange[0]))) = log(x) - log(xRange[0]) // // add log(xRange[0]) to both sides // log(xRange[0]) + (pct * (log(xRange[1]) - log(xRange[0]))) = log(x); // // Swap both sides of the equation, // log(x) = log(xRange[0]) + (pct * (log(xRange[1]) - log(xRange[0]))) // // Use both sides as the exponent in 10^exp and we're done. // x = 10 ^ (log(xRange[0]) + (pct * (log(xRange[1]) - log(xRange[0])))) var logr0 = log10(r0); var logr1 = log10(r1); var exponent = logr0 + (pct * (logr1 - logr0)); var value = Math.pow(LOG_SCALE, exponent); return value; }; /** A dotted line stroke pattern. */ export var DOTTED_LINE = [2, 2]; /** A dashed line stroke pattern. */ export var DASHED_LINE = [7, 3]; /** A dot dash stroke pattern. */ export var DOT_DASH_LINE = [7, 2, 2, 2]; // Directions for panning and zooming. Use bit operations when combined // values are possible. export var HORIZONTAL = 1; export var VERTICAL = 2; /** * Return the 2d context for a dygraph canvas. * * This method is only exposed for the sake of replacing the function in * automated tests. * * @param {!HTMLCanvasElement} canvas * @return {!CanvasRenderingContext2D} * @private */ export var getContext = function(canvas) { return /** @type{!CanvasRenderingContext2D}*/(canvas.getContext("2d")); }; /** * Add an event handler. * @param {!Node} elem The element to add the event to. * @param {string} type The type of the event, e.g. 'click' or 'mousemove'. * @param {function(Event):(boolean|undefined)} fn The function to call * on the event. The function takes one parameter: the event object. * @private */ export var addEvent = function addEvent(elem, type, fn) { elem.addEventListener(type, fn, false); }; /** * Remove an event handler. * @param {!Node} elem The element to remove the event from. * @param {string} type The type of the event, e.g. 'click' or 'mousemove'. * @param {function(Event):(boolean|undefined)} fn The function to call * on the event. The function takes one parameter: the event object. */ export function removeEvent(elem, type, fn) { elem.removeEventListener(type, fn, false); } /** * Cancels further processing of an event. This is useful to prevent default * browser actions, e.g. highlighting text on a double-click. * Based on the article at * http://www.switchonthecode.com/tutorials/javascript-tutorial-the-scroll-wheel * @param {!Event} e The event whose normal behavior should be canceled. * @private */ export function cancelEvent(e) { e = e ? e : window.event; if (e.stopPropagation) { e.stopPropagation(); } if (e.preventDefault) { e.preventDefault(); } e.cancelBubble = true; e.cancel = true; e.returnValue = false; return false; } /** * Convert hsv values to an rgb(r,g,b) string. Taken from MochiKit.Color. This * is used to generate default series colors which are evenly spaced on the * color wheel. * @param {number} hue Range is 0.0-1.0. * @param {number} saturation Range is 0.0-1.0. * @param {number} value Range is 0.0-1.0. * @return {string} "rgb(r,g,b)" where r, g and b range from 0-255. * @private */ export function hsvToRGB(hue, saturation, value) { var red; var green; var blue; if (saturation === 0) { red = value; green = value; blue = value; } else { var i = Math.floor(hue * 6); var f = (hue * 6) - i; var p = value * (1 - saturation); var q = value * (1 - (saturation * f)); var t = value * (1 - (saturation * (1 - f))); switch (i) { case 1: red = q; green = value; blue = p; break; case 2: red = p; green = value; blue = t; break; case 3: red = p; green = q; blue = value; break; case 4: red = t; green = p; blue = value; break; case 5: red = value; green = p; blue = q; break; case 6: // fall through case 0: red = value; green = t; blue = p; break; } } red = Math.floor(255 * red + 0.5); green = Math.floor(255 * green + 0.5); blue = Math.floor(255 * blue + 0.5); return 'rgb(' + red + ',' + green + ',' + blue + ')'; } /** * Find the coordinates of an object relative to the top left of the page. * * @param {Node} obj * @return {{x:number,y:number}} * @private */ export function findPos(obj) { var p = obj.getBoundingClientRect(), w = window, d = document.documentElement; return { x: p.left + (w.pageXOffset || d.scrollLeft), y: p.top + (w.pageYOffset || d.scrollTop) } } /** * Returns the x-coordinate of the event in a coordinate system where the * top-left corner of the page (not the window) is (0,0). * Taken from MochiKit.Signal * @param {!Event} e * @return {number} * @private */ export function pageX(e) { return (!e.pageX || e.pageX < 0) ? 0 : e.pageX; } /** * Returns the y-coordinate of the event in a coordinate system where the * top-left corner of the page (not the window) is (0,0). * Taken from MochiKit.Signal * @param {!Event} e * @return {number} * @private */ export function pageY(e) { return (!e.pageY || e.pageY < 0) ? 0 : e.pageY; } /** * Converts page the x-coordinate of the event to pixel x-coordinates on the * canvas (i.e. DOM Coords). * @param {!Event} e Drag event. * @param {!DygraphInteractionContext} context Interaction context object. * @return {number} The amount by which the drag has moved to the right. */ export function dragGetX_(e, context) { return pageX(e) - context.px; } /** * Converts page the y-coordinate of the event to pixel y-coordinates on the * canvas (i.e. DOM Coords). * @param {!Event} e Drag event. * @param {!DygraphInteractionContext} context Interaction context object. * @return {number} The amount by which the drag has moved down. */ export function dragGetY_(e, context) { return pageY(e) - context.py; } /** * This returns true unless the parameter is 0, null, undefined or NaN. * TODO(danvk): rename this function to something like 'isNonZeroNan'. * * @param {number} x The number to consider. * @return {boolean} Whether the number is zero or NaN. * @private */ export function isOK(x) { return !!x && !isNaN(x); } /** * @param {{x:?number,y:?number,yval:?number}} p The point to consider, valid * points are {x, y} objects * @param {boolean=} opt_allowNaNY Treat point with y=NaN as valid * @return {boolean} Whether the point has numeric x and y. * @private */ export function isValidPoint(p, opt_allowNaNY) { if (!p) return false; // null or undefined object if (p.yval === null) return false; // missing point if (p.x === null || p.x === undefined) return false; if (p.y === null || p.y === undefined) return false; if (isNaN(p.x) || (!opt_allowNaNY && isNaN(p.y))) return false; return true; } /** * Number formatting function which mimics the behavior of %g in printf, i.e. * either exponential or fixed format (without trailing 0s) is used depending on * the length of the generated string. The advantage of this format is that * there is a predictable upper bound on the resulting string length, * significant figures are not dropped, and normal numbers are not displayed in * exponential notation. * * NOTE: JavaScript's native toPrecision() is NOT a drop-in replacement for %g. * It creates strings which are too long for absolute values between 10^-4 and * 10^-6, e.g. '0.00001' instead of '1e-5'. See tests/number-format.html for * output examples. * * @param {number} x The number to format * @param {number=} opt_precision The precision to use, default 2. * @return {string} A string formatted like %g in printf. The max generated * string length should be precision + 6 (e.g 1.123e+300). */ export function floatFormat(x, opt_precision) { // Avoid invalid precision values; [1, 21] is the valid range. var p = Math.min(Math.max(1, opt_precision || 2), 21); // This is deceptively simple. The actual algorithm comes from: // // Max allowed length = p + 4 // where 4 comes from 'e+n' and '.'. // // Length of fixed format = 2 + y + p // where 2 comes from '0.' and y = # of leading zeroes. // // Equating the two and solving for y yields y = 2, or 0.00xxxx which is // 1.0e-3. // // Since the behavior of toPrecision() is identical for larger numbers, we // don't have to worry about the other bound. // // Finally, the argument for toExponential() is the number of trailing digits, // so we take off 1 for the value before the '.'. return (Math.abs(x) < 1.0e-3 && x !== 0.0) ? x.toExponential(p - 1) : x.toPrecision(p); } /** * Converts '9' to '09' (useful for dates) * @param {number} x * @return {string} * @private */ export function zeropad(x) { if (x < 10) return "0" + x; else return "" + x; } /** * Date accessors to get the parts of a calendar date (year, month, * day, hour, minute, second and millisecond) according to local time, * and factory method to call the Date constructor with an array of arguments. */ export var DateAccessorsLocal = { getFullYear: d => d.getFullYear(), getMonth: d => d.getMonth(), getDate: d => d.getDate(), getHours: d => d.getHours(), getMinutes: d => d.getMinutes(), getSeconds: d => d.getSeconds(), getMilliseconds: d => d.getMilliseconds(), getDay: d => d.getDay(), makeDate: function(y, m, d, hh, mm, ss, ms) { return new Date(y, m, d, hh, mm, ss, ms); } }; /** * Date accessors to get the parts of a calendar date (year, month, * day of month, hour, minute, second and millisecond) according to UTC time, * and factory method to call the Date constructor with an array of arguments. */ export var DateAccessorsUTC = { getFullYear: d => d.getUTCFullYear(), getMonth: d => d.getUTCMonth(), getDate: d => d.getUTCDate(), getHours: d => d.getUTCHours(), getMinutes: d => d.getUTCMinutes(), getSeconds: d => d.getUTCSeconds(), getMilliseconds: d => d.getUTCMilliseconds(), getDay: d => d.getUTCDay(), makeDate: function(y, m, d, hh, mm, ss, ms) { return new Date(Date.UTC(y, m, d, hh, mm, ss, ms)); } }; /** * Return a string version of the hours, minutes and seconds portion of a date. * @param {number} hh The hours (from 0-23) * @param {number} mm The minutes (from 0-59) * @param {number} ss The seconds (from 0-59) * @return {string} A time of the form "HH:MM" or "HH:MM:SS" * @private */ export function hmsString_(hh, mm, ss, ms) { var ret = zeropad(hh) + ":" + zeropad(mm); if (ss) { ret += ":" + zeropad(ss); if (ms) { var str = "" + ms; ret += "." + ('000'+str).substring(str.length); } } return ret; } /** * Convert a JS date (millis since epoch) to a formatted string. * @param {number} time The JavaScript time value (ms since epoch) * @param {boolean} utc Whether output UTC or local time * @return {string} A date of one of these forms: * "YYYY/MM/DD", "YYYY/MM/DD HH:MM" or "YYYY/MM/DD HH:MM:SS" * @private */ export function dateString_(time, utc) { var accessors = utc ? DateAccessorsUTC : DateAccessorsLocal; var date = new Date(time); var y = accessors.getFullYear(date); var m = accessors.getMonth(date); var d = accessors.getDate(date); var hh = accessors.getHours(date); var mm = accessors.getMinutes(date); var ss = accessors.getSeconds(date); var ms = accessors.getMilliseconds(date); // Get a year string: var year = "" + y; // Get a 0 padded month string var month = zeropad(m + 1); //months are 0-offset, sigh // Get a 0 padded day string var day = zeropad(d); var frac = hh * 3600 + mm * 60 + ss + 1e-3 * ms; var ret = year + "/" + month + "/" + day; if (frac) { ret += " " + hmsString_(hh, mm, ss, ms); } return ret; } /** * Round a number to the specified number of digits past the decimal point. * @param {number} num The number to round * @param {number} places The number of decimals to which to round * @return {number} The rounded number * @private */ export function round_(num, places) { var shift = Math.pow(10, places); return Math.round(num * shift)/shift; } /** * Implementation of binary search over an array. * Currently does not work when val is outside the range of arry's values. * @param {number} val the value to search for * @param {Array.} arry is the value over which to search * @param {number} abs If abs > 0, find the lowest entry greater than val * If abs < 0, find the highest entry less than val. * If abs == 0, find the entry that equals val. * @param {number=} low The first index in arry to consider (optional) * @param {number=} high The last index in arry to consider (optional) * @return {number} Index of the element, or -1 if it isn't found. * @private */ export function binarySearch(val, arry, abs, low, high) { if (low === null || low === undefined || high === null || high === undefined) { low = 0; high = arry.length - 1; } if (low > high) { return -1; } if (abs === null || abs === undefined) { abs = 0; } var validIndex = function(idx) { return idx >= 0 && idx < arry.length; }; var mid = parseInt((low + high) / 2, 10); var element = arry[mid]; var idx; if (element == val) { return mid; } else if (element > val) { if (abs > 0) { // Accept if element > val, but also if prior element < val. idx = mid - 1; if (validIndex(idx) && arry[idx] < val) { return mid; } } return binarySearch(val, arry, abs, low, mid - 1); } else if (element < val) { if (abs < 0) { // Accept if element < val, but also if prior element > val. idx = mid + 1; if (validIndex(idx) && arry[idx] > val) { return mid; } } return binarySearch(val, arry, abs, mid + 1, high); } return -1; // can't actually happen, but makes closure compiler happy } /** * Parses a date, returning the number of milliseconds since epoch. This can be * passed in as an xValueParser in the Dygraph constructor. * TODO(danvk): enumerate formats that this understands. * * @param {string} dateStr A date in a variety of possible string formats. * @return {number} Milliseconds since epoch. * @private */ export function dateParser(dateStr) { var dateStrSlashed; var d; // Let the system try the format first, with one caveat: // YYYY-MM-DD[ HH:MM:SS] is interpreted as UTC by a variety of browsers. // dygraphs displays dates in local time, so this will result in surprising // inconsistencies. But if you specify "T" or "Z" (i.e. YYYY-MM-DDTHH:MM:SS), // then you probably know what you're doing, so we'll let you go ahead. // Issue: http://code.google.com/p/dygraphs/issues/detail?id=255 if (dateStr.search("-") == -1 || dateStr.search("T") != -1 || dateStr.search("Z") != -1) { d = dateStrToMillis(dateStr); if (d && !isNaN(d)) return d; } if (dateStr.search("-") != -1) { // e.g. '2009-7-12' or '2009-07-12' dateStrSlashed = dateStr.replace("-", "/", "g"); while (dateStrSlashed.search("-") != -1) { dateStrSlashed = dateStrSlashed.replace("-", "/"); } d = dateStrToMillis(dateStrSlashed); } else { // Any format that Date.parse will accept, e.g. "2009/07/12" or // "2009/07/12 12:34:56" d = dateStrToMillis(dateStr); } if (!d || isNaN(d)) { console.error("Couldn't parse " + dateStr + " as a date"); } return d; } /** * This is identical to JavaScript's built-in Date.parse() method, except that * it doesn't get replaced with an incompatible method by aggressive JS * libraries like MooTools or Joomla. * @param {string} str The date string, e.g. "2011/05/06" * @return {number} millis since epoch * @private */ export function dateStrToMillis(str) { return new Date(str).getTime(); } // These functions are all based on MochiKit. /** * Copies all the properties from o to self. * * @param {!Object} self * @param {!Object} o * @return {!Object} */ export function update(self, o) { if (typeof(o) != 'undefined' && o !== null) { for (var k in o) { if (o.hasOwnProperty(k)) { self[k] = o[k]; } } } return self; } // internal: check if o is a DOM node, and we know it’s not null var _isNode = (typeof(Node) !== 'undefined' && Node !== null && typeof(Node) === 'object') ? function _isNode(o) { return (o instanceof Node); } : function _isNode(o) { return (typeof(o) === 'object' && typeof(o.nodeType) === 'number' && typeof(o.nodeName) === 'string'); }; /** * Copies all the properties from o to self. * * @param {!Object} self * @param {!Object} o * @return {!Object} * @private */ export function updateDeep(self, o) { if (typeof(o) != 'undefined' && o !== null) { for (var k in o) { if (o.hasOwnProperty(k)) { const v = o[k]; if (v === null) { self[k] = null; } else if (isArrayLike(v)) { self[k] = v.slice(); } else if (_isNode(v)) { // DOM objects are shallowly-copied. self[k] = v; } else if (typeof(v) == 'object') { if (typeof(self[k]) != 'object' || self[k] === null) { self[k] = {}; } updateDeep(self[k], v); } else { self[k] = v; } } } } return self; } /** * @param {*} o * @return {string} * @private */ export function typeArrayLike(o) { if (o === null) return 'null'; const t = typeof(o); if ((t === 'object' || (t === 'function' && typeof(o.item) === 'function')) && typeof(o.length) === 'number' && o.nodeType !== 3 && o.nodeType !== 4) return 'array'; return t; } /** * @param {*} o * @return {boolean} * @private */ export function isArrayLike(o) { const t = typeof(o); return (o !== null && (t === 'object' || (t === 'function' && typeof(o.item) === 'function')) && typeof(o.length) === 'number' && o.nodeType !== 3 && o.nodeType !== 4); } /** * @param {Object} o * @return {boolean} * @private */ export function isDateLike(o) { return (o !== null && typeof(o) === 'object' && typeof(o.getTime) === 'function'); } /** * Note: this only seems to work for arrays. * @param {!Array} o * @return {!Array} * @private */ export function clone(o) { // TODO(danvk): figure out how MochiKit's version works var r = []; for (var i = 0; i < o.length; i++) { if (isArrayLike(o[i])) { r.push(clone(o[i])); } else { r.push(o[i]); } } return r; } /** * Create a new canvas element. * * @return {!HTMLCanvasElement} * @private */ export function createCanvas() { return document.createElement('canvas'); } /** * Returns the context's pixel ratio, which is the ratio between the device * pixel ratio and the backing store ratio. Typically this is 1 for conventional * displays, and > 1 for HiDPI displays (such as the Retina MBP). * See http://www.html5rocks.com/en/tutorials/canvas/hidpi/ for more details. * * @param {!CanvasRenderingContext2D} context The canvas's 2d context. * @return {number} The ratio of the device pixel ratio and the backing store * ratio for the specified context. */ export function getContextPixelRatio(context) { try { var devicePixelRatio = window.devicePixelRatio; var backingStoreRatio = context.webkitBackingStorePixelRatio || context.mozBackingStorePixelRatio || context.msBackingStorePixelRatio || context.oBackingStorePixelRatio || context.backingStorePixelRatio || 1; if (devicePixelRatio !== undefined) { return devicePixelRatio / backingStoreRatio; } else { // At least devicePixelRatio must be defined for this ratio to make sense. // We default backingStoreRatio to 1: this does not exist on some browsers // (i.e. desktop Chrome). return 1; } } catch (e) { return 1; } } /** * TODO(danvk): use @template here when it's better supported for classes. * @param {!Array} array * @param {number} start * @param {number} length * @param {function(!Array,?):boolean=} predicate * @constructor */ export function Iterator(array, start, length, predicate) { start = start || 0; length = length || array.length; this.hasNext = true; // Use to identify if there's another element. this.peek = null; // Use for look-ahead this.start_ = start; this.array_ = array; this.predicate_ = predicate; this.end_ = Math.min(array.length, start + length); this.nextIdx_ = start - 1; // use -1 so initial advance works. this.next(); // ignoring result. } /** * @return {Object} */ Iterator.prototype.next = function() { if (!this.hasNext) { return null; } var obj = this.peek; var nextIdx = this.nextIdx_ + 1; var found = false; while (nextIdx < this.end_) { if (!this.predicate_ || this.predicate_(this.array_, nextIdx)) { this.peek = this.array_[nextIdx]; found = true; break; } nextIdx++; } this.nextIdx_ = nextIdx; if (!found) { this.hasNext = false; this.peek = null; } return obj; }; /** * Returns a new iterator over array, between indexes start and * start + length, and only returns entries that pass the accept function * * @param {!Array} array the array to iterate over. * @param {number} start the first index to iterate over, 0 if absent. * @param {number} length the number of elements in the array to iterate over. * This, along with start, defines a slice of the array, and so length * doesn't imply the number of elements in the iterator when accept doesn't * always accept all values. array.length when absent. * @param {function(?):boolean=} opt_predicate a function that takes * parameters array and idx, which returns true when the element should be * returned. If omitted, all elements are accepted. * @private */ export function createIterator(array, start, length, opt_predicate) { return new Iterator(array, start, length, opt_predicate); } // Shim layer with setTimeout fallback. // From: http://paulirish.com/2011/requestanimationframe-for-smart-animating/ // Should be called with the window context: // Dygraph.requestAnimFrame.call(window, function() {}) export var requestAnimFrame = (function() { return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function (callback) { window.setTimeout(callback, 1000 / 60); }; })(); /** * Call a function at most maxFrames times at an attempted interval of * framePeriodInMillis, then call a cleanup function once. repeatFn is called * once immediately, then at most (maxFrames - 1) times asynchronously. If * maxFrames==1, then cleanup_fn() is also called synchronously. This function * is used to sequence animation. * @param {function(number)} repeatFn Called repeatedly -- takes the frame * number (from 0 to maxFrames-1) as an argument. * @param {number} maxFrames The max number of times to call repeatFn * @param {number} framePeriodInMillis Max requested time between frames. * @param {function()} cleanupFn A function to call after all repeatFn calls. * @private */ export function repeatAndCleanup(repeatFn, maxFrames, framePeriodInMillis, cleanupFn) { var frameNumber = 0; var previousFrameNumber; var startTime = new Date().getTime(); repeatFn(frameNumber); if (maxFrames == 1) { cleanupFn(); return; } var maxFrameArg = maxFrames - 1; (function loop() { if (frameNumber >= maxFrames) return; requestAnimFrame.call(window, function() { // Determine which frame to draw based on the delay so far. Will skip // frames if necessary. var currentTime = new Date().getTime(); var delayInMillis = currentTime - startTime; previousFrameNumber = frameNumber; frameNumber = Math.floor(delayInMillis / framePeriodInMillis); var frameDelta = frameNumber - previousFrameNumber; // If we predict that the subsequent repeatFn call will overshoot our // total frame target, so our last call will cause a stutter, then jump to // the last call immediately. If we're going to cause a stutter, better // to do it faster than slower. var predictOvershootStutter = (frameNumber + frameDelta) > maxFrameArg; if (predictOvershootStutter || (frameNumber >= maxFrameArg)) { repeatFn(maxFrameArg); // Ensure final call with maxFrameArg. cleanupFn(); } else { if (frameDelta !== 0) { // Don't call repeatFn with duplicate frames. repeatFn(frameNumber); } loop(); } }); })(); } // A whitelist of options that do not change pixel positions. var pixelSafeOptions = { 'annotationClickHandler': true, 'annotationDblClickHandler': true, 'annotationMouseOutHandler': true, 'annotationMouseOverHandler': true, 'axisLineColor': true, 'axisLineWidth': true, 'clickCallback': true, 'drawCallback': true, 'drawHighlightPointCallback': true, 'drawPoints': true, 'drawPointCallback': true, 'drawGrid': true, 'fillAlpha': true, 'gridLineColor': true, 'gridLineWidth': true, 'hideOverlayOnMouseOut': true, 'highlightCallback': true, 'highlightCircleSize': true, 'interactionModel': true, 'labelsDiv': true, 'labelsKMB': true, 'labelsKMG2': true, 'labelsSeparateLines': true, 'labelsShowZeroValues': true, 'legend': true, 'panEdgeFraction': true, 'pixelsPerYLabel': true, 'pointClickCallback': true, 'pointSize': true, 'rangeSelectorPlotFillColor': true, 'rangeSelectorPlotFillGradientColor': true, 'rangeSelectorPlotStrokeColor': true, 'rangeSelectorBackgroundStrokeColor': true, 'rangeSelectorBackgroundLineWidth': true, 'rangeSelectorPlotLineWidth': true, 'rangeSelectorForegroundStrokeColor': true, 'rangeSelectorForegroundLineWidth': true, 'rangeSelectorAlpha': true, 'showLabelsOnHighlight': true, 'showRoller': true, 'strokeWidth': true, 'underlayCallback': true, 'unhighlightCallback': true, 'zoomCallback': true }; /** * This function will scan the option list and determine if they * require us to recalculate the pixel positions of each point. * TODO: move this into dygraph-options.js * @param {!Array.} labels a list of options to check. * @param {!Object} attrs * @return {boolean} true if the graph needs new points else false. * @private */ export function isPixelChangingOptionList(labels, attrs) { // Assume that we do not require new points. // This will change to true if we actually do need new points. // Create a dictionary of series names for faster lookup. // If there are no labels, then the dictionary stays empty. var seriesNamesDictionary = { }; if (labels) { for (var i = 1; i < labels.length; i++) { seriesNamesDictionary[labels[i]] = true; } } // Scan through a flat (i.e. non-nested) object of options. // Returns true/false depending on whether new points are needed. var scanFlatOptions = function(options) { for (var property in options) { if (options.hasOwnProperty(property) && !pixelSafeOptions[property]) { return true; } } return false; }; // Iterate through the list of updated options. for (var property in attrs) { if (!attrs.hasOwnProperty(property)) continue; // Find out of this field is actually a series specific options list. if (property == 'highlightSeriesOpts' || (seriesNamesDictionary[property] && !attrs.series)) { // This property value is a list of options for this series. if (scanFlatOptions(attrs[property])) return true; } else if (property == 'series' || property == 'axes') { // This is twice-nested options list. var perSeries = attrs[property]; for (var series in perSeries) { if (perSeries.hasOwnProperty(series) && scanFlatOptions(perSeries[series])) { return true; } } } else { // If this was not a series specific option list, // check if it's a pixel-changing property. if (!pixelSafeOptions[property]) return true; } } return false; } export var Circles = { DEFAULT : function(g, name, ctx, canvasx, canvasy, color, radius) { ctx.beginPath(); ctx.fillStyle = color; ctx.arc(canvasx, canvasy, radius, 0, 2 * Math.PI, false); ctx.fill(); } // For more shapes, include extras/shapes.js }; /** * Determine whether |data| is delimited by CR, CRLF, LF, LFCR. * @param {string} data * @return {?string} the delimiter that was detected (or null on failure). */ export function detectLineDelimiter(data) { for (var i = 0; i < data.length; i++) { var code = data.charAt(i); if (code === '\r') { // Might actually be "\r\n". if (((i + 1) < data.length) && (data.charAt(i + 1) === '\n')) { return '\r\n'; } return code; } if (code === '\n') { // Might actually be "\n\r". if (((i + 1) < data.length) && (data.charAt(i + 1) === '\r')) { return '\n\r'; } return code; } } return null; } /** * Is one node contained by another? * @param {Node} containee The contained node. * @param {Node} container The container node. * @return {boolean} Whether containee is inside (or equal to) container. * @private */ export function isNodeContainedBy(containee, container) { if (container === null || containee === null) { return false; } var containeeNode = /** @type {Node} */ (containee); while (containeeNode && containeeNode !== container) { containeeNode = containeeNode.parentNode; } return (containeeNode === container); } // This masks some numeric issues in older versions of Firefox, // where 1.0/Math.pow(10,2) != Math.pow(10,-2). /** @type {function(number,number):number} */ export function pow(base, exp) { if (exp < 0) { return 1.0 / Math.pow(base, -exp); } return Math.pow(base, exp); } var RGBAxRE = /^#([0-9A-Fa-f]{2})([0-9A-Fa-f]{2})([0-9A-Fa-f]{2})([0-9A-Fa-f]{2})?$/; var RGBA_RE = /^rgba?\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})(?:,\s*([01](?:\.\d+)?))?\)$/; /** * Helper for toRGB_ which parses strings of the form: * #RRGGBB (hex) * #RRGGBBAA (hex) * rgb(123, 45, 67) * rgba(123, 45, 67, 0.5) * @return parsed {r,g,b,a?} tuple or null. */ function parseRGBA(rgbStr) { var bits, r, g, b, a = null; if ((bits = RGBAxRE.exec(rgbStr))) { r = parseInt(bits[1], 16); g = parseInt(bits[2], 16); b = parseInt(bits[3], 16); if (bits[4]) a = parseInt(bits[4], 16); } else if ((bits = RGBA_RE.exec(rgbStr))) { r = parseInt(bits[1], 10); g = parseInt(bits[2], 10); b = parseInt(bits[3], 10); if (bits[4]) a = parseFloat(bits[4]); } else return null; if (a !== null) return { "r": r, "g": g, "b": b, "a": a }; return { "r": r, "g": g, "b": b }; } /** * Converts any valid CSS color (hex, rgb(), named color) to an RGB tuple. * * @param {!string} colorStr Any valid CSS color string. * @return {{r:number,g:number,b:number,a:number?}} Parsed RGB tuple. * @private */ export function toRGB_(colorStr) { // Strategy: First try to parse colorStr directly. This is fast & avoids DOM // manipulation. If that fails (e.g. for named colors like 'red'), then // create a hidden DOM element and parse its computed color. var rgb = parseRGBA(colorStr); if (rgb) return rgb; var div = document.createElement('div'); div.style.backgroundColor = colorStr; div.style.visibility = 'hidden'; document.body.appendChild(div); var rgbStr = window.getComputedStyle(div, null).backgroundColor; document.body.removeChild(div); return parseRGBA(rgbStr); } /** * Checks whether the browser supports the <canvas> tag. * @param {HTMLCanvasElement=} opt_canvasElement Pass a canvas element as an * optimization if you have one. * @return {boolean} Whether the browser supports canvas. */ export function isCanvasSupported(opt_canvasElement) { try { var canvas = opt_canvasElement || document.createElement("canvas"); canvas.getContext("2d"); } catch (e) { return false; } return true; } /** * Parses the value as a floating point number. This is like the parseFloat() * built-in, but with a few differences: * - the empty string is parsed as null, rather than NaN. * - if the string cannot be parsed at all, an error is logged. * If the string can't be parsed, this method returns null. * @param {string} x The string to be parsed * @param {number=} opt_line_no The line number from which the string comes. * @param {string=} opt_line The text of the line from which the string comes. */ export function parseFloat_(x, opt_line_no, opt_line) { var val = parseFloat(x); if (!isNaN(val)) return val; // Try to figure out what happeend. // If the value is the empty string, parse it as null. if (/^ *$/.test(x)) return null; // If it was actually "NaN", return it as NaN. if (/^ *nan *$/i.test(x)) return NaN; // Looks like a parsing error. var msg = "Unable to parse '" + x + "' as a number"; if (opt_line !== undefined && opt_line_no !== undefined) { msg += " on line " + (1+(opt_line_no||0)) + " ('" + opt_line + "') of CSV."; } console.error(msg); return null; } // Label constants for the labelsKMB and labelsKMG2 options. // (i.e. '100000' -> '100k') var KMB_LABELS_LARGE = [ 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y' ]; var KMB_LABELS_SMALL = [ 'm', 'µ', 'n', 'p', 'f', 'a', 'z', 'y' ]; var KMG2_LABELS_LARGE = [ 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi' ]; var KMG2_LABELS_SMALL = [ 'p-10', 'p-20', 'p-30', 'p-40', 'p-50', 'p-60', 'p-70', 'p-80' ]; /* if both are given (legacy/deprecated use only) */ var KMB2_LABELS_LARGE = [ 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y' ]; var KMB2_LABELS_SMALL = KMB_LABELS_SMALL; /** * @private * Return a string version of a number. This respects the digitsAfterDecimal * and maxNumberWidth options. * @param {number} x The number to be formatted * @param {Dygraph} opts An options view */ export function numberValueFormatter(x, opts) { var sigFigs = opts('sigFigs'); if (sigFigs !== null) { // User has opted for a fixed number of significant figures. return floatFormat(x, sigFigs); } // shortcut 0 so later code does not need to worry about it if (x === 0.0) return '0'; var digits = opts('digitsAfterDecimal'); var maxNumberWidth = opts('maxNumberWidth'); var kmb = opts('labelsKMB'); var kmg2 = opts('labelsKMG2'); var label; var absx = Math.abs(x); if (kmb || kmg2) { var k; var k_labels = []; var m_labels = []; if (kmb) { k = 1000; k_labels = KMB_LABELS_LARGE; m_labels = KMB_LABELS_SMALL; } if (kmg2) { k = 1024; k_labels = KMG2_LABELS_LARGE; m_labels = KMG2_LABELS_SMALL; if (kmb) { k_labels = KMB2_LABELS_LARGE; m_labels = KMB2_LABELS_SMALL; } } var n; var j; if (absx >= k) { j = k_labels.length; while (j > 0) { n = pow(k, j); --j; if (absx >= n) { // guaranteed to hit because absx >= k (pow(k, 1)) // if immensely large still switch to scientific notation if ((absx / n) >= Math.pow(10, maxNumberWidth)) label = x.toExponential(digits); else label = round_(x / n, digits) + k_labels[j]; return label; } } // not reached, fall through safely though should it ever be } else if ((absx < 1) /* && (m_labels.length > 0) */) { j = 0; while (j < m_labels.length) { ++j; n = pow(k, j); if ((absx * n) >= 1) break; } // if _still_ too small, switch to scientific notation instead if ((absx * n) < Math.pow(10, -digits)) label = x.toExponential(digits); else label = round_(x * n, digits) + m_labels[j - 1]; return label; } // else fall through } if (absx >= Math.pow(10, maxNumberWidth) || absx < Math.pow(10, -digits)) { // switch to scientific notation if we underflow or overflow fixed display label = x.toExponential(digits); } else { label = '' + round_(x, digits); } return label; } /** * variant for use as an axisLabelFormatter. * @private */ export function numberAxisLabelFormatter(x, granularity, opts) { return numberValueFormatter.call(this, x, opts); } /** * @type {!Array.} * @private * @constant */ var SHORT_MONTH_NAMES_ = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; /** * Convert a JS date to a string appropriate to display on an axis that * is displaying values at the stated granularity. This respects the * labelsUTC option. * @param {Date} date The date to format * @param {number} granularity One of the Dygraph granularity constants * @param {Dygraph} opts An options view * @return {string} The date formatted as local time * @private */ export function dateAxisLabelFormatter(date, granularity, opts) { var utc = opts('labelsUTC'); var accessors = utc ? DateAccessorsUTC : DateAccessorsLocal; var year = accessors.getFullYear(date), month = accessors.getMonth(date), day = accessors.getDate(date), hours = accessors.getHours(date), mins = accessors.getMinutes(date), secs = accessors.getSeconds(date), millis = accessors.getMilliseconds(date); if (granularity >= DygraphTickers.Granularity.DECADAL) { return '' + year; } else if (granularity >= DygraphTickers.Granularity.MONTHLY) { return SHORT_MONTH_NAMES_[month] + ' ' + year; } else { var frac = hours * 3600 + mins * 60 + secs + 1e-3 * millis; if (frac === 0 || granularity >= DygraphTickers.Granularity.DAILY) { // e.g. '21 Jan' (%d%b) return zeropad(day) + ' ' + SHORT_MONTH_NAMES_[month]; } else if (granularity < DygraphTickers.Granularity.SECONDLY) { // e.g. 40.310 (meaning 40 seconds and 310 milliseconds) var str = "" + millis; return zeropad(secs) + "." + ('000'+str).substring(str.length); } else if (granularity > DygraphTickers.Granularity.MINUTELY) { return hmsString_(hours, mins, secs, 0); } else { return hmsString_(hours, mins, secs, millis); } } } /** * Return a string version of a JS date for a value label. This respects the * labelsUTC option. * @param {Date} date The date to be formatted * @param {Dygraph} opts An options view * @private */ export function dateValueFormatter(d, opts) { return dateString_(d, opts('labelsUTC')); } // stuff for simple onDOMready implementation var deferDOM_callbacks = []; var deferDOM_handlerCalled = false; // onDOMready once DOM is ready /** * Simple onDOMready implementation * @param {function()} cb The callback to run once the DOM is ready. * @return {boolean} whether the DOM is currently ready */ function deferDOM_ready(cb) { if (typeof(cb) === "function") cb(); return (true); } /** * Setup a simple onDOMready implementation on the given objct. * @param {*} self the object to update .onDOMready on * @private */ export function setupDOMready_(self) { // only attach if there’s a DOM if (typeof(document) !== "undefined") { // called by browser const handler = function deferDOM_handler() { /* execute only once */ if (deferDOM_handlerCalled) return; deferDOM_handlerCalled = true; /* subsequent calls must not enqueue */ self.onDOMready = deferDOM_ready; /* clear event handlers */ document.removeEventListener("DOMContentLoaded", handler, false); window.removeEventListener("load", handler, false); /* run user callbacks */ for (let i = 0; i < deferDOM_callbacks.length; ++i) deferDOM_callbacks[i](); deferDOM_callbacks = null; //gc }; // make callable (mutating, do not copy) self.onDOMready = function deferDOM_initial(cb) { /* if possible, skip all that */ if (document.readyState === "complete") { self.onDOMready = deferDOM_ready; return (deferDOM_ready(cb)); } // onDOMready, after setup, before DOM is ready const enqfn = function deferDOM_enqueue(cb) { if (typeof(cb) === "function") deferDOM_callbacks.push(cb); return (false); }; /* subsequent calls will enqueue */ self.onDOMready = enqfn; /* set up handler */ document.addEventListener("DOMContentLoaded", handler, false); /* last resort: always works, but later than possible */ window.addEventListener("load", handler, false); /* except if DOM got ready in the meantime */ if (document.readyState === "complete") { /* undo all that attaching */ handler(); /* goto finish */ self.onDOMready = deferDOM_ready; return (deferDOM_ready(cb)); } /* just enqueue that */ return (enqfn(cb)); }; } } dygraphs-2.2.1/src/dygraph.js010064400000000000000000003512311437353256000130640ustar00/** * @license * Copyright 2006 Dan Vanderkam (danvdk@gmail.com) * MIT-licenced: https://opensource.org/licenses/MIT */ /** * @fileoverview Creates an interactive, zoomable graph based on a CSV file or * string. Dygraph can handle multiple series with or without high/low bands. * The date/value ranges will be automatically set. Dygraph uses the * <canvas> tag, so it only works in FF1.5+. * See the source or https://dygraphs.com/ for more information. * @author danvdk@gmail.com (Dan Vanderkam) */ /* Usage:
    The CSV file is of the form Date,SeriesA,SeriesB,SeriesC YYYY-MM-DD,A1,B1,C1 YYYY-MM-DD,A2,B2,C2 If the 'errorBars' option is set in the constructor, the input should be of the form Date,SeriesA,SeriesB,... YYYY-MM-DD,A1,sigmaA1,B1,sigmaB1,... YYYY-MM-DD,A2,sigmaA2,B2,sigmaB2,... If the 'fractions' option is set, the input should be of the form: Date,SeriesA,SeriesB,... YYYY-MM-DD,A1/B1,A2/B2,... YYYY-MM-DD,A1/B1,A2/B2,... And high/low bands will be calculated automatically using a binomial distribution. For further documentation and examples, see http://dygraphs.com/ */ import DygraphLayout from './dygraph-layout'; import DygraphCanvasRenderer from './dygraph-canvas'; import DygraphOptions from './dygraph-options'; import DygraphInteraction from './dygraph-interaction-model'; import * as DygraphTickers from './dygraph-tickers'; import * as utils from './dygraph-utils'; import DEFAULT_ATTRS from './dygraph-default-attrs'; import OPTIONS_REFERENCE from './dygraph-options-reference'; import IFrameTarp from './iframe-tarp'; import DefaultHandler from './datahandler/default'; import ErrorBarsHandler from './datahandler/bars-error'; import CustomBarsHandler from './datahandler/bars-custom'; import DefaultFractionHandler from './datahandler/default-fractions'; import FractionsBarsHandler from './datahandler/bars-fractions'; import BarsHandler from './datahandler/bars'; import AnnotationsPlugin from './plugins/annotations'; import AxesPlugin from './plugins/axes'; import ChartLabelsPlugin from './plugins/chart-labels'; import GridPlugin from './plugins/grid'; import LegendPlugin from './plugins/legend'; import RangeSelectorPlugin from './plugins/range-selector'; import GVizChart from './dygraph-gviz'; "use strict"; /** * @class Creates an interactive, zoomable chart. * @name Dygraph * * @constructor * @param {div | String} div A div or the id of a div into which to construct * the chart. Must not have any padding. * @param {String | Function} file A file containing CSV data or a function * that returns this data. The most basic expected format for each line is * "YYYY/MM/DD,val1,val2,...". For more information, see * http://dygraphs.com/data.html. * @param {Object} attrs Various other attributes, e.g. errorBars determines * whether the input data contains error ranges. For a complete list of * options, see http://dygraphs.com/options.html. */ var Dygraph = function Dygraph(div, data, opts) { this.__init__(div, data, opts); }; Dygraph.NAME = "Dygraph"; Dygraph.VERSION = "2.2.1"; // internal autoloader workaround var _addrequire = {}; Dygraph._require = function require(what) { return (what in _addrequire ? _addrequire[what] : Dygraph._require._b(what)); }; Dygraph._require._b = null; // set by xfrmmodmap-dy.js Dygraph._require.add = function add(what, towhat) { _addrequire[what] = towhat; }; // Various default values Dygraph.DEFAULT_ROLL_PERIOD = 1; Dygraph.DEFAULT_WIDTH = 480; Dygraph.DEFAULT_HEIGHT = 320; // For max 60 Hz. animation: Dygraph.ANIMATION_STEPS = 12; Dygraph.ANIMATION_DURATION = 200; /** * Standard plotters. These may be used by clients. * Available plotters are: * - Dygraph.Plotters.linePlotter: draws central lines (most common) * - Dygraph.Plotters.errorPlotter: draws high/low bands * - Dygraph.Plotters.fillPlotter: draws fills under lines (used with fillGraph) * * By default, the plotter is [fillPlotter, errorPlotter, linePlotter]. * This causes all the lines to be drawn over all the fills/bands. */ Dygraph.Plotters = DygraphCanvasRenderer._Plotters; // Used for initializing annotation CSS rules only once. Dygraph.addedAnnotationCSS = false; /** * Initializes the Dygraph. This creates a new DIV and constructs the PlotKit * and context <canvas> inside of it. See the constructor for details. * on the parameters. * @param {Element} div the Element to render the graph into. * @param {string | Function} file Source data * @param {Object} attrs Miscellaneous other options * @private */ Dygraph.prototype.__init__ = function(div, file, attrs) { this.is_initial_draw_ = true; this.readyFns_ = []; // Support two-argument constructor if (attrs === null || attrs === undefined) { attrs = {}; } attrs = Dygraph.copyUserAttrs_(attrs); if (typeof(div) == 'string') { div = document.getElementById(div); } if (!div) { throw new Error('Constructing dygraph with a non-existent div!'); } // Copy the important bits into the object // TODO(danvk): most of these should just stay in the attrs_ dictionary. this.maindiv_ = div; this.file_ = file; this.rollPeriod_ = attrs.rollPeriod || Dygraph.DEFAULT_ROLL_PERIOD; this.previousVerticalX_ = -1; this.fractions_ = attrs.fractions || false; this.dateWindow_ = attrs.dateWindow || null; this.annotations_ = []; // Clear the div. This ensure that, if multiple dygraphs are passed the same // div, then only one will be drawn. div.innerHTML = ""; const resolved = window.getComputedStyle(div, null); if (resolved.paddingLeft !== "0px" || resolved.paddingRight !== "0px" || resolved.paddingTop !== "0px" || resolved.paddingBottom !== "0px") console.error('Main div contains padding; graph will misbehave'); // For historical reasons, the 'width' and 'height' options trump all CSS // rules _except_ for an explicit 'width' or 'height' on the div. // As an added convenience, if the div has zero height (like
    does // without any styles), then we use a default height/width. if (div.style.width === '' && attrs.width) { div.style.width = attrs.width + "px"; } if (div.style.height === '' && attrs.height) { div.style.height = attrs.height + "px"; } if (div.style.height === '' && div.clientHeight === 0) { div.style.height = Dygraph.DEFAULT_HEIGHT + "px"; if (div.style.width === '') { div.style.width = Dygraph.DEFAULT_WIDTH + "px"; } } // These will be zero if the dygraph's div is hidden. In that case, // use the user-specified attributes if present. If not, use zero // and assume the user will call resize to fix things later. this.width_ = div.clientWidth || attrs.width || 0; this.height_ = div.clientHeight || attrs.height || 0; // TODO(danvk): set fillGraph to be part of attrs_ here, not user_attrs_. if (attrs.stackedGraph) { attrs.fillGraph = true; // TODO(nikhilk): Add any other stackedGraph checks here. } // DEPRECATION WARNING: All option processing should be moved from // attrs_ and user_attrs_ to options_, which holds all this information. // // Dygraphs has many options, some of which interact with one another. // To keep track of everything, we maintain two sets of options: // // this.user_attrs_ only options explicitly set by the user. // this.attrs_ defaults, options derived from user_attrs_, data. // // Options are then accessed this.attr_('attr'), which first looks at // user_attrs_ and then computed attrs_. This way Dygraphs can set intelligent // defaults without overriding behavior that the user specifically asks for. this.user_attrs_ = {}; utils.update(this.user_attrs_, attrs); // This sequence ensures that Dygraph.DEFAULT_ATTRS is never modified. this.attrs_ = {}; utils.updateDeep(this.attrs_, DEFAULT_ATTRS); this.boundaryIds_ = []; this.setIndexByName_ = {}; this.datasetIndex_ = []; this.registeredEvents_ = []; this.eventListeners_ = {}; this.attributes_ = new DygraphOptions(this); // Create the containing DIV and other interactive elements this.createInterface_(); // Activate plugins. this.plugins_ = []; var plugins = Dygraph.PLUGINS.concat(this.getOption('plugins')); for (var i = 0; i < plugins.length; i++) { // the plugins option may contain either plugin classes or instances. // Plugin instances contain an activate method. var Plugin = plugins[i]; // either a constructor or an instance. var pluginInstance; if (typeof(Plugin.activate) !== 'undefined') { pluginInstance = Plugin; } else { pluginInstance = new Plugin(); } var pluginDict = { plugin: pluginInstance, events: {}, options: {}, pluginOptions: {} }; var handlers = pluginInstance.activate(this); for (var eventName in handlers) { if (!handlers.hasOwnProperty(eventName)) continue; // TODO(danvk): validate eventName. pluginDict.events[eventName] = handlers[eventName]; } this.plugins_.push(pluginDict); } // At this point, plugins can no longer register event handlers. // Construct a map from event -> ordered list of [callback, plugin]. for (var i = 0; i < this.plugins_.length; i++) { var plugin_dict = this.plugins_[i]; for (var eventName in plugin_dict.events) { if (!plugin_dict.events.hasOwnProperty(eventName)) continue; var callback = plugin_dict.events[eventName]; var pair = [plugin_dict.plugin, callback]; if (!(eventName in this.eventListeners_)) { this.eventListeners_[eventName] = [pair]; } else { this.eventListeners_[eventName].push(pair); } } } this.createDragInterface_(); this.start_(); }; /** * Triggers a cascade of events to the various plugins which are interested in them. * Returns true if the "default behavior" should be prevented, i.e. if one * of the event listeners called event.preventDefault(). * @private */ Dygraph.prototype.cascadeEvents_ = function(name, extra_props) { if (!(name in this.eventListeners_)) return false; // QUESTION: can we use objects & prototypes to speed this up? var e = { dygraph: this, cancelable: false, defaultPrevented: false, preventDefault: function() { if (!e.cancelable) throw "Cannot call preventDefault on non-cancelable event."; e.defaultPrevented = true; }, propagationStopped: false, stopPropagation: function() { e.propagationStopped = true; } }; utils.update(e, extra_props); var callback_plugin_pairs = this.eventListeners_[name]; if (callback_plugin_pairs) { for (var i = callback_plugin_pairs.length - 1; i >= 0; i--) { var plugin = callback_plugin_pairs[i][0]; var callback = callback_plugin_pairs[i][1]; callback.call(plugin, e); if (e.propagationStopped) break; } } return e.defaultPrevented; }; /** * Fetch a plugin instance of a particular class. Only for testing. * @private * @param {!Class} type The type of the plugin. * @return {Object} Instance of the plugin, or null if there is none. */ Dygraph.prototype.getPluginInstance_ = function(type) { for (var i = 0; i < this.plugins_.length; i++) { var p = this.plugins_[i]; if (p.plugin instanceof type) { return p.plugin; } } return null; }; /** * Returns the zoomed status of the chart for one or both axes. * * Axis is an optional parameter. Can be set to 'x' or 'y'. * * The zoomed status for an axis is set whenever a user zooms using the mouse * or when the dateWindow or valueRange are updated. Double-clicking or calling * resetZoom() resets the zoom status for the chart. */ Dygraph.prototype.isZoomed = function(axis) { const isZoomedX = !!this.dateWindow_; if (axis === 'x') return isZoomedX; const isZoomedY = this.axes_.map(axis => !!axis.valueRange).indexOf(true) >= 0; if (axis === null || axis === undefined) { return isZoomedX || isZoomedY; } if (axis === 'y') return isZoomedY; throw new Error(`axis parameter is [${axis}] must be null, 'x' or 'y'.`); }; /** * Returns information about the Dygraph object, including its containing ID. */ Dygraph.prototype.toString = function() { var maindiv = this.maindiv_; var id = (maindiv && maindiv.id) ? maindiv.id : maindiv; return "[Dygraph " + id + "]"; }; /** * @private * Returns the value of an option. This may be set by the user (either in the * constructor or by calling updateOptions) or by dygraphs, and may be set to a * per-series value. * @param {string} name The name of the option, e.g. 'rollPeriod'. * @param {string} [seriesName] The name of the series to which the option * will be applied. If no per-series value of this option is available, then * the global value is returned. This is optional. * @return {...} The value of the option. */ Dygraph.prototype.attr_ = function(name, seriesName) { if (typeof process !== 'undefined' && process.env.NODE_ENV != 'production') { // For "production" code, this gets removed by uglifyjs. if (typeof(OPTIONS_REFERENCE) === 'undefined') { console.error('Must include options reference JS for testing'); } else if (!OPTIONS_REFERENCE.hasOwnProperty(name)) { console.error('Dygraphs is using property ' + name + ', which has no ' + 'entry in the Dygraphs.OPTIONS_REFERENCE listing.'); // Only log this error once. OPTIONS_REFERENCE[name] = true; } } return seriesName ? this.attributes_.getForSeries(name, seriesName) : this.attributes_.get(name); }; /** * Returns the current value for an option, as set in the constructor or via * updateOptions. You may pass in an (optional) series name to get per-series * values for the option. * * All values returned by this method should be considered immutable. If you * modify them, there is no guarantee that the changes will be honored or that * dygraphs will remain in a consistent state. If you want to modify an option, * use updateOptions() instead. * * @param {string} name The name of the option (e.g. 'strokeWidth') * @param {string=} opt_seriesName Series name to get per-series values. * @return {*} The value of the option. */ Dygraph.prototype.getOption = function(name, opt_seriesName) { return this.attr_(name, opt_seriesName); }; /** * Like getOption(), but specifically returns a number. * This is a convenience function for working with the Closure Compiler. * @param {string} name The name of the option (e.g. 'strokeWidth') * @param {string=} opt_seriesName Series name to get per-series values. * @return {number} The value of the option. * @private */ Dygraph.prototype.getNumericOption = function(name, opt_seriesName) { return /** @type{number} */(this.getOption(name, opt_seriesName)); }; /** * Like getOption(), but specifically returns a string. * This is a convenience function for working with the Closure Compiler. * @param {string} name The name of the option (e.g. 'strokeWidth') * @param {string=} opt_seriesName Series name to get per-series values. * @return {string} The value of the option. * @private */ Dygraph.prototype.getStringOption = function(name, opt_seriesName) { return /** @type{string} */(this.getOption(name, opt_seriesName)); }; /** * Like getOption(), but specifically returns a boolean. * This is a convenience function for working with the Closure Compiler. * @param {string} name The name of the option (e.g. 'strokeWidth') * @param {string=} opt_seriesName Series name to get per-series values. * @return {boolean} The value of the option. * @private */ Dygraph.prototype.getBooleanOption = function(name, opt_seriesName) { return /** @type{boolean} */(this.getOption(name, opt_seriesName)); }; /** * Like getOption(), but specifically returns a function. * This is a convenience function for working with the Closure Compiler. * @param {string} name The name of the option (e.g. 'strokeWidth') * @param {string=} opt_seriesName Series name to get per-series values. * @return {function(...)} The value of the option. * @private */ Dygraph.prototype.getFunctionOption = function(name, opt_seriesName) { return /** @type{function(...)} */(this.getOption(name, opt_seriesName)); }; Dygraph.prototype.getOptionForAxis = function(name, axis) { return this.attributes_.getForAxis(name, axis); }; /** * @private * @param {string} axis The name of the axis (i.e. 'x', 'y' or 'y2') * @return {...} A function mapping string -> option value */ Dygraph.prototype.optionsViewForAxis_ = function(axis) { var self = this; return function(opt) { var axis_opts = self.user_attrs_.axes; if (axis_opts && axis_opts[axis] && axis_opts[axis].hasOwnProperty(opt)) { return axis_opts[axis][opt]; } // I don't like that this is in a second spot. if (axis === 'x' && opt === 'logscale') { // return the default value. // TODO(konigsberg): pull the default from a global default. return false; } // user-specified attributes always trump defaults, even if they're less // specific. if (typeof(self.user_attrs_[opt]) != 'undefined') { return self.user_attrs_[opt]; } axis_opts = self.attrs_.axes; if (axis_opts && axis_opts[axis] && axis_opts[axis].hasOwnProperty(opt)) { return axis_opts[axis][opt]; } // check old-style axis options // TODO(danvk): add a deprecation warning if either of these match. if (axis == 'y' && self.axes_[0].hasOwnProperty(opt)) { return self.axes_[0][opt]; } else if (axis == 'y2' && self.axes_[1].hasOwnProperty(opt)) { return self.axes_[1][opt]; } return self.attr_(opt); }; }; /** * Returns the current rolling period, as set by the user or an option. * @return {number} The number of points in the rolling window */ Dygraph.prototype.rollPeriod = function() { return this.rollPeriod_; }; /** * Returns the currently-visible x-range. This can be affected by zooming, * panning or a call to updateOptions. * Returns a two-element array: [left, right]. * If the Dygraph has dates on the x-axis, these will be millis since epoch. */ Dygraph.prototype.xAxisRange = function() { return this.dateWindow_ ? this.dateWindow_ : this.xAxisExtremes(); }; /** * Returns the lower- and upper-bound x-axis values of the data set. */ Dygraph.prototype.xAxisExtremes = function() { var pad = this.getNumericOption('xRangePad') / this.plotter_.area.w; if (this.numRows() === 0) { return [0 - pad, 1 + pad]; } var left = this.rawData_[0][0]; var right = this.rawData_[this.rawData_.length - 1][0]; if (pad) { // Must keep this in sync with dygraph-layout _evaluateLimits() var range = right - left; left -= range * pad; right += range * pad; } return [left, right]; }; /** * Returns the lower- and upper-bound y-axis values for each axis. These are * the ranges you'll get if you double-click to zoom out or call resetZoom(). * The return value is an array of [low, high] tuples, one for each y-axis. */ Dygraph.prototype.yAxisExtremes = function() { // TODO(danvk): this is pretty inefficient const packed = this.gatherDatasets_(this.rolledSeries_, null); const { extremes } = packed; const saveAxes = this.axes_; this.computeYAxisRanges_(extremes); const newAxes = this.axes_; this.axes_ = saveAxes; return newAxes.map(axis => axis.extremeRange); } /** * Returns the currently-visible y-range for an axis. This can be affected by * zooming, panning or a call to updateOptions. Axis indices are zero-based. If * called with no arguments, returns the range of the first axis. * Returns a two-element array: [bottom, top]. */ Dygraph.prototype.yAxisRange = function(idx) { if (typeof(idx) == "undefined") idx = 0; if (idx < 0 || idx >= this.axes_.length) { return null; } var axis = this.axes_[idx]; return [ axis.computedValueRange[0], axis.computedValueRange[1] ]; }; /** * Returns the currently-visible y-ranges for each axis. This can be affected by * zooming, panning, calls to updateOptions, etc. * Returns an array of [bottom, top] pairs, one for each y-axis. */ Dygraph.prototype.yAxisRanges = function() { var ret = []; for (var i = 0; i < this.axes_.length; i++) { ret.push(this.yAxisRange(i)); } return ret; }; // TODO(danvk): use these functions throughout dygraphs. /** * Convert from data coordinates to canvas/div X/Y coordinates. * If specified, do this conversion for the coordinate system of a particular * axis. Uses the first axis by default. * Returns a two-element array: [X, Y] * * Note: use toDomXCoord instead of toDomCoords(x, null) and use toDomYCoord * instead of toDomCoords(null, y, axis). */ Dygraph.prototype.toDomCoords = function(x, y, axis) { return [ this.toDomXCoord(x), this.toDomYCoord(y, axis) ]; }; /** * Convert from data x coordinates to canvas/div X coordinate. * If specified, do this conversion for the coordinate system of a particular * axis. * Returns a single value or null if x is null. */ Dygraph.prototype.toDomXCoord = function(x) { if (x === null) { return null; } var area = this.plotter_.area; var xRange = this.xAxisRange(); return area.x + (x - xRange[0]) / (xRange[1] - xRange[0]) * area.w; }; /** * Convert from data x coordinates to canvas/div Y coordinate and optional * axis. Uses the first axis by default. * * returns a single value or null if y is null. */ Dygraph.prototype.toDomYCoord = function(y, axis) { var pct = this.toPercentYCoord(y, axis); if (pct === null) { return null; } var area = this.plotter_.area; return area.y + pct * area.h; }; /** * Convert from canvas/div coords to data coordinates. * If specified, do this conversion for the coordinate system of a particular * axis. Uses the first axis by default. * Returns a two-element array: [X, Y]. * * Note: use toDataXCoord instead of toDataCoords(x, null) and use toDataYCoord * instead of toDataCoords(null, y, axis). */ Dygraph.prototype.toDataCoords = function(x, y, axis) { return [ this.toDataXCoord(x), this.toDataYCoord(y, axis) ]; }; /** * Convert from canvas/div x coordinate to data coordinate. * * If x is null, this returns null. */ Dygraph.prototype.toDataXCoord = function(x) { if (x === null) { return null; } var area = this.plotter_.area; var xRange = this.xAxisRange(); if (!this.attributes_.getForAxis("logscale", 'x')) { return xRange[0] + (x - area.x) / area.w * (xRange[1] - xRange[0]); } else { var pct = (x - area.x) / area.w; return utils.logRangeFraction(xRange[0], xRange[1], pct); } }; /** * Convert from canvas/div y coord to value. * * If y is null, this returns null. * if axis is null, this uses the first axis. */ Dygraph.prototype.toDataYCoord = function(y, axis) { if (y === null) { return null; } var area = this.plotter_.area; var yRange = this.yAxisRange(axis); if (typeof(axis) == "undefined") axis = 0; if (!this.attributes_.getForAxis("logscale", axis)) { return yRange[0] + (area.y + area.h - y) / area.h * (yRange[1] - yRange[0]); } else { // Computing the inverse of toDomCoord. var pct = (y - area.y) / area.h; // Note reversed yRange, y1 is on top with pct==0. return utils.logRangeFraction(yRange[1], yRange[0], pct); } }; /** * Converts a y for an axis to a percentage from the top to the * bottom of the drawing area. * * If the coordinate represents a value visible on the canvas, then * the value will be between 0 and 1, where 0 is the top of the canvas. * However, this method will return values outside the range, as * values can fall outside the canvas. * * If y is null, this returns null. * if axis is null, this uses the first axis. * * @param {number} y The data y-coordinate. * @param {number} [axis] The axis number on which the data coordinate lives. * @return {number} A fraction in [0, 1] where 0 = the top edge. */ Dygraph.prototype.toPercentYCoord = function(y, axis) { if (y === null) { return null; } if (typeof(axis) == "undefined") axis = 0; var yRange = this.yAxisRange(axis); var pct; var logscale = this.attributes_.getForAxis("logscale", axis); if (logscale) { var logr0 = utils.log10(yRange[0]); var logr1 = utils.log10(yRange[1]); pct = (logr1 - utils.log10(y)) / (logr1 - logr0); } else { // yRange[1] - y is unit distance from the bottom. // yRange[1] - yRange[0] is the scale of the range. // (yRange[1] - y) / (yRange[1] - yRange[0]) is the % from the bottom. pct = (yRange[1] - y) / (yRange[1] - yRange[0]); } return pct; }; /** * Converts an x value to a percentage from the left to the right of * the drawing area. * * If the coordinate represents a value visible on the canvas, then * the value will be between 0 and 1, where 0 is the left of the canvas. * However, this method will return values outside the range, as * values can fall outside the canvas. * * If x is null, this returns null. * @param {number} x The data x-coordinate. * @return {number} A fraction in [0, 1] where 0 = the left edge. */ Dygraph.prototype.toPercentXCoord = function(x) { if (x === null) { return null; } var xRange = this.xAxisRange(); var pct; var logscale = this.attributes_.getForAxis("logscale", 'x') ; if (logscale === true) { // logscale can be null so we test for true explicitly. var logr0 = utils.log10(xRange[0]); var logr1 = utils.log10(xRange[1]); pct = (utils.log10(x) - logr0) / (logr1 - logr0); } else { // x - xRange[0] is unit distance from the left. // xRange[1] - xRange[0] is the scale of the range. // The full expression below is the % from the left. pct = (x - xRange[0]) / (xRange[1] - xRange[0]); } return pct; }; /** * Returns the number of columns (including the independent variable). * @return {number} The number of columns. */ Dygraph.prototype.numColumns = function() { if (!this.rawData_) return 0; return this.rawData_[0] ? this.rawData_[0].length : this.attr_("labels").length; }; /** * Returns the number of rows (excluding any header/label row). * @return {number} The number of rows, less any header. */ Dygraph.prototype.numRows = function() { if (!this.rawData_) return 0; return this.rawData_.length; }; /** * Returns the value in the given row and column. If the row and column exceed * the bounds on the data, returns null. Also returns null if the value is * missing. * @param {number} row The row number of the data (0-based). Row 0 is the * first row of data, not a header row. * @param {number} col The column number of the data (0-based) * @return {number} The value in the specified cell or null if the row/col * were out of range. */ Dygraph.prototype.getValue = function(row, col) { if (row < 0 || row >= this.rawData_.length) return null; if (col < 0 || col >= this.rawData_[row].length) return null; return this.rawData_[row][col]; }; /** * Generates interface elements for the Dygraph: a containing div, a div to * display the current point, and a textbox to adjust the rolling average * period. Also creates the Renderer/Layout elements. * @private */ Dygraph.prototype.createInterface_ = function() { // Create the all-enclosing graph div var enclosing = this.maindiv_; this.graphDiv = document.createElement("div"); // TODO(danvk): any other styles that are useful to set here? this.graphDiv.style.textAlign = 'left'; // This is a CSS "reset" this.graphDiv.style.position = 'relative'; enclosing.appendChild(this.graphDiv); // Create the canvas for interactive parts of the chart. this.canvas_ = utils.createCanvas(); this.canvas_.style.position = "absolute"; this.canvas_.style.top = 0; this.canvas_.style.left = 0; // ... and for static parts of the chart. this.hidden_ = this.createPlotKitCanvas_(this.canvas_); this.canvas_ctx_ = utils.getContext(this.canvas_); this.hidden_ctx_ = utils.getContext(this.hidden_); this.resizeElements_(); // The interactive parts of the graph are drawn on top of the chart. this.graphDiv.appendChild(this.hidden_); this.graphDiv.appendChild(this.canvas_); this.mouseEventElement_ = this.createMouseEventElement_(); // Create the grapher this.layout_ = new DygraphLayout(this); var dygraph = this; this.mouseMoveHandler_ = function(e) { dygraph.mouseMove_(e); }; this.mouseOutHandler_ = function(e) { // The mouse has left the chart if: // 1. e.target is inside the chart // 2. e.relatedTarget is outside the chart var target = e.target || e.fromElement; var relatedTarget = e.relatedTarget || e.toElement; if (utils.isNodeContainedBy(target, dygraph.graphDiv) && !utils.isNodeContainedBy(relatedTarget, dygraph.graphDiv)) { dygraph.mouseOut_(e); } }; this.addAndTrackEvent(window, 'mouseout', this.mouseOutHandler_); this.addAndTrackEvent(this.mouseEventElement_, 'mousemove', this.mouseMoveHandler_); // Don't recreate and register the resize handler on subsequent calls. // This happens when the graph is resized. if (!this.resizeHandler_) { this.resizeHandler_ = function(e) { dygraph.resize(); }; // Update when the window is resized. // TODO(danvk): drop frames depending on complexity of the chart. this.addAndTrackEvent(window, 'resize', this.resizeHandler_); this.resizeObserver_ = null; var resizeMode = this.getStringOption('resizable'); if ((typeof(ResizeObserver) === 'undefined') && (resizeMode !== "no")) { console.error('ResizeObserver unavailable; ignoring resizable property'); resizeMode = "no"; } if (resizeMode === "horizontal" || resizeMode === "vertical" || resizeMode === "both") { enclosing.style.resize = resizeMode; } else if (resizeMode !== "passive") { resizeMode = "no"; } if (resizeMode !== "no") { const maindivOverflow = window.getComputedStyle(enclosing).overflow; if (window.getComputedStyle(enclosing).overflow === 'visible') enclosing.style.overflow = 'hidden'; this.resizeObserver_ = new ResizeObserver(this.resizeHandler_); this.resizeObserver_.observe(enclosing); } } }; Dygraph.prototype.resizeElements_ = function() { this.graphDiv.style.width = this.width_ + "px"; this.graphDiv.style.height = this.height_ + "px"; var pixelRatioOption = this.getNumericOption('pixelRatio') var canvasScale = pixelRatioOption || utils.getContextPixelRatio(this.canvas_ctx_); this.canvas_.width = this.width_ * canvasScale; this.canvas_.height = this.height_ * canvasScale; this.canvas_.style.width = this.width_ + "px"; // for IE this.canvas_.style.height = this.height_ + "px"; // for IE if (canvasScale !== 1) { this.canvas_ctx_.scale(canvasScale, canvasScale); } var hiddenScale = pixelRatioOption || utils.getContextPixelRatio(this.hidden_ctx_); this.hidden_.width = this.width_ * hiddenScale; this.hidden_.height = this.height_ * hiddenScale; this.hidden_.style.width = this.width_ + "px"; // for IE this.hidden_.style.height = this.height_ + "px"; // for IE if (hiddenScale !== 1) { this.hidden_ctx_.scale(hiddenScale, hiddenScale); } }; /** * Detach DOM elements in the dygraph and null out all data references. * Calling this when you're done with a dygraph can dramatically reduce memory * usage. See, e.g., the tests/perf.html example. */ Dygraph.prototype.destroy = function() { this.canvas_ctx_.restore(); this.hidden_ctx_.restore(); // Destroy any plugins, in the reverse order that they were registered. for (var i = this.plugins_.length - 1; i >= 0; i--) { var p = this.plugins_.pop(); if (p.plugin.destroy) p.plugin.destroy(); } var removeRecursive = function(node) { while (node.hasChildNodes()) { removeRecursive(node.firstChild); node.removeChild(node.firstChild); } }; this.removeTrackedEvents_(); // remove mouse event handlers (This may not be necessary anymore) utils.removeEvent(window, 'mouseout', this.mouseOutHandler_); utils.removeEvent(this.mouseEventElement_, 'mousemove', this.mouseMoveHandler_); // dispose of resizing handlers if (this.resizeObserver_) { this.resizeObserver_.disconnect(); this.resizeObserver_ = null; } utils.removeEvent(window, 'resize', this.resizeHandler_); this.resizeHandler_ = null; removeRecursive(this.maindiv_); var nullOut = function nullOut(obj) { for (var n in obj) { if (typeof(obj[n]) === 'object') { obj[n] = null; } } }; // These may not all be necessary, but it can't hurt... nullOut(this.layout_); nullOut(this.plotter_); nullOut(this); }; /** * Creates the canvas on which the chart will be drawn. Only the Renderer ever * draws on this particular canvas. All Dygraph work (i.e. drawing hover dots * or the zoom rectangles) is done on this.canvas_. * @param {Object} canvas The Dygraph canvas over which to overlay the plot * @return {Object} The newly-created canvas * @private */ Dygraph.prototype.createPlotKitCanvas_ = function(canvas) { var h = utils.createCanvas(); h.style.position = "absolute"; // TODO(danvk): h should be offset from canvas. canvas needs to include // some extra area to make it easier to zoom in on the far left and far // right. h needs to be precisely the plot area, so that clipping occurs. h.style.top = canvas.style.top; h.style.left = canvas.style.left; h.width = this.width_; h.height = this.height_; h.style.width = this.width_ + "px"; // for IE h.style.height = this.height_ + "px"; // for IE return h; }; /** * Creates an overlay element used to handle mouse events. * @return {Object} The mouse event element. * @private */ Dygraph.prototype.createMouseEventElement_ = function() { return this.canvas_; }; /** * Generate a set of distinct colors for the data series. This is done with a * color wheel. Saturation/Value are customizable, and the hue is * equally-spaced around the color wheel. If a custom set of colors is * specified, that is used instead. * @private */ Dygraph.prototype.setColors_ = function() { var labels = this.getLabels(); var num = labels.length - 1; this.colors_ = []; this.colorsMap_ = {}; // These are used for when no custom colors are specified. var sat = this.getNumericOption('colorSaturation') || 1.0; var val = this.getNumericOption('colorValue') || 0.5; var half = Math.ceil(num / 2); var colors = this.getOption('colors'); var visibility = this.visibility(); for (var i = 0; i < num; i++) { if (!visibility[i]) { continue; } var label = labels[i + 1]; var colorStr = this.attributes_.getForSeries('color', label); if (!colorStr) { if (colors) { colorStr = colors[i % colors.length]; } else { // alternate colors for high contrast. var idx = i % 2 ? (half + (i + 1)/ 2) : Math.ceil((i + 1) / 2); var hue = (1.0 * idx / (1 + num)); colorStr = utils.hsvToRGB(hue, sat, val); } } this.colors_.push(colorStr); this.colorsMap_[label] = colorStr; } }; /** * Return the list of colors. This is either the list of colors passed in the * attributes or the autogenerated list of rgb(r,g,b) strings. * This does not return colors for invisible series. * @return {Array.} The list of colors. */ Dygraph.prototype.getColors = function() { return this.colors_; }; /** * Returns a few attributes of a series, i.e. its color, its visibility, which * axis it's assigned to, and its column in the original data. * Returns null if the series does not exist. * Otherwise, returns an object with column, visibility, color and axis properties. * The "axis" property will be set to 1 for y1 and 2 for y2. * The "column" property can be fed back into getValue(row, column) to get * values for this series. */ Dygraph.prototype.getPropertiesForSeries = function(series_name) { var idx = -1; var labels = this.getLabels(); for (var i = 1; i < labels.length; i++) { if (labels[i] == series_name) { idx = i; break; } } if (idx == -1) return null; return { name: series_name, column: idx, visible: this.visibility()[idx - 1], color: this.colorsMap_[series_name], axis: 1 + this.attributes_.axisForSeries(series_name) }; }; /** * Create the text box to adjust the averaging period * @private */ Dygraph.prototype.createRollInterface_ = function() { // Create a roller if one doesn't exist already. var roller = this.roller_; if (!roller) { this.roller_ = roller = document.createElement("input"); roller.type = "text"; roller.style.display = "none"; roller.className = 'dygraph-roller'; this.graphDiv.appendChild(roller); } var display = this.getBooleanOption('showRoller') ? 'block' : 'none'; var area = this.getArea(); var textAttr = { "top": (area.y + area.h - 25) + "px", "left": (area.x + 1) + "px", "display": display }; roller.size = "2"; roller.value = this.rollPeriod_; utils.update(roller.style, textAttr); const that = this; roller.onchange = function onchange() { return that.adjustRoll(roller.value); }; }; /** * Set up all the mouse handlers needed to capture dragging behavior for zoom * events. * @private */ Dygraph.prototype.createDragInterface_ = function() { var context = { // Tracks whether the mouse is down right now isZooming: false, isPanning: false, // is this drag part of a pan? is2DPan: false, // if so, is that pan 1- or 2-dimensional? dragStartX: null, // pixel coordinates dragStartY: null, // pixel coordinates dragEndX: null, // pixel coordinates dragEndY: null, // pixel coordinates dragDirection: null, prevEndX: null, // pixel coordinates prevEndY: null, // pixel coordinates prevDragDirection: null, cancelNextDblclick: false, // see comment in dygraph-interaction-model.js // The value on the left side of the graph when a pan operation starts. initialLeftmostDate: null, // The number of units each pixel spans. (This won't be valid for log // scales) xUnitsPerPixel: null, // TODO(danvk): update this comment // The range in second/value units that the viewport encompasses during a // panning operation. dateRange: null, // Top-left corner of the canvas, in DOM coords // TODO(konigsberg): Rename topLeftCanvasX, topLeftCanvasY. px: 0, py: 0, // Values for use with panEdgeFraction, which limit how far outside the // graph's data boundaries it can be panned. boundedDates: null, // [minDate, maxDate] boundedValues: null, // [[minValue, maxValue] ...] // We cover iframes during mouse interactions. See comments in // dygraph-utils.js for more info on why this is a good idea. tarp: new IFrameTarp(), // contextB is the same thing as this context object but renamed. initializeMouseDown: function(event, g, contextB) { // prevents mouse drags from selecting page text. if (event.preventDefault) { event.preventDefault(); // Firefox, Chrome, etc. } else { event.returnValue = false; // IE event.cancelBubble = true; } var canvasPos = utils.findPos(g.canvas_); contextB.px = canvasPos.x; contextB.py = canvasPos.y; contextB.dragStartX = utils.dragGetX_(event, contextB); contextB.dragStartY = utils.dragGetY_(event, contextB); contextB.cancelNextDblclick = false; contextB.tarp.cover(); }, destroy: function() { var context = this; if (context.isZooming || context.isPanning) { context.isZooming = false; context.dragStartX = null; context.dragStartY = null; } if (context.isPanning) { context.isPanning = false; context.draggingDate = null; context.dateRange = null; for (var i = 0; i < self.axes_.length; i++) { delete self.axes_[i].draggingValue; delete self.axes_[i].dragValueRange; } } context.tarp.uncover(); } }; var interactionModel = this.getOption("interactionModel"); // Self is the graph. var self = this; // Function that binds the graph and context to the handler. var bindHandler = function(handler) { return function(event) { handler(event, self, context); }; }; for (var eventName in interactionModel) { if (!interactionModel.hasOwnProperty(eventName)) continue; this.addAndTrackEvent(this.mouseEventElement_, eventName, bindHandler(interactionModel[eventName])); } // If the user releases the mouse button during a drag, but not over the // canvas, then it doesn't count as a zooming action. if (!interactionModel.willDestroyContextMyself) { var mouseUpHandler = function(event) { context.destroy(); }; this.addAndTrackEvent(document, 'mouseup', mouseUpHandler); } }; /** * Draw a gray zoom rectangle over the desired area of the canvas. Also clears * up any previous zoom rectangles that were drawn. This could be optimized to * avoid extra redrawing, but it's tricky to avoid interactions with the status * dots. * * @param {number} direction the direction of the zoom rectangle. Acceptable * values are utils.HORIZONTAL and utils.VERTICAL. * @param {number} startX The X position where the drag started, in canvas * coordinates. * @param {number} endX The current X position of the drag, in canvas coords. * @param {number} startY The Y position where the drag started, in canvas * coordinates. * @param {number} endY The current Y position of the drag, in canvas coords. * @param {number} prevDirection the value of direction on the previous call to * this function. Used to avoid excess redrawing * @param {number} prevEndX The value of endX on the previous call to this * function. Used to avoid excess redrawing * @param {number} prevEndY The value of endY on the previous call to this * function. Used to avoid excess redrawing * @private */ Dygraph.prototype.drawZoomRect_ = function(direction, startX, endX, startY, endY, prevDirection, prevEndX, prevEndY) { var ctx = this.canvas_ctx_; // Clean up from the previous rect if necessary if (prevDirection == utils.HORIZONTAL) { ctx.clearRect(Math.min(startX, prevEndX), this.layout_.getPlotArea().y, Math.abs(startX - prevEndX), this.layout_.getPlotArea().h); } else if (prevDirection == utils.VERTICAL) { ctx.clearRect(this.layout_.getPlotArea().x, Math.min(startY, prevEndY), this.layout_.getPlotArea().w, Math.abs(startY - prevEndY)); } // Draw a light-grey rectangle to show the new viewing area if (direction == utils.HORIZONTAL) { if (endX && startX) { ctx.fillStyle = "rgba(128,128,128,0.33)"; ctx.fillRect(Math.min(startX, endX), this.layout_.getPlotArea().y, Math.abs(endX - startX), this.layout_.getPlotArea().h); } } else if (direction == utils.VERTICAL) { if (endY && startY) { ctx.fillStyle = "rgba(128,128,128,0.33)"; ctx.fillRect(this.layout_.getPlotArea().x, Math.min(startY, endY), this.layout_.getPlotArea().w, Math.abs(endY - startY)); } } }; /** * Clear the zoom rectangle (and perform no zoom). * @private */ Dygraph.prototype.clearZoomRect_ = function() { this.currentZoomRectArgs_ = null; this.canvas_ctx_.clearRect(0, 0, this.width_, this.height_); }; /** * Zoom to something containing [lowX, highX]. These are pixel coordinates in * the canvas. The exact zoom window may be slightly larger if there are no data * points near lowX or highX. Don't confuse this function with doZoomXDates, * which accepts dates that match the raw data. This function redraws the graph. * * @param {number} lowX The leftmost pixel value that should be visible. * @param {number} highX The rightmost pixel value that should be visible. * @private */ Dygraph.prototype.doZoomX_ = function(lowX, highX) { this.currentZoomRectArgs_ = null; // Find the earliest and latest dates contained in this canvasx range. // Convert the call to date ranges of the raw data. var minDate = this.toDataXCoord(lowX); var maxDate = this.toDataXCoord(highX); this.doZoomXDates_(minDate, maxDate); }; /** * Zoom to something containing [minDate, maxDate] values. Don't confuse this * method with doZoomX which accepts pixel coordinates. This function redraws * the graph. * * @param {number} minDate The minimum date that should be visible. * @param {number} maxDate The maximum date that should be visible. * @private */ Dygraph.prototype.doZoomXDates_ = function(minDate, maxDate) { // TODO(danvk): when xAxisRange is null (i.e. "fit to data", the animation // can produce strange effects. Rather than the x-axis transitioning slowly // between values, it can jerk around.) var old_window = this.xAxisRange(); var new_window = [minDate, maxDate]; const zoomCallback = this.getFunctionOption('zoomCallback'); const that = this; this.doAnimatedZoom(old_window, new_window, null, null, function animatedZoomCallback() { if (zoomCallback) { zoomCallback.call(that, minDate, maxDate, that.yAxisRanges()); } }); }; /** * Zoom to something containing [lowY, highY]. These are pixel coordinates in * the canvas. This function redraws the graph. * * @param {number} lowY The topmost pixel value that should be visible. * @param {number} highY The lowest pixel value that should be visible. * @private */ Dygraph.prototype.doZoomY_ = function(lowY, highY) { this.currentZoomRectArgs_ = null; // Find the highest and lowest values in pixel range for each axis. // Note that lowY (in pixels) corresponds to the max Value (in data coords). // This is because pixels increase as you go down on the screen, whereas data // coordinates increase as you go up the screen. var oldValueRanges = this.yAxisRanges(); var newValueRanges = []; for (var i = 0; i < this.axes_.length; i++) { var hi = this.toDataYCoord(lowY, i); var low = this.toDataYCoord(highY, i); newValueRanges.push([low, hi]); } const zoomCallback = this.getFunctionOption('zoomCallback'); const that = this; this.doAnimatedZoom(null, null, oldValueRanges, newValueRanges, function animatedZoomCallback() { if (zoomCallback) { const [minX, maxX] = that.xAxisRange(); zoomCallback.call(that, minX, maxX, that.yAxisRanges()); } }); }; /** * Transition function to use in animations. Returns values between 0.0 * (totally old values) and 1.0 (totally new values) for each frame. * @private */ Dygraph.zoomAnimationFunction = function(frame, numFrames) { var k = 1.5; return (1.0 - Math.pow(k, -frame)) / (1.0 - Math.pow(k, -numFrames)); }; /** * Reset the zoom to the original view coordinates. This is the same as * double-clicking on the graph. */ Dygraph.prototype.resetZoom = function() { const dirtyX = this.isZoomed('x'); const dirtyY = this.isZoomed('y'); const dirty = dirtyX || dirtyY; // Clear any selection, since it's likely to be drawn in the wrong place. this.clearSelection(); if (!dirty) return; // Calculate extremes to avoid lack of padding on reset. const [minDate, maxDate] = this.xAxisExtremes(); const animatedZooms = this.getBooleanOption('animatedZooms'); const zoomCallback = this.getFunctionOption('zoomCallback'); // TODO(danvk): merge this block w/ the code below. // TODO(danvk): factor out a generic, public zoomTo method. if (!animatedZooms) { this.dateWindow_ = null; this.axes_.forEach(axis => { if (axis.valueRange) delete axis.valueRange; }); this.drawGraph_(); if (zoomCallback) { zoomCallback.call(this, minDate, maxDate, this.yAxisRanges()); } return; } var oldWindow=null, newWindow=null, oldValueRanges=null, newValueRanges=null; if (dirtyX) { oldWindow = this.xAxisRange(); newWindow = [minDate, maxDate]; } if (dirtyY) { oldValueRanges = this.yAxisRanges(); newValueRanges = this.yAxisExtremes(); } const that = this; this.doAnimatedZoom(oldWindow, newWindow, oldValueRanges, newValueRanges, function animatedZoomCallback() { that.dateWindow_ = null; that.axes_.forEach(axis => { if (axis.valueRange) delete axis.valueRange; }); if (zoomCallback) { zoomCallback.call(that, minDate, maxDate, that.yAxisRanges()); } }); }; /** * Combined animation logic for all zoom functions. * either the x parameters or y parameters may be null. * @private */ Dygraph.prototype.doAnimatedZoom = function(oldXRange, newXRange, oldYRanges, newYRanges, callback) { var steps = this.getBooleanOption("animatedZooms") ? Dygraph.ANIMATION_STEPS : 1; var windows = []; var valueRanges = []; var step, frac; if (oldXRange !== null && newXRange !== null) { for (step = 1; step <= steps; step++) { frac = Dygraph.zoomAnimationFunction(step, steps); windows[step-1] = [oldXRange[0]*(1-frac) + frac*newXRange[0], oldXRange[1]*(1-frac) + frac*newXRange[1]]; } } if (oldYRanges !== null && newYRanges !== null) { for (step = 1; step <= steps; step++) { frac = Dygraph.zoomAnimationFunction(step, steps); var thisRange = []; for (var j = 0; j < this.axes_.length; j++) { thisRange.push([oldYRanges[j][0]*(1-frac) + frac*newYRanges[j][0], oldYRanges[j][1]*(1-frac) + frac*newYRanges[j][1]]); } valueRanges[step-1] = thisRange; } } const that = this; utils.repeatAndCleanup(function (step) { if (valueRanges.length) { for (var i = 0; i < that.axes_.length; i++) { var w = valueRanges[step][i]; that.axes_[i].valueRange = [w[0], w[1]]; } } if (windows.length) { that.dateWindow_ = windows[step]; } that.drawGraph_(); }, steps, Dygraph.ANIMATION_DURATION / steps, callback); }; /** * Get the current graph's area object. * * Returns: {x, y, w, h} */ Dygraph.prototype.getArea = function() { return this.plotter_.area; }; /** * Convert a mouse event to DOM coordinates relative to the graph origin. * * Returns a two-element array: [X, Y]. */ Dygraph.prototype.eventToDomCoords = function(event) { if (event.offsetX && event.offsetY) { return [ event.offsetX, event.offsetY ]; } else { var eventElementPos = utils.findPos(this.mouseEventElement_); var canvasx = utils.pageX(event) - eventElementPos.x; var canvasy = utils.pageY(event) - eventElementPos.y; return [canvasx, canvasy]; } }; /** * Given a canvas X coordinate, find the closest row. * @param {number} domX graph-relative DOM X coordinate * Returns {number} row number. * @private */ Dygraph.prototype.findClosestRow = function(domX) { var minDistX = Infinity; var closestRow = -1; var sets = this.layout_.points; for (var i = 0; i < sets.length; i++) { var points = sets[i]; var len = points.length; for (var j = 0; j < len; j++) { var point = points[j]; if (!utils.isValidPoint(point, true)) continue; var dist = Math.abs(point.canvasx - domX); if (dist < minDistX) { minDistX = dist; closestRow = point.idx; } } } return closestRow; }; /** * Given canvas X,Y coordinates, find the closest point. * * This finds the individual data point across all visible series * that's closest to the supplied DOM coordinates using the standard * Euclidean X,Y distance. * * @param {number} domX graph-relative DOM X coordinate * @param {number} domY graph-relative DOM Y coordinate * Returns: {row, seriesName, point} * @private */ Dygraph.prototype.findClosestPoint = function(domX, domY) { var minDist = Infinity; var dist, dx, dy, point, closestPoint, closestSeries, closestRow; for ( var setIdx = this.layout_.points.length - 1 ; setIdx >= 0 ; --setIdx ) { var points = this.layout_.points[setIdx]; for (var i = 0; i < points.length; ++i) { point = points[i]; if (!utils.isValidPoint(point)) continue; dx = point.canvasx - domX; dy = point.canvasy - domY; dist = dx * dx + dy * dy; if (dist < minDist) { minDist = dist; closestPoint = point; closestSeries = setIdx; closestRow = point.idx; } } } var name = this.layout_.setNames[closestSeries]; return { row: closestRow, seriesName: name, point: closestPoint }; }; /** * Given canvas X,Y coordinates, find the touched area in a stacked graph. * * This first finds the X data point closest to the supplied DOM X coordinate, * then finds the series which puts the Y coordinate on top of its filled area, * using linear interpolation between adjacent point pairs. * * @param {number} domX graph-relative DOM X coordinate * @param {number} domY graph-relative DOM Y coordinate * Returns: {row, seriesName, point} * @private */ Dygraph.prototype.findStackedPoint = function(domX, domY) { var row = this.findClosestRow(domX); var closestPoint, closestSeries; for (var setIdx = 0; setIdx < this.layout_.points.length; ++setIdx) { var boundary = this.getLeftBoundary_(setIdx); var rowIdx = row - boundary; var points = this.layout_.points[setIdx]; if (rowIdx >= points.length) continue; var p1 = points[rowIdx]; if (!utils.isValidPoint(p1)) continue; var py = p1.canvasy; if (domX > p1.canvasx && rowIdx + 1 < points.length) { // interpolate series Y value using next point var p2 = points[rowIdx + 1]; if (utils.isValidPoint(p2)) { var dx = p2.canvasx - p1.canvasx; if (dx > 0) { var r = (domX - p1.canvasx) / dx; py += r * (p2.canvasy - p1.canvasy); } } } else if (domX < p1.canvasx && rowIdx > 0) { // interpolate series Y value using previous point var p0 = points[rowIdx - 1]; if (utils.isValidPoint(p0)) { var dx = p1.canvasx - p0.canvasx; if (dx > 0) { var r = (p1.canvasx - domX) / dx; py += r * (p0.canvasy - p1.canvasy); } } } // Stop if the point (domX, py) is above this series' upper edge if (setIdx === 0 || py < domY) { closestPoint = p1; closestSeries = setIdx; } } var name = this.layout_.setNames[closestSeries]; return { row: row, seriesName: name, point: closestPoint }; }; /** * When the mouse moves in the canvas, display information about a nearby data * point and draw dots over those points in the data series. This function * takes care of cleanup of previously-drawn dots. * @param {Object} event The mousemove event from the browser. * @private */ Dygraph.prototype.mouseMove_ = function(event) { // This prevents JS errors when mousing over the canvas before data loads. var points = this.layout_.points; if (points === undefined || points === null) return; var canvasCoords = this.eventToDomCoords(event); var canvasx = canvasCoords[0]; var canvasy = canvasCoords[1]; var highlightSeriesOpts = this.getOption("highlightSeriesOpts"); var selectionChanged = false; if (highlightSeriesOpts && !this.isSeriesLocked()) { var closest; if (this.getBooleanOption("stackedGraph")) { closest = this.findStackedPoint(canvasx, canvasy); } else { closest = this.findClosestPoint(canvasx, canvasy); } selectionChanged = this.setSelection(closest.row, closest.seriesName); } else { var idx = this.findClosestRow(canvasx); selectionChanged = this.setSelection(idx); } var callback = this.getFunctionOption("highlightCallback"); if (callback && selectionChanged) { callback.call(this, event, this.lastx_, this.selPoints_, this.lastRow_, this.highlightSet_); } }; /** * Fetch left offset from the specified set index or if not passed, the * first defined boundaryIds record (see bug #236). * @private */ Dygraph.prototype.getLeftBoundary_ = function(setIdx) { if (this.boundaryIds_[setIdx]) { return this.boundaryIds_[setIdx][0]; } else { for (var i = 0; i < this.boundaryIds_.length; i++) { if (this.boundaryIds_[i] !== undefined) { return this.boundaryIds_[i][0]; } } return 0; } }; Dygraph.prototype.animateSelection_ = function(direction) { var totalSteps = 10; var millis = 30; if (this.fadeLevel === undefined) this.fadeLevel = 0; if (this.animateId === undefined) this.animateId = 0; var start = this.fadeLevel; var steps = direction < 0 ? start : totalSteps - start; if (steps <= 0) { if (this.fadeLevel) { this.updateSelection_(1.0); } return; } var thisId = ++this.animateId; var that = this; var cleanupIfClearing = function() { // if we haven't reached fadeLevel 0 in the max frame time, // ensure that the clear happens and just go to 0 if (that.fadeLevel !== 0 && direction < 0) { that.fadeLevel = 0; that.clearSelection(); } }; utils.repeatAndCleanup( function(n) { // ignore simultaneous animations if (that.animateId != thisId) return; that.fadeLevel += direction; if (that.fadeLevel === 0) { that.clearSelection(); } else { that.updateSelection_(that.fadeLevel / totalSteps); } }, steps, millis, cleanupIfClearing); }; /** * Draw dots over the selectied points in the data series. This function * takes care of cleanup of previously-drawn dots. * @private */ Dygraph.prototype.updateSelection_ = function(opt_animFraction) { /*var defaultPrevented = */ this.cascadeEvents_('select', { selectedRow: this.lastRow_ === -1 ? undefined : this.lastRow_, selectedX: this.lastx_ === null ? undefined : this.lastx_, selectedPoints: this.selPoints_ }); // TODO(danvk): use defaultPrevented here? // Clear the previously drawn vertical, if there is one var i; var ctx = this.canvas_ctx_; if (this.getOption('highlightSeriesOpts')) { ctx.clearRect(0, 0, this.width_, this.height_); var alpha = 1.0 - this.getNumericOption('highlightSeriesBackgroundAlpha'); var backgroundColor = utils.toRGB_(this.getOption('highlightSeriesBackgroundColor')); if (alpha) { // Activating background fade includes an animation effect for a gradual // fade. TODO(klausw): make this independently configurable if it causes // issues? Use a shared preference to control animations? var animateBackgroundFade = this.getBooleanOption('animateBackgroundFade'); if (animateBackgroundFade) { if (opt_animFraction === undefined) { // start a new animation this.animateSelection_(1); return; } alpha *= opt_animFraction; } ctx.fillStyle = 'rgba(' + backgroundColor.r + ',' + backgroundColor.g + ',' + backgroundColor.b + ',' + alpha + ')'; ctx.fillRect(0, 0, this.width_, this.height_); } // Redraw only the highlighted series in the interactive canvas (not the // static plot canvas, which is where series are usually drawn). this.plotter_._renderLineChart(this.highlightSet_, ctx); } else if (this.previousVerticalX_ >= 0) { // Determine the maximum highlight circle size. var maxCircleSize = 0; var labels = this.attr_('labels'); for (i = 1; i < labels.length; i++) { var r = this.getNumericOption('highlightCircleSize', labels[i]); if (r > maxCircleSize) maxCircleSize = r; } var px = this.previousVerticalX_; ctx.clearRect(px - maxCircleSize - 1, 0, 2 * maxCircleSize + 2, this.height_); } if (this.selPoints_.length > 0) { // Draw colored circles over the center of each selected point var canvasx = this.selPoints_[0].canvasx; ctx.save(); for (i = 0; i < this.selPoints_.length; i++) { var pt = this.selPoints_[i]; if (isNaN(pt.canvasy)) continue; var circleSize = this.getNumericOption('highlightCircleSize', pt.name); var callback = this.getFunctionOption("drawHighlightPointCallback", pt.name); var color = this.plotter_.colors[pt.name]; if (!callback) { callback = utils.Circles.DEFAULT; } ctx.lineWidth = this.getNumericOption('strokeWidth', pt.name); ctx.strokeStyle = color; ctx.fillStyle = color; callback.call(this, this, pt.name, ctx, canvasx, pt.canvasy, color, circleSize, pt.idx); } ctx.restore(); this.previousVerticalX_ = canvasx; } }; /** * Manually set the selected points and display information about them in the * legend. The selection can be cleared using clearSelection() and queried * using getSelection(). * * To set a selected series but not a selected point, call setSelection with * row=false and the selected series name. * * @param {number} row Row number that should be highlighted (i.e. appear with * hover dots on the chart). * @param {seriesName} optional series name to highlight that series with the * the highlightSeriesOpts setting. * @param {locked} optional If true, keep seriesName selected when mousing * over the graph, disabling closest-series highlighting. Call clearSelection() * to unlock it. * @param {trigger_highlight_callback} optional If true, trigger any * user-defined highlightCallback if highlightCallback has been set. */ Dygraph.prototype.setSelection = function setSelection(row, opt_seriesName, opt_locked, opt_trigger_highlight_callback) { // Extract the points we've selected this.selPoints_ = []; var changed = false; if (row !== false && row >= 0) { if (row != this.lastRow_) changed = true; this.lastRow_ = row; for (var setIdx = 0; setIdx < this.layout_.points.length; ++setIdx) { var points = this.layout_.points[setIdx]; // Check if the point at the appropriate index is the point we're looking // for. If it is, just use it, otherwise search the array for a point // in the proper place. var setRow = row - this.getLeftBoundary_(setIdx); if (setRow >= 0 && setRow < points.length && points[setRow].idx == row) { var point = points[setRow]; if (point.yval !== null) this.selPoints_.push(point); } else { for (var pointIdx = 0; pointIdx < points.length; ++pointIdx) { var point = points[pointIdx]; if (point.idx == row) { if (point.yval !== null) { this.selPoints_.push(point); } break; } } } } } else { if (this.lastRow_ >= 0) changed = true; this.lastRow_ = -1; } if (this.selPoints_.length) { this.lastx_ = this.selPoints_[0].xval; } else { this.lastx_ = null; } if (opt_seriesName !== undefined) { if (this.highlightSet_ !== opt_seriesName) changed = true; this.highlightSet_ = opt_seriesName; } if (opt_locked !== undefined) { this.lockedSet_ = opt_locked; } if (changed) { this.updateSelection_(undefined); if (opt_trigger_highlight_callback) { var callback = this.getFunctionOption("highlightCallback"); if (callback) { var event = {}; callback.call(this, event, this.lastx_, this.selPoints_, this.lastRow_, this.highlightSet_); } } } return changed; }; /** * The mouse has left the canvas. Clear out whatever artifacts remain * @param {Object} event the mouseout event from the browser. * @private */ Dygraph.prototype.mouseOut_ = function(event) { if (this.getFunctionOption("unhighlightCallback")) { this.getFunctionOption("unhighlightCallback").call(this, event); } if (this.getBooleanOption("hideOverlayOnMouseOut") && !this.lockedSet_) { this.clearSelection(); } }; /** * Clears the current selection (i.e. points that were highlighted by moving * the mouse over the chart). */ Dygraph.prototype.clearSelection = function() { this.cascadeEvents_('deselect', {}); this.lockedSet_ = false; // Get rid of the overlay data if (this.fadeLevel) { this.animateSelection_(-1); return; } this.canvas_ctx_.clearRect(0, 0, this.width_, this.height_); this.fadeLevel = 0; this.selPoints_ = []; this.lastx_ = null; this.lastRow_ = -1; this.highlightSet_ = null; }; /** * Returns the number of the currently selected row. To get data for this row, * you can use the getValue method. * @return {number} row number, or -1 if nothing is selected */ Dygraph.prototype.getSelection = function() { if (!this.selPoints_ || this.selPoints_.length < 1) { return -1; } for (var setIdx = 0; setIdx < this.layout_.points.length; setIdx++) { var points = this.layout_.points[setIdx]; for (var row = 0; row < points.length; row++) { if (points[row].x == this.selPoints_[0].x) { return points[row].idx; } } } return -1; }; /** * Returns the name of the currently-highlighted series. * Only available when the highlightSeriesOpts option is in use. */ Dygraph.prototype.getHighlightSeries = function() { return this.highlightSet_; }; /** * Returns true if the currently-highlighted series was locked * via setSelection(..., seriesName, true). */ Dygraph.prototype.isSeriesLocked = function() { return this.lockedSet_; }; /** * Fires when there's data available to be graphed. * @param {string} data Raw CSV data to be plotted * @private */ Dygraph.prototype.loadedEvent_ = function(data) { this.rawData_ = this.parseCSV_(data); this.cascadeDataDidUpdateEvent_(); this.predraw_(); }; /** * Add ticks on the x-axis representing years, months, quarters, weeks, or days * @private */ Dygraph.prototype.addXTicks_ = function() { // Determine the correct ticks scale on the x-axis: quarterly, monthly, ... var range; if (this.dateWindow_) { range = [this.dateWindow_[0], this.dateWindow_[1]]; } else { range = this.xAxisExtremes(); } var xAxisOptionsView = this.optionsViewForAxis_('x'); var xTicks = xAxisOptionsView('ticker')( range[0], range[1], this.plotter_.area.w, // TODO(danvk): should be area.width xAxisOptionsView, this); // var msg = 'ticker(' + range[0] + ', ' + range[1] + ', ' + this.width_ + ', ' + this.attr_('pixelsPerXLabel') + ') -> ' + JSON.stringify(xTicks); // console.log(msg); this.layout_.setXTicks(xTicks); }; /** * Returns the correct handler class for the currently set options. * @private */ Dygraph.prototype.getHandlerClass_ = function() { var handlerClass; if (this.attr_('dataHandler')) { handlerClass = this.attr_('dataHandler'); } else if (this.fractions_) { if (this.getBooleanOption('errorBars')) { handlerClass = FractionsBarsHandler; } else { handlerClass = DefaultFractionHandler; } } else if (this.getBooleanOption('customBars')) { handlerClass = CustomBarsHandler; } else if (this.getBooleanOption('errorBars')) { handlerClass = ErrorBarsHandler; } else { handlerClass = DefaultHandler; } return handlerClass; }; /** * @private * This function is called once when the chart's data is changed or the options * dictionary is updated. It is _not_ called when the user pans or zooms. The * idea is that values derived from the chart's data can be computed here, * rather than every time the chart is drawn. This includes things like the * number of axes, rolling averages, etc. */ Dygraph.prototype.predraw_ = function() { var start = new Date(); // Create the correct dataHandler this.dataHandler_ = new (this.getHandlerClass_())(); this.layout_.computePlotArea(); // TODO(danvk): move more computations out of drawGraph_ and into here. this.computeYAxes_(); if (!this.is_initial_draw_) { this.canvas_ctx_.restore(); this.hidden_ctx_.restore(); } this.canvas_ctx_.save(); this.hidden_ctx_.save(); // Create a new plotter. this.plotter_ = new DygraphCanvasRenderer(this, this.hidden_, this.hidden_ctx_, this.layout_); // The roller sits in the bottom left corner of the chart. We don't know where // this will be until the options are available, so it's positioned here. this.createRollInterface_(); this.cascadeEvents_('predraw'); // Convert the raw data (a 2D array) into the internal format and compute // rolling averages. this.rolledSeries_ = [null]; // x-axis is the first series and it's special for (var i = 1; i < this.numColumns(); i++) { // var logScale = this.attr_('logscale', i); // TODO(klausw): this looks wrong // konigsberg thinks so too. var series = this.dataHandler_.extractSeries(this.rawData_, i, this.attributes_); if (this.rollPeriod_ > 1) { series = this.dataHandler_.rollingAverage(series, this.rollPeriod_, this.attributes_, i); } this.rolledSeries_.push(series); } // If the data or options have changed, then we'd better redraw. this.drawGraph_(); // This is used to determine whether to do various animations. var end = new Date(); this.drawingTimeMs_ = (end - start); }; /** * Point structure. * * xval_* and yval_* are the original unscaled data values, * while x_* and y_* are scaled to the range (0.0-1.0) for plotting. * yval_stacked is the cumulative Y value used for stacking graphs, * and bottom/top/minus/plus are used for high/low band graphs. * * @typedef {{ * idx: number, * name: string, * x: ?number, * xval: ?number, * y_bottom: ?number, * y: ?number, * y_stacked: ?number, * y_top: ?number, * yval_minus: ?number, * yval: ?number, * yval_plus: ?number, * yval_stacked * }} */ Dygraph.PointType = undefined; /** * Calculates point stacking for stackedGraph=true. * * For stacking purposes, interpolate or extend neighboring data across * NaN values based on stackedGraphNaNFill settings. This is for display * only, the underlying data value as shown in the legend remains NaN. * * @param {Array.} points Point array for a single series. * Updates each Point's yval_stacked property. * @param {Array.} cumulativeYval Accumulated top-of-graph stacked Y * values for the series seen so far. Index is the row number. Updated * based on the current series's values. * @param {Array.} seriesExtremes Min and max values, updated * to reflect the stacked values. * @param {string} fillMethod Interpolation method, one of 'all', 'inside', or * 'none'. * @private */ Dygraph.stackPoints_ = function( points, cumulativeYval, seriesExtremes, fillMethod) { var lastXval = null; var prevPoint = null; var nextPoint = null; var nextPointIdx = -1; // Find the next stackable point starting from the given index. var updateNextPoint = function(idx) { // If we've previously found a non-NaN point and haven't gone past it yet, // just use that. if (nextPointIdx >= idx) return; // We haven't found a non-NaN point yet or have moved past it, // look towards the right to find a non-NaN point. for (var j = idx; j < points.length; ++j) { // Clear out a previously-found point (if any) since it's no longer // valid, we shouldn't use it for interpolation anymore. nextPoint = null; if (!isNaN(points[j].yval) && points[j].yval !== null) { nextPointIdx = j; nextPoint = points[j]; break; } } }; for (var i = 0; i < points.length; ++i) { var point = points[i]; var xval = point.xval; if (cumulativeYval[xval] === undefined) { cumulativeYval[xval] = 0; } var actualYval = point.yval; if (isNaN(actualYval) || actualYval === null) { if(fillMethod == 'none') { actualYval = 0; } else { // Interpolate/extend for stacking purposes if possible. updateNextPoint(i); if (prevPoint && nextPoint && fillMethod != 'none') { // Use linear interpolation between prevPoint and nextPoint. actualYval = prevPoint.yval + (nextPoint.yval - prevPoint.yval) * ((xval - prevPoint.xval) / (nextPoint.xval - prevPoint.xval)); } else if (prevPoint && fillMethod == 'all') { actualYval = prevPoint.yval; } else if (nextPoint && fillMethod == 'all') { actualYval = nextPoint.yval; } else { actualYval = 0; } } } else { prevPoint = point; } var stackedYval = cumulativeYval[xval]; if (lastXval != xval) { // If an x-value is repeated, we ignore the duplicates. stackedYval += actualYval; cumulativeYval[xval] = stackedYval; } lastXval = xval; point.yval_stacked = stackedYval; if (stackedYval > seriesExtremes[1]) { seriesExtremes[1] = stackedYval; } if (stackedYval < seriesExtremes[0]) { seriesExtremes[0] = stackedYval; } } }; /** * Loop over all fields and create datasets, calculating extreme y-values for * each series and extreme x-indices as we go. * * dateWindow is passed in as an explicit parameter so that we can compute * extreme values "speculatively", i.e. without actually setting state on the * dygraph. * * @param {Array.)>>} rolledSeries, where * rolledSeries[seriesIndex][row] = raw point, where * seriesIndex is the column number starting with 1, and * rawPoint is [x,y] or [x, [y, err]] or [x, [y, yminus, yplus]]. * @param {?Array.} dateWindow [xmin, xmax] pair, or null. * @return {{ * points: Array.>, * seriesExtremes: Array.>, * boundaryIds: Array.}} * @private */ Dygraph.prototype.gatherDatasets_ = function(rolledSeries, dateWindow) { var boundaryIds = []; var points = []; var cumulativeYval = []; // For stacked series. var extremes = {}; // series name -> [low, high] var seriesIdx, sampleIdx; var firstIdx, lastIdx; var axisIdx; // Loop over the fields (series). Go from the last to the first, // because if they're stacked that's how we accumulate the values. var num_series = rolledSeries.length - 1; var series; for (seriesIdx = num_series; seriesIdx >= 1; seriesIdx--) { if (!this.visibility()[seriesIdx - 1]) continue; // Prune down to the desired range, if necessary (for zooming) // Because there can be lines going to points outside of the visible area, // we actually prune to visible points, plus one on either side. if (dateWindow) { series = rolledSeries[seriesIdx]; var low = dateWindow[0]; var high = dateWindow[1]; // TODO(danvk): do binary search instead of linear search. // TODO(danvk): pass firstIdx and lastIdx directly to the renderer. firstIdx = null; lastIdx = null; for (sampleIdx = 0; sampleIdx < series.length; sampleIdx++) { if (series[sampleIdx][0] >= low && firstIdx === null) { firstIdx = sampleIdx; } if (series[sampleIdx][0] <= high) { lastIdx = sampleIdx; } } if (firstIdx === null) firstIdx = 0; var correctedFirstIdx = firstIdx; var isInvalidValue = true; while (isInvalidValue && correctedFirstIdx > 0) { correctedFirstIdx--; // check if the y value is null. isInvalidValue = series[correctedFirstIdx][1] === null; } if (lastIdx === null) lastIdx = series.length - 1; var correctedLastIdx = lastIdx; isInvalidValue = true; while (isInvalidValue && correctedLastIdx < series.length - 1) { correctedLastIdx++; isInvalidValue = series[correctedLastIdx][1] === null; } if (correctedFirstIdx!==firstIdx) { firstIdx = correctedFirstIdx; } if (correctedLastIdx !== lastIdx) { lastIdx = correctedLastIdx; } boundaryIds[seriesIdx-1] = [firstIdx, lastIdx]; // .slice's end is exclusive, we want to include lastIdx. series = series.slice(firstIdx, lastIdx + 1); } else { series = rolledSeries[seriesIdx]; boundaryIds[seriesIdx-1] = [0, series.length-1]; } var seriesName = this.attr_("labels")[seriesIdx]; var seriesExtremes = this.dataHandler_.getExtremeYValues(series, dateWindow, this.getBooleanOption("stepPlot", seriesName)); var seriesPoints = this.dataHandler_.seriesToPoints(series, seriesName, boundaryIds[seriesIdx-1][0]); if (this.getBooleanOption("stackedGraph")) { axisIdx = this.attributes_.axisForSeries(seriesName); if (cumulativeYval[axisIdx] === undefined) { cumulativeYval[axisIdx] = []; } Dygraph.stackPoints_(seriesPoints, cumulativeYval[axisIdx], seriesExtremes, this.getBooleanOption("stackedGraphNaNFill")); } extremes[seriesName] = seriesExtremes; points[seriesIdx] = seriesPoints; } return { points: points, extremes: extremes, boundaryIds: boundaryIds }; }; /** * Update the graph with new data. This method is called when the viewing area * has changed. If the underlying data or options have changed, predraw_ will * be called before drawGraph_ is called. * * @private */ Dygraph.prototype.drawGraph_ = function() { var start = new Date(); // This is used to set the second parameter to drawCallback, below. var is_initial_draw = this.is_initial_draw_; this.is_initial_draw_ = false; this.layout_.removeAllDatasets(); this.setColors_(); this.attrs_.pointSize = 0.5 * this.getNumericOption('highlightCircleSize'); var packed = this.gatherDatasets_(this.rolledSeries_, this.dateWindow_); var points = packed.points; var extremes = packed.extremes; this.boundaryIds_ = packed.boundaryIds; this.setIndexByName_ = {}; var labels = this.attr_("labels"); var dataIdx = 0; for (var i = 1; i < points.length; i++) { if (!this.visibility()[i - 1]) continue; this.layout_.addDataset(labels[i], points[i]); this.datasetIndex_[i] = dataIdx++; } for (var i = 0; i < labels.length; i++) { this.setIndexByName_[labels[i]] = i; } this.computeYAxisRanges_(extremes); this.layout_.setYAxes(this.axes_); this.addXTicks_(); // Tell PlotKit to use this new data and render itself this.layout_.evaluate(); this.renderGraph_(is_initial_draw); if (this.getStringOption("timingName")) { var end = new Date(); console.log(this.getStringOption("timingName") + " - drawGraph: " + (end - start) + "ms"); } }; /** * This does the work of drawing the chart. It assumes that the layout and axis * scales have already been set (e.g. by predraw_). * * @private */ Dygraph.prototype.renderGraph_ = function(is_initial_draw) { this.cascadeEvents_('clearChart'); this.plotter_.clear(); const underlayCallback = this.getFunctionOption('underlayCallback'); if (underlayCallback) { // NOTE: we pass the dygraph object to this callback twice to avoid breaking // users who expect a deprecated form of this callback. underlayCallback.call(this, this.hidden_ctx_, this.layout_.getPlotArea(), this, this); } var e = { canvas: this.hidden_, drawingContext: this.hidden_ctx_ }; this.cascadeEvents_('willDrawChart', e); this.plotter_.render(); this.cascadeEvents_('didDrawChart', e); this.lastRow_ = -1; // because plugins/legend.js clears the legend // TODO(danvk): is this a performance bottleneck when panning? // The interaction canvas should already be empty in that situation. this.canvas_.getContext('2d').clearRect(0, 0, this.width_, this.height_); const drawCallback = this.getFunctionOption("drawCallback"); if (drawCallback !== null) { drawCallback.call(this, this, is_initial_draw); } if (is_initial_draw) { this.readyFired_ = true; while (this.readyFns_.length > 0) { var fn = this.readyFns_.pop(); fn(this); } } }; /** * @private * Determine properties of the y-axes which are independent of the data * currently being displayed. This includes things like the number of axes and * the style of the axes. It does not include the range of each axis and its * tick marks. * This fills in this.axes_. * axes_ = [ { options } ] * indices are into the axes_ array. */ Dygraph.prototype.computeYAxes_ = function() { var axis, index, opts, v; // this.axes_ doesn't match this.attributes_.axes_.options. It's used for // data computation as well as options storage. // Go through once and add all the axes. this.axes_ = []; for (axis = 0; axis < this.attributes_.numAxes(); axis++) { // Add a new axis, making a copy of its per-axis options. opts = { g : this }; utils.update(opts, this.attributes_.axisOptions(axis)); this.axes_[axis] = opts; } for (axis = 0; axis < this.axes_.length; axis++) { if (axis === 0) { opts = this.optionsViewForAxis_('y' + (axis ? '2' : '')); v = opts("valueRange"); if (v) this.axes_[axis].valueRange = v; } else { // To keep old behavior var axes = this.user_attrs_.axes; if (axes && axes.y2) { v = axes.y2.valueRange; if (v) this.axes_[axis].valueRange = v; } } } }; /** * Returns the number of y-axes on the chart. * @return {number} the number of axes. */ Dygraph.prototype.numAxes = function() { return this.attributes_.numAxes(); }; /** * @private * Returns axis properties for the given series. * @param {string} setName The name of the series for which to get axis * properties, e.g. 'Y1'. * @return {Object} The axis properties. */ Dygraph.prototype.axisPropertiesForSeries = function(series) { // TODO(danvk): handle errors. return this.axes_[this.attributes_.axisForSeries(series)]; }; /** * @private * Determine the value range and tick marks for each axis. * @param {Object} extremes A mapping from seriesName -> [low, high] * This fills in the valueRange and ticks fields in each entry of this.axes_. */ Dygraph.prototype.computeYAxisRanges_ = function(extremes) { var isNullUndefinedOrNaN = function(num) { return isNaN(parseFloat(num)); }; var numAxes = this.attributes_.numAxes(); var ypadCompat, span, series, ypad; var p_axis; // Compute extreme values, a span and tick marks for each axis. for (var i = 0; i < numAxes; i++) { var axis = this.axes_[i]; var logscale = this.attributes_.getForAxis("logscale", i); var includeZero = this.attributes_.getForAxis("includeZero", i); var independentTicks = this.attributes_.getForAxis("independentTicks", i); series = this.attributes_.seriesForAxis(i); // Add some padding. This supports two Y padding operation modes: // // - backwards compatible (yRangePad not set): // 10% padding for automatic Y ranges, but not for user-supplied // ranges, and move a close-to-zero edge to zero, since drawing at the edge // results in invisible lines. Unfortunately lines drawn at the edge of a // user-supplied range will still be invisible. If logscale is // set, add a variable amount of padding at the top but // none at the bottom. // // - new-style (yRangePad set by the user): // always add the specified Y padding. // ypadCompat = true; ypad = 0.1; // add 10% const yRangePad = this.getNumericOption('yRangePad'); if (yRangePad !== null) { ypadCompat = false; // Convert pixel padding to ratio ypad = yRangePad / this.plotter_.area.h; } if (series.length === 0) { // If no series are defined or visible then use a reasonable default axis.extremeRange = [0, 1]; } else { // Calculate the extremes of extremes. var minY = Infinity; // extremes[series[0]][0]; var maxY = -Infinity; // extremes[series[0]][1]; var extremeMinY, extremeMaxY; for (var j = 0; j < series.length; j++) { // this skips invisible series if (!extremes.hasOwnProperty(series[j])) continue; // Only use valid extremes to stop null data series' from corrupting the scale. extremeMinY = extremes[series[j]][0]; if (extremeMinY !== null) { minY = Math.min(extremeMinY, minY); } extremeMaxY = extremes[series[j]][1]; if (extremeMaxY !== null) { maxY = Math.max(extremeMaxY, maxY); } } // Include zero if requested by the user. if (includeZero && !logscale) { if (minY > 0) minY = 0; if (maxY < 0) maxY = 0; } // Ensure we have a valid scale, otherwise default to [0, 1] for safety. if (minY == Infinity) minY = 0; if (maxY == -Infinity) maxY = 1; span = maxY - minY; // special case: if we have no sense of scale, center on the sole value. if (span === 0) { if (maxY !== 0) { span = Math.abs(maxY); } else { // ... and if the sole value is zero, use range 0-1. maxY = 1; span = 1; } } var maxAxisY = maxY, minAxisY = minY; if (ypadCompat) { if (logscale) { maxAxisY = maxY + ypad * span; minAxisY = minY; } else { maxAxisY = maxY + ypad * span; minAxisY = minY - ypad * span; // Backwards-compatible behavior: Move the span to start or end at zero if it's // close to zero. if (minAxisY < 0 && minY >= 0) minAxisY = 0; if (maxAxisY > 0 && maxY <= 0) maxAxisY = 0; } } axis.extremeRange = [minAxisY, maxAxisY]; } if (axis.valueRange) { // This is a user-set value range for this axis. var y0 = isNullUndefinedOrNaN(axis.valueRange[0]) ? axis.extremeRange[0] : axis.valueRange[0]; var y1 = isNullUndefinedOrNaN(axis.valueRange[1]) ? axis.extremeRange[1] : axis.valueRange[1]; axis.computedValueRange = [y0, y1]; } else { axis.computedValueRange = axis.extremeRange; } if (!ypadCompat) { // When using yRangePad, adjust the upper/lower bounds to add // padding unless the user has zoomed/panned the Y axis range. y0 = axis.computedValueRange[0]; y1 = axis.computedValueRange[1]; // special case #781: if we have no sense of scale, center on the sole value. if (y0 === y1) { if(y0 === 0) { y1 = 1; } else { var delta = Math.abs(y0 / 10); y0 -= delta; y1 += delta; } } if (logscale) { var y0pct = ypad / (2 * ypad - 1); var y1pct = (ypad - 1) / (2 * ypad - 1); axis.computedValueRange[0] = utils.logRangeFraction(y0, y1, y0pct); axis.computedValueRange[1] = utils.logRangeFraction(y0, y1, y1pct); } else { span = y1 - y0; axis.computedValueRange[0] = y0 - span * ypad; axis.computedValueRange[1] = y1 + span * ypad; } } if (independentTicks) { axis.independentTicks = independentTicks; var opts = this.optionsViewForAxis_('y' + (i ? '2' : '')); var ticker = opts('ticker'); axis.ticks = ticker(axis.computedValueRange[0], axis.computedValueRange[1], this.plotter_.area.h, opts, this); // Define the first independent axis as primary axis. if (!p_axis) p_axis = axis; } } if (p_axis === undefined) { throw ("Configuration Error: At least one axis has to have the \"independentTicks\" option activated."); } // Add ticks. By default, all axes inherit the tick positions of the // primary axis. However, if an axis is specifically marked as having // independent ticks, then that is permissible as well. for (var i = 0; i < numAxes; i++) { var axis = this.axes_[i]; if (!axis.independentTicks) { var opts = this.optionsViewForAxis_('y' + (i ? '2' : '')); var ticker = opts('ticker'); var p_ticks = p_axis.ticks; var p_scale = p_axis.computedValueRange[1] - p_axis.computedValueRange[0]; var scale = axis.computedValueRange[1] - axis.computedValueRange[0]; var tick_values = []; for (var k = 0; k < p_ticks.length; k++) { var y_frac = (p_ticks[k].v - p_axis.computedValueRange[0]) / p_scale; var y_val = axis.computedValueRange[0] + y_frac * scale; tick_values.push(y_val); } axis.ticks = ticker(axis.computedValueRange[0], axis.computedValueRange[1], this.plotter_.area.h, opts, this, tick_values); } } }; /** * Detects the type of the str (date or numeric) and sets the various * formatting attributes in this.attrs_ based on this type. * @param {string} str An x value. * @private */ Dygraph.prototype.detectTypeFromString_ = function(str) { var isDate = false; var dashPos = str.indexOf('-'); // could be 2006-01-01 _or_ 1.0e-2 if ((dashPos > 0 && (str[dashPos-1] != 'e' && str[dashPos-1] != 'E')) || str.indexOf('/') >= 0 || isNaN(parseFloat(str))) { isDate = true; } this.setXAxisOptions_(isDate); }; Dygraph.prototype.setXAxisOptions_ = function(isDate) { if (isDate) { this.attrs_.xValueParser = utils.dateParser; this.attrs_.axes.x.valueFormatter = utils.dateValueFormatter; this.attrs_.axes.x.ticker = DygraphTickers.dateTicker; this.attrs_.axes.x.axisLabelFormatter = utils.dateAxisLabelFormatter; } else { /** @private (shut up, jsdoc!) */ this.attrs_.xValueParser = function(x) { return parseFloat(x); }; // TODO(danvk): use Dygraph.numberValueFormatter here? /** @private (shut up, jsdoc!) */ this.attrs_.axes.x.valueFormatter = function(x) { return x; }; this.attrs_.axes.x.ticker = DygraphTickers.numericTicks; this.attrs_.axes.x.axisLabelFormatter = this.attrs_.axes.x.valueFormatter; } }; /** * @private * Parses a string in a special csv format. We expect a csv file where each * line is a date point, and the first field in each line is the date string. * We also expect that all remaining fields represent series. * if the errorBars attribute is set, then interpret the fields as: * date, series1, stddev1, series2, stddev2, ... * @param {[Object]} data See above. * * @return [Object] An array with one entry for each row. These entries * are an array of cells in that row. The first entry is the parsed x-value for * the row. The second, third, etc. are the y-values. These can take on one of * three forms, depending on the CSV and constructor parameters: * 1. numeric value * 2. [ value, stddev ] * 3. [ low value, center value, high value ] */ Dygraph.prototype.parseCSV_ = function(data) { var ret = []; var line_delimiter = utils.detectLineDelimiter(data); var lines = data.split(line_delimiter || "\n"); var vals, j; // Use the default delimiter or fall back to a tab if that makes sense. var delim = this.getStringOption('delimiter'); if (lines[0].indexOf(delim) == -1 && lines[0].indexOf('\t') >= 0) { delim = '\t'; } var start = 0; if (!('labels' in this.user_attrs_)) { // User hasn't explicitly set labels, so they're (presumably) in the CSV. start = 1; this.attrs_.labels = lines[0].split(delim); // NOTE: _not_ user_attrs_. this.attributes_.reparseSeries(); } var line_no = 0; var xParser; var defaultParserSet = false; // attempt to auto-detect x value type var expectedCols = this.attr_("labels").length; var outOfOrder = false; for (var i = start; i < lines.length; i++) { var line = lines[i]; line_no = i; if (line.length === 0) continue; // skip blank lines if (line[0] == '#') continue; // skip comment lines var inFields = line.split(delim); if (inFields.length < 2) continue; var fields = []; if (!defaultParserSet) { this.detectTypeFromString_(inFields[0]); xParser = this.getFunctionOption("xValueParser"); defaultParserSet = true; } fields[0] = xParser(inFields[0], this); // If fractions are expected, parse the numbers as "A/B" if (this.fractions_) { for (j = 1; j < inFields.length; j++) { // TODO(danvk): figure out an appropriate way to flag parse errors. vals = inFields[j].split("/"); if (vals.length != 2) { console.error('Expected fractional "num/den" values in CSV data ' + "but found a value '" + inFields[j] + "' on line " + (1 + i) + " ('" + line + "') which is not of this form."); fields[j] = [0, 0]; } else { fields[j] = [utils.parseFloat_(vals[0], i, line), utils.parseFloat_(vals[1], i, line)]; } } } else if (this.getBooleanOption("errorBars")) { // If there are sigma-based high/low bands, values are (value, stddev) pairs if (inFields.length % 2 != 1) { console.error('Expected alternating (value, stdev.) pairs in CSV data ' + 'but line ' + (1 + i) + ' has an odd number of values (' + (inFields.length - 1) + "): '" + line + "'"); } for (j = 1; j < inFields.length; j += 2) { fields[(j + 1) / 2] = [utils.parseFloat_(inFields[j], i, line), utils.parseFloat_(inFields[j + 1], i, line)]; } } else if (this.getBooleanOption("customBars")) { // Custom high/low bands are a low;centre;high tuple for (j = 1; j < inFields.length; j++) { var val = inFields[j]; if (/^ *$/.test(val)) { fields[j] = [null, null, null]; } else { vals = val.split(";"); if (vals.length == 3) { fields[j] = [ utils.parseFloat_(vals[0], i, line), utils.parseFloat_(vals[1], i, line), utils.parseFloat_(vals[2], i, line) ]; } else { console.warn('When using customBars, values must be either blank ' + 'or "low;center;high" tuples (got "' + val + '" on line ' + (1+i) + ')'); } } } } else { // Values are just numbers for (j = 1; j < inFields.length; j++) { fields[j] = utils.parseFloat_(inFields[j], i, line); } } if (ret.length > 0 && fields[0] < ret[ret.length - 1][0]) { outOfOrder = true; } if (fields.length != expectedCols) { console.error("Number of columns in line " + i + " (" + fields.length + ") does not agree with number of labels (" + expectedCols + ") " + line); } // If the user specified the 'labels' option and none of the cells of the // first row parsed correctly, then they probably double-specified the // labels. We go with the values set in the option, discard this row and // log a warning to the JS console. if (i === 0 && this.attr_('labels')) { var all_null = true; for (j = 0; all_null && j < fields.length; j++) { if (fields[j]) all_null = false; } if (all_null) { console.warn("The dygraphs 'labels' option is set, but the first row " + "of CSV data ('" + line + "') appears to also contain " + "labels. Will drop the CSV labels and use the option " + "labels."); continue; } } ret.push(fields); } if (outOfOrder) { console.warn("CSV is out of order; order it correctly to speed loading."); ret.sort(function(a,b) { return a[0] - b[0]; }); } return ret; }; // In native format, all values must be dates or numbers. // This check isn't perfect but will catch most mistaken uses of strings. function validateNativeFormat(data) { const firstRow = data[0]; const firstX = firstRow[0]; if (typeof firstX !== 'number' && !utils.isDateLike(firstX)) { throw new Error(`Expected number or date but got ${typeof firstX}: ${firstX}.`); } for (let i = 1; i < firstRow.length; i++) { const val = firstRow[i]; if (val === null || val === undefined) continue; if (typeof val === 'number') continue; if (utils.isArrayLike(val)) continue; // e.g. errorBars or customBars throw new Error(`Expected number or array but got ${typeof val}: ${val}.`); } } /** * The user has provided their data as a pre-packaged JS array. If the x values * are numeric, this is the same as dygraphs' internal format. If the x values * are dates, we need to convert them from Date objects to ms since epoch. * @param {!Array} data * @return {Object} data with numeric x values. * @private */ Dygraph.prototype.parseArray_ = function(data) { // Peek at the first x value to see if it's numeric. if (data.length === 0) { data = [[0]]; } if (data[0].length === 0) { console.error("Data set cannot contain an empty row"); return null; } validateNativeFormat(data); var i; if (this.attr_("labels") === null) { console.warn("Using default labels. Set labels explicitly via 'labels' " + "in the options parameter"); this.attrs_.labels = [ "X" ]; for (i = 1; i < data[0].length; i++) { this.attrs_.labels.push("Y" + i); // Not user_attrs_. } this.attributes_.reparseSeries(); } else { var num_labels = this.attr_("labels"); if (num_labels.length != data[0].length) { console.error("Mismatch between number of labels (" + num_labels + ")" + " and number of columns in array (" + data[0].length + ")"); return null; } } if (utils.isDateLike(data[0][0])) { // Some intelligent defaults for a date x-axis. this.attrs_.axes.x.valueFormatter = utils.dateValueFormatter; this.attrs_.axes.x.ticker = DygraphTickers.dateTicker; this.attrs_.axes.x.axisLabelFormatter = utils.dateAxisLabelFormatter; // Assume they're all dates. var parsedData = utils.clone(data); for (i = 0; i < data.length; i++) { if (parsedData[i].length === 0) { console.error("Row " + (1 + i) + " of data is empty"); return null; } if (parsedData[i][0] === null || typeof(parsedData[i][0].getTime) != 'function' || isNaN(parsedData[i][0].getTime())) { console.error("x value in row " + (1 + i) + " is not a Date"); return null; } parsedData[i][0] = parsedData[i][0].getTime(); } return parsedData; } else { // Some intelligent defaults for a numeric x-axis. /** @private (shut up, jsdoc!) */ this.attrs_.axes.x.valueFormatter = function(x) { return x; }; this.attrs_.axes.x.ticker = DygraphTickers.numericTicks; this.attrs_.axes.x.axisLabelFormatter = utils.numberAxisLabelFormatter; return data; } }; /** * Parses a DataTable object from gviz. * The data is expected to have a first column that is either a date or a * number. All subsequent columns must be numbers. If there is a clear mismatch * between this.xValueParser_ and the type of the first column, it will be * fixed. Fills out rawData_. * @param {!google.visualization.DataTable} data See above. * @private */ Dygraph.prototype.parseDataTable_ = function(data) { var shortTextForAnnotationNum = function(num) { // converts [0-9]+ [A-Z][a-z]* // example: 0=A, 1=B, 25=Z, 26=Aa, 27=Ab // and continues like.. Ba Bb .. Za .. Zz..Aaa...Zzz Aaaa Zzzz var shortText = String.fromCharCode(65 /* A */ + num % 26); num = Math.floor(num / 26); while ( num > 0 ) { shortText = String.fromCharCode(65 /* A */ + (num - 1) % 26 ) + shortText.toLowerCase(); num = Math.floor((num - 1) / 26); } return shortText; }; var cols = data.getNumberOfColumns(); var rows = data.getNumberOfRows(); var indepType = data.getColumnType(0); if (indepType == 'date' || indepType == 'datetime') { this.attrs_.xValueParser = utils.dateParser; this.attrs_.axes.x.valueFormatter = utils.dateValueFormatter; this.attrs_.axes.x.ticker = DygraphTickers.dateTicker; this.attrs_.axes.x.axisLabelFormatter = utils.dateAxisLabelFormatter; } else if (indepType == 'number') { this.attrs_.xValueParser = function(x) { return parseFloat(x); }; this.attrs_.axes.x.valueFormatter = function(x) { return x; }; this.attrs_.axes.x.ticker = DygraphTickers.numericTicks; this.attrs_.axes.x.axisLabelFormatter = this.attrs_.axes.x.valueFormatter; } else { throw new Error( "only 'date', 'datetime' and 'number' types are supported " + "for column 1 of DataTable input (Got '" + indepType + "')"); } // Array of the column indices which contain data (and not annotations). var colIdx = []; var annotationCols = {}; // data index -> [annotation cols] var hasAnnotations = false; var i, j; for (i = 1; i < cols; i++) { var type = data.getColumnType(i); if (type == 'number') { colIdx.push(i); } else if (type == 'string' && this.getBooleanOption('displayAnnotations')) { // This is OK -- it's an annotation column. var dataIdx = colIdx[colIdx.length - 1]; if (!annotationCols.hasOwnProperty(dataIdx)) { annotationCols[dataIdx] = [i]; } else { annotationCols[dataIdx].push(i); } hasAnnotations = true; } else { throw new Error( "Only 'number' is supported as a dependent type with Gviz." + " 'string' is only supported if displayAnnotations is true"); } } // Read column labels // TODO(danvk): add support back for errorBars var labels = [data.getColumnLabel(0)]; for (i = 0; i < colIdx.length; i++) { labels.push(data.getColumnLabel(colIdx[i])); if (this.getBooleanOption("errorBars")) i += 1; } this.attrs_.labels = labels; cols = labels.length; var ret = []; var outOfOrder = false; var annotations = []; for (i = 0; i < rows; i++) { var row = []; if (typeof(data.getValue(i, 0)) === 'undefined' || data.getValue(i, 0) === null) { console.warn("Ignoring row " + i + " of DataTable because of undefined or null first column."); continue; } if (indepType == 'date' || indepType == 'datetime') { row.push(data.getValue(i, 0).getTime()); } else { row.push(data.getValue(i, 0)); } if (!this.getBooleanOption("errorBars")) { for (j = 0; j < colIdx.length; j++) { var col = colIdx[j]; row.push(data.getValue(i, col)); if (hasAnnotations && annotationCols.hasOwnProperty(col) && data.getValue(i, annotationCols[col][0]) !== null) { var ann = {}; ann.series = data.getColumnLabel(col); ann.xval = row[0]; ann.shortText = shortTextForAnnotationNum(annotations.length); ann.text = ''; for (var k = 0; k < annotationCols[col].length; k++) { if (k) ann.text += "\n"; ann.text += data.getValue(i, annotationCols[col][k]); } annotations.push(ann); } } // Strip out infinities, which give dygraphs problems later on. for (j = 0; j < row.length; j++) { if (!isFinite(row[j])) row[j] = null; } } else { for (j = 0; j < cols - 1; j++) { row.push([ data.getValue(i, 1 + 2 * j), data.getValue(i, 2 + 2 * j) ]); } } if (ret.length > 0 && row[0] < ret[ret.length - 1][0]) { outOfOrder = true; } ret.push(row); } if (outOfOrder) { console.warn("DataTable is out of order; order it correctly to speed loading."); ret.sort(function(a,b) { return a[0] - b[0]; }); } this.rawData_ = ret; if (annotations.length > 0) { this.setAnnotations(annotations, true); } this.attributes_.reparseSeries(); }; /** * Signals to plugins that the chart data has updated. * This happens after the data has updated but before the chart has redrawn. * @private */ Dygraph.prototype.cascadeDataDidUpdateEvent_ = function() { // TODO(danvk): there are some issues checking xAxisRange() and using // toDomCoords from handlers of this event. The visible range should be set // when the chart is drawn, not derived from the data. this.cascadeEvents_('dataDidUpdate', {}); }; /** * Get the CSV data. If it's in a function, call that function. If it's in a * file, do an XMLHttpRequest to get it. * @private */ Dygraph.prototype.start_ = function() { var data = this.file_; // Functions can return references of all other types. if (typeof data == 'function') { data = data(); } const datatype = utils.typeArrayLike(data); if (datatype == 'array') { this.rawData_ = this.parseArray_(data); this.cascadeDataDidUpdateEvent_(); this.predraw_(); } else if (datatype == 'object' && typeof data.getColumnRange == 'function') { // must be a DataTable from gviz. this.parseDataTable_(data); this.cascadeDataDidUpdateEvent_(); this.predraw_(); } else if (datatype == 'string') { // Heuristic: a newline means it's CSV data. Otherwise it's an URL. var line_delimiter = utils.detectLineDelimiter(data); if (line_delimiter) { this.loadedEvent_(data); } else { // REMOVE_FOR_IE var req; if (window.XMLHttpRequest) { // Firefox, Opera, IE7, and other browsers will use the native object req = new XMLHttpRequest(); } else { // IE 5 and 6 will use the ActiveX control req = new ActiveXObject("Microsoft.XMLHTTP"); } var caller = this; req.onreadystatechange = function () { if (req.readyState == 4) { if (req.status === 200 || // Normal http req.status === 0) { // Chrome w/ --allow-file-access-from-files caller.loadedEvent_(req.responseText); } } }; req.open("GET", data, true); req.send(null); } } else { console.error("Unknown data format: " + datatype); } }; /** * Changes various properties of the graph. These can include: *
      *
    • file: changes the source data for the graph
    • *
    • errorBars: changes whether the data contains stddev
    • *
    * * There's a huge variety of options that can be passed to this method. For a * full list, see http://dygraphs.com/options.html. * * @param {Object} input_attrs The new properties and values * @param {boolean} block_redraw Usually the chart is redrawn after every * call to updateOptions(). If you know better, you can pass true to * explicitly block the redraw. This can be useful for chaining * updateOptions() calls, avoiding the occasional infinite loop and * preventing redraws when it's not necessary (e.g. when updating a * callback). */ Dygraph.prototype.updateOptions = function(input_attrs, block_redraw) { if (typeof(block_redraw) == 'undefined') block_redraw = false; // copyUserAttrs_ drops the "file" parameter as a convenience to us. var file = input_attrs.file; var attrs = Dygraph.copyUserAttrs_(input_attrs); var prevNumAxes = this.attributes_.numAxes(); // TODO(danvk): this is a mess. Move these options into attr_. if ('rollPeriod' in attrs) { this.rollPeriod_ = attrs.rollPeriod; } if ('dateWindow' in attrs) { this.dateWindow_ = attrs.dateWindow; } // TODO(danvk): validate per-series options. // Supported: // strokeWidth // pointSize // drawPoints // highlightCircleSize // Check if this set options will require new points. var requiresNewPoints = utils.isPixelChangingOptionList(this.attr_("labels"), attrs); utils.updateDeep(this.user_attrs_, attrs); this.attributes_.reparseSeries(); if (prevNumAxes < this.attributes_.numAxes()) this.plotter_.clear(); if (file) { // This event indicates that the data is about to change, but hasn't yet. // TODO(danvk): support cancellation of the update via this event. this.cascadeEvents_('dataWillUpdate', {}); this.file_ = file; if (!block_redraw) this.start_(); } else { if (!block_redraw) { if (requiresNewPoints) { this.predraw_(); } else { this.renderGraph_(false); } } } }; /** * Make a copy of input attributes, removing file as a convenience. * @private */ Dygraph.copyUserAttrs_ = function(attrs) { var my_attrs = {}; for (var k in attrs) { if (!attrs.hasOwnProperty(k)) continue; if (k == 'file') continue; if (attrs.hasOwnProperty(k)) my_attrs[k] = attrs[k]; } return my_attrs; }; /** * Resizes the dygraph. If no parameters are specified, resizes to fill the * containing div (which has presumably changed size since the dygraph was * instantiated). If the width/height are specified, the div will be resized. * * This is far more efficient than destroying and re-instantiating a * Dygraph, since it doesn't have to reparse the underlying data. * * @param {number} width Width (in pixels) * @param {number} height Height (in pixels) */ Dygraph.prototype.resize = function(width, height) { if (this.resize_lock) { return; } this.resize_lock = true; if ((width === null) != (height === null)) { console.warn("Dygraph.resize() should be called with zero parameters or " + "two non-NULL parameters. Pretending it was zero."); width = height = null; } var old_width = this.width_; var old_height = this.height_; if (width) { this.maindiv_.style.width = width + "px"; this.maindiv_.style.height = height + "px"; this.width_ = width; this.height_ = height; } else { this.width_ = this.maindiv_.clientWidth; this.height_ = this.maindiv_.clientHeight; } if (old_width != this.width_ || old_height != this.height_) { // Resizing a canvas erases it, even when the size doesn't change, so // any resize needs to be followed by a redraw. this.resizeElements_(); this.predraw_(); } this.resize_lock = false; }; /** * Adjusts the number of points in the rolling average. Updates the graph to * reflect the new averaging period. * @param {number} length Number of points over which to average the data. */ Dygraph.prototype.adjustRoll = function(length) { this.rollPeriod_ = length; this.predraw_(); }; /** * Returns a boolean array of visibility statuses. */ Dygraph.prototype.visibility = function() { // Do lazy-initialization, so that this happens after we know the number of // data series. if (!this.getOption("visibility")) { this.attrs_.visibility = []; } // TODO(danvk): it looks like this could go into an infinite loop w/ user_attrs. while (this.getOption("visibility").length < this.numColumns() - 1) { this.attrs_.visibility.push(true); } return this.getOption("visibility"); }; /** * Changes the visibility of one or more series. * * @param {number|number[]|object} num the series index or an array of series indices * or a boolean array of visibility states by index * or an object mapping series numbers, as keys, to * visibility state (boolean values) * @param {boolean} value the visibility state expressed as a boolean */ Dygraph.prototype.setVisibility = function(num, value) { var x = this.visibility(); var numIsObject = false; if (!Array.isArray(num)) { if (num !== null && typeof num === 'object') { numIsObject = true; } else { num = [num]; } } if (numIsObject) { for (var i in num) { if (num.hasOwnProperty(i)) { if (i < 0 || i >= x.length) { console.warn("Invalid series number in setVisibility: " + i); } else { x[i] = num[i]; } } } } else { for (var i = 0; i < num.length; i++) { if (typeof num[i] === 'boolean') { if (i >= x.length) { console.warn("Invalid series number in setVisibility: " + i); } else { x[i] = num[i]; } } else { if (num[i] < 0 || num[i] >= x.length) { console.warn("Invalid series number in setVisibility: " + num[i]); } else { x[num[i]] = value; } } } } this.predraw_(); }; /** * How large of an area will the dygraph render itself in? * This is used for testing. * @return A {width: w, height: h} object. * @private */ Dygraph.prototype.size = function() { return { width: this.width_, height: this.height_ }; }; /** * Update the list of annotations and redraw the chart. * See dygraphs.com/annotations.html for more info on how to use annotations. * @param ann {Array} An array of annotation objects. * @param suppressDraw {Boolean} Set to "true" to block chart redraw (optional). */ Dygraph.prototype.setAnnotations = function(ann, suppressDraw) { // Only add the annotation CSS rule once we know it will be used. this.annotations_ = ann; if (!this.layout_) { console.warn("Tried to setAnnotations before dygraph was ready. " + "Try setting them in a ready() block. See " + "dygraphs.com/tests/annotation.html"); return; } this.layout_.setAnnotations(this.annotations_); if (!suppressDraw) { this.predraw_(); } }; /** * Return the list of annotations. */ Dygraph.prototype.annotations = function() { return this.annotations_; }; /** * Get the list of label names for this graph. The first column is the * x-axis, so the data series names start at index 1. * * Returns null when labels have not yet been defined. */ Dygraph.prototype.getLabels = function() { var labels = this.attr_("labels"); return labels ? labels.slice() : null; }; /** * Get the index of a series (column) given its name. The first column is the * x-axis, so the data series start with index 1. */ Dygraph.prototype.indexFromSetName = function(name) { return this.setIndexByName_[name]; }; /** * Find the row number corresponding to the given x-value. * Returns null if there is no such x-value in the data. * If there are multiple rows with the same x-value, this will return the * first one. * @param {number} xVal The x-value to look for (e.g. millis since epoch). * @return {?number} The row number, which you can pass to getValue(), or null. */ Dygraph.prototype.getRowForX = function(xVal) { var low = 0, high = this.numRows() - 1; while (low <= high) { var idx = (high + low) >> 1; var x = this.getValue(idx, 0); if (x < xVal) { low = idx + 1; } else if (x > xVal) { high = idx - 1; } else if (low != idx) { // equal, but there may be an earlier match. high = idx; } else { return idx; } } return null; }; /** * Trigger a callback when the dygraph has drawn itself and is ready to be * manipulated. This is primarily useful when dygraphs has to do an XHR for the * data (i.e. a URL is passed as the data source) and the chart is drawn * asynchronously. If the chart has already drawn, the callback will fire * immediately. * * This is a good place to call setAnnotation(). * * @param {function(!Dygraph)} callback The callback to trigger when the chart * is ready. */ Dygraph.prototype.ready = function(callback) { if (this.is_initial_draw_) { this.readyFns_.push(callback); } else { callback.call(this, this); } }; /** * Add an event handler. This event handler is kept until the graph is * destroyed with a call to graph.destroy(). * * @param {!Node} elem The element to add the event to. * @param {string} type The type of the event, e.g. 'click' or 'mousemove'. * @param {function(Event):(boolean|undefined)} fn The function to call * on the event. The function takes one parameter: the event object. * @private */ Dygraph.prototype.addAndTrackEvent = function(elem, type, fn) { utils.addEvent(elem, type, fn); this.registeredEvents_.push({elem, type, fn}); }; Dygraph.prototype.removeTrackedEvents_ = function() { if (this.registeredEvents_) { for (var idx = 0; idx < this.registeredEvents_.length; idx++) { var reg = this.registeredEvents_[idx]; utils.removeEvent(reg.elem, reg.type, reg.fn); } } this.registeredEvents_ = []; }; // Installed plugins, in order of precedence (most-general to most-specific). Dygraph.PLUGINS = [ LegendPlugin, AxesPlugin, RangeSelectorPlugin, // Has to be before ChartLabels so that its callbacks are called after ChartLabels' callbacks. ChartLabelsPlugin, AnnotationsPlugin, GridPlugin ]; // There are many symbols which have historically been available through the // Dygraph class. These are exported here for backwards compatibility. Dygraph.GVizChart = GVizChart; Dygraph.DASHED_LINE = utils.DASHED_LINE; Dygraph.DOT_DASH_LINE = utils.DOT_DASH_LINE; Dygraph.dateAxisLabelFormatter = utils.dateAxisLabelFormatter; Dygraph.toRGB_ = utils.toRGB_; Dygraph.findPos = utils.findPos; Dygraph.pageX = utils.pageX; Dygraph.pageY = utils.pageY; Dygraph.dateString_ = utils.dateString_; Dygraph.defaultInteractionModel = DygraphInteraction.defaultModel; Dygraph.nonInteractiveModel = Dygraph.nonInteractiveModel_ = DygraphInteraction.nonInteractiveModel_; Dygraph.Circles = utils.Circles; Dygraph.Plugins = { Legend: LegendPlugin, Axes: AxesPlugin, Annotations: AnnotationsPlugin, ChartLabels: ChartLabelsPlugin, Grid: GridPlugin, RangeSelector: RangeSelectorPlugin }; Dygraph.DataHandlers = { DefaultHandler, BarsHandler, CustomBarsHandler, DefaultFractionHandler, ErrorBarsHandler, FractionsBarsHandler }; Dygraph.startPan = DygraphInteraction.startPan; Dygraph.startZoom = DygraphInteraction.startZoom; Dygraph.movePan = DygraphInteraction.movePan; Dygraph.moveZoom = DygraphInteraction.moveZoom; Dygraph.endPan = DygraphInteraction.endPan; Dygraph.endZoom = DygraphInteraction.endZoom; Dygraph.numericLinearTicks = DygraphTickers.numericLinearTicks; Dygraph.numericTicks = DygraphTickers.numericTicks; Dygraph.dateTicker = DygraphTickers.dateTicker; Dygraph.Granularity = DygraphTickers.Granularity; Dygraph.getDateAxis = DygraphTickers.getDateAxis; Dygraph.floatFormat = utils.floatFormat; utils.setupDOMready_(Dygraph); export default Dygraph; dygraphs-2.2.1/src/extras/crosshair.js010064400000000000000000000054571437353256000147370ustar00/** * @license * Copyright 2015 Petr Shevtsov (petr.shevtsov@gmail.com) * MIT-licenced: https://opensource.org/licenses/MIT */ /* loader wrapper to allow browser use and ES6 imports */ (function _extras_crosshair_wrapper() { 'use strict'; var Dygraph; if (window.Dygraph) { Dygraph = window.Dygraph; } else if (typeof(module) !== 'undefined') { Dygraph = require('../dygraph'); if (typeof(Dygraph.NAME) === 'undefined' && typeof(Dygraph.default) !== 'undefined') Dygraph = Dygraph.default; } /* end of loader wrapper header */ Dygraph.Plugins.Crosshair = (function _extras_crosshair_closure() { "use strict"; /** * Creates the crosshair * * @constructor */ var crosshair = function crosshair(opt_options) { this.canvas_ = document.createElement("canvas"); opt_options = opt_options || {}; this.direction_ = opt_options.direction || null; this.strokeStyle_ = opt_options.strokeStyle || "rgba(0, 0, 0, 0.3)"; }; crosshair.prototype.toString = function toString() { return "Crosshair Plugin"; }; /** * @param {Dygraph} g Graph instance. * @return {object.} Mapping of event names to callbacks. */ crosshair.prototype.activate = function activate(g) { g.graphDiv.appendChild(this.canvas_); return { select: this.select, deselect: this.deselect }; }; crosshair.prototype.select = function select(e) { if (this.direction_ === null) { return; } var width = e.dygraph.width_; var height = e.dygraph.height_; this.canvas_.width = width; this.canvas_.height = height; this.canvas_.style.width = width + "px"; // for IE this.canvas_.style.height = height + "px"; // for IE var ctx = this.canvas_.getContext("2d"); ctx.clearRect(0, 0, width, height); ctx.strokeStyle = this.strokeStyle_; ctx.beginPath(); var canvasx = Math.floor(e.dygraph.selPoints_[0].canvasx) + 0.5; // crisper rendering if (this.direction_ === "vertical" || this.direction_ === "both") { ctx.moveTo(canvasx, 0); ctx.lineTo(canvasx, height); } if (this.direction_ === "horizontal" || this.direction_ === "both") { for (var i = 0; i < e.dygraph.selPoints_.length; i++) { var canvasy = Math.floor(e.dygraph.selPoints_[i].canvasy) + 0.5; // crisper rendering ctx.moveTo(0, canvasy); ctx.lineTo(width, canvasy); } } ctx.stroke(); ctx.closePath(); }; crosshair.prototype.deselect = function deselect(e) { var ctx = this.canvas_.getContext("2d"); ctx.clearRect(0, 0, this.canvas_.width, this.canvas_.height); }; crosshair.prototype.destroy = function destroy() { this.canvas_ = null; }; return crosshair; })(); /* loader wrapper */ Dygraph._require.add('dygraphs/src/extras/crosshair.js', /* exports */ {}); })(); dygraphs-2.2.1/src/extras/hairlines.js010064400000000000000000000331011437353256000147030ustar00/** * @license * Copyright 2013 Dan Vanderkam (danvdk@gmail.com) * MIT-licenced: https://opensource.org/licenses/MIT * * Note: This plugin requires jQuery and jQuery UI Draggable. * * See high-level documentation at ../../docs/hairlines-annotations.pdf */ /* loader wrapper to allow browser use and ES6 imports */ (function _extras_hairlines_wrapper() { 'use strict'; var Dygraph; if (window.Dygraph) { Dygraph = window.Dygraph; } else if (typeof(module) !== 'undefined') { Dygraph = require('../dygraph'); if (typeof(Dygraph.NAME) === 'undefined' && typeof(Dygraph.default) !== 'undefined') Dygraph = Dygraph.default; } /* end of loader wrapper header */ Dygraph.Plugins.Hairlines = (function _extras_hairlines_closure() { "use strict"; /** * @typedef { * xval: number, // x-value (i.e. millis or a raw number) * interpolated: bool, // alternative is to snap to closest * lineDiv: !Element // vertical hairline div * infoDiv: !Element // div containing info about the nearest points * selected: boolean // whether this hairline is selected * } Hairline */ // We have to wait a few ms after clicks to give the user a chance to // double-click to unzoom. This sets that delay period. var CLICK_DELAY_MS = 300; var hairlines = function hairlines(opt_options) { /** @private {!Array.} */ this.hairlines_ = []; // Used to detect resizes (which require the divs to be repositioned). this.lastWidth_ = -1; this.lastHeight = -1; this.dygraph_ = null; this.addTimer_ = null; opt_options = opt_options || {}; this.divFiller_ = opt_options['divFiller'] || null; }; hairlines.prototype.toString = function toString() { return "Hairlines Plugin"; }; hairlines.prototype.activate = function activate(g) { this.dygraph_ = g; this.hairlines_ = []; return { didDrawChart: this.didDrawChart, click: this.click, dblclick: this.dblclick, dataDidUpdate: this.dataDidUpdate }; }; hairlines.prototype.detachLabels = function detachLabels() { for (var i = 0; i < this.hairlines_.length; i++) { var h = this.hairlines_[i]; $(h.lineDiv).remove(); $(h.infoDiv).remove(); this.hairlines_[i] = null; } this.hairlines_ = []; }; hairlines.prototype.hairlineWasDragged = function hairlineWasDragged(h, event, ui) { var area = this.dygraph_.getArea(); var oldXVal = h.xval; h.xval = this.dygraph_.toDataXCoord(ui.position.left); this.moveHairlineToTop(h); this.updateHairlineDivPositions(); this.updateHairlineInfo(); this.updateHairlineStyles(); $(this).triggerHandler('hairlineMoved', { oldXVal: oldXVal, newXVal: h.xval }); $(this).triggerHandler('hairlinesChanged', {}); }; // This creates the hairline object and returns it. // It does not position it and does not attach it to the chart. hairlines.prototype.createHairline = function createHairline(props) { var h; var self = this; var $lineContainerDiv = $('
    ').css({ 'width': '6px', 'margin-left': '-3px', 'position': 'absolute', 'z-index': '10' }) .addClass('dygraph-hairline'); var $lineDiv = $('
    ').css({ 'width': '1px', 'position': 'relative', 'left': '3px', 'background': 'black', 'height': '100%' }); $lineDiv.appendTo($lineContainerDiv); var $infoDiv = $('#hairline-template').clone().removeAttr('id').css({ 'position': 'absolute' }) .show(); // Surely there's a more jQuery-ish way to do this! $([$infoDiv.get(0), $lineContainerDiv.get(0)]) .draggable({ 'axis': 'x', 'drag': function dragWrapper_(event, ui) { self.hairlineWasDragged(h, event, ui); } // TODO(danvk): set cursor here }); h = $.extend({ interpolated: true, selected: false, lineDiv: $lineContainerDiv.get(0), infoDiv: $infoDiv.get(0) }, props); var that = this; $infoDiv.on('click', '.hairline-kill-button', function clickEvent_(e) { that.removeHairline(h); $(that).triggerHandler('hairlineDeleted', { xval: h.xval }); $(that).triggerHandler('hairlinesChanged', {}); e.stopPropagation(); // don't want .click() to trigger, below. }).on('click', function clickHandler_() { that.moveHairlineToTop(h); }); return h; }; // Moves a hairline's divs to the top of the z-ordering. hairlines.prototype.moveHairlineToTop = function moveHairlineToTop(h) { var div = this.dygraph_.graphDiv; $(h.infoDiv).appendTo(div); $(h.lineDiv).appendTo(div); var idx = this.hairlines_.indexOf(h); this.hairlines_.splice(idx, 1); this.hairlines_.push(h); }; // Positions existing hairline divs. hairlines.prototype.updateHairlineDivPositions = function updateHairlineDivPositions() { var g = this.dygraph_; var layout = this.dygraph_.getArea(); var chartLeft = layout.x, chartRight = layout.x + layout.w; var div = this.dygraph_.graphDiv; var pos = Dygraph.findPos(div); var box = [layout.x + pos.x, layout.y + pos.y]; box.push(box[0] + layout.w); box.push(box[1] + layout.h); $.each(this.hairlines_, function iterateHairlines_(idx, h) { var left = g.toDomXCoord(h.xval); h.domX = left; // See comments in this.dataDidUpdate $(h.lineDiv).css({ 'left': left + 'px', 'top': layout.y + 'px', 'height': layout.h + 'px' }); // .draggable("option", "containment", box); $(h.infoDiv).css({ 'left': left + 'px', 'top': layout.y + 'px', }).draggable("option", "containment", box); var visible = (left >= chartLeft && left <= chartRight); $([h.infoDiv, h.lineDiv]).toggle(visible); }); }; // Sets styles on the hairline (i.e. "selected") hairlines.prototype.updateHairlineStyles = function updateHairlineStyles() { $.each(this.hairlines_, function iterateHairlines_(idx, h) { $([h.infoDiv, h.lineDiv]).toggleClass('selected', h.selected); }); }; // Find prevRow and nextRow such that // g.getValue(prevRow, 0) <= xval // g.getValue(nextRow, 0) >= xval // g.getValue({prev,next}Row, col) != null, NaN or undefined // and there's no other row such that: // g.getValue(prevRow, 0) < g.getValue(row, 0) < g.getValue(nextRow, 0) // g.getValue(row, col) != null, NaN or undefined. // Returns [prevRow, nextRow]. Either can be null (but not both). hairlines.findPrevNextRows = function findPrevNextRows(g, xval, col) { var prevRow = null, nextRow = null; var numRows = g.numRows(); for (var row = 0; row < numRows; row++) { var yval = g.getValue(row, col); if (yval === null || yval === undefined || isNaN(yval)) continue; var rowXval = g.getValue(row, 0); if (rowXval <= xval) prevRow = row; if (rowXval >= xval) { nextRow = row; break; } } return [prevRow, nextRow]; }; // Fills out the info div based on current coordinates. hairlines.prototype.updateHairlineInfo = function updateHairlineInfo() { var mode = 'closest'; var g = this.dygraph_; var xRange = g.xAxisRange(); var that = this; $.each(this.hairlines_, function iterateHairlines_(idx, h) { // To use generateLegendHTML, we synthesize an array of selected points. var selPoints = []; var labels = g.getLabels(); var row, prevRow, nextRow; if (!h.interpolated) { // "closest point" mode. // TODO(danvk): make findClosestRow method public row = g.findClosestRow(g.toDomXCoord(h.xval)); for (var i = 1; i < g.numColumns(); i++) { selPoints.push({ canvasx: 1, // TODO(danvk): real coordinate canvasy: 1, // TODO(danvk): real coordinate xval: h.xval, yval: g.getValue(row, i), name: labels[i] }); } } else { // "interpolated" mode. for (var i = 1; i < g.numColumns(); i++) { var prevNextRow = hairlines.findPrevNextRows(g, h.xval, i); prevRow = prevNextRow[0], nextRow = prevNextRow[1]; // For x-values outside the domain, interpolate "between" the extreme // point and itself. if (prevRow === null) prevRow = nextRow; if (nextRow === null) nextRow = prevRow; // linear interpolation var prevX = g.getValue(prevRow, 0), nextX = g.getValue(nextRow, 0), prevY = g.getValue(prevRow, i), nextY = g.getValue(nextRow, i), frac = prevRow == nextRow ? 0 : (h.xval - prevX) / (nextX - prevX), yval = frac * nextY + (1 - frac) * prevY; selPoints.push({ canvasx: 1, // TODO(danvk): real coordinate canvasy: 1, // TODO(danvk): real coordinate xval: h.xval, yval: yval, prevRow: prevRow, nextRow: nextRow, name: labels[i] }); } } if (that.divFiller_) { that.divFiller_(h.infoDiv, { closestRow: row, points: selPoints, hairline: that.createPublicHairline_(h), dygraph: g }); } else { var html = Dygraph.Plugins.Legend.generateLegendHTML(g, h.xval, selPoints, 10); $('.hairline-legend', h.infoDiv).html(html); } }); }; // After a resize, the hairline divs can get dettached from the chart. // This reattaches them. hairlines.prototype.attachHairlinesToChart_ = function attachHairlinesToChart_() { var div = this.dygraph_.graphDiv; $.each(this.hairlines_, function iterateHairlines_(idx, h) { $([h.lineDiv, h.infoDiv]).appendTo(div); }); }; // Deletes a hairline and removes it from the chart. hairlines.prototype.removeHairline = function removeHairline(h) { var idx = this.hairlines_.indexOf(h); if (idx >= 0) { this.hairlines_.splice(idx, 1); $([h.lineDiv, h.infoDiv]).remove(); } else { Dygraph.warn('Tried to remove non-existent hairline.'); } }; hairlines.prototype.didDrawChart = function didDrawChart(e) { var g = e.dygraph; // Early out in the (common) case of zero hairlines. if (this.hairlines_.length === 0) return; this.updateHairlineDivPositions(); this.attachHairlinesToChart_(); this.updateHairlineInfo(); this.updateHairlineStyles(); }; hairlines.prototype.dataDidUpdate = function dataDidUpdate(e) { // When the data in the chart updates, the hairlines should stay in the same // position on the screen. didDrawChart stores a domX parameter for each // hairline. We use that to reposition them on data updates. var g = this.dygraph_; $.each(this.hairlines_, function iterateHairlines_(idx, h) { if (h.hasOwnProperty('domX')) { h.xval = g.toDataXCoord(h.domX); } }); }; hairlines.prototype.click = function click(e) { if (this.addTimer_) { // Another click is in progress; ignore this one. return; } var area = e.dygraph.getArea(); var xval = this.dygraph_.toDataXCoord(e.canvasx); var that = this; this.addTimer_ = setTimeout(function click_tmo_() { that.addTimer_ = null; that.hairlines_.push(that.createHairline({xval: xval})); that.updateHairlineDivPositions(); that.updateHairlineInfo(); that.updateHairlineStyles(); that.attachHairlinesToChart_(); $(that).triggerHandler('hairlineCreated', { xval: xval }); $(that).triggerHandler('hairlinesChanged', {}); }, CLICK_DELAY_MS); }; hairlines.prototype.dblclick = function dblclick(e) { if (this.addTimer_) { clearTimeout(this.addTimer_); this.addTimer_ = null; } }; hairlines.prototype.destroy = function destroy() { this.detachLabels(); }; // Public API /** * This is a restricted view of this.hairlines_ which doesn't expose * implementation details like the handle divs. * * @typedef { * xval: number, // x-value (i.e. millis or a raw number) * interpolated: bool, // alternative is to snap to closest * selected: bool // whether the hairline is selected. * } PublicHairline */ /** * @param {!Hairline} h Internal hairline. * @return {!PublicHairline} Restricted public view of the hairline. */ hairlines.prototype.createPublicHairline_ = function createPublicHairline_(h) { return { xval: h.xval, interpolated: h.interpolated, selected: h.selected }; }; /** * @return {!Array.} The current set of hairlines, ordered * from back to front. */ hairlines.prototype.get = function get() { var result = []; for (var i = 0; i < this.hairlines_.length; i++) { var h = this.hairlines_[i]; result.push(this.createPublicHairline_(h)); } return result; }; /** * Calling this will result in a hairlinesChanged event being triggered, no * matter whether it consists of additions, deletions, moves or no changes at * all. * * @param {!Array.} hairlines The new set of hairlines, * ordered from back to front. */ hairlines.prototype.set = function set(hairlines) { // Re-use divs from the old hairlines array so far as we can. // They're already correctly z-ordered. var anyCreated = false; for (var i = 0; i < hairlines.length; i++) { var h = hairlines[i]; if (this.hairlines_.length > i) { this.hairlines_[i].xval = h.xval; this.hairlines_[i].interpolated = h.interpolated; this.hairlines_[i].selected = h.selected; } else { this.hairlines_.push(this.createHairline({ xval: h.xval, interpolated: h.interpolated, selected: h.selected })); anyCreated = true; } } // If there are any remaining hairlines, destroy them. while (hairlines.length < this.hairlines_.length) { this.removeHairline(this.hairlines_[hairlines.length]); } this.updateHairlineDivPositions(); this.updateHairlineInfo(); this.updateHairlineStyles(); if (anyCreated) { this.attachHairlinesToChart_(); } $(this).triggerHandler('hairlinesChanged', {}); }; return hairlines; })(); /* loader wrapper */ Dygraph._require.add('dygraphs/src/extras/hairlines.js', /* exports */ {}); })(); dygraphs-2.2.1/src/extras/rebase.js010064400000000000000000000104401437353256000141670ustar00/** * @license * Copyright 2015 Petr Shevtsov (petr.shevtsov@gmail.com) * MIT-licenced: https://opensource.org/licenses/MIT * * Rebase plugin * * On pan/zoom event, each series will rebase to a specified value (e.g. 100) at the * start of the displayed period. * * See http://stats.oecd.org/glossary/detail.asp?ID=2249 * * Options: * Value to rebase. Must be either Number or 'percent' or null. * * See tests/straw-broom.html for demo. */ /* loader wrapper to allow browser use and ES6 imports */ (function _extras_rebase_closure() { 'use strict'; var Dygraph; if (window.Dygraph) { Dygraph = window.Dygraph; } else if (typeof(module) !== 'undefined') { Dygraph = require('../dygraph'); if (typeof(Dygraph.NAME) === 'undefined' && typeof(Dygraph.default) !== 'undefined') Dygraph = Dygraph.default; } /* end of loader wrapper header */ // Matches DefaultHandler.parseFloat var parseFloat = function parseFloat(val) { if (val === null) return NaN; return val; }; Dygraph.DataHandlers.RebaseHandler = function RebaseHandler(baseOpt) { this.baseOpt = baseOpt; }; var RebaseHandler = Dygraph.DataHandlers.RebaseHandler; RebaseHandler.prototype = new Dygraph.DataHandlers.DefaultHandler(); RebaseHandler.rebase = function rebase(value, initial, base) { if (base === "percent") { return (value / initial - 1) * 100; } return value * base / initial; }; RebaseHandler.prototype.getExtremeYValues = function getExtremeYValues(series, dateWindow, stepPlot) { var minY = null, maxY = null, y; var firstIdx = 0, lastIdx = series.length - 1; var initial = series[firstIdx][1]; for (var j = firstIdx; j <= lastIdx; j++) { if (j === firstIdx) { y = (this.baseOpt === "percent") ? 0 : this.baseOpt; } else { y = RebaseHandler.rebase(series[j][1], initial, this.baseOpt); } if (y === null || isNaN(y)) continue; if (maxY === null || y > maxY) { maxY = y; } if (minY === null || y < minY) { minY = y; } } return [ minY, maxY ]; }; RebaseHandler.prototype.seriesToPoints = function seriesToPoints(series, setName, boundaryIdStart) { var points = []; var firstIdx = 0; var lastIdx = series.length - 1; var initial = series[firstIdx][1]; // TODO: check for null for (var i = 0; i <= lastIdx; ++i) { var item = series[i]; var yraw = item[1]; var yval = yraw === null ? null : parseFloat(yraw); if (yval !== null) { if (i === firstIdx) { yval = (this.baseOpt === "percent") ? 0 : this.baseOpt; } else { yval = RebaseHandler.rebase(yval, initial, this.baseOpt); } } var point = { x: NaN, y: NaN, xval: parseFloat(item[0]), yval: yval, name: setName, idx: i + boundaryIdStart }; points.push(point); } this.onPointsCreated_(series, points); return points; }; Dygraph.Plugins.Rebase = (function _rebase_inner_closure() { var rebase = function rebase(baseOpt) { var isNum = function isNum(v) { return !isNaN(v) && (typeof v === 'number' || {}.toString.call(v) === '[object Number]'); }; if (baseOpt === "percent" || isNum(baseOpt)) { this.baseOpt_ = baseOpt; } else { this.baseOpt_ = null; } }; rebase.prototype.toString = function toString() { return "Rebase Plugin"; }; rebase.prototype.activate = function activate(g) { if (this.baseOpt_ === null) { return; } return { predraw: this.predraw }; }; rebase.prototype.predraw = function predraw(e) { var g = e.dygraph; if (this.baseOpt_ === "percent") { g.updateOptions({ axes: { y: { axisLabelFormatter: function axisLabelFormatter(y) { return y + '%'; }, valueFormatter: function valueFormatter(y) { return Math.round(y * 100) / 100 + '%'; } } } }, true); } g.dataHandler_ = new Dygraph.DataHandlers.RebaseHandler(this.baseOpt_); }; return rebase; })(); /* closure and loader wrapper */ Dygraph._require.add('dygraphs/src/extras/rebase.js', /* exports */ {}); })(); dygraphs-2.2.1/src/extras/shapes.js010064400000000000000000000074471437353256000142260ustar00/** * @license * Copyright 2011 Dan Vanderkam (danvdk@gmail.com) * MIT-licenced: https://opensource.org/licenses/MIT */ /** * @fileoverview * Including this file will add several additional shapes to Dygraph.Circles * which can be passed to drawPointCallback. * See tests/custom-circles.html for usage. */ /* loader wrapper to allow browser use and ES6 imports */ (function _extras_shapes_closure() { 'use strict'; var Dygraph; if (window.Dygraph) { Dygraph = window.Dygraph; } else if (typeof(module) !== 'undefined') { Dygraph = require('../dygraph'); if (typeof(Dygraph.NAME) === 'undefined' && typeof(Dygraph.default) !== 'undefined') Dygraph = Dygraph.default; } /* end of loader wrapper header */ /** * @param {!CanvasRenderingContext2D} ctx the canvas context * @param {number} sides the number of sides in the shape. * @param {number} radius the radius of the image. * @param {number} cx center x coordate * @param {number} cy center y coordinate * @param {number=} rotationRadians the shift of the initial angle, in radians. * @param {number=} delta the angle shift for each line. If missing, creates a * regular polygon. */ var regularShape = function regularShape( ctx, sides, radius, cx, cy, rotationRadians, delta) { rotationRadians = rotationRadians || 0; delta = delta || Math.PI * 2 / sides; ctx.beginPath(); var initialAngle = rotationRadians; var angle = initialAngle; var computeCoordinates = function computeCoordinates() { var x = cx + (Math.sin(angle) * radius); var y = cy + (-Math.cos(angle) * radius); return [x, y]; }; var initialCoordinates = computeCoordinates(); var x = initialCoordinates[0]; var y = initialCoordinates[1]; ctx.moveTo(x, y); for (var idx = 0; idx < sides; idx++) { angle = (idx == sides - 1) ? initialAngle : (angle + delta); var coords = computeCoordinates(); ctx.lineTo(coords[0], coords[1]); } ctx.fill(); ctx.stroke(); }; /** * TODO(danvk): be more specific on the return type. * @param {number} sides * @param {number=} rotationRadians * @param {number=} delta * @return {Function} * @private */ var shapeFunction = function shapeFunction(sides, rotationRadians, delta) { return function shapedFunction(g, name, ctx, cx, cy, color, radius) { ctx.strokeStyle = color; ctx.fillStyle = "white"; regularShape(ctx, sides, radius, cx, cy, rotationRadians, delta); }; }; var customCircles = { TRIANGLE : shapeFunction(3), SQUARE : shapeFunction(4, Math.PI / 4), DIAMOND : shapeFunction(4), PENTAGON : shapeFunction(5), HEXAGON : shapeFunction(6), CIRCLE : function CIRCLE(g, name, ctx, cx, cy, color, radius) { ctx.beginPath(); ctx.strokeStyle = color; ctx.fillStyle = "white"; ctx.arc(cx, cy, radius, 0, 2 * Math.PI, false); ctx.fill(); ctx.stroke(); }, STAR : shapeFunction(5, 0, 4 * Math.PI / 5), PLUS : function PLUS(g, name, ctx, cx, cy, color, radius) { ctx.strokeStyle = color; ctx.beginPath(); ctx.moveTo(cx + radius, cy); ctx.lineTo(cx - radius, cy); ctx.closePath(); ctx.stroke(); ctx.beginPath(); ctx.moveTo(cx, cy + radius); ctx.lineTo(cx, cy - radius); ctx.closePath(); ctx.stroke(); }, EX : function EX(g, name, ctx, cx, cy, color, radius) { ctx.strokeStyle = color; ctx.beginPath(); ctx.moveTo(cx + radius, cy + radius); ctx.lineTo(cx - radius, cy - radius); ctx.closePath(); ctx.stroke(); ctx.beginPath(); ctx.moveTo(cx + radius, cy - radius); ctx.lineTo(cx - radius, cy + radius); ctx.closePath(); ctx.stroke(); } }; for (var k in customCircles) { if (!customCircles.hasOwnProperty(k)) continue; Dygraph.Circles[k] = customCircles[k]; } /* closure and loader wrapper */ Dygraph._require.add('dygraphs/src/extras/shapes.js', /* exports */ {}); })(); dygraphs-2.2.1/src/extras/smooth-plotter.js010064400000000000000000000116451437353256000157360ustar00/** * @license * Part of dygraphs, see top-level LICENSE.txt file * MIT-licenced: https://opensource.org/licenses/MIT */ /* loader wrapper to allow browser use and ES6 imports */ (function _extras_smoothPlotter_closure() { 'use strict'; var Dygraph; if (window.Dygraph) { Dygraph = window.Dygraph; } else if (typeof(module) !== 'undefined') { Dygraph = require('../dygraph'); if (typeof(Dygraph.NAME) === 'undefined' && typeof(Dygraph.default) !== 'undefined') Dygraph = Dygraph.default; } /* end of loader wrapper header */ /** * Given three sequential points, p0, p1 and p2, find the left and right * control points for p1. * * The three points are expected to have x and y properties. * * The alpha parameter controls the amount of smoothing. * If α=0, then both control points will be the same as p1 (i.e. no smoothing). * * Returns [l1x, l1y, r1x, r1y] * * It's guaranteed that the line from (l1x, l1y)-(r1x, r1y) passes through p1. * Unless allowFalseExtrema is set, then it's also guaranteed that: * l1y ∈ [p0.y, p1.y] * r1y ∈ [p1.y, p2.y] * * The basic algorithm is: * 1. Put the control points l1 and r1 α of the way down (p0, p1) and (p1, p2). * 2. Shift l1 and r2 so that the line l1–r1 passes through p1 * 3. Adjust to prevent false extrema while keeping p1 on the l1–r1 line. * * This is loosely based on the HighCharts algorithm. */ function getControlPoints(p0, p1, p2, opt_alpha, opt_allowFalseExtrema) { var alpha = (opt_alpha !== undefined) ? opt_alpha : 1/3; // 0=no smoothing, 1=crazy smoothing var allowFalseExtrema = opt_allowFalseExtrema || false; if (!p2) { return [p1.x, p1.y, null, null]; } // Step 1: Position the control points along each line segment. var l1x = (1 - alpha) * p1.x + alpha * p0.x, l1y = (1 - alpha) * p1.y + alpha * p0.y, r1x = (1 - alpha) * p1.x + alpha * p2.x, r1y = (1 - alpha) * p1.y + alpha * p2.y; // Step 2: shift the points up so that p1 is on the l1–r1 line. if (l1x != r1x) { // This can be derived w/ some basic algebra. var deltaY = p1.y - r1y - (p1.x - r1x) * (l1y - r1y) / (l1x - r1x); l1y += deltaY; r1y += deltaY; } // Step 3: correct to avoid false extrema. if (!allowFalseExtrema) { if (l1y > p0.y && l1y > p1.y) { l1y = Math.max(p0.y, p1.y); r1y = 2 * p1.y - l1y; } else if (l1y < p0.y && l1y < p1.y) { l1y = Math.min(p0.y, p1.y); r1y = 2 * p1.y - l1y; } if (r1y > p1.y && r1y > p2.y) { r1y = Math.max(p1.y, p2.y); l1y = 2 * p1.y - r1y; } else if (r1y < p1.y && r1y < p2.y) { r1y = Math.min(p1.y, p2.y); l1y = 2 * p1.y - r1y; } } return [l1x, l1y, r1x, r1y]; } // i.e. is none of (null, undefined, NaN) function isOK(x) { return !!x && !isNaN(x); }; // A plotter which uses splines to create a smooth curve. // See tests/plotters.html for a demo. // Can be controlled via smoothPlotter.smoothing function smoothPlotter(e) { var ctx = e.drawingContext, points = e.points; ctx.beginPath(); ctx.moveTo(points[0].canvasx, points[0].canvasy); // right control point for previous point var lastRightX = points[0].canvasx, lastRightY = points[0].canvasy; for (var i = 1; i < points.length; i++) { var p0 = points[i - 1], p1 = points[i], p2 = points[i + 1]; p0 = p0 && isOK(p0.canvasy) ? p0 : null; p1 = p1 && isOK(p1.canvasy) ? p1 : null; p2 = p2 && isOK(p2.canvasy) ? p2 : null; if (p0 && p1) { var controls = getControlPoints({x: p0.canvasx, y: p0.canvasy}, {x: p1.canvasx, y: p1.canvasy}, p2 && {x: p2.canvasx, y: p2.canvasy}, smoothPlotter.smoothing); // Uncomment to show the control points: // ctx.lineTo(lastRightX, lastRightY); // ctx.lineTo(controls[0], controls[1]); // ctx.lineTo(p1.canvasx, p1.canvasy); lastRightX = (lastRightX !== null) ? lastRightX : p0.canvasx; lastRightY = (lastRightY !== null) ? lastRightY : p0.canvasy; ctx.bezierCurveTo(lastRightX, lastRightY, controls[0], controls[1], p1.canvasx, p1.canvasy); lastRightX = controls[2]; lastRightY = controls[3]; } else if (p1) { // We're starting again after a missing point. ctx.moveTo(p1.canvasx, p1.canvasy); lastRightX = p1.canvasx; lastRightY = p1.canvasy; } else { lastRightX = lastRightY = null; } } ctx.stroke(); } smoothPlotter.smoothing = 1/3; smoothPlotter._getControlPoints = getControlPoints; // for testing // older versions exported a global. // This will be removed in the future. // The preferred way to access smoothPlotter is via Dygraph.smoothPlotter. window.smoothPlotter = smoothPlotter; Dygraph.smoothPlotter = smoothPlotter; /* closure and loader wrapper */ Dygraph._require.add('dygraphs/src/extras/smooth-plotter.js', /* exports */ {}); })(); dygraphs-2.2.1/src/extras/super-annotations.js010064400000000000000000000357441437353256000164350ustar00/** * @license * Copyright 2013 Dan Vanderkam (danvdk@gmail.com) * MIT-licenced: https://opensource.org/licenses/MIT * * Note: This plugin requires jQuery and jQuery UI Draggable. * * See high-level documentation at ../../docs/hairlines-annotations.pdf */ /* loader wrapper to allow browser use and ES6 imports */ (function _extras_superAnnotations_wrapper() { 'use strict'; var Dygraph; if (window.Dygraph) { Dygraph = window.Dygraph; } else if (typeof(module) !== 'undefined') { Dygraph = require('../dygraph'); if (typeof(Dygraph.NAME) === 'undefined' && typeof(Dygraph.default) !== 'undefined') Dygraph = Dygraph.default; } /* end of loader wrapper header */ Dygraph.Plugins.SuperAnnotations = (function _extras_superAnnotations_closure() { "use strict"; /** * These are just the basic requirements -- annotations can have whatever other * properties the code that displays them wants them to have. * * @typedef { * xval: number, // x-value (i.e. millis or a raw number) * series: string, // series name * yFrac: ?number, // y-positioning. Default is a few px above the point. * lineDiv: !Element // vertical div connecting point to info div. * infoDiv: !Element // div containing info about the annotation. * } Annotation */ var annotations = function annotations(opt_options) { /* @type {!Array.} */ this.annotations_ = []; // Used to detect resizes (which require the divs to be repositioned). this.lastWidth_ = -1; this.lastHeight = -1; this.dygraph_ = null; opt_options = opt_options || {}; this.defaultAnnotationProperties_ = $.extend({ 'text': 'Description' }, opt_options['defaultAnnotationProperties']); }; annotations.prototype.toString = function toString() { return "SuperAnnotations Plugin"; }; annotations.prototype.activate = function activate(g) { this.dygraph_ = g; this.annotations_ = []; return { didDrawChart: this.didDrawChart, pointClick: this.pointClick // TODO(danvk): implement in dygraphs }; }; annotations.prototype.detachLabels = function detachLabels() { for (var i = 0; i < this.annotations_.length; i++) { var a = this.annotations_[i]; $(a.lineDiv).remove(); $(a.infoDiv).remove(); this.annotations_[i] = null; } this.annotations_ = []; }; annotations.prototype.annotationWasDragged = function annotationWasDragged(a, event, ui) { var g = this.dygraph_; var area = g.getArea(); var oldYFrac = a.yFrac; var infoDiv = a.infoDiv; var newYFrac = ((infoDiv.offsetTop + infoDiv.offsetHeight) - area.y) / area.h; if (newYFrac == oldYFrac) return; a.yFrac = newYFrac; this.moveAnnotationToTop(a); this.updateAnnotationDivPositions(); this.updateAnnotationInfo(); $(this).triggerHandler('annotationMoved', { annotation: a, oldYFrac: oldYFrac, newYFrac: a.yFrac }); $(this).triggerHandler('annotationsChanged', {}); }; annotations.prototype.makeAnnotationEditable = function makeAnnotationEditable(a) { if (a.editable == true) return; this.moveAnnotationToTop(a); // Note: we have to fill out the HTML ourselves because // updateAnnotationInfo() won't touch editable annotations. a.editable = true; var editableTemplateDiv = $('#annotation-editable-template').get(0); a.infoDiv.innerHTML = this.getTemplateHTML(editableTemplateDiv, a); $(a.infoDiv).toggleClass('editable', !!a.editable); $(this).triggerHandler('beganEditAnnotation', a); }; // This creates the hairline object and returns it. // It does not position it and does not attach it to the chart. annotations.prototype.createAnnotation = function createAnnotation(a) { var self = this; var color = this.getColorForSeries_(a.series); var $lineDiv = $('
    ').css({ 'width': '1px', 'left': '3px', 'background': 'black', 'height': '100%', 'position': 'absolute', // TODO(danvk): use border-color here for consistency? 'background-color': color, 'z-index': 10 }).addClass('dygraph-annotation-line'); var $infoDiv = $('#annotation-template').clone().removeAttr('id').css({ 'position': 'absolute', 'border-color': color, 'z-index': 10 }) .show(); $.extend(a, { lineDiv: $lineDiv.get(0), infoDiv: $infoDiv.get(0) }); var that = this; $infoDiv.draggable({ 'start': function draggableStart(event, ui) { $(this).css({'bottom': ''}); a.isDragging = true; }, 'drag': function draggableDrag(event, ui) { self.annotationWasDragged(a, event, ui); }, 'stop': function draggableStop(event, ui) { $(this).css({'top': ''}); a.isDragging = false; self.updateAnnotationDivPositions(); }, 'axis': 'y', 'containment': 'parent' }); // TODO(danvk): use 'on' instead of delegate/dblclick $infoDiv.on('click', '.annotation-kill-button', function clickKill() { that.removeAnnotation(a); $(that).triggerHandler('annotationDeleted', a); $(that).triggerHandler('annotationsChanged', {}); }); $infoDiv.on('dblclick', function dblclick() { that.makeAnnotationEditable(a); }); $infoDiv.on('click', '.annotation-update', function clickUpdate() { self.extractUpdatedProperties_($infoDiv.get(0), a); a.editable = false; self.updateAnnotationInfo(); $(that).triggerHandler('annotationEdited', a); $(that).triggerHandler('annotationsChanged', {}); }); $infoDiv.on('click', '.annotation-cancel', function clickCancel() { a.editable = false; self.updateAnnotationInfo(); $(that).triggerHandler('cancelEditAnnotation', a); }); return a; }; // Find the index of a point in a series. // Returns a 2-element array, [row, col], which can be used with // dygraph.getValue() to get the value at this point. // Returns null if there's no match. annotations.prototype.findPointIndex_ = function findPointIndex_(series, xval) { var col = this.dygraph_.getLabels().indexOf(series); if (col == -1) return null; var lowIdx = 0, highIdx = this.dygraph_.numRows() - 1; while (lowIdx <= highIdx) { var idx = Math.floor((lowIdx + highIdx) / 2); var xAtIdx = this.dygraph_.getValue(idx, 0); if (xAtIdx == xval) { return [idx, col]; } else if (xAtIdx < xval) { lowIdx = idx + 1; } else { highIdx = idx - 1; } } return null; }; annotations.prototype.getColorForSeries_ = function getColorForSeries_(series) { var colors = this.dygraph_.getColors(); var col = this.dygraph_.getLabels().indexOf(series); if (col == -1) return null; return colors[(col - 1) % colors.length]; }; // Moves a hairline's divs to the top of the z-ordering. annotations.prototype.moveAnnotationToTop = function moveAnnotationToTop(a) { var div = this.dygraph_.graphDiv; $(a.infoDiv).appendTo(div); $(a.lineDiv).appendTo(div); var idx = this.annotations_.indexOf(a); this.annotations_.splice(idx, 1); this.annotations_.push(a); }; // Positions existing hairline divs. annotations.prototype.updateAnnotationDivPositions = function updateAnnotationDivPositions() { var layout = this.dygraph_.getArea(); var chartLeft = layout.x, chartRight = layout.x + layout.w; var chartTop = layout.y, chartBottom = layout.y + layout.h; var div = this.dygraph_.graphDiv; var pos = Dygraph.findPos(div); var box = [layout.x + pos.x, layout.y + pos.y]; box.push(box[0] + layout.w); box.push(box[1] + layout.h); var g = this.dygraph_; var that = this; $.each(this.annotations_, function annotationsLoop_(idx, a) { var row_col = that.findPointIndex_(a.series, a.xval); if (row_col == null) { $([a.lineDiv, a.infoDiv]).hide(); return; } else { // TODO(danvk): only do this if they're invisible? $([a.lineDiv, a.infoDiv]).show(); } var xy = g.toDomCoords(a.xval, g.getValue(row_col[0], row_col[1])); var x = xy[0], pointY = xy[1]; var lineHeight = 6; // TODO(danvk): option? var y = pointY; if (a.yFrac !== undefined) { y = layout.y + layout.h * a.yFrac; } else { y -= lineHeight; } var lineHeight = y < pointY ? (pointY - y) : (y - pointY - a.infoDiv.offsetHeight); $(a.lineDiv).css({ 'left': x + 'px', 'top': Math.min(y, pointY) + 'px', 'height': lineHeight + 'px' }); $(a.infoDiv).css({ 'left': x + 'px', }); if (!a.isDragging) { // jQuery UI draggable likes to set 'top', whereas superannotations sets // 'bottom'. Setting both will make the annotation grow and contract as // the user drags it, which looks bad. $(a.infoDiv).css({ 'bottom': (div.offsetHeight - y) + 'px' }) //.draggable("option", "containment", box); var visible = (x >= chartLeft && x <= chartRight) && (pointY >= chartTop && pointY <= chartBottom); $([a.infoDiv, a.lineDiv]).toggle(visible); } }); }; // Fills out the info div based on current coordinates. annotations.prototype.updateAnnotationInfo = function updateAnnotationInfo() { var g = this.dygraph_; var that = this; var templateDiv = $('#annotation-template').get(0); $.each(this.annotations_, function annotationsLoop_(idx, a) { // We should never update an editable div -- doing so may kill unsaved // edits to an annotation. $(a.infoDiv).toggleClass('editable', !!a.editable); if (a.editable) return; a.infoDiv.innerHTML = that.getTemplateHTML(templateDiv, a); }); }; /** * @param {!Annotation} a Internal annotation * @return {!PublicAnnotation} a view of the annotation for the public API. */ annotations.prototype.createPublicAnnotation_ = function createPublicAnnotation_(a, opt_props) { var displayAnnotation = $.extend({}, a, opt_props); delete displayAnnotation['infoDiv']; delete displayAnnotation['lineDiv']; delete displayAnnotation['isDragging']; delete displayAnnotation['editable']; return displayAnnotation; }; // Fill out a div using the values in the annotation object. // The div's html is expected to have text of the form "{{key}}" annotations.prototype.getTemplateHTML = function getTemplateHTML(div, a) { var g = this.dygraph_; var row_col = this.findPointIndex_(a.series, a.xval); if (row_col == null) return; // perhaps it's no longer a real point? var row = row_col[0]; var col = row_col[1]; var yOptView = g.optionsViewForAxis_('y1'); // TODO: support secondary, too var xOptView = g.optionsViewForAxis_('x'); var xvf = g.getOptionForAxis('valueFormatter', 'x'); var x = xvf.call(g, a.xval, xOptView); var y = g.getOption('valueFormatter', a.series).call( g, g.getValue(row, col), yOptView); var displayAnnotation = this.createPublicAnnotation_(a, {x:x, y:y}); var html = div.innerHTML; for (var k in displayAnnotation) { var v = displayAnnotation[k]; if (typeof(v) == 'object') continue; // e.g. infoDiv or lineDiv html = html.replace(new RegExp('\{\{' + k + '\}\}', 'g'), v); } return html; }; // Update the annotation object by looking for elements with a 'dg-ann-field' // attribute. For example, will have // its value placed in the 'text' attribute of the annotation. annotations.prototype.extractUpdatedProperties_ = function extractUpdatedProperties_(div, a) { $(div).find('[dg-ann-field]').each(function fieldLoop_(idx, el) { var k = $(el).attr('dg-ann-field'); var v = $(el).val(); a[k] = v; }); }; // After a resize, the hairline divs can get dettached from the chart. // This reattaches them. annotations.prototype.attachAnnotationsToChart_ = function attachAnnotationsToChart_() { var div = this.dygraph_.graphDiv; $.each(this.annotations_, function annotationsLoop_(idx, a) { // Re-attaching an editable div to the DOM can clear its focus. // This makes typing really difficult! if (a.editable) return; $([a.lineDiv, a.infoDiv]).appendTo(div); }); }; // Deletes a hairline and removes it from the chart. annotations.prototype.removeAnnotation = function removeAnnotation(a) { var idx = this.annotations_.indexOf(a); if (idx >= 0) { this.annotations_.splice(idx, 1); $([a.lineDiv, a.infoDiv]).remove(); } else { Dygraph.warn('Tried to remove non-existent annotation.'); } }; annotations.prototype.didDrawChart = function didDrawChart(e) { var g = e.dygraph; // Early out in the (common) case of zero annotations. if (this.annotations_.length === 0) return; this.updateAnnotationDivPositions(); this.attachAnnotationsToChart_(); this.updateAnnotationInfo(); }; annotations.prototype.pointClick = function pointClick(e) { // Prevent any other behavior based on this click, e.g. creation of a hairline. e.preventDefault(); var a = $.extend({}, this.defaultAnnotationProperties_, { series: e.point.name, xval: e.point.xval }); this.annotations_.push(this.createAnnotation(a)); this.updateAnnotationDivPositions(); this.updateAnnotationInfo(); this.attachAnnotationsToChart_(); $(this).triggerHandler('annotationCreated', a); $(this).triggerHandler('annotationsChanged', {}); // Annotations should begin life editable. this.makeAnnotationEditable(a); }; annotations.prototype.destroy = function destroy() { this.detachLabels(); }; // Public API /** * This is a restricted view of this.annotations_ which doesn't expose * implementation details like the line / info divs. * * @typedef { * xval: number, // x-value (i.e. millis or a raw number) * series: string, // series name * } PublicAnnotation */ /** * @return {!Array.} The current set of annotations, ordered * from back to front. */ annotations.prototype.get = function get() { var result = []; for (var i = 0; i < this.annotations_.length; i++) { result.push(this.createPublicAnnotation_(this.annotations_[i])); } return result; }; /** * Calling this will result in an annotationsChanged event being triggered, no * matter whether it consists of additions, deletions, moves or no changes at * all. * * @param {!Array.} annotations The new set of annotations, * ordered from back to front. */ annotations.prototype.set = function set(annotations) { // Re-use divs from the old annotations array so far as we can. // They're already correctly z-ordered. var anyCreated = false; for (var i = 0; i < annotations.length; i++) { var a = annotations[i]; if (this.annotations_.length > i) { // Only the divs need to be preserved. var oldA = this.annotations_[i]; this.annotations_[i] = $.extend({ infoDiv: oldA.infoDiv, lineDiv: oldA.lineDiv }, a); } else { this.annotations_.push(this.createAnnotation(a)); anyCreated = true; } } // If there are any remaining annotations, destroy them. while (annotations.length < this.annotations_.length) { this.removeAnnotation(this.annotations_[annotations.length]); } this.updateAnnotationDivPositions(); this.updateAnnotationInfo(); if (anyCreated) { this.attachAnnotationsToChart_(); } $(this).triggerHandler('annotationsChanged', {}); }; return annotations; })(); /* loader wrapper */ Dygraph._require.add('dygraphs/src/extras/super-annotations.js', /* exports */ {}); })(); dygraphs-2.2.1/src/extras/synchronizer.js010064400000000000000000000177451437353256000155020ustar00/** * @license * Part of dygraphs, see top-level LICENSE.txt file * MIT-licenced: https://opensource.org/licenses/MIT */ /** * Synchronize zooming and/or selections between a set of dygraphs. * * Usage: * * var g1 = new Dygraph(...), * g2 = new Dygraph(...), * ...; * var sync = Dygraph.synchronize(g1, g2, ...); * // charts are now synchronized * sync.detach(); * // charts are no longer synchronized * * You can set options using the last parameter, for example: * * var sync = Dygraph.synchronize(g1, g2, g3, { * selection: true, * zoom: true * }); * * The default is to synchronize both of these. * * Instead of passing one Dygraph object as each parameter, you may also pass an * array of dygraphs: * * var sync = Dygraph.synchronize([g1, g2, g3], { * selection: false, * zoom: true * }); * * You may also set `range: false` if you wish to only sync the x-axis. * The `range` option has no effect unless `zoom` is true (the default). */ /* loader wrapper to allow browser use and ES6 imports */ (function _extras_synchronizer_closure() { 'use strict'; var Dygraph; if (window.Dygraph) { Dygraph = window.Dygraph; } else if (typeof(module) !== 'undefined') { Dygraph = require('../dygraph'); if (typeof(Dygraph.NAME) === 'undefined' && typeof(Dygraph.default) !== 'undefined') Dygraph = Dygraph.default; } /* end of loader wrapper header */ var synchronize = function synchronize(/* dygraphs..., opts */) { if (arguments.length === 0) { throw 'Invalid invocation of Dygraph.synchronize(). Need >= 1 argument.'; } var OPTIONS = ['selection', 'zoom', 'range']; var opts = { selection: true, zoom: true, range: true }; var dygraphs = []; var prevCallbacks = []; var parseOpts = function parseOpts(obj) { if (!(obj instanceof Object)) { throw 'Last argument must be either Dygraph or Object.'; } else { for (var i = 0; i < OPTIONS.length; i++) { var optName = OPTIONS[i]; if (obj.hasOwnProperty(optName)) opts[optName] = obj[optName]; } } }; if (arguments[0] instanceof Dygraph) { // Arguments are Dygraph objects. for (var i = 0; i < arguments.length; i++) { if (arguments[i] instanceof Dygraph) { dygraphs.push(arguments[i]); } else { break; } } if (i < arguments.length - 1) { throw 'Invalid invocation of Dygraph.synchronize(). ' + 'All but the last argument must be Dygraph objects.'; } else if (i == arguments.length - 1) { parseOpts(arguments[arguments.length - 1]); } } else if (arguments[0].length) { // Invoked w/ list of dygraphs, options for (var i = 0; i < arguments[0].length; i++) { dygraphs.push(arguments[0][i]); } if (arguments.length == 2) { parseOpts(arguments[1]); } else if (arguments.length > 2) { throw 'Invalid invocation of Dygraph.synchronize(). ' + 'Expected two arguments: array and optional options argument.'; } // otherwise arguments.length == 1, which is fine. } else { throw 'Invalid invocation of Dygraph.synchronize(). ' + 'First parameter must be either Dygraph or list of Dygraphs.'; } if (dygraphs.length < 2) { throw 'Invalid invocation of Dygraph.synchronize(). ' + 'Need two or more dygraphs to synchronize.'; } var readycount = dygraphs.length; for (var i = 0; i < dygraphs.length; i++) { var g = dygraphs[i]; g.ready(function onReady_() { if (--readycount == 0) { // store original callbacks var callBackTypes = ['drawCallback', 'highlightCallback', 'unhighlightCallback']; for (var j = 0; j < dygraphs.length; j++) { if (!prevCallbacks[j]) { prevCallbacks[j] = {}; } for (var k = callBackTypes.length - 1; k >= 0; k--) { prevCallbacks[j][callBackTypes[k]] = dygraphs[j].getFunctionOption(callBackTypes[k]); } } // Listen for draw, highlight, unhighlight callbacks. if (opts.zoom) { attachZoomHandlers(dygraphs, opts, prevCallbacks); } if (opts.selection) { attachSelectionHandlers(dygraphs, prevCallbacks); } } }); } return { detach: function detach() { for (var i = 0; i < dygraphs.length; i++) { var g = dygraphs[i]; if (opts.zoom) { g.updateOptions({drawCallback: prevCallbacks[i].drawCallback}); } if (opts.selection) { g.updateOptions({ highlightCallback: prevCallbacks[i].highlightCallback, unhighlightCallback: prevCallbacks[i].unhighlightCallback }); } } // release references & make subsequent calls throw. dygraphs = null; opts = null; prevCallbacks = null; } }; }; function arraysAreEqual(a, b) { if (!Array.isArray(a) || !Array.isArray(b)) return false; var i = a.length; if (i !== b.length) return false; while (i--) { if (a[i] !== b[i]) return false; } return true; } function attachZoomHandlers(gs, syncOpts, prevCallbacks) { var block = false; for (var i = 0; i < gs.length; i++) { var g = gs[i]; g.updateOptions({ drawCallback: function synchronizer_drawCallback(me, initial) { if (block || initial) { // call the user’s drawCallback even if we are blocked for (let j = 0; j < gs.length; j++) { if (gs[j] == me) { if (prevCallbacks[j] && prevCallbacks[j].drawCallback) { prevCallbacks[j].drawCallback.apply(this, arguments); } break; } } return; } block = true; var opts = { dateWindow: me.xAxisRange() }; if (!me.isZoomed('x')) opts.dateWindow = null; if (syncOpts.range) opts.valueRange = me.yAxisRange(); for (let j = 0; j < gs.length; j++) { if (gs[j] == me) { if (prevCallbacks[j] && prevCallbacks[j].drawCallback) { prevCallbacks[j].drawCallback.apply(this, arguments); } continue; } // Only redraw if there are new options if (arraysAreEqual(opts.dateWindow, gs[j].getOption('dateWindow')) && (!syncOpts.range || arraysAreEqual(opts.valueRange, gs[j].getOption('valueRange')))) { continue; } gs[j].updateOptions(opts); } block = false; } }, true /* no need to redraw */); } } function attachSelectionHandlers(gs, prevCallbacks) { var block = false; for (var i = 0; i < gs.length; i++) { var g = gs[i]; g.updateOptions({ highlightCallback: function synchronizer_highlightCallback(event, x, points, row, seriesName) { if (block) return; block = true; var me = this; for (var i = 0; i < gs.length; i++) { if (me == gs[i]) { if (prevCallbacks[i] && prevCallbacks[i].highlightCallback) { prevCallbacks[i].highlightCallback.apply(this, arguments); } continue; } var idx = gs[i].getRowForX(x); if (idx !== null) { gs[i].setSelection(idx, seriesName, undefined, true); } } block = false; }, unhighlightCallback: function synchronizer_unhighlightCallback(event) { if (block) return; block = true; var me = this; for (var i = 0; i < gs.length; i++) { if (me == gs[i]) { if (prevCallbacks[i] && prevCallbacks[i].unhighlightCallback) { prevCallbacks[i].unhighlightCallback.apply(this, arguments); } continue; } gs[i].clearSelection(); } block = false; } }, true /* no need to redraw */); } } Dygraph.synchronize = synchronize; /* closure and loader wrapper */ Dygraph._require.add('dygraphs/src/extras/synchronizer.js', /* exports */ {}); })(); dygraphs-2.2.1/src/extras/unzoom.js010064400000000000000000000072111437353256000142570ustar00// Copyright (c) 2013 Google, Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. /* loader wrapper to allow browser use and ES6 imports */ (function _extras_unzoom_wrapper() { 'use strict'; var Dygraph; if (window.Dygraph) { Dygraph = window.Dygraph; } else if (typeof(module) !== 'undefined') { Dygraph = require('../dygraph'); if (typeof(Dygraph.NAME) === 'undefined' && typeof(Dygraph.default) !== 'undefined') Dygraph = Dygraph.default; } /* end of loader wrapper header */ /** * @fileoverview Plug-in for providing unzoom-on-hover. * * @author konigsberg@google.com (Robert Konigsberg) */ Dygraph.Plugins.Unzoom = (function _extras_unzoom_closure() { "use strict"; /** * Create a new instance. * * @constructor */ var unzoom = function unzoom() { this.button_ = null; // True when the mouse is over the canvas. Must be tracked // because the unzoom button state can change even when the // mouse-over state hasn't. this.over_ = false; }; unzoom.prototype.toString = function toString() { return 'Unzoom Plugin'; }; unzoom.prototype.activate = function activate(g) { return { willDrawChart: this.willDrawChart }; }; unzoom.prototype.willDrawChart = function willDrawChart(e) { var g = e.dygraph; if (this.button_ !== null) { // short-circuit: show the button only when we're moused over, and zoomed in. var showButton = g.isZoomed() && this.over_; this.show(showButton); return; } this.button_ = document.createElement('button'); this.button_.innerHTML = 'Reset Zoom'; this.button_.style.display = 'none'; this.button_.style.position = 'absolute'; var area = g.plotter_.area; this.button_.style.top = (area.y + 4) + 'px'; this.button_.style.left = (area.x + 4) + 'px'; this.button_.style.zIndex = 11; var parent = g.graphDiv; parent.insertBefore(this.button_, parent.firstChild); var self = this; this.button_.onclick = function onclick() { g.resetZoom(); }; g.addAndTrackEvent(parent, 'mouseover', function mouseover() { if (g.isZoomed()) { self.show(true); } self.over_ = true; }); g.addAndTrackEvent(parent, 'mouseout', function mouseout() { self.show(false); self.over_ = false; }); }; unzoom.prototype.show = function show(enabled) { this.button_.style.display = enabled ? '' : 'none'; }; unzoom.prototype.destroy = function destroy() { this.button_.parentElement.removeChild(this.button_); }; return unzoom; })(); /* loader wrapper */ Dygraph._require.add('dygraphs/src/extras/unzoom.js', /* exports */ {}); })(); dygraphs-2.2.1/src/iframe-tarp.js010064400000000000000000000040351437353256000136320ustar00/** * To create a "drag" interaction, you typically register a mousedown event * handler on the element where the drag begins. In that handler, you register a * mouseup handler on the window to determine when the mouse is released, * wherever that release happens. This works well, except when the user releases * the mouse over an off-domain iframe. In that case, the mouseup event is * handled by the iframe and never bubbles up to the window handler. * * To deal with this issue, we cover iframes with high z-index divs to make sure * they don't capture mouseup. * * Usage: * element.addEventListener('mousedown', function() { * var tarper = new IFrameTarp(); * tarper.cover(); * var mouseUpHandler = function() { * ... * window.removeEventListener(mouseUpHandler); * tarper.uncover(); * }; * window.addEventListener('mouseup', mouseUpHandler); * }); * * @constructor */ import * as utils from './dygraph-utils'; function IFrameTarp() { /** @type {Array.} */ this.tarps = []; } /** * Find all the iframes in the document and cover them with high z-index * transparent divs. */ IFrameTarp.prototype.cover = function() { var iframes = document.getElementsByTagName("iframe"); for (var i = 0; i < iframes.length; i++) { var iframe = iframes[i]; var pos = utils.findPos(iframe), x = pos.x, y = pos.y, width = iframe.offsetWidth, height = iframe.offsetHeight; var div = document.createElement("div"); div.style.position = "absolute"; div.style.left = x + 'px'; div.style.top = y + 'px'; div.style.width = width + 'px'; div.style.height = height + 'px'; div.style.zIndex = 999; document.body.appendChild(div); this.tarps.push(div); } }; /** * Remove all the iframe covers. You should call this in a mouseup handler. */ IFrameTarp.prototype.uncover = function() { for (var i = 0; i < this.tarps.length; i++) { this.tarps[i].parentNode.removeChild(this.tarps[i]); } this.tarps = []; }; export default IFrameTarp; dygraphs-2.2.1/src/plugins/annotations.js010064400000000000000000000117011437353256000154370ustar00/** * @license * Copyright 2012 Dan Vanderkam (danvdk@gmail.com) * MIT-licenced: https://opensource.org/licenses/MIT */ /*global Dygraph:false */ "use strict"; /** Current bits of jankiness: - Uses dygraph.layout_ to get the parsed annotations. - Uses dygraph.plotter_.area It would be nice if the plugin didn't require so much special support inside the core dygraphs classes, but annotations involve quite a bit of parsing and layout. TODO(danvk): cache DOM elements. */ var annotations = function() { this.annotations_ = []; }; annotations.prototype.toString = function() { return "Annotations Plugin"; }; annotations.prototype.activate = function(g) { return { clearChart: this.clearChart, didDrawChart: this.didDrawChart }; }; annotations.prototype.detachLabels = function() { for (var i = 0; i < this.annotations_.length; i++) { var a = this.annotations_[i]; if (a.parentNode) a.parentNode.removeChild(a); this.annotations_[i] = null; } this.annotations_ = []; }; annotations.prototype.clearChart = function(e) { this.detachLabels(); }; annotations.prototype.didDrawChart = function(e) { var g = e.dygraph; // Early out in the (common) case of zero annotations. var points = g.layout_.annotated_points; if (!points || points.length === 0) return; var containerDiv = e.canvas.parentNode; var bindEvt = function(eventName, classEventName, pt) { return function(annotation_event) { var a = pt.annotation; if (a.hasOwnProperty(eventName)) { a[eventName](a, pt, g, annotation_event); } else if (g.getOption(classEventName)) { g.getOption(classEventName)(a, pt, g, annotation_event ); } }; }; // Add the annotations one-by-one. var area = e.dygraph.getArea(); // x-coord to sum of previous annotation's heights (used for stacking). var xToUsedHeight = {}; for (var i = 0; i < points.length; i++) { var p = points[i]; if (p.canvasx < area.x || p.canvasx > area.x + area.w || p.canvasy < area.y || p.canvasy > area.y + area.h) { continue; } var a = p.annotation; var tick_height = 6; if (a.hasOwnProperty("tickHeight")) { tick_height = a.tickHeight; } // TODO: deprecate axisLabelFontSize in favor of CSS var div = document.createElement("div"); div.style['fontSize'] = g.getOption('axisLabelFontSize') + "px"; var className = 'dygraph-annotation'; if (!a.hasOwnProperty('icon')) { // camelCase class names are deprecated. className += ' dygraphDefaultAnnotation dygraph-default-annotation'; } if (a.hasOwnProperty('cssClass')) { className += " " + a.cssClass; } div.className = className; var width = a.hasOwnProperty('width') ? a.width : 16; var height = a.hasOwnProperty('height') ? a.height : 16; if (a.hasOwnProperty('icon')) { var img = document.createElement("img"); img.src = a.icon; img.width = width; img.height = height; div.appendChild(img); } else if (p.annotation.hasOwnProperty('shortText')) { div.appendChild(document.createTextNode(p.annotation.shortText)); } var left = p.canvasx - width / 2; div.style.left = left + "px"; var divTop = 0; if (a.attachAtBottom) { var y = (area.y + area.h - height - tick_height); if (xToUsedHeight[left]) { y -= xToUsedHeight[left]; } else { xToUsedHeight[left] = 0; } xToUsedHeight[left] += (tick_height + height); divTop = y; } else { divTop = p.canvasy - height - tick_height; } div.style.top = divTop + "px"; div.style.width = width + "px"; div.style.height = height + "px"; div.title = p.annotation.text; div.style.color = g.colorsMap_[p.name]; div.style.borderColor = g.colorsMap_[p.name]; a.div = div; g.addAndTrackEvent(div, 'click', bindEvt('clickHandler', 'annotationClickHandler', p, this)); g.addAndTrackEvent(div, 'mouseover', bindEvt('mouseOverHandler', 'annotationMouseOverHandler', p, this)); g.addAndTrackEvent(div, 'mouseout', bindEvt('mouseOutHandler', 'annotationMouseOutHandler', p, this)); g.addAndTrackEvent(div, 'dblclick', bindEvt('dblClickHandler', 'annotationDblClickHandler', p, this)); containerDiv.appendChild(div); this.annotations_.push(div); var ctx = e.drawingContext; ctx.save(); ctx.strokeStyle = a.hasOwnProperty('tickColor') ? a.tickColor : g.colorsMap_[p.name]; ctx.lineWidth = a.hasOwnProperty('tickWidth') ? a.tickWidth : g.getOption('strokeWidth'); ctx.beginPath(); if (!a.attachAtBottom) { ctx.moveTo(p.canvasx, p.canvasy); ctx.lineTo(p.canvasx, p.canvasy - 2 - tick_height); } else { var y = divTop + height; ctx.moveTo(p.canvasx, y); ctx.lineTo(p.canvasx, y + tick_height); } ctx.closePath(); ctx.stroke(); ctx.restore(); } }; annotations.prototype.destroy = function() { this.detachLabels(); }; export default annotations; dygraphs-2.2.1/src/plugins/axes.js010064400000000000000000000226041437353256000140460ustar00/** * @license * Copyright 2012 Dan Vanderkam (danvdk@gmail.com) * MIT-licenced: https://opensource.org/licenses/MIT */ /*global Dygraph:false */ 'use strict'; /* Bits of jankiness: - Direct layout access - Direct area access - Should include calculation of ticks, not just the drawing. Options left to make axis-friendly. ('drawAxesAtZero') ('xAxisHeight') */ import * as utils from '../dygraph-utils'; /** * Draws the axes. This includes the labels on the x- and y-axes, as well * as the tick marks on the axes. * It does _not_ draw the grid lines which span the entire chart. */ var axes = function() { this.xlabels_ = []; this.ylabels_ = []; }; axes.prototype.toString = function() { return 'Axes Plugin'; }; axes.prototype.activate = function(g) { return { layout: this.layout, clearChart: this.clearChart, willDrawChart: this.willDrawChart }; }; axes.prototype.layout = function(e) { var g = e.dygraph; if (g.getOptionForAxis('drawAxis', 'y')) { var w = g.getOptionForAxis('axisLabelWidth', 'y') + 2 * g.getOptionForAxis('axisTickSize', 'y'); e.reserveSpaceLeft(w); } if (g.getOptionForAxis('drawAxis', 'x')) { var h; // NOTE: I think this is probably broken now, since g.getOption() now // hits the dictionary. (That is, g.getOption('xAxisHeight') now always // has a value.) if (g.getOption('xAxisHeight')) { h = g.getOption('xAxisHeight'); } else { h = g.getOptionForAxis('axisLabelFontSize', 'x') + 2 * g.getOptionForAxis('axisTickSize', 'x'); } e.reserveSpaceBottom(h); } if (g.numAxes() == 2) { if (g.getOptionForAxis('drawAxis', 'y2')) { var w = g.getOptionForAxis('axisLabelWidth', 'y2') + 2 * g.getOptionForAxis('axisTickSize', 'y2'); e.reserveSpaceRight(w); } } else if (g.numAxes() > 2) { g.error('Only two y-axes are supported at this time. (Trying ' + 'to use ' + g.numAxes() + ')'); } }; axes.prototype.detachLabels = function() { function removeArray(ary) { for (var i = 0; i < ary.length; i++) { var el = ary[i]; if (el.parentNode) el.parentNode.removeChild(el); } } removeArray(this.xlabels_); removeArray(this.ylabels_); this.xlabels_ = []; this.ylabels_ = []; }; axes.prototype.clearChart = function(e) { this.detachLabels(); }; axes.prototype.willDrawChart = function(e) { var g = e.dygraph; if (!g.getOptionForAxis('drawAxis', 'x') && !g.getOptionForAxis('drawAxis', 'y') && !g.getOptionForAxis('drawAxis', 'y2')) { return; } // Round pixels to half-integer boundaries for crisper drawing. function halfUp(x) { return Math.round(x) + 0.5; } function halfDown(y){ return Math.round(y) - 0.5; } var context = e.drawingContext; var containerDiv = e.canvas.parentNode; var canvasWidth = g.width_; // e.canvas.width is affected by pixel ratio. var canvasHeight = g.height_; var label, x, y, tick, i; var makeLabelStyle = function(axis) { return { position: 'absolute', fontSize: g.getOptionForAxis('axisLabelFontSize', axis) + 'px', width: g.getOptionForAxis('axisLabelWidth', axis) + 'px', }; }; var labelStyles = { x: makeLabelStyle('x'), y: makeLabelStyle('y'), y2: makeLabelStyle('y2') }; var makeDiv = function(txt, axis, prec_axis) { /* * This seems to be called with the following three sets of axis/prec_axis: * x: undefined * y: y1 * y: y2 */ var div = document.createElement('div'); var labelStyle = labelStyles[prec_axis == 'y2' ? 'y2' : axis]; utils.update(div.style, labelStyle); // TODO: combine outer & inner divs var inner_div = document.createElement('div'); inner_div.className = 'dygraph-axis-label' + ' dygraph-axis-label-' + axis + (prec_axis ? ' dygraph-axis-label-' + prec_axis : ''); inner_div.innerHTML = txt; div.appendChild(inner_div); return div; }; // axis lines context.save(); var layout = g.layout_; var area = e.dygraph.plotter_.area; // Helper for repeated axis-option accesses. var makeOptionGetter = function(axis) { return function(option) { return g.getOptionForAxis(option, axis); }; }; const that = this; if (g.getOptionForAxis('drawAxis', 'y') || (g.numAxes() == 2 && g.getOptionForAxis('drawAxis', 'y2'))) { if (layout.yticks && layout.yticks.length > 0) { var num_axes = g.numAxes(); var getOptions = [makeOptionGetter('y'), makeOptionGetter('y2')]; layout.yticks.forEach(function (tick) { if (tick.label === undefined) return; // this tick only has a grid line. x = area.x; var sgn = 1; var prec_axis = 'y1'; var getAxisOption = getOptions[0]; if (tick.axis == 1) { // right-side y-axis x = area.x + area.w; sgn = -1; prec_axis = 'y2'; getAxisOption = getOptions[1]; } if (!getAxisOption('drawAxis')) return; var fontSize = getAxisOption('axisLabelFontSize'); y = area.y + tick.pos * area.h; /* Tick marks are currently clipped, so don't bother drawing them. context.beginPath(); context.moveTo(halfUp(x), halfDown(y)); context.lineTo(halfUp(x - sgn * that.attr_('axisTickSize')), halfDown(y)); context.closePath(); context.stroke(); */ label = makeDiv(tick.label, 'y', num_axes == 2 ? prec_axis : null); var top = (y - fontSize / 2); if (top < 0) top = 0; if (top + fontSize + 3 > canvasHeight) { label.style.bottom = '0'; } else { // The lowest tick on the y-axis often overlaps with the leftmost // tick on the x-axis. Shift the bottom tick up a little bit to // compensate if necessary. label.style.top = Math.min(top, canvasHeight - (2 * fontSize)) + 'px'; } // TODO: replace these with css classes? if (tick.axis === 0) { label.style.left = (area.x - getAxisOption('axisLabelWidth') - getAxisOption('axisTickSize')) + 'px'; label.style.textAlign = 'right'; } else if (tick.axis == 1) { label.style.left = (area.x + area.w + getAxisOption('axisTickSize')) + 'px'; label.style.textAlign = 'left'; } label.style.width = getAxisOption('axisLabelWidth') + 'px'; containerDiv.appendChild(label); that.ylabels_.push(label); }); } // draw a vertical line on the left to separate the chart from the labels. var axisX; if (g.getOption('drawAxesAtZero')) { var r = g.toPercentXCoord(0); if (r > 1 || r < 0 || isNaN(r)) r = 0; axisX = halfUp(area.x + r * area.w); } else { axisX = halfUp(area.x); } context.strokeStyle = g.getOptionForAxis('axisLineColor', 'y'); context.lineWidth = g.getOptionForAxis('axisLineWidth', 'y'); context.beginPath(); context.moveTo(axisX, halfDown(area.y)); context.lineTo(axisX, halfDown(area.y + area.h)); context.closePath(); context.stroke(); // if there's a secondary y-axis, draw a vertical line for that, too. if (g.numAxes() == 2 && g.getOptionForAxis('drawAxis', 'y2')) { context.strokeStyle = g.getOptionForAxis('axisLineColor', 'y2'); context.lineWidth = g.getOptionForAxis('axisLineWidth', 'y2'); context.beginPath(); context.moveTo(halfDown(area.x + area.w), halfDown(area.y)); context.lineTo(halfDown(area.x + area.w), halfDown(area.y + area.h)); context.closePath(); context.stroke(); } } if (g.getOptionForAxis('drawAxis', 'x')) { if (layout.xticks) { var getAxisOption = makeOptionGetter('x'); layout.xticks.forEach(function (tick) { if (tick.label === undefined) return; // this tick only has a grid line. x = area.x + tick.pos * area.w; y = area.y + area.h; /* Tick marks are currently clipped, so don't bother drawing them. context.beginPath(); context.moveTo(halfUp(x), halfDown(y)); context.lineTo(halfUp(x), halfDown(y + that.attr_('axisTickSize'))); context.closePath(); context.stroke(); */ label = makeDiv(tick.label, 'x'); label.style.textAlign = 'center'; label.style.top = (y + getAxisOption('axisTickSize')) + 'px'; var left = (x - getAxisOption('axisLabelWidth')/2); if (left + getAxisOption('axisLabelWidth') > canvasWidth) { left = canvasWidth - getAxisOption('axisLabelWidth'); label.style.textAlign = 'right'; } if (left < 0) { left = 0; label.style.textAlign = 'left'; } label.style.left = left + 'px'; label.style.width = getAxisOption('axisLabelWidth') + 'px'; containerDiv.appendChild(label); that.xlabels_.push(label); }); } context.strokeStyle = g.getOptionForAxis('axisLineColor', 'x'); context.lineWidth = g.getOptionForAxis('axisLineWidth', 'x'); context.beginPath(); var axisY; if (g.getOption('drawAxesAtZero')) { var r = g.toPercentYCoord(0, 0); if (r > 1 || r < 0) r = 1; axisY = halfDown(area.y + r * area.h); } else { axisY = halfDown(area.y + area.h); } context.moveTo(halfUp(area.x), axisY); context.lineTo(halfUp(area.x + area.w), axisY); context.closePath(); context.stroke(); } context.restore(); }; export default axes; dygraphs-2.2.1/src/plugins/chart-labels.js010064400000000000000000000123621437353256000154470ustar00/** * @license * Copyright 2012 Dan Vanderkam (danvdk@gmail.com) * MIT-licenced: https://opensource.org/licenses/MIT */ /*global Dygraph:false */ "use strict"; // TODO(danvk): move chart label options out of dygraphs and into the plugin. // TODO(danvk): only tear down & rebuild the DIVs when it's necessary. var chart_labels = function() { this.title_div_ = null; this.xlabel_div_ = null; this.ylabel_div_ = null; this.y2label_div_ = null; }; chart_labels.prototype.toString = function() { return "ChartLabels Plugin"; }; chart_labels.prototype.activate = function(g) { return { layout: this.layout, // clearChart: this.clearChart, didDrawChart: this.didDrawChart }; }; // QUESTION: should there be a plugin-utils.js? var createDivInRect = function(r) { var div = document.createElement('div'); div.style.position = 'absolute'; div.style.left = r.x + 'px'; div.style.top = r.y + 'px'; div.style.width = r.w + 'px'; div.style.height = r.h + 'px'; return div; }; // Detach and null out any existing nodes. chart_labels.prototype.detachLabels_ = function() { var els = [ this.title_div_, this.xlabel_div_, this.ylabel_div_, this.y2label_div_ ]; for (var i = 0; i < els.length; i++) { var el = els[i]; if (!el) continue; if (el.parentNode) el.parentNode.removeChild(el); } this.title_div_ = null; this.xlabel_div_ = null; this.ylabel_div_ = null; this.y2label_div_ = null; }; var createRotatedDiv = function(g, box, axis, classes, html) { // TODO(danvk): is this outer div actually necessary? var div = document.createElement("div"); div.style.position = 'absolute'; if (axis == 1) { // NOTE: this is cheating. Should be positioned relative to the box. div.style.left = '0px'; } else { div.style.left = box.x + 'px'; } div.style.top = box.y + 'px'; div.style.width = box.w + 'px'; div.style.height = box.h + 'px'; div.style.fontSize = (g.getOption('yLabelWidth') - 2) + 'px'; var inner_div = document.createElement("div"); inner_div.style.position = 'absolute'; inner_div.style.width = box.h + 'px'; inner_div.style.height = box.w + 'px'; inner_div.style.top = (box.h / 2 - box.w / 2) + 'px'; inner_div.style.left = (box.w / 2 - box.h / 2) + 'px'; // TODO: combine inner_div and class_div. inner_div.className = 'dygraph-label-rotate-' + (axis == 1 ? 'right' : 'left'); var class_div = document.createElement("div"); class_div.className = classes; class_div.innerHTML = html; inner_div.appendChild(class_div); div.appendChild(inner_div); return div; }; chart_labels.prototype.layout = function(e) { this.detachLabels_(); var g = e.dygraph; var div = e.chart_div; if (g.getOption('title')) { // QUESTION: should this return an absolutely-positioned div instead? var title_rect = e.reserveSpaceTop(g.getOption('titleHeight')); this.title_div_ = createDivInRect(title_rect); this.title_div_.style.fontSize = (g.getOption('titleHeight') - 8) + 'px'; var class_div = document.createElement("div"); class_div.className = 'dygraph-label dygraph-title'; class_div.innerHTML = g.getOption('title'); this.title_div_.appendChild(class_div); div.appendChild(this.title_div_); } if (g.getOption('xlabel')) { var x_rect = e.reserveSpaceBottom(g.getOption('xLabelHeight')); this.xlabel_div_ = createDivInRect(x_rect); this.xlabel_div_.style.fontSize = (g.getOption('xLabelHeight') - 2) + 'px'; var class_div = document.createElement("div"); class_div.className = 'dygraph-label dygraph-xlabel'; class_div.innerHTML = g.getOption('xlabel'); this.xlabel_div_.appendChild(class_div); div.appendChild(this.xlabel_div_); } if (g.getOption('ylabel')) { // It would make sense to shift the chart here to make room for the y-axis // label, but the default yAxisLabelWidth is large enough that this results // in overly-padded charts. The y-axis label should fit fine. If it // doesn't, the yAxisLabelWidth option can be increased. var y_rect = e.reserveSpaceLeft(0); this.ylabel_div_ = createRotatedDiv( g, y_rect, 1, // primary (left) y-axis 'dygraph-label dygraph-ylabel', g.getOption('ylabel')); div.appendChild(this.ylabel_div_); } if (g.getOption('y2label') && g.numAxes() == 2) { // same logic applies here as for ylabel. var y2_rect = e.reserveSpaceRight(0); this.y2label_div_ = createRotatedDiv( g, y2_rect, 2, // secondary (right) y-axis 'dygraph-label dygraph-y2label', g.getOption('y2label')); div.appendChild(this.y2label_div_); } }; chart_labels.prototype.didDrawChart = function(e) { var g = e.dygraph; if (this.title_div_) { this.title_div_.children[0].innerHTML = g.getOption('title'); } if (this.xlabel_div_) { this.xlabel_div_.children[0].innerHTML = g.getOption('xlabel'); } if (this.ylabel_div_) { this.ylabel_div_.children[0].children[0].innerHTML = g.getOption('ylabel'); } if (this.y2label_div_) { this.y2label_div_.children[0].children[0].innerHTML = g.getOption('y2label'); } }; chart_labels.prototype.clearChart = function() { }; chart_labels.prototype.destroy = function() { this.detachLabels_(); }; export default chart_labels; dygraphs-2.2.1/src/plugins/grid.js010064400000000000000000000061231437353256000140310ustar00/** * @license * Copyright 2012 Dan Vanderkam (danvdk@gmail.com) * MIT-licenced: https://opensource.org/licenses/MIT */ /*global Dygraph:false */ /* Current bits of jankiness: - Direct layout access - Direct area access */ "use strict"; /** * Draws the gridlines, i.e. the gray horizontal & vertical lines running the * length of the chart. * * @constructor */ var grid = function() { }; grid.prototype.toString = function() { return "Gridline Plugin"; }; grid.prototype.activate = function(g) { return { willDrawChart: this.willDrawChart }; }; grid.prototype.willDrawChart = function(e) { // Draw the new X/Y grid. Lines appear crisper when pixels are rounded to // half-integers. This prevents them from drawing in two rows/cols. var g = e.dygraph; var ctx = e.drawingContext; var layout = g.layout_; var area = e.dygraph.plotter_.area; function halfUp(x) { return Math.round(x) + 0.5; } function halfDown(y){ return Math.round(y) - 0.5; } var x, y, i, ticks; if (g.getOptionForAxis('drawGrid', 'y')) { var axes = ["y", "y2"]; var strokeStyles = [], lineWidths = [], drawGrid = [], stroking = [], strokePattern = []; for (var i = 0; i < axes.length; i++) { drawGrid[i] = g.getOptionForAxis('drawGrid', axes[i]); if (drawGrid[i]) { strokeStyles[i] = g.getOptionForAxis('gridLineColor', axes[i]); lineWidths[i] = g.getOptionForAxis('gridLineWidth', axes[i]); strokePattern[i] = g.getOptionForAxis('gridLinePattern', axes[i]); stroking[i] = strokePattern[i] && (strokePattern[i].length >= 2); } } ticks = layout.yticks; ctx.save(); // draw grids for the different y axes ticks.forEach(tick => { if (!tick.has_tick) return; var axis = tick.axis; if (drawGrid[axis]) { ctx.save(); if (stroking[axis]) { if (ctx.setLineDash) ctx.setLineDash(strokePattern[axis]); } ctx.strokeStyle = strokeStyles[axis]; ctx.lineWidth = lineWidths[axis]; x = halfUp(area.x); y = halfDown(area.y + tick.pos * area.h); ctx.beginPath(); ctx.moveTo(x, y); ctx.lineTo(x + area.w, y); ctx.stroke(); ctx.restore(); } }); ctx.restore(); } // draw grid for x axis if (g.getOptionForAxis('drawGrid', 'x')) { ticks = layout.xticks; ctx.save(); var strokePattern = g.getOptionForAxis('gridLinePattern', 'x'); var stroking = strokePattern && (strokePattern.length >= 2); if (stroking) { if (ctx.setLineDash) ctx.setLineDash(strokePattern); } ctx.strokeStyle = g.getOptionForAxis('gridLineColor', 'x'); ctx.lineWidth = g.getOptionForAxis('gridLineWidth', 'x'); ticks.forEach(tick => { if (!tick.has_tick) return; x = halfUp(area.x + tick.pos * area.w); y = halfDown(area.y + area.h); ctx.beginPath(); ctx.moveTo(x, y); ctx.lineTo(x, area.y); ctx.stroke(); }); if (stroking) { if (ctx.setLineDash) ctx.setLineDash([]); } ctx.restore(); } }; grid.prototype.destroy = function() { }; export default grid; dygraphs-2.2.1/src/plugins/legend.js010064400000000000000000000322621437353256000143450ustar00/** * @license * Copyright 2012 Dan Vanderkam (danvdk@gmail.com) * MIT-licenced: https://opensource.org/licenses/MIT */ /*global Dygraph:false */ /* Current bits of jankiness: - Uses two private APIs: 1. Dygraph.optionsViewForAxis_ 2. dygraph.plotter_.area - Registers for a "predraw" event, which should be renamed. - I call calculateEmWidthInDiv more often than needed. */ /*global Dygraph:false */ "use strict"; import * as utils from '../dygraph-utils'; /** * Creates the legend, which appears when the user hovers over the chart. * The legend can be either a user-specified or generated div. * * @constructor */ var Legend = function() { this.legend_div_ = null; this.is_generated_div_ = false; // do we own this div, or was it user-specified? }; Legend.prototype.toString = function() { return "Legend Plugin"; }; /** * This is called during the dygraph constructor, after options have been set * but before the data is available. * * Proper tasks to do here include: * - Reading your own options * - DOM manipulation * - Registering event listeners * * @param {Dygraph} g Graph instance. * @return {object.} Mapping of event names to callbacks. */ Legend.prototype.activate = function(g) { var div; var userLabelsDiv = g.getOption('labelsDiv'); if (userLabelsDiv && null !== userLabelsDiv) { if (typeof(userLabelsDiv) == "string" || userLabelsDiv instanceof String) { div = document.getElementById(userLabelsDiv); } else { div = userLabelsDiv; } } else { div = document.createElement("div"); div.className = "dygraph-legend"; // TODO(danvk): come up with a cleaner way to expose this. g.graphDiv.appendChild(div); this.is_generated_div_ = true; } this.legend_div_ = div; this.one_em_width_ = 10; // just a guess, will be updated. return { select: this.select, deselect: this.deselect, // TODO(danvk): rethink the name "predraw" before we commit to it in any API. predraw: this.predraw, didDrawChart: this.didDrawChart }; }; // Needed for dashed lines. var calculateEmWidthInDiv = function(div) { var sizeSpan = document.createElement('span'); sizeSpan.setAttribute('style', 'margin: 0; padding: 0 0 0 1em; border: 0;'); div.appendChild(sizeSpan); var oneEmWidth=sizeSpan.offsetWidth; div.removeChild(sizeSpan); return oneEmWidth; }; var escapeHTML = function(str) { return str.replace(/&/g, "&").replace(/"/g, """).replace(//g, ">"); }; Legend.prototype.select = function(e) { var xValue = e.selectedX; var points = e.selectedPoints; var row = e.selectedRow; var legendMode = e.dygraph.getOption('legend'); if (legendMode === 'never') { this.legend_div_.style.display = 'none'; return; } var html = Legend.generateLegendHTML(e.dygraph, xValue, points, this.one_em_width_, row); if (html instanceof Node && html.nodeType === Node.DOCUMENT_FRAGMENT_NODE) { this.legend_div_.innerHTML = ''; this.legend_div_.appendChild(html); } else this.legend_div_.innerHTML = html; // must be done now so offsetWidth isn’t 0… this.legend_div_.style.display = ''; if (legendMode === 'follow') { // create floating legend div var area = e.dygraph.plotter_.area; var labelsDivWidth = this.legend_div_.offsetWidth; var yAxisLabelWidth = e.dygraph.getOptionForAxis('axisLabelWidth', 'y'); // find the closest data point by checking the currently highlighted series, // or fall back to using the first data point available var highlightSeries = e.dygraph.getHighlightSeries() var point; if (highlightSeries) { point = points.find(p => p.name === highlightSeries); if (!point) point = points[0]; } else point = points[0]; // determine floating [left, top] coordinates of the legend div // within the plotter_ area // offset 50 px to the right and down from the first selection point // 50 px is guess based on mouse cursor size const followOffsetX = e.dygraph.getNumericOption('legendFollowOffsetX'); const followOffsetY = e.dygraph.getNumericOption('legendFollowOffsetY'); var leftLegend = point.x * area.w + followOffsetX; var topLegend = point.y * area.h + followOffsetY; // if legend floats to end of the chart area, it flips to the other // side of the selection point if ((leftLegend + labelsDivWidth + 1) > area.w) { leftLegend = leftLegend - 2 * followOffsetX - labelsDivWidth - (yAxisLabelWidth - area.x); } this.legend_div_.style.left = yAxisLabelWidth + leftLegend + "px"; this.legend_div_.style.top = topLegend + "px"; } else if (legendMode === 'onmouseover' && this.is_generated_div_) { // synchronise this with Legend.prototype.predraw below var area = e.dygraph.plotter_.area; var labelsDivWidth = this.legend_div_.offsetWidth; this.legend_div_.style.left = area.x + area.w - labelsDivWidth - 1 + "px"; this.legend_div_.style.top = area.y + "px"; } }; Legend.prototype.deselect = function(e) { var legendMode = e.dygraph.getOption('legend'); if (legendMode !== 'always') { this.legend_div_.style.display = "none"; } // Have to do this every time, since styles might have changed. var oneEmWidth = calculateEmWidthInDiv(this.legend_div_); this.one_em_width_ = oneEmWidth; var html = Legend.generateLegendHTML(e.dygraph, undefined, undefined, oneEmWidth, null); if (html instanceof Node && html.nodeType === Node.DOCUMENT_FRAGMENT_NODE) { this.legend_div_.innerHTML = ''; this.legend_div_.appendChild(html); } else this.legend_div_.innerHTML = html; }; Legend.prototype.didDrawChart = function(e) { this.deselect(e); }; // Right edge should be flush with the right edge of the charting area (which // may not be the same as the right edge of the div, if we have two y-axes). // TODO(danvk): is any of this really necessary? Could just set "right" in "activate". /** * Position the labels div so that: * - its right edge is flush with the right edge of the charting area * - its top edge is flush with the top edge of the charting area * @private */ Legend.prototype.predraw = function(e) { // Don't touch a user-specified labelsDiv. if (!this.is_generated_div_) return; // TODO(danvk): only use real APIs for this. e.dygraph.graphDiv.appendChild(this.legend_div_); // synchronise this with Legend.prototype.select above var area = e.dygraph.plotter_.area; var labelsDivWidth = this.legend_div_.offsetWidth; this.legend_div_.style.left = area.x + area.w - labelsDivWidth - 1 + "px"; this.legend_div_.style.top = area.y + "px"; }; /** * Called when dygraph.destroy() is called. * You should null out any references and detach any DOM elements. */ Legend.prototype.destroy = function() { this.legend_div_ = null; }; /** * Generates HTML for the legend which is displayed when hovering over the * chart. If no selected points are specified, a default legend is returned * (this may just be the empty string). * @param {number} x The x-value of the selected points. * @param {Object} sel_points List of selected points for the given * x-value. Should have properties like 'name', 'yval' and 'canvasy'. * @param {number} oneEmWidth The pixel width for 1em in the legend. Only * relevant when displaying a legend with no selection (i.e. {legend: * 'always'}) and with dashed lines. * @param {number} row The selected row index. * @private */ Legend.generateLegendHTML = function(g, x, sel_points, oneEmWidth, row) { // Data about the selection to pass to legendFormatter var data = { dygraph: g, x: x, i: row, series: [] }; var labelToSeries = {}; var labels = g.getLabels(); if (labels) { for (var i = 1; i < labels.length; i++) { var series = g.getPropertiesForSeries(labels[i]); var strokePattern = g.getOption('strokePattern', labels[i]); var seriesData = { dashHTML: generateLegendDashHTML(strokePattern, series.color, oneEmWidth), label: labels[i], labelHTML: escapeHTML(labels[i]), isVisible: series.visible, color: series.color }; data.series.push(seriesData); labelToSeries[labels[i]] = seriesData; } } if (typeof(x) !== 'undefined') { var xOptView = g.optionsViewForAxis_('x'); var xvf = xOptView('valueFormatter'); data.xHTML = xvf.call(g, x, xOptView, labels[0], g, row, 0); var yOptViews = []; var num_axes = g.numAxes(); for (var i = 0; i < num_axes; i++) { // TODO(danvk): remove this use of a private API yOptViews[i] = g.optionsViewForAxis_('y' + (i ? 1 + i : '')); } var showZeros = g.getOption('labelsShowZeroValues'); var highlightSeries = g.getHighlightSeries(); for (i = 0; i < sel_points.length; i++) { var pt = sel_points[i]; var seriesData = labelToSeries[pt.name]; seriesData.y = pt.yval; if ((pt.yval === 0 && !showZeros) || isNaN(pt.canvasy)) { seriesData.isVisible = false; continue; } var series = g.getPropertiesForSeries(pt.name); var yOptView = yOptViews[series.axis - 1]; var fmtFunc = yOptView('valueFormatter'); var yHTML = fmtFunc.call(g, pt.yval, yOptView, pt.name, g, row, labels.indexOf(pt.name)); utils.update(seriesData, {yHTML}); if (pt.name == highlightSeries) { seriesData.isHighlighted = true; } } } var formatter = (g.getOption('legendFormatter') || Legend.defaultFormatter); return formatter.call(g, data); } Legend.defaultFormatter = function(data) { var g = data.dygraph; // TODO(danvk): deprecate this option in place of {legend: 'never'} // XXX should this logic be in the formatter? if (g.getOption('showLabelsOnHighlight') !== true) return ''; var sepLines = g.getOption('labelsSeparateLines'); var html; if (typeof(data.x) === 'undefined') { // TODO: this check is duplicated in generateLegendHTML. Put it in one place. if (g.getOption('legend') != 'always') { return ''; } html = ''; for (var i = 0; i < data.series.length; i++) { var series = data.series[i]; if (!series.isVisible) continue; if (html !== '') html += (sepLines ? '
    ' : ' '); html += `${series.dashHTML} ${series.labelHTML}`; } return html; } html = data.xHTML + ':'; for (var i = 0; i < data.series.length; i++) { var series = data.series[i]; if (!series.y && !series.yHTML) continue; if (!series.isVisible) continue; if (sepLines) html += '
    '; var cls = series.isHighlighted ? ' class="highlight"' : ''; html += ` ${series.labelHTML}: ${series.yHTML}`; } return html; }; /** * Generates html for the "dash" displayed on the legend when using "legend: always". * In particular, this works for dashed lines with any stroke pattern. It will * try to scale the pattern to fit in 1em width. Or if small enough repeat the * pattern for 1em width. * * @param strokePattern The pattern * @param color The color of the series. * @param oneEmWidth The width in pixels of 1em in the legend. * @private */ // TODO(danvk): cache the results of this function generateLegendDashHTML(strokePattern, color, oneEmWidth) { // Easy, common case: a solid line if (!strokePattern || strokePattern.length <= 1) { return `
    `; } var i, j, paddingLeft, marginRight; var strokePixelLength = 0, segmentLoop = 0; var normalizedPattern = []; var loop; // Compute the length of the pixels including the first segment twice, // since we repeat it. for (i = 0; i <= strokePattern.length; i++) { strokePixelLength += strokePattern[i%strokePattern.length]; } // See if we can loop the pattern by itself at least twice. loop = Math.floor(oneEmWidth/(strokePixelLength-strokePattern[0])); if (loop > 1) { // This pattern fits at least two times, no scaling just convert to em; for (i = 0; i < strokePattern.length; i++) { normalizedPattern[i] = strokePattern[i]/oneEmWidth; } // Since we are repeating the pattern, we don't worry about repeating the // first segment in one draw. segmentLoop = normalizedPattern.length; } else { // If the pattern doesn't fit in the legend we scale it to fit. loop = 1; for (i = 0; i < strokePattern.length; i++) { normalizedPattern[i] = strokePattern[i]/strokePixelLength; } // For the scaled patterns we do redraw the first segment. segmentLoop = normalizedPattern.length+1; } // Now make the pattern. var dash = ""; for (j = 0; j < loop; j++) { for (i = 0; i < segmentLoop; i+=2) { // The padding is the drawn segment. paddingLeft = normalizedPattern[i%normalizedPattern.length]; if (i < strokePattern.length) { // The margin is the space segment. marginRight = normalizedPattern[(i+1)%normalizedPattern.length]; } else { // The repeated first segment has no right margin. marginRight = 0; } dash += `
    `; } } return dash; } export default Legend; dygraphs-2.2.1/src/plugins/range-selector.js010064400000000000000000000611241437353256000160200ustar00/** * @license * Copyright 2011 Paul Felix (paul.eric.felix@gmail.com) * MIT-licenced: https://opensource.org/licenses/MIT */ /*global Dygraph:false,TouchEvent:false */ /** * @fileoverview This file contains the RangeSelector plugin used to provide * a timeline range selector widget for dygraphs. */ /*global Dygraph:false */ "use strict"; import * as utils from '../dygraph-utils'; import DygraphInteraction from '../dygraph-interaction-model'; import IFrameTarp from '../iframe-tarp'; var rangeSelector = function() { this.hasTouchInterface_ = typeof(TouchEvent) != 'undefined'; this.isMobileDevice_ = /mobile|android/gi.test(navigator.appVersion); this.interfaceCreated_ = false; }; rangeSelector.prototype.toString = function() { return "RangeSelector Plugin"; }; rangeSelector.prototype.activate = function(dygraph) { this.dygraph_ = dygraph; if (this.getOption_('showRangeSelector')) { this.createInterface_(); } return { layout: this.reserveSpace_, predraw: this.renderStaticLayer_, didDrawChart: this.renderInteractiveLayer_ }; }; rangeSelector.prototype.destroy = function() { this.bgcanvas_ = null; this.fgcanvas_ = null; this.leftZoomHandle_ = null; this.rightZoomHandle_ = null; }; //------------------------------------------------------------------ // Private methods //------------------------------------------------------------------ rangeSelector.prototype.getOption_ = function(name, opt_series) { return this.dygraph_.getOption(name, opt_series); }; rangeSelector.prototype.setDefaultOption_ = function(name, value) { this.dygraph_.attrs_[name] = value; }; /** * @private * Creates the range selector elements and adds them to the graph. */ rangeSelector.prototype.createInterface_ = function() { this.createCanvases_(); this.createZoomHandles_(); this.initInteraction_(); // Range selector and animatedZooms have a bad interaction. See issue 359. if (this.getOption_('animatedZooms')) { console.warn('Animated zooms and range selector are not compatible; disabling animatedZooms.'); this.dygraph_.updateOptions({animatedZooms: false}, true); } this.interfaceCreated_ = true; this.addToGraph_(); }; /** * @private * Adds the range selector to the graph. */ rangeSelector.prototype.addToGraph_ = function() { var graphDiv = this.graphDiv_ = this.dygraph_.graphDiv; graphDiv.appendChild(this.bgcanvas_); graphDiv.appendChild(this.fgcanvas_); graphDiv.appendChild(this.leftZoomHandle_); graphDiv.appendChild(this.rightZoomHandle_); }; /** * @private * Removes the range selector from the graph. */ rangeSelector.prototype.removeFromGraph_ = function() { var graphDiv = this.graphDiv_; graphDiv.removeChild(this.bgcanvas_); graphDiv.removeChild(this.fgcanvas_); graphDiv.removeChild(this.leftZoomHandle_); graphDiv.removeChild(this.rightZoomHandle_); this.graphDiv_ = null; }; /** * @private * Called by Layout to allow range selector to reserve its space. */ rangeSelector.prototype.reserveSpace_ = function(e) { if (this.getOption_('showRangeSelector')) { e.reserveSpaceBottom(this.getOption_('rangeSelectorHeight') + 4); } }; /** * @private * Renders the static portion of the range selector at the predraw stage. */ rangeSelector.prototype.renderStaticLayer_ = function() { if (!this.updateVisibility_()) { return; } this.resize_(); this.drawStaticLayer_(); }; /** * @private * Renders the interactive portion of the range selector after the chart has been drawn. */ rangeSelector.prototype.renderInteractiveLayer_ = function() { if (!this.updateVisibility_() || this.isChangingRange_) { return; } this.placeZoomHandles_(); this.drawInteractiveLayer_(); }; /** * @private * Check to see if the range selector is enabled/disabled and update visibility accordingly. */ rangeSelector.prototype.updateVisibility_ = function() { var enabled = this.getOption_('showRangeSelector'); if (enabled) { if (!this.interfaceCreated_) { this.createInterface_(); } else if (!this.graphDiv_ || !this.graphDiv_.parentNode) { this.addToGraph_(); } } else if (this.graphDiv_) { this.removeFromGraph_(); var dygraph = this.dygraph_; setTimeout(function() { dygraph.width_ = 0; dygraph.resize(); }, 1); } return enabled; }; /** * @private * Resizes the range selector. */ rangeSelector.prototype.resize_ = function() { function setElementRect(canvas, context, rect, pixelRatioOption) { var canvasScale = pixelRatioOption || utils.getContextPixelRatio(context); canvas.style.top = rect.y + 'px'; canvas.style.left = rect.x + 'px'; canvas.width = rect.w * canvasScale; canvas.height = rect.h * canvasScale; canvas.style.width = rect.w + 'px'; canvas.style.height = rect.h + 'px'; if(canvasScale != 1) { context.scale(canvasScale, canvasScale); } } var plotArea = this.dygraph_.layout_.getPlotArea(); var xAxisLabelHeight = 0; if (this.dygraph_.getOptionForAxis('drawAxis', 'x')) { xAxisLabelHeight = this.getOption_('xAxisHeight') || (this.getOption_('axisLabelFontSize') + 2 * this.getOption_('axisTickSize')); } this.canvasRect_ = { x: plotArea.x, y: plotArea.y + plotArea.h + xAxisLabelHeight + 4, w: plotArea.w, h: this.getOption_('rangeSelectorHeight') }; var pixelRatioOption = this.dygraph_.getNumericOption('pixelRatio'); setElementRect(this.bgcanvas_, this.bgcanvas_ctx_, this.canvasRect_, pixelRatioOption); setElementRect(this.fgcanvas_, this.fgcanvas_ctx_, this.canvasRect_, pixelRatioOption); }; /** * @private * Creates the background and foreground canvases. */ rangeSelector.prototype.createCanvases_ = function() { this.bgcanvas_ = utils.createCanvas(); this.bgcanvas_.className = 'dygraph-rangesel-bgcanvas'; this.bgcanvas_.style.position = 'absolute'; this.bgcanvas_.style.zIndex = 9; this.bgcanvas_ctx_ = utils.getContext(this.bgcanvas_); this.fgcanvas_ = utils.createCanvas(); this.fgcanvas_.className = 'dygraph-rangesel-fgcanvas'; this.fgcanvas_.style.position = 'absolute'; this.fgcanvas_.style.zIndex = 9; this.fgcanvas_.style.cursor = 'default'; this.fgcanvas_ctx_ = utils.getContext(this.fgcanvas_); }; /** * @private * Creates the zoom handle elements. */ rangeSelector.prototype.createZoomHandles_ = function() { var img = new Image(); img.className = 'dygraph-rangesel-zoomhandle'; img.style.position = 'absolute'; img.style.zIndex = 10; img.style.visibility = 'hidden'; // Initially hidden so they don't show up in the wrong place. img.style.cursor = 'col-resize'; // TODO: change image to more options img.width = 9; img.height = 16; img.src = 'data:image/png;base64,' + 'iVBORw0KGgoAAAANSUhEUgAAAAkAAAAQCAYAAADESFVDAAAAAXNSR0IArs4c6QAAAAZiS0dEANAA' + 'zwDP4Z7KegAAAAlwSFlzAAAOxAAADsQBlSsOGwAAAAd0SU1FB9sHGw0cMqdt1UwAAAAZdEVYdENv' + 'bW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAaElEQVQoz+3SsRFAQBCF4Z9WJM8KCDVwownl' + '6YXsTmCUsyKGkZzcl7zkz3YLkypgAnreFmDEpHkIwVOMfpdi9CEEN2nGpFdwD03yEqDtOgCaun7s' + 'qSTDH32I1pQA2Pb9sZecAxc5r3IAb21d6878xsAAAAAASUVORK5CYII='; if (this.isMobileDevice_) { img.width *= 2; img.height *= 2; } this.leftZoomHandle_ = img; this.rightZoomHandle_ = img.cloneNode(false); }; /** * @private * Sets up the interaction for the range selector. */ rangeSelector.prototype.initInteraction_ = function() { var self = this; var topElem = document; var clientXLast = 0; var handle = null; var isZooming = false; var isPanning = false; var dynamic = !this.isMobileDevice_; // We cover iframes during mouse interactions. See comments in // dygraph-utils.js for more info on why this is a good idea. var tarp = new IFrameTarp(); // functions, defined below. Defining them this way (rather than with // "function foo() {...}") makes JSHint happy. var toXDataWindow, onZoomStart, onZoom, onZoomEnd, doZoom, isMouseInPanZone, onPanStart, onPan, onPanEnd, doPan, onCanvasHover; // Touch event functions var onZoomHandleTouchEvent, onCanvasTouchEvent, addTouchEvents; toXDataWindow = function(zoomHandleStatus) { var xDataLimits = self.dygraph_.xAxisExtremes(); var fact = (xDataLimits[1] - xDataLimits[0])/self.canvasRect_.w; var xDataMin = xDataLimits[0] + (zoomHandleStatus.leftHandlePos - self.canvasRect_.x)*fact; var xDataMax = xDataLimits[0] + (zoomHandleStatus.rightHandlePos - self.canvasRect_.x)*fact; return [xDataMin, xDataMax]; }; onZoomStart = function(e) { utils.cancelEvent(e); isZooming = true; clientXLast = e.clientX; handle = e.target ? e.target : e.srcElement; if (e.type === 'mousedown' || e.type === 'dragstart') { // These events are removed manually. utils.addEvent(topElem, 'mousemove', onZoom); utils.addEvent(topElem, 'mouseup', onZoomEnd); } self.fgcanvas_.style.cursor = 'col-resize'; tarp.cover(); return true; }; onZoom = function(e) { if (!isZooming) { return false; } utils.cancelEvent(e); var delX = e.clientX - clientXLast; if (Math.abs(delX) < 4) { return true; } clientXLast = e.clientX; // Move handle. var zoomHandleStatus = self.getZoomHandleStatus_(); var newPos; if (handle == self.leftZoomHandle_) { newPos = zoomHandleStatus.leftHandlePos + delX; newPos = Math.min(newPos, zoomHandleStatus.rightHandlePos - handle.width - 3); newPos = Math.max(newPos, self.canvasRect_.x); } else { newPos = zoomHandleStatus.rightHandlePos + delX; newPos = Math.min(newPos, self.canvasRect_.x + self.canvasRect_.w); newPos = Math.max(newPos, zoomHandleStatus.leftHandlePos + handle.width + 3); } var halfHandleWidth = handle.width/2; handle.style.left = (newPos - halfHandleWidth) + 'px'; self.drawInteractiveLayer_(); // Zoom on the fly. if (dynamic) { doZoom(); } return true; }; onZoomEnd = function(e) { if (!isZooming) { return false; } isZooming = false; tarp.uncover(); utils.removeEvent(topElem, 'mousemove', onZoom); utils.removeEvent(topElem, 'mouseup', onZoomEnd); self.fgcanvas_.style.cursor = 'default'; // If on a slower device, zoom now. if (!dynamic) { doZoom(); } return true; }; doZoom = function() { try { var zoomHandleStatus = self.getZoomHandleStatus_(); self.isChangingRange_ = true; if (!zoomHandleStatus.isZoomed) { self.dygraph_.resetZoom(); } else { var xDataWindow = toXDataWindow(zoomHandleStatus); self.dygraph_.doZoomXDates_(xDataWindow[0], xDataWindow[1]); } } finally { self.isChangingRange_ = false; } }; isMouseInPanZone = function(e) { var rect = self.leftZoomHandle_.getBoundingClientRect(); var leftHandleClientX = rect.left + rect.width/2; rect = self.rightZoomHandle_.getBoundingClientRect(); var rightHandleClientX = rect.left + rect.width/2; return (e.clientX > leftHandleClientX && e.clientX < rightHandleClientX); }; onPanStart = function(e) { if (!isPanning && isMouseInPanZone(e) && self.getZoomHandleStatus_().isZoomed) { utils.cancelEvent(e); isPanning = true; clientXLast = e.clientX; if (e.type === 'mousedown') { // These events are removed manually. utils.addEvent(topElem, 'mousemove', onPan); utils.addEvent(topElem, 'mouseup', onPanEnd); } return true; } return false; }; onPan = function(e) { if (!isPanning) { return false; } utils.cancelEvent(e); var delX = e.clientX - clientXLast; if (Math.abs(delX) < 4) { return true; } clientXLast = e.clientX; // Move range view var zoomHandleStatus = self.getZoomHandleStatus_(); var leftHandlePos = zoomHandleStatus.leftHandlePos; var rightHandlePos = zoomHandleStatus.rightHandlePos; var rangeSize = rightHandlePos - leftHandlePos; if (leftHandlePos + delX <= self.canvasRect_.x) { leftHandlePos = self.canvasRect_.x; rightHandlePos = leftHandlePos + rangeSize; } else if (rightHandlePos + delX >= self.canvasRect_.x + self.canvasRect_.w) { rightHandlePos = self.canvasRect_.x + self.canvasRect_.w; leftHandlePos = rightHandlePos - rangeSize; } else { leftHandlePos += delX; rightHandlePos += delX; } var halfHandleWidth = self.leftZoomHandle_.width/2; self.leftZoomHandle_.style.left = (leftHandlePos - halfHandleWidth) + 'px'; self.rightZoomHandle_.style.left = (rightHandlePos - halfHandleWidth) + 'px'; self.drawInteractiveLayer_(); // Do pan on the fly. if (dynamic) { doPan(); } return true; }; onPanEnd = function(e) { if (!isPanning) { return false; } isPanning = false; utils.removeEvent(topElem, 'mousemove', onPan); utils.removeEvent(topElem, 'mouseup', onPanEnd); // If on a slower device, do pan now. if (!dynamic) { doPan(); } return true; }; doPan = function() { try { self.isChangingRange_ = true; self.dygraph_.dateWindow_ = toXDataWindow(self.getZoomHandleStatus_()); self.dygraph_.drawGraph_(false); } finally { self.isChangingRange_ = false; } }; onCanvasHover = function(e) { if (isZooming || isPanning) { return; } var cursor = isMouseInPanZone(e) ? 'move' : 'default'; if (cursor != self.fgcanvas_.style.cursor) { self.fgcanvas_.style.cursor = cursor; } }; onZoomHandleTouchEvent = function(e) { if (e.type == 'touchstart' && e.targetTouches.length == 1) { if (onZoomStart(e.targetTouches[0])) { utils.cancelEvent(e); } } else if (e.type == 'touchmove' && e.targetTouches.length == 1) { if (onZoom(e.targetTouches[0])) { utils.cancelEvent(e); } } else { onZoomEnd(e); } }; onCanvasTouchEvent = function(e) { if (e.type == 'touchstart' && e.targetTouches.length == 1) { if (onPanStart(e.targetTouches[0])) { utils.cancelEvent(e); } } else if (e.type == 'touchmove' && e.targetTouches.length == 1) { if (onPan(e.targetTouches[0])) { utils.cancelEvent(e); } } else { onPanEnd(e); } }; addTouchEvents = function(elem, fn) { var types = ['touchstart', 'touchend', 'touchmove', 'touchcancel']; for (var i = 0; i < types.length; i++) { self.dygraph_.addAndTrackEvent(elem, types[i], fn); } }; this.setDefaultOption_('interactionModel', DygraphInteraction.dragIsPanInteractionModel); this.setDefaultOption_('panEdgeFraction', 0.0001); var dragStartEvent = window.opera ? 'mousedown' : 'dragstart'; this.dygraph_.addAndTrackEvent(this.leftZoomHandle_, dragStartEvent, onZoomStart); this.dygraph_.addAndTrackEvent(this.rightZoomHandle_, dragStartEvent, onZoomStart); this.dygraph_.addAndTrackEvent(this.fgcanvas_, 'mousedown', onPanStart); this.dygraph_.addAndTrackEvent(this.fgcanvas_, 'mousemove', onCanvasHover); // Touch events if (this.hasTouchInterface_) { addTouchEvents(this.leftZoomHandle_, onZoomHandleTouchEvent); addTouchEvents(this.rightZoomHandle_, onZoomHandleTouchEvent); addTouchEvents(this.fgcanvas_, onCanvasTouchEvent); } }; /** * @private * Draws the static layer in the background canvas. */ rangeSelector.prototype.drawStaticLayer_ = function() { var ctx = this.bgcanvas_ctx_; ctx.clearRect(0, 0, this.canvasRect_.w, this.canvasRect_.h); try { this.drawMiniPlot_(); } catch(ex) { console.warn(ex); } var margin = 0.5; this.bgcanvas_ctx_.lineWidth = this.getOption_('rangeSelectorBackgroundLineWidth'); ctx.strokeStyle = this.getOption_('rangeSelectorBackgroundStrokeColor'); ctx.beginPath(); ctx.moveTo(margin, margin); ctx.lineTo(margin, this.canvasRect_.h-margin); ctx.lineTo(this.canvasRect_.w-margin, this.canvasRect_.h-margin); ctx.lineTo(this.canvasRect_.w-margin, margin); ctx.stroke(); }; /** * @private * Draws the mini plot in the background canvas. */ rangeSelector.prototype.drawMiniPlot_ = function() { var fillStyle = this.getOption_('rangeSelectorPlotFillColor'); var fillGradientStyle = this.getOption_('rangeSelectorPlotFillGradientColor'); var strokeStyle = this.getOption_('rangeSelectorPlotStrokeColor'); if (!fillStyle && !strokeStyle) { return; } var stepPlot = this.getOption_('stepPlot'); var combinedSeriesData = this.computeCombinedSeriesAndLimits_(); var yRange = combinedSeriesData.yMax - combinedSeriesData.yMin; // Draw the mini plot. var ctx = this.bgcanvas_ctx_; var margin = 0.5; var xExtremes = this.dygraph_.xAxisExtremes(); var xRange = Math.max(xExtremes[1] - xExtremes[0], 1.e-30); var xFact = (this.canvasRect_.w - margin)/xRange; var yFact = (this.canvasRect_.h - margin)/yRange; var canvasWidth = this.canvasRect_.w - margin; var canvasHeight = this.canvasRect_.h - margin; var prevX = null, prevY = null; ctx.beginPath(); ctx.moveTo(margin, canvasHeight); for (var i = 0; i < combinedSeriesData.data.length; i++) { var dataPoint = combinedSeriesData.data[i]; var x = ((dataPoint[0] !== null) ? ((dataPoint[0] - xExtremes[0])*xFact) : NaN); var y = ((dataPoint[1] !== null) ? (canvasHeight - (dataPoint[1] - combinedSeriesData.yMin)*yFact) : NaN); // Skip points that don't change the x-value. Overly fine-grained points // can cause major slowdowns with the ctx.fill() call below. if (!stepPlot && prevX !== null && Math.round(x) == Math.round(prevX)) { continue; } if (isFinite(x) && isFinite(y)) { if(prevX === null) { ctx.lineTo(x, canvasHeight); } else if (stepPlot) { ctx.lineTo(x, prevY); } ctx.lineTo(x, y); prevX = x; prevY = y; } else { if(prevX !== null) { if (stepPlot) { ctx.lineTo(x, prevY); ctx.lineTo(x, canvasHeight); } else { ctx.lineTo(prevX, canvasHeight); } } prevX = prevY = null; } } ctx.lineTo(canvasWidth, canvasHeight); ctx.closePath(); if (fillStyle) { var lingrad = this.bgcanvas_ctx_.createLinearGradient(0, 0, 0, canvasHeight); if (fillGradientStyle) { lingrad.addColorStop(0, fillGradientStyle); } lingrad.addColorStop(1, fillStyle); this.bgcanvas_ctx_.fillStyle = lingrad; ctx.fill(); } if (strokeStyle) { this.bgcanvas_ctx_.strokeStyle = strokeStyle; this.bgcanvas_ctx_.lineWidth = this.getOption_('rangeSelectorPlotLineWidth'); ctx.stroke(); } }; /** * @private * Computes and returns the combined series data along with min/max for the mini plot. * The combined series consists of averaged values for all series. * When series have error bars, the error bars are ignored. * @return {Object} An object containing combined series array, ymin, ymax. */ rangeSelector.prototype.computeCombinedSeriesAndLimits_ = function() { var g = this.dygraph_; var logscale = this.getOption_('logscale'); var i; // Select series to combine. By default, all series are combined. var numColumns = g.numColumns(); var labels = g.getLabels(); var includeSeries = new Array(numColumns); var anySet = false; var visibility = g.visibility(); var inclusion = []; for (i = 1; i < numColumns; i++) { var include = this.getOption_('showInRangeSelector', labels[i]); inclusion.push(include); if (include !== null) anySet = true; // it's set explicitly for this series } if (anySet) { for (i = 1; i < numColumns; i++) { includeSeries[i] = inclusion[i - 1]; } } else { for (i = 1; i < numColumns; i++) { includeSeries[i] = visibility[i - 1]; } } // Create a combined series (average of selected series values). // TODO(danvk): short-circuit if there's only one series. var rolledSeries = []; var dataHandler = g.dataHandler_; var options = g.attributes_; for (i = 1; i < g.numColumns(); i++) { if (!includeSeries[i]) continue; var series = dataHandler.extractSeries(g.rawData_, i, options); if (g.rollPeriod() > 1) { series = dataHandler.rollingAverage(series, g.rollPeriod(), options, i); } rolledSeries.push(series); } var combinedSeries = []; for (i = 0; i < rolledSeries[0].length; i++) { var sum = 0; var count = 0; for (var j = 0; j < rolledSeries.length; j++) { var y = rolledSeries[j][i][1]; if (y === null || isNaN(y)) continue; count++; sum += y; } combinedSeries.push([rolledSeries[0][i][0], sum / count]); } // Compute the y range. var yMin = Number.MAX_VALUE; var yMax = -Number.MAX_VALUE; for (i = 0; i < combinedSeries.length; i++) { var yVal = combinedSeries[i][1]; if (yVal !== null && isFinite(yVal) && (!logscale || yVal > 0)) { yMin = Math.min(yMin, yVal); yMax = Math.max(yMax, yVal); } } // Convert Y data to log scale if needed. // Also, expand the Y range to compress the mini plot a little. var extraPercent = 0.25; if (logscale) { yMax = utils.log10(yMax); yMax += yMax*extraPercent; yMin = utils.log10(yMin); for (i = 0; i < combinedSeries.length; i++) { combinedSeries[i][1] = utils.log10(combinedSeries[i][1]); } } else { var yExtra; var yRange = yMax - yMin; if (yRange <= Number.MIN_VALUE) { yExtra = yMax*extraPercent; } else { yExtra = yRange*extraPercent; } yMax += yExtra; yMin -= yExtra; } return {data: combinedSeries, yMin: yMin, yMax: yMax}; }; /** * @private * Places the zoom handles in the proper position based on the current X data window. */ rangeSelector.prototype.placeZoomHandles_ = function() { var xExtremes = this.dygraph_.xAxisExtremes(); var xWindowLimits = this.dygraph_.xAxisRange(); var xRange = xExtremes[1] - xExtremes[0]; var leftPercent = Math.max(0, (xWindowLimits[0] - xExtremes[0])/xRange); var rightPercent = Math.max(0, (xExtremes[1] - xWindowLimits[1])/xRange); var leftCoord = this.canvasRect_.x + this.canvasRect_.w*leftPercent; var rightCoord = this.canvasRect_.x + this.canvasRect_.w*(1 - rightPercent); var handleTop = Math.max(this.canvasRect_.y, this.canvasRect_.y + (this.canvasRect_.h - this.leftZoomHandle_.height)/2); var halfHandleWidth = this.leftZoomHandle_.width/2; this.leftZoomHandle_.style.left = (leftCoord - halfHandleWidth) + 'px'; this.leftZoomHandle_.style.top = handleTop + 'px'; this.rightZoomHandle_.style.left = (rightCoord - halfHandleWidth) + 'px'; this.rightZoomHandle_.style.top = this.leftZoomHandle_.style.top; this.leftZoomHandle_.style.visibility = 'visible'; this.rightZoomHandle_.style.visibility = 'visible'; }; /** * @private * Draws the interactive layer in the foreground canvas. */ rangeSelector.prototype.drawInteractiveLayer_ = function() { var ctx = this.fgcanvas_ctx_; ctx.clearRect(0, 0, this.canvasRect_.w, this.canvasRect_.h); var margin = 1; var width = this.canvasRect_.w - margin; var height = this.canvasRect_.h - margin; var zoomHandleStatus = this.getZoomHandleStatus_(); ctx.strokeStyle = this.getOption_('rangeSelectorForegroundStrokeColor'); ctx.lineWidth = this.getOption_('rangeSelectorForegroundLineWidth'); if (!zoomHandleStatus.isZoomed) { ctx.beginPath(); ctx.moveTo(margin, margin); ctx.lineTo(margin, height); ctx.lineTo(width, height); ctx.lineTo(width, margin); ctx.stroke(); } else { var leftHandleCanvasPos = Math.max(margin, zoomHandleStatus.leftHandlePos - this.canvasRect_.x); var rightHandleCanvasPos = Math.min(width, zoomHandleStatus.rightHandlePos - this.canvasRect_.x); const veilColour = this.getOption_('rangeSelectorVeilColour'); ctx.fillStyle = veilColour ? veilColour : ('rgba(240, 240, 240, ' + this.getOption_('rangeSelectorAlpha').toString() + ')'); ctx.fillRect(0, 0, leftHandleCanvasPos, this.canvasRect_.h); ctx.fillRect(rightHandleCanvasPos, 0, this.canvasRect_.w - rightHandleCanvasPos, this.canvasRect_.h); ctx.beginPath(); ctx.moveTo(margin, margin); ctx.lineTo(leftHandleCanvasPos, margin); ctx.lineTo(leftHandleCanvasPos, height); ctx.lineTo(rightHandleCanvasPos, height); ctx.lineTo(rightHandleCanvasPos, margin); ctx.lineTo(width, margin); ctx.stroke(); } }; /** * @private * Returns the current zoom handle position information. * @return {Object} The zoom handle status. */ rangeSelector.prototype.getZoomHandleStatus_ = function() { var halfHandleWidth = this.leftZoomHandle_.width/2; var leftHandlePos = parseFloat(this.leftZoomHandle_.style.left) + halfHandleWidth; var rightHandlePos = parseFloat(this.rightZoomHandle_.style.left) + halfHandleWidth; return { leftHandlePos: leftHandlePos, rightHandlePos: rightHandlePos, isZoomed: (leftHandlePos - 1 > this.canvasRect_.x || rightHandlePos + 1 < this.canvasRect_.x+this.canvasRect_.w) }; }; export default rangeSelector; dygraphs-2.2.1/tests/annotation-gviz.html010064400000000000000000000046631437353256000154640ustar00 Google Visualisations annotations

    Google AnnotatedTimeline:

    Dygraph.GVizChart:

    dygraphs-2.2.1/tests/annotation-native.html010064400000000000000000000025501437353256000157640ustar00 Native format annotations

    This test demonstrates how annotations can be used with native-format data.

    dygraphs-2.2.1/tests/annotation.html010064400000000000000000000132701437353256000145010ustar00 demo

    Annotations Demo

    Click any point to add an annotation to it or click "Add Annotation".

    dygraphs-2.2.1/tests/border.html010064400000000000000000000017401437353256000136030ustar00 gadget border

    Hopefully this stays in its border:

    dygraphs-2.2.1/tests/callback.html010064400000000000000000000063471437353256000140720ustar00 callbacks

    Hover, click and zoom to test the callbacks:

    dygraphs-2.2.1/tests/century-scale.html010064400000000000000000000026641437353256000151120ustar00 demo

    Demo

    (Mouse over to highlight individual values. Click and drag to zoom. Double-click to zoom out.)
    dygraphs-2.2.1/tests/charting-combinations.html010064400000000000000000000055331437353256000166140ustar00 Charting combinations

    There are four options which fundamentally change the behaviour of the standard plotter:

    1. errorBars / customBars
    2. stepPlot
    3. fillGraph
    4. strokePattern

    This page exhaustively checks all combinations of these parameters.

    Valid combinations
    dygraphs-2.2.1/tests/color-cycle.html010064400000000000000000000026031437353256000145400ustar00 color cycles

    Different color cycles

    Colors:

    dygraphs-2.2.1/tests/color-visibility.html010064400000000000000000000026741437353256000156400ustar00 color visibility

    The lines should maintain their colors as their visibility is toggled.

    Display:

    dygraphs-2.2.1/tests/connect-separated.html010064400000000000000000000044651437353256000157340ustar00 connect separated

    Connecting separated points. All three of the series should have their points connected with lines, and hovering over them should produce dot and legend overlays in the proper color.

    Toggle ConnectSeparated:

    All: Series 1 Series 2 Series 3

    dygraphs-2.2.1/tests/crosshair.html010064400000000000000000000036171437353256000143300ustar00 crosshairs

    Vertical crosshair:

    Horizontal crosshairs:

    Both crosshairs:

    dygraphs-2.2.1/tests/css-positioning.html010064400000000000000000000034631437353256000154620ustar00 demo

    Mouseovers should work as expected (dots should align with mouse)


    dygraphs-2.2.1/tests/custom-bars.html010064400000000000000000000023771437353256000145740ustar00 custom bars

    Top and bottom of the bars stay mostly fixed while the middle varies.

    dygraphs-2.2.1/tests/custom-circles.html010064400000000000000000000113551437353256000152650ustar00 Custom Circles

    Custom circles and hover circles

    dygraphs-2.2.1/tests/customLabel.html010064400000000000000000000032211437353256000145740ustar00 Label styles

    Labels are transparent and repositioned:

    dygraphs-2.2.1/tests/customLabelCss3.html010064400000000000000000000023371437353256000153370ustar00 Label styles

    Labels are styled with css3:

    dygraphs-2.2.1/tests/data.js012077700000000000000000000000001437353256000160252../gallery/data.jsustar00dygraphs-2.2.1/tests/dateWindow.html010064400000000000000000000030551437353256000144340ustar00 dateWindow

    dateWindow is set to something other than an x-value in the data set. Grid lines should still go through data points. In the second chart, there should be lines going off the edge of the chart (to data points out of view).

    dygraphs-2.2.1/tests/daylight-savings.html010064400000000000000000000055551437353256000156130ustar00 Daylight Savings

    DST

    This tests that tick marks don't break when the axis crosses into daylight savings time.

    The tick marks should all be on day boundaries or nice hours (6, 12, 18), rather than on odd time boundaries like 5, 11, 17 and 23.


    This chart shows a continuous line going across the "fall back" EST/EDT event. You may need to switch your computer's time zone to Eastern to see this. The x-axis tick marks go from 01:00 → 01:55 and then back to 01:00.


    This chart is analogous to the first, except at a "spring forward".


    This chart shows a continuous series which crosses a "spring forward". The x-axis ticks should skip from 1:55AM to 3:00AM.

    dygraphs-2.2.1/tests/demo.html010064400000000000000000000040251437353256000132510ustar00 demo

    Demo

    (Mouse over to highlight individual values. Click and drag to zoom. Double-click to zoom out.)
    dygraphs-2.2.1/tests/dense-fill.html010064400000000000000000000026211437353256000143470ustar00 dense, filled plots

    These charts are substantially sped up by down-sampling.

    step plot, filled

    dygraphs-2.2.1/tests/draw-points.html010064400000000000000000000021411437353256000145710ustar00 individual points

    with points:

    without points:

    dygraphs-2.2.1/tests/drawing.html010064400000000000000000000167231437353256000137700ustar00 Dygraphs Drawing Demo

    Time Series Drawing Demo

    dygraphs-2.2.1/tests/dygraph-many-points-benchmark.html010064400000000000000000000156521437353256000201770ustar00 Benchmarking for Plots with Many Points

    Plot which can be easily generated with different numbers of points for benchmarking/profiling and improving performance of dygraphs.

    Data to plot: sinusoid function random points

    Timestamps: aligned unaligned

    x-axis type: numeric date/time

    Number of points per series (points):

    Number of series (series):

    Roll period (in points, rollPeriod):

    Repetitions (repititions):



    dygraphs-2.2.1/tests/dygraph.html010064400000000000000000000025501437353256000137640ustar00 dygraph

    Minimal example of a dygraph chart:

    Same data, specified in a parsed format:

    dygraphs-2.2.1/tests/dynamic-update.html010064400000000000000000000030771437353256000152370ustar00 Live random data

    Live random data

    This test is modeled after a highcharts test. New points should appear once per second. Try zooming and panning over to the right edge to watch them show up.

    dygraphs-2.2.1/tests/exported-symbols.html010064400000000000000000000150771437353256000156560ustar00 Exported Symbols test

    Do note that anything with an underscore _ is considered internal, not an API, and subject to change without notice, unless specifically promised otherwise.

    dygraphs exports these symbols into the global namespace:

    It exports these symbols inside each of those symbols and Dygraph.{Circles,Plugins}:

    Loading extras:

    New top-level elements after loading extras:

    New second-level and Dygraph.{Circles,Plugins} elements after loading extras:

    The following surface can be reached via the “require” system. Most of this is considered internal and possibe subject to change at few or no notice even if not, in ipso, underscore-tagged.

    dygraphs-2.2.1/tests/fillGraph-alpha.html010064400000000000000000000036751437353256000153320ustar00 fillGraph with per series fillAlpha

    Filled, using default value

    Filled, using global alpha value

    Filled, using per series alpha values

    Filled, using a mix of global and per series values

    dygraphs-2.2.1/tests/fillGraph.html010064400000000000000000000034611437353256000142400ustar00 fillGraph

    Filled, no high/low bands:

    Filled, negatives:

    Filled on a per-series basis:

    dygraphs-2.2.1/tests/fractions.html010064400000000000000000000023771437353256000143250ustar00 Fractions Fractions:
    Loading...
    With high/low bands and roller:
    Loading...
    dygraphs-2.2.1/tests/grid_dot.html010064400000000000000000000055201437353256000141210ustar00 Custom grid and Dot

    Dot and lines should be extra large. Grid is red.

    Use axis specific grid options. (independentTicks, gridLinePattern, ...)

    dygraphs-2.2.1/tests/gviz-infinity.html010064400000000000000000000037321437353256000151370ustar00 gviz

    This tests that infinite values don't break dygraphs.

    gviz line chart:

    same data drawn using dygraphs:

    date column:
    dygraphs-2.2.1/tests/gviz-selection.html010064400000000000000000000065551437353256000153010ustar00 gviz selection

    Get/Set/Clear selection via gviz API:

    dygraphs-2.2.1/tests/gviz.html010064400000000000000000000042571437353256000133130ustar00 gviz

    gviz line chart:

    same data drawn using dygraphs:

    date column:
    datetime column:
    dygraphs-2.2.1/tests/hairlines.html010064400000000000000000000262401437353256000143060ustar00 Hairlines demo

    Hairlines Demo

    Click the chart to add a hairline. Drag the hairline to move it.

    Click a point to add an editable annotation. Drag it to move it up/down.


    Hairline mode:

    Learn more about the Hairlines/Super-annotations plugins and their APIs.

    dygraphs-2.2.1/tests/highlighted-region.html010064400000000000000000000033721437353256000160720ustar00 Highlighted Region

    Should draw a time series with an unusual region highlighted:

    When you zoom/pan, the region should remain highlighted.

    dygraphs-2.2.1/tests/hourly.html010064400000000000000000000046431437353256000136550ustar00 hourly

    Hourly data:

    Minutely data:

    Secondly data:

    dygraphs-2.2.1/tests/iframe.html010064400000000000000000000037071437353256000135760ustar00 demo

    Click on various places in the chart, then drag the mouse over the iframe and release it. When you mouse back over the charts, strange things shouldn't happen.

    dygraphs-2.2.1/tests/independent-series.html010064400000000000000000000155671437353256000161270ustar00 Independent Series

    Independent Series

    By using the connectSeparated attribute, it’s possible to display a chart of several time series with completely independent x-values.

    The trick is to specify values for the series at the union of the x-values of all series. For one series' x values, specify null for each of the other series.

    For example, say you had two series:

    xA
    22
    46
    64
    xB
    13
    37
    55

    Then you would specify the following CSV or native data:

    (CSV)
    X,A,B
    1,,3
    2,2,
    3,,7
    4,6,
    5,,5
    6,4,
    (native)
    [
    [1, null, 3],
    [2, 2, null],
    [3, null, 7],
    [4, 6, null],
    [5, null, 5],
    [6, 4, null] ]

    Encoding a gap

    There's one extra wrinkle. What if one of the series has a missing value, i.e. what if your series are something like

    xA
    22
    44
    6(gap)
    88
    1010
    xB
    13
    35
    57

    The gap would normally be encoded as a null, or missing value. But when you use connectSeparatedPoints, that has a special meaning. Instead, you have to use NaN. This is a bit of a hack, but it gets the job done.

    (CSV)
    x,A,B
    1,,3
    2,2,
    3,,5
    4,4,
    5,,7
    6,NaN,
    8,8,
    10,10,
    (native)
    [ [1, null, 3],
      [2, 2, null],
      [3, null, 5],
      [4, 4, null],
      [5, null, 7],
      [6, NaN, null],
      [8, 8, null],
      [10, 10, null] ]

    Behavior at the edges of the panel for independent series

    In case only a part of the whole data is visible (e.g. after zooming in) the lines are drawn to the respective next valid point outside the visible area.

    xA
    02
    13
    23
    44
    53
    63
    73
    84
    xB
    01
    12
    21
    82

    Both graphs have no value at the right edge of the panel (x=3). The lines that are drawn to the right edge are determined by their respective next valid value outside the visible area. Therefore it is neither necessary that the next valid values are on the same point nor that they have the same index (index 4 for the green line and index 8 for the blue line).

    Use double click to unzoom and see the currently invisible points

    Index
     
    0
    1
    2
    3
    4
    5
    6
    7
    8
    (CSV)
    x,A,B
    0,2,1
    1,3,2
    2,3,1
    3,,
    4,4,
    5,3,
    6,3,
    7,3,
    8,4,2
    (native)
    [
      [0, 2, 1],
      [1, 3, 2],
      [2, 3, 1],
      [3, null, null],
      [4, 4, null],
      [5, 3, null],
      [6, 3, null],
      [7, 3, null],
      [8, 4, 2] ]
    dygraphs-2.2.1/tests/interaction.html010064400000000000000000000055551437353256000146550ustar00 interaction model
    Default interaction model
    Zoom: click-drag
    Pan: shift-click-drag
    Restore zoom level: double-click
    No interaction model
    Click and drag all you like, it won't do anything!
    Custom interaction model
    Zoom in: double-click, scroll wheel
    Zoom out: ctrl-double-click, scroll wheel
    Standard Zoom: shift-click-drag Standard Pan: click-drag
    Restore zoom level: press button
    Fun model!
    Keep the mouse button pressed, and hover over all points to mark them.
    dygraphs-2.2.1/tests/interaction.js010064400000000000000000000142721437353256000143210ustar00// Code for a variety of interaction models. Used in interaction.html, but split out from // that file so they can be tested in isolation. // function downV3(event, g, context) { context.initializeMouseDown(event, g, context); if (event.altKey || event.shiftKey) { Dygraph.startZoom(event, g, context); } else { Dygraph.startPan(event, g, context); } } function moveV3(event, g, context) { if (context.isPanning) { Dygraph.movePan(event, g, context); } else if (context.isZooming) { Dygraph.moveZoom(event, g, context); } } function upV3(event, g, context) { if (context.isPanning) { Dygraph.endPan(event, g, context); } else if (context.isZooming) { Dygraph.endZoom(event, g, context); } } // Take the offset of a mouse event on the dygraph canvas and // convert it to a pair of percentages from the bottom left. // (Not top left, bottom is where the lower value is.) function offsetToPercentage(g, offsetX, offsetY) { // This is calculating the pixel offset of the leftmost date. var xOffset = g.toDomCoords(g.xAxisRange()[0], null)[0]; var yar0 = g.yAxisRange(0); // This is calculating the pixel of the higest value. (Top pixel) var yOffset = g.toDomCoords(null, yar0[1])[1]; // x y w and h are relative to the corner of the drawing area, // so that the upper corner of the drawing area is (0, 0). var x = offsetX - xOffset; var y = offsetY - yOffset; // This is computing the rightmost pixel, effectively defining the // width. var w = g.toDomCoords(g.xAxisRange()[1], null)[0] - xOffset; // This is computing the lowest pixel, effectively defining the height. var h = g.toDomCoords(null, yar0[0])[1] - yOffset; // Percentage from the left. var xPct = w == 0 ? 0 : (x / w); // Percentage from the top. var yPct = h == 0 ? 0 : (y / h); // The (1-) part below changes it from "% distance down from the top" // to "% distance up from the bottom". return [xPct, (1-yPct)]; } function dblClickV3(event, g, context) { // Reducing by 20% makes it 80% the original size, which means // to restore to original size it must grow by 25% if (!(event.offsetX && event.offsetY)){ event.offsetX = event.layerX - event.target.offsetLeft; event.offsetY = event.layerY - event.target.offsetTop; } var percentages = offsetToPercentage(g, event.offsetX, event.offsetY); var xPct = percentages[0]; var yPct = percentages[1]; if (event.ctrlKey) { zoom(g, -.25, xPct, yPct); } else { zoom(g, +.2, xPct, yPct); } } var lastClickedGraph = null; function clickV3(event, g, context) { lastClickedGraph = g; event.preventDefault(); event.stopPropagation(); } function scrollV3(event, g, context) { if (lastClickedGraph != g) { return; } var normal = event.detail ? event.detail * -1 : event.wheelDelta / 40; // For me the normalized value shows 0.075 for one click. If I took // that verbatim, it would be a 7.5%. var percentage = normal / 50; if (!(event.offsetX && event.offsetY)){ event.offsetX = event.layerX - event.target.offsetLeft; event.offsetY = event.layerY - event.target.offsetTop; } var percentages = offsetToPercentage(g, event.offsetX, event.offsetY); var xPct = percentages[0]; var yPct = percentages[1]; zoom(g, percentage, xPct, yPct); event.preventDefault(); event.stopPropagation(); } // Adjusts [x, y] toward each other by zoomInPercentage% // Split it so the left/bottom axis gets xBias/yBias of that change and // tight/top gets (1-xBias)/(1-yBias) of that change. // // If a bias is missing it splits it down the middle. function zoom(g, zoomInPercentage, xBias, yBias) { xBias = xBias || 0.5; yBias = yBias || 0.5; function adjustAxis(axis, zoomInPercentage, bias) { var delta = axis[1] - axis[0]; var increment = delta * zoomInPercentage; var foo = [increment * bias, increment * (1-bias)]; return [ axis[0] + foo[0], axis[1] - foo[1] ]; } var yAxes = g.yAxisRanges(); var newYAxes = []; for (var i = 0; i < yAxes.length; i++) { newYAxes[i] = adjustAxis(yAxes[i], zoomInPercentage, yBias); } g.updateOptions({ dateWindow: adjustAxis(g.xAxisRange(), zoomInPercentage, xBias), valueRange: newYAxes[0] }); } var v4Active = false; var v4Canvas = null; function downV4(event, g, context) { context.initializeMouseDown(event, g, context); v4Active = true; moveV4(event, g, context); // in case the mouse went down on a data point. } var processed = []; function moveV4(event, g, context) { var RANGE = 7; if (v4Active) { var graphPos = Dygraph.findPos(g.graphDiv); var canvasx = Dygraph.pageX(event) - graphPos.x; var canvasy = Dygraph.pageY(event) - graphPos.y; var rows = g.numRows(); // Row layout: // [date, [val1, stdev1], [val2, stdev2]] for (var row = 0; row < rows; row++) { var date = g.getValue(row, 0); var x = g.toDomCoords(date, null)[0]; var diff = Math.abs(canvasx - x); if (diff < RANGE) { for (var col = 1; col < 3; col++) { // TODO(konigsberg): these will throw exceptions as data is removed. var vals = g.getValue(row, col); if (vals == null) { continue; } var val = vals[0]; var y = g.toDomCoords(null, val)[1]; var diff2 = Math.abs(canvasy - y); if (diff2 < RANGE) { var found = false; for (var i in processed) { var stored = processed[i]; if(stored[0] == row && stored[1] == col) { found = true; break; } } if (!found) { processed.push([row, col]); drawV4(x, y); } return; } } } } } } function upV4(event, g, context) { if (v4Active) { v4Active = false; } } function dblClickV4(event, g, context) { restorePositioning(g); } function drawV4(x, y) { var ctx = v4Canvas; ctx.strokeStyle = "#000000"; ctx.fillStyle = "#FFFF00"; ctx.beginPath(); ctx.arc(x,y,5,0,Math.PI*2,true); ctx.closePath(); ctx.stroke(); ctx.fill(); } function captureCanvas(canvas, area, g) { v4Canvas = canvas; } function restorePositioning(g) { g.updateOptions({ dateWindow: null, valueRange: null }); } dygraphs-2.2.1/tests/is-zoomed.html010064400000000000000000000062751437353256000142440ustar00 isZoomed method

    Determining Zoom

    It is possible to detect whether a chart has been zoomed in either axis by the use of the isZoomed function. If called with no argument, it will report whether either axis has been zoomed. Alternatively it can be called with an argument of either 'x' or 'y' and it will report the status of just that axis.

    Here's a simple example using drawCallback to display the various zoom states whenever the chart is zoomed:

    OUTPUT

    Zoomed: False

    Zoomed X: False

    Zoomed Y: False

    HTML
      new Dygraph(
    
      // containing div
      document.getElementById("zoomdiv"),
    
      // CSV or path to a CSV file.
      "Date,Temperature\n" +
      "2011-01-07,75\n" +
      "2011-01-08,70\n" +
      "2011-01-09,90\n" +
      "2011-01-10,30\n" +
      "2011-01-11,40\n" +
      "2011-01-12,60\n" +
      "2011-01-13,70\n" +
      "2011-01-14,40\n",
      {
        drawCallback: function(me, initial) {
        document.getElementById("zoomed").innerHTML = "" + me.isZoomed();
        document.getElementById("zoomedX").innerHTML = "" + me.isZoomed("x");
        document.getElementById("zoomedY").innerHTML = "" + me.isZoomed("y");
        }
      }
      );
    

    The Tests for zoom operations show a full example of this in action.

    dygraphs-2.2.1/tests/isolated-points.html010064400000000000000000000016771437353256000154550ustar00 isolated points
    dygraphs-2.2.1/tests/label-div.html010064400000000000000000000047451437353256000141750ustar00 label-div

    Chart with labels displayed in a separate div:

       

    The following chart should be the same as above. Here we use the labelsDiv id instead of the actual labelsDiv element.
    This is useful when the labelsDiv element has not been attached to the DOM when the chart/options is created:

       

    The following chart shows the labelsShowZeroValues option in use. When any point has a zero value the label is not shown. This is useful when there are many zero values in a point and the user is only interested in the non-zero points.

       
    dygraphs-2.2.1/tests/label_v.html010064400000000000000000000043731437353256000137370ustar00 demo

    This demo shows how you can create labels and ticks independently of one another.

    dygraphs-2.2.1/tests/labelsDateMilliseconds.html010064400000000000000000000034111437353256000167310ustar00 Milliseconds date labels

    Milliseconds display in date and time labels

    This shows how milliseconds are displayed when present.

    You can check it by hovering over corresponding points and comparing the value labels.

    dygraphs-2.2.1/tests/labelsDateUTC.html010064400000000000000000000040121437353256000147350ustar00 UTC date labels

    UTC date and time labels

    This shows how date ticks and labels may be generated according to local time (default) or UTC with the option labelsUTC.

    72 hours of random hourly data since 2009-Jul-23 18:00 UTC according to local time (top) and UTC (bottom):

    Please note the offset between the respective ticks in both plots. It should match your local time zone offset.

    You can also check it by hovering over corresponding points and comparing the value labels.

    Try different zoom levels to show that ticks are always placed at nice time boundaries.

    dygraphs-2.2.1/tests/labelsKMB.html010064400000000000000000000064631437353256000141310ustar00 labelsKMB

    labelsKMB:

    labelsKMG2:

    labelsKMG2 together with labelsKMB (legacy, violates standards):

    labelsKMG2 with yValueFormatter:

    labelsKMB with very small numbers:

    labelsKMG2 with very small numbers:

    labelsKMG2 together with labelsKMB (legacy, violates standards) with very small numbers:

    The curves are exponentials. Zooming in should reveal each of the 'K', 'M', 'B', etc. labels.

    dygraphs-2.2.1/tests/layout-options.html010064400000000000000000000020221437353256000153260ustar00 Layout Options

    Layout Options Update

    Stroke Width:

    dygraphs-2.2.1/tests/legend-formatter.html010064400000000000000000000046421437353256000155710ustar00 legendFormatter

    legendFormatter

    This page demonstrates use of legendFormatter, which can be used to create more complex legends than valueFormatter.

    dygraphs-2.2.1/tests/legend-values.html010064400000000000000000000045441437353256000150660ustar00 Label styles

    This page demonstrates different values for the legend option. Mouse over the charts to see the different behaviors.

    legend: "follow":
    legend: "always":
    legend: "never":
    legend: "onmouseover" (the default):
    dygraphs-2.2.1/tests/linear-regression-addseries.html010064400000000000000000000115351437353256000177220ustar00 Linear Regression

    XXX broken, needs updating to Dygraphs 2 API

    Linear Regression Demo

    Click the buttons to generate linear regressions over either data series. If you zoom in and click the regression button, the regression will only be run over visible points. Zoom back out to see what the local regression looks like over the full data.





    dygraphs-2.2.1/tests/linear-regression-fractions.html010064400000000000000000000110071437353256000177410ustar00 Linear Regression

    Linear Regression Demo

    Click the buttons to generate linear regressions over either data series. If you zoom in and click the regression button, the regression will only be run over visible points. Zoom back out to see what the local regression looks like over the full data.





    dygraphs-2.2.1/tests/linear-regression.html010064400000000000000000000104141437353256000157540ustar00 Linear Regression

    Linear Regression Demo

    Click the buttons to generate linear regressions over either data series. If you zoom in and click the regression button, the regression will only be run over visible points. Zoom back out to see what the local regression looks like over the full data.





    dygraphs-2.2.1/tests/link-interaction.html010064400000000000000000000054061437353256000156030ustar00 noise
    Zoom: hour day week month full Pan: left right dygraphs-2.2.1/tests/logscale.html010064400000000000000000000053651437353256000141260ustar00 log scale
    Current scales:

    X axis of dates

    (Note: when the x-axis is dates, logscale is ignored on that axis.)

    X axis of numbers

    dygraphs-2.2.1/tests/missing-data.html010064400000000000000000000062641437353256000147140ustar00 missing data
    dygraphs-2.2.1/tests/multi-scale.html010064400000000000000000000070101437353256000145410ustar00 multi-scale

    Gridlines and axis labels make charts easier to understand. They give the lines a clear scale. Unless you tell it otherwise, dygraphs will choose a y-axis and set of gridlines which include all of your data.

    If you have many series with different scales, this will compress the variation in all but the largest one. Standard ways to deal with this include secondary y-axes and log scales.

    If neither of these is to your liking, you can manually rescale your series and undo that scaling for the hover values. This demo shows how to do it.

    Hover over to see the original values. This is what the data looks like without any rescaling:

    dygraphs-2.2.1/tests/native-format.html010064400000000000000000000027021437353256000151010ustar00 Native Format

    These two charts should be indistinguishable:

    Same data, specified in a parsed format:

    dygraphs-2.2.1/tests/negative.html010064400000000000000000000025671437353256000141400ustar00 negatives

    All negatives (x-axis on top):

    Mixed (x-axis in middle):

    All positives (x-axis on bottom):

    dygraphs-2.2.1/tests/no-range.html010064400000000000000000000023061437353256000140330ustar00 no range

    Line should be visible in the middle of the chart:

    Line should be visible ~90% up the chart:

    dygraphs-2.2.1/tests/no-visibility.html010064400000000000000000000025301437353256000151250ustar00 no-visibility

    No visible series

    This test verifies that a chart will still draw without any JS errors when no series are visible.

    This graph's specified value range should be respected.

    dygraphs-2.2.1/tests/number-format.html010064400000000000000000000061311437353256000151030ustar00 Test of number formatting

    The default formatting mimics printf with %.pg where p is the precision to use. It turns out that JavaScript's toPrecision() method is almost but not exactly equal to %g; they differ for values with small absolute values (10^-1 to 10^-5 or so), with toPrecision() yielding strings that are longer than they should be (i.e. using fixed point where %g would use exponential).

    This test is intended to check that our formatting works properly for a variety of precisions.

    Precision to use (1 to 21):



    dygraphs-2.2.1/tests/numeric-axis.html010064400000000000000000000032721437353256000147340ustar00 numeric axis

    CSV data source:

    GViz data source:

    dygraphs-2.2.1/tests/numeric-gviz.html010064400000000000000000000027321437353256000147470ustar00 numeric gviz

    This tests that dygraphs can display gviz DataTable's with a numeric x-axis.

    dygraphs-2.2.1/tests/old-yrange-behavior.html010064400000000000000000000050011437353256000161560ustar00 Old y-axis range behavior

    Old y-axis range behavior

    In dygraphs 1.x, if you set a valueRange for the y-axis, zoomed in and then double-clicked to zoom out, then the chart would zoom out to the valueRange that you specified.

    Starting with dygraphs 2, double-clicking will zoom out to the full range of the chart's data. This makes the x- and y-axes behave identically.

    This demo shows you how to get the old behavior in dygraphs 2 using a plugin. View source to see how.

    dygraphs-2.2.1/tests/out-of-order.html010064400000000000000000000042561437353256000146550ustar00 out of order

    CSV data source out of order:

    gviz data source out of order:

    dygraphs-2.2.1/tests/per-series.html010064400000000000000000000062501437353256000144050ustar00 Per-Series Properties

    Chart with per-series properties

    Chart with per-series properties with legend.

    dygraphs-2.2.1/tests/perf.html010064400000000000000000000036641437353256000132710ustar00 perf

    Some numbers on a MacBook Pro 2.53 GHz Core 2 Duo
    commitFirefox 3.0.15Safari 4.0.3
    bb5899c56e33716db724cb60a5120b91f5fccdeb 28 ms/instantiation 15.02 ms/instantiation
    2847c1cf1a2874e9fe56b5749e6e105e37bb086a 49.27 ms/instantiation 24.48 ms/instantiation

    dygraphs-2.2.1/tests/plotter.html010064400000000000000000000061201437353256000140140ustar00 dygraphs Equation Plotter

    Equation:

    Preset functions:

    x range: to

    dygraphs-2.2.1/tests/plotters.html010064400000000000000000000265501437353256000142100ustar00 Plotters demo

    This page demonstrates how to build custom plotters with dygraphs. The plotter option allows you to write your own drawing logic. This can be used to achieve powerful customization. View source to see how the examples work.

    Bar Chart

    Here a specialized plotter is used to draw a bar plot rather than a line plot:

    Candle Chart

    Here a specialized plotter is used to combined four series into a unified "Candle" plot:

    Bar & Line Chart

    The plotter option may be set on a per-series basis to create mixed charts:

    Multi-column Bar Chart

    Mixed high/low bands and lines

    You can tweak the standard plotters list to achieve effects which would be difficult otherwise, e.g. drawing series with only confidence intervals and showing high/low bands only for some series.

    Smooth Lines

    See the smooth-plots demo for an example of a custom plotter which connects points using bezier curves instead of straight lines.

    dygraphs-2.2.1/tests/plugins.html010064400000000000000000000041051437353256000140050ustar00 plugins demo

    Plugins Demo

    dygraphs-2.2.1/tests/range-selector-anatomy.png010064400000000000000000004762541437353256000165460ustar00PNG  IHDRtgAMA a cHRMz&u0`:pQ< pHYs  iTXtXML:com.adobe.xmp 2015-03-21T13:03:13 Pixelmator 3.3.1 72 1 2 5 72 2000 1 1000 D@@IDATxdEՆLʺH*$IP+%,A,D.YA`%/ ,H\`% .Aܿifzg7Tx+ysj" " " " " " " " " " " " " " "u)C" " " " " " " " " " " " " " ]5Rtb-6 " " " " " " " " " " " " " ]J@nV%" " " " " " " " " " " " " tD@D@D@D@D@D@D@D@D@D@D@D@D@DK HҊQD@D@D@D@D@D@D@D@D@D@D@D@D@D@ڀt) ]Z1ʖHU.% AK+F j" " " " " " " " " " " " " "Хf|)[" $#+?OUL /pi#׿57 s=w?k0t (" " " " " " " " " " " u HЭFD`K-yꫯ>?xg?[s^'pg'?~cD駟>>a饗;Chl֯:ԩS݁Yf0zgeyk oV]i[ow^ţE?EzpV%"Ƣ}C< ap}UݷB /-~;;6o=sC9dPs?'~,袋?N򗿄83[.>vs'xbx*x- ?{XdaYg GuT6ljH|Gzw~я~|?ocۉ3L !" " " " " " " m {tt#۽+qw`RL%wcxm!!Çor-m"SUoZjAYzJ?y晧^z)~Ɋ|AXdM$7xcU+rj;&O\s +P#3͎7|s@0|=~#=ϐEC ,@wSPnK/h3~<Ӊ 3̐?~ W2, ay0ޛ|$tIU1ύ;*r6Hmnn$0eʔk$׻^HGz,n{t@`R+m7/ֺĵ s ]tQ.D nuQ6&>NZWcNn"},NO^_BO>p˂#_^"?9USaUi@̴TrUx[ou_ެ34SU>~ nŕt_L\Lw">{Kuk%ܽfm\?%6">=дgJlAB!)w"M,` Hө< ~0E]4k+EnWVN>e{Y@lVcVAڄN c3 [)C7C!1۾,%Ξ~,q,lyW2a其0o}Mbq\`Qv?) i wgY mG< Wn^z饴ndBV_}uW\cA_MUz/oa # AwU+w}, }zO3I~O~a=H"{nroIfR_Xzi\3a;|I#F5XF!X2oX.N}.1a8oF+{,0aM:5f*M0}Uy#7wf|SOeb~9 Z| +ne#/y@lW.ƪ Jbm87;LVX%жV^y馛|#6UDQ\aʔ)ӟt*[lh/hF[*O.MD"eYF$A8qb ,Mm|Xjvm{D?bﷵZ+ytCYM {axikcϵ{(} 'V^qlN8􏲾L[d̤8~6Ki>ǰL=箻6Q`Swc6u4jԨ*5i@P$h']8>QpRI"NV};Zq"Q,J7 7XE8Q_I"NFWr)O}SE`8oQ#q ؚ|w+EM׈~'G(x\I٪8q¼K" |'9-b8^e]61w.N:"Ns5WUv䓲yE&jQԯKE589\󌝀Q,3?a'(Q_Ҏ(NqB޲V9U% O)};mK;vlbBZEԶxvee9>ť"ZV_g?+VD$Ky_jÇ/;Ծ*/QP,hXI4iw(QJ}J w^iˋ(ʸTzr>Kh cʑbv"O,rr<+q1D᪈.Nj(x=khoe!.(VU?ue⢛"\M%cN\ؓlG]tQj3/;L8Rvg]^|"5>`I{ Tq 7(VN{<}(gԗFYmܢG!_ٗH)mgc1^;' FṈ >^, [&cS<{8L\DUu_\PDe v0.(XbTF_n;}(.+7i"h3VO>>hyg0VEUq/cqF3^K}D+kʤ" " " " " " " "`d; *'@-[}{IX_|ǒ W\qE\( kBDO/3r>/>CM7ݔGhV3|zDʭ0y+?I* !7e|8LAXaE{z#Ģ ˷c=ʊ+N&3,|=c儥,hmQ喭tBqF?:蠃 \-3?w'ĝh֒%!ꕃ#m (*$7fH۝/ u÷ *dqR>+mI'|r[?֕y[fFﭖ+7>dLZ,6RE;0)`%2i98wʔyeg|:3_ұK}.]G H͉ L+N&^$Ete^⋓pbhKrs7'6).4q z.$TgXqljkh\pFK) 5[&"=la̤-nmtm0F(ͥўAt#00UG,< s LO_q;h\2[d\Fk.vaIfCLIB40!wSiG?Jmk$S9Ubo夬aȢܑkE'q7-Rh+nVxI$Nڔ3H4cāǧ~ jk稇Fgkp> i8ӶJ_'6RgN}Sq#OKQ5erq?wa;_>!Ѯ( hEƜhٛʅ{^.gp ɍu)>X@@\38=e=3uN-m~,*mu]M6$ =pzNFXڒ{s1wl( 3Ǹk&M?)|YtA̭Sgq=F-뭖1}o'Roa'˔bȘd}q}Fg8M!"!.&:;ZQp3%/ aȂ"1m̅_+C#ec|S>ʄW J=*>d/՝v)f21">O[d{*yC{,@ ڿ/ {pFw ' iy @_M/(1GydLomSXϚKboas h,Iq+p?âu+[6K1V0X 䏶H%+.gYXؑ[_X}X1# 7yW e^߀=y1 X\vt 7~b 7(G'yoA@'Rjc|TIW&9㞟I(ca| 1)Hb3I$* L<['o}[UnL6cI8 eb,k`uc +7svG}=o,@u-܋dG<4 Mc@$o+N"OLl\L}C0 >}>NzVʎH``&Ù؆BoЩ4~Bm 1Y2Go&ϟyd݌dbmܬ\C(]#!@KSxbpK}C4Y\[zʈXmcs梇]G}뭖|/4QvHɢ'Yʉ((48J1~;5XAOaM(/BZFQG1xO lgʃtb.#PD6X}'qՔwR6,(FIꌾ>vWMV }1>DxX "#}'ݒgeB "Bc` #}1u dҖyp/x5˻@IGqDX;.?,X~+ 8XK; ,P3yam{qb {W<ϘH_O?zX\mw1?.mYzog;"cɛ\}Aܯ88n㾻ѣGۭHz"A ~DӋ `* XֵdL#01OڈNey-7wÜ˽L"ʐ<-,f|?Ǥ586k3%!&r;CiL̳x! ҝX<Zlʅ0h∗ tpc%Ov&3Lfˢfi;&Vje.;2!wxK h|(/—7֖X斚U)}#X| @(S/M8v,L||y=ְ]wuuCDFgj$ #Z"H؟!ķS 9y ,(MZKXYH ݼFD'1 :!d!X=r F9SؚuF[3A{6Ii۶xzzDªݷMy=IwY,xiN*a@\<r뭷NU( "o qyy71681nUhx.&A`lk_l]n 臼O,OwaI6n`a_Dn w0,pB#+nsH" |EAD@D@D@D@D@D@D@r-ϟo!',;)\A"^09Z\(&:0c,ϰ \e&L"xIn/Zf1Oeb0y#X[F\ʅ8&qh g(uAD6 ꕼa%)&l^k'DzfoqEzk,0lF>xߟDs,9ie'<}}1O̷3>􋲀ȈhgB0Sĕ\q Z]"nvt{RN#Dg:@9X XzṊio8#C;~a\Ҳ"'3Wm7XX !_M,hkqr"wO 'm1uFml ftLIclbw;n0clc Q/ 0Fνć/ub|MYExyG3kAŚ<[{*oꍣQ{ܸq|ǎŘЎ~|~L$Nu@[pZo"2Q C9:dR[9bWOb?Fz>0 X0Z/ ˤu/.0DE{%dx h&ss4;yd= F p&}`Q뜡݃j~D쏚穞gyA Waʔ)U"T[u ™y=ܨpO@?FkyF5+'vk.#Vi$.ukYߣX=6aoL${p˧^v;Gʇ5Y1W> \@,j/> Jڸ;1AyIql{;m^Lķw[8w~{ڽན)XX& m =9".N@OoM:,!ve}O gme<.ѧ`uoi@}cҋdm$$p;LץL2Ym.R2-DٲPo$v."@HбXTouc5;B8ga⛏Y[&O} d9ϓ?&qGJy˄PolY~H>/+|ѻ%.f 6+b47b0{_"4 UO Y>>xDYy8G;1,=w#>);_g8|c*\8[hԞՕ-\i15@~S 'qVGjØP֖y zA:ۓxL &P/wax}{(k~Mlf϶z7ujOD@D@D@D@D@D@D` AwڨgRzMPg,缘o|#liI&gkKxur>#N>=md5˽X0c!E 8NK}0>dO-([=OGrLf#7 BF(@:ŻG" " " " " " " "m$v[(?"ХtmpH\SO=5틇 >^ߙ0&7Zka&͊^_|xyyaB8-9;" E^dAoe0"v"t"M>i`x7vtxرɊݲӦr!'Hb#WwBX(kS_~O'~Sڸ`~ɳ|~T-#*5ڗrssK?L꒶K>yIt-WF+ ӲM~ɯ'H׏-!3܃8NuBbnIp)[Nz'Vz:88xwTJeVnh mfW-‹DG}!9D &TN7>/ԵƷy[{XkvQu~@ , BXV뮻n.(VWQl[lŪW~gyjC /" 7J[o ;cr-gȑ=a,%O=T83Cn-P[r%+q"@ӟ,Hh*}*ڮ#\]{IL}ׂ}Zˋh\P(Y6": lX־v}=)'uo 'Ċ|| >R/ wqV[%林#>_5fq1rZ/v#,#z6,9goV9 ++k\~ʌh~X<>o `@B믇gԐ8s>hAPw=أ1wReYښ[u@ea\~]u=N;_~?wDh+YD@D@D@D@D@D@D`RH@8[ZNlͅz\_fmE$aB{VK;yƱLd7Bw4jԨʾf%0naLl9C@b^ŕ%b.Ź+I/aITG cvq0)˯ѱi"b ~VDCqOMm/qʄ.5eh` O 6 LJ|EZ(U"-VnǏSo,@еx}rꫯ0ݲt2Bwyg2sep8\r%ᓀO۰>D~x?y%;7|<؋/䋼D)}ݩ]=:gKk97VF;V~au\ěbkƒ#Ŕ&b vZ8|Woʔ6`I~D KƘ7߼͔v#.-x`ʦnZ0 '|r qlUϡ ]v?]N{J8_»Dެ+ i{ycoq^fi[=Oω@+$BI4%$^EaBB"82z),@^`m`/nOc>l: +&,`QwrAOL+brT&xa͝k:s*B%!Z!"8/&ۙ)"^Yx 1cTY{YɃSEB(A%ćc/N'݋w#FH" A~ «~) 8bX1k7EmViǤsGB|D|ƽ7oJ=b.R}~;[)'mJ'B.qoi[=":mъ@_*1A,F`q'ȋ=q`5as?;eDߦW/Z-CX\ڌxE ,B`ToWJ"{bIEb,@[d1c6ϒ.1q ^xfq@y`Da\Xߢ" " " " " " " "$9RE(&;/ Z;Nڗ _&W&^,z=})ĝ2VL6A͑I^?ы}/.n}~$X1+{o^ibm}0¢ .L#xU#4a1 e3#VZkQD@ 6+dD\bE~ɧǝshi"f N#h8M]"MB;guC>< n/;zi#y7I< Gȃ$Qъ?KnDHN_Nj[ЛrQ?ą8O MsO S||p= >X"ZSOX">bs!S}!H'MўҖ(7gMu]Ӣ1Ziv? cƢe_W嵹&0gO`@;G{@68j} 1>v20Qo,d]A}~ hse>tnEl]ӛ@ٰ0ƍ2^',XLoډձ#eg~G x`?" " " " " " " "[t{KPϋ$zren$ܖF P4)7/WNIQ|L. .BǎV^y4)߃Ub^ܭ6|jS 0 yu3dnm ݲ{\Oˉ;;\EYspa s>&ia{\\lqN5+ UlK "|0ꫀU!B}Q6hLa@Ò Ä(@ bYFӟw-1Dm1Ye>F;J;'`;⽢b@_.NW/@G .0)oaƍ=>brN<` y4 ?Ʂ 9>#ϑz5KoLO},?mWkI('b8rҧ;y{rmxX y1s⤍:'O;VG#>L/uQ-7/[lE:E~kp|պQ>k飔2R)cK/BXYa}1k2i2:DXkY?/.x->@" J8P7`ʘs@_%62S'ћ@ݑ.k~1wS,ЖXYq">\{qr&},Pvꄱ1o3Ơz2v̘YXC,܈@ҡoO4+.[C>iً`K9x?2^›oċ^z00PŽ1x)^…~g6ERO " " " " " " " " H4]\}J L"1͇I[&Zg"T GqD+t=餓h1,7?!&}`gR_xlS[)|vhr^<%|W4H!l' <|Eq'u3y9)>3pF|9w>S?q&ne˽>`)'<Bi[QT++ˏ)wD+A+ ˞s/be'O淿~i}aAybϘMaLbm Wa6_z^D@D@D@D@D@D@D`h҉@-]z+eADh/n%.D&d- ,K>E]Tu/b,.M[n`[&{W-XsWDjҘdv*GcJ_~slt z{ zv Hm.! C]0K>;LVxqdBH'N\|zk^ef~}g%"y왍.~?4\eUV,sbF%"" " " " " " " " "0;7rA+v*/xu)➕SXr=qi=~g!oqiQ%i[2m/L,u?J,aOSܹZ`Ye|b@_ #w{@4IXn}*n_$bH'r-ܩJɆeT@IDAT  ~svN-c{WqN; 705\--" " " " " " " " " " AW W_}*瞻~4&0w97{o;;cd/XcU~KmVWE`0`#F\0<#w=X`/?Oc^Z#pyU$0]t%D2f%"Q#G ^{m% {ElThnx$B34Sqker>X)L;}7+{TxƇYg5 ,uD@D@D@D@D@D@D@D@D@D$v~ 0|+s.'S:JFD SZ$Ti>hS)lP/" " " " " " " " " " " " CݡS*4Dvz巾 tN]$]J3&Mܽ[n/~1,2"" " " " " " " " " " " "0tH:ut!w}ê^xp)4| / =\bf{wy{btrO+_ cǎ ,@83+[ž7tS8묳*za7E`ơUF9S[|ɷ~;Ys=/j WˇzhЇ>TΉ t@%Ie*7Ç+b~9$;Ӈ^;aV(}N'E@D@D@D@D@D@D@D@D@D@D@D@ CU.$p%j]w-=_vA׻gnѣönVZiGtND@D@D@D@D@D@D@D@D@D@D@D`C2U!;?4C#G,=I9#GD@D@D@D@D@D@D@D@D@D@D@D`p;OR?OֵyXϟÆ 3{n7n\;$0Ñ1 ʜ+"ƌLR;.,5k².fi0uG}4o J}=k" " " " " " " " " " " " tE 8ʺt_~9 >&_2KxìZs I\&L=3?<r!uAJ@.i)K//ѣ{${箴JI]c5Rt>묳O4Gm]vNATx0~vн" " " " " " " " " " " " ATYjx+REY$|K_9ߓzXuU+^wu}巾 tN]$]@ࢋ.*.Rz''gi[T&NX/" " " " " " " " " " " " CݡS* #G-RUUCD@D@D@D@D@D@D@D@D@D@D@D`hqhC'p7?5xü[s7'Xb0l)]nzD+{Yf%QU:Kw3Ϝ>3ΨZȐ%LG}ln>:Q);4;Cޜc9/&Oy|7.'k~?)?9| 3O>{0^,20Æ ЇWߛ$i['՛x)ST%;s30Cyp˘J믧3svXO>v?ozJ/IBg?~MP曯ߕo~lwӂ=; FBG&& _}qb嗯L >aV苨Gxw@#:L4)09%X.Bk_ZXk",K=?&-_'tRXq`n.Xu59|:ϻg sOwfٹK6,j*,)  ^#Ɛn~*QFm٦rW^ wuW[Ã>;,b-?E@D@D@D@D@D@D@D? HOJkH{Kݚ.ɒS'%vŋAT#'-8}&p,kQD@U/opꩧV=l%z ) u,Ij`FSD`ʔ)Ͳww[}sUlͰ8=v'Ondqyk]< P?l7ߋ3-]tdyKZex-1:EWn" AjCytecY@pd@/kIV\2% Iؾ|˸ "ZkVryD?&7cǎ &L:EW䷒^~D)m];E5QSyhfA@"-jfjVNx2.n$~ƒV{;ө:$;Vش{O_=_EK[R=|%y:@@eo 7Nw9G;2~:?CřgI}:c Ga޾\fǁ{R7͘@$LO@u]W.l>؄?דW=:,X 4ǯjx뭷McVa\SG! DJBTĝ%{ 21oۊA>X`9XIW|.x|Yx҄<<|p4oR`Rm$8bAM>bd/0D@a׮:x{$dDY O_ⴺB8&eJHORwʏM굌Ӿ|,≇35{OsI\9~Hu?k_0O}*׾/V\Z<=g{Y9' +'mv^OS_P 0}H|sӖDh8,M;iW>P_X&g6FگœK\}1SF5 c/?x1Ge<ߴ9+ri;21zU2'Vpe-GK |3Iuc-lq˩܌iyW+GN~Xni xF|!oA&Zx}H]$AsΩL{'V_}k oۇrHbsǧi7R'N ;S9Rǔư˜rVǚ{;mLlU]Ct`Qva}zSLv3*Æ K.뮻W\4$L}¯4)ïZHpeRK_RXeUº뮛3m|vaUWһӞw3¤5ܳ曧uW&o&xy49d",/$K/tM^N;4 dϦS̖[nz%WȈSdkY)+eb$Q~,^y*6Ԙ@$5`βv;H1I;E8afIv&]vٰF%sU\sM{3 AQ,/WŁ8裏g  bpkk%e"ؙg0+l(nCqAzS]O4)Y$R甛򱈅2bIJ?-*0y䑖t\b%G]Վ"DrvqNJX/[Vx8KGl < h,zGF}ƾ3m&93*sa"Y`,_3#"~".P[/;@U0c3u߻C9F``c>}v[`[k,>?m~bo|>`8—6fh=c-7WZiu hWGg}B߲@RAk<71;䝺!/|zXܽ=eQ5L0R/\mY@Ej>qb=G^z"NV>tP{Թ;NW‚=" 5Uk38_D񥈂j'KʎQ-uJ8_yE.QuǵoE$OyD "ZquBA"NfV"N5NJq;(U=ha'8ZW/R(^xaOq2YU- z=ztŶ@_S/Zl}>".(b刓EtZ~ ,mّ _ƅqqbEc*HUDk5BPkFU^ SD!8쳋(T/ :;qճ" B4zrmkdfh-WW"ŚkYU(qaGzɋ(zjA_„Sp"mH+~?WŬ"]|Q`K勋I1f\pAq||C[c*7L vQXgYDr{Yq/./-K[~PD}al$qDt}ß{h՛gqG}EQ`L5]оֹE6+MSt\^h\5PǼ -0~Eis` 'hZ>3ZdnmU9xp dsR {" 5GXEx+rkXml͕̻/ǧzD!jpo\DS~|^˾EE\tR<5qU(ԻN;zx(W5XGAD@D@D@D@D@D@D`` 9\`="2,Bܯ,o0*7 U5{rL*X|`3V" =7((&z.g+/'nœ~?\ #V[q*i;Ҋ+zš +)~,82<'F%cC,H9dY'Q%?q"nO@{GXaE@XZցƥP/Xz Dkk^@^2kϵrDX|aI|XQe’ #b{+;qQԑ1jB_BOVQd}0%o.h^{mjXGF#]`g7YdWei~w֞3mX1FDA+VK|be"VXa%꛺$Pk(L%VKt=/'n8eO`IL]6*Q {niqߧ1O1`Q?7֏fyL>u5γ@>W䕾jmq„ ɍ5{>`m | <;cn>vHzX3v?Ahpj-qB׋%zOCO:qF@}u Lz1^eKA}[&&mR[&kÄ,!Ze?=,vIVX&]Q@ŝ%e|ť!G-] G&}`2hbJC=g.ls3F,M[}GiS-qq/!~q;m@'<C; >礓N*3m .U >p'qznK񮳾M0A]{g̷ @|{gIѕId&~}|L"1q+!y1GFˡ}k +ā#8" X3> )ya$({bU$K>8 v/{B""B)J"N "vؽ>]ʋHK/MBtיX"W#| L3#N{qD&B_Sink$X-XgAHC]&x; 2lQ{8~L$#n'>R82&A/ ʞƓphMuʃHx86 X:i/+rխn ҄SE0}cqK&L۱S+}'!w~Opc-Hy'<g< 8xiW +ڴ1"16"u8"3(mž%*,d  r+r_~^&~7۬<t?ОSОkџxC[DH=;<8Gw8 {Ceas1@ˢ } K}e ]ް%O%.; ;cO{[;4yoQ.kq|`Iިi5ϸC_qxio}hKp 3,fG{B*ie3m{ 9ꔱG{Q`qBADM/'k>_!ZdUo=tdZ5Ōź[ꪕOtY㫹9ojc8n5{Z"ZNU1hV]t\O.dwŵ<ڴ+X\nF(T$(j"'u?/7gFQ ݦ=,0[1 DE(ZqR*(}^}}s`_8]~f{v"M}QF=G,^D!Ǧ? 5u" vK:D}<#,D-u"ZWfA*_do(fE_C,sUq:Tf\kQ(C⨣bꗴw(pVqqFEŪUD]X|i<(umx8l]H]Dw۩|Q$aQhk~QNml;ߩ0|G6QϚkڇϑfM~8UC{ 0f܎ .yߣZE i/(7B7y}c(z'^N ". m6or-UG tc`> dҾ[Q\>hQqY hjhi.{cy 7${nH>22ZWϲ@_NKϙcZC2wm".nise9#Pm5Ⲓ@bEP :=#2q⯦X!.UǻĒ ",p]'ԫC_niyrƇz+Y9rk[`O-+!{\b%:N(VX/c}eS )=cGcC<_,l`@zy^5%i7˯]vDq;ғr0gtnu(hUm#`E6'g-cJ? 0V`E}v$$ޞ;}9u2`e`ƺ,`uci"jN,ᆵԝY\'>`JE6tNXMƅE5N0-i?Y(P67wy f[잎y7]-}ȏ 89.c\( 0һîO\𓬢\ّp͏>"رc%qٳ+#}-0iHKdo\oW#Ͳn6H\ZpZ@OݰH\WSD@D@D@D@D@D@D@@*?*Wϋ`VbuDM&ҽ䥟ey&rΤ(.|qkW"-lg2O.d5OwUVZ6JW2 e5cƤ0ea@ ȸE)۸2bOdw\2YKEk>!N]]wu5i[\qgi#/m9C G=iD}.%DXA=#(}ąHSYvzϳh דrp6ho^`kvde#E*. ƛqP^ - [ݜ݃{[>jn7 hR>D<\!,fhs hO{Jw . I{LfL˅4Y1ѓD8Sʒ,=G1!zzHqk_ "ƌ\ /{7q3.gz""(O,kvd|mpOјB>c<`d=k], *{|y#c"*.hGSZ >Lg'" " " " " " "9t;V1Q{niɾWz >i=qx[DJ>I>L1g2܋ڔ}S E\Թ@fu"MD&}ULoӦrkST3H@Э'&4+ǨQ[#8>X9+?,|F[tIb\EBUxy !knʢF3]i͢8?oG.NN];Ўʄ7A1nk[ bbޞot{F!M{c,0GNa (b)Ve;t(cq'p{lP?E7n\[E˄kAfAMv{ޑė/!aqD` pgp$ A;ERXL<&wLd3IW 8.2vZ EE&HLd;=eqri&ryoώX5 ]y<^^C&qad`q qD&MJw.L4y>-i.IW+`Fwɹ'BeWY߸Ţη# ]o/d z.Whe iayh-}?wnT>|8Оrf'UoB3&?F o5 XOemY⢘qaN"/" c.@, XF A}e߉ b|g:Գ'o,y -3zkr{p{򼓏fXYnq,nm_+_{=o# Aw+AHK/-nV#̔8Ob$a7}/*+#&G})yJ?Ūޤ{t3<LLdMP F׹-wdp=w~Dv5bFmqpaac9&w1O_.,ߨv7ku"MW_˕߈w^"x$0[bX(OLVɟ=-'bϩf}Wh\X(W\~[3Fb(BZ|#-S1{S38-2_E ,­/Q(c`s  OL^Ü컚gV]/ȟiַqYdp/oMqw(;G9_I6Xvwu-3> " " " " " " "Н$vg(W]HqKm٦ sܷYbĉ,طuԨQi]Ⰰ%@OrANP=q/&Na|n' _y5W&ZmȑϯvB %73GQ|OHoy^?~C-(nH`BBR A @h));݃`[unvt+y3<<Z.1jo !{1 t1SMg t]_C.ԥoH]L?/d~? t/9S*sy1?vcuR@H9*p,Jt.?|Jxi嘶 ,vԝ%\2%|Ō&,"@s]v1'NXHsEM X fmh;9e`Q-`#LLڥ>N`E`v@세gF_hоʪKWXDh\B8t\sMMW/DP`@_Ek, >5<̉#e&W3 Ô,30Ң4]9}O7504Ԇu9bV& U1'u 78n#̸~Ku5!hi&ة]bi>};c|sj$v.`>8d{cϗs0U#}zf '/7V&VXwS RA~K?1mڐ<)W5>ǠAxw#\+/x7W#7RW(@cv@J.IЫ*U76pT|'":'p_N4ܢ3 ,ʂ|(8 <1bDJʥZ*&Bbuaq{%" /c=aO0@x0~0}b&"f.&F"\tEFALs/ _b%]Yf,J;K/d:ꨤ Eh@}^GP1 (ah:,̣G2B;׎L . \:^W'nrX`@8-Z1}SEWy_\`c/i`zaD`ncgp@OXvI''x%75؛oy=f? _X Ӓf yW;Y6r!60O3Жԍ1ڒs6&?Oݯv~& _(K?9ާїfsc5Æ K6_G &Ồ@c!xv-׾ hޙ4fX`N|h_}g?}+H;}oЖFJ;Mɢ6D~ZX|'(&VG.P_i4scǎM!i7?BB8v,L|X|94N;A*}-2i,G)q>D@PǘDVB)d\h!}O,{ @_Z}gqFY@Tl`@HNƯ݇9ahǣkP 0s^5S.}G A3YM=ذ[L"Ф?Ŝ\GyӔ|hD"a0s_ha#\F<˦w6ih33:&.ιv.s ̫ɏ7n\>F̟~"HEK ݛ /,nd'lNbg^a  V+ |/On;s '8mx; V/@!G # g5#B_.1t=ᄅ@|j 6 ᧅ`k&}!!7ڂj&tfgD@D@D@D@D@D@D@! n1\k#k=F]!?B*?]竬J@-@IDAT ,I`h%ܳ\(ژ_,2 B7Rgx|8M4N'x,s㛺M뭷^i`N,460yꗑ9s Z묳NbJ6&PW33j߉Uˋ]m,z)1|1{ɗ=>#nu6z"LFA+1_fb= | de3 }~>HvqǤ/KR_6J7i%o6!eicAA@][C gצf9oˏD@D@D@D@D@D@D@OY%@[ d-6x +.~a, R;F pfM4"4KX>SmcjӢMtM $O>9ь] cAS<Ǣrrs=*2#HCMS()Z}YU_ocei4#YhȭfWI瞃Zkͥͅ&*Bg oP#4hIϻ+7=4GۍeW^w-hYGg1g^b 'uus1tI&}EaE8h۬:2=R.^9#kR&uG?^ 6"G,?ȋ2tv*hršy-V_ޝy!;Zgs#+Vڭh3G/tgy 7ԇje)63`VM+ÙD_BOfxvL_W~(֖.=㊾L>j6a>G[-[=WpyQ,؝wa@KA5R3a`|:]~+u׳" " " " " " " hn[~~+'0 hT[Ћ=gډyiYD C=ʢ+КsH|W8,tEjJڬhy䑉`Fˏz ,ģ%iC+ fhbcG]]3"v~ ]~# LԢGp"ؑ/,H=Ӽr ϫLWG?h^NS~$ۈHVM0Izcn,4ȏ?',ZhN<#dm>/Zzaܯ+ic Bİ>҅&Xel؀A;9[7.X`LkmjR僦8sB&60MPƾp/x6i˸;̛)7Я)3ӰM'(s:\~ybL8lx`>~ȵwf9 X j3aE]i_7bLsiϻz5x]'s\Pč]? c+OX43yI qb}a#Z?|!cA<'s8&tC.?.C͕DkQ}sPslp3dȐdC$&}9֤C+1AKw߽nކ5<(1@ٵuc.8LXoh0&6 Pw߫qغXȻ|xmʬϹ:$vaRJUti,ұ  44Rhᯍb EvǕ"2&ѸN?0M5gyda:mV釦/ G! Yy#| b ~V@S$-],N#?Mb,tȇnKpO'0ƚL|vZ_N \j߆sLsM0`^hS!"r'%oJ[@"Zn^xw$ڎh2?C%s]@UHC7/ʧ@0ɢ~|F{S\xc`@t|aѪ[z?j0^{ҹ@#W6w{̩"ů,IP̒w*UHItIfwOא!C f9J.kQL {K[W\qWO@k&žf(S҈t擏8Ĝrk;flXpȑ#ݬg/" " " " " " " " " H[Z̜8qbݮF= U{WMz͛ntAW_5~m|b™)Ҍ1"L>佄^Sz'^zɜviP N,07 ۷/.쒘[&^AD@D@D@D@D@D@D@D@D@$ n4W!O$5uYnA?Gy >ܼk`s1.B9昣URe W]uA|-/1:g,R/ʈN/7n?~|2wꩧ66[b,#(@$-vm7s'C5o*D|P_L|͔P裏6O?}Wr풕SD@:J@CGp$ͮF3M7]b7L[C*(EVv[7xcTD sm" " " " " " " " " " " "- H-M.y~СxE@Qdr(ʷ[xwϞOS'{vn.A/c07JF" " " " " " " " " " " "  NPW%ϼzL0,"xE@'HCUfG Qa .(anG[F$ ;vl WtL.w?L7tT* 3̐Wt4t;E^v5\nvET@5V{=9}!CD)" " " " " " " " " " " " $ ˝JW^1 ,@)4?S!" " " " " W_}e|M?8D]IgyfD+⋣6l_d)" " " " "k<歷ފah~)" " " " " " @Uz[`gn" " " " " " ]]T s=_WE],BxE^Y| &caI&E+ND@D@D@D@D@D@ ! n!XiW#pGG)" " " " "{ N=f7,aS/~ Go." " " " " "vlh{*PHO?530ufkcmT@W& /EY\>},R8@; HCUVG\uUQa&l"anGZD@%pWTC0HϼOwYf>lh,5ƫ+{gP`s|gfqƮ\eMD@D@z4 t{t pgGAlxE^=Xo׺9qsQG;|۷Yzͮj^{mnɃ6X '`G .se]\pAB %ffR SN;tMTV[Uuŋ_~ٌ3\{W^IUq)H|7fI'MiWDw3brD@D@z t{V{m'N4ôNkVYe0Z" " " " " &]wE3첋7|c3sʴ+~?(>NzJ@~kK[omK.d"$wy3t{7>Ef 7wgwf„ 7t2dڿ1 I7D@D@D ?)(_e+]^;Б6Q@ P*;S_>I YV[07|^{͠ vWt]'v-1_?jK|{'C mR\C8#FT)Ç5\|t-" " "P tTݐosYgEkq | 34;ձ@B6?~|3^NǑ#G&l; ނO?mYds/{N >" " " h=Fk"$ Ȋ7_1JS~B!7|3Bo ڀ5~_34h!ÛniOS{!W^L6d7c>䓤{xh4v_f5aoy'?1-Yve);|w^{핺|HG 3&Z.HM \ݒe]*n%~]y:l0'x9@kT\s}~l_[o%Rwlx>䓩L29 A6*K.I=7j(;)2uO" " " *.H࣏>2W^yefS_EmvO?n?}UW_^>t+WQ}'fa`~>SSN9%|$'j6wV"D^'ыC#c1߷~|SI?L"EsȮ0"^WTuUD/ ir/ÆPK_Wfرfm 5hr-Z\tٍZt_D@DwjL=W\}7L={ ۭoOfg7x`+#8"e W>\"UW]se9s$f\]w]j6Z=0mfx$?Yp &bgy;;EfcZLCN0~G1?bĈThu:61cVV7|bƍg6d?*u ήy݉_G^NpSG`/橏U/$" " " " "[ښ>rVYmIVUӊL3MEVdͶ~WsyŎlAjQVU+.YUӧON/v1~L3T5W\dB4 ,@,$.cXAyjϣ/%+No%"R8=ѪUfZ۷oaJG.Y7L[xZ%-]3_I??f^{ǫ9BiL9O+!/.v)V.ݐJwո-OoY%Q/Pb~sysCgy( E@ o7q~#n]v .,xFZD@D@D@D@D@N[mUb2p|n?}VT}wbQK.h*QGDͺzXmޒoZ">sOMGz/;1}I'0J%e?gb&@;SVo 3̐J kJ;N5T[d]z̉]&ؘo<_p S;s#I}Ҏ;Mk8djE51Dl S?6]%i"iu>kQ<\?Q,8)Sm89>T!3~ŹSc> G=+" " ,RC1a.  " " " " " "P@ƍ" 'tҲ4|#G[dY ?TfM~>=X9ONk5/SiO8ᄊtZ@JM%M벪8^~2K/ڛ4Ep!_B#CyO+Z,.gT߰s,#FHՁoS\(Ν3*C^E@&i]1cճ?N1drc/ӧO0irOF*.Q4Ípي,}ٔZ˛X[oU?o{Ya_ÿ$TY]b3o*պkQ_Q/s}$_&3ߟʣn`ȨQ<050)T]g\W_mXpc&sIR#EZ{V'}~.sonܿycӱC9owٵ_7@3~CzF"~x^{UTbE@XSf, 5{y{9瘻+3m/x6gC̿4~'fN!DL5\wqO_V#NZk-#>SX1sIGoFjV5\ N(sĉ^ =W*LŻ| b`nEѥC˧Ѵ/ZcڳcL^Qn.ԉ4yz˭V'BX`T/y&>tŪb&Ypl7g.VshP3]5V[mee-L" " " " " "PבI_DCG1&L0Zp_*3آY_ApPMP힋 0o?wc(D0~ͭޚxͫZZmW}Q/-IDĄ0s=w*}IPj tR닙wy-%\"%RQy4pVvX3xǍ[vyuk>Zu;nvq>M" " " \n:h1}P" " " " " "Pf׿ҢzG',ܘG ?OnO;i8SGȘ`ѿ_yP6<º駟n/D&۽[Φ-̘n5@':4L5Ki3g5nHnhϳVn.92)/Lv*<1/Ij!ﱾ G $`~|kj,uOD@D@!PWV=9(tO>d~.PCUAD@D@D@D@D@G!.b8=3 ,Yqa楨YA=uiah0 3?">&2}>K~<3lp>hj.q+?o"/~*J13/SqaD, jzbVw;Bީc)Vn3qڵStD@D@D@D@D@D+f^zp Qa.#F$f1}aGّׯ_ǧ{̿jvV@cM%3.1'c@a yYXD HrI{" lE + 6 >&Wwu]b/&W\RfjkAUXv,M,97tSsq%7&]@:d裏FM/^!e"te. }H#\Μ6p161a>4XX3 F H(1r-Z7<H|sl930pѥo1 70w}wxGuT[b.3+Bo?8y~u)N~i3j(wy})\bSX^TK_^y"\tlYfeR×3R f|T.}ǢyVv駗.3E%":w})( "pFxMpճ>;[Lև#0ՆNrd!]@&?jʣ$!Ol͖ʙmCk4<]ѣeS?L7tg]½29"(; ;̓KoHRNzmU6;PM">bYg5_V ATOXc9 ?SJa;}ϛ{y0ve#x^|ee1V^}r9L:fW7h>13ai`׿u҇0-3W]uUr%x MB71&L0 ͌`b~_z饤ti":7>E%To}feX('e0rH7?މO]?s)_\sNq]7{\eU 7&/sL+sI|$*@w%pqǕBFcwQwWRE@D@D@D@D@:Dj 9cZg}Vϛj{>GVXB+x+L=VX38bݖITzWwBВ-~vs+IuyU;|<k괩Pϲ<8㌩Z!uEy\Yn(Yi|C|7vY_~ysVxT-NY#Z^{%iXo{U)z,[Vڕw/H/֒C]>ڍ%$>l7ӹ":yM1%+M#6cqvN*\jFEpԘiE)RD@D@ wkguVQF|шTXqM̔1+]N?f=LU5JSSOmnFsaBD{53Zy/j[yEe.3 (5,HFcߜ^:ѲEހ'x"(.Oc4ێE>X;餓RIz`NX@$m~\+HPT!ЕT$~1Q ,cxĉyΟ'>>w1ܮ@dD;qƙSO=51O뮻.;yfa0|li7p5JIzk1WGJF;__Q߾} 7`v}|9;}[ʶf==)z,5ZVڼg3 O?;'4'ցMgn>;Y@" " " m(#F$h'I_ON" Xv-8ЌP>Cy|?t{7-}-ao5oW1nKMn_N?_7~" wyMk|6熚m;vl]YgIh'#̃獆>7060yݧy/~E h򡽎c`Gt=fK_u1MM34Uꫯ5l*!_.~x{s(KSXj>"xRj1)M9v+30Gj$N*7=/" "{ H{۾[7|hC=Lkn%v$ ?0'뮻&|hggs[裏Nԅ@Bg ݑ1aj&anwlPչ&v. jp=$r!fM6HX+d'uw hjaa^2zk\rD@KVt{7>p B0+CxdAӅhoe4j}B憍6ڨ>+߱X*^OT}CM.}ixo S`G@D8s5:th4^"Н `V w_CkꆞSmRK-UKa qO}h6O+-jܰ^}U曛w1nN1l´͚kP?UJhUnJGLZC5xo?"= " " " " " " qƹ( x+̠0뮻QzW_}ufBT2[l?~|3^N!ȑ#3E{}Yds7]76G y{N)ݚ#"P+$-dm1/Q3۷oanMйtvVRXt<묳V[m_\=^veXeU ~/1l0'x94}юB&L?l/W~뭷qi2sZ,+fO>_>);B{5jN_:LBP{K.$U{ ݉ĉ\q[!FG) ˋ.(Ml*j .Y{(Rr^2oF3O"JPD>S3䓛5<X<ͼ5SyTD裏zË0lv\> rJsOF7,"Dd4A:09H,,h~~7MJSD(K:3u/ rǎhfi駟n(fB#}/CHu*1gwO0C@IDAThcƌgiǤ&lR_@72Fqҽ`̈́Mq.зG.uls!s?姣6a, 0k~k?U<4쳛<0|GSOm>\ .0W]uUbΕM>1W|jEj`Q͠n<Ih?]8p+w1waX@uYRK-e dz ڇz:GhaK}СfVH=GuPl2VvZBJ}9/̗^z\y橧J>n#Ȍ3h{D+dQ ?l:wup2,FA3Ϙg}ָ: :` !CB13Ŭc ]L_/>oK^ 2|h(2xXf.´zp&s'B7?\wu {~ut猥O<14heI6~ ʹ_sV5|2&ZOl V 0nܸVf(Z;R"f޵7? Cvy@ _Ewe" " " " "  G "-X ؕLvw.͒5[B¤VjFU?Pf[KV "| 6(~Ȫ Yy[͊ig=?~|if׊+XTX 'vd}3 KV /%+No%"R8=ѪUfZZz}nD(Yҏ?X|ôծ?ɊiK%]Z5I??f|UϜznH=H{ťiGŽzO,ժ53\I*>W~hO^~8{JvCLcnH*YMh+x2Ƴ9+mm͎\b>?+Y \5ijGʩ5?XƓ`7%N:q~ Jvai/vi'?E@D@D@D@D@D@DKIabe,9@{ ^R:ARL~[Uld3 _䳳\fZY~Z[ni^?:uN?殻JshZsՂ9 `[| z!Fij40acɉv'f-cfg]Uje(.ZaRXd3̇vu]S fVJD{V-gxPX7XЇ.ii/' e-?19|G&s(s1~=\+Ƈc9˯_m~OhcQ)N*1R]t/Xqb7* O |G^Ko^Cn7&#" " " " " " ]]5TXȍb' sTyL-jw,p0M˧Zx. d?>zg14fYd sW\qE407`7 ^b%O2?xT믟#^xFȁG}}0̻Aˢʤa; p|0V,3<˨ޱ{8̂cR6 l"h6ɥ՟X'6W+2.oE[λ޾WB5vyW٬oXԜסfDSϮv_JƬr!>"/y}˕BK "P]MY2J1۪/Raz7eΧrƒ*+`Φ=݉*-袩GuK57\oZ">~oS>4IVxJhL>iH߿*-L.[AF*-檭&T8K30C*mkF*;N5T[d]z_]&c7DS[橭҆ny?Lp$~JIw1qvɒc+cc)6ylJEpq7>Ym򞳪m1T}FSUV{.F'{ZǮ 1bD~m$uG7WG)?l{ _ao+?ZMFEYYl[nA#csn伨y9zQ~V;jύS X7 lsH?L1o%QD@D@D@D@D@D@ l;*hf."]X=⮾j3f̘?{%IjS"ͥV}}|.:H=zq{E|z)S"C2ޘح㊘*w[VYwzhwH5V:}ra*`5><3s :YT"x;glYxEGMfE2=s]wݕj/͚3 ?鬆AЗxO<1v8 ,DŽ.\sUѿbdZke xk ާz K .<7h#c5͚kYNSt}y'E9qDxyIŻ| b`MFѥC˧Ѵ/ZcڳcL^Qn.ԩ;nշZf;V+{OfmݰV|pꫯ _"|"v \1sngIs}U'ZFbE \XK.SIͥk&lj):5-7l@Vj/X!\"欬ݖoV|Svj7G=;S4E栃2GN\w%7%QD@D@D@D@D@D@:J@ݎW|&Kk@z\s&`!YfŌ]_- ԫмFU\c³V k=%^믿~9̃>hcDG~oe4t FB.y\0L3:&AfTX qc+w8.vsih)m ^{-O.BpKEz^]a~`&jv9+V۲͊o?bSQLe'sJv_qT\ f=V 'l# ?(" " " " " " $P]Κ,lL %P ugqFb+eAm0hB [l ֆ a>Ê}FV$ .Ȁ˿uҘ~D;tPsEt̳2"(ꫯcї_~YXzEYC]vszr%YgeFY־](CHٌ0|{yPfO>_3zc=6,K?8tc].Y_gce0 U,̰{I4f+e\^aI4¼k0}c{~4z5B2?: <{E|~ 3c?{ lc<ӏ?l⻈1K+|.=;|.&W\1u>*1|7SO/b t4" " " " " " JKŨh>bB)Ҭg' '90 |?30tp1|7x}bц;ꨣBoU槯vB{g'V1S_2yַi" m?d裏Nw=3&P/byywػKk!Kq=4c?OsJ"bs6<;1g]xڟ0OK?hРr;=t2J K;16gfU&4ӲU|' &[n<3e" " " " " =4t{B+w`G|,l-u嫸 <裩L\sTx衇i[CI? < vmg0U\+,袩$hΙ]v9S-:+(駟6Fisϭx],]rUN(sOZ)§~jMr).izBL2~Fڅ? tx]TőD}1SB'ڧ=ؔ_Ђ.lZYLoV~ǽBSO=l#m/0ZEY~1JḰ.|ډyTb<'LeƸM74_-ELg\[w'7l^뮉F6'_6w\p[aKTl͖ٝ;<>v'Y/Ov=~ S~?܏Jᄅ ~#s1I"TSMc_u7_^oJ'" " =R.GsXeYJV]=B`.\>W J1}.YT6۬zکSi?Jk$}oSΩ?Ux\ڛ*Hţyqm}u즏hZFy'/1<#%R}KvQ:+l{UhUWMV⒔{h586EYͰ1@4ҟ>K2Ǿj{"o]}t̗;~3؅ e*~{4?&PwFxmMx@Vr~cƌyַ"󌋼bƎyL@[ fWO硥PLa}Igkk{m({XPZh%3~k伨9:41gD-s@XVꤸ|C+fUf؅^X~-+('V`V[m5c74>VPetog\+pOSE\OxK+h[Con<ۃ~6.kvo2L̪ X-mc4a`*A!" "HZ}+L*t]0]Ƃ}~:wnwdBD8`5M-_PDVM@9 8,Yd/1/, 0uR`SyLUDm)BS&mT@s` ^{GC_z?-g=`ɯ}>@<{I"4>Ykd@މ-YͻyК.#`RiPO󘳚׍|;& O|E5>^:ybyL;V |JS8 "dgkay}9_c5J V_R-{$׸f\+sf1'|rΥ]3K>C%V%Nw# }{j'<:0Dn·._ǂLz[m_X++ND@D@G`N{T[eY&Ѳ +u 74~`^įlfwğ.q[@3ĜZnw}<3^{  h:9o?h}b@VϳW(L$-F4n/ٿfiۉe4iijVEs ~ Zh!\t z"4>= 8&~{į6Ev"?dO1&D1gY?juLo߾ k\0JP}<8tamZxG͢.ZX" , dH% }7;k+~~;~ul_?_@W& ]uza10*pN]*Do 'f?_6 J~T<&mĬ.4j\A"D4'eY0"d!rK>Y6bd ŜrO6C.ʹOO9Z dWx"XE ]EYyCcT<NH]wT&n*4Հi ^v+#8(Z܂X,b~ms$16a51=HzQmݖl "?'80Oz ډviB 2m];PfU=Њ_e=>tLߑl{44]V[NQ&ӯd.vXJ@pckI ><'7?A`3*'M!uCV:ʛ5]DP!C6gjcgY[~`AU.yOB50jԨd~#Z9:9nȨfn=kK1s ԕᎌ-\0$q;1#^+կpeU.Kã ]fib0 y~dR]I3nܸ*HouJ`Sh/̐~3`J`#>fY͊<1iIVrAb5*n[eR8 &~FY*R5o>GdjV %VJc5͢yi8%XT^=n+No%"R~#&x*3|SO=d`S9Ž=:eajס;W2"Q|0[K??5Wk,bbw&V_'uyQD zx ϬG< eXCBJ%J&RRok}~ w8{^6lXu=O\TqIX a>R>Rw׺d''O9Pd60䩧}ob }EiOfRxΝ}knxlWU}T  "DD43#`b]jk#@~ +2qwY=zp ٳ[L̚*]vy{$'?nv1V{*jdBE"e2;gg[x{p D +z(z뭷D]}zM۪+b!@LX Z{P7۾$IwlwbZ 9Q%ѣ dŎ{Zo]ҥKW_f}1cXԝܵ)եq۶m:uPo_TOZ׊6k,G[53͜ڮ*5 ƭ1ěR=xMWRܶtA9kt_jZV!C]S:ur7&w蹺ZV7㶤1D۲"d#*){ןlOeS%4LS9MWZh޼yHt3nJ&uCnRWȮqWU.Bg~nq=un[g@@&Pj1'pYgY]=|L 0a;.GՅQ  @ bqksgd0ri"6Jܹ+ߩJ)7=0֨G"Vw8!l⛆}M6=-mYDYuo|=8>7oQV۵\6 툲zٲe/rf͚eW l⑇O;zt&~VMuڦQYSySR3ό.c?X?cYlDY{ׯ_w}?a-k8u^̏a7l{k;xw:'r}'s,Ѯa2cxgp8qb.&t}?$J P  -ݘ_tЛ /BX끰ŋ.>`&n,0i9 چ @BXZ 0U) ]UVE*ܛs2er992q=[,FdU :>(M=۫|Vyٔ~ڵk#̰6{lSpwyrǬtznӦMXMyNxc)bѧNʸ|V2Vy'KӦ+Sl"e JC (k;o zh?ދF_~aUtUi vEǝOz(1ƺZ.\>A<+|=bt KHýСCsFWϺBg9'O_WnKi]on9}T/m/ȸB?nDZ'] d N ؗR;fEv\^Y3B EUY $f784s='߭R4 @BlcAe-1l0 mN4֟a@tƾS]azKNmѢErs"BGxժU#3juF{.v7UÞx]z+k{EKjcGfw+^zI=,YXB A4(Os,#GW?tsGWߗgۆۏ>ݲƥ7Dے7 e[Vd\ߴ !@@dI `vETBZ!$7 H @*4f?i MwZqU% *nzPҘո<;AE;t`Í5M&[|[c) J:;(ۑ؟5Uk֬|PE̕=z}xUNWa?O>b,{onGtZIch/=We>RTOWvr?c#z}usBn"y?iO;Ϙ Kd>>rxa~f&r>"F-qt _c|^Às7?ɸ⩛iT\߈! a(t+.CC֟[$@ @ 3 `ĈVRkB&äTRn|=gKY@KȋA.?c^n_??I,bMG}aY7k[ֺYO?VR# [XOԁ VWYuԌd*G@ dcZ'1*Vh+ xd_~<hRu{!Zr[Nn&khBU'l0Z(j֬)7oSU(~gQWzW[,&1(u/y'#ꫫRWFt HG999ر#BN*i~QĹ2y/1t 3)\21TX:GyjDh~+W|R nR tOX2ApVб~f*%s}35F d LP@oO1Z  @ bG@N8: /*/*Xz3f̰*sUl VKނHM4v2࣏>Zu~~9u<;#ٽ{wB-q$JצUeR9^XM6fhIݮUn֎X-۱ދunm˖se3[l[Uih:^*Zqu>{hM7S}͚5y' (h xǭ d# @C=d駟182 .;ny6M tjOj Kv66 }k`k޵lMæ@b55gkvQ9._txk-n^<XTlwۈl V{뭷 @@zMWZMdڴi%Ƅ-"A @K`׮]2f+uE\#ؽ{+Ì>~N|P{KboRoroKҕl4O?XC~pr)n3ϋ-k6}8nL is\rnonQѧ*E:dO#wulٲ%\-mJ"UVz%KZz g:s?ԟ_%gEWx5tܹ>ջ@9OVz3όenf>}xy*^W@2Ͳ ^;~xpTK CwyG&رcхa\@6rzWfl̦{jw|7:=zpU+Vu1^HΝ;=L}Uڪ}ҥyصVԠAUt1n&ZT4kL>3yWѫhVk]U]wuޡ8/Cf7vbRnذ!BQVTŋ?|P9]}*7v^renz-u#ntSjalnU!lS;fVZR~}ꫯZ |ɲlٲ*:Gʱ.]8}|{CuA:$r}p~'AYV^NzyuNJmFr6wn}UYf\ݶuûロ'[BXטd$+_B ݬ;qum_ի'C@(hwL:5w^zs\7$- $E@MUQO۷o_8ezݛԝlA%uw9qrjJTܽ{wm۶-j~2*:/VlEU?Q{WDpеm=魯 mzErW;'MGٳG+4h[ zdOuUwTqU@t3իrm.e5_~uýw^*u HA_GPף6[J'DOq㚎>ꨣR誗[:me>Ӎ9=\) >VoFR6b! (DkS$0qDkMug忉b- F.Aj1eS2DF@&p=X6Lʔ)cKP[zSA^ S׫̭Qc-6UTWRE^~eꪫvUVjUNm&B l믗QFYTIxGh u֏U* z>uZiڧM1{1[#Ӹjoԩ,XouVj]\S$z}t.5^Ӡ-n}>S2|p([nq, x2uA! @ i(tFH][֖tG7  PԂ'(Zʹ8a„"!@ Wv\ښj+_inw 2bRb[R+) |rk"6j]u˜s%*[Dz{ubڪ%ZwjYU J@U\T? ղY7nP%ūuK9S9FcOUjZ}6l0pS-5SRٲeeƌ(Z{:XcjFy;ñT+%?קqNe6*n(6mec3Tg?uӍoڴ7b' ?8%!<uע\N_s@@:fAul"S7j$@H 0`@T.Ѝ=< WpBMEPatOW~KM?N/^(X:YyCMj_n$p@0"@IDAT#8oKGH{Bݭjl͛KVr\t圜UZ5OU%K x[,\s}׫}Ts:qG=7b?x(Q `'Bi qή\x߿_9 @U΍f?HV<@M@]RNcZ t'OnbI֪LsjvmI[57iHK @ H\d$t7.Ic~~1 %p:5f^PR7z#;5oP} I&Y;wΈ2WGnڷo=N[nuuɹe˖ vG(4(,@ @O.2e?uY-֎B@@qSO=5{˜فɀ @<4|p8-=zin*-Z,V7x5JzMo6l{9 @ p _c۬X"Yf1!G@(,T0zh׿uH۷wu4i (JEj׮-k֬%Krsԩt;0=ӕ?.C HR3<#eʔIe!@ dYu v4jܸ\RB?s@@#8E67`K.ܜ>S\ @孷ފ({\˕+'_W7i*U<~DY_>˖-+W_}kL)c @ )a/bj7|\uUr J`ƍr;u#GtLr:*&2!ȲeˤM6$t Zjyzd3fL\_~aQw3f̐'|Τ;vʕ+KեUVr'駟.5jHem@ @ņ bs) Dm.׈ZJ6l!G@(nJ裏ɓ'K#@6@pرc#̙3G;<5KB7qxdΈ\@ @ЍFxg嬳Ίh邏N^{-B PT{о}?8+~x≁eȀ ԅƬ^]dlٲ@޽۱ ^tCWݛƍ'5k8 @ @ɔDCBG^pV9B@@Q!>39?[ݺu뮻ΉxX! {12k׮둓#ڵsx}3 @ <,t [OJol޼Yʕ+W0A@(~7k;:Yҝ4i\Q) d lԨlڴ)b/&" @ @ Ȳ2 qmIsQ  HTR2m4ǚ+honZfϞT9 !/Z͚5/YÁB @@yI8XǏ3p@! @(ٳcm۶x=f4"+RJ`jc  @.gtKcy|9@@q!k.9rQ `5_jըȄ P|GrQGELKClܸQ*T @ W=s~' 2*G@(.ʔ)#cǎ'|R8iM>]ڵk' ,,C H`ܸqi]~(sdB @@B7[|cVݻ7oFի!G@( 93E%UnV<@ł~+7嫯 ̳V@@ @(- G6en=P䅡o@8>Xϟ/_r%ҿ3>F: I?޽{̵A@ @Ln6_4]\`# k˗wb>C 3Hdɒ%eȀ P ܹSFm¥^j# @ d3|85k;у<# @Bw6i$p_o^bV$@ W4CSNT-[&v ,D "@ﶎr(sdB @PH &X[2dU ː@ Uv-?D4ˉ'!G@ @v.Y+/e̘1~Xª̭ZtIqA!@#pYg9/]ntQƏ\gԆ cSԟ8Z @ @.. JuذaC9^_5ΤIyC %KZB z-[ˀ6~E9eo!0zhk#FJ! @ @ .deΝ;bŊy^\94hs}y{˖-[reg}&r{3 @$裏ֵŸwѼysyUVg@)!X4,?)SF6l ˗gq@ @@XFYe˖Uz/"syҦM7nlܸQy2C(s9 d\ 4n8+WJv$F`E2  k[6l\d @ Ѝ(۶m+K.9Zj9]x9 @ m&e-Ktc @2֮]+M66j*'5! @ @ D5kjOPHzs@@\<ӎ6Z묳^={=*B ;vŋKܻws{rd@2ez?p _9 @ @@*U^gSO=U֭+\s|)iF @իK/$7|sƎ+w|7Qˑ @%;tO<1"9' @ $F dĪPxWgϞiG!3h {='?XL22uTѣG`2 (zZjn: @ @$n>juI>c:t4nX֮]~h >N8!NM^^{-!)( BsQZ  @ M ݸQeOKÆ 6r;#jK @` hnyDI.]dN(hȃ+Cx(Nj֬ @ `?).yPfͫB@a'PdI_t>iݺAECYJ .@k% @ ,t5֭+6mJ|6m*o4j( @!׿6x 7ȿ/)QQA , uVU۷/b~var @ $Fpʚ6M62{lJ[ А j{WDmyԨQrꩧ͛#(&L`Uj|n3C@ @ \zIe c=VfΜ/r 0 @ w!>;on 9}wZg9l0! @ @@8ѠA̳gϞ+HʕS@ gq,]4ƍE7n}330z wEG7v=B @ @?nžVzeԩRl٤ۢ@2O9sȐ!Cv>b۷h,M =ƌcW^)p5! @ @@8B/~8Ƭ$!@En̺dĉRDO^xAڷo/ .,3f@<DUE" @ @?roŀ@ݺu=no" @Bh"iѢE`k׮#BMb$@x*ti̵A@ @HI,p;''G1֛3f̐nݺW   ٫W/QWAI's *(B~WWl۶-bop r @ $G zA1WtiyP$E@@#Ю];qAaܹ3r@xgC=T?"2 @ @(ZP6i$jժUyQG _+WgyFFuU3\2j92!M;pĈQݰ[+! @ @ .(t”6l8q͛;wn:  dqeٳvځ^daڴieȀ />@/^1K~" @ @H X,[X̙#͚5e# @ K tI-Z5Þ={o߾|wRRLEرc>|-[֚ @ Mam~sܹ̚5K֭ ԨQCO.7tSTw}osڵQˑ k֬{:AY!@ @H X,[+mu&TZXΗIA %J_o J}jJ^{"!BBsFPka @  |aˎJ^g-Nc @@<?xѸ{l`_~EN=T5j;f Pl߾]Ԫޖ. @ @)$B70[Suqt?.999m 4 bo\}Q{[k׮G-G& yO=5uǎ# @ eBa9ᴶ]YL`׾}҆ү~n`J_}@a!'jE? *}fMвeK#:2e'B-)-[dT' @h3ҥK921@(X膖\- A@ 1PZ @ Uw`fѷm69",)skժ%~z!@@$@C@QfW: @J5Yf_u\sS6o@z u].J! @ @ P視'A  #wyL:UJ,X_mʼyː˝B~1 @  4Y@ z`nݺu` 61#srd@'pZ2dT^ݚ @ @z@ '͛ˇ~( ZCݻO4. H?~AxkG_|U @ @ @ 'e: &r JӦMv… RDZştIr衇 @ @ (tD@ 8sdѢEҢE֮]+Gy<#eȀ#g'ε/&F@ @@ M#\ @HZ}GҿKwСryˑK/$ڟ4i"ݺu9 @ @ P0C $FB 2qDV4itQ>Ȅ#0zhk+RJ` i @ F!cv߽`{n}K1 Dֶu0Y˿L4E!]4^g!֭ *4g4d@y,X@:uG'999qF  v^S2 @h-t3B P0] @ @vU۷O(]tܹӓ! (cZ\z(sdB @On @@T"SN;#j+Ǐ>ZꫨȄ֯_/ܖ.B @ @ B7 @ 9PH yVZ}gҪU+yː [ի4iĖ  @ @t@ Gu,^Xv={O>rWoX @v풻/q@ @@ t@ dԨQCf̘!dԨQMrjΜ9OKÆ ˑHow^gyĜ5#vaҥK9@ n* ,,T|yѵM͚5rʁȀ ( @%JW_ݻl۶:;.UYխ[7k(֬Y#Zj2|p, kP#FUA@ ޽{d5mT򗿈m? @w\@ Pd p dɒr)r׋c&A TXљ? \sԫWO,ޓKRR%}yD@ Xj?لkݘJ x  @[[5btM>Z1 P,2֛<#r;V|޽N[4xذaRLs @O`rʄ =@@!B\* @ Ddɒr7ӣ*}]јjH@q&Wz窯38C<@+/7;x@ d}dРAfSz BM08@ DC-[&?Zt,of9rh<^ry[a+3p@S-  @@4i"]w*l7o,{ cK#Fpb6k̖  ,L dF9W]u;6pw\U^= ںK^0]~UZ9X4:x )xц~饗F& @  5\Ν;塇r6כ~WW_9 ,$B7 /:S @ŝ@NN3Ft"ύk&mڴiӦIǎٜN`çn:o1qo7D* ^[$/yIT"U̹J*ύw&e B I arW:ʕK-*C ([\veҶm[7)[oڵkE$@^k!P9鸾[ٺzٵ0d@$лwoYdj* 6G-ƍ͓Jip`NOSkV:NU[Y=oJtyÙ*uX8y0EXyTY3sMHBwN6u|; |R  #H(.?x 2eʔ2M@$27r^0"p["d P\Ẉ"ꚱGȓbT?,%˔ uHIf3wg+WZ.]F͒Y'89Mv{l2y1k:Ց4e2~Hj|ǥsЮ]|K.wzSZGREKC!@ Ao1U[UKF妕oQ< В#rs:!+7|#x`D:tsF@@@=ךB T-zjCN:rpKT1r7NY9^EyUD78A@?nԂ@A4iQ-q7n,?,cMJ[<$̟d܍˻G+ծV:5n<((5k7vbGy >\8 )UTԲ9nq (h+8tcZjVVm6?~3 !P sβ`\peR‗dєEj!+]!߭FϗկMY+s*xױIZNw߼ytZjɬYE,U9tB7ti6| K'/VI WOYǑmq]Ǝqi4$z7 @(2( ի_Y]+"*ǧlUMw?* Zyt⺐zhY[4eǪ]3? |u[ܩ@ 4,^xL<ڵF%2d͔ _^eJc4`:dzt +ƍW|XA[:zhtB+s;Li_8nhB^ dHn1tcuެUC> G@zo W0K{ھnj9 @۶m]TV4!dYq$RK@zs3)VZoͪWݍ :}CYwj,_+Io2WǝlZB8ٹP@a& 'xBt"_|qP瞓-[K6cֵYFϓN;Y#|d-Rn4{=ޗZ!T >.w^uzj!)Suo~3wHЀJCT&5k+"۷oԃ :kiWn&p衇ZZ*G@Aq%RF`çe2ܠhS6\_PWk(_6smKQEMm!@ СCm "3|ҦM2eJ`d2t*ҬMEzityYSZ!eg4_y"eZY>:)V~ en& }@(ԽyKU!u C@Kn^{f tJxQ*Zf2{l|,tᩮӥVP$@H?UP}rivo>9sk]vOC߈ZM-\_<%'\Ԃ12Up ..<Ḃo d zYV+fU6ɵNG6U=Mo߾Jzn @ ; sxDuM^P&J&PV-k(tXB\ΚKD!]8& G^-v7|xoG麚X$@H/U:n.9rd`g>|Jfś1k,'VnX5=vOWkQ^iON~nW /.\wkΩc>ym<%[u-R5ׯzCIܿ=oʕSZgԨQr׊%A%SUUlaJ;@diVgJ{H4wRx hr/g(t@@u C >ټ|sZ,PEfKRBO|1ћfAB6ltIz-6mRXxnqͪZ#yfxKTiR%wm.tj"Mi OenI){lu_YߝmRrUo&ųƳӡ,oa*'nȐS2/[UZ#SO=%jK $J@{Ec椞T[;υy̌x(SLBwǎ{@.oz9@U%* u*Ղ/2@J裏U8P|M+ݻw˙g)ÇoYJ*e-Mo$H|77.{3Vm 5O̐Y$QbW1/|xk*wx&VL[(Ս&"CZU;=Fl*ct] t~{ 5 vk&%iO=l^YڞߖM.B 9(t3ǚ Ao} T&,Tq2UV RS~2qDv+Zڒդ}3a`sZ ln(CIAlnw fa/ FV-j`7TӞ*vQj餭5v]z@Q$P^=yeԨQrmNo>@,񦰹%Liu7yu2grdzӀwR-*5&giV9رfMsn)(t3bL@OEF&5Nvcf!UƔ,(w5Qª>~5K6}V7dUA춧|wQW5ʱ%\CiO]WE*Ͳ/2c]_諒Vwfm9lSԅ>ܤ;՝T~jIuP(&[n.]H~d׮ӛ9s7=/HPoo^(:|د͍27kD:WWSefi$_Af37zSp`~DU䷗LchwO<=ONxU&^}嗥sqK!@H/]ߩw&]q"IF`}2p]!!kZjϪnpTg-N-S5Z 餞uwێ{ȳWg<\i&]K)ښ-c{n׶S}tYsZ]e(z @ P*MB "Ս`[X \[N-z4vYl[.ֳW[n3WR양R(o OU*cusM\kEv;G]{SvW߱Z2V]JnC&q)5ϽiPq @ Zyҥҷo_OU2QNI k6=n.b--ukyyLyG4U˗4,9Vou+Tx&guQ檲6^ۦ|ָK߸_4et5fy\rVdҨQ(-@$t֒v9Ven]p [mWncz+o5k9wM se5oB 8 >)d.܅ʹenph㹱92=",5};% PR'u\1V~7T%ujWI۪b|u*kVWW_3Twbys @(ӧiFv*~^gofϣG/U [[[v}`Q~D ugY75ZC1GiiL_`JУM5U6ָ FhY>c2`hUɃ  +_]^3ߝ*skǺ!z(Q67uKSsRky9!4mqC%3 @^(t48@ K]i4ilk'ԃQEZ8FџP jb.WۯMMҢ_jL7Ǻl9wS͢ &uR QKJo6-pOs5@(j$66Se Jo ^37ޒjTݒ黦q UI`yĬW›4q}uȮA$Ɗ& LiqTEQ:1x:r*~ ]=1csLlŲ W'Jj[V( VnnV{[ٻ93߁J* ,$޻©3eH燺E]%Zj_u*7U/_lj[t՛VX+(."k?;aV˸ \P:h;-”ԚV7%R&pX|U˩qkS@#{Poibnΐ})_]1s>>-0P:747FOu%*BYq=и@:'`FcUg-d~$dc ^DpSiCE޶ 9cLRzєErY7$*t>`y嗥I&AőC@eM6߻ͦrMΚ40.+YvFFDLOh#3@@P\ޕ{ *Kg}]ɶp? ɑ*E+7d4 ~5JJ@@v@ן'I@Xܢ֨Tk]M~WOKmV5 = *٫]J~v uOGY15spB~nJu98C(hj]Sqyи`~|{`W>zVYT3(tݑn}s[S9mRϘ@@ VVWWJ~6–1.en/FiB En-Bwk/k֌{R9kII7 zؔZVcN æu(" P -饏8K_oٵ,o꩕gЂ/=P3Jkd@uy'# ]wVwk寯vx W]hڼ|Uq!fR5G_YZh0.w:B= x Lv 7s;;{U+e7 55^KyH͝Mԥ, @Ń qEXV},*ϞEq JV Ho("]e2WXeq'HZ>m2dI[ꆉTǥ g@P7:>܅ᅲ@VlO:%emPf_Lc8 4B%ńmk\6S"6Ƞ "H:i9"`tD@O$dQ5Nv&B4""@X|bZz[>M$zTKZE/[oqxw-7'uƋP'l,LIA+7/xx0_(@(gt 5wKw7Nyb#W7lqgKRw"bTa3ŭĐIv-0 t`=:mwo Ij0ٺ!lx#?b ^xJ" W&ED=Š ^HUVV:A"wܟI ɿ}d\R|<W\v_qv$!Љv,Uv:sL{`%Zc~ػE/OINbOnA@p17ƹB7(ݚX9]_QcU6i[#EhW`lLoaw8zh6Jթw"cA@B3(F6c[.Rߐ4h1.ئoE:U['c)@"uwyHBTVe`I>A@p>qA 7{.|ZL9xBF>ft$@7KHܞ)I6-e_'ųme.l  E;8~j;/ VU%UJA F d(xy%c]wbܮ rcE}2VA˭ȿ'_)': 3~ɟ8:wliL] $ @B@ub F[gO"Hxdi%< BEIbXω: qX;k7?G7.ȥ !2VJ0wB{*n`ᓅ4w曖|zBi~ +zX`ڷԻ5mf&^7$/KB^J"#e~Hw6Ҁ`?7<=xeRyl[ޢxKќPE,7@;n2'M@6! ,ȅÍedc#Е֫{e7@4; Ȝ"&R FfR6p\Frc :9 :#" DB`6d3C݂PP@u,bWd^A@,lE{Dk&|+$7.e/DI-VH7mz]39 M( u^2>o i\BiVNV}I);+ڸ+O@Z9plt&s࣠Ud@GBSZAplGH][xR"{~ kDA@Pۡ.w;YX)j}}%dS$_"1y`Y׈[:':He|N< >m$2g[I4pck;OAY<5YRQ w;/5p}Lr!Џc3 JO Vi~?o_H ||ٷʵqlyH[b |#{* ԶR'm~<Ω᥂-r6ՔG{!RF(1uS-A@ "@!bbۄV tWm.&,d0,{!ЋmfK8;d.Zcbc\e-bׂ :k,*n4։ۮALd@o#n[ {m `tbː,er!-o[PjM-X_A 1ueL fCkp ݜ9;О!w7V)QC*Hނ@+ݒx6f،a~t+N(``R@v""`G?AA@B@X}c} mQgTT:w )M !۰t~:ݴ%T.ZN@Lx45.̾-[x-Zf`d_c6Zo]s0Y{dke syɫ[~y Љ ^,=XE+=̓nݼ0'gOη=. >I[qnִGre}DSي2 $,{k$'.vk K][&oXut0_o;!L"Įd.nss3ձ[,{%zj$IA!#|~۪Ɓ[[AZU8'7> ]? ঀYcCٹov-VkzVv ꚚBNOL:uhAF{"'@٣B LV9"ŌV~w/7ЗI پYLaMmo2Y dKN~:ULG4|ĈSB:YY $mF k,>dxr7,F@#|ZWNV) P3t5mݧOۂ۶m!Cw K @o`=Y7ѽAάiJt?=A Hj-u[jgS vt*ݶ LضS 5NۙEƐ'EZd/2mic1B9L1]l#[6n%kV%LeGJ.YNI!$=nk lۅwA.8HIz TEqW5W+x~mذf]}+<%sdJF9993јySdaRc> 5v!߉rӵ](=G@c.D^7gjIyHG.QHF#s^BܝxĐ:K/c=Fts믿?aNoi{iϞ=ӟrz/2uՒz{ 1]+f`#uU:K t2lazĭRN#N3brG,UcݱloQ1LBz* uar<]`~'ױ 8G赶%tEq~$g@}wz2h+[W,]O@7zqꀰN# 6n[r\ΫN/f*nJmXS߾}<`Ϳ`K?AX:x+Vٝ7/'!t Rs&灁b]&F&1E0 1`a%^c._yf@.MT*6X&I9A@IwCmtm{4iѪ;vЦo &mTZii)557x7zRyWZEYAnA@p,ە\|CT^r @WvrG'2ŸaMKU?7LOdZO (Bk݆KKc>4P~<`xq 4sw1o̽  tvgq'XX#9b65)iFMf)߾>fH@u*-]#s=7fnE 8Bq(DLvG>9B41p 3~Ѝϧo98߀N#l=Cw_Cee%A a޽{iΝwu$h"]8MC]={tU;v~:Y.2xJ|A:ꨣj?wM鯥h0Fm%{Q_}p,&?is9wG3Zh e iB&RI#@Bn':`7i3Z_ k9.\m,b#"usW r[<-'=\u[7gjO STA 4lw* \H~2^<6e.* ^BȳF:Zln>r:Ab :;_WA<պ񦕦mqvjjםV sxJs3+v"~-Ƕ R$TCq7@`iġYT 0@6J|'ܷhN*h/1h +_W3'4}ҤItT_Rܿz!i来K\db G%\BSL1S-s&wT]]R fŃAv)A hn>LM8Mm閩_^tt-l!G?,ڑ+O:cTڣ|q 2( D袋~:Q4(CYgq~+ר|?E]Az{_r̘d:  o/$ˈ#諯՜|{ٺ,QO8^u[1.v/߿Ƚ4{Yj\ٳ=5E$55\C uzȏĸם Hc(>$ֺ9B .!su9s駟v/XT5PWpCE~ꪫo8i"(Xs:"E`ªL _f7zhe9|[2o6h৞z*]}Q\n)RܿXa}qǩ:hqq :'ϢӺ5u7cs #w\}$ uAɱ5Ahiqv-t}}A# અܟ& [$ $|5\/'=W}KqWeR8ʖӱ&`` XURM)뚲TURI8FoɊoz&-xjZ\q32"|+a=>vH'j-QvĎ.Gٹg;նOY £QnW3t{7v },tlKƲ2\5-X5k]tJXmY` ֮9*)) ZE+*l=q ԰]cRs;gy&]pBx  aekz~ /ypm'tųC) xƍ{(F4Zkc*֢vW˸,(?I}>r>C'1Vzpkitc~nuqg\ N( nl61,pj O" ?\X2fb*=F \?(^PLp+10j,X&qﯣ :%ޭqF ֺZg?M Jeэ>eo5eddة 6GK.D)&u. r}-cCXނ2 |e6p BK9Vp<~x?uM:Uy6>4 rdsΝ!`6@>ú6lٲ@e'(SN9Զٵ6,m_~eڒ9Lwy:,{6mȐWk&q?pHae},$=@#kK/|eŮs}?ҳIگk~=ڍw'\nӅuĻR7W8 )X' jH71V+,u^>*%bR&,T8ma)m =@Ihْy@.:ꫯ*'XtsTXʸ*IU|Z{ipe+B ssnbn- [x{I+aϛq$}TJ[Ɉ:nܸ8u_nF.,̈́2j) 5hGޚI9sΥ0RF9^-KJ;](yE|/%X+5 ~w$ѣr% 9m端RV݈QlknǸG^L]Y6%SVV %@>VٶmkI!7x9%1,Ig1Zy/qMϯ8\muT4E=颐dvV;fd9.#A7-)7OE7.nD~7# @TPϹBE-c:S=.Nħ$UAKBM@$oWaNd"p̄  /pk2-0~\qpAy~ Mb"edu?z)'@[;{msٔC=PQ{I@KP3ԖmyƊf* >2։>-x>SZjEX\CZl_5aTbFġZlr==ЬUS Cu,}][kV\b"Zz}npk.~a<b$ܷmZZZJ~mp#}w3&qW6fq;#D9{m?>eFC=*eCCxpćq#-I@\cDgwe;NϯIm'* 2n k7>ZYIq`AgߺF+We$ tۧ.vّw%Q`yZҍy `a*B})S˴ih'AN=ԖLt 7R7x pɭ덴M|6f'>ifIKs<|p2~uNпPO';w&ԕJ5ur~=N2Hq k[|VpOÒhM6@<&n_%fcKݗrrОqǗ@;n4@ hTL)|hGNe;ְ\-ꨞ{jkձ`>!t^m'n.z r6aU~?Yzc<b雔ogܭo ~W:D]N)DUJ=FʦXHSo܏?rL?8O{!~ʝC:#hƍw)bë.؆lpl_b;re+b,᪺~߫xF|׿E7Ϟ={o~ "#iy̙L8w}cG>X4#Y,X@^{-|pd;`kdB̂frڜ'Nяi۱sz?E;#X0=qq~:(ǼGk_N1\ֹ0:uIG'M+guF2WH'z]<9[jfBWۯZ{֏ɓ&.WһO`lvz"@[ o=6(z [ `Fcn;zE},j|ǥS1QX0aZH:dxid\A@~suy#FYF3Pފ:n&]vY*4 s9'!}p,noVeq8c ~^nm0bڑb׬ ULFZĽbAْ<y衇h a <@/wޡM6mTZonIO ΝKX@$2D۶mzO=^70;~ `p- tqغ9!fOhWC0^xjQ㏧N89e .E_ ~"xn$ .fb^s5@csYXg7.$W;aCaPV3׍:ޯHk?ڄ#"It?9vv,yO+B:H݂WM!%sQ=/"nMZkժBKXhr%=ߏD qk0HYZxE8`;:v,uIE,GN/EX Ai_>b1!iA)L6sJY|1_GfBCxB7ɛ|>YHsg&"sむ =tvޮ;:|ZEsw)>/&1AohXю'-\9K>0I[**z(L]:{)ʡGSn[aSF1--?H B}e;tA@R:&ꩄ: w-S8RdƏOg}6M>r./.\r뮣EChX2DHdˊ[wTK6(HWVQSmcPAxNqD.b]UWMZ55Q5i@9I;ZEA@lJbgp "#p+ۙ3gjQ}keƌk^A@h[:s9IʛPKuo"v-mnLѧX\΍9)# РmW.޳r\AT!e 7 '8mb:Qc8Š{ @߯>J`&lԲQ* ,uC8h^;w\OD"~+l ݂.b7qr釥X`%#$&n^=vxkrlB5Pn8T+p5Q:<% ?\ҶQF٦K{."BXYCkҥ ][" tؗ_ D`K&jn= pûoo'ӎ;bt̓.)1 miЍta x~{u^N]Z[npcNJ|E#s' "Ϩy4#)z:<v:*_$aٛw2;{$(se}kl7v%},ֹtA@HjY`VK9&vޓOg<>?霷=Z{,SN9[ :׬H[ndnk[bFlެժaA@hQI81//=_ۏc:3i޼ysѺuhӦM{nٳ;fpgP߾}GiP"#Бc-ñwfp'4JPHqce;~MY Ow1σ.Y[whnݲS$ $2@uLp{soϋU^ ']v2!x+:v *CVP&;eԉ8*tYעo>F>RM\j^ze:puCFl۶FAMMMYfY0ng3k׫4|V-+@A@hwdzQ6!t:Mے֖ _~9=.ݻ70$!"L4MG @r#c Z9;٬&cLp58,v(de78lVDkA4T}p6DA@H6n 3G?KUSYN:7/],Tg9s2pX.VLEBꪜ_I Iv@onr67p{RrД`gk.]BzT&ESZu59-pLf fcNC%!]IA L`[,3ʊ}^;mxgD;9S̶h7S:>/`.@!PA_z-=OJw^msLMNk8^*;7G$5j(G'>A}!jK!t=)g#[|2?ԅjűY=d^O\s5njI482([n>b@b CIZJK_mQ/@R PÿiN(2\􅣣Si~VY~ded'=ӮN͏u] th;Vߩ 9ӝwI3f̰d8Arc gFGu6qjQߍiG۬ogP`Jck,'/9_NKA@VrB75/]z=e:i6Z=xɃi:'yqs2  5u B[6A@R nAW-W#|JxNB-mc٥m6X:QW;aκvg^ k`êMD#Y ,;d>?XzrMyn…p}U}0/\BVa6o_.'7)N1;E7.Jnە3ءwĚxbx%^C%׃Q}EA]"#Y;؍n͖-,E_-lȋI u-" !t>Ac#еYh=Onk>ґ;ގd LLcAɫ_5a gQl \:N|Cݼhq2Q|EA@ho෭]%I]K%UgNx9^-4b'JW~Ox^LE/EƐАiCRH(-}qH۝Z&s7c"$j O0Ӷ@Т]mݨ. /{))"@GB`']B#]q:rH9s&=#y?tȣSF'1NejSA@H^22N'og q#-tlsԩ`[^b}[ai:nA&LWC"~ i:;Guw&roϷĹ]Jݎi_six,b +r'RVx.+=b,#sAnKcDk:E1l;1Pֵ{ٺ dD.oLnH  A@~{4%tp0vJ\0OҢ?, C@.<OOzFi=Jİ cy*3g*|:%8 7ʽ B)旚]ǖLui&+tJSֽlu;6fek%IA@+g4=$t<07B 2AJ+#eG^@O?}N=',Wjk9(eA@A@.J('0Fi:*ּ&+eJ׫عޭsHA@hWj䆇͹u펩T&ҭo×wd7H]myi w泃`ݬQ^zg>t[]X;Wѝ*;X(뤾f&y8-lgJ][ LDA :۩="Qb`Sʣ+5Gϡ+FٱsԔPvn#iyQg9|\F*_[nc1QiQ>3eSuSuH?r}芓If]Y ^S1ZUphA@A@A@EsbPGVp8l0vmy7\p p$&u-pDA@H]ĺz*+(S.!1`ݻ@_q@}㲹1 $'~WV9ΘS5Q.[WFOO&) ^-3aW/ҋֶ]&"V%zc!=T#@!"vOߙO'P/&w9y JWCJO4JhY#ݘثdnO}/}GT5Iꮕ XF5Ѳ9Y4y:7l xKW]](gy)e"?h-Scn ~ϋo@K*bMuՏo~V\l~K] $Ln̝G9925+[B1=Gӥt֠"ԩ' @% s 9=JP~p yD{pmWiF,}6UmysnZ&u7RV/5iLRղ{Vc~A@A@A/.pa^] _/66tsV73;yULB1@AW`!^[}S'7Ir@A 9a` 'P^ce޴<6&ԭEL⌐" Z&L5M_k47ӪMh쐱cHn1e[2}Sւ   8BB-K!aV^m]dNe|m5Pkb@8mKA/V>^ixn93ۘ.ۂ lKQ+I ̺ P{̸ǵ;w85/XشN p{M ~u mu@d-  1#u϶< Rz wʵZmDA@A Q3;& 8O@D_A@#0>''}!V"J# n!pI ĩ$ sqNnB%.=R)+ W?xեH[wAѫ  .E) [l#U6*:KJ?A@(>n8De1 _mJKA!2s&trd=kou\|N ]X? ggN};翜Pj0:?.;l'ZlebND&A@6E[¥3>Y$uֿ^R5`^[D.r:(?8`Zd U<֏4?we-  @\D%tZփE[g)ZY&j1xVA]HE+qY)ֹ^@)u RT}k$'SD0e %`XIյD.pئUެС.Hz*- q nf +H=v0@n0^vt!զM҂3)_A@A@A@A ! 4YKh>fImc6ԿbʘHK Fd[A F@"bn :]n$ 26`>rvܒ_]9٬N8zqvѲ/cZI   nHNi1k[jaJ#VPي26cm[-7L1W PoJ\i2~-.\ A@mwr<|'R` 2>NǰE=c蔐a ׈J&1I_l ^ q"ƈR7'0cj. Vฺٍi[*$8C#E~{1';~4|̴A@A@A@"uZ| 1k#0c&ŀ f8=G@nfN5 0A@tt}?QX]GVcҍT~ 72ꅠ\v܇@.axXz! `|1FiًH а@h8jMR9c 'ٻ[J7HRLEJbb 3r LBV)S1q4[&AkEnRIwoRzN9{sys?|r~ky߽kK ~ec.yEד@Wb;@`b6ߊ$EBV`E h9c5_B^on]r @v%/iL 6jdR7yiz 4+/1s ?+:0 @tﵵR/Ѝw Zm]eFҤh!i]kïmŤd;RUThҌVuϚ%ڵ}U@HooJ.}I;wڝJ { @VT+6K9 &]ye/790 @R6mKA:"09!E5?W+LB@ KT=BbaСu(&40: 4h'U/HNXο?[՛;^S=X@Hk@۷oO`ovEG7*6ꮗd?3gP'ïOEWzg}=qZoI V ΗE6ʑfdL^II 4G@ R{./'~sc^]u^u}beG\7|BqصWfWe &[ﯷ8c*T=zӦYh|9CiX @zrAwǎ 0[$@٠/'or쀛|I<]4vKB@,B,jEqt>RS&T6殣5!CvF.xךdvy0y~W/>ݺ-SJqN{;o7F-*o.]NaVd3[oegm G TAzV5@ W fnhf[sx1ԛ̍e@Cs a֘q174C@xB7|q?-P)ݗz<)̛l< @ ~4#c?[?ppXî ڛWӒ91Lz^imu핫^Z`#א4z4 7>}O>inwɂ%K[  ڵK6oZU =YӠ$:wCshbZ6={W[RnNY$@IDATp[}yXoHSnzIB@ pYy]{rt.l[̌k[6BT;r0-K&5@tf5=6%yzaW>khh퍫 ڸnD7_[zWeĀR藮 XF(x5kH `S@nh.A7ݽ7Vdfsyvbў/uz÷XOEJ5'WNW;3P-"Շ8 @~ ߗM4zKU9LxT3p^ˊxsL`K@@~ dz6MH[㷺y?歍ʉ.ѺerSu/|ЪbϥQGY Vn"Mmye~pJ  P01t3Κ)Az֎SW{Mfpfnf9VMi\)x ][MŦcӃW1iF|" njdL W~fvR֭]ab "7 zFݟ d@[YMu.waaWwZ3.G ʇ  Me ;5LܪZ{[L/94( VREN2}>ɾի[ '|b[li @\О`܍fXIo!X"! ̈́:e"@4!s4F/]OW@ >c>Z1?ao˙j ƍ孷޲7o.r!@\y2 @ʕRO(}}s^:HxE !\N t*H=䩗_|tQ_ZG@ ݻ퉙N@# cy٠c"@.T)((s=]t=^HH G~=k^Ϊ.s' Px}\?쳝q #@nk@ KgoT ^[-d\:_wt4,#PjtKM;>$y`T\) -b# \Sr3Lbn:og![lrչsۙyI -@na@ h͂@@ (0J\H@/]W,@nrr&KfdV_x5<`+WZG@ yۋMdaי/_./{48iҤX P81t #E@ 3iR*@-z޹A6ړw#t=^@N٠Kmʇ@rCs󻽇G=4<ӵkD`_@<٠˗yr9 @n֞*@A 4wۮ+e^Ll9SZf̱7ߔ6ZIu@#k{/sOr\@r^ݜ? @ Me{Sj5ljz.5֑@L/[Եsgh\ v7ߝ5KN%珑@HL`}oYXF W4`@rQx֨3%p@)zzGL/ݥy"@)›J3@ʕi}cGҡ}{ٷF k@޻3ΐJQ @ G@ ~SD4 6=mK;׫Kwt=^( ʫvqnzV @ tA2jԨVԭ[Wի'G}4h ⶬ@@ f.D  ZM/#@tLO۲&ݗ^ec?(T)'$ϼEW_I!_[6@\8nȵjS_@,٠˗Yz @ʛ#@@ 3s-ܹ}u.Ӱ@Lcl) LkZEk $&X7 @fhͬ?# Si v ,noL0vGh-s?M/GxVɂo͛[   @a Р[督F@(p5 :#hWyH\ IB:cmܨQviW^}UiLʕ+_2 @[F8vC t4P @@+k$мA7'r^/s&NZjI[}v: ,K{  %1{v}|Tj  B"$wWt= ^O{ a:H9dg |>ݻwˊ_ #  D٠W*@x @Hv\Ƚ^̥@tڱUǼkS<)PEh  C.ιH@@TXSW @> ?Hi%t[a   @a Р[X;hw-#ƽ)JB@T^2  Srs_F>[ڹ    qGu2/??-~܆   @m:ȟ3‘ {N9t>@;}  +@n{ٺmgjtò`ɺ@@Hu\O^ @͹ӻsf.r#  /P0)#t ,^XZhQB [N>o@@Cই.3cC.p f /Pq͛_|!zNB@@Aw`۶mbpwʐ!CZG@@ 6o$ϼ`c aWR[Nl7,mጮK5m:@˥YfzK/Ա!@@gt8>H9Vɓ'K*U " ~vm.c@ 57p~V5jԐe˖ɾk#  +{nc֭[#n3uTҥX"6@@@@ :T֟eر0   4 vxtuٳgKvD{@@@@ =rvwLB@@Apεu[lb2?U,#   @."i޼έ;j(+N@@W=1,V .B>|ܹ34{@@@H@ dܸqΜkU  !@nagQC7tGݻlܸ14{@@@H@^k׮Μ D@@A7iGT]^:… . @@@@lcǎu8c y饗"  @~ Р_TGS!C3^dmV^}0@@@@ z}8s\;D@@A7_BOv=~lڴ)4{@@@H@gϞҭ[7gC q "  @n Р/ k7cmz@@@(>DJ U@@@ hᓗhSՠ{gDΘ1C9D    "pQGIC"{j/;v @@r^ݜ?e?dΡ[|y4h,ZHtޞ;b   Qn&)**Yt<V   {]$$ܠA;v2f9蠃Γ @@@@ @ƍeԨQ΍F)6lp#  '@n~J(#8BN*˖-Zj֬P~   C:   ;4ιJjM5nZ}QY| >\F,[ٵksA@@@H@={† "@    q^KWI'$̝;W9TRܾ}Jz:,]TOn    @zƌ,>u@@@ 7h͍Zz_,L6MN8XhX :44T> cA@@@ q{WAKwU@@@ h́*>r7ʊ+wΓZE]$?.3gΔٳgn{@@@ȀuJɓ'gF  [蒑+yd@ndԨQR~Zvm8ps{'   hذamW^{_޵  d Y~ztN*/ @@@2+pUWI:uJl۶M+N@@_?GYSf͚I>}q    >ի˘1c?^-Z\G@@W=7YYA97u@@@@ }snYLB@@A7Wk۩S'СU;wʣ>j    @zʗ//ƍs\G@@S'   뮻˹   dVE:lӈ#2zhg    _/YݶmsA@@Ȝ ϫ'UVi޼y;Xq    QF>O{0   at3|jժ>JǏw!   @ׯ/z뮻N֮]\G@@2#@nfK/y\/,X    _`ҠA;w_+N@@2'@n J5qDg    _@M3fI&… "  @hMy^?y|'Of& A@@@2#?Aڶm,kq "  @hMy^ئM9c 2e+N@@@2#P\97n_|Q}]:   ^t] ){ҲeKx7m$O='   ;ϝ;W|I:   >tg]0%Ý;zhI    -Z:+3l0駟"  @zhMs^j֬i _@@@@ s_TPիeĉV  OYTI+WC:yܸq8A@@@Ȍ@ݺuns>j("  @hMqp%Oơ7f@@@2(pWo`׮]r뭷Zq   t\ӽ p q    *Uw,o|Wu@@@ 4ַs+?Zʹ    dF3ϔ; 1b3N@@R+@nj} >-[ʩt2e3N@@@2#P\97niӦo\G@@R'@nlA9-t-[8D@@@tYN?tgzs@@@ }4Ϻ`Kҥi:[ԩS8@@@Ȭwy't#  A75>|/g^ A@@@2'p衇UW]СCq)C@@РWr Wի狊,]TOn    @fF)+V*f8q'  A75@}וƌ C@@@ ԭ[Wnvg nFYzsA@@H $(]t+g͜9SfϞeOV!   @&.2iܸU][n@@@ vZ Gkז:5g    dNrrw;+0yd\G@@'@n,)H SN˗Ǒ    @:+:tp9|pg   $OYS͚5>}8q    9tO.of*G   p 㥸ع    dNcJS  A75ESNΡv)>h=Y   ۝Eϟ?_x :   C kѢ $@@@.C9D"7oή S@@D<9v{FY^rV  @>@ݻw?V  Mesc$ 4iDM%!   @ Ek}F  =tsSgϞ-;v\rzj]   d^@~駭sΕ *X   @[:/N@sVλwzȊ@@@@ۜꫯs#  n:EӦMN;ʽ^zl2X   d^kcZ,Y"իW@@@[PgϞҴiS5kȳ>k    @v1BVjUfÆ 2n8+N@@J'@yu x˭ڴi#s̱@@ŋEjԨxRVg Wk׮UVիRJҲeK_~^#{媫 )**KJƍu Gk9) FԸkl" 6[Z{ҥK+N@b |'2zhy7ڡJ*_ZjիL{lܸ1{G7oc!:SO=wAuԑ֭[GפI??tK O/du<ڦPǛ #<"?|:D6y?I*Wlm@<e @t;vHV-/7-󝪣?~t饗J޽˙9:,^O?sdkC "P EZj2d[*  @ |2n~'y9[_:D$_>,M6-҂oֆκufa,X@^}2׳C-ZD#4'QAe?W,o޼Y>?Ee*THmd<% dLbŊrwK߾}:<2p@9ꨣuOk̜\d/uE~GkgN)Mn'v鼾8 @0ni6t+ˢ_@@ >V{1ןۢED^XS`РAұcǰO>d{fmVuB>?zN>}ȕW^1t(+BzY-+s /A3w>祗^r6{'ڈMB@Ph-3#ǩC.S4aW Gsb%9s_e)l9lذ0-}Gg{闣eM\~eݝ@ tJ:d]נyr"0w#j! W++8T ۔)SoΉ SeC #0rHYlo~'uCᅲ3fɓE{S&}0ˇ} as|޽[6lXjSDޜwY, 4#:L`hSAK.$wS\9i߾}g3.S_tCzGE_eP~ϖm8>tU)4%ʕ+EIAn} @ Рg8M67x#%6^{aq@@`·SO [nS}ꗮ:偮={v~BtIaq O@[m֬?( . [|W]uUX<r}(EhDC6Ι3'1cdV6֏1(3j(d}$蜗:݀LJ&[)ͯ GcYCoStſe@N!SC7m۶͵ @ lܸ19wQe˖Mb~IoWҺuZt?]|aW.&M 9QKJSiT=(L[,FrО{Zj&e2qR(2,Ǫxt2KBqqh)^e6zRkarw]ڡS3}R]~2>O4rTࠃ 諭>Ԣ2J9T4W\&'k=PԶm[+VN0k,EFpұcG9DP5iФ $zkhz=uKKS:u~Eqgוb C~ÛH}]u'2gsׯ/rK godjW/ȩS fhw-HFŒW*[e޼yA{y䑢#_wu3Y+Ǣ :Doh/C>Qd2oG]?Eǫ~KEw}w+o?C / guVh(}-,2,$L|p@V h+iܸqr7&r *נ#s j7|RJr= &~ׇS]n}%Ȯe{{?yP;3ѮϣoW=ޟ&;,ZY %MY)` /Z YYg*r.` g $1+`nLծ];OvafXy/ w_X~s#hРAԼ̰_somsᇻ ̗3f\f(CH׿u4BG,,+Q_-4ZN|J*ӟ9PJ?o<馛v B}F[66K35jN#]E h>]wE׵`g5 X|'˥'C$k5tUjՀyX$V^ x6m0Su޼];BIf|@! B{z׿fԤ$94Hנ{"]~ufJ뺲_~3zNҵ֙Dc]OD]W_'ڵWҸ9xO;Uwb % JH/]jV֙J!,Q]yڠ_"~Boh]FcU:*6M/C/;']gmAd ^W;28fԀiؾ}R-'\*2w'ТE <׵6*]+W,i2Y' u̡%=-x^J H /CavZH77at:iPVFmtAÅz/R?~sC_z4^ 0=dyE"]Wua$r 5h;AmT_j{N:)X?Vf̘NZ`@oXxqI.:^˻%gEYdItD^*iy@ K !"`V >kْ qCT"6s6{zu6ZO'O8[]J۵kgm;zmC{4MS5/3OQ{du6sE1gֶ:D Jס> z{W]s#nm=*yE_MW;|amO;أZ!ZJv}c=-uIv^K6_Ą OcyjpvY`?)j*$V\˜ۚy͂>޹Y,KT3'oֲB?^%9ߩp/9z5sf{ņcmn~n*XƅTx,ʩ(?S ڵNhJs5(נNןwA3z-:ߑ~p+w}uVŊH&za^}yE^c fIbʉxq# =:|Ste2RTDM˟}7ZV03ߒ9ۭo̼yN>`C^pbӧ(lСCÆ ]iqơKi %/JVZ0=~SO3ikHE?!y{m9Pө ;p:Ow+k2EC~FF { 7X#R܌jk!gѵ{G)g |YM׻KWnc><<5j8e fD>Oe5f?rY@pM4x`1#^$b}N4נ{u ט{&M12ke,3g . zgsff:+%za ( l2nEmʔ)r7-cBHV8∈eiíy9zHzuܿ[oE6ڊ>ڹZ<@9@C6 .ߛ }_Hqm('mq5z5kLVX-Նn#$9Ei?/;w\o{U83={zaIu}K y2꫐չ=P+, d]sGo;c Rb}KN?SÇ}> I<sDZn\W`[O,}V[oUx ozHLV=kPAcq W(נڐskgGhHvڰubΝb.Ѡ"f* ch w74>t 3@2+@nf)=NAA̧'ڋI{ɐ@S Ro Wm> O-Z$蠍HOUkO ZUІh)ZC닢k[ڿ6믿^6n(f`yW Uw3_et7(/ -">[V掀Cqlm5X ,_,؋C2.ZԞf>VZ56@te˖A7?cJM:>Ocd\^v#Go:#F{#Jr Gk>A=R} #?TL "fɄ $Y`' vDxt? -3B= @fhͬ?)O޵iy֭a&/8sb3@t ĺhá>լY׭[zS+!"%Pmo?y)Zj-\ b"5ʺz~%/C 6wy0MEѾT$cU ڵ?x>}emvFʽUgo2ۥ,S>W/{ZC ֙?>o.?ϓH'N(fc-Od!)C^@IDATנٗk؆FW\1ԩ\\ak:HtcW#3Alkxt?wX& ~B>@DO'm袋"e@ |AqUW﬜ ~;_6ZPI/XI{ RJ6m;*o߾Ǥ$ڨOu(DKtY_9SQfŊlbR_͛?&ޢ|ey:n0c갃E~taÆG{jݙ>o.?ϓg+(=p^G M͛7 ]_|N k4A=Ry y?3*׹/Av9Y p=s_:~2 @h͜=%R@{B <"iҥ9O9R [Ow5jW^4f??Q:AhڴU{gBq|@ynv>ZPLiǎtmG{tC{$^ٱ^SQGi DK:٪U6/>]=f6O]۸>Sˮcq~m\yemt'ǿ[?Vҹ?(:^pm˿ٵ+o~m\y˕ϓ,# z7nܸ5j6j,'{)נ\ҿɺNԓm۶˸ 郵:{Iuh#/L}H%)W~ځ@B(WH˱涀ތL4fW @ ?]x2y0]hT.ԇ:pGv M~ 7tx/U]m۶%dַ$oRQkZ=?sj@oQOs̹+ˮc%jzmb{G\p %fe @Lm+:_Ηy饗&17f%\q 5^T_Fnyo ==X;_śoiOGnp Y2ӱc&]@h3gΔٳgx #}UӞ={Z1/0k,mZ_۷o_+:$qh'/+]vےW%;/ׯ>QFC=\_$dM*<ìa{s˕N4ZuꩧZ1W5\~Y?"mذ!l}J .du~RxiZh )s|:/O 7-5y&[Ie̛LZ#@lruL%A5^T^~7_睎7mڴaKzG2/F2_xo*y7;,UO @! Р[g=v2p@$@p C6Θ1Z8ȑ#&^yj_Y ^zIh'y .LC~Z.CO/bJNtͿj֬Y][q7(SRT%4g+֭ kf؟\=!uQmMCЮ? 4^9Q|y8gSY׹] ':/nNDۂ=w< :y':l?y:rAϛ|rׇe~AS5T__ÇK=bs͗xbg#qsZ]}=|gөz~߹Jj,~% (d( XhQ|igrX, O6m /ԭ fc fhA|W)SLiJ(K=̗5VI<7fɀyu37ֶ~]ɫi@ rk[-4:'0'6ZܹsK7QƹWʕ+Zl0sxLcpmVޚ[~}Q[*I.S36tLO]ի8qb4rwnm~o\?ӺiL ן ӈEU-T2FSA\s gk^Ձh]̴zMc 3D%$`z ހ)аaCgwXjWdɫ Z~Ns^}}5\#]<נ{~f3͞ nj2غukI'".Sw륎;vYR#xO;,R]#dC.r>A9k?~x)..v# ]tPZ[s6gݺueСց SyM:Lٳ|i.9]\J{~r.FuhyI]Y.2|6oT7/X.?VX#5s+AFN-亦Tvi{6-l"?/m4'n:ߴMkb @li[&N7HBH@Yɨoe'L:Tc4p;4{u-3WB{zw}wYfn|JUT/}-NKh=? Yۄ=#yst>̿{dx,IV|ubR#kн$W\A5g̹̙뼞1slz"]׷o_g}[h۝Qh-M Y(Puds=' mbګ\ȉZG@ b惕%K/M4s,͛7}R;ג4_P?eʕ_-; z&tRio`xX|QlöOf}22Mc_]{1yC F(5|#ׯ3[Ǵ?3G_Q=!Mï+WN8iժ!ȣ+SRoK|9s<"[֭?桚94t+"@f wOנіu_n <&|i(t6qچo%@O]OלЋ(Zo4̍&}YF@ !6mC3y嗥k׮W^y%f̘1rWX@@@ נd#O/Ld_c@@h\Fa  P-GY\\,O3f̐N:,# PfʚC+?jΜ92}t3\V  $C C7y tSJi9rdpJ:LB@ <\zJ'TrR  p A7 C.'nH+5xdu@(^(?aÆqeQJ[E.ј!  =t 2uT?˸q8@صkO… eٲeeYԩSGZn-zN;M֭hq  p ؾ}{A\FJ*,# vtNN={tʺ\rzj] a-!  QkЈ4@@F!To^:wl!޽[z!+N@TИ UD@&5h4! !@86mZpXK?Fzo}ǿe@@@@@@ZY}z\iz)M6vYf<3V      .@n!@eĈGD@@@@@@ r9u+-[aÆuVk7|Stb       3C$PZ52ds;@@@@@@Vzf뙡^eXj4iĹe˖u@@@@@@6zf> w3qDg       (@l<+)ayIv|D{֭[ZG@@@@@@ln6mȉ'hdʔ)V      (@l<+))ʫf͚b \      $@l:%ݻw-[ZynڴIz)+N@@@@@@lA7IΗ;bg~G~      @6 0r6ꖰmۤqƢrW^=z,#      5͚SAER!:ԙqq      d=tLP ]V6lbyΜ9ҦMM      $"@D7'֭+ pu„ 8A@@@@@@ApC,X [v|`^J      dPħ lRN=TgSLq "      izf P~y֭U^ժUeժURZ5k@@@@@@2)@LSvZt"mڴܺuL:Պ@@@@@@@ 4f P~Z,﮻]v9D@@@@@@ S4fJr3"зo_WUҥKeV      A7v+СC3'      @&epE 6l ݻ?#i߾'      @&衛 ų@ڵe:s=8A@@@@@@2!@LSf/^,-Zpcɒ%ҤI:      SԦh֬Y'      @衛nqYfɱkէB fQ      n:)+:u$:tsNyG8@@@@@@-@tS^V Y1c8@@@@@@HC.KrVXի':w?͘1Ct]      n&)3jԨ! ri8A@@@@@@!@t(SF ,_\>`g=.\(͚5s#      @*衛J]&MH~4i3N@@@@@@T C73gϖ;Z-W^Zj׮m#      @*衛J])KΝ:޽[z!+N@@@@@@T C7SӦMN;ͪszdٲeRbEk@@@@@@R%@TɒoN S6mj}͚5Zq      RTw /_^Fѣq      J!S%K9+eiذlݺ:7|Stb       zBH!Jݭ;Vիg넊vXjΜ92zh%Md:ux?\>lU(q>W @'fo!HI'$\r_ֽKvBŊ@x{'+R*YO|ҧOyײ/xÂ-B*Xᥗ^%K[|y(^J[M_1{ly j'cw} *o1Ym۶ˑ@s~7gڃKy)u5j(9?p7g6m>| 8Pz%kY>Uj$p^       J>tEp_s9΋F,K6m" ʣ3?0 m\\t h<%c۷NC=6ydOӻs{?܃უ=zH޽ sYg%;w.}f9 @(͗ƍ4s&N;M/WRZH5}KN: 2{os;!!"| i@#= 3d~:N#3<3y# K6m%# Hy枖K~容暢O> io֬YSyOh`E/*{]vhvt5o_]nv6Y0WG镳k/ {e|?| 87O?$&M4k@e&=r.>n:SgG!w+_~e /ޯ_?WQL#       TP VZU@ڥ^*뮻nQP?>TL,Ci?܇~N0!~];t urhM8Q8`2ϫ )l2Ϝk$Lx3@U!=4ӧO:Wfr{`EE]$>`0;߿qRn]HHHHHHHH 4UYe뮞04"X UuᇇiF ?bU$ ucL˗/h$PVZaűy-X3mSN sԩ#rK0?7!IYڛ2]JΟY )ϰjժTPڱw>F裏nLOkW촺-x=t>&c.]4U=8*b {K>},a.6nXƌ#'xyg矷ҳ%:L̹lxHHHHHHHMn.L%.B9#C7\3e:4h@YH_=-`͚5xIsΕk]d@sf믽=g/tM7T:w,ݺu_) -f^|EO0[o-[;O~`q .{W~m2e'dM<-}G @EO0eAI;,~аaC't}HB2 x q`]e9Oٳgˣ>af6`b-k׮rYg9jlK ~QЈoҤGѣGˇ~(6lҶm[iӦ+zͶPQܵ7* ;2\B^B׼Y2Gh"9{ґx5W|0RV-Zᦛn{q';v1}ko|D">ڇ_8!=8=&;'~/'ux>7 {9`x'bSV~$$2hKsm JXjS&C٣Ծꫯ:09県طڊB7b/j0\c"Ϗ|Uχx3Q&/uXιD       IBK420+mP#GZl6, *3hR+ag*uNֽ7|](iUuY]?LK+l*UMhanU0]|@f`f QG-erY 0תtQiAf5jd5`mvի9蠃2JH!TIqLC&]'SA㚋2Oifzap̃>hXyю>;h"d7|93y :w,d-F\l:Cm2#Iy]v4_~TҚxsQ!_˚}!Gn!?W&cu={tE{pY4?4u+k RI͉\E4wΡ= $@$@$@$@$@$@$@IT @(geM0sEe{AO>Bh_cZ|_'^x_AEC!v ߔUڡ42q42J;bĈP>AV~WTQڴqV'xb("LZy5lǶmf&U&hoiԉ>|Lp9;%w/ͱ6*(V~POB6o< KG^ܤ3J[niދ\hcZ{H]̹6FBxƚݱF.gS1~JKwc tQ@Z{0.SڞJлwo8B!uzNQsNG       D)Gg$@14m3ʪ4\ɩe3?\h(df/_͚5ͬ^|ƌVެ43AiSҪUе\֭jYO0FkjNs#3)UF%B L|H'4WvegXOpUW?X/AS8Ok1l]칧۔}^L-Dx^̈́ m%s^s%'"͏BU)wGN2:,v\\Y ?g$@P=Y@QAF(? <\4iRTQY۷oZfdɒ%˿~7F]!1^&:Ҁ 'I&~>ҥO< x?XZYG >DiVKΝSsڴi)XѣG[ ym/@ ƻsվ$ Νk5裏v~С3uQ :8y*(d]|hذ֭[;Xgs=$@$@$@$@$@$@$@ Ps@lWo@{??^j={nk+ж2e'R&1/Pиq"\/oP9s@-dT\//R}9@7n`/e˖y8hA7^p?묳>joi a|Qo튻^o41QV.nYbBćQe[0zGE&IOhO:UUoIZ{Ygb=ݦ̐k)30oڃZoTRqU+$]94       r Y (\hI?؜kVu]i)umYʐMY0YikQ`Lt .AT{\ףhWeȑ|z(M7'OSMNFg6 W6pÜ5?͂Eq@xy4͐w,xt6ug i!Qufo/׼2v3rco3L 7i,9,Jo}(]GCt1x{m ƣD1#٨P=8WϺJ)j~Pvw>õ8 LF/uX͹8<HHHHHHH(-;3AY @xwEəgi:thI t Eg'LBػwoϼa!C,FH o{+󍦆 ^Y 6lZQ]/a%ЅX$۫uLNmlfV-ZKk1)"2ݙ#*ˬ6Z4[u۷~JKcsn&/Ÿ^zɻ475_\,ӕUyO?$|0M+ޢE y饗B*D=} ڐUwY+@]WI=C5C] 4iN_~n Wa֎ke23{\e26̼ $C gRH8cd׶ AVOp 8ovkP˷"4_yg3 ܁0>Wp5r%kj&"뮻ZP0:+s$Qˤ&?p` KxYq>5͐ hkι2N;96m4sX.]Ș1cm M]WHkqՕ+5_{uU6|w_>3'|'x"k-'Nz{p$t1ռB֕|1\ ,k*#7{}Yq'IC̨x9L'      HeU^CCJif3LPGӜkT۵k'.QϞ=4) -N8Jwiv%M˞{`yɒ%^9xUތ3Vp=I^|wӓn_p4z-жl [nuJs%$CC4q&TBL5kUVLhnjZkX ֮Ç'ko~hXHHk )t1 1Sf$?h`>w1ZYi͏vx>O -ni53?C2`XgF9 78 @(M)K$i$gv^+D >5ts_uR6᧕K.ĺO-48a6/B7NڵgԩrUWYp_38S o pYN=Thm;LyKX s(ӥYԫW/.g9INR/bVӧ[жZxq(.K !gu 1SL fصG\ ϑ.w7{!P覱(4#G^O^C:Ӝ{.?ba߹KCiŌ`߅Y]3é AO?mfi0c5.fB#Wʺx>6P/rG嗩OOvmg 7x|fZyf]TČrYyQ}W3E)2,|mjlVw4yXlt{Z Jt(_(AU2QBVf;[yR O\F7IDATs B [01zlQtAe7Օx{QF0]vU+cNuۓOqB duu_~V>4hG;i=\sc|!d9oMa!c}]vNo>JpW&{Ǵ`B[\{L>ЉG*P!Kֱs cZ|^$y+Pn3$tdu 59K^W^ynZlcT: $޸/SE(2:uq9S>t^m@&_>g {b Oh V'Ig\2utM1(yI!ws\paҔKyVz4x&)kV~Sr܃˲TֺJqDxg~d3&d-Z9Mc}9I_Ȝsa @hr9gI~:՚+IǎeCf .v 뭷<3M4U/rK.4d6Le"gTڡ=Q W\qOc}ٖ֨?ɡ*<"F{챇{4]#j{/;,ӦMX`.ߗv)-E{/,\i~37tL]rZ{Հ i=.G@uhiƍ;/V}W_}x\=&N{]yҘz\ie]Wi<tLWg<Μ9.<9 ]# @  V M.4~%dbjefH9ق8K̲e2ІsiT*^ )IYJ3J0Qh? 4(au6hP~Gd?UV0pLÆ mx3eFzʉJHe$B;N*73QkN r8ui+OUGRii<t0x QE E8K֭MgXޜ˻&       &@R9䓭j+'tu#"TAO``0a^$wޢ48N6{4FH!UcK        C"P `z~Bk^˒bŊPR~ uH8@9ŋ[-xꩧDf ׭[WN9P#$@U3) 75˻Zj/ { 7HN @fero eT><0LN>]P>D+0s@$P5 p^ @y:$Q7n,.|g>:ʿ[oN;͏$ѣ=V1KK.2~xHRuǞ='        bbf]U/~M7LH 8yNo%լYSΝ+7Yx$           [ ](Mחs9#F8ӑ˙g sv5P  $@$@$@$@$@$@$@$@$@$@$@$PPC (SZ͛'-[t6zҴiS4pL"C [SOnMGy$           D[])=͛7=z8ҪU+ի/̭Qn0 & ,jбᕅVw\sMdutqԡz[oIu$@$@$@$@$@$@$@$@$@$@$@$@5t+ᠲKE`]w:X?ґ_ɡ*ӧOw^g" @ @nG \x :T ]c5d:u.\({L8O @"@nOD tAҤI| tK كJ@rg߹}g}VU%ZJ=Xd2:$@$@$@$@$@$@$@$@$@$@$@$@d ٍ'p '1brꩧZر 8O1c<~'$@$@$@$@$@$@$@$@$@$@$@$@@5G1E% pwJÆ &p7[tMeٲe^-[̙33HHHHHHHHHHHH P[&&pː!CtTL"ڵ$PkNxOmӦI"0uT:tL8QVXauv!+]vjժYyLٳ%X1bh"jY/?;zҨQ#iܸe˖%zmɳ>jG!|(C?ݻ[;vԫWOZGwulF~T}o߾Mȑ#uy0`km۶:fF.裏B[o\^Z\ΓPrDVZ%0~xY{dIHHHHHHH#@.' 8 zO?TG^{_W_~[l\uUY )K/$K, |P"ϷXgϖ^xvvQ Dw} *o1Y@Pu _֜~-%jA(o,$ ᣣu]׉of=D0믿. p܇3L[m= q y! #HHHHHHHr@0_6K+lfy %s s?s?n^b<&hNC=6ydOӻs{ "~YY2;VN,a.AP;}t:Θ1"%TEEHHHHHH*+ t+Ȳ_U)矫L߫BG;ԩyu/׏=Xy7749oH|w7<ɤI$?H4xm=LfHHHHHHH*%\NU%Vц/."nF ^x~`#<+'ʜe0?+7ө?@{f)wb@$P?RR 5ܾ}{o`+:q@wv eL#        t9H ]h1T[? &ȚkïkN;qD9BT=F͛;;l29snڴi:1F_~eeS (BATnri+Ẅ́G       7& $@%AvNXm=Mҕ+W_L˗/N"ŋcZ`N9ESN[t4t|B87NyqQ'LC@^>"lCYb[O>1?A>(|EN i!޲wsO)c:ҥKnZ*/ NCe]~t7[3@mĺvg96$ɱ˂HHHHHHH*&Ի*f* r 믿zZz5k& Ν+^{%dZ_/=g馛JΝ[nM_v@xʋ/(܁Ybz뭥uryoz\p{o˔)S*駟*&2 J3#TzBÆ tI>&kN4h +V5'B 4={<裞 apa 6-Bv*guԭ[KsWfO䝏7+W_ꪫI&:1'G-~' m|ͥm۶ҦMի@:*d['Z*ߕ;k2Hk,O O]F#4Giey޽{[HHjIjӚ{+>UNpM7y=xZ![ױnFo9z]w[+^'1{DV NL5?>s昃!efK{Y~r'9Zk\C9$sBDC 3pֲk֬B"_f 7ZFdpʳ6۸gI,y;Q$+! I+Iie9TfzkFi[0!sWdPkCc,+^,G Iyp-Z˜=khժu8EޓԚwUPx3&+!s kX(MQȣӇ[: 'L[,?@T}ʇlݧ>| A;)y5k5'6< T4\Bx6ʋ񞏻6ԢzmU[eT6+Z{n&(@kPȤIB4-{olA l}2k s=7ش yk2$gY0?y{pog--3Ȝ9sBШfWHk{< K8@ _b=mIēԩ5fGC5+|<)NI.Ǧ[hY Km\5u^Qu]'7|s&kH$@$@$@$@$@$@$P(-١cIVi5tذa)`FRС(M'n6L-̜9S{9? MEÇ;/1L*6:L¼j#3y !`6 I7NiՉܮвeKO82W _2~묳O6h#:X+ yy뭷tafpu]Eƿ 0nn[nrWHk'56io}ܡ9k]g-xEib;tjHOi4_@n0&:iq>f \}0_ݤI!ϋ`pgmHHHHHHH< TM24s%GQBEҶ*>QZ}';tPinVC('*U&4Q̭& LQ"|)hѢL.+ᖕZ?L҂4jk?3.sͮcz2tPF 霦upLqL^&]'S.q<dzaqO6])}ٙEE738ÙWqDi]:LujIr벓l&fmfqV& ni!w15a!Gn!?W&cu={tE:cܯ8Of pJcųR}d3f+Һg&V#Əo]W~8\Ӛ'hGEZcds       GfHJ@=NA!V>֪U/B+2=gz;oh{痉UV/jSW_Y~SFfFi]/OضmیDEi7 4DNkN8AG#gkM B&y7oLLR+!S(M `k*L b^Wګҷo_e]Jck<bWLbMi<=ĬGdzwnSB^x2 zr6&y')Wr]~t\|wtV{3֙G[@9_s}2A><'      ֬bH Lz~ʆ6&>O>9'MUT;C֬Y3YdI:Do4xQty̠ATOi@… uJ@ݤI?MtE'xJŷg'Y-;w/^IuΚ5+PWҼu% 5CQ0=_|-Z ,B/|ç~*L6-1EkX9zh0/ȓ]!x{ڗDZ!ܹs&}:tƣ:XAKX4_ >̀u_u]iG7(Us>DZx^*<)dl{HHHHHHHJ5^l- +={Ʈ_hM2)S=ZZT dlܸq >mذa8AŔoPڜ9sBqD  }^zh v[O~˲e<4 Bݳ:K>3_Q۟F6ufRqPMyY'(+@,kl$>$ ?(߂9 hף>*ov0?Oj͗xBSԩ}3OCzt3I_>+-K(+/ս{w9r|מp7Ju$ס<ڛF.l/u<ڰ•ǥig-čCx{Yo׼e1a;[Hk3xxY+OgT}{`*?WP>Wsep 2 7 5Y`VvHR ~QnE'# @%]Qy͞ A PO>2j(gW 3l@#;fKWt GL:>^kبQ#vc0u!s+VLNB0nC=T:t`&{qp׿iDtI2vX+LNj$kUF5j԰j[rfK0?ҕr8k1WٮCmo2Y/xB%~`W`db=i(L 4k]gK-)7{7$=_ϩsqk,?\Bc3OTϓB6L'      <(Эys8>Ae:r"$@$@$@$@$@$@$@yt6ȃ@6!Vŕhsf7͹R>i׮F={Ӥ,N8+ĠO@}f `~aO[ /9%Kxx8c ߿:{vqG?=g9Iέ:dC%[nź֭[7+͕2 /SЀq&TBL@CY."|8djǥ5G5| ,/AeTx(Id..?fمEY1O\ _ @ơ<$P hn"U\fAKgs }׭kلV.{ ?<3=mzJڵ3uTꪫZfx3~0۠A/멧jaQ`Ne4 !W_f rF\|VvL>J1b,^8f~S7 b5\)h͈Jz-Aa}зy=8>"뮻7q@uΪLnk>tvJ%bG xu>mzڧfHc1Os|c燚}K/ 3}ץ٣G*O?tAM<製:QoߕW^YP}Iܜk+@w]vq1UۛŅ tӞ'qN1B$@$@$@$@$@$@$@1 ЇnLPF^Cyj@k4:h OM!3gl0|HX^̙3k`dMrBv)Է{ڢ`мys裏gǠں 80' tM=߂0j *=ZGc INp>kXBSN3 ]f!v Ra ogw7s='^p'|߂6$ᄏ'ZօsJ' ȫdCZk> <~pz>uנ{_L4Pe5v` {71fϞi¿wy| ꫯ-y|~`/_<\cDc=#\1!ܲs*_}FbseEӜ'qM4jIHHHHHHH hxJC`a"샟\hIE2^/_<ʅf3^C*/Ez u "Q cSxlދm](Zo0uFGeI7 i Swq'8 ~W>9C_~$eWZ^zu`0!/*`>y睞>*Oyc I]X0u\B{,T=>/~)\ˣ]:+ᆴ29O(sy @'P (q֮]W_}/j7#Z5$_og ui@HZhtmFVСCMa+N+$JsB³>ѪvDp-FVF vW۶m5e?{72f#3 ;SL6M7 (]{/'2ۗOc+~D Zy[=܂f.κ`2*@ \h֭[י uC{&/2g$Bhh#F_ڽz'^'i a.|b6m40 lhf˃7c &xBlbȅf͚/a*4٠[*>`Q| y\lk>5LCӘ{sN+hB>o.3 cuѮpЖ7\ s}PT̶w]i쿺OQ]|RV-:#aʴ !jذa8$8rr5_3N]?cϷ$n Ӥ)c'=_O[{O?> 0Zʧ?e޾;_KzkrZe2Mc->iΓ|m$      (wJxGck瞳]x"mڴA;Wݻm0@[rH!Uoc        (/4\^Y/ $H~ۂ~|ɞTEpo~0xb0|waSN 1B$Pup:c͞ @yny'﫩- !/|̤io߾z%T{2^L+ӧO=& T C0$@$@$@$@$@$@$@$@$P!P[! _,Ç8u6+.arif.]dRV`2I!Ul]        ('4\NY- $MZj2d7<+ey'x<ҸqX] 4Hq scc&TeHHHHHHHHHnE $'|"3g~Ϸr-ҨQk-ݢV^-&Lcʜ9s/+WJzaÆҺukڵ|nGr TpI+ %         $@$ zf  |pɇ!        p@Ei$@$@$@$@$@$@$@$@$@$@$@$@$@$PЇn6HHHHHHHHHHHHH\(uQa TVA`HHHHHHHHHHHHHHE] @ K\ʡIENDB`dygraphs-2.2.1/tests/range-selector.html010064400000000000000000000156611437353256000152470ustar00 Temperatures with Range Selector

    Demo of a graph with the range selector.

    No roll period.

    Range selector options

    Here's a view of how the various range selector options affect the display:

    Roll period of 14 timesteps, various custom range selector options.

    Use the average of a specific subset of series to draw the mini plot (only the first series is used in this test). The default behaviour is to compute the average of all series.

    Demo of range selecor without the chart. (interesting if multiple charts should be synced with one range selector).

    Demo of range selector on dark background, with (left) and without (right) custom range selector gradient color.

    Demo of range selector with stepPlot

    Demo of setting a custom colour for the range selector veil.

    dygraphs-2.2.1/tests/resizable.html010064400000000000000000000014311437353256000143030ustar00 resizable

    Demo for resizable

    dygraphs-2.2.1/tests/resize.html010064400000000000000000000020361437353256000136260ustar00 resize the window

    Resize the window. The dygraph will resize with it. This is distinct from the { resizable: "both" } feature.

    dygraphs-2.2.1/tests/reverse-y-axis.html010064400000000000000000000036351437353256000152160ustar00 reverse y-axis

    Reverse y-axis

    This test uses the same data as the demo test, but reverses the y-axis.
    dygraphs-2.2.1/tests/rtl.html010064400000000000000000000077721437353256000131420ustar00 ‫غزل‬

    ﻏﹷﺰﹶﻝ

    ﺷﺎﻡﹺ ﻏﹷﻢ ﻛﮯ ﺍﺳﹻﻴﺮ ﮨﹷﻴﮟ ﮨﹷﻢ ﻟﻮﮒ
    ﺳﹹﺒﺢﹺ ﻧﹷﻮ ﻛﮯ ﺳﹷﻔﹻﻴﺮ ﮨﹷﻴﮟ ﮨﹷﻢ ﻟﻮﮒ

    ﺑﹹﺠﮫ ﭼﹹﲀ ﮨﹷﮯ ﭼﹷﺮﺍﻍ ﮔﻮ ﺩﹺﻝ ﰷ
    ﭘﮭﹻﺮ ﺑﮭﹻﯽ ﺭﻭﺷﹷﻦ ﺿﹷﻤﹻﻴﺮ ﮨﹷﻴﮟ ﮨﹷﻢ ﻟﻮﮒ

    ﻳﺎﺱﻮ ﻏﹷﻢ ﻛﹻﯽ ﮨﹷﮯ ﮔﹷﺮ ﻛﻮﺋﹻﯽ ﻗﹻﻴﻤﹷﺖ
    ﭘﮭﹻﺮ ﺗﻮ ﺳﹷﺐ ﺳﮯ ﺍﻣﹻﻴﺮ ﮨﹷﻴﮟ ﮨﹷﻢ ﻟﻮﮒ

    ﺍﻳﻚ ﻣﹷﻮﮨﯘﻡ ﺳﺎ ﺗﹷﺼﹷﻮﹽﹸﺭ ﮨﹷﻴﮟ
    ﺍﻳﻚ ﻣﹷﺪﹽﮬﹷﻢ ﻟﹷﻜﹻﻴﺮ ﮨﹷﻴﮟ ﮨﹷﻢ ﻟﻮﮒ

    ﻗﺎﺗﹻﻠﻮﮞ ﻛﮯ ﻧﹷﮕﹷﺮ ﻣﻴﮟ ﺍﺋﮯ ﻳﺎﺭﻭ
    ﺍﮨﻞﹺ ﺩﹺﻝ ﻛﮯ ﻣﹹﺸﹻﻴﺮ ﮨﹷﻴﮟ ﮨﹷﻢ ﻟﻮﮒ

    ﺍﻳﻚ ﻧﹷﻈﹷﺮ ﻛﹻﯽ ﮨﹷﻤﻴﮟ ﺑﮭﹻﯽ ﺩﮮ ﺩﻭ ﺑﮭﹻﻴﻚ
    ﺭﺍﻩ ﭼﹷﻠﺘﮯ ﻓﹷﻘﹻﻴﺮ ﮨﹷﻴﮟ ﮨﹷﻢ ﻟﻮﮒ

    ﭘﮭﹻﺮ ﻣﹻﻠﻴﮕﺎ ﻧﹷﻪ ﺳﺎﺩﻩ ﺩﹺﻝ ﮨﹷﻢ ﺳﺎ
    ﻓﹻﯽ ﺯﹶﻣﺎﻧﻪ ﻧﹷﺰﹺﻳﺮ ﮨﹷﻴﮟ ﮨﹷﻢ ﻟﻮﮒ

    ﺧﯘﺩ ﮨﹷﻤﻴﮟ ﺑﮭﹻﯽ ﮨﹷﮯ ﺁﺳﺮﺍ ﺩﹶﺭﰷﺭ
    ﻣﹷﺖ ﻛﹷﮩﻮ ﺩﹶﺳﺘﮕﹻﻴﺮ ﮨﹷﻴﮟ ﮨﹷﻢ ﻟﻮﮒ

    ﺯﹶﻓﹷﺮ ﺍﻣﹷﺮ

    Poem sourced from: http://tabish.freeshell.org/u-font/ghazal.html
    dygraphs-2.2.1/tests/series-highlight.html010064400000000000000000000041731437353256000155700ustar00 Series highlighting

    Series highlighting demo

    dygraphs-2.2.1/tests/small-range-zero.html010064400000000000000000000037451437353256000155140ustar00 no range

    There should be an appropriate amount of padding at the top of the chart:

    The second series (which is entirely zero) should be visible, and the y-axis should go down to zero.

    dygraphs-2.2.1/tests/smooth-plots.html010064400000000000000000000056101437353256000147760ustar00 Plotters demo

    Smooth Lines

    This plotter draws smooth lines between points using bezier curves.

    Smoothing:  0.33

    View source to see how this works. You'll have to source extras/smooth-plotter.js in addition to dygraphs to get this feature. (However, for better browser compatibility, use the file from src-es5/extras/ (dist/extras/) instead.) See the comment from the pull request that introduced this plotter (online) to learn more about how it smooths your curves.

    dygraphs-2.2.1/tests/spacing.html010064400000000000000000000023061437353256000137510ustar00 spacing

    Wide

    Narrow

    dygraphs-2.2.1/tests/stacked.html010064400000000000000000000063521437353256000137500ustar00 stacked

    Simple graph:

    Stacked graph:

    Simple graph with missing data:

    Stacked graph with missing data:

    Stacked graph with many series:

    Change selection/highlighting on all graphs:

    dygraphs-2.2.1/tests/steps.html010064400000000000000000000225731437353256000134730ustar00 dygraph

    1: Simple line chart:

    2: Simple step chart:

    3: Filled step chart:

    4: Line chart with high/low bands:

    5: Step chart with high/low bands:

    6: Stepped chart with gaps from CSV:

    7: Stepped chart with gaps from native data:

    8: Stacked filled step chart:

    9: Mixed mode - step and line:

    10: Mixed mode - step and line (filled):

    11: Mixed mode - step and line (stacked and filled):

    12: Mixed mode - step and line (high/low bands):

    13: Mixed mode - step and line (custombars):

    dygraphs-2.2.1/tests/stock.html010064400000000000000000000023031437353256000134450ustar00 Stock chart

    Stock Chart demo


     
    dygraphs-2.2.1/tests/straw-broom.html010064400000000000000000000033011437353256000145750ustar00 Straw broom charts demo
    Hover, click and zoom to test the rebase:

    Value:

    Percent:

    None:

    dygraphs-2.2.1/tests/styled-chart-labels.html010064400000000000000000000050051437353256000161670ustar00 two series

    In this chart, each chart label is styled independently. View source to see how it works.

    This version of the chart uses the default styles:

    dygraphs-2.2.1/tests/synchronize.html010064400000000000000000000052411437353256000147010ustar00 synchronize

    Zooming and panning on any of the charts will zoom and pan all the others. Selecting points on one will select points on the others.

    To use this, source extras/synchronizer.js on your page. See the comments in that file for usage. However, for better browser compatibility, use the file from src-es5/extras/ (dist/extras/) instead.

    Synchronize what?

    dygraphs-2.2.1/tests/temperature-sf-ny.html010064400000000000000000000031351437353256000157150ustar00 Temperatures

    Demo of a graph with many data points and custom high/low bands.

    No roll period.

    Roll period of 14 timesteps.

    dygraphs-2.2.1/tests/two-axes-vr.html010064400000000000000000000067051437353256000145300ustar00 Multiple y-axes with valueRange

    Multiple y-axes with valueRange

    The same data with both different valueRanges. Two-axis old y[40, 70] valueRange:

    Two-axis new valueRange y[40, 80] set:

    Two-axis new valueRange y[40, 80] & y2[1e6, 1.2e6] set:

    dygraphs-2.2.1/tests/two-axes.html010064400000000000000000000127011437353256000140740ustar00 Multiple y-axes

    Multiple y-axes

    The same data with both one and two y-axes. Two y-axes:

    Two y-axes with y as primary axis (default):

    Two y-axes with y2 as primary axis:

    Two y-axes using different grids:

    A single y-axis (left):

    A single y-axis (right):

    dygraphs-2.2.1/tests/unboxed-spark.html010064400000000000000000000053741437353256000151170ustar00 dygraphs unboxed

    This shows dygraphs without any boxlike elements (i.e. axes or grids):

    This style can be used to produce interactive sparklines with dygraphs:

    Disable the legend though, otherwise it’s not very interactive…

    dygraphs-2.2.1/tests/underlay-callback.html010064400000000000000000000050471437353256000157070ustar00 Custom underlay callback

    Should draw a three-colored background, split horizontally at y:2.25, and again on the top at x:19Nov:

    dygraphs-2.2.1/tests/value-axis-formatters.html010064400000000000000000000063061437353256000165730ustar00 valueFormatter and axisLabelFormatter

    Multiple y-axes

    This demonstrates how the valueFormatter and axisLabelFormatter options work. The valueFormatter controls the display of the legend. The axisLabelFormatter controls the display of axis tick marks. These can be set on a per-axis basis.

    • xvf = x-axis valueFormatter
    • yvf = y-axis valueFormatter
    • y2vf = secondary y-axis valueFormatter
    • xalf = x-axis axisLabelFormatter
    • yalf = y-axis axisLabelFormatter
    • y2alf = secondary y-axis axisLabelFormatter
    dygraphs-2.2.1/tests/visibility.html010064400000000000000000000030421437353256000145120ustar00 visibility

    Click the check boxes to toggle series visibility

    Show Series:



    g.visibility() =

    dygraphs-2.2.1/tests/x-axis-formatter.html010064400000000000000000000043451437353256000155440ustar00 X Axis Label Formatting

    Original data:

    Same data, but offset by 2 hours with the date formatter:

    Same data, but always displaying HH:MM:SS:

    dygraphs-2.2.1/tests/zero-series.html010064400000000000000000000016071437353256000145770ustar00 zero series

    Custom bars with a completely zero series. Both Y1 and Y2 should show.

    dygraphs-2.2.1/tests/zoom.html010064400000000000000000000105331437353256000133120ustar00 zoom

    Click the buttons to change the zoom level or just use the normal click-and drag. While zoom typically works by click-and-drag, the buttons are useful for testing.

    Window coordinates (in dates and values):

    Zoomed: False

    Zoomed X: False

    Zoomed Y: False

    Zoom operations:

             

           

     

         

    dygraphs-2.2.1/thumbnail.png010064400000000000000000000253311437353256000127710ustar00PNG  IHDRx<"FiCCPICC ProfilexXy8?cdC}/l3m"$l-%J$Y*eK)IȾFR3~us>>s<L$R F9sr>~@ʃJҵ2(Ҿe4ֿM!C>{X=G /|=>d;}WB㳇(s?Ews0/ekB 4/JyZAA!}l$'Ȑ.v(P %d3{@#0d6{c}@~ κ(e;;W@H'Gr!_{C x sSj Uz+(+)1;Sk'Z7(T(*#6 -ŵFFWSN_bR "~M^NnބP`V%Sv3s˫V6X%tmΡO;Zv=#ӗL<;_p>da Y!" _xY#su|DklY\Ɉxb"R3 )iYge/zp?{9ǝ/nm!(tI+/=SlZůk,oXIuvN}}M{SKs߽_Zя[Y:u<.|bTսyuO ^޷/)}xZuۛ~@`[!wi##Fۏ }p4=-3}k6wg^}0x})`Yfyc i5/n<ofܤ|? c_4aMp#4"H%b塓?n(2V13rpvs; ZDD_HH'IbrdUڪkij%kgUW=XwurR|L3V'll 8 9a<D!Qlԫ4ôkhuL.c'3şmC3>WϟH..|`Q(&(e_VQ+L yb 0zs*ڪXI;IbZO tu50z636Z;v(W6ts łֺ6(gvCGx;>2 ?]];bݗ<<hB+1Kk;Gg7oݿ,*`+zc0"6T̍LC nQiU=~\ЉXؑs'ĿL(N$2M:xz3y۔'Mg' dZ+3ۖw-?/R]Z`1}f7z/}.кJv7noVToj ilZ CG.mW{:> xR ~toeU`ݐ5SӳSKW?| (}Rb XpM (`*eػ;X 78*A7Xa`r0{X 6ۂdx% EG4#&HE;2لbҦ L5C颢Puejqjk 4244hiޠ9c_UЗh cL38c!4L`gdc bʖa/t~w_ON q ߗ%/j$&"ߐ|-չZ[:&?+#UTդ4Z8mfΪ#2#zdMMf-f,'&mlh9ĜU?ZC8@t:}vs`dL4I=6GBwyTR_' cRHjN6>3z&M0&C'CVVSfqAd<0e++֮\_^zkv=MO=Z#:bF< qMkz8 ґ1 3Rs) sW>c׏o|1M}0` A!xa0yD:9l WSwB8G2 5" /*9*"U1wF*ڜ0 6N.nބZ ]ƐAacMa03 1ba` bCOr$sJsr%p?dC0¡qeBFm 6]ϗȒ̐AP]S1o ljJ Uu 7"~]-FiS(z%_hdeqpv ut9({CwOﶿB_ŠW!p1wrFH(贘sq'cO '=]<‘|b9錨̮, !=0E܋bO.]A]=\^[v[QU'{gFf{k=Ll5kz\Ds^Sb OwMTOeΐlxK>^Ի涾qm_JKV{ [ mIF2B=HR lDnP)REPP"":yyrZZ(]+2i=t3"&k̔ǬżRj=Ìṣ»_. '&" #-T B<怆YFiUEQ5Af֬nA+&w ̇,IhZ;ymqpQ8~ӕyW`u,<f~')\8QsNzN%KQHmHLp\ʊf)˓o.*|TQPJurnxdEǕl׽$?0z҆\ ҾVGvڝdm1{BJ'w q:` ./ܵpbi 6؍sBYŢ\a}£zɣb¤fYRV|B @1 _r!C@ * K%XiZV IV+[We=J&x@ʗkՀctLόdT8BDŽq8#ЌS X59M8Z"NBKh *J)TUZ-휫SW*i|:`UAX#xyͪUyy@N)]3w\ )u.3}=}Rػȹ`Sa969m <0 "2 $\+2-0߇ɑ77[[{cb// w>'`O|^mVHPmZN"H?c돮;O9tb4+htB-^F etuaQa:t ţUuץob76 i6cz:v8EFe&2ΚϾ+)bSv|>O(<;DP,4hb㫇^E^:zޥU`L@ @mF"-շMJK Uqi1?|Vge,n֍La#sIuz]ʯduLKh`y|M&Nos:X=ɸ00:|8[#:(#q!*nE{? Q\~>%v .m Yj{f^wjٷ<vU*/#GH>p:MM2lX0vnXLNdH m6P3x'@0êS)궭<|Hܐl C4V/_?0n`2k("KҠ(^5RO'C'*Sy 5 >u|H[Mvmt$%"&'*( Hьcr#OlVIUF,mbcuDj,5զEM=y`57-]7,U:hϪq4B1zEit1!'` m$k0s?үAɈXu+m)1d%d4gֶYo"iON: $h) 7M`$| cy>tx73 !$D|7lf}IδKsy<zQZ J5sT雧ɟ3̹MFJ>E"2`A1h2wyԂ~6`@ Z-2FTje#sPֈ^ H8P#h΁./pƋ$Vdj"HG2y{TN=j:$%M+RoD6F;Os[%bJVl dʵW9 w/P htMP4G D1,ҚVB䍈A@0f1&uKwoe}PH. ' V Q29@hmļ=gw;i\qEH#ꎾSN:~(:zo~pH =&<e{oZcqe==2wzۉJ֭"؀;c5^Uh7|r8ua֖Y,JeRۙOXjs#~pU'a-|:bҤ eIS?[yw!%`_eۉ^r{r-$iDJCKe4| !5@ylvU6H;a- w[x| ߳.]1{XXLqSIBE"z" Fssjr,BPFˌN#9 Zy2GKR' ڴ,ˊʌ,i.i27E+ '~e +P|V櫋{#ĺkMmlؽ5{_^}W.+:Yg h+_"f%)Pxkߩs_5rD990_$*˜N=K76^kGZu6^hJgB*)g̎zqNW$٬>D b60Sڭ"*ӇV_7SYc"@^1c#VKl.|΢;zNa}oKPOHPi̯n`lfv훶ң.᡺`Rhc#U=Yi,9Wxy|B8TYߗv-[L)Q)''0cn[ҷ_;|3r=B '5! -Y2zfK+M t.݋G[gSxJ& }81r8fTgDKbkm&Uɼg=y@BE"iUzL#^ǿv'XN0uJv8آj{ᆸѷ[)VQÚlbď.}C_@}GtBD1A0\EBV[^R |:mpw_)1F Rg vy=|݋F,ƼS[mGNcc Ņ ҥcx fQ㓂6OŖA_z#HRlbo}ُj13y_֜r4bif}fpvݺ:YS4 n= sO[-n"hςI ZEiZ@ADF-P}o8aE@cyW)f \A#dR#*g倎O\ O@ec^(߷&99jLu$8;6זYUܗp="'hZC"xtPښ/8*473n3RiJvn+_*ӄFF ]{'Qn_V Ҩ ׬)NI[vr}!N!f]) ` \VR`}"OJ*12*bx8sS~(y:,jmr٬Ld%"%QB%g#@s s KWnV~;Ӣ k\,'9+a"fSX濐9rloGGjjp632h>WFs&tAERm>҃zn[^T8ʎ"ĜzྜྷM?>{Ɖ5iRwWh4$-io4kME73}zp@cv,x'ET9oڶ;,}nI#uPQս~u7]Gu-n"oSOPt>Q*mu#+cc(v5-OZ5iԨnD- #D~KDgg-SimDdPT޾íM?.ߞ L.,g6{=Sf` Z~$PN<>np!MmVcLE&H(7|޽?yՁWRNמU PH4yܰͺv l>hcH#h=sѢa0cU1a.m&!&BBI Vlc6yOOzf;A5R'uw=IG^UZPXգG̊u8PתFG+LI˝1 %%5*j Ts ʍ]MڶꕗJ,$)h99I/U*E( X.#D%PA`pU=sfԻSޝEE5fq/.}ɸivs({UWsR@ھlmk3 0Bۏ?H dAuGFPp.8At o1\0%_~r7*Xx:?oÇnDGU&wٴð-2R uoɦ?yږ[$QXX3fظnܟby7WnZ%.H@B~bbhs8P_PPo4/ͽ/[6v̘$ {.0S0GGK7r\|0 uhpɜW@C&#nN%.b%K5:́<vn_1=3禆aەYjrrm6όSS#P#{=Qh$*ox-+;ujΊ}cYnRuLh׻\._HM] |۶3r`ذxz\<2CU1P*',rzm5m_sNò%7dp93"D|ݶuW~!߾PD0 Wu@   AڀW%|'0@qσ~j(P1l2 &èK)n{h"#% Em h ft0A( xzJ{AW\$mPyWJ@@ļa1J&!@w PAB 7!q1Bo_%FhF>7\T]X"ߘeUw$4n| @_1n7N484vӘaړagqIߓy$"?M%L+vM{qn=I*m2\j2r TP!]__y.FǦgl?k9KHx OUBW1"j^ !bت枷 ܎ƶD%EEE999Wau3\KLLCáCv{f_~ z^j@NT (1NȵUhz7~ݣz,h>09\Alϫ 'O#oii٧i=YģηYiÆ/w9ZrT$ [N=WWmb,' m9JSdڗҩ}ecz56xU=h"AJ-g=fUM1[jZ~\(C/y_Y(M,r޶'W yפqFMntY=jJjG+[n3#}[6(w- U.%Ʈ}zn]8$_ϖ?+,)bdxW$-&FB}YUlۿ-p|=tB /xy݉7q`;dg 2L#[5T(uA&/ +{] =W|nMʝ{߭Kytʎ5Ó¹”OsmF Xh:X{TurPo~k_H"JU4w }oc?X\68K){Oۚti#bu-@v W]/.7;ux_։&wnI(%*ۇ}yiƦ~9VW@\36FzZqPWs)X:l ϻ{vZ{HE)Fx衃VVs(QLgЧSʲcpLj׆K cYC$4illQF'
  • [Ts{Ŗxhl%!ѳĊ!FԫRZBl#^AODȫ 6ܽs4CnG 0x{B@F۠G> s,2{q@NbIK Ȓ8:Ʉ9LCr{< ʡ4| p3GnH̠;eL=GJ,$67L c+Tʘz8F#JĐKW$ˎ,JԃKZK֓A tFʩE(LBX ɹIX Ą2xǫhK̈y*,KLK|F,MMN,P!0="L ոfO"]O3[+!%ҴXQT05R}t 0%RjV<E@]V~35Ne1jWIiׇ pS }XUbP } ŸM8ӁMUeӈ}UV5 2pV%ՐJlش0X~<`Ўٖ؏eTxmԆيWES Q4mb|YmZWQYY;̯-Ye۶d%ZP$SІɥZ}Ң ۸ڽ!VVĢY٩yٱM qܷ\\=#ѳ Xmܞ]MEH*:eEݠ[m-Wm8J4Mݾ]myuhE%- /]%[Y^}\][T%_ōu5_E6Q[_ŝ]@T텕```1Ō\V fF>a q 6aVe<^29ѳĭ~]-¨`8YOy mhW#]na $Nʿ H]1v]XSN28^!/&N4&~?`b誾6]%a?fFN+W=6)(0 dH%"dMҒdN^K&:=cƐY$> 3{SFe fb6x6PVGb خe_䧥 e`ƌ#A[ZXXNEfU uAfߐp>r9>[}kN~1Y[7bdy]FtPXx!gqYfP|vh"l_~drx:ii.i>iNi^ini~i.["iiiiijj!,;TC? H*\ȰÇ#JHŋjQ= CIɓ(S\ɲ˗"UM5޸^Tɳϟ@ JF)]j$PJJիPUylgW:Kٳh_kԮ۷kۥK.H{u˷߿ LÈ+^̸q/{5Lf|˙$>nMӨS^ͺװc˞Ml> ǶV'P[`~pc6l ËOӫ_Ͼn|Ͽr@80 - s 4I#dv ($h(,(4b#Oh`?SPBӏ=pL6PF)ixKoSfemG`O?ZhGIzo)&G@8 8C矀c$$ ɣ= A2j\I;p)'Tz騊hKvرB6ӄ٩ߐ*Y4-ȴ-4" (@"ݲkZe2|G#^9KBrkOL-u^fB kc,& ,06!N':!PA\DQ\1/0VqP|W,̨Rl.\sU2@޵#LjϽ(y=`3G0 C_0t(K;0q__Ԏ61Ym!sLwI$ Mo5[L.qvP >=4Y4IKH@?H05}c-KExKb)! 2hTQ+a)ړ?@R`CS `P@W}LSPNGpEUk=U7MzQ=#t/T]R + [Sf?0M?~\<R$pTQQqiӎ@9ك{~(SI\S.Bcď]'[Ď V >rܻ6E{,mj}sS6rT؜iȱx3ѐ-q|"6E.(?& S.gI@c EP xBLc$,LZJS2˓$䣠0b80lb|fIݤSg[D m &)gFMsl @ΙiܜZ9GQk|J`n3H \mLkRASH&q!!)'AZ&e8 uhHUzΕN( H xayCb )(WBaWD@IifB,!\0 F(f{%*ͭ{p S)1M?%Zbi:;,$?RtbpwhYqnX^w-lE*Lm ffF̬<:,Nҕ6nر V(g/Kʲ4",kѮ O+iݺ8$w k'YV$]VaOͶ-ruܝ5l`V!mozݢt-){3\Xv\*፬|q) h& :]{xN#6k0UXMb,Ba]R6[q08d"~n" ;,AtŮ6kylKf&;PL*[Xβ.G]iL2)2 gc6r;dc"u~msh9xȍF!,a C@$P~^gqOˣ> @XkD'w%94#}ktNڹ*'4l5{dZ7;ChϦ3so-a{{ǸN+QP𧉪Zt[ ncy㣔vimJm[o[_;Xe3Aqos, )_EExǻmj;'2DЀ!Y [S򗃻OO<:ms:hv^Vvq `@IA|ljMzԁqk)An U@͐#\5Γ뷠TN ?2_+eտ~3?3>oAO{0QjtF27 (}w|)aTWu]'x qy7'6{A#9vhC~~e|%HK} 4}cǀ$xhG_ȧTLF]4K|z"JxY{*QCOG)rDT:x<GAXgȅziUzah{ȇ{~*}a ?271[}xFIX\;Ȉ#qa+>M#`l؆耿~,'pQ3NJh((1|ɸc(UG٧8('Q5(owqzQS8}2Hjo4-%Ihɓ="48{#Xg+9Gi| )og)Ȕ:ٖnyGAku i ; Iɗ}#AAeiDEwk闋_ %A:؎ '9{ #aPWrYksٚ9pIL  ۠UM*ypKɘ :y3zɖ9ii3?I&iשڹKr9x3X ə! #t&<y;Z*u#ATZ}#kljoZ\v횭%9? jZ;K}ޚvӰ:`0ZzqJ8D7sL>K:[aʫ(>Ӳ>\{`˳b;8+fkK׍ C4q])ʲ#Vh4JSʴ !MkxYe1As eAچz:t{U:|K2{ ?`I'纛{M[dx4ZPtXDpoI*«;^uKxX@w*w۹ ˻t{dj>AHb5 YO@;o"ۣrKa{#Hj gA(o+(|2٫۵-q;딃3,=d|ݻNKR ksQ5&+ E,`7k!<7L.AH}{~U,lkEE ƸtVNngKWK q0)&}^-z<|Zv uOU*J}/ l{4H'B`ɘfɓl+ɻlhTA{ɇ?P1SLFQ!&]\ˣm<B($|`|:﹍ =|~™f ~Pf ]}e&< Zv  &V W;n<_Dlͨo*?i?ѣ@T`piKÄ(] >D( wL:|Q78]3ifP` P,{-btL MsX}jft\ Tg=H' H*gd+Fh^UlO@QHŊ)-.oFb;sOyKS5R}?;~3Dڇ}Λ{UG=̀ٻcESBLS{ =dU|e@r}Eج==1=Ta |c=47wO7O)m[S ɭ|zsA y}:0Q5 xuDiߺ{h QiMA-!ڥ׍ChSE  ,axMmg 4_.σ1WzoM)~povϽ3UZ~O P 5ݜ fuE+_QDnA9SjRNW \-G$@ YS`K93OyF$#HPCTFHc`[8Ԧ )QE :lFg!mdZp{jݐtђf-dqVОxO*˸{QHX/Ř5jNꏈz]0J,sHݑ5ָqH } bsW$|۷П7&dͥ=se`$h{ʾE9;SP + a쿺lp?6nACM!;8-83z ddLtNEl$`8 |ˁ2ADTJTƛ!0iBLCRTEMh5QMB !1l q 5^7L֥[@T@yTU/'-HcvY8Xt]Jp"ן;MieW[BZ͉T0(T[u6zml58 LEnWR2V늓MJ8ub<+=A.e9eÊ?^QbgMzjj:k%{ ; {mvjtذ1%U{oNo 7pĭ9ל\3$}[ -` (C?.itHNV熎VLf ݦ:zĞr S ;+89Qm:f/צF)XH&Y Ok~XU+9OiVu5DT.0ON 1q1>; ã$v[,Swg%f 7cUb s%޽BQ vjR-vE**tџ@ÞlGK@򗔑2aPRX=۩a{B{=D)0I3,TqLL k"[` XGg%g4I9Ԉo p9I0|y]Z3 TyIu3䴄i&(8R{f"Y2|{Hx$>b& ė"4ʍLd/ZV73FH L0)(KhGKTo%`ɏ(T}D)=GU; J)JtȎȐ2^Dd c<8 uXXf&1ʆ#:-JX@ HW,rM.Bu!թ AE$۳\7U"V!hO@݀^:$0^ܵlCkۃJNsp1ln"'$T?CaHs~xASo17z7Qo+/({0>c&D uX %du<3zRI(Jщv} (ХX‚ 1OL}3tY(7k>8$HYK9=;9>[AB'@ 8 \*-p?=袢)xCBG+BL9UD|{xDU8 @KDP EQ dD +>xC>88»<􉱛 B8L;+8 ÌmB0 5  B\F,;/І2fWc89j@B=n$88^GP̉@ "UEG̹xÃ3tǘCF0(i$vlGc\@å4IPdl PÆ2rAg,B?H4\ ,tGr KK*A:JIo; X<;$EZB2 K\ɇ@ >i,G}6@c`L|L Ơx)Z:JjIpM=(E8x ADLP :AҬ4—d >Atʜ 7mPNlNV L 0N3!! ͈@Oܜ|L<"~ώ?mڰ 0P>Et@U=a #8a,8=O9ôl̈r Pь Nќx)̄ѹe?8$>4 ߄\ \!aJЌxRJJ0Z=X/>=$ ;9Ы, CSꑀSp$0#)LX 9Bu-%ⵅ ! )}ʌcԐ #XS6e 7P-4 EQO}O#]l[TUO;PS⡩BLIP=ML- S@hUxY֚JX'q* Sb4<ųQGӱTxP #ԍ\LLD\`q:4@%SWZ 9-,"W"Վ89?%%X;lYY?/E 8RxSXGUW:2!ګŀ#"9y ?? I\yX ۻY? lؕV.Q)!UiVhTRp4;5rD\SX γw õ2eQ!huZdԊnҌRRGS@q`=]]\\]2=[%Yi\FAF⋈*dfv[\NENe!ef2gJpw-E3֝ 9iema^쓎0s c|"f n;ca= g(7@gkdS/fبjNjUF-8eePHècVgp֌"ehffiXZhf}F^r| e\h~k`{i(dT6X^[F=dƎfAg~uflu~s=cS8kXE8.hnkZ܎ 6|EVmFf˅4Z㟏!kL6]vP-mKmmSm󮕓ͨ9 ŮnolF zznvhi@0`^o>p}m^f8lm6 Ow "[ũKp7j m Wg3a pm_nnnw ަ?!q!=&/ 04 q)(sw7Twr1r )'& >?kH s0s"oخVi&osH62w+@r7tGqG Y3 $FqOpWsPsQgtvk6hlH?uX\Iu8s:&F7uvfmtflHVn?+VGugsңV6wÖv^vn0N- CGHX r?{tw_7Yr@r[busIOxvosxWw'Wxhgonں"]?xxy:b^av?tph qztyY/zB? *P2!#'DzAe<{ZoMӨS^ͺװc˞M۸I696>V'P-K`?TL1 6LËOӫ_Ͼ˟O<@'^|d4r!ق=>0A42΅fv ($h(,!Ȁ .b#O YH Tcc>qpL6PF)唸-Ph 鸣?`O?Z ! lv6N dfH@8 8C矀be$x$(QviO$EPVjKRh;p)'Pz騘c vg4lv7*+I4e`وZ(@ݱ 6C H|2>mF`R dۖ,SKnFY?PkC ́Ͻ,7!' W0T1u6lq]S,X qP#TO'LR2o,u׎h'k#A7E`HH!S4$}Q/2OSTu_Ԏ6--E{L)$ FG-uW]{_DvI==5Y4IF?Htu۽w\,Uׇԅ= M6ΝM!8ނEmT $P #KDSѲmGͶ(y֞h3νR׷>I5R-֔YE(0;) N+.A{T {+Z.zw#0n4D}/d_&)20$p糦ΈIXL0009*ڃhL-..$c4 i”Ѝ2Web g?l\z3pC$AH qT6~P)VTaUʩ!Q46@qdh9ĕ%%h8&R;,d"}v3 J 'ZwKa` %1rlpfʉh\RFVT%3^-!umDKR:SJsHp )'}AS%Y 55i}%9Uծ aĺ֑d + 4NkHױƲ݉`Xs\ؾN,.ٍl|ke'Zւ$he۲ֱ^+݂ym=+ߖbHr[:Vg;7k5l[[\V[uy ;/MbDXZc/_n-ڕnz5#DZ|iUn'u0ew@=L`Z8!8̒M1t0gL8αw@ݢ a+ H@=Xβ畿&[0NKI0^#5@M^ˡp ]6YdLt;K@{BZe?MOy`MaW;&'f?6~ukk8ӴYe3:02wYeے EU9YwoM3m5w|noW tLoq 7%AHipd2a ^ @P^-ԅ>utǤr|h&unMP6Щ[u%xĉ KNuxgɯ3zR4H gWbyc1'|xdS!7S(xk$:Ump Ѿ9z7?؛+ 8;+_|إuo'*zg}GG |l'{W#=06|8nC-kww A{4Ձ }xQܦs (}GJ׀Hmg{7~u;:H뇄8V{zTVQ?P'(zGe+A7QHDžMyP0802v`4q/D$W3:KuQ7#GF(ws^ x8 癖Yaҙω靱yrB)ms 1Oٞu) `ҙO/I[uu|}) xiڹ@7)mxS%s(aA20@͹ʢ-QX$AJCX;iz&Z{IJYL@CUD \١]ʥ$zSJgx  )OHsz*ԉR]e&9ezyspjY&9]uH ڣ*#AT%Ήɺת&WUq V`պZkBG)*HBzXu`T[uƚ*cY5v3B ZJYj8q*2E zʮ1کiSSht 3%k*ƇZ(13[!re瘴h Q+Kvhcn#K8k*(ax?5D5#V`gB LS¼z7œzF2zl[1bxh/j9m,-s D*aQOv`N.aj/kqHiai aqJt)>.Qz׍C,QL<fpKp ~Ş@s>uhKo+薪7L1 KlQJ`O>/?%6O5m<pSt+ 0G_:Ȱ Nٔ-=#xdJMn: T$ %wjؙ p} dfFQ-^+Nd|~%!A_bo$!5Oπ _%BPz.{ oO_.0dm*_O0=.o>u_ïԌyʟ5떽 .Lq1 C5tG ;$Y2H)SdhО TȒM6qӣN0e*D&&P{z1"ύ@ޔ:թUj'ABUB;مeA@aa;T3\HJ{ʪEAaU)I}wbd 6^\3IΝ&]*tqhџUDE/}H"켭޽gƈ~=q+yUP-m;Uiy޾yAxgMqGb6T][J= ݟ rIm4P,먠ɤ).Ï@F >4B?KxK(  E q,lke@ f ! Gy|(8slrG%qڰÚniPE\D`9NI.Dq 96!I+c d/:Ӥ|s?{uQH#tRJ+RKBQdSPmSH TTSUuR6`UT`CUkV\?vi+W`vXb'54eFA;5K#9mgS՚^CHE uȠ5хfjgR N!L誑Y\%Eq=opg*f1Uc-ᒯ#hp p/]ey aZU?̦[5(FhS! ήKʅC=r^QZ%\P5ͼmܟ&HRz}?idi|M8cآھw1`\WB:إK˔ ר~s@Ҕ2@H܋OB\;&-ICҹH@!,Hݮ)p& I G Ӕ6G$p]x5&C/):'%r\v㰅L*'\H9\D VI2M! 'Edj8Ә.C Efa )ф($XWa!e฾I$ Ur3`tb(&]Pw$Rj$^)N[22B6R22)`)eJ}D;$h(r2!kc2Ji$m̟$R/oEf.O͚Q (3$;y 5vMlf [> $oh0E"Kn%-œ?ޢ< AI ^&9)Qf @HBhƤhCC@9ސ!?a g©G§!@8|5KDGҡfGK4TʩV`ABH5^Mol jU +x+U*ifDbLbEza$kY⦩@]L"w7-öi͉ ޙ~B!P!$CH1{6U)u=b{oHsU8qlx'P;gISTs%q\1REk^<ȄbZS)K^ldHG͐q߆]f 8͓h&)ʑ ҲgEKM/sN'Q?Msõx ˱ 'AK$2Kt 4"L@ |&È([P2!bx2A&10 >B.A2 8%lñpBԑ9)ĈöBC=Û؝@@ ۪4LC --a݃~ġJD` z8  Ѐ!ŧcnS][<{@@` 5 zj T `{-e:>=9IpFcŔ:3&]<C6u1r$G8k9/hŞ8?DSlD;<ˉEԈF,7HYsuHd, x[ŞȽZI#YG7^I 2CA>$d2!X½ x ȎT4{6{6ӉFT =. X$ʼ ʥΪ,#1ƈLƮ| ID#S"܉׸˩P|>B$ Dv)G(9ɹL yBh dɮG˼G d̼|D4⳩D p͇"z:lE7| 8HoM(/`R?-GIH}$sNXlR! +`ȵ# l(aH%%]@ @?(/Wy 1<QO\St %D4 ZJHHTXUR-UEFŢ sO{LKLBL ?` b-Vc=BRSdDNdE^dF\ I۳ODߘ\t*_͏E: 46F'K<d1ne:Wq j9feWnJeO,X^%*^e^_1x.L[-ƈL>MmC揀Y}ʈTVe'effeLȻN*2wڦ$Dge]H^go] 8m\8x~M}fvN /`rc]苶g`n-If.i}g(,V)p~߃Ftމ>ȡFiengG剑CVxQUmV\Q"AvgH8^ۅ0kvhzna ʮVgv湆 2j3Nki~kYKc mlN셶꽈CyΣllaldž.SKo\IlFㄶfNj6恸l.jvl Vo(m錖6V YlNfȦξn˓aP,Nmmm֖0\b^܄_kNi Z0wjonoppvWVpn>j4]d <1_ppޤwrlVuW!i3h0WU/wtw\WeOV@3KKLFot{_xxxyy/y?yOy?y3`+S yyyyyy!,<TB? H*\ȰÇ#JHŋSը*{CIɓ(S\ɲ˗0kq&ɳϟ@ J(F)]j$PJJիFU}lg׍;Kٳh]kԮ۷kۥK@{u˷߿ LÈ+^̸q^p#rLfꀴy$>nMӨS^ͺװc˞M|EV'P-K`?TL1 6LËOӫ_Ͼw|Ͽxd4\g ق=>0A42΅fv ($h()b},(6Ԁv LA>=O?sX%L6I-Aiᘣ?`O?Z !h4N p&R (L|y=p!'d8)= A"*'팓馜) vc4lf ߄j4-ȴ-4髼^55 =Dg,,jji2|G#T9Kb+YL-5TfB +,&0>ބ:l'\QP1\DA%P1Ps\ձO#Tɔ*uȈ̓03c];ͣˑbHH S4$}Q,DKSTJWc}Q;f>tE_hpPg4HHC*"uJ{}Qw%sC*#F5Ydl&I.!٣B"M}zwU(|ԅ$n6Ȅ}MrgHNZAѦTjO.?IH O9*`A91 )R*d? hL)/ʣ`$4 \y f _Ip*RjųvE4>{5Yke< !U4:]fݑ}X.9r)~^r}տ~z76߷.}dj}Ozo/zf8n7sew|gw݇ėGo f|~|hv*AQgl%z vJqs hx3{''sԂCgdy*A4wmw>QVUnH(h;8J"b8 H`Q9;CNs~R~H`(ahFyfGS 9rFXAG]hY0HQz8Y7ȀsdX8X|6}|X/[؁{(u6Lj7(hXCMt$؊l878=1:3}.XM7TOteO~X}Cx8tXwٖw`X{78tEz$W)F igG))ؐJ(2! ɒ;ɓ.Y;~mu6JDdLٔNPR9TY#7 @ V&dJIHhli?@42Wٓ1i 4P8]"?I+zI-w9 YtzI:ՈqgC!!?yvy9yji59$ iin@֖EP 8iI#S5MaQuiIIhPiS# F T9ikuXsIiyyE)0HSpfYYǝ$Iq"d)*yDyIt$v{#zI83)$;C8ATDD_XZʤiruHU%SI9I9ieEJڣ1PN5*|eDZZy^oxqVZH[.|ڥ1s $@[UIڦOZʩA* Q Z&:et*幘1F*T]T!a?ڪN$*xG'WI8Sq V`!*`6z]*_uP*z (:G(a)8ګQZj1@J}*5䮽6}DESڨ ˨ Ч(ArWH3 s "&aC?@Qȫ04K7v:yGC]SjfF{p jmtND{ {@  {J=Gathj:47ģ*1JׯM;e9F_Ykk˶ t4cC);73fM=(*}+Ǵ-Z R,k@l7#7}1 fpU=ۋ5YrTx&Yu?\;pd(fLNa91:n1.@<+=w 'ӗܰ\lrDWSj T'WE4, 8yWMٞ;PaҮW/ )ToQky9imтr@7M0@) 6N]+S٠۩kk-1O84ҜW=Фj5)~a\@ݵJ܏'$|KMҟф= <3;<}\)gJea %KMg-' *$nLԜڂN.Q7Zg2۵^ X+a@U+k-f_`߈‚)`K.!pi凧-Tt+ۋ~%|џ,14oiN+gqNc'/0d7kȿ*౎:H }L,~>mrΆ)>3 Y}*u.^ @rϮ:UȞgK1o8cu9uŨfCJ] p\_7xw͉ߩ+ɹVĉ;Cij!jj?4ȟ!O(M^ƫ ?XG7=x>͒5;4Y*q$XA%dC ڻI5 Lj E$YRcG~HTݭ^%Quk&) GR3Og9 [_$!hSPN= PR5v %TIgyb/ ʱ5Cw̫Q闂5z l5xb%bdȑeܹ!$vK[J5g*mװ=eXR̳i1A?8!qqtУ&ztK2.TlXX܅_~sB$м{a<iBj&Į+K?L0z9{OB{Џ^/L+Գ*(F"dBbQ"J[N 4NEp$|lJI9,6 JC 4:{Ê?xgHH$%I 8sN:N<O $NdsPBlGP9,tQFuUbqRG3tSN˴tTRK5T; '6#.h5t4Y ^3sLMzK)TaKn3Y2C[y"Hip3/~@e$qfb.;04a"dOlMElQ^JR[ ^GkKW]yyBW7$ hI]] FM!¦63v8M(Ll%:ƟJs+3[ݜux$(Aj菝2O L) Pz)^r INշҔk#{{_5( '^yW rndFU:ocX6թ$My (&k\Ug3$EaVx Yr}#+yKڮ]L'L7 |whҌrj3;?z [~(D'! BÎhd=)#ȃHQkV=wpb1su U\Hi\pe킚Jpl#f| CN/ {, +N# dxWm/TadŻf%HxYI@b%D&9 nV^LP+, 9*lvl QZ |t&2$F`H(: cV9[p\T RIN3$FIi Wa#U fxFGKk^xĤ.DFcSZ#ujiqdʳ "@+C3_dfΏh;Q8餇PyNԋ9'޶6QD㰂B!/ ?2h3^tA}5m?3ۼn}chKRE#`񇵒%QˌC%:G@= 1_mʑFBzҨ*2JO?JA_3m"2%MQW!5HMT|dHaOzkgosYbIKm۱ݐ)e~ {&iIew{݂ܢO|Hd9WMKo~1 `pEH;bU4+tX)AF%@<9}} e\X(whkGR&w*$dHf e2;0rr[r& )_u8=_Qe)kG9,\ z- (@pbZf EVl&s5K\S:9㩈FiB3Pcz, s@\CZ~LT;'́t@A ][!d~#T>LҎTP1pnlO'۴aHvg>U2Gޔ D gwrhHQe|"EčՓWtČ_7pDZ`iSgsͥxRLI;LcK%n(,>N\<'20X&(6 jYG+#d`>z2P7Rk:}Q-(.oHy^cUGה%$j*8}GI*OObyV}_믓zq| &Iujp9}J6> >42<9Cb0>f؀z1 <=8S@mH5 Aix!KP7+;(%kc <D=0AA܉@ ,;czKsBK@T#!7#$*~ϛIzѹ"B.B4 9@K&ɓ,y}IK,4=sqnvH荞>#1M3JhJ<, :«xGfy%X%i*E.j薆 tn?^hxjH^i~у&~~If1@NhkkNj@=Ǚj&f膮띎^L$ lᔎk.gLf{vlgy`.ͥU&i.vmv()LؠǖfN #߶ wV1mF̳쓮jj.~][`&kh> n/tͼ`) n> 6V3->ͦhͼjn^nT&pJkl"k~kfF &[W>pin ,IkYv:C{6"/;ΣBZ7>k$Wr nʮ 򁨟p*W 0#=2*4g+6A\ŶWg7%G n \0$?2?H&' H3@Z.GrBstJbɹ=_ߙJ@PI's-p,Ogu+X8]lgvo1FXfBuHJ+ł738@Z XuXvQ_w} pwxx/x?xOx_xoxg 3(C3xxxxxxxy!,;TC? H*\Р=U"װŋ3jȱǏ CIɓ(SԨjRqj͛8sɳϟ@zlt˞ѣnMʴӧPJJefկ`ÊKvک]m;pʝKF{u˷߿jKWÈ+^̸ǐ#KLefP˙$>nMӨS棍p[m|T˞M۸sͻV'P[`~pc6l ËO&˟OϿr@80 @- s 4I#dv ~^G(,0(4h#,E] ㋂LA>AO?sPWTViXf喢M##=hѣ!(I8p)q(&E@8 8c=p"'hG硈&NvhOE_'팓馜B颠*hKvرB6ӄ٩ߌeO#܂L;H#* (@"ݲ+Y g|4Lf;9GVQܦtfB kc,& ,01!N'r!PAMQ\17pX3qP`=̓/j@0|òqy׎h*k#A?G`HH4$}ۢ\))sGhCs/]kQ2}` I"4Z_4G=tC3#D@Ql&I.!٣B"U] 7J6ac6ȘLwE6~zvAӦnO.?IH O9*`A9A/=I4MA94[X}rg7U5>NHv? -}K]QB 0+[R?4M?~\0Q$pXRQeiӎ9#{~Fх8Gm ohj`~H3ZRZĂ $F-g8*,з7P0j / Dɕ?wS !qE['~wO6*QtD6ϻC>jg]o8 ~wkg8{$w HPwq׀ (Y87BJw4 3Ox~gt)(m>AA~"wo}؂䶂'XB1IQk(?qȗ|V44h8 *Hp>@q%kc{a+)[B;)M`Ad,$?U%r?{k6zJ[?&PO+B̫<۳8I"3 A:#~d  p-|ȈȊ,dvŋs kKɬPAA`QvEp2?S k#1" VC@,ǠQu<P|;++{M9}?Ԍ-c2=؋mOqJ*t jҎ+pX&g58gZBsCk/m!C-_?VgIc <,)si'Pm=@-S{2{Zo`F}ۻݽ MX2= p#-\:кDٙ~ギk'V0LрAR3D>؇J=Dr~~QN"]ژH`@;ލZ]<}ñ,d?+ QeJŞjn> ] ^L׎I1o:ާNqnRXPH`f+*/<=W:;A0O{u QK / (UeLu99Ng@`=iI߾Q{e/ Hjٌ a|H^]țiV?z`tflL݁j B&GZ 3Yֽ ?Z'Uz&CqX X ZWfx`sϯ龞so;ʡoO>ikO{ $XA C 6Xb:5ndqa ni!C4dIˇ.aL(́6 A'N?:dF7r%B^Jc \ҘXgkTE_&,#؈aBTv!R ̭i!2"M܉/j yLhAª&#^ EéV[ʕw됕[//-. V3:to'^*gμ'ۚO^zsd`[@wO"xկg?nmK>눽|_, b*- @Aރ I"4&,H :cU  \/E=DHBa*!iJs*jǏ腰 $Ha$ E!) e< ʨq*-%6qIDMl% 7N 9 R/.-[ksB;*$F<$%8Ӻ%@&jΞ*ҁITPWu@Wa:D<ܪUB8LqV]ta[* ij٣-?;$PaPp`$8J6I ͊ւbi}W*mԍ PMsW5rIN2e#]w"dʃW %\4UkBHb$&y#"7@c%ű+c"q 7Kn+?UlE ש[fF!YĠIi7шS(wq툀\k H]{Xe!ͶL^9qz널Vg-Ji f<[zjVs %"[酌\ uNp<9z0o!$X}Z%@:w«Dc3\a?BTwr]фiGśc P~7*j%Jɤ5J٩oe[Df .C$|/lE7:Y)l_@)_b6 qװq`LܝkX-O~{eHѢ[M2i9D2'`A>`1|VKwVa6hqNwD) r`b S=b=IӸFC2$@GJG,F`d52PUzr*bB7*6r#HR.YGy0P+󅬈'3Jܑi Ek\ vÂjR/vy 3*e HyDdf)Y^Y&V̧]/ H}erle9SJ$Y/ųh crK;46/#8)T_Cqa*He@%0"H" 0`Htm§/gHՒT0]C(XhE,4CL$DqTGqGH`NZnaByZ=jR$T3I>$H՟b 4 ig](+cVt FU@6f4HZ#1R($%FX(a{?:Kf _AZUֶ*g;I9Vb#Ĥzkjحʅ_E ݲHwѲSm؃ crP½vmftc ( 6` ⴩lY͒T ҔhIqZZ6D\Ya Q!QâKdHQT:˝ 6b* ^l #@2Rclԥ>/aث!rO;3W5."?Ez&E=% Ne4W=OI'Bt@敟[!DF-j%I! modf7V jE˼,x4i#wI gk`(9f<_B̎7ٴ/#](+$K؜G7Y\nq iqmD3쉼1nɛW@)}FCQ=`MW1j yͣbIv}N4"ۃ}k c؄gRY(!=8-]z;Dy3ӤPF=NB.ݩCv&>PlmD^>_4 8QV."xY#yyۻ_p{Fyf uԜJsv;+Z9c)*={dX:{􂉦4K;Ȕ `w?< 4J y@ = ⢤@=1/$B?竈<<'|Bĉc)_Im&L3 +dB0D)c* :J B;;8TCB< q>*2CqIțCٓ1/A- /ɈJ,)?0D&F|°Êv8@ĒBTD4pŒ+"@@BƾCmHo T( X@FQZ: NFANFi  V|>hJ@ Y[(4)|)Ad5m 2DP++ms?\AvT.(!Hc$5qmpp7,u I D4}lG$m t[@ 2#I(,㼢;{f 8E\s &¦$ 9pcKdħ м 1HK\C!@Şʫ%:97T [8 Lp :6{3M4tt̠|,Eâ͜OIIF<܈q2LS:tǘM2βKM܃`(MN :bd 8qNhON` LP@ˈ8;jMߜO4P2Ok& ύ>4ELNAERP͸@QE| MR9*CeQ(M웿D/= 0ԿX2R= u)u }4S2L :BҼ9S+;{H*&Jzmp*0JT {UGMэ5n97u" 6x1t̿<mUUU]< ֡SӠN^=UAڐ 06kVj,T`VnVoVp }CTYf=WPT)Z bUN1|q8%TV{ MAx,P=I xUֈX|׃Պt5h1^X\VfPuWFCUPS,YEXPXՄ]LVyX؀NYl̏Ss ٗ%Z 5ؠ bGEmZYHȟZ4m}#XhXX{Xl۳M bxoqۻ5Y[K] !ؼ%Њ-\EW[m#H׵YڭP-KܵhMm@vuYؕݽZE lU[ӝE [ ,H7^ }%Խ4 (ǽ]^S\=*H۞uߘM߸QտXg_]߯PJ* UIun_EŘ`x^]``-^IU N߸ya4=aa_#fR5b)f]?`]ˉU$] 1 ZJ[8(bb9c]a& Bz*̊c:*:_ z i`8.b"vdc1r@?d(c]`Y]>,EfdPNb*cd]Byr!xXOdWnBVaBf=">c,V]e_B;[nYfNfR^fA>mpAcFIX&^~s fs>l^go+.g0Rcfm\{gh =wg^e`Ź_Ũ; ([@nhPV玎 )`M~f(i b 1hGag(5d gVfzhpZ30$y>jmi([egEgx:8kNk^knk~kkkk.kkll.l>lNl&!,;TC? H*\ȰÇ#JОFU٣ȱǏ CIɓ(S\ɲ%EUz7$U.sɳϟ@ -{Hڻ5ӧPJJUn+[8KٳhkԮ۷kۥK]˷߿ LÈ+ ^#KL˘x3I>|MӨS^ͺװc˞}6pͻ O %.P&3}p.ب2mËOӫ_Ͼ! qϿ!K#y0F,h+s$v ($h(| 3"2|(4h8HS޵?,T3dc>Y$pPF)6-P\vF=Q=ӏ?a8ti'H?9P1)hCLILݠ6h?@0Gȡir)܏h+d3M86`ȨkYa`O+% ?j=4Ru4 fmU`NR$zTR妫nTY?Pk/G s/MȨC'0uu0T;1QS 5,VCq@2$'U"rO+Բ˥"%%0Y4申K;w팖6t/Mzd*!،=OCOGVܒ+c{Ԏ63ٵ劭AL Q$ PouUdTI=EJ#SGKS$d CV_7wڕ=ML{7FZ{ Or=$H 6H?娀'4~_̌QۃT-^R޵Ҥ8E-[;R6"nh[:⇧I.Dj$4=*@>=-ȋ47{ȚG8N#k hpYc.CRl7łXX|ZA@hΉ{]4e3YM|ؠʁ5SP tppCQ$bf6!P ]ЄĥȥHG!x;J$@:gI@# 2P BOc$8P.[!^Rd< #YI&Es,I(JͱQ|VB6rt,CZu_:K$m@RDC3BPfY!)'A` (sT u(VYΖQC( H ^qG?bwIȣ,rE8|ť (BQ `ox*/TP F ,U {{ ~DzҤ'}JW?2VJL+m1tU3c\oGr}5*eUZX%Ҡd ]ҽZlX;;΅bkb[X. |5l4USa=[Xʚ-UDf.9 %t2V,hj1^_epݺvk]ZuRW(uK4lira6ޝd' hy﷑ |w;H|!2_BD.y^Il#p_n4YکuW{;'L [ΰ7'mqGL(1  )g_WK*~Ȫ1pɮ-zu.Wb0%OKB}!k_<`b* }qKwgcnt[n :w.2iJGe>X"z6^td3AƵ*gPil +9z*s jm-{RHJH)n징L\QL.U۩gd#)@( DkkM=Mc ([:5~ D5n~ 6P Av`䉻Uɵ&+\8 o $@RewxGpqECCr>[ /@f\;縯.ϧh!(Ʃ"s0#Sʓkw4ȶ r:ؕ'R'Hlgu{ +s vOMn*0_,t=[(djm$??_'=1;Av"6(Yx?Tqn98H },ꇈ%4Ck},QtHn9IXi#A?X1-@X89x&NT4@)h7y"Qvu2 nXG9UiEĂuȐ RyDiuii NÈ㦍LDS#$7cy٘<۠ 4ad9fyT9kv ֒@Dmu'hx9Y%;لd wQ)#iIBYCLi6`$~sYImx" HPT9iII$9IXyiy&3XF(Yi$\YyCAyjJ(9z P=[9)Ys8wd!j#*%: y,*4ZI&&v )jFZx~-C6 8x)WUJVب'5Os ?VZd:f:a>*)o*CJZTj-|#C$*7zJJ$<6DŽycwAoKSƟE*q 5҇ujz*qT{:QZ+Zڞ]szj6 ꙩjsڮ~4 bJZúșʝ)D Qw:3z7xqA)Qq V  *Hw %AWC4j* xZ;;@/ + [c9Zt|')Q=נ=+1 1Kw:5[k!Ke[,E2RXAaNDۻ밧ѫ873Ἓ;ʺp;QA l8^Tk-⛳{ Un;Q+K狾K_ ɒΘ+6 {Km;K-.HTh q*“, [ l8ij2f^+\K(Ǿ"|wk/FczĕIK(a5:`Pzo;Jc|+5+N|Qǵ&LƂ;S)h̎#1h5ȻyvuD$䷃0~|p]W^{jz˧TgIQp:붽mz/rOeoɡXc3sr)};,T%ceU/4 tM>ճP$3;]AMR!N(fkYH$e'?msܻ|1q8>eKp IQO~PgCyC-hsl?-0:sȘS Ia`]IDw K)?Kp\f,fX ,^[_'{yZI?:gUt{/X /X]?O/M ~}v1CB`$XA QCE8?{ݚXQ`G8 GOdqeK_Ƥp2dyO>d8hУrIR-m=mB6HI ՊkW`y~AB. 6fQRj@J^-}nHJIX_1E,qFv8#$ $՛PdL"imuꭰN}  Dv ty^k6 Wn̓#=P?S(g]K.b:Оro)*@.>,0>am< <N $ȏ QDǂ(Ŋp†&c#ދC񢌂kEeRDԴLJL- <TK:D?4G$뤉ƚꭣ#O> ɞ?2MΡ(LZK3nʠHElRPEh ATk՚KJRmh?ʑ@R{\dt[J5c˛5̚0Jzd[JNȐJZU%{u]xw^z{Kd_m_{#`Vx{iU40`+b%2cCgU);E8$DBfx>83Ou {j0&OK#@tJ $qXYf+yOv1eJ t燦U)I5,YK\ˎv42v|UTqo\JR!HNlB\3fDw3sZ+m MG<0<2 vْЮ)n${z]y?TVɡUபJ"􇳓DZY|vq 0;_m7 ECO~Ms#^%[8g*_|%0Iim[\wdG\eK4(D}x_H;"U:P6' Eg QM"2?0N?S׽sF(D1j(K"׾d/Q#l`jg\ݤ7G#Flj[YwHԤ)w{U6N@BkG2t!CC X X,8S )$>]x@3Qq.H|# Q4@7(.S191-1CX^ѕ1@lJ[3aȧ^h7`➉}7ȵKIr4IY*!u"C4ӟٛQ, m(}Lh&Za_zIHL/z*?)dĠ)-O:4 i60v4u VQR">1q+ГF\d'~: yBcv,QFk\ҴO~`+i +zOScW $C6TŢ$|*?Ip UMP\T@(և;emY4 %-`k~+oUMZ^]d=KfĚ jH"4h#6`.ǒ's+ߔ5lge(/4ϗ}8%R T_QQS>7fl('սbfJ ea {,ţO.l\K.0^J&7.8a|=*h [h%6ui>EdY6FVz?(VhB:4bT&V'[`Ba~u3IYG/d#Ffg=) :h6G[8qI7yB?,ͺ-uV8&8u[Z5?dn-99IpoY]fʎ60?qѮmopSccc,;rͲe]!!F2WW6c۸a|&̻BCP Ţ#[0aU D~Q [Uz. LA\A3 d8- x"7I"ŋ wS5x#c;=볛@,UJBI> %\B{9Kmx2{)+ +C(?HBdI"E> @; -C)B9ؚ234L xΊBy@DC a C7A "ʔ !D3LhD:,C;t 3+{s\ȍ}AU,S/$[POt̟9lM|NN @ =9 ?%%X;?҈QuP剋=,ʔ0 +3IyUVk=/,M.Ƌ43ۆ4T HTҖ@X#\lRECO OVR5הT ЃX27R<.EWP5ST;QEdnX>K`Ā|-ה ICVYԓǞ4h Vr֞1sWo-YE kٗm"ٱÒqYmZX%[KXsZXIp7EZ[BڏTۨL Rڵ[d[}Qɚ!ÅZ XŕW0̿ڿM,z[pTܻXgq28!Vm[Y纃J,]E\g.C Qk'bAFv` 顿Pɋ1&( c8VuPV检^0a-*1cv\mfa}]Etde~bhg(BMF h4fg*l17Dߑ-=hHvh_niGdO%\b ^iing+]6 i2fifwRكU(:jNiia^V֧cD6ITnFv燶$C>K7bkij~K kHF|VjN^>ZcؐK>k>l~iN jsFlFv2(CcmFk&v<(aX*vh.m6 G1^jn.[mX":.滫ofܦ 6+,nl>mXl Fop^zﮞo)X@g'Ž&ﮰ6m%nюo' FoDdpqOmfu8 )pmn ? [qO_FQ/ˎ q!(!o"g dp1q&7?~kh(r>)gY >&t6O@[2M =fn6s^8PEFws?qPpf1'9tIUuXWOMn287{@T/s^udwu_YE".qtNvOuϭr&K/b_v ?v@-%a,YX0svl?^uvoooa{N_vOWwow'Ov7OnH{8w{'wmoἦqƉ('Q`m?_ _a|gF3h!Cnzvvt'yWvH 30+0yGx{/{?{O{_{o{{{{V X{{{{||/!,;TC? H*\ȰÇ#JHŃT5ڨŏ CIɓ(S\ɲ˗UM5޸^Tɳϟ@ JF)]j$PJJիPUulg׌:Kٳh_kԮ۷kۥK.P{u˷߿ LÈ+^̸ǽKLefx˙$>nMӨS^ͺװc˞M[6mjSV'P[`~pc6l ËOӫ_ϾG'c>Ͽr@80 - s 4I#dv ($h(*'"},(4(b#Oh`?SPBӏ=`pL6PFi-"›Xfej鸣G`O?ZhGIz&r"k)T<9P1gZL Lݟ&R<@0IteO 3rȤ*E<ڒ?v4lf7zW#܂L;H#05 =H,, -كL#2B-`R【dۖSK.Y?Pk/D sk$E_h 3ZDGHC*@-Ty͒*_ԶSP->=4Y4IKH?H`~o'E=hJeH8أ 2iTSQa)ۓ?@R`CS `P@g}OSPNGXk=Xw>(o%៿ң8E-x6*/RkkG[pek 2.⇧I.Tj$4=* >=-Ƌ07{Tw3NlqMA i1s l b*Zi7as_ӬB?eGBg5aj(LAϹq`Gqr2MxDIMhLt?z4)`AHI LnsțT;P)a U Щ@.ܛ46@Bs!d%?$I.9DL0#@$#¼ɦ )"K+II̩|V B62t+?gXtyLgչΊ͙L\ AiEhJI  HjcY!)'A^'sT u(VWNQ3( H ^iG[bHnU4"vE4$"MHĚDFbk{Ӿ T |h(7P4JG֛T]QNjŁmN2PVUbw+Qn2Ml.u-k^;6XR W_X󇏅biXcldMٟTְ kgS6ڕu*XϕٹdJ+dj6vu` [6gmCۗxL)0j{ ,Q.i5ڎ5.ts]E>anO/m{.u^bmoe;wH~zFl׾]~KV݅Û)yScngzg6 !yQA7wcG~ 5RA5[IW3y'^;C| A4S$XuW=H}*T47} qqU'\)('Q4Oee}|47}{ nUh-|z*A> ugJvgXv(|*q|Tp6ӣa}ehAXw(YvA2!@w~;(y(!wmfwGǂxH%[6腺Vw胪X}dž}ELǃ؋̨#4:Hkpх!UxH؍،!ъf1tDhs7yX(BHԈ/(T791t$9}r 12Ɖאܘ#1(JL^ ! )i87X?bȎ 3Y8Wq{E7Q&AW(ft4Aw,YqT cH[zsN)r9Au:y2 Tt$hǏ0SxS4Mb9x(odHA`lc9YyKqPPa #Ěa`2YIp fd ù9t9vyBIRx.tPK؛) iXi496#H o!@ɉK= ?)(yVGyvs 8P YJ8dОQםY9#A;9ПjR94*W 1ْࠠ:A*ل!T'Rӡq)&ZD:(Iz z$1.:V0JUʜ5je Euꧣ!%:V1**s[ʥ$q Iii7O` Rj:27dJIDUEG9:Jvz9Dj*l9Oӧ9jzwQ737wubj>p$Qt@ :ɪjzEӚ?*骮e*jt J}⊔ 劭תdYګ:Ejx# Zi*x?Fg iR!V㺱a:&7Pz(`<ɲKZ;+#t{:?g4@ɛ ˰;L:^c}"E0=@0z۰gڱ^uȜ̴H*940C945DR ! QO~`bB0=Py҇:+9!h-l<+:xˍ |V|A|ux@D'-ػ<S'ULO9}; ΜԍS ޴*!{{mb 聮J ]m)=D,ϳI.~gѳLm\3=湽îdpװ9g-wxkn~tEEkJxqN K+u /$h N׎rZN[nU\o|T.ڡe~Mk@=94Wn}RyMF>` ,iMeSʹ_'1}?鐘 o`s JX-H:.tVoiT^l_:*_+ +4GKPbv{sU~plsONJ~!_X X0ZUIgfP1^|7D˯O `% @Aߍ.pې~ F Zk]ϻe漟3 ]Bק?$XA%,h\ %NEj1a!EZPR{jǑ$Am^ړ dɗ7u2̹ӧ?=ȭ&Q&չHO:IJn!-mU@Zгh*#kKn]Q$zHL$|KpcǐJj7Jg,Yo拝'ZUqN ,'Aeэck}sOO v­1H('y6Ёhm\ӫk=xRX p/ 9%Lv| p+!Z( ~`O?%B{B+p(܋òPLC4TJF4lEҪp0B*RBڎDR?{n) H1Ɔdɟd[(@L0(0GVr@Hd5QM=ELh`O B*)$Q 3\KBdvN(Y ίq:BH,TULR6W {tO2yK/8XB~X DFGeDhWuI6@uPnS8h3Zk.U_і7V  ( R-v hJu:k^bdl$WT΍3Ă$[J?._۫syg{g:VzIgtezj:h6ik j{o9*V{mZ&{-Tz]x|3[~MZRc̞[üH5No- J/\@Ʌ 3̼0Us5nƹ\r[tВ⓹KQ^bBH̬0:Nq4[{-vWq4{بOn"ZťLj3/]Y~2 \4Z׽#c t_m$iT$j1&fD>Ǫ•GcC9S %UP8/~G ;`.#s Vp<gRKL;_bs<{J_B5n7@~"1Ah@Ѝ{A8;iZ R"WԤ(z JL3uIiSn[9*wEܢT0 a *QDYc&子_ !"V8?!HAQ yG! sFYHmE? xh(n톿uOK7N4B}ގn;eU;to$ZNF/) T#k*{4K/I;*m\ۢV M!me^!^rIiY;iPO/O>)5Zc&ÔBYN")#&~xk#hP б{ c;gcrs=RlNxx 480g@@98?O[/ ( 4A4p|<س"̋<AqA!G+° /U01y4NP4,6  @7Cl ,M6YG- 3y" gIH l`Y 1SMP#O-H1L m\0?XEN[h?? QU uКBW\,_2_ !!X+X%]X?(/ؚ^H UrMƻÆ2Չ4՝OQX=YU`#X2M4TZ +qRHnQط{\p(ڢeRT ֦X=;eعLKCp?4]\ԉ;UPWNpdYDEd?}8r\'{p][Yԉq(&\ռ%ću\3aXdܗ ^0i?mܱ]P#TA-7qh<\;݈4%]7[Vh%ǵۭ60XMoݐ= i[ŷ}x6{xO Xݐ% c(e^$U2ڛtc=͈9uuht}˕k$刕Hߍ`-`N`q|Eߐ`(8Y8ħcۛ:`U5e- . suՈfY6b L!U]Xm-d8k-͡b =. F^n7 /( Wk"vc$F`U2XQ޽ w=j_<^ Jȍcx-4 (dK>@d?xhN8dfm,y䉸wzS&TiX]p /_]Vei" Vh`d[fxSc(XKm>fn 5$O66m>Neɨ !C.,*ʙ6B53 nh~hh^H|gYcWsT.(+}} o}QqF2O~ifa9 giFgE܋0P0iN穦j3類 VUz*e6j^Niƪեi>5r#1_dڹkdb*5g&F<6f&֭ L2 l&V l^ Wٻ޺>j~N ЌРZBlf^ߞԥb~Ҷö2ÜPX#{&nnn&ি˨Ԟ y,n.lH[z[m.nnnF xJlmv^yfmmTnp9>mͪpo׆p&]kXރkjߋPY`Fmb o~PfqOvpP\r#,mwC[3G$or0rPV0(E'p<&OF[im?5_q%g>O:zJ*rDGtKpN =uLs&-OG aԸ;7uYR5I@et<vaTO XfH8ubgZu[t">HMyaXBo_`6v8N/a4+g7whwwwq u6GrkptxwEG^ DWhrpokR/ vmgw~w/y?伍&/xxxx4gz7N1;w wS8xgbE p wozzg^c#E\GkGW{zWzkg870:bz{yq Ũ1쩡G__AHaw?tO|pM 1F5{~xyS":0+H!}/~?~O~_~o~~~~ 8~~~/?!,;TC? HCUJHŋ3jȱǏ CIɓ(SrT58{zMR͛8sɳϟ@k6eQ{&]ʴӧPJSխvjz&կ`ÊKY[˶mvgʝK]˷: È+^̸ǐ#KL˘g|XEӥhV԰c˞M۸sͻ \@K l]M6f\Q/d‹O^<@_˟OϿL/( =>0A42Άv衇| 1(,0(4h8c(e] LA>EO?sCBܕXf\v`я@FTEvIdixyQgFDT3)3 '(r2 wz6裐g4 Q1*= 8~I FjꩨSl+d3M8I4bTH+f(@BݳcI H|2 5NvQFjK %Jdp,>l„:"VX CU1^uqO,MA\2(qX&ղO/̨%)'2X8sN?i/k)ZG`HHS5$}Q7]*Ghss/qkQ2} I"`bSvGvߜٌPRGQSЙ$"]v㎷ wc6 KRDj>{U=$H 6H?娀'Q5~_WMlS-:祙?Ʊ^4iHGDĀ -܆qUBh0;)4X ^+NB{T}Z.w#xNp05g?Tr v\l$Fd@"wɠX03_0@:2ڃj\5v."5 kC@a3w3Ҹm4'".p?8A+-\dBgI@ bFP4 CͱUc$@GPX DZ q#""')I+:hOTL@7*^ъXHso4LmƐ 4+Kq|\]/}R#rb2BLiZRZQJRpˈ I!> #FC6hC tpFIh@G8 |gF d(T]@Ҽ4\i# xIT^#\.J CGh>QMv<Ѕ^R.WI2Mԝ!ZøvYĭc+S)ZHd \b+ gM)bla5vֳNlXf'5-SDîmke;[vd,jS*IָڿduYrk[Z.rĺS+yvU-n;J@n"ռo|+֘?QMIb7vO?X g;۞[ΰ7{ GLĻ:N0~g,}chUx6o %Vq@Ƞ{*cȅBrqL.Ǻ_ՖC0oWZ2]6QƁ `ɫE?{LfCV5Ye tSA@`T+Oo fhjōW0կta#'uTJXVa bO1cd~&X͵wF h0 YY3(ntxuvN.ɝqX.Ṽ~ryP$tQ62ӗ4O ^G\U!Ҏ(5Sٲr'Rjw9XDߘ+ 9Bts${sn>C:,n\@}˦4zҕN4BTA)sFօ rfA.hO;s5#h X k೗gc:[[J,!O͎:*7zO !DI8yn'kUޡTQ'_w}j2T X0E">:둿u.z|ׯ.%)ж> ?/'{ď?ÿ'C.֎{;UE~#b{FDVFzazg~Gn9P`{qG(% Hoq7W3GXc,~ws10{28?Z0K@Q=0"#|_&88Ah\Q(OWqփWw煖gv+hlG}bXyeAw }[`(wqx~7re1#5؇Lj7PSpw |؆\wO`9'P:K86Q20x胎>CSnha ]HˆpxF23eIpHըyt~iq 0燵'莵ayȎGȏEǸ_hj8(Ш8X9.cّ "9t!$1qUHn ِ?*Q7 5& >َ4KȊ2EΨp#O (&14W;Yy[)]'d5.y2iMH6"@3ɆywIY9A~谎xIg iى{X#D 8XIY^Y9`=<)eIlhY&!Qp9AY#{8Ȍ)Лyɕ83j`)I静}Uiɞ i88qihӄQŸ @BhڠCSV:poz)JKڧ'a:jP`ɪ!oj*1^xqPEpʩdcXŹvf%=I\Q64\|( WqS\5[,O!Ǚ9A^  4|a$)uCA)hm+:fX1'|AgU8a2lD0ǔʒkc 9xs:#R9\9|<õ@A5B4̮Z{ws@_]/=KCΏ؜'&=qU0)[>! 3#'HK(I3<T\:w=Ea>'{ W=q t_m yaj:5<;)ʅ ӅLy־=grg˘9 l UA֘K\>8]:QձL-r*ڍ 2]\ lk?]>5)^ 6@Ԕܽڪ 6S~O#j2Pݜ-M=gB ?O3^]4P 8%Aנ,@.NUA=+ =-C|3;VhO 0&5Jԭm>l^h8FMTgq aDXmTfºe T M8a*wINZr_3݂hr>޸P~]$'+ݙ7E?(R: W.nN.޿7<.?ip2X\\>&>-=}oΝ7q' /!?ϝ-u/-+j< /21~QC/ +?OKAyɾgK_ۀkQLS 4g||SOiW)4`zobl@.gP/3P VH M`#q$S poP Xa.b5B^*BA cάaxa T<Uc-?XAƈm9i;54 A=/;o}h\ÂG $XA SCI`! (#DEL`" HhK.alX͆2WC'Ο Q Q_$FHiR Z*jב6яFn`CG㒜H?2X;`_9 8rx$b(k!VKc{7WvL  DJ?]ƫ;b)m,aݻx 7B~]R笣C=ȳ5V vqR#>kl-xYE&@2̦S;jϠk $Ba.–S ʄ/қDVdQƁ*p%6RqvI[KD RNH2B$&dI10/)W%dc*nL3L 16 "T買,SI4RN6;BӣӺHq!@MҙJA 5MFU( r$/L96唢\>}ESKWRUO 0<9vZ˞'7 ؕRFjE͏xXJ%hEuM6u* D#xօU =fE0{il5mQn[bF[S6I.C2M4p!'#RMFaь pv/:~ء[x-,^|}62^][2@>K}LNW>.?_~H梜=U3jD?1dA!Ȉ^{RI3Gų"0negWrW%h }.h\3- gz:J=$ijN]E^mǽ+jЋ>[\WP;t\ &/]PdLD=xonXy} c[쏤 vH}n@MJj{, ,y٠盳D:3I ?0,7! !<§\&IF6٣H~R1P<) XAVw4j&\Wïo*6q@X_;}aN0 VC1Mk&kzA=| PmRHai&ۀӲI&`jv}> "gJIFXtF!dj$dT"#@N'གྷ9KIL:&?d)ab?L E=n 811 0y1 qZjZ>7j^T9b'K(*j^QdjSu谝"UjmZiOGWZ֓ ) ~WRdMalcXFV},28ATlg=YN8miM[ZIHqemi;I8 [Vmo}ҹcp-H,|FM.Gr0nI"{*{t5 iݸaRŞ*j3 :.0z 2%qca[/ VE5Oiq<䫤7T70eSĹ 5* Q%vDE#xo2Jmcl8p!V:)1&rxaN4r=BMۂsbp -z)iIY˪kU&<`29A)ߩn’%'$m᭍@r.JYtwcpy6AG">oztCiMp._R$RaBf{5WcJBw5cwX#V9b@d?qr,4aU~ˊ2GَS8Pq 29  <{d`J6춙0$ ȱ{ ^(d c@< x4?P ;Aͫ?#0@ 2[@ړ@#,B$3A—HN*?"B$K$ ?137;Cd9w27Bx#Q;T\5 @KCA GC5L=؆1<8܌h3D@CD#CAm E\t7.LDUL]ƒ@a^Y#z\\XDCH<F5 ByE{ P1ц`L NNPN$Lg mQOh4O +Qe3iHmХ QR+RD$O&u 0EɑM8=&ͳ5RC!RS3TL1R H=C%#IڌTX+ 9GON2-SmPq\P5 VuU߈ԭ; {IUP^-;Ҁjk}UP]]% ֣r6PATdNTI v@QfqVl- YQք:GgOkUC5WE "_rfT@uޔ#׀-suX`%XV ~ـ֔IVeXX{Wz3PX8}؉XXW %;SX}m%xRMW-tY 9I?U]ٜږMY 0仁hUZگ8n,Z=-M5ې٨Ztuٷ HZP-u Ľ bU=Zٰ[MًKEIU[EY 'kܱ Q]ܪ\3ܮɍ%ˍuѡ 4Sō[UR9VEڝ\X\M-U\u ԥB -U ,N=\_]m g}n`x^] ^x:``.`>`N`^`n`~`.( ` ```aa.!,;TC? H*\ȰÇ#JgOUMȱǏ CIɓ(S\ɲ?Uz7$U.sɳϟ@ -{Hڻ5ӧPJJU2kEWÊKٳA5jǶ[ʝKnY{u˷߿ L~{] ǐ#KLfh˙$>nMӨS^ͺװIѶ6>sͻV'P[`~pc6l ËOӫ_Ͼ=xd|j/ݽϿr@80 - s 4I#dv ($h'"|(0(4Hc#Mh`?SPBӏ=pL6dpx nOViXf-鸣F`O?ZhGIzI8ئp)gX<9P1̉=p"'h硈&R<@0GdS'팓馜2)*D<ڒ?v4lf7JeO#܂L;H#믾05 =H,, S g|4Lf-`NR【$TR妫Y?Pk/G s/MȨC'puu0T;1QS 5VCq@2$'U"rO+;Vv!%%7oG޵#ZDjϽ(=`3F0 C_0'[EN[uK_Q;f>+G_h03WFHC*4mU͒*\P ݐ-L!Mf=*\)LMX(Qg4D= vPw7FKLB=$H 6H`?娀T'4~_\NS},T!&_=Z#t/n1EjmKywܢlKAG4 P@ss#‚HQrDm%G9xN;fVR:N )~f@Mf)b?"A*H*2 漗= x΋X>1=lDM娚)(6갇>c~ nf)()qKҶ@$%:#oP?Bц-T(A4pom,́p*=іsEh>/k)B.Ǭrri@i *JQv,-i9Ψ$4K[yH.ڈV@#B{$EulP6Mle`4!ЀraaP;#GJ҉VB{WD#($fȕF eD1Pi_@A $%Tnߛ#UJEi*92KYՒ VU$-IV*bZjβ5XW9&TBWհx j+ޙ` UVqb~ijSaX-+fAKZwhM{Y5e,\۱VHrv%WpI\enBP,5:m.tKJVߝnqZV(ؕHz+;-n/  ;IO׿Aqo|<ۦf 5VB 8?.0܊2$.&KL&V3ІiQMr:ܪh@L"HN& YA*[Xβek4Ȁ2+vqWh#C4uofYZqw[\g˞V) Zk^>l*h0DåU2:WPcpg-}$Z-4iIНֳ|#Bh俣TԿ4Kbvכ4ϥ|b{lٹ& $^@ڳoI_ kUkX,"tuC[ކP'e3AP#av[sq<zNF~X7 >˩vA⩛ͧ~xM7\(f83HvBTK|yQYKZ'9M| #X:ғ瀼Xn2FzӽO|'O (u"SՏuo}nzO.]ܺoDžԷl؁'A|cHfgsX^nMH08W 1%5aͦ~肏Ȅ&$V?S5뗆(؅؇艡7ksZqUYxȉ6Kb8@=uj؆x8|8n cl狝# }ih710@ww&ňZ:a`s d x%<LyXoe4Xufz|ٗ~9 |I T"DIdYi[ɕ)b3lIوȖmy!>Q_G @`Tٕx^PacsEi?hiIO#XI98DSi9)MiԹrO6p)繖 cA^ØN9Y(Vt?!A_)Z}AwOӚ44J*I/R+C8  %*I!*wtȝ aYi%q: /o9SJ"jمNs nYCJ1Z q :_Rzc*s*s4cA`jrZv*jY|t\&A` 8Y:3Jfzhk:cY@ZtJxZva.* Jrj'AA1IeZj:T.W45J PJF: "(Q5F[c@o ZjBtʫ'MsU1z蚮27A}CJ Sq 4 7jjH , IZ)4 uʯ:WޚD)dб۰ z6*B*AAD 1dB2ˬZJ۬Q;>E+G{  <USj4XBZK5뺴 IAϗj2p4=;IM;{}3f9X۵`K*P*A<@Tꊄ;_˹t=+B\\V-L1ģ9|x#@HV)?\AlSw,a!<&QkeS=Y+SQ1s/ͳb ^㞇,?p+3ɧ(+;YLL+AA 'N7LsQcY\6`wp-ҬeU`OӜeѣ $|TeܷsTd,+Q0zLOqK<@CC J5yc\Aw= ,2{QP`;Q@?M?CeZQ=c4.sy ,ߺhV͓|P{kVb)A@pQT(97yRGDԟų,Ad DP tܐ .q㌼Kr9Q&N$bKbK LvH3Um|-ud{w>:zC<{c27FZT^H.)խ%^Eq.ډ+SMh8o^|07w~D^u+.4PO)%D2~FCջMSs?,>~B1}S>V,4:ǃ3ns(߇rQcANNpiѓPuؠAQ?N]>byM"+θ;Xr$)p۽8ecPtzOT/)0H[KaC630 1)0p2=g  ǟ\$P EKĥ l+,Aɀ򇂔Q'1L$`(2 3/.sP,(!Hr3}KKؔ$1 )¶Alӡq,K93;3TBIMR E?NQ |T*=.bx-[PKd$ cWU)5XdUvYfuYh%1d>Zlm [p2Di8]Uw]v&qw^zfWVrQDZ*G %5 , s [GY%V@}|H#{T(bUt+P=@h89/F rSK ǵ4oMoB Q^ q#3}eVUV6)1J(F ik*'&U0;AΛƘ$}',jM>u1Hr*Rb{Q1.Wr1RKEj5|9Eb#ʞ*xXc):GJC( +<;)Vd]ƹy`{rp4{vJ:ݹj$mL'6#}׎HH%[rwvtPӫ췖XOsb1YL$!sed;Y/;!qMֽ6 v\/'9H9$w0 ҈#^8] C L@eUҒ$]I01-/@tqmPIeI+2XHRfn'ZTYWfL1T%WBFmL9- %E s d=/$dQQਙ\Rcit̕BjB<[Z(E;2 z30 hZ[%|inq§ L$24)IL z4=hPԠnU;%%hE& 1RZ6+QtA-sæGV@I%*R.9J2q@{K*VE"($REc}$6D@=g(vOH[JA<'W*thSXr 0l1)Oڑ 43R%-. -# v"Aհ#ʿTf!J7i҄w)[Ps$ڨS=-P{۩`xn%x`ߴH B1&qC 5׋2Q>h?u? ,/$ "dVbX+AF_;- i;_N*A3t -} delaV[7~a&6o$gThrtpuw'+l5'*or$gƀ4l^y^fI:iAelS“d4Fu>֥MkX#\f̍b;DD'ń3h_l ͶhFZR&dcZ|fHޭdJK̒e^AmKqJ˱Dj[J35EzcIw :܁\ܬ޸{K{X/iKmO!CSiC%2WxFme+Cd`j )gJB>v5pW߼*C!.Z9&Ŷ7[5U6GࡨN~$uɽ<}+\>ߕ-?Dk ucC/2! @q*7?8䗛]DX"/yTS.#L!U!~UxT'įm0 Pu MY`_sX} x7YB>(Ya qk:h L?6֋(,ꌽȼxBgS?І`2Jc! K؞Y4‘x; b $^I00A B0$DAhc[37[ =: p"T°Q)JS{\<*SHVSD "A|dW:H( )l8EQ7C-K+@?xjA&ن>>dDNdE^dF0,.cHMb*:b;^"U,& y[Z=\:da]ĎɈ<aNWe]V ]K4Ra9e``כHr_Hf$ H._8Mfgfv>Vf]c6Ha^vgo`OvXeYꓝbgf膆hg(.VۊH fv~n~hZl$Zڕfo1f8@bt'Qcu}dgGhefvi< h©~Ur`Njv_i߉tfwꦞƋ! z^jI菆ffh}zav̝& ٰ6jNJj`P`(\>hflJvNl.ll6ퟎƋjth ǾH%Fk\Ӷ78gk2&N_k׮9FmNlVlݖp󘜈.oFŎop&oo*|`\pUnpp׌ amnop V/lknq׋O_FO'6sVsFsGrp CG/OL 0 7tMGTotrrƫA r=7u[u uO X,GYi5^'q0 I1ى+.vc\7wfu R5frz_trluv2YY@zeNo19FrSxU?8u F3hmX }xorxW0&3(ltbPyyyyzz/z??V+ozzzzzzz!,<TB? H*\ȰÇSը*{3jȱǏ CIɓ(S\rIk8sɳϟ@ 薽Hݚ4ӧPJJ*GUuM`ÊK٨[˶mvgʝKF{u˷߿ L:*uǐ#KL{hH>|TMӨS^ͺ׮hSVظsͻ O [`~pc8l ËOӫ_ڊxgOϿHr  `dK+<M8fv ($(_,0(4Sڅ/ 3?أA^ Lv[TViXvQ:SE‘\5N !p 'F_bDT3re$|$i衈E'4QJI;`)'H:駠ٕB6ӄÙ-|#jJ4-ȴ-4髼FsX9ӊtJ=4 fmA`RpޕQ妫.Y?PkB ́Ͻ,3!' C0T;qSSrUtt4IKH?H {-8+ැ8iGa cL2ܝL8K=$H 6H?娀'4d~_MMCzIUR8ARzJ5NF)-¦dxK&(0;)x T+.A{T{[Z.x״#@nQo}UiId8 6t%fٟ;ehA&. L= \ΊP&-Hf=l@M堚)(p2}`xʼnm4a۟@PK/a[nP?`Q!T&30oe, Qp{cIhJR?DY!EL͈ID7r򂔄\'̱!d'3Jr;UJWN"g8eə!.S6)1$貌xғ> *^,d?i\`)5ʦY$ {$tZ8oJEewtyGq F#lg~!A P oXr*/ PE U zHAy[ ~R]ZYUяJ,k0Վ 12ZCY8Wd b®z*H2[i#vzM_!l+Kٕ,Y%`ue1Jah%ђYRֶa}:Y~S$baWLA[|co[2a@1Н`r P.B'x9]-S4VFĿxcHWE{!*Uw7{ 9^|'W0gL!h!@ s JhnZ2&$ 1Ba6gUdUj:*/[{`uyTTQG${"-v Oq UH~{1(Ƕͳ}>Z_ a%9HZabzV)Hh,skPApڦt@Z'O' Tq 4 %:9 ~GN4P*1d&jRjrmT٤'A5FШ9ܰ 21g*(qM;~r = ڣ˚ҙA) +@` L9pIdʖqSR(p _ڣa7AZ٫ {(;H1~!J [h٪Ժ?@#eiuKКsc d6c? 2& {xʯ(oz>G*}ʲ  Ѱ+>Kkh{+a$qhTD%o8SjFK{s FP#J~mEm CiKs mo׵*3ՕfOrS4$ȡw- =IX=zIA!JڶԦ[.%_G.1w֬QL lJ#I; GG*]&6n{+[č3z#Mz))ָ-9t+'J8ܑ^ ~=ܹ0?ւ zcA9-Nno$4O;J;@P֫zTRH=A]D^-6K+C?d4`F.Ӝ|PF 1᝕ztA<O !BMq z\.OTCpK0@U M=K#)a.4dD*`iaX.;.}fTMdO1`eRN@=9 ٖ~:Nq+q.a=848K!|N[ Ծ1 [!S͠~ӞEj1*XSi:Ωq~"nmU_.d%{:a3P.狮~)*>S__{ ^^۸b)~np,@~z*ل^s7挸>/OHG,/xȟ.:ϫO9Wm@| `)o>0QRx|jn.e ?/>A)yCmOI0m=ZHkE-~n/uN& @ {UOiTo8A{,s; H4xʿSi ~-\bGF u54@ raØ?/1Zb/1=Wz$XAt^B%Nqa2Cfq\O򘻣ܱD,l5+sK`d_/<6$23 ܻex X$<3$8cekR|It@iWf!$<"ǭqieܢf$V CT-Uq*:<7!6&t&~ bVe34"vԁp|ˡ#ҼnjYXWLQf90F_*!ENVLH2QBWQmp/#јOȭ:oٻh&P~ wHD@BI"@ 8=9 DL(&<ԛU r.xJ|ANy(Jݚ$qqdkTZH #6z&Hl*Qo֢ 9JrԶέԩL:v$S4?cҾu%3{jQdԞhIZ+$ 'ZHlPbjb-L(hSi,MH-2#kl@QZ&fַڽi7Ui i !K6K*T]xWhv$0at?ÜmAT7i$/< L ^t WjRmAHl)ޱ$T#sg o8)%"{99/]Ld4MrJw&/A$d|RyAFZ!v`J/89+pUN7s/4݂H/xγg3-fHZDf}GjsFߘ%!>HjM~W9ZGֻ $^P' 鸀8v[ yE:D}mlg{,{B%˚$K³blyU8}iLEߎIzP̉5@ ޮSb[6Mĺ$A2xl)RdpGiaSi S\Fh<JU-%@p6SD_zbGec.s)^+G;iRkds0@ϣOPOOsvi$!Co""]贁ZJ5-?BpnbC]Mw|x u ;f4c3B v .$|.||}bn\iӼ ]&"}*C>=D{^Y(cpW|G7t^!B$# \!T"#\Et1^ @K2!ƻS,> ;ૂC`=,l&{}s 82>ӍC":{h⓻/9W[# θ[4\AÖ S`2[;J0h<@S 0l1 y0#l>.;/A4x >uc0Ci)Hq㋖: BsđJP¤C?C4DD? -棯7"@,,  Ž 1Z)E*0;*iŪ{E[5ϳà8A2\xB_dDbd,.z4kr$c@qf=l+4G»FM \dǛ06G$ c%(9~GEx Ng>{3H(dDd)ɀG, yB]֬ؒԐ@*AMUVnOm> (HAȪT^FԬ)œae@ͥ!aI^d#&ek^Ñ2Fc6T%Bg pgibH&W֍D,nevingf0)Xg}^Fhg lXgUhw;6~&ښ'.hf]FxFX{g9iWfhH~ #q%;i&|e*z1ӊU枞頮U .F^Vv6iV#Wa}Fk~$ĹfVOqNB!E(.^fFfn:0FSekLH3M+넞l&lm.m>mNm^mnm~m؎mٖm3(Pmmmmnn.n!,#T[? H"CUJHŋ3jȱǏ CIɓ(STjRqj͛8sɳϟ@mt˞ѣnMʴӧPJJQWG^ׯ^oլJٳhӪ js0OdF{ڿ L8jbuWp#`#]ܫ˘3k̹ϠCMӨS^ͺיԉXWt@o%mjsȓ+_μУKNسkνMQ":`RQ}%.r01F=|7Y h#Vhfv ($h("K#f |0>(~n  \+T2Ti \v`)dihlpYf#K([3Ő?<І@{eKd?SPӏ=uDTݦv駠*ꨤ^|CZ+#=h!T8,k+uƘCkDFDT33 '_r2ɖk hT0竅F;n@0E-=8I k'\1:;\ Z+d3M8BhJ%26R,+ y0@q.]ayL كL#uQG#Ȭ \wDG-kF`QPq!p-mB |ك4&'81!ۋG.qQaNq9堋dY7UPV꡷.Nzgź(0.{GShAr bHH=$}N=:_Gh#=ЖE("VxS=HG$~ :U|9AMh6#[_G$a8:b P6EH졂^ؽFP־ (UҠ? d/&0a/t(E8b~ H@ؠr(@F1&!z( p/O{=wok3 U>ď(d =8^/nbHEJ+IQ_R?DO?~\hQ$phk+Q40zh~Hq*k+3P,:9vj͜x3LEZј>stE ؄w<AAFTV҃sV=NL@e@8@.SVeMdK_t<^Pc Gl'#:њΜLb PP l>p =+_W,RjlJKD0zNO;'N[681 T"E~[@mP'{xNn]z[[*Ƴh@l(w#^> ${eNm<ĺ PB=pR3]_?_X H@p:Una^΍t}wǻ=Yh{ 8Csʖɞw97>Uكp`[ 8\׊<1xҏ~*:DDtk`+;E?sӟ~NbU+2|Ӿg $P @X}@W:2>Mw`D.{WRɯo wϧ~ؿStOcR~}d}'}&Q ~0~~vF78 sB*Q|wM ÀW懂꧂5GR|w,$a4XEh8H׃{#xDhI'ʴ XQ|82d&aQc؁ZXExQ(SH-~''c$z< hw8^5n g7|d;X8BOh p(txN'~H,`苟(k8Ldf8lM~p}9hz'Zt3|،Ψ/w긎XXhB!8wȍ(9s@؎߈H҈8jx#IGX|YBx)晴~qXuhh 0`s؇7ؓ29 Py; tTK>M 4):|way)Ȕ@"}E;IzYy[)]y6)igA&yQl57A@|AO8{h)CYIbIy(i)o{jln=YAWp Iz^ǘTɎ|ȚѝpT?zyi;{ƹH\W#nVxtiuy9qxo.p#7Zzzpѳ  Z``rEsɊ3J{ׅג(nFr0y<GцiauEy1i; T/Iɥ]ZNj@E)h19QVpJڜٖ}vLIp6d0k^٩z(oEi칠Zr:j&Ѥyjia!q Ǫ| ZYX1)y:٬ QzJګẫ&[Xx:ZK ajZJ$b:"A2ʯ # ˫{yx$|c I#K ʨ ;z2 1K'j׺&k΄x/ʪK׉Q:ۮI (*;Fytn:Ii+b[Ua-ˮK Aa_+ LQPJJjk!qDk`W;;&6YRvx˸z۴\[ &o>&XZ <+#ᶲ[eJN EɁU {˩ LɹA~+l˷4[3+&7 G k{) EP!1{=ۺK뫠$Gzj8yd ѣª: :Gy:ܾ+{׋Cܻ,:+;/<~+:c90y%X{E@Ü8ԫ[ L٭*J@L¶X+x8!$g|xjamLZF*[|ĹGˑء:ZlM|n=i]=l-O!:>E9ٷP!dq߉ 7ݻͬ ݢΔ^M n^Cا3Pg1=>E|).xܼJ õl߇+2A)ῖќ8~?M^=.ykl) BN[}^6Pk!\MsC1ωȀ\ˌ/Llف\Bᔬ CnN|?1¤6]~? ٍkp[NM>F?Q>ؙQL[=1C.: 1W~jXLČNx(<.NnLayWNiYTI!{@sBP f;n&/N?ugGHo*O>hK}v` ?n7o~ֻv ,sF (s0Z~9*Yj ]i)8|?l8ыPιhKݜk=N?bތzn7?n_w)ʒN-jܾ>s_ ZL3?#</Cމ҈?^/$XA%dC֑XѢÅ% aFbɃ"Q|˄.a(6 hfOA%RJK{$ay΢F8jEW!%RR+ƯMÎ5HŬ'qH L;W]xTNoͿFi0+ D'Ka%lٮg +Rn9tװcFABàMx3cU۸ʼybv[\ȈlB*ϾN ׭B6zvڳGhφ={{Ϟ[ Ƃ ,?.<E@*J,rK.J $K2D2TsM6D $ڴN$5SYF} 2 4KRVXh1qw dE7]UY>\%/vw28s%BgF 8Ȼ@xܕ "iT};O2H򈼁уf11|JY*|G+ 6hcvZb H:"$t8skY8s Ju]ޖU.6]ٟ={b!Yز"J6ۯ(qI&zn-51 tT_.ۢ]`"Y[A9X^z=\O5(K ( TooH~ ?{S$ߜ4a/%Cݸ~ƫucH78HvȦB+il\pI}p~4B_E7py!q"mExw&BHbTXc 3X T :I!Q"}aT([X @Șg\U0aM ;2D /a/ZSDfxďiXv4qwrh"YF(,H1rflH:h8gT"`I5@HVLd\Vʊ zil*R>q-IK:xD3T|E.x-z\RiH6ͧ]xŸaRg-lP/h<6{bEW ħ6_ P@.:u^SzPs }'+_###3*"$HMc25)faCK )x <@; dԸa4*0kr3̝df^ԗ/gVIl՜B]@vjTk6UlU* 7]D,O$HUZ?kA&{ќUL> ŷ(Q%J( JNNQP9aw,pPV>xOj-/HtW-QxRZ=Ot;2ãb/䀭JroyRmp. @0!435l.BL8kdB͉\䂊wE~\9Q(kEtCbVBͳ{&=I8QҠ&_Iuu9c'@JY& ]l0mlg[NSCa$jQ|M&y "M5WD&~ q7e9;$Gx\52f}0 *gu;啼:wX8 Jj pWz7E{#bS|JSBFص"Ἃ܄Dw'YEaxUVWa0@o Dw&D k5Q \jgx]ϴ80#bڜNnMjd 4loiݑ{0y_Imv7G01敘G@|uYߌ|G;HjUSF~//>ɭ9c5. . _:}BD@8DJRÄ9>$ؾ#A, U t 32A*Kp !t ##$T0*K:'Ǫy) Q)<: CC=,sCKÝxAC.l9 0;F\ ;ʰD-TB(GDۉEH J7$DDŽ(3H#N)K܌M,ĂȮ!$9&IBLNtEΟKÛpI$4𢇄H`ƶ$ILL O$˰ttKO @JFAO”Hmx1H |>}4A0Ϩ14M% 5Q `&Q]8QQQ(XQ')NON(&&EQ4R]{ J69Se b  S=S>S?S/ 56*0|ΰ%#ZtP)=T!QSи]s"P*cDDH$RRIܽyTH=\V:AT jTH$U/NevP  ) LT.ENab|f;GlWqESRU@ի0-uM oVWEER(WS=Ugt3wps}~ vW}؋N~u ՄxduW%ْI~dذ;<]eeV-Հ lق`X5YOٙ%F^ر({%;YaXWlO-YUYmZY%ZX]XZY5װ@VEشE۳Z ٭!۲ڞۿKrķ mMŲ˾}JuMX55tBP\EԭJlٱXDS=Ν]-J\ x[\]]۝[׬[u^5 {EOW=^}x͵ٳ!4]U =_߻m]0 յ_ Cϗ`f^uuSYߴ^0Ze^}G^¹Bza-U ` 'S '=am&`z ԚƳa%Mb 6_ kMY ʈY]Z$b&d)@:+\Ջ1̈Q-c3:cR9ϐ0] b,~+ݵ,m~cHc:a2V6%TFZ&&0e:N6,&RVMKd4Y8'6d8JA3c[Н V ].9!V/6h65V>ab& `ldJd.vc q6 XF>gpSzg{g|g}g~gghh&""(hnh~hhhhh!,n' T={u=lR|h).cF=`ʒ+OV$ dHkƼ9 jL 5&LjyN&A݂kqڴ6i˅ֹ DpN4aY{1}YO/M([!,m# HA&WÇKłQXnj63㱤v7iaI'S*g*gΕybDSgP}4)ѐ+#6EH4 zS&ϘZQAUD_Ҧ͞g|-B߾mvIZC6 B6n6$H8Kֹ2P &%T% (d!,3TK? H*\ȰÇ#EU$jȱǏ CIɓ(S\ɒIk8sɳϟ@ Z-{Hڻ5ӧPJ)ի(UalgW7K٪ ;aFʝ]ۻxŻLÈ+^̸uzѝ ˘3k̹Ϡ qi|źװc˞M۸o66> Nȓ+_[@Q}%.P&3}. ˟Oߟޓh& 6~҈tҁ.   ddK+<M80(4h8<(`(DiH&L HSֆ@Shψb?SP^ӏ=kshkx pk)ti'rEy\ZJ#=h!PF*餔gk(L@3 '7r2zb]'jOEdO 3r&9ɚ)+d3M8E )H 2ܦyxq(@2޾\ d2G#Ƞ 7>08 )w@PZ?P(C 4LȨs<쳞u3TC;U4QG#=VCAԥE5<;-?qQiR{͓c/k?dcݴV6ik\kAwrP`H `hC_~Zc57NGT\%]JsQ;dO/% Gh&2}<=d$~<'G~U /d(!GRp_t1u8IKH?H8Cr#eş&?>]kH88hk3 #Dht#W:™m Q HQ`H?$L5NG㎰;uwT_It7}CH8D,58֋o)[=4q\Eºp0"G0E"#q堝h |Ew&6;mMx>Bٵ k2"E*QXEoPD-G0+=߃=852ř@9g ||#g 4 鈎5?MH]zmR ])Ql L;"Eģ >e9 Hho,p8F:R'ۗ"7iyަݍ W,yN#@XO҄ `G63|5@A"|ʴ()QhI e6ypTӕ¹13HI!B px9H6(Y WG1T| =\0KIh@GǸPJšk&.P]ͯQI[ĵ#v5]#=5I}-}y][ꊀƝ糿7?A1HXz7{hf{('vGu:ws8,~.~T(a-1aYGQRHy2q-A1}(jDžLN{PxxoHduMqEl{=b8`^G|s4x!ԇW?(%Qrn$J䗆 DE E'}w5Fhb,D(hf'v(xȊ 7PHXȘX(J*0Xy٨z85Rvx2&CHV8~M &XȎZFZU?؆踐 S8;uGeD`ryI-)A86NYH y y? ԉX7 W-8(Q9UXh WSNُ-V3KTsOؔ_)`9tI;4ϘrDȔ̶8(qY'BhwT1f7p/ a *'2 `8Mqvq+8==Fs @Z;F)ui%!V]ymFٜ99i H 0SikCɃie4 1ǙC&ɒ陓Cly UymنP5-1(sdЖ C놛ɚi!!ZI }pٟ~"J3>d9܃ɓbVqf֠5: :;p D*jD"ɣ3iG !z`UWɥNjIbUjqRWڅsIbC䝤aJczv9u^9 (.j )8Z`bZdʛ[$S*s'Qr-  Z*eJ%;S: ZJ(GQ8৞:ԺMs9*ы#Sۚ* YJeDî**+A8ftzex^)Jf:wGQ%@w09 7)8Q暬%>бC){3 *{<+0t1PQ*4+6K&V7$#IOq 4 WvlvmۚP DCmjKlI۬Z Z(8FPH ܰako;ʵC{;f;5(0XK+ U鴝{eUQA7 J>~FUxn;4KZt&@P|+=\Ts A.R|\QA\,@lӖqc6aҴ@p 8:Өv* Tӧv;0:py a6 @pk8e`̈́"Xq<;=ad_sFѻR0K kT UH秗  ʳmQ%[ uf l^gZρ&ZЫ" R[ӡ}I PF(յiٳM>C(@$pu  $ڽD]Тw- @{!L6QU|pWbT77Ƽ,]^8!Z'GbsDO,̜Q;]ۊ-V8qyshS_#7UgQ8x{uXbmF;c S;Pćt {L,1F %wG-) = cs}øpN=qcG5US;Y^&.fpvߌ*N9}EGm,QFW7U^W> 8!yԱALE%P:z;[D8dؼ~8nd7Į= ]N<1P`[ 㗉"Į/l  3Oпu-qlW|tnv=cncS;+.ڄ.B1 ~*mذ;m&x}c}G0o`_Twq̀T80C1 mtMCxg~#?BKD:3m@O:Q^J;~ŎKh;@nԬ`FۄSz[c7ۢ%.1_Oo]#[CpK0@[`SZe ?OQ/:ښUȧ }?@c7縨NU1`eؠO|񘦸_ ;A.οzH&G:9$XA %dCREcďsIޭ[/x?{$ YfM oypgO0!B#hƟGy&UcӣL{ګ ^iܩ-#HA(,׳Aͦ] q@l]JSd#vuyR?REz#~djƉC4yWQڋ93j̰cv eOM۟gߍӪW} ;VwpУKԫk{f~$`ǟ']{{363}>誰ɴʊ5 *oj./{0$ LhŽ6KŴ̲@U*̏2FF lR@> qJ*]$3#!k '^pbr'TJ*##DB qIMƢ b"[3c&!*Fju-㼑6 .0LC?"7b¦"3r܂W>SdAY_S`g?Jh$P ssF5CNF٫!ʴ(vK՜~&O GclPFbƘ2oaG!jԣItFQx6kg@Kh.꧰N=Tb.0id3iiDwZSѥF"̥>&LeW \2B()k`n33a1[B&Lv ŰzPSC:dk_ӷP'{qlGh+u}E%3ro;AyA݂0,T{ M`\ ־06UaM!EJ: Dk+דWBd΅+I}UXA&pl (ڊ!D2 >Rđ҉z_,[ f>/fokݹz$0{pdFB9.#iwk  ,sy bj-qB:LYY{rzſngWfKHRޒfvdntL+{*cC`嬇Vu:Ya7)t#fY.ݶNHPQ7wp1+w[!=NlþoE+P,Y~eOb+-}蛂m'\/HͲ'Wj- ?JX {CCQR偬q&0ڵӯ:! "X$? k3zCs>ANpO y?# ߣ@#I DR|V1n*Y #衎kA{:ቚ!Gp- ûC1L PN0`C8 9C>C?ܚ!yd/  XOBˉrlӾ*9AAV9Dx  ij(@`"I 0>E B.ۖA(+E2ֻk19pŴ ZA sFm`[h 8Aѐ+<2HD%LP]یx< wb4) xN,(TԈTG`BKѯHB# k\ 4?AAƎ`G w|Ǵ@( Hl<)GYGv"?;ɝ K*ɩCdI>(| -&Fȏ7 TG xLCU@H5AA]Ҟ2WpUse͜H $+X%$Vl^VΥhR:K$XN,QxDhX_ /PͬءU E̛ 8=Xs숴!82 ȀhR)VF[8WaEPI\Ԝ( 9Z5bZP5[xYS۟ nq@|OmGEXF L ҚZWcۍ[׫[pJ}ۚ2'b-!:T*_8NuflS,U\KYq\ZsXMޤ?Ȝe\ I=2#]խҭ ݶb]ٌ#_;^[j^x|_( p͈vp]m\[˶:хHSÂ'd߳] ] J\S#\_~\.v] ` N'W]&(@ %etFE-a V-ԕaaV`u^ ^"Na+O-T04-bց Q6^0b5VlQ4I^Q21c>+ n@`B abą!+>~@28K> Gv>]GL0]*cQ xc.>2ZK^WvbM  R1@IOǼ"*f_lE,lmbbNR0eE^k~pQ ȀGCzg6AC1gghh wvfF_V[hVff_"8\DVh])gXeFNFe]h@M([ivVuFlpd^e}e(VU2 4nfjcIpIN9'kcN[Vh>'J eiot> dQ٦^쳀F*NLj|kl~in e0sfnl2lei69ji թ~Vn n& 660f~mfə n%kCrG6nFofo~F3!mFZЦ#IZ)g}l"`OHxOtE?t+_t>r~ . )rzsKGt//uht}못sR?0ZGp6uXbS#}=sT UNK_]u=gi*pfda'vipk{oL%{v.vlLGrh2Lu Kwws';lX7Xwjw[vLvz v oowqlefϙj.v xt9暙 .}vqxBGYR?ᅠoyA\sxUZa?wu x{#j䧇yoz-*Yxwz F5|Ǘ|>3hkHyO |0C3Gϩ}5}}}~~/~?7́Y+@~~~~~~~!,;TC? H*\ȰÇ#JHŃT5ڨŏ CIɓ(S\ɲ˗UM5޸^Tɳϟ@ JF)]j$PJJիPUulg׌:Kٳh_kԮ۷kۥK.P{u˷߿ LÈ+^̸ǽKLefx˙$>nMӨS^ͺװc˞M[6mjSV'P[`~pc6l ËOӫ_ϾG'c>Ͽr@80 - s 4I#dv ($h(*'"},(4(b#Oh`?SPBӏ=`pL6PFi-"›Xfej鸣G`O?ZhGIz&r"k)T<9P1gZL Lݟ&R<@0IteO 3rȤ*E<ڒ?v4lf7zW#܂L;H#05 =H,, -كL#2B-`R【dۖSK.Y?Pk/D sk$E_h 3ZDGHC*@-Ty͒*_ԶSP->=4Y4IKH?H`~o'E=hJeH8أ 2iTSQa)ۓ?@R`CS `P@g}OSPNGXk=Xw>(o%៿ң8E-x6*/RkkG[pek 2.⇧I.Tj$4=* >=-Ƌ07{Tw3."SB&嚂4vDb$&Q ĂX B n|hY ~0?x9j4P1!1KW RL4e%4=сѶDѢ&%0#oP?B.T*C6poq, ͅp{"D1* 3Žv䚂y$7"X;Vdi% I6jc7]WPwK=a<`ZEVg&E.˄"E4$%Ĥ% {x$ϕ2lGQBmp5ЀraePfL-/HCS4X@*v#X6X[.J @FAQ]H;p,L%6WF,$.dh^#&ڕ+8* +M)4k4+3>;˦tT[Oe}5eQk1l ik9+$/qmB'o=WMz{Ѱ;k % ./ +C H\vUj2#2;armU{[&ֿnk-ރNL ?'s% H%aȅpKO(lWt+OGx kt u1n"Ӎq1}HH>xC6}OGJ&7PA @6WH5.sȒ)!q=-Zzױ};D#)V ib~wII^rk| wm3x *O~E_CˮyI P?>~ow6 xQAW}w{E~Gn'*RGgcv`BWop#H(9lw5U 84\wlzsAcEM?w~P&x~Jx||7}{I~]%y',A>(u?J 8FWYc4y]HUx4t1=WȄ~^X8@4p'hlh9~>Hut?\1?[xɥRy-DqH{։s'XE38-"41Hk[1-Ձ((h8w,8DsQ7(vwSeIXcv8UwxhǸm8sƓx|%srav8H hY'Q4'KЇF\Xn)ŨU(}c(nmbHX9)ٸ׎z>'=9|8 ׏؍LI9YY])!<9QxEؕ^yD+932 s$6הhw5f9D5Έ%J ƘNvid!pd_uٙ9% 7 0 0BTf5ɖgȒ'BId ) \xi(S%J'44Qy8yXskA#EAiɞ퉜Ǝ4y|ٗiO)8Ds 8n9zɟ Q@繞ٟD8ɠ  g" 1+J. ?1U'aԨw<@-Ii1*9奏v?)=ؘ# Pr*:tX{ 8ЦX91*y:Y4ȥ tyy.8}CLYک1G#s1١R:Q Ci74xj{O:Hv3C?}s*:::k: 8*a[xݚvsAvت,J*`S z]= ږU%sIЮK: Q'Y@_JajbH Nhj}# :Sq V`'Js$agrO;Z qՔ ر*pN`?+g+{˷ D*+5NY2dYvkg۸z{w;xKU-߆kjI;͉:-3%$``Ɯ=8%1Q:$qヽtgmtX I|ٱ=s(獌"{0{ڂwKݬWT^cN-A-)1>O&^;J.A~y\s@]׌iHņ֦Nߋ\3(͎,tVljN'ݮ+~s >D.`-4u]x'g4VRGG5#YMtl9=9)}>(QAv:B4"_=?箃S̹(1-σ臸/^s CPՌH2tN拌D[W} -AOs`uiw_lBLN3)l;V[wYX XkIiٓrgD}y{to ;# @A^mdža$| 0+Ϻag떍QЯoeV\ ]RVg'VoN $XA5H u׭v %xPۭI0GA$YRI)'T%K8tifM+sXτ7s`[RҴwKAr-)*NWkfժ@2t IW$}݉6Blr[-!I6ߜuK4B.)sQ{9G:)>ClsƁsQRS=梣>K Gp7B=0*\qpPQU}U **8D]_Sӵq*Vm0[I# X%"N̥:*Ğ\@]T^WAUͷZ=(hU墼 snC?I MGη)a/:W쀛jtեf~E:[שrׯh}rRν^U+{瞖̯/ZSB¼9@ސ⩌ 3 ~|Mo4d T)JAPIl4?6HE)t~Dԧ|ҙS p1k9}$N$x1Cfpp[8D0)@ZQDDZAl,@[!R7рbL~fYB2{,`CM.C|J',PFz%|DBV@J ɅX/.z˗G͋!̸?bkȘZY ,QCI *,.Sgd!,A2ȍt*&0 zRe\n܆ќj.K:fhY{B6 ϑ¨a7IA]S!?@SAF R49z#d8V}M ѹOeT@NCsiDӛ$@Lxg$MVE_zfG5k [tFJ"_vUy1kt@4t! 1tG*zVU|tpx'ln KDl`]T[ M$#!հv1F"'`P,_WWL9Ŕب.fC&m4LhK/ -<8B|tUKp*-> K2ʨ6qK#Iq!2 F{ I*/GɅ¼} /ȰISv[js=xQ΃^BLR߂ P񖘺-7lI[.VJ.Ox.<{t,k ]3,y dsɆ ;O5ݕEϖ\`ms{+ui-dݜ6q=*_#Fɲ_Dmn4KPqh4K`}q]qr),kqb Ɂ]G۰S*GK7w-NB'K rST@(isdMxt[<{[ ]„r'GϞp7Rm.SՎ![f)ٔzzj0a5Nx#|SIv0 gחwh(WWZ̔>sWΥ t-tDe|DMQ,k{Wh/''>c(xPI;wm؋搱ڻcؼF c>> JN`)@c)4;0 {?[#9= lJJ/=3@a ,8){0͠ :)R?cxtYB)@%\ΡN¦)GU­C빙t C6lC7|Cy;@'08sw(? j +C 8A r<{8;($4/ن>Ԋ,0# ;8m :@D Q ´1E<0{ZKŸ#܉1ّDO슓*_,F; Ec42pE`Ƶ WB E]L3 {@lAƏ@GssD!K[ P~D x%ܻȩL/)+j\"HC;D?# j!Ȁ 8#xLƪ>p&FGCR "xݫ P![@3J؞'H+ gd;"y[fȔxFCMIJSrʉܑȏʆJw3<c Ƞ|DuB<Eɒ>XD'C<{KJpLED7] ˽IRJg )˗C!4 Llt ۆƮ2w?K ZD.|1 {=P,qLTKUh:gʔ;ܔ$𺉬HD Mɴt؋$܉'(\CR,rXLHYp"mU78ۯ]ׅ]I}%Oܜ7{#]ƽmܫJV#Xҥ T ܹTY W06{XO X+= #L*^%[4+ޝؘW @ AוZްރ7\P8\#ZF _繅@39`n_q=}_ʼnbS4E >ܷfa[9SE-%Ɯa-<f %y4U] 6X]d4nja[ȡ,,V0)eQ0!p/S b!֊Y׫]^<5bj?H#noˆc `E;u5 P>Fd0`SG* `Ή$ܒPp1R-dQeVCn`qJ]Q>eM~fh~"  x5fc`OсPA[Rdgzlh S"kXe4d@.gJ6g!Cg)B}1Ba5^hnh~h l&^kf}nko'bѾS\i{fpvko׊ƶ4OaПm~gm8Εm o@ knnؖk~={rpWq HvS]F5)nq%_ OvX_4o$?(@켉$Mdq8q= 4*߽XY.r.p?(i|)nDEgrlN ġX1.KqMwo5q0q2Pɇ6sZtLGKpe/Tfsx5i?,WP7'5+vEvv7op\2b7LskGurV.dSɮ*w[?a'>Gw6 Cdo8q7{7w7 dgbSO?rbh_[Owywr7xw~xwt/?UAL*)j݇'yxwjvexpu2yyvѨASzzl0*ϝ-;xw(x_3U0y_sP6:gavzMwz߶tyNOt M{o|nֈ 1Fp3 y{S%# +M3/_·}x9~O~_~o~~~~~~Y Ȁ3~~/?O!,<TB? H*\ȰÇ#JHŋı= CIɓ(S\ɲ˗!{j^;U`ɳϟ@ JT*m*ʴӧPJZ6{XfՖׯ`ÊK֥=UZbE[۷ՙKݻx˷߿ LaÁƩM;.ǐ#KkGGϠCMӨS^ͺװcnXm#ٸs]Z@.P&3}\.ب2mسkνËOӫ_~R˟Owd L@=>0A42΃F(Vhfv (bU;,ha#GTscc>oDiH&dh\e6-)唱%[#=h?vdQ;h'&e?@8 8| {2)e= st0Jm3Gjbt`4\f djXkGS;Lb婰95 =,+,ZjFe84b,',`NVV+TZf{-\֏/h+=`scƫȨCq}uS6L!LC1ܰ^M C@G<,E׵YhA&wE`HH5 C_|-ܓ;G_* Ȭ|~كE_h1?dI"`3AL/ETs\ mPi4E1Sp$d ~C;|6+mR96ɷ4gX=w t7jbQQruElJT $P # DsqlOG5(IΒ*qHKs嗾>MV5E~,L?HY9@ڣ3\rѺob a{j N2BD5G紜lmbaq΅f!w䇀@gI@" JP< (AY5c$WD.q a83ia/7d+\ AR};,#I*oR<aJ <%m_e(I 0jF+uI QX.V2o:{8@R O<XEv 6O=D'oֺ#(Q@.졻I$g,IDqfsa/Ц IfX^nQs\*W>xF1rQ/T_ȟI@5qdtW3`hH굊Ȩ ; _V 2ּmQ 9kMѵYuaqjV5աp d뾨ZUorYz-j#Yz_Q,b{=lhUr-#f3[Њc,g;Y6(-%b+ g_pѵmok ?an_ 3\Ay$a+BQպEnIdx _lE[73/Oлn,jrynYꪷZm}q`tkI~s5d.Njf67_ fWlngRFNM(NW0gLXeӦwD2 ?N{m` E lBX\>eU˞D BXIdpi,g9E(.K<%T(ϙ̍Ctz:PEe1aq*7U@xpNs'7'ARF4iFE׿ J9l5zn.k&IZ~;ح7=4! keTfaذTG&4ÀFVm𤘪-sS<< b2U @p޿7ΒzRc "$@B,rk 7kEC Rμdhl,77Û H-=hvyr֠gf2Y.M;Zԇ^o/onj zn6 ֑{<#o6ϩ>MgIu W#>P[e%C;xOGu~@gz^}GЙC@:5S-镏\'yB]Tt|U_݇D3H?W2JƷK1s{9ӥ?Dww/xu^t44xۓ}'t u=W okx xv|ݷ|)}J?6kǂ-3rW$u#|kɷ~y|s>'=~S!؁!ASzFX(:HL`B89P~@H)H~ 2IM؅ o%_XH7{ֆrRXv/8Tx7q p|`?!3glg s_S׈xxHw^g"|dž\؇'1Q8q׶h(80AH'pWxM#5`' QSSt}{؊臿h#XU[t<荰{84SxuiEUzȊzȋ(a6zx1qÍUǎy(QSE&()qD Ԃ"-(UFF+ً/ 9=itXIxm'dJLٔNPRdpHQ H5^YN'1"gh38ɈXf9 a=%i8^烖pX>r9Y7Ba2 kXxIAm:WW:iv#Ai1>FSa遃YthljY(" wpH/s0y({%8;#ďiɓI!qxZ! @ P) )3uCyC֐Y o"1A)a,)A)1 Н HY9 ?hk){ 8< ?3JQ ਢ7h#a6~9ٟ,ͷyt%J 3 A3GE`z.LJAis)ʟI:%jɅce? ۰1+ ^ '{w0 Đ갚ۢ)&槲ZezEyRQ ldB^-;;ok7qQk[g~*P,fh2CYMヮغ p;@l?":$>frNݓPg«n~g~V (82Q*Snm@h>4ewupKp?M e0c;IA~pH|^^5/n`p^~GA-~;l;Q x|XC[=^\*v7v:\젎nɷRBc+x!WP_,i,)w'c*ؽuou o(/?e_Л}>+TFaC?Y}_[}j(}9zo(e虝 ;ޮJ@W 9ծ^Wn!>޳\>K/.s8$8Oș7ޙ9͛ ?.SV:o9MnA .d!B{%NX1aD5nر#F I׭v!!>K*9YS"M9τnaPFGm&ULG$I햶Oί9EJ^w$`0˲̱o6ў [֭W8xT  l型~V7oLCB^"Sd_fmk܍>khi4v7;L*#_^MGyRt&B)˧f ߺD⤿~JPAn$T t =RC褔*L/=9+£XT (5_\sqG{G 2H$TrI2G`rJ*Gtذ/.b+sL2,sM6tMo)@;H\6 B3 SNnN-BC2M2{i0˸/6iTHT?&*A2HTF "۴[I(K~U*)3 TH6>Zr7nݕW8 -Ҕ3ɔōjg W˵>bw_~%ףZ+C2]̘`C؟@LVL.Qn̳N*G mT϶0CkXןnyΫ8j?{3)4=h/3 I;*ʨ>+[܃hIN#Œ P Kl25!~Ékx.%LCHZhEh4M W1uŜp]8o⊄Oû/Kv*I_twu+o3|a7W4M*ʰm X O޺‘Wn j='Qtk{8SGG5i3_22*@ T?io XWnƷN#@ H@ƒb̨Ζ!t"@##`Qb'H1ͅNlQs 0@pX^g h Y%7$R zd!UE9J$’]uW.^D!/K\#( t͒M5HetAX)̑RxJn"D!YөI87H(LcDL2| -Gf%/- A fW]qBeJ9cj^0? y8M:\6I6j =??t,9%j,|T^x1g&o TFZUX/B*Ҍ>28[HⰛ-HGg&huLLA^QTȍR} f3h6թ ւФOՕ9$IGJU;Sպ;I%m̮ V#q[Mwn $JbFg ʹz6([ZlkB)ʦaBuglP(2.C`Al2* fi]]6 E'Yœ=˘ع$6\s'CcT;u^4Ԡt 4+Ҡv+dwrb nȡuЉhS9-[BOۋOr;CϑtIn-d4L:R>@{;S뾜 $Y'>t ~B;6uk;=dH@Ҹ@N+? 97ȐA6<܈#@*3>hh 7`ڊC< A+`h',{;0Aj c PC;`Ok:(5p~20Bs^ /lÐ$(%B :C4j TD6WѺ /3j1JKAC PŰUTADxp :=Îx %` z9&l Fykq 6AssmFiF 1D@ 0(G H$n\DxGGWl.vVG W],HlEԦq{C wFW4 e3FR# W ؃|,xnL W+6 ?(U$Du2T3!IE#CÉD$E\DLJJ3F{+t숮1 1AFd8 䈌n[ƃCFP>SK ɚkK$E|U[Kt0KH ʩCL#p˷| ȳ럏llïKtPM%4L0; x)7 lYIޤtKRIx^NT 1=c S0 cڌI(K,N4Ś `AȅCA>8UT\`|! $.tʪ˓ϴ %Ҕ X3t'?P!X+X%]}QhP# E|O:O@#tʜ2 э!))ŀ#P4HDКx OѴ pS9SR mϽ|ã0ώſC r[z?.S1QTT$|ԍP<"dJARU(ԍ@2 ̌[[Su'$I<2I.\uH!%]!X= a)=Ca~aaa%qZDΨ o N`ya[Pޠl=b%WE]>`a$ه'%+ [Xa-032)ވ/5c$c9:>8%nڞX  CX$Bn1 -Vc @:d &cdmܦܹ[S> aA(Inƕ|<@bTIPe"_Zv (ň0A@VfJHfEf;ge䛃߄b0fB R榸`5dsn`_Xav ^2z4d^kkfufgfkgnD2nF{>hv>^hcKhh}蒮㍞g]M c&4~e_e\i13t6|V?6dΝ lCh.lnv.l:;FIo=j&~~LgSEJ%j9ifpf"gdff뫮k-o;߁*fjFi6$H1^gIUlXjdNQl#lɎgVɆx|AO*Bm5vfMBkS<44Q/>fg~2yl~lnayh03ݎom1uӋhoGn&kZiRhpmVn8h qZ_Fl-xmEqVm ?gVǝӍp̱4 (r9rqYk^& ߋY62'9qdݻ"{MHjˌQ—?KG@RR'*3  T$K=m:(7q}jWv"Ѯ;zvm= WYI@6aP@PUxED=1@?THJ1Up*`ag#Vձi]I1'8'{&MKwZr97oq=$]A8!q=$O.HL&`# 'A.XEOI'6dO(a2XނH7V*AA|QiQ9)i[ʒ\a W%FTQYD'T*$ h@{H`\YL JV4'8ptJaApH1*SLF$R9eD$@7歐Cg% lBư~(|dBHbgB$D"hZ*p*FZ9#z 3Dc$CNZoKb0Sgù4a"_$*$Ց\;m5Ч2IYQ.IPMZtgO;yO,sHP=0MKLY= cS T#l.d^~fesSz6Xy W 20=Tޣ1qZ6G$SyAmw E-f%+?ikT)Q2~0w+ RT=lFu\ #+<\ A`nwpN.iKʢf8RTE D; Ϧ'(LT8o. gBҰF7̡YhC?aqC ElMm 6!G(':ĊWL"Sv_l!8F1!a,ѸKq S|#ߘFz>XG$IBL"F:򑐌$@!,1Nm|#BUVʕe!TT\ %,aHS"DQR@աaK7ʱ N$[<&EQ2M/5*c2 MѴ"cAzD);CO1&B͘ĮW?!DCa/LFgS[[a@Q6'?R,Y|"D@rtgA9J}jʨMCasY L^6]C;ԃP&  Sm%[sTؐ&+W31X(e 9!լm)d#bNI DփՌ Z_)3*t%,(z;m tT2u($`R*V,DTYbEHANz:I*BZɏmyٛl0P*W LҸHd57= VeRKWᦶk;wvokF !Ƌjd8Ȇ_\ߦPDګ;ؿ>F!z"}jKX! E ,W$"4] z*T_'z]ۚO|e q"t//0B}D`bK\.'iWJ~2Wk>܈$*]>+/&ݝ~5~ۧ7F]&~vס{wd=AxOWxΧ`gwE<'X|c`kG~qUB^l8hy{wa/XW|hErgB/d7Y%l@dw؂Zv+vmC8'#~ᢄėe@ϖa~nxewHJŇJ9js@_*7p\}g#b)ֆDXfU(D&AQcOe'BE38|YLJRx;eÈׇax!45XB%x&s@oGbAfX!do؍} HQaHpHȉ,eW!"B5Ȏ {؉Mw&8!,B~ey8 `bjYY$ D Q9C.dsXiD`Zvɗ a Ć `0"Jh qӚ?IvBPG9PDvf9a/0 ue[Ih A>舽Py>aT7BWwI #wXuy>ٝc(y{8)IAY Y'x)N/p)ڠ(ќ/ fp3qA'64 .(6gFT2Q*IJCr +Ǡ5 :J[M B7Q:6~L*D`$6:Hh'Ht)BԢYirAv3d d &pD%3DQtPw 0yB^pJ }rq_F Pzt 'IڙQQl`Z^ B  DLhD[wV@4#u4Q(&(s(!,9i '*\Ȑ!#JH1+jq`Ǐ3vDݭ["'jCd)5à @$h&$_n/&ѣ0A6CdF"c˝@~0P= Jg G"][ՂRA{}Iᡤ\iUGJOh]-pO7n^!Z2^,[Y;bi\?~@ A6 Z+ry&r209Z:[ 8?wKѓYGߎÐh6L@, F(&K# Ã\H[dqWJI2qMĊ; <m>cT Db|e`k RZ)MMN.!Y\=]|d4H\+̦DH17uP={kYyI8i6T!%-CeCa<4!,;dygraphs-2.2.1/docs/smooth-plotter-p2-bezier.png010064400000000000000000000057131437353256000165350ustar00PNG  IHDR,cPLTE...222;;;===@@@BBBEEEGGGKKKMMMQQQSSSUUUVVVXXXZZZ\\\]]]___```bbbeeefffhhhiiijjjlllmmmnnnpppqqqrrrsssuuuvvvwwwxxxyyyzzz|||~~~-c IDATxCpvK͌5&14Kέ]bKDS4$pj +ʫpw\\4Bb,.XI+X\B upl` cql pkȭ5tiOS;0Vq(UK{?qkY\JJ́YzVVZ{u c{@7g?[ڢ1VÒKc_6 ceg^%32518Ab$ZX0Wl'^OW\{ A 3kF A(%^߽DnΜ°=4vmSHR"XG%|1] `ZҹI:,*iR<18t^Vo'YG%} %ԚbX)GLzp5@ǹX E \iK^1 U+9Q5zCG񮟵 p0[\01縦1瘷`ͣ՛U8pׁsL[t=o/6\PWPVcs{Vo_2/>Xunǀ Nξ1knRGݮ֬2,o'Kkw~e /UM_t 8gIXkr[im/$l ssTbs/+M;힖֮O-ƠUObN3`.lfE 9oZ.t̉5 [ׇ?I)11/}G^XvSM:X~ M?ŷ<Ƥ^ `&x췿puiòo?vN*J({Q˽hK}GXO*㠰I(qm+}T1 h/E>/+6"Ike)XXDPP# O᎕sm>*T_]X9vbKmbbJ\v<~ w\eXvJu?KuiX* |`b%AXj/pµyR /) >S~hI:uG+,X'?x< d2sa `'>>+ aٱNjo.Fׯ?凁급u_mnuJ^ F/N@)`esf r[h|o>`&~ÓR 䞯C;쭲KN v6!`ӽژX,Vt k8`Q}VzߋX{Z;֫GѰXSWٷf"b4VX#c< $,Zd;L @2?R|68X*v6^X7!`֗`zQNB6ʇ1 LhY,@İaځkA԰Ճw6fjE NeA |9m,X6cZGKXm(buO Nn,y EC|,^'V@ku;C2|D `,XZDXX-j~@ \ۊ(c}E7X? j; *j Ͻ LMf1&WA`U#̯{ =dž9sY)A҉zӱ4F1htQ\,a#<uU(t}5^!=O[;#ևOHԃS1Mb%C؄+VHa(?j'AR(jP񱭔jdk\өjϚRQMJe:JG5[#ůIJ:T,t&VzA`C=gu(:+kY<w07$Ƃ5X ca,p0XPy<5IENDB`dygraphs-2.2.1/docs/smooth-plotter-p3-setup.png010064400000000000000000000056731437353256000164230ustar00PNG  IHDR2IPLTE  """###$$$&&&'''((()))***+++,,,---...///000111222444555666777999:::;;;<<<>>>???@@@AAABBBCCCDDDEEEGGGIIIJJJKKKLLLMMMNNNOOOPPPQQQRRRSSSUUUVVVWWWXXXYYYZZZ[[[\\\]]]^^^___```aaabbbccceeegggiiijjjkkkmmmooopppqqqssstttuuuvvvwwwyyyzzz{{{|||~~~RBIDATx_[go+vA-MW[Yuv9lZKVf˅kM'tz,$Еk_"Ip1RHP >9Uj@B|?y!7a !ݖ-瑌)ן|$cI + ma}i.8{Y6ĭH0ע t6;h bBdT+t =kִLbd/}Y)|Iǯ֬` +ke7]S.NX1y~ cn;J/Y\ԓ091bKy.T(D|0&F/¯[hn"$y*Ddm{7!D81.Fkc}{=્a},*#{Wh@C)sGX?^{h2cV'ҒyE/F[*bѠ )B7GO:A)Wѓ>V,DD82702\!yɒlHݱ[kʹ( KOƕ+1` e!&""Fk֋s~ b [>bJ@11yIIl)$&dR =k%H&kPIP,dR -LFMLz )L%Lb#Xg**2*N BH&iϨ:1%mI],d :łM:XɨX'A2)}Vb$g~9%駤PӅ<ĂF:#Epܧe#$2ilĂC&+T2uJ^b'b]┝bd(H29-yRb"bɘ(2-Lb &s$CYlad9-QKML[;2GIŘd1f2;Jf?CMLc#C1V2{1+ 1+C,2c%C1V2*fD) c"yȰcdcBbdaXɦ2(B6UbLd(J6UiAB`ZtavL Pե(&/^*R(t1^_{^݁4UNͬDdCsL_>$>Ye[T*OyF(\2|e}負r -٤D&~*:6Jv>۾9C3d5 QQPnU+QB2g~Ԯsr_\TR0nXѦg VWy@o9/[ffx׷-R#[zГP| R#G&}pزdF߇t/MAR%o4=~$1E{ۑu~P4HVy'B2>d z{pwLF•@&J%VHH9? Onqxk78~}TȒ=o;9bŠ݅ d!&£HFfٗ! ɐ ɖe )Nӳ8KkBIENDB`dygraphs-2.2.1/docs/smooth-plotter-p4-on-line.png010064400000000000000000000072221437353256000166150ustar00PNG  IHDR3PLTE   "$"'(&*,)-/,/1/131  675:<9=?<"BDA! EFDGIF% (*JLI(MOL-30PROQTQ9)4 VXU6(8;7"@0Y[Y)]_\>EA+CEbdaHNJ4fhegjgLqy8D̐2Cf DL-HFfҷllf l̴_~IXBtxn&_-2?0NJ{plA^Ax8lLouL7KCCCy 0BKJgcFƎNp*fBDN8hzdhhlF2ljtqX Gx;i7訚:UeNe0[ohW\֙d0[nge@fCT#643Vf3! T@dfYJ07p`g bYxRsWMB>2mGf3~= y 3{}NYx 523*+A;*J>w] dĵ|Ų܅ "3{0~Gf[1RnZOo~aUR&;fzeW+=O֌܈>ܺㇾS*TnRTMҽ5A^xQQQqWPAa\^l0JZW04os O1,kG>qŹ2y$Ȼd8* ~ }2w(~+ 3)TREĘr2/督~j3"Rxf*IN[2Sхෛ=t+ng>(EfGT3STuzLN8s _oXxo&qh-gokiP1@p]iuz8Hd9 V{`a}Y~ z>`akvqHPy=9h?P~+aj.%j_gin.Raq8zIRA0)JfT=,~k v>',?<zO9@@zwXN6{H Qv_*伩Huۜeƾ|HlkdݯI7NBoya9N2e=hًfZ 4ץAdx r>ڢ٫dx5~@;0ɦJ,SfVRL(ߒ+d>" pٓl8lLLJZgl V\wCoͤF~9LUn$yf;oM33otev1qΟ"3,fGmh@>A9fkЪ3o3ZO9fxe3"GR+q4v)fu{_#F8lfs1l>}OBd֩?ڒY|\L?}+N3,QOrƑߩm}Ld|d8fYHclKBk-`OCfκ^)D 1DFl^#ΡZfdncD.7IEfXLkUdzcDqaqmVc9+JIcHIUȆΚV=dۣ 37)b|܍ f- n9D"4{4>tde1XWr@TӬLՋ7QM!XMreL7<6` 'Yw{)(CX̚,xv? \ r @T1!07ID5%ydaY53ϐgh2 73na&@Tl,` ՞ z=`P 1:c ,N!aw6Zƭ-3Vf/ gʉNpP9=o'OE0 dekT~'1f>{- ME83嫺Lf#3ѪYoz>^4P2Cf 3daIENDB`dygraphs-2.2.1/docs/smooth-plotter-p5-shifted.png010064400000000000000000000072041437353256000167030ustar00PNG  IHDR3#PLTE    !#%"&(%+-*-/,130 3525758:8 =?!" EGD% HJH&NPM0QSQ4 5UWU<'Z\YB0?*]_\_a^B2C3bebegdI4gifhjgL<]RlnkR=oqnUFrtq]N\Hvxuwyv`Qz|ydR}|gTyui[jhoCn]zqrfxiyj{kmsvxz匂Ř䒅ȝ蕈暊曑靓Ȫ¼覆꥘ſ맠鬣#쯦ﲨ쵩-9FQoĻZe^ُoil߂3 ?IDATxPp?V&T;t-bVndEzF&:NCȼ0'Mp" *T*Ⱥޞ!#ox&|o‘}@Y_?#INĒAlF'o|iu_ͨaVFñh jfT?|(Q3b }،b=UDe`3fb3fy a3l&hZ6468hĻ[Mt<،zNOi-logca޾~^tWWqLbҼPK;N.~oPg*6N%)-3c_  dck(XoFˬ1B"X!3 Y(tc' …2 %{8cOqi 0&=fBm 8dlf>h 2 ! C~@Epnu/$vqicYu~~磜ӂ6tl /ӛkt-pHgn|=|ʶ} 07ý GjD>θ@,$`S.CEIkLj3n>,ɒ_^Aj;SʣskAyL8h80"avOyI]–l4o0=2avģS?GGEQo2>=;쟇?ה-ʏYmV;mZOʖ Ƈ)|jtOsǖ_s(?qfb>%d>!^szP#ӉҎfM)PLQBE](fg.)?X699Sm?2BɜtKvZ"4K$bIA.JtYcӅc؅rկw2yx1JIII)JYYʜshTV˗\ZKޔׯ B֞[duԮvTሊj6h&86ssc JLY:7Q}g{27Fayf9BUEnY7fIG"] SI?}R]$QT;7-}d|uJxQ3h+ $Tݱ| foޖ$?Wr`HJ=3/'/`#f]; M̌UliHEZ.+.`!#㩙4@fK.yy_mV]ark%E $U^1WH"fYaUaܵhbRBU_jL:ӄ-t9اQL_m87KWHO^X7Q@ضr!D1l$͌wwF{.LjԌ>֋U g"Ѻ>fc !3/-9厴>B &͚#b'?0 Ƥ c!.M̖ <@n&߄3#O25OW42K1=|Y-jLt愯q={;ɐYYK$|fכg4 Ssu2dlxlvC?kvGIֺ:2${(!cU}Q"1]5>cf\mzU=V0 ChP8d/hkpג, #{AP^dk( _QvF2⾕jn4|qH5=2O0J ><f5)` %DE' Wn@2SFn&fd5/1%ar&Vs72d&Al8Jߡ]yѺ ~9fSenBBfnC:fC&0ӊ ,p"Y;Ȝg2Yfl"sk癱)fZ Ȝa62'a:Ì}d7c!H`3V9֌d5c)#J@3֒9Όd3c1L 3V9ƌd1? 9Ԍd̛UKXNƸȘ6feUqQ3 &fU!cЌ3d̙i8CƘȘ2Cf"cƌ[dq 31`rlfe7 |͸0mIql^f%WaY2͸Kf52f&όdvq3ay2f&mha2f&mha2f&mh! lE 6ӈ0=3LF 6CdxĤgkm3LF 6dt̆0-tV#Va2fLE@NW(|/e6.&ZO[kY鹸 b͠`yu$b4k46Ac?Ft5[m e s0g=\d \6c|udab8Llf? 0%i:o`V3W0f76v*XF@11䓅&o },a.fA,vw$ [6̼<7*q3֒'Ze?[Oțbo$l;>@=@A?DFC" FHE$ )IKH'KMJMOM.0ORO44 TVSWYV7"Y\YB2>)\^\A+L;_a^C-ac`C3bebH3ehegifK6L4p4VwwdK3y!R[䆋k;(EE$EZ.k:$,K--_G-""Gi* V?eH7EUzRR<3Ba2"T^<.BFDW/s"ϨUGi  *MFcѾGط$5:FPU[3o:;Rɷ:)(fo2AhLU;3%$|Ϲ h)t/$ CGJB@x;3VQJI$:[u2,ށJG6R=OB`!IҤ0:xsFv%b&ej $B#XSx 5J}hFoW7#!qcH\2<-# q#H\3<9#WG#qXGH4bAZE#Vj&GH5bF!qv>bF,!qۈznu#6%7buR##ˆ!%I)!Ĉ_ ƈG⑑H|2rWFn"=K2>7#wxgfij9-:ьxi4C$~ F3BLxk4$c#l*\DⷑkH6r F \AFH؈"a#z$lDMT+qPNwܩ I`1Q {:Ec}l<-?,PGih9+VSSSPa 'R $K  \P-2 4BımX5,Uc$ r  yD"S# @>d_ H$z\v-mRDcm9 (@cBکbbvo8{&nٻ * u/ElI ag؂:`4?tgb('lR5gNQZ2O _J&,;{[7{S$HtHsc X #1-9PqjIENDB`dygraphs-2.2.1/docs/smooth-plotter-p7-adjusted.png010064400000000000000000000065751437353256000170740ustar00PNG  IHDR(SPLTE   "$!#%#&'%')'*,)-/,/1/131 5749;8<>;@B?"# ! DFD" FHE)% IKH',MOMOQN0RTQ4 TVT7$6(XZX=(>*\^[A,_b_C3dfcI4egefhfhjgL_TcYbZproVFWRfasur\Huxu^Pxzwz}zodfVrj}|h[o^rgvgxizkɁxtŋvɎx㋁㑄薈曐ꟕ裖ưꭣڵǻ챭賭𳩿ڻӽÿ췱ܽûȽV[@ 8IDATxPSpT8:8$L/iy8VnmJ]qqq,؀@o c6;ҊZm*S435$' p(!y!!yw{|{[qXe&P Ca2\cPlR6b(ĂM_:QC͔;L-T0ΰEGh1ujZCyHh cC:..U޹=8r+NRA3Br҅n ae1ƒzƤ~XHjYAUpUz`~~𠖏(CpPHr*+G;;*/<^: E< xmU ?#DX%D"}sH3en(d(Հ:6"JU5qJ3K|ACFZ8xdYBjT:&O(NP#_/P^s2xLq˳W'<2RPdTWVWW>ul*&DY4]KTٌP]!? 'G%s~aaZP[_ȿ׻i4T9y刯M jfǓd ~<0ݲĉ)T(拾<[UI\.#SxijV]^XY=t|+ճCA*[}Y?~#"Ꜷh4Xy: zzv8urJm#p'揲_K_{P?[wjg~~Μlɶ_LH}#&F* (uԠTS:N“r:&%%%(EEz~M7OP{+T>I1r<ՁGk25sOO}39̫_X<7[6+[ϯ޶Vnۊ2OwS0#GEAY_TTTQ d'R1dYi*-@PLL6J2km?zz]=cke.s嘹eҫ}뿰^iJo\ݸۗl/g߿ PmVm/f!>D95TS2Jg/rS[u%ü@OQB${U?izۺrvVCd'ܜNm^Nc C=LX^3dP53;Y^VJVJii5ڮ֕Os% \|K JbNӠhsα0}6/H_ࠎh2=].i#3:euBk[Y V/R? WkOCOYf1)}9˘*aCFHtJ$m `K woħ`Fuի ڿJvn irAZP?krv HNS})EB"[',;"09o=}O T.'=/9vBPL,`'@Rj^;ss=95>ϝTg)O"B iC{')4QM϶(M\, AE0 [;/{plgT~jQ'{PpBPYIHy`Q:: '~ ]„e!N9 '7KW']BN]0$ԾN/Nn 'HB f,-ytP!# !'jT8N|B NHrjTPNA!nDW?Pkw%y&PBlw|@ ԉs(:q %X'-8P%vjPNA ܉3;q%x' 8PpJNC)hQ8 5P{b(Tx+S0Pr JDSPPOlD4K(9 9] 'D4 (1@>%0hS`P}EETC%v'P6;w;P؉vbeb'P؉(vbe;'6P؉vb점M  ;NP?X@a'vP: 'vP؉rq}bAa'PW&'vfdꋱ(T">ԣmX'Tofu]5 eɪh{[E|B9Pӫ|E|w杙yk 73(jryۊ9Y=P|@YL%2%"2 ն,V)X\|B9@ H<4k!$ x85{S`%Kzjd:p`B@NzHoHeNƂ,9&k{: \ 4U,0T}=p91'7|FPZ\ *3ըfgx =#zA*GMQ& .@Z9jA,3z `G-tm j'Dai]5*)-[ @WuDI,Q5iKƒ0 ӛ*]Nӡ}攻U+PCquDb(6P ( 0 :IENDB`dygraphs-2.2.1/docs/smooth-plotter.md010064400000000000000000000042451437353256000145530ustar00Here's what it looks like: ![screen recording](smooth-plotter-p1-recording.gif) This uses cubic splines (aka bezier curves) to smooth out the lines between points. To draw a bezier curve between two points, you must also specify two control points which specify how it bends: ![bezier_curve](smooth-plotter-p2-bezier.png) The problem is to determine those control points. When you connect a bunch of bezier curves, each original point in the series has both a left control point and a right control point. To avoid forming a kink in the chart, the left control point, the series point and the right control point all need to fall on a line. The algorithm I wound up using follows the HighCharts implementation of this. Here's how it works... The setup is that you have a point whose left & right controls you're trying to determine. You also know the previous and next point: ![setup](smooth-plotter-p3-setup.png) Start by placing the control points α of the way along each line segment. This is the only parameter we'll need to control the amount of smoothing: ![controls on line](smooth-plotter-p4-on-line.png) But now we've broken the rule that the original point has to be on the line between the control points! To fix that, we shift the control points up by ∆y, which can be determined with some simple algebra: ![controls shifted](smooth-plotter-p5-shifted.png) But this introduces a new problem: the right control point is above the data points, and so it will create an impression of a false maximum to the right of the data point. To fix this, we cap the y-value of the control points: ![controls capped](smooth-plotter-p6-capped.png) But now we've re-broken that rule about being on a line. So we have to do the mirror-image adjustment to the left control point: ![controls capped and adjusted](smooth-plotter-p7-adjusted.png) and now we have our control points! Hopefully the algorithm makes good sense now. I implemented this as a separate plotter in `/extras` for now, but it might make more sense to move this into core dygraphs. That would make it easier to have the smooth plots play nicely with other options like `connectSeparatedPoints`, `strokePattern` and `drawPoints`. dygraphs-2.2.1/docs/ssi.py010064400000000000000000000023271437353256000124000ustar00''' Shared code for ssi_server.py and ssi_expander.py. ''' import re import pathlib _ssierr = b"" _ssiok = b"" def _dovar(vars, match, e, fn): cmd = match.group(1) var = match.group(2) val = match.group(3) if cmd == b"set": if val is None: e("SSI set(%s) without value" % var, fn) return _ssierr vars[var] = val return _ssiok elif cmd == b"echo": if val is not None: e("SSI get(%s) with value" % var, fn) if not var in vars: e("SSI get(%s) without set" % var, fn) return _ssierr return vars[var] else: e("SSI unknown command(%s)" % cmd, fn) return _ssierr def _slurp(path): with open(path, 'rb') as f: return f.read() def InlineIncludes(path, errorfn): """Read a file, expanding statements.""" content = _slurp(path) vars = {} content = re.sub(br'\s*', lambda x: _slurp(x.group(1)), content) content = re.sub(br'\s*', lambda x: _dovar(vars, x, errorfn, path), content) return content dygraphs-2.2.1/docs/ssi_expander.py010075500000000000000000000032061437353256000142660ustar00#!/usr/bin/python3 ''' This script copies the files in one directory to another, expanding any SSI statements it encounters along the way. Only files that end in '.html' are processed, with the exceptions of {header,footer}.html. Copy or symlink anything else manually. Usage: ./ssi_expander.py [source_directory] destination_directory If source_directory is not specified, then the current directory is used. ''' import os import pathlib import ssi import sys def _errorfn(msg, fn): sys.stderr.write('E: %s: %s\n' % (fn, msg)) sys.exit(1) def process(source, dest): for dirpath, dirnames, filenames in os.walk(source): dest_dir = os.path.realpath(os.path.join(dest, os.path.relpath(dirpath, source))) if not os.path.exists(dest_dir): os.mkdir(dest_dir) assert os.path.isdir(dest_dir) for filename in filenames: if not filename.endswith('.html'): continue if filename in ('header.html', 'footer.html'): continue src_path = os.path.abspath(os.path.join(source, dirpath, filename)) dest_path = os.path.join(dest_dir, filename) pathlib.Path(dest_path).unlink(missing_ok=True) with open(dest_path, 'wb') as f: f.write(ssi.InlineIncludes(src_path, _errorfn)) # ignore hidden directories for dirname in dirnames[:]: if dirname.startswith('.'): dirnames.remove(dirname) if __name__ == '__main__': if len(sys.argv) == 2: source = '.' dest = sys.argv[1] elif len(sys.argv) == 3: source, dest = sys.argv[1:] else: _errorfn('Usage: %s [source_directory] destination_directory' % sys.argv[0]) process(source, dest) dygraphs-2.2.1/docs/ssi_server.py010075500000000000000000000041251437353256000137670ustar00#!/usr/bin/python3 ''' Use this in the same way as Python's SimpleHTTPServer: ./ssi_server.py [port] The only difference is that, for files ending in '.html', ssi_server will inline SSI (Server Side Includes) of the form: Run ./ssi_server.py in this directory and visit localhost:8000 for an example. ''' import os import ssi from http.server import SimpleHTTPRequestHandler import http.server import tempfile _errorfnp = None class SSIRequestHandler(SimpleHTTPRequestHandler): """Adds minimal support for directives. The key bit is translate_path, which intercepts requests and serves them using a temporary file which inlines the #includes. """ def __init__(self, request, client_address, server): self.temp_files = [] SimpleHTTPRequestHandler.__init__(self, request, client_address, server) def do_GET(self): SimpleHTTPRequestHandler.do_GET(self) self.delete_temp_files() def do_HEAD(self): SimpleHTTPRequestHandler.do_HEAD(self) self.delete_temp_files() def translate_path(self, path): fs_path = SimpleHTTPRequestHandler.translate_path(self, path) if self.path.endswith('/'): for index in "index.html", "index.htm": index = os.path.join(fs_path, index) if os.path.exists(index): fs_path = index break if fs_path.endswith('.html'): content = ssi.InlineIncludes(fs_path, _errorfnp) fs_path = self.create_temp_file(fs_path, content) return fs_path def delete_temp_files(self): for temp_file in self.temp_files: os.remove(temp_file) def create_temp_file(self, original_path, content): _, ext = os.path.splitext(original_path) fd, path = tempfile.mkstemp(suffix=ext) os.write(fd, content) os.close(fd) self.temp_files.append(path) return path if __name__ == '__main__': import sys def _errorf(msg): sys.stderr.write('ERROR: %s\n' % msg) _errorfnp = _errorf if len(sys.argv) > 1: port = int(sys.argv[1]) else: port = 8081 http.server.test(HandlerClass=SSIRequestHandler, port=port) dygraphs-2.2.1/docs/style.css010064400000000000000000000016041437353256000130770ustar00.thinborder { border-width: 1px; border-spacing: 0px; border-style: solid; border-color: black; border-collapse: collapse; } .thinborder td, .thinborder th { border-width: 1px; padding: 5px; border-style: solid; border-color: black; } #nav { position: absolute; width: 160px; font-size: 12px; line-height: 1em; padding: 0; margin: 0; float: left; } #nav h2 { font-size: 14px; } #nav ul { list-style: none; padding-left: 15px; margin: 0; } #nav ul li { line-height: 1.5em; } #content { position: absolute; left: 160px; top: 0px; border-left: 2px solid rgb(229, 236, 249); padding-left: 20px; margin-left: 0px; max-width: 900px; } #content h1 { font-size: 30px; } pre { padding: 0 10px; } .tests { font-size: 80%; font-weight: bold; } .tests a { font-weight: normal; text-decoration: none; } .padded-list li + li { padding-top: 10px; } .padded-list .desc { font-style: italic; } dygraphs-2.2.1/docs/suzuki-mariners.txt010064400000000000000000000040521437353256000151360ustar00Game,Suzuki,Mariners 1,1/4,7/35 2,1/5,13/39 3,0/4,7/31 4,3/5,13/39 5,2/4,4/30 6,2/5,14/44 7,2/5,11/36 8,2/5,12/36 9,0/5,8/36 10,0/5,8/32 11,1/4,5/29 12,3/4,10/33 13,2/6,10/47 14,0/3,4/28 15,1/3,9/31 16,0/4,7/31 17,2/4,12/37 18,1/4,7/32 19,1/5,15/40 20,1/4,13/40 21,0/5,4/31 22,0/4,5/32 23,1/5,9/39 24,2/4,6/31 25,2/5,14/39 26,3/8,13/55 27,2/4,5/32 28,1/4,5/28 29,2/4,12/36 30,2/4,4/31 31,1/4,8/34 32,1/6,18/50 33,2/4,10/34 34,2/4,8/31 35,2/5,12/34 36,3/5,15/49 37,2/4,9/34 38,2/4,6/33 39,0/3,7/34 40,3/5,13/36 41,3/5,8/35 42,0/3,7/34 43,1/4,9/29 44,1/6,12/48 45,4/5,13/40 46,1/5,8/35 47,2/5,11/36 48,2/5,11/37 49,1/6,8/40 50,3/4,10/31 51,2/4,9/37 52,2/5,10/38 53,1/4,5/31 54,1/2,7/28 55,2/5,11/36 56,2/5,11/33 57,0/4,6/33 58,1/4,7/32 59,1/3,10/30 60,1/3,5/26 61,1/4,14/36 62,0/3,2/27 63,1/3,6/32 64,3/5,11/35 65,0/4,9/32 66,2/4,8/35 67,1/4,6/30 68,1/4,5/32 69,0/3,8/34 70,1/7,12/61 71,0/4,8/33 72,3/5,13/35 73,0/4,3/30 74,1/4,9/32 75,0/4,8/30 76,2/5,9/35 77,3/5,12/36 78,2/4,6/32 79,0/3,6/33 80,1/4,6/32 81,2/5,10/35 82,1/5,13/38 83,2/4,14/36 84,1/4,7/33 85,0/0,9/32 86,2/4,7/32 87,1/4,4/28 88,1/3,7/33 89,1/5,13/38 90,2/4,10/36 91,2/5,10/42 92,4/5,18/43 93,4/5,12/36 94,1/4,7/29 95,1/5,9/32 96,3/5,9/35 97,1/4,8/30 98,3/5,12/35 99,2/5,9/32 100,3/4,8/35 101,5/7,18/56 102,0/4,7/33 103,3/6,14/46 104,2/5,10/34 105,5/5,16/41 106,1/1,15/42 107,3/5,9/35 108,3/6,10/41 109,0/4,2/34 110,2/5,12/36 111,2/5,9/36 112,3/4,10/32 113,2/4,7/32 114,2/5,9/35 115,0/3,7/32 116,1/4,11/36 117,3/4,12/34 118,4/4,20/47 119,0/1,5/29 120,3/4,9/34 121,4/7,21/52 122,1/5,11/35 123,1/4,5/32 124,1/4,7/35 125,0/4,7/33 126,1/5,8/33 127,2/4,14/35 128,1/4,12/33 129,3/6,12/44 130,3/4,8/33 131,3/5,16/39 132,2/3,6/33 133,3/5,11/37 134,1/4,11/37 135,5/5,15/37 136,1/4,9/35 137,2/4,5/30 138,1/5,7/34 139,2/4,9/34 140,0/4,6/33 141,0/4,5/33 142,2/4,6/28 143,0/4,7/34 144,1/4,7/27 145,1/4,6/30 146,0/2,6/33 147,2/4,10/32 148,1/5,9/36 149,0/4,8/33 150,2/4,12/39 151,5/5,18/41 152,4/6,24/47 153,2/4,14/39 154,1/5,8/36 155,1/4,13/37 156,1/4,8/33 157,2/5,13/37 158,1/3,10/35 159,1/5,9/35 160,3/5,18/41 161,1/5,10/37 162,2/4,4/30 dygraphs-2.2.1/docs/temperature-notes.txt010064400000000000000000000023031437353256000154460ustar00Notes on how to reproduce the temperature demo: 2008 page for San Francisco: http://www.wunderground.com/weatherstation/WXDailyHistory.asp?ID=KCASANFR58&graphspan=custom&month=1&day=1&year=2008&monthend=12&dayend=31&yearend=2008 Commands: for y in $(seq 2007 2009); curl "http://www.wunderground.com/weatherstation/WXDailyHistory.asp?ID=KCASANFR58&graphspan=custom&month=1&day=1&year=$y&monthend=12&dayend=31&yearend=$y&format=1" > /tmp/sf-$y.txt for y in $(seq 2007 2009); curl "http://www.wunderground.com/history/airport/KLGA/$y/1/1/CustomHistory.html?dayend=31&monthend=12&yearend=$y&req_city=NA&req_state=NA&req_statename=NA&format=1" > /tmp/ny-$y.txt egrep --no-filename '^200[789]-' /tmp/sf-* | cut -d, -f1,2,3,4 | perl -pe 's/^(\d+)-(\d+)-(\d+),(\d+),(\d+),(\d+)/sprintf("%04d-%02d-%02d,%d;%d;%d", $1, $2, $3, $6, $5, $4)/e' | grep -v ',-' | grep -v ',0' > /tmp/sf.txt egrep --no-filename '^200[789]-' /tmp/ny-* | cut -d, -f1,2,3,4 | perl -pe 's/^(\d+)-(\d+)-(\d+),(\d+),(\d+),(\d+)/sprintf("%04d-%02d-%02d,%d;%d;%d", $1, $2, $3, $6, $5, $4)/e' > /tmp/ny.txt (echo "Date,NY,SF"; join -t, -j1 -a1 /tmp/ny.txt /tmp/sf.txt) | perl -ne 'chomp; @x=split/,/; if (@x==2) {$_.=",";} print "$_\n"' > ny-vs-sf.txt dygraphs-2.2.1/docs/temperatures.csv010064400000000000000000000141131437353256000144610ustar00Date,High,Low 2007-01-01,62,39 2007-01-02,62,44 2007-01-03,62,42 2007-01-04,57,45 2007-01-05,54,44 2007-01-06,55,36 2007-01-07,62,45 2007-01-08,66,48 2007-01-09,63,39 2007-01-10,57,37 2007-01-11,50,37 2007-01-12,48,35 2007-01-13,48,30 2007-01-14,48,28 2007-01-15,53,28 2007-01-16,50,30 2007-01-17,57,37 2007-01-18,61,33 2007-01-19,55,35 2007-01-20,61,35 2007-01-21,64,43 2007-01-22,61,36 2007-01-23,57,35 2007-01-24,60,35 2007-01-25,55,39 2007-01-26,54,44 2007-01-27,57,48 2007-01-28,59,45 2007-01-29,63,45 2007-01-30,59,41 2007-01-31,55,48 2007-02-01,53,46 2007-02-02,55,44 2007-02-03,59,37 2007-02-04,66,39 2007-02-05,64,43 2007-02-06,61,46 2007-02-07,61,51 2007-02-08,60,51 2007-02-09,61,55 2007-02-10,62,55 2007-02-11,61,46 2007-02-12,59,43 2007-02-13,57,46 2007-02-14,61,39 2007-02-15,64,44 2007-02-16,71,46 2007-02-17,73,51 2007-02-18,60,46 2007-02-19,63,44 2007-02-20,57,45 2007-02-21,59,48 2007-02-22,55,44 2007-02-23,55,42 2007-02-24,57,39 2007-02-25,55,48 2007-02-26,57,44 2007-02-27,53,39 2007-02-28,53,37 2007-03-01,54,37 2007-03-02,61,39 2007-03-03,66,43 2007-03-04,70,48 2007-03-05,68,53 2007-03-06,69,46 2007-03-07,62,51 2007-03-08,61,46 2007-03-09,60,45 2007-03-10,68,46 2007-03-11,79,48 2007-03-12,80,52 2007-03-13,73,53 2007-03-14,64,48 2007-03-15,78,46 2007-03-16,78,50 2007-03-17,62,51 2007-03-18,66,46 2007-03-19,64,48 2007-03-20,60,48 2007-03-21,66,46 2007-03-22,73,43 2007-03-23,78,48 2007-03-24,68,48 2007-03-25,64,53 2007-03-26,66,48 2007-03-27,57,46 2007-03-28,66,42 2007-03-29,73,42 2007-03-30,72,46 2007-03-31,69,46 2007-04-01,64,46 2007-04-02,69,46 2007-04-03,71,46 2007-04-04,69,50 2007-04-05,71,52 2007-04-06,64,52 2007-04-07,68,51 2007-04-08,71,51 2007-04-09,66,50 2007-04-10,72,46 2007-04-11,63,50 2007-04-12,64,46 2007-04-13,70,44 2007-04-14,57,51 2007-04-15,68,46 2007-04-16,75,46 2007-04-17,62,48 2007-04-18,61,45 2007-04-19,57,42 2007-04-20,64,46 2007-04-21,61,43 2007-04-22,63,48 2007-04-23,70,44 2007-04-24,66,46 2007-04-25,66,48 2007-04-26,69,48 2007-04-27,82,50 2007-04-28,81,55 2007-04-29,70,53 2007-04-30,77,51 2007-05-01,70,48 2007-05-02,66,52 2007-05-03,63,48 2007-05-04,64,51 2007-05-05,73,46 2007-05-06,88,54 2007-05-07,91,57 2007-05-08,84,60 2007-05-09,73,55 2007-05-10,57,52 2007-05-11,64,51 2007-05-12,64,50 2007-05-13,72,46 2007-05-14,66,50 2007-05-15,63,51 2007-05-16,70,48 2007-05-17,68,50 2007-05-18,73,50 2007-05-19,70,52 2007-05-20,73,51 2007-05-21,78,54 2007-05-22,81,51 2007-05-23,86,55 2007-05-24,78,55 2007-05-25,69,54 2007-05-26,69,55 2007-05-27,69,54 2007-05-28,73,52 2007-05-29,69,53 2007-05-30,66,55 2007-05-31,64,54 2007-06-01,66,54 2007-06-02,64,54 2007-06-03,70,55 2007-06-04,73,59 2007-06-05,68,55 2007-06-06,70,53 2007-06-07,75,51 2007-06-08,70,50 2007-06-09,75,53 2007-06-10,75,55 2007-06-11,75,53 2007-06-12,79,52 2007-06-13,90,59 2007-06-14,89,60 2007-06-15,86,59 2007-06-16,72,55 2007-06-17,79,53 2007-06-18,79,57 2007-06-19,73,55 2007-06-20,71,55 2007-06-21,77,55 2007-06-22,79,54 2007-06-23,77,54 2007-06-24,77,53 2007-06-25,82,53 2007-06-26,71,54 2007-06-27,73,55 2007-06-28,73,57 2007-06-29,77,60 2007-06-30,75,54 2007-07-01,78,54 2007-07-02,82,57 2007-07-03,72,57 2007-07-04,84,59 2007-07-05,84,61 2007-07-06,75,60 2007-07-07,73,55 2007-07-08,78,55 2007-07-09,73,57 2007-07-10,73,59 2007-07-11,78,62 2007-07-12,75,59 2007-07-13,79,60 2007-07-14,73,60 2007-07-15,78,62 2007-07-16,75,59 2007-07-17,77,60 2007-07-18,75,63 2007-07-19,80,59 2007-07-20,79,59 2007-07-21,77,61 2007-07-22,75,63 2007-07-23,79,64 2007-07-24,73,61 2007-07-25,72,57 2007-07-26,75,60 2007-07-27,78,60 2007-07-28,77,57 2007-07-29,73,57 2007-07-30,80,59 2007-07-31,75,59 2007-08-01,75,59 2007-08-02,73,60 2007-08-03,79,60 2007-08-04,77,59 2007-08-05,71,57 2007-08-06,71,59 2007-08-07,73,57 2007-08-08,71,55 2007-08-09,77,60 2007-08-10,77,57 2007-08-11,73,57 2007-08-12,72,55 2007-08-13,75,55 2007-08-14,73,55 2007-08-15,75,57 2007-08-16,79,60 2007-08-17,80,55 2007-08-18,78,57 2007-08-19,77,55 2007-08-20,80,64 2007-08-21,82,62 2007-08-22,82,60 2007-08-23,82,57 2007-08-24,78,59 2007-08-25,73,61 2007-08-26,73,61 2007-08-27,78,59 2007-08-28,86,62 2007-08-29,88,68 2007-08-30,90,68 2007-08-31,80,66 2007-09-01,87,62 2007-09-02,89,61 2007-09-03,78,61 2007-09-04,78,63 2007-09-05,89,57 2007-09-06,82,64 2007-09-07,75,61 2007-09-08,73,62 2007-09-09,71,61 2007-09-10,73,59 2007-09-11,71,59 2007-09-12,72,60 2007-09-13,77,57 2007-09-14,75,60 2007-09-15,73,57 2007-09-16,72,61 2007-09-17,72,55 2007-09-18,73,55 2007-09-19,66,55 2007-09-20,71,52 2007-09-21,77,57 2007-09-22,64,57 2007-09-23,68,55 2007-09-24,78,52 2007-09-25,84,53 2007-09-26,87,57 2007-09-27,75,55 2007-09-28,66,54 2007-09-29,73,52 2007-09-30,75,48 2007-10-01,71,57 2007-10-02,81,53 2007-10-03,73,54 2007-10-04,69,55 2007-10-05,64,50 2007-10-06,73,45 2007-10-07,77,46 2007-10-08,79,53 2007-10-09,72,53 2007-10-10,69,54 2007-10-11,70,48 2007-10-12,64,54 2007-10-13,70,53 2007-10-14,66,51 2007-10-15,68,52 2007-10-16,66,52 2007-10-17,66,50 2007-10-18,73,50 2007-10-19,72,57 2007-10-20,66,54 2007-10-21,73,51 2007-10-22,81,51 2007-10-23,84,53 2007-10-24,79,55 2007-10-25,66,53 2007-10-26,68,46 2007-10-27,66,52 2007-10-28,75,52 2007-10-29,63,55 2007-10-30,63,53 2007-10-31,63,54 2007-11-01,66,53 2007-11-02,77,50 2007-11-03,80,48 2007-11-04,77,48 2007-11-05,66,48 2007-11-06,62,52 2007-11-07,61,48 2007-11-08,59,53 2007-11-09,63,48 2007-11-10,66,48 2007-11-11,63,48 2007-11-12,68,44 2007-11-13,72,51 2007-11-14,75,55 2007-11-15,69,51 2007-11-16,63,55 2007-11-17,66,51 2007-11-18,64,53 2007-11-19,66,48 2007-11-20,63,46 2007-11-21,64,43 2007-11-22,64,37 2007-11-23,70,37 2007-11-24,60,37 2007-11-25,60,46 2007-11-26,63,42 2007-11-27,63,45 2007-11-28,64,46 2007-11-29,62,41 2007-11-30,55,42 2007-12-01,57,37 2007-12-02,61,45 2007-12-03,66,50 2007-12-04,61,54 2007-12-05,60,50 2007-12-06,57,48 2007-12-07,55,45 2007-12-08,53,42 2007-12-09,57,39 2007-12-10,57,39 2007-12-11,57,41 2007-12-12,55,35 2007-12-13,59,34 2007-12-14,55,34 2007-12-15,55,39 2007-12-16,55,43 2007-12-17,57,48 2007-12-18,57,43 2007-12-19,59,41 2007-12-20,55,43 2007-12-21,53,39 2007-12-22,53,32 2007-12-23,55,37 2007-12-24,57,45 2007-12-25,57,37 2007-12-26,53,43 2007-12-27,48,37 2007-12-28,48,43 2007-12-29,57,44 2007-12-30,52,43 2007-12-31,57,42 dygraphs-2.2.1/docs/temperatures.txt010064400000000000000000000133361437353256000145130ustar00Date,High,Low 200701011,62,39 200701021,62,44 200701031,62,42 200701041,57,45 200701051,54,44 200701061,55,36 200701071,62,45 200701081,66,48 200701091,63,39 200701101,57,37 200701111,50,37 200701121,48,35 200701131,48,30 200701141,48,28 200701151,53,28 200701161,50,30 200701171,57,37 200701181,61,33 200701191,55,35 200701201,61,35 200701211,64,43 200701221,61,36 200701231,57,35 200701241,60,35 200701251,55,39 200701261,54,44 200701271,57,48 200701281,59,45 200701291,63,45 200701301,59,41 200701311,55,48 200702011,53,46 200702021,55,44 200702031,59,37 200702041,66,39 200702051,64,43 200702061,61,46 200702071,61,51 200702081,60,51 200702091,61,55 200702101,62,55 200702111,61,46 200702121,59,43 200702131,57,46 200702141,61,39 200702151,64,44 200702161,71,46 200702171,73,51 200702181,60,46 200702191,63,44 200702201,57,45 200702211,59,48 200702221,55,44 200702231,55,42 200702241,57,39 200702251,55,48 200702261,57,44 200702271,53,39 200702281,53,37 200703011,54,37 200703021,61,39 200703031,66,43 200703041,70,48 200703051,68,53 200703061,69,46 200703071,62,51 200703081,61,46 200703091,60,45 200703101,68,46 200703111,79,48 200703121,80,52 200703131,73,53 200703141,64,48 200703151,78,46 200703161,78,50 200703171,62,51 200703181,66,46 200703191,64,48 200703201,60,48 200703211,66,46 200703221,73,43 200703231,78,48 200703241,68,48 200703251,64,53 200703261,66,48 200703271,57,46 200703281,66,42 200703291,73,42 200703301,72,46 200703311,69,46 200704011,64,46 200704021,69,46 200704031,71,46 200704041,69,50 200704051,71,52 200704061,64,52 200704071,68,51 200704081,71,51 200704091,66,50 200704101,72,46 200704111,63,50 200704121,64,46 200704131,70,44 200704141,57,51 200704151,68,46 200704161,75,46 200704171,62,48 200704181,61,45 200704191,57,42 200704201,64,46 200704211,61,43 200704221,63,48 200704231,70,44 200704241,66,46 200704251,66,48 200704261,69,48 200704271,82,50 200704281,81,55 200704291,70,53 200704301,77,51 200705011,70,48 200705021,66,52 200705031,63,48 200705041,64,51 200705051,73,46 200705061,88,54 200705071,91,57 200705081,84,60 200705091,73,55 200705101,57,52 200705111,64,51 200705121,64,50 200705131,72,46 200705141,66,50 200705151,63,51 200705161,70,48 200705171,68,50 200705181,73,50 200705191,70,52 200705201,73,51 200705211,78,54 200705221,81,51 200705231,86,55 200705241,78,55 200705251,69,54 200705261,69,55 200705271,69,54 200705281,73,52 200705291,69,53 200705301,66,55 200705311,64,54 200706011,66,54 200706021,64,54 200706031,70,55 200706041,73,59 200706051,68,55 200706061,70,53 200706071,75,51 200706081,70,50 200706091,75,53 200706101,75,55 200706111,75,53 200706121,79,52 200706131,90,59 200706141,89,60 200706151,86,59 200706161,72,55 200706171,79,53 200706181,79,57 200706191,73,55 200706201,71,55 200706211,77,55 200706221,79,54 200706231,77,54 200706241,77,53 200706251,82,53 200706261,71,54 200706271,73,55 200706281,73,57 200706291,77,60 200706301,75,54 200707011,78,54 200707021,82,57 200707031,72,57 200707041,84,59 200707051,84,61 200707061,75,60 200707071,73,55 200707081,78,55 200707091,73,57 200707101,73,59 200707111,78,62 200707121,75,59 200707131,79,60 200707141,73,60 200707151,78,62 200707161,75,59 200707171,77,60 200707181,75,63 200707191,80,59 200707201,79,59 200707211,77,61 200707221,75,63 200707231,79,64 200707241,73,61 200707251,72,57 200707261,75,60 200707271,78,60 200707281,77,57 200707291,73,57 200707301,80,59 200707311,75,59 200708011,75,59 200708021,73,60 200708031,79,60 200708041,77,59 200708051,71,57 200708061,71,59 200708071,73,57 200708081,71,55 200708091,77,60 200708101,77,57 200708111,73,57 200708121,72,55 200708131,75,55 200708141,73,55 200708151,75,57 200708161,79,60 200708171,80,55 200708181,78,57 200708191,77,55 200708201,80,64 200708211,82,62 200708221,82,60 200708231,82,57 200708241,78,59 200708251,73,61 200708261,73,61 200708271,78,59 200708281,86,62 200708291,88,68 200708301,90,68 200708311,80,66 200709011,87,62 200709021,89,61 200709031,78,61 200709041,78,63 200709051,89,57 200709061,82,64 200709071,75,61 200709081,73,62 200709091,71,61 200709101,73,59 200709111,71,59 200709121,72,60 200709131,77,57 200709141,75,60 200709151,73,57 200709161,72,61 200709171,72,55 200709181,73,55 200709191,66,55 200709201,71,52 200709211,77,57 200709221,64,57 200709231,68,55 200709241,78,52 200709251,84,53 200709261,87,57 200709271,75,55 200709281,66,54 200709291,73,52 200709301,75,48 200710011,71,57 200710021,81,53 200710031,73,54 200710041,69,55 200710051,64,50 200710061,73,45 200710071,77,46 200710081,79,53 200710091,72,53 200710101,69,54 200710111,70,48 200710121,64,54 200710131,70,53 200710141,66,51 200710151,68,52 200710161,66,52 200710171,66,50 200710181,73,50 200710191,72,57 200710201,66,54 200710211,73,51 200710221,81,51 200710231,84,53 200710241,79,55 200710251,66,53 200710261,68,46 200710271,66,52 200710281,75,52 200710291,63,55 200710301,63,53 200710311,63,54 200711011,66,53 200711021,77,50 200711031,80,48 200711041,77,48 200711051,66,48 200711061,62,52 200711071,61,48 200711081,59,53 200711091,63,48 200711101,66,48 200711111,63,48 200711121,68,44 200711131,72,51 200711141,75,55 200711151,69,51 200711161,63,55 200711171,66,51 200711181,64,53 200711191,66,48 200711201,63,46 200711211,64,43 200711221,64,37 200711231,70,37 200711241,60,37 200711251,60,46 200711261,63,42 200711271,63,45 200711281,64,46 200711291,62,41 200711301,55,42 200712011,57,37 200712021,61,45 200712031,66,50 200712041,61,54 200712051,60,50 200712061,57,48 200712071,55,45 200712081,53,42 200712091,57,39 200712101,57,39 200712111,57,41 200712121,55,35 200712131,59,34 200712141,55,34 200712151,55,39 200712161,55,43 200712171,57,48 200712181,57,43 200712191,59,41 200712201,55,43 200712211,53,39 200712221,53,32 200712231,55,37 200712241,57,45 200712251,57,37 200712261,53,43 200712271,48,37 200712281,48,43 200712291,57,44 200712301,52,43 200712311,57,42 dygraphs-2.2.1/docs/tutorial.html010064400000000000000000000446671437353256000137760ustar00