diff options
Diffstat (limited to 'addons/web_graph/static')
66 files changed, 25176 insertions, 26043 deletions
diff --git a/addons/web_graph/static/lib/dropdown.js b/addons/web_graph/static/lib/dropdown.js deleted file mode 100644 index 54b61c5e9d0..00000000000 --- a/addons/web_graph/static/lib/dropdown.js +++ /dev/null @@ -1,92 +0,0 @@ -/* ============================================================ - * bootstrap-dropdown.js v2.0.2 - * http://twitter.github.com/bootstrap/javascript.html#dropdowns - * ============================================================ - * Copyright 2012 Twitter, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ============================================================ */ - - -!function( $ ){ - - "use strict" - - /* DROPDOWN CLASS DEFINITION - * ========================= */ - - var toggle = '[data-toggle="dropdown"]' - , Dropdown = function ( element ) { - var $el = $(element).on('click.dropdown.data-api', this.toggle) - $('html').on('click.dropdown.data-api', function () { - $el.parent().removeClass('open') - }) - } - - Dropdown.prototype = { - - constructor: Dropdown - - , toggle: function ( e ) { - var $this = $(this) - , selector = $this.attr('data-target') - , $parent - , isActive - - if (!selector) { - selector = $this.attr('href') - selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7 - } - - $parent = $(selector) - $parent.length || ($parent = $this.parent()) - - isActive = $parent.hasClass('open') - - clearMenus() - !isActive && $parent.toggleClass('open') - - return false - } - - } - - function clearMenus() { - $(toggle).parent().removeClass('open') - } - - - /* DROPDOWN PLUGIN DEFINITION - * ========================== */ - - $.fn.dropdown = function ( option ) { - return this.each(function () { - var $this = $(this) - , data = $this.data('dropdown') - if (!data) $this.data('dropdown', (data = new Dropdown(this))) - if (typeof option == 'string') data[option].call($this) - }) - } - - $.fn.dropdown.Constructor = Dropdown - - - /* APPLY TO STANDARD DROPDOWN ELEMENTS - * =================================== */ - - $(function () { - $('html').on('click.dropdown.data-api', clearMenus) - $('body').on('click.dropdown.data-api', toggle, Dropdown.prototype.toggle) - }) - -}( window.jQuery );
\ No newline at end of file diff --git a/addons/web_graph/static/lib/flotr2/LICENSE b/addons/web_graph/static/lib/flotr2/LICENSE deleted file mode 100644 index eeb8ce52b3b..00000000000 --- a/addons/web_graph/static/lib/flotr2/LICENSE +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (c) 2012 Carl Sutherland
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE.
diff --git a/addons/web_graph/static/lib/flotr2/Makefile b/addons/web_graph/static/lib/flotr2/Makefile deleted file mode 100644 index 70157b0f3fa..00000000000 --- a/addons/web_graph/static/lib/flotr2/Makefile +++ /dev/null @@ -1,40 +0,0 @@ -all: test flotr2 - -test: - cd spec; jasmine-headless-webkit -j jasmine.yml -c - -libraries: - smoosh make/lib.json - cat ./build/bean.js > build/lib.js - cat ./build/underscore.js >> build/lib.js - cat ./build/bean.min.js > build/lib.min.js - echo ";" >> build/lib.min.js - cat ./build/underscore.min.js >> build/lib.min.js - echo ";" >> build/lib.min.js - -ie: - smoosh make/ie.json - -flotr2: libraries ie - smoosh make/flotr2.json - cat build/lib.js build/flotr2.js > flotr2.js - cat build/lib.min.js > flotr2.min.js - cat build/flotr2.min.js >> flotr2.min.js - echo ';' >> flotr2.min.js - cp build/ie.min.js flotr2.ie.min.js - -flotr2-basic: libraries ie - smoosh make/basic.json - cat build/lib.min.js > flotr2-basic.min.js - cat build/flotr2-basic.min.js >> flotr2-basic.min.js - -flotr2-standalone: ie - smoosh make/flotr2.json - cat build/flotr2.js > flotr2.js - cp build/ie.min.js flotr2.ie.min.js - -flotr-examples: - smoosh make/examples.json - cp build/examples.min.js flotr2.examples.min.js - cp build/examples-types.js flotr2.examples.types.js - diff --git a/addons/web_graph/static/lib/flotr2/README.md b/addons/web_graph/static/lib/flotr2/README.md deleted file mode 100644 index 46f180c0fc6..00000000000 --- a/addons/web_graph/static/lib/flotr2/README.md +++ /dev/null @@ -1,89 +0,0 @@ -Flotr2 -====== - -The Canvas graphing library. - - - -http://groups.google.com/group/flotr2/ - -Please fork http://jsfiddle.net/cesutherland/ZFBj5/ with your question or bug reproduction case. - - -API ---- - -The API consists of a primary draw method which accepts a configuration object, helper methods, and several microlibs. - -### Example - -```javascript - var - // Container div: - container = document.getElementById("flotr-example-graph"), - // First data series: - d1 = [[0, 3], [4, 8], [8, 5], [9, 13]], - // Second data series: - d2 = [], - // A couple flotr configuration options: - options = { - xaxis: { - minorTickFreq: 4 - }, - grid: { - minorVerticalLines: true - } - }, - i, graph; - - // Generated second data set: - for (i = 0; i < 14; i += 0.5) { - d2.push([i, Math.sin(i)]); - } - - // Draw the graph: - graph = Flotr.draw( - container, // Container element - [ d1, d2 ], // Array of data series - options // Configuration options - ); -``` - -### Microlibs - -* [underscore.js](http://documentcloud.github.com/underscore/) -* [bean.js](https://github.com/fat/bean) - -Extending ---------- - -Flotr may be extended by adding new plugins and graph types. - -### Graph Types - -Graph types define how a particular chart is rendered. Examples include line, bar, pie. - -Existing graph types are found in `js/types/`. - -### Plugins - -Plugins extend the core of flotr with new functionality. They can add interactions, new decorations, etc. Examples -include titles, labels and selection. - -The plugins included are found in `js/plugins/`. - -Development ------------ - -This project uses [smoosh](https://github.com/fat/smoosh) to build and [jasmine](http://pivotal.github.com/jasmine/) -with [js-imagediff](https://github.com/HumbleSoftware/js-imagediff) to test. Tests may be executed by -[jasmine-headless-webkit](http://johnbintz.github.com/jasmine-headless-webkit/) with -`cd spec; jasmine-headless-webkit -j jasmine.yml -c` or by a browser by navigating to -`flotr2/spec/SpecRunner.html`. - -Shoutouts ---------- - -Thanks to Bas Wenneker, Fabien Ménager and others for all the work on the original Flotr. -Thanks to Jochen Berger and Jordan Santell for their contributions to Flotr2. - diff --git a/addons/web_graph/static/lib/flotr2/dev/notes.txt b/addons/web_graph/static/lib/flotr2/dev/notes.txt deleted file mode 100644 index 982474e579f..00000000000 --- a/addons/web_graph/static/lib/flotr2/dev/notes.txt +++ /dev/null @@ -1,86 +0,0 @@ -Flotr 2 Architecture Notes - - -Global: -====== - -Flotr.js - - versioning information - browser detection - extension (plugins, graph types) - draw - clone / merge - tick size - tick formatter - engineering notation - magnitude - rad, pixel, floor - drawText - measureText - getBestTextAlign - align map - compatibility - - -Graph Architecture: -=================== - -Axis - - all series - orientation - ticks (major, minor) - scale (d2p, p2d, logarithmic) - notion of stacks - -Series - - per 'data' - notion of range (x, y, min, max) - -Graph - - DOM constructon - event attachment - options initialization - data range calculations - canvas spacing calculations - event normalization - draw methods - DOM cleanup - event cleanup - - -Utilities: -========== - -Color - build colors - parse textual color data - convert colors - clone colors - -Text - calculate text size - canvas size - html size - -Date - formatting - constants - - -Spacing Calculation -=================== - -Flotr - calculate data - calculate margins - -Chart - calculate Data Ranges - Explicit or auto data minimum, maximums - calculate Data Range Extensions - By chart type, extend data range with needs of chart type (ie. stacked bars, stacked lines) - add Chart Padding - By chart type - -Text - use explicit margins - calculate label margins - calculate title margins - diff --git a/addons/web_graph/static/lib/flotr2/flotr2.examples.min.js b/addons/web_graph/static/lib/flotr2/flotr2.examples.min.js deleted file mode 100644 index 7bbf25592a3..00000000000 --- a/addons/web_graph/static/lib/flotr2/flotr2.examples.min.js +++ /dev/null @@ -1,2 +0,0 @@ - -(function(){var a=Flotr.EventAdapter,b=Flotr._,c="click",d="example",e="mouseenter",f="mouseleave",g=".",h="flotr-examples",i="flotr-examples-container",j="flotr-examples-reset",k="flotr-examples-thumbs",l="flotr-examples-thumb",m="flotr-examples-collapsed",n="flotr-examples-highlight",o="flotr-examples-large",p="flotr-examples-medium",q="flotr-examples-small",r="flotr-examples-mobile",s='<div class="'+l+'"></div>',t='<div class="'+h+'">'+'<div class="'+j+'">View All</div>'+'<div class="'+k+'"></div>'+'<div class="'+i+'"></div>'+"</div>";Examples=function(a){if(b.isUndefined(Flotr.ExampleList))throw"Flotr.ExampleList not defined.";this.options=a,this.list=Flotr.ExampleList,this.current=null,this.single=!1,this._initNodes(),this._example=new Flotr.Examples.Example({node:this._exampleNode}),this._initExamples()},Examples.prototype={examples:function(){function f(b){var c=$(b.currentTarget),e=c.data("example"),f=b.data.orientation;f^c.hasClass(n)&&(c.toggleClass(n).css(a),d._example.executeCallback(e,c))}var a={cursor:"pointer"},b=this._thumbsNode,c=this.list.get(),d=this,e=["basic","basic-axis","basic-bars","basic-bars-horizontal","basic-bar-stacked","basic-stacked-horizontal","basic-pie","basic-radar","basic-bubble","basic-candle","basic-legend","mouse-tracking","mouse-zoom","mouse-drag","basic-time","negative-values","click-example","download-image","download-data","advanced-titles","color-gradients","basic-timeline","advanced-markers"];(function h(){var a=e.shift(),f=c[a];if(f.type==="profile"||f.type==="test")return;var g=$(s);g.data("example",f),b.append(g),d._example.executeCallback(f,g),g.click(function(){d._loadExample(f)}),e.length&&setTimeout(h,20)})(),b.delegate(g+l,"mouseenter",{orientation:!0},f),b.delegate(g+l,"mouseleave",{orientation:!1},f)},_loadExample:function(a){a&&(window.location.hash="!"+(this.single?"single/":"")+a.key,u||(this._thumbsNode.css({position:"absolute",height:"0px",overflow:"hidden",width:"0px"}),this._resetNode.css({top:"16px"})),this._examplesNode.addClass(m),this._exampleNode.show(),this._example.setExample(a),this._resize(),$(document).scrollTop(0))},_reset:function(){window.location.hash="",u||this._thumbsNode.css({position:"",height:"",overflow:"",width:""}),this._examplesNode.removeClass(m),this._thumbsNode.height(""),this._exampleNode.hide()},_initNodes:function(){var a=$(this.options.node),b=this,c=$(t);b._resetNode=c.find(g+j),b._exampleNode=c.find(g+i),b._thumbsNode=c.find(g+k),b._examplesNode=c,b._resetNode.click(function(){b._reset()}),a.append(c),this._initResizer()},_initResizer:function(){function e(){var b=c.height()-(a.options.thumbPadding||0),e=c.width(),f;e>1760?(f=o,a._thumbsNode.height(b)):e>1140?(f=p,a._thumbsNode.height(b)):(f=q,a._thumbsNode.height("")),d!==f&&(d&&a._examplesNode.removeClass(d),a._examplesNode.addClass(f),d=f)}var a=this,b=a._examplesNode,c=$(window),d;$(window).resize(e),e(),this._resize=e},_initExamples:function(){var a=window.location.hash,b,c;a?(a=a.substring(2),c=a.split("/"),c.length==1?(b=this.list.get(a),this.examples()):c[0]=="single"&&(this.single=!0,b=this.list.get(c[1])),this._loadExample(b)):this.examples()}};var u=function(){var a=!!(navigator.userAgent.match(/Android/i)||navigator.userAgent.match(/webOS/i)||navigator.userAgent.match(/iPhone/i)||navigator.userAgent.match(/iPod/i)),b=!!$.browser.mozilla;return!a||b}();Flotr.Examples=Examples})(),function(){var a=Flotr._,b=".",c="flotr-example",d="flotr-example-label",e="flotr-example-title",f="flotr-example-description",g="flotr-example-editor",h="flotr-example-graph",i='<div class="'+c+'">'+'<div class="'+d+" "+e+'"></div>'+'<div class="'+f+'"></div>'+'<div class="'+g+'"></div>'+"</div>",j=function(a){this.options=a,this.example=null,this._initNodes()};j.prototype={setExample:function(a){var b=this.getSource(a),c=this._editorNode;this.example=a,Math.seedrandom(a.key),this._exampleNode.css({display:"block"}),this._titleNode.html(a.name||""),this._markupNode.html(a.description||""),this._editor?this._editor.setExample(b):this._editor=new Flotr.Examples.Editor(c,{example:b,teardown:function(){Flotr.EventAdapter.stopObserving($(c).find(".render")[0]),$(c).find("canvas").each(function(a,b){Flotr.EventAdapter.stopObserving(b)})}})},getSource:function(a){var b=a.callback.toString();return navigator.userAgent.search(/firefox/i)!==-1&&(b=js_beautify(b)),b},executeCallback:function(b,c){a.isElement(c)||(c=c[0]);var d=b.args?[c].concat(b.args):[c];return Math.seedrandom(b.key),b.callback.apply(this,d)},_initNodes:function(){var a=this.options.node,c=$(i);this._titleNode=c.find(b+e),this._markupNode=c.find(b+f),this._editorNode=c.find(b+g),this._exampleNode=c,a.append(c)}},Flotr.Examples.Example=j}(),function(){function Editor(a,b){function o(){i.hide(),f&&f.call(),m.render({example:d,render:h})}function p(a,b,c){var d=!1,e='<span class="error">Error: </span>',f,g;e+='<span class="message">'+a+"</span>",typeof c!="undefined"&&(e+='<span class="position">',e+='Line <span class="line">'+c+"</span>",console.log(b),b&&(e+=" of ",b==window.location?(e+='<span class="url">script</span>',!d):e+='<span class="url">'+b+"</span>"),e+=".</span>"),i.show(),i.html(e)}var c=b.type||"javascript",d=b.example||"",e=b.noRun||!1,f=b.teardown||!1,g=$(T_CONTROLS),h=$(T_RENDER),i=$(T_ERRORS),j=$(T_SOURCE),k=$(T_EDITOR),l="editor-render-"+COUNT,m,h,n;m=new TYPES[c]({onerror:p});if(!m)throw"Invalid type: API not found for type `"+c+"`.";h.attr("id",l),i.hide(),k.append(h).append(g).append(j).addClass(c).addClass(e?"no-run":""),a=$(a),a.append(k),j.append(i),d=m.example({example:d,render:h}),n=CodeMirror(j[0],{value:d,readOnly:e,lineNumbers:!0,mode:m.codeMirrorType}),e||(g.delegate(".run","click",function(){d=n.getValue(),o()}),o()),window.onerror=function(a,b,c){return p(a,b,c),console.log(a),ONERROR&&$.isFunction(ONERROR)?ONERROR(a,b,c):!1},COUNT++,this.setExample=function(a){d=m.example({example:a,render:h}),n.setValue(d),n.refresh(),o()}}var ONERROR=window.onerror,COUNT=0,TYPES={},T_CONTROLS='<div class="controls"><button class="run btn large primary">Run</button></div>',T_EDITOR='<div class="editor"></div>',T_SOURCE='<div class="source"></div>',T_ERRORS='<div class="errors"></div>',T_RENDER='<div class="render"></div>',T_IFRAME="<iframe></iframe>";TYPES.javascript=function(b){this.onerror=b.onerror},TYPES.javascript.prototype={codeMirrorType:"javascript",example:function(a){var b=a.example,c=a.render,d=$(c).attr("id");return"("+b+')(document.getElementById("'+d+'"));'},render:function(o){eval(o.example)}},TYPES.html=function(b){this.onerror=b.onerror},TYPES.html.prototype={codeMirrorType:"htmlmixed",example:function(a){return $.trim(a.example)},render:function(a){var b=a.example,c=a.render,d=$(T_IFRAME),e=this,f,g;c.html(d),f=d[0].contentWindow,g=f.document,g.open(),f.onerror=d.onerror=function(){e.onerror.apply(null,arguments)},g.write(b),g.close()}},typeof Flotr.Examples=="undefined"&&(Flotr.Examples={}),Flotr.Examples.Editor=Editor}(),function(){var a=Flotr.DOM,b=Flotr.EventAdapter,c=Flotr._,d="click",e="example-profile",f="examples",g=function(a){if(c.isUndefined(Flotr.ExampleList))throw"Flotr.ExampleList not defined.";this.editMode="off",this.list=Flotr.ExampleList,this.current=null,this.single=!1,this.init()};g.prototype=c.extend({},Flotr.Examples.prototype,{examples:function(){var e=document.getElementById(f),g=a.node("<ul></ul>"),h;c.each(this.list.getType("profile"),function(e){h=a.node("<li>"+e.name+"</li>"),a.insert(g,h),b.observe(h,d,c.bind(function(){this.example(e)},this))},this),a.insert(e,g)},example:function(a){this._renderSource(a),this.profileStart(a),setTimeout(c.bind(function(){this._renderGraph(a),this.profileEnd()},this),50)},profileStart:function(a){var b=document.getElementById(e);this._startTime=new Date,b.innerHTML='<div>Profile started for "'+a.name+'"...</div>'},profileEnd:function(a){var b=document.getElementById(e);profileTime=new Date-this._startTime,this._startTime=null,b.innerHTML+="<div>Profile complete: "+profileTime+"ms<div>"}}),Flotr.Profile=g}()
\ No newline at end of file diff --git a/addons/web_graph/static/lib/flotr2/flotr2.examples.types.js b/addons/web_graph/static/lib/flotr2/flotr2.examples.types.js deleted file mode 100644 index f22bc9596f9..00000000000 --- a/addons/web_graph/static/lib/flotr2/flotr2.examples.types.js +++ /dev/null @@ -1,1425 +0,0 @@ -(function () { - -var ExampleList = function () { - - // Map of examples. - this.examples = {}; - -}; - -ExampleList.prototype = { - - add : function (example) { - this.examples[example.key] = example; - }, - - get : function (key) { - return key ? (this.examples[key] || null) : this.examples; - }, - - getType : function (type) { - return Flotr._.select(this.examples, function (example) { - return (example.type === type); - }); - } -} - -Flotr.ExampleList = new ExampleList(); - -})(); - -(function () { - -Flotr.ExampleList.add({ - key : 'basic', - name : 'Basic', - callback : basic -}); - -function basic (container) { - - var - d1 = [[0, 3], [4, 8], [8, 5], [9, 13]], // First data series - d2 = [], // Second data series - i, graph; - - // Generate first data set - for (i = 0; i < 14; i += 0.5) { - d2.push([i, Math.sin(i)]); - } - - // Draw Graph - graph = Flotr.draw(container, [ d1, d2 ], { - xaxis: { - minorTickFreq: 4 - }, - grid: { - minorVerticalLines: true - } - }); -} - -})(); - -(function () { - -Flotr.ExampleList.add({ - key : 'basic-stacked', - name : 'Basic Stacked', - callback : basic_stacked, - type : 'test' -}); - -function basic_stacked (container) { - - var - d1 = [[0, 3], [4, 8], [8, 2], [9, 3]], // First data series - d2 = [[0, 2], [4, 3], [8, 8], [9, 4]], // Second data series - i, graph; - - // Draw Graph - graph = Flotr.draw(container, [ d1, d2 ], { - lines: { - show : true, - stacked: true - }, - xaxis: { - minorTickFreq: 4 - }, - grid: { - minorVerticalLines: true - } - }); -} - -})(); - -(function () { - -Flotr.ExampleList.add({ - key : 'basic-stepped', - name : 'Basic Stepped', - callback : basic_stepped, - type : 'test' -}); - -function basic_stepped (container) { - - var - d1 = [[0, 3], [4, 8], [8, 5], [9, 13]], // First data series - d2 = [], // Second data series - i, graph; - - // Generate first data set - for (i = 0; i < 14; i += 0.5) { - d2.push([i, Math.sin(i)]); - } - - // Draw Graph - graph = Flotr.draw(container, [ d1, d2 ], { - lines: { - steps : true, - show : true - }, - xaxis: { - minorTickFreq: 4 - }, - yaxis: { - autoscale: true - }, - grid: { - minorVerticalLines: true - }, - mouse : { - track : true, - relative : true - } - }); -} - -})(); - -(function () { - -Flotr.ExampleList.add({ - key : 'basic-axis', - name : 'Basic Axis', - callback : basic_axis -}); - -function basic_axis (container) { - - var - d1 = [], - d2 = [], - d3 = [], - d4 = [], - d5 = [], // Data - ticks = [[ 0, "Lower"], 10, 20, 30, [40, "Upper"]], // Ticks for the Y-Axis - graph; - - for(var i = 0; i <= 10; i += 0.1){ - d1.push([i, 4 + Math.pow(i,1.5)]); - d2.push([i, Math.pow(i,3)]); - d3.push([i, i*5+3*Math.sin(i*4)]); - d4.push([i, i]); - if( i.toFixed(1)%1 == 0 ){ - d5.push([i, 2*i]); - } - } - - d3[30][1] = null; - d3[31][1] = null; - - function ticksFn (n) { return '('+n+')'; } - - graph = Flotr.draw(container, [ - { data : d1, label : 'y = 4 + x^(1.5)', lines : { fill : true } }, - { data : d2, label : 'y = x^3'}, - { data : d3, label : 'y = 5x + 3sin(4x)'}, - { data : d4, label : 'y = x'}, - { data : d5, label : 'y = 2x', lines : { show : true }, points : { show : true } } - ], { - xaxis : { - noTicks : 7, // Display 7 ticks. - tickFormatter : ticksFn, // Displays tick values between brackets. - min : 1, // Part of the series is not displayed. - max : 7.5 // Part of the series is not displayed. - }, - yaxis : { - ticks : ticks, // Set Y-Axis ticks - max : 40 // Maximum value along Y-Axis - }, - grid : { - verticalLines : false, - backgroundColor : { - colors : [[0,'#fff'], [1,'#ccc']], - start : 'top', - end : 'bottom' - } - }, - legend : { - position : 'nw' - }, - title : 'Basic Axis example', - subtitle : 'This is a subtitle' - }); -} - -})(); - -(function () { - -Flotr.ExampleList.add({ - key : 'basic-bars', - name : 'Basic Bars', - callback : basic_bars -}); - -Flotr.ExampleList.add({ - key : 'basic-bars-horizontal', - name : 'Horizontal Bars', - args : [true], - callback : basic_bars, - tolerance : 5 -}); - -function basic_bars (container, horizontal) { - - var - horizontal = (horizontal ? true : false), // Show horizontal bars - d1 = [], // First data series - d2 = [], // Second data series - point, // Data point variable declaration - i; - - for (i = 0; i < 4; i++) { - - if (horizontal) { - point = [Math.ceil(Math.random()*10), i]; - } else { - point = [i, Math.ceil(Math.random()*10)]; - } - - d1.push(point); - - if (horizontal) { - point = [Math.ceil(Math.random()*10), i+0.5]; - } else { - point = [i+0.5, Math.ceil(Math.random()*10)]; - } - - d2.push(point); - }; - - // Draw the graph - Flotr.draw( - container, - [d1, d2], - { - bars : { - show : true, - horizontal : horizontal, - shadowSize : 0, - barWidth : 0.5 - }, - mouse : { - track : true, - relative : true - }, - yaxis : { - min : 0, - autoscaleMargin : 1 - } - } - ); -} - -})(); - -(function () { - -Flotr.ExampleList.add({ - key : 'basic-bar-stacked', - name : 'Stacked Bars', - callback : bars_stacked -}); - -Flotr.ExampleList.add({ - key : 'basic-stacked-horizontal', - name : 'Stacked Horizontal Bars', - args : [true], - callback : bars_stacked, - tolerance : 5 -}); - -function bars_stacked (container, horizontal) { - - var - d1 = [], - d2 = [], - d3 = [], - graph, i; - - for (i = -10; i < 10; i++) { - if (horizontal) { - d1.push([Math.random(), i]); - d2.push([Math.random(), i]); - d3.push([Math.random(), i]); - } else { - d1.push([i, Math.random()]); - d2.push([i, Math.random()]); - d3.push([i, Math.random()]); - } - } - - graph = Flotr.draw(container,[ - { data : d1, label : 'Serie 1' }, - { data : d2, label : 'Serie 2' }, - { data : d3, label : 'Serie 3' } - ], { - legend : { - backgroundColor : '#D2E8FF' // Light blue - }, - bars : { - show : true, - stacked : true, - horizontal : horizontal, - barWidth : 0.6, - lineWidth : 1, - shadowSize : 0 - }, - grid : { - verticalLines : horizontal, - horizontalLines : !horizontal - } - }); -} - -})(); - -(function () { - -Flotr.ExampleList.add({ - key : 'basic-pie', - name : 'Basic Pie', - callback : basic_pie -}); - -function basic_pie (container) { - - var - d1 = [[0, 4]], - d2 = [[0, 3]], - d3 = [[0, 1.03]], - d4 = [[0, 3.5]], - graph; - - graph = Flotr.draw(container, [ - { data : d1, label : 'Comedy' }, - { data : d2, label : 'Action' }, - { data : d3, label : 'Romance', - pie : { - explode : 50 - } - }, - { data : d4, label : 'Drama' } - ], { - HtmlText : false, - grid : { - verticalLines : false, - horizontalLines : false - }, - xaxis : { showLabels : false }, - yaxis : { showLabels : false }, - pie : { - show : true, - explode : 6 - }, - mouse : { track : true }, - legend : { - position : 'se', - backgroundColor : '#D2E8FF' - } - }); -} - -})(); - -(function () { - -Flotr.ExampleList.add({ - key : 'basic-radar', - name : 'Basic Radar', - callback : basic_radar -}); - -function basic_radar (container) { - - // Fill series s1 and s2. - var - s1 = { label : 'Actual', data : [[0, 3], [1, 8], [2, 5], [3, 5], [4, 3], [5, 9]] }, - s2 = { label : 'Target', data : [[0, 8], [1, 7], [2, 8], [3, 2], [4, 4], [5, 7]] }, - graph, ticks; - - // Radar Labels - ticks = [ - [0, "Statutory"], - [1, "External"], - [2, "Videos"], - [3, "Yippy"], - [4, "Management"], - [5, "oops"] - ]; - - // Draw the graph. - graph = Flotr.draw(container, [ s1, s2 ], { - radar : { show : true}, - grid : { circular : true, minorHorizontalLines : true}, - yaxis : { min : 0, max : 10, minorTickFreq : 2}, - xaxis : { ticks : ticks} - }); -} - -})(); - -(function () { - -Flotr.ExampleList.add({ - key : 'basic-bubble', - name : 'Basic Bubble', - callback : basic_bubble -}); - -function basic_bubble (container) { - - var - d1 = [], - d2 = [], - point, graph, i; - - for (i = 0; i < 10; i++ ){ - point = [i, Math.ceil(Math.random()*10), Math.ceil(Math.random()*10)]; - d1.push(point); - - point = [i, Math.ceil(Math.random()*10), Math.ceil(Math.random()*10)]; - d2.push(point); - } - - // Draw the graph - graph = Flotr.draw(container, [d1, d2], { - bubbles : { show : true, baseRadius : 5 }, - xaxis : { min : -4, max : 14 }, - yaxis : { min : -4, max : 14 } - }); -} - -})(); - -(function () { - -Flotr.ExampleList.add({ - key : 'basic-candle', - name : 'Basic Candle', - callback : basic_candle -}); - -function basic_candle (container) { - - var - d1 = [], - price = 3.206, - graph, - i, a, b, c; - - for (i = 0; i < 50; i++) { - a = Math.random(); - b = Math.random(); - c = (Math.random() * (a + b)) - b; - d1.push([i, price, price + a, price - b, price + c]); - price = price + c; - } - - // Graph - graph = Flotr.draw(container, [ d1 ], { - candles : { show : true, candleWidth : 0.6 }, - xaxis : { noTicks : 10 } - }); -} - -})(); - - -(function () { - -Flotr.ExampleList.add({ - key : 'basic-legend', - name : 'Basic Legend', - callback : basic_legend -}); - -function basic_legend (container) { - - var - d1 = [], - d2 = [], - d3 = [], - data, - graph, i; - - // Data Generation - for (i = 0; i < 15; i += 0.5) { - d1.push([i, i + Math.sin(i+Math.PI)]); - d2.push([i, i]); - d3.push([i, 15-Math.cos(i)]); - } - - data = [ - { data : d1, label :'x + sin(x+π)' }, - { data : d2, label :'x' }, - { data : d3, label :'15 - cos(x)' } - ]; - - - // This function prepend each label with 'y = ' - function labelFn (label) { - return 'y = ' + label; - } - - // Draw graph - graph = Flotr.draw(container, data, { - legend : { - position : 'se', // Position the legend 'south-east'. - labelFormatter : labelFn, // Format the labels. - backgroundColor : '#D2E8FF' // A light blue background color. - }, - HtmlText : false - }); -} - -})(); - -(function () { - -Flotr.ExampleList.add({ - key : 'mouse-tracking', - name : 'Mouse Tracking', - callback : mouse_tracking -}); - -function mouse_tracking (container) { - - var - d1 = [], - d2 = [], - d3 = [], - graph, i; - - for (i = 0; i < 20; i += 0.5) { - d1.push([i, 2*i]); - d2.push([i, i*1.5+1.5*Math.sin(i)]); - d3.push([i, 3*Math.cos(i)+10]); - } - - graph = Flotr.draw( - container, - [ - { - data : d1, - mouse : { track : false } // Disable mouse tracking for d1 - }, - d2, - d3 - ], - { - mouse : { - track : true, // Enable mouse tracking - lineColor : 'purple', - relative : true, - position : 'ne', - sensibility : 1, - trackDecimals : 2, - trackFormatter : function (o) { return 'x = ' + o.x +', y = ' + o.y; } - }, - crosshair : { - mode : 'xy' - } - } - ); - -}; - -})(); - -(function () { - -Flotr.ExampleList.add({ - key : 'mouse-zoom', - name : 'Mouse Zoom', - callback : mouse_zoom, - description : "<p>Select an area of the graph to zoom. Click to reset the chart.</p>" -}); - -function mouse_zoom (container) { - - var - d1 = [], - d2 = [], - d3 = [], - options, - graph, - i; - - for (i = 0; i < 40; i += 0.5) { - d1.push([i, Math.sin(i)+3*Math.cos(i)]); - d2.push([i, Math.pow(1.1, i)]); - d3.push([i, 40 - i+Math.random()*10]); - } - - options = { - selection : { mode : 'x', fps : 30 }, - title : 'Mouse Zoom' - }; - - // Draw graph with default options, overwriting with passed options - function drawGraph (opts) { - - // Clone the options, so the 'options' variable always keeps intact. - var o = Flotr._.extend(Flotr._.clone(options), opts || {}); - - // Return a new graph. - return Flotr.draw( - container, - [ d1, d2, d3 ], - o - ); - } - - // Actually draw the graph. - graph = drawGraph(); - - // Hook into the 'flotr:select' event. - Flotr.EventAdapter.observe(container, 'flotr:select', function (area) { - - // Draw graph with new area - f = drawGraph({ - xaxis: {min:area.x1, max:area.x2}, - yaxis: {min:area.y1, max:area.y2} - }); - - }); - - // When graph is clicked, draw the graph with default area. - Flotr.EventAdapter.observe(container, 'flotr:click', function () { drawGraph(); }); -}; - -})(); - - -(function () { - -Flotr.ExampleList.add({ - key : 'mouse-drag', - name : 'Mouse Drag', - callback : mouse_drag -}); - -function mouse_drag (container) { - - var - d1 = [], - d2 = [], - d3 = [], - options, - graph, - start, - i; - - for (i = -40; i < 40; i += 0.5) { - d1.push([i, Math.sin(i)+3*Math.cos(i)]); - d2.push([i, Math.pow(1.1, i)]); - d3.push([i, 40 - i+Math.random()*10]); - } - - options = { - xaxis: {min: 0, max: 20}, - title : 'Mouse Drag' - }; - - // Draw graph with default options, overwriting with passed options - function drawGraph (opts) { - - // Clone the options, so the 'options' variable always keeps intact. - var o = Flotr._.extend(Flotr._.clone(options), opts || {}); - - // Return a new graph. - return Flotr.draw( - container, - [ d1, d2, d3 ], - o - ); - } - - graph = drawGraph(); - - function initializeDrag (e) { - start = graph.getEventPosition(e); - Flotr.EventAdapter.observe(document, 'mousemove', move); - Flotr.EventAdapter.observe(document, 'mouseup', stopDrag); - } - - function move (e) { - var - end = graph.getEventPosition(e), - xaxis = graph.axes.x, - offset = start.x - end.x; - - graph = drawGraph({ - xaxis : { - min : xaxis.min + offset, - max : xaxis.max + offset - } - }); - // @todo: refector initEvents in order not to remove other observed events - Flotr.EventAdapter.observe(graph.overlay, 'mousedown', initializeDrag); - } - - function stopDrag () { - Flotr.EventAdapter.stopObserving(document, 'mousemove', move); - } - - Flotr.EventAdapter.observe(graph.overlay, 'mousedown', initializeDrag); - -}; - -})(); - -(function () { - -Flotr.ExampleList.add({ - key : 'basic-time', - name : 'Basic Time', - callback : basic_time, - description : "<p>Select an area of the graph to zoom. Click to reset the chart.</p>" -}); - -function basic_time (container) { - - var - d1 = [], - start = new Date("2009/01/01 01:00").getTime(), - options, - graph, - i, x, o; - - for (i = 0; i < 100; i++) { - x = start+(i*1000*3600*24*36.5); - d1.push([x, i+Math.random()*30+Math.sin(i/20+Math.random()*2)*20+Math.sin(i/10+Math.random())*10]); - } - - options = { - xaxis : { - mode : 'time', - labelsAngle : 45 - }, - selection : { - mode : 'x' - }, - HtmlText : false, - title : 'Time' - }; - - // Draw graph with default options, overwriting with passed options - function drawGraph (opts) { - - // Clone the options, so the 'options' variable always keeps intact. - o = Flotr._.extend(Flotr._.clone(options), opts || {}); - - // Return a new graph. - return Flotr.draw( - container, - [ d1 ], - o - ); - } - - graph = drawGraph(); - - Flotr.EventAdapter.observe(container, 'flotr:select', function(area){ - // Draw selected area - graph = drawGraph({ - xaxis : { min : area.x1, max : area.x2, mode : 'time', labelsAngle : 45 }, - yaxis : { min : area.y1, max : area.y2 } - }); - }); - - // When graph is clicked, draw the graph with default area. - Flotr.EventAdapter.observe(container, 'flotr:click', function () { graph = drawGraph(); }); -}; - -})(); - -(function () { - -Flotr.ExampleList.add({ - key : 'negative-values', - name : 'Negative Values', - callback : negative_values -}); - -function negative_values (container) { - - var - d0 = [], // Line through y = 0 - d1 = [], // Random data presented as a scatter plot. - d2 = [], // A regression line for the scatter. - sx = 0, - sy = 0, - sxy = 0, - sxsq = 0, - xmean, - ymean, - alpha, - beta, - n, x, y; - - for (n = 0; n < 20; n++){ - - x = n; - y = x + Math.random()*8 - 15; - - d0.push([x, 0]); - d1.push([x, y]); - - // Computations used for regression line - sx += x; - sy += y; - sxy += x*y; - sxsq += Math.pow(x,2); - } - - xmean = sx/n; - ymean = sy/n; - beta = ((n*sxy) - (sx*sy))/((n*sxsq)-(Math.pow(sx,2))); - alpha = ymean - (beta * xmean); - - // Compute the regression line. - for (n = 0; n < 20; n++){ - d2.push([n, alpha + beta*n]) - } - - // Draw the graph - graph = Flotr.draw( - container, [ - { data : d0, shadowSize : 0, color : '#545454' }, // Horizontal - { data : d1, label : 'y = x + (Math.random() * 8) - 15', points : { show : true } }, // Scatter - { data : d2, label : 'y = ' + alpha.toFixed(2) + ' + ' + beta.toFixed(2) + '*x' } // Regression - ], - { - legend : { position : 'se', backgroundColor : '#D2E8FF' }, - title : 'Negative Values' - } - ); -}; - -})(); - -(function () { - -Flotr.ExampleList.add({ - key : 'click-example', - name : 'Click Example', - callback : click_example -}); - -function click_example (container) { - - var - d1 = [[0,0]], // Point at origin - options, - graph; - - options = { - xaxis: {min: 0, max: 15}, - yaxis: {min: 0, max: 15}, - lines: {show: true}, - points: {show: true}, - mouse: {track:true}, - title: 'Click Example' - }; - - graph = Flotr.draw(container, [d1], options); - - // Add a point to the series and redraw the graph - Flotr.EventAdapter.observe(container, 'flotr:click', function(position){ - - // Add a point to the series at the location of the click - d1.push([position.x, position.y]); - - // Sort the series. - d1 = d1.sort(function (a, b) { return a[0] - b[0]; }); - - // Redraw the graph, with the new series. - graph = Flotr.draw(container, [d1], options); - }); -}; - -})(); - -(function () { - -Flotr.ExampleList.add({ - key : 'download-image', - name : 'Download Image', - callback : download_image, - description : '' + - '<form name="image-download" id="image-download" action="" onsubmit="return false">' + - '<label><input type="radio" name="format" value="png" checked="checked" /> PNG</label>' + - '<label><input type="radio" name="format" value="jpeg" /> JPEG</label>' + - - '<button name="to-image" onclick="CurrentExample(\'to-image\')">To Image</button>' + - '<button name="download" onclick="CurrentExample(\'download\')">Download</button>' + - '<button name="reset" onclick="CurrentExample(\'reset\')">Reset</button>' + - '</form>' -}); - -function download_image (container) { - - var - d1 = [], - d2 = [], - d3 = [], - d4 = [], - d5 = [], - graph, - i; - - for (i = 0; i <= 10; i += 0.1) { - d1.push([i, 4 + Math.pow(i,1.5)]); - d2.push([i, Math.pow(i,3)]); - d3.push([i, i*5+3*Math.sin(i*4)]); - d4.push([i, i]); - if( i.toFixed(1)%1 == 0 ){ - d5.push([i, 2*i]); - } - } - - // Draw the graph - graph = Flotr.draw( - container,[ - {data:d1, label:'y = 4 + x^(1.5)', lines:{fill:true}}, - {data:d2, label:'y = x^3', yaxis:2}, - {data:d3, label:'y = 5x + 3sin(4x)'}, - {data:d4, label:'y = x'}, - {data:d5, label:'y = 2x', lines: {show: true}, points: {show: true}} - ],{ - title: 'Download Image Example', - subtitle: 'You can save me as an image', - xaxis:{ - noTicks: 7, // Display 7 ticks. - tickFormatter: function(n){ return '('+n+')'; }, // => displays tick values between brackets. - min: 1, // => part of the series is not displayed. - max: 7.5, // => part of the series is not displayed. - labelsAngle: 45, - title: 'x Axis' - }, - yaxis:{ - ticks: [[0, "Lower"], 10, 20, 30, [40, "Upper"]], - max: 40, - title: 'y = f(x)' - }, - y2axis:{color:'#FF0000', max: 500, title: 'y = x^3'}, - grid:{ - verticalLines: false, - backgroundColor: 'white' - }, - HtmlText: false, - legend: { - position: 'nw' - } - }); - - this.CurrentExample = function (operation) { - - var - format = $('#image-download input:radio[name=format]:checked').val(); - if (Flotr.isIE && Flotr.isIE < 9) { - alert( - "Your browser doesn't allow you to get a bitmap image from the plot, " + - "you can only get a VML image that you can use in Microsoft Office.<br />" - ); - } - - if (operation == 'to-image') { - graph.download.saveImage(format, null, null, true) - } else if (operation == 'download') { - graph.download.saveImage(format); - } else if (operation == 'reset') { - graph.download.restoreCanvas(); - } - }; - - return graph; -}; - -})(); - -(function () { - -Flotr.ExampleList.add({ - key : 'download-data', - name : 'Download Data', - callback : download_data -}); - -function download_data (container) { - - var - d1 = [], - d2 = [], - d3 = [], - d4 = [], - d5 = [], - graph, - i,x; - - for (i = 0; i <= 100; i += 1) { - x = i / 10; - d1.push([x, 4 + Math.pow(x,1.5)]); - d2.push([x, Math.pow(x,3)]); - d3.push([x, i*5+3*Math.sin(x*4)]); - d4.push([x, x]); - if(x%1 === 0 ){ - d5.push([x, 2*x]); - } - } - - // Draw the graph. - graph = Flotr.draw( - container, [ - { data : d1, label : 'y = 4 + x^(1.5)', lines : { fill : true } }, - { data : d2, label : 'y = x^3' }, - { data : d3, label : 'y = 5x + 3sin(4x)' }, - { data : d4, label : 'y = x' }, - { data : d5, label : 'y = 2x', lines : { show : true }, points : { show : true } } - ],{ - xaxis : { - noTicks : 7, - tickFormatter : function (n) { return '('+n+')'; }, - min: 1, // Part of the series is not displayed. - max: 7.5 - }, - yaxis : { - ticks : [[ 0, "Lower"], 10, 20, 30, [40, "Upper"]], - max : 40 - }, - grid : { - verticalLines : false, - backgroundColor : 'white' - }, - legend : { - position : 'nw' - }, - spreadsheet : { - show : true, - tickFormatter : function (e) { return e+''; } - } - }); -}; - -})(); - -(function () { - -Flotr.ExampleList.add({ - key : 'advanced-titles', - name : 'Advanced Titles', - callback : advanced_titles -}); - -function advanced_titles (container) { - - var - d1 = [], - d2 = [], - d3 = [], - d4 = [], - d5 = [], - graph, - i; - - for (i = 0; i <= 10; i += 0.1) { - d1.push([i, 4 + Math.pow(i,1.5)]); - d2.push([i, Math.pow(i,3)]); - d3.push([i, i*5+3*Math.sin(i*4)]); - d4.push([i, i]); - if (i.toFixed(1)%1 == 0) { - d5.push([i, 2*i]); - } - } - - // Draw the graph. - graph = Flotr.draw( - container,[ - { data : d1, label : 'y = 4 + x^(1.5)', lines : { fill : true } }, - { data : d2, label : 'y = x^3', yaxis : 2 }, - { data : d3, label : 'y = 5x + 3sin(4x)' }, - { data : d4, label : 'y = x' }, - { data : d5, label : 'y = 2x', lines : { show : true }, points : { show : true } } - ], { - title : 'Advanced Titles Example', - subtitle : 'You can save me as an image', - xaxis : { - noTicks : 7, - tickFormatter : function (n) { return '('+n+')'; }, - min : 1, - max : 7.5, - labelsAngle : 45, - title : 'x Axis' - }, - yaxis : { - ticks : [[0, "Lower"], 10, 20, 30, [40, "Upper"]], - max : 40, - title : 'y = f(x)' - }, - y2axis : { color : '#FF0000', max : 500, title : 'y = x^3' }, - grid : { - verticalLines : false, - backgroundColor : 'white' - }, - HtmlText : false, - legend : { - position : 'nw' - } - }); -}; - -})(); - -(function () { - -Flotr.ExampleList.add({ - key : 'color-gradients', - name : 'Color Gradients', - callback : color_gradients -}); - -function color_gradients (container) { - - var - bars = { - data: [], - bars: { - show: true, - barWidth: 0.8, - lineWidth: 0, - fillColor: { - colors: ['#CB4B4B', '#fff'], - start: 'top', - end: 'bottom' - }, - fillOpacity: 0.8 - } - }, markers = { - data: [], - markers: { - show: true, - position: 'ct' - } - }, lines = { - data: [], - lines: { - show: true, - fillColor: ['#00A8F0', '#fff'], - fill: true, - fillOpacity: 1 - } - }, - point, - graph, - i; - - for (i = 0; i < 8; i++) { - point = [i, Math.ceil(Math.random() * 10)]; - bars.data.push(point); - markers.data.push(point); - } - - for (i = -1; i < 9; i += 0.01){ - lines.data.push([i, i*i/8+2]); - } - - graph = Flotr.draw( - container, - [lines, bars, markers], { - yaxis: { - min: 0, - max: 11 - }, - xaxis: { - min: -0.5, - max: 7.5 - }, - grid: { - verticalLines: false, - backgroundColor: ['#fff', '#ccc'] - } - } - ); -}; - -})(); - - -(function () { - -Flotr.ExampleList.add({ - key : 'profile-bars', - name : 'Profile Bars', - type : 'profile', - callback : profile_bars -}); - -/* -Flotr.ExampleList.add({ - key : 'basic-bars-horizontal', - name : 'Horizontal Bars', - args : [true], - callback : basic_bars -}); -*/ - -function profile_bars (container, horizontal) { - - var - horizontal = (horizontal ? true : false), // Show horizontal bars - d1 = [], // First data series - d2 = [], // Second data series - point, // Data point variable declaration - i; - - for (i = 0; i < 5000; i++) { - - if (horizontal) { - point = [Math.ceil(Math.random()*10), i]; - } else { - point = [i, Math.ceil(Math.random()*10)]; - } - - d1.push(point); - - if (horizontal) { - point = [Math.ceil(Math.random()*10), i+0.5]; - } else { - point = [i+0.5, Math.ceil(Math.random()*10)]; - } - - d2.push(point); - }; - - // Draw the graph - Flotr.draw( - container, - [d1, d2], - { - bars : { - show : true, - horizontal : horizontal, - barWidth : 0.5 - }, - mouse : { - track : true, - relative : true - }, - yaxis : { - min : 0, - autoscaleMargin : 1 - } - } - ); -} - -})(); - -(function () { - -Flotr.ExampleList.add({ - key : 'basic-timeline', - name : 'Basic Timeline', - callback : basic_timeline -}); - -function basic_timeline (container) { - - var - d1 = [[1, 4, 5]], - d2 = [[3.2, 3, 4]], - d3 = [[1.9, 2, 2], [5, 2, 3.3]], - d4 = [[1.55, 1, 9]], - d5 = [[5, 0, 2.3]], - data = [], - timeline = { show : true, barWidth : .5 }, - markers = [], - labels = ['Obama', 'Bush', 'Clinton', 'Palin', 'McCain'], - i, graph, point; - - // Timeline - Flotr._.each([d1, d2, d3, d4, d5], function (d) { - data.push({ - data : d, - timeline : Flotr._.clone(timeline) - }); - }); - - // Markers - Flotr._.each([d1, d2, d3, d4, d5], function (d) { - point = d[0]; - markers.push([point[0], point[1]]); - }); - data.push({ - data: markers, - markers: { - show: true, - position: 'rm', - fontSize: 11, - labelFormatter : function (o) { return labels[o.index]; } - } - }); - - // Draw Graph - graph = Flotr.draw(container, data, { - xaxis: { - noTicks: 3, - tickFormatter: function (x) { - var - x = parseInt(x), - months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; - return months[(x-1)%12]; - } - }, - yaxis: { - showLabels : false - }, - grid: { - horizontalLines : false - } - }); -} - -})(); - -(function () { - -Flotr.ExampleList.add({ - key : 'advanced-markers', - name : 'Advanced Markers', - callback : advanced_markers, - timeout : 150 -}); - -function advanced_markers (container) { - - var - xmark = new Image(), - checkmark = new Image(), - bars = { - data: [], - bars: { - show: true, - barWidth: 0.6, - lineWidth: 0, - fillOpacity: 0.8 - } - }, markers = { - data: [], - markers: { - show: true, - position: 'ct', - labelFormatter: function (o) { - return (o.y >= 5) ? checkmark : xmark; - } - } - }, - flotr = Flotr, - point, - graph, - i; - - - for (i = 0; i < 8; i++) { - point = [i, Math.ceil(Math.random() * 10)]; - bars.data.push(point); - markers.data.push(point); - } - - var runner = function () { - if (!xmark.complete || !checkmark.complete) { - setTimeout(runner, 50); - return; - } - - graph = flotr.draw( - container, - [bars, markers], { - yaxis: { - min: 0, - max: 11 - }, - xaxis: { - min: -0.5, - max: 7.5 - }, - grid: { - verticalLines: false - } - } - ); - } - - xmark.onload = runner; - xmark.src = 'images/xmark.png'; - checkmark.src = 'images/checkmark.png'; -}; - -})(); - diff --git a/addons/web_graph/static/lib/flotr2/flotr2.ie.min.js b/addons/web_graph/static/lib/flotr2/flotr2.ie.min.js deleted file mode 100644 index 0c64520fcb5..00000000000 --- a/addons/web_graph/static/lib/flotr2/flotr2.ie.min.js +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2006 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// Known Issues: -// -// * Patterns only support repeat. -// * Radial gradient are not implemented. The VML version of these look very -// different from the canvas one. -// * Clipping paths are not implemented. -// * Coordsize. The width and height attribute have higher priority than the -// width and height style values which isn't correct. -// * Painting mode isn't implemented. -// * Canvas width/height should is using content-box by default. IE in -// Quirks mode will draw the canvas using border-box. Either change your -// doctype to HTML5 -// (http://www.whatwg.org/specs/web-apps/current-work/#the-doctype) -// or use Box Sizing Behavior from WebFX -// (http://webfx.eae.net/dhtml/boxsizing/boxsizing.html) -// * Non uniform scaling does not correctly scale strokes. -// * Optimize. There is always room for speed improvements. -// Only add this code if we do not already have a canvas implementation - -document.createElement("canvas").getContext||function(){function j(){return this.context_||(this.context_=new N(this))}function l(a,b,c){var d=k.call(arguments,2);return function(){return a.apply(b,d.concat(k.call(arguments)))}}function m(a){return String(a).replace(/&/g,"&").replace(/"/g,""")}function n(a,b,c){a.namespaces[b]||a.namespaces.add(b,c,"#default#VML")}function o(a){n(a,"g_vml_","urn:schemas-microsoft-com:vml"),n(a,"g_o_","urn:schemas-microsoft-com:office:office");if(!a.styleSheets.ex_canvas_){var b=a.createStyleSheet();b.owningElement.id="ex_canvas_",b.cssText="canvas{display:inline-block;overflow:hidden;text-align:left;width:300px;height:150px}"}}function q(a){var b=a.srcElement;switch(a.propertyName){case"width":b.getContext().clearRect(),b.style.width=b.attributes.width.nodeValue+"px",b.firstChild&&(b.firstChild.style.width=b.clientWidth+"px");break;case"height":b.getContext().clearRect(),b.style.height=b.attributes.height.nodeValue+"px",b.firstChild&&(b.firstChild.style.height=b.clientHeight+"px")}}function r(a){var b=a.srcElement;b.firstChild&&(b.firstChild.style.width=b.clientWidth+"px",b.firstChild.style.height=b.clientHeight+"px")}function v(){return[[1,0,0],[0,1,0],[0,0,1]]}function w(a,b){var c=v();for(var d=0;d<3;d++)for(var e=0;e<3;e++){var f=0;for(var g=0;g<3;g++)f+=a[d][g]*b[g][e];c[d][e]=f}return c}function x(a,b){b.fillStyle=a.fillStyle,b.lineCap=a.lineCap,b.lineJoin=a.lineJoin,b.lineWidth=a.lineWidth,b.miterLimit=a.miterLimit,b.shadowBlur=a.shadowBlur,b.shadowColor=a.shadowColor,b.shadowOffsetX=a.shadowOffsetX,b.shadowOffsetY=a.shadowOffsetY,b.strokeStyle=a.strokeStyle,b.globalAlpha=a.globalAlpha,b.font=a.font,b.textAlign=a.textAlign,b.textBaseline=a.textBaseline,b.arcScaleX_=a.arcScaleX_,b.arcScaleY_=a.arcScaleY_,b.lineScale_=a.lineScale_}function z(a){var b=a.indexOf("(",3),c=a.indexOf(")",b+1),d=a.substring(b+1,c).split(",");if(d.length!=4||a.charAt(3)!="a")d[3]=1;return d}function A(a){return parseFloat(a)/100}function B(a,b,c){return Math.min(c,Math.max(b,a))}function C(a){var b,c,d,e,f,g;e=parseFloat(a[0])/360%360,e<0&&e++,f=B(A(a[1]),0,1),g=B(A(a[2]),0,1);if(f==0)b=c=d=g;else{var h=g<.5?g*(1+f):g+f-g*f,i=2*g-h;b=D(i,h,e+1/3),c=D(i,h,e),d=D(i,h,e-1/3)}return"#"+s[Math.floor(b*255)]+s[Math.floor(c*255)]+s[Math.floor(d*255)]}function D(a,b,c){return c<0&&c++,c>1&&c--,6*c<1?a+(b-a)*6*c:2*c<1?b:3*c<2?a+(b-a)*(2/3-c)*6:a}function F(a){if(a in E)return E[a];var b,c=1;a=String(a);if(a.charAt(0)=="#")b=a;else if(/^rgb/.test(a)){var d=z(a),b="#",e;for(var f=0;f<3;f++)d[f].indexOf("%")!=-1?e=Math.floor(A(d[f])*255):e=+d[f],b+=s[B(e,0,255)];c=+d[3]}else if(/^hsl/.test(a)){var d=z(a);b=C(d),c=d[3]}else b=y[a]||a;return E[a]={color:b,alpha:c}}function I(a){if(H[a])return H[a];var b=document.createElement("div"),c=b.style;try{c.font=a}catch(d){}return H[a]={style:c.fontStyle||G.style,variant:c.fontVariant||G.variant,weight:c.fontWeight||G.weight,size:c.fontSize||G.size,family:c.fontFamily||G.family}}function J(a,b){var c={};for(var d in a)c[d]=a[d];var e=parseFloat(b.currentStyle.fontSize),f=parseFloat(a.size);return typeof a.size=="number"?c.size=a.size:a.size.indexOf("px")!=-1?c.size=f:a.size.indexOf("em")!=-1?c.size=e*f:a.size.indexOf("%")!=-1?c.size=e/100*f:a.size.indexOf("pt")!=-1?c.size=f/.75:c.size=e,c}function K(a){return a.style+" "+a.variant+" "+a.weight+" "+a.size+"px "+a.family}function M(a){return L[a]||"square"}function N(a){this.m_=v(),this.mStack_=[],this.aStack_=[],this.currentPath_=[],this.strokeStyle="#000",this.fillStyle="#000",this.lineWidth=1,this.lineJoin="miter",this.lineCap="butt",this.miterLimit=g*1,this.globalAlpha=1,this.font="10px sans-serif",this.textAlign="left",this.textBaseline="alphabetic",this.canvas=a;var b="width:"+a.clientWidth+"px;height:"+a.clientHeight+"px;overflow:hidden;position:absolute",c=a.ownerDocument.createElement("div");c.style.cssText=b,a.appendChild(c);var d=c.cloneNode(!1);d.style.backgroundColor="red",d.style.filter="alpha(opacity=0)",a.appendChild(d),this.element_=c,this.arcScaleX_=1,this.arcScaleY_=1,this.lineScale_=1}function P(a,b,c,d){a.currentPath_.push({type:"bezierCurveTo",cp1x:b.x,cp1y:b.y,cp2x:c.x,cp2y:c.y,x:d.x,y:d.y}),a.currentX_=d.x,a.currentY_=d.y}function Q(a,b){var c=F(a.strokeStyle),d=c.color,e=c.alpha*a.globalAlpha,f=a.lineScale_*a.lineWidth;f<1&&(e*=f),b.push("<g_vml_:stroke",' opacity="',e,'"',' joinstyle="',a.lineJoin,'"',' miterlimit="',a.miterLimit,'"',' endcap="',M(a.lineCap),'"',' weight="',f,'px"',' color="',d,'" />')}function R(b,c,d,e){var f=b.fillStyle,h=b.arcScaleX_,i=b.arcScaleY_,j=e.x-d.x,k=e.y-d.y;if(f instanceof V){var l=0,m={x:0,y:0},n=0,o=1;if(f.type_=="gradient"){var p=f.x0_/h,q=f.y0_/i,r=f.x1_/h,s=f.y1_/i,t=S(b,p,q),u=S(b,r,s),v=u.x-t.x,w=u.y-t.y;l=Math.atan2(v,w)*180/Math.PI,l<0&&(l+=360),l<1e-6&&(l=0)}else{var t=S(b,f.x0_,f.y0_);m={x:(t.x-d.x)/j,y:(t.y-d.y)/k},j/=h*g,k/=i*g;var x=a.max(j,k);n=2*f.r0_/x,o=2*f.r1_/x-n}var y=f.colors_;y.sort(function(a,b){return a.offset-b.offset});var z=y.length,A=y[0].color,B=y[z-1].color,C=y[0].alpha*b.globalAlpha,D=y[z-1].alpha*b.globalAlpha,E=[];for(var G=0;G<z;G++){var H=y[G];E.push(H.offset*o+n+" "+H.color)}c.push('<g_vml_:fill type="',f.type_,'"',' method="none" focus="100%"',' color="',A,'"',' color2="',B,'"',' colors="',E.join(","),'"',' opacity="',D,'"',' g_o_:opacity2="',C,'"',' angle="',l,'"',' focusposition="',m.x,",",m.y,'" />')}else if(f instanceof W){if(j&&k){var I=-d.x,J=-d.y;c.push("<g_vml_:fill",' position="',I/j*h*h,",",J/k*i*i,'"',' type="tile"',' src="',f.src_,'" />')}}else{var K=F(b.fillStyle),L=K.color,M=K.alpha*b.globalAlpha;c.push('<g_vml_:fill color="',L,'" opacity="',M,'" />')}}function S(a,b,c){var d=a.m_;return{x:g*(b*d[0][0]+c*d[1][0]+d[2][0])-h,y:g*(b*d[0][1]+c*d[1][1]+d[2][1])-h}}function T(a){return isFinite(a[0][0])&&isFinite(a[0][1])&&isFinite(a[1][0])&&isFinite(a[1][1])&&isFinite(a[2][0])&&isFinite(a[2][1])}function U(a,b,c){if(!T(b))return;a.m_=b;if(c){var d=b[0][0]*b[1][1]-b[0][1]*b[1][0];a.lineScale_=f(e(d))}}function V(a){this.type_=a,this.x0_=0,this.y0_=0,this.r0_=0,this.x1_=0,this.y1_=0,this.r1_=0,this.colors_=[]}function W(a,b){Y(a);switch(b){case"repeat":case null:case"":this.repetition_="repeat";break;case"repeat-x":case"repeat-y":case"no-repeat":this.repetition_=b;break;default:X("SYNTAX_ERR")}this.src_=a.src,this.width_=a.width,this.height_=a.height}function X(a){throw new Z(a)}function Y(a){(!a||a.nodeType!=1||a.tagName!="IMG")&&X("TYPE_MISMATCH_ERR"),a.readyState!="complete"&&X("INVALID_STATE_ERR")}function Z(a){this.code=this[a],this.message=a+": DOM Exception "+this.code}var a=Math,b=a.round,c=a.sin,d=a.cos,e=a.abs,f=a.sqrt,g=10,h=g/2,i=+navigator.userAgent.match(/MSIE ([\d.]+)?/)[1],k=Array.prototype.slice;o(document);var p={init:function(a){var b=a||document;b.createElement("canvas"),b.attachEvent("onreadystatechange",l(this.init_,this,b))},init_:function(a){var b=a.getElementsByTagName("canvas");for(var c=0;c<b.length;c++)this.initElement(b[c])},initElement:function(a){if(!a.getContext){a.getContext=j,o(a.ownerDocument),a.innerHTML="",a.attachEvent("onpropertychange",q),a.attachEvent("onresize",r);var b=a.attributes;b.width&&b.width.specified?a.style.width=b.width.nodeValue+"px":a.width=a.clientWidth,b.height&&b.height.specified?a.style.height=b.height.nodeValue+"px":a.height=a.clientHeight}return a}};p.init();var s=[];for(var t=0;t<16;t++)for(var u=0;u<16;u++)s[t*16+u]=t.toString(16)+u.toString(16);var y={aliceblue:"#F0F8FF",antiquewhite:"#FAEBD7",aquamarine:"#7FFFD4",azure:"#F0FFFF",beige:"#F5F5DC",bisque:"#FFE4C4",black:"#000000",blanchedalmond:"#FFEBCD",blueviolet:"#8A2BE2",brown:"#A52A2A",burlywood:"#DEB887",cadetblue:"#5F9EA0",chartreuse:"#7FFF00",chocolate:"#D2691E",coral:"#FF7F50",cornflowerblue:"#6495ED",cornsilk:"#FFF8DC",crimson:"#DC143C",cyan:"#00FFFF",darkblue:"#00008B",darkcyan:"#008B8B",darkgoldenrod:"#B8860B",darkgray:"#A9A9A9",darkgreen:"#006400",darkgrey:"#A9A9A9",darkkhaki:"#BDB76B",darkmagenta:"#8B008B",darkolivegreen:"#556B2F",darkorange:"#FF8C00",darkorchid:"#9932CC",darkred:"#8B0000",darksalmon:"#E9967A",darkseagreen:"#8FBC8F",darkslateblue:"#483D8B",darkslategray:"#2F4F4F",darkslategrey:"#2F4F4F",darkturquoise:"#00CED1",darkviolet:"#9400D3",deeppink:"#FF1493",deepskyblue:"#00BFFF",dimgray:"#696969",dimgrey:"#696969",dodgerblue:"#1E90FF",firebrick:"#B22222",floralwhite:"#FFFAF0",forestgreen:"#228B22",gainsboro:"#DCDCDC",ghostwhite:"#F8F8FF",gold:"#FFD700",goldenrod:"#DAA520",grey:"#808080",greenyellow:"#ADFF2F",honeydew:"#F0FFF0",hotpink:"#FF69B4",indianred:"#CD5C5C",indigo:"#4B0082",ivory:"#FFFFF0",khaki:"#F0E68C",lavender:"#E6E6FA",lavenderblush:"#FFF0F5",lawngreen:"#7CFC00",lemonchiffon:"#FFFACD",lightblue:"#ADD8E6",lightcoral:"#F08080",lightcyan:"#E0FFFF",lightgoldenrodyellow:"#FAFAD2",lightgreen:"#90EE90",lightgrey:"#D3D3D3",lightpink:"#FFB6C1",lightsalmon:"#FFA07A",lightseagreen:"#20B2AA",lightskyblue:"#87CEFA",lightslategray:"#778899",lightslategrey:"#778899",lightsteelblue:"#B0C4DE",lightyellow:"#FFFFE0",limegreen:"#32CD32",linen:"#FAF0E6",magenta:"#FF00FF",mediumaquamarine:"#66CDAA",mediumblue:"#0000CD",mediumorchid:"#BA55D3",mediumpurple:"#9370DB",mediumseagreen:"#3CB371",mediumslateblue:"#7B68EE",mediumspringgreen:"#00FA9A",mediumturquoise:"#48D1CC",mediumvioletred:"#C71585",midnightblue:"#191970",mintcream:"#F5FFFA",mistyrose:"#FFE4E1",moccasin:"#FFE4B5",navajowhite:"#FFDEAD",oldlace:"#FDF5E6",olivedrab:"#6B8E23",orange:"#FFA500",orangered:"#FF4500",orchid:"#DA70D6",palegoldenrod:"#EEE8AA",palegreen:"#98FB98",paleturquoise:"#AFEEEE",palevioletred:"#DB7093",papayawhip:"#FFEFD5",peachpuff:"#FFDAB9",peru:"#CD853F",pink:"#FFC0CB",plum:"#DDA0DD",powderblue:"#B0E0E6",rosybrown:"#BC8F8F",royalblue:"#4169E1",saddlebrown:"#8B4513",salmon:"#FA8072",sandybrown:"#F4A460",seagreen:"#2E8B57",seashell:"#FFF5EE",sienna:"#A0522D",skyblue:"#87CEEB",slateblue:"#6A5ACD",slategray:"#708090",slategrey:"#708090",snow:"#FFFAFA",springgreen:"#00FF7F",steelblue:"#4682B4",tan:"#D2B48C",thistle:"#D8BFD8",tomato:"#FF6347",turquoise:"#40E0D0",violet:"#EE82EE",wheat:"#F5DEB3",whitesmoke:"#F5F5F5",yellowgreen:"#9ACD32"},E={},G={style:"normal",variant:"normal",weight:"normal",size:10,family:"sans-serif"},H={},L={butt:"flat",round:"round"},O=N.prototype;O.clearRect=function(){this.textMeasureEl_&&(this.textMeasureEl_.removeNode(!0),this.textMeasureEl_=null),this.element_.innerHTML=""},O.beginPath=function(){this.currentPath_=[]},O.moveTo=function(a,b){var c=S(this,a,b);this.currentPath_.push({type:"moveTo",x:c.x,y:c.y}),this.currentX_=c.x,this.currentY_=c.y},O.lineTo=function(a,b){var c=S(this,a,b);this.currentPath_.push({type:"lineTo",x:c.x,y:c.y}),this.currentX_=c.x,this.currentY_=c.y},O.bezierCurveTo=function(a,b,c,d,e,f){var g=S(this,e,f),h=S(this,a,b),i=S(this,c,d);P(this,h,i,g)},O.quadraticCurveTo=function(a,b,c,d){var e=S(this,a,b),f=S(this,c,d),g={x:this.currentX_+2/3*(e.x-this.currentX_),y:this.currentY_+2/3*(e.y-this.currentY_)},h={x:g.x+(f.x-this.currentX_)/3,y:g.y+(f.y-this.currentY_)/3};P(this,g,h,f)},O.arc=function(a,b,f,i,j,k){f*=g;var l=k?"at":"wa",m=a+d(i)*f-h,n=b+c(i)*f-h,o=a+d(j)*f-h,p=b+c(j)*f-h;e(m-o)<1e-7&&!k&&(m+=.125),e(n-p)<1e-7&&k&&(n-=.125);var q=S(this,a,b),r=S(this,m,n),s=S(this,o,p);this.currentPath_.push({type:l,x:q.x,y:q.y,radius:f,xStart:r.x,yStart:r.y,xEnd:s.x,yEnd:s.y})},O.rect=function(a,b,c,d){this.moveTo(a,b),this.lineTo(a+c,b),this.lineTo(a+c,b+d),this.lineTo(a,b+d),this.closePath()},O.strokeRect=function(a,b,c,d){var e=this.currentPath_;this.beginPath(),this.moveTo(a,b),this.lineTo(a+c,b),this.lineTo(a+c,b+d),this.lineTo(a,b+d),this.closePath(),this.stroke(),this.currentPath_=e},O.fillRect=function(a,b,c,d){var e=this.currentPath_;this.beginPath(),this.moveTo(a,b),this.lineTo(a+c,b),this.lineTo(a+c,b+d),this.lineTo(a,b+d),this.closePath(),this.fill(),this.currentPath_=e},O.createLinearGradient=function(a,b,c,d){var e=new V("gradient");return e.x0_=a,e.y0_=b,e.x1_=c,e.y1_=d,e},O.createRadialGradient=function(a,b,c,d,e,f){var g=new V("gradientradial");return g.x0_=a,g.y0_=b,g.r0_=c,g.x1_=d,g.y1_=e,g.r1_=f,g},O.drawImage=function(c,d){var e,f,h,i,j,k,l,m,n=c.runtimeStyle.width,o=c.runtimeStyle.height;c.runtimeStyle.width="auto",c.runtimeStyle.height="auto";var p=c.width,q=c.height;c.runtimeStyle.width=n,c.runtimeStyle.height=o;if(arguments.length==3)e=arguments[1],f=arguments[2],j=k=0,l=h=p,m=i=q;else if(arguments.length==5)e=arguments[1],f=arguments[2],h=arguments[3],i=arguments[4],j=k=0,l=p,m=q;else if(arguments.length==9)j=arguments[1],k=arguments[2],l=arguments[3],m=arguments[4],e=arguments[5],f=arguments[6],h=arguments[7],i=arguments[8];else throw Error("Invalid number of arguments");var r=S(this,e,f),s=l/2,t=m/2,u=[],v=10,w=10;u.push(" <g_vml_:group",' coordsize="',g*v,",",g*w,'"',' coordorigin="0,0"',' style="width:',v,"px;height:",w,"px;position:absolute;");if(this.m_[0][0]!=1||this.m_[0][1]||this.m_[1][1]!=1||this.m_[1][0]){var x=[];x.push("M11=",this.m_[0][0],",","M12=",this.m_[1][0],",","M21=",this.m_[0][1],",","M22=",this.m_[1][1],",","Dx=",b(r.x/g),",","Dy=",b(r.y/g),"");var y=r,z=S(this,e+h,f),A=S(this,e,f+i),B=S(this,e+h,f+i);y.x=a.max(y.x,z.x,A.x,B.x),y.y=a.max(y.y,z.y,A.y,B.y),u.push("padding:0 ",b(y.x/g),"px ",b(y.y/g),"px 0;filter:progid:DXImageTransform.Microsoft.Matrix(",x.join(""),", sizingmethod='clip');")}else u.push("top:",b(r.y/g),"px;left:",b(r.x/g),"px;");u.push(' ">','<g_vml_:image src="',c.src,'"',' style="width:',g*h,"px;"," height:",g*i,'px"',' cropleft="',j/p,'"',' croptop="',k/q,'"',' cropright="',(p-j-l)/p,'"',' cropbottom="',(q-k-m)/q,'"'," />","</g_vml_:group>"),this.element_.insertAdjacentHTML("BeforeEnd",u.join(""))},O.stroke=function(a){var c=[],d=!1,e=10,f=10;c.push("<g_vml_:shape",' filled="',!!a,'"',' style="position:absolute;width:',e,"px;height:",f,'px;"',' coordorigin="0,0"',' coordsize="',g*e,",",g*f,'"',' stroked="',!a,'"',' path="');var h=!1,i={x:null,y:null},j={x:null,y:null};for(var k=0;k<this.currentPath_.length;k++){var l=this.currentPath_[k],m;switch(l.type){case"moveTo":m=l,c.push(" m ",b(l.x),",",b(l.y));break;case"lineTo":c.push(" l ",b(l.x),",",b(l.y));break;case"close":c.push(" x "),l=null;break;case"bezierCurveTo":c.push(" c ",b(l.cp1x),",",b(l.cp1y),",",b(l.cp2x),",",b(l.cp2y),",",b(l.x),",",b(l.y));break;case"at":case"wa":c.push(" ",l.type," ",b(l.x-this.arcScaleX_*l.radius),",",b(l.y-this.arcScaleY_*l.radius)," ",b(l.x+this.arcScaleX_*l.radius),",",b(l.y+this.arcScaleY_*l.radius)," ",b(l.xStart),",",b(l.yStart)," ",b(l.xEnd),",",b(l.yEnd))}if(l){if(i.x==null||l.x<i.x)i.x=l.x;if(j.x==null||l.x>j.x)j.x=l.x;if(i.y==null||l.y<i.y)i.y=l.y;if(j.y==null||l.y>j.y)j.y=l.y}}c.push(' ">'),a?R(this,c,i,j):Q(this,c),c.push("</g_vml_:shape>"),this.element_.insertAdjacentHTML("beforeEnd",c.join(""))},O.fill=function(){this.stroke(!0)},O.closePath=function(){this.currentPath_.push({type:"close"})},O.save=function(){var a={};x(this,a),this.aStack_.push(a),this.mStack_.push(this.m_),this.m_=w(v(),this.m_)},O.restore=function(){this.aStack_.length&&(x(this.aStack_.pop(),this),this.m_=this.mStack_.pop())},O.translate=function(a,b){var c=[[1,0,0],[0,1,0],[a,b,1]];U(this,w(c,this.m_),!1)},O.rotate=function(a){var b=d(a),e=c(a),f=[[b,e,0],[-e,b,0],[0,0,1]];U(this,w(f,this.m_),!1)},O.scale=function(a,b){this.arcScaleX_*=a,this.arcScaleY_*=b;var c=[[a,0,0],[0,b,0],[0,0,1]];U(this,w(c,this.m_),!0)},O.transform=function(a,b,c,d,e,f){var g=[[a,b,0],[c,d,0],[e,f,1]];U(this,w(g,this.m_),!0)},O.setTransform=function(a,b,c,d,e,f){var g=[[a,b,0],[c,d,0],[e,f,1]];U(this,g,!0)},O.drawText_=function(a,c,d,e,f){var h=this.m_,i=1e3,j=0,k=i,l={x:0,y:0},n=[],o=J(I(this.font),this.element_),p=K(o),q=this.element_.currentStyle,r=this.textAlign.toLowerCase();switch(r){case"left":case"center":case"right":break;case"end":r=q.direction=="ltr"?"right":"left";break;case"start":r=q.direction=="rtl"?"right":"left";break;default:r="left"}switch(this.textBaseline){case"hanging":case"top":l.y=o.size/1.75;break;case"middle":break;default:case null:case"alphabetic":case"ideographic":case"bottom":l.y=-o.size/2.25}switch(r){case"right":j=i,k=.05;break;case"center":j=k=i/2}var s=S(this,c+l.x,d+l.y);n.push('<g_vml_:line from="',-j,' 0" to="',k,' 0.05" ',' coordsize="100 100" coordorigin="0 0"',' filled="',!f,'" stroked="',!!f,'" style="position:absolute;width:1px;height:1px;">'),f?Q(this,n):R(this,n,{x:-j,y:0},{x:k,y:o.size});var t=h[0][0].toFixed(3)+","+h[1][0].toFixed(3)+","+h[0][1].toFixed(3)+","+h[1][1].toFixed(3)+",0,0",u=b(s.x/g)+","+b(s.y/g);n.push('<g_vml_:skew on="t" matrix="',t,'" ',' offset="',u,'" origin="',j,' 0" />','<g_vml_:path textpathok="true" />','<g_vml_:textpath on="true" string="',m(a),'" style="v-text-align:',r,";font:",m(p),'" /></g_vml_:line>'),this.element_.insertAdjacentHTML("beforeEnd",n.join(""))},O.fillText=function(a,b,c,d){this.drawText_(a,b,c,d,!1)},O.strokeText=function(a,b,c,d){this.drawText_(a,b,c,d,!0)},O.measureText=function(a){if(!this.textMeasureEl_){var b='<span style="position:absolute;top:-20000px;left:0;padding:0;margin:0;border:none;white-space:pre;"></span>';this.element_.insertAdjacentHTML("beforeEnd",b),this.textMeasureEl_=this.element_.lastChild}var c=this.element_.ownerDocument;return this.textMeasureEl_.innerHTML="",this.textMeasureEl_.style.font=this.font,this.textMeasureEl_.appendChild(c.createTextNode(a)),{width:this.textMeasureEl_.offsetWidth}},O.clip=function(){},O.arcTo=function(){},O.createPattern=function(a,b){return new W(a,b)},V.prototype.addColorStop=function(a,b){b=F(b),this.colors_.push({offset:a,color:b.color,alpha:b.alpha})};var $=Z.prototype=new Error;$.INDEX_SIZE_ERR=1,$.DOMSTRING_SIZE_ERR=2,$.HIERARCHY_REQUEST_ERR=3,$.WRONG_DOCUMENT_ERR=4,$.INVALID_CHARACTER_ERR=5,$.NO_DATA_ALLOWED_ERR=6,$.NO_MODIFICATION_ALLOWED_ERR=7,$.NOT_FOUND_ERR=8,$.NOT_SUPPORTED_ERR=9,$.INUSE_ATTRIBUTE_ERR=10,$.INVALID_STATE_ERR=11,$.SYNTAX_ERR=12,$.INVALID_MODIFICATION_ERR=13,$.NAMESPACE_ERR=14,$.INVALID_ACCESS_ERR=15,$.VALIDATION_ERR=16,$.TYPE_MISMATCH_ERR=17,G_vmlCanvasManager=p,CanvasRenderingContext2D=N,CanvasGradient=V,CanvasPattern=W,DOMException=Z}(),function(){function c(b){var c,d,e,f,g,h;e=b.length,d=0,c="";while(d<e){f=b.charCodeAt(d++)&255;if(d==e){c+=a.charAt(f>>2),c+=a.charAt((f&3)<<4),c+="==";break}g=b.charCodeAt(d++);if(d==e){c+=a.charAt(f>>2),c+=a.charAt((f&3)<<4|(g&240)>>4),c+=a.charAt((g&15)<<2),c+="=";break}h=b.charCodeAt(d++),c+=a.charAt(f>>2),c+=a.charAt((f&3)<<4|(g&240)>>4),c+=a.charAt((g&15)<<2|(h&192)>>6),c+=a.charAt(h&63)}return c}function d(a){var c,d,e,f,g,h,i;h=a.length,g=0,i="";while(g<h){do c=b[a.charCodeAt(g++)&255];while(g<h&&c==-1);if(c==-1)break;do d=b[a.charCodeAt(g++)&255];while(g<h&&d==-1);if(d==-1)break;i+=String.fromCharCode(c<<2|(d&48)>>4);do{e=a.charCodeAt(g++)&255;if(e==61)return i;e=b[e]}while(g<h&&e==-1);if(e==-1)break;i+=String.fromCharCode((d&15)<<4|(e&60)>>2);do{f=a.charCodeAt(g++)&255;if(f==61)return i;f=b[f]}while(g<h&&f==-1);if(f==-1)break;i+=String.fromCharCode((e&3)<<6|f)}return i}var a="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",b=[-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,62,-1,-1,-1,63,52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-1,-1,-1,-1,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,-1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,-1,-1,-1,-1,-1];window.btoa||(window.btoa=c),window.atob||(window.atob=d)}();var CanvasText={letters:{"\n":{width:-1,points:[]}," ":{width:10,points:[]},"!":{width:10,points:[[5,21],[5,7],null,[5,2],[4,1],[5,0],[6,1],[5,2]]},'"':{width:16,points:[[4,21],[4,14],null,[12,21],[12,14]]},"#":{width:21,points:[[11,25],[4,-7],null,[17,25],[10,-7],null,[4,12],[18,12],null,[3,6],[17,6]]},$:{width:20,points:[[8,25],[8,-4],null,[12,25],[12,-4],null,[17,18],[15,20],[12,21],[8,21],[5,20],[3,18],[3,16],[4,14],[5,13],[7,12],[13,10],[15,9],[16,8],[17,6],[17,3],[15,1],[12,0],[8,0],[5,1],[3,3]]},"%":{width:24,points:[[21,21],[3,0],null,[8,21],[10,19],[10,17],[9,15],[7,14],[5,14],[3,16],[3,18],[4,20],[6,21],[8,21],null,[17,7],[15,6],[14,4],[14,2],[16,0],[18,0],[20,1],[21,3],[21,5],[19,7],[17,7]]},"&":{width:26,points:[[23,12],[23,13],[22,14],[21,14],[20,13],[19,11],[17,6],[15,3],[13,1],[11,0],[7,0],[5,1],[4,2],[3,4],[3,6],[4,8],[5,9],[12,13],[13,14],[14,16],[14,18],[13,20],[11,21],[9,20],[8,18],[8,16],[9,13],[11,10],[16,3],[18,1],[20,0],[22,0],[23,1],[23,2]]},"'":{width:10,points:[[5,19],[4,20],[5,21],[6,20],[6,18],[5,16],[4,15]]},"(":{width:14,points:[[11,25],[9,23],[7,20],[5,16],[4,11],[4,7],[5,2],[7,-2],[9,-5],[11,-7]]},")":{width:14,points:[[3,25],[5,23],[7,20],[9,16],[10,11],[10,7],[9,2],[7,-2],[5,-5],[3,-7]]},"*":{width:16,points:[[8,21],[8,9],null,[3,18],[13,12],null,[13,18],[3,12]]},"+":{width:26,points:[[13,18],[13,0],null,[4,9],[22,9]]},",":{width:10,points:[[6,1],[5,0],[4,1],[5,2],[6,1],[6,-1],[5,-3],[4,-4]]},"-":{width:26,points:[[4,9],[22,9]]},".":{width:10,points:[[5,2],[4,1],[5,0],[6,1],[5,2]]},"/":{width:22,points:[[20,25],[2,-7]]},0:{width:20,points:[[9,21],[6,20],[4,17],[3,12],[3,9],[4,4],[6,1],[9,0],[11,0],[14,1],[16,4],[17,9],[17,12],[16,17],[14,20],[11,21],[9,21]]},1:{width:20,points:[[6,17],[8,18],[11,21],[11,0]]},2:{width:20,points:[[4,16],[4,17],[5,19],[6,20],[8,21],[12,21],[14,20],[15,19],[16,17],[16,15],[15,13],[13,10],[3,0],[17,0]]},3:{width:20,points:[[5,21],[16,21],[10,13],[13,13],[15,12],[16,11],[17,8],[17,6],[16,3],[14,1],[11,0],[8,0],[5,1],[4,2],[3,4]]},4:{width:20,points:[[13,21],[3,7],[18,7],null,[13,21],[13,0]]},5:{width:20,points:[[15,21],[5,21],[4,12],[5,13],[8,14],[11,14],[14,13],[16,11],[17,8],[17,6],[16,3],[14,1],[11,0],[8,0],[5,1],[4,2],[3,4]]},6:{width:20,points:[[16,18],[15,20],[12,21],[10,21],[7,20],[5,17],[4,12],[4,7],[5,3],[7,1],[10,0],[11,0],[14,1],[16,3],[17,6],[17,7],[16,10],[14,12],[11,13],[10,13],[7,12],[5,10],[4,7]]},7:{width:20,points:[[17,21],[7,0],null,[3,21],[17,21]]},8:{width:20,points:[[8,21],[5,20],[4,18],[4,16],[5,14],[7,13],[11,12],[14,11],[16,9],[17,7],[17,4],[16,2],[15,1],[12,0],[8,0],[5,1],[4,2],[3,4],[3,7],[4,9],[6,11],[9,12],[13,13],[15,14],[16,16],[16,18],[15,20],[12,21],[8,21]]},9:{width:20,points:[[16,14],[15,11],[13,9],[10,8],[9,8],[6,9],[4,11],[3,14],[3,15],[4,18],[6,20],[9,21],[10,21],[13,20],[15,18],[16,14],[16,9],[15,4],[13,1],[10,0],[8,0],[5,1],[4,3]]},":":{width:10,points:[[5,14],[4,13],[5,12],[6,13],[5,14],null,[5,2],[4,1],[5,0],[6,1],[5,2]]},";":{width:10,points:[[5,14],[4,13],[5,12],[6,13],[5,14],null,[6,1],[5,0],[4,1],[5,2],[6,1],[6,-1],[5,-3],[4,-4]]},"<":{width:24,points:[[20,18],[4,9],[20,0]]},"=":{width:26,points:[[4,12],[22,12],null,[4,6],[22,6]]},">":{width:24,points:[[4,18],[20,9],[4,0]]},"?":{width:18,points:[[3,16],[3,17],[4,19],[5,20],[7,21],[11,21],[13,20],[14,19],[15,17],[15,15],[14,13],[13,12],[9,10],[9,7],null,[9,2],[8,1],[9,0],[10,1],[9,2]]},"@":{width:27,points:[[18,13],[17,15],[15,16],[12,16],[10,15],[9,14],[8,11],[8,8],[9,6],[11,5],[14,5],[16,6],[17,8],null,[12,16],[10,14],[9,11],[9,8],[10,6],[11,5],null,[18,16],[17,8],[17,6],[19,5],[21,5],[23,7],[24,10],[24,12],[23,15],[22,17],[20,19],[18,20],[15,21],[12,21],[9,20],[7,19],[5,17],[4,15],[3,12],[3,9],[4,6],[5,4],[7,2],[9,1],[12,0],[15,0],[18,1],[20,2],[21,3],null,[19,16],[18,8],[18,6],[19,5]]},A:{width:18,points:[[9,21],[1,0],null,[9,21],[17,0],null,[4,7],[14,7]]},B:{width:21,points:[[4,21],[4,0],null,[4,21],[13,21],[16,20],[17,19],[18,17],[18,15],[17,13],[16,12],[13,11],null,[4,11],[13,11],[16,10],[17,9],[18,7],[18,4],[17,2],[16,1],[13,0],[4,0]]},C:{width:21,points:[[18,16],[17,18],[15,20],[13,21],[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5]]},D:{width:21,points:[[4,21],[4,0],null,[4,21],[11,21],[14,20],[16,18],[17,16],[18,13],[18,8],[17,5],[16,3],[14,1],[11,0],[4,0]]},E:{width:19,points:[[4,21],[4,0],null,[4,21],[17,21],null,[4,11],[12,11],null,[4,0],[17,0]]},F:{width:18,points:[[4,21],[4,0],null,[4,21],[17,21],null,[4,11],[12,11]]},G:{width:21,points:[[18,16],[17,18],[15,20],[13,21],[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5],[18,8],null,[13,8],[18,8]]},H:{width:22,points:[[4,21],[4,0],null,[18,21],[18,0],null,[4,11],[18,11]]},I:{width:8,points:[[4,21],[4,0]]},J:{width:16,points:[[12,21],[12,5],[11,2],[10,1],[8,0],[6,0],[4,1],[3,2],[2,5],[2,7]]},K:{width:21,points:[[4,21],[4,0],null,[18,21],[4,7],null,[9,12],[18,0]]},L:{width:17,points:[[4,21],[4,0],null,[4,0],[16,0]]},M:{width:24,points:[[4,21],[4,0],null,[4,21],[12,0],null,[20,21],[12,0],null,[20,21],[20,0]]},N:{width:22,points:[[4,21],[4,0],null,[4,21],[18,0],null,[18,21],[18,0]]},O:{width:22,points:[[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5],[19,8],[19,13],[18,16],[17,18],[15,20],[13,21],[9,21]]},P:{width:21,points:[[4,21],[4,0],null,[4,21],[13,21],[16,20],[17,19],[18,17],[18,14],[17,12],[16,11],[13,10],[4,10]]},Q:{width:22,points:[[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5],[19,8],[19,13],[18,16],[17,18],[15,20],[13,21],[9,21],null,[12,4],[18,-2]]},R:{width:21,points:[[4,21],[4,0],null,[4,21],[13,21],[16,20],[17,19],[18,17],[18,15],[17,13],[16,12],[13,11],[4,11],null,[11,11],[18,0]]},S:{width:20,points:[[17,18],[15,20],[12,21],[8,21],[5,20],[3,18],[3,16],[4,14],[5,13],[7,12],[13,10],[15,9],[16,8],[17,6],[17,3],[15,1],[12,0],[8,0],[5,1],[3,3]]},T:{width:16,points:[[8,21],[8,0],null,[1,21],[15,21]]},U:{width:22,points:[[4,21],[4,6],[5,3],[7,1],[10,0],[12,0],[15,1],[17,3],[18,6],[18,21]]},V:{width:18,points:[[1,21],[9,0],null,[17,21],[9,0]]},W:{width:24,points:[[2,21],[7,0],null,[12,21],[7,0],null,[12,21],[17,0],null,[22,21],[17,0]]},X:{width:20,points:[[3,21],[17,0],null,[17,21],[3,0]]},Y:{width:18,points:[[1,21],[9,11],[9,0],null,[17,21],[9,11]]},Z:{width:20,points:[[17,21],[3,0],null,[3,21],[17,21],null,[3,0],[17,0]]},"[":{width:14,points:[[4,25],[4,-7],null,[5,25],[5,-7],null,[4,25],[11,25],null,[4,-7],[11,-7]]},"\\":{width:14,points:[[0,21],[14,-3]]},"]":{width:14,points:[[9,25],[9,-7],null,[10,25],[10,-7],null,[3,25],[10,25],null,[3,-7],[10,-7]]},"^":{width:14,points:[[3,10],[8,18],[13,10]]},_:{width:16,points:[[0,-2],[16,-2]]},"`":{width:10,points:[[6,21],[5,20],[4,18],[4,16],[5,15],[6,16],[5,17]]},a:{width:19,points:[[15,14],[15,0],null,[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]]},b:{width:19,points:[[4,21],[4,0],null,[4,11],[6,13],[8,14],[11,14],[13,13],[15,11],[16,8],[16,6],[15,3],[13,1],[11,0],[8,0],[6,1],[4,3]]},c:{width:18,points:[[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]]},d:{width:19,points:[[15,21],[15,0],null,[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]]},e:{width:18,points:[[3,8],[15,8],[15,10],[14,12],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]]},f:{width:12,points:[[10,21],[8,21],[6,20],[5,17],[5,0],null,[2,14],[9,14]]},g:{width:19,points:[[15,14],[15,-2],[14,-5],[13,-6],[11,-7],[8,-7],[6,-6],null,[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]]},h:{width:19,points:[[4,21],[4,0],null,[4,10],[7,13],[9,14],[12,14],[14,13],[15,10],[15,0]]},i:{width:8,points:[[3,21],[4,20],[5,21],[4,22],[3,21],null,[4,14],[4,0]]},j:{width:10,points:[[5,21],[6,20],[7,21],[6,22],[5,21],null,[6,14],[6,-3],[5,-6],[3,-7],[1,-7]]},k:{width:17,points:[[4,21],[4,0],null,[14,14],[4,4],null,[8,8],[15,0]]},l:{width:8,points:[[4,21],[4,0]]},m:{width:30,points:[[4,14],[4,0],null,[4,10],[7,13],[9,14],[12,14],[14,13],[15,10],[15,0],null,[15,10],[18,13],[20,14],[23,14],[25,13],[26,10],[26,0]]},n:{width:19,points:[[4,14],[4,0],null,[4,10],[7,13],[9,14],[12,14],[14,13],[15,10],[15,0]]},o:{width:19,points:[[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3],[16,6],[16,8],[15,11],[13,13],[11,14],[8,14]]},p:{width:19,points:[[4,14],[4,-7],null,[4,11],[6,13],[8,14],[11,14],[13,13],[15,11],[16,8],[16,6],[15,3],[13,1],[11,0],[8,0],[6,1],[4,3]]},q:{width:19,points:[[15,14],[15,-7],null,[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]]},r:{width:13,points:[[4,14],[4,0],null,[4,8],[5,11],[7,13],[9,14],[12,14]]},s:{width:17,points:[[14,11],[13,13],[10,14],[7,14],[4,13],[3,11],[4,9],[6,8],[11,7],[13,6],[14,4],[14,3],[13,1],[10,0],[7,0],[4,1],[3,3]]},t:{width:12,points:[[5,21],[5,4],[6,1],[8,0],[10,0],null,[2,14],[9,14]]},u:{width:19,points:[[4,14],[4,4],[5,1],[7,0],[10,0],[12,1],[15,4],null,[15,14],[15,0]]},v:{width:16,points:[[2,14],[8,0],null,[14,14],[8,0]]},w:{width:22,points:[[3,14],[7,0],null,[11,14],[7,0],null,[11,14],[15,0],null,[19,14],[15,0]]},x:{width:17,points:[[3,14],[14,0],null,[14,14],[3,0]]},y:{width:16,points:[[2,14],[8,0],null,[14,14],[8,0],[6,-4],[4,-6],[2,-7],[1,-7]]},z:{width:17,points:[[14,14],[3,0],null,[3,14],[14,14],null,[3,0],[14,0]]},"{":{width:14,points:[[9,25],[7,24],[6,23],[5,21],[5,19],[6,17],[7,16],[8,14],[8,12],[6,10],null,[7,24],[6,22],[6,20],[7,18],[8,17],[9,15],[9,13],[8,11],[4,9],[8,7],[9,5],[9,3],[8,1],[7,0],[6,-2],[6,-4],[7,-6],null,[6,8],[8,6],[8,4],[7,2],[6,1],[5,-1],[5,-3],[6,-5],[7,-6],[9,-7]]},"|":{width:8,points:[[4,25],[4,-7]]},"}":{width:14,points:[[5,25],[7,24],[8,23],[9,21],[9,19],[8,17],[7,16],[6,14],[6,12],[8,10],null,[7,24],[8,22],[8,20],[7,18],[6,17],[5,15],[5,13],[6,11],[10,9],[6,7],[5,5],[5,3],[6,1],[7,0],[8,-2],[8,-4],[7,-6],null,[8,8],[6,6],[6,4],[7,2],[8,1],[9,-1],[9,-3],[8,-5],[7,-6],[5,-7]]},"~":{width:24,points:[[3,6],[3,8],[4,11],[6,12],[8,12],[10,11],[14,8],[16,7],[18,7],[20,8],[21,10],null,[3,8],[4,10],[6,11],[8,11],[10,10],[14,7],[16,6],[18,6],[20,7],[21,10],[21,12]]},"�":{diacritic:"`",letter:"a"},"�":{diacritic:"�",letter:"a"},"�":{diacritic:"^",letter:"a"},"�":{diacritic:"�",letter:"a"},"�":{diacritic:"~",letter:"a"},"�":{diacritic:"`",letter:"e"},"�":{diacritic:"�",letter:"e"},"�":{diacritic:"^",letter:"e"},"�":{diacritic:"�",letter:"e"},"�":{diacritic:"`",letter:"i"},"�":{diacritic:"�",letter:"i"},"�":{diacritic:"^",letter:"i"},"�":{diacritic:"�",letter:"i"},"�":{diacritic:"`",letter:"o"},"�":{diacritic:"�",letter:"o"},"�":{diacritic:"^",letter:"o"},"�":{diacritic:"�",letter:"o"},"�":{diacritic:"~",letter:"o"},"�":{diacritic:"`",letter:"u"},"�":{diacritic:"�",letter:"u"},"�":{diacritic:"^",letter:"u"},"�":{diacritic:"�",letter:"u"},"�":{diacritic:"�",letter:"y"},"�":{diacritic:"�",letter:"y"},"�":{diacritic:"�",letter:"c"},"�":{diacritic:"~",letter:"n"},"�":{diacritic:"`",letter:"A"},"�":{diacritic:"�",letter:"A"},"�":{diacritic:"^",letter:"A"},"�":{diacritic:"�",letter:"A"},"�":{diacritic:"~",letter:"A"},"�":{diacritic:"`",letter:"E"},"�":{diacritic:"�",letter:"E"},"�":{diacritic:"^",letter:"E"},"�":{diacritic:"�",letter:"E"},"�":{diacritic:"`",letter:"I"},"�":{diacritic:"�",letter:"I"},"�":{diacritic:"^",letter:"I"},"�":{diacritic:"�",letter:"I"},"�":{diacritic:"`",letter:"O"},"�":{diacritic:"�",letter:"O"},"�":{diacritic:"^",letter:"O"},"�":{diacritic:"�",letter:"O"},"�":{diacritic:"~",letter:"O"},"�":{diacritic:"`",letter:"U"},"�":{diacritic:"�",letter:"U"},"�":{diacritic:"^",letter:"U"},"�":{diacritic:"�",letter:"U"},"�":{diacritic:"�",letter:"Y"},"�":{diacritic:"�",letter:"C"},"�":{diacritic:"~",letter:"N"}},specialchars:{pi:{width:19,points:[[6,14],[6,0],null,[14,14],[14,0],null,[2,13],[6,16],[13,13],[17,16]]}},diacritics:{"�":{entity:"cedil",points:[[6,-4],[4,-6],[2,-7],[1,-7]]},"�":{entity:"acute",points:[[8,19],[13,22]]},"`":{entity:"grave",points:[[7,22],[12,19]]},"^":{entity:"circ",points:[[5.5,19],[9.5,23],[12.5,19]]},"�":{entity:"trema",points:[[5,21],[6,20],[7,21],[6,22],[5,21],null,[12,21],[13,20],[14,21],[13,22],[12,21]]},"~":{entity:"tilde",points:[[4,18],[7,22],[10,18],[13,22]]}},style:{size:8,font:null,color:"#000000",weight:1,textAlign:"left",textBaseline:"bottom",adjustAlign:!1,angle:0,tracking:1,boundingBoxColor:"#ff0000",originPointColor:"#000000"},debug:!1,_bufferLexemes:{},extend:function(a,b){for(var c in b){if(c in a)continue;a[c]=b[c]}return a},letter:function(a){return CanvasText.letters[a]},parseLexemes:function(a){if(CanvasText._bufferLexemes[a])return CanvasText._bufferLexemes[a];var b,c,d=a.match(/&[A-Za-z]{2,5};|\s|./g),e=[],f=[];for(b=0;b<d.length;b++){c=d[b];if(c.length==1)f.push(c);else{var g=c.substring(1,c.length-1);CanvasText.specialchars[g]?f.push(g):f=f.concat(c.toArray())}}for(b=0;b<f.length;b++)c=f[b],(c=CanvasText.letters[c]||CanvasText.specialchars[c])&&e.push(c);for(b=0;b<e.length;b++)(e===null||typeof e=="undefined")&&delete e[b];return CanvasText._bufferLexemes[a]=e},ascent:function(a){return a=a||CanvasText.style,a.size||CanvasText.style.size},descent:function(a){return a=a||CanvasText.style,7*(a.size||CanvasText.style.size)/25},measure:function(a,b){if(!a)return;b=b||CanvasText.style;var d,e,f=CanvasText.parseLexemes(a),g=0;for(d=f.length-1;d>-1;--d)c=f[d],e=c.diacritic?CanvasText.letter(c.letter).width:c.width,g+=e*(b.tracking||CanvasText.style.tracking)*(b.size||CanvasText.style.size)/25;return g},getDimensions:function(a,b){b=b||CanvasText.style;var c=CanvasText.measure(a,b),d=b.size||CanvasText.style.size,e=b.angle||CanvasText.style.angle;return b.angle==0?{width:c,height:d}:{width:Math.abs(Math.cos(e)*c)+Math.abs(Math.sin(e)*d),height:Math.abs(Math.sin(e)*c)+Math.abs(Math.cos(e)*d)}},drawPoints:function(a,b,c,d,e,f){var g,h,i=!0,j=0;f=f||{x:0,y:0},a.beginPath();for(g=0;g<b.length;g++){h=b[g];if(!h){i=!0;continue}i?(a.moveTo(c+h[0]*e+f.x,d-h[1]*e+f.y),i=!1):a.lineTo(c+h[0]*e+f.x,d-h[1]*e+f.y)}a.stroke(),a.closePath()},draw:function(a,b,c,d){if(!a)return;CanvasText.extend(d,CanvasText.style);var e,f,g=0,h=d.size/25,i=0,j=0,k=CanvasText.parseLexemes(a),l={x:0,y:0},m=CanvasText.measure(a,d),n;d.adjustAlign&&(n=CanvasText.getBestAlign(d.angle,d),CanvasText.extend(d,n));switch(d.textAlign){case"left":break;case"center":l.x=-m/2;break;case"right":l.x=-m}switch(d.textBaseline){case"bottom":break;case"middle":l.y=d.size/2;break;case"top":l.y=d.size}this.save(),this.translate(b,c),this.rotate(d.angle),this.lineCap="round",this.lineWidth=2*h*(d.weight||CanvasText.style.weight),this.strokeStyle=d.color||CanvasText.style.color;for(e=0;e<k.length;e++){f=k[e];if(f.width==-1){i=0,j=d.size*1.4;continue}var o=f.points,p=f.width;if(f.diacritic){var q=CanvasText.diacritics[f.diacritic],r=CanvasText.letter(f.letter);CanvasText.drawPoints(this,q.points,i,j-(f.letter.toUpperCase()==f.letter?3:0),h,l),o=r.points,p=r.width}CanvasText.drawPoints(this,o,i,j,h,l),CanvasText.debug&&(this.save(),this.lineJoin="miter",this.lineWidth=.5,this.strokeStyle=d.boundingBoxColor||CanvasText.style.boundingBoxColor,this.strokeRect(i+l.x,j+l.y,p*h,-d.size),this.fillStyle=d.originPointColor||CanvasText.style.originPointColor,this.beginPath(),this.arc(0,0,1.5,0,Math.PI*2,!0),this.fill(),this.closePath(),this.restore()),i+=p*h*(d.tracking||CanvasText.style.tracking)}return this.restore(),g}};CanvasText.proto=window.CanvasRenderingContext2D?window.CanvasRenderingContext2D.prototype:document.createElement("canvas").getContext("2d").__proto__,CanvasText.proto&&(CanvasText.proto.drawText=CanvasText.draw,CanvasText.proto.measure=CanvasText.measure,CanvasText.proto.getTextBounds=CanvasText.getDimensions,CanvasText.proto.fontAscent=CanvasText.ascent,CanvasText.proto.fontDescent=CanvasText.descent)
\ No newline at end of file diff --git a/addons/web_graph/static/lib/flotr2/flotr2.js b/addons/web_graph/static/lib/flotr2/flotr2.js deleted file mode 100644 index 8431b4d9ee9..00000000000 --- a/addons/web_graph/static/lib/flotr2/flotr2.js +++ /dev/null @@ -1,6865 +0,0 @@ -/*! - * bean.js - copyright Jacob Thornton 2011 - * https://github.com/fat/bean - * MIT License - * special thanks to: - * dean edwards: http://dean.edwards.name/ - * dperini: https://github.com/dperini/nwevents - * the entire mootools team: github.com/mootools/mootools-core - */ -/*global module:true, define:true*/ -!function (name, context, definition) { - if (typeof module !== 'undefined') module.exports = definition(name, context); - else if (typeof define === 'function' && typeof define.amd === 'object') define(definition); - else context[name] = definition(name, context); -}('bean', this, function (name, context) { - var win = window - , old = context[name] - , overOut = /over|out/ - , namespaceRegex = /[^\.]*(?=\..*)\.|.*/ - , nameRegex = /\..*/ - , addEvent = 'addEventListener' - , attachEvent = 'attachEvent' - , removeEvent = 'removeEventListener' - , detachEvent = 'detachEvent' - , doc = document || {} - , root = doc.documentElement || {} - , W3C_MODEL = root[addEvent] - , eventSupport = W3C_MODEL ? addEvent : attachEvent - , slice = Array.prototype.slice - , mouseTypeRegex = /click|mouse|menu|drag|drop/i - , touchTypeRegex = /^touch|^gesture/i - , ONE = { one: 1 } // singleton for quick matching making add() do one() - - , nativeEvents = (function (hash, events, i) { - for (i = 0; i < events.length; i++) - hash[events[i]] = 1 - return hash - })({}, ( - 'click dblclick mouseup mousedown contextmenu ' + // mouse buttons - 'mousewheel DOMMouseScroll ' + // mouse wheel - 'mouseover mouseout mousemove selectstart selectend ' + // mouse movement - 'keydown keypress keyup ' + // keyboard - 'orientationchange ' + // mobile - 'focus blur change reset select submit ' + // form elements - 'load unload beforeunload resize move DOMContentLoaded readystatechange ' + // window - 'error abort scroll ' + // misc - (W3C_MODEL ? // element.fireEvent('onXYZ'... is not forgiving if we try to fire an event - // that doesn't actually exist, so make sure we only do these on newer browsers - 'show ' + // mouse buttons - 'input invalid ' + // form elements - 'touchstart touchmove touchend touchcancel ' + // touch - 'gesturestart gesturechange gestureend ' + // gesture - 'message readystatechange pageshow pagehide popstate ' + // window - 'hashchange offline online ' + // window - 'afterprint beforeprint ' + // printing - 'dragstart dragenter dragover dragleave drag drop dragend ' + // dnd - 'loadstart progress suspend emptied stalled loadmetadata ' + // media - 'loadeddata canplay canplaythrough playing waiting seeking ' + // media - 'seeked ended durationchange timeupdate play pause ratechange ' + // media - 'volumechange cuechange ' + // media - 'checking noupdate downloading cached updateready obsolete ' + // appcache - '' : '') - ).split(' ') - ) - - , customEvents = (function () { - function isDescendant(parent, node) { - while ((node = node.parentNode) !== null) { - if (node === parent) return true - } - return false - } - - function check(event) { - var related = event.relatedTarget - if (!related) return related === null - return (related !== this && related.prefix !== 'xul' && !/document/.test(this.toString()) && !isDescendant(this, related)) - } - - return { - mouseenter: { base: 'mouseover', condition: check } - , mouseleave: { base: 'mouseout', condition: check } - , mousewheel: { base: /Firefox/.test(navigator.userAgent) ? 'DOMMouseScroll' : 'mousewheel' } - } - })() - - , fixEvent = (function () { - var commonProps = 'altKey attrChange attrName bubbles cancelable ctrlKey currentTarget detail eventPhase getModifierState isTrusted metaKey relatedNode relatedTarget shiftKey srcElement target timeStamp type view which'.split(' ') - , mouseProps = commonProps.concat('button buttons clientX clientY dataTransfer fromElement offsetX offsetY pageX pageY screenX screenY toElement'.split(' ')) - , keyProps = commonProps.concat('char charCode key keyCode'.split(' ')) - , touchProps = commonProps.concat('touches targetTouches changedTouches scale rotation'.split(' ')) - , preventDefault = 'preventDefault' - , createPreventDefault = function (event) { - return function () { - if (event[preventDefault]) - event[preventDefault]() - else - event.returnValue = false - } - } - , stopPropagation = 'stopPropagation' - , createStopPropagation = function (event) { - return function () { - if (event[stopPropagation]) - event[stopPropagation]() - else - event.cancelBubble = true - } - } - , createStop = function (synEvent) { - return function () { - synEvent[preventDefault]() - synEvent[stopPropagation]() - synEvent.stopped = true - } - } - , copyProps = function (event, result, props) { - var i, p - for (i = props.length; i--;) { - p = props[i] - if (!(p in result) && p in event) result[p] = event[p] - } - } - - return function (event, isNative) { - var result = { originalEvent: event, isNative: isNative } - if (!event) - return result - - var props - , type = event.type - , target = event.target || event.srcElement - - result[preventDefault] = createPreventDefault(event) - result[stopPropagation] = createStopPropagation(event) - result.stop = createStop(result) - result.target = target && target.nodeType === 3 ? target.parentNode : target - - if (isNative) { // we only need basic augmentation on custom events, the rest is too expensive - if (type.indexOf('key') !== -1) { - props = keyProps - result.keyCode = event.which || event.keyCode - } else if (mouseTypeRegex.test(type)) { - props = mouseProps - result.rightClick = event.which === 3 || event.button === 2 - result.pos = { x: 0, y: 0 } - if (event.pageX || event.pageY) { - result.clientX = event.pageX - result.clientY = event.pageY - } else if (event.clientX || event.clientY) { - result.clientX = event.clientX + doc.body.scrollLeft + root.scrollLeft - result.clientY = event.clientY + doc.body.scrollTop + root.scrollTop - } - if (overOut.test(type)) - result.relatedTarget = event.relatedTarget || event[(type === 'mouseover' ? 'from' : 'to') + 'Element'] - } else if (touchTypeRegex.test(type)) { - props = touchProps - } - copyProps(event, result, props || commonProps) - } - return result - } - })() - - // if we're in old IE we can't do onpropertychange on doc or win so we use doc.documentElement for both - , targetElement = function (element, isNative) { - return !W3C_MODEL && !isNative && (element === doc || element === win) ? root : element - } - - // we use one of these per listener, of any type - , RegEntry = (function () { - function entry(element, type, handler, original, namespaces) { - this.element = element - this.type = type - this.handler = handler - this.original = original - this.namespaces = namespaces - this.custom = customEvents[type] - this.isNative = nativeEvents[type] && element[eventSupport] - this.eventType = W3C_MODEL || this.isNative ? type : 'propertychange' - this.customType = !W3C_MODEL && !this.isNative && type - this.target = targetElement(element, this.isNative) - this.eventSupport = this.target[eventSupport] - } - - entry.prototype = { - // given a list of namespaces, is our entry in any of them? - inNamespaces: function (checkNamespaces) { - var i, j - if (!checkNamespaces) - return true - if (!this.namespaces) - return false - for (i = checkNamespaces.length; i--;) { - for (j = this.namespaces.length; j--;) { - if (checkNamespaces[i] === this.namespaces[j]) - return true - } - } - return false - } - - // match by element, original fn (opt), handler fn (opt) - , matches: function (checkElement, checkOriginal, checkHandler) { - return this.element === checkElement && - (!checkOriginal || this.original === checkOriginal) && - (!checkHandler || this.handler === checkHandler) - } - } - - return entry - })() - - , registry = (function () { - // our map stores arrays by event type, just because it's better than storing - // everything in a single array. uses '$' as a prefix for the keys for safety - var map = {} - - // generic functional search of our registry for matching listeners, - // `fn` returns false to break out of the loop - , forAll = function (element, type, original, handler, fn) { - if (!type || type === '*') { - // search the whole registry - for (var t in map) { - if (t.charAt(0) === '$') - forAll(element, t.substr(1), original, handler, fn) - } - } else { - var i = 0, l, list = map['$' + type], all = element === '*' - if (!list) - return - for (l = list.length; i < l; i++) { - if (all || list[i].matches(element, original, handler)) - if (!fn(list[i], list, i, type)) - return - } - } - } - - , has = function (element, type, original) { - // we're not using forAll here simply because it's a bit slower and this - // needs to be fast - var i, list = map['$' + type] - if (list) { - for (i = list.length; i--;) { - if (list[i].matches(element, original, null)) - return true - } - } - return false - } - - , get = function (element, type, original) { - var entries = [] - forAll(element, type, original, null, function (entry) { return entries.push(entry) }) - return entries - } - - , put = function (entry) { - (map['$' + entry.type] || (map['$' + entry.type] = [])).push(entry) - return entry - } - - , del = function (entry) { - forAll(entry.element, entry.type, null, entry.handler, function (entry, list, i) { - list.splice(i, 1) - if (list.length === 0) - delete map['$' + entry.type] - return false - }) - } - - // dump all entries, used for onunload - , entries = function () { - var t, entries = [] - for (t in map) { - if (t.charAt(0) === '$') - entries = entries.concat(map[t]) - } - return entries - } - - return { has: has, get: get, put: put, del: del, entries: entries } - })() - - // add and remove listeners to DOM elements - , listener = W3C_MODEL ? function (element, type, fn, add) { - element[add ? addEvent : removeEvent](type, fn, false) - } : function (element, type, fn, add, custom) { - if (custom && add && element['_on' + custom] === null) - element['_on' + custom] = 0 - element[add ? attachEvent : detachEvent]('on' + type, fn) - } - - , nativeHandler = function (element, fn, args) { - return function (event) { - event = fixEvent(event || ((this.ownerDocument || this.document || this).parentWindow || win).event, true) - return fn.apply(element, [event].concat(args)) - } - } - - , customHandler = function (element, fn, type, condition, args, isNative) { - return function (event) { - if (condition ? condition.apply(this, arguments) : W3C_MODEL ? true : event && event.propertyName === '_on' + type || !event) { - if (event) - event = fixEvent(event || ((this.ownerDocument || this.document || this).parentWindow || win).event, isNative) - fn.apply(element, event && (!args || args.length === 0) ? arguments : slice.call(arguments, event ? 0 : 1).concat(args)) - } - } - } - - , once = function (rm, element, type, fn, originalFn) { - // wrap the handler in a handler that does a remove as well - return function () { - rm(element, type, originalFn) - fn.apply(this, arguments) - } - } - - , removeListener = function (element, orgType, handler, namespaces) { - var i, l, entry - , type = (orgType && orgType.replace(nameRegex, '')) - , handlers = registry.get(element, type, handler) - - for (i = 0, l = handlers.length; i < l; i++) { - if (handlers[i].inNamespaces(namespaces)) { - if ((entry = handlers[i]).eventSupport) - listener(entry.target, entry.eventType, entry.handler, false, entry.type) - // TODO: this is problematic, we have a registry.get() and registry.del() that - // both do registry searches so we waste cycles doing this. Needs to be rolled into - // a single registry.forAll(fn) that removes while finding, but the catch is that - // we'll be splicing the arrays that we're iterating over. Needs extra tests to - // make sure we don't screw it up. @rvagg - registry.del(entry) - } - } - } - - , addListener = function (element, orgType, fn, originalFn, args) { - var entry - , type = orgType.replace(nameRegex, '') - , namespaces = orgType.replace(namespaceRegex, '').split('.') - - if (registry.has(element, type, fn)) - return element // no dupe - if (type === 'unload') - fn = once(removeListener, element, type, fn, originalFn) // self clean-up - if (customEvents[type]) { - if (customEvents[type].condition) - fn = customHandler(element, fn, type, customEvents[type].condition, true) - type = customEvents[type].base || type - } - entry = registry.put(new RegEntry(element, type, fn, originalFn, namespaces[0] && namespaces)) - entry.handler = entry.isNative ? - nativeHandler(element, entry.handler, args) : - customHandler(element, entry.handler, type, false, args, false) - if (entry.eventSupport) - listener(entry.target, entry.eventType, entry.handler, true, entry.customType) - } - - , del = function (selector, fn, $) { - return function (e) { - var target, i, array = typeof selector === 'string' ? $(selector, this) : selector - for (target = e.target; target && target !== this; target = target.parentNode) { - for (i = array.length; i--;) { - if (array[i] === target) { - return fn.apply(target, arguments) - } - } - } - } - } - - , remove = function (element, typeSpec, fn) { - var k, m, type, namespaces, i - , rm = removeListener - , isString = typeSpec && typeof typeSpec === 'string' - - if (isString && typeSpec.indexOf(' ') > 0) { - // remove(el, 't1 t2 t3', fn) or remove(el, 't1 t2 t3') - typeSpec = typeSpec.split(' ') - for (i = typeSpec.length; i--;) - remove(element, typeSpec[i], fn) - return element - } - type = isString && typeSpec.replace(nameRegex, '') - if (type && customEvents[type]) - type = customEvents[type].type - if (!typeSpec || isString) { - // remove(el) or remove(el, t1.ns) or remove(el, .ns) or remove(el, .ns1.ns2.ns3) - if (namespaces = isString && typeSpec.replace(namespaceRegex, '')) - namespaces = namespaces.split('.') - rm(element, type, fn, namespaces) - } else if (typeof typeSpec === 'function') { - // remove(el, fn) - rm(element, null, typeSpec) - } else { - // remove(el, { t1: fn1, t2, fn2 }) - for (k in typeSpec) { - if (typeSpec.hasOwnProperty(k)) - remove(element, k, typeSpec[k]) - } - } - return element - } - - , add = function (element, events, fn, delfn, $) { - var type, types, i, args - , originalFn = fn - , isDel = fn && typeof fn === 'string' - - if (events && !fn && typeof events === 'object') { - for (type in events) { - if (events.hasOwnProperty(type)) - add.apply(this, [ element, type, events[type] ]) - } - } else { - args = arguments.length > 3 ? slice.call(arguments, 3) : [] - types = (isDel ? fn : events).split(' ') - isDel && (fn = del(events, (originalFn = delfn), $)) && (args = slice.call(args, 1)) - // special case for one() - this === ONE && (fn = once(remove, element, events, fn, originalFn)) - for (i = types.length; i--;) addListener(element, types[i], fn, originalFn, args) - } - return element - } - - , one = function () { - return add.apply(ONE, arguments) - } - - , fireListener = W3C_MODEL ? function (isNative, type, element) { - var evt = doc.createEvent(isNative ? 'HTMLEvents' : 'UIEvents') - evt[isNative ? 'initEvent' : 'initUIEvent'](type, true, true, win, 1) - element.dispatchEvent(evt) - } : function (isNative, type, element) { - element = targetElement(element, isNative) - // if not-native then we're using onpropertychange so we just increment a custom property - isNative ? element.fireEvent('on' + type, doc.createEventObject()) : element['_on' + type]++ - } - - , fire = function (element, type, args) { - var i, j, l, names, handlers - , types = type.split(' ') - - for (i = types.length; i--;) { - type = types[i].replace(nameRegex, '') - if (names = types[i].replace(namespaceRegex, '')) - names = names.split('.') - if (!names && !args && element[eventSupport]) { - fireListener(nativeEvents[type], type, element) - } else { - // non-native event, either because of a namespace, arguments or a non DOM element - // iterate over all listeners and manually 'fire' - handlers = registry.get(element, type) - args = [false].concat(args) - for (j = 0, l = handlers.length; j < l; j++) { - if (handlers[j].inNamespaces(names)) - handlers[j].handler.apply(element, args) - } - } - } - return element - } - - , clone = function (element, from, type) { - var i = 0 - , handlers = registry.get(from, type) - , l = handlers.length - - for (;i < l; i++) - handlers[i].original && add(element, handlers[i].type, handlers[i].original) - return element - } - - , bean = { - add: add - , one: one - , remove: remove - , clone: clone - , fire: fire - , noConflict: function () { - context[name] = old - return this - } - } - - if (win[attachEvent]) { - // for IE, clean up on unload to avoid leaks - var cleanup = function () { - var i, entries = registry.entries() - for (i in entries) { - if (entries[i].type && entries[i].type !== 'unload') - remove(entries[i].element, entries[i].type) - } - win[detachEvent]('onunload', cleanup) - win.CollectGarbage && win.CollectGarbage() - } - win[attachEvent]('onunload', cleanup) - } - - return bean -}); -// Underscore.js 1.1.7 -// (c) 2011 Jeremy Ashkenas, DocumentCloud Inc. -// Underscore is freely distributable under the MIT license. -// Portions of Underscore are inspired or borrowed from Prototype, -// Oliver Steele's Functional, and John Resig's Micro-Templating. -// For all details and documentation: -// http://documentcloud.github.com/underscore - -(function() { - - // Baseline setup - // -------------- - - // Establish the root object, `window` in the browser, or `global` on the server. - var root = this; - - // Save the previous value of the `_` variable. - var previousUnderscore = root._; - - // Establish the object that gets returned to break out of a loop iteration. - var breaker = {}; - - // Save bytes in the minified (but not gzipped) version: - var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype; - - // Create quick reference variables for speed access to core prototypes. - var slice = ArrayProto.slice, - unshift = ArrayProto.unshift, - toString = ObjProto.toString, - hasOwnProperty = ObjProto.hasOwnProperty; - - // All **ECMAScript 5** native function implementations that we hope to use - // are declared here. - var - nativeForEach = ArrayProto.forEach, - nativeMap = ArrayProto.map, - nativeReduce = ArrayProto.reduce, - nativeReduceRight = ArrayProto.reduceRight, - nativeFilter = ArrayProto.filter, - nativeEvery = ArrayProto.every, - nativeSome = ArrayProto.some, - nativeIndexOf = ArrayProto.indexOf, - nativeLastIndexOf = ArrayProto.lastIndexOf, - nativeIsArray = Array.isArray, - nativeKeys = Object.keys, - nativeBind = FuncProto.bind; - - // Create a safe reference to the Underscore object for use below. - var _ = function(obj) { return new wrapper(obj); }; - - // Export the Underscore object for **CommonJS**, with backwards-compatibility - // for the old `require()` API. If we're not in CommonJS, add `_` to the - // global object. - if (typeof module !== 'undefined' && module.exports) { - module.exports = _; - _._ = _; - } else { - // Exported as a string, for Closure Compiler "advanced" mode. - root['_'] = _; - } - - // Current version. - _.VERSION = '1.1.7'; - - // Collection Functions - // -------------------- - - // The cornerstone, an `each` implementation, aka `forEach`. - // Handles objects with the built-in `forEach`, arrays, and raw objects. - // Delegates to **ECMAScript 5**'s native `forEach` if available. - var each = _.each = _.forEach = function(obj, iterator, context) { - if (obj == null) return; - if (nativeForEach && obj.forEach === nativeForEach) { - obj.forEach(iterator, context); - } else if (obj.length === +obj.length) { - for (var i = 0, l = obj.length; i < l; i++) { - if (i in obj && iterator.call(context, obj[i], i, obj) === breaker) return; - } - } else { - for (var key in obj) { - if (hasOwnProperty.call(obj, key)) { - if (iterator.call(context, obj[key], key, obj) === breaker) return; - } - } - } - }; - - // Return the results of applying the iterator to each element. - // Delegates to **ECMAScript 5**'s native `map` if available. - _.map = function(obj, iterator, context) { - var results = []; - if (obj == null) return results; - if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context); - each(obj, function(value, index, list) { - results[results.length] = iterator.call(context, value, index, list); - }); - return results; - }; - - // **Reduce** builds up a single result from a list of values, aka `inject`, - // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available. - _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) { - var initial = memo !== void 0; - if (obj == null) obj = []; - if (nativeReduce && obj.reduce === nativeReduce) { - if (context) iterator = _.bind(iterator, context); - return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator); - } - each(obj, function(value, index, list) { - if (!initial) { - memo = value; - initial = true; - } else { - memo = iterator.call(context, memo, value, index, list); - } - }); - if (!initial) throw new TypeError("Reduce of empty array with no initial value"); - return memo; - }; - - // The right-associative version of reduce, also known as `foldr`. - // Delegates to **ECMAScript 5**'s native `reduceRight` if available. - _.reduceRight = _.foldr = function(obj, iterator, memo, context) { - if (obj == null) obj = []; - if (nativeReduceRight && obj.reduceRight === nativeReduceRight) { - if (context) iterator = _.bind(iterator, context); - return memo !== void 0 ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator); - } - var reversed = (_.isArray(obj) ? obj.slice() : _.toArray(obj)).reverse(); - return _.reduce(reversed, iterator, memo, context); - }; - - // Return the first value which passes a truth test. Aliased as `detect`. - _.find = _.detect = function(obj, iterator, context) { - var result; - any(obj, function(value, index, list) { - if (iterator.call(context, value, index, list)) { - result = value; - return true; - } - }); - return result; - }; - - // Return all the elements that pass a truth test. - // Delegates to **ECMAScript 5**'s native `filter` if available. - // Aliased as `select`. - _.filter = _.select = function(obj, iterator, context) { - var results = []; - if (obj == null) return results; - if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context); - each(obj, function(value, index, list) { - if (iterator.call(context, value, index, list)) results[results.length] = value; - }); - return results; - }; - - // Return all the elements for which a truth test fails. - _.reject = function(obj, iterator, context) { - var results = []; - if (obj == null) return results; - each(obj, function(value, index, list) { - if (!iterator.call(context, value, index, list)) results[results.length] = value; - }); - return results; - }; - - // Determine whether all of the elements match a truth test. - // Delegates to **ECMAScript 5**'s native `every` if available. - // Aliased as `all`. - _.every = _.all = function(obj, iterator, context) { - var result = true; - if (obj == null) return result; - if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context); - each(obj, function(value, index, list) { - if (!(result = result && iterator.call(context, value, index, list))) return breaker; - }); - return result; - }; - - // Determine if at least one element in the object matches a truth test. - // Delegates to **ECMAScript 5**'s native `some` if available. - // Aliased as `any`. - var any = _.some = _.any = function(obj, iterator, context) { - iterator = iterator || _.identity; - var result = false; - if (obj == null) return result; - if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context); - each(obj, function(value, index, list) { - if (result |= iterator.call(context, value, index, list)) return breaker; - }); - return !!result; - }; - - // Determine if a given value is included in the array or object using `===`. - // Aliased as `contains`. - _.include = _.contains = function(obj, target) { - var found = false; - if (obj == null) return found; - if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1; - any(obj, function(value) { - if (found = value === target) return true; - }); - return found; - }; - - // Invoke a method (with arguments) on every item in a collection. - _.invoke = function(obj, method) { - var args = slice.call(arguments, 2); - return _.map(obj, function(value) { - return (method.call ? method || value : value[method]).apply(value, args); - }); - }; - - // Convenience version of a common use case of `map`: fetching a property. - _.pluck = function(obj, key) { - return _.map(obj, function(value){ return value[key]; }); - }; - - // Return the maximum element or (element-based computation). - _.max = function(obj, iterator, context) { - if (!iterator && _.isArray(obj)) return Math.max.apply(Math, obj); - var result = {computed : -Infinity}; - each(obj, function(value, index, list) { - var computed = iterator ? iterator.call(context, value, index, list) : value; - computed >= result.computed && (result = {value : value, computed : computed}); - }); - return result.value; - }; - - // Return the minimum element (or element-based computation). - _.min = function(obj, iterator, context) { - if (!iterator && _.isArray(obj)) return Math.min.apply(Math, obj); - var result = {computed : Infinity}; - each(obj, function(value, index, list) { - var computed = iterator ? iterator.call(context, value, index, list) : value; - computed < result.computed && (result = {value : value, computed : computed}); - }); - return result.value; - }; - - // Sort the object's values by a criterion produced by an iterator. - _.sortBy = function(obj, iterator, context) { - return _.pluck(_.map(obj, function(value, index, list) { - return { - value : value, - criteria : iterator.call(context, value, index, list) - }; - }).sort(function(left, right) { - var a = left.criteria, b = right.criteria; - return a < b ? -1 : a > b ? 1 : 0; - }), 'value'); - }; - - // Groups the object's values by a criterion produced by an iterator - _.groupBy = function(obj, iterator) { - var result = {}; - each(obj, function(value, index) { - var key = iterator(value, index); - (result[key] || (result[key] = [])).push(value); - }); - return result; - }; - - // Use a comparator function to figure out at what index an object should - // be inserted so as to maintain order. Uses binary search. - _.sortedIndex = function(array, obj, iterator) { - iterator || (iterator = _.identity); - var low = 0, high = array.length; - while (low < high) { - var mid = (low + high) >> 1; - iterator(array[mid]) < iterator(obj) ? low = mid + 1 : high = mid; - } - return low; - }; - - // Safely convert anything iterable into a real, live array. - _.toArray = function(iterable) { - if (!iterable) return []; - if (iterable.toArray) return iterable.toArray(); - if (_.isArray(iterable)) return slice.call(iterable); - if (_.isArguments(iterable)) return slice.call(iterable); - return _.values(iterable); - }; - - // Return the number of elements in an object. - _.size = function(obj) { - return _.toArray(obj).length; - }; - - // Array Functions - // --------------- - - // Get the first element of an array. Passing **n** will return the first N - // values in the array. Aliased as `head`. The **guard** check allows it to work - // with `_.map`. - _.first = _.head = function(array, n, guard) { - return (n != null) && !guard ? slice.call(array, 0, n) : array[0]; - }; - - // Returns everything but the first entry of the array. Aliased as `tail`. - // Especially useful on the arguments object. Passing an **index** will return - // the rest of the values in the array from that index onward. The **guard** - // check allows it to work with `_.map`. - _.rest = _.tail = function(array, index, guard) { - return slice.call(array, (index == null) || guard ? 1 : index); - }; - - // Get the last element of an array. - _.last = function(array) { - return array[array.length - 1]; - }; - - // Trim out all falsy values from an array. - _.compact = function(array) { - return _.filter(array, function(value){ return !!value; }); - }; - - // Return a completely flattened version of an array. - _.flatten = function(array) { - return _.reduce(array, function(memo, value) { - if (_.isArray(value)) return memo.concat(_.flatten(value)); - memo[memo.length] = value; - return memo; - }, []); - }; - - // Return a version of the array that does not contain the specified value(s). - _.without = function(array) { - return _.difference(array, slice.call(arguments, 1)); - }; - - // Produce a duplicate-free version of the array. If the array has already - // been sorted, you have the option of using a faster algorithm. - // Aliased as `unique`. - _.uniq = _.unique = function(array, isSorted) { - return _.reduce(array, function(memo, el, i) { - if (0 == i || (isSorted === true ? _.last(memo) != el : !_.include(memo, el))) memo[memo.length] = el; - return memo; - }, []); - }; - - // Produce an array that contains the union: each distinct element from all of - // the passed-in arrays. - _.union = function() { - return _.uniq(_.flatten(arguments)); - }; - - // Produce an array that contains every item shared between all the - // passed-in arrays. (Aliased as "intersect" for back-compat.) - _.intersection = _.intersect = function(array) { - var rest = slice.call(arguments, 1); - return _.filter(_.uniq(array), function(item) { - return _.every(rest, function(other) { - return _.indexOf(other, item) >= 0; - }); - }); - }; - - // Take the difference between one array and another. - // Only the elements present in just the first array will remain. - _.difference = function(array, other) { - return _.filter(array, function(value){ return !_.include(other, value); }); - }; - - // Zip together multiple lists into a single array -- elements that share - // an index go together. - _.zip = function() { - var args = slice.call(arguments); - var length = _.max(_.pluck(args, 'length')); - var results = new Array(length); - for (var i = 0; i < length; i++) results[i] = _.pluck(args, "" + i); - return results; - }; - - // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**), - // we need this function. Return the position of the first occurrence of an - // item in an array, or -1 if the item is not included in the array. - // Delegates to **ECMAScript 5**'s native `indexOf` if available. - // If the array is large and already in sort order, pass `true` - // for **isSorted** to use binary search. - _.indexOf = function(array, item, isSorted) { - if (array == null) return -1; - var i, l; - if (isSorted) { - i = _.sortedIndex(array, item); - return array[i] === item ? i : -1; - } - if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item); - for (i = 0, l = array.length; i < l; i++) if (array[i] === item) return i; - return -1; - }; - - - // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available. - _.lastIndexOf = function(array, item) { - if (array == null) return -1; - if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) return array.lastIndexOf(item); - var i = array.length; - while (i--) if (array[i] === item) return i; - return -1; - }; - - // Generate an integer Array containing an arithmetic progression. A port of - // the native Python `range()` function. See - // [the Python documentation](http://docs.python.org/library/functions.html#range). - _.range = function(start, stop, step) { - if (arguments.length <= 1) { - stop = start || 0; - start = 0; - } - step = arguments[2] || 1; - - var len = Math.max(Math.ceil((stop - start) / step), 0); - var idx = 0; - var range = new Array(len); - - while(idx < len) { - range[idx++] = start; - start += step; - } - - return range; - }; - - // Function (ahem) Functions - // ------------------ - - // Create a function bound to a given object (assigning `this`, and arguments, - // optionally). Binding with arguments is also known as `curry`. - // Delegates to **ECMAScript 5**'s native `Function.bind` if available. - // We check for `func.bind` first, to fail fast when `func` is undefined. - _.bind = function(func, obj) { - if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1)); - var args = slice.call(arguments, 2); - return function() { - return func.apply(obj, args.concat(slice.call(arguments))); - }; - }; - - // Bind all of an object's methods to that object. Useful for ensuring that - // all callbacks defined on an object belong to it. - _.bindAll = function(obj) { - var funcs = slice.call(arguments, 1); - if (funcs.length == 0) funcs = _.functions(obj); - each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); }); - return obj; - }; - - // Memoize an expensive function by storing its results. - _.memoize = function(func, hasher) { - var memo = {}; - hasher || (hasher = _.identity); - return function() { - var key = hasher.apply(this, arguments); - return hasOwnProperty.call(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments)); - }; - }; - - // Delays a function for the given number of milliseconds, and then calls - // it with the arguments supplied. - _.delay = function(func, wait) { - var args = slice.call(arguments, 2); - return setTimeout(function(){ return func.apply(func, args); }, wait); - }; - - // Defers a function, scheduling it to run after the current call stack has - // cleared. - _.defer = function(func) { - return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1))); - }; - - // Internal function used to implement `_.throttle` and `_.debounce`. - var limit = function(func, wait, debounce) { - var timeout; - return function() { - var context = this, args = arguments; - var throttler = function() { - timeout = null; - func.apply(context, args); - }; - if (debounce) clearTimeout(timeout); - if (debounce || !timeout) timeout = setTimeout(throttler, wait); - }; - }; - - // Returns a function, that, when invoked, will only be triggered at most once - // during a given window of time. - _.throttle = function(func, wait) { - return limit(func, wait, false); - }; - - // Returns a function, that, as long as it continues to be invoked, will not - // be triggered. The function will be called after it stops being called for - // N milliseconds. - _.debounce = function(func, wait) { - return limit(func, wait, true); - }; - - // Returns a function that will be executed at most one time, no matter how - // often you call it. Useful for lazy initialization. - _.once = function(func) { - var ran = false, memo; - return function() { - if (ran) return memo; - ran = true; - return memo = func.apply(this, arguments); - }; - }; - - // Returns the first function passed as an argument to the second, - // allowing you to adjust arguments, run code before and after, and - // conditionally execute the original function. - _.wrap = function(func, wrapper) { - return function() { - var args = [func].concat(slice.call(arguments)); - return wrapper.apply(this, args); - }; - }; - - // Returns a function that is the composition of a list of functions, each - // consuming the return value of the function that follows. - _.compose = function() { - var funcs = slice.call(arguments); - return function() { - var args = slice.call(arguments); - for (var i = funcs.length - 1; i >= 0; i--) { - args = [funcs[i].apply(this, args)]; - } - return args[0]; - }; - }; - - // Returns a function that will only be executed after being called N times. - _.after = function(times, func) { - return function() { - if (--times < 1) { return func.apply(this, arguments); } - }; - }; - - - // Object Functions - // ---------------- - - // Retrieve the names of an object's properties. - // Delegates to **ECMAScript 5**'s native `Object.keys` - _.keys = nativeKeys || function(obj) { - if (obj !== Object(obj)) throw new TypeError('Invalid object'); - var keys = []; - for (var key in obj) if (hasOwnProperty.call(obj, key)) keys[keys.length] = key; - return keys; - }; - - // Retrieve the values of an object's properties. - _.values = function(obj) { - return _.map(obj, _.identity); - }; - - // Return a sorted list of the function names available on the object. - // Aliased as `methods` - _.functions = _.methods = function(obj) { - var names = []; - for (var key in obj) { - if (_.isFunction(obj[key])) names.push(key); - } - return names.sort(); - }; - - // Extend a given object with all the properties in passed-in object(s). - _.extend = function(obj) { - each(slice.call(arguments, 1), function(source) { - for (var prop in source) { - if (source[prop] !== void 0) obj[prop] = source[prop]; - } - }); - return obj; - }; - - // Fill in a given object with default properties. - _.defaults = function(obj) { - each(slice.call(arguments, 1), function(source) { - for (var prop in source) { - if (obj[prop] == null) obj[prop] = source[prop]; - } - }); - return obj; - }; - - // Create a (shallow-cloned) duplicate of an object. - _.clone = function(obj) { - return _.isArray(obj) ? obj.slice() : _.extend({}, obj); - }; - - // Invokes interceptor with the obj, and then returns obj. - // The primary purpose of this method is to "tap into" a method chain, in - // order to perform operations on intermediate results within the chain. - _.tap = function(obj, interceptor) { - interceptor(obj); - return obj; - }; - - // Perform a deep comparison to check if two objects are equal. - _.isEqual = function(a, b) { - // Check object identity. - if (a === b) return true; - // Different types? - var atype = typeof(a), btype = typeof(b); - if (atype != btype) return false; - // Basic equality test (watch out for coercions). - if (a == b) return true; - // One is falsy and the other truthy. - if ((!a && b) || (a && !b)) return false; - // Unwrap any wrapped objects. - if (a._chain) a = a._wrapped; - if (b._chain) b = b._wrapped; - // One of them implements an isEqual()? - if (a.isEqual) return a.isEqual(b); - if (b.isEqual) return b.isEqual(a); - // Check dates' integer values. - if (_.isDate(a) && _.isDate(b)) return a.getTime() === b.getTime(); - // Both are NaN? - if (_.isNaN(a) && _.isNaN(b)) return false; - // Compare regular expressions. - if (_.isRegExp(a) && _.isRegExp(b)) - return a.source === b.source && - a.global === b.global && - a.ignoreCase === b.ignoreCase && - a.multiline === b.multiline; - // If a is not an object by this point, we can't handle it. - if (atype !== 'object') return false; - // Check for different array lengths before comparing contents. - if (a.length && (a.length !== b.length)) return false; - // Nothing else worked, deep compare the contents. - var aKeys = _.keys(a), bKeys = _.keys(b); - // Different object sizes? - if (aKeys.length != bKeys.length) return false; - // Recursive comparison of contents. - for (var key in a) if (!(key in b) || !_.isEqual(a[key], b[key])) return false; - return true; - }; - - // Is a given array or object empty? - _.isEmpty = function(obj) { - if (_.isArray(obj) || _.isString(obj)) return obj.length === 0; - for (var key in obj) if (hasOwnProperty.call(obj, key)) return false; - return true; - }; - - // Is a given value a DOM element? - _.isElement = function(obj) { - return !!(obj && obj.nodeType == 1); - }; - - // Is a given value an array? - // Delegates to ECMA5's native Array.isArray - _.isArray = nativeIsArray || function(obj) { - return toString.call(obj) === '[object Array]'; - }; - - // Is a given variable an object? - _.isObject = function(obj) { - return obj === Object(obj); - }; - - // Is a given variable an arguments object? - _.isArguments = function(obj) { - return !!(obj && hasOwnProperty.call(obj, 'callee')); - }; - - // Is a given value a function? - _.isFunction = function(obj) { - return !!(obj && obj.constructor && obj.call && obj.apply); - }; - - // Is a given value a string? - _.isString = function(obj) { - return !!(obj === '' || (obj && obj.charCodeAt && obj.substr)); - }; - - // Is a given value a number? - _.isNumber = function(obj) { - return !!(obj === 0 || (obj && obj.toExponential && obj.toFixed)); - }; - - // Is the given value `NaN`? `NaN` happens to be the only value in JavaScript - // that does not equal itself. - _.isNaN = function(obj) { - return obj !== obj; - }; - - // Is a given value a boolean? - _.isBoolean = function(obj) { - return obj === true || obj === false; - }; - - // Is a given value a date? - _.isDate = function(obj) { - return !!(obj && obj.getTimezoneOffset && obj.setUTCFullYear); - }; - - // Is the given value a regular expression? - _.isRegExp = function(obj) { - return !!(obj && obj.test && obj.exec && (obj.ignoreCase || obj.ignoreCase === false)); - }; - - // Is a given value equal to null? - _.isNull = function(obj) { - return obj === null; - }; - - // Is a given variable undefined? - _.isUndefined = function(obj) { - return obj === void 0; - }; - - // Utility Functions - // ----------------- - - // Run Underscore.js in *noConflict* mode, returning the `_` variable to its - // previous owner. Returns a reference to the Underscore object. - _.noConflict = function() { - root._ = previousUnderscore; - return this; - }; - - // Keep the identity function around for default iterators. - _.identity = function(value) { - return value; - }; - - // Run a function **n** times. - _.times = function (n, iterator, context) { - for (var i = 0; i < n; i++) iterator.call(context, i); - }; - - // Add your own custom functions to the Underscore object, ensuring that - // they're correctly added to the OOP wrapper as well. - _.mixin = function(obj) { - each(_.functions(obj), function(name){ - addToWrapper(name, _[name] = obj[name]); - }); - }; - - // Generate a unique integer id (unique within the entire client session). - // Useful for temporary DOM ids. - var idCounter = 0; - _.uniqueId = function(prefix) { - var id = idCounter++; - return prefix ? prefix + id : id; - }; - - // By default, Underscore uses ERB-style template delimiters, change the - // following template settings to use alternative delimiters. - _.templateSettings = { - evaluate : /<%([\s\S]+?)%>/g, - interpolate : /<%=([\s\S]+?)%>/g - }; - - // JavaScript micro-templating, similar to John Resig's implementation. - // Underscore templating handles arbitrary delimiters, preserves whitespace, - // and correctly escapes quotes within interpolated code. - _.template = function(str, data) { - var c = _.templateSettings; - var tmpl = 'var __p=[],print=function(){__p.push.apply(__p,arguments);};' + - 'with(obj||{}){__p.push(\'' + - str.replace(/\\/g, '\\\\') - .replace(/'/g, "\\'") - .replace(c.interpolate, function(match, code) { - return "'," + code.replace(/\\'/g, "'") + ",'"; - }) - .replace(c.evaluate || null, function(match, code) { - return "');" + code.replace(/\\'/g, "'") - .replace(/[\r\n\t]/g, ' ') + "__p.push('"; - }) - .replace(/\r/g, '\\r') - .replace(/\n/g, '\\n') - .replace(/\t/g, '\\t') - + "');}return __p.join('');"; - var func = new Function('obj', tmpl); - return data ? func(data) : func; - }; - - // The OOP Wrapper - // --------------- - - // If Underscore is called as a function, it returns a wrapped object that - // can be used OO-style. This wrapper holds altered versions of all the - // underscore functions. Wrapped objects may be chained. - var wrapper = function(obj) { this._wrapped = obj; }; - - // Expose `wrapper.prototype` as `_.prototype` - _.prototype = wrapper.prototype; - - // Helper function to continue chaining intermediate results. - var result = function(obj, chain) { - return chain ? _(obj).chain() : obj; - }; - - // A method to easily add functions to the OOP wrapper. - var addToWrapper = function(name, func) { - wrapper.prototype[name] = function() { - var args = slice.call(arguments); - unshift.call(args, this._wrapped); - return result(func.apply(_, args), this._chain); - }; - }; - - // Add all of the Underscore functions to the wrapper object. - _.mixin(_); - - // Add all mutator Array functions to the wrapper. - each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { - var method = ArrayProto[name]; - wrapper.prototype[name] = function() { - method.apply(this._wrapped, arguments); - return result(this._wrapped, this._chain); - }; - }); - - // Add all accessor Array functions to the wrapper. - each(['concat', 'join', 'slice'], function(name) { - var method = ArrayProto[name]; - wrapper.prototype[name] = function() { - return result(method.apply(this._wrapped, arguments), this._chain); - }; - }); - - // Start chaining a wrapped Underscore object. - wrapper.prototype.chain = function() { - this._chain = true; - return this; - }; - - // Extracts the result from a wrapped and chained object. - wrapper.prototype.value = function() { - return this._wrapped; - }; - -})(); -/** - * Flotr2 (c) 2012 Carl Sutherland - * MIT License - * Special thanks to: - * Flotr: http://code.google.com/p/flotr/ (fork) - * Flot: https://github.com/flot/flot (original fork) - */ -(function () { - -var - global = this, - previousFlotr = this.Flotr, - Flotr; - -Flotr = { - _: _, - bean: bean, - isIphone: /iphone/i.test(navigator.userAgent), - isIE: (navigator.appVersion.indexOf("MSIE") != -1 ? parseFloat(navigator.appVersion.split("MSIE")[1]) : false), - - /** - * An object of the registered graph types. Use Flotr.addType(type, object) - * to add your own type. - */ - graphTypes: {}, - - /** - * The list of the registered plugins - */ - plugins: {}, - - /** - * Can be used to add your own chart type. - * @param {String} name - Type of chart, like 'pies', 'bars' etc. - * @param {String} graphType - The object containing the basic drawing functions (draw, etc) - */ - addType: function(name, graphType){ - Flotr.graphTypes[name] = graphType; - Flotr.defaultOptions[name] = graphType.options || {}; - Flotr.defaultOptions.defaultType = Flotr.defaultOptions.defaultType || name; - }, - - /** - * Can be used to add a plugin - * @param {String} name - The name of the plugin - * @param {String} plugin - The object containing the plugin's data (callbacks, options, function1, function2, ...) - */ - addPlugin: function(name, plugin){ - Flotr.plugins[name] = plugin; - Flotr.defaultOptions[name] = plugin.options || {}; - }, - - /** - * Draws the graph. This function is here for backwards compatibility with Flotr version 0.1.0alpha. - * You could also draw graphs by directly calling Flotr.Graph(element, data, options). - * @param {Element} el - element to insert the graph into - * @param {Object} data - an array or object of dataseries - * @param {Object} options - an object containing options - * @param {Class} _GraphKlass_ - (optional) Class to pass the arguments to, defaults to Flotr.Graph - * @return {Object} returns a new graph object and of course draws the graph. - */ - draw: function(el, data, options, GraphKlass){ - GraphKlass = GraphKlass || Flotr.Graph; - return new GraphKlass(el, data, options); - }, - - /** - * Recursively merges two objects. - * @param {Object} src - source object (likely the object with the least properties) - * @param {Object} dest - destination object (optional, object with the most properties) - * @return {Object} recursively merged Object - * @TODO See if we can't remove this. - */ - merge: function(src, dest){ - var i, v, result = dest || {}; - - for (i in src) { - v = src[i]; - if (v && typeof(v) === 'object') { - if (v.constructor === Array) { - result[i] = this._.clone(v); - } else if (v.constructor !== RegExp && !this._.isElement(v)) { - result[i] = Flotr.merge(v, (dest ? dest[i] : undefined)); - } else { - result[i] = v; - } - } else { - result[i] = v; - } - } - - return result; - }, - - /** - * Recursively clones an object. - * @param {Object} object - The object to clone - * @return {Object} the clone - * @TODO See if we can't remove this. - */ - clone: function(object){ - return Flotr.merge(object, {}); - }, - - /** - * Function calculates the ticksize and returns it. - * @param {Integer} noTicks - number of ticks - * @param {Integer} min - lower bound integer value for the current axis - * @param {Integer} max - upper bound integer value for the current axis - * @param {Integer} decimals - number of decimals for the ticks - * @return {Integer} returns the ticksize in pixels - */ - getTickSize: function(noTicks, min, max, decimals){ - var delta = (max - min) / noTicks, - magn = Flotr.getMagnitude(delta), - tickSize = 10, - norm = delta / magn; // Norm is between 1.0 and 10.0. - - if(norm < 1.5) tickSize = 1; - else if(norm < 2.25) tickSize = 2; - else if(norm < 3) tickSize = ((decimals === 0) ? 2 : 2.5); - else if(norm < 7.5) tickSize = 5; - - return tickSize * magn; - }, - - /** - * Default tick formatter. - * @param {String, Integer} val - tick value integer - * @param {Object} axisOpts - the axis' options - * @return {String} formatted tick string - */ - defaultTickFormatter: function(val, axisOpts){ - return val+''; - }, - - /** - * Formats the mouse tracker values. - * @param {Object} obj - Track value Object {x:..,y:..} - * @return {String} Formatted track string - */ - defaultTrackFormatter: function(obj){ - return '('+obj.x+', '+obj.y+')'; - }, - - /** - * Utility function to convert file size values in bytes to kB, MB, ... - * @param value {Number} - The value to convert - * @param precision {Number} - The number of digits after the comma (default: 2) - * @param base {Number} - The base (default: 1000) - */ - engineeringNotation: function(value, precision, base){ - var sizes = ['Y','Z','E','P','T','G','M','k',''], - fractionSizes = ['y','z','a','f','p','n','µ','m',''], - total = sizes.length; - - base = base || 1000; - precision = Math.pow(10, precision || 2); - - if (value === 0) return 0; - - if (value > 1) { - while (total-- && (value >= base)) value /= base; - } - else { - sizes = fractionSizes; - total = sizes.length; - while (total-- && (value < 1)) value *= base; - } - - return (Math.round(value * precision) / precision) + sizes[total]; - }, - - /** - * Returns the magnitude of the input value. - * @param {Integer, Float} x - integer or float value - * @return {Integer, Float} returns the magnitude of the input value - */ - getMagnitude: function(x){ - return Math.pow(10, Math.floor(Math.log(x) / Math.LN10)); - }, - toPixel: function(val){ - return Math.floor(val)+0.5;//((val-Math.round(val) < 0.4) ? (Math.floor(val)-0.5) : val); - }, - toRad: function(angle){ - return -angle * (Math.PI/180); - }, - floorInBase: function(n, base) { - return base * Math.floor(n / base); - }, - drawText: function(ctx, text, x, y, style) { - if (!ctx.fillText) { - ctx.drawText(text, x, y, style); - return; - } - - style = this._.extend({ - size: Flotr.defaultOptions.fontSize, - color: '#000000', - textAlign: 'left', - textBaseline: 'bottom', - weight: 1, - angle: 0 - }, style); - - ctx.save(); - ctx.translate(x, y); - ctx.rotate(style.angle); - ctx.fillStyle = style.color; - ctx.font = (style.weight > 1 ? "bold " : "") + (style.size*1.3) + "px sans-serif"; - ctx.textAlign = style.textAlign; - ctx.textBaseline = style.textBaseline; - ctx.fillText(text, 0, 0); - ctx.restore(); - }, - getBestTextAlign: function(angle, style) { - style = style || {textAlign: 'center', textBaseline: 'middle'}; - angle += Flotr.getTextAngleFromAlign(style); - - if (Math.abs(Math.cos(angle)) > 10e-3) - style.textAlign = (Math.cos(angle) > 0 ? 'right' : 'left'); - - if (Math.abs(Math.sin(angle)) > 10e-3) - style.textBaseline = (Math.sin(angle) > 0 ? 'top' : 'bottom'); - - return style; - }, - alignTable: { - 'right middle' : 0, - 'right top' : Math.PI/4, - 'center top' : Math.PI/2, - 'left top' : 3*(Math.PI/4), - 'left middle' : Math.PI, - 'left bottom' : -3*(Math.PI/4), - 'center bottom': -Math.PI/2, - 'right bottom' : -Math.PI/4, - 'center middle': 0 - }, - getTextAngleFromAlign: function(style) { - return Flotr.alignTable[style.textAlign+' '+style.textBaseline] || 0; - }, - noConflict : function () { - global.Flotr = previousFlotr; - return this; - } -}; - -global.Flotr = Flotr; - -})(); - -/** - * Flotr Defaults - */ -Flotr.defaultOptions = { - colors: ['#00A8F0', '#C0D800', '#CB4B4B', '#4DA74D', '#9440ED'], //=> The default colorscheme. When there are > 5 series, additional colors are generated. - ieBackgroundColor: '#FFFFFF', // Background color for excanvas clipping - title: null, // => The graph's title - subtitle: null, // => The graph's subtitle - shadowSize: 4, // => size of the 'fake' shadow - defaultType: null, // => default series type - HtmlText: true, // => wether to draw the text using HTML or on the canvas - fontColor: '#545454', // => default font color - fontSize: 7.5, // => canvas' text font size - resolution: 1, // => resolution of the graph, to have printer-friendly graphs ! - parseFloat: true, // => whether to preprocess data for floats (ie. if input is string) - xaxis: { - ticks: null, // => format: either [1, 3] or [[1, 'a'], 3] - minorTicks: null, // => format: either [1, 3] or [[1, 'a'], 3] - showLabels: true, // => setting to true will show the axis ticks labels, hide otherwise - showMinorLabels: false,// => true to show the axis minor ticks labels, false to hide - labelsAngle: 0, // => labels' angle, in degrees - title: null, // => axis title - titleAngle: 0, // => axis title's angle, in degrees - noTicks: 5, // => number of ticks for automagically generated ticks - minorTickFreq: null, // => number of minor ticks between major ticks for autogenerated ticks - tickFormatter: Flotr.defaultTickFormatter, // => fn: number, Object -> string - tickDecimals: null, // => no. of decimals, null means auto - min: null, // => min. value to show, null means set automatically - max: null, // => max. value to show, null means set automatically - autoscale: false, // => Turns autoscaling on with true - autoscaleMargin: 0, // => margin in % to add if auto-setting min/max - color: null, // => color of the ticks - mode: 'normal', // => can be 'time' or 'normal' - timeFormat: null, - timeMode:'UTC', // => For UTC time ('local' for local time). - timeUnit:'millisecond',// => Unit for time (millisecond, second, minute, hour, day, month, year) - scaling: 'linear', // => Scaling, can be 'linear' or 'logarithmic' - base: Math.E, - titleAlign: 'center', - margin: true // => Turn off margins with false - }, - x2axis: {}, - yaxis: { - ticks: null, // => format: either [1, 3] or [[1, 'a'], 3] - minorTicks: null, // => format: either [1, 3] or [[1, 'a'], 3] - showLabels: true, // => setting to true will show the axis ticks labels, hide otherwise - showMinorLabels: false,// => true to show the axis minor ticks labels, false to hide - labelsAngle: 0, // => labels' angle, in degrees - title: null, // => axis title - titleAngle: 90, // => axis title's angle, in degrees - noTicks: 5, // => number of ticks for automagically generated ticks - minorTickFreq: null, // => number of minor ticks between major ticks for autogenerated ticks - tickFormatter: Flotr.defaultTickFormatter, // => fn: number, Object -> string - tickDecimals: null, // => no. of decimals, null means auto - min: null, // => min. value to show, null means set automatically - max: null, // => max. value to show, null means set automatically - autoscale: false, // => Turns autoscaling on with true - autoscaleMargin: 0, // => margin in % to add if auto-setting min/max - color: null, // => The color of the ticks - scaling: 'linear', // => Scaling, can be 'linear' or 'logarithmic' - base: Math.E, - titleAlign: 'center', - margin: true // => Turn off margins with false - }, - y2axis: { - titleAngle: 270 - }, - grid: { - color: '#545454', // => primary color used for outline and labels - backgroundColor: null, // => null for transparent, else color - backgroundImage: null, // => background image. String or object with src, left and top - watermarkAlpha: 0.4, // => - tickColor: '#DDDDDD', // => color used for the ticks - labelMargin: 3, // => margin in pixels - verticalLines: true, // => whether to show gridlines in vertical direction - minorVerticalLines: null, // => whether to show gridlines for minor ticks in vertical dir. - horizontalLines: true, // => whether to show gridlines in horizontal direction - minorHorizontalLines: null, // => whether to show gridlines for minor ticks in horizontal dir. - outlineWidth: 1, // => width of the grid outline/border in pixels - outline : 'nsew', // => walls of the outline to display - circular: false // => if set to true, the grid will be circular, must be used when radars are drawn - }, - mouse: { - track: false, // => true to track the mouse, no tracking otherwise - trackAll: false, - position: 'se', // => position of the value box (default south-east) - relative: false, // => next to the mouse cursor - trackFormatter: Flotr.defaultTrackFormatter, // => formats the values in the value box - margin: 5, // => margin in pixels of the valuebox - lineColor: '#FF3F19', // => line color of points that are drawn when mouse comes near a value of a series - trackDecimals: 1, // => decimals for the track values - sensibility: 2, // => the lower this number, the more precise you have to aim to show a value - trackY: true, // => whether or not to track the mouse in the y axis - radius: 3, // => radius of the track point - fillColor: null, // => color to fill our select bar with only applies to bar and similar graphs (only bars for now) - fillOpacity: 0.4 // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill - } -}; - -/** - * Flotr Color - */ - -(function () { - -var - _ = Flotr._; - -// Constructor -function Color (r, g, b, a) { - this.rgba = ['r','g','b','a']; - var x = 4; - while(-1<--x){ - this[this.rgba[x]] = arguments[x] || ((x==3) ? 1.0 : 0); - } - this.normalize(); -} - -// Constants -var COLOR_NAMES = { - aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255], - brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169], - darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47], - darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122], - darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130], - khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144], - lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255], - maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128], - violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0] -}; - -Color.prototype = { - scale: function(rf, gf, bf, af){ - var x = 4; - while (-1 < --x) { - if (!_.isUndefined(arguments[x])) this[this.rgba[x]] *= arguments[x]; - } - return this.normalize(); - }, - alpha: function(alpha) { - if (!_.isUndefined(alpha) && !_.isNull(alpha)) { - this.a = alpha; - } - return this.normalize(); - }, - clone: function(){ - return new Color(this.r, this.b, this.g, this.a); - }, - limit: function(val,minVal,maxVal){ - return Math.max(Math.min(val, maxVal), minVal); - }, - normalize: function(){ - var limit = this.limit; - this.r = limit(parseInt(this.r, 10), 0, 255); - this.g = limit(parseInt(this.g, 10), 0, 255); - this.b = limit(parseInt(this.b, 10), 0, 255); - this.a = limit(this.a, 0, 1); - return this; - }, - distance: function(color){ - if (!color) return; - color = new Color.parse(color); - var dist = 0, x = 3; - while(-1<--x){ - dist += Math.abs(this[this.rgba[x]] - color[this.rgba[x]]); - } - return dist; - }, - toString: function(){ - return (this.a >= 1.0) ? 'rgb('+[this.r,this.g,this.b].join(',')+')' : 'rgba('+[this.r,this.g,this.b,this.a].join(',')+')'; - }, - contrast: function () { - var - test = 1 - ( 0.299 * this.r + 0.587 * this.g + 0.114 * this.b) / 255; - return (test < 0.5 ? '#000000' : '#ffffff'); - } -}; - -_.extend(Color, { - /** - * Parses a color string and returns a corresponding Color. - * The different tests are in order of probability to improve speed. - * @param {String, Color} str - string thats representing a color - * @return {Color} returns a Color object or false - */ - parse: function(color){ - if (color instanceof Color) return color; - - var result; - - // #a0b1c2 - if((result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(color))) - return new Color(parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16)); - - // rgb(num,num,num) - if((result = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(color))) - return new Color(parseInt(result[1], 10), parseInt(result[2], 10), parseInt(result[3], 10)); - - // #fff - if((result = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(color))) - return new Color(parseInt(result[1]+result[1],16), parseInt(result[2]+result[2],16), parseInt(result[3]+result[3],16)); - - // rgba(num,num,num,num) - if((result = /rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(color))) - return new Color(parseInt(result[1], 10), parseInt(result[2], 10), parseInt(result[3], 10), parseFloat(result[4])); - - // rgb(num%,num%,num%) - if((result = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(color))) - return new Color(parseFloat(result[1])*2.55, parseFloat(result[2])*2.55, parseFloat(result[3])*2.55); - - // rgba(num%,num%,num%,num) - if((result = /rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(color))) - return new Color(parseFloat(result[1])*2.55, parseFloat(result[2])*2.55, parseFloat(result[3])*2.55, parseFloat(result[4])); - - // Otherwise, we're most likely dealing with a named color. - var name = (color+'').replace(/^\s*([\S\s]*?)\s*$/, '$1').toLowerCase(); - if(name == 'transparent'){ - return new Color(255, 255, 255, 0); - } - return (result = COLOR_NAMES[name]) ? new Color(result[0], result[1], result[2]) : new Color(0, 0, 0, 0); - }, - - /** - * Process color and options into color style. - */ - processColor: function(color, options) { - - var opacity = options.opacity; - if (!color) return 'rgba(0, 0, 0, 0)'; - if (color instanceof Color) return color.alpha(opacity).toString(); - if (_.isString(color)) return Color.parse(color).alpha(opacity).toString(); - - var grad = color.colors ? color : {colors: color}; - - if (!options.ctx) { - if (!_.isArray(grad.colors)) return 'rgba(0, 0, 0, 0)'; - return Color.parse(_.isArray(grad.colors[0]) ? grad.colors[0][1] : grad.colors[0]).alpha(opacity).toString(); - } - grad = _.extend({start: 'top', end: 'bottom'}, grad); - - if (/top/i.test(grad.start)) options.x1 = 0; - if (/left/i.test(grad.start)) options.y1 = 0; - if (/bottom/i.test(grad.end)) options.x2 = 0; - if (/right/i.test(grad.end)) options.y2 = 0; - - var i, c, stop, gradient = options.ctx.createLinearGradient(options.x1, options.y1, options.x2, options.y2); - for (i = 0; i < grad.colors.length; i++) { - c = grad.colors[i]; - if (_.isArray(c)) { - stop = c[0]; - c = c[1]; - } - else stop = i / (grad.colors.length-1); - gradient.addColorStop(stop, Color.parse(c).alpha(opacity)); - } - return gradient; - } -}); - -Flotr.Color = Color; - -})(); - -/** - * Flotr Date - */ -Flotr.Date = { - - set : function (date, name, mode, value) { - mode = mode || 'UTC'; - name = 'set' + (mode === 'UTC' ? 'UTC' : '') + name; - date[name](value); - }, - - get : function (date, name, mode) { - mode = mode || 'UTC'; - name = 'get' + (mode === 'UTC' ? 'UTC' : '') + name; - return date[name](); - }, - - format: function(d, format, mode) { - if (!d) return; - - // We should maybe use an "official" date format spec, like PHP date() or ColdFusion - // http://fr.php.net/manual/en/function.date.php - // http://livedocs.adobe.com/coldfusion/8/htmldocs/help.html?content=functions_c-d_29.html - var - get = this.get, - tokens = { - h: get(d, 'Hours', mode).toString(), - H: leftPad(get(d, 'Hours', mode)), - M: leftPad(get(d, 'Minutes', mode)), - S: leftPad(get(d, 'Seconds', mode)), - s: get(d, 'Milliseconds', mode), - d: get(d, 'Date', mode).toString(), - m: (get(d, 'Month') + 1).toString(), - y: get(d, 'FullYear').toString(), - b: Flotr.Date.monthNames[get(d, 'Month', mode)] - }; - - function leftPad(n){ - n += ''; - return n.length == 1 ? "0" + n : n; - } - - var r = [], c, - escape = false; - - for (var i = 0; i < format.length; ++i) { - c = format.charAt(i); - - if (escape) { - r.push(tokens[c] || c); - escape = false; - } - else if (c == "%") - escape = true; - else - r.push(c); - } - return r.join(''); - }, - getFormat: function(time, span) { - var tu = Flotr.Date.timeUnits; - if (time < tu.second) return "%h:%M:%S.%s"; - else if (time < tu.minute) return "%h:%M:%S"; - else if (time < tu.day) return (span < 2 * tu.day) ? "%h:%M" : "%b %d %h:%M"; - else if (time < tu.month) return "%b %d"; - else if (time < tu.year) return (span < tu.year) ? "%b" : "%b %y"; - else return "%y"; - }, - formatter: function (v, axis) { - var - options = axis.options, - scale = Flotr.Date.timeUnits[options.timeUnit], - d = new Date(v * scale); - - // first check global format - if (axis.options.timeFormat) - return Flotr.Date.format(d, options.timeFormat, options.timeMode); - - var span = (axis.max - axis.min) * scale, - t = axis.tickSize * Flotr.Date.timeUnits[axis.tickUnit]; - - return Flotr.Date.format(d, Flotr.Date.getFormat(t, span), options.timeMode); - }, - generator: function(axis) { - - var - set = this.set, - get = this.get, - timeUnits = this.timeUnits, - spec = this.spec, - options = axis.options, - mode = options.timeMode, - scale = timeUnits[options.timeUnit], - min = axis.min * scale, - max = axis.max * scale, - delta = (max - min) / options.noTicks, - ticks = [], - tickSize = axis.tickSize, - tickUnit, - formatter, i; - - // Use custom formatter or time tick formatter - formatter = (options.tickFormatter === Flotr.defaultTickFormatter ? - this.formatter : options.tickFormatter - ); - - for (i = 0; i < spec.length - 1; ++i) { - var d = spec[i][0] * timeUnits[spec[i][1]]; - if (delta < (d + spec[i+1][0] * timeUnits[spec[i+1][1]]) / 2 && d >= tickSize) - break; - } - tickSize = spec[i][0]; - tickUnit = spec[i][1]; - - // special-case the possibility of several years - if (tickUnit == "year") { - tickSize = Flotr.getTickSize(options.noTicks*timeUnits.year, min, max, 0); - - // Fix for 0.5 year case - if (tickSize == 0.5) { - tickUnit = "month"; - tickSize = 6; - } - } - - axis.tickUnit = tickUnit; - axis.tickSize = tickSize; - - var - d = new Date(min); - - var step = tickSize * timeUnits[tickUnit]; - - function setTick (name) { - set(d, name, mode, Flotr.floorInBase( - get(d, name, mode), tickSize - )); - } - - switch (tickUnit) { - case "millisecond": setTick('Milliseconds'); break; - case "second": setTick('Seconds'); break; - case "minute": setTick('Minutes'); break; - case "hour": setTick('Hours'); break; - case "month": setTick('Month'); break; - case "year": setTick('FullYear'); break; - } - - // reset smaller components - if (step >= timeUnits.second) set(d, 'Milliseconds', mode, 0); - if (step >= timeUnits.minute) set(d, 'Seconds', mode, 0); - if (step >= timeUnits.hour) set(d, 'Minutes', mode, 0); - if (step >= timeUnits.day) set(d, 'Hours', mode, 0); - if (step >= timeUnits.day * 4) set(d, 'Date', mode, 1); - if (step >= timeUnits.year) set(d, 'Month', mode, 0); - - var carry = 0, v = NaN, prev; - do { - prev = v; - v = d.getTime(); - ticks.push({ v: v / scale, label: formatter(v / scale, axis) }); - if (tickUnit == "month") { - if (tickSize < 1) { - /* a bit complicated - we'll divide the month up but we need to take care of fractions - so we don't end up in the middle of a day */ - set(d, 'Date', mode, 1); - var start = d.getTime(); - set(d, 'Month', mode, get(d, 'Month', mode) + 1) - var end = d.getTime(); - d.setTime(v + carry * timeUnits.hour + (end - start) * tickSize); - carry = get(d, 'Hours', mode) - set(d, 'Hours', mode, 0); - } - else - set(d, 'Month', mode, get(d, 'Month', mode) + tickSize); - } - else if (tickUnit == "year") { - set(d, 'FullYear', mode, get(d, 'FullYear', mode) + tickSize); - } - else - d.setTime(v + step); - - } while (v < max && v != prev); - - return ticks; - }, - timeUnits: { - millisecond: 1, - second: 1000, - minute: 1000 * 60, - hour: 1000 * 60 * 60, - day: 1000 * 60 * 60 * 24, - month: 1000 * 60 * 60 * 24 * 30, - year: 1000 * 60 * 60 * 24 * 365.2425 - }, - // the allowed tick sizes, after 1 year we use an integer algorithm - spec: [ - [1, "millisecond"], [20, "millisecond"], [50, "millisecond"], [100, "millisecond"], [200, "millisecond"], [500, "millisecond"], - [1, "second"], [2, "second"], [5, "second"], [10, "second"], [30, "second"], - [1, "minute"], [2, "minute"], [5, "minute"], [10, "minute"], [30, "minute"], - [1, "hour"], [2, "hour"], [4, "hour"], [8, "hour"], [12, "hour"], - [1, "day"], [2, "day"], [3, "day"], - [0.25, "month"], [0.5, "month"], [1, "month"], [2, "month"], [3, "month"], [6, "month"], - [1, "year"] - ], - monthNames: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] -}; - -(function () { - -var _ = Flotr._; - -Flotr.DOM = { - addClass: function(element, name){ - var classList = (element.className ? element.className : ''); - if (_.include(classList.split(/\s+/g), name)) return; - element.className = (classList ? classList + ' ' : '') + name; - }, - /** - * Create an element. - */ - create: function(tag){ - return document.createElement(tag); - }, - node: function(html) { - var div = Flotr.DOM.create('div'), n; - div.innerHTML = html; - n = div.children[0]; - div.innerHTML = ''; - return n; - }, - /** - * Remove all children. - */ - empty: function(element){ - element.innerHTML = ''; - /* - if (!element) return; - _.each(element.childNodes, function (e) { - Flotr.DOM.empty(e); - element.removeChild(e); - }); - */ - }, - hide: function(element){ - Flotr.DOM.setStyles(element, {display:'none'}); - }, - /** - * Insert a child. - * @param {Element} element - * @param {Element|String} Element or string to be appended. - */ - insert: function(element, child){ - if(_.isString(child)) - element.innerHTML += child; - else if (_.isElement(child)) - element.appendChild(child); - }, - // @TODO find xbrowser implementation - opacity: function(element, opacity) { - element.style.opacity = opacity; - }, - position: function(element, p){ - if (!element.offsetParent) - return {left: (element.offsetLeft || 0), top: (element.offsetTop || 0)}; - - p = this.position(element.offsetParent); - p.left += element.offsetLeft; - p.top += element.offsetTop; - return p; - }, - removeClass: function(element, name) { - var classList = (element.className ? element.className : ''); - element.className = _.filter(classList.split(/\s+/g), function (c) { - if (c != name) return true; } - ).join(' '); - }, - setStyles: function(element, o) { - _.each(o, function (value, key) { - element.style[key] = value; - }); - }, - show: function(element){ - Flotr.DOM.setStyles(element, {display:''}); - }, - /** - * Return element size. - */ - size: function(element){ - return { - height : element.offsetHeight, - width : element.offsetWidth }; - } -}; - -})(); - -/** - * Flotr Event Adapter - */ -(function () { -var - F = Flotr, - bean = F.bean; -F.EventAdapter = { - observe: function(object, name, callback) { - bean.add(object, name, callback); - return this; - }, - fire: function(object, name, args) { - bean.fire(object, name, args); - if (typeof(Prototype) != 'undefined') - Event.fire(object, name, args); - // @TODO Someone who uses mootools, add mootools adapter for existing applciations. - return this; - }, - stopObserving: function(object, name, callback) { - bean.remove(object, name, callback); - return this; - }, - eventPointer: function(e) { - if (!F._.isUndefined(e.touches) && e.touches.length > 0) { - return { - x : e.touches[0].pageX, - y : e.touches[0].pageY - }; - } else if (!F._.isUndefined(e.changedTouches) && e.changedTouches.length > 0) { - return { - x : e.changedTouches[0].pageX, - y : e.changedTouches[0].pageY - }; - } else if (e.pageX || e.pageY) { - return { - x : e.pageX, - y : e.pageY - }; - } else if (e.clientX || e.clientY) { - var - d = document, - b = d.body, - de = d.documentElement; - return { - x: e.clientX + b.scrollLeft + de.scrollLeft, - y: e.clientY + b.scrollTop + de.scrollTop - }; - } - } -}; -})(); - -/** - * Text Utilities - */ -(function () { - -var - F = Flotr, - D = F.DOM, - _ = F._, - -Text = function (o) { - this.o = o; -}; - -Text.prototype = { - - dimensions : function (text, canvasStyle, htmlStyle, className) { - - if (!text) return { width : 0, height : 0 }; - - return (this.o.html) ? - this.html(text, this.o.element, htmlStyle, className) : - this.canvas(text, canvasStyle); - }, - - canvas : function (text, style) { - - if (!this.o.textEnabled) return; - style = style || {}; - - var - metrics = this.measureText(text, style), - width = metrics.width, - height = style.size || F.defaultOptions.fontSize, - angle = style.angle || 0, - cosAngle = Math.cos(angle), - sinAngle = Math.sin(angle), - widthPadding = 2, - heightPadding = 6, - bounds; - - bounds = { - width: Math.abs(cosAngle * width) + Math.abs(sinAngle * height) + widthPadding, - height: Math.abs(sinAngle * width) + Math.abs(cosAngle * height) + heightPadding - }; - - return bounds; - }, - - html : function (text, element, style, className) { - - var div = D.create('div'); - - D.setStyles(div, { 'position' : 'absolute', 'top' : '-10000px' }); - D.insert(div, '<div style="'+style+'" class="'+className+' flotr-dummy-div">' + text + '</div>'); - D.insert(this.o.element, div); - - return D.size(div); - }, - - measureText : function (text, style) { - - var - context = this.o.ctx, - metrics; - - if (!context.fillText || (F.isIphone && context.measure)) { - return { width : context.measure(text, style)}; - } - - style = _.extend({ - size: F.defaultOptions.fontSize, - weight: 1, - angle: 0 - }, style); - - context.save(); - context.font = (style.weight > 1 ? "bold " : "") + (style.size*1.3) + "px sans-serif"; - metrics = context.measureText(text); - context.restore(); - - return metrics; - } -}; - -Flotr.Text = Text; - -})(); - -/** - * Flotr Graph class that plots a graph on creation. - */ -(function () { - -var - D = Flotr.DOM, - E = Flotr.EventAdapter, - _ = Flotr._, - flotr = Flotr; -/** - * Flotr Graph constructor. - * @param {Element} el - element to insert the graph into - * @param {Object} data - an array or object of dataseries - * @param {Object} options - an object containing options - */ -Graph = function(el, data, options){ -// Let's see if we can get away with out this [JS] -// try { - this._setEl(el); - this._initMembers(); - this._initPlugins(); - - E.fire(this.el, 'flotr:beforeinit', [this]); - - this.data = data; - this.series = flotr.Series.getSeries(data); - this._initOptions(options); - this._initGraphTypes(); - this._initCanvas(); - this._text = new flotr.Text({ - element : this.el, - ctx : this.ctx, - html : this.options.HtmlText, - textEnabled : this.textEnabled - }); - E.fire(this.el, 'flotr:afterconstruct', [this]); - this._initEvents(); - - this.findDataRanges(); - this.calculateSpacing(); - - this.draw(_.bind(function() { - E.fire(this.el, 'flotr:afterinit', [this]); - }, this)); -/* - try { - } catch (e) { - try { - console.error(e); - } catch (e2) {} - }*/ -}; - -function observe (object, name, callback) { - E.observe.apply(this, arguments); - this._handles.push(arguments); - return this; -} - -Graph.prototype = { - - destroy: function () { - E.fire(this.el, 'flotr:destroy'); - _.each(this._handles, function (handle) { - E.stopObserving.apply(this, handle); - }); - this._handles = []; - this.el.graph = null; - }, - - observe : observe, - - /** - * @deprecated - */ - _observe : observe, - - processColor: function(color, options){ - var o = { x1: 0, y1: 0, x2: this.plotWidth, y2: this.plotHeight, opacity: 1, ctx: this.ctx }; - _.extend(o, options); - return flotr.Color.processColor(color, o); - }, - /** - * Function determines the min and max values for the xaxis and yaxis. - * - * TODO logarithmic range validation (consideration of 0) - */ - findDataRanges: function(){ - var a = this.axes, - xaxis, yaxis, range; - - _.each(this.series, function (series) { - range = series.getRange(); - if (range) { - xaxis = series.xaxis; - yaxis = series.yaxis; - xaxis.datamin = Math.min(range.xmin, xaxis.datamin); - xaxis.datamax = Math.max(range.xmax, xaxis.datamax); - yaxis.datamin = Math.min(range.ymin, yaxis.datamin); - yaxis.datamax = Math.max(range.ymax, yaxis.datamax); - xaxis.used = (xaxis.used || range.xused); - yaxis.used = (yaxis.used || range.yused); - } - }, this); - - // Check for empty data, no data case (none used) - if (!a.x.used && !a.x2.used) a.x.used = true; - if (!a.y.used && !a.y2.used) a.y.used = true; - - _.each(a, function (axis) { - axis.calculateRange(); - }); - - var - types = _.keys(flotr.graphTypes), - drawn = false; - - _.each(this.series, function (series) { - if (series.hide) return; - _.each(types, function (type) { - if (series[type] && series[type].show) { - this.extendRange(type, series); - drawn = true; - } - }, this); - if (!drawn) { - this.extendRange(this.options.defaultType, series); - } - }, this); - }, - - extendRange : function (type, series) { - if (this[type].extendRange) this[type].extendRange(series, series.data, series[type], this[type]); - if (this[type].extendYRange) this[type].extendYRange(series.yaxis, series.data, series[type], this[type]); - if (this[type].extendXRange) this[type].extendXRange(series.xaxis, series.data, series[type], this[type]); - }, - - /** - * Calculates axis label sizes. - */ - calculateSpacing: function(){ - - var a = this.axes, - options = this.options, - series = this.series, - margin = options.grid.labelMargin, - T = this._text, - x = a.x, - x2 = a.x2, - y = a.y, - y2 = a.y2, - maxOutset = options.grid.outlineWidth, - i, j, l, dim; - - // TODO post refactor, fix this - _.each(a, function (axis) { - axis.calculateTicks(); - axis.calculateTextDimensions(T, options); - }); - - // Title height - dim = T.dimensions( - options.title, - {size: options.fontSize*1.5}, - 'font-size:1em;font-weight:bold;', - 'flotr-title' - ); - this.titleHeight = dim.height; - - // Subtitle height - dim = T.dimensions( - options.subtitle, - {size: options.fontSize}, - 'font-size:smaller;', - 'flotr-subtitle' - ); - this.subtitleHeight = dim.height; - - for(j = 0; j < options.length; ++j){ - if (series[j].points.show){ - maxOutset = Math.max(maxOutset, series[j].points.radius + series[j].points.lineWidth/2); - } - } - - var p = this.plotOffset; - if (x.options.margin === false) { - p.bottom = 0; - p.top = 0; - } else { - p.bottom += (options.grid.circular ? 0 : (x.used && x.options.showLabels ? (x.maxLabel.height + margin) : 0)) + - (x.used && x.options.title ? (x.titleSize.height + margin) : 0) + maxOutset; - - p.top += (options.grid.circular ? 0 : (x2.used && x2.options.showLabels ? (x2.maxLabel.height + margin) : 0)) + - (x2.used && x2.options.title ? (x2.titleSize.height + margin) : 0) + this.subtitleHeight + this.titleHeight + maxOutset; - } - if (y.options.margin === false) { - p.left = 0; - p.right = 0; - } else { - p.left += (options.grid.circular ? 0 : (y.used && y.options.showLabels ? (y.maxLabel.width + margin) : 0)) + - (y.used && y.options.title ? (y.titleSize.width + margin) : 0) + maxOutset; - - p.right += (options.grid.circular ? 0 : (y2.used && y2.options.showLabels ? (y2.maxLabel.width + margin) : 0)) + - (y2.used && y2.options.title ? (y2.titleSize.width + margin) : 0) + maxOutset; - } - - p.top = Math.floor(p.top); // In order the outline not to be blured - - this.plotWidth = this.canvasWidth - p.left - p.right; - this.plotHeight = this.canvasHeight - p.bottom - p.top; - - // TODO post refactor, fix this - x.length = x2.length = this.plotWidth; - y.length = y2.length = this.plotHeight; - y.offset = y2.offset = this.plotHeight; - x.setScale(); - x2.setScale(); - y.setScale(); - y2.setScale(); - }, - /** - * Draws grid, labels, series and outline. - */ - draw: function(after) { - - var - context = this.ctx, - i; - - E.fire(this.el, 'flotr:beforedraw', [this.series, this]); - - if (this.series.length) { - - context.save(); - context.translate(this.plotOffset.left, this.plotOffset.top); - - for (i = 0; i < this.series.length; i++) { - if (!this.series[i].hide) this.drawSeries(this.series[i]); - } - - context.restore(); - this.clip(); - } - - E.fire(this.el, 'flotr:afterdraw', [this.series, this]); - if (after) after(); - }, - /** - * Actually draws the graph. - * @param {Object} series - series to draw - */ - drawSeries: function(series){ - - function drawChart (series, typeKey) { - var options = this.getOptions(series, typeKey); - this[typeKey].draw(options); - } - - var drawn = false; - series = series || this.series; - - _.each(flotr.graphTypes, function (type, typeKey) { - if (series[typeKey] && series[typeKey].show && this[typeKey]) { - drawn = true; - drawChart.call(this, series, typeKey); - } - }, this); - - if (!drawn) drawChart.call(this, series, this.options.defaultType); - }, - - getOptions : function (series, typeKey) { - var - type = series[typeKey], - graphType = this[typeKey], - options = { - context : this.ctx, - width : this.plotWidth, - height : this.plotHeight, - fontSize : this.options.fontSize, - fontColor : this.options.fontColor, - textEnabled : this.textEnabled, - htmlText : this.options.HtmlText, - text : this._text, // TODO Is this necessary? - element : this.el, - data : series.data, - color : series.color, - shadowSize : series.shadowSize, - xScale : _.bind(series.xaxis.d2p, series.xaxis), - yScale : _.bind(series.yaxis.d2p, series.yaxis) - }; - - options = flotr.merge(type, options); - - // Fill - options.fillStyle = this.processColor( - type.fillColor || series.color, - {opacity: type.fillOpacity} - ); - - return options; - }, - /** - * Calculates the coordinates from a mouse event object. - * @param {Event} event - Mouse Event object. - * @return {Object} Object with coordinates of the mouse. - */ - getEventPosition: function (e){ - - var - d = document, - b = d.body, - de = d.documentElement, - axes = this.axes, - plotOffset = this.plotOffset, - lastMousePos = this.lastMousePos, - pointer = E.eventPointer(e), - dx = pointer.x - lastMousePos.pageX, - dy = pointer.y - lastMousePos.pageY, - r, rx, ry; - - if ('ontouchstart' in this.el) { - r = D.position(this.overlay); - rx = pointer.x - r.left - plotOffset.left; - ry = pointer.y - r.top - plotOffset.top; - } else { - r = this.overlay.getBoundingClientRect(); - rx = e.clientX - r.left - plotOffset.left - b.scrollLeft - de.scrollLeft; - ry = e.clientY - r.top - plotOffset.top - b.scrollTop - de.scrollTop; - } - - return { - x: axes.x.p2d(rx), - x2: axes.x2.p2d(rx), - y: axes.y.p2d(ry), - y2: axes.y2.p2d(ry), - relX: rx, - relY: ry, - dX: dx, - dY: dy, - absX: pointer.x, - absY: pointer.y, - pageX: pointer.x, - pageY: pointer.y - }; - }, - /** - * Observes the 'click' event and fires the 'flotr:click' event. - * @param {Event} event - 'click' Event object. - */ - clickHandler: function(event){ - if(this.ignoreClick){ - this.ignoreClick = false; - return this.ignoreClick; - } - E.fire(this.el, 'flotr:click', [this.getEventPosition(event), this]); - }, - /** - * Observes mouse movement over the graph area. Fires the 'flotr:mousemove' event. - * @param {Event} event - 'mousemove' Event object. - */ - mouseMoveHandler: function(event){ - if (this.mouseDownMoveHandler) return; - var pos = this.getEventPosition(event); - E.fire(this.el, 'flotr:mousemove', [event, pos, this]); - this.lastMousePos = pos; - }, - /** - * Observes the 'mousedown' event. - * @param {Event} event - 'mousedown' Event object. - */ - mouseDownHandler: function (event){ - - /* - // @TODO Context menu? - if(event.isRightClick()) { - event.stop(); - - var overlay = this.overlay; - overlay.hide(); - - function cancelContextMenu () { - overlay.show(); - E.stopObserving(document, 'mousemove', cancelContextMenu); - } - E.observe(document, 'mousemove', cancelContextMenu); - return; - } - */ - - if (this.mouseUpHandler) return; - this.mouseUpHandler = _.bind(function (e) { - E.stopObserving(document, 'mouseup', this.mouseUpHandler); - E.stopObserving(document, 'mousemove', this.mouseDownMoveHandler); - this.mouseDownMoveHandler = null; - this.mouseUpHandler = null; - // @TODO why? - //e.stop(); - E.fire(this.el, 'flotr:mouseup', [e, this]); - }, this); - this.mouseDownMoveHandler = _.bind(function (e) { - var pos = this.getEventPosition(e); - E.fire(this.el, 'flotr:mousemove', [event, pos, this]); - this.lastMousePos = pos; - }, this); - E.observe(document, 'mouseup', this.mouseUpHandler); - E.observe(document, 'mousemove', this.mouseDownMoveHandler); - E.fire(this.el, 'flotr:mousedown', [event, this]); - this.ignoreClick = false; - }, - drawTooltip: function(content, x, y, options) { - var mt = this.getMouseTrack(), - style = 'opacity:0.7;background-color:#000;color:#fff;display:none;position:absolute;padding:2px 8px;-moz-border-radius:4px;border-radius:4px;white-space:nowrap;', - p = options.position, - m = options.margin, - plotOffset = this.plotOffset; - - if(x !== null && y !== null){ - if (!options.relative) { // absolute to the canvas - if(p.charAt(0) == 'n') style += 'top:' + (m + plotOffset.top) + 'px;bottom:auto;'; - else if(p.charAt(0) == 's') style += 'bottom:' + (m + plotOffset.bottom) + 'px;top:auto;'; - if(p.charAt(1) == 'e') style += 'right:' + (m + plotOffset.right) + 'px;left:auto;'; - else if(p.charAt(1) == 'w') style += 'left:' + (m + plotOffset.left) + 'px;right:auto;'; - } - else { // relative to the mouse - if(p.charAt(0) == 'n') style += 'bottom:' + (m - plotOffset.top - y + this.canvasHeight) + 'px;top:auto;'; - else if(p.charAt(0) == 's') style += 'top:' + (m + plotOffset.top + y) + 'px;bottom:auto;'; - if(p.charAt(1) == 'e') style += 'left:' + (m + plotOffset.left + x) + 'px;right:auto;'; - else if(p.charAt(1) == 'w') style += 'right:' + (m - plotOffset.left - x + this.canvasWidth) + 'px;left:auto;'; - } - - mt.style.cssText = style; - D.empty(mt); - D.insert(mt, content); - D.show(mt); - } - else { - D.hide(mt); - } - }, - - clip: function () { - - var - ctx = this.ctx, - o = this.plotOffset, - w = this.canvasWidth, - h = this.canvasHeight; - - if (flotr.isIE && flotr.isIE < 9) { - // Clipping for excanvas :-( - ctx.save(); - ctx.fillStyle = this.processColor(this.options.ieBackgroundColor); - ctx.fillRect(0, 0, w, o.top); - ctx.fillRect(0, 0, o.left, h); - ctx.fillRect(0, h - o.bottom, w, o.bottom); - ctx.fillRect(w - o.right, 0, o.right,h); - ctx.restore(); - } else { - ctx.clearRect(0, 0, w, o.top); - ctx.clearRect(0, 0, o.left, h); - ctx.clearRect(0, h - o.bottom, w, o.bottom); - ctx.clearRect(w - o.right, 0, o.right,h); - } - }, - - _initMembers: function() { - this._handles = []; - this.lastMousePos = {pageX: null, pageY: null }; - this.plotOffset = {left: 0, right: 0, top: 0, bottom: 0}; - this.ignoreClick = true; - this.prevHit = null; - }, - - _initGraphTypes: function() { - _.each(flotr.graphTypes, function(handler, graphType){ - this[graphType] = flotr.clone(handler); - }, this); - }, - - _initEvents: function () { - - var - el = this.el, - touchendHandler, movement, touchend; - - if ('ontouchstart' in el) { - - touchendHandler = _.bind(function (e) { - touchend = true; - E.stopObserving(document, 'touchend', touchendHandler); - E.fire(el, 'flotr:mouseup', [event, this]); - this.multitouches = null; - - if (!movement) { - this.clickHandler(e); - } - }, this); - - this.observe(this.overlay, 'touchstart', _.bind(function (e) { - movement = false; - touchend = false; - this.ignoreClick = false; - - if (e.touches && e.touches.length > 1) { - this.multitouches = e.touches; - } - - E.fire(el, 'flotr:mousedown', [event, this]); - this.observe(document, 'touchend', touchendHandler); - }, this)); - - this.observe(this.overlay, 'touchmove', _.bind(function (e) { - - var pos = this.getEventPosition(e); - - e.preventDefault(); - - movement = true; - - if (this.multitouches || (e.touches && e.touches.length > 1)) { - this.multitouches = e.touches; - } else { - if (!touchend) { - E.fire(el, 'flotr:mousemove', [event, pos, this]); - } - } - this.lastMousePos = pos; - }, this)); - - } else { - this. - observe(this.overlay, 'mousedown', _.bind(this.mouseDownHandler, this)). - observe(el, 'mousemove', _.bind(this.mouseMoveHandler, this)). - observe(this.overlay, 'click', _.bind(this.clickHandler, this)). - observe(el, 'mouseout', function () { - E.fire(el, 'flotr:mouseout'); - }); - } - }, - - /** - * Initializes the canvas and it's overlay canvas element. When the browser is IE, this makes use - * of excanvas. The overlay canvas is inserted for displaying interactions. After the canvas elements - * are created, the elements are inserted into the container element. - */ - _initCanvas: function(){ - var el = this.el, - o = this.options, - children = el.children, - removedChildren = [], - child, i, - size, style; - - // Empty the el - for (i = children.length; i--;) { - child = children[i]; - if (!this.canvas && child.className === 'flotr-canvas') { - this.canvas = child; - } else if (!this.overlay && child.className === 'flotr-overlay') { - this.overlay = child; - } else { - removedChildren.push(child); - } - } - for (i = removedChildren.length; i--;) { - el.removeChild(removedChildren[i]); - } - - D.setStyles(el, {position: 'relative'}); // For positioning labels and overlay. - size = {}; - size.width = el.clientWidth; - size.height = el.clientHeight; - - if(size.width <= 0 || size.height <= 0 || o.resolution <= 0){ - throw 'Invalid dimensions for plot, width = ' + size.width + ', height = ' + size.height + ', resolution = ' + o.resolution; - } - - // Main canvas for drawing graph types - this.canvas = getCanvas(this.canvas, 'canvas'); - // Overlay canvas for interactive features - this.overlay = getCanvas(this.overlay, 'overlay'); - this.ctx = getContext(this.canvas); - this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); - this.octx = getContext(this.overlay); - this.octx.clearRect(0, 0, this.overlay.width, this.overlay.height); - this.canvasHeight = size.height; - this.canvasWidth = size.width; - this.textEnabled = !!this.ctx.drawText || !!this.ctx.fillText; // Enable text functions - - function getCanvas(canvas, name){ - if(!canvas){ - canvas = D.create('canvas'); - if (typeof FlashCanvas != "undefined" && typeof canvas.getContext === 'function') { - FlashCanvas.initElement(canvas); - } - canvas.className = 'flotr-'+name; - canvas.style.cssText = 'position:absolute;left:0px;top:0px;'; - D.insert(el, canvas); - } - _.each(size, function(size, attribute){ - D.show(canvas); - if (name == 'canvas' && canvas.getAttribute(attribute) === size) { - return; - } - canvas.setAttribute(attribute, size * o.resolution); - canvas.style[attribute] = size + 'px'; - }); - canvas.context_ = null; // Reset the ExCanvas context - return canvas; - } - - function getContext(canvas){ - if(window.G_vmlCanvasManager) window.G_vmlCanvasManager.initElement(canvas); // For ExCanvas - var context = canvas.getContext('2d'); - if(!window.G_vmlCanvasManager) context.scale(o.resolution, o.resolution); - return context; - } - }, - - _initPlugins: function(){ - // TODO Should be moved to flotr and mixed in. - _.each(flotr.plugins, function(plugin, name){ - _.each(plugin.callbacks, function(fn, c){ - this.observe(this.el, c, _.bind(fn, this)); - }, this); - this[name] = flotr.clone(plugin); - _.each(this[name], function(fn, p){ - if (_.isFunction(fn)) - this[name][p] = _.bind(fn, this); - }, this); - }, this); - }, - - /** - * Sets options and initializes some variables and color specific values, used by the constructor. - * @param {Object} opts - options object - */ - _initOptions: function(opts){ - var options = flotr.clone(flotr.defaultOptions); - options.x2axis = _.extend(_.clone(options.xaxis), options.x2axis); - options.y2axis = _.extend(_.clone(options.yaxis), options.y2axis); - this.options = flotr.merge(opts || {}, options); - - if (this.options.grid.minorVerticalLines === null && - this.options.xaxis.scaling === 'logarithmic') { - this.options.grid.minorVerticalLines = true; - } - if (this.options.grid.minorHorizontalLines === null && - this.options.yaxis.scaling === 'logarithmic') { - this.options.grid.minorHorizontalLines = true; - } - - E.fire(this.el, 'flotr:afterinitoptions', [this]); - - this.axes = flotr.Axis.getAxes(this.options); - - // Initialize some variables used throughout this function. - var assignedColors = [], - colors = [], - ln = this.series.length, - neededColors = this.series.length, - oc = this.options.colors, - usedColors = [], - variation = 0, - c, i, j, s; - - // Collect user-defined colors from series. - for(i = neededColors - 1; i > -1; --i){ - c = this.series[i].color; - if(c){ - --neededColors; - if(_.isNumber(c)) assignedColors.push(c); - else usedColors.push(flotr.Color.parse(c)); - } - } - - // Calculate the number of colors that need to be generated. - for(i = assignedColors.length - 1; i > -1; --i) - neededColors = Math.max(neededColors, assignedColors[i] + 1); - - // Generate needed number of colors. - for(i = 0; colors.length < neededColors;){ - c = (oc.length == i) ? new flotr.Color(100, 100, 100) : flotr.Color.parse(oc[i]); - - // Make sure each serie gets a different color. - var sign = variation % 2 == 1 ? -1 : 1, - factor = 1 + sign * Math.ceil(variation / 2) * 0.2; - c.scale(factor, factor, factor); - - /** - * @todo if we're getting too close to something else, we should probably skip this one - */ - colors.push(c); - - if(++i >= oc.length){ - i = 0; - ++variation; - } - } - - // Fill the options with the generated colors. - for(i = 0, j = 0; i < ln; ++i){ - s = this.series[i]; - - // Assign the color. - if (!s.color){ - s.color = colors[j++].toString(); - }else if(_.isNumber(s.color)){ - s.color = colors[s.color].toString(); - } - - // Every series needs an axis - if (!s.xaxis) s.xaxis = this.axes.x; - if (s.xaxis == 1) s.xaxis = this.axes.x; - else if (s.xaxis == 2) s.xaxis = this.axes.x2; - - if (!s.yaxis) s.yaxis = this.axes.y; - if (s.yaxis == 1) s.yaxis = this.axes.y; - else if (s.yaxis == 2) s.yaxis = this.axes.y2; - - // Apply missing options to the series. - for (var t in flotr.graphTypes){ - s[t] = _.extend(_.clone(this.options[t]), s[t]); - } - s.mouse = _.extend(_.clone(this.options.mouse), s.mouse); - - if (_.isUndefined(s.shadowSize)) s.shadowSize = this.options.shadowSize; - } - }, - - _setEl: function(el) { - if (!el) throw 'The target container doesn\'t exist'; - else if (el.graph instanceof Graph) el.graph.destroy(); - else if (!el.clientWidth) throw 'The target container must be visible'; - - el.graph = this; - this.el = el; - } -}; - -Flotr.Graph = Graph; - -})(); - -/** - * Flotr Axis Library - */ - -(function () { - -var - _ = Flotr._, - LOGARITHMIC = 'logarithmic'; - -function Axis (o) { - - this.orientation = 1; - this.offset = 0; - this.datamin = Number.MAX_VALUE; - this.datamax = -Number.MAX_VALUE; - - _.extend(this, o); - - this._setTranslations(); -} - - -// Prototype -Axis.prototype = { - - setScale : function () { - var length = this.length; - if (this.options.scaling == LOGARITHMIC) { - this.scale = length / (log(this.max, this.options.base) - log(this.min, this.options.base)); - } else { - this.scale = length / (this.max - this.min); - } - }, - - calculateTicks : function () { - var options = this.options; - - this.ticks = []; - this.minorTicks = []; - - // User Ticks - if(options.ticks){ - this._cleanUserTicks(options.ticks, this.ticks); - this._cleanUserTicks(options.minorTicks || [], this.minorTicks); - } - else { - if (options.mode == 'time') { - this._calculateTimeTicks(); - } else if (options.scaling === 'logarithmic') { - this._calculateLogTicks(); - } else { - this._calculateTicks(); - } - } - }, - - /** - * Calculates the range of an axis to apply autoscaling. - */ - calculateRange: function () { - - if (!this.used) return; - - var axis = this, - o = axis.options, - min = o.min !== null ? o.min : axis.datamin, - max = o.max !== null ? o.max : axis.datamax, - margin = o.autoscaleMargin; - - if (o.scaling == 'logarithmic') { - if (min <= 0) min = axis.datamin; - - // Let it widen later on - if (max <= 0) max = min; - } - - if (max == min) { - var widen = max ? 0.01 : 1.00; - if (o.min === null) min -= widen; - if (o.max === null) max += widen; - } - - if (o.scaling === 'logarithmic') { - if (min < 0) min = max / o.base; // Could be the result of widening - - var maxexp = Math.log(max); - if (o.base != Math.E) maxexp /= Math.log(o.base); - maxexp = Math.ceil(maxexp); - - var minexp = Math.log(min); - if (o.base != Math.E) minexp /= Math.log(o.base); - minexp = Math.ceil(minexp); - - axis.tickSize = Flotr.getTickSize(o.noTicks, minexp, maxexp, o.tickDecimals === null ? 0 : o.tickDecimals); - - // Try to determine a suitable amount of miniticks based on the length of a decade - if (o.minorTickFreq === null) { - if (maxexp - minexp > 10) - o.minorTickFreq = 0; - else if (maxexp - minexp > 5) - o.minorTickFreq = 2; - else - o.minorTickFreq = 5; - } - } else { - axis.tickSize = Flotr.getTickSize(o.noTicks, min, max, o.tickDecimals); - } - - axis.min = min; - axis.max = max; //extendRange may use axis.min or axis.max, so it should be set before it is caled - - // Autoscaling. @todo This probably fails with log scale. Find a testcase and fix it - if(o.min === null && o.autoscale){ - axis.min -= axis.tickSize * margin; - // Make sure we don't go below zero if all values are positive. - if(axis.min < 0 && axis.datamin >= 0) axis.min = 0; - axis.min = axis.tickSize * Math.floor(axis.min / axis.tickSize); - } - - if(o.max === null && o.autoscale){ - axis.max += axis.tickSize * margin; - if(axis.max > 0 && axis.datamax <= 0 && axis.datamax != axis.datamin) axis.max = 0; - axis.max = axis.tickSize * Math.ceil(axis.max / axis.tickSize); - } - - if (axis.min == axis.max) axis.max = axis.min + 1; - }, - - calculateTextDimensions : function (T, options) { - - var maxLabel = '', - length, - i; - - if (this.options.showLabels) { - for (i = 0; i < this.ticks.length; ++i) { - length = this.ticks[i].label.length; - if (length > maxLabel.length){ - maxLabel = this.ticks[i].label; - } - } - } - - this.maxLabel = T.dimensions( - maxLabel, - {size:options.fontSize, angle: Flotr.toRad(this.options.labelsAngle)}, - 'font-size:smaller;', - 'flotr-grid-label' - ); - - this.titleSize = T.dimensions( - this.options.title, - {size:options.fontSize*1.2, angle: Flotr.toRad(this.options.titleAngle)}, - 'font-weight:bold;', - 'flotr-axis-title' - ); - }, - - _cleanUserTicks : function (ticks, axisTicks) { - - var axis = this, options = this.options, - v, i, label, tick; - - if(_.isFunction(ticks)) ticks = ticks({min : axis.min, max : axis.max}); - - for(i = 0; i < ticks.length; ++i){ - tick = ticks[i]; - if(typeof(tick) === 'object'){ - v = tick[0]; - label = (tick.length > 1) ? tick[1] : options.tickFormatter(v, {min : axis.min, max : axis.max}); - } else { - v = tick; - label = options.tickFormatter(v, {min : this.min, max : this.max}); - } - axisTicks[i] = { v: v, label: label }; - } - }, - - _calculateTimeTicks : function () { - this.ticks = Flotr.Date.generator(this); - }, - - _calculateLogTicks : function () { - - var axis = this, - o = axis.options, - v, - decadeStart; - - var max = Math.log(axis.max); - if (o.base != Math.E) max /= Math.log(o.base); - max = Math.ceil(max); - - var min = Math.log(axis.min); - if (o.base != Math.E) min /= Math.log(o.base); - min = Math.ceil(min); - - for (i = min; i < max; i += axis.tickSize) { - decadeStart = (o.base == Math.E) ? Math.exp(i) : Math.pow(o.base, i); - // Next decade begins here: - var decadeEnd = decadeStart * ((o.base == Math.E) ? Math.exp(axis.tickSize) : Math.pow(o.base, axis.tickSize)); - var stepSize = (decadeEnd - decadeStart) / o.minorTickFreq; - - axis.ticks.push({v: decadeStart, label: o.tickFormatter(decadeStart, {min : axis.min, max : axis.max})}); - for (v = decadeStart + stepSize; v < decadeEnd; v += stepSize) - axis.minorTicks.push({v: v, label: o.tickFormatter(v, {min : axis.min, max : axis.max})}); - } - - // Always show the value at the would-be start of next decade (end of this decade) - decadeStart = (o.base == Math.E) ? Math.exp(i) : Math.pow(o.base, i); - axis.ticks.push({v: decadeStart, label: o.tickFormatter(decadeStart, {min : axis.min, max : axis.max})}); - }, - - _calculateTicks : function () { - - var axis = this, - o = axis.options, - tickSize = axis.tickSize, - min = axis.min, - max = axis.max, - start = tickSize * Math.ceil(min / tickSize), // Round to nearest multiple of tick size. - decimals, - minorTickSize, - v, v2, - i, j; - - if (o.minorTickFreq) - minorTickSize = tickSize / o.minorTickFreq; - - // Then store all possible ticks. - for (i = 0; (v = v2 = start + i * tickSize) <= max; ++i){ - - // Round (this is always needed to fix numerical instability). - decimals = o.tickDecimals; - if (decimals === null) decimals = 1 - Math.floor(Math.log(tickSize) / Math.LN10); - if (decimals < 0) decimals = 0; - - v = v.toFixed(decimals); - axis.ticks.push({ v: v, label: o.tickFormatter(v, {min : axis.min, max : axis.max}) }); - - if (o.minorTickFreq) { - for (j = 0; j < o.minorTickFreq && (i * tickSize + j * minorTickSize) < max; ++j) { - v = v2 + j * minorTickSize; - axis.minorTicks.push({ v: v, label: o.tickFormatter(v, {min : axis.min, max : axis.max}) }); - } - } - } - - }, - - _setTranslations : function (logarithmic) { - this.d2p = (logarithmic ? d2pLog : d2p); - this.p2d = (logarithmic ? p2dLog : p2d); - } -}; - - -// Static Methods -_.extend(Axis, { - getAxes : function (options) { - return { - x: new Axis({options: options.xaxis, n: 1, length: this.plotWidth}), - x2: new Axis({options: options.x2axis, n: 2, length: this.plotWidth}), - y: new Axis({options: options.yaxis, n: 1, length: this.plotHeight, offset: this.plotHeight, orientation: -1}), - y2: new Axis({options: options.y2axis, n: 2, length: this.plotHeight, offset: this.plotHeight, orientation: -1}) - }; - } -}); - - -// Helper Methods - -function d2p (dataValue) { - return this.offset + this.orientation * (dataValue - this.min) * this.scale; -} - -function p2d (pointValue) { - return (this.offset + this.orientation * pointValue) / this.scale + this.min; -} - -function d2pLog (dataValue) { - return this.offset + this.orientation * (log(dataValue, this.options.base) - log(this.min, this.options.base)) * this.scale; -} - -function p2dLog (pointValue) { - return exp((this.offset + this.orientation * pointValue) / this.scale + log(this.min, this.options.base), this.options.base); -} - -function log (value, base) { - value = Math.log(Math.max(value, Number.MIN_VALUE)); - if (base !== Math.E) - value /= Math.log(base); - return value; -} - -function exp (value, base) { - return (base === Math.E) ? Math.exp(value) : Math.pow(base, value); -} - -Flotr.Axis = Axis; - -})(); - -/** - * Flotr Series Library - */ - -(function () { - -var - _ = Flotr._; - -function Series (o) { - _.extend(this, o); -} - -Series.prototype = { - - getRange: function () { - - var - data = this.data, - length = data.length, - xmin = Number.MAX_VALUE, - ymin = Number.MAX_VALUE, - xmax = -Number.MAX_VALUE, - ymax = -Number.MAX_VALUE, - xused = false, - yused = false, - x, y, i; - - if (length < 0 || this.hide) return false; - - for (i = 0; i < length; i++) { - x = data[i][0]; - y = data[i][1]; - if (x < xmin) { xmin = x; xused = true; } - if (x > xmax) { xmax = x; xused = true; } - if (y < ymin) { ymin = y; yused = true; } - if (y > ymax) { ymax = y; yused = true; } - } - - return { - xmin : xmin, - xmax : xmax, - ymin : ymin, - ymax : ymax, - xused : xused, - yused : yused - }; - } -}; - -_.extend(Series, { - /** - * Collects dataseries from input and parses the series into the right format. It returns an Array - * of Objects each having at least the 'data' key set. - * @param {Array, Object} data - Object or array of dataseries - * @return {Array} Array of Objects parsed into the right format ({(...,) data: [[x1,y1], [x2,y2], ...] (, ...)}) - */ - getSeries: function(data){ - return _.map(data, function(s){ - var series; - if (s.data) { - series = new Series(); - _.extend(series, s); - } else { - series = new Series({data:s}); - } - return series; - }); - } -}); - -Flotr.Series = Series; - -})(); - -/** Lines **/ -Flotr.addType('lines', { - options: { - show: false, // => setting to true will show lines, false will hide - lineWidth: 2, // => line width in pixels - fill: false, // => true to fill the area from the line to the x axis, false for (transparent) no fill - fillBorder: false, // => draw a border around the fill - fillColor: null, // => fill color - fillOpacity: 0.4, // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill - steps: false, // => draw steps - stacked: false // => setting to true will show stacked lines, false will show normal lines - }, - - stack : { - values : [] - }, - - /** - * Draws lines series in the canvas element. - * @param {Object} options - */ - draw : function (options) { - - var - context = options.context, - lineWidth = options.lineWidth, - shadowSize = options.shadowSize, - offset; - - context.save(); - context.lineJoin = 'round'; - - if (shadowSize) { - - context.lineWidth = shadowSize / 2; - offset = lineWidth / 2 + context.lineWidth / 2; - - // @TODO do this instead with a linear gradient - context.strokeStyle = "rgba(0,0,0,0.1)"; - this.plot(options, offset + shadowSize / 2, false); - - context.strokeStyle = "rgba(0,0,0,0.2)"; - this.plot(options, offset, false); - } - - context.lineWidth = lineWidth; - context.strokeStyle = options.color; - - this.plot(options, 0, true); - - context.restore(); - }, - - plot : function (options, shadowOffset, incStack) { - - var - context = options.context, - width = options.width, - height = options.height, - xScale = options.xScale, - yScale = options.yScale, - data = options.data, - stack = options.stacked ? this.stack : false, - length = data.length - 1, - prevx = null, - prevy = null, - zero = yScale(0), - x1, x2, y1, y2, stack1, stack2, i; - - if (length < 1) return; - - context.beginPath(); - - for (i = 0; i < length; ++i) { - - // To allow empty values - if (data[i][1] === null || data[i+1][1] === null) continue; - - // Zero is infinity for log scales - // TODO handle zero for logarithmic - // if (xa.options.scaling === 'logarithmic' && (data[i][0] <= 0 || data[i+1][0] <= 0)) continue; - // if (ya.options.scaling === 'logarithmic' && (data[i][1] <= 0 || data[i+1][1] <= 0)) continue; - - x1 = xScale(data[i][0]); - x2 = xScale(data[i+1][0]); - - if (stack) { - - stack1 = stack.values[data[i][0]] || 0; - stack2 = stack.values[data[i+1][0]] || stack.values[data[i][0]] || 0; - - y1 = yScale(data[i][1] + stack1); - y2 = yScale(data[i+1][1] + stack2); - - if(incStack){ - stack.values[data[i][0]] = data[i][1]+stack1; - - if(i == length-1) - stack.values[data[i+1][0]] = data[i+1][1]+stack2; - } - } - else{ - y1 = yScale(data[i][1]); - y2 = yScale(data[i+1][1]); - } - - if ( - (y1 > height && y2 > height) || - (y1 < 0 && y2 < 0) || - (x1 < 0 && x2 < 0) || - (x1 > width && x2 > width) - ) continue; - - if((prevx != x1) || (prevy != y1 + shadowOffset)) - context.moveTo(x1, y1 + shadowOffset); - - prevx = x2; - prevy = y2 + shadowOffset; - if (options.steps) { - context.lineTo(prevx + shadowOffset / 2, y1 + shadowOffset); - context.lineTo(prevx + shadowOffset / 2, prevy); - } else { - context.lineTo(prevx, prevy); - } - } - - if (!options.fill || options.fill && !options.fillBorder) context.stroke(); - - // TODO stacked lines - if(!shadowOffset && options.fill){ - x1 = xScale(data[0][0]); - context.fillStyle = options.fillStyle; - context.lineTo(x2, zero); - context.lineTo(x1, zero); - context.lineTo(x1, yScale(data[0][1])); - context.fill(); - if (options.fillBorder) { - context.stroke(); - } - } - - context.closePath(); - }, - - // Perform any pre-render precalculations (this should be run on data first) - // - Pie chart total for calculating measures - // - Stacks for lines and bars - // precalculate : function () { - // } - // - // - // Get any bounds after pre calculation (axis can fetch this if does not have explicit min/max) - // getBounds : function () { - // } - // getMin : function () { - // } - // getMax : function () { - // } - // - // - // Padding around rendered elements - // getPadding : function () { - // } - - extendYRange : function (axis, data, options, lines) { - - var o = axis.options; - - // If stacked and auto-min - if (options.stacked && ((!o.max && o.max !== 0) || (!o.min && o.min !== 0))) { - - var - newmax = axis.max, - newmin = axis.min, - positiveSums = lines.positiveSums || {}, - negativeSums = lines.negativeSums || {}, - x, j; - - for (j = 0; j < data.length; j++) { - - x = data[j][0] + ''; - - // Positive - if (data[j][1] > 0) { - positiveSums[x] = (positiveSums[x] || 0) + data[j][1]; - newmax = Math.max(newmax, positiveSums[x]); - } - - // Negative - else { - negativeSums[x] = (negativeSums[x] || 0) + data[j][1]; - newmin = Math.min(newmin, negativeSums[x]); - } - } - - lines.negativeSums = negativeSums; - lines.positiveSums = positiveSums; - - axis.max = newmax; - axis.min = newmin; - } - - if (options.steps) { - - this.hit = function (options) { - var - data = options.data, - args = options.args, - yScale = options.yScale, - mouse = args[0], - length = data.length, - n = args[1], - x = mouse.x, - relY = mouse.relY, - i; - - for (i = 0; i < length - 1; i++) { - if (x >= data[i][0] && x <= data[i+1][0]) { - if (Math.abs(yScale(data[i][1]) - relY) < 8) { - n.x = data[i][0]; - n.y = data[i][1]; - n.index = i; - n.seriesIndex = options.index; - } - break; - } - } - }; - - this.drawHit = function (options) { - var - context = options.context, - args = options.args, - data = options.data, - xScale = options.xScale, - index = args.index, - x = xScale(args.x), - y = options.yScale(args.y), - x2; - - if (data.length - 1 > index) { - x2 = options.xScale(data[index + 1][0]); - context.save(); - context.strokeStyle = options.color; - context.lineWidth = options.lineWidth; - context.beginPath(); - context.moveTo(x, y); - context.lineTo(x2, y); - context.stroke(); - context.closePath(); - context.restore(); - } - }; - - this.clearHit = function (options) { - var - context = options.context, - args = options.args, - data = options.data, - xScale = options.xScale, - width = options.lineWidth, - index = args.index, - x = xScale(args.x), - y = options.yScale(args.y), - x2; - - if (data.length - 1 > index) { - x2 = options.xScale(data[index + 1][0]); - context.clearRect(x - width, y - width, x2 - x + 2 * width, 2 * width); - } - }; - } - } - -}); - -/** Bars **/ -Flotr.addType('bars', { - - options: { - show: false, // => setting to true will show bars, false will hide - lineWidth: 2, // => in pixels - barWidth: 1, // => in units of the x axis - fill: true, // => true to fill the area from the line to the x axis, false for (transparent) no fill - fillColor: null, // => fill color - fillOpacity: 0.4, // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill - horizontal: false, // => horizontal bars (x and y inverted) - stacked: false, // => stacked bar charts - centered: true, // => center the bars to their x axis value - topPadding: 0.1 // => top padding in percent - }, - - stack : { - positive : [], - negative : [], - _positive : [], // Shadow - _negative : [] // Shadow - }, - - draw : function (options) { - var - context = options.context; - - context.save(); - context.lineJoin = 'miter'; - // @TODO linewidth not interpreted the right way. - context.lineWidth = options.lineWidth; - context.strokeStyle = options.color; - if (options.fill) context.fillStyle = options.fillStyle; - - this.plot(options); - - context.restore(); - }, - - plot : function (options) { - - var - data = options.data, - context = options.context, - shadowSize = options.shadowSize, - i, geometry, left, top, width, height; - - if (data.length < 1) return; - - this.translate(context, options.horizontal); - - for (i = 0; i < data.length; i++) { - - geometry = this.getBarGeometry(data[i][0], data[i][1], options); - if (geometry === null) continue; - - left = geometry.left; - top = geometry.top; - width = geometry.width; - height = geometry.height; - - if (options.fill) context.fillRect(left, top, width, height); - if (shadowSize) { - context.save(); - context.fillStyle = 'rgba(0,0,0,0.05)'; - context.fillRect(left + shadowSize, top + shadowSize, width, height); - context.restore(); - } - if (options.lineWidth) { - context.strokeRect(left, top, width, height); - } - } - }, - - translate : function (context, horizontal) { - if (horizontal) { - context.rotate(-Math.PI / 2); - context.scale(-1, 1); - } - }, - - getBarGeometry : function (x, y, options) { - - var - horizontal = options.horizontal, - barWidth = options.barWidth, - centered = options.centered, - stack = options.stacked ? this.stack : false, - lineWidth = options.lineWidth, - bisection = centered ? barWidth / 2 : 0, - xScale = horizontal ? options.yScale : options.xScale, - yScale = horizontal ? options.xScale : options.yScale, - xValue = horizontal ? y : x, - yValue = horizontal ? x : y, - stackOffset = 0, - stackValue, left, right, top, bottom; - - // Stacked bars - if (stack) { - stackValue = yValue > 0 ? stack.positive : stack.negative; - stackOffset = stackValue[xValue] || stackOffset; - stackValue[xValue] = stackOffset + yValue; - } - - left = xScale(xValue - bisection); - right = xScale(xValue + barWidth - bisection); - top = yScale(yValue + stackOffset); - bottom = yScale(stackOffset); - - // TODO for test passing... probably looks better without this - if (bottom < 0) bottom = 0; - - // TODO Skipping... - // if (right < xa.min || left > xa.max || top < ya.min || bottom > ya.max) continue; - - return (x === null || y === null) ? null : { - x : xValue, - y : yValue, - xScale : xScale, - yScale : yScale, - top : top, - left : Math.min(left, right) - lineWidth / 2, - width : Math.abs(right - left) - lineWidth, - height : bottom - top - }; - }, - - hit : function (options) { - var - data = options.data, - args = options.args, - mouse = args[0], - n = args[1], - x = mouse.x, - y = mouse.y, - hitGeometry = this.getBarGeometry(x, y, options), - width = hitGeometry.width / 2, - left = hitGeometry.left, - geometry, i; - - for (i = data.length; i--;) { - geometry = this.getBarGeometry(data[i][0], data[i][1], options); - if (geometry.y > hitGeometry.y && Math.abs(left - geometry.left) < width) { - n.x = data[i][0]; - n.y = data[i][1]; - n.index = i; - n.seriesIndex = options.index; - } - } - }, - - drawHit : function (options) { - // TODO hits for stacked bars; implement using calculateStack option? - var - context = options.context, - args = options.args, - geometry = this.getBarGeometry(args.x, args.y, options), - left = geometry.left, - top = geometry.top, - width = geometry.width, - height = geometry.height; - - context.save(); - context.strokeStyle = options.color; - context.lineWidth = options.lineWidth; - this.translate(context, options.horizontal); - - // Draw highlight - context.beginPath(); - context.moveTo(left, top + height); - context.lineTo(left, top); - context.lineTo(left + width, top); - context.lineTo(left + width, top + height); - if (options.fill) { - context.fillStyle = options.fillStyle; - context.fill(); - } - context.stroke(); - context.closePath(); - - context.restore(); - }, - - clearHit: function (options) { - var - context = options.context, - args = options.args, - geometry = this.getBarGeometry(args.x, args.y, options), - left = geometry.left, - width = geometry.width, - top = geometry.top, - height = geometry.height, - lineWidth = 2 * options.lineWidth; - - context.save(); - this.translate(context, options.horizontal); - context.clearRect( - left - lineWidth, - Math.min(top, top + height) - lineWidth, - width + 2 * lineWidth, - Math.abs(height) + 2 * lineWidth - ); - context.restore(); - }, - - extendXRange : function (axis, data, options, bars) { - this._extendRange(axis, data, options, bars); - }, - - extendYRange : function (axis, data, options, bars) { - this._extendRange(axis, data, options, bars); - }, - _extendRange: function (axis, data, options, bars) { - - var - max = axis.options.max; - - if (_.isNumber(max) || _.isString(max)) return; - - var - newmin = axis.min, - newmax = axis.max, - horizontal = options.horizontal, - orientation = axis.orientation, - positiveSums = this.positiveSums || {}, - negativeSums = this.negativeSums || {}, - value, datum, index, j; - - // Sides of bars - if ((orientation == 1 && !horizontal) || (orientation == -1 && horizontal)) { - if (options.centered) { - newmax = Math.max(axis.datamax + options.barWidth, newmax); - newmin = Math.min(axis.datamin - options.barWidth, newmin); - } - } - - if (options.stacked && - ((orientation == 1 && horizontal) || (orientation == -1 && !horizontal))){ - - for (j = data.length; j--;) { - value = data[j][(orientation == 1 ? 1 : 0)]+''; - datum = data[j][(orientation == 1 ? 0 : 1)]; - - // Positive - if (datum > 0) { - positiveSums[value] = (positiveSums[value] || 0) + datum; - newmax = Math.max(newmax, positiveSums[value]); - } - - // Negative - else { - negativeSums[value] = (negativeSums[value] || 0) + datum; - newmin = Math.min(newmin, negativeSums[value]); - } - } - } - - // End of bars - if ((orientation == 1 && horizontal) || (orientation == -1 && !horizontal)) { - if (options.topPadding && (axis.max === axis.datamax || (options.stacked && this.stackMax !== newmax))) { - newmax += options.topPadding * (newmax - newmin); - } - } - - this.stackMin = newmin; - this.stackMax = newmax; - this.negativeSums = negativeSums; - this.positiveSums = positiveSums; - - axis.max = newmax; - axis.min = newmin; - } - -}); - -/** Bubbles **/ -Flotr.addType('bubbles', { - options: { - show: false, // => setting to true will show radar chart, false will hide - lineWidth: 2, // => line width in pixels - fill: true, // => true to fill the area from the line to the x axis, false for (transparent) no fill - fillOpacity: 0.4, // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill - baseRadius: 2 // => ratio of the radar, against the plot size - }, - draw : function (options) { - var - context = options.context, - shadowSize = options.shadowSize; - - context.save(); - context.lineWidth = options.lineWidth; - - // Shadows - context.fillStyle = 'rgba(0,0,0,0.05)'; - context.strokeStyle = 'rgba(0,0,0,0.05)'; - this.plot(options, shadowSize / 2); - context.strokeStyle = 'rgba(0,0,0,0.1)'; - this.plot(options, shadowSize / 4); - - // Chart - context.strokeStyle = options.color; - context.fillStyle = options.fillStyle; - this.plot(options); - - context.restore(); - }, - plot : function (options, offset) { - - var - data = options.data, - context = options.context, - geometry, - i, x, y, z; - - offset = offset || 0; - - for (i = 0; i < data.length; ++i){ - - geometry = this.getGeometry(data[i], options); - - context.beginPath(); - context.arc(geometry.x + offset, geometry.y + offset, geometry.z, 0, 2 * Math.PI, true); - context.stroke(); - if (options.fill) context.fill(); - context.closePath(); - } - }, - getGeometry : function (point, options) { - return { - x : options.xScale(point[0]), - y : options.yScale(point[1]), - z : point[2] * options.baseRadius - }; - }, - hit : function (options) { - var - data = options.data, - args = options.args, - mouse = args[0], - n = args[1], - x = mouse.x, - y = mouse.y, - geometry, - dx, dy; - - for (i = data.length; i--;) { - geometry = this.getGeometry(data[i], options); - - dx = geometry.x - options.xScale(x); - dy = geometry.y - options.yScale(y); - - if (Math.sqrt(dx * dx + dy * dy) < geometry.z) { - n.x = data[i][0]; - n.y = data[i][1]; - n.index = i; - n.seriesIndex = options.index; - } - } - }, - drawHit : function (options) { - - var - context = options.context, - geometry = this.getGeometry(options.data[options.args.index], options); - - context.save(); - context.lineWidth = options.lineWidth; - context.fillStyle = options.fillStyle; - context.strokeStyle = options.color; - context.beginPath(); - context.arc(geometry.x, geometry.y, geometry.z, 0, 2 * Math.PI, true); - context.fill(); - context.stroke(); - context.closePath(); - context.restore(); - }, - clearHit : function (options) { - - var - context = options.context, - geometry = this.getGeometry(options.data[options.args.index], options), - offset = geometry.z + options.lineWidth; - - context.save(); - context.clearRect( - geometry.x - offset, - geometry.y - offset, - 2 * offset, - 2 * offset - ); - context.restore(); - } - // TODO Add a hit calculation method (like pie) -}); - -/** Candles **/ -Flotr.addType('candles', { - options: { - show: false, // => setting to true will show candle sticks, false will hide - lineWidth: 1, // => in pixels - wickLineWidth: 1, // => in pixels - candleWidth: 0.6, // => in units of the x axis - fill: true, // => true to fill the area from the line to the x axis, false for (transparent) no fill - upFillColor: '#00A8F0',// => up sticks fill color - downFillColor: '#CB4B4B',// => down sticks fill color - fillOpacity: 0.5, // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill - // TODO Test this barcharts option. - barcharts: false // => draw as barcharts (not standard bars but financial barcharts) - }, - - draw : function (options) { - - var - context = options.context; - - context.save(); - context.lineJoin = 'miter'; - context.lineCap = 'butt'; - // @TODO linewidth not interpreted the right way. - context.lineWidth = options.wickLineWidth || options.lineWidth; - - this.plot(options); - - context.restore(); - }, - - plot : function (options) { - - var - data = options.data, - context = options.context, - xScale = options.xScale, - yScale = options.yScale, - width = options.candleWidth / 2, - shadowSize = options.shadowSize, - lineWidth = options.lineWidth, - wickLineWidth = options.wickLineWidth, - pixelOffset = (wickLineWidth % 2) / 2, - color, - datum, x, y, - open, high, low, close, - left, right, bottom, top, bottom2, top2, - i; - - if (data.length < 1) return; - - for (i = 0; i < data.length; i++) { - datum = data[i]; - x = datum[0]; - open = datum[1]; - high = datum[2]; - low = datum[3]; - close = datum[4]; - left = xScale(x - width); - right = xScale(x + width); - bottom = yScale(low); - top = yScale(high); - bottom2 = yScale(Math.min(open, close)); - top2 = yScale(Math.max(open, close)); - - /* - // TODO skipping - if(right < xa.min || left > xa.max || top < ya.min || bottom > ya.max) - continue; - */ - - color = options[open > close ? 'downFillColor' : 'upFillColor']; - - // Fill the candle. - // TODO Test the barcharts option - if (options.fill && !options.barcharts) { - context.fillStyle = 'rgba(0,0,0,0.05)'; - context.fillRect(left + shadowSize, top2 + shadowSize, right - left, bottom2 - top2); - context.save(); - context.globalAlpha = options.fillOpacity; - context.fillStyle = color; - context.fillRect(left, top2 + lineWidth, right - left, bottom2 - top2); - context.restore(); - } - - // Draw candle outline/border, high, low. - if (lineWidth || wickLineWidth) { - - x = Math.floor((left + right) / 2) + pixelOffset; - - context.strokeStyle = color; - context.beginPath(); - - // TODO Again with the bartcharts - if (options.barcharts) { - - context.moveTo(x, Math.floor(top + width)); - context.lineTo(x, Math.floor(bottom + width)); - - y = Math.floor(open + width) + 0.5; - context.moveTo(Math.floor(left) + pixelOffset, y); - context.lineTo(x, y); - - y = Math.floor(close + width) + 0.5; - context.moveTo(Math.floor(right) + pixelOffset, y); - context.lineTo(x, y); - } else { - context.strokeRect(left, top2 + lineWidth, right - left, bottom2 - top2); - - context.moveTo(x, Math.floor(top2 + lineWidth)); - context.lineTo(x, Math.floor(top + lineWidth)); - context.moveTo(x, Math.floor(bottom2 + lineWidth)); - context.lineTo(x, Math.floor(bottom + lineWidth)); - } - - context.closePath(); - context.stroke(); - } - } - }, - extendXRange: function (axis, data, options) { - if (axis.options.max === null) { - axis.max = Math.max(axis.datamax + 0.5, axis.max); - axis.min = Math.min(axis.datamin - 0.5, axis.min); - } - } -}); - -/** Gantt - * Base on data in form [s,y,d] where: - * y - executor or simply y value - * s - task start value - * d - task duration - * **/ -Flotr.addType('gantt', { - options: { - show: false, // => setting to true will show gantt, false will hide - lineWidth: 2, // => in pixels - barWidth: 1, // => in units of the x axis - fill: true, // => true to fill the area from the line to the x axis, false for (transparent) no fill - fillColor: null, // => fill color - fillOpacity: 0.4, // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill - centered: true // => center the bars to their x axis value - }, - /** - * Draws gantt series in the canvas element. - * @param {Object} series - Series with options.gantt.show = true. - */ - draw: function(series) { - var ctx = this.ctx, - bw = series.gantt.barWidth, - lw = Math.min(series.gantt.lineWidth, bw); - - ctx.save(); - ctx.translate(this.plotOffset.left, this.plotOffset.top); - ctx.lineJoin = 'miter'; - - /** - * @todo linewidth not interpreted the right way. - */ - ctx.lineWidth = lw; - ctx.strokeStyle = series.color; - - ctx.save(); - this.gantt.plotShadows(series, bw, 0, series.gantt.fill); - ctx.restore(); - - if(series.gantt.fill){ - var color = series.gantt.fillColor || series.color; - ctx.fillStyle = this.processColor(color, {opacity: series.gantt.fillOpacity}); - } - - this.gantt.plot(series, bw, 0, series.gantt.fill); - ctx.restore(); - }, - plot: function(series, barWidth, offset, fill){ - var data = series.data; - if(data.length < 1) return; - - var xa = series.xaxis, - ya = series.yaxis, - ctx = this.ctx, i; - - for(i = 0; i < data.length; i++){ - var y = data[i][0], - s = data[i][1], - d = data[i][2], - drawLeft = true, drawTop = true, drawRight = true; - - if (s === null || d === null) continue; - - var left = s, - right = s + d, - bottom = y - (series.gantt.centered ? barWidth/2 : 0), - top = y + barWidth - (series.gantt.centered ? barWidth/2 : 0); - - if(right < xa.min || left > xa.max || top < ya.min || bottom > ya.max) - continue; - - if(left < xa.min){ - left = xa.min; - drawLeft = false; - } - - if(right > xa.max){ - right = xa.max; - if (xa.lastSerie != series) - drawTop = false; - } - - if(bottom < ya.min) - bottom = ya.min; - - if(top > ya.max){ - top = ya.max; - if (ya.lastSerie != series) - drawTop = false; - } - - /** - * Fill the bar. - */ - if(fill){ - ctx.beginPath(); - ctx.moveTo(xa.d2p(left), ya.d2p(bottom) + offset); - ctx.lineTo(xa.d2p(left), ya.d2p(top) + offset); - ctx.lineTo(xa.d2p(right), ya.d2p(top) + offset); - ctx.lineTo(xa.d2p(right), ya.d2p(bottom) + offset); - ctx.fill(); - ctx.closePath(); - } - - /** - * Draw bar outline/border. - */ - if(series.gantt.lineWidth && (drawLeft || drawRight || drawTop)){ - ctx.beginPath(); - ctx.moveTo(xa.d2p(left), ya.d2p(bottom) + offset); - - ctx[drawLeft ?'lineTo':'moveTo'](xa.d2p(left), ya.d2p(top) + offset); - ctx[drawTop ?'lineTo':'moveTo'](xa.d2p(right), ya.d2p(top) + offset); - ctx[drawRight?'lineTo':'moveTo'](xa.d2p(right), ya.d2p(bottom) + offset); - - ctx.stroke(); - ctx.closePath(); - } - } - }, - plotShadows: function(series, barWidth, offset){ - var data = series.data; - if(data.length < 1) return; - - var i, y, s, d, - xa = series.xaxis, - ya = series.yaxis, - ctx = this.ctx, - sw = this.options.shadowSize; - - for(i = 0; i < data.length; i++){ - y = data[i][0]; - s = data[i][1]; - d = data[i][2]; - - if (s === null || d === null) continue; - - var left = s, - right = s + d, - bottom = y - (series.gantt.centered ? barWidth/2 : 0), - top = y + barWidth - (series.gantt.centered ? barWidth/2 : 0); - - if(right < xa.min || left > xa.max || top < ya.min || bottom > ya.max) - continue; - - if(left < xa.min) left = xa.min; - if(right > xa.max) right = xa.max; - if(bottom < ya.min) bottom = ya.min; - if(top > ya.max) top = ya.max; - - var width = xa.d2p(right)-xa.d2p(left)-((xa.d2p(right)+sw <= this.plotWidth) ? 0 : sw); - var height = ya.d2p(bottom)-ya.d2p(top)-((ya.d2p(bottom)+sw <= this.plotHeight) ? 0 : sw ); - - ctx.fillStyle = 'rgba(0,0,0,0.05)'; - ctx.fillRect(Math.min(xa.d2p(left)+sw, this.plotWidth), Math.min(ya.d2p(top)+sw, this.plotHeight), width, height); - } - }, - extendXRange: function(axis) { - if(axis.options.max === null){ - var newmin = axis.min, - newmax = axis.max, - i, j, x, s, g, - stackedSumsPos = {}, - stackedSumsNeg = {}, - lastSerie = null; - - for(i = 0; i < this.series.length; ++i){ - s = this.series[i]; - g = s.gantt; - - if(g.show && s.xaxis == axis) { - for (j = 0; j < s.data.length; j++) { - if (g.show) { - y = s.data[j][0]+''; - stackedSumsPos[y] = Math.max((stackedSumsPos[y] || 0), s.data[j][1]+s.data[j][2]); - lastSerie = s; - } - } - for (j in stackedSumsPos) { - newmax = Math.max(stackedSumsPos[j], newmax); - } - } - } - axis.lastSerie = lastSerie; - axis.max = newmax; - axis.min = newmin; - } - }, - extendYRange: function(axis){ - if(axis.options.max === null){ - var newmax = Number.MIN_VALUE, - newmin = Number.MAX_VALUE, - i, j, s, g, - stackedSumsPos = {}, - stackedSumsNeg = {}, - lastSerie = null; - - for(i = 0; i < this.series.length; ++i){ - s = this.series[i]; - g = s.gantt; - - if (g.show && !s.hide && s.yaxis == axis) { - var datamax = Number.MIN_VALUE, datamin = Number.MAX_VALUE; - for(j=0; j < s.data.length; j++){ - datamax = Math.max(datamax,s.data[j][0]); - datamin = Math.min(datamin,s.data[j][0]); - } - - if (g.centered) { - newmax = Math.max(datamax + 0.5, newmax); - newmin = Math.min(datamin - 0.5, newmin); - } - else { - newmax = Math.max(datamax + 1, newmax); - newmin = Math.min(datamin, newmin); - } - // For normal horizontal bars - if (g.barWidth + datamax > newmax){ - newmax = axis.max + g.barWidth; - } - } - } - axis.lastSerie = lastSerie; - axis.max = newmax; - axis.min = newmin; - axis.tickSize = Flotr.getTickSize(axis.options.noTicks, newmin, newmax, axis.options.tickDecimals); - } - } -}); - -/** Markers **/ -/** - * Formats the marker labels. - * @param {Object} obj - Marker value Object {x:..,y:..} - * @return {String} Formatted marker string - */ -(function () { - -Flotr.defaultMarkerFormatter = function(obj){ - return (Math.round(obj.y*100)/100)+''; -}; - -Flotr.addType('markers', { - options: { - show: false, // => setting to true will show markers, false will hide - lineWidth: 1, // => line width of the rectangle around the marker - color: '#000000', // => text color - fill: false, // => fill or not the marekers' rectangles - fillColor: "#FFFFFF", // => fill color - fillOpacity: 0.4, // => fill opacity - stroke: false, // => draw the rectangle around the markers - position: 'ct', // => the markers position (vertical align: b, m, t, horizontal align: l, c, r) - verticalMargin: 0, // => the margin between the point and the text. - labelFormatter: Flotr.defaultMarkerFormatter, - fontSize: Flotr.defaultOptions.fontSize, - stacked: false, // => true if markers should be stacked - stackingType: 'b', // => define staching behavior, (b- bars like, a - area like) (see Issue 125 for details) - horizontal: false // => true if markers should be horizontal (For now only in a case on horizontal stacked bars, stacks should be calculated horizontaly) - }, - - // TODO test stacked markers. - stack : { - positive : [], - negative : [], - values : [] - }, - - draw : function (options) { - - var - data = options.data, - context = options.context, - stack = options.stacked ? options.stack : false, - stackType = options.stackingType, - stackOffsetNeg, - stackOffsetPos, - stackOffset, - i, x, y, label; - - context.save(); - context.lineJoin = 'round'; - context.lineWidth = options.lineWidth; - context.strokeStyle = 'rgba(0,0,0,0.5)'; - context.fillStyle = options.fillStyle; - - function stackPos (a, b) { - stackOffsetPos = stack.negative[a] || 0; - stackOffsetNeg = stack.positive[a] || 0; - if (b > 0) { - stack.positive[a] = stackOffsetPos + b; - return stackOffsetPos + b; - } else { - stack.negative[a] = stackOffsetNeg + b; - return stackOffsetNeg + b; - } - } - - for (i = 0; i < data.length; ++i) { - - x = data[i][0]; - y = data[i][1]; - - if (stack) { - if (stackType == 'b') { - if (options.horizontal) y = stackPos(y, x); - else x = stackPos(x, y); - } else if (stackType == 'a') { - stackOffset = stack.values[x] || 0; - stack.values[x] = stackOffset + y; - y = stackOffset + y; - } - } - - label = options.labelFormatter({x: x, y: y, index: i, data : data}); - this.plot(options.xScale(x), options.yScale(y), label, options); - } - context.restore(); - }, - plot: function(x, y, label, options) { - var context = options.context; - if (isImage(label) && !label.complete) { - throw 'Marker image not loaded.'; - } else { - this._plot(x, y, label, options); - } - }, - - _plot: function(x, y, label, options) { - var context = options.context, - margin = 2, - left = x, - top = y, - dim; - - if (isImage(label)) - dim = {height : label.height, width: label.width}; - else - dim = options.text.canvas(label); - - dim.width = Math.floor(dim.width+margin*2); - dim.height = Math.floor(dim.height+margin*2); - - if (options.position.indexOf('c') != -1) left -= dim.width/2 + margin; - else if (options.position.indexOf('l') != -1) left -= dim.width; - - if (options.position.indexOf('m') != -1) top -= dim.height/2 + margin; - else if (options.position.indexOf('t') != -1) top -= dim.height + options.verticalMargin; - else top += options.verticalMargin; - - left = Math.floor(left)+0.5; - top = Math.floor(top)+0.5; - - if(options.fill) - context.fillRect(left, top, dim.width, dim.height); - - if(options.stroke) - context.strokeRect(left, top, dim.width, dim.height); - - if (isImage(label)) - context.drawImage(label, left+margin, top+margin); - else - Flotr.drawText(context, label, left+margin, top+margin, {textBaseline: 'top', textAlign: 'left', size: options.fontSize, color: options.color}); - } -}); - -function isImage (i) { - return typeof i === 'object' && i.constructor && (Image ? true : i.constructor === Image); -} - -})(); - -/** Pie **/ -/** - * Formats the pies labels. - * @param {Object} slice - Slice object - * @return {String} Formatted pie label string - */ -(function () { - -var - _ = Flotr._; - -Flotr.defaultPieLabelFormatter = function (total, value) { - return (100 * value / total).toFixed(2)+'%'; -}; - -Flotr.addType('pie', { - options: { - show: false, // => setting to true will show bars, false will hide - lineWidth: 1, // => in pixels - fill: true, // => true to fill the area from the line to the x axis, false for (transparent) no fill - fillColor: null, // => fill color - fillOpacity: 0.6, // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill - explode: 6, // => the number of pixels the splices will be far from the center - sizeRatio: 0.6, // => the size ratio of the pie relative to the plot - startAngle: Math.PI/4, // => the first slice start angle - labelFormatter: Flotr.defaultPieLabelFormatter, - pie3D: false, // => whether to draw the pie in 3 dimenstions or not (ineffective) - pie3DviewAngle: (Math.PI/2 * 0.8), - pie3DspliceThickness: 20 - }, - - draw : function (options) { - - // TODO 3D charts what? - - var - data = options.data, - context = options.context, - canvas = context.canvas, - lineWidth = options.lineWidth, - shadowSize = options.shadowSize, - sizeRatio = options.sizeRatio, - height = options.height, - width = options.width, - explode = options.explode, - color = options.color, - fill = options.fill, - fillStyle = options.fillStyle, - radius = Math.min(canvas.width, canvas.height) * sizeRatio / 2, - value = data[0][1], - html = [], - vScale = 1,//Math.cos(series.pie.viewAngle); - measure = Math.PI * 2 * value / this.total, - startAngle = this.startAngle || (2 * Math.PI * options.startAngle), // TODO: this initial startAngle is already in radians (fixing will be test-unstable) - endAngle = startAngle + measure, - bisection = startAngle + measure / 2, - label = options.labelFormatter(this.total, value), - //plotTickness = Math.sin(series.pie.viewAngle)*series.pie.spliceThickness / vScale; - explodeCoeff = explode + radius + 4, - distX = Math.cos(bisection) * explodeCoeff, - distY = Math.sin(bisection) * explodeCoeff, - textAlign = distX < 0 ? 'right' : 'left', - textBaseline = distY > 0 ? 'top' : 'bottom', - style, - x, y, - distX, distY; - - context.save(); - context.translate(width / 2, height / 2); - context.scale(1, vScale); - - x = Math.cos(bisection) * explode; - y = Math.sin(bisection) * explode; - - // Shadows - if (shadowSize > 0) { - this.plotSlice(x + shadowSize, y + shadowSize, radius, startAngle, endAngle, context); - if (fill) { - context.fillStyle = 'rgba(0,0,0,0.1)'; - context.fill(); - } - } - - this.plotSlice(x, y, radius, startAngle, endAngle, context); - if (fill) { - context.fillStyle = fillStyle; - context.fill(); - } - context.lineWidth = lineWidth; - context.strokeStyle = color; - context.stroke(); - - style = { - size : options.fontSize * 1.2, - color : options.fontColor, - weight : 1.5 - }; - - if (label) { - if (options.htmlText || !options.textEnabled) { - divStyle = 'position:absolute;' + textBaseline + ':' + (height / 2 + (textBaseline === 'top' ? distY : -distY)) + 'px;'; - divStyle += textAlign + ':' + (width / 2 + (textAlign === 'right' ? -distX : distX)) + 'px;'; - html.push('<div style="', divStyle, '" class="flotr-grid-label">', label, '</div>'); - } - else { - style.textAlign = textAlign; - style.textBaseline = textBaseline; - Flotr.drawText(context, label, distX, distY, style); - } - } - - if (options.htmlText || !options.textEnabled) { - var div = Flotr.DOM.node('<div style="color:' + options.fontColor + '" class="flotr-labels"></div>'); - Flotr.DOM.insert(div, html.join('')); - Flotr.DOM.insert(options.element, div); - } - - context.restore(); - - // New start angle - this.startAngle = endAngle; - this.slices = this.slices || []; - this.slices.push({ - radius : Math.min(canvas.width, canvas.height) * sizeRatio / 2, - x : x, - y : y, - explode : explode, - start : startAngle, - end : endAngle - }); - }, - plotSlice : function (x, y, radius, startAngle, endAngle, context) { - context.beginPath(); - context.moveTo(x, y); - context.arc(x, y, radius, startAngle, endAngle, false); - context.lineTo(x, y); - context.closePath(); - }, - hit : function (options) { - - var - data = options.data[0], - args = options.args, - index = options.index, - mouse = args[0], - n = args[1], - slice = this.slices[index], - x = mouse.relX - options.width / 2, - y = mouse.relY - options.height / 2, - r = Math.sqrt(x * x + y * y), - theta = Math.atan(y / x), - circle = Math.PI * 2, - explode = slice.explode || options.explode, - start = slice.start % circle, - end = slice.end % circle; - - if (x < 0) { - theta += Math.PI; - } else if (x > 0 && y < 0) { - theta += circle; - } - - if (r < slice.radius + explode && r > explode) { - if ((start > end && (theta < end || theta > start)) || - (theta > start && theta < end)) { - - // TODO Decouple this from hit plugin (chart shouldn't know what n means) - n.x = data[0]; - n.y = data[1]; - n.sAngle = start; - n.eAngle = end; - n.index = 0; - n.seriesIndex = index; - n.fraction = data[1] / this.total; - } - } - }, - drawHit: function (options) { - var - context = options.context, - slice = this.slices[options.args.seriesIndex]; - - context.save(); - context.translate(options.width / 2, options.height / 2); - this.plotSlice(slice.x, slice.y, slice.radius, slice.start, slice.end, context); - context.stroke(); - context.restore(); - }, - clearHit : function (options) { - var - context = options.context, - slice = this.slices[options.args.seriesIndex], - padding = 2 * options.lineWidth, - radius = slice.radius + padding; - - context.save(); - context.translate(options.width / 2, options.height / 2); - context.clearRect( - slice.x - radius, - slice.y - radius, - 2 * radius + padding, - 2 * radius + padding - ); - context.restore(); - }, - extendYRange : function (axis, data) { - this.total = (this.total || 0) + data[0][1]; - } -}); -})(); - -/** Points **/ -Flotr.addType('points', { - options: { - show: false, // => setting to true will show points, false will hide - radius: 3, // => point radius (pixels) - lineWidth: 2, // => line width in pixels - fill: true, // => true to fill the points with a color, false for (transparent) no fill - fillColor: '#FFFFFF', // => fill color - fillOpacity: 0.4 // => opacity of color inside the points - }, - - draw : function (options) { - var - context = options.context, - lineWidth = options.lineWidth, - shadowSize = options.shadowSize; - - context.save(); - - if (shadowSize > 0) { - context.lineWidth = shadowSize / 2; - - context.strokeStyle = 'rgba(0,0,0,0.1)'; - this.plot(options, shadowSize / 2 + context.lineWidth / 2); - - context.strokeStyle = 'rgba(0,0,0,0.2)'; - this.plot(options, context.lineWidth / 2); - } - - context.lineWidth = options.lineWidth; - context.strokeStyle = options.color; - context.fillStyle = options.fillColor || options.color; - - this.plot(options); - context.restore(); - }, - - plot : function (options, offset) { - var - data = options.data, - context = options.context, - xScale = options.xScale, - yScale = options.yScale, - i, x, y; - - for (i = data.length - 1; i > -1; --i) { - y = data[i][1]; - if (y === null) continue; - - x = xScale(data[i][0]); - y = yScale(y); - - if (x < 0 || x > options.width || y < 0 || y > options.height) continue; - - context.beginPath(); - if (offset) { - context.arc(x, y + offset, options.radius, 0, Math.PI, false); - } else { - context.arc(x, y, options.radius, 0, 2 * Math.PI, true); - if (options.fill) context.fill(); - } - context.stroke(); - context.closePath(); - } - } -}); - -/** Radar **/ -Flotr.addType('radar', { - options: { - show: false, // => setting to true will show radar chart, false will hide - lineWidth: 2, // => line width in pixels - fill: true, // => true to fill the area from the line to the x axis, false for (transparent) no fill - fillOpacity: 0.4, // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill - radiusRatio: 0.90 // => ratio of the radar, against the plot size - }, - draw : function (options) { - var - context = options.context, - shadowSize = options.shadowSize; - - context.save(); - context.translate(options.width / 2, options.height / 2); - context.lineWidth = options.lineWidth; - - // Shadow - context.fillStyle = 'rgba(0,0,0,0.05)'; - context.strokeStyle = 'rgba(0,0,0,0.05)'; - this.plot(options, shadowSize / 2); - context.strokeStyle = 'rgba(0,0,0,0.1)'; - this.plot(options, shadowSize / 4); - - // Chart - context.strokeStyle = options.color; - context.fillStyle = options.fillStyle; - this.plot(options); - - context.restore(); - }, - plot : function (options, offset) { - var - data = options.data, - context = options.context, - radius = Math.min(options.height, options.width) * options.radiusRatio / 2, - step = 2 * Math.PI / data.length, - angle = -Math.PI / 2, - i, ratio; - - offset = offset || 0; - - context.beginPath(); - for (i = 0; i < data.length; ++i) { - ratio = data[i][1] / this.max; - - context[i === 0 ? 'moveTo' : 'lineTo']( - Math.cos(i * step + angle) * radius * ratio + offset, - Math.sin(i * step + angle) * radius * ratio + offset - ); - } - context.closePath(); - if (options.fill) context.fill(); - context.stroke(); - }, - extendYRange : function (axis, data) { - this.max = Math.max(axis.max, this.max || -Number.MAX_VALUE); - } -}); - -Flotr.addType('timeline', { - options: { - show: false, - lineWidth: 1, - barWidth: 0.2, - fill: true, - fillColor: null, - fillOpacity: 0.4, - centered: true - }, - - draw : function (options) { - - var - context = options.context; - - context.save(); - context.lineJoin = 'miter'; - context.lineWidth = options.lineWidth; - context.strokeStyle = options.color; - context.fillStyle = options.fillStyle; - - this.plot(options); - - context.restore(); - }, - - plot : function (options) { - - var - data = options.data, - context = options.context, - xScale = options.xScale, - yScale = options.yScale, - barWidth = options.barWidth, - lineWidth = options.lineWidth, - i; - - Flotr._.each(data, function (timeline) { - - var - x = timeline[0], - y = timeline[1], - w = timeline[2], - h = barWidth, - - xt = Math.ceil(xScale(x)), - wt = Math.ceil(xScale(x + w)) - xt, - yt = Math.round(yScale(y)), - ht = Math.round(yScale(y - h)) - yt, - - x0 = xt - lineWidth / 2, - y0 = Math.round(yt - ht / 2) - lineWidth / 2; - - context.strokeRect(x0, y0, wt, ht); - context.fillRect(x0, y0, wt, ht); - - }); - }, - - extendRange : function (series) { - - var - data = series.data, - xa = series.xaxis, - ya = series.yaxis, - w = series.timeline.barWidth; - - if (xa.options.min === null) - xa.min = xa.datamin - w / 2; - - if (xa.options.max === null) { - - var - max = xa.max; - - Flotr._.each(data, function (timeline) { - max = Math.max(max, timeline[0] + timeline[2]); - }, this); - - xa.max = max + w / 2; - } - - if (ya.options.min === null) - ya.min = ya.datamin - w; - if (ya.options.min === null) - ya.max = ya.datamax + w; - } - -}); - -(function () { - -var D = Flotr.DOM; - -Flotr.addPlugin('crosshair', { - options: { - mode: null, // => one of null, 'x', 'y' or 'xy' - color: '#FF0000', // => crosshair color - hideCursor: true // => hide the cursor when the crosshair is shown - }, - callbacks: { - 'flotr:mousemove': function(e, pos) { - if (this.options.crosshair.mode) { - this.crosshair.clearCrosshair(); - this.crosshair.drawCrosshair(pos); - } - } - }, - /** - * Draws the selection box. - */ - drawCrosshair: function(pos) { - var octx = this.octx, - options = this.options.crosshair, - plotOffset = this.plotOffset, - x = plotOffset.left + pos.relX + 0.5, - y = plotOffset.top + pos.relY + 0.5; - - if (pos.relX < 0 || pos.relY < 0 || pos.relX > this.plotWidth || pos.relY > this.plotHeight) { - this.el.style.cursor = null; - D.removeClass(this.el, 'flotr-crosshair'); - return; - } - - if (options.hideCursor) { - this.el.style.cursor = 'none'; - D.addClass(this.el, 'flotr-crosshair'); - } - - octx.save(); - octx.strokeStyle = options.color; - octx.lineWidth = 1; - octx.beginPath(); - - if (options.mode.indexOf('x') != -1) { - octx.moveTo(x, plotOffset.top); - octx.lineTo(x, plotOffset.top + this.plotHeight); - } - - if (options.mode.indexOf('y') != -1) { - octx.moveTo(plotOffset.left, y); - octx.lineTo(plotOffset.left + this.plotWidth, y); - } - - octx.stroke(); - octx.restore(); - }, - /** - * Removes the selection box from the overlay canvas. - */ - clearCrosshair: function() { - - var - plotOffset = this.plotOffset, - position = this.lastMousePos, - context = this.octx; - - if (position) { - context.clearRect( - position.relX + plotOffset.left, - plotOffset.top, - 1, - this.plotHeight + 1 - ); - context.clearRect( - plotOffset.left, - position.relY + plotOffset.top, - this.plotWidth + 1, - 1 - ); - } - } -}); -})(); - -(function() { - -var - D = Flotr.DOM, - _ = Flotr._; - -function getImage (type, canvas, width, height) { - - // TODO add scaling for w / h - var - mime = 'image/'+type, - data = canvas.toDataURL(mime), - image = new Image(); - image.src = data; - return image; -} - -Flotr.addPlugin('download', { - - saveImage: function (type, width, height, replaceCanvas) { - var image = null; - if (Flotr.isIE && Flotr.isIE < 9) { - image = '<html><body>'+this.canvas.firstChild.innerHTML+'</body></html>'; - return window.open().document.write(image); - } - - if (type !== 'jpeg' && type !== 'png') return; - - image = getImage(type, this.canvas, width, height); - - if (_.isElement(image) && replaceCanvas) { - this.download.restoreCanvas(); - D.hide(this.canvas); - D.hide(this.overlay); - D.setStyles({position: 'absolute'}); - D.insert(this.el, image); - this.saveImageElement = image; - } else { - return window.open(image.src); - } - }, - - restoreCanvas: function() { - D.show(this.canvas); - D.show(this.overlay); - if (this.saveImageElement) this.el.removeChild(this.saveImageElement); - this.saveImageElement = null; - } -}); - -})(); - -(function () { - -var E = Flotr.EventAdapter, - _ = Flotr._; - -Flotr.addPlugin('graphGrid', { - - callbacks: { - 'flotr:beforedraw' : function () { - this.graphGrid.drawGrid(); - }, - 'flotr:afterdraw' : function () { - this.graphGrid.drawOutline(); - } - }, - - drawGrid: function(){ - - var - ctx = this.ctx, - options = this.options, - grid = options.grid, - verticalLines = grid.verticalLines, - horizontalLines = grid.horizontalLines, - minorVerticalLines = grid.minorVerticalLines, - minorHorizontalLines = grid.minorHorizontalLines, - plotHeight = this.plotHeight, - plotWidth = this.plotWidth, - a, v, i, j; - - if(verticalLines || minorVerticalLines || - horizontalLines || minorHorizontalLines){ - E.fire(this.el, 'flotr:beforegrid', [this.axes.x, this.axes.y, options, this]); - } - ctx.save(); - ctx.lineWidth = 1; - ctx.strokeStyle = grid.tickColor; - - function circularHorizontalTicks (ticks) { - for(i = 0; i < ticks.length; ++i){ - var ratio = ticks[i].v / a.max; - for(j = 0; j <= sides; ++j){ - ctx[j === 0 ? 'moveTo' : 'lineTo']( - Math.cos(j*coeff+angle)*radius*ratio, - Math.sin(j*coeff+angle)*radius*ratio - ); - } - } - } - function drawGridLines (ticks, callback) { - _.each(_.pluck(ticks, 'v'), function(v){ - // Don't show lines on upper and lower bounds. - if ((v <= a.min || v >= a.max) || - (v == a.min || v == a.max) && grid.outlineWidth) - return; - callback(Math.floor(a.d2p(v)) + ctx.lineWidth/2); - }); - } - function drawVerticalLines (x) { - ctx.moveTo(x, 0); - ctx.lineTo(x, plotHeight); - } - function drawHorizontalLines (y) { - ctx.moveTo(0, y); - ctx.lineTo(plotWidth, y); - } - - if (grid.circular) { - ctx.translate(this.plotOffset.left+plotWidth/2, this.plotOffset.top+plotHeight/2); - var radius = Math.min(plotHeight, plotWidth)*options.radar.radiusRatio/2, - sides = this.axes.x.ticks.length, - coeff = 2*(Math.PI/sides), - angle = -Math.PI/2; - - // Draw grid lines in vertical direction. - ctx.beginPath(); - - a = this.axes.y; - - if(horizontalLines){ - circularHorizontalTicks(a.ticks); - } - if(minorHorizontalLines){ - circularHorizontalTicks(a.minorTicks); - } - - if(verticalLines){ - _.times(sides, function(i){ - ctx.moveTo(0, 0); - ctx.lineTo(Math.cos(i*coeff+angle)*radius, Math.sin(i*coeff+angle)*radius); - }); - } - ctx.stroke(); - } - else { - ctx.translate(this.plotOffset.left, this.plotOffset.top); - - // Draw grid background, if present in options. - if(grid.backgroundColor){ - ctx.fillStyle = this.processColor(grid.backgroundColor, {x1: 0, y1: 0, x2: plotWidth, y2: plotHeight}); - ctx.fillRect(0, 0, plotWidth, plotHeight); - } - - ctx.beginPath(); - - a = this.axes.x; - if (verticalLines) drawGridLines(a.ticks, drawVerticalLines); - if (minorVerticalLines) drawGridLines(a.minorTicks, drawVerticalLines); - - a = this.axes.y; - if (horizontalLines) drawGridLines(a.ticks, drawHorizontalLines); - if (minorHorizontalLines) drawGridLines(a.minorTicks, drawHorizontalLines); - - ctx.stroke(); - } - - ctx.restore(); - if(verticalLines || minorVerticalLines || - horizontalLines || minorHorizontalLines){ - E.fire(this.el, 'flotr:aftergrid', [this.axes.x, this.axes.y, options, this]); - } - }, - - drawOutline: function(){ - var - that = this, - options = that.options, - grid = options.grid, - outline = grid.outline, - ctx = that.ctx, - backgroundImage = grid.backgroundImage, - plotOffset = that.plotOffset, - leftOffset = plotOffset.left, - topOffset = plotOffset.top, - plotWidth = that.plotWidth, - plotHeight = that.plotHeight, - v, img, src, left, top, globalAlpha; - - if (!grid.outlineWidth) return; - - ctx.save(); - - if (grid.circular) { - ctx.translate(leftOffset + plotWidth / 2, topOffset + plotHeight / 2); - var radius = Math.min(plotHeight, plotWidth) * options.radar.radiusRatio / 2, - sides = this.axes.x.ticks.length, - coeff = 2*(Math.PI/sides), - angle = -Math.PI/2; - - // Draw axis/grid border. - ctx.beginPath(); - ctx.lineWidth = grid.outlineWidth; - ctx.strokeStyle = grid.color; - ctx.lineJoin = 'round'; - - for(i = 0; i <= sides; ++i){ - ctx[i === 0 ? 'moveTo' : 'lineTo'](Math.cos(i*coeff+angle)*radius, Math.sin(i*coeff+angle)*radius); - } - //ctx.arc(0, 0, radius, 0, Math.PI*2, true); - - ctx.stroke(); - } - else { - ctx.translate(leftOffset, topOffset); - - // Draw axis/grid border. - var lw = grid.outlineWidth, - orig = 0.5-lw+((lw+1)%2/2), - lineTo = 'lineTo', - moveTo = 'moveTo'; - ctx.lineWidth = lw; - ctx.strokeStyle = grid.color; - ctx.lineJoin = 'miter'; - ctx.beginPath(); - ctx.moveTo(orig, orig); - plotWidth = plotWidth - (lw / 2) % 1; - plotHeight = plotHeight + lw / 2; - ctx[outline.indexOf('n') !== -1 ? lineTo : moveTo](plotWidth, orig); - ctx[outline.indexOf('e') !== -1 ? lineTo : moveTo](plotWidth, plotHeight); - ctx[outline.indexOf('s') !== -1 ? lineTo : moveTo](orig, plotHeight); - ctx[outline.indexOf('w') !== -1 ? lineTo : moveTo](orig, orig); - ctx.stroke(); - ctx.closePath(); - } - - ctx.restore(); - - if (backgroundImage) { - - src = backgroundImage.src || backgroundImage; - left = (parseInt(backgroundImage.left, 10) || 0) + plotOffset.left; - top = (parseInt(backgroundImage.top, 10) || 0) + plotOffset.top; - img = new Image(); - - img.onload = function() { - ctx.save(); - if (backgroundImage.alpha) ctx.globalAlpha = backgroundImage.alpha; - ctx.globalCompositeOperation = 'destination-over'; - ctx.drawImage(img, 0, 0, img.width, img.height, left, top, plotWidth, plotHeight); - ctx.restore(); - }; - - img.src = src; - } - } -}); - -})(); - -(function () { - -var - D = Flotr.DOM, - _ = Flotr._, - flotr = Flotr, - S_MOUSETRACK = 'opacity:0.7;background-color:#000;color:#fff;display:none;position:absolute;padding:2px 8px;-moz-border-radius:4px;border-radius:4px;white-space:nowrap;'; - -Flotr.addPlugin('hit', { - callbacks: { - 'flotr:mousemove': function(e, pos) { - this.hit.track(pos); - }, - 'flotr:click': function(pos) { - this.hit.track(pos); - }, - 'flotr:mouseout': function() { - this.hit.clearHit(); - } - }, - track : function (pos) { - if (this.options.mouse.track || _.any(this.series, function(s){return s.mouse && s.mouse.track;})) { - this.hit.hit(pos); - } - }, - /** - * Try a method on a graph type. If the method exists, execute it. - * @param {Object} series - * @param {String} method Method name. - * @param {Array} args Arguments applied to method. - * @return executed successfully or failed. - */ - executeOnType: function(s, method, args){ - var - success = false, - options; - - if (!_.isArray(s)) s = [s]; - - function e(s, index) { - _.each(_.keys(flotr.graphTypes), function (type) { - if (s[type] && s[type].show && this[type][method]) { - options = this.getOptions(s, type); - - options.fill = !!s.mouse.fillColor; - options.fillStyle = this.processColor(s.mouse.fillColor || '#ffffff', {opacity: s.mouse.fillOpacity}); - options.color = s.mouse.lineColor; - options.context = this.octx; - options.index = index; - - if (args) options.args = args; - this[type][method].call(this[type], options); - success = true; - } - }, this); - } - _.each(s, e, this); - - return success; - }, - /** - * Updates the mouse tracking point on the overlay. - */ - drawHit: function(n){ - var octx = this.octx, - s = n.series; - - if (s.mouse.lineColor) { - octx.save(); - octx.lineWidth = (s.points ? s.points.lineWidth : 1); - octx.strokeStyle = s.mouse.lineColor; - octx.fillStyle = this.processColor(s.mouse.fillColor || '#ffffff', {opacity: s.mouse.fillOpacity}); - octx.translate(this.plotOffset.left, this.plotOffset.top); - - if (!this.hit.executeOnType(s, 'drawHit', n)) { - var xa = n.xaxis, - ya = n.yaxis; - - octx.beginPath(); - // TODO fix this (points) should move to general testable graph mixin - octx.arc(xa.d2p(n.x), ya.d2p(n.y), s.points.radius || s.mouse.radius, 0, 2 * Math.PI, true); - octx.fill(); - octx.stroke(); - octx.closePath(); - } - octx.restore(); - } - this.prevHit = n; - }, - /** - * Removes the mouse tracking point from the overlay. - */ - clearHit: function(){ - var prev = this.prevHit, - octx = this.octx, - plotOffset = this.plotOffset; - octx.save(); - octx.translate(plotOffset.left, plotOffset.top); - if (prev) { - if (!this.hit.executeOnType(prev.series, 'clearHit', this.prevHit)) { - // TODO fix this (points) should move to general testable graph mixin - var - s = prev.series, - lw = (s.points ? s.points.lineWidth : 1); - offset = (s.points.radius || s.mouse.radius) + lw; - octx.clearRect( - prev.xaxis.d2p(prev.x) - offset, - prev.yaxis.d2p(prev.y) - offset, - offset*2, - offset*2 - ); - } - D.hide(this.mouseTrack); - this.prevHit = null; - } - octx.restore(); - }, - /** - * Retrieves the nearest data point from the mouse cursor. If it's within - * a certain range, draw a point on the overlay canvas and display the x and y - * value of the data. - * @param {Object} mouse - Object that holds the relative x and y coordinates of the cursor. - */ - hit: function(mouse){ - - var - options = this.options, - prevHit = this.prevHit, - closest, sensibility, dataIndex, seriesIndex, series, value, xaxis, yaxis; - - if (this.series.length === 0) return; - - // Nearest data element. - // dist, x, y, relX, relY, absX, absY, sAngle, eAngle, fraction, mouse, - // xaxis, yaxis, series, index, seriesIndex - n = { - relX : mouse.relX, - relY : mouse.relY, - absX : mouse.absX, - absY : mouse.absY - }; - - if (options.mouse.trackY && - !options.mouse.trackAll && - this.hit.executeOnType(this.series, 'hit', [mouse, n])) - { - - if (!_.isUndefined(n.seriesIndex)) { - series = this.series[n.seriesIndex]; - n.series = series; - n.mouse = series.mouse; - n.xaxis = series.xaxis; - n.yaxis = series.yaxis; - } - } else { - - closest = this.hit.closest(mouse); - - if (closest) { - - closest = options.mouse.trackY ? closest.point : closest.x; - seriesIndex = closest.seriesIndex; - series = this.series[seriesIndex]; - xaxis = series.xaxis; - yaxis = series.yaxis; - sensibility = 2 * series.mouse.sensibility; - - if - (options.mouse.trackAll || - (closest.distanceX < sensibility / xaxis.scale && - (!options.mouse.trackY || closest.distanceY < sensibility / yaxis.scale))) - { - n.series = series; - n.xaxis = series.xaxis; - n.yaxis = series.yaxis; - n.mouse = series.mouse; - n.x = closest.x; - n.y = closest.y; - n.dist = closest.distance; - n.index = closest.dataIndex; - n.seriesIndex = seriesIndex; - } - } - } - - if (!prevHit || (prevHit.index !== n.index || prevHit.seriesIndex !== n.seriesIndex)) { - this.hit.clearHit(); - if (n.series && n.mouse && n.mouse.track) { - this.hit.drawMouseTrack(n); - this.hit.drawHit(n); - Flotr.EventAdapter.fire(this.el, 'flotr:hit', [n, this]); - } - } - }, - - closest : function (mouse) { - - var - series = this.series, - options = this.options, - mouseX = mouse.x, - mouseY = mouse.y, - compare = Number.MAX_VALUE, - compareX = Number.MAX_VALUE, - closest = {}, - closestX = {}, - check = false, - serie, data, - distance, distanceX, distanceY, - x, y, i, j; - - function setClosest (o) { - o.distance = distance; - o.distanceX = distanceX; - o.distanceY = distanceY; - o.seriesIndex = i; - o.dataIndex = j; - o.x = x; - o.y = y; - } - - for (i = 0; i < series.length; i++) { - - serie = series[i]; - data = serie.data; - - if (data.length) check = true; - - for (j = data.length; j--;) { - - x = data[j][0]; - y = data[j][1]; - - if (x === null || y === null) continue; - - // don't check if the point isn't visible in the current range - if (x < serie.xaxis.min || x > serie.xaxis.max) continue; - - distanceX = Math.abs(x - mouseX); - distanceY = Math.abs(y - mouseY); - - // Skip square root for speed - distance = distanceX * distanceX + distanceY * distanceY; - - if (distance < compare) { - compare = distance; - setClosest(closest); - } - - if (distanceX < compareX) { - compareX = distanceX; - setClosest(closestX); - } - } - } - - return check ? { - point : closest, - x : closestX - } : false; - }, - - drawMouseTrack : function (n) { - - var - pos = '', - s = n.series, - p = n.mouse.position, - m = n.mouse.margin, - elStyle = S_MOUSETRACK, - mouseTrack = this.mouseTrack, - plotOffset = this.plotOffset, - left = plotOffset.left, - right = plotOffset.right, - bottom = plotOffset.bottom, - top = plotOffset.top, - decimals = n.mouse.trackDecimals, - options = this.options; - - // Create - if (!mouseTrack) { - mouseTrack = D.node('<div class="flotr-mouse-value"></div>'); - this.mouseTrack = mouseTrack; - D.insert(this.el, mouseTrack); - } - - if (!n.mouse.relative) { // absolute to the canvas - - if (p.charAt(0) == 'n') pos += 'top:' + (m + top) + 'px;bottom:auto;'; - else if (p.charAt(0) == 's') pos += 'bottom:' + (m + bottom) + 'px;top:auto;'; - if (p.charAt(1) == 'e') pos += 'right:' + (m + right) + 'px;left:auto;'; - else if (p.charAt(1) == 'w') pos += 'left:' + (m + left) + 'px;right:auto;'; - - // Bars - } else if (s.bars.show) { - pos += 'bottom:' + (m - top - n.yaxis.d2p(n.y/2) + this.canvasHeight) + 'px;top:auto;'; - pos += 'left:' + (m + left + n.xaxis.d2p(n.x - options.bars.barWidth/2)) + 'px;right:auto;'; - - // Pie - } else if (s.pie.show) { - var center = { - x: (this.plotWidth)/2, - y: (this.plotHeight)/2 - }, - radius = (Math.min(this.canvasWidth, this.canvasHeight) * s.pie.sizeRatio) / 2, - bisection = n.sAngle<n.eAngle ? (n.sAngle + n.eAngle) / 2: (n.sAngle + n.eAngle + 2* Math.PI) / 2; - - pos += 'bottom:' + (m - top - center.y - Math.sin(bisection) * radius/2 + this.canvasHeight) + 'px;top:auto;'; - pos += 'left:' + (m + left + center.x + Math.cos(bisection) * radius/2) + 'px;right:auto;'; - - // Default - } else { - if (p.charAt(0) == 'n') pos += 'bottom:' + (m - top - n.yaxis.d2p(n.y) + this.canvasHeight) + 'px;top:auto;'; - else if (p.charAt(0) == 's') pos += 'top:' + (m + top + n.yaxis.d2p(n.y)) + 'px;bottom:auto;'; - if (p.charAt(1) == 'e') pos += 'left:' + (m + left + n.xaxis.d2p(n.x)) + 'px;right:auto;'; - else if (p.charAt(1) == 'w') pos += 'right:' + (m - left - n.xaxis.d2p(n.x) + this.canvasWidth) + 'px;left:auto;'; - } - - elStyle += pos; - mouseTrack.style.cssText = elStyle; - - if (!decimals || decimals < 0) decimals = 0; - - mouseTrack.innerHTML = n.mouse.trackFormatter({ - x: n.x.toFixed(decimals), - y: n.y.toFixed(decimals), - series: n.series, - index: n.index, - nearest: n, - fraction: n.fraction - }); - - D.show(mouseTrack); - } - -}); -})(); - -/** - * Selection Handles Plugin - * - * - * Options - * show - True enables the handles plugin. - * drag - Left and Right drag handles - * scroll - Scrolling handle - */ -(function () { - -function isLeftClick (e, type) { - return (e.which ? (e.which === 1) : (e.button === 0 || e.button === 1)); -} - -function boundX(x, graph) { - return Math.min(Math.max(0, x), graph.plotWidth - 1); -} - -function boundY(y, graph) { - return Math.min(Math.max(0, y), graph.plotHeight); -} - -var - D = Flotr.DOM, - E = Flotr.EventAdapter, - _ = Flotr._; - - -Flotr.addPlugin('selection', { - - options: { - pinchOnly: null, // Only select on pinch - mode: null, // => one of null, 'x', 'y' or 'xy' - color: '#B6D9FF', // => selection box color - fps: 20 // => frames-per-second - }, - - callbacks: { - 'flotr:mouseup' : function (event) { - - var - options = this.options.selection, - selection = this.selection, - pointer = this.getEventPosition(event); - - if (!options || !options.mode) return; - if (selection.interval) clearInterval(selection.interval); - - if (this.multitouches) { - selection.updateSelection(); - } else - if (!options.pinchOnly) { - selection.setSelectionPos(selection.selection.second, pointer); - } - selection.clearSelection(); - - if(selection.selecting && selection.selectionIsSane()){ - selection.drawSelection(); - selection.fireSelectEvent(); - this.ignoreClick = true; - } - }, - 'flotr:mousedown' : function (event) { - - var - options = this.options.selection, - selection = this.selection, - pointer = this.getEventPosition(event); - - if (!options || !options.mode) return; - if (!options.mode || (!isLeftClick(event) && _.isUndefined(event.touches))) return; - if (!options.pinchOnly) selection.setSelectionPos(selection.selection.first, pointer); - if (selection.interval) clearInterval(selection.interval); - - this.lastMousePos.pageX = null; - selection.selecting = false; - selection.interval = setInterval( - _.bind(selection.updateSelection, this), - 1000 / options.fps - ); - }, - 'flotr:destroy' : function (event) { - clearInterval(this.selection.interval); - } - }, - - // TODO This isn't used. Maybe it belongs in the draw area and fire select event methods? - getArea: function() { - - var s = this.selection.selection, - first = s.first, - second = s.second; - - return { - x1: Math.min(first.x, second.x), - x2: Math.max(first.x, second.x), - y1: Math.min(first.y, second.y), - y2: Math.max(first.y, second.y) - }; - }, - - selection: {first: {x: -1, y: -1}, second: {x: -1, y: -1}}, - prevSelection: null, - interval: null, - - /** - * Fires the 'flotr:select' event when the user made a selection. - */ - fireSelectEvent: function(name){ - var a = this.axes, - s = this.selection.selection, - x1, x2, y1, y2; - - name = name || 'select'; - - x1 = a.x.p2d(s.first.x); - x2 = a.x.p2d(s.second.x); - y1 = a.y.p2d(s.first.y); - y2 = a.y.p2d(s.second.y); - - E.fire(this.el, 'flotr:'+name, [{ - x1:Math.min(x1, x2), - y1:Math.min(y1, y2), - x2:Math.max(x1, x2), - y2:Math.max(y1, y2), - xfirst:x1, xsecond:x2, yfirst:y1, ysecond:y2 - }, this]); - }, - - /** - * Allows the user the manually select an area. - * @param {Object} area - Object with coordinates to select. - */ - setSelection: function(area, preventEvent){ - var options = this.options, - xa = this.axes.x, - ya = this.axes.y, - vertScale = ya.scale, - hozScale = xa.scale, - selX = options.selection.mode.indexOf('x') != -1, - selY = options.selection.mode.indexOf('y') != -1, - s = this.selection.selection; - - this.selection.clearSelection(); - - s.first.y = boundY((selX && !selY) ? 0 : (ya.max - area.y1) * vertScale, this); - s.second.y = boundY((selX && !selY) ? this.plotHeight - 1: (ya.max - area.y2) * vertScale, this); - s.first.x = boundX((selY && !selX) ? 0 : area.x1, this); - s.second.x = boundX((selY && !selX) ? this.plotWidth : area.x2, this); - - this.selection.drawSelection(); - if (!preventEvent) - this.selection.fireSelectEvent(); - }, - - /** - * Calculates the position of the selection. - * @param {Object} pos - Position object. - * @param {Event} event - Event object. - */ - setSelectionPos: function(pos, pointer) { - var mode = this.options.selection.mode, - selection = this.selection.selection; - - if(mode.indexOf('x') == -1) { - pos.x = (pos == selection.first) ? 0 : this.plotWidth; - }else{ - pos.x = boundX(pointer.relX, this); - } - - if (mode.indexOf('y') == -1) { - pos.y = (pos == selection.first) ? 0 : this.plotHeight - 1; - }else{ - pos.y = boundY(pointer.relY, this); - } - }, - /** - * Draws the selection box. - */ - drawSelection: function() { - - this.selection.fireSelectEvent('selecting'); - - var s = this.selection.selection, - octx = this.octx, - options = this.options, - plotOffset = this.plotOffset, - prevSelection = this.selection.prevSelection; - - if (prevSelection && - s.first.x == prevSelection.first.x && - s.first.y == prevSelection.first.y && - s.second.x == prevSelection.second.x && - s.second.y == prevSelection.second.y) { - return; - } - - octx.save(); - octx.strokeStyle = this.processColor(options.selection.color, {opacity: 0.8}); - octx.lineWidth = 1; - octx.lineJoin = 'miter'; - octx.fillStyle = this.processColor(options.selection.color, {opacity: 0.4}); - - this.selection.prevSelection = { - first: { x: s.first.x, y: s.first.y }, - second: { x: s.second.x, y: s.second.y } - }; - - var x = Math.min(s.first.x, s.second.x), - y = Math.min(s.first.y, s.second.y), - w = Math.abs(s.second.x - s.first.x), - h = Math.abs(s.second.y - s.first.y); - - octx.fillRect(x + plotOffset.left+0.5, y + plotOffset.top+0.5, w, h); - octx.strokeRect(x + plotOffset.left+0.5, y + plotOffset.top+0.5, w, h); - octx.restore(); - }, - - /** - * Updates (draws) the selection box. - */ - updateSelection: function(){ - if (!this.lastMousePos.pageX) return; - - this.selection.selecting = true; - - if (this.multitouches) { - this.selection.setSelectionPos(this.selection.selection.first, this.getEventPosition(this.multitouches[0])); - this.selection.setSelectionPos(this.selection.selection.second, this.getEventPosition(this.multitouches[1])); - } else - if (this.options.selection.pinchOnly) { - return; - } else { - this.selection.setSelectionPos(this.selection.selection.second, this.lastMousePos); - } - - this.selection.clearSelection(); - - if(this.selection.selectionIsSane()) { - this.selection.drawSelection(); - } - }, - - /** - * Removes the selection box from the overlay canvas. - */ - clearSelection: function() { - if (!this.selection.prevSelection) return; - - var prevSelection = this.selection.prevSelection, - lw = 1, - plotOffset = this.plotOffset, - x = Math.min(prevSelection.first.x, prevSelection.second.x), - y = Math.min(prevSelection.first.y, prevSelection.second.y), - w = Math.abs(prevSelection.second.x - prevSelection.first.x), - h = Math.abs(prevSelection.second.y - prevSelection.first.y); - - this.octx.clearRect(x + plotOffset.left - lw + 0.5, - y + plotOffset.top - lw, - w + 2 * lw + 0.5, - h + 2 * lw + 0.5); - - this.selection.prevSelection = null; - }, - /** - * Determines whether or not the selection is sane and should be drawn. - * @return {Boolean} - True when sane, false otherwise. - */ - selectionIsSane: function(){ - var s = this.selection.selection; - return Math.abs(s.second.x - s.first.x) >= 5 || - Math.abs(s.second.y - s.first.y) >= 5; - } - -}); - -})(); - -(function () { - -var D = Flotr.DOM; - -Flotr.addPlugin('labels', { - - callbacks : { - 'flotr:afterdraw' : function () { - this.labels.draw(); - } - }, - - draw: function(){ - // Construct fixed width label boxes, which can be styled easily. - var - axis, tick, left, top, xBoxWidth, - radius, sides, coeff, angle, - div, i, html = '', - noLabels = 0, - options = this.options, - ctx = this.ctx, - a = this.axes, - style = { size: options.fontSize }; - - for (i = 0; i < a.x.ticks.length; ++i){ - if (a.x.ticks[i].label) { ++noLabels; } - } - xBoxWidth = this.plotWidth / noLabels; - - if (options.grid.circular) { - ctx.save(); - ctx.translate(this.plotOffset.left + this.plotWidth / 2, - this.plotOffset.top + this.plotHeight / 2); - - radius = this.plotHeight * options.radar.radiusRatio / 2 + options.fontSize; - sides = this.axes.x.ticks.length; - coeff = 2 * (Math.PI / sides); - angle = -Math.PI / 2; - - drawLabelCircular(this, a.x, false); - drawLabelCircular(this, a.x, true); - drawLabelCircular(this, a.y, false); - drawLabelCircular(this, a.y, true); - ctx.restore(); - } - - if (!options.HtmlText && this.textEnabled) { - drawLabelNoHtmlText(this, a.x, 'center', 'top'); - drawLabelNoHtmlText(this, a.x2, 'center', 'bottom'); - drawLabelNoHtmlText(this, a.y, 'right', 'middle'); - drawLabelNoHtmlText(this, a.y2, 'left', 'middle'); - - } else if (( - a.x.options.showLabels || - a.x2.options.showLabels || - a.y.options.showLabels || - a.y2.options.showLabels) && - !options.grid.circular - ) { - - html = ''; - - drawLabelHtml(this, a.x); - drawLabelHtml(this, a.x2); - drawLabelHtml(this, a.y); - drawLabelHtml(this, a.y2); - - ctx.stroke(); - ctx.restore(); - div = D.create('div'); - D.setStyles(div, { - fontSize: 'smaller', - color: options.grid.color - }); - div.className = 'flotr-labels'; - D.insert(this.el, div); - D.insert(div, html); - } - - function drawLabelCircular (graph, axis, minorTicks) { - var - ticks = minorTicks ? axis.minorTicks : axis.ticks, - isX = axis.orientation === 1, - isFirst = axis.n === 1, - style, offset; - - style = { - color : axis.options.color || options.grid.color, - angle : Flotr.toRad(axis.options.labelsAngle), - textBaseline : 'middle' - }; - - for (i = 0; i < ticks.length && - (minorTicks ? axis.options.showMinorLabels : axis.options.showLabels); ++i){ - tick = ticks[i]; - tick.label += ''; - if (!tick.label || !tick.label.length) { continue; } - - x = Math.cos(i * coeff + angle) * radius; - y = Math.sin(i * coeff + angle) * radius; - - style.textAlign = isX ? (Math.abs(x) < 0.1 ? 'center' : (x < 0 ? 'right' : 'left')) : 'left'; - - Flotr.drawText( - ctx, tick.label, - isX ? x : 3, - isX ? y : -(axis.ticks[i].v / axis.max) * (radius - options.fontSize), - style - ); - } - } - - function drawLabelNoHtmlText (graph, axis, textAlign, textBaseline) { - var - isX = axis.orientation === 1, - isFirst = axis.n === 1, - style, offset; - - style = { - color : axis.options.color || options.grid.color, - textAlign : textAlign, - textBaseline : textBaseline, - angle : Flotr.toRad(axis.options.labelsAngle) - }; - style = Flotr.getBestTextAlign(style.angle, style); - - for (i = 0; i < axis.ticks.length && continueShowingLabels(axis); ++i) { - - tick = axis.ticks[i]; - if (!tick.label || !tick.label.length) { continue; } - - offset = axis.d2p(tick.v); - if (offset < 0 || - offset > (isX ? graph.plotWidth : graph.plotHeight)) { continue; } - - Flotr.drawText( - ctx, tick.label, - leftOffset(graph, isX, isFirst, offset), - topOffset(graph, isX, isFirst, offset), - style - ); - - // Only draw on axis y2 - if (!isX && !isFirst) { - ctx.save(); - ctx.strokeStyle = style.color; - ctx.beginPath(); - ctx.moveTo(graph.plotOffset.left + graph.plotWidth - 8, graph.plotOffset.top + axis.d2p(tick.v)); - ctx.lineTo(graph.plotOffset.left + graph.plotWidth, graph.plotOffset.top + axis.d2p(tick.v)); - ctx.stroke(); - ctx.restore(); - } - } - - function continueShowingLabels (axis) { - return axis.options.showLabels && axis.used; - } - function leftOffset (graph, isX, isFirst, offset) { - return graph.plotOffset.left + - (isX ? offset : - (isFirst ? - -options.grid.labelMargin : - options.grid.labelMargin + graph.plotWidth)); - } - function topOffset (graph, isX, isFirst, offset) { - return graph.plotOffset.top + - (isX ? options.grid.labelMargin : offset) + - ((isX && isFirst) ? graph.plotHeight : 0); - } - } - - function drawLabelHtml (graph, axis) { - var - isX = axis.orientation === 1, - isFirst = axis.n === 1, - name = '', - left, style, top, - offset = graph.plotOffset; - - if (!isX && !isFirst) { - ctx.save(); - ctx.strokeStyle = axis.options.color || options.grid.color; - ctx.beginPath(); - } - - if (axis.options.showLabels && (isFirst ? true : axis.used)) { - for (i = 0; i < axis.ticks.length; ++i) { - tick = axis.ticks[i]; - if (!tick.label || !tick.label.length || - ((isX ? offset.left : offset.top) + axis.d2p(tick.v) < 0) || - ((isX ? offset.left : offset.top) + axis.d2p(tick.v) > (isX ? graph.canvasWidth : graph.canvasHeight))) { - continue; - } - top = offset.top + - (isX ? - ((isFirst ? 1 : -1 ) * (graph.plotHeight + options.grid.labelMargin)) : - axis.d2p(tick.v) - axis.maxLabel.height / 2); - left = isX ? (offset.left + axis.d2p(tick.v) - xBoxWidth / 2) : 0; - - name = ''; - if (i === 0) { - name = ' first'; - } else if (i === axis.ticks.length - 1) { - name = ' last'; - } - name += isX ? ' flotr-grid-label-x' : ' flotr-grid-label-y'; - - html += [ - '<div style="position:absolute; text-align:' + (isX ? 'center' : 'right') + '; ', - 'top:' + top + 'px; ', - ((!isX && !isFirst) ? 'right:' : 'left:') + left + 'px; ', - 'width:' + (isX ? xBoxWidth : ((isFirst ? offset.left : offset.right) - options.grid.labelMargin)) + 'px; ', - axis.options.color ? ('color:' + axis.options.color + '; ') : ' ', - '" class="flotr-grid-label' + name + '">' + tick.label + '</div>' - ].join(' '); - - if (!isX && !isFirst) { - ctx.moveTo(offset.left + graph.plotWidth - 8, offset.top + axis.d2p(tick.v)); - ctx.lineTo(offset.left + graph.plotWidth, offset.top + axis.d2p(tick.v)); - } - } - } - } - } - -}); -})(); - -(function () { - -var - D = Flotr.DOM, - _ = Flotr._; - -Flotr.addPlugin('legend', { - options: { - show: true, // => setting to true will show the legend, hide otherwise - noColumns: 1, // => number of colums in legend table // @todo: doesn't work for HtmlText = false - labelFormatter: function(v){return v;}, // => fn: string -> string - labelBoxBorderColor: '#CCCCCC', // => border color for the little label boxes - labelBoxWidth: 14, - labelBoxHeight: 10, - labelBoxMargin: 5, - labelBoxOpacity: 0.4, - container: null, // => container (as jQuery object) to put legend in, null means default on top of graph - position: 'nw', // => position of default legend container within plot - margin: 5, // => distance from grid edge to default legend container within plot - backgroundColor: null, // => null means auto-detect - backgroundOpacity: 0.85// => set to 0 to avoid background, set to 1 for a solid background - }, - callbacks: { - 'flotr:afterinit': function() { - this.legend.insertLegend(); - } - }, - /** - * Adds a legend div to the canvas container or draws it on the canvas. - */ - insertLegend: function(){ - - if(!this.options.legend.show) - return; - - var series = this.series, - plotOffset = this.plotOffset, - options = this.options, - legend = options.legend, - fragments = [], - rowStarted = false, - ctx = this.ctx, - itemCount = _.filter(series, function(s) {return (s.label && !s.hide);}).length, - p = legend.position, - m = legend.margin, - i, label, color; - - if (itemCount) { - if (!options.HtmlText && this.textEnabled && !legend.container) { - var style = { - size: options.fontSize*1.1, - color: options.grid.color - }; - - var lbw = legend.labelBoxWidth, - lbh = legend.labelBoxHeight, - lbm = legend.labelBoxMargin, - offsetX = plotOffset.left + m, - offsetY = plotOffset.top + m; - - // We calculate the labels' max width - var labelMaxWidth = 0; - for(i = series.length - 1; i > -1; --i){ - if(!series[i].label || series[i].hide) continue; - label = legend.labelFormatter(series[i].label); - labelMaxWidth = Math.max(labelMaxWidth, this._text.measureText(label, style).width); - } - - var legendWidth = Math.round(lbw + lbm*3 + labelMaxWidth), - legendHeight = Math.round(itemCount*(lbm+lbh) + lbm); - - if(p.charAt(0) == 's') offsetY = plotOffset.top + this.plotHeight - (m + legendHeight); - if(p.charAt(1) == 'e') offsetX = plotOffset.left + this.plotWidth - (m + legendWidth); - - // Legend box - color = this.processColor(legend.backgroundColor || 'rgb(240,240,240)', {opacity: legend.backgroundOpacity || 0.1}); - - ctx.fillStyle = color; - ctx.fillRect(offsetX, offsetY, legendWidth, legendHeight); - ctx.strokeStyle = legend.labelBoxBorderColor; - ctx.strokeRect(Flotr.toPixel(offsetX), Flotr.toPixel(offsetY), legendWidth, legendHeight); - - // Legend labels - var x = offsetX + lbm; - var y = offsetY + lbm; - for(i = 0; i < series.length; i++){ - if(!series[i].label || series[i].hide) continue; - label = legend.labelFormatter(series[i].label); - - ctx.fillStyle = series[i].color; - ctx.fillRect(x, y, lbw-1, lbh-1); - - ctx.strokeStyle = legend.labelBoxBorderColor; - ctx.lineWidth = 1; - ctx.strokeRect(Math.ceil(x)-1.5, Math.ceil(y)-1.5, lbw+2, lbh+2); - - // Legend text - Flotr.drawText(ctx, label, x + lbw + lbm, y + lbh, style); - - y += lbh + lbm; - } - } - else { - for(i = 0; i < series.length; ++i){ - if(!series[i].label || series[i].hide) continue; - - if(i % legend.noColumns === 0){ - fragments.push(rowStarted ? '</tr><tr>' : '<tr>'); - rowStarted = true; - } - - // @TODO remove requirement on bars - var s = series[i], - boxWidth = legend.labelBoxWidth, - boxHeight = legend.labelBoxHeight, - opacityValue = (s.bars ? s.bars.fillOpacity : legend.labelBoxOpacity), - opacity = 'opacity:' + opacityValue + ';filter:alpha(opacity=' + opacityValue*100 + ');'; - - label = legend.labelFormatter(s.label); - color = 'background-color:' + ((s.bars && s.bars.show && s.bars.fillColor && s.bars.fill) ? s.bars.fillColor : s.color) + ';'; - - fragments.push( - '<td class="flotr-legend-color-box">', - '<div style="border:1px solid ', legend.labelBoxBorderColor, ';padding:1px">', - '<div style="width:', (boxWidth-1), 'px;height:', (boxHeight-1), 'px;border:1px solid ', series[i].color, '">', // Border - '<div style="width:', boxWidth, 'px;height:', boxHeight, 'px;', 'opacity:.4;', color, '"></div>', // Background - '</div>', - '</div>', - '</td>', - '<td class="flotr-legend-label">', label, '</td>' - ); - } - if(rowStarted) fragments.push('</tr>'); - - if(fragments.length > 0){ - var table = '<table style="font-size:smaller;color:' + options.grid.color + '">' + fragments.join('') + '</table>'; - if(legend.container){ - D.insert(legend.container, table); - } - else { - var styles = {position: 'absolute', 'z-index': 2}; - - if(p.charAt(0) == 'n') { styles.top = (m + plotOffset.top) + 'px'; styles.bottom = 'auto'; } - else if(p.charAt(0) == 's') { styles.bottom = (m + plotOffset.bottom) + 'px'; styles.top = 'auto'; } - if(p.charAt(1) == 'e') { styles.right = (m + plotOffset.right) + 'px'; styles.left = 'auto'; } - else if(p.charAt(1) == 'w') { styles.left = (m + plotOffset.left) + 'px'; styles.right = 'auto'; } - - var div = D.create('div'), size; - div.className = 'flotr-legend'; - D.setStyles(div, styles); - D.insert(div, table); - D.insert(this.el, div); - - if(!legend.backgroundOpacity) - return; - - var c = legend.backgroundColor || options.grid.backgroundColor || '#ffffff'; - - _.extend(styles, D.size(div), { - 'backgroundColor': c, - 'z-index': 1 - }); - styles.width += 'px'; - styles.height += 'px'; - - // Put in the transparent background separately to avoid blended labels and - div = D.create('div'); - div.className = 'flotr-legend-bg'; - D.setStyles(div, styles); - D.opacity(div, legend.backgroundOpacity); - D.insert(div, ' '); - D.insert(this.el, div); - } - } - } - } - } -}); -})(); - -/** Spreadsheet **/ -(function() { - -function getRowLabel(value){ - if (this.options.spreadsheet.tickFormatter){ - //TODO maybe pass the xaxis formatter to the custom tick formatter as an opt-out? - return this.options.spreadsheet.tickFormatter(value); - } - else { - var t = _.find(this.axes.x.ticks, function(t){return t.v == value;}); - if (t) { - return t.label; - } - return value; - } -} - -var - D = Flotr.DOM, - _ = Flotr._; - -Flotr.addPlugin('spreadsheet', { - options: { - show: false, // => show the data grid using two tabs - tabGraphLabel: 'Graph', - tabDataLabel: 'Data', - toolbarDownload: 'Download CSV', // @todo: add better language support - toolbarSelectAll: 'Select all', - csvFileSeparator: ',', - decimalSeparator: '.', - tickFormatter: null, - initialTab: 'graph' - }, - /** - * Builds the tabs in the DOM - */ - callbacks: { - 'flotr:afterconstruct': function(){ - // @TODO necessary? - //this.el.select('.flotr-tabs-group,.flotr-datagrid-container').invoke('remove'); - - if (!this.options.spreadsheet.show) return; - - var ss = this.spreadsheet, - container = D.node('<div class="flotr-tabs-group" style="position:absolute;left:0px;width:'+this.canvasWidth+'px"></div>'), - graph = D.node('<div style="float:left" class="flotr-tab selected">'+this.options.spreadsheet.tabGraphLabel+'</div>'), - data = D.node('<div style="float:left" class="flotr-tab">'+this.options.spreadsheet.tabDataLabel+'</div>'), - offset; - - ss.tabsContainer = container; - ss.tabs = { graph : graph, data : data }; - - D.insert(container, graph); - D.insert(container, data); - D.insert(this.el, container); - - offset = D.size(data).height + 2; - this.plotOffset.bottom += offset; - - D.setStyles(container, {top: this.canvasHeight-offset+'px'}); - - this. - observe(graph, 'click', function(){ss.showTab('graph');}). - observe(data, 'click', function(){ss.showTab('data');}); - if (this.options.spreadsheet.initialTab !== 'graph'){ - ss.showTab(this.options.spreadsheet.initialTab); - } - } - }, - /** - * Builds a matrix of the data to make the correspondance between the x values and the y values : - * X value => Y values from the axes - * @return {Array} The data grid - */ - loadDataGrid: function(){ - if (this.seriesData) return this.seriesData; - - var s = this.series, - rows = {}; - - /* The data grid is a 2 dimensions array. There is a row for each X value. - * Each row contains the x value and the corresponding y value for each serie ('undefined' if there isn't one) - **/ - _.each(s, function(serie, i){ - _.each(serie.data, function (v) { - var x = v[0], - y = v[1], - r = rows[x]; - if (r) { - r[i+1] = y; - } else { - var newRow = []; - newRow[0] = x; - newRow[i+1] = y; - rows[x] = newRow; - } - }); - }); - - // The data grid is sorted by x value - this.seriesData = _.sortBy(rows, function(row, x){ - return parseInt(x, 10); - }); - return this.seriesData; - }, - /** - * Constructs the data table for the spreadsheet - * @todo make a spreadsheet manager (Flotr.Spreadsheet) - * @return {Element} The resulting table element - */ - constructDataGrid: function(){ - // If the data grid has already been built, nothing to do here - if (this.spreadsheet.datagrid) return this.spreadsheet.datagrid; - - var s = this.series, - datagrid = this.spreadsheet.loadDataGrid(), - colgroup = ['<colgroup><col />'], - buttonDownload, buttonSelect, t; - - // First row : series' labels - var html = ['<table class="flotr-datagrid"><tr class="first-row">']; - html.push('<th> </th>'); - _.each(s, function(serie,i){ - html.push('<th scope="col">'+(serie.label || String.fromCharCode(65+i))+'</th>'); - colgroup.push('<col />'); - }); - html.push('</tr>'); - // Data rows - _.each(datagrid, function(row){ - html.push('<tr>'); - _.times(s.length+1, function(i){ - var tag = 'td', - value = row[i], - // TODO: do we really want to handle problems with floating point - // precision here? - content = (!_.isUndefined(value) ? Math.round(value*100000)/100000 : ''); - if (i === 0) { - tag = 'th'; - var label = getRowLabel.call(this, content); - if (label) content = label; - } - - html.push('<'+tag+(tag=='th'?' scope="row"':'')+'>'+content+'</'+tag+'>'); - }, this); - html.push('</tr>'); - }, this); - colgroup.push('</colgroup>'); - t = D.node(html.join('')); - - /** - * @TODO disabled this - if (!Flotr.isIE || Flotr.isIE == 9) { - function handleMouseout(){ - t.select('colgroup col.hover, th.hover').invoke('removeClassName', 'hover'); - } - function handleMouseover(e){ - var td = e.element(), - siblings = td.previousSiblings(); - t.select('th[scope=col]')[siblings.length-1].addClassName('hover'); - t.select('colgroup col')[siblings.length].addClassName('hover'); - } - _.each(t.select('td'), function(td) { - Flotr.EventAdapter. - observe(td, 'mouseover', handleMouseover). - observe(td, 'mouseout', handleMouseout); - }); - } - */ - - buttonDownload = D.node( - '<button type="button" class="flotr-datagrid-toolbar-button">' + - this.options.spreadsheet.toolbarDownload + - '</button>'); - - buttonSelect = D.node( - '<button type="button" class="flotr-datagrid-toolbar-button">' + - this.options.spreadsheet.toolbarSelectAll+ - '</button>'); - - this. - observe(buttonDownload, 'click', _.bind(this.spreadsheet.downloadCSV, this)). - observe(buttonSelect, 'click', _.bind(this.spreadsheet.selectAllData, this)); - - var toolbar = D.node('<div class="flotr-datagrid-toolbar"></div>'); - D.insert(toolbar, buttonDownload); - D.insert(toolbar, buttonSelect); - - var containerHeight =this.canvasHeight - D.size(this.spreadsheet.tabsContainer).height-2, - container = D.node('<div class="flotr-datagrid-container" style="position:absolute;left:0px;top:0px;width:'+ - this.canvasWidth+'px;height:'+containerHeight+'px;overflow:auto;z-index:10"></div>'); - - D.insert(container, toolbar); - D.insert(container, t); - D.insert(this.el, container); - this.spreadsheet.datagrid = t; - this.spreadsheet.container = container; - - return t; - }, - /** - * Shows the specified tab, by its name - * @todo make a tab manager (Flotr.Tabs) - * @param {String} tabName - The tab name - */ - showTab: function(tabName){ - if (this.spreadsheet.activeTab === tabName){ - return; - } - switch(tabName) { - case 'graph': - D.hide(this.spreadsheet.container); - D.removeClass(this.spreadsheet.tabs.data, 'selected'); - D.addClass(this.spreadsheet.tabs.graph, 'selected'); - break; - case 'data': - if (!this.spreadsheet.datagrid) - this.spreadsheet.constructDataGrid(); - D.show(this.spreadsheet.container); - D.addClass(this.spreadsheet.tabs.data, 'selected'); - D.removeClass(this.spreadsheet.tabs.graph, 'selected'); - break; - default: - throw 'Illegal tab name: ' + tabName; - } - this.spreadsheet.activeTab = tabName; - }, - /** - * Selects the data table in the DOM for copy/paste - */ - selectAllData: function(){ - if (this.spreadsheet.tabs) { - var selection, range, doc, win, node = this.spreadsheet.constructDataGrid(); - - this.spreadsheet.showTab('data'); - - // deferred to be able to select the table - setTimeout(function () { - if ((doc = node.ownerDocument) && (win = doc.defaultView) && - win.getSelection && doc.createRange && - (selection = window.getSelection()) && - selection.removeAllRanges) { - range = doc.createRange(); - range.selectNode(node); - selection.removeAllRanges(); - selection.addRange(range); - } - else if (document.body && document.body.createTextRange && - (range = document.body.createTextRange())) { - range.moveToElementText(node); - range.select(); - } - }, 0); - return true; - } - else return false; - }, - /** - * Converts the data into CSV in order to download a file - */ - downloadCSV: function(){ - var csv = '', - series = this.series, - options = this.options, - dg = this.spreadsheet.loadDataGrid(), - separator = encodeURIComponent(options.spreadsheet.csvFileSeparator); - - if (options.spreadsheet.decimalSeparator === options.spreadsheet.csvFileSeparator) { - throw "The decimal separator is the same as the column separator ("+options.spreadsheet.decimalSeparator+")"; - } - - // The first row - _.each(series, function(serie, i){ - csv += separator+'"'+(serie.label || String.fromCharCode(65+i)).replace(/\"/g, '\\"')+'"'; - }); - - csv += "%0D%0A"; // \r\n - - // For each row - csv += _.reduce(dg, function(memo, row){ - var rowLabel = getRowLabel.call(this, row[0]) || ''; - rowLabel = '"'+(rowLabel+'').replace(/\"/g, '\\"')+'"'; - var numbers = row.slice(1).join(separator); - if (options.spreadsheet.decimalSeparator !== '.') { - numbers = numbers.replace(/\./g, options.spreadsheet.decimalSeparator); - } - return memo + rowLabel+separator+numbers+"%0D%0A"; // \t and \r\n - }, '', this); - - if (Flotr.isIE && Flotr.isIE < 9) { - csv = csv.replace(new RegExp(separator, 'g'), decodeURIComponent(separator)).replace(/%0A/g, '\n').replace(/%0D/g, '\r'); - window.open().document.write(csv); - } - else window.open('data:text/csv,'+csv); - } -}); -})(); - -(function () { - -var D = Flotr.DOM; - -Flotr.addPlugin('titles', { - callbacks: { - 'flotr:afterdraw': function() { - this.titles.drawTitles(); - } - }, - /** - * Draws the title and the subtitle - */ - drawTitles : function () { - var html, - options = this.options, - margin = options.grid.labelMargin, - ctx = this.ctx, - a = this.axes; - - if (!options.HtmlText && this.textEnabled) { - var style = { - size: options.fontSize, - color: options.grid.color, - textAlign: 'center' - }; - - // Add subtitle - if (options.subtitle){ - Flotr.drawText( - ctx, options.subtitle, - this.plotOffset.left + this.plotWidth/2, - this.titleHeight + this.subtitleHeight - 2, - style - ); - } - - style.weight = 1.5; - style.size *= 1.5; - - // Add title - if (options.title){ - Flotr.drawText( - ctx, options.title, - this.plotOffset.left + this.plotWidth/2, - this.titleHeight - 2, - style - ); - } - - style.weight = 1.8; - style.size *= 0.8; - - // Add x axis title - if (a.x.options.title && a.x.used){ - style.textAlign = a.x.options.titleAlign || 'center'; - style.textBaseline = 'top'; - style.angle = Flotr.toRad(a.x.options.titleAngle); - style = Flotr.getBestTextAlign(style.angle, style); - Flotr.drawText( - ctx, a.x.options.title, - this.plotOffset.left + this.plotWidth/2, - this.plotOffset.top + a.x.maxLabel.height + this.plotHeight + 2 * margin, - style - ); - } - - // Add x2 axis title - if (a.x2.options.title && a.x2.used){ - style.textAlign = a.x2.options.titleAlign || 'center'; - style.textBaseline = 'bottom'; - style.angle = Flotr.toRad(a.x2.options.titleAngle); - style = Flotr.getBestTextAlign(style.angle, style); - Flotr.drawText( - ctx, a.x2.options.title, - this.plotOffset.left + this.plotWidth/2, - this.plotOffset.top - a.x2.maxLabel.height - 2 * margin, - style - ); - } - - // Add y axis title - if (a.y.options.title && a.y.used){ - style.textAlign = a.y.options.titleAlign || 'right'; - style.textBaseline = 'middle'; - style.angle = Flotr.toRad(a.y.options.titleAngle); - style = Flotr.getBestTextAlign(style.angle, style); - Flotr.drawText( - ctx, a.y.options.title, - this.plotOffset.left - a.y.maxLabel.width - 2 * margin, - this.plotOffset.top + this.plotHeight / 2, - style - ); - } - - // Add y2 axis title - if (a.y2.options.title && a.y2.used){ - style.textAlign = a.y2.options.titleAlign || 'left'; - style.textBaseline = 'middle'; - style.angle = Flotr.toRad(a.y2.options.titleAngle); - style = Flotr.getBestTextAlign(style.angle, style); - Flotr.drawText( - ctx, a.y2.options.title, - this.plotOffset.left + this.plotWidth + a.y2.maxLabel.width + 2 * margin, - this.plotOffset.top + this.plotHeight / 2, - style - ); - } - } - else { - html = []; - - // Add title - if (options.title) - html.push( - '<div style="position:absolute;top:0;left:', - this.plotOffset.left, 'px;font-size:1em;font-weight:bold;text-align:center;width:', - this.plotWidth,'px;" class="flotr-title">', options.title, '</div>' - ); - - // Add subtitle - if (options.subtitle) - html.push( - '<div style="position:absolute;top:', this.titleHeight, 'px;left:', - this.plotOffset.left, 'px;font-size:smaller;text-align:center;width:', - this.plotWidth, 'px;" class="flotr-subtitle">', options.subtitle, '</div>' - ); - - html.push('</div>'); - - html.push('<div class="flotr-axis-title" style="font-weight:bold;">'); - - // Add x axis title - if (a.x.options.title && a.x.used) - html.push( - '<div style="position:absolute;top:', - (this.plotOffset.top + this.plotHeight + options.grid.labelMargin + a.x.titleSize.height), - 'px;left:', this.plotOffset.left, 'px;width:', this.plotWidth, - 'px;text-align:center;" class="flotr-axis-title">', a.x.options.title, '</div>' - ); - - // Add x2 axis title - if (a.x2.options.title && a.x2.used) - html.push( - '<div style="position:absolute;top:0;left:', this.plotOffset.left, 'px;width:', - this.plotWidth, 'px;text-align:center;" class="flotr-axis-title">', a.x2.options.title, '</div>' - ); - - // Add y axis title - if (a.y.options.title && a.y.used) - html.push( - '<div style="position:absolute;top:', - (this.plotOffset.top + this.plotHeight/2 - a.y.titleSize.height/2), - 'px;left:0;text-align:right;" class="flotr-axis-title">', a.y.options.title, '</div>' - ); - - // Add y2 axis title - if (a.y2.options.title && a.y2.used) - html.push( - '<div style="position:absolute;top:', - (this.plotOffset.top + this.plotHeight/2 - a.y.titleSize.height/2), - 'px;right:0;text-align:right;" class="flotr-axis-title">', a.y2.options.title, '</div>' - ); - - html = html.join(''); - - var div = D.create('div'); - D.setStyles({ - color: options.grid.color - }); - div.className = 'flotr-titles'; - D.insert(this.el, div); - D.insert(div, html); - } - } -}); -})(); diff --git a/addons/web_graph/static/lib/flotr2/flotr2.min.js b/addons/web_graph/static/lib/flotr2/flotr2.min.js deleted file mode 100644 index 0dce3034464..00000000000 --- a/addons/web_graph/static/lib/flotr2/flotr2.min.js +++ /dev/null @@ -1,27 +0,0 @@ -/*! - * bean.js - copyright Jacob Thornton 2011 - * https://github.com/fat/bean - * MIT License - * special thanks to: - * dean edwards: http://dean.edwards.name/ - * dperini: https://github.com/dperini/nwevents - * the entire mootools team: github.com/mootools/mootools-core - *//*global module:true, define:true*/ -!function(a,b,c){typeof module!="undefined"?module.exports=c(a,b):typeof define=="function"&&typeof define.amd=="object"?define(c):b[a]=c(a,b)}("bean",this,function(a,b){var c=window,d=b[a],e=/over|out/,f=/[^\.]*(?=\..*)\.|.*/,g=/\..*/,h="addEventListener",i="attachEvent",j="removeEventListener",k="detachEvent",l=document||{},m=l.documentElement||{},n=m[h],o=n?h:i,p=Array.prototype.slice,q=/click|mouse|menu|drag|drop/i,r=/^touch|^gesture/i,s={one:1},t=function(a,b,c){for(c=0;c<b.length;c++)a[b[c]]=1;return a}({},("click dblclick mouseup mousedown contextmenu mousewheel DOMMouseScroll mouseover mouseout mousemove selectstart selectend keydown keypress keyup orientationchange focus blur change reset select submit load unload beforeunload resize move DOMContentLoaded readystatechange error abort scroll "+(n?"show input invalid touchstart touchmove touchend touchcancel gesturestart gesturechange gestureend message readystatechange pageshow pagehide popstate hashchange offline online afterprint beforeprint dragstart dragenter dragover dragleave drag drop dragend loadstart progress suspend emptied stalled loadmetadata loadeddata canplay canplaythrough playing waiting seeking seeked ended durationchange timeupdate play pause ratechange volumechange cuechange checking noupdate downloading cached updateready obsolete ":"")).split(" ")),u=function(){function a(a,b){while((b=b.parentNode)!==null)if(b===a)return!0;return!1}function b(b){var c=b.relatedTarget;return c?c!==this&&c.prefix!=="xul"&&!/document/.test(this.toString())&&!a(this,c):c===null}return{mouseenter:{base:"mouseover",condition:b},mouseleave:{base:"mouseout",condition:b},mousewheel:{base:/Firefox/.test(navigator.userAgent)?"DOMMouseScroll":"mousewheel"}}}(),v=function(){var a="altKey attrChange attrName bubbles cancelable ctrlKey currentTarget detail eventPhase getModifierState isTrusted metaKey relatedNode relatedTarget shiftKey srcElement target timeStamp type view which".split(" "),b=a.concat("button buttons clientX clientY dataTransfer fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" ")),c=a.concat("char charCode key keyCode".split(" ")),d=a.concat("touches targetTouches changedTouches scale rotation".split(" ")),f="preventDefault",g=function(a){return function(){a[f]?a[f]():a.returnValue=!1}},h="stopPropagation",i=function(a){return function(){a[h]?a[h]():a.cancelBubble=!0}},j=function(a){return function(){a[f](),a[h](),a.stopped=!0}},k=function(a,b,c){var d,e;for(d=c.length;d--;)e=c[d],!(e in b)&&e in a&&(b[e]=a[e])};return function(n,o){var p={originalEvent:n,isNative:o};if(!n)return p;var s,t=n.type,u=n.target||n.srcElement;p[f]=g(n),p[h]=i(n),p.stop=j(p),p.target=u&&u.nodeType===3?u.parentNode:u;if(o){if(t.indexOf("key")!==-1)s=c,p.keyCode=n.which||n.keyCode;else if(q.test(t)){s=b,p.rightClick=n.which===3||n.button===2,p.pos={x:0,y:0};if(n.pageX||n.pageY)p.clientX=n.pageX,p.clientY=n.pageY;else if(n.clientX||n.clientY)p.clientX=n.clientX+l.body.scrollLeft+m.scrollLeft,p.clientY=n.clientY+l.body.scrollTop+m.scrollTop;e.test(t)&&(p.relatedTarget=n.relatedTarget||n[(t==="mouseover"?"from":"to")+"Element"])}else r.test(t)&&(s=d);k(n,p,s||a)}return p}}(),w=function(a,b){return!n&&!b&&(a===l||a===c)?m:a},x=function(){function a(a,b,c,d,e){this.element=a,this.type=b,this.handler=c,this.original=d,this.namespaces=e,this.custom=u[b],this.isNative=t[b]&&a[o],this.eventType=n||this.isNative?b:"propertychange",this.customType=!n&&!this.isNative&&b,this.target=w(a,this.isNative),this.eventSupport=this.target[o]}return a.prototype={inNamespaces:function(a){var b,c;if(!a)return!0;if(!this.namespaces)return!1;for(b=a.length;b--;)for(c=this.namespaces.length;c--;)if(a[b]===this.namespaces[c])return!0;return!1},matches:function(a,b,c){return this.element===a&&(!b||this.original===b)&&(!c||this.handler===c)}},a}(),y=function(){var a={},b=function(c,d,e,f,g){if(!d||d==="*")for(var h in a)h.charAt(0)==="$"&&b(c,h.substr(1),e,f,g);else{var i=0,j,k=a["$"+d],l=c==="*";if(!k)return;for(j=k.length;i<j;i++)if(l||k[i].matches(c,e,f))if(!g(k[i],k,i,d))return}},c=function(b,c,d){var e,f=a["$"+c];if(f)for(e=f.length;e--;)if(f[e].matches(b,d,null))return!0;return!1},d=function(a,c,d){var e=[];return b(a,c,d,null,function(a){return e.push(a)}),e},e=function(b){return(a["$"+b.type]||(a["$"+b.type]=[])).push(b),b},f=function(c){b(c.element,c.type,null,c.handler,function(b,c,d){return c.splice(d,1),c.length===0&&delete a["$"+b.type],!1})},g=function(){var b,c=[];for(b in a)b.charAt(0)==="$"&&(c=c.concat(a[b]));return c};return{has:c,get:d,put:e,del:f,entries:g}}(),z=n?function(a,b,c,d){a[d?h:j](b,c,!1)}:function(a,b,c,d,e){e&&d&&a["_on"+e]===null&&(a["_on"+e]=0),a[d?i:k]("on"+b,c)},A=function(a,b,d){return function(e){return e=v(e||((this.ownerDocument||this.document||this).parentWindow||c).event,!0),b.apply(a,[e].concat(d))}},B=function(a,b,d,e,f,g){return function(h){if(e?e.apply(this,arguments):n?!0:h&&h.propertyName==="_on"+d||!h)h&&(h=v(h||((this.ownerDocument||this.document||this).parentWindow||c).event,g)),b.apply(a,h&&(!f||f.length===0)?arguments:p.call(arguments,h?0:1).concat(f))}},C=function(a,b,c,d,e){return function(){a(b,c,e),d.apply(this,arguments)}},D=function(a,b,c,d){var e,f,h,i=b&&b.replace(g,""),j=y.get(a,i,c);for(e=0,f=j.length;e<f;e++)j[e].inNamespaces(d)&&((h=j[e]).eventSupport&&z(h.target,h.eventType,h.handler,!1,h.type),y.del(h))},E=function(a,b,c,d,e){var h,i=b.replace(g,""),j=b.replace(f,"").split(".");if(y.has(a,i,c))return a;i==="unload"&&(c=C(D,a,i,c,d)),u[i]&&(u[i].condition&&(c=B(a,c,i,u[i].condition,!0)),i=u[i].base||i),h=y.put(new x(a,i,c,d,j[0]&&j)),h.handler=h.isNative?A(a,h.handler,e):B(a,h.handler,i,!1,e,!1),h.eventSupport&&z(h.target,h.eventType,h.handler,!0,h.customType)},F=function(a,b,c){return function(d){var e,f,g=typeof a=="string"?c(a,this):a;for(e=d.target;e&&e!==this;e=e.parentNode)for(f=g.length;f--;)if(g[f]===e)return b.apply(e,arguments)}},G=function(a,b,c){var d,e,h,i,j,k=D,l=b&&typeof b=="string";if(l&&b.indexOf(" ")>0){b=b.split(" ");for(j=b.length;j--;)G(a,b[j],c);return a}h=l&&b.replace(g,""),h&&u[h]&&(h=u[h].type);if(!b||l){if(i=l&&b.replace(f,""))i=i.split(".");k(a,h,c,i)}else if(typeof b=="function")k(a,null,b);else for(d in b)b.hasOwnProperty(d)&&G(a,d,b[d]);return a},H=function(a,b,c,d,e){var f,g,h,i,j=c,k=c&&typeof c=="string";if(b&&!c&&typeof b=="object")for(f in b)b.hasOwnProperty(f)&&H.apply(this,[a,f,b[f]]);else{i=arguments.length>3?p.call(arguments,3):[],g=(k?c:b).split(" "),k&&(c=F(b,j=d,e))&&(i=p.call(i,1)),this===s&&(c=C(G,a,b,c,j));for(h=g.length;h--;)E(a,g[h],c,j,i)}return a},I=function(){return H.apply(s,arguments)},J=n?function(a,b,d){var e=l.createEvent(a?"HTMLEvents":"UIEvents");e[a?"initEvent":"initUIEvent"](b,!0,!0,c,1),d.dispatchEvent(e)}:function(a,b,c){c=w(c,a),a?c.fireEvent("on"+b,l.createEventObject()):c["_on"+b]++},K=function(a,b,c){var d,e,h,i,j,k=b.split(" ");for(d=k.length;d--;){b=k[d].replace(g,"");if(i=k[d].replace(f,""))i=i.split(".");if(!i&&!c&&a[o])J(t[b],b,a);else{j=y.get(a,b),c=[!1].concat(c);for(e=0,h=j.length;e<h;e++)j[e].inNamespaces(i)&&j[e].handler.apply(a,c)}}return a},L=function(a,b,c){var d=0,e=y.get(b,c),f=e.length;for(;d<f;d++)e[d].original&&H(a,e[d].type,e[d].original);return a},M={add:H,one:I,remove:G,clone:L,fire:K,noConflict:function(){return b[a]=d,this}};if(c[i]){var N=function(){var a,b=y.entries();for(a in b)b[a].type&&b[a].type!=="unload"&&G(b[a].element,b[a].type);c[k]("onunload",N),c.CollectGarbage&&c.CollectGarbage()};c[i]("onunload",N)}return M}); -// Underscore.js 1.1.7 -// (c) 2011 Jeremy Ashkenas, DocumentCloud Inc. -// Underscore is freely distributable under the MIT license. -// Portions of Underscore are inspired or borrowed from Prototype, -// Oliver Steele's Functional, and John Resig's Micro-Templating. -// For all details and documentation: -// http://documentcloud.github.com/underscore - -(function(){var a=this,b=a._,c={},d=Array.prototype,e=Object.prototype,f=Function.prototype,g=d.slice,h=d.unshift,i=e.toString,j=e.hasOwnProperty,k=d.forEach,l=d.map,m=d.reduce,n=d.reduceRight,o=d.filter,p=d.every,q=d.some,r=d.indexOf,s=d.lastIndexOf,t=Array.isArray,u=Object.keys,v=f.bind,w=function(a){return new B(a)};typeof module!="undefined"&&module.exports?(module.exports=w,w._=w):a._=w,w.VERSION="1.1.7";var x=w.each=w.forEach=function(a,b,d){if(a==null)return;if(k&&a.forEach===k)a.forEach(b,d);else if(a.length===+a.length){for(var e=0,f=a.length;e<f;e++)if(e in a&&b.call(d,a[e],e,a)===c)return}else for(var g in a)if(j.call(a,g)&&b.call(d,a[g],g,a)===c)return};w.map=function(a,b,c){var d=[];return a==null?d:l&&a.map===l?a.map(b,c):(x(a,function(a,e,f){d[d.length]=b.call(c,a,e,f)}),d)},w.reduce=w.foldl=w.inject=function(a,b,c,d){var e=c!==void 0;a==null&&(a=[]);if(m&&a.reduce===m)return d&&(b=w.bind(b,d)),e?a.reduce(b,c):a.reduce(b);x(a,function(a,f,g){e?c=b.call(d,c,a,f,g):(c=a,e=!0)});if(!e)throw new TypeError("Reduce of empty array with no initial value");return c},w.reduceRight=w.foldr=function(a,b,c,d){a==null&&(a=[]);if(n&&a.reduceRight===n)return d&&(b=w.bind(b,d)),c!==void 0?a.reduceRight(b,c):a.reduceRight(b);var e=(w.isArray(a)?a.slice():w.toArray(a)).reverse();return w.reduce(e,b,c,d)},w.find=w.detect=function(a,b,c){var d;return y(a,function(a,e,f){if(b.call(c,a,e,f))return d=a,!0}),d},w.filter=w.select=function(a,b,c){var d=[];return a==null?d:o&&a.filter===o?a.filter(b,c):(x(a,function(a,e,f){b.call(c,a,e,f)&&(d[d.length]=a)}),d)},w.reject=function(a,b,c){var d=[];return a==null?d:(x(a,function(a,e,f){b.call(c,a,e,f)||(d[d.length]=a)}),d)},w.every=w.all=function(a,b,d){var e=!0;return a==null?e:p&&a.every===p?a.every(b,d):(x(a,function(a,f,g){if(!(e=e&&b.call(d,a,f,g)))return c}),e)};var y=w.some=w.any=function(a,b,d){b=b||w.identity;var e=!1;return a==null?e:q&&a.some===q?a.some(b,d):(x(a,function(a,f,g){if(e|=b.call(d,a,f,g))return c}),!!e)};w.include=w.contains=function(a,b){var c=!1;return a==null?c:r&&a.indexOf===r?a.indexOf(b)!=-1:(y(a,function(a){if(c=a===b)return!0}),c)},w.invoke=function(a,b){var c=g.call(arguments,2);return w.map(a,function(a){return(b.call?b||a:a[b]).apply(a,c)})},w.pluck=function(a,b){return w.map(a,function(a){return a[b]})},w.max=function(a,b,c){if(!b&&w.isArray(a))return Math.max.apply(Math,a);var d={computed:-Infinity};return x(a,function(a,e,f){var g=b?b.call(c,a,e,f):a;g>=d.computed&&(d={value:a,computed:g})}),d.value},w.min=function(a,b,c){if(!b&&w.isArray(a))return Math.min.apply(Math,a);var d={computed:Infinity};return x(a,function(a,e,f){var g=b?b.call(c,a,e,f):a;g<d.computed&&(d={value:a,computed:g})}),d.value},w.sortBy=function(a,b,c){return w.pluck(w.map(a,function(a,d,e){return{value:a,criteria:b.call(c,a,d,e)}}).sort(function(a,b){var c=a.criteria,d=b.criteria;return c<d?-1:c>d?1:0}),"value")},w.groupBy=function(a,b){var c={};return x(a,function(a,d){var e=b(a,d);(c[e]||(c[e]=[])).push(a)}),c},w.sortedIndex=function(a,b,c){c||(c=w.identity);var d=0,e=a.length;while(d<e){var f=d+e>>1;c(a[f])<c(b)?d=f+1:e=f}return d},w.toArray=function(a){return a?a.toArray?a.toArray():w.isArray(a)?g.call(a):w.isArguments(a)?g.call(a):w.values(a):[]},w.size=function(a){return w.toArray(a).length},w.first=w.head=function(a,b,c){return b!=null&&!c?g.call(a,0,b):a[0]},w.rest=w.tail=function(a,b,c){return g.call(a,b==null||c?1:b)},w.last=function(a){return a[a.length-1]},w.compact=function(a){return w.filter(a,function(a){return!!a})},w.flatten=function(a){return w.reduce(a,function(a,b){return w.isArray(b)?a.concat(w.flatten(b)):(a[a.length]=b,a)},[])},w.without=function(a){return w.difference(a,g.call(arguments,1))},w.uniq=w.unique=function(a,b){return w.reduce(a,function(a,c,d){if(0==d||(b===!0?w.last(a)!=c:!w.include(a,c)))a[a.length]=c;return a},[])},w.union=function(){return w.uniq(w.flatten(arguments))},w.intersection=w.intersect=function(a){var b=g.call(arguments,1);return w.filter(w.uniq(a),function(a){return w.every(b,function(b){return w.indexOf(b,a)>=0})})},w.difference=function(a,b){return w.filter(a,function(a){return!w.include(b,a)})},w.zip=function(){var a=g.call(arguments),b=w.max(w.pluck(a,"length")),c=new Array(b);for(var d=0;d<b;d++)c[d]=w.pluck(a,""+d);return c},w.indexOf=function(a,b,c){if(a==null)return-1;var d,e;if(c)return d=w.sortedIndex(a,b),a[d]===b?d:-1;if(r&&a.indexOf===r)return a.indexOf(b);for(d=0,e=a.length;d<e;d++)if(a[d]===b)return d;return-1},w.lastIndexOf=function(a,b){if(a==null)return-1;if(s&&a.lastIndexOf===s)return a.lastIndexOf(b);var c=a.length;while(c--)if(a[c]===b)return c;return-1},w.range=function(a,b,c){arguments.length<=1&&(b=a||0,a=0),c=arguments[2]||1;var d=Math.max(Math.ceil((b-a)/c),0),e=0,f=new Array(d);while(e<d)f[e++]=a,a+=c;return f},w.bind=function(a,b){if(a.bind===v&&v)return v.apply(a,g.call(arguments,1));var c=g.call(arguments,2);return function(){return a.apply(b,c.concat(g.call(arguments)))}},w.bindAll=function(a){var b=g.call(arguments,1);return b.length==0&&(b=w.functions(a)),x(b,function(b){a[b]=w.bind(a[b],a)}),a},w.memoize=function(a,b){var c={};return b||(b=w.identity),function(){var d=b.apply(this,arguments);return j.call(c,d)?c[d]:c[d]=a.apply(this,arguments)}},w.delay=function(a,b){var c=g.call(arguments,2);return setTimeout(function(){return a.apply(a,c)},b)},w.defer=function(a){return w.delay.apply(w,[a,1].concat(g.call(arguments,1)))};var z=function(a,b,c){var d;return function(){var e=this,f=arguments,g=function(){d=null,a.apply(e,f)};c&&clearTimeout(d);if(c||!d)d=setTimeout(g,b)}};w.throttle=function(a,b){return z(a,b,!1)},w.debounce=function(a,b){return z(a,b,!0)},w.once=function(a){var b=!1,c;return function(){return b?c:(b=!0,c=a.apply(this,arguments))}},w.wrap=function(a,b){return function(){var c=[a].concat(g.call(arguments));return b.apply(this,c)}},w.compose=function(){var a=g.call(arguments);return function(){var b=g.call(arguments);for(var c=a.length-1;c>=0;c--)b=[a[c].apply(this,b)];return b[0]}},w.after=function(a,b){return function(){if(--a<1)return b.apply(this,arguments)}},w.keys=u||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var b=[];for(var c in a)j.call(a,c)&&(b[b.length]=c);return b},w.values=function(a){return w.map(a,w.identity)},w.functions=w.methods=function(a){var b=[];for(var c in a)w.isFunction(a[c])&&b.push(c);return b.sort()},w.extend=function(a){return x(g.call(arguments,1),function(b){for(var c in b)b[c]!==void 0&&(a[c]=b[c])}),a},w.defaults=function(a){return x(g.call(arguments,1),function(b){for(var c in b)a[c]==null&&(a[c]=b[c])}),a},w.clone=function(a){return w.isArray(a)?a.slice():w.extend({},a)},w.tap=function(a,b){return b(a),a},w.isEqual=function(a,b){if(a===b)return!0;var c=typeof a,d=typeof b;if(c!=d)return!1;if(a==b)return!0;if(!a&&b||a&&!b)return!1;a._chain&&(a=a._wrapped),b._chain&&(b=b._wrapped);if(a.isEqual)return a.isEqual(b);if(b.isEqual)return b.isEqual(a);if(w.isDate(a)&&w.isDate(b))return a.getTime()===b.getTime();if(w.isNaN(a)&&w.isNaN(b))return!1;if(w.isRegExp(a)&&w.isRegExp(b))return a.source===b.source&&a.global===b.global&&a.ignoreCase===b.ignoreCase&&a.multiline===b.multiline;if(c!=="object")return!1;if(a.length&&a.length!==b.length)return!1;var e=w.keys(a),f=w.keys(b);if(e.length!=f.length)return!1;for(var g in a)if(!(g in b)||!w.isEqual(a[g],b[g]))return!1;return!0},w.isEmpty=function(a){if(w.isArray(a)||w.isString(a))return a.length===0;for(var b in a)if(j.call(a,b))return!1;return!0},w.isElement=function(a){return!!a&&a.nodeType==1},w.isArray=t||function(a){return i.call(a)==="[object Array]"},w.isObject=function(a){return a===Object(a)},w.isArguments=function(a){return!!a&&!!j.call(a,"callee")},w.isFunction=function(a){return!!(a&&a.constructor&&a.call&&a.apply)},w.isString=function(a){return!!(a===""||a&&a.charCodeAt&&a.substr)},w.isNumber=function(a){return!!(a===0||a&&a.toExponential&&a.toFixed)},w.isNaN=function(a){return a!==a},w.isBoolean=function(a){return a===!0||a===!1},w.isDate=function(a){return!!(a&&a.getTimezoneOffset&&a.setUTCFullYear)},w.isRegExp=function(a){return!(!(a&&a.test&&a.exec)||!a.ignoreCase&&a.ignoreCase!==!1)},w.isNull=function(a){return a===null},w.isUndefined=function(a){return a===void 0},w.noConflict=function(){return a._=b,this},w.identity=function(a){return a},w.times=function(a,b,c){for(var d=0;d<a;d++)b.call(c,d)},w.mixin=function(a){x(w.functions(a),function(b){D(b,w[b]=a[b])})};var A=0;w.uniqueId=function(a){var b=A++;return a?a+b:b},w.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g},w.template=function(a,b){var c=w.templateSettings,d="var __p=[],print=function(){__p.push.apply(__p,arguments);};with(obj||{}){__p.push('"+a.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(c.interpolate,function(a,b){return"',"+b.replace(/\\'/g,"'")+",'"}).replace(c.evaluate||null,function(a,b){return"');"+b.replace(/\\'/g,"'").replace(/[\r\n\t]/g," ")+"__p.push('"}).replace(/\r/g,"\\r").replace(/\n/g,"\\n").replace(/\t/g,"\\t")+"');}return __p.join('');",e=new Function("obj",d);return b?e(b):e};var B=function(a){this._wrapped=a};w.prototype=B.prototype;var C=function(a,b){return b?w(a).chain():a},D=function(a,b){B.prototype[a]=function(){var a=g.call(arguments);return h.call(a,this._wrapped),C(b.apply(w,a),this._chain)}};w.mixin(w),x(["pop","push","reverse","shift","sort","splice","unshift"],function(a){var b=d[a];B.prototype[a]=function(){return b.apply(this._wrapped,arguments),C(this._wrapped,this._chain)}}),x(["concat","join","slice"],function(a){var b=d[a];B.prototype[a]=function(){return C(b.apply(this._wrapped,arguments),this._chain)}}),B.prototype.chain=function(){return this._chain=!0,this},B.prototype.value=function(){return this._wrapped}})(); -/** - * Flotr2 (c) 2012 Carl Sutherland - * MIT License - * Special thanks to: - * Flotr: http://code.google.com/p/flotr/ (fork) - * Flot: https://github.com/flot/flot (original fork) - */ -(function(){var a=this,b=this.Flotr,c;c={_:_,bean:bean,isIphone:/iphone/i.test(navigator.userAgent),isIE:navigator.appVersion.indexOf("MSIE")!=-1?parseFloat(navigator.appVersion.split("MSIE")[1]):!1,graphTypes:{},plugins:{},addType:function(a,b){c.graphTypes[a]=b,c.defaultOptions[a]=b.options||{},c.defaultOptions.defaultType=c.defaultOptions.defaultType||a},addPlugin:function(a,b){c.plugins[a]=b,c.defaultOptions[a]=b.options||{}},draw:function(a,b,d,e){return e=e||c.Graph,new e(a,b,d)},merge:function(a,b){var d,e,f=b||{};for(d in a)e=a[d],e&&typeof e=="object"?e.constructor===Array?f[d]=this._.clone(e):e.constructor!==RegExp&&!this._.isElement(e)?f[d]=c.merge(e,b?b[d]:undefined):f[d]=e:f[d]=e;return f},clone:function(a){return c.merge(a,{})},getTickSize:function(a,b,d,e){var f=(d-b)/a,g=c.getMagnitude(f),h=10,i=f/g;return i<1.5?h=1:i<2.25?h=2:i<3?h=e===0?2:2.5:i<7.5&&(h=5),h*g},defaultTickFormatter:function(a,b){return a+""},defaultTrackFormatter:function(a){return"("+a.x+", "+a.y+")"},engineeringNotation:function(a,b,c){var d=["Y","Z","E","P","T","G","M","k",""],e=["y","z","a","f","p","n","µ","m",""],f=d.length;c=c||1e3,b=Math.pow(10,b||2);if(a===0)return 0;if(a>1)while(f--&&a>=c)a/=c;else{d=e,f=d.length;while(f--&&a<1)a*=c}return Math.round(a*b)/b+d[f]},getMagnitude:function(a){return Math.pow(10,Math.floor(Math.log(a)/Math.LN10))},toPixel:function(a){return Math.floor(a)+.5},toRad:function(a){return-a*(Math.PI/180)},floorInBase:function(a,b){return b*Math.floor(a/b)},drawText:function(a,b,d,e,f){if(!a.fillText){a.drawText(b,d,e,f);return}f=this._.extend({size:c.defaultOptions.fontSize,color:"#000000",textAlign:"left",textBaseline:"bottom",weight:1,angle:0},f),a.save(),a.translate(d,e),a.rotate(f.angle),a.fillStyle=f.color,a.font=(f.weight>1?"bold ":"")+f.size*1.3+"px sans-serif",a.textAlign=f.textAlign,a.textBaseline=f.textBaseline,a.fillText(b,0,0),a.restore()},getBestTextAlign:function(a,b){return b=b||{textAlign:"center",textBaseline:"middle"},a+=c.getTextAngleFromAlign(b),Math.abs(Math.cos(a))>.01&&(b.textAlign=Math.cos(a)>0?"right":"left"),Math.abs(Math.sin(a))>.01&&(b.textBaseline=Math.sin(a)>0?"top":"bottom"),b},alignTable:{"right middle":0,"right top":Math.PI/4,"center top":Math.PI/2,"left top":3*(Math.PI/4),"left middle":Math.PI,"left bottom":-3*(Math.PI/4),"center bottom":-Math.PI/2,"right bottom":-Math.PI/4,"center middle":0},getTextAngleFromAlign:function(a){return c.alignTable[a.textAlign+" "+a.textBaseline]||0},noConflict:function(){return a.Flotr=b,this}},a.Flotr=c})(),Flotr.defaultOptions={colors:["#00A8F0","#C0D800","#CB4B4B","#4DA74D","#9440ED"],ieBackgroundColor:"#FFFFFF",title:null,subtitle:null,shadowSize:4,defaultType:null,HtmlText:!0,fontColor:"#545454",fontSize:7.5,resolution:1,parseFloat:!0,xaxis:{ticks:null,minorTicks:null,showLabels:!0,showMinorLabels:!1,labelsAngle:0,title:null,titleAngle:0,noTicks:5,minorTickFreq:null,tickFormatter:Flotr.defaultTickFormatter,tickDecimals:null,min:null,max:null,autoscale:!1,autoscaleMargin:0,color:null,mode:"normal",timeFormat:null,timeMode:"UTC",timeUnit:"millisecond",scaling:"linear",base:Math.E,titleAlign:"center",margin:!0},x2axis:{},yaxis:{ticks:null,minorTicks:null,showLabels:!0,showMinorLabels:!1,labelsAngle:0,title:null,titleAngle:90,noTicks:5,minorTickFreq:null,tickFormatter:Flotr.defaultTickFormatter,tickDecimals:null,min:null,max:null,autoscale:!1,autoscaleMargin:0,color:null,scaling:"linear",base:Math.E,titleAlign:"center",margin:!0},y2axis:{titleAngle:270},grid:{color:"#545454",backgroundColor:null,backgroundImage:null,watermarkAlpha:.4,tickColor:"#DDDDDD",labelMargin:3,verticalLines:!0,minorVerticalLines:null,horizontalLines:!0,minorHorizontalLines:null,outlineWidth:1,outline:"nsew",circular:!1},mouse:{track:!1,trackAll:!1,position:"se",relative:!1,trackFormatter:Flotr.defaultTrackFormatter,margin:5,lineColor:"#FF3F19",trackDecimals:1,sensibility:2,trackY:!0,radius:3,fillColor:null,fillOpacity:.4}},function(){function b(a,b,c,d){this.rgba=["r","g","b","a"];var e=4;while(-1<--e)this[this.rgba[e]]=arguments[e]||(e==3?1:0);this.normalize()}var a=Flotr._,c={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]};b.prototype={scale:function(b,c,d,e){var f=4;while(-1<--f)a.isUndefined(arguments[f])||(this[this.rgba[f]]*=arguments[f]);return this.normalize()},alpha:function(b){return!a.isUndefined(b)&&!a.isNull(b)&&(this.a=b),this.normalize()},clone:function(){return new b(this.r,this.b,this.g,this.a)},limit:function(a,b,c){return Math.max(Math.min(a,c),b)},normalize:function(){var a=this.limit;return this.r=a(parseInt(this.r,10),0,255),this.g=a(parseInt(this.g,10),0,255),this.b=a(parseInt(this.b,10),0,255),this.a=a(this.a,0,1),this},distance:function(a){if(!a)return;a=new b.parse(a);var c=0,d=3;while(-1<--d)c+=Math.abs(this[this.rgba[d]]-a[this.rgba[d]]);return c},toString:function(){return this.a>=1?"rgb("+[this.r,this.g,this.b].join(",")+")":"rgba("+[this.r,this.g,this.b,this.a].join(",")+")"},contrast:function(){var a=1-(.299*this.r+.587*this.g+.114*this.b)/255;return a<.5?"#000000":"#ffffff"}},a.extend(b,{parse:function(a){if(a instanceof b)return a;var d;if(d=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(a))return new b(parseInt(d[1],16),parseInt(d[2],16),parseInt(d[3],16));if(d=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(a))return new b(parseInt(d[1],10),parseInt(d[2],10),parseInt(d[3],10));if(d=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(a))return new b(parseInt(d[1]+d[1],16),parseInt(d[2]+d[2],16),parseInt(d[3]+d[3],16));if(d=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(a))return new b(parseInt(d[1],10),parseInt(d[2],10),parseInt(d[3],10),parseFloat(d[4]));if(d=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(a))return new b(parseFloat(d[1])*2.55,parseFloat(d[2])*2.55,parseFloat(d[3])*2.55);if(d=/rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(a))return new b(parseFloat(d[1])*2.55,parseFloat(d[2])*2.55,parseFloat(d[3])*2.55,parseFloat(d[4]));var e=(a+"").replace(/^\s*([\S\s]*?)\s*$/,"$1").toLowerCase();return e=="transparent"?new b(255,255,255,0):(d=c[e])?new b(d[0],d[1],d[2]):new b(0,0,0,0)},processColor:function(c,d){var e=d.opacity;if(!c)return"rgba(0, 0, 0, 0)";if(c instanceof b)return c.alpha(e).toString();if(a.isString(c))return b.parse(c).alpha(e).toString();var f=c.colors?c:{colors:c};if(!d.ctx)return a.isArray(f.colors)?b.parse(a.isArray(f.colors[0])?f.colors[0][1]:f.colors[0]).alpha(e).toString():"rgba(0, 0, 0, 0)";f=a.extend({start:"top",end:"bottom"},f),/top/i.test(f.start)&&(d.x1=0),/left/i.test(f.start)&&(d.y1=0),/bottom/i.test(f.end)&&(d.x2=0),/right/i.test(f.end)&&(d.y2=0);var g,h,i,j=d.ctx.createLinearGradient(d.x1,d.y1,d.x2,d.y2);for(g=0;g<f.colors.length;g++)h=f.colors[g],a.isArray(h)?(i=h[0],h=h[1]):i=g/(f.colors.length-1),j.addColorStop(i,b.parse(h).alpha(e));return j}}),Flotr.Color=b}(),Flotr.Date={set:function(a,b,c,d){c=c||"UTC",b="set"+(c==="UTC"?"UTC":"")+b,a[b](d)},get:function(a,b,c){return c=c||"UTC",b="get"+(c==="UTC"?"UTC":"")+b,a[b]()},format:function(a,b,c){function f(a){return a+="",a.length==1?"0"+a:a}if(!a)return;var d=this.get,e={h:d(a,"Hours",c).toString(),H:f(d(a,"Hours",c)),M:f(d(a,"Minutes",c)),S:f(d(a,"Seconds",c)),s:d(a,"Milliseconds",c),d:d(a,"Date",c).toString(),m:(d(a,"Month")+1).toString(),y:d(a,"FullYear").toString(),b:Flotr.Date.monthNames[d(a,"Month",c)]},g=[],h,i=!1;for(var j=0;j<b.length;++j)h=b.charAt(j),i?(g.push(e[h]||h),i=!1):h=="%"?i=!0:g.push(h);return g.join("")},getFormat:function(a,b){var c=Flotr.Date.timeUnits;return a<c.second?"%h:%M:%S.%s":a<c.minute?"%h:%M:%S":a<c.day?b<2*c.day?"%h:%M":"%b %d %h:%M":a<c.month?"%b %d":a<c.year?b<c.year?"%b":"%b %y":"%y"},formatter:function(a,b){var c=b.options,d=Flotr.Date.timeUnits[c.timeUnit],e=new Date(a*d);if(b.options.timeFormat)return Flotr.Date.format(e,c.timeFormat,c.timeMode);var f=(b.max-b.min)*d,g=b.tickSize*Flotr.Date.timeUnits[b.tickUnit];return Flotr.Date.format(e,Flotr.Date.getFormat(g,f),c.timeMode)},generator:function(a){function s(a){b(q,a,g,Flotr.floorInBase(c(q,a,g),m))}var b=this.set,c=this.get,d=this.timeUnits,e=this.spec,f=a.options,g=f.timeMode,h=d[f.timeUnit],i=a.min*h,j=a.max*h,k=(j-i)/f.noTicks,l=[],m=a.tickSize,n,o,p;o=f.tickFormatter===Flotr.defaultTickFormatter?this.formatter:f.tickFormatter;for(p=0;p<e.length-1;++p){var q=e[p][0]*d[e[p][1]];if(k<(q+e[p+1][0]*d[e[p+1][1]])/2&&q>=m)break}m=e[p][0],n=e[p][1],n=="year"&&(m=Flotr.getTickSize(f.noTicks*d.year,i,j,0),m==.5&&(n="month",m=6)),a.tickUnit=n,a.tickSize=m;var q=new Date(i),r=m*d[n];switch(n){case"millisecond":s("Milliseconds");break;case"second":s("Seconds");break;case"minute":s("Minutes");break;case"hour":s("Hours");break;case"month":s("Month");break;case"year":s("FullYear")}r>=d.second&&b(q,"Milliseconds",g,0),r>=d.minute&&b(q,"Seconds",g,0),r>=d.hour&&b(q,"Minutes",g,0),r>=d.day&&b(q,"Hours",g,0),r>=d.day*4&&b(q,"Date",g,1),r>=d.year&&b(q,"Month",g,0);var t=0,u=NaN,v;do{v=u,u=q.getTime(),l.push({v:u/h,label:o(u/h,a)});if(n=="month")if(m<1){b(q,"Date",g,1);var w=q.getTime();b(q,"Month",g,c(q,"Month",g)+1);var x=q.getTime();q.setTime(u+t*d.hour+(x-w)*m),t=c(q,"Hours",g),b(q,"Hours",g,0)}else b(q,"Month",g,c(q,"Month",g)+m);else n=="year"?b(q,"FullYear",g,c(q,"FullYear",g)+m):q.setTime(u+r)}while(u<j&&u!=v);return l},timeUnits:{millisecond:1,second:1e3,minute:6e4,hour:36e5,day:864e5,month:2592e6,year:31556952e3},spec:[[1,"millisecond"],[20,"millisecond"],[50,"millisecond"],[100,"millisecond"],[200,"millisecond"],[500,"millisecond"],[1,"second"],[2,"second"],[5,"second"],[10,"second"],[30,"second"],[1,"minute"],[2,"minute"],[5,"minute"],[10,"minute"],[30,"minute"],[1,"hour"],[2,"hour"],[4,"hour"],[8,"hour"],[12,"hour"],[1,"day"],[2,"day"],[3,"day"],[.25,"month"],[.5,"month"],[1,"month"],[2,"month"],[3,"month"],[6,"month"],[1,"year"]],monthNames:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]},function(){var a=Flotr._;Flotr.DOM={addClass:function(b,c){var d=b.className?b.className:"";if(a.include(d.split(/\s+/g),c))return;b.className=(d?d+" ":"")+c},create:function(a){return document.createElement(a)},node:function(a){var b=Flotr.DOM.create("div"),c;return b.innerHTML=a,c=b.children[0],b.innerHTML="",c},empty:function(a){a.innerHTML=""},hide:function(a){Flotr.DOM.setStyles(a,{display:"none"})},insert:function(b,c){a.isString(c)?b.innerHTML+=c:a.isElement(c)&&b.appendChild(c)},opacity:function(a,b){a.style.opacity=b},position:function(a,b){return a.offsetParent?(b=this.position(a.offsetParent),b.left+=a.offsetLeft,b.top+=a.offsetTop,b):{left:a.offsetLeft||0,top:a.offsetTop||0}},removeClass:function(b,c){var d=b.className?b.className:"";b.className=a.filter(d.split(/\s+/g),function(a){if(a!=c)return!0}).join(" ")},setStyles:function(b,c){a.each(c,function(a,c){b.style[c]=a})},show:function(a){Flotr.DOM.setStyles(a,{display:""})},size:function(a){return{height:a.offsetHeight,width:a.offsetWidth}}}}(),function(){var a=Flotr,b=a.bean;a.EventAdapter={observe:function(a,c,d){return b.add(a,c,d),this},fire:function(a,c,d){return b.fire(a,c,d),typeof Prototype!="undefined"&&Event.fire(a,c,d),this},stopObserving:function(a,c,d){return b.remove(a,c,d),this},eventPointer:function(b){if(!a._.isUndefined(b.touches)&&b.touches.length>0)return{x:b.touches[0].pageX,y:b.touches[0].pageY};if(!a._.isUndefined(b.changedTouches)&&b.changedTouches.length>0)return{x:b.changedTouches[0].pageX,y:b.changedTouches[0].pageY};if(b.pageX||b.pageY)return{x:b.pageX,y:b.pageY};if(b.clientX||b.clientY){var c=document,d=c.body,e=c.documentElement;return{x:b.clientX+d.scrollLeft+e.scrollLeft,y:b.clientY+d.scrollTop+e.scrollTop}}}}}(),function(){var a=Flotr,b=a.DOM,c=a._,d=function(a){this.o=a};d.prototype={dimensions:function(a,b,c,d){return a?this.o.html?this.html(a,this.o.element,c,d):this.canvas(a,b):{width:0,height:0}},canvas:function(b,c){if(!this.o.textEnabled)return;c=c||{};var d=this.measureText(b,c),e=d.width,f=c.size||a.defaultOptions.fontSize,g=c.angle||0,h=Math.cos(g),i=Math.sin(g),j=2,k=6,l;return l={width:Math.abs(h*e)+Math.abs(i*f)+j,height:Math.abs(i*e)+Math.abs(h*f)+k},l},html:function(a,c,d,e){var f=b.create("div");return b.setStyles(f,{position:"absolute",top:"-10000px"}),b.insert(f,'<div style="'+d+'" class="'+e+' flotr-dummy-div">'+a+"</div>"),b.insert(this.o.element,f),b.size(f)},measureText:function(b,d){var e=this.o.ctx,f;return!e.fillText||a.isIphone&&e.measure?{width:e.measure(b,d)}:(d=c.extend({size:a.defaultOptions.fontSize,weight:1,angle:0},d),e.save(),e.font=(d.weight>1?"bold ":"")+d.size*1.3+"px sans-serif",f=e.measureText(b),e.restore(),f)}},Flotr.Text=d}(),function(){function e(a,c,d){return b.observe.apply(this,arguments),this._handles.push(arguments),this}var a=Flotr.DOM,b=Flotr.EventAdapter,c=Flotr._,d=Flotr;Graph=function(a,e,f){this._setEl(a),this._initMembers(),this._initPlugins(),b.fire(this.el,"flotr:beforeinit",[this]),this.data=e,this.series=d.Series.getSeries(e),this._initOptions(f),this._initGraphTypes(),this._initCanvas(),this._text=new d.Text({element:this.el,ctx:this.ctx,html:this.options.HtmlText,textEnabled:this.textEnabled}),b.fire(this.el,"flotr:afterconstruct",[this]),this._initEvents(),this.findDataRanges(),this.calculateSpacing(),this.draw(c.bind(function(){b.fire(this.el,"flotr:afterinit",[this])},this))},Graph.prototype={destroy:function(){b.fire(this.el,"flotr:destroy"),c.each(this._handles,function(a){b.stopObserving.apply(this,a)}),this._handles=[],this.el.graph=null},observe:e,_observe:e,processColor:function(a,b){var e={x1:0,y1:0,x2:this.plotWidth,y2:this.plotHeight,opacity:1,ctx:this.ctx};return c.extend(e,b),d.Color.processColor(a,e)},findDataRanges:function(){var a=this.axes,b,e,f;c.each(this.series,function(a){f=a.getRange(),f&&(b=a.xaxis,e=a.yaxis,b.datamin=Math.min(f.xmin,b.datamin),b.datamax=Math.max(f.xmax,b.datamax),e.datamin=Math.min(f.ymin,e.datamin),e.datamax=Math.max(f.ymax,e.datamax),b.used=b.used||f.xused,e.used=e.used||f.yused)},this),!a.x.used&&!a.x2.used&&(a.x.used=!0),!a.y.used&&!a.y2.used&&(a.y.used=!0),c.each(a,function(a){a.calculateRange()});var g=c.keys(d.graphTypes),h=!1;c.each(this.series,function(a){if(a.hide)return;c.each(g,function(b){a[b]&&a[b].show&&(this.extendRange(b,a),h=!0)},this),h||this.extendRange(this.options.defaultType,a)},this)},extendRange:function(a,b){this[a].extendRange&&this[a].extendRange(b,b.data,b[a],this[a]),this[a].extendYRange&&this[a].extendYRange(b.yaxis,b.data,b[a],this[a]),this[a].extendXRange&&this[a].extendXRange(b.xaxis,b.data,b[a],this[a])},calculateSpacing:function(){var a=this.axes,b=this.options,d=this.series,e=b.grid.labelMargin,f=this._text,g=a.x,h=a.x2,i=a.y,j=a.y2,k=b.grid.outlineWidth,l,m,n,o;c.each(a,function(a){a.calculateTicks(),a.calculateTextDimensions(f,b)}),o=f.dimensions(b.title,{size:b.fontSize*1.5},"font-size:1em;font-weight:bold;","flotr-title"),this.titleHeight=o.height,o=f.dimensions(b.subtitle,{size:b.fontSize},"font-size:smaller;","flotr-subtitle"),this.subtitleHeight=o.height;for(m=0;m<b.length;++m)d[m].points.show&&(k=Math.max(k,d[m].points.radius+d[m].points.lineWidth/2));var p=this.plotOffset;g.options.margin===!1?(p.bottom=0,p.top=0):(p.bottom+=(b.grid.circular?0:g.used&&g.options.showLabels?g.maxLabel.height+e:0)+(g.used&&g.options.title?g.titleSize.height+e:0)+k,p.top+=(b.grid.circular?0:h.used&&h.options.showLabels?h.maxLabel.height+e:0)+(h.used&&h.options.title?h.titleSize.height+e:0)+this.subtitleHeight+this.titleHeight+k),i.options.margin===!1?(p.left=0,p.right=0):(p.left+=(b.grid.circular?0:i.used&&i.options.showLabels?i.maxLabel.width+e:0)+(i.used&&i.options.title?i.titleSize.width+e:0)+k,p.right+=(b.grid.circular?0:j.used&&j.options.showLabels?j.maxLabel.width+e:0)+(j.used&&j.options.title?j.titleSize.width+e:0)+k),p.top=Math.floor(p.top),this.plotWidth=this.canvasWidth-p.left-p.right,this.plotHeight=this.canvasHeight-p.bottom-p.top,g.length=h.length=this.plotWidth,i.length=j.length=this.plotHeight,i.offset=j.offset=this.plotHeight,g.setScale(),h.setScale(),i.setScale(),j.setScale()},draw:function(a){var c=this.ctx,d;b.fire(this.el,"flotr:beforedraw",[this.series,this]);if(this.series.length){c.save(),c.translate(this.plotOffset.left,this.plotOffset.top);for(d=0;d<this.series.length;d++)this.series[d].hide||this.drawSeries(this.series[d]);c.restore(),this.clip()}b.fire(this.el,"flotr:afterdraw",[this.series,this]),a&&a()},drawSeries:function(a){function b(a,b){var c=this.getOptions(a,b);this[b].draw(c)}var e=!1;a=a||this.series,c.each(d.graphTypes,function(c,d){a[d]&&a[d].show&&this[d]&&(e=!0,b.call(this,a,d))},this),e||b.call(this,a,this.options.defaultType)},getOptions:function(a,b){var e=a[b],f=this[b],g={context:this.ctx,width:this.plotWidth,height:this.plotHeight,fontSize:this.options.fontSize,fontColor:this.options.fontColor,textEnabled:this.textEnabled,htmlText:this.options.HtmlText,text:this._text,element:this.el,data:a.data,color:a.color,shadowSize:a.shadowSize,xScale:c.bind(a.xaxis.d2p,a.xaxis),yScale:c.bind(a.yaxis.d2p,a.yaxis)};return g=d.merge(e,g),g.fillStyle=this.processColor(e.fillColor||a.color,{opacity:e.fillOpacity}),g},getEventPosition:function(c){var d=document,e=d.body,f=d.documentElement,g=this.axes,h=this.plotOffset,i=this.lastMousePos,j=b.eventPointer(c),k=j.x-i.pageX,l=j.y-i.pageY,m,n,o;return"ontouchstart"in this.el?(m=a.position(this.overlay),n=j.x-m.left-h.left,o=j.y-m.top-h.top):(m=this.overlay.getBoundingClientRect(),n=c.clientX-m.left-h.left-e.scrollLeft-f.scrollLeft,o=c.clientY-m.top-h.top-e.scrollTop-f.scrollTop),{x:g.x.p2d(n),x2:g.x2.p2d(n),y:g.y.p2d(o),y2:g.y2.p2d(o),relX:n,relY:o,dX:k,dY:l,absX:j.x,absY:j.y,pageX:j.x,pageY:j.y}},clickHandler:function(a){if(this.ignoreClick)return this.ignoreClick=!1,this.ignoreClick;b.fire(this.el,"flotr:click",[this.getEventPosition(a),this])},mouseMoveHandler:function(a){if(this.mouseDownMoveHandler)return;var c=this.getEventPosition(a);b.fire(this.el,"flotr:mousemove",[a,c,this]),this.lastMousePos=c},mouseDownHandler:function(a){if(this.mouseUpHandler)return;this.mouseUpHandler=c.bind(function(a){b.stopObserving(document,"mouseup",this.mouseUpHandler),b.stopObserving(document,"mousemove",this.mouseDownMoveHandler),this.mouseDownMoveHandler=null,this.mouseUpHandler=null,b.fire(this.el,"flotr:mouseup",[a,this])},this),this.mouseDownMoveHandler=c.bind(function(c){var d=this.getEventPosition(c);b.fire(this.el,"flotr:mousemove",[a,d,this]),this.lastMousePos=d},this),b.observe(document,"mouseup",this.mouseUpHandler),b.observe(document,"mousemove",this.mouseDownMoveHandler),b.fire(this.el,"flotr:mousedown",[a,this]),this.ignoreClick=!1},drawTooltip:function(b,c,d,e){var f=this.getMouseTrack(),g="opacity:0.7;background-color:#000;color:#fff;display:none;position:absolute;padding:2px 8px;-moz-border-radius:4px;border-radius:4px;white-space:nowrap;",h=e.position,i=e.margin,j=this.plotOffset;c!==null&&d!==null?(e.relative?(h.charAt(0)=="n"?g+="bottom:"+(i-j.top-d+this.canvasHeight)+"px;top:auto;":h.charAt(0)=="s"&&(g+="top:"+(i+j.top+d)+"px;bottom:auto;"),h.charAt(1)=="e"?g+="left:"+(i+j.left+c)+"px;right:auto;":h.charAt(1)=="w"&&(g+="right:"+(i-j.left-c+this.canvasWidth)+"px;left:auto;")):(h.charAt(0)=="n"?g+="top:"+(i+j.top)+"px;bottom:auto;":h.charAt(0)=="s"&&(g+="bottom:"+(i+j.bottom)+"px;top:auto;"),h.charAt(1)=="e"?g+="right:"+(i+j.right)+"px;left:auto;":h.charAt(1)=="w"&&(g+="left:"+(i+j.left)+"px;right:auto;")),f.style.cssText=g,a.empty(f),a.insert(f,b),a.show(f)):a.hide(f)},clip:function(){var a=this.ctx,b=this.plotOffset,c=this.canvasWidth,e=this.canvasHeight;d.isIE&&d.isIE<9?(a.save(),a.fillStyle=this.processColor(this.options.ieBackgroundColor),a.fillRect(0,0,c,b.top),a.fillRect(0,0,b.left,e),a.fillRect(0,e-b.bottom,c,b.bottom),a.fillRect(c-b.right,0,b.right,e),a.restore()):(a.clearRect(0,0,c,b.top),a.clearRect(0,0,b.left,e),a.clearRect(0,e-b.bottom,c,b.bottom),a.clearRect(c-b.right,0,b.right,e))},_initMembers:function(){this._handles=[],this.lastMousePos={pageX:null,pageY:null},this.plotOffset={left:0,right:0,top:0,bottom:0},this.ignoreClick=!0,this.prevHit=null},_initGraphTypes:function(){c.each(d.graphTypes,function(a,b){this[b]=d.clone(a)},this)},_initEvents:function(){var a=this.el,d,e,f;"ontouchstart"in a?(d=c.bind(function(c){f=!0,b.stopObserving(document,"touchend",d),b.fire(a,"flotr:mouseup",[event,this]),this.multitouches=null,e||this.clickHandler(c)},this),this.observe(this.overlay,"touchstart",c.bind(function(c){e=!1,f=!1,this.ignoreClick=!1,c.touches&&c.touches.length>1&&(this.multitouches=c.touches),b.fire(a,"flotr:mousedown",[event,this]),this.observe(document,"touchend",d)},this)),this.observe(this.overlay,"touchmove",c.bind(function(c){var d=this.getEventPosition(c);c.preventDefault(),e=!0,this.multitouches||c.touches&&c.touches.length>1?this.multitouches=c.touches:f||b.fire(a,"flotr:mousemove",[event,d,this]),this.lastMousePos=d},this))):this.observe(this.overlay,"mousedown",c.bind(this.mouseDownHandler,this)).observe(a,"mousemove",c.bind(this.mouseMoveHandler,this)).observe(this.overlay,"click",c.bind(this.clickHandler,this)).observe(a,"mouseout",function(){b.fire(a,"flotr:mouseout")})},_initCanvas:function(){function k(e,f){return e||(e=a.create("canvas"),typeof FlashCanvas!="undefined"&&typeof e.getContext=="function"&&FlashCanvas.initElement(e),e.className="flotr-"+f,e.style.cssText="position:absolute;left:0px;top:0px;",a.insert(b,e)),c.each(i,function(b,c){a.show(e);if(f=="canvas"&&e.getAttribute(c)===b)return;e.setAttribute(c,b*d.resolution),e.style[c]=b+"px"}),e.context_=null,e}function l(a){window.G_vmlCanvasManager&&window.G_vmlCanvasManager.initElement(a);var b=a.getContext("2d");return window.G_vmlCanvasManager||b.scale(d.resolution,d.resolution),b}var b=this.el,d=this.options,e=b.children,f=[],g,h,i,j;for(h=e.length;h--;)g=e[h],!this.canvas&&g.className==="flotr-canvas"?this.canvas=g:!this.overlay&&g.className==="flotr-overlay"?this.overlay=g:f.push(g);for(h=f.length;h--;)b.removeChild(f[h]);a.setStyles(b,{position:"relative"}),i={},i.width=b.clientWidth,i.height=b.clientHeight;if(i.width<=0||i.height<=0||d.resolution<=0)throw"Invalid dimensions for plot, width = "+i.width+", height = "+i.height+", resolution = "+d.resolution;this.canvas=k(this.canvas,"canvas"),this.overlay=k(this.overlay,"overlay"),this.ctx=l(this.canvas),this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height),this.octx=l(this.overlay),this.octx.clearRect(0,0,this.overlay.width,this.overlay.height),this.canvasHeight=i.height,this.canvasWidth=i.width,this.textEnabled=!!this.ctx.drawText||!!this.ctx.fillText},_initPlugins:function(){c.each(d.plugins,function(a,b){c.each(a.callbacks,function(a,b){this.observe(this.el,b,c.bind(a,this))},this),this[b]=d.clone(a),c.each(this[b],function(a,d){c.isFunction(a)&&(this[b][d]=c.bind(a,this))},this)},this)},_initOptions:function(a){var e=d.clone(d.defaultOptions);e.x2axis=c.extend(c.clone(e.xaxis),e.x2axis),e.y2axis=c.extend(c.clone(e.yaxis),e.y2axis),this.options=d.merge(a||{},e),this.options.grid.minorVerticalLines===null&&this.options.xaxis.scaling==="logarithmic"&&(this.options.grid.minorVerticalLines=!0),this.options.grid.minorHorizontalLines===null&&this.options.yaxis.scaling==="logarithmic"&&(this.options.grid.minorHorizontalLines=!0),b.fire(this.el,"flotr:afterinitoptions",[this]),this.axes=d.Axis.getAxes(this.options);var f=[],g=[],h=this.series.length,i=this.series.length,j=this.options.colors,k=[],l=0,m,n,o,p;for(n=i-1;n>-1;--n)m=this.series[n].color,m&&(--i,c.isNumber(m)?f.push(m):k.push(d.Color.parse(m)));for(n=f.length-1;n>-1;--n)i=Math.max(i,f[n]+1);for(n=0;g.length<i;){m=j.length==n?new d.Color(100,100,100):d.Color.parse(j[n]);var q=l%2==1?-1:1,r=1+q*Math.ceil(l/2)*.2;m.scale(r,r,r),g.push(m),++n>=j.length&&(n=0,++l)}for(n=0,o=0;n<h;++n){p=this.series[n],p.color?c.isNumber(p.color)&&(p.color=g[p.color].toString()):p.color=g[o++].toString(),p.xaxis||(p.xaxis=this.axes.x),p.xaxis==1?p.xaxis=this.axes.x:p.xaxis==2&&(p.xaxis=this.axes.x2),p.yaxis||(p.yaxis=this.axes.y),p.yaxis==1?p.yaxis=this.axes.y:p.yaxis==2&&(p.yaxis=this.axes.y2);for(var s in d.graphTypes)p[s]=c.extend(c.clone(this.options[s]),p[s]);p.mouse=c.extend(c.clone(this.options.mouse),p.mouse),c.isUndefined(p.shadowSize)&&(p.shadowSize=this.options.shadowSize)}},_setEl:function(a){if(!a)throw"The target container doesn't exist";if(a.graph instanceof Graph)a.graph.destroy();else if(!a.clientWidth)throw"The target container must be visible";a.graph=this,this.el=a}},Flotr.Graph=Graph}(),function(){function c(b){this.orientation=1,this.offset=0,this.datamin=Number.MAX_VALUE,this.datamax=-Number.MAX_VALUE,a.extend(this,b),this._setTranslations()}function d(a){return this.offset+this.orientation*(a-this.min)*this.scale}function e(a){return(this.offset+this.orientation*a)/this.scale+this.min}function f(a){return this.offset+this.orientation*(h(a,this.options.base)-h(this.min,this.options.base))*this.scale}function g(a){return j((this.offset+this.orientation*a)/this.scale+h(this.min,this.options.base),this.options.base)}function h(a,b){return a=Math.log(Math.max(a,Number.MIN_VALUE)),b!==Math.E&&(a/=Math.log(b)),a}function j(a,b){return b===Math.E?Math.exp(a):Math.pow(b,a)}var a=Flotr._,b="logarithmic";c.prototype={setScale:function(){var a=this.length;this.options.scaling==b?this.scale=a/(h(this.max,this.options.base)-h(this.min,this.options.base)):this.scale=a/(this.max-this.min)},calculateTicks:function(){var a=this.options;this.ticks=[],this.minorTicks=[],a.ticks?(this._cleanUserTicks(a.ticks,this.ticks),this._cleanUserTicks(a.minorTicks||[],this.minorTicks)):a.mode=="time"?this._calculateTimeTicks():a.scaling==="logarithmic"?this._calculateLogTicks():this._calculateTicks()},calculateRange:function(){if(!this.used)return;var a=this,b=a.options,c=b.min!==null?b.min:a.datamin,d=b.max!==null?b.max:a.datamax,e=b.autoscaleMargin;b.scaling=="logarithmic"&&(c<=0&&(c=a.datamin),d<=0&&(d=c));if(d==c){var f=d?.01:1;b.min===null&&(c-=f),b.max===null&&(d+=f)}if(b.scaling==="logarithmic"){c<0&&(c=d/b.base);var g=Math.log(d);b.base!=Math.E&&(g/=Math.log(b.base)),g=Math.ceil(g);var h=Math.log(c);b.base!=Math.E&&(h/=Math.log(b.base)),h=Math.ceil(h),a.tickSize=Flotr.getTickSize(b.noTicks,h,g,b.tickDecimals===null?0:b.tickDecimals),b.minorTickFreq===null&&(g-h>10?b.minorTickFreq=0:g-h>5?b.minorTickFreq=2:b.minorTickFreq=5)}else a.tickSize=Flotr.getTickSize(b.noTicks,c,d,b.tickDecimals);a.min=c,a.max=d,b.min===null&&b.autoscale&&(a.min-=a.tickSize*e,a.min<0&&a.datamin>=0&&(a.min=0),a.min=a.tickSize*Math.floor(a.min/a.tickSize)),b.max===null&&b.autoscale&&(a.max+=a.tickSize*e,a.max>0&&a.datamax<=0&&a.datamax!=a.datamin&&(a.max=0),a.max=a.tickSize*Math.ceil(a.max/a.tickSize)),a.min==a.max&&(a.max=a.min+1)},calculateTextDimensions:function(a,b){var c="",d,e;if(this.options.showLabels)for(e=0;e<this.ticks.length;++e)d=this.ticks[e].label.length,d>c.length&&(c=this.ticks[e].label);this.maxLabel=a.dimensions(c,{size:b.fontSize,angle:Flotr.toRad(this.options.labelsAngle)},"font-size:smaller;","flotr-grid-label"),this.titleSize=a.dimensions(this.options.title,{size:b.fontSize*1.2,angle:Flotr.toRad(this.options.titleAngle)},"font-weight:bold;","flotr-axis-title")},_cleanUserTicks:function(b,c){var d=this,e=this.options,f,g,h,i;a.isFunction(b)&&(b=b({min:d.min,max:d.max}));for(g=0;g<b.length;++g)i=b[g],typeof i=="object"?(f=i[0],h=i.length>1?i[1]:e.tickFormatter(f,{min:d.min,max:d.max})):(f=i,h=e.tickFormatter(f,{min:this.min,max:this.max})),c[g]={v:f,label:h}},_calculateTimeTicks:function(){this.ticks=Flotr.Date.generator(this)},_calculateLogTicks:function(){var a=this,b=a.options,c,d,e=Math.log(a.max);b.base!=Math.E&&(e/=Math.log(b.base)),e=Math.ceil(e);var f=Math.log(a.min);b.base!=Math.E&&(f/=Math.log(b.base)),f=Math.ceil(f);for(i=f;i<e;i+=a.tickSize){d=b.base==Math.E?Math.exp(i):Math.pow(b.base,i);var g=d*(b.base==Math.E?Math.exp(a.tickSize):Math.pow(b.base,a.tickSize)),h=(g-d)/b.minorTickFreq;a.ticks.push({v:d,label:b.tickFormatter(d,{min:a.min,max:a.max})});for(c=d+h;c<g;c+=h)a.minorTicks.push({v:c,label:b.tickFormatter(c,{min:a.min,max:a.max})})}d=b.base==Math.E?Math.exp(i):Math.pow(b.base,i),a.ticks.push({v:d,label:b.tickFormatter(d,{min:a.min,max:a.max})})},_calculateTicks:function(){var a=this,b=a.options,c=a.tickSize,d=a.min,e=a.max,f=c*Math.ceil(d/c),g,h,i,j,k,l;b.minorTickFreq&&(h=c/b.minorTickFreq);for(k=0;(i=j=f+k*c)<=e;++k){g=b.tickDecimals,g===null&&(g=1-Math.floor(Math.log(c)/Math.LN10)),g<0&&(g=0),i=i.toFixed(g),a.ticks.push({v:i,label:b.tickFormatter(i,{min:a.min,max:a.max})});if(b.minorTickFreq)for(l=0;l<b.minorTickFreq&&k*c+l*h<e;++l)i=j+l*h,a.minorTicks.push({v:i,label:b.tickFormatter(i,{min:a.min,max:a.max})})}},_setTranslations:function(a){this.d2p=a?f:d,this.p2d=a?g:e}},a.extend(c,{getAxes:function(a){return{x:new c({options:a.xaxis,n:1,length:this.plotWidth}),x2:new c({options:a.x2axis,n:2,length:this.plotWidth}),y:new c({options:a.yaxis,n:1,length:this.plotHeight,offset:this.plotHeight,orientation:-1}),y2:new c({options:a.y2axis,n:2,length:this.plotHeight,offset:this.plotHeight,orientation:-1})}}}),Flotr.Axis=c}(),function(){function b(b){a.extend(this,b)}var a=Flotr._;b.prototype={getRange:function(){var a=this.data,b=a.length,c=Number.MAX_VALUE,d=Number.MAX_VALUE,e=-Number.MAX_VALUE,f=-Number.MAX_VALUE,g=!1,h=!1,i,j,k;if(b<0||this.hide)return!1;for(k=0;k<b;k++)i=a[k][0],j=a[k][1],i<c&&(c=i,g=!0),i>e&&(e=i,g=!0),j<d&&(d=j,h=!0),j>f&&(f=j,h=!0);return{xmin:c,xmax:e,ymin:d,ymax:f,xused:g,yused:h}}},a.extend(b,{getSeries:function(c){return a.map(c,function(c){var d;return c.data?(d=new b,a.extend(d,c)):d=new b({data:c}),d})}}),Flotr.Series=b}(),Flotr.addType("lines",{options:{show:!1,lineWidth:2,fill:!1,fillBorder:!1,fillColor:null,fillOpacity:.4,steps:!1,stacked:!1},stack:{values:[]},draw:function(a){var b=a.context,c=a.lineWidth,d=a.shadowSize,e;b.save(),b.lineJoin="round",d&&(b.lineWidth=d/2,e=c/2+b.lineWidth/2,b.strokeStyle="rgba(0,0,0,0.1)",this.plot(a,e+d/2,!1),b.strokeStyle="rgba(0,0,0,0.2)",this.plot(a,e,!1)),b.lineWidth=c,b.strokeStyle=a.color,this.plot(a,0,!0),b.restore()},plot:function(a,b,c){var d=a.context,e=a.width,f=a.height,g=a.xScale,h=a.yScale,i=a.data,j=a.stacked?this.stack:!1,k=i.length-1,l=null,m=null,n=h(0),o,p,q,r,s,t,u;if(k<1)return;d.beginPath();for(u=0;u<k;++u){if(i[u][1]===null||i[u+1][1]===null)continue;o=g(i[u][0]),p=g(i[u+1][0]),j?(s=j.values[i[u][0]]||0,t=j.values[i[u+1][0]]||j.values[i[u][0]]||0,q=h(i[u][1]+s),r=h(i[u+1][1]+t),c&&(j.values[i[u][0]]=i[u][1]+s,u==k-1&&(j.values[i[u+1][0]]=i[u+1][1]+t))):(q=h(i[u][1]),r=h(i[u+1][1]));if(q>f&&r>f||q<0&&r<0||o<0&&p<0||o>e&&p>e)continue;(l!=o||m!=q+b)&&d.moveTo(o,q+b),l=p,m=r+b,a.steps?(d.lineTo(l+b/2,q+b),d.lineTo(l+b/2,m)):d.lineTo(l,m)}(!a.fill||a.fill&&!a.fillBorder)&&d.stroke(),!b&&a.fill&&(o=g(i[0][0]),d.fillStyle=a.fillStyle,d.lineTo(p,n),d.lineTo(o,n),d.lineTo(o,h(i[0][1])),d.fill(),a.fillBorder&&d.stroke()),d.closePath()},extendYRange:function(a,b,c,d){var e=a.options;if(c.stacked&&(!e.max&&e.max!==0||!e.min&&e.min!==0)){var f=a.max,g=a.min,h=d.positiveSums||{},i=d.negativeSums||{},j,k;for(k=0;k<b.length;k++)j=b[k][0]+"",b[k][1]>0?(h[j]=(h[j]||0)+b[k][1],f=Math.max(f,h[j])):(i[j]=(i[j]||0)+b[k][1],g=Math.min(g,i[j]));d.negativeSums=i,d.positiveSums=h,a.max=f,a.min=g}c.steps&&(this.hit=function(a){var b=a.data,c=a.args,d=a.yScale,e=c[0],f=b.length,g=c[1],h=e.x,i=e.relY,j;for(j=0;j<f-1;j++)if(h>=b[j][0]&&h<=b[j+1][0]){Math.abs(d(b[j][1])-i)<8&&(g.x=b[j][0],g.y=b[j][1],g.index=j,g.seriesIndex=a.index);break}},this.drawHit=function(a){var b=a.context,c=a.args,d=a.data,e=a.xScale,f=c.index,g=e(c.x),h=a.yScale(c.y),i;d.length-1>f&&(i=a.xScale(d[f+1][0]),b.save(),b.strokeStyle=a.color,b.lineWidth=a.lineWidth,b.beginPath(),b.moveTo(g,h),b.lineTo(i,h),b.stroke(),b.closePath(),b.restore())},this.clearHit=function(a){var b=a.context,c=a.args,d=a.data,e=a.xScale,f=a.lineWidth,g=c.index,h=e(c.x),i=a.yScale(c.y),j;d.length-1>g&&(j=a.xScale(d[g+1][0]),b.clearRect(h-f,i-f,j-h+2*f,2*f))})}}),Flotr.addType("bars",{options:{show:!1,lineWidth:2,barWidth:1,fill:!0,fillColor:null,fillOpacity:.4,horizontal:!1,stacked:!1,centered:!0,topPadding:.1},stack:{positive:[],negative:[],_positive:[],_negative:[]},draw:function(a){var b=a.context;b.save(),b.lineJoin="miter",b.lineWidth=a.lineWidth,b.strokeStyle=a.color,a.fill&&(b.fillStyle=a.fillStyle),this.plot(a),b.restore()},plot:function(a){var b=a.data,c=a.context,d=a.shadowSize,e,f,g,h,i,j;if(b.length<1)return;this.translate(c,a.horizontal);for(e=0;e<b.length;e++){f=this.getBarGeometry(b[e][0],b[e][1],a);if(f===null)continue;g=f.left,h=f.top,i=f.width,j=f.height,a.fill&&c.fillRect(g,h,i,j),d&&(c.save(),c.fillStyle="rgba(0,0,0,0.05)",c.fillRect(g+d,h+d,i,j),c.restore()),a.lineWidth&&c.strokeRect(g,h,i,j)}},translate:function(a,b){b&&(a.rotate(-Math.PI/2),a.scale(-1,1))},getBarGeometry:function(a,b,c){var d=c.horizontal,e=c.barWidth,f=c.centered,g=c.stacked?this.stack:!1,h=c.lineWidth,i=f?e/2:0,j=d?c.yScale:c.xScale,k=d?c.xScale:c.yScale,l=d?b:a,m=d?a:b,n=0,o,p,q,r,s;return g&&(o=m>0?g.positive:g.negative,n=o[l]||n,o[l]=n+m),p=j(l-i),q=j(l+e-i),r=k(m+n),s=k(n),s<0&&(s=0),a===null||b===null?null:{x:l,y:m,xScale:j,yScale:k,top:r,left:Math.min(p,q)-h/2,width:Math.abs(q-p)-h,height:s-r}},hit:function(a){var b=a.data,c=a.args,d=c[0],e=c[1],f=d.x,g=d.y,h=this.getBarGeometry(f,g,a),i=h.width/2,j=h.left,k,l;for(l=b.length;l--;)k=this.getBarGeometry(b[l][0],b[l][1],a),k.y>h.y&&Math.abs(j-k.left)<i&&(e.x=b[l][0],e.y=b[l][1],e.index=l,e.seriesIndex=a.index)},drawHit:function(a){var b=a.context,c=a.args,d=this.getBarGeometry(c.x,c.y,a),e=d.left,f=d.top,g=d.width,h=d.height;b.save(),b.strokeStyle=a.color,b.lineWidth=a.lineWidth,this.translate(b,a.horizontal),b.beginPath(),b.moveTo(e,f+h),b.lineTo(e,f),b.lineTo(e+g,f),b.lineTo(e+g,f+h),a.fill&&(b.fillStyle=a.fillStyle,b.fill()),b.stroke(),b.closePath(),b.restore()},clearHit:function(a){var b=a.context,c=a.args,d=this.getBarGeometry(c.x,c.y,a),e=d.left,f=d.width,g=d.top,h=d.height,i=2*a.lineWidth;b.save(),this.translate(b,a.horizontal),b.clearRect(e-i,Math.min(g,g+h)-i,f+2*i,Math.abs(h)+2*i),b.restore()},extendXRange:function(a,b,c,d){this._extendRange(a,b,c,d)},extendYRange:function(a,b,c,d){this._extendRange(a,b,c,d)},_extendRange:function(a,b,c,d){var e=a.options.max;if(_.isNumber(e)||_.isString(e))return;var f=a.min,g=a.max,h=c.horizontal,i=a.orientation,j=this.positiveSums||{},k=this.negativeSums||{},l,m,n,o;(i==1&&!h||i==-1&&h)&&c.centered&&(g=Math.max(a.datamax+c.barWidth,g),f=Math.min(a.datamin-c.barWidth,f));if(c.stacked&&(i==1&&h||i==-1&&!h))for(o=b.length;o--;)l=b[o][i==1?1:0]+"",m=b[o][i==1?0:1],m>0?(j[l]=(j[l]||0)+m,g=Math.max(g,j[l])):(k[l]=(k[l]||0)+m,f=Math.min(f,k[l]));(i==1&&h||i==-1&&!h)&&c.topPadding&&(a.max===a.datamax||c.stacked&&this.stackMax!==g)&&(g+=c.topPadding*(g-f)),this.stackMin=f,this.stackMax=g,this.negativeSums=k,this.positiveSums=j,a.max=g,a.min=f}}),Flotr.addType("bubbles",{options:{show:!1,lineWidth:2,fill:!0,fillOpacity:.4,baseRadius:2},draw:function(a){var b=a.context,c=a.shadowSize;b.save(),b.lineWidth=a.lineWidth,b.fillStyle="rgba(0,0,0,0.05)",b.strokeStyle="rgba(0,0,0,0.05)",this.plot(a,c/2),b.strokeStyle="rgba(0,0,0,0.1)",this.plot(a,c/4),b.strokeStyle=a.color,b.fillStyle=a.fillStyle,this.plot(a),b.restore()},plot:function(a,b){var c=a.data,d=a.context,e,f,g,h,i;b=b||0;for(f=0;f<c.length;++f)e=this.getGeometry(c[f],a),d.beginPath(),d.arc(e.x+b,e.y+b,e.z,0,2*Math.PI,!0),d.stroke(),a.fill&&d.fill(),d.closePath()},getGeometry:function(a,b){return{x:b.xScale(a[0]),y:b.yScale(a[1]),z:a[2]*b.baseRadius}},hit:function(a){var b=a.data,c=a.args,d=c[0],e=c[1],f=d.x,g=d.y,h,j,k;for(i=b.length;i--;)h=this.getGeometry(b[i],a),j=h.x-a.xScale(f),k=h.y-a.yScale(g),Math.sqrt(j*j+k*k)<h.z&&(e.x=b[i][0],e.y=b[i][1],e.index=i,e.seriesIndex=a.index)},drawHit:function(a){var b=a.context,c=this.getGeometry(a.data[a.args.index],a);b.save(),b.lineWidth=a.lineWidth,b.fillStyle=a.fillStyle,b.strokeStyle=a.color,b.beginPath(),b.arc(c.x,c.y,c.z,0,2*Math.PI,!0),b.fill(),b.stroke(),b.closePath(),b.restore()},clearHit:function(a){var b=a.context,c=this.getGeometry(a.data[a.args.index],a),d=c.z+a.lineWidth;b.save(),b.clearRect(c.x-d,c.y-d,2*d,2*d),b.restore()}}),Flotr.addType("candles",{options:{show:!1,lineWidth:1,wickLineWidth:1,candleWidth:.6,fill:!0,upFillColor:"#00A8F0",downFillColor:"#CB4B4B",fillOpacity:.5,barcharts:!1},draw:function(a){var b=a.context;b.save(),b.lineJoin="miter",b.lineCap="butt",b.lineWidth=a.wickLineWidth||a.lineWidth,this.plot(a),b.restore()},plot:function(a){var b=a.data,c=a.context,d=a.xScale,e=a.yScale,f=a.candleWidth/2,g=a.shadowSize,h=a.lineWidth,i=a.wickLineWidth,j=i%2/2,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y;if(b.length<1)return;for(y=0;y<b.length;y++){l=b[y],m=l[0],o=l[1],p=l[2],q=l[3],r=l[4],s=d(m-f),t=d(m+f),u=e(q),v=e(p),w=e(Math.min(o,r)),x=e(Math.max(o,r)),k=a[o>r?"downFillColor":"upFillColor"],a.fill&&!a.barcharts&&(c.fillStyle="rgba(0,0,0,0.05)",c.fillRect(s+g,x+g,t-s,w-x),c.save(),c.globalAlpha=a.fillOpacity,c.fillStyle=k,c.fillRect(s,x+h,t-s,w-x),c.restore());if(h||i)m=Math.floor((s+t)/2)+j,c.strokeStyle=k,c.beginPath(),a.barcharts?(c.moveTo(m,Math.floor(v+f)),c.lineTo(m,Math.floor(u+f)),n=Math.floor(o+f)+.5,c.moveTo(Math.floor(s)+j,n),c.lineTo(m,n),n=Math.floor(r+f)+.5,c.moveTo(Math.floor(t)+j,n),c.lineTo(m,n)):(c.strokeRect(s,x+h,t-s,w-x),c.moveTo(m,Math.floor(x+h)),c.lineTo(m,Math.floor(v+h)),c.moveTo(m,Math.floor(w+h)),c.lineTo(m,Math.floor(u+h))),c.closePath(),c.stroke()}},extendXRange:function(a,b,c){a.options.max===null&&(a.max=Math.max(a.datamax+.5,a.max),a.min=Math.min(a.datamin-.5,a.min))}}),Flotr.addType("gantt",{options:{show:!1,lineWidth:2,barWidth:1,fill:!0,fillColor:null,fillOpacity:.4,centered:!0},draw:function(a){var b=this.ctx,c=a.gantt.barWidth,d=Math.min(a.gantt.lineWidth,c);b.save(),b.translate(this.plotOffset.left,this.plotOffset.top),b.lineJoin="miter",b.lineWidth=d,b.strokeStyle=a.color,b.save(),this.gantt.plotShadows(a,c,0,a.gantt.fill),b.restore();if(a.gantt.fill){var e=a.gantt.fillColor||a.color;b.fillStyle=this.processColor(e,{opacity:a.gantt.fillOpacity})}this.gantt.plot(a,c,0,a.gantt.fill),b.restore()},plot:function(a,b,c,d){var e=a.data;if(e.length<1)return;var f=a.xaxis,g=a.yaxis,h=this.ctx,i;for(i=0;i<e.length;i++){var j=e[i][0],k=e[i][1],l=e[i][2],m=!0,n=!0,o=!0;if(k===null||l===null)continue;var p=k,q=k+l,r=j-(a.gantt.centered?b/2:0),s=j+b-(a.gantt.centered?b/2:0);if(q<f.min||p>f.max||s<g.min||r>g.max)continue;p<f.min&&(p=f.min,m=!1),q>f.max&&(q=f.max,f.lastSerie!=a&&(n=!1)),r<g.min&&(r=g.min),s>g.max&&(s=g.max,g.lastSerie!=a&&(n=!1)),d&&(h.beginPath(),h.moveTo(f.d2p(p),g.d2p(r)+c),h.lineTo(f.d2p(p),g.d2p(s)+c),h.lineTo(f.d2p(q),g.d2p(s)+c),h.lineTo(f.d2p(q),g.d2p(r)+c),h.fill(),h.closePath()),a.gantt.lineWidth&&(m||o||n)&&(h.beginPath(),h.moveTo(f.d2p(p),g.d2p(r)+c),h[m?"lineTo":"moveTo"](f.d2p(p),g.d2p(s)+c),h[n?"lineTo":"moveTo"](f.d2p(q),g.d2p(s)+c),h[o?"lineTo":"moveTo"](f.d2p(q),g.d2p(r)+c),h.stroke(),h.closePath())}},plotShadows:function(a,b,c){var d=a.data;if(d.length<1)return;var e,f,g,h,i=a.xaxis,j=a.yaxis,k=this.ctx,l=this.options.shadowSize;for(e=0;e<d.length;e++){f=d[e][0],g=d[e][1],h=d[e][2];if(g===null||h===null)continue;var m=g,n=g+h,o=f-(a.gantt.centered?b/2:0),p=f+b-(a.gantt.centered?b/2:0);if(n<i.min||m>i.max||p<j.min||o>j.max)continue;m<i.min&&(m=i.min),n>i.max&&(n=i.max),o<j.min&&(o=j.min),p>j.max&&(p=j.max);var q=i.d2p(n)-i.d2p(m)-(i.d2p(n)+l<=this.plotWidth?0:l),r=j.d2p(o)-j.d2p(p)-(j.d2p(o)+l<=this.plotHeight?0:l);k.fillStyle="rgba(0,0,0,0.05)",k.fillRect(Math.min(i.d2p(m)+l,this.plotWidth),Math.min(j.d2p(p)+l,this.plotHeight),q,r)}},extendXRange:function(a){if(a.options.max===null){var b=a.min,c=a.max,d,e,f,g,h,i={},j={},k=null;for(d=0;d<this.series.length;++d){g=this.series[d],h=g.gantt;if(h.show&&g.xaxis==a){for(e=0;e<g.data.length;e++)h.show&&(y=g.data[e][0]+"",i[y]=Math.max(i[y]||0,g.data[e][1]+g.data[e][2]),k=g);for(e in i)c=Math.max(i[e],c)}}a.lastSerie=k,a.max=c,a.min=b}},extendYRange:function(a){if(a.options.max===null){var b=Number.MIN_VALUE,c=Number.MAX_VALUE,d,e,f,g,h={},i={},j=null;for(d=0;d<this.series.length;++d){f=this.series[d],g=f.gantt;if(g.show&&!f.hide&&f.yaxis==a){var k=Number.MIN_VALUE,l=Number.MAX_VALUE;for(e=0;e<f.data.length;e++)k=Math.max(k,f.data[e][0]),l=Math.min(l,f.data[e][0]);g.centered?(b=Math.max(k+.5,b),c=Math.min(l-.5,c)):(b=Math.max(k+1,b),c=Math.min(l,c)),g.barWidth+k>b&&(b=a.max+g.barWidth)}}a.lastSerie=j,a.max=b,a.min=c,a.tickSize=Flotr.getTickSize(a.options.noTicks,c,b,a.options.tickDecimals)}}}),function(){function a(a){return typeof a=="object"&&a.constructor&&(Image?!0:a.constructor===Image)}Flotr.defaultMarkerFormatter=function(a){return Math.round(a.y*100)/100+""},Flotr.addType("markers",{options:{show:!1,lineWidth:1,color:"#000000",fill:!1,fillColor:"#FFFFFF",fillOpacity:.4,stroke:!1,position:"ct",verticalMargin:0,labelFormatter:Flotr.defaultMarkerFormatter,fontSize:Flotr.defaultOptions.fontSize,stacked:!1,stackingType:"b",horizontal:!1},stack:{positive:[],negative:[],values:[]},draw:function(a){function m(a,b){return g=d.negative[a]||0,f=d.positive[a]||0,b>0?(d.positive[a]=g+b,g+b):(d.negative[a]=f+b,f+b)}var b=a.data,c=a.context,d=a.stacked?a.stack:!1,e=a.stackingType,f,g,h,i,j,k,l;c.save(),c.lineJoin="round",c.lineWidth=a.lineWidth,c.strokeStyle="rgba(0,0,0,0.5)",c.fillStyle=a.fillStyle;for(i=0;i<b.length;++i)j=b[i][0],k=b[i][1],d&&(e=="b"?a.horizontal?k=m(k,j):j=m(j,k):e=="a"&&(h=d.values[j]||0,d.values[j]=h+k,k=h+k)),l=a.labelFormatter({x:j,y:k,index:i,data:b}),this.plot(a.xScale(j),a.yScale(k),l,a);c.restore()},plot:function(b,c,d,e){var f=e.context;if(a(d)&&!d.complete)throw"Marker image not loaded.";this._plot(b,c,d,e)},_plot:function(b,c,d,e){var f=e.context,g=2,h=b,i=c,j;a(d)?j={height:d.height,width:d.width}:j=e.text.canvas(d),j.width=Math.floor(j.width+g*2),j.height=Math.floor(j.height+g*2),e.position.indexOf("c")!=-1?h-=j.width/2+g:e.position.indexOf("l")!=-1&&(h-=j.width),e.position.indexOf("m")!=-1?i-=j.height/2+g:e.position.indexOf("t")!=-1?i-=j.height+e.verticalMargin:i+=e.verticalMargin,h=Math.floor(h)+.5,i=Math.floor(i)+.5,e.fill&&f.fillRect(h,i,j.width,j.height),e.stroke&&f.strokeRect(h,i,j.width,j.height),a(d)?f.drawImage(d,h+g,i+g):Flotr.drawText(f,d,h+g,i+g,{textBaseline:"top",textAlign:"left",size:e.fontSize,color:e.color})}})}(),function(){var a=Flotr._;Flotr.defaultPieLabelFormatter=function(a,b){return(100*b/a).toFixed(2)+"%"},Flotr.addType("pie",{options:{show:!1,lineWidth:1,fill:!0,fillColor:null,fillOpacity:.6,explode:6,sizeRatio:.6,startAngle:Math.PI/4,labelFormatter:Flotr.defaultPieLabelFormatter,pie3D:!1,pie3DviewAngle:Math.PI/2*.8,pie3DspliceThickness:20},draw:function(a){var b=a.data,c=a.context,d=c.canvas,e=a.lineWidth,f=a.shadowSize,g=a.sizeRatio,h=a.height,i=a.width,j=a.explode,k=a.color,l=a.fill,m=a.fillStyle,n=Math.min(d.width,d.height)*g/2,o=b[0][1],p=[],q=1,r=Math.PI*2*o/this.total,s=this.startAngle||2*Math.PI*a.startAngle,t=s+r,u=s+r/2,v=a.labelFormatter(this.total,o),w=j+n+4,x=Math.cos(u)*w,y=Math.sin(u)*w,z=x<0?"right":"left",A=y>0?"top":"bottom",B,C,D,x,y;c.save(),c.translate(i/2,h/2),c.scale(1,q),C=Math.cos(u)*j,D=Math.sin(u)*j,f>0&&(this.plotSlice(C+f,D+f,n,s,t,c),l&&(c.fillStyle="rgba(0,0,0,0.1)",c.fill())),this.plotSlice(C,D,n,s,t,c),l&&(c.fillStyle=m,c.fill()),c.lineWidth=e,c.strokeStyle=k,c.stroke(),B={size:a.fontSize*1.2,color:a.fontColor,weight:1.5},v&&(a.htmlText||!a.textEnabled?(divStyle="position:absolute;"+A+":"+(h/2+(A==="top"?y:-y))+"px;",divStyle+=z+":"+(i/2+(z==="right"?-x:x))+"px;",p.push('<div style="',divStyle,'" class="flotr-grid-label">',v,"</div>")):(B.textAlign=z,B.textBaseline=A,Flotr.drawText(c,v,x,y,B)));if(a.htmlText||!a.textEnabled){var E=Flotr.DOM.node('<div style="color:'+a.fontColor+'" class="flotr-labels"></div>');Flotr.DOM.insert(E,p.join("")),Flotr.DOM.insert(a.element,E)}c.restore(),this.startAngle=t,this.slices=this.slices||[],this.slices.push({radius:Math.min(d.width,d.height)*g/2,x:C,y:D,explode:j,start:s,end:t})},plotSlice:function(a,b,c,d,e,f){f.beginPath(),f.moveTo(a,b),f.arc(a,b,c,d,e,!1),f.lineTo(a,b),f.closePath()},hit:function(a){var b=a.data[0],c=a.args,d=a.index,e=c[0],f=c[1],g=this.slices[d],h=e.relX-a.width/2,i=e.relY-a.height/2,j=Math.sqrt(h*h+i*i),k=Math.atan(i/h),l=Math.PI*2,m=g.explode||a.explode,n=g.start%l,o=g.end%l;h<0?k+=Math.PI:h>0&&i<0&&(k+=l),j<g.radius+m&&j>m&&(n>o&&(k<o||k>n)||k>n&&k<o)&&(f.x=b[0],f.y=b[1],f.sAngle=n,f.eAngle=o,f.index=0,f.seriesIndex=d,f.fraction=b[1]/this.total)},drawHit:function(a){var b=a.context,c=this.slices[a.args.seriesIndex];b.save(),b.translate(a.width/2,a.height/2),this.plotSlice(c.x,c.y,c.radius,c.start,c.end,b),b.stroke(),b.restore()},clearHit:function(a){var b=a.context,c=this.slices[a.args.seriesIndex],d=2*a.lineWidth,e=c.radius+d;b.save(),b.translate(a.width/2,a.height/2),b.clearRect(c.x-e,c.y-e,2*e+d,2*e+d),b.restore()},extendYRange:function(a,b){this.total=(this.total||0)+b[0][1]}})}(),Flotr.addType("points",{options:{show:!1,radius:3,lineWidth:2,fill:!0,fillColor:"#FFFFFF",fillOpacity:.4},draw:function(a){var b=a.context,c=a.lineWidth,d=a.shadowSize;b.save(),d>0&&(b.lineWidth=d/2,b.strokeStyle="rgba(0,0,0,0.1)",this.plot(a,d/2+b.lineWidth/2),b.strokeStyle="rgba(0,0,0,0.2)",this.plot(a,b.lineWidth/2)),b.lineWidth=a.lineWidth,b.strokeStyle=a.color,b.fillStyle=a.fillColor||a.color,this.plot(a),b.restore()},plot:function(a,b){var c=a.data,d=a.context,e=a.xScale,f=a.yScale,g,h,i;for(g=c.length-1;g>-1;--g){i=c[g][1];if(i===null)continue;h=e(c[g][0]),i=f(i);if(h<0||h>a.width||i<0||i>a.height)continue;d.beginPath(),b?d.arc(h,i+b,a.radius,0,Math.PI,!1):(d.arc(h,i,a.radius,0,2*Math.PI,!0),a.fill&&d.fill()),d.stroke(),d.closePath()}}}),Flotr.addType("radar",{options:{show:!1,lineWidth:2,fill:!0,fillOpacity:.4,radiusRatio:.9},draw:function(a){var b=a.context,c=a.shadowSize;b.save(),b.translate(a.width/2,a.height/2),b.lineWidth=a.lineWidth,b.fillStyle="rgba(0,0,0,0.05)",b.strokeStyle="rgba(0,0,0,0.05)",this.plot(a,c/2),b.strokeStyle="rgba(0,0,0,0.1)",this.plot(a,c/4),b.strokeStyle=a.color,b.fillStyle=a.fillStyle,this.plot(a),b.restore()},plot:function(a,b){var c=a.data,d=a.context,e=Math.min(a.height,a.width)*a.radiusRatio/2,f=2*Math.PI/c.length,g=-Math.PI/2,h,i;b=b||0,d.beginPath();for(h=0;h<c.length;++h)i=c[h][1]/this.max,d[h===0?"moveTo":"lineTo"](Math.cos(h*f+g)*e*i+b,Math.sin(h*f+g)*e*i+b);d.closePath(),a.fill&&d.fill(),d.stroke()},extendYRange:function(a,b){this.max=Math.max(a.max,this.max||-Number.MAX_VALUE)}}),Flotr.addType("timeline",{options:{show:!1,lineWidth:1,barWidth:.2,fill:!0,fillColor:null,fillOpacity:.4,centered:!0},draw:function(a){var b=a.context;b.save(),b.lineJoin="miter",b.lineWidth=a.lineWidth,b.strokeStyle=a.color,b.fillStyle=a.fillStyle,this.plot(a),b.restore()},plot:function(a){var b=a.data,c=a.context,d=a.xScale,e=a.yScale,f=a.barWidth,g=a.lineWidth,h;Flotr._.each(b,function(a){var b=a[0],h=a[1],i=a[2],j=f,k=Math.ceil(d(b)),l=Math.ceil(d(b+i))-k,m=Math.round(e(h)),n=Math.round(e(h-j))-m,o=k-g/2,p=Math.round(m-n/2)-g/2;c.strokeRect(o,p,l,n),c.fillRect(o,p,l,n)})},extendRange:function(a){var b=a.data,c=a.xaxis,d=a.yaxis,e=a.timeline.barWidth;c.options.min===null&&(c.min=c.datamin-e/2);if(c.options.max===null){var f=c.max;Flotr._.each(b,function(a){f=Math.max(f,a[0]+a[2])},this),c.max=f+e/2}d.options.min===null&&(d.min=d.datamin-e),d.options.min===null&&(d.max=d.datamax+e)}}),function(){var a=Flotr.DOM;Flotr.addPlugin("crosshair",{options:{mode:null,color:"#FF0000",hideCursor:!0},callbacks:{"flotr:mousemove":function(a,b){this.options.crosshair.mode&&(this.crosshair.clearCrosshair(),this.crosshair.drawCrosshair(b))}},drawCrosshair:function(b){var c=this.octx,d=this.options.crosshair,e=this.plotOffset,f=e.left+b.relX+.5,g=e.top+b.relY+.5;if(b.relX<0||b.relY<0||b.relX>this.plotWidth||b.relY>this.plotHeight){this.el.style.cursor=null,a.removeClass(this.el,"flotr-crosshair");return}d.hideCursor&&(this.el.style.cursor="none",a.addClass(this.el,"flotr-crosshair")),c.save(),c.strokeStyle=d.color,c.lineWidth=1,c.beginPath(),d.mode.indexOf("x")!=-1&&(c.moveTo(f,e.top),c.lineTo(f,e.top+this.plotHeight)),d.mode.indexOf("y")!=-1&&(c.moveTo(e.left,g),c.lineTo(e.left+this.plotWidth,g)),c.stroke(),c.restore()},clearCrosshair:function(){var a=this.plotOffset,b=this.lastMousePos,c=this.octx;b&&(c.clearRect(b.relX+a.left,a.top,1,this.plotHeight+1),c.clearRect(a.left,b.relY+a.top,this.plotWidth+1,1))}})}(),function(){function c(a,b,c,d){var e="image/"+a,f=b.toDataURL(e),g=new Image;return g.src=f,g}var a=Flotr.DOM,b=Flotr._;Flotr.addPlugin("download",{saveImage:function(d,e,f,g){var h=null;if(Flotr.isIE&&Flotr.isIE<9)return h="<html><body>"+this.canvas.firstChild.innerHTML+"</body></html>",window.open().document.write(h);if(d!=="jpeg"&&d!=="png")return;h=c(d,this.canvas,e,f);if(b.isElement(h)&&g)this.download.restoreCanvas(),a.hide(this.canvas),a.hide(this.overlay),a.setStyles({position:"absolute"}),a.insert(this.el,h),this.saveImageElement=h;else return window.open(h.src)},restoreCanvas:function(){a.show(this.canvas),a.show(this.overlay),this.saveImageElement&&this.el.removeChild(this.saveImageElement),this.saveImageElement=null}})}(),function(){var a=Flotr.EventAdapter,b=Flotr._;Flotr.addPlugin("graphGrid",{callbacks:{"flotr:beforedraw":function(){this.graphGrid.drawGrid()},"flotr:afterdraw":function(){this.graphGrid.drawOutline()}},drawGrid:function(){function p(a){for(n=0;n<a.length;++n){var b=a[n].v/l.max;for(o=0;o<=u;++o)c[o===0?"moveTo":"lineTo"](Math.cos(o*v+w)*t*b,Math.sin(o*v+w)*t*b)}}function q(a,d){b.each(b.pluck(a,"v"),function(a){if(a<=l.min||a>=l.max||(a==l.min||a==l.max)&&e.outlineWidth)return;d(Math.floor(l.d2p(a))+c.lineWidth/2)})}function r(a){c.moveTo(a,0),c.lineTo(a,j)}function s(a){c.moveTo(0,a),c.lineTo(k,a)}var c=this.ctx,d=this.options,e=d.grid,f=e.verticalLines,g=e.horizontalLines,h=e.minorVerticalLines,i=e.minorHorizontalLines,j=this.plotHeight,k=this.plotWidth,l,m,n,o;(f||h||g||i)&&a.fire(this.el,"flotr:beforegrid",[this.axes.x,this.axes.y,d,this]),c.save(),c.lineWidth=1,c.strokeStyle=e.tickColor;if(e.circular){c.translate(this.plotOffset.left+k/2,this.plotOffset.top+j/2);var t=Math.min(j,k)*d.radar.radiusRatio/2,u=this.axes.x.ticks.length,v=2*(Math.PI/u),w=-Math.PI/2;c.beginPath(),l=this.axes.y,g&&p(l.ticks),i&&p(l.minorTicks),f&&b.times(u,function(a){c.moveTo(0,0),c.lineTo(Math.cos(a*v+w)*t,Math.sin(a*v+w)*t)}),c.stroke()}else c.translate(this.plotOffset.left,this.plotOffset.top),e.backgroundColor&&(c.fillStyle=this.processColor(e.backgroundColor,{x1:0,y1:0,x2:k,y2:j}),c.fillRect(0,0,k,j)),c.beginPath(),l=this.axes.x,f&&q(l.ticks,r),h&&q(l.minorTicks,r),l=this.axes.y,g&&q(l.ticks,s),i&&q(l.minorTicks,s),c.stroke();c.restore(),(f||h||g||i)&&a.fire(this.el,"flotr:aftergrid",[this.axes.x,this.axes.y,d,this])},drawOutline:function(){var a=this,b=a.options,c=b.grid,d=c.outline,e=a.ctx,f=c.backgroundImage,g=a.plotOffset,h=g.left,j=g.top,k=a.plotWidth,l=a.plotHeight,m,n,o,p,q,r;if(!c.outlineWidth)return;e.save();if(c.circular){e.translate(h+k/2,j+l/2);var s=Math.min(l,k)*b.radar.radiusRatio/2,t=this.axes.x.ticks.length,u=2*(Math.PI/t),v=-Math.PI/2;e.beginPath(),e.lineWidth=c.outlineWidth,e.strokeStyle=c.color,e.lineJoin="round";for(i=0;i<=t;++i)e[i===0?"moveTo":"lineTo"](Math.cos(i*u+v)*s,Math.sin(i*u+v)*s);e.stroke()}else{e.translate(h,j);var w=c.outlineWidth,x=.5-w+(w+1)%2/2,y="lineTo",z="moveTo";e.lineWidth=w,e.strokeStyle=c.color,e.lineJoin="miter",e.beginPath(),e.moveTo(x,x),k-=w/2%1,l+=w/2,e[d.indexOf("n")!==-1?y:z](k,x),e[d.indexOf("e")!==-1?y:z](k,l),e[d.indexOf("s")!==-1?y:z](x,l),e[d.indexOf("w")!==-1?y:z](x,x),e.stroke(),e.closePath()}e.restore(),f&&(o=f.src||f,p=(parseInt(f.left,10)||0)+g.left,q=(parseInt(f.top,10)||0)+g.top,n=new Image,n.onload=function(){e.save(),f.alpha&&(e.globalAlpha=f.alpha),e.globalCompositeOperation="destination-over",e.drawImage(n,0,0,n.width,n.height,p,q,k,l),e.restore()},n.src=o)}})}(),function(){var a=Flotr.DOM,b=Flotr._,c=Flotr,d="opacity:0.7;background-color:#000;color:#fff;display:none;position:absolute;padding:2px 8px;-moz-border-radius:4px;border-radius:4px;white-space:nowrap;";Flotr.addPlugin("hit",{callbacks:{"flotr:mousemove":function(a,b){this.hit.track(b)},"flotr:click":function(a){this.hit.track(a)},"flotr:mouseout":function(){this.hit.clearHit()}},track:function(a){(this.options.mouse.track||b.any(this.series,function(a){return a.mouse&&a.mouse.track}))&&this.hit.hit(a)},executeOnType:function(a,d,e){function h(a,h){b.each(b.keys(c.graphTypes),function(b){a[b]&&a[b].show&&this[b][d]&&(g=this.getOptions(a,b),g.fill=!!a.mouse.fillColor,g.fillStyle=this.processColor(a.mouse.fillColor||"#ffffff",{opacity:a.mouse.fillOpacity}),g.color=a.mouse.lineColor,g.context=this.octx,g.index=h,e&&(g.args=e),this[b][d].call(this[b],g),f=!0)},this)}var f=!1,g;return b.isArray(a)||(a=[a]),b.each(a,h,this),f},drawHit:function(a){var b=this.octx,c=a.series;if(c.mouse.lineColor){b.save(),b.lineWidth=c.points?c.points.lineWidth:1,b.strokeStyle=c.mouse.lineColor,b.fillStyle=this.processColor(c.mouse.fillColor||"#ffffff",{opacity:c.mouse.fillOpacity}),b.translate(this.plotOffset.left,this.plotOffset.top);if(!this.hit.executeOnType(c,"drawHit",a)){var d=a.xaxis,e=a.yaxis;b.beginPath(),b.arc(d.d2p(a.x),e.d2p(a.y),c.points.radius||c.mouse.radius,0,2*Math.PI,!0),b.fill(),b.stroke(),b.closePath()}b.restore()}this.prevHit=a},clearHit:function(){var b=this.prevHit,c=this.octx,d=this.plotOffset;c.save(),c.translate(d.left,d.top);if(b){if(!this.hit.executeOnType(b.series,"clearHit",this.prevHit)){var e=b.series,f=e.points?e.points.lineWidth:1;offset=(e.points.radius||e.mouse.radius)+f,c.clearRect(b.xaxis.d2p(b.x)-offset,b.yaxis.d2p(b.y)-offset,offset*2,offset*2)}a.hide(this.mouseTrack),this.prevHit=null}c.restore()},hit:function(a){var c=this.options,d=this.prevHit,e,f,g,h,i,j,k,l;if(this.series.length===0)return;n={relX:a.relX,relY:a.relY,absX:a.absX,absY:a.absY};if(c.mouse.trackY&&!c.mouse.trackAll&&this.hit.executeOnType(this.series,"hit",[a,n]))b.isUndefined(n.seriesIndex)||(i=this.series[n.seriesIndex],n.series=i,n.mouse=i.mouse,n.xaxis=i.xaxis,n.yaxis=i.yaxis);else{e=this.hit.closest(a);if(e){e=c.mouse.trackY?e.point:e.x,h=e.seriesIndex,i=this.series[h],k=i.xaxis,l=i.yaxis,f=2*i.mouse.sensibility;if(c.mouse.trackAll||e.distanceX<f/k.scale&&(!c.mouse.trackY||e.distanceY<f/l.scale))n.series=i,n.xaxis=i.xaxis,n.yaxis=i.yaxis,n.mouse=i.mouse,n.x=e.x,n.y=e.y,n.dist=e.distance,n.index=e.dataIndex,n.seriesIndex=h}}if(!d||d.index!==n.index||d.seriesIndex!==n.seriesIndex)this.hit.clearHit(),n.series&&n.mouse&&n.mouse.track&&(this.hit.drawMouseTrack(n),this.hit.drawHit(n),Flotr.EventAdapter.fire(this.el,"flotr:hit",[n,this]))},closest:function(a){function t(a){a.distance=m,a.distanceX=n,a.distanceY=o,a.seriesIndex=r,a.dataIndex=s,a.x=p,a.y=q}var b=this.series,c=this.options,d=a.x,e=a.y,f=Number.MAX_VALUE,g=Number.MAX_VALUE,h={},i={},j=!1,k,l,m,n,o,p,q,r,s;for(r=0;r<b.length;r++){k=b[r],l=k.data,l.length&&(j=!0);for(s=l.length;s--;){p=l[s][0],q=l[s][1];if(p===null||q===null)continue;if(p<k.xaxis.min||p>k.xaxis.max)continue;n=Math.abs(p-d),o=Math.abs(q-e),m=n*n+o*o,m<f&&(f=m,t(h)),n<g&&(g=n,t(i))}}return j?{point:h,x:i}:!1},drawMouseTrack:function(b){var c="",e=b.series,f=b.mouse.position,g=b.mouse.margin,h=d,i=this.mouseTrack,j=this.plotOffset,k=j.left,l=j.right,m=j.bottom,n=j.top,o=b.mouse.trackDecimals,p=this.options;i||(i=a.node('<div class="flotr-mouse-value"></div>'),this.mouseTrack=i,a.insert(this.el,i));if(!b.mouse.relative)f.charAt(0)=="n"?c+="top:"+(g+n)+"px;bottom:auto;":f.charAt(0)=="s"&&(c+="bottom:"+(g+m)+"px;top:auto;"),f.charAt(1)=="e"?c+="right:"+(g+l)+"px;left:auto;":f.charAt(1)=="w"&&(c+="left:"+(g+k)+"px;right:auto;");else if(e.bars.show)c+="bottom:"+(g-n-b.yaxis.d2p(b.y/2)+this.canvasHeight)+"px;top:auto;",c+="left:"+(g+k+b.xaxis.d2p(b.x-p.bars.barWidth/2))+"px;right:auto;";else if(e.pie.show){var q={x:this.plotWidth/2,y:this.plotHeight/2},r=Math.min(this.canvasWidth,this.canvasHeight)*e.pie.sizeRatio/2,s=b.sAngle<b.eAngle?(b.sAngle+b.eAngle)/2:(b.sAngle+b.eAngle+2*Math.PI)/2;c+="bottom:"+(g-n-q.y-Math.sin(s)*r/2+this.canvasHeight)+"px;top:auto;",c+="left:"+(g+k+q.x+Math.cos(s)*r/2)+"px;right:auto;"}else f.charAt(0)=="n"?c+="bottom:"+(g-n-b.yaxis.d2p(b.y)+this.canvasHeight)+"px;top:auto;":f.charAt(0)=="s"&&(c+="top:"+(g+n+b.yaxis.d2p(b.y))+"px;bottom:auto;"),f.charAt(1)=="e"?c+="left:"+(g+k+b.xaxis.d2p(b.x))+"px;right:auto;":f.charAt(1)=="w"&&(c+="right:"+(g-k-b.xaxis.d2p(b.x)+this.canvasWidth)+"px;left:auto;");h+=c,i.style.cssText=h;if(!o||o<0)o=0;i.innerHTML=b.mouse.trackFormatter({x:b.x.toFixed(o),y:b.y.toFixed(o),series:b.series,index:b.index,nearest:b,fraction:b.fraction}),a.show(i)}})}(),function(){function a(a,b){return a.which?a.which===1:a.button===0||a.button===1}function b(a,b){return Math.min(Math.max(0,a),b.plotWidth-1)}function c(a,b){return Math.min(Math.max(0,a),b.plotHeight)}var d=Flotr.DOM,e=Flotr.EventAdapter,f=Flotr._;Flotr.addPlugin("selection",{options:{pinchOnly:null,mode:null,color:"#B6D9FF",fps:20},callbacks:{"flotr:mouseup":function(a){var b=this.options.selection,c=this.selection,d=this.getEventPosition(a);if(!b||!b.mode)return;c.interval&&clearInterval(c.interval),this.multitouches?c.updateSelection():b.pinchOnly||c.setSelectionPos(c.selection.second,d),c.clearSelection(),c.selecting&&c.selectionIsSane()&&(c.drawSelection(),c.fireSelectEvent(),this.ignoreClick=!0)},"flotr:mousedown":function(b){var c=this.options.selection,d=this.selection,e=this.getEventPosition(b);if(!c||!c.mode)return;if(!c.mode||!a(b)&&f.isUndefined(b.touches))return;c.pinchOnly||d.setSelectionPos(d.selection.first,e),d.interval&&clearInterval(d.interval),this.lastMousePos.pageX=null,d.selecting=!1,d.interval=setInterval(f.bind(d.updateSelection,this),1e3/c.fps)},"flotr:destroy":function(a){clearInterval(this.selection.interval)}},getArea:function(){var a=this.selection.selection,b=a.first,c=a.second;return{x1:Math.min(b.x,c.x),x2:Math.max(b.x,c.x),y1:Math.min(b.y,c.y),y2:Math.max(b.y,c.y)}},selection:{first:{x:-1,y:-1},second:{x:-1,y:-1}},prevSelection:null,interval:null,fireSelectEvent:function(a){var b=this.axes,c=this.selection.selection,d,f,g,h;a=a||"select",d=b.x.p2d(c.first.x),f=b.x.p2d(c.second.x),g=b.y.p2d(c.first.y),h=b.y.p2d(c.second.y),e.fire(this.el,"flotr:"+a,[{x1:Math.min(d,f),y1:Math.min(g,h),x2:Math.max(d,f),y2:Math.max(g,h),xfirst:d,xsecond:f,yfirst:g,ysecond:h},this])},setSelection:function(a,d){var e=this.options,f=this.axes.x,g=this.axes.y,h=g.scale,i=f.scale,j=e.selection.mode.indexOf("x")!=-1,k=e.selection.mode.indexOf("y")!=-1,l=this.selection.selection;this.selection.clearSelection(),l.first.y=c(j&&!k?0:(g.max-a.y1)*h,this),l.second.y=c(j&&!k?this.plotHeight-1:(g.max-a.y2)*h,this),l.first.x=b(k&&!j?0:a.x1,this),l.second.x=b(k&&!j?this.plotWidth:a.x2,this),this.selection.drawSelection(),d||this.selection.fireSelectEvent()},setSelectionPos:function(a,d){var e=this.options.selection.mode,f=this.selection.selection;e.indexOf("x")==-1?a.x=a==f.first?0:this.plotWidth:a.x=b(d.relX,this),e.indexOf("y")==-1?a.y=a==f.first?0:this.plotHeight-1:a.y=c(d.relY,this)},drawSelection:function(){this.selection.fireSelectEvent("selecting");var a=this.selection.selection,b=this.octx,c=this.options,d=this.plotOffset,e=this.selection.prevSelection;if(e&&a.first.x==e.first.x&&a.first.y==e.first.y&&a.second.x==e.second.x&&a.second.y==e.second.y)return;b.save(),b.strokeStyle=this.processColor(c.selection.color,{opacity:.8}),b.lineWidth=1,b.lineJoin="miter",b.fillStyle=this.processColor(c.selection.color,{opacity:.4}),this.selection.prevSelection={first:{x:a.first.x,y:a.first.y},second:{x:a.second.x,y:a.second.y}};var f=Math.min(a.first.x,a.second.x),g=Math.min(a.first.y,a.second.y),h=Math.abs(a.second.x-a.first.x),i=Math.abs(a.second.y-a.first.y);b.fillRect(f+d.left+.5,g+d.top+.5,h,i),b.strokeRect(f+d.left+.5,g+d.top+.5,h,i),b.restore()},updateSelection:function(){if(!this.lastMousePos.pageX)return;this.selection.selecting=!0;if(this.multitouches)this.selection.setSelectionPos(this.selection.selection.first,this.getEventPosition(this.multitouches[0])),this.selection.setSelectionPos(this.selection.selection.second,this.getEventPosition(this.multitouches[1]));else{if(this.options.selection.pinchOnly)return;this.selection.setSelectionPos(this.selection.selection.second,this.lastMousePos)}this.selection.clearSelection(),this.selection.selectionIsSane()&&this.selection.drawSelection()},clearSelection:function(){if(!this.selection.prevSelection)return;var a=this.selection.prevSelection,b=1,c=this.plotOffset,d=Math.min(a.first.x,a.second.x),e=Math.min(a.first.y,a.second.y),f=Math.abs(a.second.x-a.first.x),g=Math.abs(a.second.y-a.first.y);this.octx.clearRect(d+c.left-b+.5,e+c.top-b,f+2*b+.5,g+2*b+.5),this.selection.prevSelection=null},selectionIsSane:function(){var a=this.selection.selection;return Math.abs(a.second.x-a.first.x)>=5||Math.abs(a.second.y-a.first.y)>=5}})}(),function(){var a=Flotr.DOM;Flotr.addPlugin("labels",{callbacks:{"flotr:afterdraw":function(){this.labels.draw()}},draw:function(){function s(a,b,d){var e=d?b.minorTicks:b.ticks,f=b.orientation===1,h=b.n===1,k,m;k={color:b.options.color||o.grid.color,angle:Flotr.toRad(b.options.labelsAngle),textBaseline:"middle"};for(l=0;l<e.length&&(d?b.options.showMinorLabels:b.options.showLabels);++l){c=e[l],c.label+="";if(!c.label||!c.label.length)continue;x=Math.cos(l*i+j)*g,y=Math.sin(l*i+j)*g,k.textAlign=f?Math.abs(x)<.1?"center":x<0?"right":"left":"left",Flotr.drawText(p,c.label,f?x:3,f?y:-(b.ticks[l].v/b.max)*(g-o.fontSize),k)}}function t(a,b,d,e){function j(a){return a.options.showLabels&&a.used}function k(a,b,c,d){return a.plotOffset.left+(b?d:c?-o.grid.labelMargin:o.grid.labelMargin+a.plotWidth)}function m(a,b,c,d){return a.plotOffset.top+(b?o.grid.labelMargin:d)+(b&&c?a.plotHeight:0)}var f=b.orientation===1,g=b.n===1,h,i;h={color:b.options.color||o.grid.color,textAlign:d,textBaseline:e,angle:Flotr.toRad(b.options.labelsAngle)},h=Flotr.getBestTextAlign(h.angle,h);for(l=0;l<b.ticks.length&&j(b);++l){c=b.ticks[l];if(!c.label||!c.label.length)continue;i=b.d2p(c.v);if(i<0||i>(f?a.plotWidth:a.plotHeight))continue;Flotr.drawText(p,c.label,k(a,f,g,i),m(a,f,g,i),h),!f&&!g&&(p.save(),p.strokeStyle=h.color,p.beginPath(),p.moveTo(a.plotOffset.left+a.plotWidth-8,a.plotOffset.top+b.d2p(c.v)),p.lineTo(a.plotOffset.left+a.plotWidth,a.plotOffset.top+b.d2p(c.v)),p.stroke(),p.restore())}}function u(a,b){var d=b.orientation===1,e=b.n===1,g="",h,i,j,k=a.plotOffset;!d&&!e&&(p.save(),p.strokeStyle=b.options.color||o.grid.color,p.beginPath());if(b.options.showLabels&&(e?!0:b.used))for(l=0;l<b.ticks.length;++l){c=b.ticks[l];if(!c.label||!c.label.length||(d?k.left:k.top)+b.d2p(c.v)<0||(d?k.left:k.top)+b.d2p(c.v)>(d?a.canvasWidth:a.canvasHeight))continue;j=k.top+(d?(e?1:-1)*(a.plotHeight+o.grid.labelMargin):b.d2p(c.v)-b.maxLabel.height/2),h=d?k.left+b.d2p(c.v)-f/2:0,g="",l===0?g=" first":l===b.ticks.length-1&&(g=" last"),g+=d?" flotr-grid-label-x":" flotr-grid-label-y",m+=['<div style="position:absolute; text-align:'+(d?"center":"right")+"; ","top:"+j+"px; ",(!d&&!e?"right:":"left:")+h+"px; ","width:"+(d?f:(e?k.left:k.right)-o.grid.labelMargin)+"px; ",b.options.color?"color:"+b.options.color+"; ":" ",'" class="flotr-grid-label'+g+'">'+c.label+"</div>"].join(" "),!d&&!e&&(p.moveTo(k.left+a.plotWidth-8,k.top+b.d2p(c.v)),p.lineTo(k.left+a.plotWidth,k.top+b.d2p(c.v)))}}var b,c,d,e,f,g,h,i,j,k,l,m="",n=0,o=this.options,p=this.ctx,q=this.axes,r={size:o.fontSize};for(l=0;l<q.x.ticks.length;++l)q.x.ticks[l].label&&++n;f=this.plotWidth/n,o.grid.circular&&(p.save(),p.translate(this.plotOffset.left+this.plotWidth/2,this.plotOffset.top+this.plotHeight/2),g=this.plotHeight*o.radar.radiusRatio/2+o.fontSize,h=this.axes.x.ticks.length,i=2*(Math.PI/h),j=-Math.PI/2,s(this,q.x,!1),s(this,q.x,!0),s(this,q.y,!1),s(this,q.y,!0),p.restore()),!o.HtmlText&&this.textEnabled?(t(this,q.x,"center","top"),t(this,q.x2,"center","bottom"),t(this,q.y,"right","middle"),t(this,q.y2,"left","middle")):(q.x.options.showLabels||q.x2.options.showLabels||q.y.options.showLabels||q.y2.options.showLabels)&&!o.grid.circular&&(m="",u(this,q.x),u(this,q.x2),u(this,q.y),u(this,q.y2),p.stroke(),p.restore(),k=a.create("div"),a.setStyles(k,{fontSize:"smaller",color:o.grid.color}),k.className="flotr-labels",a.insert(this.el,k),a.insert(k,m))}})}(),function(){var a=Flotr.DOM,b=Flotr._;Flotr.addPlugin("legend",{options:{show:!0,noColumns:1,labelFormatter:function(a){return a},labelBoxBorderColor:"#CCCCCC",labelBoxWidth:14,labelBoxHeight:10,labelBoxMargin:5,labelBoxOpacity:.4,container:null,position:"nw",margin:5,backgroundColor:null,backgroundOpacity:.85},callbacks:{"flotr:afterinit":function(){this.legend.insertLegend()}},insertLegend:function(){if(!this.options.legend.show)return;var c=this.series,d=this.plotOffset,e=this.options,f=e.legend,g=[],h=!1,i=this.ctx,j=b.filter(c,function(a){return a.label&&!a.hide}).length,k=f.position,l=f.margin,m,n,o;if(j)if(!e.HtmlText&&this.textEnabled&&!f.container){var p={size:e.fontSize*1.1,color:e.grid.color},q=f.labelBoxWidth,r=f.labelBoxHeight,s=f.labelBoxMargin,t=d.left+l,u=d.top+l,v=0;for(m=c.length-1;m>-1;--m){if(!c[m].label||c[m].hide)continue;n=f.labelFormatter(c[m].label),v=Math.max(v,this._text.measureText(n,p).width)}var w=Math.round(q+s*3+v),x=Math.round(j*(s+r)+s);k.charAt(0)=="s"&&(u=d.top+this.plotHeight-(l+x)),k.charAt(1)=="e"&&(t=d.left+this.plotWidth-(l+w)),o=this.processColor(f.backgroundColor||"rgb(240,240,240)",{opacity:f.backgroundOpacity||.1}),i.fillStyle=o,i.fillRect(t,u,w,x),i.strokeStyle=f.labelBoxBorderColor,i.strokeRect(Flotr.toPixel(t),Flotr.toPixel(u),w,x);var y=t+s,z=u+s;for(m=0;m<c.length;m++){if(!c[m].label||c[m].hide)continue;n=f.labelFormatter(c[m].label),i.fillStyle=c[m].color,i.fillRect(y,z,q-1,r-1),i.strokeStyle=f.labelBoxBorderColor,i.lineWidth=1,i.strokeRect(Math.ceil(y)-1.5,Math.ceil(z)-1.5,q+2,r+2),Flotr.drawText(i,n,y+q+s,z+r,p),z+=r+s}}else{for(m=0;m<c.length;++m){if(!c[m].label||c[m].hide)continue;m%f.noColumns===0&&(g.push(h?"</tr><tr>":"<tr>"),h=!0);var A=c[m],B=f.labelBoxWidth,C=f.labelBoxHeight,E=A.bars?A.bars.fillOpacity:f.labelBoxOpacity,F="opacity:"+E+";filter:alpha(opacity="+E*100+");";n=f.labelFormatter(A.label),o="background-color:"+(A.bars&&A.bars.show&&A.bars.fillColor&&A.bars.fill?A.bars.fillColor:A.color)+";",g.push('<td class="flotr-legend-color-box">','<div style="border:1px solid ',f.labelBoxBorderColor,';padding:1px">','<div style="width:',B-1,"px;height:",C-1,"px;border:1px solid ",c[m].color,'">','<div style="width:',B,"px;height:",C,"px;","opacity:.4;",o,'"></div>',"</div>","</div>","</td>",'<td class="flotr-legend-label">',n,"</td>")}h&&g.push("</tr>");if(g.length>0){var G='<table style="font-size:smaller;color:'+e.grid.color+'">'+g.join("")+"</table>";if(f.container)a.insert(f.container,G);else{var H={position:"absolute","z-index":2};k.charAt(0)=="n"?(H.top=l+d.top+"px",H.bottom="auto"):k.charAt(0)=="s"&&(H.bottom=l+d.bottom+"px",H.top="auto"),k.charAt(1)=="e"?(H.right=l+d.right+"px",H.left="auto"):k.charAt(1)=="w"&&(H.left=l+d.left+"px",H.right="auto");var I=a.create("div"),J;I.className="flotr-legend",a.setStyles(I,H),a.insert(I,G),a.insert(this.el,I);if(!f.backgroundOpacity)return;var K=f.backgroundColor||e.grid.backgroundColor||"#ffffff";b.extend(H,a.size(I),{backgroundColor:K,"z-index":1}),H.width+="px",H.height+="px",I=a.create("div"),I.className="flotr-legend-bg",a.setStyles(I,H),a.opacity(I,f.backgroundOpacity),a.insert(I," "),a.insert(this.el,I)}}}}})}(),function(){function a(a){if(this.options.spreadsheet.tickFormatter)return this.options.spreadsheet.tickFormatter(a);var b=c.find(this.axes.x.ticks,function(b){return b.v==a});return b?b.label:a}var b=Flotr.DOM,c=Flotr._;Flotr.addPlugin("spreadsheet",{options:{show:!1,tabGraphLabel:"Graph",tabDataLabel:"Data",toolbarDownload:"Download CSV",toolbarSelectAll:"Select all",csvFileSeparator:",",decimalSeparator:".",tickFormatter:null,initialTab:"graph"},callbacks:{"flotr:afterconstruct":function(){if(!this.options.spreadsheet.show)return;var a=this.spreadsheet,c=b.node('<div class="flotr-tabs-group" style="position:absolute;left:0px;width:'+this.canvasWidth+'px"></div>'),d=b.node('<div style="float:left" class="flotr-tab selected">'+this.options.spreadsheet.tabGraphLabel+"</div>"),e=b.node('<div style="float:left" class="flotr-tab">'+this.options.spreadsheet.tabDataLabel+"</div>"),f;a.tabsContainer=c,a.tabs={graph:d,data:e},b.insert(c,d),b.insert(c,e),b.insert(this.el,c),f=b.size(e).height+2,this.plotOffset.bottom+=f,b.setStyles(c,{top:this.canvasHeight-f+"px"}),this.observe(d,"click",function(){a.showTab("graph")}).observe(e,"click",function(){a.showTab("data")}),this.options.spreadsheet.initialTab!=="graph"&&a.showTab(this.options.spreadsheet.initialTab)}},loadDataGrid:function(){if(this.seriesData)return this.seriesData;var a=this.series,b={};return c.each(a,function(a,d){c.each(a.data,function(a){var c=a[0],e=a[1],f=b[c];if(f)f[d+1]=e;else{var g=[];g[0]=c,g[d+1]=e,b[c]=g}})}),this.seriesData=c.sortBy(b,function(a,b){return parseInt(b,10)}),this.seriesData},constructDataGrid:function(){if(this.spreadsheet.datagrid)return this.spreadsheet.datagrid;var d=this.series,e=this.spreadsheet.loadDataGrid(),f=["<colgroup><col />"],g,h,i,j=['<table class="flotr-datagrid"><tr class="first-row">'];j.push("<th> </th>"),c.each(d,function(a,b){j.push('<th scope="col">'+(a.label||String.fromCharCode(65+b))+"</th>"),f.push("<col />")}),j.push("</tr>"),c.each(e,function(b){j.push("<tr>"),c.times(d.length+1,function(d){var e="td",f=b[d],g=c.isUndefined(f)?"":Math.round(f*1e5)/1e5;if(d===0){e="th";var h=a.call(this,g);h&&(g=h)}j.push("<"+e+(e=="th"?' scope="row"':"")+">"+g+"</"+e+">")},this),j.push("</tr>")},this),f.push("</colgroup>"),i=b.node(j.join("")),g=b.node('<button type="button" class="flotr-datagrid-toolbar-button">'+this.options.spreadsheet.toolbarDownload+"</button>"),h=b.node('<button type="button" class="flotr-datagrid-toolbar-button">'+this.options.spreadsheet.toolbarSelectAll+"</button>"),this.observe(g,"click",c.bind(this.spreadsheet.downloadCSV,this)).observe(h,"click",c.bind(this.spreadsheet.selectAllData,this));var k=b.node('<div class="flotr-datagrid-toolbar"></div>');b.insert(k,g),b.insert(k,h);var l=this.canvasHeight-b.size(this.spreadsheet.tabsContainer).height-2,m=b.node('<div class="flotr-datagrid-container" style="position:absolute;left:0px;top:0px;width:'+this.canvasWidth+"px;height:"+l+'px;overflow:auto;z-index:10"></div>');return b.insert(m,k),b.insert(m,i),b.insert(this.el,m),this.spreadsheet.datagrid=i,this.spreadsheet.container=m,i},showTab:function(a){if(this.spreadsheet.activeTab===a)return;switch(a){case"graph":b.hide(this.spreadsheet.container),b.removeClass(this.spreadsheet.tabs.data,"selected"),b.addClass(this.spreadsheet.tabs.graph,"selected");break;case"data":this.spreadsheet.datagrid||this.spreadsheet.constructDataGrid(),b.show(this.spreadsheet.container),b.addClass(this.spreadsheet.tabs.data,"selected"),b.removeClass(this.spreadsheet.tabs.graph,"selected");break;default:throw"Illegal tab name: "+a}this.spreadsheet.activeTab=a},selectAllData:function(){if(this.spreadsheet.tabs){var a,b,c,d,e=this.spreadsheet.constructDataGrid();return this.spreadsheet.showTab("data"),setTimeout(function(){(c=e.ownerDocument)&&(d=c.defaultView)&&d.getSelection&&c.createRange&&(a=window.getSelection())&&a.removeAllRanges?(b=c.createRange(),b.selectNode(e),a.removeAllRanges(),a.addRange(b)):document.body&&document.body.createTextRange&&(b=document.body.createTextRange())&&(b.moveToElementText(e),b.select())},0),!0}return!1},downloadCSV:function(){var b="",d=this.series,e=this.options,f=this.spreadsheet.loadDataGrid(),g=encodeURIComponent(e.spreadsheet.csvFileSeparator);if(e.spreadsheet.decimalSeparator===e.spreadsheet.csvFileSeparator)throw"The decimal separator is the same as the column separator ("+e.spreadsheet.decimalSeparator+")";c.each(d,function(a,c){b+=g+'"'+(a.label||String.fromCharCode(65+c)).replace(/\"/g,'\\"')+'"'}),b+="%0D%0A",b+=c.reduce(f,function(b,c){var d=a.call(this,c[0])||"";d='"'+(d+"").replace(/\"/g,'\\"')+'"';var f=c.slice(1).join(g);return e.spreadsheet.decimalSeparator!=="."&&(f=f.replace(/\./g,e.spreadsheet.decimalSeparator)),b+d+g+f+"%0D%0A"},"",this),Flotr.isIE&&Flotr.isIE<9?(b=b.replace(new RegExp(g,"g"),decodeURIComponent(g)).replace(/%0A/g,"\n").replace(/%0D/g,"\r"),window.open().document.write(b)):window.open("data:text/csv,"+b)}})}(),function(){var a=Flotr.DOM;Flotr.addPlugin("titles",{callbacks:{"flotr:afterdraw":function(){this.titles.drawTitles()}},drawTitles:function(){var b,c=this.options,d=c.grid.labelMargin,e=this.ctx,f=this.axes;if(!c.HtmlText&&this.textEnabled){var g={size:c.fontSize,color:c.grid.color,textAlign:"center"};c.subtitle&&Flotr.drawText(e,c.subtitle,this.plotOffset.left+this.plotWidth/2,this.titleHeight+this.subtitleHeight-2,g),g.weight=1.5,g.size*=1.5,c.title&&Flotr.drawText(e,c.title,this.plotOffset.left+this.plotWidth/2,this.titleHeight-2,g),g.weight=1.8,g.size*=.8,f.x.options.title&&f.x.used&&(g.textAlign=f.x.options.titleAlign||"center",g.textBaseline="top",g.angle=Flotr.toRad(f.x.options.titleAngle),g=Flotr.getBestTextAlign(g.angle,g),Flotr.drawText(e,f.x.options.title,this.plotOffset.left+this.plotWidth/2,this.plotOffset.top+f.x.maxLabel.height+this.plotHeight+2*d,g)),f.x2.options.title&&f.x2.used&&(g.textAlign=f.x2.options.titleAlign||"center",g.textBaseline="bottom",g.angle=Flotr.toRad(f.x2.options.titleAngle),g=Flotr.getBestTextAlign(g.angle,g),Flotr.drawText(e,f.x2.options.title,this.plotOffset.left+this.plotWidth/2,this.plotOffset.top-f.x2.maxLabel.height-2*d,g)),f.y.options.title&&f.y.used&&(g.textAlign=f.y.options.titleAlign||"right",g.textBaseline="middle",g.angle=Flotr.toRad(f.y.options.titleAngle),g=Flotr.getBestTextAlign(g.angle,g),Flotr.drawText(e,f.y.options.title,this.plotOffset.left-f.y.maxLabel.width-2*d,this.plotOffset.top+this.plotHeight/2,g)),f.y2.options.title&&f.y2.used&&(g.textAlign=f.y2.options.titleAlign||"left",g.textBaseline="middle",g.angle=Flotr.toRad(f.y2.options.titleAngle),g=Flotr.getBestTextAlign(g.angle,g),Flotr.drawText(e,f.y2.options.title,this.plotOffset.left+this.plotWidth+f.y2.maxLabel.width+2*d,this.plotOffset.top+this.plotHeight/2,g))}else{b=[],c.title&&b.push('<div style="position:absolute;top:0;left:',this.plotOffset.left,"px;font-size:1em;font-weight:bold;text-align:center;width:",this.plotWidth,'px;" class="flotr-title">',c.title,"</div>"),c.subtitle&&b.push('<div style="position:absolute;top:',this.titleHeight,"px;left:",this.plotOffset.left,"px;font-size:smaller;text-align:center;width:",this.plotWidth,'px;" class="flotr-subtitle">',c.subtitle,"</div>"),b.push("</div>"),b.push('<div class="flotr-axis-title" style="font-weight:bold;">'),f.x.options.title&&f.x.used&&b.push('<div style="position:absolute;top:',this.plotOffset.top+this.plotHeight+c.grid.labelMargin+f.x.titleSize.height,"px;left:",this.plotOffset.left,"px;width:",this.plotWidth,'px;text-align:center;" class="flotr-axis-title">',f.x.options.title,"</div>"),f.x2.options.title&&f.x2.used&&b.push('<div style="position:absolute;top:0;left:',this.plotOffset.left,"px;width:",this.plotWidth,'px;text-align:center;" class="flotr-axis-title">',f.x2.options.title,"</div>"),f.y.options.title&&f.y.used&&b.push('<div style="position:absolute;top:',this.plotOffset.top+this.plotHeight/2-f.y.titleSize.height/2,'px;left:0;text-align:right;" class="flotr-axis-title">',f.y.options.title,"</div>"),f.y2.options.title&&f.y2.used&&b.push('<div style="position:absolute;top:',this.plotOffset.top+this.plotHeight/2-f.y.titleSize.height/2,'px;right:0;text-align:right;" class="flotr-axis-title">',f.y2.options.title,"</div>"),b=b.join("");var h=a.create("div");a.setStyles({color:c.grid.color}),h.className="flotr-titles",a.insert(this.el,h),a.insert(h,b)}}})}(); diff --git a/addons/web_graph/static/lib/flotr2/js/Axis.js b/addons/web_graph/static/lib/flotr2/js/Axis.js deleted file mode 100644 index 1c697309ef8..00000000000 --- a/addons/web_graph/static/lib/flotr2/js/Axis.js +++ /dev/null @@ -1,303 +0,0 @@ -/** - * Flotr Axis Library - */ - -(function () { - -var - _ = Flotr._, - LOGARITHMIC = 'logarithmic'; - -function Axis (o) { - - this.orientation = 1; - this.offset = 0; - this.datamin = Number.MAX_VALUE; - this.datamax = -Number.MAX_VALUE; - - _.extend(this, o); - - this._setTranslations(); -} - - -// Prototype -Axis.prototype = { - - setScale : function () { - var length = this.length; - if (this.options.scaling == LOGARITHMIC) { - this.scale = length / (log(this.max, this.options.base) - log(this.min, this.options.base)); - } else { - this.scale = length / (this.max - this.min); - } - }, - - calculateTicks : function () { - var options = this.options; - - this.ticks = []; - this.minorTicks = []; - - // User Ticks - if(options.ticks){ - this._cleanUserTicks(options.ticks, this.ticks); - this._cleanUserTicks(options.minorTicks || [], this.minorTicks); - } - else { - if (options.mode == 'time') { - this._calculateTimeTicks(); - } else if (options.scaling === 'logarithmic') { - this._calculateLogTicks(); - } else { - this._calculateTicks(); - } - } - }, - - /** - * Calculates the range of an axis to apply autoscaling. - */ - calculateRange: function () { - - if (!this.used) return; - - var axis = this, - o = axis.options, - min = o.min !== null ? o.min : axis.datamin, - max = o.max !== null ? o.max : axis.datamax, - margin = o.autoscaleMargin; - - if (o.scaling == 'logarithmic') { - if (min <= 0) min = axis.datamin; - - // Let it widen later on - if (max <= 0) max = min; - } - - if (max == min) { - var widen = max ? 0.01 : 1.00; - if (o.min === null) min -= widen; - if (o.max === null) max += widen; - } - - if (o.scaling === 'logarithmic') { - if (min < 0) min = max / o.base; // Could be the result of widening - - var maxexp = Math.log(max); - if (o.base != Math.E) maxexp /= Math.log(o.base); - maxexp = Math.ceil(maxexp); - - var minexp = Math.log(min); - if (o.base != Math.E) minexp /= Math.log(o.base); - minexp = Math.ceil(minexp); - - axis.tickSize = Flotr.getTickSize(o.noTicks, minexp, maxexp, o.tickDecimals === null ? 0 : o.tickDecimals); - - // Try to determine a suitable amount of miniticks based on the length of a decade - if (o.minorTickFreq === null) { - if (maxexp - minexp > 10) - o.minorTickFreq = 0; - else if (maxexp - minexp > 5) - o.minorTickFreq = 2; - else - o.minorTickFreq = 5; - } - } else { - axis.tickSize = Flotr.getTickSize(o.noTicks, min, max, o.tickDecimals); - } - - axis.min = min; - axis.max = max; //extendRange may use axis.min or axis.max, so it should be set before it is caled - - // Autoscaling. @todo This probably fails with log scale. Find a testcase and fix it - if(o.min === null && o.autoscale){ - axis.min -= axis.tickSize * margin; - // Make sure we don't go below zero if all values are positive. - if(axis.min < 0 && axis.datamin >= 0) axis.min = 0; - axis.min = axis.tickSize * Math.floor(axis.min / axis.tickSize); - } - - if(o.max === null && o.autoscale){ - axis.max += axis.tickSize * margin; - if(axis.max > 0 && axis.datamax <= 0 && axis.datamax != axis.datamin) axis.max = 0; - axis.max = axis.tickSize * Math.ceil(axis.max / axis.tickSize); - } - - if (axis.min == axis.max) axis.max = axis.min + 1; - }, - - calculateTextDimensions : function (T, options) { - - var maxLabel = '', - length, - i; - - if (this.options.showLabels) { - for (i = 0; i < this.ticks.length; ++i) { - length = this.ticks[i].label.length; - if (length > maxLabel.length){ - maxLabel = this.ticks[i].label; - } - } - } - - this.maxLabel = T.dimensions( - maxLabel, - {size:options.fontSize, angle: Flotr.toRad(this.options.labelsAngle)}, - 'font-size:smaller;', - 'flotr-grid-label' - ); - - this.titleSize = T.dimensions( - this.options.title, - {size:options.fontSize*1.2, angle: Flotr.toRad(this.options.titleAngle)}, - 'font-weight:bold;', - 'flotr-axis-title' - ); - }, - - _cleanUserTicks : function (ticks, axisTicks) { - - var axis = this, options = this.options, - v, i, label, tick; - - if(_.isFunction(ticks)) ticks = ticks({min : axis.min, max : axis.max}); - - for(i = 0; i < ticks.length; ++i){ - tick = ticks[i]; - if(typeof(tick) === 'object'){ - v = tick[0]; - label = (tick.length > 1) ? tick[1] : options.tickFormatter(v, {min : axis.min, max : axis.max}); - } else { - v = tick; - label = options.tickFormatter(v, {min : this.min, max : this.max}); - } - axisTicks[i] = { v: v, label: label }; - } - }, - - _calculateTimeTicks : function () { - this.ticks = Flotr.Date.generator(this); - }, - - _calculateLogTicks : function () { - - var axis = this, - o = axis.options, - v, - decadeStart; - - var max = Math.log(axis.max); - if (o.base != Math.E) max /= Math.log(o.base); - max = Math.ceil(max); - - var min = Math.log(axis.min); - if (o.base != Math.E) min /= Math.log(o.base); - min = Math.ceil(min); - - for (i = min; i < max; i += axis.tickSize) { - decadeStart = (o.base == Math.E) ? Math.exp(i) : Math.pow(o.base, i); - // Next decade begins here: - var decadeEnd = decadeStart * ((o.base == Math.E) ? Math.exp(axis.tickSize) : Math.pow(o.base, axis.tickSize)); - var stepSize = (decadeEnd - decadeStart) / o.minorTickFreq; - - axis.ticks.push({v: decadeStart, label: o.tickFormatter(decadeStart, {min : axis.min, max : axis.max})}); - for (v = decadeStart + stepSize; v < decadeEnd; v += stepSize) - axis.minorTicks.push({v: v, label: o.tickFormatter(v, {min : axis.min, max : axis.max})}); - } - - // Always show the value at the would-be start of next decade (end of this decade) - decadeStart = (o.base == Math.E) ? Math.exp(i) : Math.pow(o.base, i); - axis.ticks.push({v: decadeStart, label: o.tickFormatter(decadeStart, {min : axis.min, max : axis.max})}); - }, - - _calculateTicks : function () { - - var axis = this, - o = axis.options, - tickSize = axis.tickSize, - min = axis.min, - max = axis.max, - start = tickSize * Math.ceil(min / tickSize), // Round to nearest multiple of tick size. - decimals, - minorTickSize, - v, v2, - i, j; - - if (o.minorTickFreq) - minorTickSize = tickSize / o.minorTickFreq; - - // Then store all possible ticks. - for (i = 0; (v = v2 = start + i * tickSize) <= max; ++i){ - - // Round (this is always needed to fix numerical instability). - decimals = o.tickDecimals; - if (decimals === null) decimals = 1 - Math.floor(Math.log(tickSize) / Math.LN10); - if (decimals < 0) decimals = 0; - - v = v.toFixed(decimals); - axis.ticks.push({ v: v, label: o.tickFormatter(v, {min : axis.min, max : axis.max}) }); - - if (o.minorTickFreq) { - for (j = 0; j < o.minorTickFreq && (i * tickSize + j * minorTickSize) < max; ++j) { - v = v2 + j * minorTickSize; - axis.minorTicks.push({ v: v, label: o.tickFormatter(v, {min : axis.min, max : axis.max}) }); - } - } - } - - }, - - _setTranslations : function (logarithmic) { - this.d2p = (logarithmic ? d2pLog : d2p); - this.p2d = (logarithmic ? p2dLog : p2d); - } -}; - - -// Static Methods -_.extend(Axis, { - getAxes : function (options) { - return { - x: new Axis({options: options.xaxis, n: 1, length: this.plotWidth}), - x2: new Axis({options: options.x2axis, n: 2, length: this.plotWidth}), - y: new Axis({options: options.yaxis, n: 1, length: this.plotHeight, offset: this.plotHeight, orientation: -1}), - y2: new Axis({options: options.y2axis, n: 2, length: this.plotHeight, offset: this.plotHeight, orientation: -1}) - }; - } -}); - - -// Helper Methods - -function d2p (dataValue) { - return this.offset + this.orientation * (dataValue - this.min) * this.scale; -} - -function p2d (pointValue) { - return (this.offset + this.orientation * pointValue) / this.scale + this.min; -} - -function d2pLog (dataValue) { - return this.offset + this.orientation * (log(dataValue, this.options.base) - log(this.min, this.options.base)) * this.scale; -} - -function p2dLog (pointValue) { - return exp((this.offset + this.orientation * pointValue) / this.scale + log(this.min, this.options.base), this.options.base); -} - -function log (value, base) { - value = Math.log(Math.max(value, Number.MIN_VALUE)); - if (base !== Math.E) - value /= Math.log(base); - return value; -} - -function exp (value, base) { - return (base === Math.E) ? Math.exp(value) : Math.pow(base, value); -} - -Flotr.Axis = Axis; - -})(); diff --git a/addons/web_graph/static/lib/flotr2/js/Color.js b/addons/web_graph/static/lib/flotr2/js/Color.js deleted file mode 100644 index ab4ba0b432d..00000000000 --- a/addons/web_graph/static/lib/flotr2/js/Color.js +++ /dev/null @@ -1,163 +0,0 @@ -/** - * Flotr Color - */ - -(function () { - -var - _ = Flotr._; - -// Constructor -function Color (r, g, b, a) { - this.rgba = ['r','g','b','a']; - var x = 4; - while(-1<--x){ - this[this.rgba[x]] = arguments[x] || ((x==3) ? 1.0 : 0); - } - this.normalize(); -} - -// Constants -var COLOR_NAMES = { - aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255], - brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169], - darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47], - darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122], - darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130], - khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144], - lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255], - maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128], - violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0] -}; - -Color.prototype = { - scale: function(rf, gf, bf, af){ - var x = 4; - while (-1 < --x) { - if (!_.isUndefined(arguments[x])) this[this.rgba[x]] *= arguments[x]; - } - return this.normalize(); - }, - alpha: function(alpha) { - if (!_.isUndefined(alpha) && !_.isNull(alpha)) { - this.a = alpha; - } - return this.normalize(); - }, - clone: function(){ - return new Color(this.r, this.b, this.g, this.a); - }, - limit: function(val,minVal,maxVal){ - return Math.max(Math.min(val, maxVal), minVal); - }, - normalize: function(){ - var limit = this.limit; - this.r = limit(parseInt(this.r, 10), 0, 255); - this.g = limit(parseInt(this.g, 10), 0, 255); - this.b = limit(parseInt(this.b, 10), 0, 255); - this.a = limit(this.a, 0, 1); - return this; - }, - distance: function(color){ - if (!color) return; - color = new Color.parse(color); - var dist = 0, x = 3; - while(-1<--x){ - dist += Math.abs(this[this.rgba[x]] - color[this.rgba[x]]); - } - return dist; - }, - toString: function(){ - return (this.a >= 1.0) ? 'rgb('+[this.r,this.g,this.b].join(',')+')' : 'rgba('+[this.r,this.g,this.b,this.a].join(',')+')'; - }, - contrast: function () { - var - test = 1 - ( 0.299 * this.r + 0.587 * this.g + 0.114 * this.b) / 255; - return (test < 0.5 ? '#000000' : '#ffffff'); - } -}; - -_.extend(Color, { - /** - * Parses a color string and returns a corresponding Color. - * The different tests are in order of probability to improve speed. - * @param {String, Color} str - string thats representing a color - * @return {Color} returns a Color object or false - */ - parse: function(color){ - if (color instanceof Color) return color; - - var result; - - // #a0b1c2 - if((result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(color))) - return new Color(parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16)); - - // rgb(num,num,num) - if((result = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(color))) - return new Color(parseInt(result[1], 10), parseInt(result[2], 10), parseInt(result[3], 10)); - - // #fff - if((result = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(color))) - return new Color(parseInt(result[1]+result[1],16), parseInt(result[2]+result[2],16), parseInt(result[3]+result[3],16)); - - // rgba(num,num,num,num) - if((result = /rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(color))) - return new Color(parseInt(result[1], 10), parseInt(result[2], 10), parseInt(result[3], 10), parseFloat(result[4])); - - // rgb(num%,num%,num%) - if((result = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(color))) - return new Color(parseFloat(result[1])*2.55, parseFloat(result[2])*2.55, parseFloat(result[3])*2.55); - - // rgba(num%,num%,num%,num) - if((result = /rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(color))) - return new Color(parseFloat(result[1])*2.55, parseFloat(result[2])*2.55, parseFloat(result[3])*2.55, parseFloat(result[4])); - - // Otherwise, we're most likely dealing with a named color. - var name = (color+'').replace(/^\s*([\S\s]*?)\s*$/, '$1').toLowerCase(); - if(name == 'transparent'){ - return new Color(255, 255, 255, 0); - } - return (result = COLOR_NAMES[name]) ? new Color(result[0], result[1], result[2]) : new Color(0, 0, 0, 0); - }, - - /** - * Process color and options into color style. - */ - processColor: function(color, options) { - - var opacity = options.opacity; - if (!color) return 'rgba(0, 0, 0, 0)'; - if (color instanceof Color) return color.alpha(opacity).toString(); - if (_.isString(color)) return Color.parse(color).alpha(opacity).toString(); - - var grad = color.colors ? color : {colors: color}; - - if (!options.ctx) { - if (!_.isArray(grad.colors)) return 'rgba(0, 0, 0, 0)'; - return Color.parse(_.isArray(grad.colors[0]) ? grad.colors[0][1] : grad.colors[0]).alpha(opacity).toString(); - } - grad = _.extend({start: 'top', end: 'bottom'}, grad); - - if (/top/i.test(grad.start)) options.x1 = 0; - if (/left/i.test(grad.start)) options.y1 = 0; - if (/bottom/i.test(grad.end)) options.x2 = 0; - if (/right/i.test(grad.end)) options.y2 = 0; - - var i, c, stop, gradient = options.ctx.createLinearGradient(options.x1, options.y1, options.x2, options.y2); - for (i = 0; i < grad.colors.length; i++) { - c = grad.colors[i]; - if (_.isArray(c)) { - stop = c[0]; - c = c[1]; - } - else stop = i / (grad.colors.length-1); - gradient.addColorStop(stop, Color.parse(c).alpha(opacity)); - } - return gradient; - } -}); - -Flotr.Color = Color; - -})(); diff --git a/addons/web_graph/static/lib/flotr2/js/DOM.js b/addons/web_graph/static/lib/flotr2/js/DOM.js deleted file mode 100644 index 43df857395b..00000000000 --- a/addons/web_graph/static/lib/flotr2/js/DOM.js +++ /dev/null @@ -1,88 +0,0 @@ -(function () { - -var _ = Flotr._; - -Flotr.DOM = { - addClass: function(element, name){ - var classList = (element.className ? element.className : ''); - if (_.include(classList.split(/\s+/g), name)) return; - element.className = (classList ? classList + ' ' : '') + name; - }, - /** - * Create an element. - */ - create: function(tag){ - return document.createElement(tag); - }, - node: function(html) { - var div = Flotr.DOM.create('div'), n; - div.innerHTML = html; - n = div.children[0]; - div.innerHTML = ''; - return n; - }, - /** - * Remove all children. - */ - empty: function(element){ - element.innerHTML = ''; - /* - if (!element) return; - _.each(element.childNodes, function (e) { - Flotr.DOM.empty(e); - element.removeChild(e); - }); - */ - }, - hide: function(element){ - Flotr.DOM.setStyles(element, {display:'none'}); - }, - /** - * Insert a child. - * @param {Element} element - * @param {Element|String} Element or string to be appended. - */ - insert: function(element, child){ - if(_.isString(child)) - element.innerHTML += child; - else if (_.isElement(child)) - element.appendChild(child); - }, - // @TODO find xbrowser implementation - opacity: function(element, opacity) { - element.style.opacity = opacity; - }, - position: function(element, p){ - if (!element.offsetParent) - return {left: (element.offsetLeft || 0), top: (element.offsetTop || 0)}; - - p = this.position(element.offsetParent); - p.left += element.offsetLeft; - p.top += element.offsetTop; - return p; - }, - removeClass: function(element, name) { - var classList = (element.className ? element.className : ''); - element.className = _.filter(classList.split(/\s+/g), function (c) { - if (c != name) return true; } - ).join(' '); - }, - setStyles: function(element, o) { - _.each(o, function (value, key) { - element.style[key] = value; - }); - }, - show: function(element){ - Flotr.DOM.setStyles(element, {display:''}); - }, - /** - * Return element size. - */ - size: function(element){ - return { - height : element.offsetHeight, - width : element.offsetWidth }; - } -}; - -})(); diff --git a/addons/web_graph/static/lib/flotr2/js/Date.js b/addons/web_graph/static/lib/flotr2/js/Date.js deleted file mode 100644 index fe72dba709f..00000000000 --- a/addons/web_graph/static/lib/flotr2/js/Date.js +++ /dev/null @@ -1,207 +0,0 @@ -/** - * Flotr Date - */ -Flotr.Date = { - - set : function (date, name, mode, value) { - mode = mode || 'UTC'; - name = 'set' + (mode === 'UTC' ? 'UTC' : '') + name; - date[name](value); - }, - - get : function (date, name, mode) { - mode = mode || 'UTC'; - name = 'get' + (mode === 'UTC' ? 'UTC' : '') + name; - return date[name](); - }, - - format: function(d, format, mode) { - if (!d) return; - - // We should maybe use an "official" date format spec, like PHP date() or ColdFusion - // http://fr.php.net/manual/en/function.date.php - // http://livedocs.adobe.com/coldfusion/8/htmldocs/help.html?content=functions_c-d_29.html - var - get = this.get, - tokens = { - h: get(d, 'Hours', mode).toString(), - H: leftPad(get(d, 'Hours', mode)), - M: leftPad(get(d, 'Minutes', mode)), - S: leftPad(get(d, 'Seconds', mode)), - s: get(d, 'Milliseconds', mode), - d: get(d, 'Date', mode).toString(), - m: (get(d, 'Month') + 1).toString(), - y: get(d, 'FullYear').toString(), - b: Flotr.Date.monthNames[get(d, 'Month', mode)] - }; - - function leftPad(n){ - n += ''; - return n.length == 1 ? "0" + n : n; - } - - var r = [], c, - escape = false; - - for (var i = 0; i < format.length; ++i) { - c = format.charAt(i); - - if (escape) { - r.push(tokens[c] || c); - escape = false; - } - else if (c == "%") - escape = true; - else - r.push(c); - } - return r.join(''); - }, - getFormat: function(time, span) { - var tu = Flotr.Date.timeUnits; - if (time < tu.second) return "%h:%M:%S.%s"; - else if (time < tu.minute) return "%h:%M:%S"; - else if (time < tu.day) return (span < 2 * tu.day) ? "%h:%M" : "%b %d %h:%M"; - else if (time < tu.month) return "%b %d"; - else if (time < tu.year) return (span < tu.year) ? "%b" : "%b %y"; - else return "%y"; - }, - formatter: function (v, axis) { - var - options = axis.options, - scale = Flotr.Date.timeUnits[options.timeUnit], - d = new Date(v * scale); - - // first check global format - if (axis.options.timeFormat) - return Flotr.Date.format(d, options.timeFormat, options.timeMode); - - var span = (axis.max - axis.min) * scale, - t = axis.tickSize * Flotr.Date.timeUnits[axis.tickUnit]; - - return Flotr.Date.format(d, Flotr.Date.getFormat(t, span), options.timeMode); - }, - generator: function(axis) { - - var - set = this.set, - get = this.get, - timeUnits = this.timeUnits, - spec = this.spec, - options = axis.options, - mode = options.timeMode, - scale = timeUnits[options.timeUnit], - min = axis.min * scale, - max = axis.max * scale, - delta = (max - min) / options.noTicks, - ticks = [], - tickSize = axis.tickSize, - tickUnit, - formatter, i; - - // Use custom formatter or time tick formatter - formatter = (options.tickFormatter === Flotr.defaultTickFormatter ? - this.formatter : options.tickFormatter - ); - - for (i = 0; i < spec.length - 1; ++i) { - var d = spec[i][0] * timeUnits[spec[i][1]]; - if (delta < (d + spec[i+1][0] * timeUnits[spec[i+1][1]]) / 2 && d >= tickSize) - break; - } - tickSize = spec[i][0]; - tickUnit = spec[i][1]; - - // special-case the possibility of several years - if (tickUnit == "year") { - tickSize = Flotr.getTickSize(options.noTicks*timeUnits.year, min, max, 0); - - // Fix for 0.5 year case - if (tickSize == 0.5) { - tickUnit = "month"; - tickSize = 6; - } - } - - axis.tickUnit = tickUnit; - axis.tickSize = tickSize; - - var - d = new Date(min); - - var step = tickSize * timeUnits[tickUnit]; - - function setTick (name) { - set(d, name, mode, Flotr.floorInBase( - get(d, name, mode), tickSize - )); - } - - switch (tickUnit) { - case "millisecond": setTick('Milliseconds'); break; - case "second": setTick('Seconds'); break; - case "minute": setTick('Minutes'); break; - case "hour": setTick('Hours'); break; - case "month": setTick('Month'); break; - case "year": setTick('FullYear'); break; - } - - // reset smaller components - if (step >= timeUnits.second) set(d, 'Milliseconds', mode, 0); - if (step >= timeUnits.minute) set(d, 'Seconds', mode, 0); - if (step >= timeUnits.hour) set(d, 'Minutes', mode, 0); - if (step >= timeUnits.day) set(d, 'Hours', mode, 0); - if (step >= timeUnits.day * 4) set(d, 'Date', mode, 1); - if (step >= timeUnits.year) set(d, 'Month', mode, 0); - - var carry = 0, v = NaN, prev; - do { - prev = v; - v = d.getTime(); - ticks.push({ v: v / scale, label: formatter(v / scale, axis) }); - if (tickUnit == "month") { - if (tickSize < 1) { - /* a bit complicated - we'll divide the month up but we need to take care of fractions - so we don't end up in the middle of a day */ - set(d, 'Date', mode, 1); - var start = d.getTime(); - set(d, 'Month', mode, get(d, 'Month', mode) + 1) - var end = d.getTime(); - d.setTime(v + carry * timeUnits.hour + (end - start) * tickSize); - carry = get(d, 'Hours', mode) - set(d, 'Hours', mode, 0); - } - else - set(d, 'Month', mode, get(d, 'Month', mode) + tickSize); - } - else if (tickUnit == "year") { - set(d, 'FullYear', mode, get(d, 'FullYear', mode) + tickSize); - } - else - d.setTime(v + step); - - } while (v < max && v != prev); - - return ticks; - }, - timeUnits: { - millisecond: 1, - second: 1000, - minute: 1000 * 60, - hour: 1000 * 60 * 60, - day: 1000 * 60 * 60 * 24, - month: 1000 * 60 * 60 * 24 * 30, - year: 1000 * 60 * 60 * 24 * 365.2425 - }, - // the allowed tick sizes, after 1 year we use an integer algorithm - spec: [ - [1, "millisecond"], [20, "millisecond"], [50, "millisecond"], [100, "millisecond"], [200, "millisecond"], [500, "millisecond"], - [1, "second"], [2, "second"], [5, "second"], [10, "second"], [30, "second"], - [1, "minute"], [2, "minute"], [5, "minute"], [10, "minute"], [30, "minute"], - [1, "hour"], [2, "hour"], [4, "hour"], [8, "hour"], [12, "hour"], - [1, "day"], [2, "day"], [3, "day"], - [0.25, "month"], [0.5, "month"], [1, "month"], [2, "month"], [3, "month"], [6, "month"], - [1, "year"] - ], - monthNames: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] -}; diff --git a/addons/web_graph/static/lib/flotr2/js/DefaultOptions.js b/addons/web_graph/static/lib/flotr2/js/DefaultOptions.js deleted file mode 100644 index 7399553df6a..00000000000 --- a/addons/web_graph/static/lib/flotr2/js/DefaultOptions.js +++ /dev/null @@ -1,98 +0,0 @@ -/** - * Flotr Defaults - */ -Flotr.defaultOptions = { - colors: ['#00A8F0', '#C0D800', '#CB4B4B', '#4DA74D', '#9440ED'], //=> The default colorscheme. When there are > 5 series, additional colors are generated. - ieBackgroundColor: '#FFFFFF', // Background color for excanvas clipping - title: null, // => The graph's title - subtitle: null, // => The graph's subtitle - shadowSize: 4, // => size of the 'fake' shadow - defaultType: null, // => default series type - HtmlText: true, // => wether to draw the text using HTML or on the canvas - fontColor: '#545454', // => default font color - fontSize: 7.5, // => canvas' text font size - resolution: 1, // => resolution of the graph, to have printer-friendly graphs ! - parseFloat: true, // => whether to preprocess data for floats (ie. if input is string) - xaxis: { - ticks: null, // => format: either [1, 3] or [[1, 'a'], 3] - minorTicks: null, // => format: either [1, 3] or [[1, 'a'], 3] - showLabels: true, // => setting to true will show the axis ticks labels, hide otherwise - showMinorLabels: false,// => true to show the axis minor ticks labels, false to hide - labelsAngle: 0, // => labels' angle, in degrees - title: null, // => axis title - titleAngle: 0, // => axis title's angle, in degrees - noTicks: 5, // => number of ticks for automagically generated ticks - minorTickFreq: null, // => number of minor ticks between major ticks for autogenerated ticks - tickFormatter: Flotr.defaultTickFormatter, // => fn: number, Object -> string - tickDecimals: null, // => no. of decimals, null means auto - min: null, // => min. value to show, null means set automatically - max: null, // => max. value to show, null means set automatically - autoscale: false, // => Turns autoscaling on with true - autoscaleMargin: 0, // => margin in % to add if auto-setting min/max - color: null, // => color of the ticks - mode: 'normal', // => can be 'time' or 'normal' - timeFormat: null, - timeMode:'UTC', // => For UTC time ('local' for local time). - timeUnit:'millisecond',// => Unit for time (millisecond, second, minute, hour, day, month, year) - scaling: 'linear', // => Scaling, can be 'linear' or 'logarithmic' - base: Math.E, - titleAlign: 'center', - margin: true // => Turn off margins with false - }, - x2axis: {}, - yaxis: { - ticks: null, // => format: either [1, 3] or [[1, 'a'], 3] - minorTicks: null, // => format: either [1, 3] or [[1, 'a'], 3] - showLabels: true, // => setting to true will show the axis ticks labels, hide otherwise - showMinorLabels: false,// => true to show the axis minor ticks labels, false to hide - labelsAngle: 0, // => labels' angle, in degrees - title: null, // => axis title - titleAngle: 90, // => axis title's angle, in degrees - noTicks: 5, // => number of ticks for automagically generated ticks - minorTickFreq: null, // => number of minor ticks between major ticks for autogenerated ticks - tickFormatter: Flotr.defaultTickFormatter, // => fn: number, Object -> string - tickDecimals: null, // => no. of decimals, null means auto - min: null, // => min. value to show, null means set automatically - max: null, // => max. value to show, null means set automatically - autoscale: false, // => Turns autoscaling on with true - autoscaleMargin: 0, // => margin in % to add if auto-setting min/max - color: null, // => The color of the ticks - scaling: 'linear', // => Scaling, can be 'linear' or 'logarithmic' - base: Math.E, - titleAlign: 'center', - margin: true // => Turn off margins with false - }, - y2axis: { - titleAngle: 270 - }, - grid: { - color: '#545454', // => primary color used for outline and labels - backgroundColor: null, // => null for transparent, else color - backgroundImage: null, // => background image. String or object with src, left and top - watermarkAlpha: 0.4, // => - tickColor: '#DDDDDD', // => color used for the ticks - labelMargin: 3, // => margin in pixels - verticalLines: true, // => whether to show gridlines in vertical direction - minorVerticalLines: null, // => whether to show gridlines for minor ticks in vertical dir. - horizontalLines: true, // => whether to show gridlines in horizontal direction - minorHorizontalLines: null, // => whether to show gridlines for minor ticks in horizontal dir. - outlineWidth: 1, // => width of the grid outline/border in pixels - outline : 'nsew', // => walls of the outline to display - circular: false // => if set to true, the grid will be circular, must be used when radars are drawn - }, - mouse: { - track: false, // => true to track the mouse, no tracking otherwise - trackAll: false, - position: 'se', // => position of the value box (default south-east) - relative: false, // => next to the mouse cursor - trackFormatter: Flotr.defaultTrackFormatter, // => formats the values in the value box - margin: 5, // => margin in pixels of the valuebox - lineColor: '#FF3F19', // => line color of points that are drawn when mouse comes near a value of a series - trackDecimals: 1, // => decimals for the track values - sensibility: 2, // => the lower this number, the more precise you have to aim to show a value - trackY: true, // => whether or not to track the mouse in the y axis - radius: 3, // => radius of the track point - fillColor: null, // => color to fill our select bar with only applies to bar and similar graphs (only bars for now) - fillOpacity: 0.4 // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill - } -}; diff --git a/addons/web_graph/static/lib/flotr2/js/EventAdapter.js b/addons/web_graph/static/lib/flotr2/js/EventAdapter.js deleted file mode 100644 index 062ceeab294..00000000000 --- a/addons/web_graph/static/lib/flotr2/js/EventAdapter.js +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Flotr Event Adapter - */ -(function () { -var - F = Flotr, - bean = F.bean; -F.EventAdapter = { - observe: function(object, name, callback) { - bean.add(object, name, callback); - return this; - }, - fire: function(object, name, args) { - bean.fire(object, name, args); - if (typeof(Prototype) != 'undefined') - Event.fire(object, name, args); - // @TODO Someone who uses mootools, add mootools adapter for existing applciations. - return this; - }, - stopObserving: function(object, name, callback) { - bean.remove(object, name, callback); - return this; - }, - eventPointer: function(e) { - if (!F._.isUndefined(e.touches) && e.touches.length > 0) { - return { - x : e.touches[0].pageX, - y : e.touches[0].pageY - }; - } else if (!F._.isUndefined(e.changedTouches) && e.changedTouches.length > 0) { - return { - x : e.changedTouches[0].pageX, - y : e.changedTouches[0].pageY - }; - } else if (e.pageX || e.pageY) { - return { - x : e.pageX, - y : e.pageY - }; - } else if (e.clientX || e.clientY) { - var - d = document, - b = d.body, - de = d.documentElement; - return { - x: e.clientX + b.scrollLeft + de.scrollLeft, - y: e.clientY + b.scrollTop + de.scrollTop - }; - } - } -}; -})(); diff --git a/addons/web_graph/static/lib/flotr2/js/Flotr.js b/addons/web_graph/static/lib/flotr2/js/Flotr.js deleted file mode 100644 index e8256096adb..00000000000 --- a/addons/web_graph/static/lib/flotr2/js/Flotr.js +++ /dev/null @@ -1,250 +0,0 @@ -/** - * Flotr2 (c) 2012 Carl Sutherland - * MIT License - * Special thanks to: - * Flotr: http://code.google.com/p/flotr/ (fork) - * Flot: https://github.com/flot/flot (original fork) - */ -(function () { - -var - global = this, - previousFlotr = this.Flotr, - Flotr; - -Flotr = { - _: _, - bean: bean, - isIphone: /iphone/i.test(navigator.userAgent), - isIE: (navigator.appVersion.indexOf("MSIE") != -1 ? parseFloat(navigator.appVersion.split("MSIE")[1]) : false), - - /** - * An object of the registered graph types. Use Flotr.addType(type, object) - * to add your own type. - */ - graphTypes: {}, - - /** - * The list of the registered plugins - */ - plugins: {}, - - /** - * Can be used to add your own chart type. - * @param {String} name - Type of chart, like 'pies', 'bars' etc. - * @param {String} graphType - The object containing the basic drawing functions (draw, etc) - */ - addType: function(name, graphType){ - Flotr.graphTypes[name] = graphType; - Flotr.defaultOptions[name] = graphType.options || {}; - Flotr.defaultOptions.defaultType = Flotr.defaultOptions.defaultType || name; - }, - - /** - * Can be used to add a plugin - * @param {String} name - The name of the plugin - * @param {String} plugin - The object containing the plugin's data (callbacks, options, function1, function2, ...) - */ - addPlugin: function(name, plugin){ - Flotr.plugins[name] = plugin; - Flotr.defaultOptions[name] = plugin.options || {}; - }, - - /** - * Draws the graph. This function is here for backwards compatibility with Flotr version 0.1.0alpha. - * You could also draw graphs by directly calling Flotr.Graph(element, data, options). - * @param {Element} el - element to insert the graph into - * @param {Object} data - an array or object of dataseries - * @param {Object} options - an object containing options - * @param {Class} _GraphKlass_ - (optional) Class to pass the arguments to, defaults to Flotr.Graph - * @return {Object} returns a new graph object and of course draws the graph. - */ - draw: function(el, data, options, GraphKlass){ - GraphKlass = GraphKlass || Flotr.Graph; - return new GraphKlass(el, data, options); - }, - - /** - * Recursively merges two objects. - * @param {Object} src - source object (likely the object with the least properties) - * @param {Object} dest - destination object (optional, object with the most properties) - * @return {Object} recursively merged Object - * @TODO See if we can't remove this. - */ - merge: function(src, dest){ - var i, v, result = dest || {}; - - for (i in src) { - v = src[i]; - if (v && typeof(v) === 'object') { - if (v.constructor === Array) { - result[i] = this._.clone(v); - } else if (v.constructor !== RegExp && !this._.isElement(v)) { - result[i] = Flotr.merge(v, (dest ? dest[i] : undefined)); - } else { - result[i] = v; - } - } else { - result[i] = v; - } - } - - return result; - }, - - /** - * Recursively clones an object. - * @param {Object} object - The object to clone - * @return {Object} the clone - * @TODO See if we can't remove this. - */ - clone: function(object){ - return Flotr.merge(object, {}); - }, - - /** - * Function calculates the ticksize and returns it. - * @param {Integer} noTicks - number of ticks - * @param {Integer} min - lower bound integer value for the current axis - * @param {Integer} max - upper bound integer value for the current axis - * @param {Integer} decimals - number of decimals for the ticks - * @return {Integer} returns the ticksize in pixels - */ - getTickSize: function(noTicks, min, max, decimals){ - var delta = (max - min) / noTicks, - magn = Flotr.getMagnitude(delta), - tickSize = 10, - norm = delta / magn; // Norm is between 1.0 and 10.0. - - if(norm < 1.5) tickSize = 1; - else if(norm < 2.25) tickSize = 2; - else if(norm < 3) tickSize = ((decimals === 0) ? 2 : 2.5); - else if(norm < 7.5) tickSize = 5; - - return tickSize * magn; - }, - - /** - * Default tick formatter. - * @param {String, Integer} val - tick value integer - * @param {Object} axisOpts - the axis' options - * @return {String} formatted tick string - */ - defaultTickFormatter: function(val, axisOpts){ - return val+''; - }, - - /** - * Formats the mouse tracker values. - * @param {Object} obj - Track value Object {x:..,y:..} - * @return {String} Formatted track string - */ - defaultTrackFormatter: function(obj){ - return '('+obj.x+', '+obj.y+')'; - }, - - /** - * Utility function to convert file size values in bytes to kB, MB, ... - * @param value {Number} - The value to convert - * @param precision {Number} - The number of digits after the comma (default: 2) - * @param base {Number} - The base (default: 1000) - */ - engineeringNotation: function(value, precision, base){ - var sizes = ['Y','Z','E','P','T','G','M','k',''], - fractionSizes = ['y','z','a','f','p','n','µ','m',''], - total = sizes.length; - - base = base || 1000; - precision = Math.pow(10, precision || 2); - - if (value === 0) return 0; - - if (value > 1) { - while (total-- && (value >= base)) value /= base; - } - else { - sizes = fractionSizes; - total = sizes.length; - while (total-- && (value < 1)) value *= base; - } - - return (Math.round(value * precision) / precision) + sizes[total]; - }, - - /** - * Returns the magnitude of the input value. - * @param {Integer, Float} x - integer or float value - * @return {Integer, Float} returns the magnitude of the input value - */ - getMagnitude: function(x){ - return Math.pow(10, Math.floor(Math.log(x) / Math.LN10)); - }, - toPixel: function(val){ - return Math.floor(val)+0.5;//((val-Math.round(val) < 0.4) ? (Math.floor(val)-0.5) : val); - }, - toRad: function(angle){ - return -angle * (Math.PI/180); - }, - floorInBase: function(n, base) { - return base * Math.floor(n / base); - }, - drawText: function(ctx, text, x, y, style) { - if (!ctx.fillText) { - ctx.drawText(text, x, y, style); - return; - } - - style = this._.extend({ - size: Flotr.defaultOptions.fontSize, - color: '#000000', - textAlign: 'left', - textBaseline: 'bottom', - weight: 1, - angle: 0 - }, style); - - ctx.save(); - ctx.translate(x, y); - ctx.rotate(style.angle); - ctx.fillStyle = style.color; - ctx.font = (style.weight > 1 ? "bold " : "") + (style.size*1.3) + "px sans-serif"; - ctx.textAlign = style.textAlign; - ctx.textBaseline = style.textBaseline; - ctx.fillText(text, 0, 0); - ctx.restore(); - }, - getBestTextAlign: function(angle, style) { - style = style || {textAlign: 'center', textBaseline: 'middle'}; - angle += Flotr.getTextAngleFromAlign(style); - - if (Math.abs(Math.cos(angle)) > 10e-3) - style.textAlign = (Math.cos(angle) > 0 ? 'right' : 'left'); - - if (Math.abs(Math.sin(angle)) > 10e-3) - style.textBaseline = (Math.sin(angle) > 0 ? 'top' : 'bottom'); - - return style; - }, - alignTable: { - 'right middle' : 0, - 'right top' : Math.PI/4, - 'center top' : Math.PI/2, - 'left top' : 3*(Math.PI/4), - 'left middle' : Math.PI, - 'left bottom' : -3*(Math.PI/4), - 'center bottom': -Math.PI/2, - 'right bottom' : -Math.PI/4, - 'center middle': 0 - }, - getTextAngleFromAlign: function(style) { - return Flotr.alignTable[style.textAlign+' '+style.textBaseline] || 0; - }, - noConflict : function () { - global.Flotr = previousFlotr; - return this; - } -}; - -global.Flotr = Flotr; - -})(); diff --git a/addons/web_graph/static/lib/flotr2/js/Graph.js b/addons/web_graph/static/lib/flotr2/js/Graph.js deleted file mode 100644 index 0e0310a5f4e..00000000000 --- a/addons/web_graph/static/lib/flotr2/js/Graph.js +++ /dev/null @@ -1,745 +0,0 @@ -/** - * Flotr Graph class that plots a graph on creation. - */ -(function () { - -var - D = Flotr.DOM, - E = Flotr.EventAdapter, - _ = Flotr._, - flotr = Flotr; -/** - * Flotr Graph constructor. - * @param {Element} el - element to insert the graph into - * @param {Object} data - an array or object of dataseries - * @param {Object} options - an object containing options - */ -Graph = function(el, data, options){ -// Let's see if we can get away with out this [JS] -// try { - this._setEl(el); - this._initMembers(); - this._initPlugins(); - - E.fire(this.el, 'flotr:beforeinit', [this]); - - this.data = data; - this.series = flotr.Series.getSeries(data); - this._initOptions(options); - this._initGraphTypes(); - this._initCanvas(); - this._text = new flotr.Text({ - element : this.el, - ctx : this.ctx, - html : this.options.HtmlText, - textEnabled : this.textEnabled - }); - E.fire(this.el, 'flotr:afterconstruct', [this]); - this._initEvents(); - - this.findDataRanges(); - this.calculateSpacing(); - - this.draw(_.bind(function() { - E.fire(this.el, 'flotr:afterinit', [this]); - }, this)); -/* - try { - } catch (e) { - try { - console.error(e); - } catch (e2) {} - }*/ -}; - -function observe (object, name, callback) { - E.observe.apply(this, arguments); - this._handles.push(arguments); - return this; -} - -Graph.prototype = { - - destroy: function () { - E.fire(this.el, 'flotr:destroy'); - _.each(this._handles, function (handle) { - E.stopObserving.apply(this, handle); - }); - this._handles = []; - this.el.graph = null; - }, - - observe : observe, - - /** - * @deprecated - */ - _observe : observe, - - processColor: function(color, options){ - var o = { x1: 0, y1: 0, x2: this.plotWidth, y2: this.plotHeight, opacity: 1, ctx: this.ctx }; - _.extend(o, options); - return flotr.Color.processColor(color, o); - }, - /** - * Function determines the min and max values for the xaxis and yaxis. - * - * TODO logarithmic range validation (consideration of 0) - */ - findDataRanges: function(){ - var a = this.axes, - xaxis, yaxis, range; - - _.each(this.series, function (series) { - range = series.getRange(); - if (range) { - xaxis = series.xaxis; - yaxis = series.yaxis; - xaxis.datamin = Math.min(range.xmin, xaxis.datamin); - xaxis.datamax = Math.max(range.xmax, xaxis.datamax); - yaxis.datamin = Math.min(range.ymin, yaxis.datamin); - yaxis.datamax = Math.max(range.ymax, yaxis.datamax); - xaxis.used = (xaxis.used || range.xused); - yaxis.used = (yaxis.used || range.yused); - } - }, this); - - // Check for empty data, no data case (none used) - if (!a.x.used && !a.x2.used) a.x.used = true; - if (!a.y.used && !a.y2.used) a.y.used = true; - - _.each(a, function (axis) { - axis.calculateRange(); - }); - - var - types = _.keys(flotr.graphTypes), - drawn = false; - - _.each(this.series, function (series) { - if (series.hide) return; - _.each(types, function (type) { - if (series[type] && series[type].show) { - this.extendRange(type, series); - drawn = true; - } - }, this); - if (!drawn) { - this.extendRange(this.options.defaultType, series); - } - }, this); - }, - - extendRange : function (type, series) { - if (this[type].extendRange) this[type].extendRange(series, series.data, series[type], this[type]); - if (this[type].extendYRange) this[type].extendYRange(series.yaxis, series.data, series[type], this[type]); - if (this[type].extendXRange) this[type].extendXRange(series.xaxis, series.data, series[type], this[type]); - }, - - /** - * Calculates axis label sizes. - */ - calculateSpacing: function(){ - - var a = this.axes, - options = this.options, - series = this.series, - margin = options.grid.labelMargin, - T = this._text, - x = a.x, - x2 = a.x2, - y = a.y, - y2 = a.y2, - maxOutset = options.grid.outlineWidth, - i, j, l, dim; - - // TODO post refactor, fix this - _.each(a, function (axis) { - axis.calculateTicks(); - axis.calculateTextDimensions(T, options); - }); - - // Title height - dim = T.dimensions( - options.title, - {size: options.fontSize*1.5}, - 'font-size:1em;font-weight:bold;', - 'flotr-title' - ); - this.titleHeight = dim.height; - - // Subtitle height - dim = T.dimensions( - options.subtitle, - {size: options.fontSize}, - 'font-size:smaller;', - 'flotr-subtitle' - ); - this.subtitleHeight = dim.height; - - for(j = 0; j < options.length; ++j){ - if (series[j].points.show){ - maxOutset = Math.max(maxOutset, series[j].points.radius + series[j].points.lineWidth/2); - } - } - - var p = this.plotOffset; - if (x.options.margin === false) { - p.bottom = 0; - p.top = 0; - } else { - p.bottom += (options.grid.circular ? 0 : (x.used && x.options.showLabels ? (x.maxLabel.height + margin) : 0)) + - (x.used && x.options.title ? (x.titleSize.height + margin) : 0) + maxOutset; - - p.top += (options.grid.circular ? 0 : (x2.used && x2.options.showLabels ? (x2.maxLabel.height + margin) : 0)) + - (x2.used && x2.options.title ? (x2.titleSize.height + margin) : 0) + this.subtitleHeight + this.titleHeight + maxOutset; - } - if (y.options.margin === false) { - p.left = 0; - p.right = 0; - } else { - p.left += (options.grid.circular ? 0 : (y.used && y.options.showLabels ? (y.maxLabel.width + margin) : 0)) + - (y.used && y.options.title ? (y.titleSize.width + margin) : 0) + maxOutset; - - p.right += (options.grid.circular ? 0 : (y2.used && y2.options.showLabels ? (y2.maxLabel.width + margin) : 0)) + - (y2.used && y2.options.title ? (y2.titleSize.width + margin) : 0) + maxOutset; - } - - p.top = Math.floor(p.top); // In order the outline not to be blured - - this.plotWidth = this.canvasWidth - p.left - p.right; - this.plotHeight = this.canvasHeight - p.bottom - p.top; - - // TODO post refactor, fix this - x.length = x2.length = this.plotWidth; - y.length = y2.length = this.plotHeight; - y.offset = y2.offset = this.plotHeight; - x.setScale(); - x2.setScale(); - y.setScale(); - y2.setScale(); - }, - /** - * Draws grid, labels, series and outline. - */ - draw: function(after) { - - var - context = this.ctx, - i; - - E.fire(this.el, 'flotr:beforedraw', [this.series, this]); - - if (this.series.length) { - - context.save(); - context.translate(this.plotOffset.left, this.plotOffset.top); - - for (i = 0; i < this.series.length; i++) { - if (!this.series[i].hide) this.drawSeries(this.series[i]); - } - - context.restore(); - this.clip(); - } - - E.fire(this.el, 'flotr:afterdraw', [this.series, this]); - if (after) after(); - }, - /** - * Actually draws the graph. - * @param {Object} series - series to draw - */ - drawSeries: function(series){ - - function drawChart (series, typeKey) { - var options = this.getOptions(series, typeKey); - this[typeKey].draw(options); - } - - var drawn = false; - series = series || this.series; - - _.each(flotr.graphTypes, function (type, typeKey) { - if (series[typeKey] && series[typeKey].show && this[typeKey]) { - drawn = true; - drawChart.call(this, series, typeKey); - } - }, this); - - if (!drawn) drawChart.call(this, series, this.options.defaultType); - }, - - getOptions : function (series, typeKey) { - var - type = series[typeKey], - graphType = this[typeKey], - options = { - context : this.ctx, - width : this.plotWidth, - height : this.plotHeight, - fontSize : this.options.fontSize, - fontColor : this.options.fontColor, - textEnabled : this.textEnabled, - htmlText : this.options.HtmlText, - text : this._text, // TODO Is this necessary? - element : this.el, - data : series.data, - color : series.color, - shadowSize : series.shadowSize, - xScale : _.bind(series.xaxis.d2p, series.xaxis), - yScale : _.bind(series.yaxis.d2p, series.yaxis) - }; - - options = flotr.merge(type, options); - - // Fill - options.fillStyle = this.processColor( - type.fillColor || series.color, - {opacity: type.fillOpacity} - ); - - return options; - }, - /** - * Calculates the coordinates from a mouse event object. - * @param {Event} event - Mouse Event object. - * @return {Object} Object with coordinates of the mouse. - */ - getEventPosition: function (e){ - - var - d = document, - b = d.body, - de = d.documentElement, - axes = this.axes, - plotOffset = this.plotOffset, - lastMousePos = this.lastMousePos, - pointer = E.eventPointer(e), - dx = pointer.x - lastMousePos.pageX, - dy = pointer.y - lastMousePos.pageY, - r, rx, ry; - - if ('ontouchstart' in this.el) { - r = D.position(this.overlay); - rx = pointer.x - r.left - plotOffset.left; - ry = pointer.y - r.top - plotOffset.top; - } else { - r = this.overlay.getBoundingClientRect(); - rx = e.clientX - r.left - plotOffset.left - b.scrollLeft - de.scrollLeft; - ry = e.clientY - r.top - plotOffset.top - b.scrollTop - de.scrollTop; - } - - return { - x: axes.x.p2d(rx), - x2: axes.x2.p2d(rx), - y: axes.y.p2d(ry), - y2: axes.y2.p2d(ry), - relX: rx, - relY: ry, - dX: dx, - dY: dy, - absX: pointer.x, - absY: pointer.y, - pageX: pointer.x, - pageY: pointer.y - }; - }, - /** - * Observes the 'click' event and fires the 'flotr:click' event. - * @param {Event} event - 'click' Event object. - */ - clickHandler: function(event){ - if(this.ignoreClick){ - this.ignoreClick = false; - return this.ignoreClick; - } - E.fire(this.el, 'flotr:click', [this.getEventPosition(event), this]); - }, - /** - * Observes mouse movement over the graph area. Fires the 'flotr:mousemove' event. - * @param {Event} event - 'mousemove' Event object. - */ - mouseMoveHandler: function(event){ - if (this.mouseDownMoveHandler) return; - var pos = this.getEventPosition(event); - E.fire(this.el, 'flotr:mousemove', [event, pos, this]); - this.lastMousePos = pos; - }, - /** - * Observes the 'mousedown' event. - * @param {Event} event - 'mousedown' Event object. - */ - mouseDownHandler: function (event){ - - /* - // @TODO Context menu? - if(event.isRightClick()) { - event.stop(); - - var overlay = this.overlay; - overlay.hide(); - - function cancelContextMenu () { - overlay.show(); - E.stopObserving(document, 'mousemove', cancelContextMenu); - } - E.observe(document, 'mousemove', cancelContextMenu); - return; - } - */ - - if (this.mouseUpHandler) return; - this.mouseUpHandler = _.bind(function (e) { - E.stopObserving(document, 'mouseup', this.mouseUpHandler); - E.stopObserving(document, 'mousemove', this.mouseDownMoveHandler); - this.mouseDownMoveHandler = null; - this.mouseUpHandler = null; - // @TODO why? - //e.stop(); - E.fire(this.el, 'flotr:mouseup', [e, this]); - }, this); - this.mouseDownMoveHandler = _.bind(function (e) { - var pos = this.getEventPosition(e); - E.fire(this.el, 'flotr:mousemove', [event, pos, this]); - this.lastMousePos = pos; - }, this); - E.observe(document, 'mouseup', this.mouseUpHandler); - E.observe(document, 'mousemove', this.mouseDownMoveHandler); - E.fire(this.el, 'flotr:mousedown', [event, this]); - this.ignoreClick = false; - }, - drawTooltip: function(content, x, y, options) { - var mt = this.getMouseTrack(), - style = 'opacity:0.7;background-color:#000;color:#fff;display:none;position:absolute;padding:2px 8px;-moz-border-radius:4px;border-radius:4px;white-space:nowrap;', - p = options.position, - m = options.margin, - plotOffset = this.plotOffset; - - if(x !== null && y !== null){ - if (!options.relative) { // absolute to the canvas - if(p.charAt(0) == 'n') style += 'top:' + (m + plotOffset.top) + 'px;bottom:auto;'; - else if(p.charAt(0) == 's') style += 'bottom:' + (m + plotOffset.bottom) + 'px;top:auto;'; - if(p.charAt(1) == 'e') style += 'right:' + (m + plotOffset.right) + 'px;left:auto;'; - else if(p.charAt(1) == 'w') style += 'left:' + (m + plotOffset.left) + 'px;right:auto;'; - } - else { // relative to the mouse - if(p.charAt(0) == 'n') style += 'bottom:' + (m - plotOffset.top - y + this.canvasHeight) + 'px;top:auto;'; - else if(p.charAt(0) == 's') style += 'top:' + (m + plotOffset.top + y) + 'px;bottom:auto;'; - if(p.charAt(1) == 'e') style += 'left:' + (m + plotOffset.left + x) + 'px;right:auto;'; - else if(p.charAt(1) == 'w') style += 'right:' + (m - plotOffset.left - x + this.canvasWidth) + 'px;left:auto;'; - } - - mt.style.cssText = style; - D.empty(mt); - D.insert(mt, content); - D.show(mt); - } - else { - D.hide(mt); - } - }, - - clip: function () { - - var - ctx = this.ctx, - o = this.plotOffset, - w = this.canvasWidth, - h = this.canvasHeight; - - if (flotr.isIE && flotr.isIE < 9) { - // Clipping for excanvas :-( - ctx.save(); - ctx.fillStyle = this.processColor(this.options.ieBackgroundColor); - ctx.fillRect(0, 0, w, o.top); - ctx.fillRect(0, 0, o.left, h); - ctx.fillRect(0, h - o.bottom, w, o.bottom); - ctx.fillRect(w - o.right, 0, o.right,h); - ctx.restore(); - } else { - ctx.clearRect(0, 0, w, o.top); - ctx.clearRect(0, 0, o.left, h); - ctx.clearRect(0, h - o.bottom, w, o.bottom); - ctx.clearRect(w - o.right, 0, o.right,h); - } - }, - - _initMembers: function() { - this._handles = []; - this.lastMousePos = {pageX: null, pageY: null }; - this.plotOffset = {left: 0, right: 0, top: 0, bottom: 0}; - this.ignoreClick = true; - this.prevHit = null; - }, - - _initGraphTypes: function() { - _.each(flotr.graphTypes, function(handler, graphType){ - this[graphType] = flotr.clone(handler); - }, this); - }, - - _initEvents: function () { - - var - el = this.el, - touchendHandler, movement, touchend; - - if ('ontouchstart' in el) { - - touchendHandler = _.bind(function (e) { - touchend = true; - E.stopObserving(document, 'touchend', touchendHandler); - E.fire(el, 'flotr:mouseup', [event, this]); - this.multitouches = null; - - if (!movement) { - this.clickHandler(e); - } - }, this); - - this.observe(this.overlay, 'touchstart', _.bind(function (e) { - movement = false; - touchend = false; - this.ignoreClick = false; - - if (e.touches && e.touches.length > 1) { - this.multitouches = e.touches; - } - - E.fire(el, 'flotr:mousedown', [event, this]); - this.observe(document, 'touchend', touchendHandler); - }, this)); - - this.observe(this.overlay, 'touchmove', _.bind(function (e) { - - var pos = this.getEventPosition(e); - - e.preventDefault(); - - movement = true; - - if (this.multitouches || (e.touches && e.touches.length > 1)) { - this.multitouches = e.touches; - } else { - if (!touchend) { - E.fire(el, 'flotr:mousemove', [event, pos, this]); - } - } - this.lastMousePos = pos; - }, this)); - - } else { - this. - observe(this.overlay, 'mousedown', _.bind(this.mouseDownHandler, this)). - observe(el, 'mousemove', _.bind(this.mouseMoveHandler, this)). - observe(this.overlay, 'click', _.bind(this.clickHandler, this)). - observe(el, 'mouseout', function () { - E.fire(el, 'flotr:mouseout'); - }); - } - }, - - /** - * Initializes the canvas and it's overlay canvas element. When the browser is IE, this makes use - * of excanvas. The overlay canvas is inserted for displaying interactions. After the canvas elements - * are created, the elements are inserted into the container element. - */ - _initCanvas: function(){ - var el = this.el, - o = this.options, - children = el.children, - removedChildren = [], - child, i, - size, style; - - // Empty the el - for (i = children.length; i--;) { - child = children[i]; - if (!this.canvas && child.className === 'flotr-canvas') { - this.canvas = child; - } else if (!this.overlay && child.className === 'flotr-overlay') { - this.overlay = child; - } else { - removedChildren.push(child); - } - } - for (i = removedChildren.length; i--;) { - el.removeChild(removedChildren[i]); - } - - D.setStyles(el, {position: 'relative'}); // For positioning labels and overlay. - size = {}; - size.width = el.clientWidth; - size.height = el.clientHeight; - - if(size.width <= 0 || size.height <= 0 || o.resolution <= 0){ - throw 'Invalid dimensions for plot, width = ' + size.width + ', height = ' + size.height + ', resolution = ' + o.resolution; - } - - // Main canvas for drawing graph types - this.canvas = getCanvas(this.canvas, 'canvas'); - // Overlay canvas for interactive features - this.overlay = getCanvas(this.overlay, 'overlay'); - this.ctx = getContext(this.canvas); - this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); - this.octx = getContext(this.overlay); - this.octx.clearRect(0, 0, this.overlay.width, this.overlay.height); - this.canvasHeight = size.height; - this.canvasWidth = size.width; - this.textEnabled = !!this.ctx.drawText || !!this.ctx.fillText; // Enable text functions - - function getCanvas(canvas, name){ - if(!canvas){ - canvas = D.create('canvas'); - if (typeof FlashCanvas != "undefined" && typeof canvas.getContext === 'function') { - FlashCanvas.initElement(canvas); - } - canvas.className = 'flotr-'+name; - canvas.style.cssText = 'position:absolute;left:0px;top:0px;'; - D.insert(el, canvas); - } - _.each(size, function(size, attribute){ - D.show(canvas); - if (name == 'canvas' && canvas.getAttribute(attribute) === size) { - return; - } - canvas.setAttribute(attribute, size * o.resolution); - canvas.style[attribute] = size + 'px'; - }); - canvas.context_ = null; // Reset the ExCanvas context - return canvas; - } - - function getContext(canvas){ - if(window.G_vmlCanvasManager) window.G_vmlCanvasManager.initElement(canvas); // For ExCanvas - var context = canvas.getContext('2d'); - if(!window.G_vmlCanvasManager) context.scale(o.resolution, o.resolution); - return context; - } - }, - - _initPlugins: function(){ - // TODO Should be moved to flotr and mixed in. - _.each(flotr.plugins, function(plugin, name){ - _.each(plugin.callbacks, function(fn, c){ - this.observe(this.el, c, _.bind(fn, this)); - }, this); - this[name] = flotr.clone(plugin); - _.each(this[name], function(fn, p){ - if (_.isFunction(fn)) - this[name][p] = _.bind(fn, this); - }, this); - }, this); - }, - - /** - * Sets options and initializes some variables and color specific values, used by the constructor. - * @param {Object} opts - options object - */ - _initOptions: function(opts){ - var options = flotr.clone(flotr.defaultOptions); - options.x2axis = _.extend(_.clone(options.xaxis), options.x2axis); - options.y2axis = _.extend(_.clone(options.yaxis), options.y2axis); - this.options = flotr.merge(opts || {}, options); - - if (this.options.grid.minorVerticalLines === null && - this.options.xaxis.scaling === 'logarithmic') { - this.options.grid.minorVerticalLines = true; - } - if (this.options.grid.minorHorizontalLines === null && - this.options.yaxis.scaling === 'logarithmic') { - this.options.grid.minorHorizontalLines = true; - } - - E.fire(this.el, 'flotr:afterinitoptions', [this]); - - this.axes = flotr.Axis.getAxes(this.options); - - // Initialize some variables used throughout this function. - var assignedColors = [], - colors = [], - ln = this.series.length, - neededColors = this.series.length, - oc = this.options.colors, - usedColors = [], - variation = 0, - c, i, j, s; - - // Collect user-defined colors from series. - for(i = neededColors - 1; i > -1; --i){ - c = this.series[i].color; - if(c){ - --neededColors; - if(_.isNumber(c)) assignedColors.push(c); - else usedColors.push(flotr.Color.parse(c)); - } - } - - // Calculate the number of colors that need to be generated. - for(i = assignedColors.length - 1; i > -1; --i) - neededColors = Math.max(neededColors, assignedColors[i] + 1); - - // Generate needed number of colors. - for(i = 0; colors.length < neededColors;){ - c = (oc.length == i) ? new flotr.Color(100, 100, 100) : flotr.Color.parse(oc[i]); - - // Make sure each serie gets a different color. - var sign = variation % 2 == 1 ? -1 : 1, - factor = 1 + sign * Math.ceil(variation / 2) * 0.2; - c.scale(factor, factor, factor); - - /** - * @todo if we're getting too close to something else, we should probably skip this one - */ - colors.push(c); - - if(++i >= oc.length){ - i = 0; - ++variation; - } - } - - // Fill the options with the generated colors. - for(i = 0, j = 0; i < ln; ++i){ - s = this.series[i]; - - // Assign the color. - if (!s.color){ - s.color = colors[j++].toString(); - }else if(_.isNumber(s.color)){ - s.color = colors[s.color].toString(); - } - - // Every series needs an axis - if (!s.xaxis) s.xaxis = this.axes.x; - if (s.xaxis == 1) s.xaxis = this.axes.x; - else if (s.xaxis == 2) s.xaxis = this.axes.x2; - - if (!s.yaxis) s.yaxis = this.axes.y; - if (s.yaxis == 1) s.yaxis = this.axes.y; - else if (s.yaxis == 2) s.yaxis = this.axes.y2; - - // Apply missing options to the series. - for (var t in flotr.graphTypes){ - s[t] = _.extend(_.clone(this.options[t]), s[t]); - } - s.mouse = _.extend(_.clone(this.options.mouse), s.mouse); - - if (_.isUndefined(s.shadowSize)) s.shadowSize = this.options.shadowSize; - } - }, - - _setEl: function(el) { - if (!el) throw 'The target container doesn\'t exist'; - else if (el.graph instanceof Graph) el.graph.destroy(); - else if (!el.clientWidth) throw 'The target container must be visible'; - - el.graph = this; - this.el = el; - } -}; - -Flotr.Graph = Graph; - -})(); diff --git a/addons/web_graph/static/lib/flotr2/js/Series.js b/addons/web_graph/static/lib/flotr2/js/Series.js deleted file mode 100644 index e9a0c0fbeaa..00000000000 --- a/addons/web_graph/static/lib/flotr2/js/Series.js +++ /dev/null @@ -1,74 +0,0 @@ -/** - * Flotr Series Library - */ - -(function () { - -var - _ = Flotr._; - -function Series (o) { - _.extend(this, o); -} - -Series.prototype = { - - getRange: function () { - - var - data = this.data, - length = data.length, - xmin = Number.MAX_VALUE, - ymin = Number.MAX_VALUE, - xmax = -Number.MAX_VALUE, - ymax = -Number.MAX_VALUE, - xused = false, - yused = false, - x, y, i; - - if (length < 0 || this.hide) return false; - - for (i = 0; i < length; i++) { - x = data[i][0]; - y = data[i][1]; - if (x < xmin) { xmin = x; xused = true; } - if (x > xmax) { xmax = x; xused = true; } - if (y < ymin) { ymin = y; yused = true; } - if (y > ymax) { ymax = y; yused = true; } - } - - return { - xmin : xmin, - xmax : xmax, - ymin : ymin, - ymax : ymax, - xused : xused, - yused : yused - }; - } -}; - -_.extend(Series, { - /** - * Collects dataseries from input and parses the series into the right format. It returns an Array - * of Objects each having at least the 'data' key set. - * @param {Array, Object} data - Object or array of dataseries - * @return {Array} Array of Objects parsed into the right format ({(...,) data: [[x1,y1], [x2,y2], ...] (, ...)}) - */ - getSeries: function(data){ - return _.map(data, function(s){ - var series; - if (s.data) { - series = new Series(); - _.extend(series, s); - } else { - series = new Series({data:s}); - } - return series; - }); - } -}); - -Flotr.Series = Series; - -})(); diff --git a/addons/web_graph/static/lib/flotr2/js/Text.js b/addons/web_graph/static/lib/flotr2/js/Text.js deleted file mode 100644 index f24cf7ac4bf..00000000000 --- a/addons/web_graph/static/lib/flotr2/js/Text.js +++ /dev/null @@ -1,88 +0,0 @@ -/** - * Text Utilities - */ -(function () { - -var - F = Flotr, - D = F.DOM, - _ = F._, - -Text = function (o) { - this.o = o; -}; - -Text.prototype = { - - dimensions : function (text, canvasStyle, htmlStyle, className) { - - if (!text) return { width : 0, height : 0 }; - - return (this.o.html) ? - this.html(text, this.o.element, htmlStyle, className) : - this.canvas(text, canvasStyle); - }, - - canvas : function (text, style) { - - if (!this.o.textEnabled) return; - style = style || {}; - - var - metrics = this.measureText(text, style), - width = metrics.width, - height = style.size || F.defaultOptions.fontSize, - angle = style.angle || 0, - cosAngle = Math.cos(angle), - sinAngle = Math.sin(angle), - widthPadding = 2, - heightPadding = 6, - bounds; - - bounds = { - width: Math.abs(cosAngle * width) + Math.abs(sinAngle * height) + widthPadding, - height: Math.abs(sinAngle * width) + Math.abs(cosAngle * height) + heightPadding - }; - - return bounds; - }, - - html : function (text, element, style, className) { - - var div = D.create('div'); - - D.setStyles(div, { 'position' : 'absolute', 'top' : '-10000px' }); - D.insert(div, '<div style="'+style+'" class="'+className+' flotr-dummy-div">' + text + '</div>'); - D.insert(this.o.element, div); - - return D.size(div); - }, - - measureText : function (text, style) { - - var - context = this.o.ctx, - metrics; - - if (!context.fillText || (F.isIphone && context.measure)) { - return { width : context.measure(text, style)}; - } - - style = _.extend({ - size: F.defaultOptions.fontSize, - weight: 1, - angle: 0 - }, style); - - context.save(); - context.font = (style.weight > 1 ? "bold " : "") + (style.size*1.3) + "px sans-serif"; - metrics = context.measureText(text); - context.restore(); - - return metrics; - } -}; - -Flotr.Text = Text; - -})(); diff --git a/addons/web_graph/static/lib/flotr2/js/plugins/crosshair.js b/addons/web_graph/static/lib/flotr2/js/plugins/crosshair.js deleted file mode 100644 index 611ebf85800..00000000000 --- a/addons/web_graph/static/lib/flotr2/js/plugins/crosshair.js +++ /dev/null @@ -1,84 +0,0 @@ -(function () { - -var D = Flotr.DOM; - -Flotr.addPlugin('crosshair', { - options: { - mode: null, // => one of null, 'x', 'y' or 'xy' - color: '#FF0000', // => crosshair color - hideCursor: true // => hide the cursor when the crosshair is shown - }, - callbacks: { - 'flotr:mousemove': function(e, pos) { - if (this.options.crosshair.mode) { - this.crosshair.clearCrosshair(); - this.crosshair.drawCrosshair(pos); - } - } - }, - /** - * Draws the selection box. - */ - drawCrosshair: function(pos) { - var octx = this.octx, - options = this.options.crosshair, - plotOffset = this.plotOffset, - x = plotOffset.left + pos.relX + 0.5, - y = plotOffset.top + pos.relY + 0.5; - - if (pos.relX < 0 || pos.relY < 0 || pos.relX > this.plotWidth || pos.relY > this.plotHeight) { - this.el.style.cursor = null; - D.removeClass(this.el, 'flotr-crosshair'); - return; - } - - if (options.hideCursor) { - this.el.style.cursor = 'none'; - D.addClass(this.el, 'flotr-crosshair'); - } - - octx.save(); - octx.strokeStyle = options.color; - octx.lineWidth = 1; - octx.beginPath(); - - if (options.mode.indexOf('x') != -1) { - octx.moveTo(x, plotOffset.top); - octx.lineTo(x, plotOffset.top + this.plotHeight); - } - - if (options.mode.indexOf('y') != -1) { - octx.moveTo(plotOffset.left, y); - octx.lineTo(plotOffset.left + this.plotWidth, y); - } - - octx.stroke(); - octx.restore(); - }, - /** - * Removes the selection box from the overlay canvas. - */ - clearCrosshair: function() { - - var - plotOffset = this.plotOffset, - position = this.lastMousePos, - context = this.octx; - - if (position) { - context.clearRect( - position.relX + plotOffset.left, - plotOffset.top, - 1, - this.plotHeight + 1 - ); - context.clearRect( - plotOffset.left, - position.relY + plotOffset.top, - this.plotWidth + 1, - 1 - ); - } - } -}); -})(); diff --git a/addons/web_graph/static/lib/flotr2/js/plugins/download.js b/addons/web_graph/static/lib/flotr2/js/plugins/download.js deleted file mode 100644 index 5d585325be9..00000000000 --- a/addons/web_graph/static/lib/flotr2/js/plugins/download.js +++ /dev/null @@ -1,51 +0,0 @@ -(function() { - -var - D = Flotr.DOM, - _ = Flotr._; - -function getImage (type, canvas, width, height) { - - // TODO add scaling for w / h - var - mime = 'image/'+type, - data = canvas.toDataURL(mime), - image = new Image(); - image.src = data; - return image; -} - -Flotr.addPlugin('download', { - - saveImage: function (type, width, height, replaceCanvas) { - var image = null; - if (Flotr.isIE && Flotr.isIE < 9) { - image = '<html><body>'+this.canvas.firstChild.innerHTML+'</body></html>'; - return window.open().document.write(image); - } - - if (type !== 'jpeg' && type !== 'png') return; - - image = getImage(type, this.canvas, width, height); - - if (_.isElement(image) && replaceCanvas) { - this.download.restoreCanvas(); - D.hide(this.canvas); - D.hide(this.overlay); - D.setStyles({position: 'absolute'}); - D.insert(this.el, image); - this.saveImageElement = image; - } else { - return window.open(image.src); - } - }, - - restoreCanvas: function() { - D.show(this.canvas); - D.show(this.overlay); - if (this.saveImageElement) this.el.removeChild(this.saveImageElement); - this.saveImageElement = null; - } -}); - -})(); diff --git a/addons/web_graph/static/lib/flotr2/js/plugins/grid.js b/addons/web_graph/static/lib/flotr2/js/plugins/grid.js deleted file mode 100644 index ab90d8d591a..00000000000 --- a/addons/web_graph/static/lib/flotr2/js/plugins/grid.js +++ /dev/null @@ -1,208 +0,0 @@ -(function () { - -var E = Flotr.EventAdapter, - _ = Flotr._; - -Flotr.addPlugin('graphGrid', { - - callbacks: { - 'flotr:beforedraw' : function () { - this.graphGrid.drawGrid(); - }, - 'flotr:afterdraw' : function () { - this.graphGrid.drawOutline(); - } - }, - - drawGrid: function(){ - - var - ctx = this.ctx, - options = this.options, - grid = options.grid, - verticalLines = grid.verticalLines, - horizontalLines = grid.horizontalLines, - minorVerticalLines = grid.minorVerticalLines, - minorHorizontalLines = grid.minorHorizontalLines, - plotHeight = this.plotHeight, - plotWidth = this.plotWidth, - a, v, i, j; - - if(verticalLines || minorVerticalLines || - horizontalLines || minorHorizontalLines){ - E.fire(this.el, 'flotr:beforegrid', [this.axes.x, this.axes.y, options, this]); - } - ctx.save(); - ctx.lineWidth = 1; - ctx.strokeStyle = grid.tickColor; - - function circularHorizontalTicks (ticks) { - for(i = 0; i < ticks.length; ++i){ - var ratio = ticks[i].v / a.max; - for(j = 0; j <= sides; ++j){ - ctx[j === 0 ? 'moveTo' : 'lineTo']( - Math.cos(j*coeff+angle)*radius*ratio, - Math.sin(j*coeff+angle)*radius*ratio - ); - } - } - } - function drawGridLines (ticks, callback) { - _.each(_.pluck(ticks, 'v'), function(v){ - // Don't show lines on upper and lower bounds. - if ((v <= a.min || v >= a.max) || - (v == a.min || v == a.max) && grid.outlineWidth) - return; - callback(Math.floor(a.d2p(v)) + ctx.lineWidth/2); - }); - } - function drawVerticalLines (x) { - ctx.moveTo(x, 0); - ctx.lineTo(x, plotHeight); - } - function drawHorizontalLines (y) { - ctx.moveTo(0, y); - ctx.lineTo(plotWidth, y); - } - - if (grid.circular) { - ctx.translate(this.plotOffset.left+plotWidth/2, this.plotOffset.top+plotHeight/2); - var radius = Math.min(plotHeight, plotWidth)*options.radar.radiusRatio/2, - sides = this.axes.x.ticks.length, - coeff = 2*(Math.PI/sides), - angle = -Math.PI/2; - - // Draw grid lines in vertical direction. - ctx.beginPath(); - - a = this.axes.y; - - if(horizontalLines){ - circularHorizontalTicks(a.ticks); - } - if(minorHorizontalLines){ - circularHorizontalTicks(a.minorTicks); - } - - if(verticalLines){ - _.times(sides, function(i){ - ctx.moveTo(0, 0); - ctx.lineTo(Math.cos(i*coeff+angle)*radius, Math.sin(i*coeff+angle)*radius); - }); - } - ctx.stroke(); - } - else { - ctx.translate(this.plotOffset.left, this.plotOffset.top); - - // Draw grid background, if present in options. - if(grid.backgroundColor){ - ctx.fillStyle = this.processColor(grid.backgroundColor, {x1: 0, y1: 0, x2: plotWidth, y2: plotHeight}); - ctx.fillRect(0, 0, plotWidth, plotHeight); - } - - ctx.beginPath(); - - a = this.axes.x; - if (verticalLines) drawGridLines(a.ticks, drawVerticalLines); - if (minorVerticalLines) drawGridLines(a.minorTicks, drawVerticalLines); - - a = this.axes.y; - if (horizontalLines) drawGridLines(a.ticks, drawHorizontalLines); - if (minorHorizontalLines) drawGridLines(a.minorTicks, drawHorizontalLines); - - ctx.stroke(); - } - - ctx.restore(); - if(verticalLines || minorVerticalLines || - horizontalLines || minorHorizontalLines){ - E.fire(this.el, 'flotr:aftergrid', [this.axes.x, this.axes.y, options, this]); - } - }, - - drawOutline: function(){ - var - that = this, - options = that.options, - grid = options.grid, - outline = grid.outline, - ctx = that.ctx, - backgroundImage = grid.backgroundImage, - plotOffset = that.plotOffset, - leftOffset = plotOffset.left, - topOffset = plotOffset.top, - plotWidth = that.plotWidth, - plotHeight = that.plotHeight, - v, img, src, left, top, globalAlpha; - - if (!grid.outlineWidth) return; - - ctx.save(); - - if (grid.circular) { - ctx.translate(leftOffset + plotWidth / 2, topOffset + plotHeight / 2); - var radius = Math.min(plotHeight, plotWidth) * options.radar.radiusRatio / 2, - sides = this.axes.x.ticks.length, - coeff = 2*(Math.PI/sides), - angle = -Math.PI/2; - - // Draw axis/grid border. - ctx.beginPath(); - ctx.lineWidth = grid.outlineWidth; - ctx.strokeStyle = grid.color; - ctx.lineJoin = 'round'; - - for(i = 0; i <= sides; ++i){ - ctx[i === 0 ? 'moveTo' : 'lineTo'](Math.cos(i*coeff+angle)*radius, Math.sin(i*coeff+angle)*radius); - } - //ctx.arc(0, 0, radius, 0, Math.PI*2, true); - - ctx.stroke(); - } - else { - ctx.translate(leftOffset, topOffset); - - // Draw axis/grid border. - var lw = grid.outlineWidth, - orig = 0.5-lw+((lw+1)%2/2), - lineTo = 'lineTo', - moveTo = 'moveTo'; - ctx.lineWidth = lw; - ctx.strokeStyle = grid.color; - ctx.lineJoin = 'miter'; - ctx.beginPath(); - ctx.moveTo(orig, orig); - plotWidth = plotWidth - (lw / 2) % 1; - plotHeight = plotHeight + lw / 2; - ctx[outline.indexOf('n') !== -1 ? lineTo : moveTo](plotWidth, orig); - ctx[outline.indexOf('e') !== -1 ? lineTo : moveTo](plotWidth, plotHeight); - ctx[outline.indexOf('s') !== -1 ? lineTo : moveTo](orig, plotHeight); - ctx[outline.indexOf('w') !== -1 ? lineTo : moveTo](orig, orig); - ctx.stroke(); - ctx.closePath(); - } - - ctx.restore(); - - if (backgroundImage) { - - src = backgroundImage.src || backgroundImage; - left = (parseInt(backgroundImage.left, 10) || 0) + plotOffset.left; - top = (parseInt(backgroundImage.top, 10) || 0) + plotOffset.top; - img = new Image(); - - img.onload = function() { - ctx.save(); - if (backgroundImage.alpha) ctx.globalAlpha = backgroundImage.alpha; - ctx.globalCompositeOperation = 'destination-over'; - ctx.drawImage(img, 0, 0, img.width, img.height, left, top, plotWidth, plotHeight); - ctx.restore(); - }; - - img.src = src; - } - } -}); - -})(); diff --git a/addons/web_graph/static/lib/flotr2/js/plugins/handles.js b/addons/web_graph/static/lib/flotr2/js/plugins/handles.js deleted file mode 100644 index 92fd3247fdf..00000000000 --- a/addons/web_graph/static/lib/flotr2/js/plugins/handles.js +++ /dev/null @@ -1,199 +0,0 @@ -/** - * Selection Handles Plugin - * - * Depends upon options.selection.mode - * - * Options - * show - True enables the handles plugin. - * drag - Left and Right drag handles - * scroll - Scrolling handle - */ -(function () { - -var D = Flotr.DOM; - -Flotr.addPlugin('handles', { - - options: { - show: false, - drag: true, - scroll: true - }, - - callbacks: { - 'flotr:afterinit': init, - 'flotr:select': handleSelect, - 'flotr:mousedown': reset, - 'flotr:mousemove': mouseMoveHandler - } - -}); - - -function init() { - - var - options = this.options, - handles = this.handles, - el = this.el, - scroll, left, right, container; - - if (!options.selection.mode || !options.handles.show || 'ontouchstart' in el) return; - - handles.initialized = true; - - container = D.node('<div class="flotr-handles"></div>'); - options = options.handles; - - // Drag handles - if (options.drag) { - right = D.node('<div class="flotr-handles-handle flotr-handles-drag flotr-handles-right"></div>'); - left = D.node('<div class="flotr-handles-handle flotr-handles-drag flotr-handles-left"></div>'); - D.insert(container, right); - D.insert(container, left); - D.hide(left); - D.hide(right); - handles.left = left; - handles.right = right; - - this.observe(left, 'mousedown', function () { - handles.moveHandler = leftMoveHandler; - }); - this.observe(right, 'mousedown', function () { - handles.moveHandler = rightMoveHandler; - }); - } - - // Scroll handle - if (options.scroll) { - scroll = D.node('<div class="flotr-handles-handle flotr-handles-scroll"></div>'); - D.insert(container, scroll); - D.hide(scroll); - handles.scroll = scroll; - this.observe(scroll, 'mousedown', function () { - handles.moveHandler = scrollMoveHandler; - }); - } - - this.observe(document, 'mouseup', function() { - handles.moveHandler = null; - }); - - D.insert(el, container); -} - - -function handleSelect(selection) { - - if (!this.handles.initialized) return; - - var - handles = this.handles, - options = this.options.handles, - left = handles.left, - right = handles.right, - scroll = handles.scroll; - - if (options) { - if (options.drag) { - positionDrag(this, left, selection.x1); - positionDrag(this, right, selection.x2); - } - - if (options.scroll) { - positionScroll( - this, - scroll, - selection.x1, - selection.x2 - ); - } - } -} - -function positionDrag(graph, handle, x) { - - D.show(handle); - - var size = D.size(handle), - l = Math.round(graph.axes.x.d2p(x) - size.width / 2), - t = (graph.plotHeight - size.height) / 2; - - D.setStyles(handle, { - 'left' : l+'px', - 'top' : t+'px' - }); -} - -function positionScroll(graph, handle, x1, x2) { - - D.show(handle); - - var size = D.size(handle), - l = Math.round(graph.axes.x.d2p(x1)), - t = (graph.plotHeight) - size.height / 2, - w = (graph.axes.x.d2p(x2) - graph.axes.x.d2p(x1)); - - D.setStyles(handle, { - 'left' : l+'px', - 'top' : t+'px', - 'width': w+'px' - }); -} - -function reset() { - - if (!this.handles.initialized) return; - - var - handles = this.handles; - if (handles) { - D.hide(handles.left); - D.hide(handles.right); - D.hide(handles.scroll); - } -} - -function mouseMoveHandler(e, position) { - - if (!this.handles.initialized) return; - if (!this.handles.moveHandler) return; - - var - delta = position.dX, - selection = this.selection.selection, - area = this.selection.getArea(), - handles = this.handles; - - handles.moveHandler(area, delta); - checkSwap(area, handles); - - this.selection.setSelection(area); -} - -function checkSwap (area, handles) { - var moveHandler = handles.moveHandler; - if (area.x1 > area.x2) { - if (moveHandler == leftMoveHandler) { - moveHandler = rightMoveHandler; - } else if (moveHandler == rightMoveHandler) { - moveHandler = leftMoveHandler; - } - handles.moveHandler = moveHandler; - } -} - -function leftMoveHandler(area, delta) { - area.x1 += delta; -} - -function rightMoveHandler(area, delta) { - area.x2 += delta; -} - -function scrollMoveHandler(area, delta) { - area.x1 += delta; - area.x2 += delta; -} - -})(); diff --git a/addons/web_graph/static/lib/flotr2/js/plugins/hit.js b/addons/web_graph/static/lib/flotr2/js/plugins/hit.js deleted file mode 100644 index eeff570df35..00000000000 --- a/addons/web_graph/static/lib/flotr2/js/plugins/hit.js +++ /dev/null @@ -1,337 +0,0 @@ -(function () { - -var - D = Flotr.DOM, - _ = Flotr._, - flotr = Flotr, - S_MOUSETRACK = 'opacity:0.7;background-color:#000;color:#fff;display:none;position:absolute;padding:2px 8px;-moz-border-radius:4px;border-radius:4px;white-space:nowrap;'; - -Flotr.addPlugin('hit', { - callbacks: { - 'flotr:mousemove': function(e, pos) { - this.hit.track(pos); - }, - 'flotr:click': function(pos) { - this.hit.track(pos); - }, - 'flotr:mouseout': function() { - this.hit.clearHit(); - } - }, - track : function (pos) { - if (this.options.mouse.track || _.any(this.series, function(s){return s.mouse && s.mouse.track;})) { - this.hit.hit(pos); - } - }, - /** - * Try a method on a graph type. If the method exists, execute it. - * @param {Object} series - * @param {String} method Method name. - * @param {Array} args Arguments applied to method. - * @return executed successfully or failed. - */ - executeOnType: function(s, method, args){ - var - success = false, - options; - - if (!_.isArray(s)) s = [s]; - - function e(s, index) { - _.each(_.keys(flotr.graphTypes), function (type) { - if (s[type] && s[type].show && this[type][method]) { - options = this.getOptions(s, type); - - options.fill = !!s.mouse.fillColor; - options.fillStyle = this.processColor(s.mouse.fillColor || '#ffffff', {opacity: s.mouse.fillOpacity}); - options.color = s.mouse.lineColor; - options.context = this.octx; - options.index = index; - - if (args) options.args = args; - this[type][method].call(this[type], options); - success = true; - } - }, this); - } - _.each(s, e, this); - - return success; - }, - /** - * Updates the mouse tracking point on the overlay. - */ - drawHit: function(n){ - var octx = this.octx, - s = n.series; - - if (s.mouse.lineColor) { - octx.save(); - octx.lineWidth = (s.points ? s.points.lineWidth : 1); - octx.strokeStyle = s.mouse.lineColor; - octx.fillStyle = this.processColor(s.mouse.fillColor || '#ffffff', {opacity: s.mouse.fillOpacity}); - octx.translate(this.plotOffset.left, this.plotOffset.top); - - if (!this.hit.executeOnType(s, 'drawHit', n)) { - var xa = n.xaxis, - ya = n.yaxis; - - octx.beginPath(); - // TODO fix this (points) should move to general testable graph mixin - octx.arc(xa.d2p(n.x), ya.d2p(n.y), s.points.radius || s.mouse.radius, 0, 2 * Math.PI, true); - octx.fill(); - octx.stroke(); - octx.closePath(); - } - octx.restore(); - } - this.prevHit = n; - }, - /** - * Removes the mouse tracking point from the overlay. - */ - clearHit: function(){ - var prev = this.prevHit, - octx = this.octx, - plotOffset = this.plotOffset; - octx.save(); - octx.translate(plotOffset.left, plotOffset.top); - if (prev) { - if (!this.hit.executeOnType(prev.series, 'clearHit', this.prevHit)) { - // TODO fix this (points) should move to general testable graph mixin - var - s = prev.series, - lw = (s.points ? s.points.lineWidth : 1); - offset = (s.points.radius || s.mouse.radius) + lw; - octx.clearRect( - prev.xaxis.d2p(prev.x) - offset, - prev.yaxis.d2p(prev.y) - offset, - offset*2, - offset*2 - ); - } - D.hide(this.mouseTrack); - this.prevHit = null; - } - octx.restore(); - }, - /** - * Retrieves the nearest data point from the mouse cursor. If it's within - * a certain range, draw a point on the overlay canvas and display the x and y - * value of the data. - * @param {Object} mouse - Object that holds the relative x and y coordinates of the cursor. - */ - hit: function(mouse){ - - var - options = this.options, - prevHit = this.prevHit, - closest, sensibility, dataIndex, seriesIndex, series, value, xaxis, yaxis; - - if (this.series.length === 0) return; - - // Nearest data element. - // dist, x, y, relX, relY, absX, absY, sAngle, eAngle, fraction, mouse, - // xaxis, yaxis, series, index, seriesIndex - n = { - relX : mouse.relX, - relY : mouse.relY, - absX : mouse.absX, - absY : mouse.absY - }; - - if (options.mouse.trackY && - !options.mouse.trackAll && - this.hit.executeOnType(this.series, 'hit', [mouse, n])) - { - - if (!_.isUndefined(n.seriesIndex)) { - series = this.series[n.seriesIndex]; - n.series = series; - n.mouse = series.mouse; - n.xaxis = series.xaxis; - n.yaxis = series.yaxis; - } - } else { - - closest = this.hit.closest(mouse); - - if (closest) { - - closest = options.mouse.trackY ? closest.point : closest.x; - seriesIndex = closest.seriesIndex; - series = this.series[seriesIndex]; - xaxis = series.xaxis; - yaxis = series.yaxis; - sensibility = 2 * series.mouse.sensibility; - - if - (options.mouse.trackAll || - (closest.distanceX < sensibility / xaxis.scale && - (!options.mouse.trackY || closest.distanceY < sensibility / yaxis.scale))) - { - n.series = series; - n.xaxis = series.xaxis; - n.yaxis = series.yaxis; - n.mouse = series.mouse; - n.x = closest.x; - n.y = closest.y; - n.dist = closest.distance; - n.index = closest.dataIndex; - n.seriesIndex = seriesIndex; - } - } - } - - if (!prevHit || (prevHit.index !== n.index || prevHit.seriesIndex !== n.seriesIndex)) { - this.hit.clearHit(); - if (n.series && n.mouse && n.mouse.track) { - this.hit.drawMouseTrack(n); - this.hit.drawHit(n); - Flotr.EventAdapter.fire(this.el, 'flotr:hit', [n, this]); - } - } - }, - - closest : function (mouse) { - - var - series = this.series, - options = this.options, - mouseX = mouse.x, - mouseY = mouse.y, - compare = Number.MAX_VALUE, - compareX = Number.MAX_VALUE, - closest = {}, - closestX = {}, - check = false, - serie, data, - distance, distanceX, distanceY, - x, y, i, j; - - function setClosest (o) { - o.distance = distance; - o.distanceX = distanceX; - o.distanceY = distanceY; - o.seriesIndex = i; - o.dataIndex = j; - o.x = x; - o.y = y; - } - - for (i = 0; i < series.length; i++) { - - serie = series[i]; - data = serie.data; - - if (data.length) check = true; - - for (j = data.length; j--;) { - - x = data[j][0]; - y = data[j][1]; - - if (x === null || y === null) continue; - - // don't check if the point isn't visible in the current range - if (x < serie.xaxis.min || x > serie.xaxis.max) continue; - - distanceX = Math.abs(x - mouseX); - distanceY = Math.abs(y - mouseY); - - // Skip square root for speed - distance = distanceX * distanceX + distanceY * distanceY; - - if (distance < compare) { - compare = distance; - setClosest(closest); - } - - if (distanceX < compareX) { - compareX = distanceX; - setClosest(closestX); - } - } - } - - return check ? { - point : closest, - x : closestX - } : false; - }, - - drawMouseTrack : function (n) { - - var - pos = '', - s = n.series, - p = n.mouse.position, - m = n.mouse.margin, - elStyle = S_MOUSETRACK, - mouseTrack = this.mouseTrack, - plotOffset = this.plotOffset, - left = plotOffset.left, - right = plotOffset.right, - bottom = plotOffset.bottom, - top = plotOffset.top, - decimals = n.mouse.trackDecimals, - options = this.options; - - // Create - if (!mouseTrack) { - mouseTrack = D.node('<div class="flotr-mouse-value"></div>'); - this.mouseTrack = mouseTrack; - D.insert(this.el, mouseTrack); - } - - if (!n.mouse.relative) { // absolute to the canvas - - if (p.charAt(0) == 'n') pos += 'top:' + (m + top) + 'px;bottom:auto;'; - else if (p.charAt(0) == 's') pos += 'bottom:' + (m + bottom) + 'px;top:auto;'; - if (p.charAt(1) == 'e') pos += 'right:' + (m + right) + 'px;left:auto;'; - else if (p.charAt(1) == 'w') pos += 'left:' + (m + left) + 'px;right:auto;'; - - // Bars - } else if (s.bars.show) { - pos += 'bottom:' + (m - top - n.yaxis.d2p(n.y/2) + this.canvasHeight) + 'px;top:auto;'; - pos += 'left:' + (m + left + n.xaxis.d2p(n.x - options.bars.barWidth/2)) + 'px;right:auto;'; - - // Pie - } else if (s.pie.show) { - var center = { - x: (this.plotWidth)/2, - y: (this.plotHeight)/2 - }, - radius = (Math.min(this.canvasWidth, this.canvasHeight) * s.pie.sizeRatio) / 2, - bisection = n.sAngle<n.eAngle ? (n.sAngle + n.eAngle) / 2: (n.sAngle + n.eAngle + 2* Math.PI) / 2; - - pos += 'bottom:' + (m - top - center.y - Math.sin(bisection) * radius/2 + this.canvasHeight) + 'px;top:auto;'; - pos += 'left:' + (m + left + center.x + Math.cos(bisection) * radius/2) + 'px;right:auto;'; - - // Default - } else { - if (p.charAt(0) == 'n') pos += 'bottom:' + (m - top - n.yaxis.d2p(n.y) + this.canvasHeight) + 'px;top:auto;'; - else if (p.charAt(0) == 's') pos += 'top:' + (m + top + n.yaxis.d2p(n.y)) + 'px;bottom:auto;'; - if (p.charAt(1) == 'e') pos += 'left:' + (m + left + n.xaxis.d2p(n.x)) + 'px;right:auto;'; - else if (p.charAt(1) == 'w') pos += 'right:' + (m - left - n.xaxis.d2p(n.x) + this.canvasWidth) + 'px;left:auto;'; - } - - elStyle += pos; - mouseTrack.style.cssText = elStyle; - - if (!decimals || decimals < 0) decimals = 0; - - mouseTrack.innerHTML = n.mouse.trackFormatter({ - x: n.x.toFixed(decimals), - y: n.y.toFixed(decimals), - series: n.series, - index: n.index, - nearest: n, - fraction: n.fraction - }); - - D.show(mouseTrack); - } - -}); -})(); diff --git a/addons/web_graph/static/lib/flotr2/js/plugins/labels.js b/addons/web_graph/static/lib/flotr2/js/plugins/labels.js deleted file mode 100644 index 8e016c8fa03..00000000000 --- a/addons/web_graph/static/lib/flotr2/js/plugins/labels.js +++ /dev/null @@ -1,227 +0,0 @@ -(function () { - -var D = Flotr.DOM; - -Flotr.addPlugin('labels', { - - callbacks : { - 'flotr:afterdraw' : function () { - this.labels.draw(); - } - }, - - draw: function(){ - // Construct fixed width label boxes, which can be styled easily. - var - axis, tick, left, top, xBoxWidth, - radius, sides, coeff, angle, - div, i, html = '', - noLabels = 0, - options = this.options, - ctx = this.ctx, - a = this.axes, - style = { size: options.fontSize }; - - for (i = 0; i < a.x.ticks.length; ++i){ - if (a.x.ticks[i].label) { ++noLabels; } - } - xBoxWidth = this.plotWidth / noLabels; - - if (options.grid.circular) { - ctx.save(); - ctx.translate(this.plotOffset.left + this.plotWidth / 2, - this.plotOffset.top + this.plotHeight / 2); - - radius = this.plotHeight * options.radar.radiusRatio / 2 + options.fontSize; - sides = this.axes.x.ticks.length; - coeff = 2 * (Math.PI / sides); - angle = -Math.PI / 2; - - drawLabelCircular(this, a.x, false); - drawLabelCircular(this, a.x, true); - drawLabelCircular(this, a.y, false); - drawLabelCircular(this, a.y, true); - ctx.restore(); - } - - if (!options.HtmlText && this.textEnabled) { - drawLabelNoHtmlText(this, a.x, 'center', 'top'); - drawLabelNoHtmlText(this, a.x2, 'center', 'bottom'); - drawLabelNoHtmlText(this, a.y, 'right', 'middle'); - drawLabelNoHtmlText(this, a.y2, 'left', 'middle'); - - } else if (( - a.x.options.showLabels || - a.x2.options.showLabels || - a.y.options.showLabels || - a.y2.options.showLabels) && - !options.grid.circular - ) { - - html = ''; - - drawLabelHtml(this, a.x); - drawLabelHtml(this, a.x2); - drawLabelHtml(this, a.y); - drawLabelHtml(this, a.y2); - - ctx.stroke(); - ctx.restore(); - div = D.create('div'); - D.setStyles(div, { - fontSize: 'smaller', - color: options.grid.color - }); - div.className = 'flotr-labels'; - D.insert(this.el, div); - D.insert(div, html); - } - - function drawLabelCircular (graph, axis, minorTicks) { - var - ticks = minorTicks ? axis.minorTicks : axis.ticks, - isX = axis.orientation === 1, - isFirst = axis.n === 1, - style, offset; - - style = { - color : axis.options.color || options.grid.color, - angle : Flotr.toRad(axis.options.labelsAngle), - textBaseline : 'middle' - }; - - for (i = 0; i < ticks.length && - (minorTicks ? axis.options.showMinorLabels : axis.options.showLabels); ++i){ - tick = ticks[i]; - tick.label += ''; - if (!tick.label || !tick.label.length) { continue; } - - x = Math.cos(i * coeff + angle) * radius; - y = Math.sin(i * coeff + angle) * radius; - - style.textAlign = isX ? (Math.abs(x) < 0.1 ? 'center' : (x < 0 ? 'right' : 'left')) : 'left'; - - Flotr.drawText( - ctx, tick.label, - isX ? x : 3, - isX ? y : -(axis.ticks[i].v / axis.max) * (radius - options.fontSize), - style - ); - } - } - - function drawLabelNoHtmlText (graph, axis, textAlign, textBaseline) { - var - isX = axis.orientation === 1, - isFirst = axis.n === 1, - style, offset; - - style = { - color : axis.options.color || options.grid.color, - textAlign : textAlign, - textBaseline : textBaseline, - angle : Flotr.toRad(axis.options.labelsAngle) - }; - style = Flotr.getBestTextAlign(style.angle, style); - - for (i = 0; i < axis.ticks.length && continueShowingLabels(axis); ++i) { - - tick = axis.ticks[i]; - if (!tick.label || !tick.label.length) { continue; } - - offset = axis.d2p(tick.v); - if (offset < 0 || - offset > (isX ? graph.plotWidth : graph.plotHeight)) { continue; } - - Flotr.drawText( - ctx, tick.label, - leftOffset(graph, isX, isFirst, offset), - topOffset(graph, isX, isFirst, offset), - style - ); - - // Only draw on axis y2 - if (!isX && !isFirst) { - ctx.save(); - ctx.strokeStyle = style.color; - ctx.beginPath(); - ctx.moveTo(graph.plotOffset.left + graph.plotWidth - 8, graph.plotOffset.top + axis.d2p(tick.v)); - ctx.lineTo(graph.plotOffset.left + graph.plotWidth, graph.plotOffset.top + axis.d2p(tick.v)); - ctx.stroke(); - ctx.restore(); - } - } - - function continueShowingLabels (axis) { - return axis.options.showLabels && axis.used; - } - function leftOffset (graph, isX, isFirst, offset) { - return graph.plotOffset.left + - (isX ? offset : - (isFirst ? - -options.grid.labelMargin : - options.grid.labelMargin + graph.plotWidth)); - } - function topOffset (graph, isX, isFirst, offset) { - return graph.plotOffset.top + - (isX ? options.grid.labelMargin : offset) + - ((isX && isFirst) ? graph.plotHeight : 0); - } - } - - function drawLabelHtml (graph, axis) { - var - isX = axis.orientation === 1, - isFirst = axis.n === 1, - name = '', - left, style, top, - offset = graph.plotOffset; - - if (!isX && !isFirst) { - ctx.save(); - ctx.strokeStyle = axis.options.color || options.grid.color; - ctx.beginPath(); - } - - if (axis.options.showLabels && (isFirst ? true : axis.used)) { - for (i = 0; i < axis.ticks.length; ++i) { - tick = axis.ticks[i]; - if (!tick.label || !tick.label.length || - ((isX ? offset.left : offset.top) + axis.d2p(tick.v) < 0) || - ((isX ? offset.left : offset.top) + axis.d2p(tick.v) > (isX ? graph.canvasWidth : graph.canvasHeight))) { - continue; - } - top = offset.top + - (isX ? - ((isFirst ? 1 : -1 ) * (graph.plotHeight + options.grid.labelMargin)) : - axis.d2p(tick.v) - axis.maxLabel.height / 2); - left = isX ? (offset.left + axis.d2p(tick.v) - xBoxWidth / 2) : 0; - - name = ''; - if (i === 0) { - name = ' first'; - } else if (i === axis.ticks.length - 1) { - name = ' last'; - } - name += isX ? ' flotr-grid-label-x' : ' flotr-grid-label-y'; - - html += [ - '<div style="position:absolute; text-align:' + (isX ? 'center' : 'right') + '; ', - 'top:' + top + 'px; ', - ((!isX && !isFirst) ? 'right:' : 'left:') + left + 'px; ', - 'width:' + (isX ? xBoxWidth : ((isFirst ? offset.left : offset.right) - options.grid.labelMargin)) + 'px; ', - axis.options.color ? ('color:' + axis.options.color + '; ') : ' ', - '" class="flotr-grid-label' + name + '">' + tick.label + '</div>' - ].join(' '); - - if (!isX && !isFirst) { - ctx.moveTo(offset.left + graph.plotWidth - 8, offset.top + axis.d2p(tick.v)); - ctx.lineTo(offset.left + graph.plotWidth, offset.top + axis.d2p(tick.v)); - } - } - } - } - } - -}); -})(); diff --git a/addons/web_graph/static/lib/flotr2/js/plugins/legend.js b/addons/web_graph/static/lib/flotr2/js/plugins/legend.js deleted file mode 100644 index d130cb5e90b..00000000000 --- a/addons/web_graph/static/lib/flotr2/js/plugins/legend.js +++ /dev/null @@ -1,179 +0,0 @@ -(function () { - -var - D = Flotr.DOM, - _ = Flotr._; - -Flotr.addPlugin('legend', { - options: { - show: true, // => setting to true will show the legend, hide otherwise - noColumns: 1, // => number of colums in legend table // @todo: doesn't work for HtmlText = false - labelFormatter: function(v){return v;}, // => fn: string -> string - labelBoxBorderColor: '#CCCCCC', // => border color for the little label boxes - labelBoxWidth: 14, - labelBoxHeight: 10, - labelBoxMargin: 5, - labelBoxOpacity: 0.4, - container: null, // => container (as jQuery object) to put legend in, null means default on top of graph - position: 'nw', // => position of default legend container within plot - margin: 5, // => distance from grid edge to default legend container within plot - backgroundColor: null, // => null means auto-detect - backgroundOpacity: 0.85// => set to 0 to avoid background, set to 1 for a solid background - }, - callbacks: { - 'flotr:afterinit': function() { - this.legend.insertLegend(); - } - }, - /** - * Adds a legend div to the canvas container or draws it on the canvas. - */ - insertLegend: function(){ - - if(!this.options.legend.show) - return; - - var series = this.series, - plotOffset = this.plotOffset, - options = this.options, - legend = options.legend, - fragments = [], - rowStarted = false, - ctx = this.ctx, - itemCount = _.filter(series, function(s) {return (s.label && !s.hide);}).length, - p = legend.position, - m = legend.margin, - i, label, color; - - if (itemCount) { - if (!options.HtmlText && this.textEnabled && !legend.container) { - var style = { - size: options.fontSize*1.1, - color: options.grid.color - }; - - var lbw = legend.labelBoxWidth, - lbh = legend.labelBoxHeight, - lbm = legend.labelBoxMargin, - offsetX = plotOffset.left + m, - offsetY = plotOffset.top + m; - - // We calculate the labels' max width - var labelMaxWidth = 0; - for(i = series.length - 1; i > -1; --i){ - if(!series[i].label || series[i].hide) continue; - label = legend.labelFormatter(series[i].label); - labelMaxWidth = Math.max(labelMaxWidth, this._text.measureText(label, style).width); - } - - var legendWidth = Math.round(lbw + lbm*3 + labelMaxWidth), - legendHeight = Math.round(itemCount*(lbm+lbh) + lbm); - - if(p.charAt(0) == 's') offsetY = plotOffset.top + this.plotHeight - (m + legendHeight); - if(p.charAt(1) == 'e') offsetX = plotOffset.left + this.plotWidth - (m + legendWidth); - - // Legend box - color = this.processColor(legend.backgroundColor || 'rgb(240,240,240)', {opacity: legend.backgroundOpacity || 0.1}); - - ctx.fillStyle = color; - ctx.fillRect(offsetX, offsetY, legendWidth, legendHeight); - ctx.strokeStyle = legend.labelBoxBorderColor; - ctx.strokeRect(Flotr.toPixel(offsetX), Flotr.toPixel(offsetY), legendWidth, legendHeight); - - // Legend labels - var x = offsetX + lbm; - var y = offsetY + lbm; - for(i = 0; i < series.length; i++){ - if(!series[i].label || series[i].hide) continue; - label = legend.labelFormatter(series[i].label); - - ctx.fillStyle = series[i].color; - ctx.fillRect(x, y, lbw-1, lbh-1); - - ctx.strokeStyle = legend.labelBoxBorderColor; - ctx.lineWidth = 1; - ctx.strokeRect(Math.ceil(x)-1.5, Math.ceil(y)-1.5, lbw+2, lbh+2); - - // Legend text - Flotr.drawText(ctx, label, x + lbw + lbm, y + lbh, style); - - y += lbh + lbm; - } - } - else { - for(i = 0; i < series.length; ++i){ - if(!series[i].label || series[i].hide) continue; - - if(i % legend.noColumns === 0){ - fragments.push(rowStarted ? '</tr><tr>' : '<tr>'); - rowStarted = true; - } - - // @TODO remove requirement on bars - var s = series[i], - boxWidth = legend.labelBoxWidth, - boxHeight = legend.labelBoxHeight, - opacityValue = (s.bars ? s.bars.fillOpacity : legend.labelBoxOpacity), - opacity = 'opacity:' + opacityValue + ';filter:alpha(opacity=' + opacityValue*100 + ');'; - - label = legend.labelFormatter(s.label); - color = 'background-color:' + ((s.bars && s.bars.show && s.bars.fillColor && s.bars.fill) ? s.bars.fillColor : s.color) + ';'; - - fragments.push( - '<td class="flotr-legend-color-box">', - '<div style="border:1px solid ', legend.labelBoxBorderColor, ';padding:1px">', - '<div style="width:', (boxWidth-1), 'px;height:', (boxHeight-1), 'px;border:1px solid ', series[i].color, '">', // Border - '<div style="width:', boxWidth, 'px;height:', boxHeight, 'px;', 'opacity:.4;', color, '"></div>', // Background - '</div>', - '</div>', - '</td>', - '<td class="flotr-legend-label">', label, '</td>' - ); - } - if(rowStarted) fragments.push('</tr>'); - - if(fragments.length > 0){ - var table = '<table style="font-size:smaller;color:' + options.grid.color + '">' + fragments.join('') + '</table>'; - if(legend.container){ - D.insert(legend.container, table); - } - else { - var styles = {position: 'absolute', 'z-index': 2}; - - if(p.charAt(0) == 'n') { styles.top = (m + plotOffset.top) + 'px'; styles.bottom = 'auto'; } - else if(p.charAt(0) == 's') { styles.bottom = (m + plotOffset.bottom) + 'px'; styles.top = 'auto'; } - if(p.charAt(1) == 'e') { styles.right = (m + plotOffset.right) + 'px'; styles.left = 'auto'; } - else if(p.charAt(1) == 'w') { styles.left = (m + plotOffset.left) + 'px'; styles.right = 'auto'; } - - var div = D.create('div'), size; - div.className = 'flotr-legend'; - D.setStyles(div, styles); - D.insert(div, table); - D.insert(this.el, div); - - if(!legend.backgroundOpacity) - return; - - var c = legend.backgroundColor || options.grid.backgroundColor || '#ffffff'; - - _.extend(styles, D.size(div), { - 'backgroundColor': c, - 'z-index': 1 - }); - styles.width += 'px'; - styles.height += 'px'; - - // Put in the transparent background separately to avoid blended labels and - div = D.create('div'); - div.className = 'flotr-legend-bg'; - D.setStyles(div, styles); - D.opacity(div, legend.backgroundOpacity); - D.insert(div, ' '); - D.insert(this.el, div); - } - } - } - } - } -}); -})(); diff --git a/addons/web_graph/static/lib/flotr2/js/plugins/selection.js b/addons/web_graph/static/lib/flotr2/js/plugins/selection.js deleted file mode 100644 index bb508840d6d..00000000000 --- a/addons/web_graph/static/lib/flotr2/js/plugins/selection.js +++ /dev/null @@ -1,278 +0,0 @@ -/** - * Selection Handles Plugin - * - * - * Options - * show - True enables the handles plugin. - * drag - Left and Right drag handles - * scroll - Scrolling handle - */ -(function () { - -function isLeftClick (e, type) { - return (e.which ? (e.which === 1) : (e.button === 0 || e.button === 1)); -} - -function boundX(x, graph) { - return Math.min(Math.max(0, x), graph.plotWidth - 1); -} - -function boundY(y, graph) { - return Math.min(Math.max(0, y), graph.plotHeight); -} - -var - D = Flotr.DOM, - E = Flotr.EventAdapter, - _ = Flotr._; - - -Flotr.addPlugin('selection', { - - options: { - pinchOnly: null, // Only select on pinch - mode: null, // => one of null, 'x', 'y' or 'xy' - color: '#B6D9FF', // => selection box color - fps: 20 // => frames-per-second - }, - - callbacks: { - 'flotr:mouseup' : function (event) { - - var - options = this.options.selection, - selection = this.selection, - pointer = this.getEventPosition(event); - - if (!options || !options.mode) return; - if (selection.interval) clearInterval(selection.interval); - - if (this.multitouches) { - selection.updateSelection(); - } else - if (!options.pinchOnly) { - selection.setSelectionPos(selection.selection.second, pointer); - } - selection.clearSelection(); - - if(selection.selecting && selection.selectionIsSane()){ - selection.drawSelection(); - selection.fireSelectEvent(); - this.ignoreClick = true; - } - }, - 'flotr:mousedown' : function (event) { - - var - options = this.options.selection, - selection = this.selection, - pointer = this.getEventPosition(event); - - if (!options || !options.mode) return; - if (!options.mode || (!isLeftClick(event) && _.isUndefined(event.touches))) return; - if (!options.pinchOnly) selection.setSelectionPos(selection.selection.first, pointer); - if (selection.interval) clearInterval(selection.interval); - - this.lastMousePos.pageX = null; - selection.selecting = false; - selection.interval = setInterval( - _.bind(selection.updateSelection, this), - 1000 / options.fps - ); - }, - 'flotr:destroy' : function (event) { - clearInterval(this.selection.interval); - } - }, - - // TODO This isn't used. Maybe it belongs in the draw area and fire select event methods? - getArea: function() { - - var s = this.selection.selection, - first = s.first, - second = s.second; - - return { - x1: Math.min(first.x, second.x), - x2: Math.max(first.x, second.x), - y1: Math.min(first.y, second.y), - y2: Math.max(first.y, second.y) - }; - }, - - selection: {first: {x: -1, y: -1}, second: {x: -1, y: -1}}, - prevSelection: null, - interval: null, - - /** - * Fires the 'flotr:select' event when the user made a selection. - */ - fireSelectEvent: function(name){ - var a = this.axes, - s = this.selection.selection, - x1, x2, y1, y2; - - name = name || 'select'; - - x1 = a.x.p2d(s.first.x); - x2 = a.x.p2d(s.second.x); - y1 = a.y.p2d(s.first.y); - y2 = a.y.p2d(s.second.y); - - E.fire(this.el, 'flotr:'+name, [{ - x1:Math.min(x1, x2), - y1:Math.min(y1, y2), - x2:Math.max(x1, x2), - y2:Math.max(y1, y2), - xfirst:x1, xsecond:x2, yfirst:y1, ysecond:y2 - }, this]); - }, - - /** - * Allows the user the manually select an area. - * @param {Object} area - Object with coordinates to select. - */ - setSelection: function(area, preventEvent){ - var options = this.options, - xa = this.axes.x, - ya = this.axes.y, - vertScale = ya.scale, - hozScale = xa.scale, - selX = options.selection.mode.indexOf('x') != -1, - selY = options.selection.mode.indexOf('y') != -1, - s = this.selection.selection; - - this.selection.clearSelection(); - - s.first.y = boundY((selX && !selY) ? 0 : (ya.max - area.y1) * vertScale, this); - s.second.y = boundY((selX && !selY) ? this.plotHeight - 1: (ya.max - area.y2) * vertScale, this); - s.first.x = boundX((selY && !selX) ? 0 : area.x1, this); - s.second.x = boundX((selY && !selX) ? this.plotWidth : area.x2, this); - - this.selection.drawSelection(); - if (!preventEvent) - this.selection.fireSelectEvent(); - }, - - /** - * Calculates the position of the selection. - * @param {Object} pos - Position object. - * @param {Event} event - Event object. - */ - setSelectionPos: function(pos, pointer) { - var mode = this.options.selection.mode, - selection = this.selection.selection; - - if(mode.indexOf('x') == -1) { - pos.x = (pos == selection.first) ? 0 : this.plotWidth; - }else{ - pos.x = boundX(pointer.relX, this); - } - - if (mode.indexOf('y') == -1) { - pos.y = (pos == selection.first) ? 0 : this.plotHeight - 1; - }else{ - pos.y = boundY(pointer.relY, this); - } - }, - /** - * Draws the selection box. - */ - drawSelection: function() { - - this.selection.fireSelectEvent('selecting'); - - var s = this.selection.selection, - octx = this.octx, - options = this.options, - plotOffset = this.plotOffset, - prevSelection = this.selection.prevSelection; - - if (prevSelection && - s.first.x == prevSelection.first.x && - s.first.y == prevSelection.first.y && - s.second.x == prevSelection.second.x && - s.second.y == prevSelection.second.y) { - return; - } - - octx.save(); - octx.strokeStyle = this.processColor(options.selection.color, {opacity: 0.8}); - octx.lineWidth = 1; - octx.lineJoin = 'miter'; - octx.fillStyle = this.processColor(options.selection.color, {opacity: 0.4}); - - this.selection.prevSelection = { - first: { x: s.first.x, y: s.first.y }, - second: { x: s.second.x, y: s.second.y } - }; - - var x = Math.min(s.first.x, s.second.x), - y = Math.min(s.first.y, s.second.y), - w = Math.abs(s.second.x - s.first.x), - h = Math.abs(s.second.y - s.first.y); - - octx.fillRect(x + plotOffset.left+0.5, y + plotOffset.top+0.5, w, h); - octx.strokeRect(x + plotOffset.left+0.5, y + plotOffset.top+0.5, w, h); - octx.restore(); - }, - - /** - * Updates (draws) the selection box. - */ - updateSelection: function(){ - if (!this.lastMousePos.pageX) return; - - this.selection.selecting = true; - - if (this.multitouches) { - this.selection.setSelectionPos(this.selection.selection.first, this.getEventPosition(this.multitouches[0])); - this.selection.setSelectionPos(this.selection.selection.second, this.getEventPosition(this.multitouches[1])); - } else - if (this.options.selection.pinchOnly) { - return; - } else { - this.selection.setSelectionPos(this.selection.selection.second, this.lastMousePos); - } - - this.selection.clearSelection(); - - if(this.selection.selectionIsSane()) { - this.selection.drawSelection(); - } - }, - - /** - * Removes the selection box from the overlay canvas. - */ - clearSelection: function() { - if (!this.selection.prevSelection) return; - - var prevSelection = this.selection.prevSelection, - lw = 1, - plotOffset = this.plotOffset, - x = Math.min(prevSelection.first.x, prevSelection.second.x), - y = Math.min(prevSelection.first.y, prevSelection.second.y), - w = Math.abs(prevSelection.second.x - prevSelection.first.x), - h = Math.abs(prevSelection.second.y - prevSelection.first.y); - - this.octx.clearRect(x + plotOffset.left - lw + 0.5, - y + plotOffset.top - lw, - w + 2 * lw + 0.5, - h + 2 * lw + 0.5); - - this.selection.prevSelection = null; - }, - /** - * Determines whether or not the selection is sane and should be drawn. - * @return {Boolean} - True when sane, false otherwise. - */ - selectionIsSane: function(){ - var s = this.selection.selection; - return Math.abs(s.second.x - s.first.x) >= 5 || - Math.abs(s.second.y - s.first.y) >= 5; - } - -}); - -})(); diff --git a/addons/web_graph/static/lib/flotr2/js/plugins/spreadsheet.js b/addons/web_graph/static/lib/flotr2/js/plugins/spreadsheet.js deleted file mode 100644 index d75cb1991b1..00000000000 --- a/addons/web_graph/static/lib/flotr2/js/plugins/spreadsheet.js +++ /dev/null @@ -1,296 +0,0 @@ -/** Spreadsheet **/ -(function() { - -function getRowLabel(value){ - if (this.options.spreadsheet.tickFormatter){ - //TODO maybe pass the xaxis formatter to the custom tick formatter as an opt-out? - return this.options.spreadsheet.tickFormatter(value); - } - else { - var t = _.find(this.axes.x.ticks, function(t){return t.v == value;}); - if (t) { - return t.label; - } - return value; - } -} - -var - D = Flotr.DOM, - _ = Flotr._; - -Flotr.addPlugin('spreadsheet', { - options: { - show: false, // => show the data grid using two tabs - tabGraphLabel: 'Graph', - tabDataLabel: 'Data', - toolbarDownload: 'Download CSV', // @todo: add better language support - toolbarSelectAll: 'Select all', - csvFileSeparator: ',', - decimalSeparator: '.', - tickFormatter: null, - initialTab: 'graph' - }, - /** - * Builds the tabs in the DOM - */ - callbacks: { - 'flotr:afterconstruct': function(){ - // @TODO necessary? - //this.el.select('.flotr-tabs-group,.flotr-datagrid-container').invoke('remove'); - - if (!this.options.spreadsheet.show) return; - - var ss = this.spreadsheet, - container = D.node('<div class="flotr-tabs-group" style="position:absolute;left:0px;width:'+this.canvasWidth+'px"></div>'), - graph = D.node('<div style="float:left" class="flotr-tab selected">'+this.options.spreadsheet.tabGraphLabel+'</div>'), - data = D.node('<div style="float:left" class="flotr-tab">'+this.options.spreadsheet.tabDataLabel+'</div>'), - offset; - - ss.tabsContainer = container; - ss.tabs = { graph : graph, data : data }; - - D.insert(container, graph); - D.insert(container, data); - D.insert(this.el, container); - - offset = D.size(data).height + 2; - this.plotOffset.bottom += offset; - - D.setStyles(container, {top: this.canvasHeight-offset+'px'}); - - this. - observe(graph, 'click', function(){ss.showTab('graph');}). - observe(data, 'click', function(){ss.showTab('data');}); - if (this.options.spreadsheet.initialTab !== 'graph'){ - ss.showTab(this.options.spreadsheet.initialTab); - } - } - }, - /** - * Builds a matrix of the data to make the correspondance between the x values and the y values : - * X value => Y values from the axes - * @return {Array} The data grid - */ - loadDataGrid: function(){ - if (this.seriesData) return this.seriesData; - - var s = this.series, - rows = {}; - - /* The data grid is a 2 dimensions array. There is a row for each X value. - * Each row contains the x value and the corresponding y value for each serie ('undefined' if there isn't one) - **/ - _.each(s, function(serie, i){ - _.each(serie.data, function (v) { - var x = v[0], - y = v[1], - r = rows[x]; - if (r) { - r[i+1] = y; - } else { - var newRow = []; - newRow[0] = x; - newRow[i+1] = y; - rows[x] = newRow; - } - }); - }); - - // The data grid is sorted by x value - this.seriesData = _.sortBy(rows, function(row, x){ - return parseInt(x, 10); - }); - return this.seriesData; - }, - /** - * Constructs the data table for the spreadsheet - * @todo make a spreadsheet manager (Flotr.Spreadsheet) - * @return {Element} The resulting table element - */ - constructDataGrid: function(){ - // If the data grid has already been built, nothing to do here - if (this.spreadsheet.datagrid) return this.spreadsheet.datagrid; - - var s = this.series, - datagrid = this.spreadsheet.loadDataGrid(), - colgroup = ['<colgroup><col />'], - buttonDownload, buttonSelect, t; - - // First row : series' labels - var html = ['<table class="flotr-datagrid"><tr class="first-row">']; - html.push('<th> </th>'); - _.each(s, function(serie,i){ - html.push('<th scope="col">'+(serie.label || String.fromCharCode(65+i))+'</th>'); - colgroup.push('<col />'); - }); - html.push('</tr>'); - // Data rows - _.each(datagrid, function(row){ - html.push('<tr>'); - _.times(s.length+1, function(i){ - var tag = 'td', - value = row[i], - // TODO: do we really want to handle problems with floating point - // precision here? - content = (!_.isUndefined(value) ? Math.round(value*100000)/100000 : ''); - if (i === 0) { - tag = 'th'; - var label = getRowLabel.call(this, content); - if (label) content = label; - } - - html.push('<'+tag+(tag=='th'?' scope="row"':'')+'>'+content+'</'+tag+'>'); - }, this); - html.push('</tr>'); - }, this); - colgroup.push('</colgroup>'); - t = D.node(html.join('')); - - /** - * @TODO disabled this - if (!Flotr.isIE || Flotr.isIE == 9) { - function handleMouseout(){ - t.select('colgroup col.hover, th.hover').invoke('removeClassName', 'hover'); - } - function handleMouseover(e){ - var td = e.element(), - siblings = td.previousSiblings(); - t.select('th[scope=col]')[siblings.length-1].addClassName('hover'); - t.select('colgroup col')[siblings.length].addClassName('hover'); - } - _.each(t.select('td'), function(td) { - Flotr.EventAdapter. - observe(td, 'mouseover', handleMouseover). - observe(td, 'mouseout', handleMouseout); - }); - } - */ - - buttonDownload = D.node( - '<button type="button" class="flotr-datagrid-toolbar-button">' + - this.options.spreadsheet.toolbarDownload + - '</button>'); - - buttonSelect = D.node( - '<button type="button" class="flotr-datagrid-toolbar-button">' + - this.options.spreadsheet.toolbarSelectAll+ - '</button>'); - - this. - observe(buttonDownload, 'click', _.bind(this.spreadsheet.downloadCSV, this)). - observe(buttonSelect, 'click', _.bind(this.spreadsheet.selectAllData, this)); - - var toolbar = D.node('<div class="flotr-datagrid-toolbar"></div>'); - D.insert(toolbar, buttonDownload); - D.insert(toolbar, buttonSelect); - - var containerHeight =this.canvasHeight - D.size(this.spreadsheet.tabsContainer).height-2, - container = D.node('<div class="flotr-datagrid-container" style="position:absolute;left:0px;top:0px;width:'+ - this.canvasWidth+'px;height:'+containerHeight+'px;overflow:auto;z-index:10"></div>'); - - D.insert(container, toolbar); - D.insert(container, t); - D.insert(this.el, container); - this.spreadsheet.datagrid = t; - this.spreadsheet.container = container; - - return t; - }, - /** - * Shows the specified tab, by its name - * @todo make a tab manager (Flotr.Tabs) - * @param {String} tabName - The tab name - */ - showTab: function(tabName){ - if (this.spreadsheet.activeTab === tabName){ - return; - } - switch(tabName) { - case 'graph': - D.hide(this.spreadsheet.container); - D.removeClass(this.spreadsheet.tabs.data, 'selected'); - D.addClass(this.spreadsheet.tabs.graph, 'selected'); - break; - case 'data': - if (!this.spreadsheet.datagrid) - this.spreadsheet.constructDataGrid(); - D.show(this.spreadsheet.container); - D.addClass(this.spreadsheet.tabs.data, 'selected'); - D.removeClass(this.spreadsheet.tabs.graph, 'selected'); - break; - default: - throw 'Illegal tab name: ' + tabName; - } - this.spreadsheet.activeTab = tabName; - }, - /** - * Selects the data table in the DOM for copy/paste - */ - selectAllData: function(){ - if (this.spreadsheet.tabs) { - var selection, range, doc, win, node = this.spreadsheet.constructDataGrid(); - - this.spreadsheet.showTab('data'); - - // deferred to be able to select the table - setTimeout(function () { - if ((doc = node.ownerDocument) && (win = doc.defaultView) && - win.getSelection && doc.createRange && - (selection = window.getSelection()) && - selection.removeAllRanges) { - range = doc.createRange(); - range.selectNode(node); - selection.removeAllRanges(); - selection.addRange(range); - } - else if (document.body && document.body.createTextRange && - (range = document.body.createTextRange())) { - range.moveToElementText(node); - range.select(); - } - }, 0); - return true; - } - else return false; - }, - /** - * Converts the data into CSV in order to download a file - */ - downloadCSV: function(){ - var csv = '', - series = this.series, - options = this.options, - dg = this.spreadsheet.loadDataGrid(), - separator = encodeURIComponent(options.spreadsheet.csvFileSeparator); - - if (options.spreadsheet.decimalSeparator === options.spreadsheet.csvFileSeparator) { - throw "The decimal separator is the same as the column separator ("+options.spreadsheet.decimalSeparator+")"; - } - - // The first row - _.each(series, function(serie, i){ - csv += separator+'"'+(serie.label || String.fromCharCode(65+i)).replace(/\"/g, '\\"')+'"'; - }); - - csv += "%0D%0A"; // \r\n - - // For each row - csv += _.reduce(dg, function(memo, row){ - var rowLabel = getRowLabel.call(this, row[0]) || ''; - rowLabel = '"'+(rowLabel+'').replace(/\"/g, '\\"')+'"'; - var numbers = row.slice(1).join(separator); - if (options.spreadsheet.decimalSeparator !== '.') { - numbers = numbers.replace(/\./g, options.spreadsheet.decimalSeparator); - } - return memo + rowLabel+separator+numbers+"%0D%0A"; // \t and \r\n - }, '', this); - - if (Flotr.isIE && Flotr.isIE < 9) { - csv = csv.replace(new RegExp(separator, 'g'), decodeURIComponent(separator)).replace(/%0A/g, '\n').replace(/%0D/g, '\r'); - window.open().document.write(csv); - } - else window.open('data:text/csv,'+csv); - } -}); -})(); diff --git a/addons/web_graph/static/lib/flotr2/js/plugins/titles.js b/addons/web_graph/static/lib/flotr2/js/plugins/titles.js deleted file mode 100644 index cf95d9e7c80..00000000000 --- a/addons/web_graph/static/lib/flotr2/js/plugins/titles.js +++ /dev/null @@ -1,177 +0,0 @@ -(function () { - -var D = Flotr.DOM; - -Flotr.addPlugin('titles', { - callbacks: { - 'flotr:afterdraw': function() { - this.titles.drawTitles(); - } - }, - /** - * Draws the title and the subtitle - */ - drawTitles : function () { - var html, - options = this.options, - margin = options.grid.labelMargin, - ctx = this.ctx, - a = this.axes; - - if (!options.HtmlText && this.textEnabled) { - var style = { - size: options.fontSize, - color: options.grid.color, - textAlign: 'center' - }; - - // Add subtitle - if (options.subtitle){ - Flotr.drawText( - ctx, options.subtitle, - this.plotOffset.left + this.plotWidth/2, - this.titleHeight + this.subtitleHeight - 2, - style - ); - } - - style.weight = 1.5; - style.size *= 1.5; - - // Add title - if (options.title){ - Flotr.drawText( - ctx, options.title, - this.plotOffset.left + this.plotWidth/2, - this.titleHeight - 2, - style - ); - } - - style.weight = 1.8; - style.size *= 0.8; - - // Add x axis title - if (a.x.options.title && a.x.used){ - style.textAlign = a.x.options.titleAlign || 'center'; - style.textBaseline = 'top'; - style.angle = Flotr.toRad(a.x.options.titleAngle); - style = Flotr.getBestTextAlign(style.angle, style); - Flotr.drawText( - ctx, a.x.options.title, - this.plotOffset.left + this.plotWidth/2, - this.plotOffset.top + a.x.maxLabel.height + this.plotHeight + 2 * margin, - style - ); - } - - // Add x2 axis title - if (a.x2.options.title && a.x2.used){ - style.textAlign = a.x2.options.titleAlign || 'center'; - style.textBaseline = 'bottom'; - style.angle = Flotr.toRad(a.x2.options.titleAngle); - style = Flotr.getBestTextAlign(style.angle, style); - Flotr.drawText( - ctx, a.x2.options.title, - this.plotOffset.left + this.plotWidth/2, - this.plotOffset.top - a.x2.maxLabel.height - 2 * margin, - style - ); - } - - // Add y axis title - if (a.y.options.title && a.y.used){ - style.textAlign = a.y.options.titleAlign || 'right'; - style.textBaseline = 'middle'; - style.angle = Flotr.toRad(a.y.options.titleAngle); - style = Flotr.getBestTextAlign(style.angle, style); - Flotr.drawText( - ctx, a.y.options.title, - this.plotOffset.left - a.y.maxLabel.width - 2 * margin, - this.plotOffset.top + this.plotHeight / 2, - style - ); - } - - // Add y2 axis title - if (a.y2.options.title && a.y2.used){ - style.textAlign = a.y2.options.titleAlign || 'left'; - style.textBaseline = 'middle'; - style.angle = Flotr.toRad(a.y2.options.titleAngle); - style = Flotr.getBestTextAlign(style.angle, style); - Flotr.drawText( - ctx, a.y2.options.title, - this.plotOffset.left + this.plotWidth + a.y2.maxLabel.width + 2 * margin, - this.plotOffset.top + this.plotHeight / 2, - style - ); - } - } - else { - html = []; - - // Add title - if (options.title) - html.push( - '<div style="position:absolute;top:0;left:', - this.plotOffset.left, 'px;font-size:1em;font-weight:bold;text-align:center;width:', - this.plotWidth,'px;" class="flotr-title">', options.title, '</div>' - ); - - // Add subtitle - if (options.subtitle) - html.push( - '<div style="position:absolute;top:', this.titleHeight, 'px;left:', - this.plotOffset.left, 'px;font-size:smaller;text-align:center;width:', - this.plotWidth, 'px;" class="flotr-subtitle">', options.subtitle, '</div>' - ); - - html.push('</div>'); - - html.push('<div class="flotr-axis-title" style="font-weight:bold;">'); - - // Add x axis title - if (a.x.options.title && a.x.used) - html.push( - '<div style="position:absolute;top:', - (this.plotOffset.top + this.plotHeight + options.grid.labelMargin + a.x.titleSize.height), - 'px;left:', this.plotOffset.left, 'px;width:', this.plotWidth, - 'px;text-align:center;" class="flotr-axis-title">', a.x.options.title, '</div>' - ); - - // Add x2 axis title - if (a.x2.options.title && a.x2.used) - html.push( - '<div style="position:absolute;top:0;left:', this.plotOffset.left, 'px;width:', - this.plotWidth, 'px;text-align:center;" class="flotr-axis-title">', a.x2.options.title, '</div>' - ); - - // Add y axis title - if (a.y.options.title && a.y.used) - html.push( - '<div style="position:absolute;top:', - (this.plotOffset.top + this.plotHeight/2 - a.y.titleSize.height/2), - 'px;left:0;text-align:right;" class="flotr-axis-title">', a.y.options.title, '</div>' - ); - - // Add y2 axis title - if (a.y2.options.title && a.y2.used) - html.push( - '<div style="position:absolute;top:', - (this.plotOffset.top + this.plotHeight/2 - a.y.titleSize.height/2), - 'px;right:0;text-align:right;" class="flotr-axis-title">', a.y2.options.title, '</div>' - ); - - html = html.join(''); - - var div = D.create('div'); - D.setStyles({ - color: options.grid.color - }); - div.className = 'flotr-titles'; - D.insert(this.el, div); - D.insert(div, html); - } - } -}); -})(); diff --git a/addons/web_graph/static/lib/flotr2/js/types/bars.js b/addons/web_graph/static/lib/flotr2/js/types/bars.js deleted file mode 100644 index 03e7988b785..00000000000 --- a/addons/web_graph/static/lib/flotr2/js/types/bars.js +++ /dev/null @@ -1,274 +0,0 @@ -/** Bars **/ -Flotr.addType('bars', { - - options: { - show: false, // => setting to true will show bars, false will hide - lineWidth: 2, // => in pixels - barWidth: 1, // => in units of the x axis - fill: true, // => true to fill the area from the line to the x axis, false for (transparent) no fill - fillColor: null, // => fill color - fillOpacity: 0.4, // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill - horizontal: false, // => horizontal bars (x and y inverted) - stacked: false, // => stacked bar charts - centered: true, // => center the bars to their x axis value - topPadding: 0.1 // => top padding in percent - }, - - stack : { - positive : [], - negative : [], - _positive : [], // Shadow - _negative : [] // Shadow - }, - - draw : function (options) { - var - context = options.context; - - context.save(); - context.lineJoin = 'miter'; - // @TODO linewidth not interpreted the right way. - context.lineWidth = options.lineWidth; - context.strokeStyle = options.color; - if (options.fill) context.fillStyle = options.fillStyle; - - this.plot(options); - - context.restore(); - }, - - plot : function (options) { - - var - data = options.data, - context = options.context, - shadowSize = options.shadowSize, - i, geometry, left, top, width, height; - - if (data.length < 1) return; - - this.translate(context, options.horizontal); - - for (i = 0; i < data.length; i++) { - - geometry = this.getBarGeometry(data[i][0], data[i][1], options); - if (geometry === null) continue; - - left = geometry.left; - top = geometry.top; - width = geometry.width; - height = geometry.height; - - if (options.fill) context.fillRect(left, top, width, height); - if (shadowSize) { - context.save(); - context.fillStyle = 'rgba(0,0,0,0.05)'; - context.fillRect(left + shadowSize, top + shadowSize, width, height); - context.restore(); - } - if (options.lineWidth) { - context.strokeRect(left, top, width, height); - } - } - }, - - translate : function (context, horizontal) { - if (horizontal) { - context.rotate(-Math.PI / 2); - context.scale(-1, 1); - } - }, - - getBarGeometry : function (x, y, options) { - - var - horizontal = options.horizontal, - barWidth = options.barWidth, - centered = options.centered, - stack = options.stacked ? this.stack : false, - lineWidth = options.lineWidth, - bisection = centered ? barWidth / 2 : 0, - xScale = horizontal ? options.yScale : options.xScale, - yScale = horizontal ? options.xScale : options.yScale, - xValue = horizontal ? y : x, - yValue = horizontal ? x : y, - stackOffset = 0, - stackValue, left, right, top, bottom; - - // Stacked bars - if (stack) { - stackValue = yValue > 0 ? stack.positive : stack.negative; - stackOffset = stackValue[xValue] || stackOffset; - stackValue[xValue] = stackOffset + yValue; - } - - left = xScale(xValue - bisection); - right = xScale(xValue + barWidth - bisection); - top = yScale(yValue + stackOffset); - bottom = yScale(stackOffset); - - // TODO for test passing... probably looks better without this - if (bottom < 0) bottom = 0; - - // TODO Skipping... - // if (right < xa.min || left > xa.max || top < ya.min || bottom > ya.max) continue; - - return (x === null || y === null) ? null : { - x : xValue, - y : yValue, - xScale : xScale, - yScale : yScale, - top : top, - left : Math.min(left, right) - lineWidth / 2, - width : Math.abs(right - left) - lineWidth, - height : bottom - top - }; - }, - - hit : function (options) { - var - data = options.data, - args = options.args, - mouse = args[0], - n = args[1], - x = mouse.x, - y = mouse.y, - hitGeometry = this.getBarGeometry(x, y, options), - width = hitGeometry.width / 2, - left = hitGeometry.left, - geometry, i; - - for (i = data.length; i--;) { - geometry = this.getBarGeometry(data[i][0], data[i][1], options); - if (geometry.y > hitGeometry.y && Math.abs(left - geometry.left) < width) { - n.x = data[i][0]; - n.y = data[i][1]; - n.index = i; - n.seriesIndex = options.index; - } - } - }, - - drawHit : function (options) { - // TODO hits for stacked bars; implement using calculateStack option? - var - context = options.context, - args = options.args, - geometry = this.getBarGeometry(args.x, args.y, options), - left = geometry.left, - top = geometry.top, - width = geometry.width, - height = geometry.height; - - context.save(); - context.strokeStyle = options.color; - context.lineWidth = options.lineWidth; - this.translate(context, options.horizontal); - - // Draw highlight - context.beginPath(); - context.moveTo(left, top + height); - context.lineTo(left, top); - context.lineTo(left + width, top); - context.lineTo(left + width, top + height); - if (options.fill) { - context.fillStyle = options.fillStyle; - context.fill(); - } - context.stroke(); - context.closePath(); - - context.restore(); - }, - - clearHit: function (options) { - var - context = options.context, - args = options.args, - geometry = this.getBarGeometry(args.x, args.y, options), - left = geometry.left, - width = geometry.width, - top = geometry.top, - height = geometry.height, - lineWidth = 2 * options.lineWidth; - - context.save(); - this.translate(context, options.horizontal); - context.clearRect( - left - lineWidth, - Math.min(top, top + height) - lineWidth, - width + 2 * lineWidth, - Math.abs(height) + 2 * lineWidth - ); - context.restore(); - }, - - extendXRange : function (axis, data, options, bars) { - this._extendRange(axis, data, options, bars); - }, - - extendYRange : function (axis, data, options, bars) { - this._extendRange(axis, data, options, bars); - }, - _extendRange: function (axis, data, options, bars) { - - var - max = axis.options.max; - - if (_.isNumber(max) || _.isString(max)) return; - - var - newmin = axis.min, - newmax = axis.max, - horizontal = options.horizontal, - orientation = axis.orientation, - positiveSums = this.positiveSums || {}, - negativeSums = this.negativeSums || {}, - value, datum, index, j; - - // Sides of bars - if ((orientation == 1 && !horizontal) || (orientation == -1 && horizontal)) { - if (options.centered) { - newmax = Math.max(axis.datamax + options.barWidth, newmax); - newmin = Math.min(axis.datamin - options.barWidth, newmin); - } - } - - if (options.stacked && - ((orientation == 1 && horizontal) || (orientation == -1 && !horizontal))){ - - for (j = data.length; j--;) { - value = data[j][(orientation == 1 ? 1 : 0)]+''; - datum = data[j][(orientation == 1 ? 0 : 1)]; - - // Positive - if (datum > 0) { - positiveSums[value] = (positiveSums[value] || 0) + datum; - newmax = Math.max(newmax, positiveSums[value]); - } - - // Negative - else { - negativeSums[value] = (negativeSums[value] || 0) + datum; - newmin = Math.min(newmin, negativeSums[value]); - } - } - } - - // End of bars - if ((orientation == 1 && horizontal) || (orientation == -1 && !horizontal)) { - if (options.topPadding && (axis.max === axis.datamax || (options.stacked && this.stackMax !== newmax))) { - newmax += options.topPadding * (newmax - newmin); - } - } - - this.stackMin = newmin; - this.stackMax = newmax; - this.negativeSums = negativeSums; - this.positiveSums = positiveSums; - - axis.max = newmax; - axis.min = newmin; - } - -}); diff --git a/addons/web_graph/static/lib/flotr2/js/types/bubbles.js b/addons/web_graph/static/lib/flotr2/js/types/bubbles.js deleted file mode 100644 index a97c5a28a93..00000000000 --- a/addons/web_graph/static/lib/flotr2/js/types/bubbles.js +++ /dev/null @@ -1,119 +0,0 @@ -/** Bubbles **/ -Flotr.addType('bubbles', { - options: { - show: false, // => setting to true will show radar chart, false will hide - lineWidth: 2, // => line width in pixels - fill: true, // => true to fill the area from the line to the x axis, false for (transparent) no fill - fillOpacity: 0.4, // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill - baseRadius: 2 // => ratio of the radar, against the plot size - }, - draw : function (options) { - var - context = options.context, - shadowSize = options.shadowSize; - - context.save(); - context.lineWidth = options.lineWidth; - - // Shadows - context.fillStyle = 'rgba(0,0,0,0.05)'; - context.strokeStyle = 'rgba(0,0,0,0.05)'; - this.plot(options, shadowSize / 2); - context.strokeStyle = 'rgba(0,0,0,0.1)'; - this.plot(options, shadowSize / 4); - - // Chart - context.strokeStyle = options.color; - context.fillStyle = options.fillStyle; - this.plot(options); - - context.restore(); - }, - plot : function (options, offset) { - - var - data = options.data, - context = options.context, - geometry, - i, x, y, z; - - offset = offset || 0; - - for (i = 0; i < data.length; ++i){ - - geometry = this.getGeometry(data[i], options); - - context.beginPath(); - context.arc(geometry.x + offset, geometry.y + offset, geometry.z, 0, 2 * Math.PI, true); - context.stroke(); - if (options.fill) context.fill(); - context.closePath(); - } - }, - getGeometry : function (point, options) { - return { - x : options.xScale(point[0]), - y : options.yScale(point[1]), - z : point[2] * options.baseRadius - }; - }, - hit : function (options) { - var - data = options.data, - args = options.args, - mouse = args[0], - n = args[1], - x = mouse.x, - y = mouse.y, - geometry, - dx, dy; - - for (i = data.length; i--;) { - geometry = this.getGeometry(data[i], options); - - dx = geometry.x - options.xScale(x); - dy = geometry.y - options.yScale(y); - - if (Math.sqrt(dx * dx + dy * dy) < geometry.z) { - n.x = data[i][0]; - n.y = data[i][1]; - n.index = i; - n.seriesIndex = options.index; - } - } - }, - drawHit : function (options) { - - var - context = options.context, - geometry = this.getGeometry(options.data[options.args.index], options); - - context.save(); - context.lineWidth = options.lineWidth; - context.fillStyle = options.fillStyle; - context.strokeStyle = options.color; - context.beginPath(); - context.arc(geometry.x, geometry.y, geometry.z, 0, 2 * Math.PI, true); - context.fill(); - context.stroke(); - context.closePath(); - context.restore(); - }, - clearHit : function (options) { - - var - context = options.context, - geometry = this.getGeometry(options.data[options.args.index], options), - offset = geometry.z + options.lineWidth; - - context.save(); - context.clearRect( - geometry.x - offset, - geometry.y - offset, - 2 * offset, - 2 * offset - ); - context.restore(); - } - // TODO Add a hit calculation method (like pie) -}); diff --git a/addons/web_graph/static/lib/flotr2/js/types/candles.js b/addons/web_graph/static/lib/flotr2/js/types/candles.js deleted file mode 100644 index 0e484b366cc..00000000000 --- a/addons/web_graph/static/lib/flotr2/js/types/candles.js +++ /dev/null @@ -1,127 +0,0 @@ -/** Candles **/ -Flotr.addType('candles', { - options: { - show: false, // => setting to true will show candle sticks, false will hide - lineWidth: 1, // => in pixels - wickLineWidth: 1, // => in pixels - candleWidth: 0.6, // => in units of the x axis - fill: true, // => true to fill the area from the line to the x axis, false for (transparent) no fill - upFillColor: '#00A8F0',// => up sticks fill color - downFillColor: '#CB4B4B',// => down sticks fill color - fillOpacity: 0.5, // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill - // TODO Test this barcharts option. - barcharts: false // => draw as barcharts (not standard bars but financial barcharts) - }, - - draw : function (options) { - - var - context = options.context; - - context.save(); - context.lineJoin = 'miter'; - context.lineCap = 'butt'; - // @TODO linewidth not interpreted the right way. - context.lineWidth = options.wickLineWidth || options.lineWidth; - - this.plot(options); - - context.restore(); - }, - - plot : function (options) { - - var - data = options.data, - context = options.context, - xScale = options.xScale, - yScale = options.yScale, - width = options.candleWidth / 2, - shadowSize = options.shadowSize, - lineWidth = options.lineWidth, - wickLineWidth = options.wickLineWidth, - pixelOffset = (wickLineWidth % 2) / 2, - color, - datum, x, y, - open, high, low, close, - left, right, bottom, top, bottom2, top2, - i; - - if (data.length < 1) return; - - for (i = 0; i < data.length; i++) { - datum = data[i]; - x = datum[0]; - open = datum[1]; - high = datum[2]; - low = datum[3]; - close = datum[4]; - left = xScale(x - width); - right = xScale(x + width); - bottom = yScale(low); - top = yScale(high); - bottom2 = yScale(Math.min(open, close)); - top2 = yScale(Math.max(open, close)); - - /* - // TODO skipping - if(right < xa.min || left > xa.max || top < ya.min || bottom > ya.max) - continue; - */ - - color = options[open > close ? 'downFillColor' : 'upFillColor']; - - // Fill the candle. - // TODO Test the barcharts option - if (options.fill && !options.barcharts) { - context.fillStyle = 'rgba(0,0,0,0.05)'; - context.fillRect(left + shadowSize, top2 + shadowSize, right - left, bottom2 - top2); - context.save(); - context.globalAlpha = options.fillOpacity; - context.fillStyle = color; - context.fillRect(left, top2 + lineWidth, right - left, bottom2 - top2); - context.restore(); - } - - // Draw candle outline/border, high, low. - if (lineWidth || wickLineWidth) { - - x = Math.floor((left + right) / 2) + pixelOffset; - - context.strokeStyle = color; - context.beginPath(); - - // TODO Again with the bartcharts - if (options.barcharts) { - - context.moveTo(x, Math.floor(top + width)); - context.lineTo(x, Math.floor(bottom + width)); - - y = Math.floor(open + width) + 0.5; - context.moveTo(Math.floor(left) + pixelOffset, y); - context.lineTo(x, y); - - y = Math.floor(close + width) + 0.5; - context.moveTo(Math.floor(right) + pixelOffset, y); - context.lineTo(x, y); - } else { - context.strokeRect(left, top2 + lineWidth, right - left, bottom2 - top2); - - context.moveTo(x, Math.floor(top2 + lineWidth)); - context.lineTo(x, Math.floor(top + lineWidth)); - context.moveTo(x, Math.floor(bottom2 + lineWidth)); - context.lineTo(x, Math.floor(bottom + lineWidth)); - } - - context.closePath(); - context.stroke(); - } - } - }, - extendXRange: function (axis, data, options) { - if (axis.options.max === null) { - axis.max = Math.max(axis.datamax + 0.5, axis.max); - axis.min = Math.min(axis.datamin - 0.5, axis.min); - } - } -}); diff --git a/addons/web_graph/static/lib/flotr2/js/types/gantt.js b/addons/web_graph/static/lib/flotr2/js/types/gantt.js deleted file mode 100644 index 1498bcb51cf..00000000000 --- a/addons/web_graph/static/lib/flotr2/js/types/gantt.js +++ /dev/null @@ -1,229 +0,0 @@ -/** Gantt - * Base on data in form [s,y,d] where: - * y - executor or simply y value - * s - task start value - * d - task duration - * **/ -Flotr.addType('gantt', { - options: { - show: false, // => setting to true will show gantt, false will hide - lineWidth: 2, // => in pixels - barWidth: 1, // => in units of the x axis - fill: true, // => true to fill the area from the line to the x axis, false for (transparent) no fill - fillColor: null, // => fill color - fillOpacity: 0.4, // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill - centered: true // => center the bars to their x axis value - }, - /** - * Draws gantt series in the canvas element. - * @param {Object} series - Series with options.gantt.show = true. - */ - draw: function(series) { - var ctx = this.ctx, - bw = series.gantt.barWidth, - lw = Math.min(series.gantt.lineWidth, bw); - - ctx.save(); - ctx.translate(this.plotOffset.left, this.plotOffset.top); - ctx.lineJoin = 'miter'; - - /** - * @todo linewidth not interpreted the right way. - */ - ctx.lineWidth = lw; - ctx.strokeStyle = series.color; - - ctx.save(); - this.gantt.plotShadows(series, bw, 0, series.gantt.fill); - ctx.restore(); - - if(series.gantt.fill){ - var color = series.gantt.fillColor || series.color; - ctx.fillStyle = this.processColor(color, {opacity: series.gantt.fillOpacity}); - } - - this.gantt.plot(series, bw, 0, series.gantt.fill); - ctx.restore(); - }, - plot: function(series, barWidth, offset, fill){ - var data = series.data; - if(data.length < 1) return; - - var xa = series.xaxis, - ya = series.yaxis, - ctx = this.ctx, i; - - for(i = 0; i < data.length; i++){ - var y = data[i][0], - s = data[i][1], - d = data[i][2], - drawLeft = true, drawTop = true, drawRight = true; - - if (s === null || d === null) continue; - - var left = s, - right = s + d, - bottom = y - (series.gantt.centered ? barWidth/2 : 0), - top = y + barWidth - (series.gantt.centered ? barWidth/2 : 0); - - if(right < xa.min || left > xa.max || top < ya.min || bottom > ya.max) - continue; - - if(left < xa.min){ - left = xa.min; - drawLeft = false; - } - - if(right > xa.max){ - right = xa.max; - if (xa.lastSerie != series) - drawTop = false; - } - - if(bottom < ya.min) - bottom = ya.min; - - if(top > ya.max){ - top = ya.max; - if (ya.lastSerie != series) - drawTop = false; - } - - /** - * Fill the bar. - */ - if(fill){ - ctx.beginPath(); - ctx.moveTo(xa.d2p(left), ya.d2p(bottom) + offset); - ctx.lineTo(xa.d2p(left), ya.d2p(top) + offset); - ctx.lineTo(xa.d2p(right), ya.d2p(top) + offset); - ctx.lineTo(xa.d2p(right), ya.d2p(bottom) + offset); - ctx.fill(); - ctx.closePath(); - } - - /** - * Draw bar outline/border. - */ - if(series.gantt.lineWidth && (drawLeft || drawRight || drawTop)){ - ctx.beginPath(); - ctx.moveTo(xa.d2p(left), ya.d2p(bottom) + offset); - - ctx[drawLeft ?'lineTo':'moveTo'](xa.d2p(left), ya.d2p(top) + offset); - ctx[drawTop ?'lineTo':'moveTo'](xa.d2p(right), ya.d2p(top) + offset); - ctx[drawRight?'lineTo':'moveTo'](xa.d2p(right), ya.d2p(bottom) + offset); - - ctx.stroke(); - ctx.closePath(); - } - } - }, - plotShadows: function(series, barWidth, offset){ - var data = series.data; - if(data.length < 1) return; - - var i, y, s, d, - xa = series.xaxis, - ya = series.yaxis, - ctx = this.ctx, - sw = this.options.shadowSize; - - for(i = 0; i < data.length; i++){ - y = data[i][0]; - s = data[i][1]; - d = data[i][2]; - - if (s === null || d === null) continue; - - var left = s, - right = s + d, - bottom = y - (series.gantt.centered ? barWidth/2 : 0), - top = y + barWidth - (series.gantt.centered ? barWidth/2 : 0); - - if(right < xa.min || left > xa.max || top < ya.min || bottom > ya.max) - continue; - - if(left < xa.min) left = xa.min; - if(right > xa.max) right = xa.max; - if(bottom < ya.min) bottom = ya.min; - if(top > ya.max) top = ya.max; - - var width = xa.d2p(right)-xa.d2p(left)-((xa.d2p(right)+sw <= this.plotWidth) ? 0 : sw); - var height = ya.d2p(bottom)-ya.d2p(top)-((ya.d2p(bottom)+sw <= this.plotHeight) ? 0 : sw ); - - ctx.fillStyle = 'rgba(0,0,0,0.05)'; - ctx.fillRect(Math.min(xa.d2p(left)+sw, this.plotWidth), Math.min(ya.d2p(top)+sw, this.plotHeight), width, height); - } - }, - extendXRange: function(axis) { - if(axis.options.max === null){ - var newmin = axis.min, - newmax = axis.max, - i, j, x, s, g, - stackedSumsPos = {}, - stackedSumsNeg = {}, - lastSerie = null; - - for(i = 0; i < this.series.length; ++i){ - s = this.series[i]; - g = s.gantt; - - if(g.show && s.xaxis == axis) { - for (j = 0; j < s.data.length; j++) { - if (g.show) { - y = s.data[j][0]+''; - stackedSumsPos[y] = Math.max((stackedSumsPos[y] || 0), s.data[j][1]+s.data[j][2]); - lastSerie = s; - } - } - for (j in stackedSumsPos) { - newmax = Math.max(stackedSumsPos[j], newmax); - } - } - } - axis.lastSerie = lastSerie; - axis.max = newmax; - axis.min = newmin; - } - }, - extendYRange: function(axis){ - if(axis.options.max === null){ - var newmax = Number.MIN_VALUE, - newmin = Number.MAX_VALUE, - i, j, s, g, - stackedSumsPos = {}, - stackedSumsNeg = {}, - lastSerie = null; - - for(i = 0; i < this.series.length; ++i){ - s = this.series[i]; - g = s.gantt; - - if (g.show && !s.hide && s.yaxis == axis) { - var datamax = Number.MIN_VALUE, datamin = Number.MAX_VALUE; - for(j=0; j < s.data.length; j++){ - datamax = Math.max(datamax,s.data[j][0]); - datamin = Math.min(datamin,s.data[j][0]); - } - - if (g.centered) { - newmax = Math.max(datamax + 0.5, newmax); - newmin = Math.min(datamin - 0.5, newmin); - } - else { - newmax = Math.max(datamax + 1, newmax); - newmin = Math.min(datamin, newmin); - } - // For normal horizontal bars - if (g.barWidth + datamax > newmax){ - newmax = axis.max + g.barWidth; - } - } - } - axis.lastSerie = lastSerie; - axis.max = newmax; - axis.min = newmin; - axis.tickSize = Flotr.getTickSize(axis.options.noTicks, newmin, newmax, axis.options.tickDecimals); - } - } -}); diff --git a/addons/web_graph/static/lib/flotr2/js/types/lines.js b/addons/web_graph/static/lib/flotr2/js/types/lines.js deleted file mode 100644 index d65971b2dfe..00000000000 --- a/addons/web_graph/static/lib/flotr2/js/types/lines.js +++ /dev/null @@ -1,275 +0,0 @@ -/** Lines **/ -Flotr.addType('lines', { - options: { - show: false, // => setting to true will show lines, false will hide - lineWidth: 2, // => line width in pixels - fill: false, // => true to fill the area from the line to the x axis, false for (transparent) no fill - fillBorder: false, // => draw a border around the fill - fillColor: null, // => fill color - fillOpacity: 0.4, // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill - steps: false, // => draw steps - stacked: false // => setting to true will show stacked lines, false will show normal lines - }, - - stack : { - values : [] - }, - - /** - * Draws lines series in the canvas element. - * @param {Object} options - */ - draw : function (options) { - - var - context = options.context, - lineWidth = options.lineWidth, - shadowSize = options.shadowSize, - offset; - - context.save(); - context.lineJoin = 'round'; - - if (shadowSize) { - - context.lineWidth = shadowSize / 2; - offset = lineWidth / 2 + context.lineWidth / 2; - - // @TODO do this instead with a linear gradient - context.strokeStyle = "rgba(0,0,0,0.1)"; - this.plot(options, offset + shadowSize / 2, false); - - context.strokeStyle = "rgba(0,0,0,0.2)"; - this.plot(options, offset, false); - } - - context.lineWidth = lineWidth; - context.strokeStyle = options.color; - - this.plot(options, 0, true); - - context.restore(); - }, - - plot : function (options, shadowOffset, incStack) { - - var - context = options.context, - width = options.width, - height = options.height, - xScale = options.xScale, - yScale = options.yScale, - data = options.data, - stack = options.stacked ? this.stack : false, - length = data.length - 1, - prevx = null, - prevy = null, - zero = yScale(0), - x1, x2, y1, y2, stack1, stack2, i; - - if (length < 1) return; - - context.beginPath(); - - for (i = 0; i < length; ++i) { - - // To allow empty values - if (data[i][1] === null || data[i+1][1] === null) continue; - - // Zero is infinity for log scales - // TODO handle zero for logarithmic - // if (xa.options.scaling === 'logarithmic' && (data[i][0] <= 0 || data[i+1][0] <= 0)) continue; - // if (ya.options.scaling === 'logarithmic' && (data[i][1] <= 0 || data[i+1][1] <= 0)) continue; - - x1 = xScale(data[i][0]); - x2 = xScale(data[i+1][0]); - - if (stack) { - - stack1 = stack.values[data[i][0]] || 0; - stack2 = stack.values[data[i+1][0]] || stack.values[data[i][0]] || 0; - - y1 = yScale(data[i][1] + stack1); - y2 = yScale(data[i+1][1] + stack2); - - if(incStack){ - stack.values[data[i][0]] = data[i][1]+stack1; - - if(i == length-1) - stack.values[data[i+1][0]] = data[i+1][1]+stack2; - } - } - else{ - y1 = yScale(data[i][1]); - y2 = yScale(data[i+1][1]); - } - - if ( - (y1 > height && y2 > height) || - (y1 < 0 && y2 < 0) || - (x1 < 0 && x2 < 0) || - (x1 > width && x2 > width) - ) continue; - - if((prevx != x1) || (prevy != y1 + shadowOffset)) - context.moveTo(x1, y1 + shadowOffset); - - prevx = x2; - prevy = y2 + shadowOffset; - if (options.steps) { - context.lineTo(prevx + shadowOffset / 2, y1 + shadowOffset); - context.lineTo(prevx + shadowOffset / 2, prevy); - } else { - context.lineTo(prevx, prevy); - } - } - - if (!options.fill || options.fill && !options.fillBorder) context.stroke(); - - // TODO stacked lines - if(!shadowOffset && options.fill){ - x1 = xScale(data[0][0]); - context.fillStyle = options.fillStyle; - context.lineTo(x2, zero); - context.lineTo(x1, zero); - context.lineTo(x1, yScale(data[0][1])); - context.fill(); - if (options.fillBorder) { - context.stroke(); - } - } - - context.closePath(); - }, - - // Perform any pre-render precalculations (this should be run on data first) - // - Pie chart total for calculating measures - // - Stacks for lines and bars - // precalculate : function () { - // } - // - // - // Get any bounds after pre calculation (axis can fetch this if does not have explicit min/max) - // getBounds : function () { - // } - // getMin : function () { - // } - // getMax : function () { - // } - // - // - // Padding around rendered elements - // getPadding : function () { - // } - - extendYRange : function (axis, data, options, lines) { - - var o = axis.options; - - // If stacked and auto-min - if (options.stacked && ((!o.max && o.max !== 0) || (!o.min && o.min !== 0))) { - - var - newmax = axis.max, - newmin = axis.min, - positiveSums = lines.positiveSums || {}, - negativeSums = lines.negativeSums || {}, - x, j; - - for (j = 0; j < data.length; j++) { - - x = data[j][0] + ''; - - // Positive - if (data[j][1] > 0) { - positiveSums[x] = (positiveSums[x] || 0) + data[j][1]; - newmax = Math.max(newmax, positiveSums[x]); - } - - // Negative - else { - negativeSums[x] = (negativeSums[x] || 0) + data[j][1]; - newmin = Math.min(newmin, negativeSums[x]); - } - } - - lines.negativeSums = negativeSums; - lines.positiveSums = positiveSums; - - axis.max = newmax; - axis.min = newmin; - } - - if (options.steps) { - - this.hit = function (options) { - var - data = options.data, - args = options.args, - yScale = options.yScale, - mouse = args[0], - length = data.length, - n = args[1], - x = mouse.x, - relY = mouse.relY, - i; - - for (i = 0; i < length - 1; i++) { - if (x >= data[i][0] && x <= data[i+1][0]) { - if (Math.abs(yScale(data[i][1]) - relY) < 8) { - n.x = data[i][0]; - n.y = data[i][1]; - n.index = i; - n.seriesIndex = options.index; - } - break; - } - } - }; - - this.drawHit = function (options) { - var - context = options.context, - args = options.args, - data = options.data, - xScale = options.xScale, - index = args.index, - x = xScale(args.x), - y = options.yScale(args.y), - x2; - - if (data.length - 1 > index) { - x2 = options.xScale(data[index + 1][0]); - context.save(); - context.strokeStyle = options.color; - context.lineWidth = options.lineWidth; - context.beginPath(); - context.moveTo(x, y); - context.lineTo(x2, y); - context.stroke(); - context.closePath(); - context.restore(); - } - }; - - this.clearHit = function (options) { - var - context = options.context, - args = options.args, - data = options.data, - xScale = options.xScale, - width = options.lineWidth, - index = args.index, - x = xScale(args.x), - y = options.yScale(args.y), - x2; - - if (data.length - 1 > index) { - x2 = options.xScale(data[index + 1][0]); - context.clearRect(x - width, y - width, x2 - x + 2 * width, 2 * width); - } - }; - } - } - -}); diff --git a/addons/web_graph/static/lib/flotr2/js/types/markers.js b/addons/web_graph/static/lib/flotr2/js/types/markers.js deleted file mode 100644 index ee1104e3568..00000000000 --- a/addons/web_graph/static/lib/flotr2/js/types/markers.js +++ /dev/null @@ -1,140 +0,0 @@ -/** Markers **/ -/** - * Formats the marker labels. - * @param {Object} obj - Marker value Object {x:..,y:..} - * @return {String} Formatted marker string - */ -(function () { - -Flotr.defaultMarkerFormatter = function(obj){ - return (Math.round(obj.y*100)/100)+''; -}; - -Flotr.addType('markers', { - options: { - show: false, // => setting to true will show markers, false will hide - lineWidth: 1, // => line width of the rectangle around the marker - color: '#000000', // => text color - fill: false, // => fill or not the marekers' rectangles - fillColor: "#FFFFFF", // => fill color - fillOpacity: 0.4, // => fill opacity - stroke: false, // => draw the rectangle around the markers - position: 'ct', // => the markers position (vertical align: b, m, t, horizontal align: l, c, r) - verticalMargin: 0, // => the margin between the point and the text. - labelFormatter: Flotr.defaultMarkerFormatter, - fontSize: Flotr.defaultOptions.fontSize, - stacked: false, // => true if markers should be stacked - stackingType: 'b', // => define staching behavior, (b- bars like, a - area like) (see Issue 125 for details) - horizontal: false // => true if markers should be horizontal (For now only in a case on horizontal stacked bars, stacks should be calculated horizontaly) - }, - - // TODO test stacked markers. - stack : { - positive : [], - negative : [], - values : [] - }, - - draw : function (options) { - - var - data = options.data, - context = options.context, - stack = options.stacked ? options.stack : false, - stackType = options.stackingType, - stackOffsetNeg, - stackOffsetPos, - stackOffset, - i, x, y, label; - - context.save(); - context.lineJoin = 'round'; - context.lineWidth = options.lineWidth; - context.strokeStyle = 'rgba(0,0,0,0.5)'; - context.fillStyle = options.fillStyle; - - function stackPos (a, b) { - stackOffsetPos = stack.negative[a] || 0; - stackOffsetNeg = stack.positive[a] || 0; - if (b > 0) { - stack.positive[a] = stackOffsetPos + b; - return stackOffsetPos + b; - } else { - stack.negative[a] = stackOffsetNeg + b; - return stackOffsetNeg + b; - } - } - - for (i = 0; i < data.length; ++i) { - - x = data[i][0]; - y = data[i][1]; - - if (stack) { - if (stackType == 'b') { - if (options.horizontal) y = stackPos(y, x); - else x = stackPos(x, y); - } else if (stackType == 'a') { - stackOffset = stack.values[x] || 0; - stack.values[x] = stackOffset + y; - y = stackOffset + y; - } - } - - label = options.labelFormatter({x: x, y: y, index: i, data : data}); - this.plot(options.xScale(x), options.yScale(y), label, options); - } - context.restore(); - }, - plot: function(x, y, label, options) { - var context = options.context; - if (isImage(label) && !label.complete) { - throw 'Marker image not loaded.'; - } else { - this._plot(x, y, label, options); - } - }, - - _plot: function(x, y, label, options) { - var context = options.context, - margin = 2, - left = x, - top = y, - dim; - - if (isImage(label)) - dim = {height : label.height, width: label.width}; - else - dim = options.text.canvas(label); - - dim.width = Math.floor(dim.width+margin*2); - dim.height = Math.floor(dim.height+margin*2); - - if (options.position.indexOf('c') != -1) left -= dim.width/2 + margin; - else if (options.position.indexOf('l') != -1) left -= dim.width; - - if (options.position.indexOf('m') != -1) top -= dim.height/2 + margin; - else if (options.position.indexOf('t') != -1) top -= dim.height + options.verticalMargin; - else top += options.verticalMargin; - - left = Math.floor(left)+0.5; - top = Math.floor(top)+0.5; - - if(options.fill) - context.fillRect(left, top, dim.width, dim.height); - - if(options.stroke) - context.strokeRect(left, top, dim.width, dim.height); - - if (isImage(label)) - context.drawImage(label, left+margin, top+margin); - else - Flotr.drawText(context, label, left+margin, top+margin, {textBaseline: 'top', textAlign: 'left', size: options.fontSize, color: options.color}); - } -}); - -function isImage (i) { - return typeof i === 'object' && i.constructor && (Image ? true : i.constructor === Image); -} - -})(); diff --git a/addons/web_graph/static/lib/flotr2/js/types/pie.js b/addons/web_graph/static/lib/flotr2/js/types/pie.js deleted file mode 100644 index ec932be5e6d..00000000000 --- a/addons/web_graph/static/lib/flotr2/js/types/pie.js +++ /dev/null @@ -1,210 +0,0 @@ -/** Pie **/ -/** - * Formats the pies labels. - * @param {Object} slice - Slice object - * @return {String} Formatted pie label string - */ -(function () { - -var - _ = Flotr._; - -Flotr.defaultPieLabelFormatter = function (total, value) { - return (100 * value / total).toFixed(2)+'%'; -}; - -Flotr.addType('pie', { - options: { - show: false, // => setting to true will show bars, false will hide - lineWidth: 1, // => in pixels - fill: true, // => true to fill the area from the line to the x axis, false for (transparent) no fill - fillColor: null, // => fill color - fillOpacity: 0.6, // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill - explode: 6, // => the number of pixels the splices will be far from the center - sizeRatio: 0.6, // => the size ratio of the pie relative to the plot - startAngle: Math.PI/4, // => the first slice start angle - labelFormatter: Flotr.defaultPieLabelFormatter, - pie3D: false, // => whether to draw the pie in 3 dimenstions or not (ineffective) - pie3DviewAngle: (Math.PI/2 * 0.8), - pie3DspliceThickness: 20 - }, - - draw : function (options) { - - // TODO 3D charts what? - - var - data = options.data, - context = options.context, - canvas = context.canvas, - lineWidth = options.lineWidth, - shadowSize = options.shadowSize, - sizeRatio = options.sizeRatio, - height = options.height, - width = options.width, - explode = options.explode, - color = options.color, - fill = options.fill, - fillStyle = options.fillStyle, - radius = Math.min(canvas.width, canvas.height) * sizeRatio / 2, - value = data[0][1], - html = [], - vScale = 1,//Math.cos(series.pie.viewAngle); - measure = Math.PI * 2 * value / this.total, - startAngle = this.startAngle || (2 * Math.PI * options.startAngle), // TODO: this initial startAngle is already in radians (fixing will be test-unstable) - endAngle = startAngle + measure, - bisection = startAngle + measure / 2, - label = options.labelFormatter(this.total, value), - //plotTickness = Math.sin(series.pie.viewAngle)*series.pie.spliceThickness / vScale; - explodeCoeff = explode + radius + 4, - distX = Math.cos(bisection) * explodeCoeff, - distY = Math.sin(bisection) * explodeCoeff, - textAlign = distX < 0 ? 'right' : 'left', - textBaseline = distY > 0 ? 'top' : 'bottom', - style, - x, y, - distX, distY; - - context.save(); - context.translate(width / 2, height / 2); - context.scale(1, vScale); - - x = Math.cos(bisection) * explode; - y = Math.sin(bisection) * explode; - - // Shadows - if (shadowSize > 0) { - this.plotSlice(x + shadowSize, y + shadowSize, radius, startAngle, endAngle, context); - if (fill) { - context.fillStyle = 'rgba(0,0,0,0.1)'; - context.fill(); - } - } - - this.plotSlice(x, y, radius, startAngle, endAngle, context); - if (fill) { - context.fillStyle = fillStyle; - context.fill(); - } - context.lineWidth = lineWidth; - context.strokeStyle = color; - context.stroke(); - - style = { - size : options.fontSize * 1.2, - color : options.fontColor, - weight : 1.5 - }; - - if (label) { - if (options.htmlText || !options.textEnabled) { - divStyle = 'position:absolute;' + textBaseline + ':' + (height / 2 + (textBaseline === 'top' ? distY : -distY)) + 'px;'; - divStyle += textAlign + ':' + (width / 2 + (textAlign === 'right' ? -distX : distX)) + 'px;'; - html.push('<div style="', divStyle, '" class="flotr-grid-label">', label, '</div>'); - } - else { - style.textAlign = textAlign; - style.textBaseline = textBaseline; - Flotr.drawText(context, label, distX, distY, style); - } - } - - if (options.htmlText || !options.textEnabled) { - var div = Flotr.DOM.node('<div style="color:' + options.fontColor + '" class="flotr-labels"></div>'); - Flotr.DOM.insert(div, html.join('')); - Flotr.DOM.insert(options.element, div); - } - - context.restore(); - - // New start angle - this.startAngle = endAngle; - this.slices = this.slices || []; - this.slices.push({ - radius : Math.min(canvas.width, canvas.height) * sizeRatio / 2, - x : x, - y : y, - explode : explode, - start : startAngle, - end : endAngle - }); - }, - plotSlice : function (x, y, radius, startAngle, endAngle, context) { - context.beginPath(); - context.moveTo(x, y); - context.arc(x, y, radius, startAngle, endAngle, false); - context.lineTo(x, y); - context.closePath(); - }, - hit : function (options) { - - var - data = options.data[0], - args = options.args, - index = options.index, - mouse = args[0], - n = args[1], - slice = this.slices[index], - x = mouse.relX - options.width / 2, - y = mouse.relY - options.height / 2, - r = Math.sqrt(x * x + y * y), - theta = Math.atan(y / x), - circle = Math.PI * 2, - explode = slice.explode || options.explode, - start = slice.start % circle, - end = slice.end % circle; - - if (x < 0) { - theta += Math.PI; - } else if (x > 0 && y < 0) { - theta += circle; - } - - if (r < slice.radius + explode && r > explode) { - if ((start > end && (theta < end || theta > start)) || - (theta > start && theta < end)) { - - // TODO Decouple this from hit plugin (chart shouldn't know what n means) - n.x = data[0]; - n.y = data[1]; - n.sAngle = start; - n.eAngle = end; - n.index = 0; - n.seriesIndex = index; - n.fraction = data[1] / this.total; - } - } - }, - drawHit: function (options) { - var - context = options.context, - slice = this.slices[options.args.seriesIndex]; - - context.save(); - context.translate(options.width / 2, options.height / 2); - this.plotSlice(slice.x, slice.y, slice.radius, slice.start, slice.end, context); - context.stroke(); - context.restore(); - }, - clearHit : function (options) { - var - context = options.context, - slice = this.slices[options.args.seriesIndex], - padding = 2 * options.lineWidth, - radius = slice.radius + padding; - - context.save(); - context.translate(options.width / 2, options.height / 2); - context.clearRect( - slice.x - radius, - slice.y - radius, - 2 * radius + padding, - 2 * radius + padding - ); - context.restore(); - }, - extendYRange : function (axis, data) { - this.total = (this.total || 0) + data[0][1]; - } -}); -})(); diff --git a/addons/web_graph/static/lib/flotr2/js/types/points.js b/addons/web_graph/static/lib/flotr2/js/types/points.js deleted file mode 100644 index ebb360e6fae..00000000000 --- a/addons/web_graph/static/lib/flotr2/js/types/points.js +++ /dev/null @@ -1,66 +0,0 @@ -/** Points **/ -Flotr.addType('points', { - options: { - show: false, // => setting to true will show points, false will hide - radius: 3, // => point radius (pixels) - lineWidth: 2, // => line width in pixels - fill: true, // => true to fill the points with a color, false for (transparent) no fill - fillColor: '#FFFFFF', // => fill color - fillOpacity: 0.4 // => opacity of color inside the points - }, - - draw : function (options) { - var - context = options.context, - lineWidth = options.lineWidth, - shadowSize = options.shadowSize; - - context.save(); - - if (shadowSize > 0) { - context.lineWidth = shadowSize / 2; - - context.strokeStyle = 'rgba(0,0,0,0.1)'; - this.plot(options, shadowSize / 2 + context.lineWidth / 2); - - context.strokeStyle = 'rgba(0,0,0,0.2)'; - this.plot(options, context.lineWidth / 2); - } - - context.lineWidth = options.lineWidth; - context.strokeStyle = options.color; - context.fillStyle = options.fillColor || options.color; - - this.plot(options); - context.restore(); - }, - - plot : function (options, offset) { - var - data = options.data, - context = options.context, - xScale = options.xScale, - yScale = options.yScale, - i, x, y; - - for (i = data.length - 1; i > -1; --i) { - y = data[i][1]; - if (y === null) continue; - - x = xScale(data[i][0]); - y = yScale(y); - - if (x < 0 || x > options.width || y < 0 || y > options.height) continue; - - context.beginPath(); - if (offset) { - context.arc(x, y + offset, options.radius, 0, Math.PI, false); - } else { - context.arc(x, y, options.radius, 0, 2 * Math.PI, true); - if (options.fill) context.fill(); - } - context.stroke(); - context.closePath(); - } - } -}); diff --git a/addons/web_graph/static/lib/flotr2/js/types/radar.js b/addons/web_graph/static/lib/flotr2/js/types/radar.js deleted file mode 100644 index d693ddc78ec..00000000000 --- a/addons/web_graph/static/lib/flotr2/js/types/radar.js +++ /dev/null @@ -1,60 +0,0 @@ -/** Radar **/ -Flotr.addType('radar', { - options: { - show: false, // => setting to true will show radar chart, false will hide - lineWidth: 2, // => line width in pixels - fill: true, // => true to fill the area from the line to the x axis, false for (transparent) no fill - fillOpacity: 0.4, // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill - radiusRatio: 0.90 // => ratio of the radar, against the plot size - }, - draw : function (options) { - var - context = options.context, - shadowSize = options.shadowSize; - - context.save(); - context.translate(options.width / 2, options.height / 2); - context.lineWidth = options.lineWidth; - - // Shadow - context.fillStyle = 'rgba(0,0,0,0.05)'; - context.strokeStyle = 'rgba(0,0,0,0.05)'; - this.plot(options, shadowSize / 2); - context.strokeStyle = 'rgba(0,0,0,0.1)'; - this.plot(options, shadowSize / 4); - - // Chart - context.strokeStyle = options.color; - context.fillStyle = options.fillStyle; - this.plot(options); - - context.restore(); - }, - plot : function (options, offset) { - var - data = options.data, - context = options.context, - radius = Math.min(options.height, options.width) * options.radiusRatio / 2, - step = 2 * Math.PI / data.length, - angle = -Math.PI / 2, - i, ratio; - - offset = offset || 0; - - context.beginPath(); - for (i = 0; i < data.length; ++i) { - ratio = data[i][1] / this.max; - - context[i === 0 ? 'moveTo' : 'lineTo']( - Math.cos(i * step + angle) * radius * ratio + offset, - Math.sin(i * step + angle) * radius * ratio + offset - ); - } - context.closePath(); - if (options.fill) context.fill(); - context.stroke(); - }, - extendYRange : function (axis, data) { - this.max = Math.max(axis.max, this.max || -Number.MAX_VALUE); - } -}); diff --git a/addons/web_graph/static/lib/flotr2/js/types/timeline.js b/addons/web_graph/static/lib/flotr2/js/types/timeline.js deleted file mode 100644 index 59ea6094c01..00000000000 --- a/addons/web_graph/static/lib/flotr2/js/types/timeline.js +++ /dev/null @@ -1,90 +0,0 @@ -Flotr.addType('timeline', { - options: { - show: false, - lineWidth: 1, - barWidth: 0.2, - fill: true, - fillColor: null, - fillOpacity: 0.4, - centered: true - }, - - draw : function (options) { - - var - context = options.context; - - context.save(); - context.lineJoin = 'miter'; - context.lineWidth = options.lineWidth; - context.strokeStyle = options.color; - context.fillStyle = options.fillStyle; - - this.plot(options); - - context.restore(); - }, - - plot : function (options) { - - var - data = options.data, - context = options.context, - xScale = options.xScale, - yScale = options.yScale, - barWidth = options.barWidth, - lineWidth = options.lineWidth, - i; - - Flotr._.each(data, function (timeline) { - - var - x = timeline[0], - y = timeline[1], - w = timeline[2], - h = barWidth, - - xt = Math.ceil(xScale(x)), - wt = Math.ceil(xScale(x + w)) - xt, - yt = Math.round(yScale(y)), - ht = Math.round(yScale(y - h)) - yt, - - x0 = xt - lineWidth / 2, - y0 = Math.round(yt - ht / 2) - lineWidth / 2; - - context.strokeRect(x0, y0, wt, ht); - context.fillRect(x0, y0, wt, ht); - - }); - }, - - extendRange : function (series) { - - var - data = series.data, - xa = series.xaxis, - ya = series.yaxis, - w = series.timeline.barWidth; - - if (xa.options.min === null) - xa.min = xa.datamin - w / 2; - - if (xa.options.max === null) { - - var - max = xa.max; - - Flotr._.each(data, function (timeline) { - max = Math.max(max, timeline[0] + timeline[2]); - }, this); - - xa.max = max + w / 2; - } - - if (ya.options.min === null) - ya.min = ya.datamin - w; - if (ya.options.min === null) - ya.max = ya.datamax + w; - } - -}); diff --git a/addons/web_graph/static/lib/flotr2/lib/base64.js b/addons/web_graph/static/lib/flotr2/lib/base64.js deleted file mode 100644 index 3306e49f9f7..00000000000 --- a/addons/web_graph/static/lib/flotr2/lib/base64.js +++ /dev/null @@ -1,113 +0,0 @@ -/* Copyright (C) 1999 Masanao Izumo <iz@onicos.co.jp>
- * Version: 1.0
- * LastModified: Dec 25 1999
- * This library is free. You can redistribute it and/or modify it.
- */
-
-/*
- * Interfaces:
- * b64 = base64encode(data);
- * data = base64decode(b64);
- */
-
-(function() {
-
-var base64EncodeChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
-var base64DecodeChars = [
- -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
- 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
- -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
- 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
- -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
- 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1];
-
-function base64encode(str) {
- var out, i, len;
- var c1, c2, c3;
-
- len = str.length;
- i = 0;
- out = "";
- while(i < len) {
- c1 = str.charCodeAt(i++) & 0xff;
- if(i == len)
- {
- out += base64EncodeChars.charAt(c1 >> 2);
- out += base64EncodeChars.charAt((c1 & 0x3) << 4);
- out += "==";
- break;
- }
- c2 = str.charCodeAt(i++);
- if(i == len)
- {
- out += base64EncodeChars.charAt(c1 >> 2);
- out += base64EncodeChars.charAt(((c1 & 0x3)<< 4) | ((c2 & 0xF0) >> 4));
- out += base64EncodeChars.charAt((c2 & 0xF) << 2);
- out += "=";
- break;
- }
- c3 = str.charCodeAt(i++);
- out += base64EncodeChars.charAt(c1 >> 2);
- out += base64EncodeChars.charAt(((c1 & 0x3)<< 4) | ((c2 & 0xF0) >> 4));
- out += base64EncodeChars.charAt(((c2 & 0xF) << 2) | ((c3 & 0xC0) >>6));
- out += base64EncodeChars.charAt(c3 & 0x3F);
- }
- return out;
-}
-
-function base64decode(str) {
- var c1, c2, c3, c4;
- var i, len, out;
-
- len = str.length;
- i = 0;
- out = "";
- while(i < len) {
- /* c1 */
- do {
- c1 = base64DecodeChars[str.charCodeAt(i++) & 0xff];
- } while(i < len && c1 == -1);
- if(c1 == -1)
- break;
-
- /* c2 */
- do {
- c2 = base64DecodeChars[str.charCodeAt(i++) & 0xff];
- } while(i < len && c2 == -1);
- if(c2 == -1)
- break;
-
- out += String.fromCharCode((c1 << 2) | ((c2 & 0x30) >> 4));
-
- /* c3 */
- do {
- c3 = str.charCodeAt(i++) & 0xff;
- if(c3 == 61)
- return out;
- c3 = base64DecodeChars[c3];
- } while(i < len && c3 == -1);
- if(c3 == -1)
- break;
-
- out += String.fromCharCode(((c2 & 0XF) << 4) | ((c3 & 0x3C) >> 2));
-
- /* c4 */
- do {
- c4 = str.charCodeAt(i++) & 0xff;
- if(c4 == 61)
- return out;
- c4 = base64DecodeChars[c4];
- } while(i < len && c4 == -1);
- if(c4 == -1)
- break;
- out += String.fromCharCode(((c3 & 0x03) << 6) | c4);
- }
- return out;
-}
-
-if (!window.btoa) window.btoa = base64encode;
-if (!window.atob) window.atob = base64decode;
-
-})();
\ No newline at end of file diff --git a/addons/web_graph/static/lib/flotr2/lib/bean-min.js b/addons/web_graph/static/lib/flotr2/lib/bean-min.js deleted file mode 100644 index 2471420fe25..00000000000 --- a/addons/web_graph/static/lib/flotr2/lib/bean-min.js +++ /dev/null @@ -1,10 +0,0 @@ -/*! - * bean.js - copyright Jacob Thornton 2011 - * https://github.com/fat/bean - * MIT License - * special thanks to: - * dean edwards: http://dean.edwards.name/ - * dperini: https://github.com/dperini/nwevents - * the entire mootools team: github.com/mootools/mootools-core - *//*global module:true, define:true*/ -!function(a,b,c){typeof module!="undefined"?module.exports=c(a,b):typeof define=="function"&&typeof define.amd=="object"?define(c):b[a]=c(a,b)}("bean",this,function(a,b){var c=window,d=b[a],e=/over|out/,f=/[^\.]*(?=\..*)\.|.*/,g=/\..*/,h="addEventListener",i="attachEvent",j="removeEventListener",k="detachEvent",l=document||{},m=l.documentElement||{},n=m[h],o=n?h:i,p=Array.prototype.slice,q=/click|mouse|menu|drag|drop/i,r=/^touch|^gesture/i,s={one:1},t=function(a,b,c){for(c=0;c<b.length;c++)a[b[c]]=1;return a}({},("click dblclick mouseup mousedown contextmenu mousewheel DOMMouseScroll mouseover mouseout mousemove selectstart selectend keydown keypress keyup orientationchange focus blur change reset select submit load unload beforeunload resize move DOMContentLoaded readystatechange error abort scroll "+(n?"show input invalid touchstart touchmove touchend touchcancel gesturestart gesturechange gestureend message readystatechange pageshow pagehide popstate hashchange offline online afterprint beforeprint dragstart dragenter dragover dragleave drag drop dragend loadstart progress suspend emptied stalled loadmetadata loadeddata canplay canplaythrough playing waiting seeking seeked ended durationchange timeupdate play pause ratechange volumechange cuechange checking noupdate downloading cached updateready obsolete ":"")).split(" ")),u=function(){function a(a,b){while((b=b.parentNode)!==null)if(b===a)return!0;return!1}function b(b){var c=b.relatedTarget;return c?c!==this&&c.prefix!=="xul"&&!/document/.test(this.toString())&&!a(this,c):c===null}return{mouseenter:{base:"mouseover",condition:b},mouseleave:{base:"mouseout",condition:b},mousewheel:{base:/Firefox/.test(navigator.userAgent)?"DOMMouseScroll":"mousewheel"}}}(),v=function(){var a="altKey attrChange attrName bubbles cancelable ctrlKey currentTarget detail eventPhase getModifierState isTrusted metaKey relatedNode relatedTarget shiftKey srcElement target timeStamp type view which".split(" "),b=a.concat("button buttons clientX clientY dataTransfer fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" ")),c=a.concat("char charCode key keyCode".split(" ")),d=a.concat("touches targetTouches changedTouches scale rotation".split(" ")),f="preventDefault",g=function(a){return function(){a[f]?a[f]():a.returnValue=!1}},h="stopPropagation",i=function(a){return function(){a[h]?a[h]():a.cancelBubble=!0}},j=function(a){return function(){a[f](),a[h](),a.stopped=!0}},k=function(a,b,c){var d,e;for(d=c.length;d--;)e=c[d],!(e in b)&&e in a&&(b[e]=a[e])};return function(n,o){var p={originalEvent:n,isNative:o};if(!n)return p;var s,t=n.type,u=n.target||n.srcElement;p[f]=g(n),p[h]=i(n),p.stop=j(p),p.target=u&&u.nodeType===3?u.parentNode:u;if(o){if(t.indexOf("key")!==-1)s=c,p.keyCode=n.which||n.keyCode;else if(q.test(t)){s=b,p.rightClick=n.which===3||n.button===2,p.pos={x:0,y:0};if(n.pageX||n.pageY)p.clientX=n.pageX,p.clientY=n.pageY;else if(n.clientX||n.clientY)p.clientX=n.clientX+l.body.scrollLeft+m.scrollLeft,p.clientY=n.clientY+l.body.scrollTop+m.scrollTop;e.test(t)&&(p.relatedTarget=n.relatedTarget||n[(t==="mouseover"?"from":"to")+"Element"])}else r.test(t)&&(s=d);k(n,p,s||a)}return p}}(),w=function(a,b){return!n&&!b&&(a===l||a===c)?m:a},x=function(){function a(a,b,c,d,e){this.element=a,this.type=b,this.handler=c,this.original=d,this.namespaces=e,this.custom=u[b],this.isNative=t[b]&&a[o],this.eventType=n||this.isNative?b:"propertychange",this.customType=!n&&!this.isNative&&b,this.target=w(a,this.isNative),this.eventSupport=this.target[o]}return a.prototype={inNamespaces:function(a){var b,c;if(!a)return!0;if(!this.namespaces)return!1;for(b=a.length;b--;)for(c=this.namespaces.length;c--;)if(a[b]===this.namespaces[c])return!0;return!1},matches:function(a,b,c){return this.element===a&&(!b||this.original===b)&&(!c||this.handler===c)}},a}(),y=function(){var a={},b=function(c,d,e,f,g){if(!d||d==="*")for(var h in a)h.charAt(0)==="$"&&b(c,h.substr(1),e,f,g);else{var i=0,j,k=a["$"+d],l=c==="*";if(!k)return;for(j=k.length;i<j;i++)if(l||k[i].matches(c,e,f))if(!g(k[i],k,i,d))return}},c=function(b,c,d){var e,f=a["$"+c];if(f)for(e=f.length;e--;)if(f[e].matches(b,d,null))return!0;return!1},d=function(a,c,d){var e=[];return b(a,c,d,null,function(a){return e.push(a)}),e},e=function(b){return(a["$"+b.type]||(a["$"+b.type]=[])).push(b),b},f=function(c){b(c.element,c.type,null,c.handler,function(b,c,d){return c.splice(d,1),c.length===0&&delete a["$"+b.type],!1})},g=function(){var b,c=[];for(b in a)b.charAt(0)==="$"&&(c=c.concat(a[b]));return c};return{has:c,get:d,put:e,del:f,entries:g}}(),z=n?function(a,b,c,d){a[d?h:j](b,c,!1)}:function(a,b,c,d,e){e&&d&&a["_on"+e]===null&&(a["_on"+e]=0),a[d?i:k]("on"+b,c)},A=function(a,b,d){return function(e){return e=v(e||((this.ownerDocument||this.document||this).parentWindow||c).event,!0),b.apply(a,[e].concat(d))}},B=function(a,b,d,e,f,g){return function(h){if(e?e.apply(this,arguments):n?!0:h&&h.propertyName==="_on"+d||!h)h&&(h=v(h||((this.ownerDocument||this.document||this).parentWindow||c).event,g)),b.apply(a,h&&(!f||f.length===0)?arguments:p.call(arguments,h?0:1).concat(f))}},C=function(a,b,c,d,e){return function(){a(b,c,e),d.apply(this,arguments)}},D=function(a,b,c,d){var e,f,h,i=b&&b.replace(g,""),j=y.get(a,i,c);for(e=0,f=j.length;e<f;e++)j[e].inNamespaces(d)&&((h=j[e]).eventSupport&&z(h.target,h.eventType,h.handler,!1,h.type),y.del(h))},E=function(a,b,c,d,e){var h,i=b.replace(g,""),j=b.replace(f,"").split(".");if(y.has(a,i,c))return a;i==="unload"&&(c=C(D,a,i,c,d)),u[i]&&(u[i].condition&&(c=B(a,c,i,u[i].condition,!0)),i=u[i].base||i),h=y.put(new x(a,i,c,d,j[0]&&j)),h.handler=h.isNative?A(a,h.handler,e):B(a,h.handler,i,!1,e,!1),h.eventSupport&&z(h.target,h.eventType,h.handler,!0,h.customType)},F=function(a,b,c){return function(d){var e,f,g=typeof a=="string"?c(a,this):a;for(e=d.target;e&&e!==this;e=e.parentNode)for(f=g.length;f--;)if(g[f]===e)return b.apply(e,arguments)}},G=function(a,b,c){var d,e,h,i,j,k=D,l=b&&typeof b=="string";if(l&&b.indexOf(" ")>0){b=b.split(" ");for(j=b.length;j--;)G(a,b[j],c);return a}h=l&&b.replace(g,""),h&&u[h]&&(h=u[h].type);if(!b||l){if(i=l&&b.replace(f,""))i=i.split(".");k(a,h,c,i)}else if(typeof b=="function")k(a,null,b);else for(d in b)b.hasOwnProperty(d)&&G(a,d,b[d]);return a},H=function(a,b,c,d,e){var f,g,h,i,j=c,k=c&&typeof c=="string";if(b&&!c&&typeof b=="object")for(f in b)b.hasOwnProperty(f)&&H.apply(this,[a,f,b[f]]);else{i=arguments.length>3?p.call(arguments,3):[],g=(k?c:b).split(" "),k&&(c=F(b,j=d,e))&&(i=p.call(i,1)),this===s&&(c=C(G,a,b,c,j));for(h=g.length;h--;)E(a,g[h],c,j,i)}return a},I=function(){return H.apply(s,arguments)},J=n?function(a,b,d){var e=l.createEvent(a?"HTMLEvents":"UIEvents");e[a?"initEvent":"initUIEvent"](b,!0,!0,c,1),d.dispatchEvent(e)}:function(a,b,c){c=w(c,a),a?c.fireEvent("on"+b,l.createEventObject()):c["_on"+b]++},K=function(a,b,c){var d,e,h,i,j,k=b.split(" ");for(d=k.length;d--;){b=k[d].replace(g,"");if(i=k[d].replace(f,""))i=i.split(".");if(!i&&!c&&a[o])J(t[b],b,a);else{j=y.get(a,b),c=[!1].concat(c);for(e=0,h=j.length;e<h;e++)j[e].inNamespaces(i)&&j[e].handler.apply(a,c)}}return a},L=function(a,b,c){var d=0,e=y.get(b,c),f=e.length;for(;d<f;d++)e[d].original&&H(a,e[d].type,e[d].original);return a},M={add:H,one:I,remove:G,clone:L,fire:K,noConflict:function(){return b[a]=d,this}};if(c[i]){var N=function(){var a,b=y.entries();for(a in b)b[a].type&&b[a].type!=="unload"&&G(b[a].element,b[a].type);c[k]("onunload",N),c.CollectGarbage&&c.CollectGarbage()};c[i]("onunload",N)}return M})
\ No newline at end of file diff --git a/addons/web_graph/static/lib/flotr2/lib/bean.js b/addons/web_graph/static/lib/flotr2/lib/bean.js deleted file mode 100644 index 1a854771006..00000000000 --- a/addons/web_graph/static/lib/flotr2/lib/bean.js +++ /dev/null @@ -1,501 +0,0 @@ -/*! - * bean.js - copyright Jacob Thornton 2011 - * https://github.com/fat/bean - * MIT License - * special thanks to: - * dean edwards: http://dean.edwards.name/ - * dperini: https://github.com/dperini/nwevents - * the entire mootools team: github.com/mootools/mootools-core - */ -/*global module:true, define:true*/ -!function (name, context, definition) { - context[name] = definition(name, context); -}('bean', this, function (name, context) { - var win = window - , old = context[name] - , overOut = /over|out/ - , namespaceRegex = /[^\.]*(?=\..*)\.|.*/ - , nameRegex = /\..*/ - , addEvent = 'addEventListener' - , attachEvent = 'attachEvent' - , removeEvent = 'removeEventListener' - , detachEvent = 'detachEvent' - , doc = document || {} - , root = doc.documentElement || {} - , W3C_MODEL = root[addEvent] - , eventSupport = W3C_MODEL ? addEvent : attachEvent - , slice = Array.prototype.slice - , mouseTypeRegex = /click|mouse|menu|drag|drop/i - , touchTypeRegex = /^touch|^gesture/i - , ONE = { one: 1 } // singleton for quick matching making add() do one() - - , nativeEvents = (function (hash, events, i) { - for (i = 0; i < events.length; i++) - hash[events[i]] = 1 - return hash - })({}, ( - 'click dblclick mouseup mousedown contextmenu ' + // mouse buttons - 'mousewheel DOMMouseScroll ' + // mouse wheel - 'mouseover mouseout mousemove selectstart selectend ' + // mouse movement - 'keydown keypress keyup ' + // keyboard - 'orientationchange ' + // mobile - 'focus blur change reset select submit ' + // form elements - 'load unload beforeunload resize move DOMContentLoaded readystatechange ' + // window - 'error abort scroll ' + // misc - (W3C_MODEL ? // element.fireEvent('onXYZ'... is not forgiving if we try to fire an event - // that doesn't actually exist, so make sure we only do these on newer browsers - 'show ' + // mouse buttons - 'input invalid ' + // form elements - 'touchstart touchmove touchend touchcancel ' + // touch - 'gesturestart gesturechange gestureend ' + // gesture - 'message readystatechange pageshow pagehide popstate ' + // window - 'hashchange offline online ' + // window - 'afterprint beforeprint ' + // printing - 'dragstart dragenter dragover dragleave drag drop dragend ' + // dnd - 'loadstart progress suspend emptied stalled loadmetadata ' + // media - 'loadeddata canplay canplaythrough playing waiting seeking ' + // media - 'seeked ended durationchange timeupdate play pause ratechange ' + // media - 'volumechange cuechange ' + // media - 'checking noupdate downloading cached updateready obsolete ' + // appcache - '' : '') - ).split(' ') - ) - - , customEvents = (function () { - function isDescendant(parent, node) { - while ((node = node.parentNode) !== null) { - if (node === parent) return true - } - return false - } - - function check(event) { - var related = event.relatedTarget - if (!related) return related === null - return (related !== this && related.prefix !== 'xul' && !/document/.test(this.toString()) && !isDescendant(this, related)) - } - - return { - mouseenter: { base: 'mouseover', condition: check } - , mouseleave: { base: 'mouseout', condition: check } - , mousewheel: { base: /Firefox/.test(navigator.userAgent) ? 'DOMMouseScroll' : 'mousewheel' } - } - })() - - , fixEvent = (function () { - var commonProps = 'altKey attrChange attrName bubbles cancelable ctrlKey currentTarget detail eventPhase getModifierState isTrusted metaKey relatedNode relatedTarget shiftKey srcElement target timeStamp type view which'.split(' ') - , mouseProps = commonProps.concat('button buttons clientX clientY dataTransfer fromElement offsetX offsetY pageX pageY screenX screenY toElement'.split(' ')) - , keyProps = commonProps.concat('char charCode key keyCode'.split(' ')) - , touchProps = commonProps.concat('touches targetTouches changedTouches scale rotation'.split(' ')) - , preventDefault = 'preventDefault' - , createPreventDefault = function (event) { - return function () { - if (event[preventDefault]) - event[preventDefault]() - else - event.returnValue = false - } - } - , stopPropagation = 'stopPropagation' - , createStopPropagation = function (event) { - return function () { - if (event[stopPropagation]) - event[stopPropagation]() - else - event.cancelBubble = true - } - } - , createStop = function (synEvent) { - return function () { - synEvent[preventDefault]() - synEvent[stopPropagation]() - synEvent.stopped = true - } - } - , copyProps = function (event, result, props) { - var i, p - for (i = props.length; i--;) { - p = props[i] - if (!(p in result) && p in event) result[p] = event[p] - } - } - - return function (event, isNative) { - var result = { originalEvent: event, isNative: isNative } - if (!event) - return result - - var props - , type = event.type - , target = event.target || event.srcElement - - result[preventDefault] = createPreventDefault(event) - result[stopPropagation] = createStopPropagation(event) - result.stop = createStop(result) - result.target = target && target.nodeType === 3 ? target.parentNode : target - - if (isNative) { // we only need basic augmentation on custom events, the rest is too expensive - if (type.indexOf('key') !== -1) { - props = keyProps - result.keyCode = event.which || event.keyCode - } else if (mouseTypeRegex.test(type)) { - props = mouseProps - result.rightClick = event.which === 3 || event.button === 2 - result.pos = { x: 0, y: 0 } - if (event.pageX || event.pageY) { - result.clientX = event.pageX - result.clientY = event.pageY - } else if (event.clientX || event.clientY) { - result.clientX = event.clientX + doc.body.scrollLeft + root.scrollLeft - result.clientY = event.clientY + doc.body.scrollTop + root.scrollTop - } - if (overOut.test(type)) - result.relatedTarget = event.relatedTarget || event[(type === 'mouseover' ? 'from' : 'to') + 'Element'] - } else if (touchTypeRegex.test(type)) { - props = touchProps - } - copyProps(event, result, props || commonProps) - } - return result - } - })() - - // if we're in old IE we can't do onpropertychange on doc or win so we use doc.documentElement for both - , targetElement = function (element, isNative) { - return !W3C_MODEL && !isNative && (element === doc || element === win) ? root : element - } - - // we use one of these per listener, of any type - , RegEntry = (function () { - function entry(element, type, handler, original, namespaces) { - this.element = element - this.type = type - this.handler = handler - this.original = original - this.namespaces = namespaces - this.custom = customEvents[type] - this.isNative = nativeEvents[type] && element[eventSupport] - this.eventType = W3C_MODEL || this.isNative ? type : 'propertychange' - this.customType = !W3C_MODEL && !this.isNative && type - this.target = targetElement(element, this.isNative) - this.eventSupport = this.target[eventSupport] - } - - entry.prototype = { - // given a list of namespaces, is our entry in any of them? - inNamespaces: function (checkNamespaces) { - var i, j - if (!checkNamespaces) - return true - if (!this.namespaces) - return false - for (i = checkNamespaces.length; i--;) { - for (j = this.namespaces.length; j--;) { - if (checkNamespaces[i] === this.namespaces[j]) - return true - } - } - return false - } - - // match by element, original fn (opt), handler fn (opt) - , matches: function (checkElement, checkOriginal, checkHandler) { - return this.element === checkElement && - (!checkOriginal || this.original === checkOriginal) && - (!checkHandler || this.handler === checkHandler) - } - } - - return entry - })() - - , registry = (function () { - // our map stores arrays by event type, just because it's better than storing - // everything in a single array. uses '$' as a prefix for the keys for safety - var map = {} - - // generic functional search of our registry for matching listeners, - // `fn` returns false to break out of the loop - , forAll = function (element, type, original, handler, fn) { - if (!type || type === '*') { - // search the whole registry - for (var t in map) { - if (t.charAt(0) === '$') - forAll(element, t.substr(1), original, handler, fn) - } - } else { - var i = 0, l, list = map['$' + type], all = element === '*' - if (!list) - return - for (l = list.length; i < l; i++) { - if (all || list[i].matches(element, original, handler)) - if (!fn(list[i], list, i, type)) - return - } - } - } - - , has = function (element, type, original) { - // we're not using forAll here simply because it's a bit slower and this - // needs to be fast - var i, list = map['$' + type] - if (list) { - for (i = list.length; i--;) { - if (list[i].matches(element, original, null)) - return true - } - } - return false - } - - , get = function (element, type, original) { - var entries = [] - forAll(element, type, original, null, function (entry) { return entries.push(entry) }) - return entries - } - - , put = function (entry) { - (map['$' + entry.type] || (map['$' + entry.type] = [])).push(entry) - return entry - } - - , del = function (entry) { - forAll(entry.element, entry.type, null, entry.handler, function (entry, list, i) { - list.splice(i, 1) - if (list.length === 0) - delete map['$' + entry.type] - return false - }) - } - - // dump all entries, used for onunload - , entries = function () { - var t, entries = [] - for (t in map) { - if (t.charAt(0) === '$') - entries = entries.concat(map[t]) - } - return entries - } - - return { has: has, get: get, put: put, del: del, entries: entries } - })() - - // add and remove listeners to DOM elements - , listener = W3C_MODEL ? function (element, type, fn, add) { - element[add ? addEvent : removeEvent](type, fn, false) - } : function (element, type, fn, add, custom) { - if (custom && add && element['_on' + custom] === null) - element['_on' + custom] = 0 - element[add ? attachEvent : detachEvent]('on' + type, fn) - } - - , nativeHandler = function (element, fn, args) { - return function (event) { - event = fixEvent(event || ((this.ownerDocument || this.document || this).parentWindow || win).event, true) - return fn.apply(element, [event].concat(args)) - } - } - - , customHandler = function (element, fn, type, condition, args, isNative) { - return function (event) { - if (condition ? condition.apply(this, arguments) : W3C_MODEL ? true : event && event.propertyName === '_on' + type || !event) { - if (event) - event = fixEvent(event || ((this.ownerDocument || this.document || this).parentWindow || win).event, isNative) - fn.apply(element, event && (!args || args.length === 0) ? arguments : slice.call(arguments, event ? 0 : 1).concat(args)) - } - } - } - - , once = function (rm, element, type, fn, originalFn) { - // wrap the handler in a handler that does a remove as well - return function () { - rm(element, type, originalFn) - fn.apply(this, arguments) - } - } - - , removeListener = function (element, orgType, handler, namespaces) { - var i, l, entry - , type = (orgType && orgType.replace(nameRegex, '')) - , handlers = registry.get(element, type, handler) - - for (i = 0, l = handlers.length; i < l; i++) { - if (handlers[i].inNamespaces(namespaces)) { - if ((entry = handlers[i]).eventSupport) - listener(entry.target, entry.eventType, entry.handler, false, entry.type) - // TODO: this is problematic, we have a registry.get() and registry.del() that - // both do registry searches so we waste cycles doing this. Needs to be rolled into - // a single registry.forAll(fn) that removes while finding, but the catch is that - // we'll be splicing the arrays that we're iterating over. Needs extra tests to - // make sure we don't screw it up. @rvagg - registry.del(entry) - } - } - } - - , addListener = function (element, orgType, fn, originalFn, args) { - var entry - , type = orgType.replace(nameRegex, '') - , namespaces = orgType.replace(namespaceRegex, '').split('.') - - if (registry.has(element, type, fn)) - return element // no dupe - if (type === 'unload') - fn = once(removeListener, element, type, fn, originalFn) // self clean-up - if (customEvents[type]) { - if (customEvents[type].condition) - fn = customHandler(element, fn, type, customEvents[type].condition, true) - type = customEvents[type].base || type - } - entry = registry.put(new RegEntry(element, type, fn, originalFn, namespaces[0] && namespaces)) - entry.handler = entry.isNative ? - nativeHandler(element, entry.handler, args) : - customHandler(element, entry.handler, type, false, args, false) - if (entry.eventSupport) - listener(entry.target, entry.eventType, entry.handler, true, entry.customType) - } - - , del = function (selector, fn, $) { - return function (e) { - var target, i, array = typeof selector === 'string' ? $(selector, this) : selector - for (target = e.target; target && target !== this; target = target.parentNode) { - for (i = array.length; i--;) { - if (array[i] === target) { - return fn.apply(target, arguments) - } - } - } - } - } - - , remove = function (element, typeSpec, fn) { - var k, m, type, namespaces, i - , rm = removeListener - , isString = typeSpec && typeof typeSpec === 'string' - - if (isString && typeSpec.indexOf(' ') > 0) { - // remove(el, 't1 t2 t3', fn) or remove(el, 't1 t2 t3') - typeSpec = typeSpec.split(' ') - for (i = typeSpec.length; i--;) - remove(element, typeSpec[i], fn) - return element - } - type = isString && typeSpec.replace(nameRegex, '') - if (type && customEvents[type]) - type = customEvents[type].type - if (!typeSpec || isString) { - // remove(el) or remove(el, t1.ns) or remove(el, .ns) or remove(el, .ns1.ns2.ns3) - if (namespaces = isString && typeSpec.replace(namespaceRegex, '')) - namespaces = namespaces.split('.') - rm(element, type, fn, namespaces) - } else if (typeof typeSpec === 'function') { - // remove(el, fn) - rm(element, null, typeSpec) - } else { - // remove(el, { t1: fn1, t2, fn2 }) - for (k in typeSpec) { - if (typeSpec.hasOwnProperty(k)) - remove(element, k, typeSpec[k]) - } - } - return element - } - - , add = function (element, events, fn, delfn, $) { - var type, types, i, args - , originalFn = fn - , isDel = fn && typeof fn === 'string' - - if (events && !fn && typeof events === 'object') { - for (type in events) { - if (events.hasOwnProperty(type)) - add.apply(this, [ element, type, events[type] ]) - } - } else { - args = arguments.length > 3 ? slice.call(arguments, 3) : [] - types = (isDel ? fn : events).split(' ') - isDel && (fn = del(events, (originalFn = delfn), $)) && (args = slice.call(args, 1)) - // special case for one() - this === ONE && (fn = once(remove, element, events, fn, originalFn)) - for (i = types.length; i--;) addListener(element, types[i], fn, originalFn, args) - } - return element - } - - , one = function () { - return add.apply(ONE, arguments) - } - - , fireListener = W3C_MODEL ? function (isNative, type, element) { - var evt = doc.createEvent(isNative ? 'HTMLEvents' : 'UIEvents') - evt[isNative ? 'initEvent' : 'initUIEvent'](type, true, true, win, 1) - element.dispatchEvent(evt) - } : function (isNative, type, element) { - element = targetElement(element, isNative) - // if not-native then we're using onpropertychange so we just increment a custom property - isNative ? element.fireEvent('on' + type, doc.createEventObject()) : element['_on' + type]++ - } - - , fire = function (element, type, args) { - var i, j, l, names, handlers - , types = type.split(' ') - - for (i = types.length; i--;) { - type = types[i].replace(nameRegex, '') - if (names = types[i].replace(namespaceRegex, '')) - names = names.split('.') - if (!names && !args && element[eventSupport]) { - fireListener(nativeEvents[type], type, element) - } else { - // non-native event, either because of a namespace, arguments or a non DOM element - // iterate over all listeners and manually 'fire' - handlers = registry.get(element, type) - args = [false].concat(args) - for (j = 0, l = handlers.length; j < l; j++) { - if (handlers[j].inNamespaces(names)) - handlers[j].handler.apply(element, args) - } - } - } - return element - } - - , clone = function (element, from, type) { - var i = 0 - , handlers = registry.get(from, type) - , l = handlers.length - - for (;i < l; i++) - handlers[i].original && add(element, handlers[i].type, handlers[i].original) - return element - } - - , bean = { - add: add - , one: one - , remove: remove - , clone: clone - , fire: fire - , noConflict: function () { - context[name] = old - return this - } - } - - if (win[attachEvent]) { - // for IE, clean up on unload to avoid leaks - var cleanup = function () { - var i, entries = registry.entries() - for (i in entries) { - if (entries[i].type && entries[i].type !== 'unload') - remove(entries[i].element, entries[i].type) - } - win[detachEvent]('onunload', cleanup) - win.CollectGarbage && win.CollectGarbage() - } - win[attachEvent]('onunload', cleanup) - } - - return bean -}); diff --git a/addons/web_graph/static/lib/flotr2/lib/canvas2image.js b/addons/web_graph/static/lib/flotr2/lib/canvas2image.js deleted file mode 100644 index e064cdac8b9..00000000000 --- a/addons/web_graph/static/lib/flotr2/lib/canvas2image.js +++ /dev/null @@ -1,198 +0,0 @@ -/* - * Canvas2Image v0.1 - * Copyright (c) 2008 Jacob Seidelin, cupboy@gmail.com - * MIT License [http://www.opensource.org/licenses/mit-license.php] - */ - -var Canvas2Image = (function() { - // check if we have canvas support - var oCanvas = document.createElement("canvas"), - sc = String.fromCharCode, - strDownloadMime = "image/octet-stream", - bReplaceDownloadMime = false; - - // no canvas, bail out. - if (!oCanvas.getContext) { - return { - saveAsBMP : function(){}, - saveAsPNG : function(){}, - saveAsJPEG : function(){} - } - } - - var bHasImageData = !!(oCanvas.getContext("2d").getImageData), - bHasDataURL = !!(oCanvas.toDataURL), - bHasBase64 = !!(window.btoa); - - // ok, we're good - var readCanvasData = function(oCanvas) { - var iWidth = parseInt(oCanvas.width), - iHeight = parseInt(oCanvas.height); - return oCanvas.getContext("2d").getImageData(0,0,iWidth,iHeight); - } - - // base64 encodes either a string or an array of charcodes - var encodeData = function(data) { - var i, aData, strData = ""; - - if (typeof data == "string") { - strData = data; - } else { - aData = data; - for (i = 0; i < aData.length; i++) { - strData += sc(aData[i]); - } - } - return btoa(strData); - } - - // creates a base64 encoded string containing BMP data takes an imagedata object as argument - var createBMP = function(oData) { - var strHeader = '', - iWidth = oData.width, - iHeight = oData.height; - - strHeader += 'BM'; - - var iFileSize = iWidth*iHeight*4 + 54; // total header size = 54 bytes - strHeader += sc(iFileSize % 256); iFileSize = Math.floor(iFileSize / 256); - strHeader += sc(iFileSize % 256); iFileSize = Math.floor(iFileSize / 256); - strHeader += sc(iFileSize % 256); iFileSize = Math.floor(iFileSize / 256); - strHeader += sc(iFileSize % 256); - - strHeader += sc(0, 0, 0, 0, 54, 0, 0, 0); // data offset - strHeader += sc(40, 0, 0, 0); // info header size - - var iImageWidth = iWidth; - strHeader += sc(iImageWidth % 256); iImageWidth = Math.floor(iImageWidth / 256); - strHeader += sc(iImageWidth % 256); iImageWidth = Math.floor(iImageWidth / 256); - strHeader += sc(iImageWidth % 256); iImageWidth = Math.floor(iImageWidth / 256); - strHeader += sc(iImageWidth % 256); - - var iImageHeight = iHeight; - strHeader += sc(iImageHeight % 256); iImageHeight = Math.floor(iImageHeight / 256); - strHeader += sc(iImageHeight % 256); iImageHeight = Math.floor(iImageHeight / 256); - strHeader += sc(iImageHeight % 256); iImageHeight = Math.floor(iImageHeight / 256); - strHeader += sc(iImageHeight % 256); - - strHeader += sc(1, 0, 32, 0); // num of planes & num of bits per pixel - strHeader += sc(0, 0, 0, 0); // compression = none - - var iDataSize = iWidth*iHeight*4; - strHeader += sc(iDataSize % 256); iDataSize = Math.floor(iDataSize / 256); - strHeader += sc(iDataSize % 256); iDataSize = Math.floor(iDataSize / 256); - strHeader += sc(iDataSize % 256); iDataSize = Math.floor(iDataSize / 256); - strHeader += sc(iDataSize % 256); - - strHeader += sc(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); // these bytes are not used - - var aImgData = oData.data, - strPixelData = "", - c, x, y = iHeight, - iOffsetX, iOffsetY, strPixelRow; - - do { - iOffsetY = iWidth*(y-1)*4; - strPixelRow = ""; - for (x = 0; x < iWidth; x++) { - iOffsetX = 4*x; - strPixelRow += sc( - aImgData[iOffsetY + iOffsetX + 2], // B - aImgData[iOffsetY + iOffsetX + 1], // G - aImgData[iOffsetY + iOffsetX], // R - aImgData[iOffsetY + iOffsetX + 3] // A - ); - } - strPixelData += strPixelRow; - } while (--y); - - return encodeData(strHeader + strPixelData); - } - - // sends the generated file to the client - var saveFile = function(strData) { - if (!window.open(strData)) { - document.location.href = strData; - } - } - - var makeDataURI = function(strData, strMime) { - return "data:" + strMime + ";base64," + strData; - } - - // generates a <img> object containing the imagedata - var makeImageObject = function(strSource) { - var oImgElement = document.createElement("img"); - oImgElement.src = strSource; - return oImgElement; - } - - var scaleCanvas = function(oCanvas, iWidth, iHeight) { - if (iWidth && iHeight) { - var oSaveCanvas = document.createElement("canvas"); - - oSaveCanvas.width = iWidth; - oSaveCanvas.height = iHeight; - oSaveCanvas.style.width = iWidth+"px"; - oSaveCanvas.style.height = iHeight+"px"; - - var oSaveCtx = oSaveCanvas.getContext("2d"); - - oSaveCtx.drawImage(oCanvas, 0, 0, oCanvas.width, oCanvas.height, 0, 0, iWidth, iWidth); - - return oSaveCanvas; - } - return oCanvas; - } - - return { - saveAsPNG : function(oCanvas, bReturnImg, iWidth, iHeight) { - if (!bHasDataURL) return false; - - var oScaledCanvas = scaleCanvas(oCanvas, iWidth, iHeight), - strMime = "image/png", - strData = oScaledCanvas.toDataURL(strMime); - - if (bReturnImg) { - return makeImageObject(strData); - } else { - saveFile(bReplaceDownloadMime ? strData.replace(strMime, strDownloadMime) : strData); - } - return true; - }, - - saveAsJPEG : function(oCanvas, bReturnImg, iWidth, iHeight) { - if (!bHasDataURL) return false; - - var oScaledCanvas = scaleCanvas(oCanvas, iWidth, iHeight), - strMime = "image/jpeg", - strData = oScaledCanvas.toDataURL(strMime); - - // check if browser actually supports jpeg by looking for the mime type in the data uri. if not, return false - if (strData.indexOf(strMime) != 5) return false; - - if (bReturnImg) { - return makeImageObject(strData); - } else { - saveFile(bReplaceDownloadMime ? strData.replace(strMime, strDownloadMime) : strData); - } - return true; - }, - - saveAsBMP : function(oCanvas, bReturnImg, iWidth, iHeight) { - if (!(bHasDataURL && bHasImageData && bHasBase64)) return false; - - var oScaledCanvas = scaleCanvas(oCanvas, iWidth, iHeight), - strMime = "image/bmp", - oData = readCanvasData(oScaledCanvas), - strImgData = createBMP(oData); - - if (bReturnImg) { - return makeImageObject(makeDataURI(strImgData, strMime)); - } else { - saveFile(makeDataURI(strImgData, strMime)); - } - return true; - } - }; -})();
\ No newline at end of file diff --git a/addons/web_graph/static/lib/flotr2/lib/canvastext.js b/addons/web_graph/static/lib/flotr2/lib/canvastext.js deleted file mode 100644 index 2df45bae0d8..00000000000 --- a/addons/web_graph/static/lib/flotr2/lib/canvastext.js +++ /dev/null @@ -1,429 +0,0 @@ -/** - * This code is released to the public domain by Jim Studt, 2007. - * He may keep some sort of up to date copy at http://www.federated.com/~jim/canvastext/ - * It as been modified by Fabien Ménager to handle font style like size, weight, color and rotation. - * A partial support for special characters has been added too. - */ -var CanvasText = { - /** The letters definition. It is a list of letters, - * with their width, and the coordinates of points compositing them. - * The syntax for the points is : [x, y], null value means "pen up" - */ - letters: { - '\n':{ width: -1, points: [] }, - ' ': { width: 10, points: [] }, - '!': { width: 10, points: [[5,21],[5,7],null,[5,2],[4,1],[5,0],[6,1],[5,2]] }, - '"': { width: 16, points: [[4,21],[4,14],null,[12,21],[12,14]] }, - '#': { width: 21, points: [[11,25],[4,-7],null,[17,25],[10,-7],null,[4,12],[18,12],null,[3,6],[17,6]] }, - '$': { width: 20, points: [[8,25],[8,-4],null,[12,25],[12,-4],null,[17,18],[15,20],[12,21],[8,21],[5,20],[3,18],[3,16],[4,14],[5,13],[7,12],[13,10],[15,9],[16,8],[17,6],[17,3],[15,1],[12,0],[8,0],[5,1],[3,3]] }, - '%': { width: 24, points: [[21,21],[3,0],null,[8,21],[10,19],[10,17],[9,15],[7,14],[5,14],[3,16],[3,18],[4,20],[6,21],[8,21],null,[17,7],[15,6],[14,4],[14,2],[16,0],[18,0],[20,1],[21,3],[21,5],[19,7],[17,7]] }, - '&': { width: 26, points: [[23,12],[23,13],[22,14],[21,14],[20,13],[19,11],[17,6],[15,3],[13,1],[11,0],[7,0],[5,1],[4,2],[3,4],[3,6],[4,8],[5,9],[12,13],[13,14],[14,16],[14,18],[13,20],[11,21],[9,20],[8,18],[8,16],[9,13],[11,10],[16,3],[18,1],[20,0],[22,0],[23,1],[23,2]] }, - '\'':{ width: 10, points: [[5,19],[4,20],[5,21],[6,20],[6,18],[5,16],[4,15]] }, - '(': { width: 14, points: [[11,25],[9,23],[7,20],[5,16],[4,11],[4,7],[5,2],[7,-2],[9,-5],[11,-7]] }, - ')': { width: 14, points: [[3,25],[5,23],[7,20],[9,16],[10,11],[10,7],[9,2],[7,-2],[5,-5],[3,-7]] }, - '*': { width: 16, points: [[8,21],[8,9],null,[3,18],[13,12],null,[13,18],[3,12]] }, - '+': { width: 26, points: [[13,18],[13,0],null,[4,9],[22,9]] }, - ',': { width: 10, points: [[6,1],[5,0],[4,1],[5,2],[6,1],[6,-1],[5,-3],[4,-4]] }, - '-': { width: 26, points: [[4,9],[22,9]] }, - '.': { width: 10, points: [[5,2],[4,1],[5,0],[6,1],[5,2]] }, - '/': { width: 22, points: [[20,25],[2,-7]] }, - '0': { width: 20, points: [[9,21],[6,20],[4,17],[3,12],[3,9],[4,4],[6,1],[9,0],[11,0],[14,1],[16,4],[17,9],[17,12],[16,17],[14,20],[11,21],[9,21]] }, - '1': { width: 20, points: [[6,17],[8,18],[11,21],[11,0]] }, - '2': { width: 20, points: [[4,16],[4,17],[5,19],[6,20],[8,21],[12,21],[14,20],[15,19],[16,17],[16,15],[15,13],[13,10],[3,0],[17,0]] }, - '3': { width: 20, points: [[5,21],[16,21],[10,13],[13,13],[15,12],[16,11],[17,8],[17,6],[16,3],[14,1],[11,0],[8,0],[5,1],[4,2],[3,4]] }, - '4': { width: 20, points: [[13,21],[3,7],[18,7],null,[13,21],[13,0]] }, - '5': { width: 20, points: [[15,21],[5,21],[4,12],[5,13],[8,14],[11,14],[14,13],[16,11],[17,8],[17,6],[16,3],[14,1],[11,0],[8,0],[5,1],[4,2],[3,4]] }, - '6': { width: 20, points: [[16,18],[15,20],[12,21],[10,21],[7,20],[5,17],[4,12],[4,7],[5,3],[7,1],[10,0],[11,0],[14,1],[16,3],[17,6],[17,7],[16,10],[14,12],[11,13],[10,13],[7,12],[5,10],[4,7]] }, - '7': { width: 20, points: [[17,21],[7,0],null,[3,21],[17,21]] }, - '8': { width: 20, points: [[8,21],[5,20],[4,18],[4,16],[5,14],[7,13],[11,12],[14,11],[16,9],[17,7],[17,4],[16,2],[15,1],[12,0],[8,0],[5,1],[4,2],[3,4],[3,7],[4,9],[6,11],[9,12],[13,13],[15,14],[16,16],[16,18],[15,20],[12,21],[8,21]] }, - '9': { width: 20, points: [[16,14],[15,11],[13,9],[10,8],[9,8],[6,9],[4,11],[3,14],[3,15],[4,18],[6,20],[9,21],[10,21],[13,20],[15,18],[16,14],[16,9],[15,4],[13,1],[10,0],[8,0],[5,1],[4,3]] }, - ':': { width: 10, points: [[5,14],[4,13],[5,12],[6,13],[5,14],null,[5,2],[4,1],[5,0],[6,1],[5,2]] }, - ';': { width: 10, points: [[5,14],[4,13],[5,12],[6,13],[5,14],null,[6,1],[5,0],[4,1],[5,2],[6,1],[6,-1],[5,-3],[4,-4]] }, - '<': { width: 24, points: [[20,18],[4,9],[20,0]] }, - '=': { width: 26, points: [[4,12],[22,12],null,[4,6],[22,6]] }, - '>': { width: 24, points: [[4,18],[20,9],[4,0]] }, - '?': { width: 18, points: [[3,16],[3,17],[4,19],[5,20],[7,21],[11,21],[13,20],[14,19],[15,17],[15,15],[14,13],[13,12],[9,10],[9,7],null,[9,2],[8,1],[9,0],[10,1],[9,2]] }, - '@': { width: 27, points: [[18,13],[17,15],[15,16],[12,16],[10,15],[9,14],[8,11],[8,8],[9,6],[11,5],[14,5],[16,6],[17,8],null,[12,16],[10,14],[9,11],[9,8],[10,6],[11,5],null,[18,16],[17,8],[17,6],[19,5],[21,5],[23,7],[24,10],[24,12],[23,15],[22,17],[20,19],[18,20],[15,21],[12,21],[9,20],[7,19],[5,17],[4,15],[3,12],[3,9],[4,6],[5,4],[7,2],[9,1],[12,0],[15,0],[18,1],[20,2],[21,3],null,[19,16],[18,8],[18,6],[19,5]] }, - 'A': { width: 18, points: [[9,21],[1,0],null,[9,21],[17,0],null,[4,7],[14,7]] }, - 'B': { width: 21, points: [[4,21],[4,0],null,[4,21],[13,21],[16,20],[17,19],[18,17],[18,15],[17,13],[16,12],[13,11],null,[4,11],[13,11],[16,10],[17,9],[18,7],[18,4],[17,2],[16,1],[13,0],[4,0]] }, - 'C': { width: 21, points: [[18,16],[17,18],[15,20],[13,21],[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5]] }, - 'D': { width: 21, points: [[4,21],[4,0],null,[4,21],[11,21],[14,20],[16,18],[17,16],[18,13],[18,8],[17,5],[16,3],[14,1],[11,0],[4,0]] }, - 'E': { width: 19, points: [[4,21],[4,0],null,[4,21],[17,21],null,[4,11],[12,11],null,[4,0],[17,0]] }, - 'F': { width: 18, points: [[4,21],[4,0],null,[4,21],[17,21],null,[4,11],[12,11]] }, - 'G': { width: 21, points: [[18,16],[17,18],[15,20],[13,21],[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5],[18,8],null,[13,8],[18,8]] }, - 'H': { width: 22, points: [[4,21],[4,0],null,[18,21],[18,0],null,[4,11],[18,11]] }, - 'I': { width: 8, points: [[4,21],[4,0]] }, - 'J': { width: 16, points: [[12,21],[12,5],[11,2],[10,1],[8,0],[6,0],[4,1],[3,2],[2,5],[2,7]] }, - 'K': { width: 21, points: [[4,21],[4,0],null,[18,21],[4,7],null,[9,12],[18,0]] }, - 'L': { width: 17, points: [[4,21],[4,0],null,[4,0],[16,0]] }, - 'M': { width: 24, points: [[4,21],[4,0],null,[4,21],[12,0],null,[20,21],[12,0],null,[20,21],[20,0]] }, - 'N': { width: 22, points: [[4,21],[4,0],null,[4,21],[18,0],null,[18,21],[18,0]] }, - 'O': { width: 22, points: [[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5],[19,8],[19,13],[18,16],[17,18],[15,20],[13,21],[9,21]] }, - 'P': { width: 21, points: [[4,21],[4,0],null,[4,21],[13,21],[16,20],[17,19],[18,17],[18,14],[17,12],[16,11],[13,10],[4,10]] }, - 'Q': { width: 22, points: [[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5],[19,8],[19,13],[18,16],[17,18],[15,20],[13,21],[9,21],null,[12,4],[18,-2]] }, - 'R': { width: 21, points: [[4,21],[4,0],null,[4,21],[13,21],[16,20],[17,19],[18,17],[18,15],[17,13],[16,12],[13,11],[4,11],null,[11,11],[18,0]] }, - 'S': { width: 20, points: [[17,18],[15,20],[12,21],[8,21],[5,20],[3,18],[3,16],[4,14],[5,13],[7,12],[13,10],[15,9],[16,8],[17,6],[17,3],[15,1],[12,0],[8,0],[5,1],[3,3]] }, - 'T': { width: 16, points: [[8,21],[8,0],null,[1,21],[15,21]] }, - 'U': { width: 22, points: [[4,21],[4,6],[5,3],[7,1],[10,0],[12,0],[15,1],[17,3],[18,6],[18,21]] }, - 'V': { width: 18, points: [[1,21],[9,0],null,[17,21],[9,0]] }, - 'W': { width: 24, points: [[2,21],[7,0],null,[12,21],[7,0],null,[12,21],[17,0],null,[22,21],[17,0]] }, - 'X': { width: 20, points: [[3,21],[17,0],null,[17,21],[3,0]] }, - 'Y': { width: 18, points: [[1,21],[9,11],[9,0],null,[17,21],[9,11]] }, - 'Z': { width: 20, points: [[17,21],[3,0],null,[3,21],[17,21],null,[3,0],[17,0]] }, - '[': { width: 14, points: [[4,25],[4,-7],null,[5,25],[5,-7],null,[4,25],[11,25],null,[4,-7],[11,-7]] }, - '\\':{ width: 14, points: [[0,21],[14,-3]] }, - ']': { width: 14, points: [[9,25],[9,-7],null,[10,25],[10,-7],null,[3,25],[10,25],null,[3,-7],[10,-7]] }, - '^': { width: 14, points: [[3,10],[8,18],[13,10]] }, - '_': { width: 16, points: [[0,-2],[16,-2]] }, - '`': { width: 10, points: [[6,21],[5,20],[4,18],[4,16],[5,15],[6,16],[5,17]] }, - 'a': { width: 19, points: [[15,14],[15,0],null,[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] }, - 'b': { width: 19, points: [[4,21],[4,0],null,[4,11],[6,13],[8,14],[11,14],[13,13],[15,11],[16,8],[16,6],[15,3],[13,1],[11,0],[8,0],[6,1],[4,3]] }, - 'c': { width: 18, points: [[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] }, - 'd': { width: 19, points: [[15,21],[15,0],null,[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] }, - 'e': { width: 18, points: [[3,8],[15,8],[15,10],[14,12],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] }, - 'f': { width: 12, points: [[10,21],[8,21],[6,20],[5,17],[5,0],null,[2,14],[9,14]] }, - 'g': { width: 19, points: [[15,14],[15,-2],[14,-5],[13,-6],[11,-7],[8,-7],[6,-6],null,[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] }, - 'h': { width: 19, points: [[4,21],[4,0],null,[4,10],[7,13],[9,14],[12,14],[14,13],[15,10],[15,0]] }, - 'i': { width: 8, points: [[3,21],[4,20],[5,21],[4,22],[3,21],null,[4,14],[4,0]] }, - 'j': { width: 10, points: [[5,21],[6,20],[7,21],[6,22],[5,21],null,[6,14],[6,-3],[5,-6],[3,-7],[1,-7]] }, - 'k': { width: 17, points: [[4,21],[4,0],null,[14,14],[4,4],null,[8,8],[15,0]] }, - 'l': { width: 8, points: [[4,21],[4,0]] }, - 'm': { width: 30, points: [[4,14],[4,0],null,[4,10],[7,13],[9,14],[12,14],[14,13],[15,10],[15,0],null,[15,10],[18,13],[20,14],[23,14],[25,13],[26,10],[26,0]] }, - 'n': { width: 19, points: [[4,14],[4,0],null,[4,10],[7,13],[9,14],[12,14],[14,13],[15,10],[15,0]] }, - 'o': { width: 19, points: [[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3],[16,6],[16,8],[15,11],[13,13],[11,14],[8,14]] }, - 'p': { width: 19, points: [[4,14],[4,-7],null,[4,11],[6,13],[8,14],[11,14],[13,13],[15,11],[16,8],[16,6],[15,3],[13,1],[11,0],[8,0],[6,1],[4,3]] }, - 'q': { width: 19, points: [[15,14],[15,-7],null,[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] }, - 'r': { width: 13, points: [[4,14],[4,0],null,[4,8],[5,11],[7,13],[9,14],[12,14]] }, - 's': { width: 17, points: [[14,11],[13,13],[10,14],[7,14],[4,13],[3,11],[4,9],[6,8],[11,7],[13,6],[14,4],[14,3],[13,1],[10,0],[7,0],[4,1],[3,3]] }, - 't': { width: 12, points: [[5,21],[5,4],[6,1],[8,0],[10,0],null,[2,14],[9,14]] }, - 'u': { width: 19, points: [[4,14],[4,4],[5,1],[7,0],[10,0],[12,1],[15,4],null,[15,14],[15,0]] }, - 'v': { width: 16, points: [[2,14],[8,0],null,[14,14],[8,0]] }, - 'w': { width: 22, points: [[3,14],[7,0],null,[11,14],[7,0],null,[11,14],[15,0],null,[19,14],[15,0]] }, - 'x': { width: 17, points: [[3,14],[14,0],null,[14,14],[3,0]] }, - 'y': { width: 16, points: [[2,14],[8,0],null,[14,14],[8,0],[6,-4],[4,-6],[2,-7],[1,-7]] }, - 'z': { width: 17, points: [[14,14],[3,0],null,[3,14],[14,14],null,[3,0],[14,0]] }, - '{': { width: 14, points: [[9,25],[7,24],[6,23],[5,21],[5,19],[6,17],[7,16],[8,14],[8,12],[6,10],null,[7,24],[6,22],[6,20],[7,18],[8,17],[9,15],[9,13],[8,11],[4,9],[8,7],[9,5],[9,3],[8,1],[7,0],[6,-2],[6,-4],[7,-6],null,[6,8],[8,6],[8,4],[7,2],[6,1],[5,-1],[5,-3],[6,-5],[7,-6],[9,-7]] }, - '|': { width: 8, points: [[4,25],[4,-7]] }, - '}': { width: 14, points: [[5,25],[7,24],[8,23],[9,21],[9,19],[8,17],[7,16],[6,14],[6,12],[8,10],null,[7,24],[8,22],[8,20],[7,18],[6,17],[5,15],[5,13],[6,11],[10,9],[6,7],[5,5],[5,3],[6,1],[7,0],[8,-2],[8,-4],[7,-6],null,[8,8],[6,6],[6,4],[7,2],[8,1],[9,-1],[9,-3],[8,-5],[7,-6],[5,-7]] }, - '~': { width: 24, points: [[3,6],[3,8],[4,11],[6,12],[8,12],[10,11],[14,8],[16,7],[18,7],[20,8],[21,10],null,[3,8],[4,10],[6,11],[8,11],[10,10],[14,7],[16,6],[18,6],[20,7],[21,10],[21,12]] }, - - // Lower case Latin-1 - 'à': { diacritic: '`', letter: 'a' }, - 'á': { diacritic: '´', letter: 'a' }, - 'â': { diacritic: '^', letter: 'a' }, - 'ä': { diacritic: '¨', letter: 'a' }, - 'ã': { diacritic: '~', letter: 'a' }, - - 'è': { diacritic: '`', letter: 'e' }, - 'é': { diacritic: '´', letter: 'e' }, - 'ê': { diacritic: '^', letter: 'e' }, - 'ë': { diacritic: '¨', letter: 'e' }, - - 'ì': { diacritic: '`', letter: 'i' }, - 'í': { diacritic: '´', letter: 'i' }, - 'î': { diacritic: '^', letter: 'i' }, - 'ï': { diacritic: '¨', letter: 'i' }, - - 'ò': { diacritic: '`', letter: 'o' }, - 'ó': { diacritic: '´', letter: 'o' }, - 'ô': { diacritic: '^', letter: 'o' }, - 'ö': { diacritic: '¨', letter: 'o' }, - 'õ': { diacritic: '~', letter: 'o' }, - - 'ù': { diacritic: '`', letter: 'u' }, - 'ú': { diacritic: '´', letter: 'u' }, - 'û': { diacritic: '^', letter: 'u' }, - 'ü': { diacritic: '¨', letter: 'u' }, - - 'ý': { diacritic: '´', letter: 'y' }, - 'ÿ': { diacritic: '¨', letter: 'y' }, - - 'ç': { diacritic: '¸', letter: 'c' }, - 'ñ': { diacritic: '~', letter: 'n' }, - - // Upper case Latin-1 - 'À': { diacritic: '`', letter: 'A' }, - 'Á': { diacritic: '´', letter: 'A' }, - 'Â': { diacritic: '^', letter: 'A' }, - 'Ä': { diacritic: '¨', letter: 'A' }, - 'Ã': { diacritic: '~', letter: 'A' }, - - 'È': { diacritic: '`', letter: 'E' }, - 'É': { diacritic: '´', letter: 'E' }, - 'Ê': { diacritic: '^', letter: 'E' }, - 'Ë': { diacritic: '¨', letter: 'E' }, - - 'Ì': { diacritic: '`', letter: 'I' }, - 'Í': { diacritic: '´', letter: 'I' }, - 'Î': { diacritic: '^', letter: 'I' }, - 'Ï': { diacritic: '¨', letter: 'I' }, - - 'Ò': { diacritic: '`', letter: 'O' }, - 'Ó': { diacritic: '´', letter: 'O' }, - 'Ô': { diacritic: '^', letter: 'O' }, - 'Ö': { diacritic: '¨', letter: 'O' }, - 'Õ': { diacritic: '~', letter: 'O' }, - - 'Ù': { diacritic: '`', letter: 'U' }, - 'Ú': { diacritic: '´', letter: 'U' }, - 'Û': { diacritic: '^', letter: 'U' }, - 'Ü': { diacritic: '¨', letter: 'U' }, - - 'Ý': { diacritic: '´', letter: 'Y' }, - - 'Ç': { diacritic: '¸', letter: 'C' }, - 'Ñ': { diacritic: '~', letter: 'N' } - }, - - specialchars: { - 'pi': { width: 19, points: [[6,14],[6,0],null,[14,14],[14,0],null,[2,13],[6,16],[13,13],[17,16]] } - }, - - /** Diacritics, used to draw accentuated letters */ - diacritics: { - '¸': { entity: 'cedil', points: [[6,-4],[4,-6],[2,-7],[1,-7]] }, - '´': { entity: 'acute', points: [[8,19],[13,22]] }, - '`': { entity: 'grave', points: [[7,22],[12,19]] }, - '^': { entity: 'circ', points: [[5.5,19],[9.5,23],[12.5,19]] }, - '¨': { entity: 'trema', points: [[5,21],[6,20],[7,21],[6,22],[5,21],null,[12,21],[13,20],[14,21],[13,22],[12,21]] }, - '~': { entity: 'tilde', points: [[4,18],[7,22],[10,18],[13,22]] } - }, - - /** The default font styling */ - style: { - size: 8, // font height in pixels - font: null, // not yet implemented - color: '#000000', // font color - weight: 1, // float, 1 for 'normal' - textAlign: 'left', // left, right, center - textBaseline: 'bottom', // top, middle, bottom - adjustAlign: false, // modifies the alignments if the angle is different from 0 to make the spin point always at the good position - angle: 0, // in radians, anticlockwise - tracking: 1, // space between the letters, float, 1 for 'normal' - boundingBoxColor: '#ff0000', // color of the bounding box (null to hide), can be used for debug and font drawing - originPointColor: '#000000' // color of the bounding box (null to hide), can be used for debug and font drawing - }, - - debug: false, - _bufferLexemes: {}, - - extend: function(dest, src) { - for (var property in src) { - if (property in dest) continue; - dest[property] = src[property]; - } - return dest; - }, - - /** Get the letter data corresponding to a char - * @param {String} ch - The char - */ - letter: function(ch) { - return CanvasText.letters[ch]; - }, - - parseLexemes: function(str) { - if (CanvasText._bufferLexemes[str]) - return CanvasText._bufferLexemes[str]; - - var i, c, matches = str.match(/&[A-Za-z]{2,5};|\s|./g), - result = [], chars = []; - - for (i = 0; i < matches.length; i++) { - c = matches[i]; - if (c.length == 1) - chars.push(c); - else { - var entity = c.substring(1, c.length-1); - if (CanvasText.specialchars[entity]) - chars.push(entity); - else - chars = chars.concat(c.toArray()); - } - } - for (i = 0; i < chars.length; i++) { - c = chars[i]; - if (c = CanvasText.letters[c] || CanvasText.specialchars[c]) result.push(c); - } - for (i = 0; i < result.length; i++) { - if (result === null || typeof result === 'undefined') - delete result[i]; - } - return CanvasText._bufferLexemes[str] = result; - }, - - /** Get the font ascent for a given style - * @param {Object} style - The reference style - */ - ascent: function(style) { - style = style || CanvasText.style; - return (style.size || CanvasText.style.size); - }, - - /** Get the font descent for a given style - * @param {Object} style - The reference style - * */ - descent: function(style) { - style = style || CanvasText.style; - return 7.0*(style.size || CanvasText.style.size)/25.0; - }, - - /** Measure the text horizontal size - * @param {String} str - The text - * @param {Object} style - Text style - * */ - measure: function(str, style) { - if (!str) return; - style = style || CanvasText.style; - - var i, width, lexemes = CanvasText.parseLexemes(str), - total = 0; - - for (i = lexemes.length-1; i > -1; --i) { - c = lexemes[i]; - width = (c.diacritic) ? CanvasText.letter(c.letter).width : c.width; - total += width * (style.tracking || CanvasText.style.tracking) * (style.size || CanvasText.style.size) / 25.0; - } - return total; - }, - - getDimensions: function(str, style) { - style = style || CanvasText.style; - - var width = CanvasText.measure(str, style), - height = style.size || CanvasText.style.size, - angle = style.angle || CanvasText.style.angle; - - if (style.angle == 0) return {width: width, height: height}; - return { - width: Math.abs(Math.cos(angle) * width) + Math.abs(Math.sin(angle) * height), - height: Math.abs(Math.sin(angle) * width) + Math.abs(Math.cos(angle) * height) - } - }, - - /** Draws serie of points at given coordinates - * @param {Canvas context} ctx - The canvas context - * @param {Array} points - The points to draw - * @param {Number} x - The X coordinate - * @param {Number} y - The Y coordinate - * @param {Number} mag - The scale - */ - drawPoints: function (ctx, points, x, y, mag, offset) { - var i, a, penUp = true, needStroke = 0; - offset = offset || {x:0, y:0}; - - ctx.beginPath(); - for (i = 0; i < points.length; i++) { - a = points[i]; - if (!a) { - penUp = true; - continue; - } - if (penUp) { - ctx.moveTo(x + a[0]*mag + offset.x, y - a[1]*mag + offset.y); - penUp = false; - } - else { - ctx.lineTo(x + a[0]*mag + offset.x, y - a[1]*mag + offset.y); - } - } - ctx.stroke(); - ctx.closePath(); - }, - - /** Draws a text at given coordinates and with a given style - * @param {String} str - The text to draw - * @param {Number} xOrig - The X coordinate - * @param {Number} yOrig - The Y coordinate - * @param {Object} style - The font style - */ - draw: function(str, xOrig, yOrig, style) { - if (!str) return; - CanvasText.extend(style, CanvasText.style); - - var i, c, total = 0, - mag = style.size / 25.0, - x = 0, y = 0, - lexemes = CanvasText.parseLexemes(str), - offset = {x: 0, y: 0}, - measure = CanvasText.measure(str, style), - align; - - if (style.adjustAlign) { - align = CanvasText.getBestAlign(style.angle, style); - CanvasText.extend(style, align); - } - - switch (style.textAlign) { - case 'left': break; - case 'center': offset.x = -measure / 2; break; - case 'right': offset.x = -measure; break; - } - - switch (style.textBaseline) { - case 'bottom': break; - case 'middle': offset.y = style.size / 2; break; - case 'top': offset.y = style.size; break; - } - - this.save(); - this.translate(xOrig, yOrig); - this.rotate(style.angle); - this.lineCap = "round"; - this.lineWidth = 2.0 * mag * (style.weight || CanvasText.style.weight); - this.strokeStyle = style.color || CanvasText.style.color; - - for (i = 0; i < lexemes.length; i++) { - c = lexemes[i]; - if (c.width == -1) { - x = 0; - y = style.size * 1.4; - continue; - } - - var points = c.points, - width = c.width; - - if (c.diacritic) { - var dia = CanvasText.diacritics[c.diacritic], - character = CanvasText.letter(c.letter); - - CanvasText.drawPoints(this, dia.points, x, y - (c.letter.toUpperCase() == c.letter ? 3 : 0), mag, offset); - points = character.points; - width = character.width; - } - - CanvasText.drawPoints(this, points, x, y, mag, offset); - - if (CanvasText.debug) { - this.save(); - this.lineJoin = "miter"; - this.lineWidth = 0.5; - this.strokeStyle = (style.boundingBoxColor || CanvasText.style.boundingBoxColor); - this.strokeRect(x+offset.x, y+offset.y, width*mag, -style.size); - - this.fillStyle = (style.originPointColor || CanvasText.style.originPointColor); - this.beginPath(); - this.arc(0, 0, 1.5, 0, Math.PI*2, true); - this.fill(); - this.closePath(); - this.restore(); - } - - x += width*mag*(style.tracking || CanvasText.style.tracking); - } - this.restore(); - return total; - } -}; - -/** The text functions are bound to the CanvasRenderingContext2D prototype */ -CanvasText.proto = window.CanvasRenderingContext2D ? window.CanvasRenderingContext2D.prototype : document.createElement('canvas').getContext('2d').__proto__; - -if (CanvasText.proto) { - CanvasText.proto.drawText = CanvasText.draw; - CanvasText.proto.measure = CanvasText.measure; - CanvasText.proto.getTextBounds = CanvasText.getDimensions; - CanvasText.proto.fontAscent = CanvasText.ascent; - CanvasText.proto.fontDescent = CanvasText.descent; -}
\ No newline at end of file diff --git a/addons/web_graph/static/lib/flotr2/lib/excanvas.js b/addons/web_graph/static/lib/flotr2/lib/excanvas.js deleted file mode 100644 index 005bab1186f..00000000000 --- a/addons/web_graph/static/lib/flotr2/lib/excanvas.js +++ /dev/null @@ -1,1425 +0,0 @@ -// Copyright 2006 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - - -// Known Issues: -// -// * Patterns only support repeat. -// * Radial gradient are not implemented. The VML version of these look very -// different from the canvas one. -// * Clipping paths are not implemented. -// * Coordsize. The width and height attribute have higher priority than the -// width and height style values which isn't correct. -// * Painting mode isn't implemented. -// * Canvas width/height should is using content-box by default. IE in -// Quirks mode will draw the canvas using border-box. Either change your -// doctype to HTML5 -// (http://www.whatwg.org/specs/web-apps/current-work/#the-doctype) -// or use Box Sizing Behavior from WebFX -// (http://webfx.eae.net/dhtml/boxsizing/boxsizing.html) -// * Non uniform scaling does not correctly scale strokes. -// * Optimize. There is always room for speed improvements. - -// Only add this code if we do not already have a canvas implementation -if (!document.createElement('canvas').getContext) { - -(function() { - - // alias some functions to make (compiled) code shorter - var m = Math; - var mr = m.round; - var ms = m.sin; - var mc = m.cos; - var abs = m.abs; - var sqrt = m.sqrt; - - // this is used for sub pixel precision - var Z = 10; - var Z2 = Z / 2; - - var IE_VERSION = +navigator.userAgent.match(/MSIE ([\d.]+)?/)[1]; - - /** - * This funtion is assigned to the <canvas> elements as element.getContext(). - * @this {HTMLElement} - * @return {CanvasRenderingContext2D_} - */ - function getContext() { - return this.context_ || - (this.context_ = new CanvasRenderingContext2D_(this)); - } - - var slice = Array.prototype.slice; - - /** - * Binds a function to an object. The returned function will always use the - * passed in {@code obj} as {@code this}. - * - * Example: - * - * g = bind(f, obj, a, b) - * g(c, d) // will do f.call(obj, a, b, c, d) - * - * @param {Function} f The function to bind the object to - * @param {Object} obj The object that should act as this when the function - * is called - * @param {*} var_args Rest arguments that will be used as the initial - * arguments when the function is called - * @return {Function} A new function that has bound this - */ - function bind(f, obj, var_args) { - var a = slice.call(arguments, 2); - return function() { - return f.apply(obj, a.concat(slice.call(arguments))); - }; - } - - function encodeHtmlAttribute(s) { - return String(s).replace(/&/g, '&').replace(/"/g, '"'); - } - - function addNamespace(doc, prefix, urn) { - if (!doc.namespaces[prefix]) { - doc.namespaces.add(prefix, urn, '#default#VML'); - } - } - - function addNamespacesAndStylesheet(doc) { - addNamespace(doc, 'g_vml_', 'urn:schemas-microsoft-com:vml'); - addNamespace(doc, 'g_o_', 'urn:schemas-microsoft-com:office:office'); - - // Setup default CSS. Only add one style sheet per document - if (!doc.styleSheets['ex_canvas_']) { - var ss = doc.createStyleSheet(); - ss.owningElement.id = 'ex_canvas_'; - ss.cssText = 'canvas{display:inline-block;overflow:hidden;' + - // default size is 300x150 in Gecko and Opera - 'text-align:left;width:300px;height:150px}'; - } - } - - // Add namespaces and stylesheet at startup. - addNamespacesAndStylesheet(document); - - var G_vmlCanvasManager_ = { - init: function(opt_doc) { - var doc = opt_doc || document; - // Create a dummy element so that IE will allow canvas elements to be - // recognized. - doc.createElement('canvas'); - doc.attachEvent('onreadystatechange', bind(this.init_, this, doc)); - }, - - init_: function(doc) { - // find all canvas elements - var els = doc.getElementsByTagName('canvas'); - for (var i = 0; i < els.length; i++) { - this.initElement(els[i]); - } - }, - - /** - * Public initializes a canvas element so that it can be used as canvas - * element from now on. This is called automatically before the page is - * loaded but if you are creating elements using createElement you need to - * make sure this is called on the element. - * @param {HTMLElement} el The canvas element to initialize. - * @return {HTMLElement} the element that was created. - */ - initElement: function(el) { - if (!el.getContext) { - el.getContext = getContext; - - // Add namespaces and stylesheet to document of the element. - addNamespacesAndStylesheet(el.ownerDocument); - - // Remove fallback content. There is no way to hide text nodes so we - // just remove all childNodes. We could hide all elements and remove - // text nodes but who really cares about the fallback content. - el.innerHTML = ''; - - // do not use inline function because that will leak memory - el.attachEvent('onpropertychange', onPropertyChange); - el.attachEvent('onresize', onResize); - - var attrs = el.attributes; - if (attrs.width && attrs.width.specified) { - // TODO: use runtimeStyle and coordsize - // el.getContext().setWidth_(attrs.width.nodeValue); - el.style.width = attrs.width.nodeValue + 'px'; - } else { - el.width = el.clientWidth; - } - if (attrs.height && attrs.height.specified) { - // TODO: use runtimeStyle and coordsize - // el.getContext().setHeight_(attrs.height.nodeValue); - el.style.height = attrs.height.nodeValue + 'px'; - } else { - el.height = el.clientHeight; - } - //el.getContext().setCoordsize_() - } - return el; - } - }; - - function onPropertyChange(e) { - var el = e.srcElement; - - switch (e.propertyName) { - case 'width': - el.getContext().clearRect(); - el.style.width = el.attributes.width.nodeValue + 'px'; - // In IE8 this does not trigger onresize. - if (el.firstChild) { - el.firstChild.style.width = el.clientWidth + 'px'; - } - break; - case 'height': - el.getContext().clearRect(); - el.style.height = el.attributes.height.nodeValue + 'px'; - if (el.firstChild) { - el.firstChild.style.height = el.clientHeight + 'px'; - } - break; - } - } - - function onResize(e) { - var el = e.srcElement; - if (el.firstChild) { - el.firstChild.style.width = el.clientWidth + 'px'; - el.firstChild.style.height = el.clientHeight + 'px'; - } - } - - G_vmlCanvasManager_.init(); - - // precompute "00" to "FF" - var decToHex = []; - for (var i = 0; i < 16; i++) { - for (var j = 0; j < 16; j++) { - decToHex[i * 16 + j] = i.toString(16) + j.toString(16); - } - } - - function createMatrixIdentity() { - return [ - [1, 0, 0], - [0, 1, 0], - [0, 0, 1] - ]; - } - - function matrixMultiply(m1, m2) { - var result = createMatrixIdentity(); - - for (var x = 0; x < 3; x++) { - for (var y = 0; y < 3; y++) { - var sum = 0; - - for (var z = 0; z < 3; z++) { - sum += m1[x][z] * m2[z][y]; - } - - result[x][y] = sum; - } - } - return result; - } - - function copyState(o1, o2) { - o2.fillStyle = o1.fillStyle; - o2.lineCap = o1.lineCap; - o2.lineJoin = o1.lineJoin; - o2.lineWidth = o1.lineWidth; - o2.miterLimit = o1.miterLimit; - o2.shadowBlur = o1.shadowBlur; - o2.shadowColor = o1.shadowColor; - o2.shadowOffsetX = o1.shadowOffsetX; - o2.shadowOffsetY = o1.shadowOffsetY; - o2.strokeStyle = o1.strokeStyle; - o2.globalAlpha = o1.globalAlpha; - o2.font = o1.font; - o2.textAlign = o1.textAlign; - o2.textBaseline = o1.textBaseline; - o2.arcScaleX_ = o1.arcScaleX_; - o2.arcScaleY_ = o1.arcScaleY_; - o2.lineScale_ = o1.lineScale_; - } - - var colorData = { - aliceblue: '#F0F8FF', - antiquewhite: '#FAEBD7', - aquamarine: '#7FFFD4', - azure: '#F0FFFF', - beige: '#F5F5DC', - bisque: '#FFE4C4', - black: '#000000', - blanchedalmond: '#FFEBCD', - blueviolet: '#8A2BE2', - brown: '#A52A2A', - burlywood: '#DEB887', - cadetblue: '#5F9EA0', - chartreuse: '#7FFF00', - chocolate: '#D2691E', - coral: '#FF7F50', - cornflowerblue: '#6495ED', - cornsilk: '#FFF8DC', - crimson: '#DC143C', - cyan: '#00FFFF', - darkblue: '#00008B', - darkcyan: '#008B8B', - darkgoldenrod: '#B8860B', - darkgray: '#A9A9A9', - darkgreen: '#006400', - darkgrey: '#A9A9A9', - darkkhaki: '#BDB76B', - darkmagenta: '#8B008B', - darkolivegreen: '#556B2F', - darkorange: '#FF8C00', - darkorchid: '#9932CC', - darkred: '#8B0000', - darksalmon: '#E9967A', - darkseagreen: '#8FBC8F', - darkslateblue: '#483D8B', - darkslategray: '#2F4F4F', - darkslategrey: '#2F4F4F', - darkturquoise: '#00CED1', - darkviolet: '#9400D3', - deeppink: '#FF1493', - deepskyblue: '#00BFFF', - dimgray: '#696969', - dimgrey: '#696969', - dodgerblue: '#1E90FF', - firebrick: '#B22222', - floralwhite: '#FFFAF0', - forestgreen: '#228B22', - gainsboro: '#DCDCDC', - ghostwhite: '#F8F8FF', - gold: '#FFD700', - goldenrod: '#DAA520', - grey: '#808080', - greenyellow: '#ADFF2F', - honeydew: '#F0FFF0', - hotpink: '#FF69B4', - indianred: '#CD5C5C', - indigo: '#4B0082', - ivory: '#FFFFF0', - khaki: '#F0E68C', - lavender: '#E6E6FA', - lavenderblush: '#FFF0F5', - lawngreen: '#7CFC00', - lemonchiffon: '#FFFACD', - lightblue: '#ADD8E6', - lightcoral: '#F08080', - lightcyan: '#E0FFFF', - lightgoldenrodyellow: '#FAFAD2', - lightgreen: '#90EE90', - lightgrey: '#D3D3D3', - lightpink: '#FFB6C1', - lightsalmon: '#FFA07A', - lightseagreen: '#20B2AA', - lightskyblue: '#87CEFA', - lightslategray: '#778899', - lightslategrey: '#778899', - lightsteelblue: '#B0C4DE', - lightyellow: '#FFFFE0', - limegreen: '#32CD32', - linen: '#FAF0E6', - magenta: '#FF00FF', - mediumaquamarine: '#66CDAA', - mediumblue: '#0000CD', - mediumorchid: '#BA55D3', - mediumpurple: '#9370DB', - mediumseagreen: '#3CB371', - mediumslateblue: '#7B68EE', - mediumspringgreen: '#00FA9A', - mediumturquoise: '#48D1CC', - mediumvioletred: '#C71585', - midnightblue: '#191970', - mintcream: '#F5FFFA', - mistyrose: '#FFE4E1', - moccasin: '#FFE4B5', - navajowhite: '#FFDEAD', - oldlace: '#FDF5E6', - olivedrab: '#6B8E23', - orange: '#FFA500', - orangered: '#FF4500', - orchid: '#DA70D6', - palegoldenrod: '#EEE8AA', - palegreen: '#98FB98', - paleturquoise: '#AFEEEE', - palevioletred: '#DB7093', - papayawhip: '#FFEFD5', - peachpuff: '#FFDAB9', - peru: '#CD853F', - pink: '#FFC0CB', - plum: '#DDA0DD', - powderblue: '#B0E0E6', - rosybrown: '#BC8F8F', - royalblue: '#4169E1', - saddlebrown: '#8B4513', - salmon: '#FA8072', - sandybrown: '#F4A460', - seagreen: '#2E8B57', - seashell: '#FFF5EE', - sienna: '#A0522D', - skyblue: '#87CEEB', - slateblue: '#6A5ACD', - slategray: '#708090', - slategrey: '#708090', - snow: '#FFFAFA', - springgreen: '#00FF7F', - steelblue: '#4682B4', - tan: '#D2B48C', - thistle: '#D8BFD8', - tomato: '#FF6347', - turquoise: '#40E0D0', - violet: '#EE82EE', - wheat: '#F5DEB3', - whitesmoke: '#F5F5F5', - yellowgreen: '#9ACD32' - }; - - - function getRgbHslContent(styleString) { - var start = styleString.indexOf('(', 3); - var end = styleString.indexOf(')', start + 1); - var parts = styleString.substring(start + 1, end).split(','); - // add alpha if needed - if (parts.length != 4 || styleString.charAt(3) != 'a') { - parts[3] = 1; - } - return parts; - } - - function percent(s) { - return parseFloat(s) / 100; - } - - function clamp(v, min, max) { - return Math.min(max, Math.max(min, v)); - } - - function hslToRgb(parts){ - var r, g, b, h, s, l; - h = parseFloat(parts[0]) / 360 % 360; - if (h < 0) - h++; - s = clamp(percent(parts[1]), 0, 1); - l = clamp(percent(parts[2]), 0, 1); - if (s == 0) { - r = g = b = l; // achromatic - } else { - var q = l < 0.5 ? l * (1 + s) : l + s - l * s; - var p = 2 * l - q; - r = hueToRgb(p, q, h + 1 / 3); - g = hueToRgb(p, q, h); - b = hueToRgb(p, q, h - 1 / 3); - } - - return '#' + decToHex[Math.floor(r * 255)] + - decToHex[Math.floor(g * 255)] + - decToHex[Math.floor(b * 255)]; - } - - function hueToRgb(m1, m2, h) { - if (h < 0) - h++; - if (h > 1) - h--; - - if (6 * h < 1) - return m1 + (m2 - m1) * 6 * h; - else if (2 * h < 1) - return m2; - else if (3 * h < 2) - return m1 + (m2 - m1) * (2 / 3 - h) * 6; - else - return m1; - } - - var processStyleCache = {}; - - function processStyle(styleString) { - if (styleString in processStyleCache) { - return processStyleCache[styleString]; - } - - var str, alpha = 1; - - styleString = String(styleString); - if (styleString.charAt(0) == '#') { - str = styleString; - } else if (/^rgb/.test(styleString)) { - var parts = getRgbHslContent(styleString); - var str = '#', n; - for (var i = 0; i < 3; i++) { - if (parts[i].indexOf('%') != -1) { - n = Math.floor(percent(parts[i]) * 255); - } else { - n = +parts[i]; - } - str += decToHex[clamp(n, 0, 255)]; - } - alpha = +parts[3]; - } else if (/^hsl/.test(styleString)) { - var parts = getRgbHslContent(styleString); - str = hslToRgb(parts); - alpha = parts[3]; - } else { - str = colorData[styleString] || styleString; - } - return processStyleCache[styleString] = {color: str, alpha: alpha}; - } - - var DEFAULT_STYLE = { - style: 'normal', - variant: 'normal', - weight: 'normal', - size: 10, - family: 'sans-serif' - }; - - // Internal text style cache - var fontStyleCache = {}; - - function processFontStyle(styleString) { - if (fontStyleCache[styleString]) { - return fontStyleCache[styleString]; - } - - var el = document.createElement('div'); - var style = el.style; - try { - style.font = styleString; - } catch (ex) { - // Ignore failures to set to invalid font. - } - - return fontStyleCache[styleString] = { - style: style.fontStyle || DEFAULT_STYLE.style, - variant: style.fontVariant || DEFAULT_STYLE.variant, - weight: style.fontWeight || DEFAULT_STYLE.weight, - size: style.fontSize || DEFAULT_STYLE.size, - family: style.fontFamily || DEFAULT_STYLE.family - }; - } - - function getComputedStyle(style, element) { - var computedStyle = {}; - - for (var p in style) { - computedStyle[p] = style[p]; - } - - // Compute the size - var canvasFontSize = parseFloat(element.currentStyle.fontSize), - fontSize = parseFloat(style.size); - - if (typeof style.size == 'number') { - computedStyle.size = style.size; - } else if (style.size.indexOf('px') != -1) { - computedStyle.size = fontSize; - } else if (style.size.indexOf('em') != -1) { - computedStyle.size = canvasFontSize * fontSize; - } else if(style.size.indexOf('%') != -1) { - computedStyle.size = (canvasFontSize / 100) * fontSize; - } else if (style.size.indexOf('pt') != -1) { - computedStyle.size = fontSize / .75; - } else { - computedStyle.size = canvasFontSize; - } - - // Different scaling between normal text and VML text. This was found using - // trial and error to get the same size as non VML text. - //computedStyle.size *= 0.981; - - return computedStyle; - } - - function buildStyle(style) { - return style.style + ' ' + style.variant + ' ' + style.weight + ' ' + - style.size + 'px ' + style.family; - } - - var lineCapMap = { - 'butt': 'flat', - 'round': 'round' - }; - - function processLineCap(lineCap) { - return lineCapMap[lineCap] || 'square'; - } - - /** - * This class implements CanvasRenderingContext2D interface as described by - * the WHATWG. - * @param {HTMLElement} canvasElement The element that the 2D context should - * be associated with - */ - function CanvasRenderingContext2D_(canvasElement) { - this.m_ = createMatrixIdentity(); - - this.mStack_ = []; - this.aStack_ = []; - this.currentPath_ = []; - - // Canvas context properties - this.strokeStyle = '#000'; - this.fillStyle = '#000'; - - this.lineWidth = 1; - this.lineJoin = 'miter'; - this.lineCap = 'butt'; - this.miterLimit = Z * 1; - this.globalAlpha = 1; - this.font = '10px sans-serif'; - this.textAlign = 'left'; - this.textBaseline = 'alphabetic'; - this.canvas = canvasElement; - - var cssText = 'width:' + canvasElement.clientWidth + 'px;height:' + - canvasElement.clientHeight + 'px;overflow:hidden;position:absolute'; - var el = canvasElement.ownerDocument.createElement('div'); - el.style.cssText = cssText; - canvasElement.appendChild(el); - - var overlayEl = el.cloneNode(false); - // Use a non transparent background. - overlayEl.style.backgroundColor = 'red'; - overlayEl.style.filter = 'alpha(opacity=0)'; - canvasElement.appendChild(overlayEl); - - this.element_ = el; - this.arcScaleX_ = 1; - this.arcScaleY_ = 1; - this.lineScale_ = 1; - } - - var contextPrototype = CanvasRenderingContext2D_.prototype; - contextPrototype.clearRect = function() { - if (this.textMeasureEl_) { - this.textMeasureEl_.removeNode(true); - this.textMeasureEl_ = null; - } - this.element_.innerHTML = ''; - }; - - contextPrototype.beginPath = function() { - // TODO: Branch current matrix so that save/restore has no effect - // as per safari docs. - this.currentPath_ = []; - }; - - contextPrototype.moveTo = function(aX, aY) { - var p = getCoords(this, aX, aY); - this.currentPath_.push({type: 'moveTo', x: p.x, y: p.y}); - this.currentX_ = p.x; - this.currentY_ = p.y; - }; - - contextPrototype.lineTo = function(aX, aY) { - var p = getCoords(this, aX, aY); - this.currentPath_.push({type: 'lineTo', x: p.x, y: p.y}); - - this.currentX_ = p.x; - this.currentY_ = p.y; - }; - - contextPrototype.bezierCurveTo = function(aCP1x, aCP1y, - aCP2x, aCP2y, - aX, aY) { - var p = getCoords(this, aX, aY); - var cp1 = getCoords(this, aCP1x, aCP1y); - var cp2 = getCoords(this, aCP2x, aCP2y); - bezierCurveTo(this, cp1, cp2, p); - }; - - // Helper function that takes the already fixed cordinates. - function bezierCurveTo(self, cp1, cp2, p) { - self.currentPath_.push({ - type: 'bezierCurveTo', - cp1x: cp1.x, - cp1y: cp1.y, - cp2x: cp2.x, - cp2y: cp2.y, - x: p.x, - y: p.y - }); - self.currentX_ = p.x; - self.currentY_ = p.y; - } - - contextPrototype.quadraticCurveTo = function(aCPx, aCPy, aX, aY) { - // the following is lifted almost directly from - // http://developer.mozilla.org/en/docs/Canvas_tutorial:Drawing_shapes - - var cp = getCoords(this, aCPx, aCPy); - var p = getCoords(this, aX, aY); - - var cp1 = { - x: this.currentX_ + 2.0 / 3.0 * (cp.x - this.currentX_), - y: this.currentY_ + 2.0 / 3.0 * (cp.y - this.currentY_) - }; - var cp2 = { - x: cp1.x + (p.x - this.currentX_) / 3.0, - y: cp1.y + (p.y - this.currentY_) / 3.0 - }; - - bezierCurveTo(this, cp1, cp2, p); - }; - - contextPrototype.arc = function(aX, aY, aRadius, - aStartAngle, aEndAngle, aClockwise) { - aRadius *= Z; - var arcType = aClockwise ? 'at' : 'wa'; - - var xStart = aX + mc(aStartAngle) * aRadius - Z2; - var yStart = aY + ms(aStartAngle) * aRadius - Z2; - - var xEnd = aX + mc(aEndAngle) * aRadius - Z2; - var yEnd = aY + ms(aEndAngle) * aRadius - Z2; - - // IE won't render arches drawn counter clockwise if xStart == xEnd. - if ((abs(xStart - xEnd) < 10e-8) && !aClockwise) { - xStart += 0.125; // Offset xStart by 1/80 of a pixel. Use something - // that can be represented in binary - } - // IE won't render arches drawn clockwise if yStart is very close to yEnd. - if ((abs(yStart - yEnd) < 10e-8) && aClockwise) { - yStart -= 0.125; // Offset yStart by 1/80 of a pixel. Use something - // that can be represented in binary - } - - var p = getCoords(this, aX, aY); - var pStart = getCoords(this, xStart, yStart); - var pEnd = getCoords(this, xEnd, yEnd); - - this.currentPath_.push({type: arcType, - x: p.x, - y: p.y, - radius: aRadius, - xStart: pStart.x, - yStart: pStart.y, - xEnd: pEnd.x, - yEnd: pEnd.y}); - - }; - - contextPrototype.rect = function(aX, aY, aWidth, aHeight) { - this.moveTo(aX, aY); - this.lineTo(aX + aWidth, aY); - this.lineTo(aX + aWidth, aY + aHeight); - this.lineTo(aX, aY + aHeight); - this.closePath(); - }; - - contextPrototype.strokeRect = function(aX, aY, aWidth, aHeight) { - var oldPath = this.currentPath_; - this.beginPath(); - - this.moveTo(aX, aY); - this.lineTo(aX + aWidth, aY); - this.lineTo(aX + aWidth, aY + aHeight); - this.lineTo(aX, aY + aHeight); - this.closePath(); - this.stroke(); - - this.currentPath_ = oldPath; - }; - - contextPrototype.fillRect = function(aX, aY, aWidth, aHeight) { - var oldPath = this.currentPath_; - this.beginPath(); - - this.moveTo(aX, aY); - this.lineTo(aX + aWidth, aY); - this.lineTo(aX + aWidth, aY + aHeight); - this.lineTo(aX, aY + aHeight); - this.closePath(); - this.fill(); - - this.currentPath_ = oldPath; - }; - - contextPrototype.createLinearGradient = function(aX0, aY0, aX1, aY1) { - var gradient = new CanvasGradient_('gradient'); - gradient.x0_ = aX0; - gradient.y0_ = aY0; - gradient.x1_ = aX1; - gradient.y1_ = aY1; - return gradient; - }; - - contextPrototype.createRadialGradient = function(aX0, aY0, aR0, - aX1, aY1, aR1) { - var gradient = new CanvasGradient_('gradientradial'); - gradient.x0_ = aX0; - gradient.y0_ = aY0; - gradient.r0_ = aR0; - gradient.x1_ = aX1; - gradient.y1_ = aY1; - gradient.r1_ = aR1; - return gradient; - }; - - contextPrototype.drawImage = function(image, var_args) { - var dx, dy, dw, dh, sx, sy, sw, sh; - - // to find the original width we overide the width and height - var oldRuntimeWidth = image.runtimeStyle.width; - var oldRuntimeHeight = image.runtimeStyle.height; - image.runtimeStyle.width = 'auto'; - image.runtimeStyle.height = 'auto'; - - // get the original size - var w = image.width; - var h = image.height; - - // and remove overides - image.runtimeStyle.width = oldRuntimeWidth; - image.runtimeStyle.height = oldRuntimeHeight; - - if (arguments.length == 3) { - dx = arguments[1]; - dy = arguments[2]; - sx = sy = 0; - sw = dw = w; - sh = dh = h; - } else if (arguments.length == 5) { - dx = arguments[1]; - dy = arguments[2]; - dw = arguments[3]; - dh = arguments[4]; - sx = sy = 0; - sw = w; - sh = h; - } else if (arguments.length == 9) { - sx = arguments[1]; - sy = arguments[2]; - sw = arguments[3]; - sh = arguments[4]; - dx = arguments[5]; - dy = arguments[6]; - dw = arguments[7]; - dh = arguments[8]; - } else { - throw Error('Invalid number of arguments'); - } - - var d = getCoords(this, dx, dy); - - var w2 = sw / 2; - var h2 = sh / 2; - - var vmlStr = []; - - var W = 10; - var H = 10; - - // For some reason that I've now forgotten, using divs didn't work - vmlStr.push(' <g_vml_:group', - ' coordsize="', Z * W, ',', Z * H, '"', - ' coordorigin="0,0"' , - ' style="width:', W, 'px;height:', H, 'px;position:absolute;'); - - // If filters are necessary (rotation exists), create them - // filters are bog-slow, so only create them if abbsolutely necessary - // The following check doesn't account for skews (which don't exist - // in the canvas spec (yet) anyway. - - if (this.m_[0][0] != 1 || this.m_[0][1] || - this.m_[1][1] != 1 || this.m_[1][0]) { - var filter = []; - - // Note the 12/21 reversal - filter.push('M11=', this.m_[0][0], ',', - 'M12=', this.m_[1][0], ',', - 'M21=', this.m_[0][1], ',', - 'M22=', this.m_[1][1], ',', - 'Dx=', mr(d.x / Z), ',', - 'Dy=', mr(d.y / Z), ''); - - // Bounding box calculation (need to minimize displayed area so that - // filters don't waste time on unused pixels. - var max = d; - var c2 = getCoords(this, dx + dw, dy); - var c3 = getCoords(this, dx, dy + dh); - var c4 = getCoords(this, dx + dw, dy + dh); - - max.x = m.max(max.x, c2.x, c3.x, c4.x); - max.y = m.max(max.y, c2.y, c3.y, c4.y); - - vmlStr.push('padding:0 ', mr(max.x / Z), 'px ', mr(max.y / Z), - 'px 0;filter:progid:DXImageTransform.Microsoft.Matrix(', - filter.join(''), ", sizingmethod='clip');"); - - } else { - vmlStr.push('top:', mr(d.y / Z), 'px;left:', mr(d.x / Z), 'px;'); - } - - vmlStr.push(' ">' , - '<g_vml_:image src="', image.src, '"', - ' style="width:', Z * dw, 'px;', - ' height:', Z * dh, 'px"', - ' cropleft="', sx / w, '"', - ' croptop="', sy / h, '"', - ' cropright="', (w - sx - sw) / w, '"', - ' cropbottom="', (h - sy - sh) / h, '"', - ' />', - '</g_vml_:group>'); - - this.element_.insertAdjacentHTML('BeforeEnd', vmlStr.join('')); - }; - - contextPrototype.stroke = function(aFill) { - var lineStr = []; - var lineOpen = false; - - var W = 10; - var H = 10; - - lineStr.push('<g_vml_:shape', - ' filled="', !!aFill, '"', - ' style="position:absolute;width:', W, 'px;height:', H, 'px;"', - ' coordorigin="0,0"', - ' coordsize="', Z * W, ',', Z * H, '"', - ' stroked="', !aFill, '"', - ' path="'); - - var newSeq = false; - var min = {x: null, y: null}; - var max = {x: null, y: null}; - - for (var i = 0; i < this.currentPath_.length; i++) { - var p = this.currentPath_[i]; - var c; - - switch (p.type) { - case 'moveTo': - c = p; - lineStr.push(' m ', mr(p.x), ',', mr(p.y)); - break; - case 'lineTo': - lineStr.push(' l ', mr(p.x), ',', mr(p.y)); - break; - case 'close': - lineStr.push(' x '); - p = null; - break; - case 'bezierCurveTo': - lineStr.push(' c ', - mr(p.cp1x), ',', mr(p.cp1y), ',', - mr(p.cp2x), ',', mr(p.cp2y), ',', - mr(p.x), ',', mr(p.y)); - break; - case 'at': - case 'wa': - lineStr.push(' ', p.type, ' ', - mr(p.x - this.arcScaleX_ * p.radius), ',', - mr(p.y - this.arcScaleY_ * p.radius), ' ', - mr(p.x + this.arcScaleX_ * p.radius), ',', - mr(p.y + this.arcScaleY_ * p.radius), ' ', - mr(p.xStart), ',', mr(p.yStart), ' ', - mr(p.xEnd), ',', mr(p.yEnd)); - break; - } - - - // TODO: Following is broken for curves due to - // move to proper paths. - - // Figure out dimensions so we can do gradient fills - // properly - if (p) { - if (min.x == null || p.x < min.x) { - min.x = p.x; - } - if (max.x == null || p.x > max.x) { - max.x = p.x; - } - if (min.y == null || p.y < min.y) { - min.y = p.y; - } - if (max.y == null || p.y > max.y) { - max.y = p.y; - } - } - } - lineStr.push(' ">'); - - if (!aFill) { - appendStroke(this, lineStr); - } else { - appendFill(this, lineStr, min, max); - } - - lineStr.push('</g_vml_:shape>'); - - this.element_.insertAdjacentHTML('beforeEnd', lineStr.join('')); - }; - - function appendStroke(ctx, lineStr) { - var a = processStyle(ctx.strokeStyle); - var color = a.color; - var opacity = a.alpha * ctx.globalAlpha; - var lineWidth = ctx.lineScale_ * ctx.lineWidth; - - // VML cannot correctly render a line if the width is less than 1px. - // In that case, we dilute the color to make the line look thinner. - if (lineWidth < 1) { - opacity *= lineWidth; - } - - lineStr.push( - '<g_vml_:stroke', - ' opacity="', opacity, '"', - ' joinstyle="', ctx.lineJoin, '"', - ' miterlimit="', ctx.miterLimit, '"', - ' endcap="', processLineCap(ctx.lineCap), '"', - ' weight="', lineWidth, 'px"', - ' color="', color, '" />' - ); - } - - function appendFill(ctx, lineStr, min, max) { - var fillStyle = ctx.fillStyle; - var arcScaleX = ctx.arcScaleX_; - var arcScaleY = ctx.arcScaleY_; - var width = max.x - min.x; - var height = max.y - min.y; - if (fillStyle instanceof CanvasGradient_) { - // TODO: Gradients transformed with the transformation matrix. - var angle = 0; - var focus = {x: 0, y: 0}; - - // additional offset - var shift = 0; - // scale factor for offset - var expansion = 1; - - if (fillStyle.type_ == 'gradient') { - var x0 = fillStyle.x0_ / arcScaleX; - var y0 = fillStyle.y0_ / arcScaleY; - var x1 = fillStyle.x1_ / arcScaleX; - var y1 = fillStyle.y1_ / arcScaleY; - var p0 = getCoords(ctx, x0, y0); - var p1 = getCoords(ctx, x1, y1); - var dx = p1.x - p0.x; - var dy = p1.y - p0.y; - angle = Math.atan2(dx, dy) * 180 / Math.PI; - - // The angle should be a non-negative number. - if (angle < 0) { - angle += 360; - } - - // Very small angles produce an unexpected result because they are - // converted to a scientific notation string. - if (angle < 1e-6) { - angle = 0; - } - } else { - var p0 = getCoords(ctx, fillStyle.x0_, fillStyle.y0_); - focus = { - x: (p0.x - min.x) / width, - y: (p0.y - min.y) / height - }; - - width /= arcScaleX * Z; - height /= arcScaleY * Z; - var dimension = m.max(width, height); - shift = 2 * fillStyle.r0_ / dimension; - expansion = 2 * fillStyle.r1_ / dimension - shift; - } - - // We need to sort the color stops in ascending order by offset, - // otherwise IE won't interpret it correctly. - var stops = fillStyle.colors_; - stops.sort(function(cs1, cs2) { - return cs1.offset - cs2.offset; - }); - - var length = stops.length; - var color1 = stops[0].color; - var color2 = stops[length - 1].color; - var opacity1 = stops[0].alpha * ctx.globalAlpha; - var opacity2 = stops[length - 1].alpha * ctx.globalAlpha; - - var colors = []; - for (var i = 0; i < length; i++) { - var stop = stops[i]; - colors.push(stop.offset * expansion + shift + ' ' + stop.color); - } - - // When colors attribute is used, the meanings of opacity and o:opacity2 - // are reversed. - lineStr.push('<g_vml_:fill type="', fillStyle.type_, '"', - ' method="none" focus="100%"', - ' color="', color1, '"', - ' color2="', color2, '"', - ' colors="', colors.join(','), '"', - ' opacity="', opacity2, '"', - ' g_o_:opacity2="', opacity1, '"', - ' angle="', angle, '"', - ' focusposition="', focus.x, ',', focus.y, '" />'); - } else if (fillStyle instanceof CanvasPattern_) { - if (width && height) { - var deltaLeft = -min.x; - var deltaTop = -min.y; - lineStr.push('<g_vml_:fill', - ' position="', - deltaLeft / width * arcScaleX * arcScaleX, ',', - deltaTop / height * arcScaleY * arcScaleY, '"', - ' type="tile"', - // TODO: Figure out the correct size to fit the scale. - //' size="', w, 'px ', h, 'px"', - ' src="', fillStyle.src_, '" />'); - } - } else { - var a = processStyle(ctx.fillStyle); - var color = a.color; - var opacity = a.alpha * ctx.globalAlpha; - lineStr.push('<g_vml_:fill color="', color, '" opacity="', opacity, - '" />'); - } - } - - contextPrototype.fill = function() { - this.stroke(true); - }; - - contextPrototype.closePath = function() { - this.currentPath_.push({type: 'close'}); - }; - - function getCoords(ctx, aX, aY) { - var m = ctx.m_; - return { - x: Z * (aX * m[0][0] + aY * m[1][0] + m[2][0]) - Z2, - y: Z * (aX * m[0][1] + aY * m[1][1] + m[2][1]) - Z2 - }; - }; - - contextPrototype.save = function() { - var o = {}; - copyState(this, o); - this.aStack_.push(o); - this.mStack_.push(this.m_); - this.m_ = matrixMultiply(createMatrixIdentity(), this.m_); - }; - - contextPrototype.restore = function() { - if (this.aStack_.length) { - copyState(this.aStack_.pop(), this); - this.m_ = this.mStack_.pop(); - } - }; - - function matrixIsFinite(m) { - return isFinite(m[0][0]) && isFinite(m[0][1]) && - isFinite(m[1][0]) && isFinite(m[1][1]) && - isFinite(m[2][0]) && isFinite(m[2][1]); - } - - function setM(ctx, m, updateLineScale) { - if (!matrixIsFinite(m)) { - return; - } - ctx.m_ = m; - - if (updateLineScale) { - // Get the line scale. - // Determinant of this.m_ means how much the area is enlarged by the - // transformation. So its square root can be used as a scale factor - // for width. - var det = m[0][0] * m[1][1] - m[0][1] * m[1][0]; - ctx.lineScale_ = sqrt(abs(det)); - } - } - - contextPrototype.translate = function(aX, aY) { - var m1 = [ - [1, 0, 0], - [0, 1, 0], - [aX, aY, 1] - ]; - - setM(this, matrixMultiply(m1, this.m_), false); - }; - - contextPrototype.rotate = function(aRot) { - var c = mc(aRot); - var s = ms(aRot); - - var m1 = [ - [c, s, 0], - [-s, c, 0], - [0, 0, 1] - ]; - - setM(this, matrixMultiply(m1, this.m_), false); - }; - - contextPrototype.scale = function(aX, aY) { - this.arcScaleX_ *= aX; - this.arcScaleY_ *= aY; - var m1 = [ - [aX, 0, 0], - [0, aY, 0], - [0, 0, 1] - ]; - - setM(this, matrixMultiply(m1, this.m_), true); - }; - - contextPrototype.transform = function(m11, m12, m21, m22, dx, dy) { - var m1 = [ - [m11, m12, 0], - [m21, m22, 0], - [dx, dy, 1] - ]; - - setM(this, matrixMultiply(m1, this.m_), true); - }; - - contextPrototype.setTransform = function(m11, m12, m21, m22, dx, dy) { - var m = [ - [m11, m12, 0], - [m21, m22, 0], - [dx, dy, 1] - ]; - - setM(this, m, true); - }; - - /** - * The text drawing function. - * The maxWidth argument isn't taken in account, since no browser supports - * it yet. - */ - contextPrototype.drawText_ = function(text, x, y, maxWidth, stroke) { - var m = this.m_, - delta = 1000, - left = 0, - right = delta, - offset = {x: 0, y: 0}, - lineStr = []; - - var fontStyle = getComputedStyle(processFontStyle(this.font), - this.element_); - - var fontStyleString = buildStyle(fontStyle); - - var elementStyle = this.element_.currentStyle; - var textAlign = this.textAlign.toLowerCase(); - switch (textAlign) { - case 'left': - case 'center': - case 'right': - break; - case 'end': - textAlign = elementStyle.direction == 'ltr' ? 'right' : 'left'; - break; - case 'start': - textAlign = elementStyle.direction == 'rtl' ? 'right' : 'left'; - break; - default: - textAlign = 'left'; - } - - // 1.75 is an arbitrary number, as there is no info about the text baseline - switch (this.textBaseline) { - case 'hanging': - case 'top': - offset.y = fontStyle.size / 1.75; - break; - case 'middle': - break; - default: - case null: - case 'alphabetic': - case 'ideographic': - case 'bottom': - offset.y = -fontStyle.size / 2.25; - break; - } - - switch(textAlign) { - case 'right': - left = delta; - right = 0.05; - break; - case 'center': - left = right = delta / 2; - break; - } - - var d = getCoords(this, x + offset.x, y + offset.y); - - lineStr.push('<g_vml_:line from="', -left ,' 0" to="', right ,' 0.05" ', - ' coordsize="100 100" coordorigin="0 0"', - ' filled="', !stroke, '" stroked="', !!stroke, - '" style="position:absolute;width:1px;height:1px;">'); - - if (stroke) { - appendStroke(this, lineStr); - } else { - // TODO: Fix the min and max params. - appendFill(this, lineStr, {x: -left, y: 0}, - {x: right, y: fontStyle.size}); - } - - var skewM = m[0][0].toFixed(3) + ',' + m[1][0].toFixed(3) + ',' + - m[0][1].toFixed(3) + ',' + m[1][1].toFixed(3) + ',0,0'; - - var skewOffset = mr(d.x / Z) + ',' + mr(d.y / Z); - - lineStr.push('<g_vml_:skew on="t" matrix="', skewM ,'" ', - ' offset="', skewOffset, '" origin="', left ,' 0" />', - '<g_vml_:path textpathok="true" />', - '<g_vml_:textpath on="true" string="', - encodeHtmlAttribute(text), - '" style="v-text-align:', textAlign, - ';font:', encodeHtmlAttribute(fontStyleString), - '" /></g_vml_:line>'); - - this.element_.insertAdjacentHTML('beforeEnd', lineStr.join('')); - }; - - contextPrototype.fillText = function(text, x, y, maxWidth) { - this.drawText_(text, x, y, maxWidth, false); - }; - - contextPrototype.strokeText = function(text, x, y, maxWidth) { - this.drawText_(text, x, y, maxWidth, true); - }; - - contextPrototype.measureText = function(text) { - if (!this.textMeasureEl_) { - var s = '<span style="position:absolute;' + - 'top:-20000px;left:0;padding:0;margin:0;border:none;' + - 'white-space:pre;"></span>'; - this.element_.insertAdjacentHTML('beforeEnd', s); - this.textMeasureEl_ = this.element_.lastChild; - } - var doc = this.element_.ownerDocument; - this.textMeasureEl_.innerHTML = ''; - this.textMeasureEl_.style.font = this.font; - // Don't use innerHTML or innerText because they allow markup/whitespace. - this.textMeasureEl_.appendChild(doc.createTextNode(text)); - return {width: this.textMeasureEl_.offsetWidth}; - }; - - /******** STUBS ********/ - contextPrototype.clip = function() { - // TODO: Implement - }; - - contextPrototype.arcTo = function() { - // TODO: Implement - }; - - contextPrototype.createPattern = function(image, repetition) { - return new CanvasPattern_(image, repetition); - }; - - // Gradient / Pattern Stubs - function CanvasGradient_(aType) { - this.type_ = aType; - this.x0_ = 0; - this.y0_ = 0; - this.r0_ = 0; - this.x1_ = 0; - this.y1_ = 0; - this.r1_ = 0; - this.colors_ = []; - } - - CanvasGradient_.prototype.addColorStop = function(aOffset, aColor) { - aColor = processStyle(aColor); - this.colors_.push({offset: aOffset, - color: aColor.color, - alpha: aColor.alpha}); - }; - - function CanvasPattern_(image, repetition) { - assertImageIsValid(image); - switch (repetition) { - case 'repeat': - case null: - case '': - this.repetition_ = 'repeat'; - break - case 'repeat-x': - case 'repeat-y': - case 'no-repeat': - this.repetition_ = repetition; - break; - default: - throwException('SYNTAX_ERR'); - } - - this.src_ = image.src; - this.width_ = image.width; - this.height_ = image.height; - } - - function throwException(s) { - throw new DOMException_(s); - } - - function assertImageIsValid(img) { - if (!img || img.nodeType != 1 || img.tagName != 'IMG') { - throwException('TYPE_MISMATCH_ERR'); - } - if (img.readyState != 'complete') { - throwException('INVALID_STATE_ERR'); - } - } - - function DOMException_(s) { - this.code = this[s]; - this.message = s +': DOM Exception ' + this.code; - } - var p = DOMException_.prototype = new Error; - p.INDEX_SIZE_ERR = 1; - p.DOMSTRING_SIZE_ERR = 2; - p.HIERARCHY_REQUEST_ERR = 3; - p.WRONG_DOCUMENT_ERR = 4; - p.INVALID_CHARACTER_ERR = 5; - p.NO_DATA_ALLOWED_ERR = 6; - p.NO_MODIFICATION_ALLOWED_ERR = 7; - p.NOT_FOUND_ERR = 8; - p.NOT_SUPPORTED_ERR = 9; - p.INUSE_ATTRIBUTE_ERR = 10; - p.INVALID_STATE_ERR = 11; - p.SYNTAX_ERR = 12; - p.INVALID_MODIFICATION_ERR = 13; - p.NAMESPACE_ERR = 14; - p.INVALID_ACCESS_ERR = 15; - p.VALIDATION_ERR = 16; - p.TYPE_MISMATCH_ERR = 17; - - // set up externs - G_vmlCanvasManager = G_vmlCanvasManager_; - CanvasRenderingContext2D = CanvasRenderingContext2D_; - CanvasGradient = CanvasGradient_; - CanvasPattern = CanvasPattern_; - DOMException = DOMException_; -})(); - -} // if diff --git a/addons/web_graph/static/lib/flotr2/lib/imagediff.js b/addons/web_graph/static/lib/flotr2/lib/imagediff.js deleted file mode 100644 index ac1d2745018..00000000000 --- a/addons/web_graph/static/lib/flotr2/lib/imagediff.js +++ /dev/null @@ -1,343 +0,0 @@ -/*! imagediff.js 1.0.2 - * (c) 2011 Carl Sutherland, Humble Software Development - * imagediff.js is freely distributable under the MIT license. - * Thanks to Jacob Thornton for the node/amd integration bits. - * For details and documentation: - * https://github.com/HumbleSoftware/js-imagediff - */ -(function (name, definition) { - var root = this; - if (typeof module != 'undefined') { - module.exports = definition(); - } else if (typeof define == 'function' && typeof define.amd == 'object') { - define(definition); - } else { - root[name] = definition(root, name); - } -})('imagediff', function (root, name) { - - var - TYPE_ARRAY = '[object Array]', - TYPE_CANVAS = '[object HTMLCanvasElement]', - TYPE_CONTEXT = '[object CanvasRenderingContext2D]', - TYPE_IMAGE = '[object HTMLImageElement]', - - OBJECT = 'object', - UNDEFINED = 'undefined', - - canvas = getCanvas(), - context = canvas.getContext('2d'), - previous = root[name], - imagediff, jasmine; - - // Creation - function getCanvas (width, height) { - var - canvas = document.createElement('canvas'); - if (width) canvas.width = width; - if (height) canvas.height = height; - return canvas; - } - function getImageData (width, height) { - canvas.width = width; - canvas.height = height; - context.clearRect(0, 0, width, height); - return context.createImageData(width, height); - } - - - // Type Checking - function isImage (object) { - return isType(object, TYPE_IMAGE); - } - function isCanvas (object) { - return isType(object, TYPE_CANVAS); - } - function isContext (object) { - return isType(object, TYPE_CONTEXT); - } - function isImageData (object) { - var - imageData = getImageData(1, 1); - isImageData = function (object) { - return (object && imageData.constructor === object.constructor ? true : false); - }; - return isImageData(object); - } - function isImageType (object) { - return ( - isImage(object) || - isCanvas(object) || - isContext(object) || - isImageData(object) - ); - } - function isType (object, type) { - return typeof (object) === 'object' && Object.prototype.toString.apply(object) === type; - } - - - // Type Conversion - function copyImageData (imageData) { - var - height = imageData.height, - width = imageData.width; - canvas.width = width; - canvas.height = height; - context.putImageData(imageData, 0, 0); - return context.getImageData(0, 0, width, height); - } - function toImageData (object) { - if (isImage(object)) { return toImageDataFromImage(object); } - if (isCanvas(object)) { return toImageDataFromCanvas(object); } - if (isContext(object)) { return toImageDataFromContext(object); } - if (isImageData(object)) { return object; } - } - function toImageDataFromImage (image) { - var - height = image.height, - width = image.width; - canvas.width = width; - canvas.height = height; - context.clearRect(0, 0, width, height); - context.drawImage(image, 0, 0); - return context.getImageData(0, 0, width, height); - } - function toImageDataFromCanvas (canvas) { - var - height = canvas.height, - width = canvas.width, - context = canvas.getContext('2d'); - return context.getImageData(0, 0, width, height); - } - function toImageDataFromContext (context) { - var - canvas = context.canvas, - height = canvas.height, - width = canvas.width; - return context.getImageData(0, 0, width, height); - } - function toCanvas (object) { - var - data = toImageData(object), - canvas = getCanvas(data.width, data.height), - context = canvas.getContext('2d'); - - context.putImageData(data, 0, 0); - return canvas; - } - - - // ImageData Equality Operators - function equalWidth (a, b) { - return a.width === b.width; - } - function equalHeight (a, b) { - return a.height === b.height; - } - function equalDimensions (a, b) { - return equalHeight(a, b) && equalWidth(a, b); - } - function equal (a, b, tolerance) { - - var - aData = a.data, - bData = b.data, - length = aData.length, - tolerance = tolerance || 0, - i; - - if (!equalDimensions(a, b)) return false; - for (i = length; i--;) if (aData[i] !== bData[i] && Math.abs(aData[i] - bData[i]) > tolerance) return false; - - return true; - } - - - // Diff - function diff (a, b) { - return (equalDimensions(a, b) ? diffEqual : diffUnequal)(a, b); - } - function diffEqual (a, b) { - - var - height = a.height, - width = a.width, - c = getImageData(width, height), // c = a - b - aData = a.data, - bData = b.data, - cData = c.data, - length = cData.length, - row, column, - i, j, k, v; - - for (i = 0; i < length; i += 4) { - cData[i] = Math.abs(aData[i] - bData[i]); - cData[i+1] = Math.abs(aData[i+1] - bData[i+1]); - cData[i+2] = Math.abs(aData[i+2] - bData[i+2]); - cData[i+3] = Math.abs(255 - aData[i+3] - bData[i+3]); - } - - return c; - } - function diffUnequal (a, b) { - - var - height = Math.max(a.height, b.height), - width = Math.max(a.width, b.width), - c = getImageData(width, height), // c = a - b - aData = a.data, - bData = b.data, - cData = c.data, - rowOffset, - columnOffset, - row, column, - i, j, k, v; - - - for (i = cData.length - 1; i > 0; i = i - 4) { - cData[i] = 255; - } - - // Add First Image - offsets(a); - for (row = a.height; row--;){ - for (column = a.width; column--;) { - i = 4 * ((row + rowOffset) * width + (column + columnOffset)); - j = 4 * (row * a.width + column); - cData[i+0] = aData[j+0]; // r - cData[i+1] = aData[j+1]; // g - cData[i+2] = aData[j+2]; // b - // cData[i+3] = aData[j+3]; // a - } - } - - // Subtract Second Image - offsets(b); - for (row = b.height; row--;){ - for (column = b.width; column--;) { - i = 4 * ((row + rowOffset) * width + (column + columnOffset)); - j = 4 * (row * b.width + column); - cData[i+0] = Math.abs(cData[i+0] - bData[j+0]); // r - cData[i+1] = Math.abs(cData[i+1] - bData[j+1]); // g - cData[i+2] = Math.abs(cData[i+2] - bData[j+2]); // b - } - } - - // Helpers - function offsets (imageData) { - rowOffset = Math.floor((height - imageData.height) / 2); - columnOffset = Math.floor((width - imageData.width) / 2); - } - - return c; - } - - - // Validation - function checkType () { - var i; - for (i = 0; i < arguments.length; i++) { - if (!isImageType(arguments[i])) { - throw { - name : 'ImageTypeError', - message : 'Submitted object was not an image.' - }; - } - } - } - - - // Jasmine Matchers - function get (element, content) { - element = document.createElement(element); - if (element && content) { - element.innerHTML = content; - } - return element; - } - jasmine = { - - toBeImageData : function () { - return imagediff.isImageData(this.actual); - }, - - toImageDiffEqual : function (expected, tolerance) { - - this.message = function() { - - var - div = get('div'), - a = get('div', '<div>Actual:</div>'), - b = get('div', '<div>Expected:</div>'), - c = get('div', '<div>Diff:</div>'), - diff = imagediff.diff(this.actual, expected), - canvas = getCanvas(), - context; - - canvas.height = diff.height; - canvas.width = diff.width; - - context = canvas.getContext('2d'); - context.putImageData(diff, 0, 0); - - a.appendChild(toCanvas(this.actual)); - b.appendChild(toCanvas(expected)); - c.appendChild(canvas); - - div.appendChild(a); - div.appendChild(b); - div.appendChild(c); - - return [ - div, - "Expected not to be equal." - ]; - }; - - return imagediff.equal(this.actual, expected, tolerance); - } - }; - - // Definition - imagediff = { - - createCanvas : getCanvas, - createImageData : getImageData, - - isImage : isImage, - isCanvas : isCanvas, - isContext : isContext, - isImageData : isImageData, - isImageType : isImageType, - - toImageData : function (object) { - checkType(object); - if (isImageData(object)) { return copyImageData(object); } - return toImageData(object); - }, - - equal : function (a, b, tolerance) { - checkType(a, b); - a = toImageData(a); - b = toImageData(b); - return equal(a, b, tolerance); - }, - diff : function (a, b) { - checkType(a, b); - a = toImageData(a); - b = toImageData(b); - return diff(a, b); - }, - - jasmine : jasmine, - - // Compatibility - noConflict : function () { - root[name] = previous; - return imagediff; - } - }; - - return imagediff; -}); diff --git a/addons/web_graph/static/lib/flotr2/lib/jasmine/MIT.LICENSE b/addons/web_graph/static/lib/flotr2/lib/jasmine/MIT.LICENSE deleted file mode 100644 index 7c435baaec8..00000000000 --- a/addons/web_graph/static/lib/flotr2/lib/jasmine/MIT.LICENSE +++ /dev/null @@ -1,20 +0,0 @@ -Copyright (c) 2008-2011 Pivotal Labs - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/addons/web_graph/static/lib/flotr2/lib/jasmine/jasmine-html.js b/addons/web_graph/static/lib/flotr2/lib/jasmine/jasmine-html.js deleted file mode 100644 index 73834010f60..00000000000 --- a/addons/web_graph/static/lib/flotr2/lib/jasmine/jasmine-html.js +++ /dev/null @@ -1,190 +0,0 @@ -jasmine.TrivialReporter = function(doc) { - this.document = doc || document; - this.suiteDivs = {}; - this.logRunningSpecs = false; -}; - -jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) { - var el = document.createElement(type); - - for (var i = 2; i < arguments.length; i++) { - var child = arguments[i]; - - if (typeof child === 'string') { - el.appendChild(document.createTextNode(child)); - } else { - if (child) { el.appendChild(child); } - } - } - - for (var attr in attrs) { - if (attr == "className") { - el[attr] = attrs[attr]; - } else { - el.setAttribute(attr, attrs[attr]); - } - } - - return el; -}; - -jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) { - var showPassed, showSkipped; - - this.outerDiv = this.createDom('div', { className: 'jasmine_reporter' }, - this.createDom('div', { className: 'banner' }, - this.createDom('div', { className: 'logo' }, - this.createDom('span', { className: 'title' }, "Jasmine"), - this.createDom('span', { className: 'version' }, runner.env.versionString())), - this.createDom('div', { className: 'options' }, - "Show ", - showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }), - this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "), - showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }), - this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped") - ) - ), - - this.runnerDiv = this.createDom('div', { className: 'runner running' }, - this.createDom('a', { className: 'run_spec', href: '?' }, "run all"), - this.runnerMessageSpan = this.createDom('span', {}, "Running..."), - this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, "")) - ); - - this.document.body.appendChild(this.outerDiv); - - var suites = runner.suites(); - for (var i = 0; i < suites.length; i++) { - var suite = suites[i]; - var suiteDiv = this.createDom('div', { className: 'suite' }, - this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"), - this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description)); - this.suiteDivs[suite.id] = suiteDiv; - var parentDiv = this.outerDiv; - if (suite.parentSuite) { - parentDiv = this.suiteDivs[suite.parentSuite.id]; - } - parentDiv.appendChild(suiteDiv); - } - - this.startedAt = new Date(); - - var self = this; - showPassed.onclick = function(evt) { - if (showPassed.checked) { - self.outerDiv.className += ' show-passed'; - } else { - self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, ''); - } - }; - - showSkipped.onclick = function(evt) { - if (showSkipped.checked) { - self.outerDiv.className += ' show-skipped'; - } else { - self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, ''); - } - }; -}; - -jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) { - var results = runner.results(); - var className = (results.failedCount > 0) ? "runner failed" : "runner passed"; - this.runnerDiv.setAttribute("class", className); - //do it twice for IE - this.runnerDiv.setAttribute("className", className); - var specs = runner.specs(); - var specCount = 0; - for (var i = 0; i < specs.length; i++) { - if (this.specFilter(specs[i])) { - specCount++; - } - } - var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s"); - message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s"; - this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild); - - this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString())); -}; - -jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) { - var results = suite.results(); - var status = results.passed() ? 'passed' : 'failed'; - if (results.totalCount === 0) { // todo: change this to check results.skipped - status = 'skipped'; - } - this.suiteDivs[suite.id].className += " " + status; -}; - -jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) { - if (this.logRunningSpecs) { - this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...'); - } -}; - -jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) { - var results = spec.results(); - var status = results.passed() ? 'passed' : 'failed'; - if (results.skipped) { - status = 'skipped'; - } - var specDiv = this.createDom('div', { className: 'spec ' + status }, - this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"), - this.createDom('a', { - className: 'description', - href: '?spec=' + encodeURIComponent(spec.getFullName()), - title: spec.getFullName() - }, spec.description)); - - - var resultItems = results.getItems(); - var messagesDiv = this.createDom('div', { className: 'messages' }); - for (var i = 0; i < resultItems.length; i++) { - var result = resultItems[i]; - - if (result.type == 'log') { - messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString())); - } else if (result.type == 'expect' && result.passed && !result.passed()) { - messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message)); - - if (result.trace.stack) { - messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack)); - } - } - } - - if (messagesDiv.childNodes.length > 0) { - specDiv.appendChild(messagesDiv); - } - - this.suiteDivs[spec.suite.id].appendChild(specDiv); -}; - -jasmine.TrivialReporter.prototype.log = function() { - var console = jasmine.getGlobal().console; - if (console && console.log) { - if (console.log.apply) { - console.log.apply(console, arguments); - } else { - console.log(arguments); // ie fix: console.log.apply doesn't exist on ie - } - } -}; - -jasmine.TrivialReporter.prototype.getLocation = function() { - return this.document.location; -}; - -jasmine.TrivialReporter.prototype.specFilter = function(spec) { - var paramMap = {}; - var params = this.getLocation().search.substring(1).split('&'); - for (var i = 0; i < params.length; i++) { - var p = params[i].split('='); - paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]); - } - - if (!paramMap.spec) { - return true; - } - return spec.getFullName().indexOf(paramMap.spec) === 0; -}; diff --git a/addons/web_graph/static/lib/flotr2/lib/jasmine/jasmine.css b/addons/web_graph/static/lib/flotr2/lib/jasmine/jasmine.css deleted file mode 100644 index 6583fe7c66d..00000000000 --- a/addons/web_graph/static/lib/flotr2/lib/jasmine/jasmine.css +++ /dev/null @@ -1,166 +0,0 @@ -body { - font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; -} - - -.jasmine_reporter a:visited, .jasmine_reporter a { - color: #303; -} - -.jasmine_reporter a:hover, .jasmine_reporter a:active { - color: blue; -} - -.run_spec { - float:right; - padding-right: 5px; - font-size: .8em; - text-decoration: none; -} - -.jasmine_reporter { - margin: 0 5px; -} - -.banner { - color: #303; - background-color: #fef; - padding: 5px; -} - -.logo { - float: left; - font-size: 1.1em; - padding-left: 5px; -} - -.logo .version { - font-size: .6em; - padding-left: 1em; -} - -.runner.running { - background-color: yellow; -} - - -.options { - text-align: right; - font-size: .8em; -} - - - - -.suite { - border: 1px outset gray; - margin: 5px 0; - padding-left: 1em; -} - -.suite .suite { - margin: 5px; -} - -.suite.passed { - background-color: #dfd; -} - -.suite.failed { - background-color: #fdd; -} - -.spec { - margin: 5px; - padding-left: 1em; - clear: both; -} - -.spec.failed, .spec.passed, .spec.skipped { - padding-bottom: 5px; - border: 1px solid gray; -} - -.spec.failed { - background-color: #fbb; - border-color: red; -} - -.spec.passed { - background-color: #bfb; - border-color: green; -} - -.spec.skipped { - background-color: #bbb; -} - -.messages { - border-left: 1px dashed gray; - padding-left: 1em; - padding-right: 1em; -} - -.passed { - background-color: #cfc; - display: none; -} - -.failed { - background-color: #fbb; -} - -.skipped { - color: #777; - background-color: #eee; - display: none; -} - - -/*.resultMessage {*/ - /*white-space: pre;*/ -/*}*/ - -.resultMessage span.result { - display: block; - line-height: 2em; - color: black; -} - -.resultMessage .mismatch { - color: black; -} - -.stackTrace { - white-space: pre; - font-size: .8em; - margin-left: 10px; - max-height: 5em; - overflow: auto; - border: 1px inset red; - padding: 1em; - background: #eef; -} - -.finished-at { - padding-left: 1em; - font-size: .6em; -} - -.show-passed .passed, -.show-skipped .skipped { - display: block; -} - - -#jasmine_content { - position:fixed; - right: 100%; -} - -.runner { - border: 1px solid gray; - display: block; - margin: 5px 0; - padding: 2px 0 2px 10px; -} diff --git a/addons/web_graph/static/lib/flotr2/lib/jasmine/jasmine.js b/addons/web_graph/static/lib/flotr2/lib/jasmine/jasmine.js deleted file mode 100644 index c3d2dc7d2d3..00000000000 --- a/addons/web_graph/static/lib/flotr2/lib/jasmine/jasmine.js +++ /dev/null @@ -1,2476 +0,0 @@ -var isCommonJS = typeof window == "undefined"; - -/** - * Top level namespace for Jasmine, a lightweight JavaScript BDD/spec/testing framework. - * - * @namespace - */ -var jasmine = {}; -if (isCommonJS) exports.jasmine = jasmine; -/** - * @private - */ -jasmine.unimplementedMethod_ = function() { - throw new Error("unimplemented method"); -}; - -/** - * Use <code>jasmine.undefined</code> instead of <code>undefined</code>, since <code>undefined</code> is just - * a plain old variable and may be redefined by somebody else. - * - * @private - */ -jasmine.undefined = jasmine.___undefined___; - -/** - * Show diagnostic messages in the console if set to true - * - */ -jasmine.VERBOSE = false; - -/** - * Default interval in milliseconds for event loop yields (e.g. to allow network activity or to refresh the screen with the HTML-based runner). Small values here may result in slow test running. Zero means no updates until all tests have completed. - * - */ -jasmine.DEFAULT_UPDATE_INTERVAL = 250; - -/** - * Default timeout interval in milliseconds for waitsFor() blocks. - */ -jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000; - -jasmine.getGlobal = function() { - function getGlobal() { - return this; - } - - return getGlobal(); -}; - -/** - * Allows for bound functions to be compared. Internal use only. - * - * @ignore - * @private - * @param base {Object} bound 'this' for the function - * @param name {Function} function to find - */ -jasmine.bindOriginal_ = function(base, name) { - var original = base[name]; - if (original.apply) { - return function() { - return original.apply(base, arguments); - }; - } else { - // IE support - return jasmine.getGlobal()[name]; - } -}; - -jasmine.setTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'setTimeout'); -jasmine.clearTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearTimeout'); -jasmine.setInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'setInterval'); -jasmine.clearInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearInterval'); - -jasmine.MessageResult = function(values) { - this.type = 'log'; - this.values = values; - this.trace = new Error(); // todo: test better -}; - -jasmine.MessageResult.prototype.toString = function() { - var text = ""; - for (var i = 0; i < this.values.length; i++) { - if (i > 0) text += " "; - if (jasmine.isString_(this.values[i])) { - text += this.values[i]; - } else { - text += jasmine.pp(this.values[i]); - } - } - return text; -}; - -jasmine.ExpectationResult = function(params) { - this.type = 'expect'; - this.matcherName = params.matcherName; - this.passed_ = params.passed; - this.expected = params.expected; - this.actual = params.actual; - this.message = this.passed_ ? 'Passed.' : params.message; - - var trace = (params.trace || new Error(this.message)); - this.trace = this.passed_ ? '' : trace; -}; - -jasmine.ExpectationResult.prototype.toString = function () { - return this.message; -}; - -jasmine.ExpectationResult.prototype.passed = function () { - return this.passed_; -}; - -/** - * Getter for the Jasmine environment. Ensures one gets created - */ -jasmine.getEnv = function() { - var env = jasmine.currentEnv_ = jasmine.currentEnv_ || new jasmine.Env(); - return env; -}; - -/** - * @ignore - * @private - * @param value - * @returns {Boolean} - */ -jasmine.isArray_ = function(value) { - return jasmine.isA_("Array", value); -}; - -/** - * @ignore - * @private - * @param value - * @returns {Boolean} - */ -jasmine.isString_ = function(value) { - return jasmine.isA_("String", value); -}; - -/** - * @ignore - * @private - * @param value - * @returns {Boolean} - */ -jasmine.isNumber_ = function(value) { - return jasmine.isA_("Number", value); -}; - -/** - * @ignore - * @private - * @param {String} typeName - * @param value - * @returns {Boolean} - */ -jasmine.isA_ = function(typeName, value) { - return Object.prototype.toString.apply(value) === '[object ' + typeName + ']'; -}; - -/** - * Pretty printer for expecations. Takes any object and turns it into a human-readable string. - * - * @param value {Object} an object to be outputted - * @returns {String} - */ -jasmine.pp = function(value) { - var stringPrettyPrinter = new jasmine.StringPrettyPrinter(); - stringPrettyPrinter.format(value); - return stringPrettyPrinter.string; -}; - -/** - * Returns true if the object is a DOM Node. - * - * @param {Object} obj object to check - * @returns {Boolean} - */ -jasmine.isDomNode = function(obj) { - return obj.nodeType > 0; -}; - -/** - * Returns a matchable 'generic' object of the class type. For use in expecations of type when values don't matter. - * - * @example - * // don't care about which function is passed in, as long as it's a function - * expect(mySpy).toHaveBeenCalledWith(jasmine.any(Function)); - * - * @param {Class} clazz - * @returns matchable object of the type clazz - */ -jasmine.any = function(clazz) { - return new jasmine.Matchers.Any(clazz); -}; - -/** - * Jasmine Spies are test doubles that can act as stubs, spies, fakes or when used in an expecation, mocks. - * - * Spies should be created in test setup, before expectations. They can then be checked, using the standard Jasmine - * expectation syntax. Spies can be checked if they were called or not and what the calling params were. - * - * A Spy has the following fields: wasCalled, callCount, mostRecentCall, and argsForCall (see docs). - * - * Spies are torn down at the end of every spec. - * - * Note: Do <b>not</b> call new jasmine.Spy() directly - a spy must be created using spyOn, jasmine.createSpy or jasmine.createSpyObj. - * - * @example - * // a stub - * var myStub = jasmine.createSpy('myStub'); // can be used anywhere - * - * // spy example - * var foo = { - * not: function(bool) { return !bool; } - * } - * - * // actual foo.not will not be called, execution stops - * spyOn(foo, 'not'); - - // foo.not spied upon, execution will continue to implementation - * spyOn(foo, 'not').andCallThrough(); - * - * // fake example - * var foo = { - * not: function(bool) { return !bool; } - * } - * - * // foo.not(val) will return val - * spyOn(foo, 'not').andCallFake(function(value) {return value;}); - * - * // mock example - * foo.not(7 == 7); - * expect(foo.not).toHaveBeenCalled(); - * expect(foo.not).toHaveBeenCalledWith(true); - * - * @constructor - * @see spyOn, jasmine.createSpy, jasmine.createSpyObj - * @param {String} name - */ -jasmine.Spy = function(name) { - /** - * The name of the spy, if provided. - */ - this.identity = name || 'unknown'; - /** - * Is this Object a spy? - */ - this.isSpy = true; - /** - * The actual function this spy stubs. - */ - this.plan = function() { - }; - /** - * Tracking of the most recent call to the spy. - * @example - * var mySpy = jasmine.createSpy('foo'); - * mySpy(1, 2); - * mySpy.mostRecentCall.args = [1, 2]; - */ - this.mostRecentCall = {}; - - /** - * Holds arguments for each call to the spy, indexed by call count - * @example - * var mySpy = jasmine.createSpy('foo'); - * mySpy(1, 2); - * mySpy(7, 8); - * mySpy.mostRecentCall.args = [7, 8]; - * mySpy.argsForCall[0] = [1, 2]; - * mySpy.argsForCall[1] = [7, 8]; - */ - this.argsForCall = []; - this.calls = []; -}; - -/** - * Tells a spy to call through to the actual implemenatation. - * - * @example - * var foo = { - * bar: function() { // do some stuff } - * } - * - * // defining a spy on an existing property: foo.bar - * spyOn(foo, 'bar').andCallThrough(); - */ -jasmine.Spy.prototype.andCallThrough = function() { - this.plan = this.originalValue; - return this; -}; - -/** - * For setting the return value of a spy. - * - * @example - * // defining a spy from scratch: foo() returns 'baz' - * var foo = jasmine.createSpy('spy on foo').andReturn('baz'); - * - * // defining a spy on an existing property: foo.bar() returns 'baz' - * spyOn(foo, 'bar').andReturn('baz'); - * - * @param {Object} value - */ -jasmine.Spy.prototype.andReturn = function(value) { - this.plan = function() { - return value; - }; - return this; -}; - -/** - * For throwing an exception when a spy is called. - * - * @example - * // defining a spy from scratch: foo() throws an exception w/ message 'ouch' - * var foo = jasmine.createSpy('spy on foo').andThrow('baz'); - * - * // defining a spy on an existing property: foo.bar() throws an exception w/ message 'ouch' - * spyOn(foo, 'bar').andThrow('baz'); - * - * @param {String} exceptionMsg - */ -jasmine.Spy.prototype.andThrow = function(exceptionMsg) { - this.plan = function() { - throw exceptionMsg; - }; - return this; -}; - -/** - * Calls an alternate implementation when a spy is called. - * - * @example - * var baz = function() { - * // do some stuff, return something - * } - * // defining a spy from scratch: foo() calls the function baz - * var foo = jasmine.createSpy('spy on foo').andCall(baz); - * - * // defining a spy on an existing property: foo.bar() calls an anonymnous function - * spyOn(foo, 'bar').andCall(function() { return 'baz';} ); - * - * @param {Function} fakeFunc - */ -jasmine.Spy.prototype.andCallFake = function(fakeFunc) { - this.plan = fakeFunc; - return this; -}; - -/** - * Resets all of a spy's the tracking variables so that it can be used again. - * - * @example - * spyOn(foo, 'bar'); - * - * foo.bar(); - * - * expect(foo.bar.callCount).toEqual(1); - * - * foo.bar.reset(); - * - * expect(foo.bar.callCount).toEqual(0); - */ -jasmine.Spy.prototype.reset = function() { - this.wasCalled = false; - this.callCount = 0; - this.argsForCall = []; - this.calls = []; - this.mostRecentCall = {}; -}; - -jasmine.createSpy = function(name) { - - var spyObj = function() { - spyObj.wasCalled = true; - spyObj.callCount++; - var args = jasmine.util.argsToArray(arguments); - spyObj.mostRecentCall.object = this; - spyObj.mostRecentCall.args = args; - spyObj.argsForCall.push(args); - spyObj.calls.push({object: this, args: args}); - return spyObj.plan.apply(this, arguments); - }; - - var spy = new jasmine.Spy(name); - - for (var prop in spy) { - spyObj[prop] = spy[prop]; - } - - spyObj.reset(); - - return spyObj; -}; - -/** - * Determines whether an object is a spy. - * - * @param {jasmine.Spy|Object} putativeSpy - * @returns {Boolean} - */ -jasmine.isSpy = function(putativeSpy) { - return putativeSpy && putativeSpy.isSpy; -}; - -/** - * Creates a more complicated spy: an Object that has every property a function that is a spy. Used for stubbing something - * large in one call. - * - * @param {String} baseName name of spy class - * @param {Array} methodNames array of names of methods to make spies - */ -jasmine.createSpyObj = function(baseName, methodNames) { - if (!jasmine.isArray_(methodNames) || methodNames.length === 0) { - throw new Error('createSpyObj requires a non-empty array of method names to create spies for'); - } - var obj = {}; - for (var i = 0; i < methodNames.length; i++) { - obj[methodNames[i]] = jasmine.createSpy(baseName + '.' + methodNames[i]); - } - return obj; -}; - -/** - * All parameters are pretty-printed and concatenated together, then written to the current spec's output. - * - * Be careful not to leave calls to <code>jasmine.log</code> in production code. - */ -jasmine.log = function() { - var spec = jasmine.getEnv().currentSpec; - spec.log.apply(spec, arguments); -}; - -/** - * Function that installs a spy on an existing object's method name. Used within a Spec to create a spy. - * - * @example - * // spy example - * var foo = { - * not: function(bool) { return !bool; } - * } - * spyOn(foo, 'not'); // actual foo.not will not be called, execution stops - * - * @see jasmine.createSpy - * @param obj - * @param methodName - * @returns a Jasmine spy that can be chained with all spy methods - */ -var spyOn = function(obj, methodName) { - return jasmine.getEnv().currentSpec.spyOn(obj, methodName); -}; -if (isCommonJS) exports.spyOn = spyOn; - -/** - * Creates a Jasmine spec that will be added to the current suite. - * - * // TODO: pending tests - * - * @example - * it('should be true', function() { - * expect(true).toEqual(true); - * }); - * - * @param {String} desc description of this specification - * @param {Function} func defines the preconditions and expectations of the spec - */ -var it = function(desc, func) { - return jasmine.getEnv().it(desc, func); -}; -if (isCommonJS) exports.it = it; - -/** - * Creates a <em>disabled</em> Jasmine spec. - * - * A convenience method that allows existing specs to be disabled temporarily during development. - * - * @param {String} desc description of this specification - * @param {Function} func defines the preconditions and expectations of the spec - */ -var xit = function(desc, func) { - return jasmine.getEnv().xit(desc, func); -}; -if (isCommonJS) exports.xit = xit; - -/** - * Starts a chain for a Jasmine expectation. - * - * It is passed an Object that is the actual value and should chain to one of the many - * jasmine.Matchers functions. - * - * @param {Object} actual Actual value to test against and expected value - */ -var expect = function(actual) { - return jasmine.getEnv().currentSpec.expect(actual); -}; -if (isCommonJS) exports.expect = expect; - -/** - * Defines part of a jasmine spec. Used in cominbination with waits or waitsFor in asynchrnous specs. - * - * @param {Function} func Function that defines part of a jasmine spec. - */ -var runs = function(func) { - jasmine.getEnv().currentSpec.runs(func); -}; -if (isCommonJS) exports.runs = runs; - -/** - * Waits a fixed time period before moving to the next block. - * - * @deprecated Use waitsFor() instead - * @param {Number} timeout milliseconds to wait - */ -var waits = function(timeout) { - jasmine.getEnv().currentSpec.waits(timeout); -}; -if (isCommonJS) exports.waits = waits; - -/** - * Waits for the latchFunction to return true before proceeding to the next block. - * - * @param {Function} latchFunction - * @param {String} optional_timeoutMessage - * @param {Number} optional_timeout - */ -var waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) { - jasmine.getEnv().currentSpec.waitsFor.apply(jasmine.getEnv().currentSpec, arguments); -}; -if (isCommonJS) exports.waitsFor = waitsFor; - -/** - * A function that is called before each spec in a suite. - * - * Used for spec setup, including validating assumptions. - * - * @param {Function} beforeEachFunction - */ -var beforeEach = function(beforeEachFunction) { - jasmine.getEnv().beforeEach(beforeEachFunction); -}; -if (isCommonJS) exports.beforeEach = beforeEach; - -/** - * A function that is called after each spec in a suite. - * - * Used for restoring any state that is hijacked during spec execution. - * - * @param {Function} afterEachFunction - */ -var afterEach = function(afterEachFunction) { - jasmine.getEnv().afterEach(afterEachFunction); -}; -if (isCommonJS) exports.afterEach = afterEach; - -/** - * Defines a suite of specifications. - * - * Stores the description and all defined specs in the Jasmine environment as one suite of specs. Variables declared - * are accessible by calls to beforeEach, it, and afterEach. Describe blocks can be nested, allowing for specialization - * of setup in some tests. - * - * @example - * // TODO: a simple suite - * - * // TODO: a simple suite with a nested describe block - * - * @param {String} description A string, usually the class under test. - * @param {Function} specDefinitions function that defines several specs. - */ -var describe = function(description, specDefinitions) { - return jasmine.getEnv().describe(description, specDefinitions); -}; -if (isCommonJS) exports.describe = describe; - -/** - * Disables a suite of specifications. Used to disable some suites in a file, or files, temporarily during development. - * - * @param {String} description A string, usually the class under test. - * @param {Function} specDefinitions function that defines several specs. - */ -var xdescribe = function(description, specDefinitions) { - return jasmine.getEnv().xdescribe(description, specDefinitions); -}; -if (isCommonJS) exports.xdescribe = xdescribe; - - -// Provide the XMLHttpRequest class for IE 5.x-6.x: -jasmine.XmlHttpRequest = (typeof XMLHttpRequest == "undefined") ? function() { - function tryIt(f) { - try { - return f(); - } catch(e) { - } - return null; - } - - var xhr = tryIt(function() { - return new ActiveXObject("Msxml2.XMLHTTP.6.0"); - }) || - tryIt(function() { - return new ActiveXObject("Msxml2.XMLHTTP.3.0"); - }) || - tryIt(function() { - return new ActiveXObject("Msxml2.XMLHTTP"); - }) || - tryIt(function() { - return new ActiveXObject("Microsoft.XMLHTTP"); - }); - - if (!xhr) throw new Error("This browser does not support XMLHttpRequest."); - - return xhr; -} : XMLHttpRequest; -/** - * @namespace - */ -jasmine.util = {}; - -/** - * Declare that a child class inherit it's prototype from the parent class. - * - * @private - * @param {Function} childClass - * @param {Function} parentClass - */ -jasmine.util.inherit = function(childClass, parentClass) { - /** - * @private - */ - var subclass = function() { - }; - subclass.prototype = parentClass.prototype; - childClass.prototype = new subclass(); -}; - -jasmine.util.formatException = function(e) { - var lineNumber; - if (e.line) { - lineNumber = e.line; - } - else if (e.lineNumber) { - lineNumber = e.lineNumber; - } - - var file; - - if (e.sourceURL) { - file = e.sourceURL; - } - else if (e.fileName) { - file = e.fileName; - } - - var message = (e.name && e.message) ? (e.name + ': ' + e.message) : e.toString(); - - if (file && lineNumber) { - message += ' in ' + file + ' (line ' + lineNumber + ')'; - } - - return message; -}; - -jasmine.util.htmlEscape = function(str) { - if (!str) return str; - return str.replace(/&/g, '&') - .replace(/</g, '<') - .replace(/>/g, '>'); -}; - -jasmine.util.argsToArray = function(args) { - var arrayOfArgs = []; - for (var i = 0; i < args.length; i++) arrayOfArgs.push(args[i]); - return arrayOfArgs; -}; - -jasmine.util.extend = function(destination, source) { - for (var property in source) destination[property] = source[property]; - return destination; -}; - -/** - * Environment for Jasmine - * - * @constructor - */ -jasmine.Env = function() { - this.currentSpec = null; - this.currentSuite = null; - this.currentRunner_ = new jasmine.Runner(this); - - this.reporter = new jasmine.MultiReporter(); - - this.updateInterval = jasmine.DEFAULT_UPDATE_INTERVAL; - this.defaultTimeoutInterval = jasmine.DEFAULT_TIMEOUT_INTERVAL; - this.lastUpdate = 0; - this.specFilter = function() { - return true; - }; - - this.nextSpecId_ = 0; - this.nextSuiteId_ = 0; - this.equalityTesters_ = []; - - // wrap matchers - this.matchersClass = function() { - jasmine.Matchers.apply(this, arguments); - }; - jasmine.util.inherit(this.matchersClass, jasmine.Matchers); - - jasmine.Matchers.wrapInto_(jasmine.Matchers.prototype, this.matchersClass); -}; - - -jasmine.Env.prototype.setTimeout = jasmine.setTimeout; -jasmine.Env.prototype.clearTimeout = jasmine.clearTimeout; -jasmine.Env.prototype.setInterval = jasmine.setInterval; -jasmine.Env.prototype.clearInterval = jasmine.clearInterval; - -/** - * @returns an object containing jasmine version build info, if set. - */ -jasmine.Env.prototype.version = function () { - if (jasmine.version_) { - return jasmine.version_; - } else { - throw new Error('Version not set'); - } -}; - -/** - * @returns string containing jasmine version build info, if set. - */ -jasmine.Env.prototype.versionString = function() { - if (!jasmine.version_) { - return "version unknown"; - } - - var version = this.version(); - var versionString = version.major + "." + version.minor + "." + version.build; - if (version.release_candidate) { - versionString += ".rc" + version.release_candidate; - } - versionString += " revision " + version.revision; - return versionString; -}; - -/** - * @returns a sequential integer starting at 0 - */ -jasmine.Env.prototype.nextSpecId = function () { - return this.nextSpecId_++; -}; - -/** - * @returns a sequential integer starting at 0 - */ -jasmine.Env.prototype.nextSuiteId = function () { - return this.nextSuiteId_++; -}; - -/** - * Register a reporter to receive status updates from Jasmine. - * @param {jasmine.Reporter} reporter An object which will receive status updates. - */ -jasmine.Env.prototype.addReporter = function(reporter) { - this.reporter.addReporter(reporter); -}; - -jasmine.Env.prototype.execute = function() { - this.currentRunner_.execute(); -}; - -jasmine.Env.prototype.describe = function(description, specDefinitions) { - var suite = new jasmine.Suite(this, description, specDefinitions, this.currentSuite); - - var parentSuite = this.currentSuite; - if (parentSuite) { - parentSuite.add(suite); - } else { - this.currentRunner_.add(suite); - } - - this.currentSuite = suite; - - var declarationError = null; - try { - specDefinitions.call(suite); - } catch(e) { - declarationError = e; - } - - if (declarationError) { - this.it("encountered a declaration exception", function() { - throw declarationError; - }); - } - - this.currentSuite = parentSuite; - - return suite; -}; - -jasmine.Env.prototype.beforeEach = function(beforeEachFunction) { - if (this.currentSuite) { - this.currentSuite.beforeEach(beforeEachFunction); - } else { - this.currentRunner_.beforeEach(beforeEachFunction); - } -}; - -jasmine.Env.prototype.currentRunner = function () { - return this.currentRunner_; -}; - -jasmine.Env.prototype.afterEach = function(afterEachFunction) { - if (this.currentSuite) { - this.currentSuite.afterEach(afterEachFunction); - } else { - this.currentRunner_.afterEach(afterEachFunction); - } - -}; - -jasmine.Env.prototype.xdescribe = function(desc, specDefinitions) { - return { - execute: function() { - } - }; -}; - -jasmine.Env.prototype.it = function(description, func) { - var spec = new jasmine.Spec(this, this.currentSuite, description); - this.currentSuite.add(spec); - this.currentSpec = spec; - - if (func) { - spec.runs(func); - } - - return spec; -}; - -jasmine.Env.prototype.xit = function(desc, func) { - return { - id: this.nextSpecId(), - runs: function() { - } - }; -}; - -jasmine.Env.prototype.compareObjects_ = function(a, b, mismatchKeys, mismatchValues) { - if (a.__Jasmine_been_here_before__ === b && b.__Jasmine_been_here_before__ === a) { - return true; - } - - a.__Jasmine_been_here_before__ = b; - b.__Jasmine_been_here_before__ = a; - - var hasKey = function(obj, keyName) { - return obj !== null && obj[keyName] !== jasmine.undefined; - }; - - for (var property in b) { - if (!hasKey(a, property) && hasKey(b, property)) { - mismatchKeys.push("expected has key '" + property + "', but missing from actual."); - } - } - for (property in a) { - if (!hasKey(b, property) && hasKey(a, property)) { - mismatchKeys.push("expected missing key '" + property + "', but present in actual."); - } - } - for (property in b) { - if (property == '__Jasmine_been_here_before__') continue; - if (!this.equals_(a[property], b[property], mismatchKeys, mismatchValues)) { - mismatchValues.push("'" + property + "' was '" + (b[property] ? jasmine.util.htmlEscape(b[property].toString()) : b[property]) + "' in expected, but was '" + (a[property] ? jasmine.util.htmlEscape(a[property].toString()) : a[property]) + "' in actual."); - } - } - - if (jasmine.isArray_(a) && jasmine.isArray_(b) && a.length != b.length) { - mismatchValues.push("arrays were not the same length"); - } - - delete a.__Jasmine_been_here_before__; - delete b.__Jasmine_been_here_before__; - return (mismatchKeys.length === 0 && mismatchValues.length === 0); -}; - -jasmine.Env.prototype.equals_ = function(a, b, mismatchKeys, mismatchValues) { - mismatchKeys = mismatchKeys || []; - mismatchValues = mismatchValues || []; - - for (var i = 0; i < this.equalityTesters_.length; i++) { - var equalityTester = this.equalityTesters_[i]; - var result = equalityTester(a, b, this, mismatchKeys, mismatchValues); - if (result !== jasmine.undefined) return result; - } - - if (a === b) return true; - - if (a === jasmine.undefined || a === null || b === jasmine.undefined || b === null) { - return (a == jasmine.undefined && b == jasmine.undefined); - } - - if (jasmine.isDomNode(a) && jasmine.isDomNode(b)) { - return a === b; - } - - if (a instanceof Date && b instanceof Date) { - return a.getTime() == b.getTime(); - } - - if (a instanceof jasmine.Matchers.Any) { - return a.matches(b); - } - - if (b instanceof jasmine.Matchers.Any) { - return b.matches(a); - } - - if (jasmine.isString_(a) && jasmine.isString_(b)) { - return (a == b); - } - - if (jasmine.isNumber_(a) && jasmine.isNumber_(b)) { - return (a == b); - } - - if (typeof a === "object" && typeof b === "object") { - return this.compareObjects_(a, b, mismatchKeys, mismatchValues); - } - - //Straight check - return (a === b); -}; - -jasmine.Env.prototype.contains_ = function(haystack, needle) { - if (jasmine.isArray_(haystack)) { - for (var i = 0; i < haystack.length; i++) { - if (this.equals_(haystack[i], needle)) return true; - } - return false; - } - return haystack.indexOf(needle) >= 0; -}; - -jasmine.Env.prototype.addEqualityTester = function(equalityTester) { - this.equalityTesters_.push(equalityTester); -}; -/** No-op base class for Jasmine reporters. - * - * @constructor - */ -jasmine.Reporter = function() { -}; - -//noinspection JSUnusedLocalSymbols -jasmine.Reporter.prototype.reportRunnerStarting = function(runner) { -}; - -//noinspection JSUnusedLocalSymbols -jasmine.Reporter.prototype.reportRunnerResults = function(runner) { -}; - -//noinspection JSUnusedLocalSymbols -jasmine.Reporter.prototype.reportSuiteResults = function(suite) { -}; - -//noinspection JSUnusedLocalSymbols -jasmine.Reporter.prototype.reportSpecStarting = function(spec) { -}; - -//noinspection JSUnusedLocalSymbols -jasmine.Reporter.prototype.reportSpecResults = function(spec) { -}; - -//noinspection JSUnusedLocalSymbols -jasmine.Reporter.prototype.log = function(str) { -}; - -/** - * Blocks are functions with executable code that make up a spec. - * - * @constructor - * @param {jasmine.Env} env - * @param {Function} func - * @param {jasmine.Spec} spec - */ -jasmine.Block = function(env, func, spec) { - this.env = env; - this.func = func; - this.spec = spec; -}; - -jasmine.Block.prototype.execute = function(onComplete) { - try { - this.func.apply(this.spec); - } catch (e) { - this.spec.fail(e); - } - onComplete(); -}; -/** JavaScript API reporter. - * - * @constructor - */ -jasmine.JsApiReporter = function() { - this.started = false; - this.finished = false; - this.suites_ = []; - this.results_ = {}; -}; - -jasmine.JsApiReporter.prototype.reportRunnerStarting = function(runner) { - this.started = true; - var suites = runner.topLevelSuites(); - for (var i = 0; i < suites.length; i++) { - var suite = suites[i]; - this.suites_.push(this.summarize_(suite)); - } -}; - -jasmine.JsApiReporter.prototype.suites = function() { - return this.suites_; -}; - -jasmine.JsApiReporter.prototype.summarize_ = function(suiteOrSpec) { - var isSuite = suiteOrSpec instanceof jasmine.Suite; - var summary = { - id: suiteOrSpec.id, - name: suiteOrSpec.description, - type: isSuite ? 'suite' : 'spec', - children: [] - }; - - if (isSuite) { - var children = suiteOrSpec.children(); - for (var i = 0; i < children.length; i++) { - summary.children.push(this.summarize_(children[i])); - } - } - return summary; -}; - -jasmine.JsApiReporter.prototype.results = function() { - return this.results_; -}; - -jasmine.JsApiReporter.prototype.resultsForSpec = function(specId) { - return this.results_[specId]; -}; - -//noinspection JSUnusedLocalSymbols -jasmine.JsApiReporter.prototype.reportRunnerResults = function(runner) { - this.finished = true; -}; - -//noinspection JSUnusedLocalSymbols -jasmine.JsApiReporter.prototype.reportSuiteResults = function(suite) { -}; - -//noinspection JSUnusedLocalSymbols -jasmine.JsApiReporter.prototype.reportSpecResults = function(spec) { - this.results_[spec.id] = { - messages: spec.results().getItems(), - result: spec.results().failedCount > 0 ? "failed" : "passed" - }; -}; - -//noinspection JSUnusedLocalSymbols -jasmine.JsApiReporter.prototype.log = function(str) { -}; - -jasmine.JsApiReporter.prototype.resultsForSpecs = function(specIds){ - var results = {}; - for (var i = 0; i < specIds.length; i++) { - var specId = specIds[i]; - results[specId] = this.summarizeResult_(this.results_[specId]); - } - return results; -}; - -jasmine.JsApiReporter.prototype.summarizeResult_ = function(result){ - var summaryMessages = []; - var messagesLength = result.messages.length; - for (var messageIndex = 0; messageIndex < messagesLength; messageIndex++) { - var resultMessage = result.messages[messageIndex]; - summaryMessages.push({ - text: resultMessage.type == 'log' ? resultMessage.toString() : jasmine.undefined, - passed: resultMessage.passed ? resultMessage.passed() : true, - type: resultMessage.type, - message: resultMessage.message, - trace: { - stack: resultMessage.passed && !resultMessage.passed() ? resultMessage.trace.stack : jasmine.undefined - } - }); - } - - return { - result : result.result, - messages : summaryMessages - }; -}; - -/** - * @constructor - * @param {jasmine.Env} env - * @param actual - * @param {jasmine.Spec} spec - */ -jasmine.Matchers = function(env, actual, spec, opt_isNot) { - this.env = env; - this.actual = actual; - this.spec = spec; - this.isNot = opt_isNot || false; - this.reportWasCalled_ = false; -}; - -// todo: @deprecated as of Jasmine 0.11, remove soon [xw] -jasmine.Matchers.pp = function(str) { - throw new Error("jasmine.Matchers.pp() is no longer supported, please use jasmine.pp() instead!"); -}; - -// todo: @deprecated Deprecated as of Jasmine 0.10. Rewrite your custom matchers to return true or false. [xw] -jasmine.Matchers.prototype.report = function(result, failing_message, details) { - throw new Error("As of jasmine 0.11, custom matchers must be implemented differently -- please see jasmine docs"); -}; - -jasmine.Matchers.wrapInto_ = function(prototype, matchersClass) { - for (var methodName in prototype) { - if (methodName == 'report') continue; - var orig = prototype[methodName]; - matchersClass.prototype[methodName] = jasmine.Matchers.matcherFn_(methodName, orig); - } -}; - -jasmine.Matchers.matcherFn_ = function(matcherName, matcherFunction) { - return function() { - var matcherArgs = jasmine.util.argsToArray(arguments); - var result = matcherFunction.apply(this, arguments); - - if (this.isNot) { - result = !result; - } - - if (this.reportWasCalled_) return result; - - var message; - if (!result) { - if (this.message) { - message = this.message.apply(this, arguments); - if (jasmine.isArray_(message)) { - message = message[this.isNot ? 1 : 0]; - } - } else { - var englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); }); - message = "Expected " + jasmine.pp(this.actual) + (this.isNot ? " not " : " ") + englishyPredicate; - if (matcherArgs.length > 0) { - for (var i = 0; i < matcherArgs.length; i++) { - if (i > 0) message += ","; - message += " " + jasmine.pp(matcherArgs[i]); - } - } - message += "."; - } - } - var expectationResult = new jasmine.ExpectationResult({ - matcherName: matcherName, - passed: result, - expected: matcherArgs.length > 1 ? matcherArgs : matcherArgs[0], - actual: this.actual, - message: message - }); - this.spec.addMatcherResult(expectationResult); - return jasmine.undefined; - }; -}; - - - - -/** - * toBe: compares the actual to the expected using === - * @param expected - */ -jasmine.Matchers.prototype.toBe = function(expected) { - return this.actual === expected; -}; - -/** - * toNotBe: compares the actual to the expected using !== - * @param expected - * @deprecated as of 1.0. Use not.toBe() instead. - */ -jasmine.Matchers.prototype.toNotBe = function(expected) { - return this.actual !== expected; -}; - -/** - * toEqual: compares the actual to the expected using common sense equality. Handles Objects, Arrays, etc. - * - * @param expected - */ -jasmine.Matchers.prototype.toEqual = function(expected) { - return this.env.equals_(this.actual, expected); -}; - -/** - * toNotEqual: compares the actual to the expected using the ! of jasmine.Matchers.toEqual - * @param expected - * @deprecated as of 1.0. Use not.toNotEqual() instead. - */ -jasmine.Matchers.prototype.toNotEqual = function(expected) { - return !this.env.equals_(this.actual, expected); -}; - -/** - * Matcher that compares the actual to the expected using a regular expression. Constructs a RegExp, so takes - * a pattern or a String. - * - * @param expected - */ -jasmine.Matchers.prototype.toMatch = function(expected) { - return new RegExp(expected).test(this.actual); -}; - -/** - * Matcher that compares the actual to the expected using the boolean inverse of jasmine.Matchers.toMatch - * @param expected - * @deprecated as of 1.0. Use not.toMatch() instead. - */ -jasmine.Matchers.prototype.toNotMatch = function(expected) { - return !(new RegExp(expected).test(this.actual)); -}; - -/** - * Matcher that compares the actual to jasmine.undefined. - */ -jasmine.Matchers.prototype.toBeDefined = function() { - return (this.actual !== jasmine.undefined); -}; - -/** - * Matcher that compares the actual to jasmine.undefined. - */ -jasmine.Matchers.prototype.toBeUndefined = function() { - return (this.actual === jasmine.undefined); -}; - -/** - * Matcher that compares the actual to null. - */ -jasmine.Matchers.prototype.toBeNull = function() { - return (this.actual === null); -}; - -/** - * Matcher that boolean not-nots the actual. - */ -jasmine.Matchers.prototype.toBeTruthy = function() { - return !!this.actual; -}; - - -/** - * Matcher that boolean nots the actual. - */ -jasmine.Matchers.prototype.toBeFalsy = function() { - return !this.actual; -}; - - -/** - * Matcher that checks to see if the actual, a Jasmine spy, was called. - */ -jasmine.Matchers.prototype.toHaveBeenCalled = function() { - if (arguments.length > 0) { - throw new Error('toHaveBeenCalled does not take arguments, use toHaveBeenCalledWith'); - } - - if (!jasmine.isSpy(this.actual)) { - throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); - } - - this.message = function() { - return [ - "Expected spy " + this.actual.identity + " to have been called.", - "Expected spy " + this.actual.identity + " not to have been called." - ]; - }; - - return this.actual.wasCalled; -}; - -/** @deprecated Use expect(xxx).toHaveBeenCalled() instead */ -jasmine.Matchers.prototype.wasCalled = jasmine.Matchers.prototype.toHaveBeenCalled; - -/** - * Matcher that checks to see if the actual, a Jasmine spy, was not called. - * - * @deprecated Use expect(xxx).not.toHaveBeenCalled() instead - */ -jasmine.Matchers.prototype.wasNotCalled = function() { - if (arguments.length > 0) { - throw new Error('wasNotCalled does not take arguments'); - } - - if (!jasmine.isSpy(this.actual)) { - throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); - } - - this.message = function() { - return [ - "Expected spy " + this.actual.identity + " to not have been called.", - "Expected spy " + this.actual.identity + " to have been called." - ]; - }; - - return !this.actual.wasCalled; -}; - -/** - * Matcher that checks to see if the actual, a Jasmine spy, was called with a set of parameters. - * - * @example - * - */ -jasmine.Matchers.prototype.toHaveBeenCalledWith = function() { - var expectedArgs = jasmine.util.argsToArray(arguments); - if (!jasmine.isSpy(this.actual)) { - throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); - } - this.message = function() { - if (this.actual.callCount === 0) { - // todo: what should the failure message for .not.toHaveBeenCalledWith() be? is this right? test better. [xw] - return [ - "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but it was never called.", - "Expected spy " + this.actual.identity + " not to have been called with " + jasmine.pp(expectedArgs) + " but it was." - ]; - } else { - return [ - "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall), - "Expected spy " + this.actual.identity + " not to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall) - ]; - } - }; - - return this.env.contains_(this.actual.argsForCall, expectedArgs); -}; - -/** @deprecated Use expect(xxx).toHaveBeenCalledWith() instead */ -jasmine.Matchers.prototype.wasCalledWith = jasmine.Matchers.prototype.toHaveBeenCalledWith; - -/** @deprecated Use expect(xxx).not.toHaveBeenCalledWith() instead */ -jasmine.Matchers.prototype.wasNotCalledWith = function() { - var expectedArgs = jasmine.util.argsToArray(arguments); - if (!jasmine.isSpy(this.actual)) { - throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); - } - - this.message = function() { - return [ - "Expected spy not to have been called with " + jasmine.pp(expectedArgs) + " but it was", - "Expected spy to have been called with " + jasmine.pp(expectedArgs) + " but it was" - ]; - }; - - return !this.env.contains_(this.actual.argsForCall, expectedArgs); -}; - -/** - * Matcher that checks that the expected item is an element in the actual Array. - * - * @param {Object} expected - */ -jasmine.Matchers.prototype.toContain = function(expected) { - return this.env.contains_(this.actual, expected); -}; - -/** - * Matcher that checks that the expected item is NOT an element in the actual Array. - * - * @param {Object} expected - * @deprecated as of 1.0. Use not.toNotContain() instead. - */ -jasmine.Matchers.prototype.toNotContain = function(expected) { - return !this.env.contains_(this.actual, expected); -}; - -jasmine.Matchers.prototype.toBeLessThan = function(expected) { - return this.actual < expected; -}; - -jasmine.Matchers.prototype.toBeGreaterThan = function(expected) { - return this.actual > expected; -}; - -/** - * Matcher that checks that the expected item is equal to the actual item - * up to a given level of decimal precision (default 2). - * - * @param {Number} expected - * @param {Number} precision - */ -jasmine.Matchers.prototype.toBeCloseTo = function(expected, precision) { - if (!(precision === 0)) { - precision = precision || 2; - } - var multiplier = Math.pow(10, precision); - var actual = Math.round(this.actual * multiplier); - expected = Math.round(expected * multiplier); - return expected == actual; -}; - -/** - * Matcher that checks that the expected exception was thrown by the actual. - * - * @param {String} expected - */ -jasmine.Matchers.prototype.toThrow = function(expected) { - var result = false; - var exception; - if (typeof this.actual != 'function') { - throw new Error('Actual is not a function'); - } - try { - this.actual(); - } catch (e) { - exception = e; - } - if (exception) { - result = (expected === jasmine.undefined || this.env.equals_(exception.message || exception, expected.message || expected)); - } - - var not = this.isNot ? "not " : ""; - - this.message = function() { - if (exception && (expected === jasmine.undefined || !this.env.equals_(exception.message || exception, expected.message || expected))) { - return ["Expected function " + not + "to throw", expected ? expected.message || expected : "an exception", ", but it threw", exception.message || exception].join(' '); - } else { - return "Expected function to throw an exception."; - } - }; - - return result; -}; - -jasmine.Matchers.Any = function(expectedClass) { - this.expectedClass = expectedClass; -}; - -jasmine.Matchers.Any.prototype.matches = function(other) { - if (this.expectedClass == String) { - return typeof other == 'string' || other instanceof String; - } - - if (this.expectedClass == Number) { - return typeof other == 'number' || other instanceof Number; - } - - if (this.expectedClass == Function) { - return typeof other == 'function' || other instanceof Function; - } - - if (this.expectedClass == Object) { - return typeof other == 'object'; - } - - return other instanceof this.expectedClass; -}; - -jasmine.Matchers.Any.prototype.toString = function() { - return '<jasmine.any(' + this.expectedClass + ')>'; -}; - -/** - * @constructor - */ -jasmine.MultiReporter = function() { - this.subReporters_ = []; -}; -jasmine.util.inherit(jasmine.MultiReporter, jasmine.Reporter); - -jasmine.MultiReporter.prototype.addReporter = function(reporter) { - this.subReporters_.push(reporter); -}; - -(function() { - var functionNames = [ - "reportRunnerStarting", - "reportRunnerResults", - "reportSuiteResults", - "reportSpecStarting", - "reportSpecResults", - "log" - ]; - for (var i = 0; i < functionNames.length; i++) { - var functionName = functionNames[i]; - jasmine.MultiReporter.prototype[functionName] = (function(functionName) { - return function() { - for (var j = 0; j < this.subReporters_.length; j++) { - var subReporter = this.subReporters_[j]; - if (subReporter[functionName]) { - subReporter[functionName].apply(subReporter, arguments); - } - } - }; - })(functionName); - } -})(); -/** - * Holds results for a set of Jasmine spec. Allows for the results array to hold another jasmine.NestedResults - * - * @constructor - */ -jasmine.NestedResults = function() { - /** - * The total count of results - */ - this.totalCount = 0; - /** - * Number of passed results - */ - this.passedCount = 0; - /** - * Number of failed results - */ - this.failedCount = 0; - /** - * Was this suite/spec skipped? - */ - this.skipped = false; - /** - * @ignore - */ - this.items_ = []; -}; - -/** - * Roll up the result counts. - * - * @param result - */ -jasmine.NestedResults.prototype.rollupCounts = function(result) { - this.totalCount += result.totalCount; - this.passedCount += result.passedCount; - this.failedCount += result.failedCount; -}; - -/** - * Adds a log message. - * @param values Array of message parts which will be concatenated later. - */ -jasmine.NestedResults.prototype.log = function(values) { - this.items_.push(new jasmine.MessageResult(values)); -}; - -/** - * Getter for the results: message & results. - */ -jasmine.NestedResults.prototype.getItems = function() { - return this.items_; -}; - -/** - * Adds a result, tracking counts (total, passed, & failed) - * @param {jasmine.ExpectationResult|jasmine.NestedResults} result - */ -jasmine.NestedResults.prototype.addResult = function(result) { - if (result.type != 'log') { - if (result.items_) { - this.rollupCounts(result); - } else { - this.totalCount++; - if (result.passed()) { - this.passedCount++; - } else { - this.failedCount++; - } - } - } - this.items_.push(result); -}; - -/** - * @returns {Boolean} True if <b>everything</b> below passed - */ -jasmine.NestedResults.prototype.passed = function() { - return this.passedCount === this.totalCount; -}; -/** - * Base class for pretty printing for expectation results. - */ -jasmine.PrettyPrinter = function() { - this.ppNestLevel_ = 0; -}; - -/** - * Formats a value in a nice, human-readable string. - * - * @param value - */ -jasmine.PrettyPrinter.prototype.format = function(value) { - if (this.ppNestLevel_ > 40) { - throw new Error('jasmine.PrettyPrinter: format() nested too deeply!'); - } - - this.ppNestLevel_++; - try { - if (value === jasmine.undefined) { - this.emitScalar('undefined'); - } else if (value === null) { - this.emitScalar('null'); - } else if (value === jasmine.getGlobal()) { - this.emitScalar('<global>'); - } else if (value instanceof jasmine.Matchers.Any) { - this.emitScalar(value.toString()); - } else if (typeof value === 'string') { - this.emitString(value); - } else if (jasmine.isSpy(value)) { - this.emitScalar("spy on " + value.identity); - } else if (value instanceof RegExp) { - this.emitScalar(value.toString()); - } else if (typeof value === 'function') { - this.emitScalar('Function'); - } else if (typeof value.nodeType === 'number') { - this.emitScalar('HTMLNode'); - } else if (value instanceof Date) { - this.emitScalar('Date(' + value + ')'); - } else if (value.__Jasmine_been_here_before__) { - this.emitScalar('<circular reference: ' + (jasmine.isArray_(value) ? 'Array' : 'Object') + '>'); - } else if (jasmine.isArray_(value) || typeof value == 'object') { - value.__Jasmine_been_here_before__ = true; - if (jasmine.isArray_(value)) { - this.emitArray(value); - } else { - this.emitObject(value); - } - delete value.__Jasmine_been_here_before__; - } else { - this.emitScalar(value.toString()); - } - } finally { - this.ppNestLevel_--; - } -}; - -jasmine.PrettyPrinter.prototype.iterateObject = function(obj, fn) { - for (var property in obj) { - if (property == '__Jasmine_been_here_before__') continue; - fn(property, obj.__lookupGetter__ ? (obj.__lookupGetter__(property) !== jasmine.undefined && - obj.__lookupGetter__(property) !== null) : false); - } -}; - -jasmine.PrettyPrinter.prototype.emitArray = jasmine.unimplementedMethod_; -jasmine.PrettyPrinter.prototype.emitObject = jasmine.unimplementedMethod_; -jasmine.PrettyPrinter.prototype.emitScalar = jasmine.unimplementedMethod_; -jasmine.PrettyPrinter.prototype.emitString = jasmine.unimplementedMethod_; - -jasmine.StringPrettyPrinter = function() { - jasmine.PrettyPrinter.call(this); - - this.string = ''; -}; -jasmine.util.inherit(jasmine.StringPrettyPrinter, jasmine.PrettyPrinter); - -jasmine.StringPrettyPrinter.prototype.emitScalar = function(value) { - this.append(value); -}; - -jasmine.StringPrettyPrinter.prototype.emitString = function(value) { - this.append("'" + value + "'"); -}; - -jasmine.StringPrettyPrinter.prototype.emitArray = function(array) { - this.append('[ '); - for (var i = 0; i < array.length; i++) { - if (i > 0) { - this.append(', '); - } - this.format(array[i]); - } - this.append(' ]'); -}; - -jasmine.StringPrettyPrinter.prototype.emitObject = function(obj) { - var self = this; - this.append('{ '); - var first = true; - - this.iterateObject(obj, function(property, isGetter) { - if (first) { - first = false; - } else { - self.append(', '); - } - - self.append(property); - self.append(' : '); - if (isGetter) { - self.append('<getter>'); - } else { - self.format(obj[property]); - } - }); - - this.append(' }'); -}; - -jasmine.StringPrettyPrinter.prototype.append = function(value) { - this.string += value; -}; -jasmine.Queue = function(env) { - this.env = env; - this.blocks = []; - this.running = false; - this.index = 0; - this.offset = 0; - this.abort = false; -}; - -jasmine.Queue.prototype.addBefore = function(block) { - this.blocks.unshift(block); -}; - -jasmine.Queue.prototype.add = function(block) { - this.blocks.push(block); -}; - -jasmine.Queue.prototype.insertNext = function(block) { - this.blocks.splice((this.index + this.offset + 1), 0, block); - this.offset++; -}; - -jasmine.Queue.prototype.start = function(onComplete) { - this.running = true; - this.onComplete = onComplete; - this.next_(); -}; - -jasmine.Queue.prototype.isRunning = function() { - return this.running; -}; - -jasmine.Queue.LOOP_DONT_RECURSE = true; - -jasmine.Queue.prototype.next_ = function() { - var self = this; - var goAgain = true; - - while (goAgain) { - goAgain = false; - - if (self.index < self.blocks.length && !this.abort) { - var calledSynchronously = true; - var completedSynchronously = false; - - var onComplete = function () { - if (jasmine.Queue.LOOP_DONT_RECURSE && calledSynchronously) { - completedSynchronously = true; - return; - } - - if (self.blocks[self.index].abort) { - self.abort = true; - } - - self.offset = 0; - self.index++; - - var now = new Date().getTime(); - if (self.env.updateInterval && now - self.env.lastUpdate > self.env.updateInterval) { - self.env.lastUpdate = now; - self.env.setTimeout(function() { - self.next_(); - }, 0); - } else { - if (jasmine.Queue.LOOP_DONT_RECURSE && completedSynchronously) { - goAgain = true; - } else { - self.next_(); - } - } - }; - self.blocks[self.index].execute(onComplete); - - calledSynchronously = false; - if (completedSynchronously) { - onComplete(); - } - - } else { - self.running = false; - if (self.onComplete) { - self.onComplete(); - } - } - } -}; - -jasmine.Queue.prototype.results = function() { - var results = new jasmine.NestedResults(); - for (var i = 0; i < this.blocks.length; i++) { - if (this.blocks[i].results) { - results.addResult(this.blocks[i].results()); - } - } - return results; -}; - - -/** - * Runner - * - * @constructor - * @param {jasmine.Env} env - */ -jasmine.Runner = function(env) { - var self = this; - self.env = env; - self.queue = new jasmine.Queue(env); - self.before_ = []; - self.after_ = []; - self.suites_ = []; -}; - -jasmine.Runner.prototype.execute = function() { - var self = this; - if (self.env.reporter.reportRunnerStarting) { - self.env.reporter.reportRunnerStarting(this); - } - self.queue.start(function () { - self.finishCallback(); - }); -}; - -jasmine.Runner.prototype.beforeEach = function(beforeEachFunction) { - beforeEachFunction.typeName = 'beforeEach'; - this.before_.splice(0,0,beforeEachFunction); -}; - -jasmine.Runner.prototype.afterEach = function(afterEachFunction) { - afterEachFunction.typeName = 'afterEach'; - this.after_.splice(0,0,afterEachFunction); -}; - - -jasmine.Runner.prototype.finishCallback = function() { - this.env.reporter.reportRunnerResults(this); -}; - -jasmine.Runner.prototype.addSuite = function(suite) { - this.suites_.push(suite); -}; - -jasmine.Runner.prototype.add = function(block) { - if (block instanceof jasmine.Suite) { - this.addSuite(block); - } - this.queue.add(block); -}; - -jasmine.Runner.prototype.specs = function () { - var suites = this.suites(); - var specs = []; - for (var i = 0; i < suites.length; i++) { - specs = specs.concat(suites[i].specs()); - } - return specs; -}; - -jasmine.Runner.prototype.suites = function() { - return this.suites_; -}; - -jasmine.Runner.prototype.topLevelSuites = function() { - var topLevelSuites = []; - for (var i = 0; i < this.suites_.length; i++) { - if (!this.suites_[i].parentSuite) { - topLevelSuites.push(this.suites_[i]); - } - } - return topLevelSuites; -}; - -jasmine.Runner.prototype.results = function() { - return this.queue.results(); -}; -/** - * Internal representation of a Jasmine specification, or test. - * - * @constructor - * @param {jasmine.Env} env - * @param {jasmine.Suite} suite - * @param {String} description - */ -jasmine.Spec = function(env, suite, description) { - if (!env) { - throw new Error('jasmine.Env() required'); - } - if (!suite) { - throw new Error('jasmine.Suite() required'); - } - var spec = this; - spec.id = env.nextSpecId ? env.nextSpecId() : null; - spec.env = env; - spec.suite = suite; - spec.description = description; - spec.queue = new jasmine.Queue(env); - - spec.afterCallbacks = []; - spec.spies_ = []; - - spec.results_ = new jasmine.NestedResults(); - spec.results_.description = description; - spec.matchersClass = null; -}; - -jasmine.Spec.prototype.getFullName = function() { - return this.suite.getFullName() + ' ' + this.description + '.'; -}; - - -jasmine.Spec.prototype.results = function() { - return this.results_; -}; - -/** - * All parameters are pretty-printed and concatenated together, then written to the spec's output. - * - * Be careful not to leave calls to <code>jasmine.log</code> in production code. - */ -jasmine.Spec.prototype.log = function() { - return this.results_.log(arguments); -}; - -jasmine.Spec.prototype.runs = function (func) { - var block = new jasmine.Block(this.env, func, this); - this.addToQueue(block); - return this; -}; - -jasmine.Spec.prototype.addToQueue = function (block) { - if (this.queue.isRunning()) { - this.queue.insertNext(block); - } else { - this.queue.add(block); - } -}; - -/** - * @param {jasmine.ExpectationResult} result - */ -jasmine.Spec.prototype.addMatcherResult = function(result) { - this.results_.addResult(result); -}; - -jasmine.Spec.prototype.expect = function(actual) { - var positive = new (this.getMatchersClass_())(this.env, actual, this); - positive.not = new (this.getMatchersClass_())(this.env, actual, this, true); - return positive; -}; - -/** - * Waits a fixed time period before moving to the next block. - * - * @deprecated Use waitsFor() instead - * @param {Number} timeout milliseconds to wait - */ -jasmine.Spec.prototype.waits = function(timeout) { - var waitsFunc = new jasmine.WaitsBlock(this.env, timeout, this); - this.addToQueue(waitsFunc); - return this; -}; - -/** - * Waits for the latchFunction to return true before proceeding to the next block. - * - * @param {Function} latchFunction - * @param {String} optional_timeoutMessage - * @param {Number} optional_timeout - */ -jasmine.Spec.prototype.waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) { - var latchFunction_ = null; - var optional_timeoutMessage_ = null; - var optional_timeout_ = null; - - for (var i = 0; i < arguments.length; i++) { - var arg = arguments[i]; - switch (typeof arg) { - case 'function': - latchFunction_ = arg; - break; - case 'string': - optional_timeoutMessage_ = arg; - break; - case 'number': - optional_timeout_ = arg; - break; - } - } - - var waitsForFunc = new jasmine.WaitsForBlock(this.env, optional_timeout_, latchFunction_, optional_timeoutMessage_, this); - this.addToQueue(waitsForFunc); - return this; -}; - -jasmine.Spec.prototype.fail = function (e) { - var expectationResult = new jasmine.ExpectationResult({ - passed: false, - message: e ? jasmine.util.formatException(e) : 'Exception', - trace: { stack: e.stack } - }); - this.results_.addResult(expectationResult); -}; - -jasmine.Spec.prototype.getMatchersClass_ = function() { - return this.matchersClass || this.env.matchersClass; -}; - -jasmine.Spec.prototype.addMatchers = function(matchersPrototype) { - var parent = this.getMatchersClass_(); - var newMatchersClass = function() { - parent.apply(this, arguments); - }; - jasmine.util.inherit(newMatchersClass, parent); - jasmine.Matchers.wrapInto_(matchersPrototype, newMatchersClass); - this.matchersClass = newMatchersClass; -}; - -jasmine.Spec.prototype.finishCallback = function() { - this.env.reporter.reportSpecResults(this); -}; - -jasmine.Spec.prototype.finish = function(onComplete) { - this.removeAllSpies(); - this.finishCallback(); - if (onComplete) { - onComplete(); - } -}; - -jasmine.Spec.prototype.after = function(doAfter) { - if (this.queue.isRunning()) { - this.queue.add(new jasmine.Block(this.env, doAfter, this)); - } else { - this.afterCallbacks.unshift(doAfter); - } -}; - -jasmine.Spec.prototype.execute = function(onComplete) { - var spec = this; - if (!spec.env.specFilter(spec)) { - spec.results_.skipped = true; - spec.finish(onComplete); - return; - } - - this.env.reporter.reportSpecStarting(this); - - spec.env.currentSpec = spec; - - spec.addBeforesAndAftersToQueue(); - - spec.queue.start(function () { - spec.finish(onComplete); - }); -}; - -jasmine.Spec.prototype.addBeforesAndAftersToQueue = function() { - var runner = this.env.currentRunner(); - var i; - - for (var suite = this.suite; suite; suite = suite.parentSuite) { - for (i = 0; i < suite.before_.length; i++) { - this.queue.addBefore(new jasmine.Block(this.env, suite.before_[i], this)); - } - } - for (i = 0; i < runner.before_.length; i++) { - this.queue.addBefore(new jasmine.Block(this.env, runner.before_[i], this)); - } - for (i = 0; i < this.afterCallbacks.length; i++) { - this.queue.add(new jasmine.Block(this.env, this.afterCallbacks[i], this)); - } - for (suite = this.suite; suite; suite = suite.parentSuite) { - for (i = 0; i < suite.after_.length; i++) { - this.queue.add(new jasmine.Block(this.env, suite.after_[i], this)); - } - } - for (i = 0; i < runner.after_.length; i++) { - this.queue.add(new jasmine.Block(this.env, runner.after_[i], this)); - } -}; - -jasmine.Spec.prototype.explodes = function() { - throw 'explodes function should not have been called'; -}; - -jasmine.Spec.prototype.spyOn = function(obj, methodName, ignoreMethodDoesntExist) { - if (obj == jasmine.undefined) { - throw "spyOn could not find an object to spy upon for " + methodName + "()"; - } - - if (!ignoreMethodDoesntExist && obj[methodName] === jasmine.undefined) { - throw methodName + '() method does not exist'; - } - - if (!ignoreMethodDoesntExist && obj[methodName] && obj[methodName].isSpy) { - throw new Error(methodName + ' has already been spied upon'); - } - - var spyObj = jasmine.createSpy(methodName); - - this.spies_.push(spyObj); - spyObj.baseObj = obj; - spyObj.methodName = methodName; - spyObj.originalValue = obj[methodName]; - - obj[methodName] = spyObj; - - return spyObj; -}; - -jasmine.Spec.prototype.removeAllSpies = function() { - for (var i = 0; i < this.spies_.length; i++) { - var spy = this.spies_[i]; - spy.baseObj[spy.methodName] = spy.originalValue; - } - this.spies_ = []; -}; - -/** - * Internal representation of a Jasmine suite. - * - * @constructor - * @param {jasmine.Env} env - * @param {String} description - * @param {Function} specDefinitions - * @param {jasmine.Suite} parentSuite - */ -jasmine.Suite = function(env, description, specDefinitions, parentSuite) { - var self = this; - self.id = env.nextSuiteId ? env.nextSuiteId() : null; - self.description = description; - self.queue = new jasmine.Queue(env); - self.parentSuite = parentSuite; - self.env = env; - self.before_ = []; - self.after_ = []; - self.children_ = []; - self.suites_ = []; - self.specs_ = []; -}; - -jasmine.Suite.prototype.getFullName = function() { - var fullName = this.description; - for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) { - fullName = parentSuite.description + ' ' + fullName; - } - return fullName; -}; - -jasmine.Suite.prototype.finish = function(onComplete) { - this.env.reporter.reportSuiteResults(this); - this.finished = true; - if (typeof(onComplete) == 'function') { - onComplete(); - } -}; - -jasmine.Suite.prototype.beforeEach = function(beforeEachFunction) { - beforeEachFunction.typeName = 'beforeEach'; - this.before_.unshift(beforeEachFunction); -}; - -jasmine.Suite.prototype.afterEach = function(afterEachFunction) { - afterEachFunction.typeName = 'afterEach'; - this.after_.unshift(afterEachFunction); -}; - -jasmine.Suite.prototype.results = function() { - return this.queue.results(); -}; - -jasmine.Suite.prototype.add = function(suiteOrSpec) { - this.children_.push(suiteOrSpec); - if (suiteOrSpec instanceof jasmine.Suite) { - this.suites_.push(suiteOrSpec); - this.env.currentRunner().addSuite(suiteOrSpec); - } else { - this.specs_.push(suiteOrSpec); - } - this.queue.add(suiteOrSpec); -}; - -jasmine.Suite.prototype.specs = function() { - return this.specs_; -}; - -jasmine.Suite.prototype.suites = function() { - return this.suites_; -}; - -jasmine.Suite.prototype.children = function() { - return this.children_; -}; - -jasmine.Suite.prototype.execute = function(onComplete) { - var self = this; - this.queue.start(function () { - self.finish(onComplete); - }); -}; -jasmine.WaitsBlock = function(env, timeout, spec) { - this.timeout = timeout; - jasmine.Block.call(this, env, null, spec); -}; - -jasmine.util.inherit(jasmine.WaitsBlock, jasmine.Block); - -jasmine.WaitsBlock.prototype.execute = function (onComplete) { - if (jasmine.VERBOSE) { - this.env.reporter.log('>> Jasmine waiting for ' + this.timeout + ' ms...'); - } - this.env.setTimeout(function () { - onComplete(); - }, this.timeout); -}; -/** - * A block which waits for some condition to become true, with timeout. - * - * @constructor - * @extends jasmine.Block - * @param {jasmine.Env} env The Jasmine environment. - * @param {Number} timeout The maximum time in milliseconds to wait for the condition to become true. - * @param {Function} latchFunction A function which returns true when the desired condition has been met. - * @param {String} message The message to display if the desired condition hasn't been met within the given time period. - * @param {jasmine.Spec} spec The Jasmine spec. - */ -jasmine.WaitsForBlock = function(env, timeout, latchFunction, message, spec) { - this.timeout = timeout || env.defaultTimeoutInterval; - this.latchFunction = latchFunction; - this.message = message; - this.totalTimeSpentWaitingForLatch = 0; - jasmine.Block.call(this, env, null, spec); -}; -jasmine.util.inherit(jasmine.WaitsForBlock, jasmine.Block); - -jasmine.WaitsForBlock.TIMEOUT_INCREMENT = 10; - -jasmine.WaitsForBlock.prototype.execute = function(onComplete) { - if (jasmine.VERBOSE) { - this.env.reporter.log('>> Jasmine waiting for ' + (this.message || 'something to happen')); - } - var latchFunctionResult; - try { - latchFunctionResult = this.latchFunction.apply(this.spec); - } catch (e) { - this.spec.fail(e); - onComplete(); - return; - } - - if (latchFunctionResult) { - onComplete(); - } else if (this.totalTimeSpentWaitingForLatch >= this.timeout) { - var message = 'timed out after ' + this.timeout + ' msec waiting for ' + (this.message || 'something to happen'); - this.spec.fail({ - name: 'timeout', - message: message - }); - - this.abort = true; - onComplete(); - } else { - this.totalTimeSpentWaitingForLatch += jasmine.WaitsForBlock.TIMEOUT_INCREMENT; - var self = this; - this.env.setTimeout(function() { - self.execute(onComplete); - }, jasmine.WaitsForBlock.TIMEOUT_INCREMENT); - } -}; -// Mock setTimeout, clearTimeout -// Contributed by Pivotal Computer Systems, www.pivotalsf.com - -jasmine.FakeTimer = function() { - this.reset(); - - var self = this; - self.setTimeout = function(funcToCall, millis) { - self.timeoutsMade++; - self.scheduleFunction(self.timeoutsMade, funcToCall, millis, false); - return self.timeoutsMade; - }; - - self.setInterval = function(funcToCall, millis) { - self.timeoutsMade++; - self.scheduleFunction(self.timeoutsMade, funcToCall, millis, true); - return self.timeoutsMade; - }; - - self.clearTimeout = function(timeoutKey) { - self.scheduledFunctions[timeoutKey] = jasmine.undefined; - }; - - self.clearInterval = function(timeoutKey) { - self.scheduledFunctions[timeoutKey] = jasmine.undefined; - }; - -}; - -jasmine.FakeTimer.prototype.reset = function() { - this.timeoutsMade = 0; - this.scheduledFunctions = {}; - this.nowMillis = 0; -}; - -jasmine.FakeTimer.prototype.tick = function(millis) { - var oldMillis = this.nowMillis; - var newMillis = oldMillis + millis; - this.runFunctionsWithinRange(oldMillis, newMillis); - this.nowMillis = newMillis; -}; - -jasmine.FakeTimer.prototype.runFunctionsWithinRange = function(oldMillis, nowMillis) { - var scheduledFunc; - var funcsToRun = []; - for (var timeoutKey in this.scheduledFunctions) { - scheduledFunc = this.scheduledFunctions[timeoutKey]; - if (scheduledFunc != jasmine.undefined && - scheduledFunc.runAtMillis >= oldMillis && - scheduledFunc.runAtMillis <= nowMillis) { - funcsToRun.push(scheduledFunc); - this.scheduledFunctions[timeoutKey] = jasmine.undefined; - } - } - - if (funcsToRun.length > 0) { - funcsToRun.sort(function(a, b) { - return a.runAtMillis - b.runAtMillis; - }); - for (var i = 0; i < funcsToRun.length; ++i) { - try { - var funcToRun = funcsToRun[i]; - this.nowMillis = funcToRun.runAtMillis; - funcToRun.funcToCall(); - if (funcToRun.recurring) { - this.scheduleFunction(funcToRun.timeoutKey, - funcToRun.funcToCall, - funcToRun.millis, - true); - } - } catch(e) { - } - } - this.runFunctionsWithinRange(oldMillis, nowMillis); - } -}; - -jasmine.FakeTimer.prototype.scheduleFunction = function(timeoutKey, funcToCall, millis, recurring) { - this.scheduledFunctions[timeoutKey] = { - runAtMillis: this.nowMillis + millis, - funcToCall: funcToCall, - recurring: recurring, - timeoutKey: timeoutKey, - millis: millis - }; -}; - -/** - * @namespace - */ -jasmine.Clock = { - defaultFakeTimer: new jasmine.FakeTimer(), - - reset: function() { - jasmine.Clock.assertInstalled(); - jasmine.Clock.defaultFakeTimer.reset(); - }, - - tick: function(millis) { - jasmine.Clock.assertInstalled(); - jasmine.Clock.defaultFakeTimer.tick(millis); - }, - - runFunctionsWithinRange: function(oldMillis, nowMillis) { - jasmine.Clock.defaultFakeTimer.runFunctionsWithinRange(oldMillis, nowMillis); - }, - - scheduleFunction: function(timeoutKey, funcToCall, millis, recurring) { - jasmine.Clock.defaultFakeTimer.scheduleFunction(timeoutKey, funcToCall, millis, recurring); - }, - - useMock: function() { - if (!jasmine.Clock.isInstalled()) { - var spec = jasmine.getEnv().currentSpec; - spec.after(jasmine.Clock.uninstallMock); - - jasmine.Clock.installMock(); - } - }, - - installMock: function() { - jasmine.Clock.installed = jasmine.Clock.defaultFakeTimer; - }, - - uninstallMock: function() { - jasmine.Clock.assertInstalled(); - jasmine.Clock.installed = jasmine.Clock.real; - }, - - real: { - setTimeout: jasmine.getGlobal().setTimeout, - clearTimeout: jasmine.getGlobal().clearTimeout, - setInterval: jasmine.getGlobal().setInterval, - clearInterval: jasmine.getGlobal().clearInterval - }, - - assertInstalled: function() { - if (!jasmine.Clock.isInstalled()) { - throw new Error("Mock clock is not installed, use jasmine.Clock.useMock()"); - } - }, - - isInstalled: function() { - return jasmine.Clock.installed == jasmine.Clock.defaultFakeTimer; - }, - - installed: null -}; -jasmine.Clock.installed = jasmine.Clock.real; - -//else for IE support -jasmine.getGlobal().setTimeout = function(funcToCall, millis) { - if (jasmine.Clock.installed.setTimeout.apply) { - return jasmine.Clock.installed.setTimeout.apply(this, arguments); - } else { - return jasmine.Clock.installed.setTimeout(funcToCall, millis); - } -}; - -jasmine.getGlobal().setInterval = function(funcToCall, millis) { - if (jasmine.Clock.installed.setInterval.apply) { - return jasmine.Clock.installed.setInterval.apply(this, arguments); - } else { - return jasmine.Clock.installed.setInterval(funcToCall, millis); - } -}; - -jasmine.getGlobal().clearTimeout = function(timeoutKey) { - if (jasmine.Clock.installed.clearTimeout.apply) { - return jasmine.Clock.installed.clearTimeout.apply(this, arguments); - } else { - return jasmine.Clock.installed.clearTimeout(timeoutKey); - } -}; - -jasmine.getGlobal().clearInterval = function(timeoutKey) { - if (jasmine.Clock.installed.clearTimeout.apply) { - return jasmine.Clock.installed.clearInterval.apply(this, arguments); - } else { - return jasmine.Clock.installed.clearInterval(timeoutKey); - } -}; - -jasmine.version_= { - "major": 1, - "minor": 1, - "build": 0, - "revision": 1315677058 -}; diff --git a/addons/web_graph/static/lib/flotr2/lib/jasmine/jasmine_favicon.png b/addons/web_graph/static/lib/flotr2/lib/jasmine/jasmine_favicon.png Binary files differdeleted file mode 100644 index bd147955254..00000000000 --- a/addons/web_graph/static/lib/flotr2/lib/jasmine/jasmine_favicon.png +++ /dev/null diff --git a/addons/web_graph/static/lib/flotr2/lib/prototype.js b/addons/web_graph/static/lib/flotr2/lib/prototype.js deleted file mode 100644 index 41bd8e30e4a..00000000000 --- a/addons/web_graph/static/lib/flotr2/lib/prototype.js +++ /dev/null @@ -1,4320 +0,0 @@ -/* Prototype JavaScript framework, version 1.6.0.3
- * (c) 2005-2008 Sam Stephenson
- *
- * Prototype is freely distributable under the terms of an MIT-style license.
- * For details, see the Prototype web site: http://www.prototypejs.org/
- *
- *--------------------------------------------------------------------------*/
-
-var Prototype = {
- Version: '1.6.0.3',
-
- Browser: {
- IE: !!(window.attachEvent &&
- navigator.userAgent.indexOf('Opera') === -1),
- Opera: navigator.userAgent.indexOf('Opera') > -1,
- WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1,
- Gecko: navigator.userAgent.indexOf('Gecko') > -1 &&
- navigator.userAgent.indexOf('KHTML') === -1,
- MobileSafari: !!navigator.userAgent.match(/Apple.*Mobile.*Safari/)
- },
-
- BrowserFeatures: {
- XPath: !!document.evaluate,
- SelectorsAPI: !!document.querySelector,
- ElementExtensions: !!window.HTMLElement,
- SpecificElementExtensions:
- document.createElement('div')['__proto__'] &&
- document.createElement('div')['__proto__'] !==
- document.createElement('form')['__proto__']
- },
-
- ScriptFragment: '<script[^>]*>([\\S\\s]*?)<\/script>',
- JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/,
-
- emptyFunction: function() { },
- K: function(x) { return x }
-};
-
-if (Prototype.Browser.MobileSafari)
- Prototype.BrowserFeatures.SpecificElementExtensions = false;
-
-
-/* Based on Alex Arnell's inheritance implementation. */
-var Class = {
- create: function() {
- var parent = null, properties = $A(arguments);
- if (Object.isFunction(properties[0]))
- parent = properties.shift();
-
- function klass() {
- this.initialize.apply(this, arguments);
- }
-
- Object.extend(klass, Class.Methods);
- klass.superclass = parent;
- klass.subclasses = [];
-
- if (parent) {
- var subclass = function() { };
- subclass.prototype = parent.prototype;
- klass.prototype = new subclass;
- parent.subclasses.push(klass);
- }
-
- for (var i = 0; i < properties.length; i++)
- klass.addMethods(properties[i]);
-
- if (!klass.prototype.initialize)
- klass.prototype.initialize = Prototype.emptyFunction;
-
- klass.prototype.constructor = klass;
-
- return klass;
- }
-};
-
-Class.Methods = {
- addMethods: function(source) {
- var ancestor = this.superclass && this.superclass.prototype;
- var properties = Object.keys(source);
-
- if (!Object.keys({ toString: true }).length)
- properties.push("toString", "valueOf");
-
- for (var i = 0, length = properties.length; i < length; i++) {
- var property = properties[i], value = source[property];
- if (ancestor && Object.isFunction(value) &&
- value.argumentNames().first() == "$super") {
- var method = value;
- value = (function(m) {
- return function() { return ancestor[m].apply(this, arguments) };
- })(property).wrap(method);
-
- value.valueOf = method.valueOf.bind(method);
- value.toString = method.toString.bind(method);
- }
- this.prototype[property] = value;
- }
-
- return this;
- }
-};
-
-var Abstract = { };
-
-Object.extend = function(destination, source) {
- for (var property in source)
- destination[property] = source[property];
- return destination;
-};
-
-Object.extend(Object, {
- inspect: function(object) {
- try {
- if (Object.isUndefined(object)) return 'undefined';
- if (object === null) return 'null';
- return object.inspect ? object.inspect() : String(object);
- } catch (e) {
- if (e instanceof RangeError) return '...';
- throw e;
- }
- },
-
- toJSON: function(object) {
- var type = typeof object;
- switch (type) {
- case 'undefined':
- case 'function':
- case 'unknown': return;
- case 'boolean': return object.toString();
- }
-
- if (object === null) return 'null';
- if (object.toJSON) return object.toJSON();
- if (Object.isElement(object)) return;
-
- var results = [];
- for (var property in object) {
- var value = Object.toJSON(object[property]);
- if (!Object.isUndefined(value))
- results.push(property.toJSON() + ': ' + value);
- }
-
- return '{' + results.join(', ') + '}';
- },
-
- toQueryString: function(object) {
- return $H(object).toQueryString();
- },
-
- toHTML: function(object) {
- return object && object.toHTML ? object.toHTML() : String.interpret(object);
- },
-
- keys: function(object) {
- var keys = [];
- for (var property in object)
- keys.push(property);
- return keys;
- },
-
- values: function(object) {
- var values = [];
- for (var property in object)
- values.push(object[property]);
- return values;
- },
-
- clone: function(object) {
- return Object.extend({ }, object);
- },
-
- isElement: function(object) {
- return !!(object && object.nodeType == 1);
- },
-
- isArray: function(object) {
- return object != null && typeof object == "object" &&
- 'splice' in object && 'join' in object;
- },
-
- isHash: function(object) {
- return object instanceof Hash;
- },
-
- isFunction: function(object) {
- return typeof object == "function";
- },
-
- isString: function(object) {
- return typeof object == "string";
- },
-
- isNumber: function(object) {
- return typeof object == "number";
- },
-
- isUndefined: function(object) {
- return typeof object == "undefined";
- }
-});
-
-Object.extend(Function.prototype, {
- argumentNames: function() {
- var names = this.toString().match(/^[\s\(]*function[^(]*\(([^\)]*)\)/)[1]
- .replace(/\s+/g, '').split(',');
- return names.length == 1 && !names[0] ? [] : names;
- },
-
- bind: function() {
- if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this;
- var __method = this, args = $A(arguments), object = args.shift();
- return function() {
- return __method.apply(object, args.concat($A(arguments)));
- }
- },
-
- bindAsEventListener: function() {
- var __method = this, args = $A(arguments), object = args.shift();
- return function(event) {
- return __method.apply(object, [event || window.event].concat(args));
- }
- },
-
- curry: function() {
- if (!arguments.length) return this;
- var __method = this, args = $A(arguments);
- return function() {
- return __method.apply(this, args.concat($A(arguments)));
- }
- },
-
- delay: function() {
- var __method = this, args = $A(arguments), timeout = args.shift() * 1000;
- return window.setTimeout(function() {
- return __method.apply(__method, args);
- }, timeout);
- },
-
- defer: function() {
- var args = [0.01].concat($A(arguments));
- return this.delay.apply(this, args);
- },
-
- wrap: function(wrapper) {
- var __method = this;
- return function() {
- return wrapper.apply(this, [__method.bind(this)].concat($A(arguments)));
- }
- },
-
- methodize: function() {
- if (this._methodized) return this._methodized;
- var __method = this;
- return this._methodized = function() {
- return __method.apply(null, [this].concat($A(arguments)));
- };
- }
-});
-
-Date.prototype.toJSON = function() {
- return '"' + this.getUTCFullYear() + '-' +
- (this.getUTCMonth() + 1).toPaddedString(2) + '-' +
- this.getUTCDate().toPaddedString(2) + 'T' +
- this.getUTCHours().toPaddedString(2) + ':' +
- this.getUTCMinutes().toPaddedString(2) + ':' +
- this.getUTCSeconds().toPaddedString(2) + 'Z"';
-};
-
-var Try = {
- these: function() {
- var returnValue;
-
- for (var i = 0, length = arguments.length; i < length; i++) {
- var lambda = arguments[i];
- try {
- returnValue = lambda();
- break;
- } catch (e) { }
- }
-
- return returnValue;
- }
-};
-
-RegExp.prototype.match = RegExp.prototype.test;
-
-RegExp.escape = function(str) {
- return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
-};
-
-/*--------------------------------------------------------------------------*/
-
-var PeriodicalExecuter = Class.create({
- initialize: function(callback, frequency) {
- this.callback = callback;
- this.frequency = frequency;
- this.currentlyExecuting = false;
-
- this.registerCallback();
- },
-
- registerCallback: function() {
- this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
- },
-
- execute: function() {
- this.callback(this);
- },
-
- stop: function() {
- if (!this.timer) return;
- clearInterval(this.timer);
- this.timer = null;
- },
-
- onTimerEvent: function() {
- if (!this.currentlyExecuting) {
- try {
- this.currentlyExecuting = true;
- this.execute();
- } finally {
- this.currentlyExecuting = false;
- }
- }
- }
-});
-Object.extend(String, {
- interpret: function(value) {
- return value == null ? '' : String(value);
- },
- specialChar: {
- '\b': '\\b',
- '\t': '\\t',
- '\n': '\\n',
- '\f': '\\f',
- '\r': '\\r',
- '\\': '\\\\'
- }
-});
-
-Object.extend(String.prototype, {
- gsub: function(pattern, replacement) {
- var result = '', source = this, match;
- replacement = arguments.callee.prepareReplacement(replacement);
-
- while (source.length > 0) {
- if (match = source.match(pattern)) {
- result += source.slice(0, match.index);
- result += String.interpret(replacement(match));
- source = source.slice(match.index + match[0].length);
- } else {
- result += source, source = '';
- }
- }
- return result;
- },
-
- sub: function(pattern, replacement, count) {
- replacement = this.gsub.prepareReplacement(replacement);
- count = Object.isUndefined(count) ? 1 : count;
-
- return this.gsub(pattern, function(match) {
- if (--count < 0) return match[0];
- return replacement(match);
- });
- },
-
- scan: function(pattern, iterator) {
- this.gsub(pattern, iterator);
- return String(this);
- },
-
- truncate: function(length, truncation) {
- length = length || 30;
- truncation = Object.isUndefined(truncation) ? '...' : truncation;
- return this.length > length ?
- this.slice(0, length - truncation.length) + truncation : String(this);
- },
-
- strip: function() {
- return this.replace(/^\s+/, '').replace(/\s+$/, '');
- },
-
- stripTags: function() {
- return this.replace(/<\/?[^>]+>/gi, '');
- },
-
- stripScripts: function() {
- return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), '');
- },
-
- extractScripts: function() {
- var matchAll = new RegExp(Prototype.ScriptFragment, 'img');
- var matchOne = new RegExp(Prototype.ScriptFragment, 'im');
- return (this.match(matchAll) || []).map(function(scriptTag) {
- return (scriptTag.match(matchOne) || ['', ''])[1];
- });
- },
-
- evalScripts: function() {
- return this.extractScripts().map(function(script) { return eval(script) });
- },
-
- escapeHTML: function() {
- var self = arguments.callee;
- self.text.data = this;
- return self.div.innerHTML;
- },
-
- unescapeHTML: function() {
- var div = new Element('div');
- div.innerHTML = this.stripTags();
- return div.childNodes[0] ? (div.childNodes.length > 1 ?
- $A(div.childNodes).inject('', function(memo, node) { return memo+node.nodeValue }) :
- div.childNodes[0].nodeValue) : '';
- },
-
- toQueryParams: function(separator) {
- var match = this.strip().match(/([^?#]*)(#.*)?$/);
- if (!match) return { };
-
- return match[1].split(separator || '&').inject({ }, function(hash, pair) {
- if ((pair = pair.split('='))[0]) {
- var key = decodeURIComponent(pair.shift());
- var value = pair.length > 1 ? pair.join('=') : pair[0];
- if (value != undefined) value = decodeURIComponent(value);
-
- if (key in hash) {
- if (!Object.isArray(hash[key])) hash[key] = [hash[key]];
- hash[key].push(value);
- }
- else hash[key] = value;
- }
- return hash;
- });
- },
-
- toArray: function() {
- return this.split('');
- },
-
- succ: function() {
- return this.slice(0, this.length - 1) +
- String.fromCharCode(this.charCodeAt(this.length - 1) + 1);
- },
-
- times: function(count) {
- return count < 1 ? '' : new Array(count + 1).join(this);
- },
-
- camelize: function() {
- var parts = this.split('-'), len = parts.length;
- if (len == 1) return parts[0];
-
- var camelized = this.charAt(0) == '-'
- ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1)
- : parts[0];
-
- for (var i = 1; i < len; i++)
- camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1);
-
- return camelized;
- },
-
- capitalize: function() {
- return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase();
- },
-
- underscore: function() {
- return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase();
- },
-
- dasherize: function() {
- return this.gsub(/_/,'-');
- },
-
- inspect: function(useDoubleQuotes) {
- var escapedString = this.gsub(/[\x00-\x1f\\]/, function(match) {
- var character = String.specialChar[match[0]];
- return character ? character : '\\u00' + match[0].charCodeAt().toPaddedString(2, 16);
- });
- if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"';
- return "'" + escapedString.replace(/'/g, '\\\'') + "'";
- },
-
- toJSON: function() {
- return this.inspect(true);
- },
-
- unfilterJSON: function(filter) {
- return this.sub(filter || Prototype.JSONFilter, '#{1}');
- },
-
- isJSON: function() {
- var str = this;
- if (str.blank()) return false;
- str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, '');
- return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str);
- },
-
- evalJSON: function(sanitize) {
- var json = this.unfilterJSON();
- try {
- if (!sanitize || json.isJSON()) return eval('(' + json + ')');
- } catch (e) { }
- throw new SyntaxError('Badly formed JSON string: ' + this.inspect());
- },
-
- include: function(pattern) {
- return this.indexOf(pattern) > -1;
- },
-
- startsWith: function(pattern) {
- return this.indexOf(pattern) === 0;
- },
-
- endsWith: function(pattern) {
- var d = this.length - pattern.length;
- return d >= 0 && this.lastIndexOf(pattern) === d;
- },
-
- empty: function() {
- return this == '';
- },
-
- blank: function() {
- return /^\s*$/.test(this);
- },
-
- interpolate: function(object, pattern) {
- return new Template(this, pattern).evaluate(object);
- }
-});
-
-if (Prototype.Browser.WebKit || Prototype.Browser.IE) Object.extend(String.prototype, {
- escapeHTML: function() {
- return this.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
- },
- unescapeHTML: function() {
- return this.stripTags().replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
- }
-});
-
-String.prototype.gsub.prepareReplacement = function(replacement) {
- if (Object.isFunction(replacement)) return replacement;
- var template = new Template(replacement);
- return function(match) { return template.evaluate(match) };
-};
-
-String.prototype.parseQuery = String.prototype.toQueryParams;
-
-Object.extend(String.prototype.escapeHTML, {
- div: document.createElement('div'),
- text: document.createTextNode('')
-});
-
-String.prototype.escapeHTML.div.appendChild(String.prototype.escapeHTML.text);
-
-var Template = Class.create({
- initialize: function(template, pattern) {
- this.template = template.toString();
- this.pattern = pattern || Template.Pattern;
- },
-
- evaluate: function(object) {
- if (Object.isFunction(object.toTemplateReplacements))
- object = object.toTemplateReplacements();
-
- return this.template.gsub(this.pattern, function(match) {
- if (object == null) return '';
-
- var before = match[1] || '';
- if (before == '\\') return match[2];
-
- var ctx = object, expr = match[3];
- var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/;
- match = pattern.exec(expr);
- if (match == null) return before;
-
- while (match != null) {
- var comp = match[1].startsWith('[') ? match[2].gsub('\\\\]', ']') : match[1];
- ctx = ctx[comp];
- if (null == ctx || '' == match[3]) break;
- expr = expr.substring('[' == match[3] ? match[1].length : match[0].length);
- match = pattern.exec(expr);
- }
-
- return before + String.interpret(ctx);
- });
- }
-});
-Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;
-
-var $break = { };
-
-var Enumerable = {
- each: function(iterator, context) {
- var index = 0;
- try {
- this._each(function(value) {
- iterator.call(context, value, index++);
- });
- } catch (e) {
- if (e != $break) throw e;
- }
- return this;
- },
-
- eachSlice: function(number, iterator, context) {
- var index = -number, slices = [], array = this.toArray();
- if (number < 1) return array;
- while ((index += number) < array.length)
- slices.push(array.slice(index, index+number));
- return slices.collect(iterator, context);
- },
-
- all: function(iterator, context) {
- iterator = iterator || Prototype.K;
- var result = true;
- this.each(function(value, index) {
- result = result && !!iterator.call(context, value, index);
- if (!result) throw $break;
- });
- return result;
- },
-
- any: function(iterator, context) {
- iterator = iterator || Prototype.K;
- var result = false;
- this.each(function(value, index) {
- if (result = !!iterator.call(context, value, index))
- throw $break;
- });
- return result;
- },
-
- collect: function(iterator, context) {
- iterator = iterator || Prototype.K;
- var results = [];
- this.each(function(value, index) {
- results.push(iterator.call(context, value, index));
- });
- return results;
- },
-
- detect: function(iterator, context) {
- var result;
- this.each(function(value, index) {
- if (iterator.call(context, value, index)) {
- result = value;
- throw $break;
- }
- });
- return result;
- },
-
- findAll: function(iterator, context) {
- var results = [];
- this.each(function(value, index) {
- if (iterator.call(context, value, index))
- results.push(value);
- });
- return results;
- },
-
- grep: function(filter, iterator, context) {
- iterator = iterator || Prototype.K;
- var results = [];
-
- if (Object.isString(filter))
- filter = new RegExp(filter);
-
- this.each(function(value, index) {
- if (filter.match(value))
- results.push(iterator.call(context, value, index));
- });
- return results;
- },
-
- include: function(object) {
- if (Object.isFunction(this.indexOf))
- if (this.indexOf(object) != -1) return true;
-
- var found = false;
- this.each(function(value) {
- if (value == object) {
- found = true;
- throw $break;
- }
- });
- return found;
- },
-
- inGroupsOf: function(number, fillWith) {
- fillWith = Object.isUndefined(fillWith) ? null : fillWith;
- return this.eachSlice(number, function(slice) {
- while(slice.length < number) slice.push(fillWith);
- return slice;
- });
- },
-
- inject: function(memo, iterator, context) {
- this.each(function(value, index) {
- memo = iterator.call(context, memo, value, index);
- });
- return memo;
- },
-
- invoke: function(method) {
- var args = $A(arguments).slice(1);
- return this.map(function(value) {
- return value[method].apply(value, args);
- });
- },
-
- max: function(iterator, context) {
- iterator = iterator || Prototype.K;
- var result;
- this.each(function(value, index) {
- value = iterator.call(context, value, index);
- if (result == null || value >= result)
- result = value;
- });
- return result;
- },
-
- min: function(iterator, context) {
- iterator = iterator || Prototype.K;
- var result;
- this.each(function(value, index) {
- value = iterator.call(context, value, index);
- if (result == null || value < result)
- result = value;
- });
- return result;
- },
-
- partition: function(iterator, context) {
- iterator = iterator || Prototype.K;
- var trues = [], falses = [];
- this.each(function(value, index) {
- (iterator.call(context, value, index) ?
- trues : falses).push(value);
- });
- return [trues, falses];
- },
-
- pluck: function(property) {
- var results = [];
- this.each(function(value) {
- results.push(value[property]);
- });
- return results;
- },
-
- reject: function(iterator, context) {
- var results = [];
- this.each(function(value, index) {
- if (!iterator.call(context, value, index))
- results.push(value);
- });
- return results;
- },
-
- sortBy: function(iterator, context) {
- return this.map(function(value, index) {
- return {
- value: value,
- criteria: iterator.call(context, value, index)
- };
- }).sort(function(left, right) {
- var a = left.criteria, b = right.criteria;
- return a < b ? -1 : a > b ? 1 : 0;
- }).pluck('value');
- },
-
- toArray: function() {
- return this.map();
- },
-
- zip: function() {
- var iterator = Prototype.K, args = $A(arguments);
- if (Object.isFunction(args.last()))
- iterator = args.pop();
-
- var collections = [this].concat(args).map($A);
- return this.map(function(value, index) {
- return iterator(collections.pluck(index));
- });
- },
-
- size: function() {
- return this.toArray().length;
- },
-
- inspect: function() {
- return '#<Enumerable:' + this.toArray().inspect() + '>';
- }
-};
-
-Object.extend(Enumerable, {
- map: Enumerable.collect,
- find: Enumerable.detect,
- select: Enumerable.findAll,
- filter: Enumerable.findAll,
- member: Enumerable.include,
- entries: Enumerable.toArray,
- every: Enumerable.all,
- some: Enumerable.any
-});
-function $A(iterable) {
- if (!iterable) return [];
- if (iterable.toArray) return iterable.toArray();
- var length = iterable.length || 0, results = new Array(length);
- while (length--) results[length] = iterable[length];
- return results;
-}
-
-if (Prototype.Browser.WebKit) {
- $A = function(iterable) {
- if (!iterable) return [];
- // In Safari, only use the `toArray` method if it's not a NodeList.
- // A NodeList is a function, has an function `item` property, and a numeric
- // `length` property. Adapted from Google Doctype.
- if (!(typeof iterable === 'function' && typeof iterable.length ===
- 'number' && typeof iterable.item === 'function') && iterable.toArray)
- return iterable.toArray();
- var length = iterable.length || 0, results = new Array(length);
- while (length--) results[length] = iterable[length];
- return results;
- };
-}
-
-Array.from = $A;
-
-Object.extend(Array.prototype, Enumerable);
-
-if (!Array.prototype._reverse) Array.prototype._reverse = Array.prototype.reverse;
-
-Object.extend(Array.prototype, {
- _each: function(iterator) {
- for (var i = 0, length = this.length; i < length; i++)
- iterator(this[i]);
- },
-
- clear: function() {
- this.length = 0;
- return this;
- },
-
- first: function() {
- return this[0];
- },
-
- last: function() {
- return this[this.length - 1];
- },
-
- compact: function() {
- return this.select(function(value) {
- return value != null;
- });
- },
-
- flatten: function() {
- return this.inject([], function(array, value) {
- return array.concat(Object.isArray(value) ?
- value.flatten() : [value]);
- });
- },
-
- without: function() {
- var values = $A(arguments);
- return this.select(function(value) {
- return !values.include(value);
- });
- },
-
- reverse: function(inline) {
- return (inline !== false ? this : this.toArray())._reverse();
- },
-
- reduce: function() {
- return this.length > 1 ? this : this[0];
- },
-
- uniq: function(sorted) {
- return this.inject([], function(array, value, index) {
- if (0 == index || (sorted ? array.last() != value : !array.include(value)))
- array.push(value);
- return array;
- });
- },
-
- intersect: function(array) {
- return this.uniq().findAll(function(item) {
- return array.detect(function(value) { return item === value });
- });
- },
-
- clone: function() {
- return [].concat(this);
- },
-
- size: function() {
- return this.length;
- },
-
- inspect: function() {
- return '[' + this.map(Object.inspect).join(', ') + ']';
- },
-
- toJSON: function() {
- var results = [];
- this.each(function(object) {
- var value = Object.toJSON(object);
- if (!Object.isUndefined(value)) results.push(value);
- });
- return '[' + results.join(', ') + ']';
- }
-});
-
-// use native browser JS 1.6 implementation if available
-if (Object.isFunction(Array.prototype.forEach))
- Array.prototype._each = Array.prototype.forEach;
-
-if (!Array.prototype.indexOf) Array.prototype.indexOf = function(item, i) {
- i || (i = 0);
- var length = this.length;
- if (i < 0) i = length + i;
- for (; i < length; i++)
- if (this[i] === item) return i;
- return -1;
-};
-
-if (!Array.prototype.lastIndexOf) Array.prototype.lastIndexOf = function(item, i) {
- i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1;
- var n = this.slice(0, i).reverse().indexOf(item);
- return (n < 0) ? n : i - n - 1;
-};
-
-Array.prototype.toArray = Array.prototype.clone;
-
-function $w(string) {
- if (!Object.isString(string)) return [];
- string = string.strip();
- return string ? string.split(/\s+/) : [];
-}
-
-if (Prototype.Browser.Opera){
- Array.prototype.concat = function() {
- var array = [];
- for (var i = 0, length = this.length; i < length; i++) array.push(this[i]);
- for (var i = 0, length = arguments.length; i < length; i++) {
- if (Object.isArray(arguments[i])) {
- for (var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++)
- array.push(arguments[i][j]);
- } else {
- array.push(arguments[i]);
- }
- }
- return array;
- };
-}
-Object.extend(Number.prototype, {
- toColorPart: function() {
- return this.toPaddedString(2, 16);
- },
-
- succ: function() {
- return this + 1;
- },
-
- times: function(iterator, context) {
- $R(0, this, true).each(iterator, context);
- return this;
- },
-
- toPaddedString: function(length, radix) {
- var string = this.toString(radix || 10);
- return '0'.times(length - string.length) + string;
- },
-
- toJSON: function() {
- return isFinite(this) ? this.toString() : 'null';
- }
-});
-
-$w('abs round ceil floor').each(function(method){
- Number.prototype[method] = Math[method].methodize();
-});
-function $H(object) {
- return new Hash(object);
-};
-
-var Hash = Class.create(Enumerable, (function() {
-
- function toQueryPair(key, value) {
- if (Object.isUndefined(value)) return key;
- return key + '=' + encodeURIComponent(String.interpret(value));
- }
-
- return {
- initialize: function(object) {
- this._object = Object.isHash(object) ? object.toObject() : Object.clone(object);
- },
-
- _each: function(iterator) {
- for (var key in this._object) {
- var value = this._object[key], pair = [key, value];
- pair.key = key;
- pair.value = value;
- iterator(pair);
- }
- },
-
- set: function(key, value) {
- return this._object[key] = value;
- },
-
- get: function(key) {
- // simulating poorly supported hasOwnProperty
- if (this._object[key] !== Object.prototype[key])
- return this._object[key];
- },
-
- unset: function(key) {
- var value = this._object[key];
- delete this._object[key];
- return value;
- },
-
- toObject: function() {
- return Object.clone(this._object);
- },
-
- keys: function() {
- return this.pluck('key');
- },
-
- values: function() {
- return this.pluck('value');
- },
-
- index: function(value) {
- var match = this.detect(function(pair) {
- return pair.value === value;
- });
- return match && match.key;
- },
-
- merge: function(object) {
- return this.clone().update(object);
- },
-
- update: function(object) {
- return new Hash(object).inject(this, function(result, pair) {
- result.set(pair.key, pair.value);
- return result;
- });
- },
-
- toQueryString: function() {
- return this.inject([], function(results, pair) {
- var key = encodeURIComponent(pair.key), values = pair.value;
-
- if (values && typeof values == 'object') {
- if (Object.isArray(values))
- return results.concat(values.map(toQueryPair.curry(key)));
- } else results.push(toQueryPair(key, values));
- return results;
- }).join('&');
- },
-
- inspect: function() {
- return '#<Hash:{' + this.map(function(pair) {
- return pair.map(Object.inspect).join(': ');
- }).join(', ') + '}>';
- },
-
- toJSON: function() {
- return Object.toJSON(this.toObject());
- },
-
- clone: function() {
- return new Hash(this);
- }
- }
-})());
-
-Hash.prototype.toTemplateReplacements = Hash.prototype.toObject;
-Hash.from = $H;
-var ObjectRange = Class.create(Enumerable, {
- initialize: function(start, end, exclusive) {
- this.start = start;
- this.end = end;
- this.exclusive = exclusive;
- },
-
- _each: function(iterator) {
- var value = this.start;
- while (this.include(value)) {
- iterator(value);
- value = value.succ();
- }
- },
-
- include: function(value) {
- if (value < this.start)
- return false;
- if (this.exclusive)
- return value < this.end;
- return value <= this.end;
- }
-});
-
-var $R = function(start, end, exclusive) {
- return new ObjectRange(start, end, exclusive);
-};
-
-var Ajax = {
- getTransport: function() {
- return Try.these(
- function() {return new XMLHttpRequest()},
- function() {return new ActiveXObject('Msxml2.XMLHTTP')},
- function() {return new ActiveXObject('Microsoft.XMLHTTP')}
- ) || false;
- },
-
- activeRequestCount: 0
-};
-
-Ajax.Responders = {
- responders: [],
-
- _each: function(iterator) {
- this.responders._each(iterator);
- },
-
- register: function(responder) {
- if (!this.include(responder))
- this.responders.push(responder);
- },
-
- unregister: function(responder) {
- this.responders = this.responders.without(responder);
- },
-
- dispatch: function(callback, request, transport, json) {
- this.each(function(responder) {
- if (Object.isFunction(responder[callback])) {
- try {
- responder[callback].apply(responder, [request, transport, json]);
- } catch (e) { }
- }
- });
- }
-};
-
-Object.extend(Ajax.Responders, Enumerable);
-
-Ajax.Responders.register({
- onCreate: function() { Ajax.activeRequestCount++ },
- onComplete: function() { Ajax.activeRequestCount-- }
-});
-
-Ajax.Base = Class.create({
- initialize: function(options) {
- this.options = {
- method: 'post',
- asynchronous: true,
- contentType: 'application/x-www-form-urlencoded',
- encoding: 'UTF-8',
- parameters: '',
- evalJSON: true,
- evalJS: true
- };
- Object.extend(this.options, options || { });
-
- this.options.method = this.options.method.toLowerCase();
-
- if (Object.isString(this.options.parameters))
- this.options.parameters = this.options.parameters.toQueryParams();
- else if (Object.isHash(this.options.parameters))
- this.options.parameters = this.options.parameters.toObject();
- }
-});
-
-Ajax.Request = Class.create(Ajax.Base, {
- _complete: false,
-
- initialize: function($super, url, options) {
- $super(options);
- this.transport = Ajax.getTransport();
- this.request(url);
- },
-
- request: function(url) {
- this.url = url;
- this.method = this.options.method;
- var params = Object.clone(this.options.parameters);
-
- if (!['get', 'post'].include(this.method)) {
- // simulate other verbs over post
- params['_method'] = this.method;
- this.method = 'post';
- }
-
- this.parameters = params;
-
- if (params = Object.toQueryString(params)) {
- // when GET, append parameters to URL
- if (this.method == 'get')
- this.url += (this.url.include('?') ? '&' : '?') + params;
- else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent))
- params += '&_=';
- }
-
- try {
- var response = new Ajax.Response(this);
- if (this.options.onCreate) this.options.onCreate(response);
- Ajax.Responders.dispatch('onCreate', this, response);
-
- this.transport.open(this.method.toUpperCase(), this.url,
- this.options.asynchronous);
-
- if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1);
-
- this.transport.onreadystatechange = this.onStateChange.bind(this);
- this.setRequestHeaders();
-
- this.body = this.method == 'post' ? (this.options.postBody || params) : null;
- this.transport.send(this.body);
-
- /* Force Firefox to handle ready state 4 for synchronous requests */
- if (!this.options.asynchronous && this.transport.overrideMimeType)
- this.onStateChange();
-
- }
- catch (e) {
- this.dispatchException(e);
- }
- },
-
- onStateChange: function() {
- var readyState = this.transport.readyState;
- if (readyState > 1 && !((readyState == 4) && this._complete))
- this.respondToReadyState(this.transport.readyState);
- },
-
- setRequestHeaders: function() {
- var headers = {
- 'X-Requested-With': 'XMLHttpRequest',
- 'X-Prototype-Version': Prototype.Version,
- 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
- };
-
- if (this.method == 'post') {
- headers['Content-type'] = this.options.contentType +
- (this.options.encoding ? '; charset=' + this.options.encoding : '');
-
- /* Force "Connection: close" for older Mozilla browsers to work
- * around a bug where XMLHttpRequest sends an incorrect
- * Content-length header. See Mozilla Bugzilla #246651.
- */
- if (this.transport.overrideMimeType &&
- (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005)
- headers['Connection'] = 'close';
- }
-
- // user-defined headers
- if (typeof this.options.requestHeaders == 'object') {
- var extras = this.options.requestHeaders;
-
- if (Object.isFunction(extras.push))
- for (var i = 0, length = extras.length; i < length; i += 2)
- headers[extras[i]] = extras[i+1];
- else
- $H(extras).each(function(pair) { headers[pair.key] = pair.value });
- }
-
- for (var name in headers)
- this.transport.setRequestHeader(name, headers[name]);
- },
-
- success: function() {
- var status = this.getStatus();
- return !status || (status >= 200 && status < 300);
- },
-
- getStatus: function() {
- try {
- return this.transport.status || 0;
- } catch (e) { return 0 }
- },
-
- respondToReadyState: function(readyState) {
- var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this);
-
- if (state == 'Complete') {
- try {
- this._complete = true;
- (this.options['on' + response.status]
- || this.options['on' + (this.success() ? 'Success' : 'Failure')]
- || Prototype.emptyFunction)(response, response.headerJSON);
- } catch (e) {
- this.dispatchException(e);
- }
-
- var contentType = response.getHeader('Content-type');
- if (this.options.evalJS == 'force'
- || (this.options.evalJS && this.isSameOrigin() && contentType
- && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i)))
- this.evalResponse();
- }
-
- try {
- (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON);
- Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON);
- } catch (e) {
- this.dispatchException(e);
- }
-
- if (state == 'Complete') {
- // avoid memory leak in MSIE: clean up
- this.transport.onreadystatechange = Prototype.emptyFunction;
- }
- },
-
- isSameOrigin: function() {
- var m = this.url.match(/^\s*https?:\/\/[^\/]*/);
- return !m || (m[0] == '#{protocol}//#{domain}#{port}'.interpolate({
- protocol: location.protocol,
- domain: document.domain,
- port: location.port ? ':' + location.port : ''
- }));
- },
-
- getHeader: function(name) {
- try {
- return this.transport.getResponseHeader(name) || null;
- } catch (e) { return null }
- },
-
- evalResponse: function() {
- try {
- return eval((this.transport.responseText || '').unfilterJSON());
- } catch (e) {
- this.dispatchException(e);
- }
- },
-
- dispatchException: function(exception) {
- (this.options.onException || Prototype.emptyFunction)(this, exception);
- Ajax.Responders.dispatch('onException', this, exception);
- }
-});
-
-Ajax.Request.Events =
- ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
-
-Ajax.Response = Class.create({
- initialize: function(request){
- this.request = request;
- var transport = this.transport = request.transport,
- readyState = this.readyState = transport.readyState;
-
- if((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) {
- this.status = this.getStatus();
- this.statusText = this.getStatusText();
- this.responseText = String.interpret(transport.responseText);
- this.headerJSON = this._getHeaderJSON();
- }
-
- if(readyState == 4) {
- var xml = transport.responseXML;
- this.responseXML = Object.isUndefined(xml) ? null : xml;
- this.responseJSON = this._getResponseJSON();
- }
- },
-
- status: 0,
- statusText: '',
-
- getStatus: Ajax.Request.prototype.getStatus,
-
- getStatusText: function() {
- try {
- return this.transport.statusText || '';
- } catch (e) { return '' }
- },
-
- getHeader: Ajax.Request.prototype.getHeader,
-
- getAllHeaders: function() {
- try {
- return this.getAllResponseHeaders();
- } catch (e) { return null }
- },
-
- getResponseHeader: function(name) {
- return this.transport.getResponseHeader(name);
- },
-
- getAllResponseHeaders: function() {
- return this.transport.getAllResponseHeaders();
- },
-
- _getHeaderJSON: function() {
- var json = this.getHeader('X-JSON');
- if (!json) return null;
- json = decodeURIComponent(escape(json));
- try {
- return json.evalJSON(this.request.options.sanitizeJSON ||
- !this.request.isSameOrigin());
- } catch (e) {
- this.request.dispatchException(e);
- }
- },
-
- _getResponseJSON: function() {
- var options = this.request.options;
- if (!options.evalJSON || (options.evalJSON != 'force' &&
- !(this.getHeader('Content-type') || '').include('application/json')) ||
- this.responseText.blank())
- return null;
- try {
- return this.responseText.evalJSON(options.sanitizeJSON ||
- !this.request.isSameOrigin());
- } catch (e) {
- this.request.dispatchException(e);
- }
- }
-});
-
-Ajax.Updater = Class.create(Ajax.Request, {
- initialize: function($super, container, url, options) {
- this.container = {
- success: (container.success || container),
- failure: (container.failure || (container.success ? null : container))
- };
-
- options = Object.clone(options);
- var onComplete = options.onComplete;
- options.onComplete = (function(response, json) {
- this.updateContent(response.responseText);
- if (Object.isFunction(onComplete)) onComplete(response, json);
- }).bind(this);
-
- $super(url, options);
- },
-
- updateContent: function(responseText) {
- var receiver = this.container[this.success() ? 'success' : 'failure'],
- options = this.options;
-
- if (!options.evalScripts) responseText = responseText.stripScripts();
-
- if (receiver = $(receiver)) {
- if (options.insertion) {
- if (Object.isString(options.insertion)) {
- var insertion = { }; insertion[options.insertion] = responseText;
- receiver.insert(insertion);
- }
- else options.insertion(receiver, responseText);
- }
- else receiver.update(responseText);
- }
- }
-});
-
-Ajax.PeriodicalUpdater = Class.create(Ajax.Base, {
- initialize: function($super, container, url, options) {
- $super(options);
- this.onComplete = this.options.onComplete;
-
- this.frequency = (this.options.frequency || 2);
- this.decay = (this.options.decay || 1);
-
- this.updater = { };
- this.container = container;
- this.url = url;
-
- this.start();
- },
-
- start: function() {
- this.options.onComplete = this.updateComplete.bind(this);
- this.onTimerEvent();
- },
-
- stop: function() {
- this.updater.options.onComplete = undefined;
- clearTimeout(this.timer);
- (this.onComplete || Prototype.emptyFunction).apply(this, arguments);
- },
-
- updateComplete: function(response) {
- if (this.options.decay) {
- this.decay = (response.responseText == this.lastText ?
- this.decay * this.options.decay : 1);
-
- this.lastText = response.responseText;
- }
- this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency);
- },
-
- onTimerEvent: function() {
- this.updater = new Ajax.Updater(this.container, this.url, this.options);
- }
-});
-function $(element) {
- if (arguments.length > 1) {
- for (var i = 0, elements = [], length = arguments.length; i < length; i++)
- elements.push($(arguments[i]));
- return elements;
- }
- if (Object.isString(element))
- element = document.getElementById(element);
- return Element.extend(element);
-}
-
-if (Prototype.BrowserFeatures.XPath) {
- document._getElementsByXPath = function(expression, parentElement) {
- var results = [];
- var query = document.evaluate(expression, $(parentElement) || document,
- null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
- for (var i = 0, length = query.snapshotLength; i < length; i++)
- results.push(Element.extend(query.snapshotItem(i)));
- return results;
- };
-}
-
-/*--------------------------------------------------------------------------*/
-
-if (!window.Node) var Node = { };
-
-if (!Node.ELEMENT_NODE) {
- // DOM level 2 ECMAScript Language Binding
- Object.extend(Node, {
- ELEMENT_NODE: 1,
- ATTRIBUTE_NODE: 2,
- TEXT_NODE: 3,
- CDATA_SECTION_NODE: 4,
- ENTITY_REFERENCE_NODE: 5,
- ENTITY_NODE: 6,
- PROCESSING_INSTRUCTION_NODE: 7,
- COMMENT_NODE: 8,
- DOCUMENT_NODE: 9,
- DOCUMENT_TYPE_NODE: 10,
- DOCUMENT_FRAGMENT_NODE: 11,
- NOTATION_NODE: 12
- });
-}
-
-(function() {
- var element = this.Element;
- this.Element = function(tagName, attributes) {
- attributes = attributes || { };
- tagName = tagName.toLowerCase();
- var cache = Element.cache;
- if (Prototype.Browser.IE && attributes.name) {
- tagName = '<' + tagName + ' name="' + attributes.name + '">';
- delete attributes.name;
- return Element.writeAttribute(document.createElement(tagName), attributes);
- }
- if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName));
- return Element.writeAttribute(cache[tagName].cloneNode(false), attributes);
- };
- Object.extend(this.Element, element || { });
- if (element) this.Element.prototype = element.prototype;
-}).call(window);
-
-Element.cache = { };
-
-Element.Methods = {
- visible: function(element) {
- return $(element).style.display != 'none';
- },
-
- toggle: function(element) {
- element = $(element);
- Element[Element.visible(element) ? 'hide' : 'show'](element);
- return element;
- },
-
- hide: function(element) {
- element = $(element);
- element.style.display = 'none';
- return element;
- },
-
- show: function(element) {
- element = $(element);
- element.style.display = '';
- return element;
- },
-
- remove: function(element) {
- element = $(element);
- element.parentNode.removeChild(element);
- return element;
- },
-
- update: function(element, content) {
- element = $(element);
- if (content && content.toElement) content = content.toElement();
- if (Object.isElement(content)) return element.update().insert(content);
- content = Object.toHTML(content);
- element.innerHTML = content.stripScripts();
- content.evalScripts.bind(content).defer();
- return element;
- },
-
- replace: function(element, content) {
- element = $(element);
- if (content && content.toElement) content = content.toElement();
- else if (!Object.isElement(content)) {
- content = Object.toHTML(content);
- var range = element.ownerDocument.createRange();
- range.selectNode(element);
- content.evalScripts.bind(content).defer();
- content = range.createContextualFragment(content.stripScripts());
- }
- element.parentNode.replaceChild(content, element);
- return element;
- },
-
- insert: function(element, insertions) {
- element = $(element);
-
- if (Object.isString(insertions) || Object.isNumber(insertions) ||
- Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML)))
- insertions = {bottom:insertions};
-
- var content, insert, tagName, childNodes;
-
- for (var position in insertions) {
- content = insertions[position];
- position = position.toLowerCase();
- insert = Element._insertionTranslations[position];
-
- if (content && content.toElement) content = content.toElement();
- if (Object.isElement(content)) {
- insert(element, content);
- continue;
- }
-
- content = Object.toHTML(content);
-
- tagName = ((position == 'before' || position == 'after')
- ? element.parentNode : element).tagName.toUpperCase();
-
- childNodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
-
- if (position == 'top' || position == 'after') childNodes.reverse();
- childNodes.each(insert.curry(element));
-
- content.evalScripts.bind(content).defer();
- }
-
- return element;
- },
-
- wrap: function(element, wrapper, attributes) {
- element = $(element);
- if (Object.isElement(wrapper))
- $(wrapper).writeAttribute(attributes || { });
- else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes);
- else wrapper = new Element('div', wrapper);
- if (element.parentNode)
- element.parentNode.replaceChild(wrapper, element);
- wrapper.appendChild(element);
- return wrapper;
- },
-
- inspect: function(element) {
- element = $(element);
- var result = '<' + element.tagName.toLowerCase();
- $H({'id': 'id', 'className': 'class'}).each(function(pair) {
- var property = pair.first(), attribute = pair.last();
- var value = (element[property] || '').toString();
- if (value) result += ' ' + attribute + '=' + value.inspect(true);
- });
- return result + '>';
- },
-
- recursivelyCollect: function(element, property) {
- element = $(element);
- var elements = [];
- while (element = element[property])
- if (element.nodeType == 1)
- elements.push(Element.extend(element));
- return elements;
- },
-
- ancestors: function(element) {
- return $(element).recursivelyCollect('parentNode');
- },
-
- descendants: function(element) {
- return $(element).select("*");
- },
-
- firstDescendant: function(element) {
- element = $(element).firstChild;
- while (element && element.nodeType != 1) element = element.nextSibling;
- return $(element);
- },
-
- immediateDescendants: function(element) {
- if (!(element = $(element).firstChild)) return [];
- while (element && element.nodeType != 1) element = element.nextSibling;
- if (element) return [element].concat($(element).nextSiblings());
- return [];
- },
-
- previousSiblings: function(element) {
- return $(element).recursivelyCollect('previousSibling');
- },
-
- nextSiblings: function(element) {
- return $(element).recursivelyCollect('nextSibling');
- },
-
- siblings: function(element) {
- element = $(element);
- return element.previousSiblings().reverse().concat(element.nextSiblings());
- },
-
- match: function(element, selector) {
- if (Object.isString(selector))
- selector = new Selector(selector);
- return selector.match($(element));
- },
-
- up: function(element, expression, index) {
- element = $(element);
- if (arguments.length == 1) return $(element.parentNode);
- var ancestors = element.ancestors();
- return Object.isNumber(expression) ? ancestors[expression] :
- Selector.findElement(ancestors, expression, index);
- },
-
- down: function(element, expression, index) {
- element = $(element);
- if (arguments.length == 1) return element.firstDescendant();
- return Object.isNumber(expression) ? element.descendants()[expression] :
- Element.select(element, expression)[index || 0];
- },
-
- previous: function(element, expression, index) {
- element = $(element);
- if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element));
- var previousSiblings = element.previousSiblings();
- return Object.isNumber(expression) ? previousSiblings[expression] :
- Selector.findElement(previousSiblings, expression, index);
- },
-
- next: function(element, expression, index) {
- element = $(element);
- if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element));
- var nextSiblings = element.nextSiblings();
- return Object.isNumber(expression) ? nextSiblings[expression] :
- Selector.findElement(nextSiblings, expression, index);
- },
-
- select: function() {
- var args = $A(arguments), element = $(args.shift());
- return Selector.findChildElements(element, args);
- },
-
- adjacent: function() {
- var args = $A(arguments), element = $(args.shift());
- return Selector.findChildElements(element.parentNode, args).without(element);
- },
-
- identify: function(element) {
- element = $(element);
- var id = element.readAttribute('id'), self = arguments.callee;
- if (id) return id;
- do { id = 'anonymous_element_' + self.counter++ } while ($(id));
- element.writeAttribute('id', id);
- return id;
- },
-
- readAttribute: function(element, name) {
- element = $(element);
- if (Prototype.Browser.IE) {
- var t = Element._attributeTranslations.read;
- if (t.values[name]) return t.values[name](element, name);
- if (t.names[name]) name = t.names[name];
- if (name.include(':')) {
- return (!element.attributes || !element.attributes[name]) ? null :
- element.attributes[name].value;
- }
- }
- return element.getAttribute(name);
- },
-
- writeAttribute: function(element, name, value) {
- element = $(element);
- var attributes = { }, t = Element._attributeTranslations.write;
-
- if (typeof name == 'object') attributes = name;
- else attributes[name] = Object.isUndefined(value) ? true : value;
-
- for (var attr in attributes) {
- name = t.names[attr] || attr;
- value = attributes[attr];
- if (t.values[attr]) name = t.values[attr](element, value);
- if (value === false || value === null)
- element.removeAttribute(name);
- else if (value === true)
- element.setAttribute(name, name);
- else element.setAttribute(name, value);
- }
- return element;
- },
-
- getHeight: function(element) {
- return $(element).getDimensions().height;
- },
-
- getWidth: function(element) {
- return $(element).getDimensions().width;
- },
-
- classNames: function(element) {
- return new Element.ClassNames(element);
- },
-
- hasClassName: function(element, className) {
- if (!(element = $(element))) return;
- var elementClassName = element.className;
- return (elementClassName.length > 0 && (elementClassName == className ||
- new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName)));
- },
-
- addClassName: function(element, className) {
- if (!(element = $(element))) return;
- if (!element.hasClassName(className))
- element.className += (element.className ? ' ' : '') + className;
- return element;
- },
-
- removeClassName: function(element, className) {
- if (!(element = $(element))) return;
- element.className = element.className.replace(
- new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip();
- return element;
- },
-
- toggleClassName: function(element, className) {
- if (!(element = $(element))) return;
- return element[element.hasClassName(className) ?
- 'removeClassName' : 'addClassName'](className);
- },
-
- // removes whitespace-only text node children
- cleanWhitespace: function(element) {
- element = $(element);
- var node = element.firstChild;
- while (node) {
- var nextNode = node.nextSibling;
- if (node.nodeType == 3 && !/\S/.test(node.nodeValue))
- element.removeChild(node);
- node = nextNode;
- }
- return element;
- },
-
- empty: function(element) {
- return $(element).innerHTML.blank();
- },
-
- descendantOf: function(element, ancestor) {
- element = $(element), ancestor = $(ancestor);
-
- if (element.compareDocumentPosition)
- return (element.compareDocumentPosition(ancestor) & 8) === 8;
-
- if (ancestor.contains)
- return ancestor.contains(element) && ancestor !== element;
-
- while (element = element.parentNode)
- if (element == ancestor) return true;
-
- return false;
- },
-
- scrollTo: function(element) {
- element = $(element);
- var pos = element.cumulativeOffset();
- window.scrollTo(pos[0], pos[1]);
- return element;
- },
-
- getStyle: function(element, style) {
- element = $(element);
- style = style == 'float' ? 'cssFloat' : style.camelize();
- var value = element.style[style];
- if (!value || value == 'auto') {
- var css = document.defaultView.getComputedStyle(element, null);
- value = css ? css[style] : null;
- }
- if (style == 'opacity') return value ? parseFloat(value) : 1.0;
- return value == 'auto' ? null : value;
- },
-
- getOpacity: function(element) {
- return $(element).getStyle('opacity');
- },
-
- setStyle: function(element, styles) {
- element = $(element);
- var elementStyle = element.style, match;
- if (Object.isString(styles)) {
- element.style.cssText += ';' + styles;
- return styles.include('opacity') ?
- element.setOpacity(styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element;
- }
- for (var property in styles)
- if (property == 'opacity') element.setOpacity(styles[property]);
- else
- elementStyle[(property == 'float' || property == 'cssFloat') ?
- (Object.isUndefined(elementStyle.styleFloat) ? 'cssFloat' : 'styleFloat') :
- property] = styles[property];
-
- return element;
- },
-
- setOpacity: function(element, value) {
- element = $(element);
- element.style.opacity = (value == 1 || value === '') ? '' :
- (value < 0.00001) ? 0 : value;
- return element;
- },
-
- getDimensions: function(element) {
- element = $(element);
- var display = element.getStyle('display');
- if (display != 'none' && display != null) // Safari bug
- return {width: element.offsetWidth, height: element.offsetHeight};
-
- // All *Width and *Height properties give 0 on elements with display none,
- // so enable the element temporarily
- var els = element.style;
- var originalVisibility = els.visibility;
- var originalPosition = els.position;
- var originalDisplay = els.display;
- els.visibility = 'hidden';
- els.position = 'absolute';
- els.display = 'block';
- var originalWidth = element.clientWidth;
- var originalHeight = element.clientHeight;
- els.display = originalDisplay;
- els.position = originalPosition;
- els.visibility = originalVisibility;
- return {width: originalWidth, height: originalHeight};
- },
-
- makePositioned: function(element) {
- element = $(element);
- var pos = Element.getStyle(element, 'position');
- if (pos == 'static' || !pos) {
- element._madePositioned = true;
- element.style.position = 'relative';
- // Opera returns the offset relative to the positioning context, when an
- // element is position relative but top and left have not been defined
- if (Prototype.Browser.Opera) {
- element.style.top = 0;
- element.style.left = 0;
- }
- }
- return element;
- },
-
- undoPositioned: function(element) {
- element = $(element);
- if (element._madePositioned) {
- element._madePositioned = undefined;
- element.style.position =
- element.style.top =
- element.style.left =
- element.style.bottom =
- element.style.right = '';
- }
- return element;
- },
-
- makeClipping: function(element) {
- element = $(element);
- if (element._overflow) return element;
- element._overflow = Element.getStyle(element, 'overflow') || 'auto';
- if (element._overflow !== 'hidden')
- element.style.overflow = 'hidden';
- return element;
- },
-
- undoClipping: function(element) {
- element = $(element);
- if (!element._overflow) return element;
- element.style.overflow = element._overflow == 'auto' ? '' : element._overflow;
- element._overflow = null;
- return element;
- },
-
- cumulativeOffset: function(element) {
- var valueT = 0, valueL = 0;
- do {
- valueT += element.offsetTop || 0;
- valueL += element.offsetLeft || 0;
- element = element.offsetParent;
- } while (element);
- return Element._returnOffset(valueL, valueT);
- },
-
- positionedOffset: function(element) {
- var valueT = 0, valueL = 0;
- do {
- valueT += element.offsetTop || 0;
- valueL += element.offsetLeft || 0;
- element = element.offsetParent;
- if (element) {
- if (element.tagName.toUpperCase() == 'BODY') break;
- var p = Element.getStyle(element, 'position');
- if (p !== 'static') break;
- }
- } while (element);
- return Element._returnOffset(valueL, valueT);
- },
-
- absolutize: function(element) {
- element = $(element);
- if (element.getStyle('position') == 'absolute') return element;
- // Position.prepare(); // To be done manually by Scripty when it needs it.
-
- var offsets = element.positionedOffset();
- var top = offsets[1];
- var left = offsets[0];
- var width = element.clientWidth;
- var height = element.clientHeight;
-
- element._originalLeft = left - parseFloat(element.style.left || 0);
- element._originalTop = top - parseFloat(element.style.top || 0);
- element._originalWidth = element.style.width;
- element._originalHeight = element.style.height;
-
- element.style.position = 'absolute';
- element.style.top = top + 'px';
- element.style.left = left + 'px';
- element.style.width = width + 'px';
- element.style.height = height + 'px';
- return element;
- },
-
- relativize: function(element) {
- element = $(element);
- if (element.getStyle('position') == 'relative') return element;
- // Position.prepare(); // To be done manually by Scripty when it needs it.
-
- element.style.position = 'relative';
- var top = parseFloat(element.style.top || 0) - (element._originalTop || 0);
- var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);
-
- element.style.top = top + 'px';
- element.style.left = left + 'px';
- element.style.height = element._originalHeight;
- element.style.width = element._originalWidth;
- return element;
- },
-
- cumulativeScrollOffset: function(element) {
- var valueT = 0, valueL = 0;
- do {
- valueT += element.scrollTop || 0;
- valueL += element.scrollLeft || 0;
- element = element.parentNode;
- } while (element);
- return Element._returnOffset(valueL, valueT);
- },
-
- getOffsetParent: function(element) {
- if (element.offsetParent) return $(element.offsetParent);
- if (element == document.body) return $(element);
-
- while ((element = element.parentNode) && element != document.body)
- if (Element.getStyle(element, 'position') != 'static')
- return $(element);
-
- return $(document.body);
- },
-
- viewportOffset: function(forElement) {
- var valueT = 0, valueL = 0;
-
- var element = forElement;
- do {
- valueT += element.offsetTop || 0;
- valueL += element.offsetLeft || 0;
-
- // Safari fix
- if (element.offsetParent == document.body &&
- Element.getStyle(element, 'position') == 'absolute') break;
-
- } while (element = element.offsetParent);
-
- element = forElement;
- do {
- if (!Prototype.Browser.Opera || (element.tagName && (element.tagName.toUpperCase() == 'BODY'))) {
- valueT -= element.scrollTop || 0;
- valueL -= element.scrollLeft || 0;
- }
- } while (element = element.parentNode);
-
- return Element._returnOffset(valueL, valueT);
- },
-
- clonePosition: function(element, source) {
- var options = Object.extend({
- setLeft: true,
- setTop: true,
- setWidth: true,
- setHeight: true,
- offsetTop: 0,
- offsetLeft: 0
- }, arguments[2] || { });
-
- // find page position of source
- source = $(source);
- var p = source.viewportOffset();
-
- // find coordinate system to use
- element = $(element);
- var delta = [0, 0];
- var parent = null;
- // delta [0,0] will do fine with position: fixed elements,
- // position:absolute needs offsetParent deltas
- if (Element.getStyle(element, 'position') == 'absolute') {
- parent = element.getOffsetParent();
- delta = parent.viewportOffset();
- }
-
- // correct by body offsets (fixes Safari)
- if (parent == document.body) {
- delta[0] -= document.body.offsetLeft;
- delta[1] -= document.body.offsetTop;
- }
-
- // set position
- if (options.setLeft) element.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px';
- if (options.setTop) element.style.top = (p[1] - delta[1] + options.offsetTop) + 'px';
- if (options.setWidth) element.style.width = source.offsetWidth + 'px';
- if (options.setHeight) element.style.height = source.offsetHeight + 'px';
- return element;
- }
-};
-
-Element.Methods.identify.counter = 1;
-
-Object.extend(Element.Methods, {
- getElementsBySelector: Element.Methods.select,
- childElements: Element.Methods.immediateDescendants
-});
-
-Element._attributeTranslations = {
- write: {
- names: {
- className: 'class',
- htmlFor: 'for'
- },
- values: { }
- }
-};
-
-if (Prototype.Browser.Opera) {
- Element.Methods.getStyle = Element.Methods.getStyle.wrap(
- function(proceed, element, style) {
- switch (style) {
- case 'left': case 'top': case 'right': case 'bottom':
- if (proceed(element, 'position') === 'static') return null;
- case 'height': case 'width':
- // returns '0px' for hidden elements; we want it to return null
- if (!Element.visible(element)) return null;
-
- // returns the border-box dimensions rather than the content-box
- // dimensions, so we subtract padding and borders from the value
- var dim = parseInt(proceed(element, style), 10);
-
- if (dim !== element['offset' + style.capitalize()])
- return dim + 'px';
-
- var properties;
- if (style === 'height') {
- properties = ['border-top-width', 'padding-top',
- 'padding-bottom', 'border-bottom-width'];
- }
- else {
- properties = ['border-left-width', 'padding-left',
- 'padding-right', 'border-right-width'];
- }
- return properties.inject(dim, function(memo, property) {
- var val = proceed(element, property);
- return val === null ? memo : memo - parseInt(val, 10);
- }) + 'px';
- default: return proceed(element, style);
- }
- }
- );
-
- Element.Methods.readAttribute = Element.Methods.readAttribute.wrap(
- function(proceed, element, attribute) {
- if (attribute === 'title') return element.title;
- return proceed(element, attribute);
- }
- );
-}
-
-else if (Prototype.Browser.IE) {
- // IE doesn't report offsets correctly for static elements, so we change them
- // to "relative" to get the values, then change them back.
- Element.Methods.getOffsetParent = Element.Methods.getOffsetParent.wrap(
- function(proceed, element) {
- element = $(element);
- // IE throws an error if element is not in document
- try { element.offsetParent }
- catch(e) { return $(document.body) }
- var position = element.getStyle('position');
- if (position !== 'static') return proceed(element);
- element.setStyle({ position: 'relative' });
- var value = proceed(element);
- element.setStyle({ position: position });
- return value;
- }
- );
-
- $w('positionedOffset viewportOffset').each(function(method) {
- Element.Methods[method] = Element.Methods[method].wrap(
- function(proceed, element) {
- element = $(element);
- try { element.offsetParent }
- catch(e) { return Element._returnOffset(0,0) }
- var position = element.getStyle('position');
- if (position !== 'static') return proceed(element);
- // Trigger hasLayout on the offset parent so that IE6 reports
- // accurate offsetTop and offsetLeft values for position: fixed.
- var offsetParent = element.getOffsetParent();
- if (offsetParent && offsetParent.getStyle('position') === 'fixed')
- offsetParent.setStyle({ zoom: 1 });
- element.setStyle({ position: 'relative' });
- var value = proceed(element);
- element.setStyle({ position: position });
- return value;
- }
- );
- });
-
- Element.Methods.cumulativeOffset = Element.Methods.cumulativeOffset.wrap(
- function(proceed, element) {
- try { element.offsetParent }
- catch(e) { return Element._returnOffset(0,0) }
- return proceed(element);
- }
- );
-
- Element.Methods.getStyle = function(element, style) {
- element = $(element);
- style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize();
- var value = element.style[style];
- if (!value && element.currentStyle) value = element.currentStyle[style];
-
- if (style == 'opacity') {
- if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/))
- if (value[1]) return parseFloat(value[1]) / 100;
- return 1.0;
- }
-
- if (value == 'auto') {
- if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none'))
- return element['offset' + style.capitalize()] + 'px';
- return null;
- }
- return value;
- };
-
- Element.Methods.setOpacity = function(element, value) {
- function stripAlpha(filter){
- return filter.replace(/alpha\([^\)]*\)/gi,'');
- }
- element = $(element);
- var currentStyle = element.currentStyle;
- if ((currentStyle && !currentStyle.hasLayout) ||
- (!currentStyle && element.style.zoom == 'normal'))
- element.style.zoom = 1;
-
- var filter = element.getStyle('filter'), style = element.style;
- if (value == 1 || value === '') {
- (filter = stripAlpha(filter)) ?
- style.filter = filter : style.removeAttribute('filter');
- return element;
- } else if (value < 0.00001) value = 0;
- style.filter = stripAlpha(filter) +
- 'alpha(opacity=' + (value * 100) + ')';
- return element;
- };
-
- Element._attributeTranslations = {
- read: {
- names: {
- 'class': 'className',
- 'for': 'htmlFor'
- },
- values: {
- _getAttr: function(element, attribute) {
- return element.getAttribute(attribute, 2);
- },
- _getAttrNode: function(element, attribute) {
- var node = element.getAttributeNode(attribute);
- return node ? node.value : "";
- },
- _getEv: function(element, attribute) {
- attribute = element.getAttribute(attribute);
- return attribute ? attribute.toString().slice(23, -2) : null;
- },
- _flag: function(element, attribute) {
- return $(element).hasAttribute(attribute) ? attribute : null;
- },
- style: function(element) {
- return element.style.cssText.toLowerCase();
- },
- title: function(element) {
- return element.title;
- }
- }
- }
- };
-
- Element._attributeTranslations.write = {
- names: Object.extend({
- cellpadding: 'cellPadding',
- cellspacing: 'cellSpacing'
- }, Element._attributeTranslations.read.names),
- values: {
- checked: function(element, value) {
- element.checked = !!value;
- },
-
- style: function(element, value) {
- element.style.cssText = value ? value : '';
- }
- }
- };
-
- Element._attributeTranslations.has = {};
-
- $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' +
- 'encType maxLength readOnly longDesc frameBorder').each(function(attr) {
- Element._attributeTranslations.write.names[attr.toLowerCase()] = attr;
- Element._attributeTranslations.has[attr.toLowerCase()] = attr;
- });
-
- (function(v) {
- Object.extend(v, {
- href: v._getAttr,
- src: v._getAttr,
- type: v._getAttr,
- action: v._getAttrNode,
- disabled: v._flag,
- checked: v._flag,
- readonly: v._flag,
- multiple: v._flag,
- onload: v._getEv,
- onunload: v._getEv,
- onclick: v._getEv,
- ondblclick: v._getEv,
- onmousedown: v._getEv,
- onmouseup: v._getEv,
- onmouseover: v._getEv,
- onmousemove: v._getEv,
- onmouseout: v._getEv,
- onfocus: v._getEv,
- onblur: v._getEv,
- onkeypress: v._getEv,
- onkeydown: v._getEv,
- onkeyup: v._getEv,
- onsubmit: v._getEv,
- onreset: v._getEv,
- onselect: v._getEv,
- onchange: v._getEv
- });
- })(Element._attributeTranslations.read.values);
-}
-
-else if (Prototype.Browser.Gecko && /rv:1\.8\.0/.test(navigator.userAgent)) {
- Element.Methods.setOpacity = function(element, value) {
- element = $(element);
- element.style.opacity = (value == 1) ? 0.999999 :
- (value === '') ? '' : (value < 0.00001) ? 0 : value;
- return element;
- };
-}
-
-else if (Prototype.Browser.WebKit) {
- Element.Methods.setOpacity = function(element, value) {
- element = $(element);
- element.style.opacity = (value == 1 || value === '') ? '' :
- (value < 0.00001) ? 0 : value;
-
- if (value == 1)
- if(element.tagName.toUpperCase() == 'IMG' && element.width) {
- element.width++; element.width--;
- } else try {
- var n = document.createTextNode(' ');
- element.appendChild(n);
- element.removeChild(n);
- } catch (e) { }
-
- return element;
- };
-
- // Safari returns margins on body which is incorrect if the child is absolutely
- // positioned. For performance reasons, redefine Element#cumulativeOffset for
- // KHTML/WebKit only.
- Element.Methods.cumulativeOffset = function(element) {
- var valueT = 0, valueL = 0;
- do {
- valueT += element.offsetTop || 0;
- valueL += element.offsetLeft || 0;
- if (element.offsetParent == document.body)
- if (Element.getStyle(element, 'position') == 'absolute') break;
-
- element = element.offsetParent;
- } while (element);
-
- return Element._returnOffset(valueL, valueT);
- };
-}
-
-if (Prototype.Browser.IE || Prototype.Browser.Opera) {
- // IE and Opera are missing .innerHTML support for TABLE-related and SELECT elements
- Element.Methods.update = function(element, content) {
- element = $(element);
-
- if (content && content.toElement) content = content.toElement();
- if (Object.isElement(content)) return element.update().insert(content);
-
- content = Object.toHTML(content);
- var tagName = element.tagName.toUpperCase();
-
- if (tagName in Element._insertionTranslations.tags) {
- $A(element.childNodes).each(function(node) { element.removeChild(node) });
- Element._getContentFromAnonymousElement(tagName, content.stripScripts())
- .each(function(node) { element.appendChild(node) });
- }
- else element.innerHTML = content.stripScripts();
-
- content.evalScripts.bind(content).defer();
- return element;
- };
-}
-
-if ('outerHTML' in document.createElement('div')) {
- Element.Methods.replace = function(element, content) {
- element = $(element);
-
- if (content && content.toElement) content = content.toElement();
- if (Object.isElement(content)) {
- element.parentNode.replaceChild(content, element);
- return element;
- }
-
- content = Object.toHTML(content);
- var parent = element.parentNode, tagName = parent.tagName.toUpperCase();
-
- if (Element._insertionTranslations.tags[tagName]) {
- var nextSibling = element.next();
- var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
- parent.removeChild(element);
- if (nextSibling)
- fragments.each(function(node) { parent.insertBefore(node, nextSibling) });
- else
- fragments.each(function(node) { parent.appendChild(node) });
- }
- else element.outerHTML = content.stripScripts();
-
- content.evalScripts.bind(content).defer();
- return element;
- };
-}
-
-Element._returnOffset = function(l, t) {
- var result = [l, t];
- result.left = l;
- result.top = t;
- return result;
-};
-
-Element._getContentFromAnonymousElement = function(tagName, html) {
- var div = new Element('div'), t = Element._insertionTranslations.tags[tagName];
- if (t) {
- div.innerHTML = t[0] + html + t[1];
- t[2].times(function() { div = div.firstChild });
- } else div.innerHTML = html;
- return $A(div.childNodes);
-};
-
-Element._insertionTranslations = {
- before: function(element, node) {
- element.parentNode.insertBefore(node, element);
- },
- top: function(element, node) {
- element.insertBefore(node, element.firstChild);
- },
- bottom: function(element, node) {
- element.appendChild(node);
- },
- after: function(element, node) {
- element.parentNode.insertBefore(node, element.nextSibling);
- },
- tags: {
- TABLE: ['<table>', '</table>', 1],
- TBODY: ['<table><tbody>', '</tbody></table>', 2],
- TR: ['<table><tbody><tr>', '</tr></tbody></table>', 3],
- TD: ['<table><tbody><tr><td>', '</td></tr></tbody></table>', 4],
- SELECT: ['<select>', '</select>', 1]
- }
-};
-
-(function() {
- Object.extend(this.tags, {
- THEAD: this.tags.TBODY,
- TFOOT: this.tags.TBODY,
- TH: this.tags.TD
- });
-}).call(Element._insertionTranslations);
-
-Element.Methods.Simulated = {
- hasAttribute: function(element, attribute) {
- attribute = Element._attributeTranslations.has[attribute] || attribute;
- var node = $(element).getAttributeNode(attribute);
- return !!(node && node.specified);
- }
-};
-
-Element.Methods.ByTag = { };
-
-Object.extend(Element, Element.Methods);
-
-if (!Prototype.BrowserFeatures.ElementExtensions &&
- document.createElement('div')['__proto__']) {
- window.HTMLElement = { };
- window.HTMLElement.prototype = document.createElement('div')['__proto__'];
- Prototype.BrowserFeatures.ElementExtensions = true;
-}
-
-Element.extend = (function() {
- if (Prototype.BrowserFeatures.SpecificElementExtensions)
- return Prototype.K;
-
- var Methods = { }, ByTag = Element.Methods.ByTag;
-
- var extend = Object.extend(function(element) {
- if (!element || element._extendedByPrototype ||
- element.nodeType != 1 || element == window) return element;
-
- var methods = Object.clone(Methods),
- tagName = element.tagName.toUpperCase(), property, value;
-
- // extend methods for specific tags
- if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]);
-
- for (property in methods) {
- value = methods[property];
- if (Object.isFunction(value) && !(property in element))
- element[property] = value.methodize();
- }
-
- element._extendedByPrototype = Prototype.emptyFunction;
- return element;
-
- }, {
- refresh: function() {
- // extend methods for all tags (Safari doesn't need this)
- if (!Prototype.BrowserFeatures.ElementExtensions) {
- Object.extend(Methods, Element.Methods);
- Object.extend(Methods, Element.Methods.Simulated);
- }
- }
- });
-
- extend.refresh();
- return extend;
-})();
-
-Element.hasAttribute = function(element, attribute) {
- if (element.hasAttribute) return element.hasAttribute(attribute);
- return Element.Methods.Simulated.hasAttribute(element, attribute);
-};
-
-Element.addMethods = function(methods) {
- var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag;
-
- if (!methods) {
- Object.extend(Form, Form.Methods);
- Object.extend(Form.Element, Form.Element.Methods);
- Object.extend(Element.Methods.ByTag, {
- "FORM": Object.clone(Form.Methods),
- "INPUT": Object.clone(Form.Element.Methods),
- "SELECT": Object.clone(Form.Element.Methods),
- "TEXTAREA": Object.clone(Form.Element.Methods)
- });
- }
-
- if (arguments.length == 2) {
- var tagName = methods;
- methods = arguments[1];
- }
-
- if (!tagName) Object.extend(Element.Methods, methods || { });
- else {
- if (Object.isArray(tagName)) tagName.each(extend);
- else extend(tagName);
- }
-
- function extend(tagName) {
- tagName = tagName.toUpperCase();
- if (!Element.Methods.ByTag[tagName])
- Element.Methods.ByTag[tagName] = { };
- Object.extend(Element.Methods.ByTag[tagName], methods);
- }
-
- function copy(methods, destination, onlyIfAbsent) {
- onlyIfAbsent = onlyIfAbsent || false;
- for (var property in methods) {
- var value = methods[property];
- if (!Object.isFunction(value)) continue;
- if (!onlyIfAbsent || !(property in destination))
- destination[property] = value.methodize();
- }
- }
-
- function findDOMClass(tagName) {
- var klass;
- var trans = {
- "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph",
- "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList",
- "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading",
- "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote",
- "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION":
- "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD":
- "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR":
- "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET":
- "FrameSet", "IFRAME": "IFrame"
- };
- if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element';
- if (window[klass]) return window[klass];
- klass = 'HTML' + tagName + 'Element';
- if (window[klass]) return window[klass];
- klass = 'HTML' + tagName.capitalize() + 'Element';
- if (window[klass]) return window[klass];
-
- window[klass] = { };
- window[klass].prototype = document.createElement(tagName)['__proto__'];
- return window[klass];
- }
-
- if (F.ElementExtensions) {
- copy(Element.Methods, HTMLElement.prototype);
- copy(Element.Methods.Simulated, HTMLElement.prototype, true);
- }
-
- if (F.SpecificElementExtensions) {
- for (var tag in Element.Methods.ByTag) {
- var klass = findDOMClass(tag);
- if (Object.isUndefined(klass)) continue;
- copy(T[tag], klass.prototype);
- }
- }
-
- Object.extend(Element, Element.Methods);
- delete Element.ByTag;
-
- if (Element.extend.refresh) Element.extend.refresh();
- Element.cache = { };
-};
-
-document.viewport = {
- getDimensions: function() {
- var dimensions = { }, B = Prototype.Browser;
- $w('width height').each(function(d) {
- var D = d.capitalize();
- if (B.WebKit && !document.evaluate) {
- // Safari <3.0 needs self.innerWidth/Height
- dimensions[d] = self['inner' + D];
- } else if (B.Opera && parseFloat(window.opera.version()) < 9.5) {
- // Opera <9.5 needs document.body.clientWidth/Height
- dimensions[d] = document.body['client' + D]
- } else {
- dimensions[d] = document.documentElement['client' + D];
- }
- });
- return dimensions;
- },
-
- getWidth: function() {
- return this.getDimensions().width;
- },
-
- getHeight: function() {
- return this.getDimensions().height;
- },
-
- getScrollOffsets: function() {
- return Element._returnOffset(
- window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft,
- window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop);
- }
-};
-/* Portions of the Selector class are derived from Jack Slocum's DomQuery,
- * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style
- * license. Please see http://www.yui-ext.com/ for more information. */
-
-var Selector = Class.create({
- initialize: function(expression) {
- this.expression = expression.strip();
-
- if (this.shouldUseSelectorsAPI()) {
- this.mode = 'selectorsAPI';
- } else if (this.shouldUseXPath()) {
- this.mode = 'xpath';
- this.compileXPathMatcher();
- } else {
- this.mode = "normal";
- this.compileMatcher();
- }
-
- },
-
- shouldUseXPath: function() {
- if (!Prototype.BrowserFeatures.XPath) return false;
-
- var e = this.expression;
-
- // Safari 3 chokes on :*-of-type and :empty
- if (Prototype.Browser.WebKit &&
- (e.include("-of-type") || e.include(":empty")))
- return false;
-
- // XPath can't do namespaced attributes, nor can it read
- // the "checked" property from DOM nodes
- if ((/(\[[\w-]*?:|:checked)/).test(e))
- return false;
-
- return true;
- },
-
- shouldUseSelectorsAPI: function() {
- if (!Prototype.BrowserFeatures.SelectorsAPI) return false;
-
- if (!Selector._div) Selector._div = new Element('div');
-
- // Make sure the browser treats the selector as valid. Test on an
- // isolated element to minimize cost of this check.
- try {
- Selector._div.querySelector(this.expression);
- } catch(e) {
- return false;
- }
-
- return true;
- },
-
- compileMatcher: function() {
- var e = this.expression, ps = Selector.patterns, h = Selector.handlers,
- c = Selector.criteria, le, p, m;
-
- if (Selector._cache[e]) {
- this.matcher = Selector._cache[e];
- return;
- }
-
- this.matcher = ["this.matcher = function(root) {",
- "var r = root, h = Selector.handlers, c = false, n;"];
-
- while (e && le != e && (/\S/).test(e)) {
- le = e;
- for (var i in ps) {
- p = ps[i];
- if (m = e.match(p)) {
- this.matcher.push(Object.isFunction(c[i]) ? c[i](m) :
- new Template(c[i]).evaluate(m));
- e = e.replace(m[0], '');
- break;
- }
- }
- }
-
- this.matcher.push("return h.unique(n);\n}");
- eval(this.matcher.join('\n'));
- Selector._cache[this.expression] = this.matcher;
- },
-
- compileXPathMatcher: function() {
- var e = this.expression, ps = Selector.patterns,
- x = Selector.xpath, le, m;
-
- if (Selector._cache[e]) {
- this.xpath = Selector._cache[e]; return;
- }
-
- this.matcher = ['.//*'];
- while (e && le != e && (/\S/).test(e)) {
- le = e;
- for (var i in ps) {
- if (m = e.match(ps[i])) {
- this.matcher.push(Object.isFunction(x[i]) ? x[i](m) :
- new Template(x[i]).evaluate(m));
- e = e.replace(m[0], '');
- break;
- }
- }
- }
-
- this.xpath = this.matcher.join('');
- Selector._cache[this.expression] = this.xpath;
- },
-
- findElements: function(root) {
- root = root || document;
- var e = this.expression, results;
-
- switch (this.mode) {
- case 'selectorsAPI':
- // querySelectorAll queries document-wide, then filters to descendants
- // of the context element. That's not what we want.
- // Add an explicit context to the selector if necessary.
- if (root !== document) {
- var oldId = root.id, id = $(root).identify();
- e = "#" + id + " " + e;
- }
-
- results = $A(root.querySelectorAll(e)).map(Element.extend);
- root.id = oldId;
-
- return results;
- case 'xpath':
- return document._getElementsByXPath(this.xpath, root);
- default:
- return this.matcher(root);
- }
- },
-
- match: function(element) {
- this.tokens = [];
-
- var e = this.expression, ps = Selector.patterns, as = Selector.assertions;
- var le, p, m;
-
- while (e && le !== e && (/\S/).test(e)) {
- le = e;
- for (var i in ps) {
- p = ps[i];
- if (m = e.match(p)) {
- // use the Selector.assertions methods unless the selector
- // is too complex.
- if (as[i]) {
- this.tokens.push([i, Object.clone(m)]);
- e = e.replace(m[0], '');
- } else {
- // reluctantly do a document-wide search
- // and look for a match in the array
- return this.findElements(document).include(element);
- }
- }
- }
- }
-
- var match = true, name, matches;
- for (var i = 0, token; token = this.tokens[i]; i++) {
- name = token[0], matches = token[1];
- if (!Selector.assertions[name](element, matches)) {
- match = false; break;
- }
- }
-
- return match;
- },
-
- toString: function() {
- return this.expression;
- },
-
- inspect: function() {
- return "#<Selector:" + this.expression.inspect() + ">";
- }
-});
-
-Object.extend(Selector, {
- _cache: { },
-
- xpath: {
- descendant: "//*",
- child: "/*",
- adjacent: "/following-sibling::*[1]",
- laterSibling: '/following-sibling::*',
- tagName: function(m) {
- if (m[1] == '*') return '';
- return "[local-name()='" + m[1].toLowerCase() +
- "' or local-name()='" + m[1].toUpperCase() + "']";
- },
- className: "[contains(concat(' ', @class, ' '), ' #{1} ')]",
- id: "[@id='#{1}']",
- attrPresence: function(m) {
- m[1] = m[1].toLowerCase();
- return new Template("[@#{1}]").evaluate(m);
- },
- attr: function(m) {
- m[1] = m[1].toLowerCase();
- m[3] = m[5] || m[6];
- return new Template(Selector.xpath.operators[m[2]]).evaluate(m);
- },
- pseudo: function(m) {
- var h = Selector.xpath.pseudos[m[1]];
- if (!h) return '';
- if (Object.isFunction(h)) return h(m);
- return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m);
- },
- operators: {
- '=': "[@#{1}='#{3}']",
- '!=': "[@#{1}!='#{3}']",
- '^=': "[starts-with(@#{1}, '#{3}')]",
- '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']",
- '*=': "[contains(@#{1}, '#{3}')]",
- '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]",
- '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]"
- },
- pseudos: {
- 'first-child': '[not(preceding-sibling::*)]',
- 'last-child': '[not(following-sibling::*)]',
- 'only-child': '[not(preceding-sibling::* or following-sibling::*)]',
- 'empty': "[count(*) = 0 and (count(text()) = 0)]",
- 'checked': "[@checked]",
- 'disabled': "[(@disabled) and (@type!='hidden')]",
- 'enabled': "[not(@disabled) and (@type!='hidden')]",
- 'not': function(m) {
- var e = m[6], p = Selector.patterns,
- x = Selector.xpath, le, v;
-
- var exclusion = [];
- while (e && le != e && (/\S/).test(e)) {
- le = e;
- for (var i in p) {
- if (m = e.match(p[i])) {
- v = Object.isFunction(x[i]) ? x[i](m) : new Template(x[i]).evaluate(m);
- exclusion.push("(" + v.substring(1, v.length - 1) + ")");
- e = e.replace(m[0], '');
- break;
- }
- }
- }
- return "[not(" + exclusion.join(" and ") + ")]";
- },
- 'nth-child': function(m) {
- return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m);
- },
- 'nth-last-child': function(m) {
- return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m);
- },
- 'nth-of-type': function(m) {
- return Selector.xpath.pseudos.nth("position() ", m);
- },
- 'nth-last-of-type': function(m) {
- return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m);
- },
- 'first-of-type': function(m) {
- m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](m);
- },
- 'last-of-type': function(m) {
- m[6] = "1"; return Selector.xpath.pseudos['nth-last-of-type'](m);
- },
- 'only-of-type': function(m) {
- var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m);
- },
- nth: function(fragment, m) {
- var mm, formula = m[6], predicate;
- if (formula == 'even') formula = '2n+0';
- if (formula == 'odd') formula = '2n+1';
- if (mm = formula.match(/^(\d+)$/)) // digit only
- return '[' + fragment + "= " + mm[1] + ']';
- if (mm = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
- if (mm[1] == "-") mm[1] = -1;
- var a = mm[1] ? Number(mm[1]) : 1;
- var b = mm[2] ? Number(mm[2]) : 0;
- predicate = "[((#{fragment} - #{b}) mod #{a} = 0) and " +
- "((#{fragment} - #{b}) div #{a} >= 0)]";
- return new Template(predicate).evaluate({
- fragment: fragment, a: a, b: b });
- }
- }
- }
- },
-
- criteria: {
- tagName: 'n = h.tagName(n, r, "#{1}", c); c = false;',
- className: 'n = h.className(n, r, "#{1}", c); c = false;',
- id: 'n = h.id(n, r, "#{1}", c); c = false;',
- attrPresence: 'n = h.attrPresence(n, r, "#{1}", c); c = false;',
- attr: function(m) {
- m[3] = (m[5] || m[6]);
- return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}", c); c = false;').evaluate(m);
- },
- pseudo: function(m) {
- if (m[6]) m[6] = m[6].replace(/"/g, '\\"');
- return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m);
- },
- descendant: 'c = "descendant";',
- child: 'c = "child";',
- adjacent: 'c = "adjacent";',
- laterSibling: 'c = "laterSibling";'
- },
-
- patterns: {
- // combinators must be listed first
- // (and descendant needs to be last combinator)
- laterSibling: /^\s*~\s*/,
- child: /^\s*>\s*/,
- adjacent: /^\s*\+\s*/,
- descendant: /^\s/,
-
- // selectors follow
- tagName: /^\s*(\*|[\w\-]+)(\b|$)?/,
- id: /^#([\w\-\*]+)(\b|$)/,
- className: /^\.([\w\-\*]+)(\b|$)/,
- pseudo:
-/^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/,
- attrPresence: /^\[((?:[\w]+:)?[\w]+)\]/,
- attr: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/
- },
-
- // for Selector.match and Element#match
- assertions: {
- tagName: function(element, matches) {
- return matches[1].toUpperCase() == element.tagName.toUpperCase();
- },
-
- className: function(element, matches) {
- return Element.hasClassName(element, matches[1]);
- },
-
- id: function(element, matches) {
- return element.id === matches[1];
- },
-
- attrPresence: function(element, matches) {
- return Element.hasAttribute(element, matches[1]);
- },
-
- attr: function(element, matches) {
- var nodeValue = Element.readAttribute(element, matches[1]);
- return nodeValue && Selector.operators[matches[2]](nodeValue, matches[5] || matches[6]);
- }
- },
-
- handlers: {
- // UTILITY FUNCTIONS
- // joins two collections
- concat: function(a, b) {
- for (var i = 0, node; node = b[i]; i++)
- a.push(node);
- return a;
- },
-
- // marks an array of nodes for counting
- mark: function(nodes) {
- var _true = Prototype.emptyFunction;
- for (var i = 0, node; node = nodes[i]; i++)
- node._countedByPrototype = _true;
- return nodes;
- },
-
- unmark: function(nodes) {
- for (var i = 0, node; node = nodes[i]; i++)
- node._countedByPrototype = undefined;
- return nodes;
- },
-
- // mark each child node with its position (for nth calls)
- // "ofType" flag indicates whether we're indexing for nth-of-type
- // rather than nth-child
- index: function(parentNode, reverse, ofType) {
- parentNode._countedByPrototype = Prototype.emptyFunction;
- if (reverse) {
- for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) {
- var node = nodes[i];
- if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;
- }
- } else {
- for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++)
- if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;
- }
- },
-
- // filters out duplicates and extends all nodes
- unique: function(nodes) {
- if (nodes.length == 0) return nodes;
- var results = [], n;
- for (var i = 0, l = nodes.length; i < l; i++)
- if (!(n = nodes[i])._countedByPrototype) {
- n._countedByPrototype = Prototype.emptyFunction;
- results.push(Element.extend(n));
- }
- return Selector.handlers.unmark(results);
- },
-
- // COMBINATOR FUNCTIONS
- descendant: function(nodes) {
- var h = Selector.handlers;
- for (var i = 0, results = [], node; node = nodes[i]; i++)
- h.concat(results, node.getElementsByTagName('*'));
- return results;
- },
-
- child: function(nodes) {
- var h = Selector.handlers;
- for (var i = 0, results = [], node; node = nodes[i]; i++) {
- for (var j = 0, child; child = node.childNodes[j]; j++)
- if (child.nodeType == 1 && child.tagName != '!') results.push(child);
- }
- return results;
- },
-
- adjacent: function(nodes) {
- for (var i = 0, results = [], node; node = nodes[i]; i++) {
- var next = this.nextElementSibling(node);
- if (next) results.push(next);
- }
- return results;
- },
-
- laterSibling: function(nodes) {
- var h = Selector.handlers;
- for (var i = 0, results = [], node; node = nodes[i]; i++)
- h.concat(results, Element.nextSiblings(node));
- return results;
- },
-
- nextElementSibling: function(node) {
- while (node = node.nextSibling)
- if (node.nodeType == 1) return node;
- return null;
- },
-
- previousElementSibling: function(node) {
- while (node = node.previousSibling)
- if (node.nodeType == 1) return node;
- return null;
- },
-
- // TOKEN FUNCTIONS
- tagName: function(nodes, root, tagName, combinator) {
- var uTagName = tagName.toUpperCase();
- var results = [], h = Selector.handlers;
- if (nodes) {
- if (combinator) {
- // fastlane for ordinary descendant combinators
- if (combinator == "descendant") {
- for (var i = 0, node; node = nodes[i]; i++)
- h.concat(results, node.getElementsByTagName(tagName));
- return results;
- } else nodes = this[combinator](nodes);
- if (tagName == "*") return nodes;
- }
- for (var i = 0, node; node = nodes[i]; i++)
- if (node.tagName.toUpperCase() === uTagName) results.push(node);
- return results;
- } else return root.getElementsByTagName(tagName);
- },
-
- id: function(nodes, root, id, combinator) {
- var targetNode = $(id), h = Selector.handlers;
- if (!targetNode) return [];
- if (!nodes && root == document) return [targetNode];
- if (nodes) {
- if (combinator) {
- if (combinator == 'child') {
- for (var i = 0, node; node = nodes[i]; i++)
- if (targetNode.parentNode == node) return [targetNode];
- } else if (combinator == 'descendant') {
- for (var i = 0, node; node = nodes[i]; i++)
- if (Element.descendantOf(targetNode, node)) return [targetNode];
- } else if (combinator == 'adjacent') {
- for (var i = 0, node; node = nodes[i]; i++)
- if (Selector.handlers.previousElementSibling(targetNode) == node)
- return [targetNode];
- } else nodes = h[combinator](nodes);
- }
- for (var i = 0, node; node = nodes[i]; i++)
- if (node == targetNode) return [targetNode];
- return [];
- }
- return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : [];
- },
-
- className: function(nodes, root, className, combinator) {
- if (nodes && combinator) nodes = this[combinator](nodes);
- return Selector.handlers.byClassName(nodes, root, className);
- },
-
- byClassName: function(nodes, root, className) {
- if (!nodes) nodes = Selector.handlers.descendant([root]);
- var needle = ' ' + className + ' ';
- for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) {
- nodeClassName = node.className;
- if (nodeClassName.length == 0) continue;
- if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle))
- results.push(node);
- }
- return results;
- },
-
- attrPresence: function(nodes, root, attr, combinator) {
- if (!nodes) nodes = root.getElementsByTagName("*");
- if (nodes && combinator) nodes = this[combinator](nodes);
- var results = [];
- for (var i = 0, node; node = nodes[i]; i++)
- if (Element.hasAttribute(node, attr)) results.push(node);
- return results;
- },
-
- attr: function(nodes, root, attr, value, operator, combinator) {
- if (!nodes) nodes = root.getElementsByTagName("*");
- if (nodes && combinator) nodes = this[combinator](nodes);
- var handler = Selector.operators[operator], results = [];
- for (var i = 0, node; node = nodes[i]; i++) {
- var nodeValue = Element.readAttribute(node, attr);
- if (nodeValue === null) continue;
- if (handler(nodeValue, value)) results.push(node);
- }
- return results;
- },
-
- pseudo: function(nodes, name, value, root, combinator) {
- if (nodes && combinator) nodes = this[combinator](nodes);
- if (!nodes) nodes = root.getElementsByTagName("*");
- return Selector.pseudos[name](nodes, value, root);
- }
- },
-
- pseudos: {
- 'first-child': function(nodes, value, root) {
- for (var i = 0, results = [], node; node = nodes[i]; i++) {
- if (Selector.handlers.previousElementSibling(node)) continue;
- results.push(node);
- }
- return results;
- },
- 'last-child': function(nodes, value, root) {
- for (var i = 0, results = [], node; node = nodes[i]; i++) {
- if (Selector.handlers.nextElementSibling(node)) continue;
- results.push(node);
- }
- return results;
- },
- 'only-child': function(nodes, value, root) {
- var h = Selector.handlers;
- for (var i = 0, results = [], node; node = nodes[i]; i++)
- if (!h.previousElementSibling(node) && !h.nextElementSibling(node))
- results.push(node);
- return results;
- },
- 'nth-child': function(nodes, formula, root) {
- return Selector.pseudos.nth(nodes, formula, root);
- },
- 'nth-last-child': function(nodes, formula, root) {
- return Selector.pseudos.nth(nodes, formula, root, true);
- },
- 'nth-of-type': function(nodes, formula, root) {
- return Selector.pseudos.nth(nodes, formula, root, false, true);
- },
- 'nth-last-of-type': function(nodes, formula, root) {
- return Selector.pseudos.nth(nodes, formula, root, true, true);
- },
- 'first-of-type': function(nodes, formula, root) {
- return Selector.pseudos.nth(nodes, "1", root, false, true);
- },
- 'last-of-type': function(nodes, formula, root) {
- return Selector.pseudos.nth(nodes, "1", root, true, true);
- },
- 'only-of-type': function(nodes, formula, root) {
- var p = Selector.pseudos;
- return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root);
- },
-
- // handles the an+b logic
- getIndices: function(a, b, total) {
- if (a == 0) return b > 0 ? [b] : [];
- return $R(1, total).inject([], function(memo, i) {
- if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i);
- return memo;
- });
- },
-
- // handles nth(-last)-child, nth(-last)-of-type, and (first|last)-of-type
- nth: function(nodes, formula, root, reverse, ofType) {
- if (nodes.length == 0) return [];
- if (formula == 'even') formula = '2n+0';
- if (formula == 'odd') formula = '2n+1';
- var h = Selector.handlers, results = [], indexed = [], m;
- h.mark(nodes);
- for (var i = 0, node; node = nodes[i]; i++) {
- if (!node.parentNode._countedByPrototype) {
- h.index(node.parentNode, reverse, ofType);
- indexed.push(node.parentNode);
- }
- }
- if (formula.match(/^\d+$/)) { // just a number
- formula = Number(formula);
- for (var i = 0, node; node = nodes[i]; i++)
- if (node.nodeIndex == formula) results.push(node);
- } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
- if (m[1] == "-") m[1] = -1;
- var a = m[1] ? Number(m[1]) : 1;
- var b = m[2] ? Number(m[2]) : 0;
- var indices = Selector.pseudos.getIndices(a, b, nodes.length);
- for (var i = 0, node, l = indices.length; node = nodes[i]; i++) {
- for (var j = 0; j < l; j++)
- if (node.nodeIndex == indices[j]) results.push(node);
- }
- }
- h.unmark(nodes);
- h.unmark(indexed);
- return results;
- },
-
- 'empty': function(nodes, value, root) {
- for (var i = 0, results = [], node; node = nodes[i]; i++) {
- // IE treats comments as element nodes
- if (node.tagName == '!' || node.firstChild) continue;
- results.push(node);
- }
- return results;
- },
-
- 'not': function(nodes, selector, root) {
- var h = Selector.handlers, selectorType, m;
- var exclusions = new Selector(selector).findElements(root);
- h.mark(exclusions);
- for (var i = 0, results = [], node; node = nodes[i]; i++)
- if (!node._countedByPrototype) results.push(node);
- h.unmark(exclusions);
- return results;
- },
-
- 'enabled': function(nodes, value, root) {
- for (var i = 0, results = [], node; node = nodes[i]; i++)
- if (!node.disabled && (!node.type || node.type !== 'hidden'))
- results.push(node);
- return results;
- },
-
- 'disabled': function(nodes, value, root) {
- for (var i = 0, results = [], node; node = nodes[i]; i++)
- if (node.disabled) results.push(node);
- return results;
- },
-
- 'checked': function(nodes, value, root) {
- for (var i = 0, results = [], node; node = nodes[i]; i++)
- if (node.checked) results.push(node);
- return results;
- }
- },
-
- operators: {
- '=': function(nv, v) { return nv == v; },
- '!=': function(nv, v) { return nv != v; },
- '^=': function(nv, v) { return nv == v || nv && nv.startsWith(v); },
- '$=': function(nv, v) { return nv == v || nv && nv.endsWith(v); },
- '*=': function(nv, v) { return nv == v || nv && nv.include(v); },
- '$=': function(nv, v) { return nv.endsWith(v); },
- '*=': function(nv, v) { return nv.include(v); },
- '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); },
- '|=': function(nv, v) { return ('-' + (nv || "").toUpperCase() +
- '-').include('-' + (v || "").toUpperCase() + '-'); }
- },
-
- split: function(expression) {
- var expressions = [];
- expression.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) {
- expressions.push(m[1].strip());
- });
- return expressions;
- },
-
- matchElements: function(elements, expression) {
- var matches = $$(expression), h = Selector.handlers;
- h.mark(matches);
- for (var i = 0, results = [], element; element = elements[i]; i++)
- if (element._countedByPrototype) results.push(element);
- h.unmark(matches);
- return results;
- },
-
- findElement: function(elements, expression, index) {
- if (Object.isNumber(expression)) {
- index = expression; expression = false;
- }
- return Selector.matchElements(elements, expression || '*')[index || 0];
- },
-
- findChildElements: function(element, expressions) {
- expressions = Selector.split(expressions.join(','));
- var results = [], h = Selector.handlers;
- for (var i = 0, l = expressions.length, selector; i < l; i++) {
- selector = new Selector(expressions[i].strip());
- h.concat(results, selector.findElements(element));
- }
- return (l > 1) ? h.unique(results) : results;
- }
-});
-
-if (Prototype.Browser.IE) {
- Object.extend(Selector.handlers, {
- // IE returns comment nodes on getElementsByTagName("*").
- // Filter them out.
- concat: function(a, b) {
- for (var i = 0, node; node = b[i]; i++)
- if (node.tagName !== "!") a.push(node);
- return a;
- },
-
- // IE improperly serializes _countedByPrototype in (inner|outer)HTML.
- unmark: function(nodes) {
- for (var i = 0, node; node = nodes[i]; i++)
- node.removeAttribute('_countedByPrototype');
- return nodes;
- }
- });
-}
-
-function $$() {
- return Selector.findChildElements(document, $A(arguments));
-}
-var Form = {
- reset: function(form) {
- $(form).reset();
- return form;
- },
-
- serializeElements: function(elements, options) {
- if (typeof options != 'object') options = { hash: !!options };
- else if (Object.isUndefined(options.hash)) options.hash = true;
- var key, value, submitted = false, submit = options.submit;
-
- var data = elements.inject({ }, function(result, element) {
- if (!element.disabled && element.name) {
- key = element.name; value = $(element).getValue();
- if (value != null && element.type != 'file' && (element.type != 'submit' || (!submitted &&
- submit !== false && (!submit || key == submit) && (submitted = true)))) {
- if (key in result) {
- // a key is already present; construct an array of values
- if (!Object.isArray(result[key])) result[key] = [result[key]];
- result[key].push(value);
- }
- else result[key] = value;
- }
- }
- return result;
- });
-
- return options.hash ? data : Object.toQueryString(data);
- }
-};
-
-Form.Methods = {
- serialize: function(form, options) {
- return Form.serializeElements(Form.getElements(form), options);
- },
-
- getElements: function(form) {
- return $A($(form).getElementsByTagName('*')).inject([],
- function(elements, child) {
- if (Form.Element.Serializers[child.tagName.toLowerCase()])
- elements.push(Element.extend(child));
- return elements;
- }
- );
- },
-
- getInputs: function(form, typeName, name) {
- form = $(form);
- var inputs = form.getElementsByTagName('input');
-
- if (!typeName && !name) return $A(inputs).map(Element.extend);
-
- for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) {
- var input = inputs[i];
- if ((typeName && input.type != typeName) || (name && input.name != name))
- continue;
- matchingInputs.push(Element.extend(input));
- }
-
- return matchingInputs;
- },
-
- disable: function(form) {
- form = $(form);
- Form.getElements(form).invoke('disable');
- return form;
- },
-
- enable: function(form) {
- form = $(form);
- Form.getElements(form).invoke('enable');
- return form;
- },
-
- findFirstElement: function(form) {
- var elements = $(form).getElements().findAll(function(element) {
- return 'hidden' != element.type && !element.disabled;
- });
- var firstByIndex = elements.findAll(function(element) {
- return element.hasAttribute('tabIndex') && element.tabIndex >= 0;
- }).sortBy(function(element) { return element.tabIndex }).first();
-
- return firstByIndex ? firstByIndex : elements.find(function(element) {
- return ['input', 'select', 'textarea'].include(element.tagName.toLowerCase());
- });
- },
-
- focusFirstElement: function(form) {
- form = $(form);
- form.findFirstElement().activate();
- return form;
- },
-
- request: function(form, options) {
- form = $(form), options = Object.clone(options || { });
-
- var params = options.parameters, action = form.readAttribute('action') || '';
- if (action.blank()) action = window.location.href;
- options.parameters = form.serialize(true);
-
- if (params) {
- if (Object.isString(params)) params = params.toQueryParams();
- Object.extend(options.parameters, params);
- }
-
- if (form.hasAttribute('method') && !options.method)
- options.method = form.method;
-
- return new Ajax.Request(action, options);
- }
-};
-
-/*--------------------------------------------------------------------------*/
-
-Form.Element = {
- focus: function(element) {
- $(element).focus();
- return element;
- },
-
- select: function(element) {
- $(element).select();
- return element;
- }
-};
-
-Form.Element.Methods = {
- serialize: function(element) {
- element = $(element);
- if (!element.disabled && element.name) {
- var value = element.getValue();
- if (value != undefined) {
- var pair = { };
- pair[element.name] = value;
- return Object.toQueryString(pair);
- }
- }
- return '';
- },
-
- getValue: function(element) {
- element = $(element);
- var method = element.tagName.toLowerCase();
- return Form.Element.Serializers[method](element);
- },
-
- setValue: function(element, value) {
- element = $(element);
- var method = element.tagName.toLowerCase();
- Form.Element.Serializers[method](element, value);
- return element;
- },
-
- clear: function(element) {
- $(element).value = '';
- return element;
- },
-
- present: function(element) {
- return $(element).value != '';
- },
-
- activate: function(element) {
- element = $(element);
- try {
- element.focus();
- if (element.select && (element.tagName.toLowerCase() != 'input' ||
- !['button', 'reset', 'submit'].include(element.type)))
- element.select();
- } catch (e) { }
- return element;
- },
-
- disable: function(element) {
- element = $(element);
- element.disabled = true;
- return element;
- },
-
- enable: function(element) {
- element = $(element);
- element.disabled = false;
- return element;
- }
-};
-
-/*--------------------------------------------------------------------------*/
-
-var Field = Form.Element;
-var $F = Form.Element.Methods.getValue;
-
-/*--------------------------------------------------------------------------*/
-
-Form.Element.Serializers = {
- input: function(element, value) {
- switch (element.type.toLowerCase()) {
- case 'checkbox':
- case 'radio':
- return Form.Element.Serializers.inputSelector(element, value);
- default:
- return Form.Element.Serializers.textarea(element, value);
- }
- },
-
- inputSelector: function(element, value) {
- if (Object.isUndefined(value)) return element.checked ? element.value : null;
- else element.checked = !!value;
- },
-
- textarea: function(element, value) {
- if (Object.isUndefined(value)) return element.value;
- else element.value = value;
- },
-
- select: function(element, value) {
- if (Object.isUndefined(value))
- return this[element.type == 'select-one' ?
- 'selectOne' : 'selectMany'](element);
- else {
- var opt, currentValue, single = !Object.isArray(value);
- for (var i = 0, length = element.length; i < length; i++) {
- opt = element.options[i];
- currentValue = this.optionValue(opt);
- if (single) {
- if (currentValue == value) {
- opt.selected = true;
- return;
- }
- }
- else opt.selected = value.include(currentValue);
- }
- }
- },
-
- selectOne: function(element) {
- var index = element.selectedIndex;
- return index >= 0 ? this.optionValue(element.options[index]) : null;
- },
-
- selectMany: function(element) {
- var values, length = element.length;
- if (!length) return null;
-
- for (var i = 0, values = []; i < length; i++) {
- var opt = element.options[i];
- if (opt.selected) values.push(this.optionValue(opt));
- }
- return values;
- },
-
- optionValue: function(opt) {
- // extend element because hasAttribute may not be native
- return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text;
- }
-};
-
-/*--------------------------------------------------------------------------*/
-
-Abstract.TimedObserver = Class.create(PeriodicalExecuter, {
- initialize: function($super, element, frequency, callback) {
- $super(callback, frequency);
- this.element = $(element);
- this.lastValue = this.getValue();
- },
-
- execute: function() {
- var value = this.getValue();
- if (Object.isString(this.lastValue) && Object.isString(value) ?
- this.lastValue != value : String(this.lastValue) != String(value)) {
- this.callback(this.element, value);
- this.lastValue = value;
- }
- }
-});
-
-Form.Element.Observer = Class.create(Abstract.TimedObserver, {
- getValue: function() {
- return Form.Element.getValue(this.element);
- }
-});
-
-Form.Observer = Class.create(Abstract.TimedObserver, {
- getValue: function() {
- return Form.serialize(this.element);
- }
-});
-
-/*--------------------------------------------------------------------------*/
-
-Abstract.EventObserver = Class.create({
- initialize: function(element, callback) {
- this.element = $(element);
- this.callback = callback;
-
- this.lastValue = this.getValue();
- if (this.element.tagName.toLowerCase() == 'form')
- this.registerFormCallbacks();
- else
- this.registerCallback(this.element);
- },
-
- onElementEvent: function() {
- var value = this.getValue();
- if (this.lastValue != value) {
- this.callback(this.element, value);
- this.lastValue = value;
- }
- },
-
- registerFormCallbacks: function() {
- Form.getElements(this.element).each(this.registerCallback, this);
- },
-
- registerCallback: function(element) {
- if (element.type) {
- switch (element.type.toLowerCase()) {
- case 'checkbox':
- case 'radio':
- Event.observe(element, 'click', this.onElementEvent.bind(this));
- break;
- default:
- Event.observe(element, 'change', this.onElementEvent.bind(this));
- break;
- }
- }
- }
-});
-
-Form.Element.EventObserver = Class.create(Abstract.EventObserver, {
- getValue: function() {
- return Form.Element.getValue(this.element);
- }
-});
-
-Form.EventObserver = Class.create(Abstract.EventObserver, {
- getValue: function() {
- return Form.serialize(this.element);
- }
-});
-if (!window.Event) var Event = { };
-
-Object.extend(Event, {
- KEY_BACKSPACE: 8,
- KEY_TAB: 9,
- KEY_RETURN: 13,
- KEY_ESC: 27,
- KEY_LEFT: 37,
- KEY_UP: 38,
- KEY_RIGHT: 39,
- KEY_DOWN: 40,
- KEY_DELETE: 46,
- KEY_HOME: 36,
- KEY_END: 35,
- KEY_PAGEUP: 33,
- KEY_PAGEDOWN: 34,
- KEY_INSERT: 45,
-
- cache: { },
-
- relatedTarget: function(event) {
- var element;
- switch(event.type) {
- case 'mouseover': element = event.fromElement; break;
- case 'mouseout': element = event.toElement; break;
- default: return null;
- }
- return Element.extend(element);
- }
-});
-
-Event.Methods = (function() {
- var isButton;
-
- if (Prototype.Browser.IE) {
- var buttonMap = { 0: 1, 1: 4, 2: 2 };
- isButton = function(event, code) {
- return event.button == buttonMap[code];
- };
-
- } else if (Prototype.Browser.WebKit) {
- isButton = function(event, code) {
- switch (code) {
- case 0: return event.which == 1 && !event.metaKey;
- case 1: return event.which == 1 && event.metaKey;
- default: return false;
- }
- };
-
- } else {
- isButton = function(event, code) {
- return event.which ? (event.which === code + 1) : (event.button === code);
- };
- }
-
- return {
- isLeftClick: function(event) { return isButton(event, 0) },
- isMiddleClick: function(event) { return isButton(event, 1) },
- isRightClick: function(event) { return isButton(event, 2) },
-
- element: function(event) {
- event = Event.extend(event);
-
- var node = event.target,
- type = event.type,
- currentTarget = event.currentTarget;
-
- if (currentTarget && currentTarget.tagName) {
- // Firefox screws up the "click" event when moving between radio buttons
- // via arrow keys. It also screws up the "load" and "error" events on images,
- // reporting the document as the target instead of the original image.
- if (type === 'load' || type === 'error' ||
- (type === 'click' && currentTarget.tagName.toLowerCase() === 'input'
- && currentTarget.type === 'radio'))
- node = currentTarget;
- }
- if (node.nodeType == Node.TEXT_NODE) node = node.parentNode;
- return Element.extend(node);
- },
-
- findElement: function(event, expression) {
- var element = Event.element(event);
- if (!expression) return element;
- var elements = [element].concat(element.ancestors());
- return Selector.findElement(elements, expression, 0);
- },
-
- pointer: function(event) {
- var docElement = document.documentElement,
- body = document.body || { scrollLeft: 0, scrollTop: 0 };
- return {
- x: event.pageX || (event.clientX +
- (docElement.scrollLeft || body.scrollLeft) -
- (docElement.clientLeft || 0)),
- y: event.pageY || (event.clientY +
- (docElement.scrollTop || body.scrollTop) -
- (docElement.clientTop || 0))
- };
- },
-
- pointerX: function(event) { return Event.pointer(event).x },
- pointerY: function(event) { return Event.pointer(event).y },
-
- stop: function(event) {
- Event.extend(event);
- event.preventDefault();
- event.stopPropagation();
- event.stopped = true;
- }
- };
-})();
-
-Event.extend = (function() {
- var methods = Object.keys(Event.Methods).inject({ }, function(m, name) {
- m[name] = Event.Methods[name].methodize();
- return m;
- });
-
- if (Prototype.Browser.IE) {
- Object.extend(methods, {
- stopPropagation: function() { this.cancelBubble = true },
- preventDefault: function() { this.returnValue = false },
- inspect: function() { return "[object Event]" }
- });
-
- return function(event) {
- if (!event) return false;
- if (event._extendedByPrototype) return event;
-
- event._extendedByPrototype = Prototype.emptyFunction;
- var pointer = Event.pointer(event);
- Object.extend(event, {
- target: event.srcElement,
- relatedTarget: Event.relatedTarget(event),
- pageX: pointer.x,
- pageY: pointer.y
- });
- return Object.extend(event, methods);
- };
-
- } else {
- Event.prototype = Event.prototype || document.createEvent("HTMLEvents")['__proto__'];
- Object.extend(Event.prototype, methods);
- return Prototype.K;
- }
-})();
-
-Object.extend(Event, (function() {
- var cache = Event.cache;
-
- function getEventID(element) {
- if (element._prototypeEventID) return element._prototypeEventID[0];
- arguments.callee.id = arguments.callee.id || 1;
- return element._prototypeEventID = [++arguments.callee.id];
- }
-
- function getDOMEventName(eventName) {
- if (eventName && eventName.include(':')) return "dataavailable";
- return eventName;
- }
-
- function getCacheForID(id) {
- return cache[id] = cache[id] || { };
- }
-
- function getWrappersForEventName(id, eventName) {
- var c = getCacheForID(id);
- return c[eventName] = c[eventName] || [];
- }
-
- function createWrapper(element, eventName, handler) {
- var id = getEventID(element);
- var c = getWrappersForEventName(id, eventName);
- if (c.pluck("handler").include(handler)) return false;
-
- var wrapper = function(event) {
- if (!Event || !Event.extend ||
- (event.eventName && event.eventName != eventName))
- return false;
-
- Event.extend(event);
- handler.call(element, event);
- };
-
- wrapper.handler = handler;
- c.push(wrapper);
- return wrapper;
- }
-
- function findWrapper(id, eventName, handler) {
- var c = getWrappersForEventName(id, eventName);
- return c.find(function(wrapper) { return wrapper.handler == handler });
- }
-
- function destroyWrapper(id, eventName, handler) {
- var c = getCacheForID(id);
- if (!c[eventName]) return false;
- c[eventName] = c[eventName].without(findWrapper(id, eventName, handler));
- }
-
- function destroyCache() {
- for (var id in cache)
- for (var eventName in cache[id])
- cache[id][eventName] = null;
- }
-
-
- // Internet Explorer needs to remove event handlers on page unload
- // in order to avoid memory leaks.
- if (window.attachEvent) {
- window.attachEvent("onunload", destroyCache);
- }
-
- // Safari has a dummy event handler on page unload so that it won't
- // use its bfcache. Safari <= 3.1 has an issue with restoring the "document"
- // object when page is returned to via the back button using its bfcache.
- if (Prototype.Browser.WebKit) {
- window.addEventListener('unload', Prototype.emptyFunction, false);
- }
-
- return {
- observe: function(element, eventName, handler) {
- element = $(element);
- var name = getDOMEventName(eventName);
-
- var wrapper = createWrapper(element, eventName, handler);
- if (!wrapper) return element;
-
- if (element.addEventListener) {
- element.addEventListener(name, wrapper, false);
- } else {
- element.attachEvent("on" + name, wrapper);
- }
-
- return element;
- },
-
- stopObserving: function(element, eventName, handler) {
- element = $(element);
- var id = getEventID(element), name = getDOMEventName(eventName);
-
- if (!handler && eventName) {
- getWrappersForEventName(id, eventName).each(function(wrapper) {
- element.stopObserving(eventName, wrapper.handler);
- });
- return element;
-
- } else if (!eventName) {
- Object.keys(getCacheForID(id)).each(function(eventName) {
- element.stopObserving(eventName);
- });
- return element;
- }
-
- var wrapper = findWrapper(id, eventName, handler);
- if (!wrapper) return element;
-
- if (element.removeEventListener) {
- element.removeEventListener(name, wrapper, false);
- } else {
- element.detachEvent("on" + name, wrapper);
- }
-
- destroyWrapper(id, eventName, handler);
-
- return element;
- },
-
- fire: function(element, eventName, memo) {
- element = $(element);
- if (element == document && document.createEvent && !element.dispatchEvent)
- element = document.documentElement;
-
- var event;
- if (document.createEvent) {
- event = document.createEvent("HTMLEvents");
- event.initEvent("dataavailable", true, true);
- } else {
- event = document.createEventObject();
- event.eventType = "ondataavailable";
- }
-
- event.eventName = eventName;
- event.memo = memo || { };
-
- if (document.createEvent) {
- element.dispatchEvent(event);
- } else {
- element.fireEvent(event.eventType, event);
- }
-
- return Event.extend(event);
- }
- };
-})());
-
-Object.extend(Event, Event.Methods);
-
-Element.addMethods({
- fire: Event.fire,
- observe: Event.observe,
- stopObserving: Event.stopObserving
-});
-
-Object.extend(document, {
- fire: Element.Methods.fire.methodize(),
- observe: Element.Methods.observe.methodize(),
- stopObserving: Element.Methods.stopObserving.methodize(),
- loaded: false
-});
-
-(function() {
- /* Support for the DOMContentLoaded event is based on work by Dan Webb,
- Matthias Miller, Dean Edwards and John Resig. */
-
- var timer;
-
- function fireContentLoadedEvent() {
- if (document.loaded) return;
- if (timer) window.clearInterval(timer);
- document.fire("dom:loaded");
- document.loaded = true;
- }
-
- if (document.addEventListener) {
- if (Prototype.Browser.WebKit) {
- timer = window.setInterval(function() {
- if (/loaded|complete/.test(document.readyState))
- fireContentLoadedEvent();
- }, 0);
-
- Event.observe(window, "load", fireContentLoadedEvent);
-
- } else {
- document.addEventListener("DOMContentLoaded",
- fireContentLoadedEvent, false);
- }
-
- } else {
- document.write("<script id=__onDOMContentLoaded defer src=//:><\/script>");
- $("__onDOMContentLoaded").onreadystatechange = function() {
- if (this.readyState == "complete") {
- this.onreadystatechange = null;
- fireContentLoadedEvent();
- }
- };
- }
-})();
-/*------------------------------- DEPRECATED -------------------------------*/
-
-Hash.toQueryString = Object.toQueryString;
-
-var Toggle = { display: Element.toggle };
-
-Element.Methods.childOf = Element.Methods.descendantOf;
-
-var Insertion = {
- Before: function(element, content) {
- return Element.insert(element, {before:content});
- },
-
- Top: function(element, content) {
- return Element.insert(element, {top:content});
- },
-
- Bottom: function(element, content) {
- return Element.insert(element, {bottom:content});
- },
-
- After: function(element, content) {
- return Element.insert(element, {after:content});
- }
-};
-
-var $continue = new Error('"throw $continue" is deprecated, use "return" instead');
-
-// This should be moved to script.aculo.us; notice the deprecated methods
-// further below, that map to the newer Element methods.
-var Position = {
- // set to true if needed, warning: firefox performance problems
- // NOT neeeded for page scrolling, only if draggable contained in
- // scrollable elements
- includeScrollOffsets: false,
-
- // must be called before calling withinIncludingScrolloffset, every time the
- // page is scrolled
- prepare: function() {
- this.deltaX = window.pageXOffset
- || document.documentElement.scrollLeft
- || document.body.scrollLeft
- || 0;
- this.deltaY = window.pageYOffset
- || document.documentElement.scrollTop
- || document.body.scrollTop
- || 0;
- },
-
- // caches x/y coordinate pair to use with overlap
- within: function(element, x, y) {
- if (this.includeScrollOffsets)
- return this.withinIncludingScrolloffsets(element, x, y);
- this.xcomp = x;
- this.ycomp = y;
- this.offset = Element.cumulativeOffset(element);
-
- return (y >= this.offset[1] &&
- y < this.offset[1] + element.offsetHeight &&
- x >= this.offset[0] &&
- x < this.offset[0] + element.offsetWidth);
- },
-
- withinIncludingScrolloffsets: function(element, x, y) {
- var offsetcache = Element.cumulativeScrollOffset(element);
-
- this.xcomp = x + offsetcache[0] - this.deltaX;
- this.ycomp = y + offsetcache[1] - this.deltaY;
- this.offset = Element.cumulativeOffset(element);
-
- return (this.ycomp >= this.offset[1] &&
- this.ycomp < this.offset[1] + element.offsetHeight &&
- this.xcomp >= this.offset[0] &&
- this.xcomp < this.offset[0] + element.offsetWidth);
- },
-
- // within must be called directly before
- overlap: function(mode, element) {
- if (!mode) return 0;
- if (mode == 'vertical')
- return ((this.offset[1] + element.offsetHeight) - this.ycomp) /
- element.offsetHeight;
- if (mode == 'horizontal')
- return ((this.offset[0] + element.offsetWidth) - this.xcomp) /
- element.offsetWidth;
- },
-
- // Deprecation layer -- use newer Element methods now (1.5.2).
-
- cumulativeOffset: Element.Methods.cumulativeOffset,
-
- positionedOffset: Element.Methods.positionedOffset,
-
- absolutize: function(element) {
- Position.prepare();
- return Element.absolutize(element);
- },
-
- relativize: function(element) {
- Position.prepare();
- return Element.relativize(element);
- },
-
- realOffset: Element.Methods.cumulativeScrollOffset,
-
- offsetParent: Element.Methods.getOffsetParent,
-
- page: Element.Methods.viewportOffset,
-
- clone: function(source, target, options) {
- options = options || { };
- return Element.clonePosition(target, source, options);
- }
-};
-
-/*--------------------------------------------------------------------------*/
-
-if (!document.getElementsByClassName) document.getElementsByClassName = function(instanceMethods){
- function iter(name) {
- return name.blank() ? null : "[contains(concat(' ', @class, ' '), ' " + name + " ')]";
- }
-
- instanceMethods.getElementsByClassName = Prototype.BrowserFeatures.XPath ?
- function(element, className) {
- className = className.toString().strip();
- var cond = /\s/.test(className) ? $w(className).map(iter).join('') : iter(className);
- return cond ? document._getElementsByXPath('.//*' + cond, element) : [];
- } : function(element, className) {
- className = className.toString().strip();
- var elements = [], classNames = (/\s/.test(className) ? $w(className) : null);
- if (!classNames && !className) return elements;
-
- var nodes = $(element).getElementsByTagName('*');
- className = ' ' + className + ' ';
-
- for (var i = 0, child, cn; child = nodes[i]; i++) {
- if (child.className && (cn = ' ' + child.className + ' ') && (cn.include(className) ||
- (classNames && classNames.all(function(name) {
- return !name.toString().blank() && cn.include(' ' + name + ' ');
- }))))
- elements.push(Element.extend(child));
- }
- return elements;
- };
-
- return function(className, parentElement) {
- return $(parentElement || document.body).getElementsByClassName(className);
- };
-}(Element.Methods);
-
-/*--------------------------------------------------------------------------*/
-
-Element.ClassNames = Class.create();
-Element.ClassNames.prototype = {
- initialize: function(element) {
- this.element = $(element);
- },
-
- _each: function(iterator) {
- this.element.className.split(/\s+/).select(function(name) {
- return name.length > 0;
- })._each(iterator);
- },
-
- set: function(className) {
- this.element.className = className;
- },
-
- add: function(classNameToAdd) {
- if (this.include(classNameToAdd)) return;
- this.set($A(this).concat(classNameToAdd).join(' '));
- },
-
- remove: function(classNameToRemove) {
- if (!this.include(classNameToRemove)) return;
- this.set($A(this).without(classNameToRemove).join(' '));
- },
-
- toString: function() {
- return $A(this).join(' ');
- }
-};
-
-Object.extend(Element.ClassNames.prototype, Enumerable);
-
-/*--------------------------------------------------------------------------*/
-
-Element.addMethods();
\ No newline at end of file diff --git a/addons/web_graph/static/lib/flotr2/lib/underscore-min.js b/addons/web_graph/static/lib/flotr2/lib/underscore-min.js deleted file mode 100644 index 5983694cfbb..00000000000 --- a/addons/web_graph/static/lib/flotr2/lib/underscore-min.js +++ /dev/null @@ -1,27 +0,0 @@ -// Underscore.js 1.1.7 -// (c) 2011 Jeremy Ashkenas, DocumentCloud Inc. -// Underscore is freely distributable under the MIT license. -// Portions of Underscore are inspired or borrowed from Prototype, -// Oliver Steele's Functional, and John Resig's Micro-Templating. -// For all details and documentation: -// http://documentcloud.github.com/underscore -(function(){var p=this,C=p._,m={},i=Array.prototype,n=Object.prototype,f=i.slice,D=i.unshift,E=n.toString,l=n.hasOwnProperty,s=i.forEach,t=i.map,u=i.reduce,v=i.reduceRight,w=i.filter,x=i.every,y=i.some,o=i.indexOf,z=i.lastIndexOf;n=Array.isArray;var F=Object.keys,q=Function.prototype.bind,b=function(a){return new j(a)};typeof module!=="undefined"&&module.exports?(module.exports=b,b._=b):p._=b;b.VERSION="1.1.7";var h=b.each=b.forEach=function(a,c,b){if(a!=null)if(s&&a.forEach===s)a.forEach(c,b);else if(a.length=== -+a.length)for(var e=0,k=a.length;e<k;e++){if(e in a&&c.call(b,a[e],e,a)===m)break}else for(e in a)if(l.call(a,e)&&c.call(b,a[e],e,a)===m)break};b.map=function(a,c,b){var e=[];if(a==null)return e;if(t&&a.map===t)return a.map(c,b);h(a,function(a,g,G){e[e.length]=c.call(b,a,g,G)});return e};b.reduce=b.foldl=b.inject=function(a,c,d,e){var k=d!==void 0;a==null&&(a=[]);if(u&&a.reduce===u)return e&&(c=b.bind(c,e)),k?a.reduce(c,d):a.reduce(c);h(a,function(a,b,f){k?d=c.call(e,d,a,b,f):(d=a,k=!0)});if(!k)throw new TypeError("Reduce of empty array with no initial value"); -return d};b.reduceRight=b.foldr=function(a,c,d,e){a==null&&(a=[]);if(v&&a.reduceRight===v)return e&&(c=b.bind(c,e)),d!==void 0?a.reduceRight(c,d):a.reduceRight(c);a=(b.isArray(a)?a.slice():b.toArray(a)).reverse();return b.reduce(a,c,d,e)};b.find=b.detect=function(a,c,b){var e;A(a,function(a,g,f){if(c.call(b,a,g,f))return e=a,!0});return e};b.filter=b.select=function(a,c,b){var e=[];if(a==null)return e;if(w&&a.filter===w)return a.filter(c,b);h(a,function(a,g,f){c.call(b,a,g,f)&&(e[e.length]=a)});return e}; -b.reject=function(a,c,b){var e=[];if(a==null)return e;h(a,function(a,g,f){c.call(b,a,g,f)||(e[e.length]=a)});return e};b.every=b.all=function(a,c,b){var e=!0;if(a==null)return e;if(x&&a.every===x)return a.every(c,b);h(a,function(a,g,f){if(!(e=e&&c.call(b,a,g,f)))return m});return e};var A=b.some=b.any=function(a,c,d){c=c||b.identity;var e=!1;if(a==null)return e;if(y&&a.some===y)return a.some(c,d);h(a,function(a,b,f){if(e|=c.call(d,a,b,f))return m});return!!e};b.include=b.contains=function(a,c){var b= -!1;if(a==null)return b;if(o&&a.indexOf===o)return a.indexOf(c)!=-1;A(a,function(a){if(b=a===c)return!0});return b};b.invoke=function(a,c){var d=f.call(arguments,2);return b.map(a,function(a){return(c.call?c||a:a[c]).apply(a,d)})};b.pluck=function(a,c){return b.map(a,function(a){return a[c]})};b.max=function(a,c,d){if(!c&&b.isArray(a))return Math.max.apply(Math,a);var e={computed:-Infinity};h(a,function(a,b,f){b=c?c.call(d,a,b,f):a;b>=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a, -c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);var e={computed:Infinity};h(a,function(a,b,f){b=c?c.call(d,a,b,f):a;b<e.computed&&(e={value:a,computed:b})});return e.value};b.sortBy=function(a,c,d){return b.pluck(b.map(a,function(a,b,f){return{value:a,criteria:c.call(d,a,b,f)}}).sort(function(a,b){var c=a.criteria,d=b.criteria;return c<d?-1:c>d?1:0}),"value")};b.groupBy=function(a,b){var d={};h(a,function(a,f){var g=b(a,f);(d[g]||(d[g]=[])).push(a)});return d};b.sortedIndex=function(a,c,d){d|| -(d=b.identity);for(var e=0,f=a.length;e<f;){var g=e+f>>1;d(a[g])<d(c)?e=g+1:f=g}return e};b.toArray=function(a){if(!a)return[];if(a.toArray)return a.toArray();if(b.isArray(a))return f.call(a);if(b.isArguments(a))return f.call(a);return b.values(a)};b.size=function(a){return b.toArray(a).length};b.first=b.head=function(a,b,d){return b!=null&&!d?f.call(a,0,b):a[0]};b.rest=b.tail=function(a,b,d){return f.call(a,b==null||d?1:b)};b.last=function(a){return a[a.length-1]};b.compact=function(a){return b.filter(a, -function(a){return!!a})};b.flatten=function(a){return b.reduce(a,function(a,d){if(b.isArray(d))return a.concat(b.flatten(d));a[a.length]=d;return a},[])};b.without=function(a){return b.difference(a,f.call(arguments,1))};b.uniq=b.unique=function(a,c){return b.reduce(a,function(a,e,f){if(0==f||(c===!0?b.last(a)!=e:!b.include(a,e)))a[a.length]=e;return a},[])};b.union=function(){return b.uniq(b.flatten(arguments))};b.intersection=b.intersect=function(a){var c=f.call(arguments,1);return b.filter(b.uniq(a), -function(a){return b.every(c,function(c){return b.indexOf(c,a)>=0})})};b.difference=function(a,c){return b.filter(a,function(a){return!b.include(c,a)})};b.zip=function(){for(var a=f.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c),e=0;e<c;e++)d[e]=b.pluck(a,""+e);return d};b.indexOf=function(a,c,d){if(a==null)return-1;var e;if(d)return d=b.sortedIndex(a,c),a[d]===c?d:-1;if(o&&a.indexOf===o)return a.indexOf(c);d=0;for(e=a.length;d<e;d++)if(a[d]===c)return d;return-1};b.lastIndexOf=function(a, -b){if(a==null)return-1;if(z&&a.lastIndexOf===z)return a.lastIndexOf(b);for(var d=a.length;d--;)if(a[d]===b)return d;return-1};b.range=function(a,b,d){arguments.length<=1&&(b=a||0,a=0);d=arguments[2]||1;for(var e=Math.max(Math.ceil((b-a)/d),0),f=0,g=Array(e);f<e;)g[f++]=a,a+=d;return g};b.bind=function(a,b){if(a.bind===q&&q)return q.apply(a,f.call(arguments,1));var d=f.call(arguments,2);return function(){return a.apply(b,d.concat(f.call(arguments)))}};b.bindAll=function(a){var c=f.call(arguments,1); -c.length==0&&(c=b.functions(a));h(c,function(c){a[c]=b.bind(a[c],a)});return a};b.memoize=function(a,c){var d={};c||(c=b.identity);return function(){var b=c.apply(this,arguments);return l.call(d,b)?d[b]:d[b]=a.apply(this,arguments)}};b.delay=function(a,b){var d=f.call(arguments,2);return setTimeout(function(){return a.apply(a,d)},b)};b.defer=function(a){return b.delay.apply(b,[a,1].concat(f.call(arguments,1)))};var B=function(a,b,d){var e;return function(){var f=this,g=arguments,h=function(){e=null; -a.apply(f,g)};d&&clearTimeout(e);if(d||!e)e=setTimeout(h,b)}};b.throttle=function(a,b){return B(a,b,!1)};b.debounce=function(a,b){return B(a,b,!0)};b.once=function(a){var b=!1,d;return function(){if(b)return d;b=!0;return d=a.apply(this,arguments)}};b.wrap=function(a,b){return function(){var d=[a].concat(f.call(arguments));return b.apply(this,d)}};b.compose=function(){var a=f.call(arguments);return function(){for(var b=f.call(arguments),d=a.length-1;d>=0;d--)b=[a[d].apply(this,b)];return b[0]}};b.after= -function(a,b){return function(){if(--a<1)return b.apply(this,arguments)}};b.keys=F||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var b=[],d;for(d in a)l.call(a,d)&&(b[b.length]=d);return b};b.values=function(a){return b.map(a,b.identity)};b.functions=b.methods=function(a){var c=[],d;for(d in a)b.isFunction(a[d])&&c.push(d);return c.sort()};b.extend=function(a){h(f.call(arguments,1),function(b){for(var d in b)b[d]!==void 0&&(a[d]=b[d])});return a};b.defaults=function(a){h(f.call(arguments, -1),function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};b.isEqual=function(a,c){if(a===c)return!0;var d=typeof a;if(d!=typeof c)return!1;if(a==c)return!0;if(!a&&c||a&&!c)return!1;if(a._chain)a=a._wrapped;if(c._chain)c=c._wrapped;if(a.isEqual)return a.isEqual(c);if(c.isEqual)return c.isEqual(a);if(b.isDate(a)&&b.isDate(c))return a.getTime()===c.getTime();if(b.isNaN(a)&&b.isNaN(c))return!1; -if(b.isRegExp(a)&&b.isRegExp(c))return a.source===c.source&&a.global===c.global&&a.ignoreCase===c.ignoreCase&&a.multiline===c.multiline;if(d!=="object")return!1;if(a.length&&a.length!==c.length)return!1;d=b.keys(a);var e=b.keys(c);if(d.length!=e.length)return!1;for(var f in a)if(!(f in c)||!b.isEqual(a[f],c[f]))return!1;return!0};b.isEmpty=function(a){if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(l.call(a,c))return!1;return!0};b.isElement=function(a){return!!(a&&a.nodeType== -1)};b.isArray=n||function(a){return E.call(a)==="[object Array]"};b.isObject=function(a){return a===Object(a)};b.isArguments=function(a){return!(!a||!l.call(a,"callee"))};b.isFunction=function(a){return!(!a||!a.constructor||!a.call||!a.apply)};b.isString=function(a){return!!(a===""||a&&a.charCodeAt&&a.substr)};b.isNumber=function(a){return!!(a===0||a&&a.toExponential&&a.toFixed)};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===!0||a===!1};b.isDate=function(a){return!(!a||!a.getTimezoneOffset|| -!a.setUTCFullYear)};b.isRegExp=function(a){return!(!a||!a.test||!a.exec||!(a.ignoreCase||a.ignoreCase===!1))};b.isNull=function(a){return a===null};b.isUndefined=function(a){return a===void 0};b.noConflict=function(){p._=C;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e=0;e<a;e++)b.call(d,e)};b.mixin=function(a){h(b.functions(a),function(c){H(c,b[c]=a[c])})};var I=0;b.uniqueId=function(a){var b=I++;return a?a+b:b};b.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g}; -b.template=function(a,c){var d=b.templateSettings;d="var __p=[],print=function(){__p.push.apply(__p,arguments);};with(obj||{}){__p.push('"+a.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(d.interpolate,function(a,b){return"',"+b.replace(/\\'/g,"'")+",'"}).replace(d.evaluate||null,function(a,b){return"');"+b.replace(/\\'/g,"'").replace(/[\r\n\t]/g," ")+"__p.push('"}).replace(/\r/g,"\\r").replace(/\n/g,"\\n").replace(/\t/g,"\\t")+"');}return __p.join('');";d=new Function("obj",d);return c?d(c):d}; -var j=function(a){this._wrapped=a};b.prototype=j.prototype;var r=function(a,c){return c?b(a).chain():a},H=function(a,c){j.prototype[a]=function(){var a=f.call(arguments);D.call(a,this._wrapped);return r(c.apply(b,a),this._chain)}};b.mixin(b);h(["pop","push","reverse","shift","sort","splice","unshift"],function(a){var b=i[a];j.prototype[a]=function(){b.apply(this._wrapped,arguments);return r(this._wrapped,this._chain)}});h(["concat","join","slice"],function(a){var b=i[a];j.prototype[a]=function(){return r(b.apply(this._wrapped, -arguments),this._chain)}});j.prototype.chain=function(){this._chain=!0;return this};j.prototype.value=function(){return this._wrapped}})(); diff --git a/addons/web_graph/static/lib/flotr2/lib/underscore.js b/addons/web_graph/static/lib/flotr2/lib/underscore.js deleted file mode 100644 index 4f56013259b..00000000000 --- a/addons/web_graph/static/lib/flotr2/lib/underscore.js +++ /dev/null @@ -1,839 +0,0 @@ -// Underscore.js 1.1.7 -// (c) 2011 Jeremy Ashkenas, DocumentCloud Inc. -// Underscore is freely distributable under the MIT license. -// Portions of Underscore are inspired or borrowed from Prototype, -// Oliver Steele's Functional, and John Resig's Micro-Templating. -// For all details and documentation: -// http://documentcloud.github.com/underscore - -(function() { - - // Baseline setup - // -------------- - - // Establish the root object, `window` in the browser, or `global` on the server. - var root = this; - - // Save the previous value of the `_` variable. - var previousUnderscore = root._; - - // Establish the object that gets returned to break out of a loop iteration. - var breaker = {}; - - // Save bytes in the minified (but not gzipped) version: - var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype; - - // Create quick reference variables for speed access to core prototypes. - var slice = ArrayProto.slice, - unshift = ArrayProto.unshift, - toString = ObjProto.toString, - hasOwnProperty = ObjProto.hasOwnProperty; - - // All **ECMAScript 5** native function implementations that we hope to use - // are declared here. - var - nativeForEach = ArrayProto.forEach, - nativeMap = ArrayProto.map, - nativeReduce = ArrayProto.reduce, - nativeReduceRight = ArrayProto.reduceRight, - nativeFilter = ArrayProto.filter, - nativeEvery = ArrayProto.every, - nativeSome = ArrayProto.some, - nativeIndexOf = ArrayProto.indexOf, - nativeLastIndexOf = ArrayProto.lastIndexOf, - nativeIsArray = Array.isArray, - nativeKeys = Object.keys, - nativeBind = FuncProto.bind; - - // Create a safe reference to the Underscore object for use below. - var _ = function(obj) { return new wrapper(obj); }; - - // Export the Underscore object for **CommonJS**, with backwards-compatibility - // for the old `require()` API. If we're not in CommonJS, add `_` to the - // global object. - if (typeof module !== 'undefined' && module.exports) { - module.exports = _; - _._ = _; - } else { - // Exported as a string, for Closure Compiler "advanced" mode. - root['_'] = _; - } - - // Current version. - _.VERSION = '1.1.7'; - - // Collection Functions - // -------------------- - - // The cornerstone, an `each` implementation, aka `forEach`. - // Handles objects with the built-in `forEach`, arrays, and raw objects. - // Delegates to **ECMAScript 5**'s native `forEach` if available. - var each = _.each = _.forEach = function(obj, iterator, context) { - if (obj == null) return; - if (nativeForEach && obj.forEach === nativeForEach) { - obj.forEach(iterator, context); - } else if (obj.length === +obj.length) { - for (var i = 0, l = obj.length; i < l; i++) { - if (i in obj && iterator.call(context, obj[i], i, obj) === breaker) return; - } - } else { - for (var key in obj) { - if (hasOwnProperty.call(obj, key)) { - if (iterator.call(context, obj[key], key, obj) === breaker) return; - } - } - } - }; - - // Return the results of applying the iterator to each element. - // Delegates to **ECMAScript 5**'s native `map` if available. - _.map = function(obj, iterator, context) { - var results = []; - if (obj == null) return results; - if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context); - each(obj, function(value, index, list) { - results[results.length] = iterator.call(context, value, index, list); - }); - return results; - }; - - // **Reduce** builds up a single result from a list of values, aka `inject`, - // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available. - _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) { - var initial = memo !== void 0; - if (obj == null) obj = []; - if (nativeReduce && obj.reduce === nativeReduce) { - if (context) iterator = _.bind(iterator, context); - return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator); - } - each(obj, function(value, index, list) { - if (!initial) { - memo = value; - initial = true; - } else { - memo = iterator.call(context, memo, value, index, list); - } - }); - if (!initial) throw new TypeError("Reduce of empty array with no initial value"); - return memo; - }; - - // The right-associative version of reduce, also known as `foldr`. - // Delegates to **ECMAScript 5**'s native `reduceRight` if available. - _.reduceRight = _.foldr = function(obj, iterator, memo, context) { - if (obj == null) obj = []; - if (nativeReduceRight && obj.reduceRight === nativeReduceRight) { - if (context) iterator = _.bind(iterator, context); - return memo !== void 0 ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator); - } - var reversed = (_.isArray(obj) ? obj.slice() : _.toArray(obj)).reverse(); - return _.reduce(reversed, iterator, memo, context); - }; - - // Return the first value which passes a truth test. Aliased as `detect`. - _.find = _.detect = function(obj, iterator, context) { - var result; - any(obj, function(value, index, list) { - if (iterator.call(context, value, index, list)) { - result = value; - return true; - } - }); - return result; - }; - - // Return all the elements that pass a truth test. - // Delegates to **ECMAScript 5**'s native `filter` if available. - // Aliased as `select`. - _.filter = _.select = function(obj, iterator, context) { - var results = []; - if (obj == null) return results; - if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context); - each(obj, function(value, index, list) { - if (iterator.call(context, value, index, list)) results[results.length] = value; - }); - return results; - }; - - // Return all the elements for which a truth test fails. - _.reject = function(obj, iterator, context) { - var results = []; - if (obj == null) return results; - each(obj, function(value, index, list) { - if (!iterator.call(context, value, index, list)) results[results.length] = value; - }); - return results; - }; - - // Determine whether all of the elements match a truth test. - // Delegates to **ECMAScript 5**'s native `every` if available. - // Aliased as `all`. - _.every = _.all = function(obj, iterator, context) { - var result = true; - if (obj == null) return result; - if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context); - each(obj, function(value, index, list) { - if (!(result = result && iterator.call(context, value, index, list))) return breaker; - }); - return result; - }; - - // Determine if at least one element in the object matches a truth test. - // Delegates to **ECMAScript 5**'s native `some` if available. - // Aliased as `any`. - var any = _.some = _.any = function(obj, iterator, context) { - iterator = iterator || _.identity; - var result = false; - if (obj == null) return result; - if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context); - each(obj, function(value, index, list) { - if (result |= iterator.call(context, value, index, list)) return breaker; - }); - return !!result; - }; - - // Determine if a given value is included in the array or object using `===`. - // Aliased as `contains`. - _.include = _.contains = function(obj, target) { - var found = false; - if (obj == null) return found; - if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1; - any(obj, function(value) { - if (found = value === target) return true; - }); - return found; - }; - - // Invoke a method (with arguments) on every item in a collection. - _.invoke = function(obj, method) { - var args = slice.call(arguments, 2); - return _.map(obj, function(value) { - return (method.call ? method || value : value[method]).apply(value, args); - }); - }; - - // Convenience version of a common use case of `map`: fetching a property. - _.pluck = function(obj, key) { - return _.map(obj, function(value){ return value[key]; }); - }; - - // Return the maximum element or (element-based computation). - _.max = function(obj, iterator, context) { - if (!iterator && _.isArray(obj)) return Math.max.apply(Math, obj); - var result = {computed : -Infinity}; - each(obj, function(value, index, list) { - var computed = iterator ? iterator.call(context, value, index, list) : value; - computed >= result.computed && (result = {value : value, computed : computed}); - }); - return result.value; - }; - - // Return the minimum element (or element-based computation). - _.min = function(obj, iterator, context) { - if (!iterator && _.isArray(obj)) return Math.min.apply(Math, obj); - var result = {computed : Infinity}; - each(obj, function(value, index, list) { - var computed = iterator ? iterator.call(context, value, index, list) : value; - computed < result.computed && (result = {value : value, computed : computed}); - }); - return result.value; - }; - - // Sort the object's values by a criterion produced by an iterator. - _.sortBy = function(obj, iterator, context) { - return _.pluck(_.map(obj, function(value, index, list) { - return { - value : value, - criteria : iterator.call(context, value, index, list) - }; - }).sort(function(left, right) { - var a = left.criteria, b = right.criteria; - return a < b ? -1 : a > b ? 1 : 0; - }), 'value'); - }; - - // Groups the object's values by a criterion produced by an iterator - _.groupBy = function(obj, iterator) { - var result = {}; - each(obj, function(value, index) { - var key = iterator(value, index); - (result[key] || (result[key] = [])).push(value); - }); - return result; - }; - - // Use a comparator function to figure out at what index an object should - // be inserted so as to maintain order. Uses binary search. - _.sortedIndex = function(array, obj, iterator) { - iterator || (iterator = _.identity); - var low = 0, high = array.length; - while (low < high) { - var mid = (low + high) >> 1; - iterator(array[mid]) < iterator(obj) ? low = mid + 1 : high = mid; - } - return low; - }; - - // Safely convert anything iterable into a real, live array. - _.toArray = function(iterable) { - if (!iterable) return []; - if (iterable.toArray) return iterable.toArray(); - if (_.isArray(iterable)) return slice.call(iterable); - if (_.isArguments(iterable)) return slice.call(iterable); - return _.values(iterable); - }; - - // Return the number of elements in an object. - _.size = function(obj) { - return _.toArray(obj).length; - }; - - // Array Functions - // --------------- - - // Get the first element of an array. Passing **n** will return the first N - // values in the array. Aliased as `head`. The **guard** check allows it to work - // with `_.map`. - _.first = _.head = function(array, n, guard) { - return (n != null) && !guard ? slice.call(array, 0, n) : array[0]; - }; - - // Returns everything but the first entry of the array. Aliased as `tail`. - // Especially useful on the arguments object. Passing an **index** will return - // the rest of the values in the array from that index onward. The **guard** - // check allows it to work with `_.map`. - _.rest = _.tail = function(array, index, guard) { - return slice.call(array, (index == null) || guard ? 1 : index); - }; - - // Get the last element of an array. - _.last = function(array) { - return array[array.length - 1]; - }; - - // Trim out all falsy values from an array. - _.compact = function(array) { - return _.filter(array, function(value){ return !!value; }); - }; - - // Return a completely flattened version of an array. - _.flatten = function(array) { - return _.reduce(array, function(memo, value) { - if (_.isArray(value)) return memo.concat(_.flatten(value)); - memo[memo.length] = value; - return memo; - }, []); - }; - - // Return a version of the array that does not contain the specified value(s). - _.without = function(array) { - return _.difference(array, slice.call(arguments, 1)); - }; - - // Produce a duplicate-free version of the array. If the array has already - // been sorted, you have the option of using a faster algorithm. - // Aliased as `unique`. - _.uniq = _.unique = function(array, isSorted) { - return _.reduce(array, function(memo, el, i) { - if (0 == i || (isSorted === true ? _.last(memo) != el : !_.include(memo, el))) memo[memo.length] = el; - return memo; - }, []); - }; - - // Produce an array that contains the union: each distinct element from all of - // the passed-in arrays. - _.union = function() { - return _.uniq(_.flatten(arguments)); - }; - - // Produce an array that contains every item shared between all the - // passed-in arrays. (Aliased as "intersect" for back-compat.) - _.intersection = _.intersect = function(array) { - var rest = slice.call(arguments, 1); - return _.filter(_.uniq(array), function(item) { - return _.every(rest, function(other) { - return _.indexOf(other, item) >= 0; - }); - }); - }; - - // Take the difference between one array and another. - // Only the elements present in just the first array will remain. - _.difference = function(array, other) { - return _.filter(array, function(value){ return !_.include(other, value); }); - }; - - // Zip together multiple lists into a single array -- elements that share - // an index go together. - _.zip = function() { - var args = slice.call(arguments); - var length = _.max(_.pluck(args, 'length')); - var results = new Array(length); - for (var i = 0; i < length; i++) results[i] = _.pluck(args, "" + i); - return results; - }; - - // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**), - // we need this function. Return the position of the first occurrence of an - // item in an array, or -1 if the item is not included in the array. - // Delegates to **ECMAScript 5**'s native `indexOf` if available. - // If the array is large and already in sort order, pass `true` - // for **isSorted** to use binary search. - _.indexOf = function(array, item, isSorted) { - if (array == null) return -1; - var i, l; - if (isSorted) { - i = _.sortedIndex(array, item); - return array[i] === item ? i : -1; - } - if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item); - for (i = 0, l = array.length; i < l; i++) if (array[i] === item) return i; - return -1; - }; - - - // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available. - _.lastIndexOf = function(array, item) { - if (array == null) return -1; - if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) return array.lastIndexOf(item); - var i = array.length; - while (i--) if (array[i] === item) return i; - return -1; - }; - - // Generate an integer Array containing an arithmetic progression. A port of - // the native Python `range()` function. See - // [the Python documentation](http://docs.python.org/library/functions.html#range). - _.range = function(start, stop, step) { - if (arguments.length <= 1) { - stop = start || 0; - start = 0; - } - step = arguments[2] || 1; - - var len = Math.max(Math.ceil((stop - start) / step), 0); - var idx = 0; - var range = new Array(len); - - while(idx < len) { - range[idx++] = start; - start += step; - } - - return range; - }; - - // Function (ahem) Functions - // ------------------ - - // Create a function bound to a given object (assigning `this`, and arguments, - // optionally). Binding with arguments is also known as `curry`. - // Delegates to **ECMAScript 5**'s native `Function.bind` if available. - // We check for `func.bind` first, to fail fast when `func` is undefined. - _.bind = function(func, obj) { - if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1)); - var args = slice.call(arguments, 2); - return function() { - return func.apply(obj, args.concat(slice.call(arguments))); - }; - }; - - // Bind all of an object's methods to that object. Useful for ensuring that - // all callbacks defined on an object belong to it. - _.bindAll = function(obj) { - var funcs = slice.call(arguments, 1); - if (funcs.length == 0) funcs = _.functions(obj); - each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); }); - return obj; - }; - - // Memoize an expensive function by storing its results. - _.memoize = function(func, hasher) { - var memo = {}; - hasher || (hasher = _.identity); - return function() { - var key = hasher.apply(this, arguments); - return hasOwnProperty.call(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments)); - }; - }; - - // Delays a function for the given number of milliseconds, and then calls - // it with the arguments supplied. - _.delay = function(func, wait) { - var args = slice.call(arguments, 2); - return setTimeout(function(){ return func.apply(func, args); }, wait); - }; - - // Defers a function, scheduling it to run after the current call stack has - // cleared. - _.defer = function(func) { - return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1))); - }; - - // Internal function used to implement `_.throttle` and `_.debounce`. - var limit = function(func, wait, debounce) { - var timeout; - return function() { - var context = this, args = arguments; - var throttler = function() { - timeout = null; - func.apply(context, args); - }; - if (debounce) clearTimeout(timeout); - if (debounce || !timeout) timeout = setTimeout(throttler, wait); - }; - }; - - // Returns a function, that, when invoked, will only be triggered at most once - // during a given window of time. - _.throttle = function(func, wait) { - return limit(func, wait, false); - }; - - // Returns a function, that, as long as it continues to be invoked, will not - // be triggered. The function will be called after it stops being called for - // N milliseconds. - _.debounce = function(func, wait) { - return limit(func, wait, true); - }; - - // Returns a function that will be executed at most one time, no matter how - // often you call it. Useful for lazy initialization. - _.once = function(func) { - var ran = false, memo; - return function() { - if (ran) return memo; - ran = true; - return memo = func.apply(this, arguments); - }; - }; - - // Returns the first function passed as an argument to the second, - // allowing you to adjust arguments, run code before and after, and - // conditionally execute the original function. - _.wrap = function(func, wrapper) { - return function() { - var args = [func].concat(slice.call(arguments)); - return wrapper.apply(this, args); - }; - }; - - // Returns a function that is the composition of a list of functions, each - // consuming the return value of the function that follows. - _.compose = function() { - var funcs = slice.call(arguments); - return function() { - var args = slice.call(arguments); - for (var i = funcs.length - 1; i >= 0; i--) { - args = [funcs[i].apply(this, args)]; - } - return args[0]; - }; - }; - - // Returns a function that will only be executed after being called N times. - _.after = function(times, func) { - return function() { - if (--times < 1) { return func.apply(this, arguments); } - }; - }; - - - // Object Functions - // ---------------- - - // Retrieve the names of an object's properties. - // Delegates to **ECMAScript 5**'s native `Object.keys` - _.keys = nativeKeys || function(obj) { - if (obj !== Object(obj)) throw new TypeError('Invalid object'); - var keys = []; - for (var key in obj) if (hasOwnProperty.call(obj, key)) keys[keys.length] = key; - return keys; - }; - - // Retrieve the values of an object's properties. - _.values = function(obj) { - return _.map(obj, _.identity); - }; - - // Return a sorted list of the function names available on the object. - // Aliased as `methods` - _.functions = _.methods = function(obj) { - var names = []; - for (var key in obj) { - if (_.isFunction(obj[key])) names.push(key); - } - return names.sort(); - }; - - // Extend a given object with all the properties in passed-in object(s). - _.extend = function(obj) { - each(slice.call(arguments, 1), function(source) { - for (var prop in source) { - if (source[prop] !== void 0) obj[prop] = source[prop]; - } - }); - return obj; - }; - - // Fill in a given object with default properties. - _.defaults = function(obj) { - each(slice.call(arguments, 1), function(source) { - for (var prop in source) { - if (obj[prop] == null) obj[prop] = source[prop]; - } - }); - return obj; - }; - - // Create a (shallow-cloned) duplicate of an object. - _.clone = function(obj) { - return _.isArray(obj) ? obj.slice() : _.extend({}, obj); - }; - - // Invokes interceptor with the obj, and then returns obj. - // The primary purpose of this method is to "tap into" a method chain, in - // order to perform operations on intermediate results within the chain. - _.tap = function(obj, interceptor) { - interceptor(obj); - return obj; - }; - - // Perform a deep comparison to check if two objects are equal. - _.isEqual = function(a, b) { - // Check object identity. - if (a === b) return true; - // Different types? - var atype = typeof(a), btype = typeof(b); - if (atype != btype) return false; - // Basic equality test (watch out for coercions). - if (a == b) return true; - // One is falsy and the other truthy. - if ((!a && b) || (a && !b)) return false; - // Unwrap any wrapped objects. - if (a._chain) a = a._wrapped; - if (b._chain) b = b._wrapped; - // One of them implements an isEqual()? - if (a.isEqual) return a.isEqual(b); - if (b.isEqual) return b.isEqual(a); - // Check dates' integer values. - if (_.isDate(a) && _.isDate(b)) return a.getTime() === b.getTime(); - // Both are NaN? - if (_.isNaN(a) && _.isNaN(b)) return false; - // Compare regular expressions. - if (_.isRegExp(a) && _.isRegExp(b)) - return a.source === b.source && - a.global === b.global && - a.ignoreCase === b.ignoreCase && - a.multiline === b.multiline; - // If a is not an object by this point, we can't handle it. - if (atype !== 'object') return false; - // Check for different array lengths before comparing contents. - if (a.length && (a.length !== b.length)) return false; - // Nothing else worked, deep compare the contents. - var aKeys = _.keys(a), bKeys = _.keys(b); - // Different object sizes? - if (aKeys.length != bKeys.length) return false; - // Recursive comparison of contents. - for (var key in a) if (!(key in b) || !_.isEqual(a[key], b[key])) return false; - return true; - }; - - // Is a given array or object empty? - _.isEmpty = function(obj) { - if (_.isArray(obj) || _.isString(obj)) return obj.length === 0; - for (var key in obj) if (hasOwnProperty.call(obj, key)) return false; - return true; - }; - - // Is a given value a DOM element? - _.isElement = function(obj) { - return !!(obj && obj.nodeType == 1); - }; - - // Is a given value an array? - // Delegates to ECMA5's native Array.isArray - _.isArray = nativeIsArray || function(obj) { - return toString.call(obj) === '[object Array]'; - }; - - // Is a given variable an object? - _.isObject = function(obj) { - return obj === Object(obj); - }; - - // Is a given variable an arguments object? - _.isArguments = function(obj) { - return !!(obj && hasOwnProperty.call(obj, 'callee')); - }; - - // Is a given value a function? - _.isFunction = function(obj) { - return !!(obj && obj.constructor && obj.call && obj.apply); - }; - - // Is a given value a string? - _.isString = function(obj) { - return !!(obj === '' || (obj && obj.charCodeAt && obj.substr)); - }; - - // Is a given value a number? - _.isNumber = function(obj) { - return !!(obj === 0 || (obj && obj.toExponential && obj.toFixed)); - }; - - // Is the given value `NaN`? `NaN` happens to be the only value in JavaScript - // that does not equal itself. - _.isNaN = function(obj) { - return obj !== obj; - }; - - // Is a given value a boolean? - _.isBoolean = function(obj) { - return obj === true || obj === false; - }; - - // Is a given value a date? - _.isDate = function(obj) { - return !!(obj && obj.getTimezoneOffset && obj.setUTCFullYear); - }; - - // Is the given value a regular expression? - _.isRegExp = function(obj) { - return !!(obj && obj.test && obj.exec && (obj.ignoreCase || obj.ignoreCase === false)); - }; - - // Is a given value equal to null? - _.isNull = function(obj) { - return obj === null; - }; - - // Is a given variable undefined? - _.isUndefined = function(obj) { - return obj === void 0; - }; - - // Utility Functions - // ----------------- - - // Run Underscore.js in *noConflict* mode, returning the `_` variable to its - // previous owner. Returns a reference to the Underscore object. - _.noConflict = function() { - root._ = previousUnderscore; - return this; - }; - - // Keep the identity function around for default iterators. - _.identity = function(value) { - return value; - }; - - // Run a function **n** times. - _.times = function (n, iterator, context) { - for (var i = 0; i < n; i++) iterator.call(context, i); - }; - - // Add your own custom functions to the Underscore object, ensuring that - // they're correctly added to the OOP wrapper as well. - _.mixin = function(obj) { - each(_.functions(obj), function(name){ - addToWrapper(name, _[name] = obj[name]); - }); - }; - - // Generate a unique integer id (unique within the entire client session). - // Useful for temporary DOM ids. - var idCounter = 0; - _.uniqueId = function(prefix) { - var id = idCounter++; - return prefix ? prefix + id : id; - }; - - // By default, Underscore uses ERB-style template delimiters, change the - // following template settings to use alternative delimiters. - _.templateSettings = { - evaluate : /<%([\s\S]+?)%>/g, - interpolate : /<%=([\s\S]+?)%>/g - }; - - // JavaScript micro-templating, similar to John Resig's implementation. - // Underscore templating handles arbitrary delimiters, preserves whitespace, - // and correctly escapes quotes within interpolated code. - _.template = function(str, data) { - var c = _.templateSettings; - var tmpl = 'var __p=[],print=function(){__p.push.apply(__p,arguments);};' + - 'with(obj||{}){__p.push(\'' + - str.replace(/\\/g, '\\\\') - .replace(/'/g, "\\'") - .replace(c.interpolate, function(match, code) { - return "'," + code.replace(/\\'/g, "'") + ",'"; - }) - .replace(c.evaluate || null, function(match, code) { - return "');" + code.replace(/\\'/g, "'") - .replace(/[\r\n\t]/g, ' ') + "__p.push('"; - }) - .replace(/\r/g, '\\r') - .replace(/\n/g, '\\n') - .replace(/\t/g, '\\t') - + "');}return __p.join('');"; - var func = new Function('obj', tmpl); - return data ? func(data) : func; - }; - - // The OOP Wrapper - // --------------- - - // If Underscore is called as a function, it returns a wrapped object that - // can be used OO-style. This wrapper holds altered versions of all the - // underscore functions. Wrapped objects may be chained. - var wrapper = function(obj) { this._wrapped = obj; }; - - // Expose `wrapper.prototype` as `_.prototype` - _.prototype = wrapper.prototype; - - // Helper function to continue chaining intermediate results. - var result = function(obj, chain) { - return chain ? _(obj).chain() : obj; - }; - - // A method to easily add functions to the OOP wrapper. - var addToWrapper = function(name, func) { - wrapper.prototype[name] = function() { - var args = slice.call(arguments); - unshift.call(args, this._wrapped); - return result(func.apply(_, args), this._chain); - }; - }; - - // Add all of the Underscore functions to the wrapper object. - _.mixin(_); - - // Add all mutator Array functions to the wrapper. - each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { - var method = ArrayProto[name]; - wrapper.prototype[name] = function() { - method.apply(this._wrapped, arguments); - return result(this._wrapped, this._chain); - }; - }); - - // Add all accessor Array functions to the wrapper. - each(['concat', 'join', 'slice'], function(name) { - var method = ArrayProto[name]; - wrapper.prototype[name] = function() { - return result(method.apply(this._wrapped, arguments), this._chain); - }; - }); - - // Start chaining a wrapped Underscore object. - wrapper.prototype.chain = function() { - this._chain = true; - return this; - }; - - // Extracts the result from a wrapped and chained object. - wrapper.prototype.value = function() { - return this._wrapped; - }; - -})(); diff --git a/addons/web_graph/static/lib/flotr2/lib/yepnope.js b/addons/web_graph/static/lib/flotr2/lib/yepnope.js deleted file mode 100644 index e856225343a..00000000000 --- a/addons/web_graph/static/lib/flotr2/lib/yepnope.js +++ /dev/null @@ -1 +0,0 @@ -/*yepnope1.0.2|WTFPL*/(function(a,b,c){function H(){var a=z;a.loader={load:G,i:0};return a}function G(a,b,c){var e=b=="c"?r:q;i=0,b=b||"j",u(a)?F(e,a,b,this.i++,d,c):(h.splice(this.i++,0,a),h.length==1&&E());return this}function F(a,c,d,g,j,l){function q(){!o&&A(n.readyState)&&(p.r=o=1,!i&&B(),n.onload=n.onreadystatechange=null,e(function(){m.removeChild(n)},0))}var n=b.createElement(a),o=0,p={t:d,s:c,e:l};n.src=n.data=c,!k&&(n.style.display="none"),n.width=n.height="0",a!="object"&&(n.type=d),n.onload=n.onreadystatechange=q,a=="img"?n.onerror=q:a=="script"&&(n.onerror=function(){p.e=p.r=1,E()}),h.splice(g,0,p),m.insertBefore(n,k?null:f),e(function(){o||(m.removeChild(n),p.r=p.e=o=1,B())},z.errorTimeout)}function E(){var a=h.shift();i=1,a?a.t?e(function(){a.t=="c"?D(a):C(a)},0):(a(),B()):i=0}function D(a){var c=b.createElement("link"),d;c.href=a.s,c.rel="stylesheet",c.type="text/css";if(!a.e&&(o||j)){var g=function(a){e(function(){if(!d)try{a.sheet.cssRules.length?(d=1,B()):g(a)}catch(b){b.code==1e3||b.message=="security"||b.message=="denied"?(d=1,e(function(){B()},0)):g(a)}},0)};g(c)}else c.onload=function(){d||(d=1,e(function(){B()},0))},a.e&&c.onload();e(function(){d||(d=1,B())},z.errorTimeout),!a.e&&f.parentNode.insertBefore(c,f)}function C(a){var c=b.createElement("script"),d;c.src=a.s,c.onreadystatechange=c.onload=function(){!d&&A(c.readyState)&&(d=1,B(),c.onload=c.onreadystatechange=null)},e(function(){d||(d=1,B())},z.errorTimeout),a.e?c.onload():f.parentNode.insertBefore(c,f)}function B(){var a=1,b=-1;while(h.length- ++b)if(h[b].s&&!(a=h[b].r))break;a&&E()}function A(a){return!a||a=="loaded"||a=="complete"}var d=b.documentElement,e=a.setTimeout,f=b.getElementsByTagName("script")[0],g={}.toString,h=[],i=0,j="MozAppearance"in d.style,k=j&&!!b.createRange().compareNode,l=j&&!k,m=k?d:f.parentNode,n=a.opera&&g.call(a.opera)=="[object Opera]",o="webkitAppearance"in d.style,p=o&&"async"in b.createElement("script"),q=j?"object":n||p?"img":"script",r=o?"img":q,s=Array.isArray||function(a){return g.call(a)=="[object Array]"},t=function(a){return Object(a)===a},u=function(a){return typeof a=="string"},v=function(a){return g.call(a)=="[object Function]"},w=[],x={},y,z;z=function(a){function h(a,b){function i(a){if(u(a))g(a,f,b,0,c);else if(t(a))for(h in a)a.hasOwnProperty(h)&&g(a[h],f,b,h,c)}var c=!!a.test,d=c?a.yep:a.nope,e=a.load||a.both,f=a.callback,h;i(d),i(e),a.complete&&b.load(a.complete)}function g(a,b,d,e,g){var h=f(a),i=h.autoCallback;if(!h.bypass){b&&(b=v(b)?b:b[a]||b[e]||b[a.split("/").pop().split("?")[0]]);if(h.instead)return h.instead(a,b,d,e,g);d.load(h.url,h.forceCSS||!h.forceJS&&/css$/.test(h.url)?"c":c,h.noexec),(v(b)||v(i))&&d.load(function(){H(),b&&b(h.origUrl,g,e),i&&i(h.origUrl,g,e)})}}function f(a){var b=a.split("!"),c=w.length,d=b.pop(),e=b.length,f={url:d,origUrl:d,prefixes:b},g,h;for(h=0;h<e;h++)g=x[b[h]],g&&(f=g(f));for(h=0;h<c;h++)f=w[h](f);return f}var b,d,e=this.yepnope.loader;if(u(a))g(a,0,e,0);else if(s(a))for(b=0;b<a.length;b++)d=a[b],u(d)?g(d,0,e,0):s(d)?z(d):t(d)&&h(d,e);else t(a)&&h(a,e)},z.addPrefix=function(a,b){x[a]=b},z.addFilter=function(a){w.push(a)},z.errorTimeout=1e4,b.readyState==null&&b.addEventListener&&(b.readyState="loading",b.addEventListener("DOMContentLoaded",y=function(){b.removeEventListener("DOMContentLoaded",y,0),b.readyState="complete"},0)),a.yepnope=H()})(this,this.document) diff --git a/addons/web_graph/static/lib/nvd3/d3.v3.js b/addons/web_graph/static/lib/nvd3/d3.v3.js new file mode 100644 index 00000000000..8dd7ffe7419 --- /dev/null +++ b/addons/web_graph/static/lib/nvd3/d3.v3.js @@ -0,0 +1,8436 @@ +d3 = function() { + var d3 = { + version: "3.1.5" + }; + if (!Date.now) Date.now = function() { + return +new Date(); + }; + var d3_document = document, d3_window = window; + try { + d3_document.createElement("div").style.setProperty("opacity", 0, ""); + } catch (error) { + var d3_style_prototype = d3_window.CSSStyleDeclaration.prototype, d3_style_setProperty = d3_style_prototype.setProperty; + d3_style_prototype.setProperty = function(name, value, priority) { + d3_style_setProperty.call(this, name, value + "", priority); + }; + } + d3.ascending = function(a, b) { + return a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN; + }; + d3.descending = function(a, b) { + return b < a ? -1 : b > a ? 1 : b >= a ? 0 : NaN; + }; + d3.min = function(array, f) { + var i = -1, n = array.length, a, b; + if (arguments.length === 1) { + while (++i < n && ((a = array[i]) == null || a != a)) a = undefined; + while (++i < n) if ((b = array[i]) != null && a > b) a = b; + } else { + while (++i < n && ((a = f.call(array, array[i], i)) == null || a != a)) a = undefined; + while (++i < n) if ((b = f.call(array, array[i], i)) != null && a > b) a = b; + } + return a; + }; + d3.max = function(array, f) { + var i = -1, n = array.length, a, b; + if (arguments.length === 1) { + while (++i < n && ((a = array[i]) == null || a != a)) a = undefined; + while (++i < n) if ((b = array[i]) != null && b > a) a = b; + } else { + while (++i < n && ((a = f.call(array, array[i], i)) == null || a != a)) a = undefined; + while (++i < n) if ((b = f.call(array, array[i], i)) != null && b > a) a = b; + } + return a; + }; + d3.extent = function(array, f) { + var i = -1, n = array.length, a, b, c; + if (arguments.length === 1) { + while (++i < n && ((a = c = array[i]) == null || a != a)) a = c = undefined; + while (++i < n) if ((b = array[i]) != null) { + if (a > b) a = b; + if (c < b) c = b; + } + } else { + while (++i < n && ((a = c = f.call(array, array[i], i)) == null || a != a)) a = undefined; + while (++i < n) if ((b = f.call(array, array[i], i)) != null) { + if (a > b) a = b; + if (c < b) c = b; + } + } + return [ a, c ]; + }; + d3.sum = function(array, f) { + var s = 0, n = array.length, a, i = -1; + if (arguments.length === 1) { + while (++i < n) if (!isNaN(a = +array[i])) s += a; + } else { + while (++i < n) if (!isNaN(a = +f.call(array, array[i], i))) s += a; + } + return s; + }; + function d3_number(x) { + return x != null && !isNaN(x); + } + d3.mean = function(array, f) { + var n = array.length, a, m = 0, i = -1, j = 0; + if (arguments.length === 1) { + while (++i < n) if (d3_number(a = array[i])) m += (a - m) / ++j; + } else { + while (++i < n) if (d3_number(a = f.call(array, array[i], i))) m += (a - m) / ++j; + } + return j ? m : undefined; + }; + d3.quantile = function(values, p) { + var H = (values.length - 1) * p + 1, h = Math.floor(H), v = +values[h - 1], e = H - h; + return e ? v + e * (values[h] - v) : v; + }; + d3.median = function(array, f) { + if (arguments.length > 1) array = array.map(f); + array = array.filter(d3_number); + return array.length ? d3.quantile(array.sort(d3.ascending), .5) : undefined; + }; + d3.bisector = function(f) { + return { + left: function(a, x, lo, hi) { + if (arguments.length < 3) lo = 0; + if (arguments.length < 4) hi = a.length; + while (lo < hi) { + var mid = lo + hi >>> 1; + if (f.call(a, a[mid], mid) < x) lo = mid + 1; else hi = mid; + } + return lo; + }, + right: function(a, x, lo, hi) { + if (arguments.length < 3) lo = 0; + if (arguments.length < 4) hi = a.length; + while (lo < hi) { + var mid = lo + hi >>> 1; + if (x < f.call(a, a[mid], mid)) hi = mid; else lo = mid + 1; + } + return lo; + } + }; + }; + var d3_bisector = d3.bisector(function(d) { + return d; + }); + d3.bisectLeft = d3_bisector.left; + d3.bisect = d3.bisectRight = d3_bisector.right; + d3.shuffle = function(array) { + var m = array.length, t, i; + while (m) { + i = Math.random() * m-- | 0; + t = array[m], array[m] = array[i], array[i] = t; + } + return array; + }; + d3.permute = function(array, indexes) { + var permutes = [], i = -1, n = indexes.length; + while (++i < n) permutes[i] = array[indexes[i]]; + return permutes; + }; + d3.zip = function() { + if (!(n = arguments.length)) return []; + for (var i = -1, m = d3.min(arguments, d3_zipLength), zips = new Array(m); ++i < m; ) { + for (var j = -1, n, zip = zips[i] = new Array(n); ++j < n; ) { + zip[j] = arguments[j][i]; + } + } + return zips; + }; + function d3_zipLength(d) { + return d.length; + } + d3.transpose = function(matrix) { + return d3.zip.apply(d3, matrix); + }; + d3.keys = function(map) { + var keys = []; + for (var key in map) keys.push(key); + return keys; + }; + d3.values = function(map) { + var values = []; + for (var key in map) values.push(map[key]); + return values; + }; + d3.entries = function(map) { + var entries = []; + for (var key in map) entries.push({ + key: key, + value: map[key] + }); + return entries; + }; + d3.merge = function(arrays) { + return Array.prototype.concat.apply([], arrays); + }; + d3.range = function(start, stop, step) { + if (arguments.length < 3) { + step = 1; + if (arguments.length < 2) { + stop = start; + start = 0; + } + } + if ((stop - start) / step === Infinity) throw new Error("infinite range"); + var range = [], k = d3_range_integerScale(Math.abs(step)), i = -1, j; + start *= k, stop *= k, step *= k; + if (step < 0) while ((j = start + step * ++i) > stop) range.push(j / k); else while ((j = start + step * ++i) < stop) range.push(j / k); + return range; + }; + function d3_range_integerScale(x) { + var k = 1; + while (x * k % 1) k *= 10; + return k; + } + function d3_class(ctor, properties) { + try { + for (var key in properties) { + Object.defineProperty(ctor.prototype, key, { + value: properties[key], + enumerable: false + }); + } + } catch (e) { + ctor.prototype = properties; + } + } + d3.map = function(object) { + var map = new d3_Map(); + for (var key in object) map.set(key, object[key]); + return map; + }; + function d3_Map() {} + d3_class(d3_Map, { + has: function(key) { + return d3_map_prefix + key in this; + }, + get: function(key) { + return this[d3_map_prefix + key]; + }, + set: function(key, value) { + return this[d3_map_prefix + key] = value; + }, + remove: function(key) { + key = d3_map_prefix + key; + return key in this && delete this[key]; + }, + keys: function() { + var keys = []; + this.forEach(function(key) { + keys.push(key); + }); + return keys; + }, + values: function() { + var values = []; + this.forEach(function(key, value) { + values.push(value); + }); + return values; + }, + entries: function() { + var entries = []; + this.forEach(function(key, value) { + entries.push({ + key: key, + value: value + }); + }); + return entries; + }, + forEach: function(f) { + for (var key in this) { + if (key.charCodeAt(0) === d3_map_prefixCode) { + f.call(this, key.substring(1), this[key]); + } + } + } + }); + var d3_map_prefix = "\0", d3_map_prefixCode = d3_map_prefix.charCodeAt(0); + d3.nest = function() { + var nest = {}, keys = [], sortKeys = [], sortValues, rollup; + function map(mapType, array, depth) { + if (depth >= keys.length) return rollup ? rollup.call(nest, array) : sortValues ? array.sort(sortValues) : array; + var i = -1, n = array.length, key = keys[depth++], keyValue, object, setter, valuesByKey = new d3_Map(), values; + while (++i < n) { + if (values = valuesByKey.get(keyValue = key(object = array[i]))) { + values.push(object); + } else { + valuesByKey.set(keyValue, [ object ]); + } + } + if (mapType) { + object = mapType(); + setter = function(keyValue, values) { + object.set(keyValue, map(mapType, values, depth)); + }; + } else { + object = {}; + setter = function(keyValue, values) { + object[keyValue] = map(mapType, values, depth); + }; + } + valuesByKey.forEach(setter); + return object; + } + function entries(map, depth) { + if (depth >= keys.length) return map; + var array = [], sortKey = sortKeys[depth++]; + map.forEach(function(key, keyMap) { + array.push({ + key: key, + values: entries(keyMap, depth) + }); + }); + return sortKey ? array.sort(function(a, b) { + return sortKey(a.key, b.key); + }) : array; + } + nest.map = function(array, mapType) { + return map(mapType, array, 0); + }; + nest.entries = function(array) { + return entries(map(d3.map, array, 0), 0); + }; + nest.key = function(d) { + keys.push(d); + return nest; + }; + nest.sortKeys = function(order) { + sortKeys[keys.length - 1] = order; + return nest; + }; + nest.sortValues = function(order) { + sortValues = order; + return nest; + }; + nest.rollup = function(f) { + rollup = f; + return nest; + }; + return nest; + }; + d3.set = function(array) { + var set = new d3_Set(); + if (array) for (var i = 0; i < array.length; i++) set.add(array[i]); + return set; + }; + function d3_Set() {} + d3_class(d3_Set, { + has: function(value) { + return d3_map_prefix + value in this; + }, + add: function(value) { + this[d3_map_prefix + value] = true; + return value; + }, + remove: function(value) { + value = d3_map_prefix + value; + return value in this && delete this[value]; + }, + values: function() { + var values = []; + this.forEach(function(value) { + values.push(value); + }); + return values; + }, + forEach: function(f) { + for (var value in this) { + if (value.charCodeAt(0) === d3_map_prefixCode) { + f.call(this, value.substring(1)); + } + } + } + }); + d3.behavior = {}; + d3.rebind = function(target, source) { + var i = 1, n = arguments.length, method; + while (++i < n) target[method = arguments[i]] = d3_rebind(target, source, source[method]); + return target; + }; + function d3_rebind(target, source, method) { + return function() { + var value = method.apply(source, arguments); + return value === source ? target : value; + }; + } + d3.dispatch = function() { + var dispatch = new d3_dispatch(), i = -1, n = arguments.length; + while (++i < n) dispatch[arguments[i]] = d3_dispatch_event(dispatch); + return dispatch; + }; + function d3_dispatch() {} + d3_dispatch.prototype.on = function(type, listener) { + var i = type.indexOf("."), name = ""; + if (i >= 0) { + name = type.substring(i + 1); + type = type.substring(0, i); + } + if (type) return arguments.length < 2 ? this[type].on(name) : this[type].on(name, listener); + if (arguments.length === 2) { + if (listener == null) for (type in this) { + if (this.hasOwnProperty(type)) this[type].on(name, null); + } + return this; + } + }; + function d3_dispatch_event(dispatch) { + var listeners = [], listenerByName = new d3_Map(); + function event() { + var z = listeners, i = -1, n = z.length, l; + while (++i < n) if (l = z[i].on) l.apply(this, arguments); + return dispatch; + } + event.on = function(name, listener) { + var l = listenerByName.get(name), i; + if (arguments.length < 2) return l && l.on; + if (l) { + l.on = null; + listeners = listeners.slice(0, i = listeners.indexOf(l)).concat(listeners.slice(i + 1)); + listenerByName.remove(name); + } + if (listener) listeners.push(listenerByName.set(name, { + on: listener + })); + return dispatch; + }; + return event; + } + d3.event = null; + function d3_eventCancel() { + d3.event.stopPropagation(); + d3.event.preventDefault(); + } + function d3_eventSource() { + var e = d3.event, s; + while (s = e.sourceEvent) e = s; + return e; + } + function d3_eventSuppress(target, type) { + function off() { + target.on(type, null); + } + target.on(type, function() { + d3_eventCancel(); + off(); + }, true); + setTimeout(off, 0); + } + function d3_eventDispatch(target) { + var dispatch = new d3_dispatch(), i = 0, n = arguments.length; + while (++i < n) dispatch[arguments[i]] = d3_dispatch_event(dispatch); + dispatch.of = function(thiz, argumentz) { + return function(e1) { + try { + var e0 = e1.sourceEvent = d3.event; + e1.target = target; + d3.event = e1; + dispatch[e1.type].apply(thiz, argumentz); + } finally { + d3.event = e0; + } + }; + }; + return dispatch; + } + d3.mouse = function(container) { + return d3_mousePoint(container, d3_eventSource()); + }; + var d3_mouse_bug44083 = /WebKit/.test(d3_window.navigator.userAgent) ? -1 : 0; + function d3_mousePoint(container, e) { + var svg = container.ownerSVGElement || container; + if (svg.createSVGPoint) { + var point = svg.createSVGPoint(); + if (d3_mouse_bug44083 < 0 && (d3_window.scrollX || d3_window.scrollY)) { + svg = d3.select(d3_document.body).append("svg").style("position", "absolute").style("top", 0).style("left", 0); + var ctm = svg[0][0].getScreenCTM(); + d3_mouse_bug44083 = !(ctm.f || ctm.e); + svg.remove(); + } + if (d3_mouse_bug44083) { + point.x = e.pageX; + point.y = e.pageY; + } else { + point.x = e.clientX; + point.y = e.clientY; + } + point = point.matrixTransform(container.getScreenCTM().inverse()); + return [ point.x, point.y ]; + } + var rect = container.getBoundingClientRect(); + return [ e.clientX - rect.left - container.clientLeft, e.clientY - rect.top - container.clientTop ]; + } + var d3_array = d3_arraySlice; + function d3_arrayCopy(pseudoarray) { + var i = -1, n = pseudoarray.length, array = []; + while (++i < n) array.push(pseudoarray[i]); + return array; + } + function d3_arraySlice(pseudoarray) { + return Array.prototype.slice.call(pseudoarray); + } + try { + d3_array(d3_document.documentElement.childNodes)[0].nodeType; + } catch (e) { + d3_array = d3_arrayCopy; + } + var d3_arraySubclass = [].__proto__ ? function(array, prototype) { + array.__proto__ = prototype; + } : function(array, prototype) { + for (var property in prototype) array[property] = prototype[property]; + }; + d3.touches = function(container, touches) { + if (arguments.length < 2) touches = d3_eventSource().touches; + return touches ? d3_array(touches).map(function(touch) { + var point = d3_mousePoint(container, touch); + point.identifier = touch.identifier; + return point; + }) : []; + }; + d3.behavior.drag = function() { + var event = d3_eventDispatch(drag, "drag", "dragstart", "dragend"), origin = null; + function drag() { + this.on("mousedown.drag", mousedown).on("touchstart.drag", mousedown); + } + function mousedown() { + var target = this, event_ = event.of(target, arguments), eventTarget = d3.event.target, touchId = d3.event.touches ? d3.event.changedTouches[0].identifier : null, offset, origin_ = point(), moved = 0; + var w = d3.select(d3_window).on(touchId != null ? "touchmove.drag-" + touchId : "mousemove.drag", dragmove).on(touchId != null ? "touchend.drag-" + touchId : "mouseup.drag", dragend, true); + if (origin) { + offset = origin.apply(target, arguments); + offset = [ offset.x - origin_[0], offset.y - origin_[1] ]; + } else { + offset = [ 0, 0 ]; + } + if (touchId == null) d3_eventCancel(); + event_({ + type: "dragstart" + }); + function point() { + var p = target.parentNode; + return touchId != null ? d3.touches(p).filter(function(p) { + return p.identifier === touchId; + })[0] : d3.mouse(p); + } + function dragmove() { + if (!target.parentNode) return dragend(); + var p = point(), dx = p[0] - origin_[0], dy = p[1] - origin_[1]; + moved |= dx | dy; + origin_ = p; + d3_eventCancel(); + event_({ + type: "drag", + x: p[0] + offset[0], + y: p[1] + offset[1], + dx: dx, + dy: dy + }); + } + function dragend() { + event_({ + type: "dragend" + }); + if (moved) { + d3_eventCancel(); + if (d3.event.target === eventTarget) d3_eventSuppress(w, "click"); + } + w.on(touchId != null ? "touchmove.drag-" + touchId : "mousemove.drag", null).on(touchId != null ? "touchend.drag-" + touchId : "mouseup.drag", null); + } + } + drag.origin = function(x) { + if (!arguments.length) return origin; + origin = x; + return drag; + }; + return d3.rebind(drag, event, "on"); + }; + function d3_selection(groups) { + d3_arraySubclass(groups, d3_selectionPrototype); + return groups; + } + var d3_select = function(s, n) { + return n.querySelector(s); + }, d3_selectAll = function(s, n) { + return n.querySelectorAll(s); + }, d3_selectRoot = d3_document.documentElement, d3_selectMatcher = d3_selectRoot.matchesSelector || d3_selectRoot.webkitMatchesSelector || d3_selectRoot.mozMatchesSelector || d3_selectRoot.msMatchesSelector || d3_selectRoot.oMatchesSelector, d3_selectMatches = function(n, s) { + return d3_selectMatcher.call(n, s); + }; + if (typeof Sizzle === "function") { + d3_select = function(s, n) { + return Sizzle(s, n)[0] || null; + }; + d3_selectAll = function(s, n) { + return Sizzle.uniqueSort(Sizzle(s, n)); + }; + d3_selectMatches = Sizzle.matchesSelector; + } + var d3_selectionPrototype = []; + d3.selection = function() { + return d3_selectionRoot; + }; + d3.selection.prototype = d3_selectionPrototype; + d3_selectionPrototype.select = function(selector) { + var subgroups = [], subgroup, subnode, group, node; + if (typeof selector !== "function") selector = d3_selection_selector(selector); + for (var j = -1, m = this.length; ++j < m; ) { + subgroups.push(subgroup = []); + subgroup.parentNode = (group = this[j]).parentNode; + for (var i = -1, n = group.length; ++i < n; ) { + if (node = group[i]) { + subgroup.push(subnode = selector.call(node, node.__data__, i)); + if (subnode && "__data__" in node) subnode.__data__ = node.__data__; + } else { + subgroup.push(null); + } + } + } + return d3_selection(subgroups); + }; + function d3_selection_selector(selector) { + return function() { + return d3_select(selector, this); + }; + } + d3_selectionPrototype.selectAll = function(selector) { + var subgroups = [], subgroup, node; + if (typeof selector !== "function") selector = d3_selection_selectorAll(selector); + for (var j = -1, m = this.length; ++j < m; ) { + for (var group = this[j], i = -1, n = group.length; ++i < n; ) { + if (node = group[i]) { + subgroups.push(subgroup = d3_array(selector.call(node, node.__data__, i))); + subgroup.parentNode = node; + } + } + } + return d3_selection(subgroups); + }; + function d3_selection_selectorAll(selector) { + return function() { + return d3_selectAll(selector, this); + }; + } + var d3_nsPrefix = { + svg: "http://www.w3.org/2000/svg", + xhtml: "http://www.w3.org/1999/xhtml", + xlink: "http://www.w3.org/1999/xlink", + xml: "http://www.w3.org/XML/1998/namespace", + xmlns: "http://www.w3.org/2000/xmlns/" + }; + d3.ns = { + prefix: d3_nsPrefix, + qualify: function(name) { + var i = name.indexOf(":"), prefix = name; + if (i >= 0) { + prefix = name.substring(0, i); + name = name.substring(i + 1); + } + return d3_nsPrefix.hasOwnProperty(prefix) ? { + space: d3_nsPrefix[prefix], + local: name + } : name; + } + }; + d3_selectionPrototype.attr = function(name, value) { + if (arguments.length < 2) { + if (typeof name === "string") { + var node = this.node(); + name = d3.ns.qualify(name); + return name.local ? node.getAttributeNS(name.space, name.local) : node.getAttribute(name); + } + for (value in name) this.each(d3_selection_attr(value, name[value])); + return this; + } + return this.each(d3_selection_attr(name, value)); + }; + function d3_selection_attr(name, value) { + name = d3.ns.qualify(name); + function attrNull() { + this.removeAttribute(name); + } + function attrNullNS() { + this.removeAttributeNS(name.space, name.local); + } + function attrConstant() { + this.setAttribute(name, value); + } + function attrConstantNS() { + this.setAttributeNS(name.space, name.local, value); + } + function attrFunction() { + var x = value.apply(this, arguments); + if (x == null) this.removeAttribute(name); else this.setAttribute(name, x); + } + function attrFunctionNS() { + var x = value.apply(this, arguments); + if (x == null) this.removeAttributeNS(name.space, name.local); else this.setAttributeNS(name.space, name.local, x); + } + return value == null ? name.local ? attrNullNS : attrNull : typeof value === "function" ? name.local ? attrFunctionNS : attrFunction : name.local ? attrConstantNS : attrConstant; + } + function d3_collapse(s) { + return s.trim().replace(/\s+/g, " "); + } + d3.requote = function(s) { + return s.replace(d3_requote_re, "\\$&"); + }; + var d3_requote_re = /[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g; + d3_selectionPrototype.classed = function(name, value) { + if (arguments.length < 2) { + if (typeof name === "string") { + var node = this.node(), n = (name = name.trim().split(/^|\s+/g)).length, i = -1; + if (value = node.classList) { + while (++i < n) if (!value.contains(name[i])) return false; + } else { + value = node.getAttribute("class"); + while (++i < n) if (!d3_selection_classedRe(name[i]).test(value)) return false; + } + return true; + } + for (value in name) this.each(d3_selection_classed(value, name[value])); + return this; + } + return this.each(d3_selection_classed(name, value)); + }; + function d3_selection_classedRe(name) { + return new RegExp("(?:^|\\s+)" + d3.requote(name) + "(?:\\s+|$)", "g"); + } + function d3_selection_classed(name, value) { + name = name.trim().split(/\s+/).map(d3_selection_classedName); + var n = name.length; + function classedConstant() { + var i = -1; + while (++i < n) name[i](this, value); + } + function classedFunction() { + var i = -1, x = value.apply(this, arguments); + while (++i < n) name[i](this, x); + } + return typeof value === "function" ? classedFunction : classedConstant; + } + function d3_selection_classedName(name) { + var re = d3_selection_classedRe(name); + return function(node, value) { + if (c = node.classList) return value ? c.add(name) : c.remove(name); + var c = node.getAttribute("class") || ""; + if (value) { + re.lastIndex = 0; + if (!re.test(c)) node.setAttribute("class", d3_collapse(c + " " + name)); + } else { + node.setAttribute("class", d3_collapse(c.replace(re, " "))); + } + }; + } + d3_selectionPrototype.style = function(name, value, priority) { + var n = arguments.length; + if (n < 3) { + if (typeof name !== "string") { + if (n < 2) value = ""; + for (priority in name) this.each(d3_selection_style(priority, name[priority], value)); + return this; + } + if (n < 2) return d3_window.getComputedStyle(this.node(), null).getPropertyValue(name); + priority = ""; + } + return this.each(d3_selection_style(name, value, priority)); + }; + function d3_selection_style(name, value, priority) { + function styleNull() { + this.style.removeProperty(name); + } + function styleConstant() { + this.style.setProperty(name, value, priority); + } + function styleFunction() { + var x = value.apply(this, arguments); + if (x == null) this.style.removeProperty(name); else this.style.setProperty(name, x, priority); + } + return value == null ? styleNull : typeof value === "function" ? styleFunction : styleConstant; + } + d3_selectionPrototype.property = function(name, value) { + if (arguments.length < 2) { + if (typeof name === "string") return this.node()[name]; + for (value in name) this.each(d3_selection_property(value, name[value])); + return this; + } + return this.each(d3_selection_property(name, value)); + }; + function d3_selection_property(name, value) { + function propertyNull() { + delete this[name]; + } + function propertyConstant() { + this[name] = value; + } + function propertyFunction() { + var x = value.apply(this, arguments); + if (x == null) delete this[name]; else this[name] = x; + } + return value == null ? propertyNull : typeof value === "function" ? propertyFunction : propertyConstant; + } + d3_selectionPrototype.text = function(value) { + return arguments.length ? this.each(typeof value === "function" ? function() { + var v = value.apply(this, arguments); + this.textContent = v == null ? "" : v; + } : value == null ? function() { + this.textContent = ""; + } : function() { + this.textContent = value; + }) : this.node().textContent; + }; + d3_selectionPrototype.html = function(value) { + return arguments.length ? this.each(typeof value === "function" ? function() { + var v = value.apply(this, arguments); + this.innerHTML = v == null ? "" : v; + } : value == null ? function() { + this.innerHTML = ""; + } : function() { + this.innerHTML = value; + }) : this.node().innerHTML; + }; + d3_selectionPrototype.append = function(name) { + name = d3.ns.qualify(name); + function append() { + return this.appendChild(d3_document.createElementNS(this.namespaceURI, name)); + } + function appendNS() { + return this.appendChild(d3_document.createElementNS(name.space, name.local)); + } + return this.select(name.local ? appendNS : append); + }; + d3_selectionPrototype.insert = function(name, before) { + name = d3.ns.qualify(name); + if (typeof before !== "function") before = d3_selection_selector(before); + function insert(d, i) { + return this.insertBefore(d3_document.createElementNS(this.namespaceURI, name), before.call(this, d, i)); + } + function insertNS(d, i) { + return this.insertBefore(d3_document.createElementNS(name.space, name.local), before.call(this, d, i)); + } + return this.select(name.local ? insertNS : insert); + }; + d3_selectionPrototype.remove = function() { + return this.each(function() { + var parent = this.parentNode; + if (parent) parent.removeChild(this); + }); + }; + d3_selectionPrototype.data = function(value, key) { + var i = -1, n = this.length, group, node; + if (!arguments.length) { + value = new Array(n = (group = this[0]).length); + while (++i < n) { + if (node = group[i]) { + value[i] = node.__data__; + } + } + return value; + } + function bind(group, groupData) { + var i, n = group.length, m = groupData.length, n0 = Math.min(n, m), updateNodes = new Array(m), enterNodes = new Array(m), exitNodes = new Array(n), node, nodeData; + if (key) { + var nodeByKeyValue = new d3_Map(), dataByKeyValue = new d3_Map(), keyValues = [], keyValue; + for (i = -1; ++i < n; ) { + keyValue = key.call(node = group[i], node.__data__, i); + if (nodeByKeyValue.has(keyValue)) { + exitNodes[i] = node; + } else { + nodeByKeyValue.set(keyValue, node); + } + keyValues.push(keyValue); + } + for (i = -1; ++i < m; ) { + keyValue = key.call(groupData, nodeData = groupData[i], i); + if (node = nodeByKeyValue.get(keyValue)) { + updateNodes[i] = node; + node.__data__ = nodeData; + } else if (!dataByKeyValue.has(keyValue)) { + enterNodes[i] = d3_selection_dataNode(nodeData); + } + dataByKeyValue.set(keyValue, nodeData); + nodeByKeyValue.remove(keyValue); + } + for (i = -1; ++i < n; ) { + if (nodeByKeyValue.has(keyValues[i])) { + exitNodes[i] = group[i]; + } + } + } else { + for (i = -1; ++i < n0; ) { + node = group[i]; + nodeData = groupData[i]; + if (node) { + node.__data__ = nodeData; + updateNodes[i] = node; + } else { + enterNodes[i] = d3_selection_dataNode(nodeData); + } + } + for (;i < m; ++i) { + enterNodes[i] = d3_selection_dataNode(groupData[i]); + } + for (;i < n; ++i) { + exitNodes[i] = group[i]; + } + } + enterNodes.update = updateNodes; + enterNodes.parentNode = updateNodes.parentNode = exitNodes.parentNode = group.parentNode; + enter.push(enterNodes); + update.push(updateNodes); + exit.push(exitNodes); + } + var enter = d3_selection_enter([]), update = d3_selection([]), exit = d3_selection([]); + if (typeof value === "function") { + while (++i < n) { + bind(group = this[i], value.call(group, group.parentNode.__data__, i)); + } + } else { + while (++i < n) { + bind(group = this[i], value); + } + } + update.enter = function() { + return enter; + }; + update.exit = function() { + return exit; + }; + return update; + }; + function d3_selection_dataNode(data) { + return { + __data__: data + }; + } + d3_selectionPrototype.datum = function(value) { + return arguments.length ? this.property("__data__", value) : this.property("__data__"); + }; + d3_selectionPrototype.filter = function(filter) { + var subgroups = [], subgroup, group, node; + if (typeof filter !== "function") filter = d3_selection_filter(filter); + for (var j = 0, m = this.length; j < m; j++) { + subgroups.push(subgroup = []); + subgroup.parentNode = (group = this[j]).parentNode; + for (var i = 0, n = group.length; i < n; i++) { + if ((node = group[i]) && filter.call(node, node.__data__, i)) { + subgroup.push(node); + } + } + } + return d3_selection(subgroups); + }; + function d3_selection_filter(selector) { + return function() { + return d3_selectMatches(this, selector); + }; + } + d3_selectionPrototype.order = function() { + for (var j = -1, m = this.length; ++j < m; ) { + for (var group = this[j], i = group.length - 1, next = group[i], node; --i >= 0; ) { + if (node = group[i]) { + if (next && next !== node.nextSibling) next.parentNode.insertBefore(node, next); + next = node; + } + } + } + return this; + }; + d3_selectionPrototype.sort = function(comparator) { + comparator = d3_selection_sortComparator.apply(this, arguments); + for (var j = -1, m = this.length; ++j < m; ) this[j].sort(comparator); + return this.order(); + }; + function d3_selection_sortComparator(comparator) { + if (!arguments.length) comparator = d3.ascending; + return function(a, b) { + return !a - !b || comparator(a.__data__, b.__data__); + }; + } + function d3_noop() {} + d3_selectionPrototype.on = function(type, listener, capture) { + var n = arguments.length; + if (n < 3) { + if (typeof type !== "string") { + if (n < 2) listener = false; + for (capture in type) this.each(d3_selection_on(capture, type[capture], listener)); + return this; + } + if (n < 2) return (n = this.node()["__on" + type]) && n._; + capture = false; + } + return this.each(d3_selection_on(type, listener, capture)); + }; + function d3_selection_on(type, listener, capture) { + var name = "__on" + type, i = type.indexOf("."), wrap = d3_selection_onListener; + if (i > 0) type = type.substring(0, i); + var filter = d3_selection_onFilters.get(type); + if (filter) type = filter, wrap = d3_selection_onFilter; + function onRemove() { + var l = this[name]; + if (l) { + this.removeEventListener(type, l, l.$); + delete this[name]; + } + } + function onAdd() { + var l = wrap(listener, d3_array(arguments)); + onRemove.call(this); + this.addEventListener(type, this[name] = l, l.$ = capture); + l._ = listener; + } + function removeAll() { + var re = new RegExp("^__on([^.]+)" + d3.requote(type) + "$"), match; + for (var name in this) { + if (match = name.match(re)) { + var l = this[name]; + this.removeEventListener(match[1], l, l.$); + delete this[name]; + } + } + } + return i ? listener ? onAdd : onRemove : listener ? d3_noop : removeAll; + } + var d3_selection_onFilters = d3.map({ + mouseenter: "mouseover", + mouseleave: "mouseout" + }); + d3_selection_onFilters.forEach(function(k) { + if ("on" + k in d3_document) d3_selection_onFilters.remove(k); + }); + function d3_selection_onListener(listener, argumentz) { + return function(e) { + var o = d3.event; + d3.event = e; + argumentz[0] = this.__data__; + try { + listener.apply(this, argumentz); + } finally { + d3.event = o; + } + }; + } + function d3_selection_onFilter(listener, argumentz) { + var l = d3_selection_onListener(listener, argumentz); + return function(e) { + var target = this, related = e.relatedTarget; + if (!related || related !== target && !(related.compareDocumentPosition(target) & 8)) { + l.call(target, e); + } + }; + } + d3_selectionPrototype.each = function(callback) { + return d3_selection_each(this, function(node, i, j) { + callback.call(node, node.__data__, i, j); + }); + }; + function d3_selection_each(groups, callback) { + for (var j = 0, m = groups.length; j < m; j++) { + for (var group = groups[j], i = 0, n = group.length, node; i < n; i++) { + if (node = group[i]) callback(node, i, j); + } + } + return groups; + } + d3_selectionPrototype.call = function(callback) { + var args = d3_array(arguments); + callback.apply(args[0] = this, args); + return this; + }; + d3_selectionPrototype.empty = function() { + return !this.node(); + }; + d3_selectionPrototype.node = function() { + for (var j = 0, m = this.length; j < m; j++) { + for (var group = this[j], i = 0, n = group.length; i < n; i++) { + var node = group[i]; + if (node) return node; + } + } + return null; + }; + function d3_selection_enter(selection) { + d3_arraySubclass(selection, d3_selection_enterPrototype); + return selection; + } + var d3_selection_enterPrototype = []; + d3.selection.enter = d3_selection_enter; + d3.selection.enter.prototype = d3_selection_enterPrototype; + d3_selection_enterPrototype.append = d3_selectionPrototype.append; + d3_selection_enterPrototype.insert = d3_selectionPrototype.insert; + d3_selection_enterPrototype.empty = d3_selectionPrototype.empty; + d3_selection_enterPrototype.node = d3_selectionPrototype.node; + d3_selection_enterPrototype.select = function(selector) { + var subgroups = [], subgroup, subnode, upgroup, group, node; + for (var j = -1, m = this.length; ++j < m; ) { + upgroup = (group = this[j]).update; + subgroups.push(subgroup = []); + subgroup.parentNode = group.parentNode; + for (var i = -1, n = group.length; ++i < n; ) { + if (node = group[i]) { + subgroup.push(upgroup[i] = subnode = selector.call(group.parentNode, node.__data__, i)); + subnode.__data__ = node.__data__; + } else { + subgroup.push(null); + } + } + } + return d3_selection(subgroups); + }; + d3_selectionPrototype.transition = function() { + var id = d3_transitionInheritId || ++d3_transitionId, subgroups = [], subgroup, node, transition = Object.create(d3_transitionInherit); + transition.time = Date.now(); + for (var j = -1, m = this.length; ++j < m; ) { + subgroups.push(subgroup = []); + for (var group = this[j], i = -1, n = group.length; ++i < n; ) { + if (node = group[i]) d3_transitionNode(node, i, id, transition); + subgroup.push(node); + } + } + return d3_transition(subgroups, id); + }; + var d3_selectionRoot = d3_selection([ [ d3_document ] ]); + d3_selectionRoot[0].parentNode = d3_selectRoot; + d3.select = function(selector) { + return typeof selector === "string" ? d3_selectionRoot.select(selector) : d3_selection([ [ selector ] ]); + }; + d3.selectAll = function(selector) { + return typeof selector === "string" ? d3_selectionRoot.selectAll(selector) : d3_selection([ d3_array(selector) ]); + }; + d3.behavior.zoom = function() { + var translate = [ 0, 0 ], translate0, scale = 1, scale0, scaleExtent = d3_behavior_zoomInfinity, event = d3_eventDispatch(zoom, "zoom"), x0, x1, y0, y1, touchtime; + function zoom() { + this.on("mousedown.zoom", mousedown).on("mousemove.zoom", mousemove).on(d3_behavior_zoomWheel + ".zoom", mousewheel).on("dblclick.zoom", dblclick).on("touchstart.zoom", touchstart).on("touchmove.zoom", touchmove).on("touchend.zoom", touchstart); + } + zoom.translate = function(x) { + if (!arguments.length) return translate; + translate = x.map(Number); + rescale(); + return zoom; + }; + zoom.scale = function(x) { + if (!arguments.length) return scale; + scale = +x; + rescale(); + return zoom; + }; + zoom.scaleExtent = function(x) { + if (!arguments.length) return scaleExtent; + scaleExtent = x == null ? d3_behavior_zoomInfinity : x.map(Number); + return zoom; + }; + zoom.x = function(z) { + if (!arguments.length) return x1; + x1 = z; + x0 = z.copy(); + translate = [ 0, 0 ]; + scale = 1; + return zoom; + }; + zoom.y = function(z) { + if (!arguments.length) return y1; + y1 = z; + y0 = z.copy(); + translate = [ 0, 0 ]; + scale = 1; + return zoom; + }; + function location(p) { + return [ (p[0] - translate[0]) / scale, (p[1] - translate[1]) / scale ]; + } + function point(l) { + return [ l[0] * scale + translate[0], l[1] * scale + translate[1] ]; + } + function scaleTo(s) { + scale = Math.max(scaleExtent[0], Math.min(scaleExtent[1], s)); + } + function translateTo(p, l) { + l = point(l); + translate[0] += p[0] - l[0]; + translate[1] += p[1] - l[1]; + } + function rescale() { + if (x1) x1.domain(x0.range().map(function(x) { + return (x - translate[0]) / scale; + }).map(x0.invert)); + if (y1) y1.domain(y0.range().map(function(y) { + return (y - translate[1]) / scale; + }).map(y0.invert)); + } + function dispatch(event) { + rescale(); + d3.event.preventDefault(); + event({ + type: "zoom", + scale: scale, + translate: translate + }); + } + function mousedown() { + var target = this, event_ = event.of(target, arguments), eventTarget = d3.event.target, moved = 0, w = d3.select(d3_window).on("mousemove.zoom", mousemove).on("mouseup.zoom", mouseup), l = location(d3.mouse(target)); + d3_window.focus(); + d3_eventCancel(); + function mousemove() { + moved = 1; + translateTo(d3.mouse(target), l); + dispatch(event_); + } + function mouseup() { + if (moved) d3_eventCancel(); + w.on("mousemove.zoom", null).on("mouseup.zoom", null); + if (moved && d3.event.target === eventTarget) d3_eventSuppress(w, "click.zoom"); + } + } + function mousewheel() { + if (!translate0) translate0 = location(d3.mouse(this)); + scaleTo(Math.pow(2, d3_behavior_zoomDelta() * .002) * scale); + translateTo(d3.mouse(this), translate0); + dispatch(event.of(this, arguments)); + } + function mousemove() { + translate0 = null; + } + function dblclick() { + var p = d3.mouse(this), l = location(p), k = Math.log(scale) / Math.LN2; + scaleTo(Math.pow(2, d3.event.shiftKey ? Math.ceil(k) - 1 : Math.floor(k) + 1)); + translateTo(p, l); + dispatch(event.of(this, arguments)); + } + function touchstart() { + var touches = d3.touches(this), now = Date.now(); + scale0 = scale; + translate0 = {}; + touches.forEach(function(t) { + translate0[t.identifier] = location(t); + }); + d3_eventCancel(); + if (touches.length === 1) { + if (now - touchtime < 500) { + var p = touches[0], l = location(touches[0]); + scaleTo(scale * 2); + translateTo(p, l); + dispatch(event.of(this, arguments)); + } + touchtime = now; + } + } + function touchmove() { + var touches = d3.touches(this), p0 = touches[0], l0 = translate0[p0.identifier]; + if (p1 = touches[1]) { + var p1, l1 = translate0[p1.identifier]; + p0 = [ (p0[0] + p1[0]) / 2, (p0[1] + p1[1]) / 2 ]; + l0 = [ (l0[0] + l1[0]) / 2, (l0[1] + l1[1]) / 2 ]; + scaleTo(d3.event.scale * scale0); + } + translateTo(p0, l0); + touchtime = null; + dispatch(event.of(this, arguments)); + } + return d3.rebind(zoom, event, "on"); + }; + var d3_behavior_zoomInfinity = [ 0, Infinity ]; + var d3_behavior_zoomDelta, d3_behavior_zoomWheel = "onwheel" in d3_document ? (d3_behavior_zoomDelta = function() { + return -d3.event.deltaY * (d3.event.deltaMode ? 120 : 1); + }, "wheel") : "onmousewheel" in d3_document ? (d3_behavior_zoomDelta = function() { + return d3.event.wheelDelta; + }, "mousewheel") : (d3_behavior_zoomDelta = function() { + return -d3.event.detail; + }, "MozMousePixelScroll"); + function d3_Color() {} + d3_Color.prototype.toString = function() { + return this.rgb() + ""; + }; + d3.hsl = function(h, s, l) { + return arguments.length === 1 ? h instanceof d3_Hsl ? d3_hsl(h.h, h.s, h.l) : d3_rgb_parse("" + h, d3_rgb_hsl, d3_hsl) : d3_hsl(+h, +s, +l); + }; + function d3_hsl(h, s, l) { + return new d3_Hsl(h, s, l); + } + function d3_Hsl(h, s, l) { + this.h = h; + this.s = s; + this.l = l; + } + var d3_hslPrototype = d3_Hsl.prototype = new d3_Color(); + d3_hslPrototype.brighter = function(k) { + k = Math.pow(.7, arguments.length ? k : 1); + return d3_hsl(this.h, this.s, this.l / k); + }; + d3_hslPrototype.darker = function(k) { + k = Math.pow(.7, arguments.length ? k : 1); + return d3_hsl(this.h, this.s, k * this.l); + }; + d3_hslPrototype.rgb = function() { + return d3_hsl_rgb(this.h, this.s, this.l); + }; + function d3_hsl_rgb(h, s, l) { + var m1, m2; + h = h % 360; + if (h < 0) h += 360; + s = s < 0 ? 0 : s > 1 ? 1 : s; + l = l < 0 ? 0 : l > 1 ? 1 : l; + m2 = l <= .5 ? l * (1 + s) : l + s - l * s; + m1 = 2 * l - m2; + function v(h) { + if (h > 360) h -= 360; else if (h < 0) h += 360; + if (h < 60) return m1 + (m2 - m1) * h / 60; + if (h < 180) return m2; + if (h < 240) return m1 + (m2 - m1) * (240 - h) / 60; + return m1; + } + function vv(h) { + return Math.round(v(h) * 255); + } + return d3_rgb(vv(h + 120), vv(h), vv(h - 120)); + } + var π = Math.PI, ε = 1e-6, d3_radians = π / 180, d3_degrees = 180 / π; + function d3_sgn(x) { + return x > 0 ? 1 : x < 0 ? -1 : 0; + } + function d3_acos(x) { + return Math.acos(Math.max(-1, Math.min(1, x))); + } + function d3_asin(x) { + return x > 1 ? π / 2 : x < -1 ? -π / 2 : Math.asin(x); + } + function d3_sinh(x) { + return (Math.exp(x) - Math.exp(-x)) / 2; + } + function d3_cosh(x) { + return (Math.exp(x) + Math.exp(-x)) / 2; + } + function d3_haversin(x) { + return (x = Math.sin(x / 2)) * x; + } + d3.hcl = function(h, c, l) { + return arguments.length === 1 ? h instanceof d3_Hcl ? d3_hcl(h.h, h.c, h.l) : h instanceof d3_Lab ? d3_lab_hcl(h.l, h.a, h.b) : d3_lab_hcl((h = d3_rgb_lab((h = d3.rgb(h)).r, h.g, h.b)).l, h.a, h.b) : d3_hcl(+h, +c, +l); + }; + function d3_hcl(h, c, l) { + return new d3_Hcl(h, c, l); + } + function d3_Hcl(h, c, l) { + this.h = h; + this.c = c; + this.l = l; + } + var d3_hclPrototype = d3_Hcl.prototype = new d3_Color(); + d3_hclPrototype.brighter = function(k) { + return d3_hcl(this.h, this.c, Math.min(100, this.l + d3_lab_K * (arguments.length ? k : 1))); + }; + d3_hclPrototype.darker = function(k) { + return d3_hcl(this.h, this.c, Math.max(0, this.l - d3_lab_K * (arguments.length ? k : 1))); + }; + d3_hclPrototype.rgb = function() { + return d3_hcl_lab(this.h, this.c, this.l).rgb(); + }; + function d3_hcl_lab(h, c, l) { + return d3_lab(l, Math.cos(h *= d3_radians) * c, Math.sin(h) * c); + } + d3.lab = function(l, a, b) { + return arguments.length === 1 ? l instanceof d3_Lab ? d3_lab(l.l, l.a, l.b) : l instanceof d3_Hcl ? d3_hcl_lab(l.l, l.c, l.h) : d3_rgb_lab((l = d3.rgb(l)).r, l.g, l.b) : d3_lab(+l, +a, +b); + }; + function d3_lab(l, a, b) { + return new d3_Lab(l, a, b); + } + function d3_Lab(l, a, b) { + this.l = l; + this.a = a; + this.b = b; + } + var d3_lab_K = 18; + var d3_lab_X = .95047, d3_lab_Y = 1, d3_lab_Z = 1.08883; + var d3_labPrototype = d3_Lab.prototype = new d3_Color(); + d3_labPrototype.brighter = function(k) { + return d3_lab(Math.min(100, this.l + d3_lab_K * (arguments.length ? k : 1)), this.a, this.b); + }; + d3_labPrototype.darker = function(k) { + return d3_lab(Math.max(0, this.l - d3_lab_K * (arguments.length ? k : 1)), this.a, this.b); + }; + d3_labPrototype.rgb = function() { + return d3_lab_rgb(this.l, this.a, this.b); + }; + function d3_lab_rgb(l, a, b) { + var y = (l + 16) / 116, x = y + a / 500, z = y - b / 200; + x = d3_lab_xyz(x) * d3_lab_X; + y = d3_lab_xyz(y) * d3_lab_Y; + z = d3_lab_xyz(z) * d3_lab_Z; + return d3_rgb(d3_xyz_rgb(3.2404542 * x - 1.5371385 * y - .4985314 * z), d3_xyz_rgb(-.969266 * x + 1.8760108 * y + .041556 * z), d3_xyz_rgb(.0556434 * x - .2040259 * y + 1.0572252 * z)); + } + function d3_lab_hcl(l, a, b) { + return d3_hcl(Math.atan2(b, a) * d3_degrees, Math.sqrt(a * a + b * b), l); + } + function d3_lab_xyz(x) { + return x > .206893034 ? x * x * x : (x - 4 / 29) / 7.787037; + } + function d3_xyz_lab(x) { + return x > .008856 ? Math.pow(x, 1 / 3) : 7.787037 * x + 4 / 29; + } + function d3_xyz_rgb(r) { + return Math.round(255 * (r <= .00304 ? 12.92 * r : 1.055 * Math.pow(r, 1 / 2.4) - .055)); + } + d3.rgb = function(r, g, b) { + return arguments.length === 1 ? r instanceof d3_Rgb ? d3_rgb(r.r, r.g, r.b) : d3_rgb_parse("" + r, d3_rgb, d3_hsl_rgb) : d3_rgb(~~r, ~~g, ~~b); + }; + function d3_rgb(r, g, b) { + return new d3_Rgb(r, g, b); + } + function d3_Rgb(r, g, b) { + this.r = r; + this.g = g; + this.b = b; + } + var d3_rgbPrototype = d3_Rgb.prototype = new d3_Color(); + d3_rgbPrototype.brighter = function(k) { + k = Math.pow(.7, arguments.length ? k : 1); + var r = this.r, g = this.g, b = this.b, i = 30; + if (!r && !g && !b) return d3_rgb(i, i, i); + if (r && r < i) r = i; + if (g && g < i) g = i; + if (b && b < i) b = i; + return d3_rgb(Math.min(255, Math.floor(r / k)), Math.min(255, Math.floor(g / k)), Math.min(255, Math.floor(b / k))); + }; + d3_rgbPrototype.darker = function(k) { + k = Math.pow(.7, arguments.length ? k : 1); + return d3_rgb(Math.floor(k * this.r), Math.floor(k * this.g), Math.floor(k * this.b)); + }; + d3_rgbPrototype.hsl = function() { + return d3_rgb_hsl(this.r, this.g, this.b); + }; + d3_rgbPrototype.toString = function() { + return "#" + d3_rgb_hex(this.r) + d3_rgb_hex(this.g) + d3_rgb_hex(this.b); + }; + function d3_rgb_hex(v) { + return v < 16 ? "0" + Math.max(0, v).toString(16) : Math.min(255, v).toString(16); + } + function d3_rgb_parse(format, rgb, hsl) { + var r = 0, g = 0, b = 0, m1, m2, name; + m1 = /([a-z]+)\((.*)\)/i.exec(format); + if (m1) { + m2 = m1[2].split(","); + switch (m1[1]) { + case "hsl": + { + return hsl(parseFloat(m2[0]), parseFloat(m2[1]) / 100, parseFloat(m2[2]) / 100); + } + + case "rgb": + { + return rgb(d3_rgb_parseNumber(m2[0]), d3_rgb_parseNumber(m2[1]), d3_rgb_parseNumber(m2[2])); + } + } + } + if (name = d3_rgb_names.get(format)) return rgb(name.r, name.g, name.b); + if (format != null && format.charAt(0) === "#") { + if (format.length === 4) { + r = format.charAt(1); + r += r; + g = format.charAt(2); + g += g; + b = format.charAt(3); + b += b; + } else if (format.length === 7) { + r = format.substring(1, 3); + g = format.substring(3, 5); + b = format.substring(5, 7); + } + r = parseInt(r, 16); + g = parseInt(g, 16); + b = parseInt(b, 16); + } + return rgb(r, g, b); + } + function d3_rgb_hsl(r, g, b) { + var min = Math.min(r /= 255, g /= 255, b /= 255), max = Math.max(r, g, b), d = max - min, h, s, l = (max + min) / 2; + if (d) { + s = l < .5 ? d / (max + min) : d / (2 - max - min); + if (r == max) h = (g - b) / d + (g < b ? 6 : 0); else if (g == max) h = (b - r) / d + 2; else h = (r - g) / d + 4; + h *= 60; + } else { + s = h = 0; + } + return d3_hsl(h, s, l); + } + function d3_rgb_lab(r, g, b) { + r = d3_rgb_xyz(r); + g = d3_rgb_xyz(g); + b = d3_rgb_xyz(b); + var x = d3_xyz_lab((.4124564 * r + .3575761 * g + .1804375 * b) / d3_lab_X), y = d3_xyz_lab((.2126729 * r + .7151522 * g + .072175 * b) / d3_lab_Y), z = d3_xyz_lab((.0193339 * r + .119192 * g + .9503041 * b) / d3_lab_Z); + return d3_lab(116 * y - 16, 500 * (x - y), 200 * (y - z)); + } + function d3_rgb_xyz(r) { + return (r /= 255) <= .04045 ? r / 12.92 : Math.pow((r + .055) / 1.055, 2.4); + } + function d3_rgb_parseNumber(c) { + var f = parseFloat(c); + return c.charAt(c.length - 1) === "%" ? Math.round(f * 2.55) : f; + } + var d3_rgb_names = d3.map({ + aliceblue: "#f0f8ff", + antiquewhite: "#faebd7", + aqua: "#00ffff", + aquamarine: "#7fffd4", + azure: "#f0ffff", + beige: "#f5f5dc", + bisque: "#ffe4c4", + black: "#000000", + blanchedalmond: "#ffebcd", + blue: "#0000ff", + blueviolet: "#8a2be2", + brown: "#a52a2a", + burlywood: "#deb887", + cadetblue: "#5f9ea0", + chartreuse: "#7fff00", + chocolate: "#d2691e", + coral: "#ff7f50", + cornflowerblue: "#6495ed", + cornsilk: "#fff8dc", + crimson: "#dc143c", + cyan: "#00ffff", + darkblue: "#00008b", + darkcyan: "#008b8b", + darkgoldenrod: "#b8860b", + darkgray: "#a9a9a9", + darkgreen: "#006400", + darkgrey: "#a9a9a9", + darkkhaki: "#bdb76b", + darkmagenta: "#8b008b", + darkolivegreen: "#556b2f", + darkorange: "#ff8c00", + darkorchid: "#9932cc", + darkred: "#8b0000", + darksalmon: "#e9967a", + darkseagreen: "#8fbc8f", + darkslateblue: "#483d8b", + darkslategray: "#2f4f4f", + darkslategrey: "#2f4f4f", + darkturquoise: "#00ced1", + darkviolet: "#9400d3", + deeppink: "#ff1493", + deepskyblue: "#00bfff", + dimgray: "#696969", + dimgrey: "#696969", + dodgerblue: "#1e90ff", + firebrick: "#b22222", + floralwhite: "#fffaf0", + forestgreen: "#228b22", + fuchsia: "#ff00ff", + gainsboro: "#dcdcdc", + ghostwhite: "#f8f8ff", + gold: "#ffd700", + goldenrod: "#daa520", + gray: "#808080", + green: "#008000", + greenyellow: "#adff2f", + grey: "#808080", + honeydew: "#f0fff0", + hotpink: "#ff69b4", + indianred: "#cd5c5c", + indigo: "#4b0082", + ivory: "#fffff0", + khaki: "#f0e68c", + lavender: "#e6e6fa", + lavenderblush: "#fff0f5", + lawngreen: "#7cfc00", + lemonchiffon: "#fffacd", + lightblue: "#add8e6", + lightcoral: "#f08080", + lightcyan: "#e0ffff", + lightgoldenrodyellow: "#fafad2", + lightgray: "#d3d3d3", + lightgreen: "#90ee90", + lightgrey: "#d3d3d3", + lightpink: "#ffb6c1", + lightsalmon: "#ffa07a", + lightseagreen: "#20b2aa", + lightskyblue: "#87cefa", + lightslategray: "#778899", + lightslategrey: "#778899", + lightsteelblue: "#b0c4de", + lightyellow: "#ffffe0", + lime: "#00ff00", + limegreen: "#32cd32", + linen: "#faf0e6", + magenta: "#ff00ff", + maroon: "#800000", + mediumaquamarine: "#66cdaa", + mediumblue: "#0000cd", + mediumorchid: "#ba55d3", + mediumpurple: "#9370db", + mediumseagreen: "#3cb371", + mediumslateblue: "#7b68ee", + mediumspringgreen: "#00fa9a", + mediumturquoise: "#48d1cc", + mediumvioletred: "#c71585", + midnightblue: "#191970", + mintcream: "#f5fffa", + mistyrose: "#ffe4e1", + moccasin: "#ffe4b5", + navajowhite: "#ffdead", + navy: "#000080", + oldlace: "#fdf5e6", + olive: "#808000", + olivedrab: "#6b8e23", + orange: "#ffa500", + orangered: "#ff4500", + orchid: "#da70d6", + palegoldenrod: "#eee8aa", + palegreen: "#98fb98", + paleturquoise: "#afeeee", + palevioletred: "#db7093", + papayawhip: "#ffefd5", + peachpuff: "#ffdab9", + peru: "#cd853f", + pink: "#ffc0cb", + plum: "#dda0dd", + powderblue: "#b0e0e6", + purple: "#800080", + red: "#ff0000", + rosybrown: "#bc8f8f", + royalblue: "#4169e1", + saddlebrown: "#8b4513", + salmon: "#fa8072", + sandybrown: "#f4a460", + seagreen: "#2e8b57", + seashell: "#fff5ee", + sienna: "#a0522d", + silver: "#c0c0c0", + skyblue: "#87ceeb", + slateblue: "#6a5acd", + slategray: "#708090", + slategrey: "#708090", + snow: "#fffafa", + springgreen: "#00ff7f", + steelblue: "#4682b4", + tan: "#d2b48c", + teal: "#008080", + thistle: "#d8bfd8", + tomato: "#ff6347", + turquoise: "#40e0d0", + violet: "#ee82ee", + wheat: "#f5deb3", + white: "#ffffff", + whitesmoke: "#f5f5f5", + yellow: "#ffff00", + yellowgreen: "#9acd32" + }); + d3_rgb_names.forEach(function(key, value) { + d3_rgb_names.set(key, d3_rgb_parse(value, d3_rgb, d3_hsl_rgb)); + }); + function d3_functor(v) { + return typeof v === "function" ? v : function() { + return v; + }; + } + d3.functor = d3_functor; + function d3_identity(d) { + return d; + } + d3.xhr = function(url, mimeType, callback) { + var xhr = {}, dispatch = d3.dispatch("progress", "load", "error"), headers = {}, response = d3_identity, request = new (d3_window.XDomainRequest && /^(http(s)?:)?\/\//.test(url) ? XDomainRequest : XMLHttpRequest)(); + "onload" in request ? request.onload = request.onerror = respond : request.onreadystatechange = function() { + request.readyState > 3 && respond(); + }; + function respond() { + var s = request.status; + !s && request.responseText || s >= 200 && s < 300 || s === 304 ? dispatch.load.call(xhr, response.call(xhr, request)) : dispatch.error.call(xhr, request); + } + request.onprogress = function(event) { + var o = d3.event; + d3.event = event; + try { + dispatch.progress.call(xhr, request); + } finally { + d3.event = o; + } + }; + xhr.header = function(name, value) { + name = (name + "").toLowerCase(); + if (arguments.length < 2) return headers[name]; + if (value == null) delete headers[name]; else headers[name] = value + ""; + return xhr; + }; + xhr.mimeType = function(value) { + if (!arguments.length) return mimeType; + mimeType = value == null ? null : value + ""; + return xhr; + }; + xhr.response = function(value) { + response = value; + return xhr; + }; + [ "get", "post" ].forEach(function(method) { + xhr[method] = function() { + return xhr.send.apply(xhr, [ method ].concat(d3_array(arguments))); + }; + }); + xhr.send = function(method, data, callback) { + if (arguments.length === 2 && typeof data === "function") callback = data, data = null; + request.open(method, url, true); + if (mimeType != null && !("accept" in headers)) headers["accept"] = mimeType + ",*/*"; + if (request.setRequestHeader) for (var name in headers) request.setRequestHeader(name, headers[name]); + if (mimeType != null && request.overrideMimeType) request.overrideMimeType(mimeType); + if (callback != null) xhr.on("error", callback).on("load", function(request) { + callback(null, request); + }); + request.send(data == null ? null : data); + return xhr; + }; + xhr.abort = function() { + request.abort(); + return xhr; + }; + d3.rebind(xhr, dispatch, "on"); + if (arguments.length === 2 && typeof mimeType === "function") callback = mimeType, + mimeType = null; + return callback == null ? xhr : xhr.get(d3_xhr_fixCallback(callback)); + }; + function d3_xhr_fixCallback(callback) { + return callback.length === 1 ? function(error, request) { + callback(error == null ? request : null); + } : callback; + } + function d3_dsv(delimiter, mimeType) { + var reFormat = new RegExp('["' + delimiter + "\n]"), delimiterCode = delimiter.charCodeAt(0); + function dsv(url, row, callback) { + if (arguments.length < 3) callback = row, row = null; + var xhr = d3.xhr(url, mimeType, callback); + xhr.row = function(_) { + return arguments.length ? xhr.response((row = _) == null ? response : typedResponse(_)) : row; + }; + return xhr.row(row); + } + function response(request) { + return dsv.parse(request.responseText); + } + function typedResponse(f) { + return function(request) { + return dsv.parse(request.responseText, f); + }; + } + dsv.parse = function(text, f) { + var o; + return dsv.parseRows(text, function(row, i) { + if (o) return o(row, i - 1); + var a = new Function("d", "return {" + row.map(function(name, i) { + return JSON.stringify(name) + ": d[" + i + "]"; + }).join(",") + "}"); + o = f ? function(row, i) { + return f(a(row), i); + } : a; + }); + }; + dsv.parseRows = function(text, f) { + var EOL = {}, EOF = {}, rows = [], N = text.length, I = 0, n = 0, t, eol; + function token() { + if (I >= N) return EOF; + if (eol) return eol = false, EOL; + var j = I; + if (text.charCodeAt(j) === 34) { + var i = j; + while (i++ < N) { + if (text.charCodeAt(i) === 34) { + if (text.charCodeAt(i + 1) !== 34) break; + ++i; + } + } + I = i + 2; + var c = text.charCodeAt(i + 1); + if (c === 13) { + eol = true; + if (text.charCodeAt(i + 2) === 10) ++I; + } else if (c === 10) { + eol = true; + } + return text.substring(j + 1, i).replace(/""/g, '"'); + } + while (I < N) { + var c = text.charCodeAt(I++), k = 1; + if (c === 10) eol = true; else if (c === 13) { + eol = true; + if (text.charCodeAt(I) === 10) ++I, ++k; + } else if (c !== delimiterCode) continue; + return text.substring(j, I - k); + } + return text.substring(j); + } + while ((t = token()) !== EOF) { + var a = []; + while (t !== EOL && t !== EOF) { + a.push(t); + t = token(); + } + if (f && !(a = f(a, n++))) continue; + rows.push(a); + } + return rows; + }; + dsv.format = function(rows) { + if (Array.isArray(rows[0])) return dsv.formatRows(rows); + var fieldSet = new d3_Set(), fields = []; + rows.forEach(function(row) { + for (var field in row) { + if (!fieldSet.has(field)) { + fields.push(fieldSet.add(field)); + } + } + }); + return [ fields.map(formatValue).join(delimiter) ].concat(rows.map(function(row) { + return fields.map(function(field) { + return formatValue(row[field]); + }).join(delimiter); + })).join("\n"); + }; + dsv.formatRows = function(rows) { + return rows.map(formatRow).join("\n"); + }; + function formatRow(row) { + return row.map(formatValue).join(delimiter); + } + function formatValue(text) { + return reFormat.test(text) ? '"' + text.replace(/\"/g, '""') + '"' : text; + } + return dsv; + } + d3.csv = d3_dsv(",", "text/csv"); + d3.tsv = d3_dsv(" ", "text/tab-separated-values"); + var d3_timer_id = 0, d3_timer_byId = {}, d3_timer_queue = null, d3_timer_interval, d3_timer_timeout; + d3.timer = function(callback, delay, then) { + if (arguments.length < 3) { + if (arguments.length < 2) delay = 0; else if (!isFinite(delay)) return; + then = Date.now(); + } + var timer = d3_timer_byId[callback.id]; + if (timer && timer.callback === callback) { + timer.then = then; + timer.delay = delay; + } else d3_timer_byId[callback.id = ++d3_timer_id] = d3_timer_queue = { + callback: callback, + then: then, + delay: delay, + next: d3_timer_queue + }; + if (!d3_timer_interval) { + d3_timer_timeout = clearTimeout(d3_timer_timeout); + d3_timer_interval = 1; + d3_timer_frame(d3_timer_step); + } + }; + function d3_timer_step() { + var elapsed, now = Date.now(), t1 = d3_timer_queue; + while (t1) { + elapsed = now - t1.then; + if (elapsed >= t1.delay) t1.flush = t1.callback(elapsed); + t1 = t1.next; + } + var delay = d3_timer_flush() - now; + if (delay > 24) { + if (isFinite(delay)) { + clearTimeout(d3_timer_timeout); + d3_timer_timeout = setTimeout(d3_timer_step, delay); + } + d3_timer_interval = 0; + } else { + d3_timer_interval = 1; + d3_timer_frame(d3_timer_step); + } + } + d3.timer.flush = function() { + var elapsed, now = Date.now(), t1 = d3_timer_queue; + while (t1) { + elapsed = now - t1.then; + if (!t1.delay) t1.flush = t1.callback(elapsed); + t1 = t1.next; + } + d3_timer_flush(); + }; + function d3_timer_flush() { + var t0 = null, t1 = d3_timer_queue, then = Infinity; + while (t1) { + if (t1.flush) { + delete d3_timer_byId[t1.callback.id]; + t1 = t0 ? t0.next = t1.next : d3_timer_queue = t1.next; + } else { + then = Math.min(then, t1.then + t1.delay); + t1 = (t0 = t1).next; + } + } + return then; + } + var d3_timer_frame = d3_window.requestAnimationFrame || d3_window.webkitRequestAnimationFrame || d3_window.mozRequestAnimationFrame || d3_window.oRequestAnimationFrame || d3_window.msRequestAnimationFrame || function(callback) { + setTimeout(callback, 17); + }; + var d3_format_decimalPoint = ".", d3_format_thousandsSeparator = ",", d3_format_grouping = [ 3, 3 ]; + var d3_formatPrefixes = [ "y", "z", "a", "f", "p", "n", "µ", "m", "", "k", "M", "G", "T", "P", "E", "Z", "Y" ].map(d3_formatPrefix); + d3.formatPrefix = function(value, precision) { + var i = 0; + if (value) { + if (value < 0) value *= -1; + if (precision) value = d3.round(value, d3_format_precision(value, precision)); + i = 1 + Math.floor(1e-12 + Math.log(value) / Math.LN10); + i = Math.max(-24, Math.min(24, Math.floor((i <= 0 ? i + 1 : i - 1) / 3) * 3)); + } + return d3_formatPrefixes[8 + i / 3]; + }; + function d3_formatPrefix(d, i) { + var k = Math.pow(10, Math.abs(8 - i) * 3); + return { + scale: i > 8 ? function(d) { + return d / k; + } : function(d) { + return d * k; + }, + symbol: d + }; + } + d3.round = function(x, n) { + return n ? Math.round(x * (n = Math.pow(10, n))) / n : Math.round(x); + }; + d3.format = function(specifier) { + var match = d3_format_re.exec(specifier), fill = match[1] || " ", align = match[2] || ">", sign = match[3] || "", basePrefix = match[4] || "", zfill = match[5], width = +match[6], comma = match[7], precision = match[8], type = match[9], scale = 1, suffix = "", integer = false; + if (precision) precision = +precision.substring(1); + if (zfill || fill === "0" && align === "=") { + zfill = fill = "0"; + align = "="; + if (comma) width -= Math.floor((width - 1) / 4); + } + switch (type) { + case "n": + comma = true; + type = "g"; + break; + + case "%": + scale = 100; + suffix = "%"; + type = "f"; + break; + + case "p": + scale = 100; + suffix = "%"; + type = "r"; + break; + + case "b": + case "o": + case "x": + case "X": + if (basePrefix) basePrefix = "0" + type.toLowerCase(); + + case "c": + case "d": + integer = true; + precision = 0; + break; + + case "s": + scale = -1; + type = "r"; + break; + } + if (basePrefix === "#") basePrefix = ""; + if (type == "r" && !precision) type = "g"; + if (precision != null) { + if (type == "g") precision = Math.max(1, Math.min(21, precision)); else if (type == "e" || type == "f") precision = Math.max(0, Math.min(20, precision)); + } + type = d3_format_types.get(type) || d3_format_typeDefault; + var zcomma = zfill && comma; + return function(value) { + if (integer && value % 1) return ""; + var negative = value < 0 || value === 0 && 1 / value < 0 ? (value = -value, "-") : sign; + if (scale < 0) { + var prefix = d3.formatPrefix(value, precision); + value = prefix.scale(value); + suffix = prefix.symbol; + } else { + value *= scale; + } + value = type(value, precision); + if (!zfill && comma) value = d3_format_group(value); + var length = basePrefix.length + value.length + (zcomma ? 0 : negative.length), padding = length < width ? new Array(length = width - length + 1).join(fill) : ""; + if (zcomma) value = d3_format_group(padding + value); + if (d3_format_decimalPoint) value.replace(".", d3_format_decimalPoint); + negative += basePrefix; + return (align === "<" ? negative + value + padding : align === ">" ? padding + negative + value : align === "^" ? padding.substring(0, length >>= 1) + negative + value + padding.substring(length) : negative + (zcomma ? value : padding + value)) + suffix; + }; + }; + var d3_format_re = /(?:([^{])?([<>=^]))?([+\- ])?(#)?(0)?(\d+)?(,)?(\.-?\d+)?([a-z%])?/i; + var d3_format_types = d3.map({ + b: function(x) { + return x.toString(2); + }, + c: function(x) { + return String.fromCharCode(x); + }, + o: function(x) { + return x.toString(8); + }, + x: function(x) { + return x.toString(16); + }, + X: function(x) { + return x.toString(16).toUpperCase(); + }, + g: function(x, p) { + return x.toPrecision(p); + }, + e: function(x, p) { + return x.toExponential(p); + }, + f: function(x, p) { + return x.toFixed(p); + }, + r: function(x, p) { + return (x = d3.round(x, d3_format_precision(x, p))).toFixed(Math.max(0, Math.min(20, d3_format_precision(x * (1 + 1e-15), p)))); + } + }); + function d3_format_precision(x, p) { + return p - (x ? Math.ceil(Math.log(x) / Math.LN10) : 1); + } + function d3_format_typeDefault(x) { + return x + ""; + } + var d3_format_group = d3_identity; + if (d3_format_grouping) { + var d3_format_groupingLength = d3_format_grouping.length; + d3_format_group = function(value) { + var i = value.lastIndexOf("."), f = i >= 0 ? "." + value.substring(i + 1) : (i = value.length, + ""), t = [], j = 0, g = d3_format_grouping[0]; + while (i > 0 && g > 0) { + t.push(value.substring(i -= g, i + g)); + g = d3_format_grouping[j = (j + 1) % d3_format_groupingLength]; + } + return t.reverse().join(d3_format_thousandsSeparator || "") + f; + }; + } + d3.geo = {}; + d3.geo.stream = function(object, listener) { + if (object && d3_geo_streamObjectType.hasOwnProperty(object.type)) { + d3_geo_streamObjectType[object.type](object, listener); + } else { + d3_geo_streamGeometry(object, listener); + } + }; + function d3_geo_streamGeometry(geometry, listener) { + if (geometry && d3_geo_streamGeometryType.hasOwnProperty(geometry.type)) { + d3_geo_streamGeometryType[geometry.type](geometry, listener); + } + } + var d3_geo_streamObjectType = { + Feature: function(feature, listener) { + d3_geo_streamGeometry(feature.geometry, listener); + }, + FeatureCollection: function(object, listener) { + var features = object.features, i = -1, n = features.length; + while (++i < n) d3_geo_streamGeometry(features[i].geometry, listener); + } + }; + var d3_geo_streamGeometryType = { + Sphere: function(object, listener) { + listener.sphere(); + }, + Point: function(object, listener) { + var coordinate = object.coordinates; + listener.point(coordinate[0], coordinate[1]); + }, + MultiPoint: function(object, listener) { + var coordinates = object.coordinates, i = -1, n = coordinates.length, coordinate; + while (++i < n) coordinate = coordinates[i], listener.point(coordinate[0], coordinate[1]); + }, + LineString: function(object, listener) { + d3_geo_streamLine(object.coordinates, listener, 0); + }, + MultiLineString: function(object, listener) { + var coordinates = object.coordinates, i = -1, n = coordinates.length; + while (++i < n) d3_geo_streamLine(coordinates[i], listener, 0); + }, + Polygon: function(object, listener) { + d3_geo_streamPolygon(object.coordinates, listener); + }, + MultiPolygon: function(object, listener) { + var coordinates = object.coordinates, i = -1, n = coordinates.length; + while (++i < n) d3_geo_streamPolygon(coordinates[i], listener); + }, + GeometryCollection: function(object, listener) { + var geometries = object.geometries, i = -1, n = geometries.length; + while (++i < n) d3_geo_streamGeometry(geometries[i], listener); + } + }; + function d3_geo_streamLine(coordinates, listener, closed) { + var i = -1, n = coordinates.length - closed, coordinate; + listener.lineStart(); + while (++i < n) coordinate = coordinates[i], listener.point(coordinate[0], coordinate[1]); + listener.lineEnd(); + } + function d3_geo_streamPolygon(coordinates, listener) { + var i = -1, n = coordinates.length; + listener.polygonStart(); + while (++i < n) d3_geo_streamLine(coordinates[i], listener, 1); + listener.polygonEnd(); + } + d3.geo.area = function(object) { + d3_geo_areaSum = 0; + d3.geo.stream(object, d3_geo_area); + return d3_geo_areaSum; + }; + var d3_geo_areaSum, d3_geo_areaRingU, d3_geo_areaRingV; + var d3_geo_area = { + sphere: function() { + d3_geo_areaSum += 4 * π; + }, + point: d3_noop, + lineStart: d3_noop, + lineEnd: d3_noop, + polygonStart: function() { + d3_geo_areaRingU = 1, d3_geo_areaRingV = 0; + d3_geo_area.lineStart = d3_geo_areaRingStart; + }, + polygonEnd: function() { + var area = 2 * Math.atan2(d3_geo_areaRingV, d3_geo_areaRingU); + d3_geo_areaSum += area < 0 ? 4 * π + area : area; + d3_geo_area.lineStart = d3_geo_area.lineEnd = d3_geo_area.point = d3_noop; + } + }; + function d3_geo_areaRingStart() { + var λ00, φ00, λ0, cosφ0, sinφ0; + d3_geo_area.point = function(λ, φ) { + d3_geo_area.point = nextPoint; + λ0 = (λ00 = λ) * d3_radians, cosφ0 = Math.cos(φ = (φ00 = φ) * d3_radians / 2 + π / 4), + sinφ0 = Math.sin(φ); + }; + function nextPoint(λ, φ) { + λ *= d3_radians; + φ = φ * d3_radians / 2 + π / 4; + var dλ = λ - λ0, cosφ = Math.cos(φ), sinφ = Math.sin(φ), k = sinφ0 * sinφ, u0 = d3_geo_areaRingU, v0 = d3_geo_areaRingV, u = cosφ0 * cosφ + k * Math.cos(dλ), v = k * Math.sin(dλ); + d3_geo_areaRingU = u0 * u - v0 * v; + d3_geo_areaRingV = v0 * u + u0 * v; + λ0 = λ, cosφ0 = cosφ, sinφ0 = sinφ; + } + d3_geo_area.lineEnd = function() { + nextPoint(λ00, φ00); + }; + } + d3.geo.bounds = d3_geo_bounds(d3_identity); + function d3_geo_bounds(projectStream) { + var x0, y0, x1, y1; + var bound = { + point: boundPoint, + lineStart: d3_noop, + lineEnd: d3_noop, + polygonStart: function() { + bound.lineEnd = boundPolygonLineEnd; + }, + polygonEnd: function() { + bound.point = boundPoint; + } + }; + function boundPoint(x, y) { + if (x < x0) x0 = x; + if (x > x1) x1 = x; + if (y < y0) y0 = y; + if (y > y1) y1 = y; + } + function boundPolygonLineEnd() { + bound.point = bound.lineEnd = d3_noop; + } + return function(feature) { + y1 = x1 = -(x0 = y0 = Infinity); + d3.geo.stream(feature, projectStream(bound)); + return [ [ x0, y0 ], [ x1, y1 ] ]; + }; + } + d3.geo.centroid = function(object) { + d3_geo_centroidDimension = d3_geo_centroidW = d3_geo_centroidX = d3_geo_centroidY = d3_geo_centroidZ = 0; + d3.geo.stream(object, d3_geo_centroid); + var m; + if (d3_geo_centroidW && Math.abs(m = Math.sqrt(d3_geo_centroidX * d3_geo_centroidX + d3_geo_centroidY * d3_geo_centroidY + d3_geo_centroidZ * d3_geo_centroidZ)) > ε) { + return [ Math.atan2(d3_geo_centroidY, d3_geo_centroidX) * d3_degrees, Math.asin(Math.max(-1, Math.min(1, d3_geo_centroidZ / m))) * d3_degrees ]; + } + }; + var d3_geo_centroidDimension, d3_geo_centroidW, d3_geo_centroidX, d3_geo_centroidY, d3_geo_centroidZ; + var d3_geo_centroid = { + sphere: function() { + if (d3_geo_centroidDimension < 2) { + d3_geo_centroidDimension = 2; + d3_geo_centroidW = d3_geo_centroidX = d3_geo_centroidY = d3_geo_centroidZ = 0; + } + }, + point: d3_geo_centroidPoint, + lineStart: d3_geo_centroidLineStart, + lineEnd: d3_geo_centroidLineEnd, + polygonStart: function() { + if (d3_geo_centroidDimension < 2) { + d3_geo_centroidDimension = 2; + d3_geo_centroidW = d3_geo_centroidX = d3_geo_centroidY = d3_geo_centroidZ = 0; + } + d3_geo_centroid.lineStart = d3_geo_centroidRingStart; + }, + polygonEnd: function() { + d3_geo_centroid.lineStart = d3_geo_centroidLineStart; + } + }; + function d3_geo_centroidPoint(λ, φ) { + if (d3_geo_centroidDimension) return; + ++d3_geo_centroidW; + λ *= d3_radians; + var cosφ = Math.cos(φ *= d3_radians); + d3_geo_centroidX += (cosφ * Math.cos(λ) - d3_geo_centroidX) / d3_geo_centroidW; + d3_geo_centroidY += (cosφ * Math.sin(λ) - d3_geo_centroidY) / d3_geo_centroidW; + d3_geo_centroidZ += (Math.sin(φ) - d3_geo_centroidZ) / d3_geo_centroidW; + } + function d3_geo_centroidRingStart() { + var λ00, φ00; + d3_geo_centroidDimension = 1; + d3_geo_centroidLineStart(); + d3_geo_centroidDimension = 2; + var linePoint = d3_geo_centroid.point; + d3_geo_centroid.point = function(λ, φ) { + linePoint(λ00 = λ, φ00 = φ); + }; + d3_geo_centroid.lineEnd = function() { + d3_geo_centroid.point(λ00, φ00); + d3_geo_centroidLineEnd(); + d3_geo_centroid.lineEnd = d3_geo_centroidLineEnd; + }; + } + function d3_geo_centroidLineStart() { + var x0, y0, z0; + if (d3_geo_centroidDimension > 1) return; + if (d3_geo_centroidDimension < 1) { + d3_geo_centroidDimension = 1; + d3_geo_centroidW = d3_geo_centroidX = d3_geo_centroidY = d3_geo_centroidZ = 0; + } + d3_geo_centroid.point = function(λ, φ) { + λ *= d3_radians; + var cosφ = Math.cos(φ *= d3_radians); + x0 = cosφ * Math.cos(λ); + y0 = cosφ * Math.sin(λ); + z0 = Math.sin(φ); + d3_geo_centroid.point = nextPoint; + }; + function nextPoint(λ, φ) { + λ *= d3_radians; + var cosφ = Math.cos(φ *= d3_radians), x = cosφ * Math.cos(λ), y = cosφ * Math.sin(λ), z = Math.sin(φ), w = Math.atan2(Math.sqrt((w = y0 * z - z0 * y) * w + (w = z0 * x - x0 * z) * w + (w = x0 * y - y0 * x) * w), x0 * x + y0 * y + z0 * z); + d3_geo_centroidW += w; + d3_geo_centroidX += w * (x0 + (x0 = x)); + d3_geo_centroidY += w * (y0 + (y0 = y)); + d3_geo_centroidZ += w * (z0 + (z0 = z)); + } + } + function d3_geo_centroidLineEnd() { + d3_geo_centroid.point = d3_geo_centroidPoint; + } + function d3_geo_cartesian(spherical) { + var λ = spherical[0], φ = spherical[1], cosφ = Math.cos(φ); + return [ cosφ * Math.cos(λ), cosφ * Math.sin(λ), Math.sin(φ) ]; + } + function d3_geo_cartesianDot(a, b) { + return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; + } + function d3_geo_cartesianCross(a, b) { + return [ a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0] ]; + } + function d3_geo_cartesianAdd(a, b) { + a[0] += b[0]; + a[1] += b[1]; + a[2] += b[2]; + } + function d3_geo_cartesianScale(vector, k) { + return [ vector[0] * k, vector[1] * k, vector[2] * k ]; + } + function d3_geo_cartesianNormalize(d) { + var l = Math.sqrt(d[0] * d[0] + d[1] * d[1] + d[2] * d[2]); + d[0] /= l; + d[1] /= l; + d[2] /= l; + } + function d3_true() { + return true; + } + function d3_geo_spherical(cartesian) { + return [ Math.atan2(cartesian[1], cartesian[0]), Math.asin(Math.max(-1, Math.min(1, cartesian[2]))) ]; + } + function d3_geo_sphericalEqual(a, b) { + return Math.abs(a[0] - b[0]) < ε && Math.abs(a[1] - b[1]) < ε; + } + function d3_geo_clipPolygon(segments, compare, inside, interpolate, listener) { + var subject = [], clip = []; + segments.forEach(function(segment) { + if ((n = segment.length - 1) <= 0) return; + var n, p0 = segment[0], p1 = segment[n]; + if (d3_geo_sphericalEqual(p0, p1)) { + listener.lineStart(); + for (var i = 0; i < n; ++i) listener.point((p0 = segment[i])[0], p0[1]); + listener.lineEnd(); + return; + } + var a = { + point: p0, + points: segment, + other: null, + visited: false, + entry: true, + subject: true + }, b = { + point: p0, + points: [ p0 ], + other: a, + visited: false, + entry: false, + subject: false + }; + a.other = b; + subject.push(a); + clip.push(b); + a = { + point: p1, + points: [ p1 ], + other: null, + visited: false, + entry: false, + subject: true + }; + b = { + point: p1, + points: [ p1 ], + other: a, + visited: false, + entry: true, + subject: false + }; + a.other = b; + subject.push(a); + clip.push(b); + }); + clip.sort(compare); + d3_geo_clipPolygonLinkCircular(subject); + d3_geo_clipPolygonLinkCircular(clip); + if (!subject.length) return; + if (inside) for (var i = 1, e = !inside(clip[0].point), n = clip.length; i < n; ++i) { + clip[i].entry = e = !e; + } + var start = subject[0], current, points, point; + while (1) { + current = start; + while (current.visited) if ((current = current.next) === start) return; + points = current.points; + listener.lineStart(); + do { + current.visited = current.other.visited = true; + if (current.entry) { + if (current.subject) { + for (var i = 0; i < points.length; i++) listener.point((point = points[i])[0], point[1]); + } else { + interpolate(current.point, current.next.point, 1, listener); + } + current = current.next; + } else { + if (current.subject) { + points = current.prev.points; + for (var i = points.length; --i >= 0; ) listener.point((point = points[i])[0], point[1]); + } else { + interpolate(current.point, current.prev.point, -1, listener); + } + current = current.prev; + } + current = current.other; + points = current.points; + } while (!current.visited); + listener.lineEnd(); + } + } + function d3_geo_clipPolygonLinkCircular(array) { + if (!(n = array.length)) return; + var n, i = 0, a = array[0], b; + while (++i < n) { + a.next = b = array[i]; + b.prev = a; + a = b; + } + a.next = b = array[0]; + b.prev = a; + } + function d3_geo_clip(pointVisible, clipLine, interpolate) { + return function(listener) { + var line = clipLine(listener); + var clip = { + point: point, + lineStart: lineStart, + lineEnd: lineEnd, + polygonStart: function() { + clip.point = pointRing; + clip.lineStart = ringStart; + clip.lineEnd = ringEnd; + invisible = false; + invisibleArea = visibleArea = 0; + segments = []; + listener.polygonStart(); + }, + polygonEnd: function() { + clip.point = point; + clip.lineStart = lineStart; + clip.lineEnd = lineEnd; + segments = d3.merge(segments); + if (segments.length) { + d3_geo_clipPolygon(segments, d3_geo_clipSort, null, interpolate, listener); + } else if (visibleArea < -ε || invisible && invisibleArea < -ε) { + listener.lineStart(); + interpolate(null, null, 1, listener); + listener.lineEnd(); + } + listener.polygonEnd(); + segments = null; + }, + sphere: function() { + listener.polygonStart(); + listener.lineStart(); + interpolate(null, null, 1, listener); + listener.lineEnd(); + listener.polygonEnd(); + } + }; + function point(λ, φ) { + if (pointVisible(λ, φ)) listener.point(λ, φ); + } + function pointLine(λ, φ) { + line.point(λ, φ); + } + function lineStart() { + clip.point = pointLine; + line.lineStart(); + } + function lineEnd() { + clip.point = point; + line.lineEnd(); + } + var segments, visibleArea, invisibleArea, invisible; + var buffer = d3_geo_clipBufferListener(), ringListener = clipLine(buffer), ring; + function pointRing(λ, φ) { + ringListener.point(λ, φ); + ring.push([ λ, φ ]); + } + function ringStart() { + ringListener.lineStart(); + ring = []; + } + function ringEnd() { + pointRing(ring[0][0], ring[0][1]); + ringListener.lineEnd(); + var clean = ringListener.clean(), ringSegments = buffer.buffer(), segment, n = ringSegments.length; + if (!n) { + invisible = true; + invisibleArea += d3_geo_clipAreaRing(ring, -1); + ring = null; + return; + } + ring = null; + if (clean & 1) { + segment = ringSegments[0]; + visibleArea += d3_geo_clipAreaRing(segment, 1); + var n = segment.length - 1, i = -1, point; + listener.lineStart(); + while (++i < n) listener.point((point = segment[i])[0], point[1]); + listener.lineEnd(); + return; + } + if (n > 1 && clean & 2) ringSegments.push(ringSegments.pop().concat(ringSegments.shift())); + segments.push(ringSegments.filter(d3_geo_clipSegmentLength1)); + } + return clip; + }; + } + function d3_geo_clipSegmentLength1(segment) { + return segment.length > 1; + } + function d3_geo_clipBufferListener() { + var lines = [], line; + return { + lineStart: function() { + lines.push(line = []); + }, + point: function(λ, φ) { + line.push([ λ, φ ]); + }, + lineEnd: d3_noop, + buffer: function() { + var buffer = lines; + lines = []; + line = null; + return buffer; + }, + rejoin: function() { + if (lines.length > 1) lines.push(lines.pop().concat(lines.shift())); + } + }; + } + function d3_geo_clipAreaRing(ring, invisible) { + if (!(n = ring.length)) return 0; + var n, i = 0, area = 0, p = ring[0], λ = p[0], φ = p[1], cosφ = Math.cos(φ), x0 = Math.atan2(invisible * Math.sin(λ) * cosφ, Math.sin(φ)), y0 = 1 - invisible * Math.cos(λ) * cosφ, x1 = x0, x, y; + while (++i < n) { + p = ring[i]; + cosφ = Math.cos(φ = p[1]); + x = Math.atan2(invisible * Math.sin(λ = p[0]) * cosφ, Math.sin(φ)); + y = 1 - invisible * Math.cos(λ) * cosφ; + if (Math.abs(y0 - 2) < ε && Math.abs(y - 2) < ε) continue; + if (Math.abs(y) < ε || Math.abs(y0) < ε) {} else if (Math.abs(Math.abs(x - x0) - π) < ε) { + if (y + y0 > 2) area += 4 * (x - x0); + } else if (Math.abs(y0 - 2) < ε) area += 4 * (x - x1); else area += ((3 * π + x - x0) % (2 * π) - π) * (y0 + y); + x1 = x0, x0 = x, y0 = y; + } + return area; + } + function d3_geo_clipSort(a, b) { + return ((a = a.point)[0] < 0 ? a[1] - π / 2 - ε : π / 2 - a[1]) - ((b = b.point)[0] < 0 ? b[1] - π / 2 - ε : π / 2 - b[1]); + } + var d3_geo_clipAntimeridian = d3_geo_clip(d3_true, d3_geo_clipAntimeridianLine, d3_geo_clipAntimeridianInterpolate); + function d3_geo_clipAntimeridianLine(listener) { + var λ0 = NaN, φ0 = NaN, sλ0 = NaN, clean; + return { + lineStart: function() { + listener.lineStart(); + clean = 1; + }, + point: function(λ1, φ1) { + var sλ1 = λ1 > 0 ? π : -π, dλ = Math.abs(λ1 - λ0); + if (Math.abs(dλ - π) < ε) { + listener.point(λ0, φ0 = (φ0 + φ1) / 2 > 0 ? π / 2 : -π / 2); + listener.point(sλ0, φ0); + listener.lineEnd(); + listener.lineStart(); + listener.point(sλ1, φ0); + listener.point(λ1, φ0); + clean = 0; + } else if (sλ0 !== sλ1 && dλ >= π) { + if (Math.abs(λ0 - sλ0) < ε) λ0 -= sλ0 * ε; + if (Math.abs(λ1 - sλ1) < ε) λ1 -= sλ1 * ε; + φ0 = d3_geo_clipAntimeridianIntersect(λ0, φ0, λ1, φ1); + listener.point(sλ0, φ0); + listener.lineEnd(); + listener.lineStart(); + listener.point(sλ1, φ0); + clean = 0; + } + listener.point(λ0 = λ1, φ0 = φ1); + sλ0 = sλ1; + }, + lineEnd: function() { + listener.lineEnd(); + λ0 = φ0 = NaN; + }, + clean: function() { + return 2 - clean; + } + }; + } + function d3_geo_clipAntimeridianIntersect(λ0, φ0, λ1, φ1) { + var cosφ0, cosφ1, sinλ0_λ1 = Math.sin(λ0 - λ1); + return Math.abs(sinλ0_λ1) > ε ? Math.atan((Math.sin(φ0) * (cosφ1 = Math.cos(φ1)) * Math.sin(λ1) - Math.sin(φ1) * (cosφ0 = Math.cos(φ0)) * Math.sin(λ0)) / (cosφ0 * cosφ1 * sinλ0_λ1)) : (φ0 + φ1) / 2; + } + function d3_geo_clipAntimeridianInterpolate(from, to, direction, listener) { + var φ; + if (from == null) { + φ = direction * π / 2; + listener.point(-π, φ); + listener.point(0, φ); + listener.point(π, φ); + listener.point(π, 0); + listener.point(π, -φ); + listener.point(0, -φ); + listener.point(-π, -φ); + listener.point(-π, 0); + listener.point(-π, φ); + } else if (Math.abs(from[0] - to[0]) > ε) { + var s = (from[0] < to[0] ? 1 : -1) * π; + φ = direction * s / 2; + listener.point(-s, φ); + listener.point(0, φ); + listener.point(s, φ); + } else { + listener.point(to[0], to[1]); + } + } + function d3_geo_clipCircle(radius) { + var cr = Math.cos(radius), smallRadius = cr > 0, notHemisphere = Math.abs(cr) > ε, interpolate = d3_geo_circleInterpolate(radius, 6 * d3_radians); + return d3_geo_clip(visible, clipLine, interpolate); + function visible(λ, φ) { + return Math.cos(λ) * Math.cos(φ) > cr; + } + function clipLine(listener) { + var point0, c0, v0, v00, clean; + return { + lineStart: function() { + v00 = v0 = false; + clean = 1; + }, + point: function(λ, φ) { + var point1 = [ λ, φ ], point2, v = visible(λ, φ), c = smallRadius ? v ? 0 : code(λ, φ) : v ? code(λ + (λ < 0 ? π : -π), φ) : 0; + if (!point0 && (v00 = v0 = v)) listener.lineStart(); + if (v !== v0) { + point2 = intersect(point0, point1); + if (d3_geo_sphericalEqual(point0, point2) || d3_geo_sphericalEqual(point1, point2)) { + point1[0] += ε; + point1[1] += ε; + v = visible(point1[0], point1[1]); + } + } + if (v !== v0) { + clean = 0; + if (v) { + listener.lineStart(); + point2 = intersect(point1, point0); + listener.point(point2[0], point2[1]); + } else { + point2 = intersect(point0, point1); + listener.point(point2[0], point2[1]); + listener.lineEnd(); + } + point0 = point2; + } else if (notHemisphere && point0 && smallRadius ^ v) { + var t; + if (!(c & c0) && (t = intersect(point1, point0, true))) { + clean = 0; + if (smallRadius) { + listener.lineStart(); + listener.point(t[0][0], t[0][1]); + listener.point(t[1][0], t[1][1]); + listener.lineEnd(); + } else { + listener.point(t[1][0], t[1][1]); + listener.lineEnd(); + listener.lineStart(); + listener.point(t[0][0], t[0][1]); + } + } + } + if (v && (!point0 || !d3_geo_sphericalEqual(point0, point1))) { + listener.point(point1[0], point1[1]); + } + point0 = point1, v0 = v, c0 = c; + }, + lineEnd: function() { + if (v0) listener.lineEnd(); + point0 = null; + }, + clean: function() { + return clean | (v00 && v0) << 1; + } + }; + } + function intersect(a, b, two) { + var pa = d3_geo_cartesian(a), pb = d3_geo_cartesian(b); + var n1 = [ 1, 0, 0 ], n2 = d3_geo_cartesianCross(pa, pb), n2n2 = d3_geo_cartesianDot(n2, n2), n1n2 = n2[0], determinant = n2n2 - n1n2 * n1n2; + if (!determinant) return !two && a; + var c1 = cr * n2n2 / determinant, c2 = -cr * n1n2 / determinant, n1xn2 = d3_geo_cartesianCross(n1, n2), A = d3_geo_cartesianScale(n1, c1), B = d3_geo_cartesianScale(n2, c2); + d3_geo_cartesianAdd(A, B); + var u = n1xn2, w = d3_geo_cartesianDot(A, u), uu = d3_geo_cartesianDot(u, u), t2 = w * w - uu * (d3_geo_cartesianDot(A, A) - 1); + if (t2 < 0) return; + var t = Math.sqrt(t2), q = d3_geo_cartesianScale(u, (-w - t) / uu); + d3_geo_cartesianAdd(q, A); + q = d3_geo_spherical(q); + if (!two) return q; + var λ0 = a[0], λ1 = b[0], φ0 = a[1], φ1 = b[1], z; + if (λ1 < λ0) z = λ0, λ0 = λ1, λ1 = z; + var δλ = λ1 - λ0, polar = Math.abs(δλ - π) < ε, meridian = polar || δλ < ε; + if (!polar && φ1 < φ0) z = φ0, φ0 = φ1, φ1 = z; + if (meridian ? polar ? φ0 + φ1 > 0 ^ q[1] < (Math.abs(q[0] - λ0) < ε ? φ0 : φ1) : φ0 <= q[1] && q[1] <= φ1 : δλ > π ^ (λ0 <= q[0] && q[0] <= λ1)) { + var q1 = d3_geo_cartesianScale(u, (-w + t) / uu); + d3_geo_cartesianAdd(q1, A); + return [ q, d3_geo_spherical(q1) ]; + } + } + function code(λ, φ) { + var r = smallRadius ? radius : π - radius, code = 0; + if (λ < -r) code |= 1; else if (λ > r) code |= 2; + if (φ < -r) code |= 4; else if (φ > r) code |= 8; + return code; + } + } + var d3_geo_clipViewMAX = 1e9; + function d3_geo_clipView(x0, y0, x1, y1) { + return function(listener) { + var listener_ = listener, bufferListener = d3_geo_clipBufferListener(), segments, polygon, ring; + var clip = { + point: point, + lineStart: lineStart, + lineEnd: lineEnd, + polygonStart: function() { + listener = bufferListener; + segments = []; + polygon = []; + }, + polygonEnd: function() { + listener = listener_; + if ((segments = d3.merge(segments)).length) { + listener.polygonStart(); + d3_geo_clipPolygon(segments, compare, inside, interpolate, listener); + listener.polygonEnd(); + } else if (insidePolygon([ x0, y0 ])) { + listener.polygonStart(), listener.lineStart(); + interpolate(null, null, 1, listener); + listener.lineEnd(), listener.polygonEnd(); + } + segments = polygon = ring = null; + } + }; + function inside(point) { + var a = corner(point, -1), i = insidePolygon([ a === 0 || a === 3 ? x0 : x1, a > 1 ? y1 : y0 ]); + return i; + } + function insidePolygon(p) { + var wn = 0, n = polygon.length, y = p[1]; + for (var i = 0; i < n; ++i) { + for (var j = 1, v = polygon[i], m = v.length, a = v[0]; j < m; ++j) { + b = v[j]; + if (a[1] <= y) { + if (b[1] > y && isLeft(a, b, p) > 0) ++wn; + } else { + if (b[1] <= y && isLeft(a, b, p) < 0) --wn; + } + a = b; + } + } + return wn !== 0; + } + function isLeft(a, b, c) { + return (b[0] - a[0]) * (c[1] - a[1]) - (c[0] - a[0]) * (b[1] - a[1]); + } + function interpolate(from, to, direction, listener) { + var a = 0, a1 = 0; + if (from == null || (a = corner(from, direction)) !== (a1 = corner(to, direction)) || comparePoints(from, to) < 0 ^ direction > 0) { + do { + listener.point(a === 0 || a === 3 ? x0 : x1, a > 1 ? y1 : y0); + } while ((a = (a + direction + 4) % 4) !== a1); + } else { + listener.point(to[0], to[1]); + } + } + function visible(x, y) { + return x0 <= x && x <= x1 && y0 <= y && y <= y1; + } + function point(x, y) { + if (visible(x, y)) listener.point(x, y); + } + var x__, y__, v__, x_, y_, v_, first; + function lineStart() { + clip.point = linePoint; + if (polygon) polygon.push(ring = []); + first = true; + v_ = false; + x_ = y_ = NaN; + } + function lineEnd() { + if (segments) { + linePoint(x__, y__); + if (v__ && v_) bufferListener.rejoin(); + segments.push(bufferListener.buffer()); + } + clip.point = point; + if (v_) listener.lineEnd(); + } + function linePoint(x, y) { + x = Math.max(-d3_geo_clipViewMAX, Math.min(d3_geo_clipViewMAX, x)); + y = Math.max(-d3_geo_clipViewMAX, Math.min(d3_geo_clipViewMAX, y)); + var v = visible(x, y); + if (polygon) ring.push([ x, y ]); + if (first) { + x__ = x, y__ = y, v__ = v; + first = false; + if (v) { + listener.lineStart(); + listener.point(x, y); + } + } else { + if (v && v_) listener.point(x, y); else { + var a = [ x_, y_ ], b = [ x, y ]; + if (clipLine(a, b)) { + if (!v_) { + listener.lineStart(); + listener.point(a[0], a[1]); + } + listener.point(b[0], b[1]); + if (!v) listener.lineEnd(); + } else { + listener.lineStart(); + listener.point(x, y); + } + } + } + x_ = x, y_ = y, v_ = v; + } + return clip; + }; + function corner(p, direction) { + return Math.abs(p[0] - x0) < ε ? direction > 0 ? 0 : 3 : Math.abs(p[0] - x1) < ε ? direction > 0 ? 2 : 1 : Math.abs(p[1] - y0) < ε ? direction > 0 ? 1 : 0 : direction > 0 ? 3 : 2; + } + function compare(a, b) { + return comparePoints(a.point, b.point); + } + function comparePoints(a, b) { + var ca = corner(a, 1), cb = corner(b, 1); + return ca !== cb ? ca - cb : ca === 0 ? b[1] - a[1] : ca === 1 ? a[0] - b[0] : ca === 2 ? a[1] - b[1] : b[0] - a[0]; + } + function clipLine(a, b) { + var dx = b[0] - a[0], dy = b[1] - a[1], t = [ 0, 1 ]; + if (Math.abs(dx) < ε && Math.abs(dy) < ε) return x0 <= a[0] && a[0] <= x1 && y0 <= a[1] && a[1] <= y1; + if (d3_geo_clipViewT(x0 - a[0], dx, t) && d3_geo_clipViewT(a[0] - x1, -dx, t) && d3_geo_clipViewT(y0 - a[1], dy, t) && d3_geo_clipViewT(a[1] - y1, -dy, t)) { + if (t[1] < 1) { + b[0] = a[0] + t[1] * dx; + b[1] = a[1] + t[1] * dy; + } + if (t[0] > 0) { + a[0] += t[0] * dx; + a[1] += t[0] * dy; + } + return true; + } + return false; + } + } + function d3_geo_clipViewT(num, denominator, t) { + if (Math.abs(denominator) < ε) return num <= 0; + var u = num / denominator; + if (denominator > 0) { + if (u > t[1]) return false; + if (u > t[0]) t[0] = u; + } else { + if (u < t[0]) return false; + if (u < t[1]) t[1] = u; + } + return true; + } + function d3_geo_compose(a, b) { + function compose(x, y) { + return x = a(x, y), b(x[0], x[1]); + } + if (a.invert && b.invert) compose.invert = function(x, y) { + return x = b.invert(x, y), x && a.invert(x[0], x[1]); + }; + return compose; + } + function d3_geo_resample(project) { + var δ2 = .5, maxDepth = 16; + function resample(stream) { + var λ0, x0, y0, a0, b0, c0; + var resample = { + point: point, + lineStart: lineStart, + lineEnd: lineEnd, + polygonStart: function() { + stream.polygonStart(); + resample.lineStart = polygonLineStart; + }, + polygonEnd: function() { + stream.polygonEnd(); + resample.lineStart = lineStart; + } + }; + function point(x, y) { + x = project(x, y); + stream.point(x[0], x[1]); + } + function lineStart() { + x0 = NaN; + resample.point = linePoint; + stream.lineStart(); + } + function linePoint(λ, φ) { + var c = d3_geo_cartesian([ λ, φ ]), p = project(λ, φ); + resampleLineTo(x0, y0, λ0, a0, b0, c0, x0 = p[0], y0 = p[1], λ0 = λ, a0 = c[0], b0 = c[1], c0 = c[2], maxDepth, stream); + stream.point(x0, y0); + } + function lineEnd() { + resample.point = point; + stream.lineEnd(); + } + function polygonLineStart() { + var λ00, φ00, x00, y00, a00, b00, c00; + lineStart(); + resample.point = function(λ, φ) { + linePoint(λ00 = λ, φ00 = φ), x00 = x0, y00 = y0, a00 = a0, b00 = b0, c00 = c0; + resample.point = linePoint; + }; + resample.lineEnd = function() { + resampleLineTo(x0, y0, λ0, a0, b0, c0, x00, y00, λ00, a00, b00, c00, maxDepth, stream); + resample.lineEnd = lineEnd; + lineEnd(); + }; + } + return resample; + } + function resampleLineTo(x0, y0, λ0, a0, b0, c0, x1, y1, λ1, a1, b1, c1, depth, stream) { + var dx = x1 - x0, dy = y1 - y0, d2 = dx * dx + dy * dy; + if (d2 > 4 * δ2 && depth--) { + var a = a0 + a1, b = b0 + b1, c = c0 + c1, m = Math.sqrt(a * a + b * b + c * c), φ2 = Math.asin(c /= m), λ2 = Math.abs(Math.abs(c) - 1) < ε ? (λ0 + λ1) / 2 : Math.atan2(b, a), p = project(λ2, φ2), x2 = p[0], y2 = p[1], dx2 = x2 - x0, dy2 = y2 - y0, dz = dy * dx2 - dx * dy2; + if (dz * dz / d2 > δ2 || Math.abs((dx * dx2 + dy * dy2) / d2 - .5) > .3) { + resampleLineTo(x0, y0, λ0, a0, b0, c0, x2, y2, λ2, a /= m, b /= m, c, depth, stream); + stream.point(x2, y2); + resampleLineTo(x2, y2, λ2, a, b, c, x1, y1, λ1, a1, b1, c1, depth, stream); + } + } + } + resample.precision = function(_) { + if (!arguments.length) return Math.sqrt(δ2); + maxDepth = (δ2 = _ * _) > 0 && 16; + return resample; + }; + return resample; + } + d3.geo.projection = d3_geo_projection; + d3.geo.projectionMutator = d3_geo_projectionMutator; + function d3_geo_projection(project) { + return d3_geo_projectionMutator(function() { + return project; + })(); + } + function d3_geo_projectionMutator(projectAt) { + var project, rotate, projectRotate, projectResample = d3_geo_resample(function(x, y) { + x = project(x, y); + return [ x[0] * k + δx, δy - x[1] * k ]; + }), k = 150, x = 480, y = 250, λ = 0, φ = 0, δλ = 0, δφ = 0, δγ = 0, δx, δy, preclip = d3_geo_clipAntimeridian, postclip = d3_identity, clipAngle = null, clipExtent = null; + function projection(point) { + point = projectRotate(point[0] * d3_radians, point[1] * d3_radians); + return [ point[0] * k + δx, δy - point[1] * k ]; + } + function invert(point) { + point = projectRotate.invert((point[0] - δx) / k, (δy - point[1]) / k); + return point && [ point[0] * d3_degrees, point[1] * d3_degrees ]; + } + projection.stream = function(stream) { + return d3_geo_projectionRadiansRotate(rotate, preclip(projectResample(postclip(stream)))); + }; + projection.clipAngle = function(_) { + if (!arguments.length) return clipAngle; + preclip = _ == null ? (clipAngle = _, d3_geo_clipAntimeridian) : d3_geo_clipCircle((clipAngle = +_) * d3_radians); + return projection; + }; + projection.clipExtent = function(_) { + if (!arguments.length) return clipExtent; + clipExtent = _; + postclip = _ == null ? d3_identity : d3_geo_clipView(_[0][0], _[0][1], _[1][0], _[1][1]); + return projection; + }; + projection.scale = function(_) { + if (!arguments.length) return k; + k = +_; + return reset(); + }; + projection.translate = function(_) { + if (!arguments.length) return [ x, y ]; + x = +_[0]; + y = +_[1]; + return reset(); + }; + projection.center = function(_) { + if (!arguments.length) return [ λ * d3_degrees, φ * d3_degrees ]; + λ = _[0] % 360 * d3_radians; + φ = _[1] % 360 * d3_radians; + return reset(); + }; + projection.rotate = function(_) { + if (!arguments.length) return [ δλ * d3_degrees, δφ * d3_degrees, δγ * d3_degrees ]; + δλ = _[0] % 360 * d3_radians; + δφ = _[1] % 360 * d3_radians; + δγ = _.length > 2 ? _[2] % 360 * d3_radians : 0; + return reset(); + }; + d3.rebind(projection, projectResample, "precision"); + function reset() { + projectRotate = d3_geo_compose(rotate = d3_geo_rotation(δλ, δφ, δγ), project); + var center = project(λ, φ); + δx = x - center[0] * k; + δy = y + center[1] * k; + return projection; + } + return function() { + project = projectAt.apply(this, arguments); + projection.invert = project.invert && invert; + return reset(); + }; + } + function d3_geo_projectionRadiansRotate(rotate, stream) { + return { + point: function(x, y) { + y = rotate(x * d3_radians, y * d3_radians), x = y[0]; + stream.point(x > π ? x - 2 * π : x < -π ? x + 2 * π : x, y[1]); + }, + sphere: function() { + stream.sphere(); + }, + lineStart: function() { + stream.lineStart(); + }, + lineEnd: function() { + stream.lineEnd(); + }, + polygonStart: function() { + stream.polygonStart(); + }, + polygonEnd: function() { + stream.polygonEnd(); + } + }; + } + function d3_geo_equirectangular(λ, φ) { + return [ λ, φ ]; + } + (d3.geo.equirectangular = function() { + return d3_geo_projection(d3_geo_equirectangular); + }).raw = d3_geo_equirectangular.invert = d3_geo_equirectangular; + d3.geo.rotation = function(rotate) { + rotate = d3_geo_rotation(rotate[0] % 360 * d3_radians, rotate[1] * d3_radians, rotate.length > 2 ? rotate[2] * d3_radians : 0); + function forward(coordinates) { + coordinates = rotate(coordinates[0] * d3_radians, coordinates[1] * d3_radians); + return coordinates[0] *= d3_degrees, coordinates[1] *= d3_degrees, coordinates; + } + forward.invert = function(coordinates) { + coordinates = rotate.invert(coordinates[0] * d3_radians, coordinates[1] * d3_radians); + return coordinates[0] *= d3_degrees, coordinates[1] *= d3_degrees, coordinates; + }; + return forward; + }; + function d3_geo_rotation(δλ, δφ, δγ) { + return δλ ? δφ || δγ ? d3_geo_compose(d3_geo_rotationλ(δλ), d3_geo_rotationφγ(δφ, δγ)) : d3_geo_rotationλ(δλ) : δφ || δγ ? d3_geo_rotationφγ(δφ, δγ) : d3_geo_equirectangular; + } + function d3_geo_forwardRotationλ(δλ) { + return function(λ, φ) { + return λ += δλ, [ λ > π ? λ - 2 * π : λ < -π ? λ + 2 * π : λ, φ ]; + }; + } + function d3_geo_rotationλ(δλ) { + var rotation = d3_geo_forwardRotationλ(δλ); + rotation.invert = d3_geo_forwardRotationλ(-δλ); + return rotation; + } + function d3_geo_rotationφγ(δφ, δγ) { + var cosδφ = Math.cos(δφ), sinδφ = Math.sin(δφ), cosδγ = Math.cos(δγ), sinδγ = Math.sin(δγ); + function rotation(λ, φ) { + var cosφ = Math.cos(φ), x = Math.cos(λ) * cosφ, y = Math.sin(λ) * cosφ, z = Math.sin(φ), k = z * cosδφ + x * sinδφ; + return [ Math.atan2(y * cosδγ - k * sinδγ, x * cosδφ - z * sinδφ), Math.asin(Math.max(-1, Math.min(1, k * cosδγ + y * sinδγ))) ]; + } + rotation.invert = function(λ, φ) { + var cosφ = Math.cos(φ), x = Math.cos(λ) * cosφ, y = Math.sin(λ) * cosφ, z = Math.sin(φ), k = z * cosδγ - y * sinδγ; + return [ Math.atan2(y * cosδγ + z * sinδγ, x * cosδφ + k * sinδφ), Math.asin(Math.max(-1, Math.min(1, k * cosδφ - x * sinδφ))) ]; + }; + return rotation; + } + d3.geo.circle = function() { + var origin = [ 0, 0 ], angle, precision = 6, interpolate; + function circle() { + var center = typeof origin === "function" ? origin.apply(this, arguments) : origin, rotate = d3_geo_rotation(-center[0] * d3_radians, -center[1] * d3_radians, 0).invert, ring = []; + interpolate(null, null, 1, { + point: function(x, y) { + ring.push(x = rotate(x, y)); + x[0] *= d3_degrees, x[1] *= d3_degrees; + } + }); + return { + type: "Polygon", + coordinates: [ ring ] + }; + } + circle.origin = function(x) { + if (!arguments.length) return origin; + origin = x; + return circle; + }; + circle.angle = function(x) { + if (!arguments.length) return angle; + interpolate = d3_geo_circleInterpolate((angle = +x) * d3_radians, precision * d3_radians); + return circle; + }; + circle.precision = function(_) { + if (!arguments.length) return precision; + interpolate = d3_geo_circleInterpolate(angle * d3_radians, (precision = +_) * d3_radians); + return circle; + }; + return circle.angle(90); + }; + function d3_geo_circleInterpolate(radius, precision) { + var cr = Math.cos(radius), sr = Math.sin(radius); + return function(from, to, direction, listener) { + if (from != null) { + from = d3_geo_circleAngle(cr, from); + to = d3_geo_circleAngle(cr, to); + if (direction > 0 ? from < to : from > to) from += direction * 2 * π; + } else { + from = radius + direction * 2 * π; + to = radius; + } + var point; + for (var step = direction * precision, t = from; direction > 0 ? t > to : t < to; t -= step) { + listener.point((point = d3_geo_spherical([ cr, -sr * Math.cos(t), -sr * Math.sin(t) ]))[0], point[1]); + } + }; + } + function d3_geo_circleAngle(cr, point) { + var a = d3_geo_cartesian(point); + a[0] -= cr; + d3_geo_cartesianNormalize(a); + var angle = d3_acos(-a[1]); + return ((-a[2] < 0 ? -angle : angle) + 2 * Math.PI - ε) % (2 * Math.PI); + } + d3.geo.distance = function(a, b) { + var Δλ = (b[0] - a[0]) * d3_radians, φ0 = a[1] * d3_radians, φ1 = b[1] * d3_radians, sinΔλ = Math.sin(Δλ), cosΔλ = Math.cos(Δλ), sinφ0 = Math.sin(φ0), cosφ0 = Math.cos(φ0), sinφ1 = Math.sin(φ1), cosφ1 = Math.cos(φ1), t; + return Math.atan2(Math.sqrt((t = cosφ1 * sinΔλ) * t + (t = cosφ0 * sinφ1 - sinφ0 * cosφ1 * cosΔλ) * t), sinφ0 * sinφ1 + cosφ0 * cosφ1 * cosΔλ); + }; + d3.geo.graticule = function() { + var x1, x0, X1, X0, y1, y0, Y1, Y0, dx = 10, dy = dx, DX = 90, DY = 360, x, y, X, Y, precision = 2.5; + function graticule() { + return { + type: "MultiLineString", + coordinates: lines() + }; + } + function lines() { + return d3.range(Math.ceil(X0 / DX) * DX, X1, DX).map(X).concat(d3.range(Math.ceil(Y0 / DY) * DY, Y1, DY).map(Y)).concat(d3.range(Math.ceil(x0 / dx) * dx, x1, dx).filter(function(x) { + return Math.abs(x % DX) > ε; + }).map(x)).concat(d3.range(Math.ceil(y0 / dy) * dy, y1, dy).filter(function(y) { + return Math.abs(y % DY) > ε; + }).map(y)); + } + graticule.lines = function() { + return lines().map(function(coordinates) { + return { + type: "LineString", + coordinates: coordinates + }; + }); + }; + graticule.outline = function() { + return { + type: "Polygon", + coordinates: [ X(X0).concat(Y(Y1).slice(1), X(X1).reverse().slice(1), Y(Y0).reverse().slice(1)) ] + }; + }; + graticule.extent = function(_) { + if (!arguments.length) return graticule.minorExtent(); + return graticule.majorExtent(_).minorExtent(_); + }; + graticule.majorExtent = function(_) { + if (!arguments.length) return [ [ X0, Y0 ], [ X1, Y1 ] ]; + X0 = +_[0][0], X1 = +_[1][0]; + Y0 = +_[0][1], Y1 = +_[1][1]; + if (X0 > X1) _ = X0, X0 = X1, X1 = _; + if (Y0 > Y1) _ = Y0, Y0 = Y1, Y1 = _; + return graticule.precision(precision); + }; + graticule.minorExtent = function(_) { + if (!arguments.length) return [ [ x0, y0 ], [ x1, y1 ] ]; + x0 = +_[0][0], x1 = +_[1][0]; + y0 = +_[0][1], y1 = +_[1][1]; + if (x0 > x1) _ = x0, x0 = x1, x1 = _; + if (y0 > y1) _ = y0, y0 = y1, y1 = _; + return graticule.precision(precision); + }; + graticule.step = function(_) { + if (!arguments.length) return graticule.minorStep(); + return graticule.majorStep(_).minorStep(_); + }; + graticule.majorStep = function(_) { + if (!arguments.length) return [ DX, DY ]; + DX = +_[0], DY = +_[1]; + return graticule; + }; + graticule.minorStep = function(_) { + if (!arguments.length) return [ dx, dy ]; + dx = +_[0], dy = +_[1]; + return graticule; + }; + graticule.precision = function(_) { + if (!arguments.length) return precision; + precision = +_; + x = d3_geo_graticuleX(y0, y1, 90); + y = d3_geo_graticuleY(x0, x1, precision); + X = d3_geo_graticuleX(Y0, Y1, 90); + Y = d3_geo_graticuleY(X0, X1, precision); + return graticule; + }; + return graticule.majorExtent([ [ -180, -90 + ε ], [ 180, 90 - ε ] ]).minorExtent([ [ -180, -80 - ε ], [ 180, 80 + ε ] ]); + }; + function d3_geo_graticuleX(y0, y1, dy) { + var y = d3.range(y0, y1 - ε, dy).concat(y1); + return function(x) { + return y.map(function(y) { + return [ x, y ]; + }); + }; + } + function d3_geo_graticuleY(x0, x1, dx) { + var x = d3.range(x0, x1 - ε, dx).concat(x1); + return function(y) { + return x.map(function(x) { + return [ x, y ]; + }); + }; + } + function d3_source(d) { + return d.source; + } + function d3_target(d) { + return d.target; + } + d3.geo.greatArc = function() { + var source = d3_source, source_, target = d3_target, target_; + function greatArc() { + return { + type: "LineString", + coordinates: [ source_ || source.apply(this, arguments), target_ || target.apply(this, arguments) ] + }; + } + greatArc.distance = function() { + return d3.geo.distance(source_ || source.apply(this, arguments), target_ || target.apply(this, arguments)); + }; + greatArc.source = function(_) { + if (!arguments.length) return source; + source = _, source_ = typeof _ === "function" ? null : _; + return greatArc; + }; + greatArc.target = function(_) { + if (!arguments.length) return target; + target = _, target_ = typeof _ === "function" ? null : _; + return greatArc; + }; + greatArc.precision = function() { + return arguments.length ? greatArc : 0; + }; + return greatArc; + }; + d3.geo.interpolate = function(source, target) { + return d3_geo_interpolate(source[0] * d3_radians, source[1] * d3_radians, target[0] * d3_radians, target[1] * d3_radians); + }; + function d3_geo_interpolate(x0, y0, x1, y1) { + var cy0 = Math.cos(y0), sy0 = Math.sin(y0), cy1 = Math.cos(y1), sy1 = Math.sin(y1), kx0 = cy0 * Math.cos(x0), ky0 = cy0 * Math.sin(x0), kx1 = cy1 * Math.cos(x1), ky1 = cy1 * Math.sin(x1), d = 2 * Math.asin(Math.sqrt(d3_haversin(y1 - y0) + cy0 * cy1 * d3_haversin(x1 - x0))), k = 1 / Math.sin(d); + var interpolate = d ? function(t) { + var B = Math.sin(t *= d) * k, A = Math.sin(d - t) * k, x = A * kx0 + B * kx1, y = A * ky0 + B * ky1, z = A * sy0 + B * sy1; + return [ Math.atan2(y, x) * d3_degrees, Math.atan2(z, Math.sqrt(x * x + y * y)) * d3_degrees ]; + } : function() { + return [ x0 * d3_degrees, y0 * d3_degrees ]; + }; + interpolate.distance = d; + return interpolate; + } + d3.geo.length = function(object) { + d3_geo_lengthSum = 0; + d3.geo.stream(object, d3_geo_length); + return d3_geo_lengthSum; + }; + var d3_geo_lengthSum; + var d3_geo_length = { + sphere: d3_noop, + point: d3_noop, + lineStart: d3_geo_lengthLineStart, + lineEnd: d3_noop, + polygonStart: d3_noop, + polygonEnd: d3_noop + }; + function d3_geo_lengthLineStart() { + var λ0, sinφ0, cosφ0; + d3_geo_length.point = function(λ, φ) { + λ0 = λ * d3_radians, sinφ0 = Math.sin(φ *= d3_radians), cosφ0 = Math.cos(φ); + d3_geo_length.point = nextPoint; + }; + d3_geo_length.lineEnd = function() { + d3_geo_length.point = d3_geo_length.lineEnd = d3_noop; + }; + function nextPoint(λ, φ) { + var sinφ = Math.sin(φ *= d3_radians), cosφ = Math.cos(φ), t = Math.abs((λ *= d3_radians) - λ0), cosΔλ = Math.cos(t); + d3_geo_lengthSum += Math.atan2(Math.sqrt((t = cosφ * Math.sin(t)) * t + (t = cosφ0 * sinφ - sinφ0 * cosφ * cosΔλ) * t), sinφ0 * sinφ + cosφ0 * cosφ * cosΔλ); + λ0 = λ, sinφ0 = sinφ, cosφ0 = cosφ; + } + } + function d3_geo_conic(projectAt) { + var φ0 = 0, φ1 = π / 3, m = d3_geo_projectionMutator(projectAt), p = m(φ0, φ1); + p.parallels = function(_) { + if (!arguments.length) return [ φ0 / π * 180, φ1 / π * 180 ]; + return m(φ0 = _[0] * π / 180, φ1 = _[1] * π / 180); + }; + return p; + } + function d3_geo_conicEqualArea(φ0, φ1) { + var sinφ0 = Math.sin(φ0), n = (sinφ0 + Math.sin(φ1)) / 2, C = 1 + sinφ0 * (2 * n - sinφ0), ρ0 = Math.sqrt(C) / n; + function forward(λ, φ) { + var ρ = Math.sqrt(C - 2 * n * Math.sin(φ)) / n; + return [ ρ * Math.sin(λ *= n), ρ0 - ρ * Math.cos(λ) ]; + } + forward.invert = function(x, y) { + var ρ0_y = ρ0 - y; + return [ Math.atan2(x, ρ0_y) / n, Math.asin((C - (x * x + ρ0_y * ρ0_y) * n * n) / (2 * n)) ]; + }; + return forward; + } + (d3.geo.conicEqualArea = function() { + return d3_geo_conic(d3_geo_conicEqualArea); + }).raw = d3_geo_conicEqualArea; + d3.geo.albersUsa = function() { + var lower48 = d3.geo.conicEqualArea().rotate([ 98, 0 ]).center([ 0, 38 ]).parallels([ 29.5, 45.5 ]); + var alaska = d3.geo.conicEqualArea().rotate([ 160, 0 ]).center([ 0, 60 ]).parallels([ 55, 65 ]); + var hawaii = d3.geo.conicEqualArea().rotate([ 160, 0 ]).center([ 0, 20 ]).parallels([ 8, 18 ]); + var puertoRico = d3.geo.conicEqualArea().rotate([ 60, 0 ]).center([ 0, 10 ]).parallels([ 8, 18 ]); + var alaskaInvert, hawaiiInvert, puertoRicoInvert; + function albersUsa(coordinates) { + return projection(coordinates)(coordinates); + } + function projection(point) { + var lon = point[0], lat = point[1]; + return lat > 50 ? alaska : lon < -140 ? hawaii : lat < 21 ? puertoRico : lower48; + } + albersUsa.invert = function(coordinates) { + return alaskaInvert(coordinates) || hawaiiInvert(coordinates) || puertoRicoInvert(coordinates) || lower48.invert(coordinates); + }; + albersUsa.scale = function(x) { + if (!arguments.length) return lower48.scale(); + lower48.scale(x); + alaska.scale(x * .6); + hawaii.scale(x); + puertoRico.scale(x * 1.5); + return albersUsa.translate(lower48.translate()); + }; + albersUsa.translate = function(x) { + if (!arguments.length) return lower48.translate(); + var dz = lower48.scale(), dx = x[0], dy = x[1]; + lower48.translate(x); + alaska.translate([ dx - .4 * dz, dy + .17 * dz ]); + hawaii.translate([ dx - .19 * dz, dy + .2 * dz ]); + puertoRico.translate([ dx + .58 * dz, dy + .43 * dz ]); + alaskaInvert = d3_geo_albersUsaInvert(alaska, [ [ -180, 50 ], [ -130, 72 ] ]); + hawaiiInvert = d3_geo_albersUsaInvert(hawaii, [ [ -164, 18 ], [ -154, 24 ] ]); + puertoRicoInvert = d3_geo_albersUsaInvert(puertoRico, [ [ -67.5, 17.5 ], [ -65, 19 ] ]); + return albersUsa; + }; + return albersUsa.scale(1e3); + }; + function d3_geo_albersUsaInvert(projection, extent) { + var a = projection(extent[0]), b = projection([ .5 * (extent[0][0] + extent[1][0]), extent[0][1] ]), c = projection([ extent[1][0], extent[0][1] ]), d = projection(extent[1]); + var dya = b[1] - a[1], dxa = b[0] - a[0], dyb = c[1] - b[1], dxb = c[0] - b[0]; + var ma = dya / dxa, mb = dyb / dxb; + var cx = .5 * (ma * mb * (a[1] - c[1]) + mb * (a[0] + b[0]) - ma * (b[0] + c[0])) / (mb - ma), cy = (.5 * (a[0] + b[0]) - cx) / ma + .5 * (a[1] + b[1]); + var dx0 = d[0] - cx, dy0 = d[1] - cy, dx1 = a[0] - cx, dy1 = a[1] - cy, r0 = dx0 * dx0 + dy0 * dy0, r1 = dx1 * dx1 + dy1 * dy1; + var a0 = Math.atan2(dy0, dx0), a1 = Math.atan2(dy1, dx1); + return function(coordinates) { + var dx = coordinates[0] - cx, dy = coordinates[1] - cy, r = dx * dx + dy * dy, a = Math.atan2(dy, dx); + if (r0 < r && r < r1 && a0 < a && a < a1) return projection.invert(coordinates); + }; + } + var d3_geo_pathAreaSum, d3_geo_pathAreaPolygon, d3_geo_pathArea = { + point: d3_noop, + lineStart: d3_noop, + lineEnd: d3_noop, + polygonStart: function() { + d3_geo_pathAreaPolygon = 0; + d3_geo_pathArea.lineStart = d3_geo_pathAreaRingStart; + }, + polygonEnd: function() { + d3_geo_pathArea.lineStart = d3_geo_pathArea.lineEnd = d3_geo_pathArea.point = d3_noop; + d3_geo_pathAreaSum += Math.abs(d3_geo_pathAreaPolygon / 2); + } + }; + function d3_geo_pathAreaRingStart() { + var x00, y00, x0, y0; + d3_geo_pathArea.point = function(x, y) { + d3_geo_pathArea.point = nextPoint; + x00 = x0 = x, y00 = y0 = y; + }; + function nextPoint(x, y) { + d3_geo_pathAreaPolygon += y0 * x - x0 * y; + x0 = x, y0 = y; + } + d3_geo_pathArea.lineEnd = function() { + nextPoint(x00, y00); + }; + } + function d3_geo_pathBuffer() { + var pointCircle = d3_geo_pathCircle(4.5), buffer = []; + var stream = { + point: point, + lineStart: function() { + stream.point = pointLineStart; + }, + lineEnd: lineEnd, + polygonStart: function() { + stream.lineEnd = lineEndPolygon; + }, + polygonEnd: function() { + stream.lineEnd = lineEnd; + stream.point = point; + }, + pointRadius: function(_) { + pointCircle = d3_geo_pathCircle(_); + return stream; + }, + result: function() { + if (buffer.length) { + var result = buffer.join(""); + buffer = []; + return result; + } + } + }; + function point(x, y) { + buffer.push("M", x, ",", y, pointCircle); + } + function pointLineStart(x, y) { + buffer.push("M", x, ",", y); + stream.point = pointLine; + } + function pointLine(x, y) { + buffer.push("L", x, ",", y); + } + function lineEnd() { + stream.point = point; + } + function lineEndPolygon() { + buffer.push("Z"); + } + return stream; + } + var d3_geo_pathCentroid = { + point: d3_geo_pathCentroidPoint, + lineStart: d3_geo_pathCentroidLineStart, + lineEnd: d3_geo_pathCentroidLineEnd, + polygonStart: function() { + d3_geo_pathCentroid.lineStart = d3_geo_pathCentroidRingStart; + }, + polygonEnd: function() { + d3_geo_pathCentroid.point = d3_geo_pathCentroidPoint; + d3_geo_pathCentroid.lineStart = d3_geo_pathCentroidLineStart; + d3_geo_pathCentroid.lineEnd = d3_geo_pathCentroidLineEnd; + } + }; + function d3_geo_pathCentroidPoint(x, y) { + if (d3_geo_centroidDimension) return; + d3_geo_centroidX += x; + d3_geo_centroidY += y; + ++d3_geo_centroidZ; + } + function d3_geo_pathCentroidLineStart() { + var x0, y0; + if (d3_geo_centroidDimension !== 1) { + if (d3_geo_centroidDimension < 1) { + d3_geo_centroidDimension = 1; + d3_geo_centroidX = d3_geo_centroidY = d3_geo_centroidZ = 0; + } else return; + } + d3_geo_pathCentroid.point = function(x, y) { + d3_geo_pathCentroid.point = nextPoint; + x0 = x, y0 = y; + }; + function nextPoint(x, y) { + var dx = x - x0, dy = y - y0, z = Math.sqrt(dx * dx + dy * dy); + d3_geo_centroidX += z * (x0 + x) / 2; + d3_geo_centroidY += z * (y0 + y) / 2; + d3_geo_centroidZ += z; + x0 = x, y0 = y; + } + } + function d3_geo_pathCentroidLineEnd() { + d3_geo_pathCentroid.point = d3_geo_pathCentroidPoint; + } + function d3_geo_pathCentroidRingStart() { + var x00, y00, x0, y0; + if (d3_geo_centroidDimension < 2) { + d3_geo_centroidDimension = 2; + d3_geo_centroidX = d3_geo_centroidY = d3_geo_centroidZ = 0; + } + d3_geo_pathCentroid.point = function(x, y) { + d3_geo_pathCentroid.point = nextPoint; + x00 = x0 = x, y00 = y0 = y; + }; + function nextPoint(x, y) { + var z = y0 * x - x0 * y; + d3_geo_centroidX += z * (x0 + x); + d3_geo_centroidY += z * (y0 + y); + d3_geo_centroidZ += z * 3; + x0 = x, y0 = y; + } + d3_geo_pathCentroid.lineEnd = function() { + nextPoint(x00, y00); + }; + } + function d3_geo_pathContext(context) { + var pointRadius = 4.5; + var stream = { + point: point, + lineStart: function() { + stream.point = pointLineStart; + }, + lineEnd: lineEnd, + polygonStart: function() { + stream.lineEnd = lineEndPolygon; + }, + polygonEnd: function() { + stream.lineEnd = lineEnd; + stream.point = point; + }, + pointRadius: function(_) { + pointRadius = _; + return stream; + }, + result: d3_noop + }; + function point(x, y) { + context.moveTo(x, y); + context.arc(x, y, pointRadius, 0, 2 * π); + } + function pointLineStart(x, y) { + context.moveTo(x, y); + stream.point = pointLine; + } + function pointLine(x, y) { + context.lineTo(x, y); + } + function lineEnd() { + stream.point = point; + } + function lineEndPolygon() { + context.closePath(); + } + return stream; + } + d3.geo.path = function() { + var pointRadius = 4.5, projection, context, projectStream, contextStream; + function path(object) { + if (object) d3.geo.stream(object, projectStream(contextStream.pointRadius(typeof pointRadius === "function" ? +pointRadius.apply(this, arguments) : pointRadius))); + return contextStream.result(); + } + path.area = function(object) { + d3_geo_pathAreaSum = 0; + d3.geo.stream(object, projectStream(d3_geo_pathArea)); + return d3_geo_pathAreaSum; + }; + path.centroid = function(object) { + d3_geo_centroidDimension = d3_geo_centroidX = d3_geo_centroidY = d3_geo_centroidZ = 0; + d3.geo.stream(object, projectStream(d3_geo_pathCentroid)); + return d3_geo_centroidZ ? [ d3_geo_centroidX / d3_geo_centroidZ, d3_geo_centroidY / d3_geo_centroidZ ] : undefined; + }; + path.bounds = function(object) { + return d3_geo_bounds(projectStream)(object); + }; + path.projection = function(_) { + if (!arguments.length) return projection; + projectStream = (projection = _) ? _.stream || d3_geo_pathProjectStream(_) : d3_identity; + return path; + }; + path.context = function(_) { + if (!arguments.length) return context; + contextStream = (context = _) == null ? new d3_geo_pathBuffer() : new d3_geo_pathContext(_); + return path; + }; + path.pointRadius = function(_) { + if (!arguments.length) return pointRadius; + pointRadius = typeof _ === "function" ? _ : +_; + return path; + }; + return path.projection(d3.geo.albersUsa()).context(null); + }; + function d3_geo_pathCircle(radius) { + return "m0," + radius + "a" + radius + "," + radius + " 0 1,1 0," + -2 * radius + "a" + radius + "," + radius + " 0 1,1 0," + +2 * radius + "z"; + } + function d3_geo_pathProjectStream(project) { + var resample = d3_geo_resample(function(λ, φ) { + return project([ λ * d3_degrees, φ * d3_degrees ]); + }); + return function(stream) { + stream = resample(stream); + return { + point: function(λ, φ) { + stream.point(λ * d3_radians, φ * d3_radians); + }, + sphere: function() { + stream.sphere(); + }, + lineStart: function() { + stream.lineStart(); + }, + lineEnd: function() { + stream.lineEnd(); + }, + polygonStart: function() { + stream.polygonStart(); + }, + polygonEnd: function() { + stream.polygonEnd(); + } + }; + }; + } + d3.geo.albers = function() { + return d3.geo.conicEqualArea().parallels([ 29.5, 45.5 ]).rotate([ 98, 0 ]).center([ 0, 38 ]).scale(1e3); + }; + function d3_geo_azimuthal(scale, angle) { + function azimuthal(λ, φ) { + var cosλ = Math.cos(λ), cosφ = Math.cos(φ), k = scale(cosλ * cosφ); + return [ k * cosφ * Math.sin(λ), k * Math.sin(φ) ]; + } + azimuthal.invert = function(x, y) { + var ρ = Math.sqrt(x * x + y * y), c = angle(ρ), sinc = Math.sin(c), cosc = Math.cos(c); + return [ Math.atan2(x * sinc, ρ * cosc), Math.asin(ρ && y * sinc / ρ) ]; + }; + return azimuthal; + } + var d3_geo_azimuthalEqualArea = d3_geo_azimuthal(function(cosλcosφ) { + return Math.sqrt(2 / (1 + cosλcosφ)); + }, function(ρ) { + return 2 * Math.asin(ρ / 2); + }); + (d3.geo.azimuthalEqualArea = function() { + return d3_geo_projection(d3_geo_azimuthalEqualArea); + }).raw = d3_geo_azimuthalEqualArea; + var d3_geo_azimuthalEquidistant = d3_geo_azimuthal(function(cosλcosφ) { + var c = Math.acos(cosλcosφ); + return c && c / Math.sin(c); + }, d3_identity); + (d3.geo.azimuthalEquidistant = function() { + return d3_geo_projection(d3_geo_azimuthalEquidistant); + }).raw = d3_geo_azimuthalEquidistant; + function d3_geo_conicConformal(φ0, φ1) { + var cosφ0 = Math.cos(φ0), t = function(φ) { + return Math.tan(π / 4 + φ / 2); + }, n = φ0 === φ1 ? Math.sin(φ0) : Math.log(cosφ0 / Math.cos(φ1)) / Math.log(t(φ1) / t(φ0)), F = cosφ0 * Math.pow(t(φ0), n) / n; + if (!n) return d3_geo_mercator; + function forward(λ, φ) { + var ρ = Math.abs(Math.abs(φ) - π / 2) < ε ? 0 : F / Math.pow(t(φ), n); + return [ ρ * Math.sin(n * λ), F - ρ * Math.cos(n * λ) ]; + } + forward.invert = function(x, y) { + var ρ0_y = F - y, ρ = d3_sgn(n) * Math.sqrt(x * x + ρ0_y * ρ0_y); + return [ Math.atan2(x, ρ0_y) / n, 2 * Math.atan(Math.pow(F / ρ, 1 / n)) - π / 2 ]; + }; + return forward; + } + (d3.geo.conicConformal = function() { + return d3_geo_conic(d3_geo_conicConformal); + }).raw = d3_geo_conicConformal; + function d3_geo_conicEquidistant(φ0, φ1) { + var cosφ0 = Math.cos(φ0), n = φ0 === φ1 ? Math.sin(φ0) : (cosφ0 - Math.cos(φ1)) / (φ1 - φ0), G = cosφ0 / n + φ0; + if (Math.abs(n) < ε) return d3_geo_equirectangular; + function forward(λ, φ) { + var ρ = G - φ; + return [ ρ * Math.sin(n * λ), G - ρ * Math.cos(n * λ) ]; + } + forward.invert = function(x, y) { + var ρ0_y = G - y; + return [ Math.atan2(x, ρ0_y) / n, G - d3_sgn(n) * Math.sqrt(x * x + ρ0_y * ρ0_y) ]; + }; + return forward; + } + (d3.geo.conicEquidistant = function() { + return d3_geo_conic(d3_geo_conicEquidistant); + }).raw = d3_geo_conicEquidistant; + var d3_geo_gnomonic = d3_geo_azimuthal(function(cosλcosφ) { + return 1 / cosλcosφ; + }, Math.atan); + (d3.geo.gnomonic = function() { + return d3_geo_projection(d3_geo_gnomonic); + }).raw = d3_geo_gnomonic; + function d3_geo_mercator(λ, φ) { + return [ λ, Math.log(Math.tan(π / 4 + φ / 2)) ]; + } + d3_geo_mercator.invert = function(x, y) { + return [ x, 2 * Math.atan(Math.exp(y)) - π / 2 ]; + }; + function d3_geo_mercatorProjection(project) { + var m = d3_geo_projection(project), scale = m.scale, translate = m.translate, clipExtent = m.clipExtent, clipAuto; + m.scale = function() { + var v = scale.apply(m, arguments); + return v === m ? clipAuto ? m.clipExtent(null) : m : v; + }; + m.translate = function() { + var v = translate.apply(m, arguments); + return v === m ? clipAuto ? m.clipExtent(null) : m : v; + }; + m.clipExtent = function(_) { + var v = clipExtent.apply(m, arguments); + if (v === m) { + if (clipAuto = _ == null) { + var k = π * scale(), t = translate(); + clipExtent([ [ t[0] - k, t[1] - k ], [ t[0] + k, t[1] + k ] ]); + } + } else if (clipAuto) { + v = null; + } + return v; + }; + return m.clipExtent(null); + } + (d3.geo.mercator = function() { + return d3_geo_mercatorProjection(d3_geo_mercator); + }).raw = d3_geo_mercator; + var d3_geo_orthographic = d3_geo_azimuthal(function() { + return 1; + }, Math.asin); + (d3.geo.orthographic = function() { + return d3_geo_projection(d3_geo_orthographic); + }).raw = d3_geo_orthographic; + var d3_geo_stereographic = d3_geo_azimuthal(function(cosλcosφ) { + return 1 / (1 + cosλcosφ); + }, function(ρ) { + return 2 * Math.atan(ρ); + }); + (d3.geo.stereographic = function() { + return d3_geo_projection(d3_geo_stereographic); + }).raw = d3_geo_stereographic; + function d3_geo_transverseMercator(λ, φ) { + var B = Math.cos(φ) * Math.sin(λ); + return [ Math.log((1 + B) / (1 - B)) / 2, Math.atan2(Math.tan(φ), Math.cos(λ)) ]; + } + d3_geo_transverseMercator.invert = function(x, y) { + return [ Math.atan2(d3_sinh(x), Math.cos(y)), d3_asin(Math.sin(y) / d3_cosh(x)) ]; + }; + (d3.geo.transverseMercator = function() { + return d3_geo_mercatorProjection(d3_geo_transverseMercator); + }).raw = d3_geo_transverseMercator; + d3.geom = {}; + d3.svg = {}; + function d3_svg_line(projection) { + var x = d3_svg_lineX, y = d3_svg_lineY, defined = d3_true, interpolate = d3_svg_lineLinear, interpolateKey = interpolate.key, tension = .7; + function line(data) { + var segments = [], points = [], i = -1, n = data.length, d, fx = d3_functor(x), fy = d3_functor(y); + function segment() { + segments.push("M", interpolate(projection(points), tension)); + } + while (++i < n) { + if (defined.call(this, d = data[i], i)) { + points.push([ +fx.call(this, d, i), +fy.call(this, d, i) ]); + } else if (points.length) { + segment(); + points = []; + } + } + if (points.length) segment(); + return segments.length ? segments.join("") : null; + } + line.x = function(_) { + if (!arguments.length) return x; + x = _; + return line; + }; + line.y = function(_) { + if (!arguments.length) return y; + y = _; + return line; + }; + line.defined = function(_) { + if (!arguments.length) return defined; + defined = _; + return line; + }; + line.interpolate = function(_) { + if (!arguments.length) return interpolateKey; + if (typeof _ === "function") interpolateKey = interpolate = _; else interpolateKey = (interpolate = d3_svg_lineInterpolators.get(_) || d3_svg_lineLinear).key; + return line; + }; + line.tension = function(_) { + if (!arguments.length) return tension; + tension = _; + return line; + }; + return line; + } + d3.svg.line = function() { + return d3_svg_line(d3_identity); + }; + function d3_svg_lineX(d) { + return d[0]; + } + function d3_svg_lineY(d) { + return d[1]; + } + var d3_svg_lineInterpolators = d3.map({ + linear: d3_svg_lineLinear, + "linear-closed": d3_svg_lineLinearClosed, + "step-before": d3_svg_lineStepBefore, + "step-after": d3_svg_lineStepAfter, + basis: d3_svg_lineBasis, + "basis-open": d3_svg_lineBasisOpen, + "basis-closed": d3_svg_lineBasisClosed, + bundle: d3_svg_lineBundle, + cardinal: d3_svg_lineCardinal, + "cardinal-open": d3_svg_lineCardinalOpen, + "cardinal-closed": d3_svg_lineCardinalClosed, + monotone: d3_svg_lineMonotone + }); + d3_svg_lineInterpolators.forEach(function(key, value) { + value.key = key; + value.closed = /-closed$/.test(key); + }); + function d3_svg_lineLinear(points) { + return points.join("L"); + } + function d3_svg_lineLinearClosed(points) { + return d3_svg_lineLinear(points) + "Z"; + } + function d3_svg_lineStepBefore(points) { + var i = 0, n = points.length, p = points[0], path = [ p[0], ",", p[1] ]; + while (++i < n) path.push("V", (p = points[i])[1], "H", p[0]); + return path.join(""); + } + function d3_svg_lineStepAfter(points) { + var i = 0, n = points.length, p = points[0], path = [ p[0], ",", p[1] ]; + while (++i < n) path.push("H", (p = points[i])[0], "V", p[1]); + return path.join(""); + } + function d3_svg_lineCardinalOpen(points, tension) { + return points.length < 4 ? d3_svg_lineLinear(points) : points[1] + d3_svg_lineHermite(points.slice(1, points.length - 1), d3_svg_lineCardinalTangents(points, tension)); + } + function d3_svg_lineCardinalClosed(points, tension) { + return points.length < 3 ? d3_svg_lineLinear(points) : points[0] + d3_svg_lineHermite((points.push(points[0]), + points), d3_svg_lineCardinalTangents([ points[points.length - 2] ].concat(points, [ points[1] ]), tension)); + } + function d3_svg_lineCardinal(points, tension) { + return points.length < 3 ? d3_svg_lineLinear(points) : points[0] + d3_svg_lineHermite(points, d3_svg_lineCardinalTangents(points, tension)); + } + function d3_svg_lineHermite(points, tangents) { + if (tangents.length < 1 || points.length != tangents.length && points.length != tangents.length + 2) { + return d3_svg_lineLinear(points); + } + var quad = points.length != tangents.length, path = "", p0 = points[0], p = points[1], t0 = tangents[0], t = t0, pi = 1; + if (quad) { + path += "Q" + (p[0] - t0[0] * 2 / 3) + "," + (p[1] - t0[1] * 2 / 3) + "," + p[0] + "," + p[1]; + p0 = points[1]; + pi = 2; + } + if (tangents.length > 1) { + t = tangents[1]; + p = points[pi]; + pi++; + path += "C" + (p0[0] + t0[0]) + "," + (p0[1] + t0[1]) + "," + (p[0] - t[0]) + "," + (p[1] - t[1]) + "," + p[0] + "," + p[1]; + for (var i = 2; i < tangents.length; i++, pi++) { + p = points[pi]; + t = tangents[i]; + path += "S" + (p[0] - t[0]) + "," + (p[1] - t[1]) + "," + p[0] + "," + p[1]; + } + } + if (quad) { + var lp = points[pi]; + path += "Q" + (p[0] + t[0] * 2 / 3) + "," + (p[1] + t[1] * 2 / 3) + "," + lp[0] + "," + lp[1]; + } + return path; + } + function d3_svg_lineCardinalTangents(points, tension) { + var tangents = [], a = (1 - tension) / 2, p0, p1 = points[0], p2 = points[1], i = 1, n = points.length; + while (++i < n) { + p0 = p1; + p1 = p2; + p2 = points[i]; + tangents.push([ a * (p2[0] - p0[0]), a * (p2[1] - p0[1]) ]); + } + return tangents; + } + function d3_svg_lineBasis(points) { + if (points.length < 3) return d3_svg_lineLinear(points); + var i = 1, n = points.length, pi = points[0], x0 = pi[0], y0 = pi[1], px = [ x0, x0, x0, (pi = points[1])[0] ], py = [ y0, y0, y0, pi[1] ], path = [ x0, ",", y0 ]; + d3_svg_lineBasisBezier(path, px, py); + while (++i < n) { + pi = points[i]; + px.shift(); + px.push(pi[0]); + py.shift(); + py.push(pi[1]); + d3_svg_lineBasisBezier(path, px, py); + } + i = -1; + while (++i < 2) { + px.shift(); + px.push(pi[0]); + py.shift(); + py.push(pi[1]); + d3_svg_lineBasisBezier(path, px, py); + } + return path.join(""); + } + function d3_svg_lineBasisOpen(points) { + if (points.length < 4) return d3_svg_lineLinear(points); + var path = [], i = -1, n = points.length, pi, px = [ 0 ], py = [ 0 ]; + while (++i < 3) { + pi = points[i]; + px.push(pi[0]); + py.push(pi[1]); + } + path.push(d3_svg_lineDot4(d3_svg_lineBasisBezier3, px) + "," + d3_svg_lineDot4(d3_svg_lineBasisBezier3, py)); + --i; + while (++i < n) { + pi = points[i]; + px.shift(); + px.push(pi[0]); + py.shift(); + py.push(pi[1]); + d3_svg_lineBasisBezier(path, px, py); + } + return path.join(""); + } + function d3_svg_lineBasisClosed(points) { + var path, i = -1, n = points.length, m = n + 4, pi, px = [], py = []; + while (++i < 4) { + pi = points[i % n]; + px.push(pi[0]); + py.push(pi[1]); + } + path = [ d3_svg_lineDot4(d3_svg_lineBasisBezier3, px), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, py) ]; + --i; + while (++i < m) { + pi = points[i % n]; + px.shift(); + px.push(pi[0]); + py.shift(); + py.push(pi[1]); + d3_svg_lineBasisBezier(path, px, py); + } + return path.join(""); + } + function d3_svg_lineBundle(points, tension) { + var n = points.length - 1; + if (n) { + var x0 = points[0][0], y0 = points[0][1], dx = points[n][0] - x0, dy = points[n][1] - y0, i = -1, p, t; + while (++i <= n) { + p = points[i]; + t = i / n; + p[0] = tension * p[0] + (1 - tension) * (x0 + t * dx); + p[1] = tension * p[1] + (1 - tension) * (y0 + t * dy); + } + } + return d3_svg_lineBasis(points); + } + function d3_svg_lineDot4(a, b) { + return a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3]; + } + var d3_svg_lineBasisBezier1 = [ 0, 2 / 3, 1 / 3, 0 ], d3_svg_lineBasisBezier2 = [ 0, 1 / 3, 2 / 3, 0 ], d3_svg_lineBasisBezier3 = [ 0, 1 / 6, 2 / 3, 1 / 6 ]; + function d3_svg_lineBasisBezier(path, x, y) { + path.push("C", d3_svg_lineDot4(d3_svg_lineBasisBezier1, x), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier1, y), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier2, x), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier2, y), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, x), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, y)); + } + function d3_svg_lineSlope(p0, p1) { + return (p1[1] - p0[1]) / (p1[0] - p0[0]); + } + function d3_svg_lineFiniteDifferences(points) { + var i = 0, j = points.length - 1, m = [], p0 = points[0], p1 = points[1], d = m[0] = d3_svg_lineSlope(p0, p1); + while (++i < j) { + m[i] = (d + (d = d3_svg_lineSlope(p0 = p1, p1 = points[i + 1]))) / 2; + } + m[i] = d; + return m; + } + function d3_svg_lineMonotoneTangents(points) { + var tangents = [], d, a, b, s, m = d3_svg_lineFiniteDifferences(points), i = -1, j = points.length - 1; + while (++i < j) { + d = d3_svg_lineSlope(points[i], points[i + 1]); + if (Math.abs(d) < 1e-6) { + m[i] = m[i + 1] = 0; + } else { + a = m[i] / d; + b = m[i + 1] / d; + s = a * a + b * b; + if (s > 9) { + s = d * 3 / Math.sqrt(s); + m[i] = s * a; + m[i + 1] = s * b; + } + } + } + i = -1; + while (++i <= j) { + s = (points[Math.min(j, i + 1)][0] - points[Math.max(0, i - 1)][0]) / (6 * (1 + m[i] * m[i])); + tangents.push([ s || 0, m[i] * s || 0 ]); + } + return tangents; + } + function d3_svg_lineMonotone(points) { + return points.length < 3 ? d3_svg_lineLinear(points) : points[0] + d3_svg_lineHermite(points, d3_svg_lineMonotoneTangents(points)); + } + d3.geom.hull = function(vertices) { + var x = d3_svg_lineX, y = d3_svg_lineY; + if (arguments.length) return hull(vertices); + function hull(data) { + if (data.length < 3) return []; + var fx = d3_functor(x), fy = d3_functor(y), n = data.length, vertices, plen = n - 1, points = [], stack = [], d, i, j, h = 0, x1, y1, x2, y2, u, v, a, sp; + if (fx === d3_svg_lineX && y === d3_svg_lineY) vertices = data; else for (i = 0, + vertices = []; i < n; ++i) { + vertices.push([ +fx.call(this, d = data[i], i), +fy.call(this, d, i) ]); + } + for (i = 1; i < n; ++i) { + if (vertices[i][1] < vertices[h][1]) { + h = i; + } else if (vertices[i][1] == vertices[h][1]) { + h = vertices[i][0] < vertices[h][0] ? i : h; + } + } + for (i = 0; i < n; ++i) { + if (i === h) continue; + y1 = vertices[i][1] - vertices[h][1]; + x1 = vertices[i][0] - vertices[h][0]; + points.push({ + angle: Math.atan2(y1, x1), + index: i + }); + } + points.sort(function(a, b) { + return a.angle - b.angle; + }); + a = points[0].angle; + v = points[0].index; + u = 0; + for (i = 1; i < plen; ++i) { + j = points[i].index; + if (a == points[i].angle) { + x1 = vertices[v][0] - vertices[h][0]; + y1 = vertices[v][1] - vertices[h][1]; + x2 = vertices[j][0] - vertices[h][0]; + y2 = vertices[j][1] - vertices[h][1]; + if (x1 * x1 + y1 * y1 >= x2 * x2 + y2 * y2) { + points[i].index = -1; + } else { + points[u].index = -1; + a = points[i].angle; + u = i; + v = j; + } + } else { + a = points[i].angle; + u = i; + v = j; + } + } + stack.push(h); + for (i = 0, j = 0; i < 2; ++j) { + if (points[j].index !== -1) { + stack.push(points[j].index); + i++; + } + } + sp = stack.length; + for (;j < plen; ++j) { + if (points[j].index === -1) continue; + while (!d3_geom_hullCCW(stack[sp - 2], stack[sp - 1], points[j].index, vertices)) { + --sp; + } + stack[sp++] = points[j].index; + } + var poly = []; + for (i = 0; i < sp; ++i) { + poly.push(data[stack[i]]); + } + return poly; + } + hull.x = function(_) { + return arguments.length ? (x = _, hull) : x; + }; + hull.y = function(_) { + return arguments.length ? (y = _, hull) : y; + }; + return hull; + }; + function d3_geom_hullCCW(i1, i2, i3, v) { + var t, a, b, c, d, e, f; + t = v[i1]; + a = t[0]; + b = t[1]; + t = v[i2]; + c = t[0]; + d = t[1]; + t = v[i3]; + e = t[0]; + f = t[1]; + return (f - b) * (c - a) - (d - b) * (e - a) > 0; + } + d3.geom.polygon = function(coordinates) { + coordinates.area = function() { + var i = 0, n = coordinates.length, area = coordinates[n - 1][1] * coordinates[0][0] - coordinates[n - 1][0] * coordinates[0][1]; + while (++i < n) { + area += coordinates[i - 1][1] * coordinates[i][0] - coordinates[i - 1][0] * coordinates[i][1]; + } + return area * .5; + }; + coordinates.centroid = function(k) { + var i = -1, n = coordinates.length, x = 0, y = 0, a, b = coordinates[n - 1], c; + if (!arguments.length) k = -1 / (6 * coordinates.area()); + while (++i < n) { + a = b; + b = coordinates[i]; + c = a[0] * b[1] - b[0] * a[1]; + x += (a[0] + b[0]) * c; + y += (a[1] + b[1]) * c; + } + return [ x * k, y * k ]; + }; + coordinates.clip = function(subject) { + var input, i = -1, n = coordinates.length, j, m, a = coordinates[n - 1], b, c, d; + while (++i < n) { + input = subject.slice(); + subject.length = 0; + b = coordinates[i]; + c = input[(m = input.length) - 1]; + j = -1; + while (++j < m) { + d = input[j]; + if (d3_geom_polygonInside(d, a, b)) { + if (!d3_geom_polygonInside(c, a, b)) { + subject.push(d3_geom_polygonIntersect(c, d, a, b)); + } + subject.push(d); + } else if (d3_geom_polygonInside(c, a, b)) { + subject.push(d3_geom_polygonIntersect(c, d, a, b)); + } + c = d; + } + a = b; + } + return subject; + }; + return coordinates; + }; + function d3_geom_polygonInside(p, a, b) { + return (b[0] - a[0]) * (p[1] - a[1]) < (b[1] - a[1]) * (p[0] - a[0]); + } + function d3_geom_polygonIntersect(c, d, a, b) { + var x1 = c[0], x3 = a[0], x21 = d[0] - x1, x43 = b[0] - x3, y1 = c[1], y3 = a[1], y21 = d[1] - y1, y43 = b[1] - y3, ua = (x43 * (y1 - y3) - y43 * (x1 - x3)) / (y43 * x21 - x43 * y21); + return [ x1 + ua * x21, y1 + ua * y21 ]; + } + d3.geom.delaunay = function(vertices) { + var edges = vertices.map(function() { + return []; + }), triangles = []; + d3_geom_voronoiTessellate(vertices, function(e) { + edges[e.region.l.index].push(vertices[e.region.r.index]); + }); + edges.forEach(function(edge, i) { + var v = vertices[i], cx = v[0], cy = v[1]; + edge.forEach(function(v) { + v.angle = Math.atan2(v[0] - cx, v[1] - cy); + }); + edge.sort(function(a, b) { + return a.angle - b.angle; + }); + for (var j = 0, m = edge.length - 1; j < m; j++) { + triangles.push([ v, edge[j], edge[j + 1] ]); + } + }); + return triangles; + }; + d3.geom.voronoi = function(points) { + var size = null, x = d3_svg_lineX, y = d3_svg_lineY, clip; + if (arguments.length) return voronoi(points); + function voronoi(data) { + var points, polygons = data.map(function() { + return []; + }), fx = d3_functor(x), fy = d3_functor(y), d, i, n = data.length, Z = 1e6; + if (fx === d3_svg_lineX && fy === d3_svg_lineY) points = data; else for (points = [], + i = 0; i < n; ++i) { + points.push([ +fx.call(this, d = data[i], i), +fy.call(this, d, i) ]); + } + d3_geom_voronoiTessellate(points, function(e) { + var s1, s2, x1, x2, y1, y2; + if (e.a === 1 && e.b >= 0) { + s1 = e.ep.r; + s2 = e.ep.l; + } else { + s1 = e.ep.l; + s2 = e.ep.r; + } + if (e.a === 1) { + y1 = s1 ? s1.y : -Z; + x1 = e.c - e.b * y1; + y2 = s2 ? s2.y : Z; + x2 = e.c - e.b * y2; + } else { + x1 = s1 ? s1.x : -Z; + y1 = e.c - e.a * x1; + x2 = s2 ? s2.x : Z; + y2 = e.c - e.a * x2; + } + var v1 = [ x1, y1 ], v2 = [ x2, y2 ]; + polygons[e.region.l.index].push(v1, v2); + polygons[e.region.r.index].push(v1, v2); + }); + polygons = polygons.map(function(polygon, i) { + var cx = points[i][0], cy = points[i][1], angle = polygon.map(function(v) { + return Math.atan2(v[0] - cx, v[1] - cy); + }), order = d3.range(polygon.length).sort(function(a, b) { + return angle[a] - angle[b]; + }); + return order.filter(function(d, i) { + return !i || angle[d] - angle[order[i - 1]] > ε; + }).map(function(d) { + return polygon[d]; + }); + }); + polygons.forEach(function(polygon, i) { + var n = polygon.length; + if (!n) return polygon.push([ -Z, -Z ], [ -Z, Z ], [ Z, Z ], [ Z, -Z ]); + if (n > 2) return; + var p0 = points[i], p1 = polygon[0], p2 = polygon[1], x0 = p0[0], y0 = p0[1], x1 = p1[0], y1 = p1[1], x2 = p2[0], y2 = p2[1], dx = Math.abs(x2 - x1), dy = y2 - y1; + if (Math.abs(dy) < ε) { + var y = y0 < y1 ? -Z : Z; + polygon.push([ -Z, y ], [ Z, y ]); + } else if (dx < ε) { + var x = x0 < x1 ? -Z : Z; + polygon.push([ x, -Z ], [ x, Z ]); + } else { + var y = (x2 - x1) * (y1 - y0) < (x1 - x0) * (y2 - y1) ? Z : -Z, z = Math.abs(dy) - dx; + if (Math.abs(z) < ε) { + polygon.push([ dy < 0 ? y : -y, y ]); + } else { + if (z > 0) y *= -1; + polygon.push([ -Z, y ], [ Z, y ]); + } + } + }); + if (clip) for (i = 0; i < n; ++i) clip(polygons[i]); + for (i = 0; i < n; ++i) polygons[i].point = data[i]; + return polygons; + } + voronoi.x = function(_) { + return arguments.length ? (x = _, voronoi) : x; + }; + voronoi.y = function(_) { + return arguments.length ? (y = _, voronoi) : y; + }; + voronoi.size = function(_) { + if (!arguments.length) return size; + if (_ == null) { + clip = null; + } else { + size = [ +_[0], +_[1] ]; + clip = d3.geom.polygon([ [ 0, 0 ], [ 0, size[1] ], size, [ size[0], 0 ] ]).clip; + } + return voronoi; + }; + voronoi.links = function(data) { + var points, graph = data.map(function() { + return []; + }), links = [], fx = d3_functor(x), fy = d3_functor(y), d, i, n = data.length; + if (fx === d3_svg_lineX && fy === d3_svg_lineY) points = data; else for (i = 0; i < n; ++i) { + points.push([ +fx.call(this, d = data[i], i), +fy.call(this, d, i) ]); + } + d3_geom_voronoiTessellate(points, function(e) { + var l = e.region.l.index, r = e.region.r.index; + if (graph[l][r]) return; + graph[l][r] = graph[r][l] = true; + links.push({ + source: data[l], + target: data[r] + }); + }); + return links; + }; + voronoi.triangles = function(data) { + if (x === d3_svg_lineX && y === d3_svg_lineY) return d3.geom.delaunay(data); + var points, point, fx = d3_functor(x), fy = d3_functor(y), d, i, n; + for (i = 0, points = [], n = data.length; i < n; ++i) { + point = [ +fx.call(this, d = data[i], i), +fy.call(this, d, i) ]; + point.data = d; + points.push(point); + } + return d3.geom.delaunay(points).map(function(triangle) { + return triangle.map(function(point) { + return point.data; + }); + }); + }; + return voronoi; + }; + var d3_geom_voronoiOpposite = { + l: "r", + r: "l" + }; + function d3_geom_voronoiTessellate(points, callback) { + var Sites = { + list: points.map(function(v, i) { + return { + index: i, + x: v[0], + y: v[1] + }; + }).sort(function(a, b) { + return a.y < b.y ? -1 : a.y > b.y ? 1 : a.x < b.x ? -1 : a.x > b.x ? 1 : 0; + }), + bottomSite: null + }; + var EdgeList = { + list: [], + leftEnd: null, + rightEnd: null, + init: function() { + EdgeList.leftEnd = EdgeList.createHalfEdge(null, "l"); + EdgeList.rightEnd = EdgeList.createHalfEdge(null, "l"); + EdgeList.leftEnd.r = EdgeList.rightEnd; + EdgeList.rightEnd.l = EdgeList.leftEnd; + EdgeList.list.unshift(EdgeList.leftEnd, EdgeList.rightEnd); + }, + createHalfEdge: function(edge, side) { + return { + edge: edge, + side: side, + vertex: null, + l: null, + r: null + }; + }, + insert: function(lb, he) { + he.l = lb; + he.r = lb.r; + lb.r.l = he; + lb.r = he; + }, + leftBound: function(p) { + var he = EdgeList.leftEnd; + do { + he = he.r; + } while (he != EdgeList.rightEnd && Geom.rightOf(he, p)); + he = he.l; + return he; + }, + del: function(he) { + he.l.r = he.r; + he.r.l = he.l; + he.edge = null; + }, + right: function(he) { + return he.r; + }, + left: function(he) { + return he.l; + }, + leftRegion: function(he) { + return he.edge == null ? Sites.bottomSite : he.edge.region[he.side]; + }, + rightRegion: function(he) { + return he.edge == null ? Sites.bottomSite : he.edge.region[d3_geom_voronoiOpposite[he.side]]; + } + }; + var Geom = { + bisect: function(s1, s2) { + var newEdge = { + region: { + l: s1, + r: s2 + }, + ep: { + l: null, + r: null + } + }; + var dx = s2.x - s1.x, dy = s2.y - s1.y, adx = dx > 0 ? dx : -dx, ady = dy > 0 ? dy : -dy; + newEdge.c = s1.x * dx + s1.y * dy + (dx * dx + dy * dy) * .5; + if (adx > ady) { + newEdge.a = 1; + newEdge.b = dy / dx; + newEdge.c /= dx; + } else { + newEdge.b = 1; + newEdge.a = dx / dy; + newEdge.c /= dy; + } + return newEdge; + }, + intersect: function(el1, el2) { + var e1 = el1.edge, e2 = el2.edge; + if (!e1 || !e2 || e1.region.r == e2.region.r) { + return null; + } + var d = e1.a * e2.b - e1.b * e2.a; + if (Math.abs(d) < 1e-10) { + return null; + } + var xint = (e1.c * e2.b - e2.c * e1.b) / d, yint = (e2.c * e1.a - e1.c * e2.a) / d, e1r = e1.region.r, e2r = e2.region.r, el, e; + if (e1r.y < e2r.y || e1r.y == e2r.y && e1r.x < e2r.x) { + el = el1; + e = e1; + } else { + el = el2; + e = e2; + } + var rightOfSite = xint >= e.region.r.x; + if (rightOfSite && el.side === "l" || !rightOfSite && el.side === "r") { + return null; + } + return { + x: xint, + y: yint + }; + }, + rightOf: function(he, p) { + var e = he.edge, topsite = e.region.r, rightOfSite = p.x > topsite.x; + if (rightOfSite && he.side === "l") { + return 1; + } + if (!rightOfSite && he.side === "r") { + return 0; + } + if (e.a === 1) { + var dyp = p.y - topsite.y, dxp = p.x - topsite.x, fast = 0, above = 0; + if (!rightOfSite && e.b < 0 || rightOfSite && e.b >= 0) { + above = fast = dyp >= e.b * dxp; + } else { + above = p.x + p.y * e.b > e.c; + if (e.b < 0) { + above = !above; + } + if (!above) { + fast = 1; + } + } + if (!fast) { + var dxs = topsite.x - e.region.l.x; + above = e.b * (dxp * dxp - dyp * dyp) < dxs * dyp * (1 + 2 * dxp / dxs + e.b * e.b); + if (e.b < 0) { + above = !above; + } + } + } else { + var yl = e.c - e.a * p.x, t1 = p.y - yl, t2 = p.x - topsite.x, t3 = yl - topsite.y; + above = t1 * t1 > t2 * t2 + t3 * t3; + } + return he.side === "l" ? above : !above; + }, + endPoint: function(edge, side, site) { + edge.ep[side] = site; + if (!edge.ep[d3_geom_voronoiOpposite[side]]) return; + callback(edge); + }, + distance: function(s, t) { + var dx = s.x - t.x, dy = s.y - t.y; + return Math.sqrt(dx * dx + dy * dy); + } + }; + var EventQueue = { + list: [], + insert: function(he, site, offset) { + he.vertex = site; + he.ystar = site.y + offset; + for (var i = 0, list = EventQueue.list, l = list.length; i < l; i++) { + var next = list[i]; + if (he.ystar > next.ystar || he.ystar == next.ystar && site.x > next.vertex.x) { + continue; + } else { + break; + } + } + list.splice(i, 0, he); + }, + del: function(he) { + for (var i = 0, ls = EventQueue.list, l = ls.length; i < l && ls[i] != he; ++i) {} + ls.splice(i, 1); + }, + empty: function() { + return EventQueue.list.length === 0; + }, + nextEvent: function(he) { + for (var i = 0, ls = EventQueue.list, l = ls.length; i < l; ++i) { + if (ls[i] == he) return ls[i + 1]; + } + return null; + }, + min: function() { + var elem = EventQueue.list[0]; + return { + x: elem.vertex.x, + y: elem.ystar + }; + }, + extractMin: function() { + return EventQueue.list.shift(); + } + }; + EdgeList.init(); + Sites.bottomSite = Sites.list.shift(); + var newSite = Sites.list.shift(), newIntStar; + var lbnd, rbnd, llbnd, rrbnd, bisector; + var bot, top, temp, p, v; + var e, pm; + while (true) { + if (!EventQueue.empty()) { + newIntStar = EventQueue.min(); + } + if (newSite && (EventQueue.empty() || newSite.y < newIntStar.y || newSite.y == newIntStar.y && newSite.x < newIntStar.x)) { + lbnd = EdgeList.leftBound(newSite); + rbnd = EdgeList.right(lbnd); + bot = EdgeList.rightRegion(lbnd); + e = Geom.bisect(bot, newSite); + bisector = EdgeList.createHalfEdge(e, "l"); + EdgeList.insert(lbnd, bisector); + p = Geom.intersect(lbnd, bisector); + if (p) { + EventQueue.del(lbnd); + EventQueue.insert(lbnd, p, Geom.distance(p, newSite)); + } + lbnd = bisector; + bisector = EdgeList.createHalfEdge(e, "r"); + EdgeList.insert(lbnd, bisector); + p = Geom.intersect(bisector, rbnd); + if (p) { + EventQueue.insert(bisector, p, Geom.distance(p, newSite)); + } + newSite = Sites.list.shift(); + } else if (!EventQueue.empty()) { + lbnd = EventQueue.extractMin(); + llbnd = EdgeList.left(lbnd); + rbnd = EdgeList.right(lbnd); + rrbnd = EdgeList.right(rbnd); + bot = EdgeList.leftRegion(lbnd); + top = EdgeList.rightRegion(rbnd); + v = lbnd.vertex; + Geom.endPoint(lbnd.edge, lbnd.side, v); + Geom.endPoint(rbnd.edge, rbnd.side, v); + EdgeList.del(lbnd); + EventQueue.del(rbnd); + EdgeList.del(rbnd); + pm = "l"; + if (bot.y > top.y) { + temp = bot; + bot = top; + top = temp; + pm = "r"; + } + e = Geom.bisect(bot, top); + bisector = EdgeList.createHalfEdge(e, pm); + EdgeList.insert(llbnd, bisector); + Geom.endPoint(e, d3_geom_voronoiOpposite[pm], v); + p = Geom.intersect(llbnd, bisector); + if (p) { + EventQueue.del(llbnd); + EventQueue.insert(llbnd, p, Geom.distance(p, bot)); + } + p = Geom.intersect(bisector, rrbnd); + if (p) { + EventQueue.insert(bisector, p, Geom.distance(p, bot)); + } + } else { + break; + } + } + for (lbnd = EdgeList.right(EdgeList.leftEnd); lbnd != EdgeList.rightEnd; lbnd = EdgeList.right(lbnd)) { + callback(lbnd.edge); + } + } + d3.geom.quadtree = function(points, x1, y1, x2, y2) { + var x = d3_svg_lineX, y = d3_svg_lineY, compat; + if (compat = arguments.length) { + x = d3_geom_quadtreeCompatX; + y = d3_geom_quadtreeCompatY; + if (compat === 3) { + y2 = y1; + x2 = x1; + y1 = x1 = 0; + } + return quadtree(points); + } + function quadtree(data) { + var d, fx = d3_functor(x), fy = d3_functor(y), xs, ys, i, n, x1_, y1_, x2_, y2_; + if (x1 != null) { + x1_ = x1, y1_ = y1, x2_ = x2, y2_ = y2; + } else { + x2_ = y2_ = -(x1_ = y1_ = Infinity); + xs = [], ys = []; + n = data.length; + if (compat) for (i = 0; i < n; ++i) { + d = data[i]; + if (d.x < x1_) x1_ = d.x; + if (d.y < y1_) y1_ = d.y; + if (d.x > x2_) x2_ = d.x; + if (d.y > y2_) y2_ = d.y; + xs.push(d.x); + ys.push(d.y); + } else for (i = 0; i < n; ++i) { + var x_ = +fx(d = data[i], i), y_ = +fy(d, i); + if (x_ < x1_) x1_ = x_; + if (y_ < y1_) y1_ = y_; + if (x_ > x2_) x2_ = x_; + if (y_ > y2_) y2_ = y_; + xs.push(x_); + ys.push(y_); + } + } + var dx = x2_ - x1_, dy = y2_ - y1_; + if (dx > dy) y2_ = y1_ + dx; else x2_ = x1_ + dy; + function insert(n, d, x, y, x1, y1, x2, y2) { + if (isNaN(x) || isNaN(y)) return; + if (n.leaf) { + var nx = n.x, ny = n.y; + if (nx != null) { + if (Math.abs(nx - x) + Math.abs(ny - y) < .01) { + insertChild(n, d, x, y, x1, y1, x2, y2); + } else { + var nPoint = n.point; + n.x = n.y = n.point = null; + insertChild(n, nPoint, nx, ny, x1, y1, x2, y2); + insertChild(n, d, x, y, x1, y1, x2, y2); + } + } else { + n.x = x, n.y = y, n.point = d; + } + } else { + insertChild(n, d, x, y, x1, y1, x2, y2); + } + } + function insertChild(n, d, x, y, x1, y1, x2, y2) { + var sx = (x1 + x2) * .5, sy = (y1 + y2) * .5, right = x >= sx, bottom = y >= sy, i = (bottom << 1) + right; + n.leaf = false; + n = n.nodes[i] || (n.nodes[i] = d3_geom_quadtreeNode()); + if (right) x1 = sx; else x2 = sx; + if (bottom) y1 = sy; else y2 = sy; + insert(n, d, x, y, x1, y1, x2, y2); + } + var root = d3_geom_quadtreeNode(); + root.add = function(d) { + insert(root, d, +fx(d, ++i), +fy(d, i), x1_, y1_, x2_, y2_); + }; + root.visit = function(f) { + d3_geom_quadtreeVisit(f, root, x1_, y1_, x2_, y2_); + }; + i = -1; + if (x1 == null) { + while (++i < n) { + insert(root, data[i], xs[i], ys[i], x1_, y1_, x2_, y2_); + } + --i; + } else data.forEach(root.add); + xs = ys = data = d = null; + return root; + } + quadtree.x = function(_) { + return arguments.length ? (x = _, quadtree) : x; + }; + quadtree.y = function(_) { + return arguments.length ? (y = _, quadtree) : y; + }; + quadtree.size = function(_) { + if (!arguments.length) return x1 == null ? null : [ x2, y2 ]; + if (_ == null) { + x1 = y1 = x2 = y2 = null; + } else { + x1 = y1 = 0; + x2 = +_[0], y2 = +_[1]; + } + return quadtree; + }; + return quadtree; + }; + function d3_geom_quadtreeCompatX(d) { + return d.x; + } + function d3_geom_quadtreeCompatY(d) { + return d.y; + } + function d3_geom_quadtreeNode() { + return { + leaf: true, + nodes: [], + point: null, + x: null, + y: null + }; + } + function d3_geom_quadtreeVisit(f, node, x1, y1, x2, y2) { + if (!f(node, x1, y1, x2, y2)) { + var sx = (x1 + x2) * .5, sy = (y1 + y2) * .5, children = node.nodes; + if (children[0]) d3_geom_quadtreeVisit(f, children[0], x1, y1, sx, sy); + if (children[1]) d3_geom_quadtreeVisit(f, children[1], sx, y1, x2, sy); + if (children[2]) d3_geom_quadtreeVisit(f, children[2], x1, sy, sx, y2); + if (children[3]) d3_geom_quadtreeVisit(f, children[3], sx, sy, x2, y2); + } + } + d3.interpolateRgb = d3_interpolateRgb; + function d3_interpolateRgb(a, b) { + a = d3.rgb(a); + b = d3.rgb(b); + var ar = a.r, ag = a.g, ab = a.b, br = b.r - ar, bg = b.g - ag, bb = b.b - ab; + return function(t) { + return "#" + d3_rgb_hex(Math.round(ar + br * t)) + d3_rgb_hex(Math.round(ag + bg * t)) + d3_rgb_hex(Math.round(ab + bb * t)); + }; + } + d3.transform = function(string) { + var g = d3_document.createElementNS(d3.ns.prefix.svg, "g"); + return (d3.transform = function(string) { + g.setAttribute("transform", string); + var t = g.transform.baseVal.consolidate(); + return new d3_transform(t ? t.matrix : d3_transformIdentity); + })(string); + }; + function d3_transform(m) { + var r0 = [ m.a, m.b ], r1 = [ m.c, m.d ], kx = d3_transformNormalize(r0), kz = d3_transformDot(r0, r1), ky = d3_transformNormalize(d3_transformCombine(r1, r0, -kz)) || 0; + if (r0[0] * r1[1] < r1[0] * r0[1]) { + r0[0] *= -1; + r0[1] *= -1; + kx *= -1; + kz *= -1; + } + this.rotate = (kx ? Math.atan2(r0[1], r0[0]) : Math.atan2(-r1[0], r1[1])) * d3_degrees; + this.translate = [ m.e, m.f ]; + this.scale = [ kx, ky ]; + this.skew = ky ? Math.atan2(kz, ky) * d3_degrees : 0; + } + d3_transform.prototype.toString = function() { + return "translate(" + this.translate + ")rotate(" + this.rotate + ")skewX(" + this.skew + ")scale(" + this.scale + ")"; + }; + function d3_transformDot(a, b) { + return a[0] * b[0] + a[1] * b[1]; + } + function d3_transformNormalize(a) { + var k = Math.sqrt(d3_transformDot(a, a)); + if (k) { + a[0] /= k; + a[1] /= k; + } + return k; + } + function d3_transformCombine(a, b, k) { + a[0] += k * b[0]; + a[1] += k * b[1]; + return a; + } + var d3_transformIdentity = { + a: 1, + b: 0, + c: 0, + d: 1, + e: 0, + f: 0 + }; + d3.interpolateNumber = d3_interpolateNumber; + function d3_interpolateNumber(a, b) { + b -= a = +a; + return function(t) { + return a + b * t; + }; + } + d3.interpolateTransform = d3_interpolateTransform; + function d3_interpolateTransform(a, b) { + var s = [], q = [], n, A = d3.transform(a), B = d3.transform(b), ta = A.translate, tb = B.translate, ra = A.rotate, rb = B.rotate, wa = A.skew, wb = B.skew, ka = A.scale, kb = B.scale; + if (ta[0] != tb[0] || ta[1] != tb[1]) { + s.push("translate(", null, ",", null, ")"); + q.push({ + i: 1, + x: d3_interpolateNumber(ta[0], tb[0]) + }, { + i: 3, + x: d3_interpolateNumber(ta[1], tb[1]) + }); + } else if (tb[0] || tb[1]) { + s.push("translate(" + tb + ")"); + } else { + s.push(""); + } + if (ra != rb) { + if (ra - rb > 180) rb += 360; else if (rb - ra > 180) ra += 360; + q.push({ + i: s.push(s.pop() + "rotate(", null, ")") - 2, + x: d3_interpolateNumber(ra, rb) + }); + } else if (rb) { + s.push(s.pop() + "rotate(" + rb + ")"); + } + if (wa != wb) { + q.push({ + i: s.push(s.pop() + "skewX(", null, ")") - 2, + x: d3_interpolateNumber(wa, wb) + }); + } else if (wb) { + s.push(s.pop() + "skewX(" + wb + ")"); + } + if (ka[0] != kb[0] || ka[1] != kb[1]) { + n = s.push(s.pop() + "scale(", null, ",", null, ")"); + q.push({ + i: n - 4, + x: d3_interpolateNumber(ka[0], kb[0]) + }, { + i: n - 2, + x: d3_interpolateNumber(ka[1], kb[1]) + }); + } else if (kb[0] != 1 || kb[1] != 1) { + s.push(s.pop() + "scale(" + kb + ")"); + } + n = q.length; + return function(t) { + var i = -1, o; + while (++i < n) s[(o = q[i]).i] = o.x(t); + return s.join(""); + }; + } + d3.interpolateObject = d3_interpolateObject; + function d3_interpolateObject(a, b) { + var i = {}, c = {}, k; + for (k in a) { + if (k in b) { + i[k] = d3_interpolateByName(k)(a[k], b[k]); + } else { + c[k] = a[k]; + } + } + for (k in b) { + if (!(k in a)) { + c[k] = b[k]; + } + } + return function(t) { + for (k in i) c[k] = i[k](t); + return c; + }; + } + d3.interpolateString = d3_interpolateString; + function d3_interpolateString(a, b) { + var m, i, j, s0 = 0, s1 = 0, s = [], q = [], n, o; + a = a + "", b = b + ""; + d3_interpolate_number.lastIndex = 0; + for (i = 0; m = d3_interpolate_number.exec(b); ++i) { + if (m.index) s.push(b.substring(s0, s1 = m.index)); + q.push({ + i: s.length, + x: m[0] + }); + s.push(null); + s0 = d3_interpolate_number.lastIndex; + } + if (s0 < b.length) s.push(b.substring(s0)); + for (i = 0, n = q.length; (m = d3_interpolate_number.exec(a)) && i < n; ++i) { + o = q[i]; + if (o.x == m[0]) { + if (o.i) { + if (s[o.i + 1] == null) { + s[o.i - 1] += o.x; + s.splice(o.i, 1); + for (j = i + 1; j < n; ++j) q[j].i--; + } else { + s[o.i - 1] += o.x + s[o.i + 1]; + s.splice(o.i, 2); + for (j = i + 1; j < n; ++j) q[j].i -= 2; + } + } else { + if (s[o.i + 1] == null) { + s[o.i] = o.x; + } else { + s[o.i] = o.x + s[o.i + 1]; + s.splice(o.i + 1, 1); + for (j = i + 1; j < n; ++j) q[j].i--; + } + } + q.splice(i, 1); + n--; + i--; + } else { + o.x = d3_interpolateNumber(parseFloat(m[0]), parseFloat(o.x)); + } + } + while (i < n) { + o = q.pop(); + if (s[o.i + 1] == null) { + s[o.i] = o.x; + } else { + s[o.i] = o.x + s[o.i + 1]; + s.splice(o.i + 1, 1); + } + n--; + } + if (s.length === 1) { + return s[0] == null ? q[0].x : function() { + return b; + }; + } + return function(t) { + for (i = 0; i < n; ++i) s[(o = q[i]).i] = o.x(t); + return s.join(""); + }; + } + var d3_interpolate_number = /[-+]?(?:\d+\.?\d*|\.?\d+)(?:[eE][-+]?\d+)?/g; + d3.interpolate = d3_interpolate; + function d3_interpolate(a, b) { + var i = d3.interpolators.length, f; + while (--i >= 0 && !(f = d3.interpolators[i](a, b))) ; + return f; + } + function d3_interpolateByName(name) { + return name == "transform" ? d3_interpolateTransform : d3_interpolate; + } + d3.interpolators = [ function(a, b) { + var t = typeof b; + return (t === "string" || t !== typeof a ? d3_rgb_names.has(b) || /^(#|rgb\(|hsl\()/.test(b) ? d3_interpolateRgb : d3_interpolateString : b instanceof d3_Color ? d3_interpolateRgb : t === "object" ? Array.isArray(b) ? d3_interpolateArray : d3_interpolateObject : d3_interpolateNumber)(a, b); + } ]; + d3.interpolateArray = d3_interpolateArray; + function d3_interpolateArray(a, b) { + var x = [], c = [], na = a.length, nb = b.length, n0 = Math.min(a.length, b.length), i; + for (i = 0; i < n0; ++i) x.push(d3_interpolate(a[i], b[i])); + for (;i < na; ++i) c[i] = a[i]; + for (;i < nb; ++i) c[i] = b[i]; + return function(t) { + for (i = 0; i < n0; ++i) c[i] = x[i](t); + return c; + }; + } + var d3_ease_default = function() { + return d3_identity; + }; + var d3_ease = d3.map({ + linear: d3_ease_default, + poly: d3_ease_poly, + quad: function() { + return d3_ease_quad; + }, + cubic: function() { + return d3_ease_cubic; + }, + sin: function() { + return d3_ease_sin; + }, + exp: function() { + return d3_ease_exp; + }, + circle: function() { + return d3_ease_circle; + }, + elastic: d3_ease_elastic, + back: d3_ease_back, + bounce: function() { + return d3_ease_bounce; + } + }); + var d3_ease_mode = d3.map({ + "in": d3_identity, + out: d3_ease_reverse, + "in-out": d3_ease_reflect, + "out-in": function(f) { + return d3_ease_reflect(d3_ease_reverse(f)); + } + }); + d3.ease = function(name) { + var i = name.indexOf("-"), t = i >= 0 ? name.substring(0, i) : name, m = i >= 0 ? name.substring(i + 1) : "in"; + t = d3_ease.get(t) || d3_ease_default; + m = d3_ease_mode.get(m) || d3_identity; + return d3_ease_clamp(m(t.apply(null, Array.prototype.slice.call(arguments, 1)))); + }; + function d3_ease_clamp(f) { + return function(t) { + return t <= 0 ? 0 : t >= 1 ? 1 : f(t); + }; + } + function d3_ease_reverse(f) { + return function(t) { + return 1 - f(1 - t); + }; + } + function d3_ease_reflect(f) { + return function(t) { + return .5 * (t < .5 ? f(2 * t) : 2 - f(2 - 2 * t)); + }; + } + function d3_ease_quad(t) { + return t * t; + } + function d3_ease_cubic(t) { + return t * t * t; + } + function d3_ease_cubicInOut(t) { + if (t <= 0) return 0; + if (t >= 1) return 1; + var t2 = t * t, t3 = t2 * t; + return 4 * (t < .5 ? t3 : 3 * (t - t2) + t3 - .75); + } + function d3_ease_poly(e) { + return function(t) { + return Math.pow(t, e); + }; + } + function d3_ease_sin(t) { + return 1 - Math.cos(t * π / 2); + } + function d3_ease_exp(t) { + return Math.pow(2, 10 * (t - 1)); + } + function d3_ease_circle(t) { + return 1 - Math.sqrt(1 - t * t); + } + function d3_ease_elastic(a, p) { + var s; + if (arguments.length < 2) p = .45; + if (arguments.length) s = p / (2 * π) * Math.asin(1 / a); else a = 1, s = p / 4; + return function(t) { + return 1 + a * Math.pow(2, 10 * -t) * Math.sin((t - s) * 2 * π / p); + }; + } + function d3_ease_back(s) { + if (!s) s = 1.70158; + return function(t) { + return t * t * ((s + 1) * t - s); + }; + } + function d3_ease_bounce(t) { + return t < 1 / 2.75 ? 7.5625 * t * t : t < 2 / 2.75 ? 7.5625 * (t -= 1.5 / 2.75) * t + .75 : t < 2.5 / 2.75 ? 7.5625 * (t -= 2.25 / 2.75) * t + .9375 : 7.5625 * (t -= 2.625 / 2.75) * t + .984375; + } + d3.interpolateHcl = d3_interpolateHcl; + function d3_interpolateHcl(a, b) { + a = d3.hcl(a); + b = d3.hcl(b); + var ah = a.h, ac = a.c, al = a.l, bh = b.h - ah, bc = b.c - ac, bl = b.l - al; + if (bh > 180) bh -= 360; else if (bh < -180) bh += 360; + return function(t) { + return d3_hcl_lab(ah + bh * t, ac + bc * t, al + bl * t) + ""; + }; + } + d3.interpolateHsl = d3_interpolateHsl; + function d3_interpolateHsl(a, b) { + a = d3.hsl(a); + b = d3.hsl(b); + var h0 = a.h, s0 = a.s, l0 = a.l, h1 = b.h - h0, s1 = b.s - s0, l1 = b.l - l0; + if (h1 > 180) h1 -= 360; else if (h1 < -180) h1 += 360; + return function(t) { + return d3_hsl_rgb(h0 + h1 * t, s0 + s1 * t, l0 + l1 * t) + ""; + }; + } + d3.interpolateLab = d3_interpolateLab; + function d3_interpolateLab(a, b) { + a = d3.lab(a); + b = d3.lab(b); + var al = a.l, aa = a.a, ab = a.b, bl = b.l - al, ba = b.a - aa, bb = b.b - ab; + return function(t) { + return d3_lab_rgb(al + bl * t, aa + ba * t, ab + bb * t) + ""; + }; + } + d3.interpolateRound = d3_interpolateRound; + function d3_interpolateRound(a, b) { + b -= a; + return function(t) { + return Math.round(a + b * t); + }; + } + function d3_uninterpolateNumber(a, b) { + b = b - (a = +a) ? 1 / (b - a) : 0; + return function(x) { + return (x - a) * b; + }; + } + function d3_uninterpolateClamp(a, b) { + b = b - (a = +a) ? 1 / (b - a) : 0; + return function(x) { + return Math.max(0, Math.min(1, (x - a) * b)); + }; + } + d3.layout = {}; + d3.layout.bundle = function() { + return function(links) { + var paths = [], i = -1, n = links.length; + while (++i < n) paths.push(d3_layout_bundlePath(links[i])); + return paths; + }; + }; + function d3_layout_bundlePath(link) { + var start = link.source, end = link.target, lca = d3_layout_bundleLeastCommonAncestor(start, end), points = [ start ]; + while (start !== lca) { + start = start.parent; + points.push(start); + } + var k = points.length; + while (end !== lca) { + points.splice(k, 0, end); + end = end.parent; + } + return points; + } + function d3_layout_bundleAncestors(node) { + var ancestors = [], parent = node.parent; + while (parent != null) { + ancestors.push(node); + node = parent; + parent = parent.parent; + } + ancestors.push(node); + return ancestors; + } + function d3_layout_bundleLeastCommonAncestor(a, b) { + if (a === b) return a; + var aNodes = d3_layout_bundleAncestors(a), bNodes = d3_layout_bundleAncestors(b), aNode = aNodes.pop(), bNode = bNodes.pop(), sharedNode = null; + while (aNode === bNode) { + sharedNode = aNode; + aNode = aNodes.pop(); + bNode = bNodes.pop(); + } + return sharedNode; + } + d3.layout.chord = function() { + var chord = {}, chords, groups, matrix, n, padding = 0, sortGroups, sortSubgroups, sortChords; + function relayout() { + var subgroups = {}, groupSums = [], groupIndex = d3.range(n), subgroupIndex = [], k, x, x0, i, j; + chords = []; + groups = []; + k = 0, i = -1; + while (++i < n) { + x = 0, j = -1; + while (++j < n) { + x += matrix[i][j]; + } + groupSums.push(x); + subgroupIndex.push(d3.range(n)); + k += x; + } + if (sortGroups) { + groupIndex.sort(function(a, b) { + return sortGroups(groupSums[a], groupSums[b]); + }); + } + if (sortSubgroups) { + subgroupIndex.forEach(function(d, i) { + d.sort(function(a, b) { + return sortSubgroups(matrix[i][a], matrix[i][b]); + }); + }); + } + k = (2 * π - padding * n) / k; + x = 0, i = -1; + while (++i < n) { + x0 = x, j = -1; + while (++j < n) { + var di = groupIndex[i], dj = subgroupIndex[di][j], v = matrix[di][dj], a0 = x, a1 = x += v * k; + subgroups[di + "-" + dj] = { + index: di, + subindex: dj, + startAngle: a0, + endAngle: a1, + value: v + }; + } + groups[di] = { + index: di, + startAngle: x0, + endAngle: x, + value: (x - x0) / k + }; + x += padding; + } + i = -1; + while (++i < n) { + j = i - 1; + while (++j < n) { + var source = subgroups[i + "-" + j], target = subgroups[j + "-" + i]; + if (source.value || target.value) { + chords.push(source.value < target.value ? { + source: target, + target: source + } : { + source: source, + target: target + }); + } + } + } + if (sortChords) resort(); + } + function resort() { + chords.sort(function(a, b) { + return sortChords((a.source.value + a.target.value) / 2, (b.source.value + b.target.value) / 2); + }); + } + chord.matrix = function(x) { + if (!arguments.length) return matrix; + n = (matrix = x) && matrix.length; + chords = groups = null; + return chord; + }; + chord.padding = function(x) { + if (!arguments.length) return padding; + padding = x; + chords = groups = null; + return chord; + }; + chord.sortGroups = function(x) { + if (!arguments.length) return sortGroups; + sortGroups = x; + chords = groups = null; + return chord; + }; + chord.sortSubgroups = function(x) { + if (!arguments.length) return sortSubgroups; + sortSubgroups = x; + chords = null; + return chord; + }; + chord.sortChords = function(x) { + if (!arguments.length) return sortChords; + sortChords = x; + if (chords) resort(); + return chord; + }; + chord.chords = function() { + if (!chords) relayout(); + return chords; + }; + chord.groups = function() { + if (!groups) relayout(); + return groups; + }; + return chord; + }; + d3.layout.force = function() { + var force = {}, event = d3.dispatch("start", "tick", "end"), size = [ 1, 1 ], drag, alpha, friction = .9, linkDistance = d3_layout_forceLinkDistance, linkStrength = d3_layout_forceLinkStrength, charge = -30, gravity = .1, theta = .8, nodes = [], links = [], distances, strengths, charges; + function repulse(node) { + return function(quad, x1, _, x2) { + if (quad.point !== node) { + var dx = quad.cx - node.x, dy = quad.cy - node.y, dn = 1 / Math.sqrt(dx * dx + dy * dy); + if ((x2 - x1) * dn < theta) { + var k = quad.charge * dn * dn; + node.px -= dx * k; + node.py -= dy * k; + return true; + } + if (quad.point && isFinite(dn)) { + var k = quad.pointCharge * dn * dn; + node.px -= dx * k; + node.py -= dy * k; + } + } + return !quad.charge; + }; + } + force.tick = function() { + if ((alpha *= .99) < .005) { + event.end({ + type: "end", + alpha: alpha = 0 + }); + return true; + } + var n = nodes.length, m = links.length, q, i, o, s, t, l, k, x, y; + for (i = 0; i < m; ++i) { + o = links[i]; + s = o.source; + t = o.target; + x = t.x - s.x; + y = t.y - s.y; + if (l = x * x + y * y) { + l = alpha * strengths[i] * ((l = Math.sqrt(l)) - distances[i]) / l; + x *= l; + y *= l; + t.x -= x * (k = s.weight / (t.weight + s.weight)); + t.y -= y * k; + s.x += x * (k = 1 - k); + s.y += y * k; + } + } + if (k = alpha * gravity) { + x = size[0] / 2; + y = size[1] / 2; + i = -1; + if (k) while (++i < n) { + o = nodes[i]; + o.x += (x - o.x) * k; + o.y += (y - o.y) * k; + } + } + if (charge) { + d3_layout_forceAccumulate(q = d3.geom.quadtree(nodes), alpha, charges); + i = -1; + while (++i < n) { + if (!(o = nodes[i]).fixed) { + q.visit(repulse(o)); + } + } + } + i = -1; + while (++i < n) { + o = nodes[i]; + if (o.fixed) { + o.x = o.px; + o.y = o.py; + } else { + o.x -= (o.px - (o.px = o.x)) * friction; + o.y -= (o.py - (o.py = o.y)) * friction; + } + } + event.tick({ + type: "tick", + alpha: alpha + }); + }; + force.nodes = function(x) { + if (!arguments.length) return nodes; + nodes = x; + return force; + }; + force.links = function(x) { + if (!arguments.length) return links; + links = x; + return force; + }; + force.size = function(x) { + if (!arguments.length) return size; + size = x; + return force; + }; + force.linkDistance = function(x) { + if (!arguments.length) return linkDistance; + linkDistance = typeof x === "function" ? x : +x; + return force; + }; + force.distance = force.linkDistance; + force.linkStrength = function(x) { + if (!arguments.length) return linkStrength; + linkStrength = typeof x === "function" ? x : +x; + return force; + }; + force.friction = function(x) { + if (!arguments.length) return friction; + friction = +x; + return force; + }; + force.charge = function(x) { + if (!arguments.length) return charge; + charge = typeof x === "function" ? x : +x; + return force; + }; + force.gravity = function(x) { + if (!arguments.length) return gravity; + gravity = +x; + return force; + }; + force.theta = function(x) { + if (!arguments.length) return theta; + theta = +x; + return force; + }; + force.alpha = function(x) { + if (!arguments.length) return alpha; + x = +x; + if (alpha) { + if (x > 0) alpha = x; else alpha = 0; + } else if (x > 0) { + event.start({ + type: "start", + alpha: alpha = x + }); + d3.timer(force.tick); + } + return force; + }; + force.start = function() { + var i, j, n = nodes.length, m = links.length, w = size[0], h = size[1], neighbors, o; + for (i = 0; i < n; ++i) { + (o = nodes[i]).index = i; + o.weight = 0; + } + for (i = 0; i < m; ++i) { + o = links[i]; + if (typeof o.source == "number") o.source = nodes[o.source]; + if (typeof o.target == "number") o.target = nodes[o.target]; + ++o.source.weight; + ++o.target.weight; + } + for (i = 0; i < n; ++i) { + o = nodes[i]; + if (isNaN(o.x)) o.x = position("x", w); + if (isNaN(o.y)) o.y = position("y", h); + if (isNaN(o.px)) o.px = o.x; + if (isNaN(o.py)) o.py = o.y; + } + distances = []; + if (typeof linkDistance === "function") for (i = 0; i < m; ++i) distances[i] = +linkDistance.call(this, links[i], i); else for (i = 0; i < m; ++i) distances[i] = linkDistance; + strengths = []; + if (typeof linkStrength === "function") for (i = 0; i < m; ++i) strengths[i] = +linkStrength.call(this, links[i], i); else for (i = 0; i < m; ++i) strengths[i] = linkStrength; + charges = []; + if (typeof charge === "function") for (i = 0; i < n; ++i) charges[i] = +charge.call(this, nodes[i], i); else for (i = 0; i < n; ++i) charges[i] = charge; + function position(dimension, size) { + var neighbors = neighbor(i), j = -1, m = neighbors.length, x; + while (++j < m) if (!isNaN(x = neighbors[j][dimension])) return x; + return Math.random() * size; + } + function neighbor() { + if (!neighbors) { + neighbors = []; + for (j = 0; j < n; ++j) { + neighbors[j] = []; + } + for (j = 0; j < m; ++j) { + var o = links[j]; + neighbors[o.source.index].push(o.target); + neighbors[o.target.index].push(o.source); + } + } + return neighbors[i]; + } + return force.resume(); + }; + force.resume = function() { + return force.alpha(.1); + }; + force.stop = function() { + return force.alpha(0); + }; + force.drag = function() { + if (!drag) drag = d3.behavior.drag().origin(d3_identity).on("dragstart.force", d3_layout_forceDragstart).on("drag.force", dragmove).on("dragend.force", d3_layout_forceDragend); + if (!arguments.length) return drag; + this.on("mouseover.force", d3_layout_forceMouseover).on("mouseout.force", d3_layout_forceMouseout).call(drag); + }; + function dragmove(d) { + d.px = d3.event.x, d.py = d3.event.y; + force.resume(); + } + return d3.rebind(force, event, "on"); + }; + function d3_layout_forceDragstart(d) { + d.fixed |= 2; + } + function d3_layout_forceDragend(d) { + d.fixed &= ~6; + } + function d3_layout_forceMouseover(d) { + d.fixed |= 4; + d.px = d.x, d.py = d.y; + } + function d3_layout_forceMouseout(d) { + d.fixed &= ~4; + } + function d3_layout_forceAccumulate(quad, alpha, charges) { + var cx = 0, cy = 0; + quad.charge = 0; + if (!quad.leaf) { + var nodes = quad.nodes, n = nodes.length, i = -1, c; + while (++i < n) { + c = nodes[i]; + if (c == null) continue; + d3_layout_forceAccumulate(c, alpha, charges); + quad.charge += c.charge; + cx += c.charge * c.cx; + cy += c.charge * c.cy; + } + } + if (quad.point) { + if (!quad.leaf) { + quad.point.x += Math.random() - .5; + quad.point.y += Math.random() - .5; + } + var k = alpha * charges[quad.point.index]; + quad.charge += quad.pointCharge = k; + cx += k * quad.point.x; + cy += k * quad.point.y; + } + quad.cx = cx / quad.charge; + quad.cy = cy / quad.charge; + } + var d3_layout_forceLinkDistance = 20, d3_layout_forceLinkStrength = 1; + d3.layout.hierarchy = function() { + var sort = d3_layout_hierarchySort, children = d3_layout_hierarchyChildren, value = d3_layout_hierarchyValue; + function recurse(node, depth, nodes) { + var childs = children.call(hierarchy, node, depth); + node.depth = depth; + nodes.push(node); + if (childs && (n = childs.length)) { + var i = -1, n, c = node.children = [], v = 0, j = depth + 1, d; + while (++i < n) { + d = recurse(childs[i], j, nodes); + d.parent = node; + c.push(d); + v += d.value; + } + if (sort) c.sort(sort); + if (value) node.value = v; + } else if (value) { + node.value = +value.call(hierarchy, node, depth) || 0; + } + return node; + } + function revalue(node, depth) { + var children = node.children, v = 0; + if (children && (n = children.length)) { + var i = -1, n, j = depth + 1; + while (++i < n) v += revalue(children[i], j); + } else if (value) { + v = +value.call(hierarchy, node, depth) || 0; + } + if (value) node.value = v; + return v; + } + function hierarchy(d) { + var nodes = []; + recurse(d, 0, nodes); + return nodes; + } + hierarchy.sort = function(x) { + if (!arguments.length) return sort; + sort = x; + return hierarchy; + }; + hierarchy.children = function(x) { + if (!arguments.length) return children; + children = x; + return hierarchy; + }; + hierarchy.value = function(x) { + if (!arguments.length) return value; + value = x; + return hierarchy; + }; + hierarchy.revalue = function(root) { + revalue(root, 0); + return root; + }; + return hierarchy; + }; + function d3_layout_hierarchyRebind(object, hierarchy) { + d3.rebind(object, hierarchy, "sort", "children", "value"); + object.nodes = object; + object.links = d3_layout_hierarchyLinks; + return object; + } + function d3_layout_hierarchyChildren(d) { + return d.children; + } + function d3_layout_hierarchyValue(d) { + return d.value; + } + function d3_layout_hierarchySort(a, b) { + return b.value - a.value; + } + function d3_layout_hierarchyLinks(nodes) { + return d3.merge(nodes.map(function(parent) { + return (parent.children || []).map(function(child) { + return { + source: parent, + target: child + }; + }); + })); + } + d3.layout.partition = function() { + var hierarchy = d3.layout.hierarchy(), size = [ 1, 1 ]; + function position(node, x, dx, dy) { + var children = node.children; + node.x = x; + node.y = node.depth * dy; + node.dx = dx; + node.dy = dy; + if (children && (n = children.length)) { + var i = -1, n, c, d; + dx = node.value ? dx / node.value : 0; + while (++i < n) { + position(c = children[i], x, d = c.value * dx, dy); + x += d; + } + } + } + function depth(node) { + var children = node.children, d = 0; + if (children && (n = children.length)) { + var i = -1, n; + while (++i < n) d = Math.max(d, depth(children[i])); + } + return 1 + d; + } + function partition(d, i) { + var nodes = hierarchy.call(this, d, i); + position(nodes[0], 0, size[0], size[1] / depth(nodes[0])); + return nodes; + } + partition.size = function(x) { + if (!arguments.length) return size; + size = x; + return partition; + }; + return d3_layout_hierarchyRebind(partition, hierarchy); + }; + d3.layout.pie = function() { + var value = Number, sort = d3_layout_pieSortByValue, startAngle = 0, endAngle = 2 * π; + function pie(data) { + var values = data.map(function(d, i) { + return +value.call(pie, d, i); + }); + var a = +(typeof startAngle === "function" ? startAngle.apply(this, arguments) : startAngle); + var k = ((typeof endAngle === "function" ? endAngle.apply(this, arguments) : endAngle) - a) / d3.sum(values); + var index = d3.range(data.length); + if (sort != null) index.sort(sort === d3_layout_pieSortByValue ? function(i, j) { + return values[j] - values[i]; + } : function(i, j) { + return sort(data[i], data[j]); + }); + var arcs = []; + index.forEach(function(i) { + var d; + arcs[i] = { + data: data[i], + value: d = values[i], + startAngle: a, + endAngle: a += d * k + }; + }); + return arcs; + } + pie.value = function(x) { + if (!arguments.length) return value; + value = x; + return pie; + }; + pie.sort = function(x) { + if (!arguments.length) return sort; + sort = x; + return pie; + }; + pie.startAngle = function(x) { + if (!arguments.length) return startAngle; + startAngle = x; + return pie; + }; + pie.endAngle = function(x) { + if (!arguments.length) return endAngle; + endAngle = x; + return pie; + }; + return pie; + }; + var d3_layout_pieSortByValue = {}; + d3.layout.stack = function() { + var values = d3_identity, order = d3_layout_stackOrderDefault, offset = d3_layout_stackOffsetZero, out = d3_layout_stackOut, x = d3_layout_stackX, y = d3_layout_stackY; + function stack(data, index) { + var series = data.map(function(d, i) { + return values.call(stack, d, i); + }); + var points = series.map(function(d) { + return d.map(function(v, i) { + return [ x.call(stack, v, i), y.call(stack, v, i) ]; + }); + }); + var orders = order.call(stack, points, index); + series = d3.permute(series, orders); + points = d3.permute(points, orders); + var offsets = offset.call(stack, points, index); + var n = series.length, m = series[0].length, i, j, o; + for (j = 0; j < m; ++j) { + out.call(stack, series[0][j], o = offsets[j], points[0][j][1]); + for (i = 1; i < n; ++i) { + out.call(stack, series[i][j], o += points[i - 1][j][1], points[i][j][1]); + } + } + return data; + } + stack.values = function(x) { + if (!arguments.length) return values; + values = x; + return stack; + }; + stack.order = function(x) { + if (!arguments.length) return order; + order = typeof x === "function" ? x : d3_layout_stackOrders.get(x) || d3_layout_stackOrderDefault; + return stack; + }; + stack.offset = function(x) { + if (!arguments.length) return offset; + offset = typeof x === "function" ? x : d3_layout_stackOffsets.get(x) || d3_layout_stackOffsetZero; + return stack; + }; + stack.x = function(z) { + if (!arguments.length) return x; + x = z; + return stack; + }; + stack.y = function(z) { + if (!arguments.length) return y; + y = z; + return stack; + }; + stack.out = function(z) { + if (!arguments.length) return out; + out = z; + return stack; + }; + return stack; + }; + function d3_layout_stackX(d) { + return d.x; + } + function d3_layout_stackY(d) { + return d.y; + } + function d3_layout_stackOut(d, y0, y) { + d.y0 = y0; + d.y = y; + } + var d3_layout_stackOrders = d3.map({ + "inside-out": function(data) { + var n = data.length, i, j, max = data.map(d3_layout_stackMaxIndex), sums = data.map(d3_layout_stackReduceSum), index = d3.range(n).sort(function(a, b) { + return max[a] - max[b]; + }), top = 0, bottom = 0, tops = [], bottoms = []; + for (i = 0; i < n; ++i) { + j = index[i]; + if (top < bottom) { + top += sums[j]; + tops.push(j); + } else { + bottom += sums[j]; + bottoms.push(j); + } + } + return bottoms.reverse().concat(tops); + }, + reverse: function(data) { + return d3.range(data.length).reverse(); + }, + "default": d3_layout_stackOrderDefault + }); + var d3_layout_stackOffsets = d3.map({ + silhouette: function(data) { + var n = data.length, m = data[0].length, sums = [], max = 0, i, j, o, y0 = []; + for (j = 0; j < m; ++j) { + for (i = 0, o = 0; i < n; i++) o += data[i][j][1]; + if (o > max) max = o; + sums.push(o); + } + for (j = 0; j < m; ++j) { + y0[j] = (max - sums[j]) / 2; + } + return y0; + }, + wiggle: function(data) { + var n = data.length, x = data[0], m = x.length, i, j, k, s1, s2, s3, dx, o, o0, y0 = []; + y0[0] = o = o0 = 0; + for (j = 1; j < m; ++j) { + for (i = 0, s1 = 0; i < n; ++i) s1 += data[i][j][1]; + for (i = 0, s2 = 0, dx = x[j][0] - x[j - 1][0]; i < n; ++i) { + for (k = 0, s3 = (data[i][j][1] - data[i][j - 1][1]) / (2 * dx); k < i; ++k) { + s3 += (data[k][j][1] - data[k][j - 1][1]) / dx; + } + s2 += s3 * data[i][j][1]; + } + y0[j] = o -= s1 ? s2 / s1 * dx : 0; + if (o < o0) o0 = o; + } + for (j = 0; j < m; ++j) y0[j] -= o0; + return y0; + }, + expand: function(data) { + var n = data.length, m = data[0].length, k = 1 / n, i, j, o, y0 = []; + for (j = 0; j < m; ++j) { + for (i = 0, o = 0; i < n; i++) o += data[i][j][1]; + if (o) for (i = 0; i < n; i++) data[i][j][1] /= o; else for (i = 0; i < n; i++) data[i][j][1] = k; + } + for (j = 0; j < m; ++j) y0[j] = 0; + return y0; + }, + zero: d3_layout_stackOffsetZero + }); + function d3_layout_stackOrderDefault(data) { + return d3.range(data.length); + } + function d3_layout_stackOffsetZero(data) { + var j = -1, m = data[0].length, y0 = []; + while (++j < m) y0[j] = 0; + return y0; + } + function d3_layout_stackMaxIndex(array) { + var i = 1, j = 0, v = array[0][1], k, n = array.length; + for (;i < n; ++i) { + if ((k = array[i][1]) > v) { + j = i; + v = k; + } + } + return j; + } + function d3_layout_stackReduceSum(d) { + return d.reduce(d3_layout_stackSum, 0); + } + function d3_layout_stackSum(p, d) { + return p + d[1]; + } + d3.layout.histogram = function() { + var frequency = true, valuer = Number, ranger = d3_layout_histogramRange, binner = d3_layout_histogramBinSturges; + function histogram(data, i) { + var bins = [], values = data.map(valuer, this), range = ranger.call(this, values, i), thresholds = binner.call(this, range, values, i), bin, i = -1, n = values.length, m = thresholds.length - 1, k = frequency ? 1 : 1 / n, x; + while (++i < m) { + bin = bins[i] = []; + bin.dx = thresholds[i + 1] - (bin.x = thresholds[i]); + bin.y = 0; + } + if (m > 0) { + i = -1; + while (++i < n) { + x = values[i]; + if (x >= range[0] && x <= range[1]) { + bin = bins[d3.bisect(thresholds, x, 1, m) - 1]; + bin.y += k; + bin.push(data[i]); + } + } + } + return bins; + } + histogram.value = function(x) { + if (!arguments.length) return valuer; + valuer = x; + return histogram; + }; + histogram.range = function(x) { + if (!arguments.length) return ranger; + ranger = d3_functor(x); + return histogram; + }; + histogram.bins = function(x) { + if (!arguments.length) return binner; + binner = typeof x === "number" ? function(range) { + return d3_layout_histogramBinFixed(range, x); + } : d3_functor(x); + return histogram; + }; + histogram.frequency = function(x) { + if (!arguments.length) return frequency; + frequency = !!x; + return histogram; + }; + return histogram; + }; + function d3_layout_histogramBinSturges(range, values) { + return d3_layout_histogramBinFixed(range, Math.ceil(Math.log(values.length) / Math.LN2 + 1)); + } + function d3_layout_histogramBinFixed(range, n) { + var x = -1, b = +range[0], m = (range[1] - b) / n, f = []; + while (++x <= n) f[x] = m * x + b; + return f; + } + function d3_layout_histogramRange(values) { + return [ d3.min(values), d3.max(values) ]; + } + d3.layout.tree = function() { + var hierarchy = d3.layout.hierarchy().sort(null).value(null), separation = d3_layout_treeSeparation, size = [ 1, 1 ]; + function tree(d, i) { + var nodes = hierarchy.call(this, d, i), root = nodes[0]; + function firstWalk(node, previousSibling) { + var children = node.children, layout = node._tree; + if (children && (n = children.length)) { + var n, firstChild = children[0], previousChild, ancestor = firstChild, child, i = -1; + while (++i < n) { + child = children[i]; + firstWalk(child, previousChild); + ancestor = apportion(child, previousChild, ancestor); + previousChild = child; + } + d3_layout_treeShift(node); + var midpoint = .5 * (firstChild._tree.prelim + child._tree.prelim); + if (previousSibling) { + layout.prelim = previousSibling._tree.prelim + separation(node, previousSibling); + layout.mod = layout.prelim - midpoint; + } else { + layout.prelim = midpoint; + } + } else { + if (previousSibling) { + layout.prelim = previousSibling._tree.prelim + separation(node, previousSibling); + } + } + } + function secondWalk(node, x) { + node.x = node._tree.prelim + x; + var children = node.children; + if (children && (n = children.length)) { + var i = -1, n; + x += node._tree.mod; + while (++i < n) { + secondWalk(children[i], x); + } + } + } + function apportion(node, previousSibling, ancestor) { + if (previousSibling) { + var vip = node, vop = node, vim = previousSibling, vom = node.parent.children[0], sip = vip._tree.mod, sop = vop._tree.mod, sim = vim._tree.mod, som = vom._tree.mod, shift; + while (vim = d3_layout_treeRight(vim), vip = d3_layout_treeLeft(vip), vim && vip) { + vom = d3_layout_treeLeft(vom); + vop = d3_layout_treeRight(vop); + vop._tree.ancestor = node; + shift = vim._tree.prelim + sim - vip._tree.prelim - sip + separation(vim, vip); + if (shift > 0) { + d3_layout_treeMove(d3_layout_treeAncestor(vim, node, ancestor), node, shift); + sip += shift; + sop += shift; + } + sim += vim._tree.mod; + sip += vip._tree.mod; + som += vom._tree.mod; + sop += vop._tree.mod; + } + if (vim && !d3_layout_treeRight(vop)) { + vop._tree.thread = vim; + vop._tree.mod += sim - sop; + } + if (vip && !d3_layout_treeLeft(vom)) { + vom._tree.thread = vip; + vom._tree.mod += sip - som; + ancestor = node; + } + } + return ancestor; + } + d3_layout_treeVisitAfter(root, function(node, previousSibling) { + node._tree = { + ancestor: node, + prelim: 0, + mod: 0, + change: 0, + shift: 0, + number: previousSibling ? previousSibling._tree.number + 1 : 0 + }; + }); + firstWalk(root); + secondWalk(root, -root._tree.prelim); + var left = d3_layout_treeSearch(root, d3_layout_treeLeftmost), right = d3_layout_treeSearch(root, d3_layout_treeRightmost), deep = d3_layout_treeSearch(root, d3_layout_treeDeepest), x0 = left.x - separation(left, right) / 2, x1 = right.x + separation(right, left) / 2, y1 = deep.depth || 1; + d3_layout_treeVisitAfter(root, function(node) { + node.x = (node.x - x0) / (x1 - x0) * size[0]; + node.y = node.depth / y1 * size[1]; + delete node._tree; + }); + return nodes; + } + tree.separation = function(x) { + if (!arguments.length) return separation; + separation = x; + return tree; + }; + tree.size = function(x) { + if (!arguments.length) return size; + size = x; + return tree; + }; + return d3_layout_hierarchyRebind(tree, hierarchy); + }; + function d3_layout_treeSeparation(a, b) { + return a.parent == b.parent ? 1 : 2; + } + function d3_layout_treeLeft(node) { + var children = node.children; + return children && children.length ? children[0] : node._tree.thread; + } + function d3_layout_treeRight(node) { + var children = node.children, n; + return children && (n = children.length) ? children[n - 1] : node._tree.thread; + } + function d3_layout_treeSearch(node, compare) { + var children = node.children; + if (children && (n = children.length)) { + var child, n, i = -1; + while (++i < n) { + if (compare(child = d3_layout_treeSearch(children[i], compare), node) > 0) { + node = child; + } + } + } + return node; + } + function d3_layout_treeRightmost(a, b) { + return a.x - b.x; + } + function d3_layout_treeLeftmost(a, b) { + return b.x - a.x; + } + function d3_layout_treeDeepest(a, b) { + return a.depth - b.depth; + } + function d3_layout_treeVisitAfter(node, callback) { + function visit(node, previousSibling) { + var children = node.children; + if (children && (n = children.length)) { + var child, previousChild = null, i = -1, n; + while (++i < n) { + child = children[i]; + visit(child, previousChild); + previousChild = child; + } + } + callback(node, previousSibling); + } + visit(node, null); + } + function d3_layout_treeShift(node) { + var shift = 0, change = 0, children = node.children, i = children.length, child; + while (--i >= 0) { + child = children[i]._tree; + child.prelim += shift; + child.mod += shift; + shift += child.shift + (change += child.change); + } + } + function d3_layout_treeMove(ancestor, node, shift) { + ancestor = ancestor._tree; + node = node._tree; + var change = shift / (node.number - ancestor.number); + ancestor.change += change; + node.change -= change; + node.shift += shift; + node.prelim += shift; + node.mod += shift; + } + function d3_layout_treeAncestor(vim, node, ancestor) { + return vim._tree.ancestor.parent == node.parent ? vim._tree.ancestor : ancestor; + } + d3.layout.pack = function() { + var hierarchy = d3.layout.hierarchy().sort(d3_layout_packSort), padding = 0, size = [ 1, 1 ]; + function pack(d, i) { + var nodes = hierarchy.call(this, d, i), root = nodes[0]; + root.x = 0; + root.y = 0; + d3_layout_treeVisitAfter(root, function(d) { + d.r = Math.sqrt(d.value); + }); + d3_layout_treeVisitAfter(root, d3_layout_packSiblings); + var w = size[0], h = size[1], k = Math.max(2 * root.r / w, 2 * root.r / h); + if (padding > 0) { + var dr = padding * k / 2; + d3_layout_treeVisitAfter(root, function(d) { + d.r += dr; + }); + d3_layout_treeVisitAfter(root, d3_layout_packSiblings); + d3_layout_treeVisitAfter(root, function(d) { + d.r -= dr; + }); + k = Math.max(2 * root.r / w, 2 * root.r / h); + } + d3_layout_packTransform(root, w / 2, h / 2, 1 / k); + return nodes; + } + pack.size = function(x) { + if (!arguments.length) return size; + size = x; + return pack; + }; + pack.padding = function(_) { + if (!arguments.length) return padding; + padding = +_; + return pack; + }; + return d3_layout_hierarchyRebind(pack, hierarchy); + }; + function d3_layout_packSort(a, b) { + return a.value - b.value; + } + function d3_layout_packInsert(a, b) { + var c = a._pack_next; + a._pack_next = b; + b._pack_prev = a; + b._pack_next = c; + c._pack_prev = b; + } + function d3_layout_packSplice(a, b) { + a._pack_next = b; + b._pack_prev = a; + } + function d3_layout_packIntersects(a, b) { + var dx = b.x - a.x, dy = b.y - a.y, dr = a.r + b.r; + return dr * dr - dx * dx - dy * dy > .001; + } + function d3_layout_packSiblings(node) { + if (!(nodes = node.children) || !(n = nodes.length)) return; + var nodes, xMin = Infinity, xMax = -Infinity, yMin = Infinity, yMax = -Infinity, a, b, c, i, j, k, n; + function bound(node) { + xMin = Math.min(node.x - node.r, xMin); + xMax = Math.max(node.x + node.r, xMax); + yMin = Math.min(node.y - node.r, yMin); + yMax = Math.max(node.y + node.r, yMax); + } + nodes.forEach(d3_layout_packLink); + a = nodes[0]; + a.x = -a.r; + a.y = 0; + bound(a); + if (n > 1) { + b = nodes[1]; + b.x = b.r; + b.y = 0; + bound(b); + if (n > 2) { + c = nodes[2]; + d3_layout_packPlace(a, b, c); + bound(c); + d3_layout_packInsert(a, c); + a._pack_prev = c; + d3_layout_packInsert(c, b); + b = a._pack_next; + for (i = 3; i < n; i++) { + d3_layout_packPlace(a, b, c = nodes[i]); + var isect = 0, s1 = 1, s2 = 1; + for (j = b._pack_next; j !== b; j = j._pack_next, s1++) { + if (d3_layout_packIntersects(j, c)) { + isect = 1; + break; + } + } + if (isect == 1) { + for (k = a._pack_prev; k !== j._pack_prev; k = k._pack_prev, s2++) { + if (d3_layout_packIntersects(k, c)) { + break; + } + } + } + if (isect) { + if (s1 < s2 || s1 == s2 && b.r < a.r) d3_layout_packSplice(a, b = j); else d3_layout_packSplice(a = k, b); + i--; + } else { + d3_layout_packInsert(a, c); + b = c; + bound(c); + } + } + } + } + var cx = (xMin + xMax) / 2, cy = (yMin + yMax) / 2, cr = 0; + for (i = 0; i < n; i++) { + c = nodes[i]; + c.x -= cx; + c.y -= cy; + cr = Math.max(cr, c.r + Math.sqrt(c.x * c.x + c.y * c.y)); + } + node.r = cr; + nodes.forEach(d3_layout_packUnlink); + } + function d3_layout_packLink(node) { + node._pack_next = node._pack_prev = node; + } + function d3_layout_packUnlink(node) { + delete node._pack_next; + delete node._pack_prev; + } + function d3_layout_packTransform(node, x, y, k) { + var children = node.children; + node.x = x += k * node.x; + node.y = y += k * node.y; + node.r *= k; + if (children) { + var i = -1, n = children.length; + while (++i < n) d3_layout_packTransform(children[i], x, y, k); + } + } + function d3_layout_packPlace(a, b, c) { + var db = a.r + c.r, dx = b.x - a.x, dy = b.y - a.y; + if (db && (dx || dy)) { + var da = b.r + c.r, dc = dx * dx + dy * dy; + da *= da; + db *= db; + var x = .5 + (db - da) / (2 * dc), y = Math.sqrt(Math.max(0, 2 * da * (db + dc) - (db -= dc) * db - da * da)) / (2 * dc); + c.x = a.x + x * dx + y * dy; + c.y = a.y + x * dy - y * dx; + } else { + c.x = a.x + db; + c.y = a.y; + } + } + d3.layout.cluster = function() { + var hierarchy = d3.layout.hierarchy().sort(null).value(null), separation = d3_layout_treeSeparation, size = [ 1, 1 ]; + function cluster(d, i) { + var nodes = hierarchy.call(this, d, i), root = nodes[0], previousNode, x = 0; + d3_layout_treeVisitAfter(root, function(node) { + var children = node.children; + if (children && children.length) { + node.x = d3_layout_clusterX(children); + node.y = d3_layout_clusterY(children); + } else { + node.x = previousNode ? x += separation(node, previousNode) : 0; + node.y = 0; + previousNode = node; + } + }); + var left = d3_layout_clusterLeft(root), right = d3_layout_clusterRight(root), x0 = left.x - separation(left, right) / 2, x1 = right.x + separation(right, left) / 2; + d3_layout_treeVisitAfter(root, function(node) { + node.x = (node.x - x0) / (x1 - x0) * size[0]; + node.y = (1 - (root.y ? node.y / root.y : 1)) * size[1]; + }); + return nodes; + } + cluster.separation = function(x) { + if (!arguments.length) return separation; + separation = x; + return cluster; + }; + cluster.size = function(x) { + if (!arguments.length) return size; + size = x; + return cluster; + }; + return d3_layout_hierarchyRebind(cluster, hierarchy); + }; + function d3_layout_clusterY(children) { + return 1 + d3.max(children, function(child) { + return child.y; + }); + } + function d3_layout_clusterX(children) { + return children.reduce(function(x, child) { + return x + child.x; + }, 0) / children.length; + } + function d3_layout_clusterLeft(node) { + var children = node.children; + return children && children.length ? d3_layout_clusterLeft(children[0]) : node; + } + function d3_layout_clusterRight(node) { + var children = node.children, n; + return children && (n = children.length) ? d3_layout_clusterRight(children[n - 1]) : node; + } + d3.layout.treemap = function() { + var hierarchy = d3.layout.hierarchy(), round = Math.round, size = [ 1, 1 ], padding = null, pad = d3_layout_treemapPadNull, sticky = false, stickies, mode = "squarify", ratio = .5 * (1 + Math.sqrt(5)); + function scale(children, k) { + var i = -1, n = children.length, child, area; + while (++i < n) { + area = (child = children[i]).value * (k < 0 ? 0 : k); + child.area = isNaN(area) || area <= 0 ? 0 : area; + } + } + function squarify(node) { + var children = node.children; + if (children && children.length) { + var rect = pad(node), row = [], remaining = children.slice(), child, best = Infinity, score, u = mode === "slice" ? rect.dx : mode === "dice" ? rect.dy : mode === "slice-dice" ? node.depth & 1 ? rect.dy : rect.dx : Math.min(rect.dx, rect.dy), n; + scale(remaining, rect.dx * rect.dy / node.value); + row.area = 0; + while ((n = remaining.length) > 0) { + row.push(child = remaining[n - 1]); + row.area += child.area; + if (mode !== "squarify" || (score = worst(row, u)) <= best) { + remaining.pop(); + best = score; + } else { + row.area -= row.pop().area; + position(row, u, rect, false); + u = Math.min(rect.dx, rect.dy); + row.length = row.area = 0; + best = Infinity; + } + } + if (row.length) { + position(row, u, rect, true); + row.length = row.area = 0; + } + children.forEach(squarify); + } + } + function stickify(node) { + var children = node.children; + if (children && children.length) { + var rect = pad(node), remaining = children.slice(), child, row = []; + scale(remaining, rect.dx * rect.dy / node.value); + row.area = 0; + while (child = remaining.pop()) { + row.push(child); + row.area += child.area; + if (child.z != null) { + position(row, child.z ? rect.dx : rect.dy, rect, !remaining.length); + row.length = row.area = 0; + } + } + children.forEach(stickify); + } + } + function worst(row, u) { + var s = row.area, r, rmax = 0, rmin = Infinity, i = -1, n = row.length; + while (++i < n) { + if (!(r = row[i].area)) continue; + if (r < rmin) rmin = r; + if (r > rmax) rmax = r; + } + s *= s; + u *= u; + return s ? Math.max(u * rmax * ratio / s, s / (u * rmin * ratio)) : Infinity; + } + function position(row, u, rect, flush) { + var i = -1, n = row.length, x = rect.x, y = rect.y, v = u ? round(row.area / u) : 0, o; + if (u == rect.dx) { + if (flush || v > rect.dy) v = rect.dy; + while (++i < n) { + o = row[i]; + o.x = x; + o.y = y; + o.dy = v; + x += o.dx = Math.min(rect.x + rect.dx - x, v ? round(o.area / v) : 0); + } + o.z = true; + o.dx += rect.x + rect.dx - x; + rect.y += v; + rect.dy -= v; + } else { + if (flush || v > rect.dx) v = rect.dx; + while (++i < n) { + o = row[i]; + o.x = x; + o.y = y; + o.dx = v; + y += o.dy = Math.min(rect.y + rect.dy - y, v ? round(o.area / v) : 0); + } + o.z = false; + o.dy += rect.y + rect.dy - y; + rect.x += v; + rect.dx -= v; + } + } + function treemap(d) { + var nodes = stickies || hierarchy(d), root = nodes[0]; + root.x = 0; + root.y = 0; + root.dx = size[0]; + root.dy = size[1]; + if (stickies) hierarchy.revalue(root); + scale([ root ], root.dx * root.dy / root.value); + (stickies ? stickify : squarify)(root); + if (sticky) stickies = nodes; + return nodes; + } + treemap.size = function(x) { + if (!arguments.length) return size; + size = x; + return treemap; + }; + treemap.padding = function(x) { + if (!arguments.length) return padding; + function padFunction(node) { + var p = x.call(treemap, node, node.depth); + return p == null ? d3_layout_treemapPadNull(node) : d3_layout_treemapPad(node, typeof p === "number" ? [ p, p, p, p ] : p); + } + function padConstant(node) { + return d3_layout_treemapPad(node, x); + } + var type; + pad = (padding = x) == null ? d3_layout_treemapPadNull : (type = typeof x) === "function" ? padFunction : type === "number" ? (x = [ x, x, x, x ], + padConstant) : padConstant; + return treemap; + }; + treemap.round = function(x) { + if (!arguments.length) return round != Number; + round = x ? Math.round : Number; + return treemap; + }; + treemap.sticky = function(x) { + if (!arguments.length) return sticky; + sticky = x; + stickies = null; + return treemap; + }; + treemap.ratio = function(x) { + if (!arguments.length) return ratio; + ratio = x; + return treemap; + }; + treemap.mode = function(x) { + if (!arguments.length) return mode; + mode = x + ""; + return treemap; + }; + return d3_layout_hierarchyRebind(treemap, hierarchy); + }; + function d3_layout_treemapPadNull(node) { + return { + x: node.x, + y: node.y, + dx: node.dx, + dy: node.dy + }; + } + function d3_layout_treemapPad(node, padding) { + var x = node.x + padding[3], y = node.y + padding[0], dx = node.dx - padding[1] - padding[3], dy = node.dy - padding[0] - padding[2]; + if (dx < 0) { + x += dx / 2; + dx = 0; + } + if (dy < 0) { + y += dy / 2; + dy = 0; + } + return { + x: x, + y: y, + dx: dx, + dy: dy + }; + } + d3.random = { + normal: function(µ, σ) { + var n = arguments.length; + if (n < 2) σ = 1; + if (n < 1) µ = 0; + return function() { + var x, y, r; + do { + x = Math.random() * 2 - 1; + y = Math.random() * 2 - 1; + r = x * x + y * y; + } while (!r || r > 1); + return µ + σ * x * Math.sqrt(-2 * Math.log(r) / r); + }; + }, + logNormal: function() { + var random = d3.random.normal.apply(d3, arguments); + return function() { + return Math.exp(random()); + }; + }, + irwinHall: function(m) { + return function() { + for (var s = 0, j = 0; j < m; j++) s += Math.random(); + return s / m; + }; + } + }; + d3.scale = {}; + function d3_scaleExtent(domain) { + var start = domain[0], stop = domain[domain.length - 1]; + return start < stop ? [ start, stop ] : [ stop, start ]; + } + function d3_scaleRange(scale) { + return scale.rangeExtent ? scale.rangeExtent() : d3_scaleExtent(scale.range()); + } + function d3_scale_bilinear(domain, range, uninterpolate, interpolate) { + var u = uninterpolate(domain[0], domain[1]), i = interpolate(range[0], range[1]); + return function(x) { + return i(u(x)); + }; + } + function d3_scale_nice(domain, nice) { + var i0 = 0, i1 = domain.length - 1, x0 = domain[i0], x1 = domain[i1], dx; + if (x1 < x0) { + dx = i0, i0 = i1, i1 = dx; + dx = x0, x0 = x1, x1 = dx; + } + if (nice = nice(x1 - x0)) { + domain[i0] = nice.floor(x0); + domain[i1] = nice.ceil(x1); + } + return domain; + } + function d3_scale_polylinear(domain, range, uninterpolate, interpolate) { + var u = [], i = [], j = 0, k = Math.min(domain.length, range.length) - 1; + if (domain[k] < domain[0]) { + domain = domain.slice().reverse(); + range = range.slice().reverse(); + } + while (++j <= k) { + u.push(uninterpolate(domain[j - 1], domain[j])); + i.push(interpolate(range[j - 1], range[j])); + } + return function(x) { + var j = d3.bisect(domain, x, 1, k) - 1; + return i[j](u[j](x)); + }; + } + d3.scale.linear = function() { + return d3_scale_linear([ 0, 1 ], [ 0, 1 ], d3_interpolate, false); + }; + function d3_scale_linear(domain, range, interpolate, clamp) { + var output, input; + function rescale() { + var linear = Math.min(domain.length, range.length) > 2 ? d3_scale_polylinear : d3_scale_bilinear, uninterpolate = clamp ? d3_uninterpolateClamp : d3_uninterpolateNumber; + output = linear(domain, range, uninterpolate, interpolate); + input = linear(range, domain, uninterpolate, d3_interpolate); + return scale; + } + function scale(x) { + return output(x); + } + scale.invert = function(y) { + return input(y); + }; + scale.domain = function(x) { + if (!arguments.length) return domain; + domain = x.map(Number); + return rescale(); + }; + scale.range = function(x) { + if (!arguments.length) return range; + range = x; + return rescale(); + }; + scale.rangeRound = function(x) { + return scale.range(x).interpolate(d3_interpolateRound); + }; + scale.clamp = function(x) { + if (!arguments.length) return clamp; + clamp = x; + return rescale(); + }; + scale.interpolate = function(x) { + if (!arguments.length) return interpolate; + interpolate = x; + return rescale(); + }; + scale.ticks = function(m) { + return d3_scale_linearTicks(domain, m); + }; + scale.tickFormat = function(m, format) { + return d3_scale_linearTickFormat(domain, m, format); + }; + scale.nice = function() { + d3_scale_nice(domain, d3_scale_linearNice); + return rescale(); + }; + scale.copy = function() { + return d3_scale_linear(domain, range, interpolate, clamp); + }; + return rescale(); + } + function d3_scale_linearRebind(scale, linear) { + return d3.rebind(scale, linear, "range", "rangeRound", "interpolate", "clamp"); + } + function d3_scale_linearNice(dx) { + dx = Math.pow(10, Math.round(Math.log(dx) / Math.LN10) - 1); + return dx && { + floor: function(x) { + return Math.floor(x / dx) * dx; + }, + ceil: function(x) { + return Math.ceil(x / dx) * dx; + } + }; + } + function d3_scale_linearTickRange(domain, m) { + var extent = d3_scaleExtent(domain), span = extent[1] - extent[0], step = Math.pow(10, Math.floor(Math.log(span / m) / Math.LN10)), err = m / span * step; + if (err <= .15) step *= 10; else if (err <= .35) step *= 5; else if (err <= .75) step *= 2; + extent[0] = Math.ceil(extent[0] / step) * step; + extent[1] = Math.floor(extent[1] / step) * step + step * .5; + extent[2] = step; + return extent; + } + function d3_scale_linearTicks(domain, m) { + return d3.range.apply(d3, d3_scale_linearTickRange(domain, m)); + } + function d3_scale_linearTickFormat(domain, m, format) { + var precision = -Math.floor(Math.log(d3_scale_linearTickRange(domain, m)[2]) / Math.LN10 + .01); + return d3.format(format ? format.replace(d3_format_re, function(a, b, c, d, e, f, g, h, i, j) { + return [ b, c, d, e, f, g, h, i || "." + (precision - (j === "%") * 2), j ].join(""); + }) : ",." + precision + "f"); + } + d3.scale.log = function() { + return d3_scale_log(d3.scale.linear().domain([ 0, Math.LN10 ]), 10, d3_scale_logp, d3_scale_powp); + }; + function d3_scale_log(linear, base, log, pow) { + function scale(x) { + return linear(log(x)); + } + scale.invert = function(x) { + return pow(linear.invert(x)); + }; + scale.domain = function(x) { + if (!arguments.length) return linear.domain().map(pow); + if (x[0] < 0) log = d3_scale_logn, pow = d3_scale_pown; else log = d3_scale_logp, + pow = d3_scale_powp; + linear.domain(x.map(log)); + return scale; + }; + scale.base = function(_) { + if (!arguments.length) return base; + base = +_; + return scale; + }; + scale.nice = function() { + linear.domain(d3_scale_nice(linear.domain(), d3_scale_logNice(base))); + return scale; + }; + scale.ticks = function() { + var extent = d3_scaleExtent(linear.domain()), ticks = []; + if (extent.every(isFinite)) { + var b = Math.log(base), i = Math.floor(extent[0] / b), j = Math.ceil(extent[1] / b), u = pow(extent[0]), v = pow(extent[1]), n = base % 1 ? 2 : base; + if (log === d3_scale_logn) { + ticks.push(-Math.pow(base, -i)); + for (;i++ < j; ) for (var k = n - 1; k > 0; k--) ticks.push(-Math.pow(base, -i) * k); + } else { + for (;i < j; i++) for (var k = 1; k < n; k++) ticks.push(Math.pow(base, i) * k); + ticks.push(Math.pow(base, i)); + } + for (i = 0; ticks[i] < u; i++) {} + for (j = ticks.length; ticks[j - 1] > v; j--) {} + ticks = ticks.slice(i, j); + } + return ticks; + }; + scale.tickFormat = function(n, format) { + if (arguments.length < 2) format = d3_scale_logFormat; + if (!arguments.length) return format; + var b = Math.log(base), k = Math.max(.1, n / scale.ticks().length), f = log === d3_scale_logn ? (e = -1e-12, + Math.floor) : (e = 1e-12, Math.ceil), e; + return function(d) { + return d / pow(b * f(log(d) / b + e)) <= k ? format(d) : ""; + }; + }; + scale.copy = function() { + return d3_scale_log(linear.copy(), base, log, pow); + }; + return d3_scale_linearRebind(scale, linear); + } + var d3_scale_logFormat = d3.format(".0e"); + function d3_scale_logp(x) { + return Math.log(x < 0 ? 0 : x); + } + function d3_scale_powp(x) { + return Math.exp(x); + } + function d3_scale_logn(x) { + return -Math.log(x > 0 ? 0 : -x); + } + function d3_scale_pown(x) { + return -Math.exp(-x); + } + function d3_scale_logNice(base) { + base = Math.log(base); + var nice = { + floor: function(x) { + return Math.floor(x / base) * base; + }, + ceil: function(x) { + return Math.ceil(x / base) * base; + } + }; + return function() { + return nice; + }; + } + d3.scale.pow = function() { + return d3_scale_pow(d3.scale.linear(), 1); + }; + function d3_scale_pow(linear, exponent) { + var powp = d3_scale_powPow(exponent), powb = d3_scale_powPow(1 / exponent); + function scale(x) { + return linear(powp(x)); + } + scale.invert = function(x) { + return powb(linear.invert(x)); + }; + scale.domain = function(x) { + if (!arguments.length) return linear.domain().map(powb); + linear.domain(x.map(powp)); + return scale; + }; + scale.ticks = function(m) { + return d3_scale_linearTicks(scale.domain(), m); + }; + scale.tickFormat = function(m, format) { + return d3_scale_linearTickFormat(scale.domain(), m, format); + }; + scale.nice = function() { + return scale.domain(d3_scale_nice(scale.domain(), d3_scale_linearNice)); + }; + scale.exponent = function(x) { + if (!arguments.length) return exponent; + var domain = scale.domain(); + powp = d3_scale_powPow(exponent = x); + powb = d3_scale_powPow(1 / exponent); + return scale.domain(domain); + }; + scale.copy = function() { + return d3_scale_pow(linear.copy(), exponent); + }; + return d3_scale_linearRebind(scale, linear); + } + function d3_scale_powPow(e) { + return function(x) { + return x < 0 ? -Math.pow(-x, e) : Math.pow(x, e); + }; + } + d3.scale.sqrt = function() { + return d3.scale.pow().exponent(.5); + }; + d3.scale.ordinal = function() { + return d3_scale_ordinal([], { + t: "range", + a: [ [] ] + }); + }; + function d3_scale_ordinal(domain, ranger) { + var index, range, rangeBand; + function scale(x) { + return range[((index.get(x) || index.set(x, domain.push(x))) - 1) % range.length]; + } + function steps(start, step) { + return d3.range(domain.length).map(function(i) { + return start + step * i; + }); + } + scale.domain = function(x) { + if (!arguments.length) return domain; + domain = []; + index = new d3_Map(); + var i = -1, n = x.length, xi; + while (++i < n) if (!index.has(xi = x[i])) index.set(xi, domain.push(xi)); + return scale[ranger.t].apply(scale, ranger.a); + }; + scale.range = function(x) { + if (!arguments.length) return range; + range = x; + rangeBand = 0; + ranger = { + t: "range", + a: arguments + }; + return scale; + }; + scale.rangePoints = function(x, padding) { + if (arguments.length < 2) padding = 0; + var start = x[0], stop = x[1], step = (stop - start) / (Math.max(1, domain.length - 1) + padding); + range = steps(domain.length < 2 ? (start + stop) / 2 : start + step * padding / 2, step); + rangeBand = 0; + ranger = { + t: "rangePoints", + a: arguments + }; + return scale; + }; + scale.rangeBands = function(x, padding, outerPadding) { + if (arguments.length < 2) padding = 0; + if (arguments.length < 3) outerPadding = padding; + var reverse = x[1] < x[0], start = x[reverse - 0], stop = x[1 - reverse], step = (stop - start) / (domain.length - padding + 2 * outerPadding); + range = steps(start + step * outerPadding, step); + if (reverse) range.reverse(); + rangeBand = step * (1 - padding); + ranger = { + t: "rangeBands", + a: arguments + }; + return scale; + }; + scale.rangeRoundBands = function(x, padding, outerPadding) { + if (arguments.length < 2) padding = 0; + if (arguments.length < 3) outerPadding = padding; + var reverse = x[1] < x[0], start = x[reverse - 0], stop = x[1 - reverse], step = Math.floor((stop - start) / (domain.length - padding + 2 * outerPadding)), error = stop - start - (domain.length - padding) * step; + range = steps(start + Math.round(error / 2), step); + if (reverse) range.reverse(); + rangeBand = Math.round(step * (1 - padding)); + ranger = { + t: "rangeRoundBands", + a: arguments + }; + return scale; + }; + scale.rangeBand = function() { + return rangeBand; + }; + scale.rangeExtent = function() { + return d3_scaleExtent(ranger.a[0]); + }; + scale.copy = function() { + return d3_scale_ordinal(domain, ranger); + }; + return scale.domain(domain); + } + d3.scale.category10 = function() { + return d3.scale.ordinal().range(d3_category10); + }; + d3.scale.category20 = function() { + return d3.scale.ordinal().range(d3_category20); + }; + d3.scale.category20b = function() { + return d3.scale.ordinal().range(d3_category20b); + }; + d3.scale.category20c = function() { + return d3.scale.ordinal().range(d3_category20c); + }; + var d3_category10 = [ "#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", "#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf" ]; + var d3_category20 = [ "#1f77b4", "#aec7e8", "#ff7f0e", "#ffbb78", "#2ca02c", "#98df8a", "#d62728", "#ff9896", "#9467bd", "#c5b0d5", "#8c564b", "#c49c94", "#e377c2", "#f7b6d2", "#7f7f7f", "#c7c7c7", "#bcbd22", "#dbdb8d", "#17becf", "#9edae5" ]; + var d3_category20b = [ "#393b79", "#5254a3", "#6b6ecf", "#9c9ede", "#637939", "#8ca252", "#b5cf6b", "#cedb9c", "#8c6d31", "#bd9e39", "#e7ba52", "#e7cb94", "#843c39", "#ad494a", "#d6616b", "#e7969c", "#7b4173", "#a55194", "#ce6dbd", "#de9ed6" ]; + var d3_category20c = [ "#3182bd", "#6baed6", "#9ecae1", "#c6dbef", "#e6550d", "#fd8d3c", "#fdae6b", "#fdd0a2", "#31a354", "#74c476", "#a1d99b", "#c7e9c0", "#756bb1", "#9e9ac8", "#bcbddc", "#dadaeb", "#636363", "#969696", "#bdbdbd", "#d9d9d9" ]; + d3.scale.quantile = function() { + return d3_scale_quantile([], []); + }; + function d3_scale_quantile(domain, range) { + var thresholds; + function rescale() { + var k = 0, q = range.length; + thresholds = []; + while (++k < q) thresholds[k - 1] = d3.quantile(domain, k / q); + return scale; + } + function scale(x) { + if (isNaN(x = +x)) return NaN; + return range[d3.bisect(thresholds, x)]; + } + scale.domain = function(x) { + if (!arguments.length) return domain; + domain = x.filter(function(d) { + return !isNaN(d); + }).sort(d3.ascending); + return rescale(); + }; + scale.range = function(x) { + if (!arguments.length) return range; + range = x; + return rescale(); + }; + scale.quantiles = function() { + return thresholds; + }; + scale.copy = function() { + return d3_scale_quantile(domain, range); + }; + return rescale(); + } + d3.scale.quantize = function() { + return d3_scale_quantize(0, 1, [ 0, 1 ]); + }; + function d3_scale_quantize(x0, x1, range) { + var kx, i; + function scale(x) { + return range[Math.max(0, Math.min(i, Math.floor(kx * (x - x0))))]; + } + function rescale() { + kx = range.length / (x1 - x0); + i = range.length - 1; + return scale; + } + scale.domain = function(x) { + if (!arguments.length) return [ x0, x1 ]; + x0 = +x[0]; + x1 = +x[x.length - 1]; + return rescale(); + }; + scale.range = function(x) { + if (!arguments.length) return range; + range = x; + return rescale(); + }; + scale.copy = function() { + return d3_scale_quantize(x0, x1, range); + }; + return rescale(); + } + d3.scale.threshold = function() { + return d3_scale_threshold([ .5 ], [ 0, 1 ]); + }; + function d3_scale_threshold(domain, range) { + function scale(x) { + return range[d3.bisect(domain, x)]; + } + scale.domain = function(_) { + if (!arguments.length) return domain; + domain = _; + return scale; + }; + scale.range = function(_) { + if (!arguments.length) return range; + range = _; + return scale; + }; + scale.copy = function() { + return d3_scale_threshold(domain, range); + }; + return scale; + } + d3.scale.identity = function() { + return d3_scale_identity([ 0, 1 ]); + }; + function d3_scale_identity(domain) { + function identity(x) { + return +x; + } + identity.invert = identity; + identity.domain = identity.range = function(x) { + if (!arguments.length) return domain; + domain = x.map(identity); + return identity; + }; + identity.ticks = function(m) { + return d3_scale_linearTicks(domain, m); + }; + identity.tickFormat = function(m, format) { + return d3_scale_linearTickFormat(domain, m, format); + }; + identity.copy = function() { + return d3_scale_identity(domain); + }; + return identity; + } + d3.svg.arc = function() { + var innerRadius = d3_svg_arcInnerRadius, outerRadius = d3_svg_arcOuterRadius, startAngle = d3_svg_arcStartAngle, endAngle = d3_svg_arcEndAngle; + function arc() { + var r0 = innerRadius.apply(this, arguments), r1 = outerRadius.apply(this, arguments), a0 = startAngle.apply(this, arguments) + d3_svg_arcOffset, a1 = endAngle.apply(this, arguments) + d3_svg_arcOffset, da = (a1 < a0 && (da = a0, + a0 = a1, a1 = da), a1 - a0), df = da < π ? "0" : "1", c0 = Math.cos(a0), s0 = Math.sin(a0), c1 = Math.cos(a1), s1 = Math.sin(a1); + return da >= d3_svg_arcMax ? r0 ? "M0," + r1 + "A" + r1 + "," + r1 + " 0 1,1 0," + -r1 + "A" + r1 + "," + r1 + " 0 1,1 0," + r1 + "M0," + r0 + "A" + r0 + "," + r0 + " 0 1,0 0," + -r0 + "A" + r0 + "," + r0 + " 0 1,0 0," + r0 + "Z" : "M0," + r1 + "A" + r1 + "," + r1 + " 0 1,1 0," + -r1 + "A" + r1 + "," + r1 + " 0 1,1 0," + r1 + "Z" : r0 ? "M" + r1 * c0 + "," + r1 * s0 + "A" + r1 + "," + r1 + " 0 " + df + ",1 " + r1 * c1 + "," + r1 * s1 + "L" + r0 * c1 + "," + r0 * s1 + "A" + r0 + "," + r0 + " 0 " + df + ",0 " + r0 * c0 + "," + r0 * s0 + "Z" : "M" + r1 * c0 + "," + r1 * s0 + "A" + r1 + "," + r1 + " 0 " + df + ",1 " + r1 * c1 + "," + r1 * s1 + "L0,0" + "Z"; + } + arc.innerRadius = function(v) { + if (!arguments.length) return innerRadius; + innerRadius = d3_functor(v); + return arc; + }; + arc.outerRadius = function(v) { + if (!arguments.length) return outerRadius; + outerRadius = d3_functor(v); + return arc; + }; + arc.startAngle = function(v) { + if (!arguments.length) return startAngle; + startAngle = d3_functor(v); + return arc; + }; + arc.endAngle = function(v) { + if (!arguments.length) return endAngle; + endAngle = d3_functor(v); + return arc; + }; + arc.centroid = function() { + var r = (innerRadius.apply(this, arguments) + outerRadius.apply(this, arguments)) / 2, a = (startAngle.apply(this, arguments) + endAngle.apply(this, arguments)) / 2 + d3_svg_arcOffset; + return [ Math.cos(a) * r, Math.sin(a) * r ]; + }; + return arc; + }; + var d3_svg_arcOffset = -π / 2, d3_svg_arcMax = 2 * π - 1e-6; + function d3_svg_arcInnerRadius(d) { + return d.innerRadius; + } + function d3_svg_arcOuterRadius(d) { + return d.outerRadius; + } + function d3_svg_arcStartAngle(d) { + return d.startAngle; + } + function d3_svg_arcEndAngle(d) { + return d.endAngle; + } + d3.svg.line.radial = function() { + var line = d3_svg_line(d3_svg_lineRadial); + line.radius = line.x, delete line.x; + line.angle = line.y, delete line.y; + return line; + }; + function d3_svg_lineRadial(points) { + var point, i = -1, n = points.length, r, a; + while (++i < n) { + point = points[i]; + r = point[0]; + a = point[1] + d3_svg_arcOffset; + point[0] = r * Math.cos(a); + point[1] = r * Math.sin(a); + } + return points; + } + function d3_svg_area(projection) { + var x0 = d3_svg_lineX, x1 = d3_svg_lineX, y0 = 0, y1 = d3_svg_lineY, defined = d3_true, interpolate = d3_svg_lineLinear, interpolateKey = interpolate.key, interpolateReverse = interpolate, L = "L", tension = .7; + function area(data) { + var segments = [], points0 = [], points1 = [], i = -1, n = data.length, d, fx0 = d3_functor(x0), fy0 = d3_functor(y0), fx1 = x0 === x1 ? function() { + return x; + } : d3_functor(x1), fy1 = y0 === y1 ? function() { + return y; + } : d3_functor(y1), x, y; + function segment() { + segments.push("M", interpolate(projection(points1), tension), L, interpolateReverse(projection(points0.reverse()), tension), "Z"); + } + while (++i < n) { + if (defined.call(this, d = data[i], i)) { + points0.push([ x = +fx0.call(this, d, i), y = +fy0.call(this, d, i) ]); + points1.push([ +fx1.call(this, d, i), +fy1.call(this, d, i) ]); + } else if (points0.length) { + segment(); + points0 = []; + points1 = []; + } + } + if (points0.length) segment(); + return segments.length ? segments.join("") : null; + } + area.x = function(_) { + if (!arguments.length) return x1; + x0 = x1 = _; + return area; + }; + area.x0 = function(_) { + if (!arguments.length) return x0; + x0 = _; + return area; + }; + area.x1 = function(_) { + if (!arguments.length) return x1; + x1 = _; + return area; + }; + area.y = function(_) { + if (!arguments.length) return y1; + y0 = y1 = _; + return area; + }; + area.y0 = function(_) { + if (!arguments.length) return y0; + y0 = _; + return area; + }; + area.y1 = function(_) { + if (!arguments.length) return y1; + y1 = _; + return area; + }; + area.defined = function(_) { + if (!arguments.length) return defined; + defined = _; + return area; + }; + area.interpolate = function(_) { + if (!arguments.length) return interpolateKey; + if (typeof _ === "function") interpolateKey = interpolate = _; else interpolateKey = (interpolate = d3_svg_lineInterpolators.get(_) || d3_svg_lineLinear).key; + interpolateReverse = interpolate.reverse || interpolate; + L = interpolate.closed ? "M" : "L"; + return area; + }; + area.tension = function(_) { + if (!arguments.length) return tension; + tension = _; + return area; + }; + return area; + } + d3_svg_lineStepBefore.reverse = d3_svg_lineStepAfter; + d3_svg_lineStepAfter.reverse = d3_svg_lineStepBefore; + d3.svg.area = function() { + return d3_svg_area(d3_identity); + }; + d3.svg.area.radial = function() { + var area = d3_svg_area(d3_svg_lineRadial); + area.radius = area.x, delete area.x; + area.innerRadius = area.x0, delete area.x0; + area.outerRadius = area.x1, delete area.x1; + area.angle = area.y, delete area.y; + area.startAngle = area.y0, delete area.y0; + area.endAngle = area.y1, delete area.y1; + return area; + }; + d3.svg.chord = function() { + var source = d3_source, target = d3_target, radius = d3_svg_chordRadius, startAngle = d3_svg_arcStartAngle, endAngle = d3_svg_arcEndAngle; + function chord(d, i) { + var s = subgroup(this, source, d, i), t = subgroup(this, target, d, i); + return "M" + s.p0 + arc(s.r, s.p1, s.a1 - s.a0) + (equals(s, t) ? curve(s.r, s.p1, s.r, s.p0) : curve(s.r, s.p1, t.r, t.p0) + arc(t.r, t.p1, t.a1 - t.a0) + curve(t.r, t.p1, s.r, s.p0)) + "Z"; + } + function subgroup(self, f, d, i) { + var subgroup = f.call(self, d, i), r = radius.call(self, subgroup, i), a0 = startAngle.call(self, subgroup, i) + d3_svg_arcOffset, a1 = endAngle.call(self, subgroup, i) + d3_svg_arcOffset; + return { + r: r, + a0: a0, + a1: a1, + p0: [ r * Math.cos(a0), r * Math.sin(a0) ], + p1: [ r * Math.cos(a1), r * Math.sin(a1) ] + }; + } + function equals(a, b) { + return a.a0 == b.a0 && a.a1 == b.a1; + } + function arc(r, p, a) { + return "A" + r + "," + r + " 0 " + +(a > π) + ",1 " + p; + } + function curve(r0, p0, r1, p1) { + return "Q 0,0 " + p1; + } + chord.radius = function(v) { + if (!arguments.length) return radius; + radius = d3_functor(v); + return chord; + }; + chord.source = function(v) { + if (!arguments.length) return source; + source = d3_functor(v); + return chord; + }; + chord.target = function(v) { + if (!arguments.length) return target; + target = d3_functor(v); + return chord; + }; + chord.startAngle = function(v) { + if (!arguments.length) return startAngle; + startAngle = d3_functor(v); + return chord; + }; + chord.endAngle = function(v) { + if (!arguments.length) return endAngle; + endAngle = d3_functor(v); + return chord; + }; + return chord; + }; + function d3_svg_chordRadius(d) { + return d.radius; + } + d3.svg.diagonal = function() { + var source = d3_source, target = d3_target, projection = d3_svg_diagonalProjection; + function diagonal(d, i) { + var p0 = source.call(this, d, i), p3 = target.call(this, d, i), m = (p0.y + p3.y) / 2, p = [ p0, { + x: p0.x, + y: m + }, { + x: p3.x, + y: m + }, p3 ]; + p = p.map(projection); + return "M" + p[0] + "C" + p[1] + " " + p[2] + " " + p[3]; + } + diagonal.source = function(x) { + if (!arguments.length) return source; + source = d3_functor(x); + return diagonal; + }; + diagonal.target = function(x) { + if (!arguments.length) return target; + target = d3_functor(x); + return diagonal; + }; + diagonal.projection = function(x) { + if (!arguments.length) return projection; + projection = x; + return diagonal; + }; + return diagonal; + }; + function d3_svg_diagonalProjection(d) { + return [ d.x, d.y ]; + } + d3.svg.diagonal.radial = function() { + var diagonal = d3.svg.diagonal(), projection = d3_svg_diagonalProjection, projection_ = diagonal.projection; + diagonal.projection = function(x) { + return arguments.length ? projection_(d3_svg_diagonalRadialProjection(projection = x)) : projection; + }; + return diagonal; + }; + function d3_svg_diagonalRadialProjection(projection) { + return function() { + var d = projection.apply(this, arguments), r = d[0], a = d[1] + d3_svg_arcOffset; + return [ r * Math.cos(a), r * Math.sin(a) ]; + }; + } + d3.svg.symbol = function() { + var type = d3_svg_symbolType, size = d3_svg_symbolSize; + function symbol(d, i) { + return (d3_svg_symbols.get(type.call(this, d, i)) || d3_svg_symbolCircle)(size.call(this, d, i)); + } + symbol.type = function(x) { + if (!arguments.length) return type; + type = d3_functor(x); + return symbol; + }; + symbol.size = function(x) { + if (!arguments.length) return size; + size = d3_functor(x); + return symbol; + }; + return symbol; + }; + function d3_svg_symbolSize() { + return 64; + } + function d3_svg_symbolType() { + return "circle"; + } + function d3_svg_symbolCircle(size) { + var r = Math.sqrt(size / π); + return "M0," + r + "A" + r + "," + r + " 0 1,1 0," + -r + "A" + r + "," + r + " 0 1,1 0," + r + "Z"; + } + var d3_svg_symbols = d3.map({ + circle: d3_svg_symbolCircle, + cross: function(size) { + var r = Math.sqrt(size / 5) / 2; + return "M" + -3 * r + "," + -r + "H" + -r + "V" + -3 * r + "H" + r + "V" + -r + "H" + 3 * r + "V" + r + "H" + r + "V" + 3 * r + "H" + -r + "V" + r + "H" + -3 * r + "Z"; + }, + diamond: function(size) { + var ry = Math.sqrt(size / (2 * d3_svg_symbolTan30)), rx = ry * d3_svg_symbolTan30; + return "M0," + -ry + "L" + rx + ",0" + " 0," + ry + " " + -rx + ",0" + "Z"; + }, + square: function(size) { + var r = Math.sqrt(size) / 2; + return "M" + -r + "," + -r + "L" + r + "," + -r + " " + r + "," + r + " " + -r + "," + r + "Z"; + }, + "triangle-down": function(size) { + var rx = Math.sqrt(size / d3_svg_symbolSqrt3), ry = rx * d3_svg_symbolSqrt3 / 2; + return "M0," + ry + "L" + rx + "," + -ry + " " + -rx + "," + -ry + "Z"; + }, + "triangle-up": function(size) { + var rx = Math.sqrt(size / d3_svg_symbolSqrt3), ry = rx * d3_svg_symbolSqrt3 / 2; + return "M0," + -ry + "L" + rx + "," + ry + " " + -rx + "," + ry + "Z"; + } + }); + d3.svg.symbolTypes = d3_svg_symbols.keys(); + var d3_svg_symbolSqrt3 = Math.sqrt(3), d3_svg_symbolTan30 = Math.tan(30 * d3_radians); + function d3_transition(groups, id) { + d3_arraySubclass(groups, d3_transitionPrototype); + groups.id = id; + return groups; + } + var d3_transitionPrototype = [], d3_transitionId = 0, d3_transitionInheritId, d3_transitionInherit = { + ease: d3_ease_cubicInOut, + delay: 0, + duration: 250 + }; + d3_transitionPrototype.call = d3_selectionPrototype.call; + d3_transitionPrototype.empty = d3_selectionPrototype.empty; + d3_transitionPrototype.node = d3_selectionPrototype.node; + d3.transition = function(selection) { + return arguments.length ? d3_transitionInheritId ? selection.transition() : selection : d3_selectionRoot.transition(); + }; + d3.transition.prototype = d3_transitionPrototype; + d3_transitionPrototype.select = function(selector) { + var id = this.id, subgroups = [], subgroup, subnode, node; + if (typeof selector !== "function") selector = d3_selection_selector(selector); + for (var j = -1, m = this.length; ++j < m; ) { + subgroups.push(subgroup = []); + for (var group = this[j], i = -1, n = group.length; ++i < n; ) { + if ((node = group[i]) && (subnode = selector.call(node, node.__data__, i))) { + if ("__data__" in node) subnode.__data__ = node.__data__; + d3_transitionNode(subnode, i, id, node.__transition__[id]); + subgroup.push(subnode); + } else { + subgroup.push(null); + } + } + } + return d3_transition(subgroups, id); + }; + d3_transitionPrototype.selectAll = function(selector) { + var id = this.id, subgroups = [], subgroup, subnodes, node, subnode, transition; + if (typeof selector !== "function") selector = d3_selection_selectorAll(selector); + for (var j = -1, m = this.length; ++j < m; ) { + for (var group = this[j], i = -1, n = group.length; ++i < n; ) { + if (node = group[i]) { + transition = node.__transition__[id]; + subnodes = selector.call(node, node.__data__, i); + subgroups.push(subgroup = []); + for (var k = -1, o = subnodes.length; ++k < o; ) { + d3_transitionNode(subnode = subnodes[k], k, id, transition); + subgroup.push(subnode); + } + } + } + } + return d3_transition(subgroups, id); + }; + d3_transitionPrototype.filter = function(filter) { + var subgroups = [], subgroup, group, node; + if (typeof filter !== "function") filter = d3_selection_filter(filter); + for (var j = 0, m = this.length; j < m; j++) { + subgroups.push(subgroup = []); + for (var group = this[j], i = 0, n = group.length; i < n; i++) { + if ((node = group[i]) && filter.call(node, node.__data__, i)) { + subgroup.push(node); + } + } + } + return d3_transition(subgroups, this.id, this.time).ease(this.ease()); + }; + d3_transitionPrototype.tween = function(name, tween) { + var id = this.id; + if (arguments.length < 2) return this.node().__transition__[id].tween.get(name); + return d3_selection_each(this, tween == null ? function(node) { + node.__transition__[id].tween.remove(name); + } : function(node) { + node.__transition__[id].tween.set(name, tween); + }); + }; + function d3_transition_tween(groups, name, value, tween) { + var id = groups.id; + return d3_selection_each(groups, typeof value === "function" ? function(node, i, j) { + node.__transition__[id].tween.set(name, tween(value.call(node, node.__data__, i, j))); + } : (value = tween(value), function(node) { + node.__transition__[id].tween.set(name, value); + })); + } + d3_transitionPrototype.attr = function(nameNS, value) { + if (arguments.length < 2) { + for (value in nameNS) this.attr(value, nameNS[value]); + return this; + } + var interpolate = d3_interpolateByName(nameNS), name = d3.ns.qualify(nameNS); + function attrNull() { + this.removeAttribute(name); + } + function attrNullNS() { + this.removeAttributeNS(name.space, name.local); + } + return d3_transition_tween(this, "attr." + nameNS, value, function(b) { + function attrString() { + var a = this.getAttribute(name), i; + return a !== b && (i = interpolate(a, b), function(t) { + this.setAttribute(name, i(t)); + }); + } + function attrStringNS() { + var a = this.getAttributeNS(name.space, name.local), i; + return a !== b && (i = interpolate(a, b), function(t) { + this.setAttributeNS(name.space, name.local, i(t)); + }); + } + return b == null ? name.local ? attrNullNS : attrNull : (b += "", name.local ? attrStringNS : attrString); + }); + }; + d3_transitionPrototype.attrTween = function(nameNS, tween) { + var name = d3.ns.qualify(nameNS); + function attrTween(d, i) { + var f = tween.call(this, d, i, this.getAttribute(name)); + return f && function(t) { + this.setAttribute(name, f(t)); + }; + } + function attrTweenNS(d, i) { + var f = tween.call(this, d, i, this.getAttributeNS(name.space, name.local)); + return f && function(t) { + this.setAttributeNS(name.space, name.local, f(t)); + }; + } + return this.tween("attr." + nameNS, name.local ? attrTweenNS : attrTween); + }; + d3_transitionPrototype.style = function(name, value, priority) { + var n = arguments.length; + if (n < 3) { + if (typeof name !== "string") { + if (n < 2) value = ""; + for (priority in name) this.style(priority, name[priority], value); + return this; + } + priority = ""; + } + var interpolate = d3_interpolateByName(name); + function styleNull() { + this.style.removeProperty(name); + } + return d3_transition_tween(this, "style." + name, value, function(b) { + function styleString() { + var a = d3_window.getComputedStyle(this, null).getPropertyValue(name), i; + return a !== b && (i = interpolate(a, b), function(t) { + this.style.setProperty(name, i(t), priority); + }); + } + return b == null ? styleNull : (b += "", styleString); + }); + }; + d3_transitionPrototype.styleTween = function(name, tween, priority) { + if (arguments.length < 3) priority = ""; + return this.tween("style." + name, function(d, i) { + var f = tween.call(this, d, i, d3_window.getComputedStyle(this, null).getPropertyValue(name)); + return f && function(t) { + this.style.setProperty(name, f(t), priority); + }; + }); + }; + d3_transitionPrototype.text = function(value) { + return d3_transition_tween(this, "text", value, d3_transition_text); + }; + function d3_transition_text(b) { + if (b == null) b = ""; + return function() { + this.textContent = b; + }; + } + d3_transitionPrototype.remove = function() { + return this.each("end.transition", function() { + var p; + if (!this.__transition__ && (p = this.parentNode)) p.removeChild(this); + }); + }; + d3_transitionPrototype.ease = function(value) { + var id = this.id; + if (arguments.length < 1) return this.node().__transition__[id].ease; + if (typeof value !== "function") value = d3.ease.apply(d3, arguments); + return d3_selection_each(this, function(node) { + node.__transition__[id].ease = value; + }); + }; + d3_transitionPrototype.delay = function(value) { + var id = this.id; + return d3_selection_each(this, typeof value === "function" ? function(node, i, j) { + node.__transition__[id].delay = value.call(node, node.__data__, i, j) | 0; + } : (value |= 0, function(node) { + node.__transition__[id].delay = value; + })); + }; + d3_transitionPrototype.duration = function(value) { + var id = this.id; + return d3_selection_each(this, typeof value === "function" ? function(node, i, j) { + node.__transition__[id].duration = Math.max(1, value.call(node, node.__data__, i, j) | 0); + } : (value = Math.max(1, value | 0), function(node) { + node.__transition__[id].duration = value; + })); + }; + d3_transitionPrototype.each = function(type, listener) { + var id = this.id; + if (arguments.length < 2) { + var inherit = d3_transitionInherit, inheritId = d3_transitionInheritId; + d3_transitionInheritId = id; + d3_selection_each(this, function(node, i, j) { + d3_transitionInherit = node.__transition__[id]; + type.call(node, node.__data__, i, j); + }); + d3_transitionInherit = inherit; + d3_transitionInheritId = inheritId; + } else { + d3_selection_each(this, function(node) { + node.__transition__[id].event.on(type, listener); + }); + } + return this; + }; + d3_transitionPrototype.transition = function() { + var id0 = this.id, id1 = ++d3_transitionId, subgroups = [], subgroup, group, node, transition; + for (var j = 0, m = this.length; j < m; j++) { + subgroups.push(subgroup = []); + for (var group = this[j], i = 0, n = group.length; i < n; i++) { + if (node = group[i]) { + transition = Object.create(node.__transition__[id0]); + transition.delay += transition.duration; + d3_transitionNode(node, i, id1, transition); + } + subgroup.push(node); + } + } + return d3_transition(subgroups, id1); + }; + function d3_transitionNode(node, i, id, inherit) { + var lock = node.__transition__ || (node.__transition__ = { + active: 0, + count: 0 + }), transition = lock[id]; + if (!transition) { + var time = inherit.time; + transition = lock[id] = { + tween: new d3_Map(), + event: d3.dispatch("start", "end"), + time: time, + ease: inherit.ease, + delay: inherit.delay, + duration: inherit.duration + }; + ++lock.count; + d3.timer(function(elapsed) { + var d = node.__data__, ease = transition.ease, event = transition.event, delay = transition.delay, duration = transition.duration, tweened = []; + return delay <= elapsed ? start(elapsed) : d3.timer(start, delay, time), 1; + function start(elapsed) { + if (lock.active > id) return stop(); + lock.active = id; + event.start.call(node, d, i); + transition.tween.forEach(function(key, value) { + if (value = value.call(node, d, i)) { + tweened.push(value); + } + }); + if (!tick(elapsed)) d3.timer(tick, 0, time); + return 1; + } + function tick(elapsed) { + if (lock.active !== id) return stop(); + var t = (elapsed - delay) / duration, e = ease(t), n = tweened.length; + while (n > 0) { + tweened[--n].call(node, e); + } + if (t >= 1) { + stop(); + event.end.call(node, d, i); + return 1; + } + } + function stop() { + if (--lock.count) delete lock[id]; else delete node.__transition__; + return 1; + } + }, 0, time); + return transition; + } + } + d3.svg.axis = function() { + var scale = d3.scale.linear(), orient = d3_svg_axisDefaultOrient, tickMajorSize = 6, tickMinorSize = 6, tickEndSize = 6, tickPadding = 3, tickArguments_ = [ 10 ], tickValues = null, tickFormat_, tickSubdivide = 0; + function axis(g) { + g.each(function() { + var g = d3.select(this); + var ticks = tickValues == null ? scale.ticks ? scale.ticks.apply(scale, tickArguments_) : scale.domain() : tickValues, tickFormat = tickFormat_ == null ? scale.tickFormat ? scale.tickFormat.apply(scale, tickArguments_) : String : tickFormat_; + var subticks = d3_svg_axisSubdivide(scale, ticks, tickSubdivide), subtick = g.selectAll(".tick.minor").data(subticks, String), subtickEnter = subtick.enter().insert("line", ".tick").attr("class", "tick minor").style("opacity", 1e-6), subtickExit = d3.transition(subtick.exit()).style("opacity", 1e-6).remove(), subtickUpdate = d3.transition(subtick).style("opacity", 1); + var tick = g.selectAll(".tick.major").data(ticks, String), tickEnter = tick.enter().insert("g", "path").attr("class", "tick major").style("opacity", 1e-6), tickExit = d3.transition(tick.exit()).style("opacity", 1e-6).remove(), tickUpdate = d3.transition(tick).style("opacity", 1), tickTransform; + var range = d3_scaleRange(scale), path = g.selectAll(".domain").data([ 0 ]), pathUpdate = (path.enter().append("path").attr("class", "domain"), + d3.transition(path)); + var scale1 = scale.copy(), scale0 = this.__chart__ || scale1; + this.__chart__ = scale1; + tickEnter.append("line"); + tickEnter.append("text"); + var lineEnter = tickEnter.select("line"), lineUpdate = tickUpdate.select("line"), text = tick.select("text").text(tickFormat), textEnter = tickEnter.select("text"), textUpdate = tickUpdate.select("text"); + switch (orient) { + case "bottom": + { + tickTransform = d3_svg_axisX; + subtickEnter.attr("y2", tickMinorSize); + subtickUpdate.attr("x2", 0).attr("y2", tickMinorSize); + lineEnter.attr("y2", tickMajorSize); + textEnter.attr("y", Math.max(tickMajorSize, 0) + tickPadding); + lineUpdate.attr("x2", 0).attr("y2", tickMajorSize); + textUpdate.attr("x", 0).attr("y", Math.max(tickMajorSize, 0) + tickPadding); + text.attr("dy", ".71em").style("text-anchor", "middle"); + pathUpdate.attr("d", "M" + range[0] + "," + tickEndSize + "V0H" + range[1] + "V" + tickEndSize); + break; + } + + case "top": + { + tickTransform = d3_svg_axisX; + subtickEnter.attr("y2", -tickMinorSize); + subtickUpdate.attr("x2", 0).attr("y2", -tickMinorSize); + lineEnter.attr("y2", -tickMajorSize); + textEnter.attr("y", -(Math.max(tickMajorSize, 0) + tickPadding)); + lineUpdate.attr("x2", 0).attr("y2", -tickMajorSize); + textUpdate.attr("x", 0).attr("y", -(Math.max(tickMajorSize, 0) + tickPadding)); + text.attr("dy", "0em").style("text-anchor", "middle"); + pathUpdate.attr("d", "M" + range[0] + "," + -tickEndSize + "V0H" + range[1] + "V" + -tickEndSize); + break; + } + + case "left": + { + tickTransform = d3_svg_axisY; + subtickEnter.attr("x2", -tickMinorSize); + subtickUpdate.attr("x2", -tickMinorSize).attr("y2", 0); + lineEnter.attr("x2", -tickMajorSize); + textEnter.attr("x", -(Math.max(tickMajorSize, 0) + tickPadding)); + lineUpdate.attr("x2", -tickMajorSize).attr("y2", 0); + textUpdate.attr("x", -(Math.max(tickMajorSize, 0) + tickPadding)).attr("y", 0); + text.attr("dy", ".32em").style("text-anchor", "end"); + pathUpdate.attr("d", "M" + -tickEndSize + "," + range[0] + "H0V" + range[1] + "H" + -tickEndSize); + break; + } + + case "right": + { + tickTransform = d3_svg_axisY; + subtickEnter.attr("x2", tickMinorSize); + subtickUpdate.attr("x2", tickMinorSize).attr("y2", 0); + lineEnter.attr("x2", tickMajorSize); + textEnter.attr("x", Math.max(tickMajorSize, 0) + tickPadding); + lineUpdate.attr("x2", tickMajorSize).attr("y2", 0); + textUpdate.attr("x", Math.max(tickMajorSize, 0) + tickPadding).attr("y", 0); + text.attr("dy", ".32em").style("text-anchor", "start"); + pathUpdate.attr("d", "M" + tickEndSize + "," + range[0] + "H0V" + range[1] + "H" + tickEndSize); + break; + } + } + if (scale.ticks) { + tickEnter.call(tickTransform, scale0); + tickUpdate.call(tickTransform, scale1); + tickExit.call(tickTransform, scale1); + subtickEnter.call(tickTransform, scale0); + subtickUpdate.call(tickTransform, scale1); + subtickExit.call(tickTransform, scale1); + } else { + var dx = scale1.rangeBand() / 2, x = function(d) { + return scale1(d) + dx; + }; + tickEnter.call(tickTransform, x); + tickUpdate.call(tickTransform, x); + } + }); + } + axis.scale = function(x) { + if (!arguments.length) return scale; + scale = x; + return axis; + }; + axis.orient = function(x) { + if (!arguments.length) return orient; + orient = x in d3_svg_axisOrients ? x + "" : d3_svg_axisDefaultOrient; + return axis; + }; + axis.ticks = function() { + if (!arguments.length) return tickArguments_; + tickArguments_ = arguments; + return axis; + }; + axis.tickValues = function(x) { + if (!arguments.length) return tickValues; + tickValues = x; + return axis; + }; + axis.tickFormat = function(x) { + if (!arguments.length) return tickFormat_; + tickFormat_ = x; + return axis; + }; + axis.tickSize = function(x, y) { + if (!arguments.length) return tickMajorSize; + var n = arguments.length - 1; + tickMajorSize = +x; + tickMinorSize = n > 1 ? +y : tickMajorSize; + tickEndSize = n > 0 ? +arguments[n] : tickMajorSize; + return axis; + }; + axis.tickPadding = function(x) { + if (!arguments.length) return tickPadding; + tickPadding = +x; + return axis; + }; + axis.tickSubdivide = function(x) { + if (!arguments.length) return tickSubdivide; + tickSubdivide = +x; + return axis; + }; + return axis; + }; + var d3_svg_axisDefaultOrient = "bottom", d3_svg_axisOrients = { + top: 1, + right: 1, + bottom: 1, + left: 1 + }; + function d3_svg_axisX(selection, x) { + selection.attr("transform", function(d) { + return "translate(" + x(d) + ",0)"; + }); + } + function d3_svg_axisY(selection, y) { + selection.attr("transform", function(d) { + return "translate(0," + y(d) + ")"; + }); + } + function d3_svg_axisSubdivide(scale, ticks, m) { + subticks = []; + if (m && ticks.length > 1) { + var extent = d3_scaleExtent(scale.domain()), subticks, i = -1, n = ticks.length, d = (ticks[1] - ticks[0]) / ++m, j, v; + while (++i < n) { + for (j = m; --j > 0; ) { + if ((v = +ticks[i] - j * d) >= extent[0]) { + subticks.push(v); + } + } + } + for (--i, j = 0; ++j < m && (v = +ticks[i] + j * d) < extent[1]; ) { + subticks.push(v); + } + } + return subticks; + } + d3.svg.brush = function() { + var event = d3_eventDispatch(brush, "brushstart", "brush", "brushend"), x = null, y = null, resizes = d3_svg_brushResizes[0], extent = [ [ 0, 0 ], [ 0, 0 ] ], extentDomain; + function brush(g) { + g.each(function() { + var g = d3.select(this), bg = g.selectAll(".background").data([ 0 ]), fg = g.selectAll(".extent").data([ 0 ]), tz = g.selectAll(".resize").data(resizes, String), e; + g.style("pointer-events", "all").on("mousedown.brush", brushstart).on("touchstart.brush", brushstart); + bg.enter().append("rect").attr("class", "background").style("visibility", "hidden").style("cursor", "crosshair"); + fg.enter().append("rect").attr("class", "extent").style("cursor", "move"); + tz.enter().append("g").attr("class", function(d) { + return "resize " + d; + }).style("cursor", function(d) { + return d3_svg_brushCursor[d]; + }).append("rect").attr("x", function(d) { + return /[ew]$/.test(d) ? -3 : null; + }).attr("y", function(d) { + return /^[ns]/.test(d) ? -3 : null; + }).attr("width", 6).attr("height", 6).style("visibility", "hidden"); + tz.style("display", brush.empty() ? "none" : null); + tz.exit().remove(); + if (x) { + e = d3_scaleRange(x); + bg.attr("x", e[0]).attr("width", e[1] - e[0]); + redrawX(g); + } + if (y) { + e = d3_scaleRange(y); + bg.attr("y", e[0]).attr("height", e[1] - e[0]); + redrawY(g); + } + redraw(g); + }); + } + function redraw(g) { + g.selectAll(".resize").attr("transform", function(d) { + return "translate(" + extent[+/e$/.test(d)][0] + "," + extent[+/^s/.test(d)][1] + ")"; + }); + } + function redrawX(g) { + g.select(".extent").attr("x", extent[0][0]); + g.selectAll(".extent,.n>rect,.s>rect").attr("width", extent[1][0] - extent[0][0]); + } + function redrawY(g) { + g.select(".extent").attr("y", extent[0][1]); + g.selectAll(".extent,.e>rect,.w>rect").attr("height", extent[1][1] - extent[0][1]); + } + function brushstart() { + var target = this, eventTarget = d3.select(d3.event.target), event_ = event.of(target, arguments), g = d3.select(target), resizing = eventTarget.datum(), resizingX = !/^(n|s)$/.test(resizing) && x, resizingY = !/^(e|w)$/.test(resizing) && y, dragging = eventTarget.classed("extent"), center, origin = mouse(), offset; + var w = d3.select(d3_window).on("mousemove.brush", brushmove).on("mouseup.brush", brushend).on("touchmove.brush", brushmove).on("touchend.brush", brushend).on("keydown.brush", keydown).on("keyup.brush", keyup); + if (dragging) { + origin[0] = extent[0][0] - origin[0]; + origin[1] = extent[0][1] - origin[1]; + } else if (resizing) { + var ex = +/w$/.test(resizing), ey = +/^n/.test(resizing); + offset = [ extent[1 - ex][0] - origin[0], extent[1 - ey][1] - origin[1] ]; + origin[0] = extent[ex][0]; + origin[1] = extent[ey][1]; + } else if (d3.event.altKey) center = origin.slice(); + g.style("pointer-events", "none").selectAll(".resize").style("display", null); + d3.select("body").style("cursor", eventTarget.style("cursor")); + event_({ + type: "brushstart" + }); + brushmove(); + d3_eventCancel(); + function mouse() { + var touches = d3.event.changedTouches; + return touches ? d3.touches(target, touches)[0] : d3.mouse(target); + } + function keydown() { + if (d3.event.keyCode == 32) { + if (!dragging) { + center = null; + origin[0] -= extent[1][0]; + origin[1] -= extent[1][1]; + dragging = 2; + } + d3_eventCancel(); + } + } + function keyup() { + if (d3.event.keyCode == 32 && dragging == 2) { + origin[0] += extent[1][0]; + origin[1] += extent[1][1]; + dragging = 0; + d3_eventCancel(); + } + } + function brushmove() { + var point = mouse(), moved = false; + if (offset) { + point[0] += offset[0]; + point[1] += offset[1]; + } + if (!dragging) { + if (d3.event.altKey) { + if (!center) center = [ (extent[0][0] + extent[1][0]) / 2, (extent[0][1] + extent[1][1]) / 2 ]; + origin[0] = extent[+(point[0] < center[0])][0]; + origin[1] = extent[+(point[1] < center[1])][1]; + } else center = null; + } + if (resizingX && move1(point, x, 0)) { + redrawX(g); + moved = true; + } + if (resizingY && move1(point, y, 1)) { + redrawY(g); + moved = true; + } + if (moved) { + redraw(g); + event_({ + type: "brush", + mode: dragging ? "move" : "resize" + }); + } + } + function move1(point, scale, i) { + var range = d3_scaleRange(scale), r0 = range[0], r1 = range[1], position = origin[i], size = extent[1][i] - extent[0][i], min, max; + if (dragging) { + r0 -= position; + r1 -= size + position; + } + min = Math.max(r0, Math.min(r1, point[i])); + if (dragging) { + max = (min += position) + size; + } else { + if (center) position = Math.max(r0, Math.min(r1, 2 * center[i] - min)); + if (position < min) { + max = min; + min = position; + } else { + max = position; + } + } + if (extent[0][i] !== min || extent[1][i] !== max) { + extentDomain = null; + extent[0][i] = min; + extent[1][i] = max; + return true; + } + } + function brushend() { + brushmove(); + g.style("pointer-events", "all").selectAll(".resize").style("display", brush.empty() ? "none" : null); + d3.select("body").style("cursor", null); + w.on("mousemove.brush", null).on("mouseup.brush", null).on("touchmove.brush", null).on("touchend.brush", null).on("keydown.brush", null).on("keyup.brush", null); + event_({ + type: "brushend" + }); + d3_eventCancel(); + } + } + brush.x = function(z) { + if (!arguments.length) return x; + x = z; + resizes = d3_svg_brushResizes[!x << 1 | !y]; + return brush; + }; + brush.y = function(z) { + if (!arguments.length) return y; + y = z; + resizes = d3_svg_brushResizes[!x << 1 | !y]; + return brush; + }; + brush.extent = function(z) { + var x0, x1, y0, y1, t; + if (!arguments.length) { + z = extentDomain || extent; + if (x) { + x0 = z[0][0], x1 = z[1][0]; + if (!extentDomain) { + x0 = extent[0][0], x1 = extent[1][0]; + if (x.invert) x0 = x.invert(x0), x1 = x.invert(x1); + if (x1 < x0) t = x0, x0 = x1, x1 = t; + } + } + if (y) { + y0 = z[0][1], y1 = z[1][1]; + if (!extentDomain) { + y0 = extent[0][1], y1 = extent[1][1]; + if (y.invert) y0 = y.invert(y0), y1 = y.invert(y1); + if (y1 < y0) t = y0, y0 = y1, y1 = t; + } + } + return x && y ? [ [ x0, y0 ], [ x1, y1 ] ] : x ? [ x0, x1 ] : y && [ y0, y1 ]; + } + extentDomain = [ [ 0, 0 ], [ 0, 0 ] ]; + if (x) { + x0 = z[0], x1 = z[1]; + if (y) x0 = x0[0], x1 = x1[0]; + extentDomain[0][0] = x0, extentDomain[1][0] = x1; + if (x.invert) x0 = x(x0), x1 = x(x1); + if (x1 < x0) t = x0, x0 = x1, x1 = t; + extent[0][0] = x0 | 0, extent[1][0] = x1 | 0; + } + if (y) { + y0 = z[0], y1 = z[1]; + if (x) y0 = y0[1], y1 = y1[1]; + extentDomain[0][1] = y0, extentDomain[1][1] = y1; + if (y.invert) y0 = y(y0), y1 = y(y1); + if (y1 < y0) t = y0, y0 = y1, y1 = t; + extent[0][1] = y0 | 0, extent[1][1] = y1 | 0; + } + return brush; + }; + brush.clear = function() { + extentDomain = null; + extent[0][0] = extent[0][1] = extent[1][0] = extent[1][1] = 0; + return brush; + }; + brush.empty = function() { + return x && extent[0][0] === extent[1][0] || y && extent[0][1] === extent[1][1]; + }; + return d3.rebind(brush, event, "on"); + }; + var d3_svg_brushCursor = { + n: "ns-resize", + e: "ew-resize", + s: "ns-resize", + w: "ew-resize", + nw: "nwse-resize", + ne: "nesw-resize", + se: "nwse-resize", + sw: "nesw-resize" + }; + var d3_svg_brushResizes = [ [ "n", "e", "s", "w", "nw", "ne", "se", "sw" ], [ "e", "w" ], [ "n", "s" ], [] ]; + d3.time = {}; + var d3_time = Date, d3_time_daySymbols = [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ]; + function d3_time_utc() { + this._ = new Date(arguments.length > 1 ? Date.UTC.apply(this, arguments) : arguments[0]); + } + d3_time_utc.prototype = { + getDate: function() { + return this._.getUTCDate(); + }, + getDay: function() { + return this._.getUTCDay(); + }, + getFullYear: function() { + return this._.getUTCFullYear(); + }, + getHours: function() { + return this._.getUTCHours(); + }, + getMilliseconds: function() { + return this._.getUTCMilliseconds(); + }, + getMinutes: function() { + return this._.getUTCMinutes(); + }, + getMonth: function() { + return this._.getUTCMonth(); + }, + getSeconds: function() { + return this._.getUTCSeconds(); + }, + getTime: function() { + return this._.getTime(); + }, + getTimezoneOffset: function() { + return 0; + }, + valueOf: function() { + return this._.valueOf(); + }, + setDate: function() { + d3_time_prototype.setUTCDate.apply(this._, arguments); + }, + setDay: function() { + d3_time_prototype.setUTCDay.apply(this._, arguments); + }, + setFullYear: function() { + d3_time_prototype.setUTCFullYear.apply(this._, arguments); + }, + setHours: function() { + d3_time_prototype.setUTCHours.apply(this._, arguments); + }, + setMilliseconds: function() { + d3_time_prototype.setUTCMilliseconds.apply(this._, arguments); + }, + setMinutes: function() { + d3_time_prototype.setUTCMinutes.apply(this._, arguments); + }, + setMonth: function() { + d3_time_prototype.setUTCMonth.apply(this._, arguments); + }, + setSeconds: function() { + d3_time_prototype.setUTCSeconds.apply(this._, arguments); + }, + setTime: function() { + d3_time_prototype.setTime.apply(this._, arguments); + } + }; + var d3_time_prototype = Date.prototype; + var d3_time_formatDateTime = "%a %b %e %X %Y", d3_time_formatDate = "%m/%d/%Y", d3_time_formatTime = "%H:%M:%S"; + var d3_time_days = [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ], d3_time_dayAbbreviations = [ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" ], d3_time_months = [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ], d3_time_monthAbbreviations = [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ]; + function d3_time_interval(local, step, number) { + function round(date) { + var d0 = local(date), d1 = offset(d0, 1); + return date - d0 < d1 - date ? d0 : d1; + } + function ceil(date) { + step(date = local(new d3_time(date - 1)), 1); + return date; + } + function offset(date, k) { + step(date = new d3_time(+date), k); + return date; + } + function range(t0, t1, dt) { + var time = ceil(t0), times = []; + if (dt > 1) { + while (time < t1) { + if (!(number(time) % dt)) times.push(new Date(+time)); + step(time, 1); + } + } else { + while (time < t1) times.push(new Date(+time)), step(time, 1); + } + return times; + } + function range_utc(t0, t1, dt) { + try { + d3_time = d3_time_utc; + var utc = new d3_time_utc(); + utc._ = t0; + return range(utc, t1, dt); + } finally { + d3_time = Date; + } + } + local.floor = local; + local.round = round; + local.ceil = ceil; + local.offset = offset; + local.range = range; + var utc = local.utc = d3_time_interval_utc(local); + utc.floor = utc; + utc.round = d3_time_interval_utc(round); + utc.ceil = d3_time_interval_utc(ceil); + utc.offset = d3_time_interval_utc(offset); + utc.range = range_utc; + return local; + } + function d3_time_interval_utc(method) { + return function(date, k) { + try { + d3_time = d3_time_utc; + var utc = new d3_time_utc(); + utc._ = date; + return method(utc, k)._; + } finally { + d3_time = Date; + } + }; + } + d3.time.year = d3_time_interval(function(date) { + date = d3.time.day(date); + date.setMonth(0, 1); + return date; + }, function(date, offset) { + date.setFullYear(date.getFullYear() + offset); + }, function(date) { + return date.getFullYear(); + }); + d3.time.years = d3.time.year.range; + d3.time.years.utc = d3.time.year.utc.range; + d3.time.day = d3_time_interval(function(date) { + var day = new d3_time(1970, 0); + day.setFullYear(date.getFullYear(), date.getMonth(), date.getDate()); + return day; + }, function(date, offset) { + date.setDate(date.getDate() + offset); + }, function(date) { + return date.getDate() - 1; + }); + d3.time.days = d3.time.day.range; + d3.time.days.utc = d3.time.day.utc.range; + d3.time.dayOfYear = function(date) { + var year = d3.time.year(date); + return Math.floor((date - year - (date.getTimezoneOffset() - year.getTimezoneOffset()) * 6e4) / 864e5); + }; + d3_time_daySymbols.forEach(function(day, i) { + day = day.toLowerCase(); + i = 7 - i; + var interval = d3.time[day] = d3_time_interval(function(date) { + (date = d3.time.day(date)).setDate(date.getDate() - (date.getDay() + i) % 7); + return date; + }, function(date, offset) { + date.setDate(date.getDate() + Math.floor(offset) * 7); + }, function(date) { + var day = d3.time.year(date).getDay(); + return Math.floor((d3.time.dayOfYear(date) + (day + i) % 7) / 7) - (day !== i); + }); + d3.time[day + "s"] = interval.range; + d3.time[day + "s"].utc = interval.utc.range; + d3.time[day + "OfYear"] = function(date) { + var day = d3.time.year(date).getDay(); + return Math.floor((d3.time.dayOfYear(date) + (day + i) % 7) / 7); + }; + }); + d3.time.week = d3.time.sunday; + d3.time.weeks = d3.time.sunday.range; + d3.time.weeks.utc = d3.time.sunday.utc.range; + d3.time.weekOfYear = d3.time.sundayOfYear; + d3.time.format = function(template) { + var n = template.length; + function format(date) { + var string = [], i = -1, j = 0, c, p, f; + while (++i < n) { + if (template.charCodeAt(i) === 37) { + string.push(template.substring(j, i)); + if ((p = d3_time_formatPads[c = template.charAt(++i)]) != null) c = template.charAt(++i); + if (f = d3_time_formats[c]) c = f(date, p == null ? c === "e" ? " " : "0" : p); + string.push(c); + j = i + 1; + } + } + string.push(template.substring(j, i)); + return string.join(""); + } + format.parse = function(string) { + var d = { + y: 1900, + m: 0, + d: 1, + H: 0, + M: 0, + S: 0, + L: 0 + }, i = d3_time_parse(d, template, string, 0); + if (i != string.length) return null; + if ("p" in d) d.H = d.H % 12 + d.p * 12; + var date = new d3_time(); + date.setFullYear(d.y, d.m, d.d); + date.setHours(d.H, d.M, d.S, d.L); + return date; + }; + format.toString = function() { + return template; + }; + return format; + }; + function d3_time_parse(date, template, string, j) { + var c, p, i = 0, n = template.length, m = string.length; + while (i < n) { + if (j >= m) return -1; + c = template.charCodeAt(i++); + if (c === 37) { + p = d3_time_parsers[template.charAt(i++)]; + if (!p || (j = p(date, string, j)) < 0) return -1; + } else if (c != string.charCodeAt(j++)) { + return -1; + } + } + return j; + } + function d3_time_formatRe(names) { + return new RegExp("^(?:" + names.map(d3.requote).join("|") + ")", "i"); + } + function d3_time_formatLookup(names) { + var map = new d3_Map(), i = -1, n = names.length; + while (++i < n) map.set(names[i].toLowerCase(), i); + return map; + } + function d3_time_formatPad(value, fill, width) { + value += ""; + var length = value.length; + return length < width ? new Array(width - length + 1).join(fill) + value : value; + } + var d3_time_dayRe = d3_time_formatRe(d3_time_days), d3_time_dayAbbrevRe = d3_time_formatRe(d3_time_dayAbbreviations), d3_time_monthRe = d3_time_formatRe(d3_time_months), d3_time_monthLookup = d3_time_formatLookup(d3_time_months), d3_time_monthAbbrevRe = d3_time_formatRe(d3_time_monthAbbreviations), d3_time_monthAbbrevLookup = d3_time_formatLookup(d3_time_monthAbbreviations); + var d3_time_formatPads = { + "-": "", + _: " ", + "0": "0" + }; + var d3_time_formats = { + a: function(d) { + return d3_time_dayAbbreviations[d.getDay()]; + }, + A: function(d) { + return d3_time_days[d.getDay()]; + }, + b: function(d) { + return d3_time_monthAbbreviations[d.getMonth()]; + }, + B: function(d) { + return d3_time_months[d.getMonth()]; + }, + c: d3.time.format(d3_time_formatDateTime), + d: function(d, p) { + return d3_time_formatPad(d.getDate(), p, 2); + }, + e: function(d, p) { + return d3_time_formatPad(d.getDate(), p, 2); + }, + H: function(d, p) { + return d3_time_formatPad(d.getHours(), p, 2); + }, + I: function(d, p) { + return d3_time_formatPad(d.getHours() % 12 || 12, p, 2); + }, + j: function(d, p) { + return d3_time_formatPad(1 + d3.time.dayOfYear(d), p, 3); + }, + L: function(d, p) { + return d3_time_formatPad(d.getMilliseconds(), p, 3); + }, + m: function(d, p) { + return d3_time_formatPad(d.getMonth() + 1, p, 2); + }, + M: function(d, p) { + return d3_time_formatPad(d.getMinutes(), p, 2); + }, + p: function(d) { + return d.getHours() >= 12 ? "PM" : "AM"; + }, + S: function(d, p) { + return d3_time_formatPad(d.getSeconds(), p, 2); + }, + U: function(d, p) { + return d3_time_formatPad(d3.time.sundayOfYear(d), p, 2); + }, + w: function(d) { + return d.getDay(); + }, + W: function(d, p) { + return d3_time_formatPad(d3.time.mondayOfYear(d), p, 2); + }, + x: d3.time.format(d3_time_formatDate), + X: d3.time.format(d3_time_formatTime), + y: function(d, p) { + return d3_time_formatPad(d.getFullYear() % 100, p, 2); + }, + Y: function(d, p) { + return d3_time_formatPad(d.getFullYear() % 1e4, p, 4); + }, + Z: d3_time_zone, + "%": function() { + return "%"; + } + }; + var d3_time_parsers = { + a: d3_time_parseWeekdayAbbrev, + A: d3_time_parseWeekday, + b: d3_time_parseMonthAbbrev, + B: d3_time_parseMonth, + c: d3_time_parseLocaleFull, + d: d3_time_parseDay, + e: d3_time_parseDay, + H: d3_time_parseHour24, + I: d3_time_parseHour24, + L: d3_time_parseMilliseconds, + m: d3_time_parseMonthNumber, + M: d3_time_parseMinutes, + p: d3_time_parseAmPm, + S: d3_time_parseSeconds, + x: d3_time_parseLocaleDate, + X: d3_time_parseLocaleTime, + y: d3_time_parseYear, + Y: d3_time_parseFullYear + }; + function d3_time_parseWeekdayAbbrev(date, string, i) { + d3_time_dayAbbrevRe.lastIndex = 0; + var n = d3_time_dayAbbrevRe.exec(string.substring(i)); + return n ? i += n[0].length : -1; + } + function d3_time_parseWeekday(date, string, i) { + d3_time_dayRe.lastIndex = 0; + var n = d3_time_dayRe.exec(string.substring(i)); + return n ? i += n[0].length : -1; + } + function d3_time_parseMonthAbbrev(date, string, i) { + d3_time_monthAbbrevRe.lastIndex = 0; + var n = d3_time_monthAbbrevRe.exec(string.substring(i)); + return n ? (date.m = d3_time_monthAbbrevLookup.get(n[0].toLowerCase()), i += n[0].length) : -1; + } + function d3_time_parseMonth(date, string, i) { + d3_time_monthRe.lastIndex = 0; + var n = d3_time_monthRe.exec(string.substring(i)); + return n ? (date.m = d3_time_monthLookup.get(n[0].toLowerCase()), i += n[0].length) : -1; + } + function d3_time_parseLocaleFull(date, string, i) { + return d3_time_parse(date, d3_time_formats.c.toString(), string, i); + } + function d3_time_parseLocaleDate(date, string, i) { + return d3_time_parse(date, d3_time_formats.x.toString(), string, i); + } + function d3_time_parseLocaleTime(date, string, i) { + return d3_time_parse(date, d3_time_formats.X.toString(), string, i); + } + function d3_time_parseFullYear(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.substring(i, i + 4)); + return n ? (date.y = +n[0], i += n[0].length) : -1; + } + function d3_time_parseYear(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.substring(i, i + 2)); + return n ? (date.y = d3_time_expandYear(+n[0]), i += n[0].length) : -1; + } + function d3_time_expandYear(d) { + return d + (d > 68 ? 1900 : 2e3); + } + function d3_time_parseMonthNumber(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.substring(i, i + 2)); + return n ? (date.m = n[0] - 1, i += n[0].length) : -1; + } + function d3_time_parseDay(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.substring(i, i + 2)); + return n ? (date.d = +n[0], i += n[0].length) : -1; + } + function d3_time_parseHour24(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.substring(i, i + 2)); + return n ? (date.H = +n[0], i += n[0].length) : -1; + } + function d3_time_parseMinutes(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.substring(i, i + 2)); + return n ? (date.M = +n[0], i += n[0].length) : -1; + } + function d3_time_parseSeconds(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.substring(i, i + 2)); + return n ? (date.S = +n[0], i += n[0].length) : -1; + } + function d3_time_parseMilliseconds(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.substring(i, i + 3)); + return n ? (date.L = +n[0], i += n[0].length) : -1; + } + var d3_time_numberRe = /^\s*\d+/; + function d3_time_parseAmPm(date, string, i) { + var n = d3_time_amPmLookup.get(string.substring(i, i += 2).toLowerCase()); + return n == null ? -1 : (date.p = n, i); + } + var d3_time_amPmLookup = d3.map({ + am: 0, + pm: 1 + }); + function d3_time_zone(d) { + var z = d.getTimezoneOffset(), zs = z > 0 ? "-" : "+", zh = ~~(Math.abs(z) / 60), zm = Math.abs(z) % 60; + return zs + d3_time_formatPad(zh, "0", 2) + d3_time_formatPad(zm, "0", 2); + } + d3.time.format.utc = function(template) { + var local = d3.time.format(template); + function format(date) { + try { + d3_time = d3_time_utc; + var utc = new d3_time(); + utc._ = date; + return local(utc); + } finally { + d3_time = Date; + } + } + format.parse = function(string) { + try { + d3_time = d3_time_utc; + var date = local.parse(string); + return date && date._; + } finally { + d3_time = Date; + } + }; + format.toString = local.toString; + return format; + }; + var d3_time_formatIso = d3.time.format.utc("%Y-%m-%dT%H:%M:%S.%LZ"); + d3.time.format.iso = Date.prototype.toISOString && +new Date("2000-01-01T00:00:00.000Z") ? d3_time_formatIsoNative : d3_time_formatIso; + function d3_time_formatIsoNative(date) { + return date.toISOString(); + } + d3_time_formatIsoNative.parse = function(string) { + var date = new Date(string); + return isNaN(date) ? null : date; + }; + d3_time_formatIsoNative.toString = d3_time_formatIso.toString; + d3.time.second = d3_time_interval(function(date) { + return new d3_time(Math.floor(date / 1e3) * 1e3); + }, function(date, offset) { + date.setTime(date.getTime() + Math.floor(offset) * 1e3); + }, function(date) { + return date.getSeconds(); + }); + d3.time.seconds = d3.time.second.range; + d3.time.seconds.utc = d3.time.second.utc.range; + d3.time.minute = d3_time_interval(function(date) { + return new d3_time(Math.floor(date / 6e4) * 6e4); + }, function(date, offset) { + date.setTime(date.getTime() + Math.floor(offset) * 6e4); + }, function(date) { + return date.getMinutes(); + }); + d3.time.minutes = d3.time.minute.range; + d3.time.minutes.utc = d3.time.minute.utc.range; + d3.time.hour = d3_time_interval(function(date) { + var timezone = date.getTimezoneOffset() / 60; + return new d3_time((Math.floor(date / 36e5 - timezone) + timezone) * 36e5); + }, function(date, offset) { + date.setTime(date.getTime() + Math.floor(offset) * 36e5); + }, function(date) { + return date.getHours(); + }); + d3.time.hours = d3.time.hour.range; + d3.time.hours.utc = d3.time.hour.utc.range; + d3.time.month = d3_time_interval(function(date) { + date = d3.time.day(date); + date.setDate(1); + return date; + }, function(date, offset) { + date.setMonth(date.getMonth() + offset); + }, function(date) { + return date.getMonth(); + }); + d3.time.months = d3.time.month.range; + d3.time.months.utc = d3.time.month.utc.range; + function d3_time_scale(linear, methods, format) { + function scale(x) { + return linear(x); + } + scale.invert = function(x) { + return d3_time_scaleDate(linear.invert(x)); + }; + scale.domain = function(x) { + if (!arguments.length) return linear.domain().map(d3_time_scaleDate); + linear.domain(x); + return scale; + }; + scale.nice = function(m) { + return scale.domain(d3_scale_nice(scale.domain(), function() { + return m; + })); + }; + scale.ticks = function(m, k) { + var extent = d3_time_scaleExtent(scale.domain()); + if (typeof m !== "function") { + var span = extent[1] - extent[0], target = span / m, i = d3.bisect(d3_time_scaleSteps, target); + if (i == d3_time_scaleSteps.length) return methods.year(extent, m); + if (!i) return linear.ticks(m).map(d3_time_scaleDate); + if (Math.log(target / d3_time_scaleSteps[i - 1]) < Math.log(d3_time_scaleSteps[i] / target)) --i; + m = methods[i]; + k = m[1]; + m = m[0].range; + } + return m(extent[0], new Date(+extent[1] + 1), k); + }; + scale.tickFormat = function() { + return format; + }; + scale.copy = function() { + return d3_time_scale(linear.copy(), methods, format); + }; + return d3.rebind(scale, linear, "range", "rangeRound", "interpolate", "clamp"); + } + function d3_time_scaleExtent(domain) { + var start = domain[0], stop = domain[domain.length - 1]; + return start < stop ? [ start, stop ] : [ stop, start ]; + } + function d3_time_scaleDate(t) { + return new Date(t); + } + function d3_time_scaleFormat(formats) { + return function(date) { + var i = formats.length - 1, f = formats[i]; + while (!f[1](date)) f = formats[--i]; + return f[0](date); + }; + } + function d3_time_scaleSetYear(y) { + var d = new Date(y, 0, 1); + d.setFullYear(y); + return d; + } + function d3_time_scaleGetYear(d) { + var y = d.getFullYear(), d0 = d3_time_scaleSetYear(y), d1 = d3_time_scaleSetYear(y + 1); + return y + (d - d0) / (d1 - d0); + } + var d3_time_scaleSteps = [ 1e3, 5e3, 15e3, 3e4, 6e4, 3e5, 9e5, 18e5, 36e5, 108e5, 216e5, 432e5, 864e5, 1728e5, 6048e5, 2592e6, 7776e6, 31536e6 ]; + var d3_time_scaleLocalMethods = [ [ d3.time.second, 1 ], [ d3.time.second, 5 ], [ d3.time.second, 15 ], [ d3.time.second, 30 ], [ d3.time.minute, 1 ], [ d3.time.minute, 5 ], [ d3.time.minute, 15 ], [ d3.time.minute, 30 ], [ d3.time.hour, 1 ], [ d3.time.hour, 3 ], [ d3.time.hour, 6 ], [ d3.time.hour, 12 ], [ d3.time.day, 1 ], [ d3.time.day, 2 ], [ d3.time.week, 1 ], [ d3.time.month, 1 ], [ d3.time.month, 3 ], [ d3.time.year, 1 ] ]; + var d3_time_scaleLocalFormats = [ [ d3.time.format("%Y"), d3_true ], [ d3.time.format("%B"), function(d) { + return d.getMonth(); + } ], [ d3.time.format("%b %d"), function(d) { + return d.getDate() != 1; + } ], [ d3.time.format("%a %d"), function(d) { + return d.getDay() && d.getDate() != 1; + } ], [ d3.time.format("%I %p"), function(d) { + return d.getHours(); + } ], [ d3.time.format("%I:%M"), function(d) { + return d.getMinutes(); + } ], [ d3.time.format(":%S"), function(d) { + return d.getSeconds(); + } ], [ d3.time.format(".%L"), function(d) { + return d.getMilliseconds(); + } ] ]; + var d3_time_scaleLinear = d3.scale.linear(), d3_time_scaleLocalFormat = d3_time_scaleFormat(d3_time_scaleLocalFormats); + d3_time_scaleLocalMethods.year = function(extent, m) { + return d3_time_scaleLinear.domain(extent.map(d3_time_scaleGetYear)).ticks(m).map(d3_time_scaleSetYear); + }; + d3.time.scale = function() { + return d3_time_scale(d3.scale.linear(), d3_time_scaleLocalMethods, d3_time_scaleLocalFormat); + }; + var d3_time_scaleUTCMethods = d3_time_scaleLocalMethods.map(function(m) { + return [ m[0].utc, m[1] ]; + }); + var d3_time_scaleUTCFormats = [ [ d3.time.format.utc("%Y"), d3_true ], [ d3.time.format.utc("%B"), function(d) { + return d.getUTCMonth(); + } ], [ d3.time.format.utc("%b %d"), function(d) { + return d.getUTCDate() != 1; + } ], [ d3.time.format.utc("%a %d"), function(d) { + return d.getUTCDay() && d.getUTCDate() != 1; + } ], [ d3.time.format.utc("%I %p"), function(d) { + return d.getUTCHours(); + } ], [ d3.time.format.utc("%I:%M"), function(d) { + return d.getUTCMinutes(); + } ], [ d3.time.format.utc(":%S"), function(d) { + return d.getUTCSeconds(); + } ], [ d3.time.format.utc(".%L"), function(d) { + return d.getUTCMilliseconds(); + } ] ]; + var d3_time_scaleUTCFormat = d3_time_scaleFormat(d3_time_scaleUTCFormats); + function d3_time_scaleUTCSetYear(y) { + var d = new Date(Date.UTC(y, 0, 1)); + d.setUTCFullYear(y); + return d; + } + function d3_time_scaleUTCGetYear(d) { + var y = d.getUTCFullYear(), d0 = d3_time_scaleUTCSetYear(y), d1 = d3_time_scaleUTCSetYear(y + 1); + return y + (d - d0) / (d1 - d0); + } + d3_time_scaleUTCMethods.year = function(extent, m) { + return d3_time_scaleLinear.domain(extent.map(d3_time_scaleUTCGetYear)).ticks(m).map(d3_time_scaleUTCSetYear); + }; + d3.time.scale.utc = function() { + return d3_time_scale(d3.scale.linear(), d3_time_scaleUTCMethods, d3_time_scaleUTCFormat); + }; + d3.text = function() { + return d3.xhr.apply(d3, arguments).response(d3_text); + }; + function d3_text(request) { + return request.responseText; + } + d3.json = function(url, callback) { + return d3.xhr(url, "application/json", callback).response(d3_json); + }; + function d3_json(request) { + return JSON.parse(request.responseText); + } + d3.html = function(url, callback) { + return d3.xhr(url, "text/html", callback).response(d3_html); + }; + function d3_html(request) { + var range = d3_document.createRange(); + range.selectNode(d3_document.body); + return range.createContextualFragment(request.responseText); + } + d3.xml = function() { + return d3.xhr.apply(d3, arguments).response(d3_xml); + }; + function d3_xml(request) { + return request.responseXML; + } + return d3; +}();
\ No newline at end of file diff --git a/addons/web_graph/static/lib/nvd3/nv.d3.js b/addons/web_graph/static/lib/nvd3/nv.d3.js new file mode 100644 index 00000000000..4534016f9b3 --- /dev/null +++ b/addons/web_graph/static/lib/nvd3/nv.d3.js @@ -0,0 +1,14303 @@ +(function(){ + +var nv = window.nv || {}; + + +nv.version = '1.1.14b'; +nv.dev = true //set false when in production + +window.nv = nv; + +nv.tooltip = nv.tooltip || {}; // For the tooltip system +nv.utils = nv.utils || {}; // Utility subsystem +nv.models = nv.models || {}; //stores all the possible models/components +nv.charts = {}; //stores all the ready to use charts +nv.graphs = []; //stores all the graphs currently on the page +nv.logs = {}; //stores some statistics and potential error messages + +nv.dispatch = d3.dispatch('render_start', 'render_end'); + +// ************************************************************************* +// Development render timers - disabled if dev = false + +if (nv.dev) { + nv.dispatch.on('render_start', function(e) { + nv.logs.startTime = +new Date(); + }); + + nv.dispatch.on('render_end', function(e) { + nv.logs.endTime = +new Date(); + nv.logs.totalTime = nv.logs.endTime - nv.logs.startTime; + nv.log('total', nv.logs.totalTime); // used for development, to keep track of graph generation times + }); +} + +// ******************************************** +// Public Core NV functions + +// Logs all arguments, and returns the last so you can test things in place +// Note: in IE8 console.log is an object not a function, and if modernizr is used +// then calling Function.prototype.bind with with anything other than a function +// causes a TypeError to be thrown. +nv.log = function() { + if (nv.dev && console.log && console.log.apply) + console.log.apply(console, arguments) + else if (nv.dev && typeof console.log == "function" && Function.prototype.bind) { + var log = Function.prototype.bind.call(console.log, console); + log.apply(console, arguments); + } + return arguments[arguments.length - 1]; +}; + + +nv.render = function render(step) { + step = step || 1; // number of graphs to generate in each timeout loop + + nv.render.active = true; + nv.dispatch.render_start(); + + setTimeout(function() { + var chart, graph; + + for (var i = 0; i < step && (graph = nv.render.queue[i]); i++) { + chart = graph.generate(); + if (typeof graph.callback == typeof(Function)) graph.callback(chart); + nv.graphs.push(chart); + } + + nv.render.queue.splice(0, i); + + if (nv.render.queue.length) setTimeout(arguments.callee, 0); + else { nv.render.active = false; nv.dispatch.render_end(); } + }, 0); +}; + +nv.render.active = false; +nv.render.queue = []; + +nv.addGraph = function(obj) { + if (typeof arguments[0] === typeof(Function)) + obj = {generate: arguments[0], callback: arguments[1]}; + + nv.render.queue.push(obj); + + if (!nv.render.active) nv.render(); +}; + +nv.identity = function(d) { return d; }; + +nv.strip = function(s) { return s.replace(/(\s|&)/g,''); }; + +function daysInMonth(month,year) { + return (new Date(year, month+1, 0)).getDate(); +} + +function d3_time_range(floor, step, number) { + return function(t0, t1, dt) { + var time = floor(t0), times = []; + if (time < t0) step(time); + if (dt > 1) { + while (time < t1) { + var date = new Date(+time); + if ((number(date) % dt === 0)) times.push(date); + step(time); + } + } else { + while (time < t1) { times.push(new Date(+time)); step(time); } + } + return times; + }; +} + +d3.time.monthEnd = function(date) { + return new Date(date.getFullYear(), date.getMonth(), 0); +}; + +d3.time.monthEnds = d3_time_range(d3.time.monthEnd, function(date) { + date.setUTCDate(date.getUTCDate() + 1); + date.setDate(daysInMonth(date.getMonth() + 1, date.getFullYear())); + }, function(date) { + return date.getMonth(); + } +); + +/* Utility class to handle creation of an interactive layer. +This places a rectangle on top of the chart. When you mouse move over it, it sends a dispatch +containing the X-coordinate. It can also render a vertical line where the mouse is located. + +dispatch.elementMousemove is the important event to latch onto. It is fired whenever the mouse moves over +the rectangle. The dispatch is given one object which contains the mouseX/Y location. +It also has 'pointXValue', which is the conversion of mouseX to the x-axis scale. +*/ +nv.interactiveGuideline = function() { + "use strict"; + var tooltip = nv.models.tooltip(); + //Public settings + var width = null + , height = null + //Please pass in the bounding chart's top and left margins + //This is important for calculating the correct mouseX/Y positions. + , margin = {left: 0, top: 0} + , xScale = d3.scale.linear() + , yScale = d3.scale.linear() + , dispatch = d3.dispatch('elementMousemove', 'elementMouseout','elementDblclick') + , showGuideLine = true + , svgContainer = null + //Must pass in the bounding chart's <svg> container. + //The mousemove event is attached to this container. + ; + + //Private variables + var isMSIE = navigator.userAgent.indexOf("MSIE") !== -1 //Check user-agent for Microsoft Internet Explorer. + ; + + + function layer(selection) { + selection.each(function(data) { + var container = d3.select(this); + + var availableWidth = (width || 960), availableHeight = (height || 400); + + var wrap = container.selectAll("g.nv-wrap.nv-interactiveLineLayer").data([data]); + var wrapEnter = wrap.enter() + .append("g").attr("class", " nv-wrap nv-interactiveLineLayer"); + + + wrapEnter.append("g").attr("class","nv-interactiveGuideLine"); + + if (!svgContainer) { + return; + } + + function mouseHandler() { + var d3mouse = d3.mouse(this); + var mouseX = d3mouse[0]; + var mouseY = d3mouse[1]; + var subtractMargin = true; + var mouseOutAnyReason = false; + if (isMSIE) { + /* + D3.js (or maybe SVG.getScreenCTM) has a nasty bug in Internet Explorer 10. + d3.mouse() returns incorrect X,Y mouse coordinates when mouse moving + over a rect in IE 10. + However, d3.event.offsetX/Y also returns the mouse coordinates + relative to the triggering <rect>. So we use offsetX/Y on IE. + */ + mouseX = d3.event.offsetX; + mouseY = d3.event.offsetY; + + /* + On IE, if you attach a mouse event listener to the <svg> container, + it will actually trigger it for all the child elements (like <path>, <circle>, etc). + When this happens on IE, the offsetX/Y is set to where ever the child element + is located. + As a result, we do NOT need to subtract margins to figure out the mouse X/Y + position under this scenario. Removing the line below *will* cause + the interactive layer to not work right on IE. + */ + if(d3.event.target.tagName !== "svg") + subtractMargin = false; + + if (d3.event.target.className.baseVal.match("nv-legend")) + mouseOutAnyReason = true; + + } + + if(subtractMargin) { + mouseX -= margin.left; + mouseY -= margin.top; + } + + /* If mouseX/Y is outside of the chart's bounds, + trigger a mouseOut event. + */ + if (mouseX < 0 || mouseY < 0 + || mouseX > availableWidth || mouseY > availableHeight + || (d3.event.relatedTarget && d3.event.relatedTarget.ownerSVGElement === undefined) + || mouseOutAnyReason + ) + { + if (isMSIE) { + if (d3.event.relatedTarget + && d3.event.relatedTarget.ownerSVGElement === undefined + && d3.event.relatedTarget.className.match(tooltip.nvPointerEventsClass)) { + return; + } + } + dispatch.elementMouseout({ + mouseX: mouseX, + mouseY: mouseY + }); + layer.renderGuideLine(null); //hide the guideline + return; + } + + var pointXValue = xScale.invert(mouseX); + dispatch.elementMousemove({ + mouseX: mouseX, + mouseY: mouseY, + pointXValue: pointXValue + }); + + //If user double clicks the layer, fire a elementDblclick dispatch. + if (d3.event.type === "dblclick") { + dispatch.elementDblclick({ + mouseX: mouseX, + mouseY: mouseY, + pointXValue: pointXValue + }); + } + } + + svgContainer + .on("mousemove",mouseHandler, true) + .on("mouseout" ,mouseHandler,true) + .on("dblclick" ,mouseHandler) + ; + + //Draws a vertical guideline at the given X postion. + layer.renderGuideLine = function(x) { + if (!showGuideLine) return; + var line = wrap.select(".nv-interactiveGuideLine") + .selectAll("line") + .data((x != null) ? [nv.utils.NaNtoZero(x)] : [], String); + + line.enter() + .append("line") + .attr("class", "nv-guideline") + .attr("x1", function(d) { return d;}) + .attr("x2", function(d) { return d;}) + .attr("y1", availableHeight) + .attr("y2",0) + ; + line.exit().remove(); + + } + }); + } + + layer.dispatch = dispatch; + layer.tooltip = tooltip; + + layer.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return layer; + }; + + layer.width = function(_) { + if (!arguments.length) return width; + width = _; + return layer; + }; + + layer.height = function(_) { + if (!arguments.length) return height; + height = _; + return layer; + }; + + layer.xScale = function(_) { + if (!arguments.length) return xScale; + xScale = _; + return layer; + }; + + layer.showGuideLine = function(_) { + if (!arguments.length) return showGuideLine; + showGuideLine = _; + return layer; + }; + + layer.svgContainer = function(_) { + if (!arguments.length) return svgContainer; + svgContainer = _; + return layer; + }; + + + return layer; +}; + +/* Utility class that uses d3.bisect to find the index in a given array, where a search value can be inserted. +This is different from normal bisectLeft; this function finds the nearest index to insert the search value. + +For instance, lets say your array is [1,2,3,5,10,30], and you search for 28. +Normal d3.bisectLeft will return 4, because 28 is inserted after the number 10. But interactiveBisect will return 5 +because 28 is closer to 30 than 10. + +Unit tests can be found in: interactiveBisectTest.html + +Has the following known issues: + * Will not work if the data points move backwards (ie, 10,9,8,7, etc) or if the data points are in random order. + * Won't work if there are duplicate x coordinate values. +*/ +nv.interactiveBisect = function (values, searchVal, xAccessor) { + "use strict"; + if (! values instanceof Array) return null; + if (typeof xAccessor !== 'function') xAccessor = function(d,i) { return d.x;} + + var bisect = d3.bisector(xAccessor).left; + var index = d3.max([0, bisect(values,searchVal) - 1]); + var currentValue = xAccessor(values[index], index); + if (typeof currentValue === 'undefined') currentValue = index; + + if (currentValue === searchVal) return index; //found exact match + + var nextIndex = d3.min([index+1, values.length - 1]); + var nextValue = xAccessor(values[nextIndex], nextIndex); + if (typeof nextValue === 'undefined') nextValue = nextIndex; + + if (Math.abs(nextValue - searchVal) >= Math.abs(currentValue - searchVal)) + return index; + else + return nextIndex +}; + +/* +Returns the index in the array "values" that is closest to searchVal. +Only returns an index if searchVal is within some "threshold". +Otherwise, returns null. +*/ +nv.nearestValueIndex = function (values, searchVal, threshold) { + "use strict"; + var yDistMax = Infinity, indexToHighlight = null; + values.forEach(function(d,i) { + var delta = Math.abs(searchVal - d); + if ( delta <= yDistMax && delta < threshold) { + yDistMax = delta; + indexToHighlight = i; + } + }); + return indexToHighlight; +};/* Tooltip rendering model for nvd3 charts. +window.nv.models.tooltip is the updated,new way to render tooltips. + +window.nv.tooltip.show is the old tooltip code. +window.nv.tooltip.* also has various helper methods. +*/ +(function() { + "use strict"; + window.nv.tooltip = {}; + + /* Model which can be instantiated to handle tooltip rendering. + Example usage: + var tip = nv.models.tooltip().gravity('w').distance(23) + .data(myDataObject); + + tip(); //just invoke the returned function to render tooltip. + */ + window.nv.models.tooltip = function() { + var content = null //HTML contents of the tooltip. If null, the content is generated via the data variable. + , data = null /* Tooltip data. If data is given in the proper format, a consistent tooltip is generated. + Format of data: + { + key: "Date", + value: "August 2009", + series: [ + { + key: "Series 1", + value: "Value 1", + color: "#000" + }, + { + key: "Series 2", + value: "Value 2", + color: "#00f" + } + ] + + } + + */ + , gravity = 'w' //Can be 'n','s','e','w'. Determines how tooltip is positioned. + , distance = 50 //Distance to offset tooltip from the mouse location. + , snapDistance = 25 //Tolerance allowed before tooltip is moved from its current position (creates 'snapping' effect) + , fixedTop = null //If not null, this fixes the top position of the tooltip. + , classes = null //Attaches additional CSS classes to the tooltip DIV that is created. + , chartContainer = null //Parent DIV, of the SVG Container that holds the chart. + , tooltipElem = null //actual DOM element representing the tooltip. + , position = {left: null, top: null} //Relative position of the tooltip inside chartContainer. + , enabled = true //True -> tooltips are rendered. False -> don't render tooltips. + //Generates a unique id when you create a new tooltip() object + , id = "nvtooltip-" + Math.floor(Math.random() * 100000) + ; + + //CSS class to specify whether element should not have mouse events. + var nvPointerEventsClass = "nv-pointer-events-none"; + + //Format function for the tooltip values column + var valueFormatter = function(d,i) { + return d; + }; + + //Format function for the tooltip header value. + var headerFormatter = function(d) { + return d; + }; + + //By default, the tooltip model renders a beautiful table inside a DIV. + //You can override this function if a custom tooltip is desired. + var contentGenerator = function(d) { + if (content != null) return content; + + if (d == null) return ''; + + var table = d3.select(document.createElement("table")); + var theadEnter = table.selectAll("thead") + .data([d]) + .enter().append("thead"); + theadEnter.append("tr") + .append("td") + .attr("colspan",3) + .append("strong") + .classed("x-value",true) + .html(headerFormatter(d.value)); + + var tbodyEnter = table.selectAll("tbody") + .data([d]) + .enter().append("tbody"); + var trowEnter = tbodyEnter.selectAll("tr") + .data(function(p) { return p.series}) + .enter() + .append("tr") + .classed("highlight", function(p) { return p.highlight}) + ; + + trowEnter.append("td") + .classed("legend-color-guide",true) + .append("div") + .style("background-color", function(p) { return p.color}); + trowEnter.append("td") + .classed("key",true) + .html(function(p) {return p.key}); + trowEnter.append("td") + .classed("value",true) + .html(function(p,i) { return valueFormatter(p.value,i) }); + + + trowEnter.selectAll("td").each(function(p) { + if (p.highlight) { + var opacityScale = d3.scale.linear().domain([0,1]).range(["#fff",p.color]); + var opacity = 0.6; + d3.select(this) + .style("border-bottom-color", opacityScale(opacity)) + .style("border-top-color", opacityScale(opacity)) + ; + } + }); + + var html = table.node().outerHTML; + if (d.footer !== undefined) + html += "<div class='footer'>" + d.footer + "</div>"; + return html; + + }; + + var dataSeriesExists = function(d) { + if (d && d.series && d.series.length > 0) return true; + + return false; + }; + + //In situations where the chart is in a 'viewBox', re-position the tooltip based on how far chart is zoomed. + function convertViewBoxRatio() { + if (chartContainer) { + var svg = d3.select(chartContainer); + if (svg.node().tagName !== "svg") { + svg = svg.select("svg"); + } + var viewBox = (svg.node()) ? svg.attr('viewBox') : null; + if (viewBox) { + viewBox = viewBox.split(' '); + var ratio = parseInt(svg.style('width')) / viewBox[2]; + + position.left = position.left * ratio; + position.top = position.top * ratio; + } + } + } + + //Creates new tooltip container, or uses existing one on DOM. + function getTooltipContainer(newContent) { + var body; + if (chartContainer) + body = d3.select(chartContainer); + else + body = d3.select("body"); + + var container = body.select(".nvtooltip"); + if (container.node() === null) { + //Create new tooltip div if it doesn't exist on DOM. + container = body.append("div") + .attr("class", "nvtooltip " + (classes? classes: "xy-tooltip")) + .attr("id",id) + ; + } + + + container.node().innerHTML = newContent; + container.style("top",0).style("left",0).style("opacity",0); + container.selectAll("div, table, td, tr").classed(nvPointerEventsClass,true) + container.classed(nvPointerEventsClass,true); + return container.node(); + } + + + + //Draw the tooltip onto the DOM. + function nvtooltip() { + if (!enabled) return; + if (!dataSeriesExists(data)) return; + + convertViewBoxRatio(); + + var left = position.left; + var top = (fixedTop != null) ? fixedTop : position.top; + var container = getTooltipContainer(contentGenerator(data)); + tooltipElem = container; + if (chartContainer) { + var svgComp = chartContainer.getElementsByTagName("svg")[0]; + var boundRect = (svgComp) ? svgComp.getBoundingClientRect() : chartContainer.getBoundingClientRect(); + var svgOffset = {left:0,top:0}; + if (svgComp) { + var svgBound = svgComp.getBoundingClientRect(); + var chartBound = chartContainer.getBoundingClientRect(); + var svgBoundTop = svgBound.top; + + //Defensive code. Sometimes, svgBoundTop can be a really negative + // number, like -134254. That's a bug. + // If such a number is found, use zero instead. FireFox bug only + if (svgBoundTop < 0) { + var containerBound = chartContainer.getBoundingClientRect(); + svgBoundTop = (Math.abs(svgBoundTop) > containerBound.height) ? 0 : svgBoundTop; + } + svgOffset.top = Math.abs(svgBoundTop - chartBound.top); + svgOffset.left = Math.abs(svgBound.left - chartBound.left); + } + //If the parent container is an overflow <div> with scrollbars, subtract the scroll offsets. + //You need to also add any offset between the <svg> element and its containing <div> + //Finally, add any offset of the containing <div> on the whole page. + left += chartContainer.offsetLeft + svgOffset.left - 2*chartContainer.scrollLeft; + top += chartContainer.offsetTop + svgOffset.top - 2*chartContainer.scrollTop; + } + + if (snapDistance && snapDistance > 0) { + top = Math.floor(top/snapDistance) * snapDistance; + } + + nv.tooltip.calcTooltipPosition([left,top], gravity, distance, container); + return nvtooltip; + }; + + nvtooltip.nvPointerEventsClass = nvPointerEventsClass; + + nvtooltip.content = function(_) { + if (!arguments.length) return content; + content = _; + return nvtooltip; + }; + + //Returns tooltipElem...not able to set it. + nvtooltip.tooltipElem = function() { + return tooltipElem; + }; + + nvtooltip.contentGenerator = function(_) { + if (!arguments.length) return contentGenerator; + if (typeof _ === 'function') { + contentGenerator = _; + } + return nvtooltip; + }; + + nvtooltip.data = function(_) { + if (!arguments.length) return data; + data = _; + return nvtooltip; + }; + + nvtooltip.gravity = function(_) { + if (!arguments.length) return gravity; + gravity = _; + return nvtooltip; + }; + + nvtooltip.distance = function(_) { + if (!arguments.length) return distance; + distance = _; + return nvtooltip; + }; + + nvtooltip.snapDistance = function(_) { + if (!arguments.length) return snapDistance; + snapDistance = _; + return nvtooltip; + }; + + nvtooltip.classes = function(_) { + if (!arguments.length) return classes; + classes = _; + return nvtooltip; + }; + + nvtooltip.chartContainer = function(_) { + if (!arguments.length) return chartContainer; + chartContainer = _; + return nvtooltip; + }; + + nvtooltip.position = function(_) { + if (!arguments.length) return position; + position.left = (typeof _.left !== 'undefined') ? _.left : position.left; + position.top = (typeof _.top !== 'undefined') ? _.top : position.top; + return nvtooltip; + }; + + nvtooltip.fixedTop = function(_) { + if (!arguments.length) return fixedTop; + fixedTop = _; + return nvtooltip; + }; + + nvtooltip.enabled = function(_) { + if (!arguments.length) return enabled; + enabled = _; + return nvtooltip; + }; + + nvtooltip.valueFormatter = function(_) { + if (!arguments.length) return valueFormatter; + if (typeof _ === 'function') { + valueFormatter = _; + } + return nvtooltip; + }; + + nvtooltip.headerFormatter = function(_) { + if (!arguments.length) return headerFormatter; + if (typeof _ === 'function') { + headerFormatter = _; + } + return nvtooltip; + }; + + //id() is a read-only function. You can't use it to set the id. + nvtooltip.id = function() { + return id; + }; + + + return nvtooltip; + }; + + + //Original tooltip.show function. Kept for backward compatibility. + // pos = [left,top] + nv.tooltip.show = function(pos, content, gravity, dist, parentContainer, classes) { + + //Create new tooltip div if it doesn't exist on DOM. + var container = document.createElement('div'); + container.className = 'nvtooltip ' + (classes ? classes : 'xy-tooltip'); + + var body = parentContainer; + if ( !parentContainer || parentContainer.tagName.match(/g|svg/i)) { + //If the parent element is an SVG element, place tooltip in the <body> element. + body = document.getElementsByTagName('body')[0]; + } + + container.style.left = 0; + container.style.top = 0; + container.style.opacity = 0; + container.innerHTML = content; + body.appendChild(container); + + //If the parent container is an overflow <div> with scrollbars, subtract the scroll offsets. + if (parentContainer) { + pos[0] = pos[0] - parentContainer.scrollLeft; + pos[1] = pos[1] - parentContainer.scrollTop; + } + nv.tooltip.calcTooltipPosition(pos, gravity, dist, container); + }; + + //Looks up the ancestry of a DOM element, and returns the first NON-svg node. + nv.tooltip.findFirstNonSVGParent = function(Elem) { + while(Elem.tagName.match(/^g|svg$/i) !== null) { + Elem = Elem.parentNode; + } + return Elem; + }; + + //Finds the total offsetTop of a given DOM element. + //Looks up the entire ancestry of an element, up to the first relatively positioned element. + nv.tooltip.findTotalOffsetTop = function ( Elem, initialTop ) { + var offsetTop = initialTop; + + do { + if( !isNaN( Elem.offsetTop ) ) { + offsetTop += (Elem.offsetTop); + } + } while( Elem = Elem.offsetParent ); + return offsetTop; + }; + + //Finds the total offsetLeft of a given DOM element. + //Looks up the entire ancestry of an element, up to the first relatively positioned element. + nv.tooltip.findTotalOffsetLeft = function ( Elem, initialLeft) { + var offsetLeft = initialLeft; + + do { + if( !isNaN( Elem.offsetLeft ) ) { + offsetLeft += (Elem.offsetLeft); + } + } while( Elem = Elem.offsetParent ); + return offsetLeft; + }; + + //Global utility function to render a tooltip on the DOM. + //pos = [left,top] coordinates of where to place the tooltip, relative to the SVG chart container. + //gravity = how to orient the tooltip + //dist = how far away from the mouse to place tooltip + //container = tooltip DIV + nv.tooltip.calcTooltipPosition = function(pos, gravity, dist, container) { + + var height = parseInt(container.offsetHeight), + width = parseInt(container.offsetWidth), + windowWidth = nv.utils.windowSize().width, + windowHeight = nv.utils.windowSize().height, + scrollTop = window.pageYOffset, + scrollLeft = window.pageXOffset, + left, top; + + windowHeight = window.innerWidth >= document.body.scrollWidth ? windowHeight : windowHeight - 16; + windowWidth = window.innerHeight >= document.body.scrollHeight ? windowWidth : windowWidth - 16; + + gravity = gravity || 's'; + dist = dist || 20; + + var tooltipTop = function ( Elem ) { + return nv.tooltip.findTotalOffsetTop(Elem, top); + }; + + var tooltipLeft = function ( Elem ) { + return nv.tooltip.findTotalOffsetLeft(Elem,left); + }; + + switch (gravity) { + case 'e': + left = pos[0] - width - dist; + top = pos[1] - (height / 2); + var tLeft = tooltipLeft(container); + var tTop = tooltipTop(container); + if (tLeft < scrollLeft) left = pos[0] + dist > scrollLeft ? pos[0] + dist : scrollLeft - tLeft + left; + if (tTop < scrollTop) top = scrollTop - tTop + top; + if (tTop + height > scrollTop + windowHeight) top = scrollTop + windowHeight - tTop + top - height; + break; + case 'w': + left = pos[0] + dist; + top = pos[1] - (height / 2); + var tLeft = tooltipLeft(container); + var tTop = tooltipTop(container); + if (tLeft + width > windowWidth) left = pos[0] - width - dist; + if (tTop < scrollTop) top = scrollTop + 5; + if (tTop + height > scrollTop + windowHeight) top = scrollTop + windowHeight - tTop + top - height; + break; + case 'n': + left = pos[0] - (width / 2) - 5; + top = pos[1] + dist; + var tLeft = tooltipLeft(container); + var tTop = tooltipTop(container); + if (tLeft < scrollLeft) left = scrollLeft + 5; + if (tLeft + width > windowWidth) left = left - width/2 + 5; + if (tTop + height > scrollTop + windowHeight) top = scrollTop + windowHeight - tTop + top - height; + break; + case 's': + left = pos[0] - (width / 2); + top = pos[1] - height - dist; + var tLeft = tooltipLeft(container); + var tTop = tooltipTop(container); + if (tLeft < scrollLeft) left = scrollLeft + 5; + if (tLeft + width > windowWidth) left = left - width/2 + 5; + if (scrollTop > tTop) top = scrollTop; + break; + case 'none': + left = pos[0]; + top = pos[1] - dist; + var tLeft = tooltipLeft(container); + var tTop = tooltipTop(container); + break; + } + + + container.style.left = left+'px'; + container.style.top = top+'px'; + container.style.opacity = 1; + container.style.position = 'absolute'; + + return container; + }; + + //Global utility function to remove tooltips from the DOM. + nv.tooltip.cleanup = function() { + + // Find the tooltips, mark them for removal by this class (so others cleanups won't find it) + var tooltips = document.getElementsByClassName('nvtooltip'); + var purging = []; + while(tooltips.length) { + purging.push(tooltips[0]); + tooltips[0].style.transitionDelay = '0 !important'; + tooltips[0].style.opacity = 0; + tooltips[0].className = 'nvtooltip-pending-removal'; + } + + setTimeout(function() { + + while (purging.length) { + var removeMe = purging.pop(); + removeMe.parentNode.removeChild(removeMe); + } + }, 500); + }; + +})(); + +nv.utils.windowSize = function() { + // Sane defaults + var size = {width: 640, height: 480}; + + // Earlier IE uses Doc.body + if (document.body && document.body.offsetWidth) { + size.width = document.body.offsetWidth; + size.height = document.body.offsetHeight; + } + + // IE can use depending on mode it is in + if (document.compatMode=='CSS1Compat' && + document.documentElement && + document.documentElement.offsetWidth ) { + size.width = document.documentElement.offsetWidth; + size.height = document.documentElement.offsetHeight; + } + + // Most recent browsers use + if (window.innerWidth && window.innerHeight) { + size.width = window.innerWidth; + size.height = window.innerHeight; + } + return (size); +}; + + + +// Easy way to bind multiple functions to window.onresize +// TODO: give a way to remove a function after its bound, other than removing all of them +nv.utils.windowResize = function(fun){ + if (fun === undefined) return; + var oldresize = window.onresize; + + window.onresize = function(e) { + if (typeof oldresize == 'function') oldresize(e); + fun(e); + } +} + +// Backwards compatible way to implement more d3-like coloring of graphs. +// If passed an array, wrap it in a function which implements the old default +// behavior +nv.utils.getColor = function(color) { + if (!arguments.length) return nv.utils.defaultColor(); //if you pass in nothing, get default colors back + + if( Object.prototype.toString.call( color ) === '[object Array]' ) + return function(d, i) { return d.color || color[i % color.length]; }; + else + return color; + //can't really help it if someone passes rubbish as color +} + +// Default color chooser uses the index of an object as before. +nv.utils.defaultColor = function() { + var colors = d3.scale.category20().range(); + return function(d, i) { return d.color || colors[i % colors.length] }; +} + + +// Returns a color function that takes the result of 'getKey' for each series and +// looks for a corresponding color from the dictionary, +nv.utils.customTheme = function(dictionary, getKey, defaultColors) { + getKey = getKey || function(series) { return series.key }; // use default series.key if getKey is undefined + defaultColors = defaultColors || d3.scale.category20().range(); //default color function + + var defIndex = defaultColors.length; //current default color (going in reverse) + + return function(series, index) { + var key = getKey(series); + + if (!defIndex) defIndex = defaultColors.length; //used all the default colors, start over + + if (typeof dictionary[key] !== "undefined") + return (typeof dictionary[key] === "function") ? dictionary[key]() : dictionary[key]; + else + return defaultColors[--defIndex]; // no match in dictionary, use default color + } +} + + + +// From the PJAX example on d3js.org, while this is not really directly needed +// it's a very cool method for doing pjax, I may expand upon it a little bit, +// open to suggestions on anything that may be useful +nv.utils.pjax = function(links, content) { + d3.selectAll(links).on("click", function() { + history.pushState(this.href, this.textContent, this.href); + load(this.href); + d3.event.preventDefault(); + }); + + function load(href) { + d3.html(href, function(fragment) { + var target = d3.select(content).node(); + target.parentNode.replaceChild(d3.select(fragment).select(content).node(), target); + nv.utils.pjax(links, content); + }); + } + + d3.select(window).on("popstate", function() { + if (d3.event.state) load(d3.event.state); + }); +} + +/* For situations where we want to approximate the width in pixels for an SVG:text element. +Most common instance is when the element is in a display:none; container. +Forumla is : text.length * font-size * constant_factor +*/ +nv.utils.calcApproxTextWidth = function (svgTextElem) { + if (svgTextElem instanceof d3.selection) { + var fontSize = parseInt(svgTextElem.style("font-size").replace("px","")); + var textLength = svgTextElem.text().length; + + return textLength * fontSize * 0.5; + } + return 0; +}; + +/* Numbers that are undefined, null or NaN, convert them to zeros. +*/ +nv.utils.NaNtoZero = function(n) { + if (typeof n !== 'number' + || isNaN(n) + || n === null + || n === Infinity) return 0; + + return n; +}; + +/* +Snippet of code you can insert into each nv.models.* to give you the ability to +do things like: +chart.options({ + showXAxis: true, + tooltips: true +}); + +To enable in the chart: +chart.options = nv.utils.optionsFunc.bind(chart); +*/ +nv.utils.optionsFunc = function(args) { + if (args) { + d3.map(args).forEach((function(key,value) { + if (typeof this[key] === "function") { + this[key](value); + } + }).bind(this)); + } + return this; +};nv.models.axis = function() { + "use strict"; + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var axis = d3.svg.axis() + ; + + var margin = {top: 0, right: 0, bottom: 0, left: 0} + , width = 75 //only used for tickLabel currently + , height = 60 //only used for tickLabel currently + , scale = d3.scale.linear() + , axisLabelText = null + , showMaxMin = true //TODO: showMaxMin should be disabled on all ordinal scaled axes + , highlightZero = true + , rotateLabels = 0 + , rotateYLabel = true + , staggerLabels = false + , isOrdinal = false + , ticks = null + , axisLabelDistance = 12 //The larger this number is, the closer the axis label is to the axis. + ; + + axis + .scale(scale) + .orient('bottom') + .tickFormat(function(d) { return d }) + ; + + //============================================================ + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var scale0; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var container = d3.select(this); + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-axis').data([data]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-axis'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g') + + //------------------------------------------------------------ + + + if (ticks !== null) + axis.ticks(ticks); + else if (axis.orient() == 'top' || axis.orient() == 'bottom') + axis.ticks(Math.abs(scale.range()[1] - scale.range()[0]) / 100); + + + //TODO: consider calculating width/height based on whether or not label is added, for reference in charts using this component + + + g.transition().call(axis); + + scale0 = scale0 || axis.scale(); + + var fmt = axis.tickFormat(); + if (fmt == null) { + fmt = scale0.tickFormat(); + } + + var axisLabel = g.selectAll('text.nv-axislabel') + .data([axisLabelText || null]); + axisLabel.exit().remove(); + switch (axis.orient()) { + case 'top': + axisLabel.enter().append('text').attr('class', 'nv-axislabel'); + var w = (scale.range().length==2) ? scale.range()[1] : (scale.range()[scale.range().length-1]+(scale.range()[1]-scale.range()[0])); + axisLabel + .attr('text-anchor', 'middle') + .attr('y', 0) + .attr('x', w/2); + if (showMaxMin) { + var axisMaxMin = wrap.selectAll('g.nv-axisMaxMin') + .data(scale.domain()); + axisMaxMin.enter().append('g').attr('class', 'nv-axisMaxMin').append('text'); + axisMaxMin.exit().remove(); + axisMaxMin + .attr('transform', function(d,i) { + return 'translate(' + scale(d) + ',0)' + }) + .select('text') + .attr('dy', '0em') + .attr('y', -axis.tickPadding()) + .attr('text-anchor', 'middle') + .text(function(d,i) { + var v = fmt(d); + return ('' + v).match('NaN') ? '' : v; + }); + axisMaxMin.transition() + .attr('transform', function(d,i) { + return 'translate(' + scale.range()[i] + ',0)' + }); + } + break; + case 'bottom': + var xLabelMargin = 36; + var maxTextWidth = 30; + var xTicks = g.selectAll('g').select("text"); + if (rotateLabels%360) { + //Calculate the longest xTick width + xTicks.each(function(d,i){ + var width = this.getBBox().width; + if(width > maxTextWidth) maxTextWidth = width; + }); + //Convert to radians before calculating sin. Add 30 to margin for healthy padding. + var sin = Math.abs(Math.sin(rotateLabels*Math.PI/180)); + var xLabelMargin = (sin ? sin*maxTextWidth : maxTextWidth)+30; + //Rotate all xTicks + xTicks + .attr('transform', function(d,i,j) { return 'rotate(' + rotateLabels + ' 0,0)' }) + .style('text-anchor', rotateLabels%360 > 0 ? 'start' : 'end'); + } + axisLabel.enter().append('text').attr('class', 'nv-axislabel'); + var w = (scale.range().length==2) ? scale.range()[1] : (scale.range()[scale.range().length-1]+(scale.range()[1]-scale.range()[0])); + axisLabel + .attr('text-anchor', 'middle') + .attr('y', xLabelMargin) + .attr('x', w/2); + if (showMaxMin) { + //if (showMaxMin && !isOrdinal) { + var axisMaxMin = wrap.selectAll('g.nv-axisMaxMin') + //.data(scale.domain()) + .data([scale.domain()[0], scale.domain()[scale.domain().length - 1]]); + axisMaxMin.enter().append('g').attr('class', 'nv-axisMaxMin').append('text'); + axisMaxMin.exit().remove(); + axisMaxMin + .attr('transform', function(d,i) { + return 'translate(' + (scale(d) + (isOrdinal ? scale.rangeBand() / 2 : 0)) + ',0)' + }) + .select('text') + .attr('dy', '.71em') + .attr('y', axis.tickPadding()) + .attr('transform', function(d,i,j) { return 'rotate(' + rotateLabels + ' 0,0)' }) + .style('text-anchor', rotateLabels ? (rotateLabels%360 > 0 ? 'start' : 'end') : 'middle') + .text(function(d,i) { + var v = fmt(d); + return ('' + v).match('NaN') ? '' : v; + }); + axisMaxMin.transition() + .attr('transform', function(d,i) { + //return 'translate(' + scale.range()[i] + ',0)' + //return 'translate(' + scale(d) + ',0)' + return 'translate(' + (scale(d) + (isOrdinal ? scale.rangeBand() / 2 : 0)) + ',0)' + }); + } + if (staggerLabels) + xTicks + .attr('transform', function(d,i) { return 'translate(0,' + (i % 2 == 0 ? '0' : '12') + ')' }); + + break; + case 'right': + axisLabel.enter().append('text').attr('class', 'nv-axislabel'); + axisLabel + .style('text-anchor', rotateYLabel ? 'middle' : 'begin') + .attr('transform', rotateYLabel ? 'rotate(90)' : '') + .attr('y', rotateYLabel ? (-Math.max(margin.right,width) + 12) : -10) //TODO: consider calculating this based on largest tick width... OR at least expose this on chart + .attr('x', rotateYLabel ? (scale.range()[0] / 2) : axis.tickPadding()); + if (showMaxMin) { + var axisMaxMin = wrap.selectAll('g.nv-axisMaxMin') + .data(scale.domain()); + axisMaxMin.enter().append('g').attr('class', 'nv-axisMaxMin').append('text') + .style('opacity', 0); + axisMaxMin.exit().remove(); + axisMaxMin + .attr('transform', function(d,i) { + return 'translate(0,' + scale(d) + ')' + }) + .select('text') + .attr('dy', '.32em') + .attr('y', 0) + .attr('x', axis.tickPadding()) + .style('text-anchor', 'start') + .text(function(d,i) { + var v = fmt(d); + return ('' + v).match('NaN') ? '' : v; + }); + axisMaxMin.transition() + .attr('transform', function(d,i) { + return 'translate(0,' + scale.range()[i] + ')' + }) + .select('text') + .style('opacity', 1); + } + break; + case 'left': + /* + //For dynamically placing the label. Can be used with dynamically-sized chart axis margins + var yTicks = g.selectAll('g').select("text"); + yTicks.each(function(d,i){ + var labelPadding = this.getBBox().width + axis.tickPadding() + 16; + if(labelPadding > width) width = labelPadding; + }); + */ + axisLabel.enter().append('text').attr('class', 'nv-axislabel'); + axisLabel + .style('text-anchor', rotateYLabel ? 'middle' : 'end') + .attr('transform', rotateYLabel ? 'rotate(-90)' : '') + .attr('y', rotateYLabel ? (-Math.max(margin.left,width) + axisLabelDistance) : -10) //TODO: consider calculating this based on largest tick width... OR at least expose this on chart + .attr('x', rotateYLabel ? (-scale.range()[0] / 2) : -axis.tickPadding()); + if (showMaxMin) { + var axisMaxMin = wrap.selectAll('g.nv-axisMaxMin') + .data(scale.domain()); + axisMaxMin.enter().append('g').attr('class', 'nv-axisMaxMin').append('text') + .style('opacity', 0); + axisMaxMin.exit().remove(); + axisMaxMin + .attr('transform', function(d,i) { + return 'translate(0,' + scale0(d) + ')' + }) + .select('text') + .attr('dy', '.32em') + .attr('y', 0) + .attr('x', -axis.tickPadding()) + .attr('text-anchor', 'end') + .text(function(d,i) { + var v = fmt(d); + return ('' + v).match('NaN') ? '' : v; + }); + axisMaxMin.transition() + .attr('transform', function(d,i) { + return 'translate(0,' + scale.range()[i] + ')' + }) + .select('text') + .style('opacity', 1); + } + break; + } + axisLabel + .text(function(d) { return d }); + + + if (showMaxMin && (axis.orient() === 'left' || axis.orient() === 'right')) { + //check if max and min overlap other values, if so, hide the values that overlap + g.selectAll('g') // the g's wrapping each tick + .each(function(d,i) { + d3.select(this).select('text').attr('opacity', 1); + if (scale(d) < scale.range()[1] + 10 || scale(d) > scale.range()[0] - 10) { // 10 is assuming text height is 16... if d is 0, leave it! + if (d > 1e-10 || d < -1e-10) // accounts for minor floating point errors... though could be problematic if the scale is EXTREMELY SMALL + d3.select(this).attr('opacity', 0); + + d3.select(this).select('text').attr('opacity', 0); // Don't remove the ZERO line!! + } + }); + + //if Max and Min = 0 only show min, Issue #281 + if (scale.domain()[0] == scale.domain()[1] && scale.domain()[0] == 0) + wrap.selectAll('g.nv-axisMaxMin') + .style('opacity', function(d,i) { return !i ? 1 : 0 }); + + } + + if (showMaxMin && (axis.orient() === 'top' || axis.orient() === 'bottom')) { + var maxMinRange = []; + wrap.selectAll('g.nv-axisMaxMin') + .each(function(d,i) { + try { + if (i) // i== 1, max position + maxMinRange.push(scale(d) - this.getBBox().width - 4) //assuming the max and min labels are as wide as the next tick (with an extra 4 pixels just in case) + else // i==0, min position + maxMinRange.push(scale(d) + this.getBBox().width + 4) + }catch (err) { + if (i) // i== 1, max position + maxMinRange.push(scale(d) - 4) //assuming the max and min labels are as wide as the next tick (with an extra 4 pixels just in case) + else // i==0, min position + maxMinRange.push(scale(d) + 4) + } + }); + g.selectAll('g') // the g's wrapping each tick + .each(function(d,i) { + if (scale(d) < maxMinRange[0] || scale(d) > maxMinRange[1]) { + if (d > 1e-10 || d < -1e-10) // accounts for minor floating point errors... though could be problematic if the scale is EXTREMELY SMALL + d3.select(this).remove(); + else + d3.select(this).select('text').remove(); // Don't remove the ZERO line!! + } + }); + } + + + //highlight zero line ... Maybe should not be an option and should just be in CSS? + if (highlightZero) + g.selectAll('.tick') + .filter(function(d) { return !parseFloat(Math.round(d.__data__*100000)/1000000) && (d.__data__ !== undefined) }) //this is because sometimes the 0 tick is a very small fraction, TODO: think of cleaner technique + .classed('zero', true); + + //store old scales for use in transitions on update + scale0 = scale.copy(); + + }); + + return chart; + } + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + // expose chart's sub-components + chart.axis = axis; + + d3.rebind(chart, axis, 'orient', 'tickValues', 'tickSubdivide', 'tickSize', 'tickPadding', 'tickFormat'); + d3.rebind(chart, scale, 'domain', 'range', 'rangeBand', 'rangeBands'); //these are also accessible by chart.scale(), but added common ones directly for ease of use + + chart.options = nv.utils.optionsFunc.bind(chart); + + chart.margin = function(_) { + if(!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + } + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.ticks = function(_) { + if (!arguments.length) return ticks; + ticks = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.axisLabel = function(_) { + if (!arguments.length) return axisLabelText; + axisLabelText = _; + return chart; + } + + chart.showMaxMin = function(_) { + if (!arguments.length) return showMaxMin; + showMaxMin = _; + return chart; + } + + chart.highlightZero = function(_) { + if (!arguments.length) return highlightZero; + highlightZero = _; + return chart; + } + + chart.scale = function(_) { + if (!arguments.length) return scale; + scale = _; + axis.scale(scale); + isOrdinal = typeof scale.rangeBands === 'function'; + d3.rebind(chart, scale, 'domain', 'range', 'rangeBand', 'rangeBands'); + return chart; + } + + chart.rotateYLabel = function(_) { + if(!arguments.length) return rotateYLabel; + rotateYLabel = _; + return chart; + } + + chart.rotateLabels = function(_) { + if(!arguments.length) return rotateLabels; + rotateLabels = _; + return chart; + } + + chart.staggerLabels = function(_) { + if (!arguments.length) return staggerLabels; + staggerLabels = _; + return chart; + }; + + chart.axisLabelDistance = function(_) { + if (!arguments.length) return axisLabelDistance; + axisLabelDistance = _; + return chart; + }; + + //============================================================ + + + return chart; +} +//TODO: consider deprecating and using multibar with single series for this +nv.models.historicalBar = function() { + "use strict"; + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var margin = {top: 0, right: 0, bottom: 0, left: 0} + , width = 960 + , height = 500 + , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one + , x = d3.scale.linear() + , y = d3.scale.linear() + , getX = function(d) { return d.x } + , getY = function(d) { return d.y } + , forceX = [] + , forceY = [0] + , padData = false + , clipEdge = true + , color = nv.utils.defaultColor() + , xDomain + , yDomain + , xRange + , yRange + , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout') + , interactive = true + ; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var availableWidth = width - margin.left - margin.right, + availableHeight = height - margin.top - margin.bottom, + container = d3.select(this); + + + //------------------------------------------------------------ + // Setup Scales + + x .domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) )) + + if (padData) + x.range(xRange || [availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]); + else + x.range(xRange || [0, availableWidth]); + + y .domain(yDomain || d3.extent(data[0].values.map(getY).concat(forceY) )) + .range(yRange || [availableHeight, 0]); + + // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point + + if (x.domain()[0] === x.domain()[1]) + x.domain()[0] ? + x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01]) + : x.domain([-1,1]); + + if (y.domain()[0] === y.domain()[1]) + y.domain()[0] ? + y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01]) + : y.domain([-1,1]); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-historicalBar-' + id).data([data[0].values]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-historicalBar-' + id); + var defsEnter = wrapEnter.append('defs'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-bars'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + //------------------------------------------------------------ + + + container + .on('click', function(d,i) { + dispatch.chartClick({ + data: d, + index: i, + pos: d3.event, + id: id + }); + }); + + + defsEnter.append('clipPath') + .attr('id', 'nv-chart-clip-path-' + id) + .append('rect'); + + wrap.select('#nv-chart-clip-path-' + id + ' rect') + .attr('width', availableWidth) + .attr('height', availableHeight); + + g .attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : ''); + + + + var bars = wrap.select('.nv-bars').selectAll('.nv-bar') + .data(function(d) { return d }, function(d,i) {return getX(d,i)}); + + bars.exit().remove(); + + + var barsEnter = bars.enter().append('rect') + //.attr('class', function(d,i,j) { return (getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive') + ' nv-bar-' + j + '-' + i }) + .attr('x', 0 ) + .attr('y', function(d,i) { return nv.utils.NaNtoZero(y(Math.max(0, getY(d,i)))) }) + .attr('height', function(d,i) { return nv.utils.NaNtoZero(Math.abs(y(getY(d,i)) - y(0))) }) + .attr('transform', function(d,i) { return 'translate(' + (x(getX(d,i)) - availableWidth / data[0].values.length * .45) + ',0)'; }) + .on('mouseover', function(d,i) { + if (!interactive) return; + d3.select(this).classed('hover', true); + dispatch.elementMouseover({ + point: d, + series: data[0], + pos: [x(getX(d,i)), y(getY(d,i))], // TODO: Figure out why the value appears to be shifted + pointIndex: i, + seriesIndex: 0, + e: d3.event + }); + + }) + .on('mouseout', function(d,i) { + if (!interactive) return; + d3.select(this).classed('hover', false); + dispatch.elementMouseout({ + point: d, + series: data[0], + pointIndex: i, + seriesIndex: 0, + e: d3.event + }); + }) + .on('click', function(d,i) { + if (!interactive) return; + dispatch.elementClick({ + //label: d[label], + value: getY(d,i), + data: d, + index: i, + pos: [x(getX(d,i)), y(getY(d,i))], + e: d3.event, + id: id + }); + d3.event.stopPropagation(); + }) + .on('dblclick', function(d,i) { + if (!interactive) return; + dispatch.elementDblClick({ + //label: d[label], + value: getY(d,i), + data: d, + index: i, + pos: [x(getX(d,i)), y(getY(d,i))], + e: d3.event, + id: id + }); + d3.event.stopPropagation(); + }); + + bars + .attr('fill', function(d,i) { return color(d, i); }) + .attr('class', function(d,i,j) { return (getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive') + ' nv-bar-' + j + '-' + i }) + .transition() + .attr('transform', function(d,i) { return 'translate(' + (x(getX(d,i)) - availableWidth / data[0].values.length * .45) + ',0)'; }) + //TODO: better width calculations that don't assume always uniform data spacing;w + .attr('width', (availableWidth / data[0].values.length) * .9 ); + + + bars.transition() + .attr('y', function(d,i) { + var rval = getY(d,i) < 0 ? + y(0) : + y(0) - y(getY(d,i)) < 1 ? + y(0) - 1 : + y(getY(d,i)); + return nv.utils.NaNtoZero(rval); + }) + .attr('height', function(d,i) { return nv.utils.NaNtoZero(Math.max(Math.abs(y(getY(d,i)) - y(0)),1)) }); + + }); + + return chart; + } + + //Create methods to allow outside functions to highlight a specific bar. + chart.highlightPoint = function(pointIndex, isHoverOver) { + d3.select(".nv-historicalBar-" + id) + .select(".nv-bars .nv-bar-0-" + pointIndex) + .classed("hover", isHoverOver) + ; + }; + + chart.clearHighlights = function() { + d3.select(".nv-historicalBar-" + id) + .select(".nv-bars .nv-bar.hover") + .classed("hover", false) + ; + }; + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + chart.dispatch = dispatch; + + chart.options = nv.utils.optionsFunc.bind(chart); + + chart.x = function(_) { + if (!arguments.length) return getX; + getX = _; + return chart; + }; + + chart.y = function(_) { + if (!arguments.length) return getY; + getY = _; + return chart; + }; + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.xScale = function(_) { + if (!arguments.length) return x; + x = _; + return chart; + }; + + chart.yScale = function(_) { + if (!arguments.length) return y; + y = _; + return chart; + }; + + chart.xDomain = function(_) { + if (!arguments.length) return xDomain; + xDomain = _; + return chart; + }; + + chart.yDomain = function(_) { + if (!arguments.length) return yDomain; + yDomain = _; + return chart; + }; + + chart.xRange = function(_) { + if (!arguments.length) return xRange; + xRange = _; + return chart; + }; + + chart.yRange = function(_) { + if (!arguments.length) return yRange; + yRange = _; + return chart; + }; + + chart.forceX = function(_) { + if (!arguments.length) return forceX; + forceX = _; + return chart; + }; + + chart.forceY = function(_) { + if (!arguments.length) return forceY; + forceY = _; + return chart; + }; + + chart.padData = function(_) { + if (!arguments.length) return padData; + padData = _; + return chart; + }; + + chart.clipEdge = function(_) { + if (!arguments.length) return clipEdge; + clipEdge = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + return chart; + }; + + chart.id = function(_) { + if (!arguments.length) return id; + id = _; + return chart; + }; + + chart.interactive = function(_) { + if(!arguments.length) return interactive; + interactive = false; + return chart; + }; + + //============================================================ + + + return chart; +} + +// Chart design based on the recommendations of Stephen Few. Implementation +// based on the work of Clint Ivy, Jamie Love, and Jason Davies. +// http://projects.instantcognition.com/protovis/bulletchart/ + +nv.models.bullet = function() { + "use strict"; + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var margin = {top: 0, right: 0, bottom: 0, left: 0} + , orient = 'left' // TODO top & bottom + , reverse = false + , ranges = function(d) { return d.ranges } + , markers = function(d) { return d.markers } + , measures = function(d) { return d.measures } + , rangeLabels = function(d) { return d.rangeLabels ? d.rangeLabels : [] } + , markerLabels = function(d) { return d.markerLabels ? d.markerLabels : [] } + , measureLabels = function(d) { return d.measureLabels ? d.measureLabels : [] } + , forceX = [0] // List of numbers to Force into the X scale (ie. 0, or a max / min, etc.) + , width = 380 + , height = 30 + , tickFormat = null + , color = nv.utils.getColor(['#1f77b4']) + , dispatch = d3.dispatch('elementMouseover', 'elementMouseout') + ; + + //============================================================ + + + function chart(selection) { + selection.each(function(d, i) { + var availableWidth = width - margin.left - margin.right, + availableHeight = height - margin.top - margin.bottom, + container = d3.select(this); + + var rangez = ranges.call(this, d, i).slice().sort(d3.descending), + markerz = markers.call(this, d, i).slice().sort(d3.descending), + measurez = measures.call(this, d, i).slice().sort(d3.descending), + rangeLabelz = rangeLabels.call(this, d, i).slice(), + markerLabelz = markerLabels.call(this, d, i).slice(), + measureLabelz = measureLabels.call(this, d, i).slice(); + + + //------------------------------------------------------------ + // Setup Scales + + // Compute the new x-scale. + var x1 = d3.scale.linear() + .domain( d3.extent(d3.merge([forceX, rangez])) ) + .range(reverse ? [availableWidth, 0] : [0, availableWidth]); + + // Retrieve the old x-scale, if this is an update. + var x0 = this.__chart__ || d3.scale.linear() + .domain([0, Infinity]) + .range(x1.range()); + + // Stash the new scale. + this.__chart__ = x1; + + + var rangeMin = d3.min(rangez), //rangez[2] + rangeMax = d3.max(rangez), //rangez[0] + rangeAvg = rangez[1]; + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-bullet').data([d]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-bullet'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); + + gEnter.append('rect').attr('class', 'nv-range nv-rangeMax'); + gEnter.append('rect').attr('class', 'nv-range nv-rangeAvg'); + gEnter.append('rect').attr('class', 'nv-range nv-rangeMin'); + gEnter.append('rect').attr('class', 'nv-measure'); + gEnter.append('path').attr('class', 'nv-markerTriangle'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + //------------------------------------------------------------ + + + + var w0 = function(d) { return Math.abs(x0(d) - x0(0)) }, // TODO: could optimize by precalculating x0(0) and x1(0) + w1 = function(d) { return Math.abs(x1(d) - x1(0)) }; + var xp0 = function(d) { return d < 0 ? x0(d) : x0(0) }, + xp1 = function(d) { return d < 0 ? x1(d) : x1(0) }; + + + g.select('rect.nv-rangeMax') + .attr('height', availableHeight) + .attr('width', w1(rangeMax > 0 ? rangeMax : rangeMin)) + .attr('x', xp1(rangeMax > 0 ? rangeMax : rangeMin)) + .datum(rangeMax > 0 ? rangeMax : rangeMin) + /* + .attr('x', rangeMin < 0 ? + rangeMax > 0 ? + x1(rangeMin) + : x1(rangeMax) + : x1(0)) + */ + + g.select('rect.nv-rangeAvg') + .attr('height', availableHeight) + .attr('width', w1(rangeAvg)) + .attr('x', xp1(rangeAvg)) + .datum(rangeAvg) + /* + .attr('width', rangeMax <= 0 ? + x1(rangeMax) - x1(rangeAvg) + : x1(rangeAvg) - x1(rangeMin)) + .attr('x', rangeMax <= 0 ? + x1(rangeAvg) + : x1(rangeMin)) + */ + + g.select('rect.nv-rangeMin') + .attr('height', availableHeight) + .attr('width', w1(rangeMax)) + .attr('x', xp1(rangeMax)) + .attr('width', w1(rangeMax > 0 ? rangeMin : rangeMax)) + .attr('x', xp1(rangeMax > 0 ? rangeMin : rangeMax)) + .datum(rangeMax > 0 ? rangeMin : rangeMax) + /* + .attr('width', rangeMax <= 0 ? + x1(rangeAvg) - x1(rangeMin) + : x1(rangeMax) - x1(rangeAvg)) + .attr('x', rangeMax <= 0 ? + x1(rangeMin) + : x1(rangeAvg)) + */ + + g.select('rect.nv-measure') + .style('fill', color) + .attr('height', availableHeight / 3) + .attr('y', availableHeight / 3) + .attr('width', measurez < 0 ? + x1(0) - x1(measurez[0]) + : x1(measurez[0]) - x1(0)) + .attr('x', xp1(measurez)) + .on('mouseover', function() { + dispatch.elementMouseover({ + value: measurez[0], + label: measureLabelz[0] || 'Current', + pos: [x1(measurez[0]), availableHeight/2] + }) + }) + .on('mouseout', function() { + dispatch.elementMouseout({ + value: measurez[0], + label: measureLabelz[0] || 'Current' + }) + }) + + var h3 = availableHeight / 6; + if (markerz[0]) { + g.selectAll('path.nv-markerTriangle') + .attr('transform', function(d) { return 'translate(' + x1(markerz[0]) + ',' + (availableHeight / 2) + ')' }) + .attr('d', 'M0,' + h3 + 'L' + h3 + ',' + (-h3) + ' ' + (-h3) + ',' + (-h3) + 'Z') + .on('mouseover', function() { + dispatch.elementMouseover({ + value: markerz[0], + label: markerLabelz[0] || 'Previous', + pos: [x1(markerz[0]), availableHeight/2] + }) + }) + .on('mouseout', function() { + dispatch.elementMouseout({ + value: markerz[0], + label: markerLabelz[0] || 'Previous' + }) + }); + } else { + g.selectAll('path.nv-markerTriangle').remove(); + } + + + wrap.selectAll('.nv-range') + .on('mouseover', function(d,i) { + var label = rangeLabelz[i] || (!i ? "Maximum" : i == 1 ? "Mean" : "Minimum"); + + dispatch.elementMouseover({ + value: d, + label: label, + pos: [x1(d), availableHeight/2] + }) + }) + .on('mouseout', function(d,i) { + var label = rangeLabelz[i] || (!i ? "Maximum" : i == 1 ? "Mean" : "Minimum"); + + dispatch.elementMouseout({ + value: d, + label: label + }) + }) + +/* // THIS IS THE PREVIOUS BULLET IMPLEMENTATION, WILL REMOVE SHORTLY + // Update the range rects. + var range = g.selectAll('rect.nv-range') + .data(rangez); + + range.enter().append('rect') + .attr('class', function(d, i) { return 'nv-range nv-s' + i; }) + .attr('width', w0) + .attr('height', availableHeight) + .attr('x', reverse ? x0 : 0) + .on('mouseover', function(d,i) { + dispatch.elementMouseover({ + value: d, + label: (i <= 0) ? 'Maximum' : (i > 1) ? 'Minimum' : 'Mean', //TODO: make these labels a variable + pos: [x1(d), availableHeight/2] + }) + }) + .on('mouseout', function(d,i) { + dispatch.elementMouseout({ + value: d, + label: (i <= 0) ? 'Minimum' : (i >=1) ? 'Maximum' : 'Mean' //TODO: make these labels a variable + }) + }) + + d3.transition(range) + .attr('x', reverse ? x1 : 0) + .attr('width', w1) + .attr('height', availableHeight); + + + // Update the measure rects. + var measure = g.selectAll('rect.nv-measure') + .data(measurez); + + measure.enter().append('rect') + .attr('class', function(d, i) { return 'nv-measure nv-s' + i; }) + .style('fill', function(d,i) { return color(d,i ) }) + .attr('width', w0) + .attr('height', availableHeight / 3) + .attr('x', reverse ? x0 : 0) + .attr('y', availableHeight / 3) + .on('mouseover', function(d) { + dispatch.elementMouseover({ + value: d, + label: 'Current', //TODO: make these labels a variable + pos: [x1(d), availableHeight/2] + }) + }) + .on('mouseout', function(d) { + dispatch.elementMouseout({ + value: d, + label: 'Current' //TODO: make these labels a variable + }) + }) + + d3.transition(measure) + .attr('width', w1) + .attr('height', availableHeight / 3) + .attr('x', reverse ? x1 : 0) + .attr('y', availableHeight / 3); + + + + // Update the marker lines. + var marker = g.selectAll('path.nv-markerTriangle') + .data(markerz); + + var h3 = availableHeight / 6; + marker.enter().append('path') + .attr('class', 'nv-markerTriangle') + .attr('transform', function(d) { return 'translate(' + x0(d) + ',' + (availableHeight / 2) + ')' }) + .attr('d', 'M0,' + h3 + 'L' + h3 + ',' + (-h3) + ' ' + (-h3) + ',' + (-h3) + 'Z') + .on('mouseover', function(d,i) { + dispatch.elementMouseover({ + value: d, + label: 'Previous', + pos: [x1(d), availableHeight/2] + }) + }) + .on('mouseout', function(d,i) { + dispatch.elementMouseout({ + value: d, + label: 'Previous' + }) + }); + + d3.transition(marker) + .attr('transform', function(d) { return 'translate(' + (x1(d) - x1(0)) + ',' + (availableHeight / 2) + ')' }); + + marker.exit().remove(); +*/ + + }); + + // d3.timer.flush(); // Not needed? + + return chart; + } + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + chart.dispatch = dispatch; + + chart.options = nv.utils.optionsFunc.bind(chart); + + // left, right, top, bottom + chart.orient = function(_) { + if (!arguments.length) return orient; + orient = _; + reverse = orient == 'right' || orient == 'bottom'; + return chart; + }; + + // ranges (bad, satisfactory, good) + chart.ranges = function(_) { + if (!arguments.length) return ranges; + ranges = _; + return chart; + }; + + // markers (previous, goal) + chart.markers = function(_) { + if (!arguments.length) return markers; + markers = _; + return chart; + }; + + // measures (actual, forecast) + chart.measures = function(_) { + if (!arguments.length) return measures; + measures = _; + return chart; + }; + + chart.forceX = function(_) { + if (!arguments.length) return forceX; + forceX = _; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.tickFormat = function(_) { + if (!arguments.length) return tickFormat; + tickFormat = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + return chart; + }; + + //============================================================ + + + return chart; +}; + + + +// Chart design based on the recommendations of Stephen Few. Implementation +// based on the work of Clint Ivy, Jamie Love, and Jason Davies. +// http://projects.instantcognition.com/protovis/bulletchart/ +nv.models.bulletChart = function() { + "use strict"; + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var bullet = nv.models.bullet() + ; + + var orient = 'left' // TODO top & bottom + , reverse = false + , margin = {top: 5, right: 40, bottom: 20, left: 120} + , ranges = function(d) { return d.ranges } + , markers = function(d) { return d.markers } + , measures = function(d) { return d.measures } + , width = null + , height = 55 + , tickFormat = null + , tooltips = true + , tooltip = function(key, x, y, e, graph) { + return '<h3>' + x + '</h3>' + + '<p>' + y + '</p>' + } + , noData = 'No Data Available.' + , dispatch = d3.dispatch('tooltipShow', 'tooltipHide') + ; + + //============================================================ + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var showTooltip = function(e, offsetElement) { + var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ) + margin.left, + top = e.pos[1] + ( offsetElement.offsetTop || 0) + margin.top, + content = tooltip(e.key, e.label, e.value, e, chart); + + nv.tooltip.show([left, top], content, e.value < 0 ? 'e' : 'w', null, offsetElement); + }; + + //============================================================ + + + function chart(selection) { + selection.each(function(d, i) { + var container = d3.select(this); + + var availableWidth = (width || parseInt(container.style('width')) || 960) + - margin.left - margin.right, + availableHeight = height - margin.top - margin.bottom, + that = this; + + + chart.update = function() { chart(selection) }; + chart.container = this; + + //------------------------------------------------------------ + // Display No Data message if there's nothing to show. + + if (!d || !ranges.call(this, d, i)) { + var noDataText = container.selectAll('.nv-noData').data([noData]); + + noDataText.enter().append('text') + .attr('class', 'nvd3 nv-noData') + .attr('dy', '-.7em') + .style('text-anchor', 'middle'); + + noDataText + .attr('x', margin.left + availableWidth / 2) + .attr('y', 18 + margin.top + availableHeight / 2) + .text(function(d) { return d }); + + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } + + //------------------------------------------------------------ + + + + var rangez = ranges.call(this, d, i).slice().sort(d3.descending), + markerz = markers.call(this, d, i).slice().sort(d3.descending), + measurez = measures.call(this, d, i).slice().sort(d3.descending); + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-bulletChart').data([d]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-bulletChart'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-bulletWrap'); + gEnter.append('g').attr('class', 'nv-titles'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + //------------------------------------------------------------ + + + // Compute the new x-scale. + var x1 = d3.scale.linear() + .domain([0, Math.max(rangez[0], markerz[0], measurez[0])]) // TODO: need to allow forceX and forceY, and xDomain, yDomain + .range(reverse ? [availableWidth, 0] : [0, availableWidth]); + + // Retrieve the old x-scale, if this is an update. + var x0 = this.__chart__ || d3.scale.linear() + .domain([0, Infinity]) + .range(x1.range()); + + // Stash the new scale. + this.__chart__ = x1; + + /* + // Derive width-scales from the x-scales. + var w0 = bulletWidth(x0), + w1 = bulletWidth(x1); + + function bulletWidth(x) { + var x0 = x(0); + return function(d) { + return Math.abs(x(d) - x(0)); + }; + } + + function bulletTranslate(x) { + return function(d) { + return 'translate(' + x(d) + ',0)'; + }; + } + */ + + var w0 = function(d) { return Math.abs(x0(d) - x0(0)) }, // TODO: could optimize by precalculating x0(0) and x1(0) + w1 = function(d) { return Math.abs(x1(d) - x1(0)) }; + + + var title = gEnter.select('.nv-titles').append('g') + .attr('text-anchor', 'end') + .attr('transform', 'translate(-6,' + (height - margin.top - margin.bottom) / 2 + ')'); + title.append('text') + .attr('class', 'nv-title') + .text(function(d) { return d.title; }); + + title.append('text') + .attr('class', 'nv-subtitle') + .attr('dy', '1em') + .text(function(d) { return d.subtitle; }); + + + + bullet + .width(availableWidth) + .height(availableHeight) + + var bulletWrap = g.select('.nv-bulletWrap'); + + d3.transition(bulletWrap).call(bullet); + + + + // Compute the tick format. + var format = tickFormat || x1.tickFormat( availableWidth / 100 ); + + // Update the tick groups. + var tick = g.selectAll('g.nv-tick') + .data(x1.ticks( availableWidth / 50 ), function(d) { + return this.textContent || format(d); + }); + + // Initialize the ticks with the old scale, x0. + var tickEnter = tick.enter().append('g') + .attr('class', 'nv-tick') + .attr('transform', function(d) { return 'translate(' + x0(d) + ',0)' }) + .style('opacity', 1e-6); + + tickEnter.append('line') + .attr('y1', availableHeight) + .attr('y2', availableHeight * 7 / 6); + + tickEnter.append('text') + .attr('text-anchor', 'middle') + .attr('dy', '1em') + .attr('y', availableHeight * 7 / 6) + .text(format); + + + // Transition the updating ticks to the new scale, x1. + var tickUpdate = d3.transition(tick) + .attr('transform', function(d) { return 'translate(' + x1(d) + ',0)' }) + .style('opacity', 1); + + tickUpdate.select('line') + .attr('y1', availableHeight) + .attr('y2', availableHeight * 7 / 6); + + tickUpdate.select('text') + .attr('y', availableHeight * 7 / 6); + + // Transition the exiting ticks to the new scale, x1. + d3.transition(tick.exit()) + .attr('transform', function(d) { return 'translate(' + x1(d) + ',0)' }) + .style('opacity', 1e-6) + .remove(); + + + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ + + dispatch.on('tooltipShow', function(e) { + e.key = d.title; + if (tooltips) showTooltip(e, that.parentNode); + }); + + //============================================================ + + }); + + d3.timer.flush(); + + return chart; + } + + + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ + + bullet.dispatch.on('elementMouseover.tooltip', function(e) { + dispatch.tooltipShow(e); + }); + + bullet.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + + dispatch.on('tooltipHide', function() { + if (tooltips) nv.tooltip.cleanup(); + }); + + //============================================================ + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + chart.dispatch = dispatch; + chart.bullet = bullet; + + d3.rebind(chart, bullet, 'color'); + + chart.options = nv.utils.optionsFunc.bind(chart); + + // left, right, top, bottom + chart.orient = function(x) { + if (!arguments.length) return orient; + orient = x; + reverse = orient == 'right' || orient == 'bottom'; + return chart; + }; + + // ranges (bad, satisfactory, good) + chart.ranges = function(x) { + if (!arguments.length) return ranges; + ranges = x; + return chart; + }; + + // markers (previous, goal) + chart.markers = function(x) { + if (!arguments.length) return markers; + markers = x; + return chart; + }; + + // measures (actual, forecast) + chart.measures = function(x) { + if (!arguments.length) return measures; + measures = x; + return chart; + }; + + chart.width = function(x) { + if (!arguments.length) return width; + width = x; + return chart; + }; + + chart.height = function(x) { + if (!arguments.length) return height; + height = x; + return chart; + }; + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.tickFormat = function(x) { + if (!arguments.length) return tickFormat; + tickFormat = x; + return chart; + }; + + chart.tooltips = function(_) { + if (!arguments.length) return tooltips; + tooltips = _; + return chart; + }; + + chart.tooltipContent = function(_) { + if (!arguments.length) return tooltip; + tooltip = _; + return chart; + }; + + chart.noData = function(_) { + if (!arguments.length) return noData; + noData = _; + return chart; + }; + + //============================================================ + + + return chart; +}; + + + +nv.models.cumulativeLineChart = function() { + "use strict"; + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var lines = nv.models.line() + , xAxis = nv.models.axis() + , yAxis = nv.models.axis() + , legend = nv.models.legend() + , controls = nv.models.legend() + , interactiveLayer = nv.interactiveGuideline() + ; + + var margin = {top: 30, right: 30, bottom: 50, left: 60} + , color = nv.utils.defaultColor() + , width = null + , height = null + , showLegend = true + , showXAxis = true + , showYAxis = true + , rightAlignYAxis = false + , tooltips = true + , showControls = true + , useInteractiveGuideline = false + , rescaleY = true + , tooltip = function(key, x, y, e, graph) { + return '<h3>' + key + '</h3>' + + '<p>' + y + ' at ' + x + '</p>' + } + , x //can be accessed via chart.xScale() + , y //can be accessed via chart.yScale() + , id = lines.id() + , state = { index: 0, rescaleY: rescaleY } + , defaultState = null + , noData = 'No Data Available.' + , average = function(d) { return d.average } + , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState') + , transitionDuration = 250 + ; + + xAxis + .orient('bottom') + .tickPadding(7) + ; + yAxis + .orient((rightAlignYAxis) ? 'right' : 'left') + ; + + //============================================================ + controls.updateState(false); + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var dx = d3.scale.linear() + , index = {i: 0, x: 0} + ; + + var showTooltip = function(e, offsetElement) { + var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ), + top = e.pos[1] + ( offsetElement.offsetTop || 0), + x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)), + y = yAxis.tickFormat()(lines.y()(e.point, e.pointIndex)), + content = tooltip(e.series.key, x, y, e, chart); + + nv.tooltip.show([left, top], content, null, null, offsetElement); + }; + +/* + //Moved to see if we can get better behavior to fix issue #315 + var indexDrag = d3.behavior.drag() + .on('dragstart', dragStart) + .on('drag', dragMove) + .on('dragend', dragEnd); + + function dragStart(d,i) { + d3.select(chart.container) + .style('cursor', 'ew-resize'); + } + + function dragMove(d,i) { + d.x += d3.event.dx; + d.i = Math.round(dx.invert(d.x)); + + d3.select(this).attr('transform', 'translate(' + dx(d.i) + ',0)'); + chart.update(); + } + + function dragEnd(d,i) { + d3.select(chart.container) + .style('cursor', 'auto'); + chart.update(); + } +*/ + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var container = d3.select(this).classed('nv-chart-' + id, true), + that = this; + + var availableWidth = (width || parseInt(container.style('width')) || 960) + - margin.left - margin.right, + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + + + chart.update = function() { container.transition().duration(transitionDuration).call(chart) }; + chart.container = this; + + //set state.disabled + state.disabled = data.map(function(d) { return !!d.disabled }); + + if (!defaultState) { + var key; + defaultState = {}; + for (key in state) { + if (state[key] instanceof Array) + defaultState[key] = state[key].slice(0); + else + defaultState[key] = state[key]; + } + } + + var indexDrag = d3.behavior.drag() + .on('dragstart', dragStart) + .on('drag', dragMove) + .on('dragend', dragEnd); + + + function dragStart(d,i) { + d3.select(chart.container) + .style('cursor', 'ew-resize'); + } + + function dragMove(d,i) { + index.x = d3.event.x; + index.i = Math.round(dx.invert(index.x)); + updateZero(); + } + + function dragEnd(d,i) { + d3.select(chart.container) + .style('cursor', 'auto'); + + // update state and send stateChange with new index + state.index = index.i; + dispatch.stateChange(state); + } + + //------------------------------------------------------------ + // Display No Data message if there's nothing to show. + + if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { + var noDataText = container.selectAll('.nv-noData').data([noData]); + + noDataText.enter().append('text') + .attr('class', 'nvd3 nv-noData') + .attr('dy', '-.7em') + .style('text-anchor', 'middle'); + + noDataText + .attr('x', margin.left + availableWidth / 2) + .attr('y', margin.top + availableHeight / 2) + .text(function(d) { return d }); + + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup Scales + + x = lines.xScale(); + y = lines.yScale(); + + + if (!rescaleY) { + var seriesDomains = data + .filter(function(series) { return !series.disabled }) + .map(function(series,i) { + var initialDomain = d3.extent(series.values, lines.y()); + + //account for series being disabled when losing 95% or more + if (initialDomain[0] < -.95) initialDomain[0] = -.95; + + return [ + (initialDomain[0] - initialDomain[1]) / (1 + initialDomain[1]), + (initialDomain[1] - initialDomain[0]) / (1 + initialDomain[0]) + ]; + }); + + var completeDomain = [ + d3.min(seriesDomains, function(d) { return d[0] }), + d3.max(seriesDomains, function(d) { return d[1] }) + ] + + lines.yDomain(completeDomain); + } else { + lines.yDomain(null); + } + + + dx .domain([0, data[0].values.length - 1]) //Assumes all series have same length + .range([0, availableWidth]) + .clamp(true); + + //------------------------------------------------------------ + + + var data = indexify(index.i, data); + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + var interactivePointerEvents = (useInteractiveGuideline) ? "none" : "all"; + var wrap = container.selectAll('g.nv-wrap.nv-cumulativeLine').data([data]); + var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-cumulativeLine').append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-interactive'); + gEnter.append('g').attr('class', 'nv-x nv-axis').style("pointer-events","none"); + gEnter.append('g').attr('class', 'nv-y nv-axis'); + gEnter.append('g').attr('class', 'nv-background'); + gEnter.append('g').attr('class', 'nv-linesWrap').style("pointer-events",interactivePointerEvents); + gEnter.append('g').attr('class', 'nv-avgLinesWrap').style("pointer-events","none"); + gEnter.append('g').attr('class', 'nv-legendWrap'); + gEnter.append('g').attr('class', 'nv-controlsWrap'); + + + //------------------------------------------------------------ + // Legend + + if (showLegend) { + legend.width(availableWidth); + + g.select('.nv-legendWrap') + .datum(data) + .call(legend); + + if ( margin.top != legend.height()) { + margin.top = legend.height(); + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + } + + g.select('.nv-legendWrap') + .attr('transform', 'translate(0,' + (-margin.top) +')') + } + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Controls + + if (showControls) { + var controlsData = [ + { key: 'Re-scale y-axis', disabled: !rescaleY } + ]; + + controls.width(140).color(['#444', '#444', '#444']); + g.select('.nv-controlsWrap') + .datum(controlsData) + .attr('transform', 'translate(0,' + (-margin.top) +')') + .call(controls); + } + + //------------------------------------------------------------ + + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + if (rightAlignYAxis) { + g.select(".nv-y.nv-axis") + .attr("transform", "translate(" + availableWidth + ",0)"); + } + + // Show error if series goes below 100% + var tempDisabled = data.filter(function(d) { return d.tempDisabled }); + + wrap.select('.tempDisabled').remove(); //clean-up and prevent duplicates + if (tempDisabled.length) { + wrap.append('text').attr('class', 'tempDisabled') + .attr('x', availableWidth / 2) + .attr('y', '-.71em') + .style('text-anchor', 'end') + .text(tempDisabled.map(function(d) { return d.key }).join(', ') + ' values cannot be calculated for this time period.'); + } + + //------------------------------------------------------------ + // Main Chart Component(s) + + //------------------------------------------------------------ + //Set up interactive layer + if (useInteractiveGuideline) { + interactiveLayer + .width(availableWidth) + .height(availableHeight) + .margin({left:margin.left,top:margin.top}) + .svgContainer(container) + .xScale(x); + wrap.select(".nv-interactive").call(interactiveLayer); + } + + gEnter.select('.nv-background') + .append('rect'); + + g.select('.nv-background rect') + .attr('width', availableWidth) + .attr('height', availableHeight); + + lines + //.x(function(d) { return d.x }) + .y(function(d) { return d.display.y }) + .width(availableWidth) + .height(availableHeight) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled && !data[i].tempDisabled; })); + + + + var linesWrap = g.select('.nv-linesWrap') + .datum(data.filter(function(d) { return !d.disabled && !d.tempDisabled })); + + //d3.transition(linesWrap).call(lines); + linesWrap.call(lines); + + /*Handle average lines [AN-612] ----------------------------*/ + + //Store a series index number in the data array. + data.forEach(function(d,i) { + d.seriesIndex = i; + }); + + var avgLineData = data.filter(function(d) { + return !d.disabled && !!average(d); + }); + + var avgLines = g.select(".nv-avgLinesWrap").selectAll("line") + .data(avgLineData, function(d) { return d.key; }); + + var getAvgLineY = function(d) { + //If average lines go off the svg element, clamp them to the svg bounds. + var yVal = y(average(d)); + if (yVal < 0) return 0; + if (yVal > availableHeight) return availableHeight; + return yVal; + }; + + avgLines.enter() + .append('line') + .style('stroke-width',2) + .style('stroke-dasharray','10,10') + .style('stroke',function (d,i) { + return lines.color()(d,d.seriesIndex); + }) + .attr('x1',0) + .attr('x2',availableWidth) + .attr('y1', getAvgLineY) + .attr('y2', getAvgLineY); + + avgLines + .style('stroke-opacity',function(d){ + //If average lines go offscreen, make them transparent + var yVal = y(average(d)); + if (yVal < 0 || yVal > availableHeight) return 0; + return 1; + }) + .attr('x1',0) + .attr('x2',availableWidth) + .attr('y1', getAvgLineY) + .attr('y2', getAvgLineY); + + avgLines.exit().remove(); + + //Create index line ----------------------------------------- + + var indexLine = linesWrap.selectAll('.nv-indexLine') + .data([index]); + indexLine.enter().append('rect').attr('class', 'nv-indexLine') + .attr('width', 3) + .attr('x', -2) + .attr('fill', 'red') + .attr('fill-opacity', .5) + .style("pointer-events","all") + .call(indexDrag) + + indexLine + .attr('transform', function(d) { return 'translate(' + dx(d.i) + ',0)' }) + .attr('height', availableHeight) + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup Axes + + if (showXAxis) { + xAxis + .scale(x) + //Suggest how many ticks based on the chart width and D3 should listen (70 is the optimal number for MM/DD/YY dates) + .ticks( Math.min(data[0].values.length,availableWidth/70) ) + .tickSize(-availableHeight, 0); + + g.select('.nv-x.nv-axis') + .attr('transform', 'translate(0,' + y.range()[0] + ')'); + d3.transition(g.select('.nv-x.nv-axis')) + .call(xAxis); + } + + + if (showYAxis) { + yAxis + .scale(y) + .ticks( availableHeight / 36 ) + .tickSize( -availableWidth, 0); + + d3.transition(g.select('.nv-y.nv-axis')) + .call(yAxis); + } + //------------------------------------------------------------ + + + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ + + + function updateZero() { + indexLine + .data([index]); + + //When dragging the index line, turn off line transitions. + // Then turn them back on when done dragging. + var oldDuration = chart.transitionDuration(); + chart.transitionDuration(0); + chart.update(); + chart.transitionDuration(oldDuration); + } + + g.select('.nv-background rect') + .on('click', function() { + index.x = d3.mouse(this)[0]; + index.i = Math.round(dx.invert(index.x)); + + // update state and send stateChange with new index + state.index = index.i; + dispatch.stateChange(state); + + updateZero(); + }); + + lines.dispatch.on('elementClick', function(e) { + index.i = e.pointIndex; + index.x = dx(index.i); + + // update state and send stateChange with new index + state.index = index.i; + dispatch.stateChange(state); + + updateZero(); + }); + + controls.dispatch.on('legendClick', function(d,i) { + d.disabled = !d.disabled; + rescaleY = !d.disabled; + + state.rescaleY = rescaleY; + dispatch.stateChange(state); + chart.update(); + }); + + + legend.dispatch.on('stateChange', function(newState) { + state.disabled = newState.disabled; + dispatch.stateChange(state); + chart.update(); + }); + + interactiveLayer.dispatch.on('elementMousemove', function(e) { + lines.clearHighlights(); + var singlePoint, pointIndex, pointXLocation, allData = []; + + + data + .filter(function(series, i) { + series.seriesIndex = i; + return !series.disabled; + }) + .forEach(function(series,i) { + pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x()); + lines.highlightPoint(i, pointIndex, true); + var point = series.values[pointIndex]; + if (typeof point === 'undefined') return; + if (typeof singlePoint === 'undefined') singlePoint = point; + if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex)); + allData.push({ + key: series.key, + value: chart.y()(point, pointIndex), + color: color(series,series.seriesIndex) + }); + }); + + //Highlight the tooltip entry based on which point the mouse is closest to. + if (allData.length > 2) { + var yValue = chart.yScale().invert(e.mouseY); + var domainExtent = Math.abs(chart.yScale().domain()[0] - chart.yScale().domain()[1]); + var threshold = 0.03 * domainExtent; + var indexToHighlight = nv.nearestValueIndex(allData.map(function(d){return d.value}),yValue,threshold); + if (indexToHighlight !== null) + allData[indexToHighlight].highlight = true; + } + + var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex), pointIndex); + interactiveLayer.tooltip + .position({left: pointXLocation + margin.left, top: e.mouseY + margin.top}) + .chartContainer(that.parentNode) + .enabled(tooltips) + .valueFormatter(function(d,i) { + return yAxis.tickFormat()(d); + }) + .data( + { + value: xValue, + series: allData + } + )(); + + interactiveLayer.renderGuideLine(pointXLocation); + + }); + + interactiveLayer.dispatch.on("elementMouseout",function(e) { + dispatch.tooltipHide(); + lines.clearHighlights(); + }); + + dispatch.on('tooltipShow', function(e) { + if (tooltips) showTooltip(e, that.parentNode); + }); + + + // Update chart from a state object passed to event handler + dispatch.on('changeState', function(e) { + + if (typeof e.disabled !== 'undefined') { + data.forEach(function(series,i) { + series.disabled = e.disabled[i]; + }); + + state.disabled = e.disabled; + } + + + if (typeof e.index !== 'undefined') { + index.i = e.index; + index.x = dx(index.i); + + state.index = e.index; + + indexLine + .data([index]); + } + + + if (typeof e.rescaleY !== 'undefined') { + rescaleY = e.rescaleY; + } + + chart.update(); + }); + + //============================================================ + + }); + + return chart; + } + + + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ + + lines.dispatch.on('elementMouseover.tooltip', function(e) { + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); + + lines.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + + dispatch.on('tooltipHide', function() { + if (tooltips) nv.tooltip.cleanup(); + }); + + //============================================================ + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + // expose chart's sub-components + chart.dispatch = dispatch; + chart.lines = lines; + chart.legend = legend; + chart.xAxis = xAxis; + chart.yAxis = yAxis; + chart.interactiveLayer = interactiveLayer; + + d3.rebind(chart, lines, 'defined', 'isArea', 'x', 'y', 'xScale','yScale', 'size', 'xDomain', 'yDomain', 'xRange', 'yRange', 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi','useVoronoi', 'id'); + + chart.options = nv.utils.optionsFunc.bind(chart); + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + legend.color(color); + return chart; + }; + + chart.rescaleY = function(_) { + if (!arguments.length) return rescaleY; + rescaleY = _; + return chart; + }; + + chart.showControls = function(_) { + if (!arguments.length) return showControls; + showControls = _; + return chart; + }; + + chart.useInteractiveGuideline = function(_) { + if(!arguments.length) return useInteractiveGuideline; + useInteractiveGuideline = _; + if (_ === true) { + chart.interactive(false); + chart.useVoronoi(false); + } + return chart; + }; + + chart.showLegend = function(_) { + if (!arguments.length) return showLegend; + showLegend = _; + return chart; + }; + + chart.showXAxis = function(_) { + if (!arguments.length) return showXAxis; + showXAxis = _; + return chart; + }; + + chart.showYAxis = function(_) { + if (!arguments.length) return showYAxis; + showYAxis = _; + return chart; + }; + + chart.rightAlignYAxis = function(_) { + if(!arguments.length) return rightAlignYAxis; + rightAlignYAxis = _; + yAxis.orient( (_) ? 'right' : 'left'); + return chart; + }; + + chart.tooltips = function(_) { + if (!arguments.length) return tooltips; + tooltips = _; + return chart; + }; + + chart.tooltipContent = function(_) { + if (!arguments.length) return tooltip; + tooltip = _; + return chart; + }; + + chart.state = function(_) { + if (!arguments.length) return state; + state = _; + return chart; + }; + + chart.defaultState = function(_) { + if (!arguments.length) return defaultState; + defaultState = _; + return chart; + }; + + chart.noData = function(_) { + if (!arguments.length) return noData; + noData = _; + return chart; + }; + + chart.average = function(_) { + if(!arguments.length) return average; + average = _; + return chart; + }; + + chart.transitionDuration = function(_) { + if (!arguments.length) return transitionDuration; + transitionDuration = _; + return chart; + }; + + //============================================================ + + + //============================================================ + // Functions + //------------------------------------------------------------ + + /* Normalize the data according to an index point. */ + function indexify(idx, data) { + return data.map(function(line, i) { + if (!line.values) { + return line; + } + var v = lines.y()(line.values[idx], idx); + + //TODO: implement check below, and disable series if series loses 100% or more cause divide by 0 issue + if (v < -.95) { + //if a series loses more than 100%, calculations fail.. anything close can cause major distortion (but is mathematically correct till it hits 100) + line.tempDisabled = true; + return line; + } + + line.tempDisabled = false; + + line.values = line.values.map(function(point, pointIndex) { + point.display = {'y': (lines.y()(point, pointIndex) - v) / (1 + v) }; + return point; + }) + + return line; + }) + } + + //============================================================ + + + return chart; +} +//TODO: consider deprecating by adding necessary features to multiBar model +nv.models.discreteBar = function() { + "use strict"; + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var margin = {top: 0, right: 0, bottom: 0, left: 0} + , width = 960 + , height = 500 + , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one + , x = d3.scale.ordinal() + , y = d3.scale.linear() + , getX = function(d) { return d.x } + , getY = function(d) { return d.y } + , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove + , color = nv.utils.defaultColor() + , showValues = false + , valueFormat = d3.format(',.2f') + , xDomain + , yDomain + , xRange + , yRange + , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout') + , rectClass = 'discreteBar' + ; + + //============================================================ + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var x0, y0; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var availableWidth = width - margin.left - margin.right, + availableHeight = height - margin.top - margin.bottom, + container = d3.select(this); + + + //add series index to each data point for reference + data.forEach(function(series, i) { + series.values.forEach(function(point) { + point.series = i; + }); + }); + + + //------------------------------------------------------------ + // Setup Scales + + // remap and flatten the data for use in calculating the scales' domains + var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate + data.map(function(d) { + return d.values.map(function(d,i) { + return { x: getX(d,i), y: getY(d,i), y0: d.y0 } + }) + }); + + x .domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x })) + .rangeBands(xRange || [0, availableWidth], .1); + + y .domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return d.y }).concat(forceY))); + + + // If showValues, pad the Y axis range to account for label height + if (showValues) y.range(yRange || [availableHeight - (y.domain()[0] < 0 ? 12 : 0), y.domain()[1] > 0 ? 12 : 0]); + else y.range(yRange || [availableHeight, 0]); + + //store old scales if they exist + x0 = x0 || x; + y0 = y0 || y.copy().range([y(0),y(0)]); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-discretebar').data([data]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-discretebar'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-groups'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + //------------------------------------------------------------ + + + + //TODO: by definition, the discrete bar should not have multiple groups, will modify/remove later + var groups = wrap.select('.nv-groups').selectAll('.nv-group') + .data(function(d) { return d }, function(d) { return d.key }); + groups.enter().append('g') + .style('stroke-opacity', 1e-6) + .style('fill-opacity', 1e-6); + groups.exit() + .transition() + .style('stroke-opacity', 1e-6) + .style('fill-opacity', 1e-6) + .remove(); + groups + .attr('class', function(d,i) { return 'nv-group nv-series-' + i }) + .classed('hover', function(d) { return d.hover }); + groups + .transition() + .style('stroke-opacity', 1) + .style('fill-opacity', .75); + + + var bars = groups.selectAll('g.nv-bar') + .data(function(d) { return d.values }); + + bars.exit().remove(); + + + var barsEnter = bars.enter().append('g') + .attr('transform', function(d,i,j) { + return 'translate(' + (x(getX(d,i)) + x.rangeBand() * .05 ) + ', ' + y(0) + ')' + }) + .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here + d3.select(this).classed('hover', true); + dispatch.elementMouseover({ + value: getY(d,i), + point: d, + series: data[d.series], + pos: [x(getX(d,i)) + (x.rangeBand() * (d.series + .5) / data.length), y(getY(d,i))], // TODO: Figure out why the value appears to be shifted + pointIndex: i, + seriesIndex: d.series, + e: d3.event + }); + }) + .on('mouseout', function(d,i) { + d3.select(this).classed('hover', false); + dispatch.elementMouseout({ + value: getY(d,i), + point: d, + series: data[d.series], + pointIndex: i, + seriesIndex: d.series, + e: d3.event + }); + }) + .on('click', function(d,i) { + dispatch.elementClick({ + value: getY(d,i), + point: d, + series: data[d.series], + pos: [x(getX(d,i)) + (x.rangeBand() * (d.series + .5) / data.length), y(getY(d,i))], // TODO: Figure out why the value appears to be shifted + pointIndex: i, + seriesIndex: d.series, + e: d3.event + }); + d3.event.stopPropagation(); + }) + .on('dblclick', function(d,i) { + dispatch.elementDblClick({ + value: getY(d,i), + point: d, + series: data[d.series], + pos: [x(getX(d,i)) + (x.rangeBand() * (d.series + .5) / data.length), y(getY(d,i))], // TODO: Figure out why the value appears to be shifted + pointIndex: i, + seriesIndex: d.series, + e: d3.event + }); + d3.event.stopPropagation(); + }); + + barsEnter.append('rect') + .attr('height', 0) + .attr('width', x.rangeBand() * .9 / data.length ) + + if (showValues) { + barsEnter.append('text') + .attr('text-anchor', 'middle') + ; + + bars.select('text') + .text(function(d,i) { return valueFormat(getY(d,i)) }) + .transition() + .attr('x', x.rangeBand() * .9 / 2) + .attr('y', function(d,i) { return getY(d,i) < 0 ? y(getY(d,i)) - y(0) + 12 : -4 }) + + ; + } else { + bars.selectAll('text').remove(); + } + + bars + .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive' }) + .style('fill', function(d,i) { return d.color || color(d,i) }) + .style('stroke', function(d,i) { return d.color || color(d,i) }) + .select('rect') + .attr('class', rectClass) + .transition() + .attr('width', x.rangeBand() * .9 / data.length); + bars.transition() + //.delay(function(d,i) { return i * 1200 / data[0].values.length }) + .attr('transform', function(d,i) { + var left = x(getX(d,i)) + x.rangeBand() * .05, + top = getY(d,i) < 0 ? + y(0) : + y(0) - y(getY(d,i)) < 1 ? + y(0) - 1 : //make 1 px positive bars show up above y=0 + y(getY(d,i)); + + return 'translate(' + left + ', ' + top + ')' + }) + .select('rect') + .attr('height', function(d,i) { + return Math.max(Math.abs(y(getY(d,i)) - y((yDomain && yDomain[0]) || 0)) || 1) + }); + + + //store old scales for use in transitions on update + x0 = x.copy(); + y0 = y.copy(); + + }); + + return chart; + } + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + chart.dispatch = dispatch; + + chart.options = nv.utils.optionsFunc.bind(chart); + + chart.x = function(_) { + if (!arguments.length) return getX; + getX = _; + return chart; + }; + + chart.y = function(_) { + if (!arguments.length) return getY; + getY = _; + return chart; + }; + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.xScale = function(_) { + if (!arguments.length) return x; + x = _; + return chart; + }; + + chart.yScale = function(_) { + if (!arguments.length) return y; + y = _; + return chart; + }; + + chart.xDomain = function(_) { + if (!arguments.length) return xDomain; + xDomain = _; + return chart; + }; + + chart.yDomain = function(_) { + if (!arguments.length) return yDomain; + yDomain = _; + return chart; + }; + + chart.xRange = function(_) { + if (!arguments.length) return xRange; + xRange = _; + return chart; + }; + + chart.yRange = function(_) { + if (!arguments.length) return yRange; + yRange = _; + return chart; + }; + + chart.forceY = function(_) { + if (!arguments.length) return forceY; + forceY = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + return chart; + }; + + chart.id = function(_) { + if (!arguments.length) return id; + id = _; + return chart; + }; + + chart.showValues = function(_) { + if (!arguments.length) return showValues; + showValues = _; + return chart; + }; + + chart.valueFormat= function(_) { + if (!arguments.length) return valueFormat; + valueFormat = _; + return chart; + }; + + chart.rectClass= function(_) { + if (!arguments.length) return rectClass; + rectClass = _; + return chart; + }; + //============================================================ + + + return chart; +} + +nv.models.discreteBarChart = function() { + "use strict"; + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var discretebar = nv.models.discreteBar() + , xAxis = nv.models.axis() + , yAxis = nv.models.axis() + ; + + var margin = {top: 15, right: 10, bottom: 50, left: 60} + , width = null + , height = null + , color = nv.utils.getColor() + , showXAxis = true + , showYAxis = true + , rightAlignYAxis = false + , staggerLabels = false + , tooltips = true + , tooltip = function(key, x, y, e, graph) { + return '<h3>' + x + '</h3>' + + '<p>' + y + '</p>' + } + , x + , y + , noData = "No Data Available." + , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'beforeUpdate') + , transitionDuration = 250 + ; + + xAxis + .orient('bottom') + .highlightZero(false) + .showMaxMin(false) + .tickFormat(function(d) { return d }) + ; + yAxis + .orient((rightAlignYAxis) ? 'right' : 'left') + .tickFormat(d3.format(',.1f')) + ; + + //============================================================ + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var showTooltip = function(e, offsetElement) { + var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ), + top = e.pos[1] + ( offsetElement.offsetTop || 0), + x = xAxis.tickFormat()(discretebar.x()(e.point, e.pointIndex)), + y = yAxis.tickFormat()(discretebar.y()(e.point, e.pointIndex)), + content = tooltip(e.series.key, x, y, e, chart); + + nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement); + }; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var container = d3.select(this), + that = this; + + var availableWidth = (width || parseInt(container.style('width')) || 960) + - margin.left - margin.right, + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + + + chart.update = function() { + dispatch.beforeUpdate(); + container.transition().duration(transitionDuration).call(chart); + }; + chart.container = this; + + + //------------------------------------------------------------ + // Display No Data message if there's nothing to show. + + if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { + var noDataText = container.selectAll('.nv-noData').data([noData]); + + noDataText.enter().append('text') + .attr('class', 'nvd3 nv-noData') + .attr('dy', '-.7em') + .style('text-anchor', 'middle'); + + noDataText + .attr('x', margin.left + availableWidth / 2) + .attr('y', margin.top + availableHeight / 2) + .text(function(d) { return d }); + + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup Scales + + x = discretebar.xScale(); + y = discretebar.yScale().clamp(true); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-discreteBarWithAxes').data([data]); + var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-discreteBarWithAxes').append('g'); + var defsEnter = gEnter.append('defs'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-x nv-axis'); + gEnter.append('g').attr('class', 'nv-y nv-axis'); + gEnter.append('g').attr('class', 'nv-barsWrap'); + + g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + if (rightAlignYAxis) { + g.select(".nv-y.nv-axis") + .attr("transform", "translate(" + availableWidth + ",0)"); + } + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Main Chart Component(s) + + discretebar + .width(availableWidth) + .height(availableHeight); + + + var barsWrap = g.select('.nv-barsWrap') + .datum(data.filter(function(d) { return !d.disabled })) + + barsWrap.transition().call(discretebar); + + //------------------------------------------------------------ + + + + defsEnter.append('clipPath') + .attr('id', 'nv-x-label-clip-' + discretebar.id()) + .append('rect'); + + g.select('#nv-x-label-clip-' + discretebar.id() + ' rect') + .attr('width', x.rangeBand() * (staggerLabels ? 2 : 1)) + .attr('height', 16) + .attr('x', -x.rangeBand() / (staggerLabels ? 1 : 2 )); + + + //------------------------------------------------------------ + // Setup Axes + + if (showXAxis) { + xAxis + .scale(x) + .ticks( availableWidth / 100 ) + .tickSize(-availableHeight, 0); + + g.select('.nv-x.nv-axis') + .attr('transform', 'translate(0,' + (y.range()[0] + ((discretebar.showValues() && y.domain()[0] < 0) ? 16 : 0)) + ')'); + //d3.transition(g.select('.nv-x.nv-axis')) + g.select('.nv-x.nv-axis').transition() + .call(xAxis); + + + var xTicks = g.select('.nv-x.nv-axis').selectAll('g'); + + if (staggerLabels) { + xTicks + .selectAll('text') + .attr('transform', function(d,i,j) { return 'translate(0,' + (j % 2 == 0 ? '5' : '17') + ')' }) + } + } + + if (showYAxis) { + yAxis + .scale(y) + .ticks( availableHeight / 36 ) + .tickSize( -availableWidth, 0); + + g.select('.nv-y.nv-axis').transition() + .call(yAxis); + } + + //------------------------------------------------------------ + + + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ + + dispatch.on('tooltipShow', function(e) { + if (tooltips) showTooltip(e, that.parentNode); + }); + + //============================================================ + + + }); + + return chart; + } + + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ + + discretebar.dispatch.on('elementMouseover.tooltip', function(e) { + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); + + discretebar.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + + dispatch.on('tooltipHide', function() { + if (tooltips) nv.tooltip.cleanup(); + }); + + //============================================================ + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + // expose chart's sub-components + chart.dispatch = dispatch; + chart.discretebar = discretebar; + chart.xAxis = xAxis; + chart.yAxis = yAxis; + + d3.rebind(chart, discretebar, 'x', 'y', 'xDomain', 'yDomain', 'xRange', 'yRange', 'forceX', 'forceY', 'id', 'showValues', 'valueFormat'); + + chart.options = nv.utils.optionsFunc.bind(chart); + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + discretebar.color(color); + return chart; + }; + + chart.showXAxis = function(_) { + if (!arguments.length) return showXAxis; + showXAxis = _; + return chart; + }; + + chart.showYAxis = function(_) { + if (!arguments.length) return showYAxis; + showYAxis = _; + return chart; + }; + + chart.rightAlignYAxis = function(_) { + if(!arguments.length) return rightAlignYAxis; + rightAlignYAxis = _; + yAxis.orient( (_) ? 'right' : 'left'); + return chart; + }; + + chart.staggerLabels = function(_) { + if (!arguments.length) return staggerLabels; + staggerLabels = _; + return chart; + }; + + chart.tooltips = function(_) { + if (!arguments.length) return tooltips; + tooltips = _; + return chart; + }; + + chart.tooltipContent = function(_) { + if (!arguments.length) return tooltip; + tooltip = _; + return chart; + }; + + chart.noData = function(_) { + if (!arguments.length) return noData; + noData = _; + return chart; + }; + + chart.transitionDuration = function(_) { + if (!arguments.length) return transitionDuration; + transitionDuration = _; + return chart; + }; + + //============================================================ + + + return chart; +} + +nv.models.distribution = function() { + "use strict"; + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var margin = {top: 0, right: 0, bottom: 0, left: 0} + , width = 400 //technically width or height depending on x or y.... + , size = 8 + , axis = 'x' // 'x' or 'y'... horizontal or vertical + , getData = function(d) { return d[axis] } // defaults d.x or d.y + , color = nv.utils.defaultColor() + , scale = d3.scale.linear() + , domain + ; + + //============================================================ + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var scale0; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var availableLength = width - (axis === 'x' ? margin.left + margin.right : margin.top + margin.bottom), + naxis = axis == 'x' ? 'y' : 'x', + container = d3.select(this); + + + //------------------------------------------------------------ + // Setup Scales + + scale0 = scale0 || scale; + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-distribution').data([data]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-distribution'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')') + + //------------------------------------------------------------ + + + var distWrap = g.selectAll('g.nv-dist') + .data(function(d) { return d }, function(d) { return d.key }); + + distWrap.enter().append('g'); + distWrap + .attr('class', function(d,i) { return 'nv-dist nv-series-' + i }) + .style('stroke', function(d,i) { return color(d, i) }); + + var dist = distWrap.selectAll('line.nv-dist' + axis) + .data(function(d) { return d.values }) + dist.enter().append('line') + .attr(axis + '1', function(d,i) { return scale0(getData(d,i)) }) + .attr(axis + '2', function(d,i) { return scale0(getData(d,i)) }) + distWrap.exit().selectAll('line.nv-dist' + axis) + .transition() + .attr(axis + '1', function(d,i) { return scale(getData(d,i)) }) + .attr(axis + '2', function(d,i) { return scale(getData(d,i)) }) + .style('stroke-opacity', 0) + .remove(); + dist + .attr('class', function(d,i) { return 'nv-dist' + axis + ' nv-dist' + axis + '-' + i }) + .attr(naxis + '1', 0) + .attr(naxis + '2', size); + dist + .transition() + .attr(axis + '1', function(d,i) { return scale(getData(d,i)) }) + .attr(axis + '2', function(d,i) { return scale(getData(d,i)) }) + + + scale0 = scale.copy(); + + }); + + return chart; + } + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + chart.options = nv.utils.optionsFunc.bind(chart); + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.axis = function(_) { + if (!arguments.length) return axis; + axis = _; + return chart; + }; + + chart.size = function(_) { + if (!arguments.length) return size; + size = _; + return chart; + }; + + chart.getData = function(_) { + if (!arguments.length) return getData; + getData = d3.functor(_); + return chart; + }; + + chart.scale = function(_) { + if (!arguments.length) return scale; + scale = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + return chart; + }; + //============================================================ + + + return chart; +} + +nv.models.historicalBarChart = function() { + "use strict"; + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var bars = nv.models.historicalBar() + , xAxis = nv.models.axis() + , yAxis = nv.models.axis() + , legend = nv.models.legend() + ; + + + var margin = {top: 30, right: 90, bottom: 50, left: 90} + , color = nv.utils.defaultColor() + , width = null + , height = null + , showLegend = false + , showXAxis = true + , showYAxis = true + , rightAlignYAxis = false + , tooltips = true + , tooltip = function(key, x, y, e, graph) { + return '<h3>' + key + '</h3>' + + '<p>' + y + ' at ' + x + '</p>' + } + , x + , y + , state = {} + , defaultState = null + , noData = 'No Data Available.' + , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState') + , transitionDuration = 250 + ; + + xAxis + .orient('bottom') + .tickPadding(7) + ; + yAxis + .orient( (rightAlignYAxis) ? 'right' : 'left') + ; + + //============================================================ + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var showTooltip = function(e, offsetElement) { + + // New addition to calculate position if SVG is scaled with viewBox, may move TODO: consider implementing everywhere else + if (offsetElement) { + var svg = d3.select(offsetElement).select('svg'); + var viewBox = (svg.node()) ? svg.attr('viewBox') : null; + if (viewBox) { + viewBox = viewBox.split(' '); + var ratio = parseInt(svg.style('width')) / viewBox[2]; + e.pos[0] = e.pos[0] * ratio; + e.pos[1] = e.pos[1] * ratio; + } + } + + var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ), + top = e.pos[1] + ( offsetElement.offsetTop || 0), + x = xAxis.tickFormat()(bars.x()(e.point, e.pointIndex)), + y = yAxis.tickFormat()(bars.y()(e.point, e.pointIndex)), + content = tooltip(e.series.key, x, y, e, chart); + + nv.tooltip.show([left, top], content, null, null, offsetElement); + }; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var container = d3.select(this), + that = this; + + var availableWidth = (width || parseInt(container.style('width')) || 960) + - margin.left - margin.right, + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + + + chart.update = function() { container.transition().duration(transitionDuration).call(chart) }; + chart.container = this; + + //set state.disabled + state.disabled = data.map(function(d) { return !!d.disabled }); + + if (!defaultState) { + var key; + defaultState = {}; + for (key in state) { + if (state[key] instanceof Array) + defaultState[key] = state[key].slice(0); + else + defaultState[key] = state[key]; + } + } + + //------------------------------------------------------------ + // Display noData message if there's nothing to show. + + if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { + var noDataText = container.selectAll('.nv-noData').data([noData]); + + noDataText.enter().append('text') + .attr('class', 'nvd3 nv-noData') + .attr('dy', '-.7em') + .style('text-anchor', 'middle'); + + noDataText + .attr('x', margin.left + availableWidth / 2) + .attr('y', margin.top + availableHeight / 2) + .text(function(d) { return d }); + + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup Scales + + x = bars.xScale(); + y = bars.yScale(); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-historicalBarChart').data([data]); + var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-historicalBarChart').append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-x nv-axis'); + gEnter.append('g').attr('class', 'nv-y nv-axis'); + gEnter.append('g').attr('class', 'nv-barsWrap'); + gEnter.append('g').attr('class', 'nv-legendWrap'); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Legend + + if (showLegend) { + legend.width(availableWidth); + + g.select('.nv-legendWrap') + .datum(data) + .call(legend); + + if ( margin.top != legend.height()) { + margin.top = legend.height(); + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + } + + wrap.select('.nv-legendWrap') + .attr('transform', 'translate(0,' + (-margin.top) +')') + } + + //------------------------------------------------------------ + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + if (rightAlignYAxis) { + g.select(".nv-y.nv-axis") + .attr("transform", "translate(" + availableWidth + ",0)"); + } + + + //------------------------------------------------------------ + // Main Chart Component(s) + + bars + .width(availableWidth) + .height(availableHeight) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled })); + + + var barsWrap = g.select('.nv-barsWrap') + .datum(data.filter(function(d) { return !d.disabled })) + + barsWrap.transition().call(bars); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup Axes + + if (showXAxis) { + xAxis + .scale(x) + .tickSize(-availableHeight, 0); + + g.select('.nv-x.nv-axis') + .attr('transform', 'translate(0,' + y.range()[0] + ')'); + g.select('.nv-x.nv-axis') + .transition() + .call(xAxis); + } + + if (showYAxis) { + yAxis + .scale(y) + .ticks( availableHeight / 36 ) + .tickSize( -availableWidth, 0); + + g.select('.nv-y.nv-axis') + .transition() + .call(yAxis); + } + //------------------------------------------------------------ + + + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ + + legend.dispatch.on('legendClick', function(d,i) { + d.disabled = !d.disabled; + + if (!data.filter(function(d) { return !d.disabled }).length) { + data.map(function(d) { + d.disabled = false; + wrap.selectAll('.nv-series').classed('disabled', false); + return d; + }); + } + + state.disabled = data.map(function(d) { return !!d.disabled }); + dispatch.stateChange(state); + + selection.transition().call(chart); + }); + + legend.dispatch.on('legendDblclick', function(d) { + //Double clicking should always enable current series, and disabled all others. + data.forEach(function(d) { + d.disabled = true; + }); + d.disabled = false; + + state.disabled = data.map(function(d) { return !!d.disabled }); + dispatch.stateChange(state); + chart.update(); + }); + + dispatch.on('tooltipShow', function(e) { + if (tooltips) showTooltip(e, that.parentNode); + }); + + + dispatch.on('changeState', function(e) { + + if (typeof e.disabled !== 'undefined') { + data.forEach(function(series,i) { + series.disabled = e.disabled[i]; + }); + + state.disabled = e.disabled; + } + + selection.call(chart); + }); + + //============================================================ + + }); + + return chart; + } + + + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ + + bars.dispatch.on('elementMouseover.tooltip', function(e) { + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); + + bars.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + + dispatch.on('tooltipHide', function() { + if (tooltips) nv.tooltip.cleanup(); + }); + + //============================================================ + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + // expose chart's sub-components + chart.dispatch = dispatch; + chart.bars = bars; + chart.legend = legend; + chart.xAxis = xAxis; + chart.yAxis = yAxis; + + d3.rebind(chart, bars, 'defined', 'isArea', 'x', 'y', 'size', 'xScale', 'yScale', + 'xDomain', 'yDomain', 'xRange', 'yRange', 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi', 'id', 'interpolate','highlightPoint','clearHighlights', 'interactive'); + + chart.options = nv.utils.optionsFunc.bind(chart); + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + legend.color(color); + return chart; + }; + + chart.showLegend = function(_) { + if (!arguments.length) return showLegend; + showLegend = _; + return chart; + }; + + chart.showXAxis = function(_) { + if (!arguments.length) return showXAxis; + showXAxis = _; + return chart; + }; + + chart.showYAxis = function(_) { + if (!arguments.length) return showYAxis; + showYAxis = _; + return chart; + }; + + chart.rightAlignYAxis = function(_) { + if(!arguments.length) return rightAlignYAxis; + rightAlignYAxis = _; + yAxis.orient( (_) ? 'right' : 'left'); + return chart; + }; + + chart.tooltips = function(_) { + if (!arguments.length) return tooltips; + tooltips = _; + return chart; + }; + + chart.tooltipContent = function(_) { + if (!arguments.length) return tooltip; + tooltip = _; + return chart; + }; + + chart.state = function(_) { + if (!arguments.length) return state; + state = _; + return chart; + }; + + chart.defaultState = function(_) { + if (!arguments.length) return defaultState; + defaultState = _; + return chart; + }; + + chart.noData = function(_) { + if (!arguments.length) return noData; + noData = _; + return chart; + }; + + chart.transitionDuration = function(_) { + if (!arguments.length) return transitionDuration; + transitionDuration = _; + return chart; + }; + + //============================================================ + + + return chart; +} +nv.models.indentedTree = function() { + "use strict"; + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var margin = {top: 0, right: 0, bottom: 0, left: 0} //TODO: implement, maybe as margin on the containing div + , width = 960 + , height = 500 + , color = nv.utils.defaultColor() + , id = Math.floor(Math.random() * 10000) + , header = true + , filterZero = false + , noData = "No Data Available." + , childIndent = 20 + , columns = [{key:'key', label: 'Name', type:'text'}] //TODO: consider functions like chart.addColumn, chart.removeColumn, instead of a block like this + , tableClass = null + , iconOpen = 'images/grey-plus.png' //TODO: consider removing this and replacing with a '+' or '-' unless user defines images + , iconClose = 'images/grey-minus.png' + , dispatch = d3.dispatch('elementClick', 'elementDblclick', 'elementMouseover', 'elementMouseout') + , getUrl = function(d) { return d.url } + ; + + //============================================================ + + var idx = 0; + + function chart(selection) { + selection.each(function(data) { + var depth = 1, + container = d3.select(this); + + var tree = d3.layout.tree() + .children(function(d) { return d.values }) + .size([height, childIndent]); //Not sure if this is needed now that the result is HTML + + chart.update = function() { container.transition().duration(600).call(chart) }; + + + //------------------------------------------------------------ + // Display No Data message if there's nothing to show. + if (!data[0]) data[0] = {key: noData}; + + //------------------------------------------------------------ + + + var nodes = tree.nodes(data[0]); + + // nodes.map(function(d) { + // d.id = i++; + // }) + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = d3.select(this).selectAll('div').data([[nodes]]); + var wrapEnter = wrap.enter().append('div').attr('class', 'nvd3 nv-wrap nv-indentedtree'); + var tableEnter = wrapEnter.append('table'); + var table = wrap.select('table').attr('width', '100%').attr('class', tableClass); + + //------------------------------------------------------------ + + + if (header) { + var thead = tableEnter.append('thead'); + + var theadRow1 = thead.append('tr'); + + columns.forEach(function(column) { + theadRow1 + .append('th') + .attr('width', column.width ? column.width : '10%') + .style('text-align', column.type == 'numeric' ? 'right' : 'left') + .append('span') + .text(column.label); + }); + } + + + var tbody = table.selectAll('tbody') + .data(function(d) { return d }); + tbody.enter().append('tbody'); + + + + //compute max generations + depth = d3.max(nodes, function(node) { return node.depth }); + tree.size([height, depth * childIndent]); //TODO: see if this is necessary at all + + + // Update the nodes… + var node = tbody.selectAll('tr') + // .data(function(d) { return d; }, function(d) { return d.id || (d.id == ++i)}); + .data(function(d) { return d.filter(function(d) { return (filterZero && !d.children) ? filterZero(d) : true; } )}, function(d,i) { return d.id || (d.id || ++idx)}); + //.style('display', 'table-row'); //TODO: see if this does anything + + node.exit().remove(); + + node.select('img.nv-treeicon') + .attr('src', icon) + .classed('folded', folded); + + var nodeEnter = node.enter().append('tr'); + + + columns.forEach(function(column, index) { + + var nodeName = nodeEnter.append('td') + .style('padding-left', function(d) { return (index ? 0 : d.depth * childIndent + 12 + (icon(d) ? 0 : 16)) + 'px' }, 'important') //TODO: check why I did the ternary here + .style('text-align', column.type == 'numeric' ? 'right' : 'left'); + + + if (index == 0) { + nodeName.append('img') + .classed('nv-treeicon', true) + .classed('nv-folded', folded) + .attr('src', icon) + .style('width', '14px') + .style('height', '14px') + .style('padding', '0 1px') + .style('display', function(d) { return icon(d) ? 'inline-block' : 'none'; }) + .on('click', click); + } + + + nodeName.each(function(d) { + if (!index && getUrl(d)) + d3.select(this) + .append('a') + .attr('href',getUrl) + .attr('class', d3.functor(column.classes)) + .append('span') + else + d3.select(this) + .append('span') + + d3.select(this).select('span') + .attr('class', d3.functor(column.classes) ) + .text(function(d) { return column.format ? column.format(d) : + (d[column.key] || '-') }); + }); + + if (column.showCount) { + nodeName.append('span') + .attr('class', 'nv-childrenCount'); + + node.selectAll('span.nv-childrenCount').text(function(d) { + return ((d.values && d.values.length) || (d._values && d._values.length)) ? //If this is a parent + '(' + ((d.values && (d.values.filter(function(d) { return filterZero ? filterZero(d) : true; }).length)) //If children are in values check its children and filter + || (d._values && d._values.filter(function(d) { return filterZero ? filterZero(d) : true; }).length) //Otherwise, do the same, but with the other name, _values... + || 0) + ')' //This is the catch-all in case there are no children after a filter + : '' //If this is not a parent, just give an empty string + }); + } + + // if (column.click) + // nodeName.select('span').on('click', column.click); + + }); + + node + .order() + .on('click', function(d) { + dispatch.elementClick({ + row: this, //TODO: decide whether or not this should be consistent with scatter/line events or should be an html link (a href) + data: d, + pos: [d.x, d.y] + }); + }) + .on('dblclick', function(d) { + dispatch.elementDblclick({ + row: this, + data: d, + pos: [d.x, d.y] + }); + }) + .on('mouseover', function(d) { + dispatch.elementMouseover({ + row: this, + data: d, + pos: [d.x, d.y] + }); + }) + .on('mouseout', function(d) { + dispatch.elementMouseout({ + row: this, + data: d, + pos: [d.x, d.y] + }); + }); + + + + + // Toggle children on click. + function click(d, _, unshift) { + d3.event.stopPropagation(); + + if(d3.event.shiftKey && !unshift) { + //If you shift-click, it'll toggle fold all the children, instead of itself + d3.event.shiftKey = false; + d.values && d.values.forEach(function(node){ + if (node.values || node._values) { + click(node, 0, true); + } + }); + return true; + } + if(!hasChildren(d)) { + //download file + //window.location.href = d.url; + return true; + } + if (d.values) { + d._values = d.values; + d.values = null; + } else { + d.values = d._values; + d._values = null; + } + chart.update(); + } + + + function icon(d) { + return (d._values && d._values.length) ? iconOpen : (d.values && d.values.length) ? iconClose : ''; + } + + function folded(d) { + return (d._values && d._values.length); + } + + function hasChildren(d) { + var values = d.values || d._values; + + return (values && values.length); + } + + + }); + + return chart; + } + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + chart.options = nv.utils.optionsFunc.bind(chart); + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + scatter.color(color); + return chart; + }; + + chart.id = function(_) { + if (!arguments.length) return id; + id = _; + return chart; + }; + + chart.header = function(_) { + if (!arguments.length) return header; + header = _; + return chart; + }; + + chart.noData = function(_) { + if (!arguments.length) return noData; + noData = _; + return chart; + }; + + chart.filterZero = function(_) { + if (!arguments.length) return filterZero; + filterZero = _; + return chart; + }; + + chart.columns = function(_) { + if (!arguments.length) return columns; + columns = _; + return chart; + }; + + chart.tableClass = function(_) { + if (!arguments.length) return tableClass; + tableClass = _; + return chart; + }; + + chart.iconOpen = function(_){ + if (!arguments.length) return iconOpen; + iconOpen = _; + return chart; + } + + chart.iconClose = function(_){ + if (!arguments.length) return iconClose; + iconClose = _; + return chart; + } + + chart.getUrl = function(_){ + if (!arguments.length) return getUrl; + getUrl = _; + return chart; + } + + //============================================================ + + + return chart; +};nv.models.legend = function() { + "use strict"; + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var margin = {top: 5, right: 0, bottom: 5, left: 0} + , width = 400 + , height = 20 + , getKey = function(d) { return d.key } + , color = nv.utils.defaultColor() + , align = true + , rightAlign = true + , updateState = true //If true, legend will update data.disabled and trigger a 'stateChange' dispatch. + , radioButtonMode = false //If true, clicking legend items will cause it to behave like a radio button. (only one can be selected at a time) + , dispatch = d3.dispatch('legendClick', 'legendDblclick', 'legendMouseover', 'legendMouseout', 'stateChange') + ; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var availableWidth = width - margin.left - margin.right, + container = d3.select(this); + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-legend').data([data]); + var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-legend').append('g'); + var g = wrap.select('g'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + //------------------------------------------------------------ + + + var series = g.selectAll('.nv-series') + .data(function(d) { return d }); + var seriesEnter = series.enter().append('g').attr('class', 'nv-series') + .on('mouseover', function(d,i) { + dispatch.legendMouseover(d,i); //TODO: Make consistent with other event objects + }) + .on('mouseout', function(d,i) { + dispatch.legendMouseout(d,i); + }) + .on('click', function(d,i) { + dispatch.legendClick(d,i); + if (updateState) { + if (radioButtonMode) { + //Radio button mode: set every series to disabled, + // and enable the clicked series. + data.forEach(function(series) { series.disabled = true}); + d.disabled = false; + } + else { + d.disabled = !d.disabled; + if (data.every(function(series) { return series.disabled})) { + //the default behavior of NVD3 legends is, if every single series + // is disabled, turn all series' back on. + data.forEach(function(series) { series.disabled = false}); + } + } + dispatch.stateChange({ + disabled: data.map(function(d) { return !!d.disabled }) + }); + } + }) + .on('dblclick', function(d,i) { + dispatch.legendDblclick(d,i); + if (updateState) { + //the default behavior of NVD3 legends, when double clicking one, + // is to set all other series' to false, and make the double clicked series enabled. + data.forEach(function(series) { + series.disabled = true; + }); + d.disabled = false; + dispatch.stateChange({ + disabled: data.map(function(d) { return !!d.disabled }) + }); + } + }); + seriesEnter.append('circle') + .style('stroke-width', 2) + .attr('class','nv-legend-symbol') + .attr('r', 5); + seriesEnter.append('text') + .attr('text-anchor', 'start') + .attr('class','nv-legend-text') + .attr('dy', '.32em') + .attr('dx', '8'); + series.classed('disabled', function(d) { return d.disabled }); + series.exit().remove(); + series.select('circle') + .style('fill', function(d,i) { return d.color || color(d,i)}) + .style('stroke', function(d,i) { return d.color || color(d, i) }); + series.select('text').text(getKey); + + + //TODO: implement fixed-width and max-width options (max-width is especially useful with the align option) + + // NEW ALIGNING CODE, TODO: clean up + if (align) { + + var seriesWidths = []; + series.each(function(d,i) { + var legendText = d3.select(this).select('text'); + var nodeTextLength; + try { + nodeTextLength = legendText.node().getComputedTextLength(); + } + catch(e) { + nodeTextLength = nv.utils.calcApproxTextWidth(legendText); + } + + seriesWidths.push(nodeTextLength + 28); // 28 is ~ the width of the circle plus some padding + }); + + var seriesPerRow = 0; + var legendWidth = 0; + var columnWidths = []; + + while ( legendWidth < availableWidth && seriesPerRow < seriesWidths.length) { + columnWidths[seriesPerRow] = seriesWidths[seriesPerRow]; + legendWidth += seriesWidths[seriesPerRow++]; + } + if (seriesPerRow === 0) seriesPerRow = 1; //minimum of one series per row + + + while ( legendWidth > availableWidth && seriesPerRow > 1 ) { + columnWidths = []; + seriesPerRow--; + + for (var k = 0; k < seriesWidths.length; k++) { + if (seriesWidths[k] > (columnWidths[k % seriesPerRow] || 0) ) + columnWidths[k % seriesPerRow] = seriesWidths[k]; + } + + legendWidth = columnWidths.reduce(function(prev, cur, index, array) { + return prev + cur; + }); + } + + var xPositions = []; + for (var i = 0, curX = 0; i < seriesPerRow; i++) { + xPositions[i] = curX; + curX += columnWidths[i]; + } + + series + .attr('transform', function(d, i) { + return 'translate(' + xPositions[i % seriesPerRow] + ',' + (5 + Math.floor(i / seriesPerRow) * 20) + ')'; + }); + + //position legend as far right as possible within the total width + if (rightAlign) { + g.attr('transform', 'translate(' + (width - margin.right - legendWidth) + ',' + margin.top + ')'); + } + else { + g.attr('transform', 'translate(0' + ',' + margin.top + ')'); + } + + height = margin.top + margin.bottom + (Math.ceil(seriesWidths.length / seriesPerRow) * 20); + + } else { + + var ypos = 5, + newxpos = 5, + maxwidth = 0, + xpos; + series + .attr('transform', function(d, i) { + var length = d3.select(this).select('text').node().getComputedTextLength() + 28; + xpos = newxpos; + + if (width < margin.left + margin.right + xpos + length) { + newxpos = xpos = 5; + ypos += 20; + } + + newxpos += length; + if (newxpos > maxwidth) maxwidth = newxpos; + + return 'translate(' + xpos + ',' + ypos + ')'; + }); + + //position legend as far right as possible within the total width + g.attr('transform', 'translate(' + (width - margin.right - maxwidth) + ',' + margin.top + ')'); + + height = margin.top + margin.bottom + ypos + 15; + + } + + }); + + return chart; + } + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + chart.dispatch = dispatch; + chart.options = nv.utils.optionsFunc.bind(chart); + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.key = function(_) { + if (!arguments.length) return getKey; + getKey = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + return chart; + }; + + chart.align = function(_) { + if (!arguments.length) return align; + align = _; + return chart; + }; + + chart.rightAlign = function(_) { + if (!arguments.length) return rightAlign; + rightAlign = _; + return chart; + }; + + chart.updateState = function(_) { + if (!arguments.length) return updateState; + updateState = _; + return chart; + }; + + chart.radioButtonMode = function(_) { + if (!arguments.length) return radioButtonMode; + radioButtonMode = _; + return chart; + }; + + //============================================================ + + + return chart; +} + +nv.models.line = function() { + "use strict"; + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var scatter = nv.models.scatter() + ; + + var margin = {top: 0, right: 0, bottom: 0, left: 0} + , width = 960 + , height = 500 + , color = nv.utils.defaultColor() // a function that returns a color + , getX = function(d) { return d.x } // accessor to get the x value from a data point + , getY = function(d) { return d.y } // accessor to get the y value from a data point + , defined = function(d,i) { return !isNaN(getY(d,i)) && getY(d,i) !== null } // allows a line to be not continuous when it is not defined + , isArea = function(d) { return d.area } // decides if a line is an area or just a line + , clipEdge = false // if true, masks lines within x and y scale + , x //can be accessed via chart.xScale() + , y //can be accessed via chart.yScale() + , interpolate = "linear" // controls the line interpolation + ; + + scatter + .size(16) // default size + .sizeDomain([16,256]) //set to speed up calculation, needs to be unset if there is a custom size accessor + ; + + //============================================================ + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var x0, y0 //used to store previous scales + ; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var availableWidth = width - margin.left - margin.right, + availableHeight = height - margin.top - margin.bottom, + container = d3.select(this); + + //------------------------------------------------------------ + // Setup Scales + + x = scatter.xScale(); + y = scatter.yScale(); + + x0 = x0 || x; + y0 = y0 || y; + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-line').data([data]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-line'); + var defsEnter = wrapEnter.append('defs'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g') + + gEnter.append('g').attr('class', 'nv-groups'); + gEnter.append('g').attr('class', 'nv-scatterWrap'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + //------------------------------------------------------------ + + + + + scatter + .width(availableWidth) + .height(availableHeight) + + var scatterWrap = wrap.select('.nv-scatterWrap'); + //.datum(data); // Data automatically trickles down from the wrap + + scatterWrap.transition().call(scatter); + + + + defsEnter.append('clipPath') + .attr('id', 'nv-edge-clip-' + scatter.id()) + .append('rect'); + + wrap.select('#nv-edge-clip-' + scatter.id() + ' rect') + .attr('width', availableWidth) + .attr('height', availableHeight); + + g .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + scatter.id() + ')' : ''); + scatterWrap + .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + scatter.id() + ')' : ''); + + + + + var groups = wrap.select('.nv-groups').selectAll('.nv-group') + .data(function(d) { return d }, function(d) { return d.key }); + groups.enter().append('g') + .style('stroke-opacity', 1e-6) + .style('fill-opacity', 1e-6); + groups.exit() + .transition() + .style('stroke-opacity', 1e-6) + .style('fill-opacity', 1e-6) + .remove(); + groups + .attr('class', function(d,i) { return 'nv-group nv-series-' + i }) + .classed('hover', function(d) { return d.hover }) + .style('fill', function(d,i){ return color(d, i) }) + .style('stroke', function(d,i){ return color(d, i)}); + groups + .transition() + .style('stroke-opacity', 1) + .style('fill-opacity', .5); + + + + var areaPaths = groups.selectAll('path.nv-area') + .data(function(d) { return isArea(d) ? [d] : [] }); // this is done differently than lines because I need to check if series is an area + areaPaths.enter().append('path') + .attr('class', 'nv-area') + .attr('d', function(d) { + return d3.svg.area() + .interpolate(interpolate) + .defined(defined) + .x(function(d,i) { return nv.utils.NaNtoZero(x0(getX(d,i))) }) + .y0(function(d,i) { return nv.utils.NaNtoZero(y0(getY(d,i))) }) + .y1(function(d,i) { return y0( y.domain()[0] <= 0 ? y.domain()[1] >= 0 ? 0 : y.domain()[1] : y.domain()[0] ) }) + //.y1(function(d,i) { return y0(0) }) //assuming 0 is within y domain.. may need to tweak this + .apply(this, [d.values]) + }); + groups.exit().selectAll('path.nv-area') + .remove(); + + areaPaths + .transition() + .attr('d', function(d) { + return d3.svg.area() + .interpolate(interpolate) + .defined(defined) + .x(function(d,i) { return nv.utils.NaNtoZero(x(getX(d,i))) }) + .y0(function(d,i) { return nv.utils.NaNtoZero(y(getY(d,i))) }) + .y1(function(d,i) { return y( y.domain()[0] <= 0 ? y.domain()[1] >= 0 ? 0 : y.domain()[1] : y.domain()[0] ) }) + //.y1(function(d,i) { return y0(0) }) //assuming 0 is within y domain.. may need to tweak this + .apply(this, [d.values]) + }); + + + + var linePaths = groups.selectAll('path.nv-line') + .data(function(d) { return [d.values] }); + linePaths.enter().append('path') + .attr('class', 'nv-line') + .attr('d', + d3.svg.line() + .interpolate(interpolate) + .defined(defined) + .x(function(d,i) { return nv.utils.NaNtoZero(x0(getX(d,i))) }) + .y(function(d,i) { return nv.utils.NaNtoZero(y0(getY(d,i))) }) + ); + groups.exit().selectAll('path.nv-line') + .transition() + .attr('d', + d3.svg.line() + .interpolate(interpolate) + .defined(defined) + .x(function(d,i) { return nv.utils.NaNtoZero(x(getX(d,i))) }) + .y(function(d,i) { return nv.utils.NaNtoZero(y(getY(d,i))) }) + ); + linePaths + .transition() + .attr('d', + d3.svg.line() + .interpolate(interpolate) + .defined(defined) + .x(function(d,i) { return nv.utils.NaNtoZero(x(getX(d,i))) }) + .y(function(d,i) { return nv.utils.NaNtoZero(y(getY(d,i))) }) + ); + + + + //store old scales for use in transitions on update + x0 = x.copy(); + y0 = y.copy(); + + }); + + return chart; + } + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + chart.dispatch = scatter.dispatch; + chart.scatter = scatter; + + d3.rebind(chart, scatter, 'id', 'interactive', 'size', 'xScale', 'yScale', 'zScale', 'xDomain', 'yDomain', 'xRange', 'yRange', + 'sizeDomain', 'forceX', 'forceY', 'forceSize', 'clipVoronoi', 'useVoronoi', 'clipRadius', 'padData','highlightPoint','clearHighlights'); + + chart.options = nv.utils.optionsFunc.bind(chart); + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.x = function(_) { + if (!arguments.length) return getX; + getX = _; + scatter.x(_); + return chart; + }; + + chart.y = function(_) { + if (!arguments.length) return getY; + getY = _; + scatter.y(_); + return chart; + }; + + chart.clipEdge = function(_) { + if (!arguments.length) return clipEdge; + clipEdge = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + scatter.color(color); + return chart; + }; + + chart.interpolate = function(_) { + if (!arguments.length) return interpolate; + interpolate = _; + return chart; + }; + + chart.defined = function(_) { + if (!arguments.length) return defined; + defined = _; + return chart; + }; + + chart.isArea = function(_) { + if (!arguments.length) return isArea; + isArea = d3.functor(_); + return chart; + }; + + //============================================================ + + + return chart; +} + +nv.models.lineChart = function() { + "use strict"; + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var lines = nv.models.line() + , xAxis = nv.models.axis() + , yAxis = nv.models.axis() + , legend = nv.models.legend() + , interactiveLayer = nv.interactiveGuideline() + ; + + var margin = {top: 30, right: 20, bottom: 50, left: 60} + , color = nv.utils.defaultColor() + , width = null + , height = null + , showLegend = true + , showXAxis = true + , showYAxis = true + , rightAlignYAxis = false + , useInteractiveGuideline = false + , tooltips = true + , tooltip = function(key, x, y, e, graph) { + return '<h3>' + key + '</h3>' + + '<p>' + y + ' at ' + x + '</p>' + } + , x + , y + , state = {} + , defaultState = null + , noData = 'No Data Available.' + , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState') + , transitionDuration = 250 + ; + + xAxis + .orient('bottom') + .tickPadding(7) + ; + yAxis + .orient((rightAlignYAxis) ? 'right' : 'left') + ; + + //============================================================ + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var showTooltip = function(e, offsetElement) { + var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ), + top = e.pos[1] + ( offsetElement.offsetTop || 0), + x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)), + y = yAxis.tickFormat()(lines.y()(e.point, e.pointIndex)), + content = tooltip(e.series.key, x, y, e, chart); + + nv.tooltip.show([left, top], content, null, null, offsetElement); + }; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var container = d3.select(this), + that = this; + + var availableWidth = (width || parseInt(container.style('width')) || 960) + - margin.left - margin.right, + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + + + chart.update = function() { container.transition().duration(transitionDuration).call(chart) }; + chart.container = this; + + //set state.disabled + state.disabled = data.map(function(d) { return !!d.disabled }); + + + if (!defaultState) { + var key; + defaultState = {}; + for (key in state) { + if (state[key] instanceof Array) + defaultState[key] = state[key].slice(0); + else + defaultState[key] = state[key]; + } + } + + //------------------------------------------------------------ + // Display noData message if there's nothing to show. + + if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { + var noDataText = container.selectAll('.nv-noData').data([noData]); + + noDataText.enter().append('text') + .attr('class', 'nvd3 nv-noData') + .attr('dy', '-.7em') + .style('text-anchor', 'middle'); + + noDataText + .attr('x', margin.left + availableWidth / 2) + .attr('y', margin.top + availableHeight / 2) + .text(function(d) { return d }); + + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup Scales + + x = lines.xScale(); + y = lines.yScale(); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-lineChart').data([data]); + var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-lineChart').append('g'); + var g = wrap.select('g'); + + gEnter.append("rect").style("opacity",0); + gEnter.append('g').attr('class', 'nv-x nv-axis'); + gEnter.append('g').attr('class', 'nv-y nv-axis'); + gEnter.append('g').attr('class', 'nv-linesWrap'); + gEnter.append('g').attr('class', 'nv-legendWrap'); + gEnter.append('g').attr('class', 'nv-interactive'); + + g.select("rect").attr("width",availableWidth).attr("height",availableHeight); + //------------------------------------------------------------ + // Legend + + if (showLegend) { + legend.width(availableWidth); + + g.select('.nv-legendWrap') + .datum(data) + .call(legend); + + if ( margin.top != legend.height()) { + margin.top = legend.height(); + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + } + + wrap.select('.nv-legendWrap') + .attr('transform', 'translate(0,' + (-margin.top) +')') + } + + //------------------------------------------------------------ + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + if (rightAlignYAxis) { + g.select(".nv-y.nv-axis") + .attr("transform", "translate(" + availableWidth + ",0)"); + } + + //------------------------------------------------------------ + // Main Chart Component(s) + + + //------------------------------------------------------------ + //Set up interactive layer + if (useInteractiveGuideline) { + interactiveLayer + .width(availableWidth) + .height(availableHeight) + .margin({left:margin.left, top:margin.top}) + .svgContainer(container) + .xScale(x); + wrap.select(".nv-interactive").call(interactiveLayer); + } + + + lines + .width(availableWidth) + .height(availableHeight) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled })); + + + var linesWrap = g.select('.nv-linesWrap') + .datum(data.filter(function(d) { return !d.disabled })) + + linesWrap.transition().call(lines); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup Axes + + if (showXAxis) { + xAxis + .scale(x) + .ticks( availableWidth / 100 ) + .tickSize(-availableHeight, 0); + + g.select('.nv-x.nv-axis') + .attr('transform', 'translate(0,' + y.range()[0] + ')'); + g.select('.nv-x.nv-axis') + .transition() + .call(xAxis); + } + + if (showYAxis) { + yAxis + .scale(y) + .ticks( availableHeight / 36 ) + .tickSize( -availableWidth, 0); + + g.select('.nv-y.nv-axis') + .transition() + .call(yAxis); + } + //------------------------------------------------------------ + + + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ + + legend.dispatch.on('stateChange', function(newState) { + state = newState; + dispatch.stateChange(state); + chart.update(); + }); + + interactiveLayer.dispatch.on('elementMousemove', function(e) { + lines.clearHighlights(); + var singlePoint, pointIndex, pointXLocation, allData = []; + data + .filter(function(series, i) { + series.seriesIndex = i; + return !series.disabled; + }) + .forEach(function(series,i) { + pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x()); + lines.highlightPoint(i, pointIndex, true); + var point = series.values[pointIndex]; + if (typeof point === 'undefined') return; + if (typeof singlePoint === 'undefined') singlePoint = point; + if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex)); + allData.push({ + key: series.key, + value: chart.y()(point, pointIndex), + color: color(series,series.seriesIndex) + }); + }); + //Highlight the tooltip entry based on which point the mouse is closest to. + if (allData.length > 2) { + var yValue = chart.yScale().invert(e.mouseY); + var domainExtent = Math.abs(chart.yScale().domain()[0] - chart.yScale().domain()[1]); + var threshold = 0.03 * domainExtent; + var indexToHighlight = nv.nearestValueIndex(allData.map(function(d){return d.value}),yValue,threshold); + if (indexToHighlight !== null) + allData[indexToHighlight].highlight = true; + } + + var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex)); + interactiveLayer.tooltip + .position({left: pointXLocation + margin.left, top: e.mouseY + margin.top}) + .chartContainer(that.parentNode) + .enabled(tooltips) + .valueFormatter(function(d,i) { + return yAxis.tickFormat()(d); + }) + .data( + { + value: xValue, + series: allData + } + )(); + + interactiveLayer.renderGuideLine(pointXLocation); + + }); + + interactiveLayer.dispatch.on("elementMouseout",function(e) { + dispatch.tooltipHide(); + lines.clearHighlights(); + }); + + dispatch.on('tooltipShow', function(e) { + if (tooltips) showTooltip(e, that.parentNode); + }); + + + dispatch.on('changeState', function(e) { + + if (typeof e.disabled !== 'undefined') { + data.forEach(function(series,i) { + series.disabled = e.disabled[i]; + }); + + state.disabled = e.disabled; + } + + chart.update(); + }); + + //============================================================ + + }); + + return chart; + } + + + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ + + lines.dispatch.on('elementMouseover.tooltip', function(e) { + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); + + lines.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + + dispatch.on('tooltipHide', function() { + if (tooltips) nv.tooltip.cleanup(); + }); + + //============================================================ + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + // expose chart's sub-components + chart.dispatch = dispatch; + chart.lines = lines; + chart.legend = legend; + chart.xAxis = xAxis; + chart.yAxis = yAxis; + chart.interactiveLayer = interactiveLayer; + + d3.rebind(chart, lines, 'defined', 'isArea', 'x', 'y', 'size', 'xScale', 'yScale', 'xDomain', 'yDomain', 'xRange', 'yRange' + , 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi', 'useVoronoi','id', 'interpolate'); + + chart.options = nv.utils.optionsFunc.bind(chart); + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + legend.color(color); + return chart; + }; + + chart.showLegend = function(_) { + if (!arguments.length) return showLegend; + showLegend = _; + return chart; + }; + + chart.showXAxis = function(_) { + if (!arguments.length) return showXAxis; + showXAxis = _; + return chart; + }; + + chart.showYAxis = function(_) { + if (!arguments.length) return showYAxis; + showYAxis = _; + return chart; + }; + + chart.rightAlignYAxis = function(_) { + if(!arguments.length) return rightAlignYAxis; + rightAlignYAxis = _; + yAxis.orient( (_) ? 'right' : 'left'); + return chart; + }; + + chart.useInteractiveGuideline = function(_) { + if(!arguments.length) return useInteractiveGuideline; + useInteractiveGuideline = _; + if (_ === true) { + chart.interactive(false); + chart.useVoronoi(false); + } + return chart; + }; + + chart.tooltips = function(_) { + if (!arguments.length) return tooltips; + tooltips = _; + return chart; + }; + + chart.tooltipContent = function(_) { + if (!arguments.length) return tooltip; + tooltip = _; + return chart; + }; + + chart.state = function(_) { + if (!arguments.length) return state; + state = _; + return chart; + }; + + chart.defaultState = function(_) { + if (!arguments.length) return defaultState; + defaultState = _; + return chart; + }; + + chart.noData = function(_) { + if (!arguments.length) return noData; + noData = _; + return chart; + }; + + chart.transitionDuration = function(_) { + if (!arguments.length) return transitionDuration; + transitionDuration = _; + return chart; + }; + + //============================================================ + + + return chart; +} + +nv.models.linePlusBarChart = function() { + "use strict"; + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var lines = nv.models.line() + , bars = nv.models.historicalBar() + , xAxis = nv.models.axis() + , y1Axis = nv.models.axis() + , y2Axis = nv.models.axis() + , legend = nv.models.legend() + ; + + var margin = {top: 30, right: 60, bottom: 50, left: 60} + , width = null + , height = null + , getX = function(d) { return d.x } + , getY = function(d) { return d.y } + , color = nv.utils.defaultColor() + , showLegend = true + , tooltips = true + , tooltip = function(key, x, y, e, graph) { + return '<h3>' + key + '</h3>' + + '<p>' + y + ' at ' + x + '</p>'; + } + , x + , y1 + , y2 + , state = {} + , defaultState = null + , noData = "No Data Available." + , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState') + ; + + bars + .padData(true) + ; + lines + .clipEdge(false) + .padData(true) + ; + xAxis + .orient('bottom') + .tickPadding(7) + .highlightZero(false) + ; + y1Axis + .orient('left') + ; + y2Axis + .orient('right') + ; + + //============================================================ + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var showTooltip = function(e, offsetElement) { + var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ), + top = e.pos[1] + ( offsetElement.offsetTop || 0), + x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)), + y = (e.series.bar ? y1Axis : y2Axis).tickFormat()(lines.y()(e.point, e.pointIndex)), + content = tooltip(e.series.key, x, y, e, chart); + + nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement); + } + ; + + //------------------------------------------------------------ + + + + function chart(selection) { + selection.each(function(data) { + var container = d3.select(this), + that = this; + + var availableWidth = (width || parseInt(container.style('width')) || 960) + - margin.left - margin.right, + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + + chart.update = function() { container.transition().call(chart); }; + // chart.container = this; + + //set state.disabled + state.disabled = data.map(function(d) { return !!d.disabled }); + + if (!defaultState) { + var key; + defaultState = {}; + for (key in state) { + if (state[key] instanceof Array) + defaultState[key] = state[key].slice(0); + else + defaultState[key] = state[key]; + } + } + + //------------------------------------------------------------ + // Display No Data message if there's nothing to show. + + if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { + var noDataText = container.selectAll('.nv-noData').data([noData]); + + noDataText.enter().append('text') + .attr('class', 'nvd3 nv-noData') + .attr('dy', '-.7em') + .style('text-anchor', 'middle'); + + noDataText + .attr('x', margin.left + availableWidth / 2) + .attr('y', margin.top + availableHeight / 2) + .text(function(d) { return d }); + + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup Scales + + var dataBars = data.filter(function(d) { return !d.disabled && d.bar }); + var dataLines = data.filter(function(d) { return !d.bar }); // removed the !d.disabled clause here to fix Issue #240 + + //x = xAxis.scale(); + x = dataLines.filter(function(d) { return !d.disabled; }).length && dataLines.filter(function(d) { return !d.disabled; })[0].values.length ? lines.xScale() : bars.xScale(); + //x = dataLines.filter(function(d) { return !d.disabled; }).length ? lines.xScale() : bars.xScale(); //old code before change above + y1 = bars.yScale(); + y2 = lines.yScale(); + + //------------------------------------------------------------ + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = d3.select(this).selectAll('g.nv-wrap.nv-linePlusBar').data([data]); + var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-linePlusBar').append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-x nv-axis'); + gEnter.append('g').attr('class', 'nv-y1 nv-axis'); + gEnter.append('g').attr('class', 'nv-y2 nv-axis'); + gEnter.append('g').attr('class', 'nv-barsWrap'); + gEnter.append('g').attr('class', 'nv-linesWrap'); + gEnter.append('g').attr('class', 'nv-legendWrap'); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Legend + + if (showLegend) { + legend.width( availableWidth / 2 ); + + g.select('.nv-legendWrap') + .datum(data.map(function(series) { + series.originalKey = series.originalKey === undefined ? series.key : series.originalKey; + series.key = series.originalKey + (series.bar ? ' (left axis)' : ' (right axis)'); + return series; + })) + .call(legend); + + if ( margin.top != legend.height()) { + margin.top = legend.height(); + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + } + + g.select('.nv-legendWrap') + .attr('transform', 'translate(' + ( availableWidth / 2 ) + ',' + (-margin.top) +')'); + } + + //------------------------------------------------------------ + + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + + //------------------------------------------------------------ + // Main Chart Component(s) + + + lines + .width(availableWidth) + .height(availableHeight) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled && !data[i].bar })) + + bars + .width(availableWidth) + .height(availableHeight) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled && data[i].bar })) + + + + var barsWrap = g.select('.nv-barsWrap') + .datum(dataBars.length ? dataBars : [{values:[]}]) + + var linesWrap = g.select('.nv-linesWrap') + .datum(dataLines[0] && !dataLines[0].disabled ? dataLines : [{values:[]}] ); + //.datum(!dataLines[0].disabled ? dataLines : [{values:dataLines[0].values.map(function(d) { return [d[0], null] }) }] ); + + d3.transition(barsWrap).call(bars); + d3.transition(linesWrap).call(lines); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup Axes + + xAxis + .scale(x) + .ticks( availableWidth / 100 ) + .tickSize(-availableHeight, 0); + + g.select('.nv-x.nv-axis') + .attr('transform', 'translate(0,' + y1.range()[0] + ')'); + d3.transition(g.select('.nv-x.nv-axis')) + .call(xAxis); + + + y1Axis + .scale(y1) + .ticks( availableHeight / 36 ) + .tickSize(-availableWidth, 0); + + d3.transition(g.select('.nv-y1.nv-axis')) + .style('opacity', dataBars.length ? 1 : 0) + .call(y1Axis); + + + y2Axis + .scale(y2) + .ticks( availableHeight / 36 ) + .tickSize(dataBars.length ? 0 : -availableWidth, 0); // Show the y2 rules only if y1 has none + + g.select('.nv-y2.nv-axis') + .style('opacity', dataLines.length ? 1 : 0) + .attr('transform', 'translate(' + availableWidth + ',0)'); + //.attr('transform', 'translate(' + x.range()[1] + ',0)'); + + d3.transition(g.select('.nv-y2.nv-axis')) + .call(y2Axis); + + //------------------------------------------------------------ + + + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ + + legend.dispatch.on('stateChange', function(newState) { + state = newState; + dispatch.stateChange(state); + chart.update(); + }); + + dispatch.on('tooltipShow', function(e) { + if (tooltips) showTooltip(e, that.parentNode); + }); + + + // Update chart from a state object passed to event handler + dispatch.on('changeState', function(e) { + + if (typeof e.disabled !== 'undefined') { + data.forEach(function(series,i) { + series.disabled = e.disabled[i]; + }); + + state.disabled = e.disabled; + } + + chart.update(); + }); + + //============================================================ + + + }); + + return chart; + } + + + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ + + lines.dispatch.on('elementMouseover.tooltip', function(e) { + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); + + lines.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + + bars.dispatch.on('elementMouseover.tooltip', function(e) { + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); + + bars.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + + dispatch.on('tooltipHide', function() { + if (tooltips) nv.tooltip.cleanup(); + }); + + //============================================================ + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + // expose chart's sub-components + chart.dispatch = dispatch; + chart.legend = legend; + chart.lines = lines; + chart.bars = bars; + chart.xAxis = xAxis; + chart.y1Axis = y1Axis; + chart.y2Axis = y2Axis; + + d3.rebind(chart, lines, 'defined', 'size', 'clipVoronoi', 'interpolate'); + //TODO: consider rebinding x, y and some other stuff, and simply do soemthign lile bars.x(lines.x()), etc. + //d3.rebind(chart, lines, 'x', 'y', 'size', 'xDomain', 'yDomain', 'xRange', 'yRange', 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi', 'id'); + + chart.options = nv.utils.optionsFunc.bind(chart); + + chart.x = function(_) { + if (!arguments.length) return getX; + getX = _; + lines.x(_); + bars.x(_); + return chart; + }; + + chart.y = function(_) { + if (!arguments.length) return getY; + getY = _; + lines.y(_); + bars.y(_); + return chart; + }; + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + legend.color(color); + return chart; + }; + + chart.showLegend = function(_) { + if (!arguments.length) return showLegend; + showLegend = _; + return chart; + }; + + chart.tooltips = function(_) { + if (!arguments.length) return tooltips; + tooltips = _; + return chart; + }; + + chart.tooltipContent = function(_) { + if (!arguments.length) return tooltip; + tooltip = _; + return chart; + }; + + chart.state = function(_) { + if (!arguments.length) return state; + state = _; + return chart; + }; + + chart.defaultState = function(_) { + if (!arguments.length) return defaultState; + defaultState = _; + return chart; + }; + + chart.noData = function(_) { + if (!arguments.length) return noData; + noData = _; + return chart; + }; + + //============================================================ + + + return chart; +} +nv.models.lineWithFocusChart = function() { + "use strict"; + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var lines = nv.models.line() + , lines2 = nv.models.line() + , xAxis = nv.models.axis() + , yAxis = nv.models.axis() + , x2Axis = nv.models.axis() + , y2Axis = nv.models.axis() + , legend = nv.models.legend() + , brush = d3.svg.brush() + ; + + var margin = {top: 30, right: 30, bottom: 30, left: 60} + , margin2 = {top: 0, right: 30, bottom: 20, left: 60} + , color = nv.utils.defaultColor() + , width = null + , height = null + , height2 = 100 + , x + , y + , x2 + , y2 + , showLegend = true + , brushExtent = null + , tooltips = true + , tooltip = function(key, x, y, e, graph) { + return '<h3>' + key + '</h3>' + + '<p>' + y + ' at ' + x + '</p>' + } + , noData = "No Data Available." + , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'brush') + , transitionDuration = 250 + ; + + lines + .clipEdge(true) + ; + lines2 + .interactive(false) + ; + xAxis + .orient('bottom') + .tickPadding(5) + ; + yAxis + .orient('left') + ; + x2Axis + .orient('bottom') + .tickPadding(5) + ; + y2Axis + .orient('left') + ; + //============================================================ + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var showTooltip = function(e, offsetElement) { + var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ), + top = e.pos[1] + ( offsetElement.offsetTop || 0), + x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)), + y = yAxis.tickFormat()(lines.y()(e.point, e.pointIndex)), + content = tooltip(e.series.key, x, y, e, chart); + + nv.tooltip.show([left, top], content, null, null, offsetElement); + }; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var container = d3.select(this), + that = this; + + var availableWidth = (width || parseInt(container.style('width')) || 960) + - margin.left - margin.right, + availableHeight1 = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom - height2, + availableHeight2 = height2 - margin2.top - margin2.bottom; + + chart.update = function() { container.transition().duration(transitionDuration).call(chart) }; + chart.container = this; + + + //------------------------------------------------------------ + // Display No Data message if there's nothing to show. + + if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { + var noDataText = container.selectAll('.nv-noData').data([noData]); + + noDataText.enter().append('text') + .attr('class', 'nvd3 nv-noData') + .attr('dy', '-.7em') + .style('text-anchor', 'middle'); + + noDataText + .attr('x', margin.left + availableWidth / 2) + .attr('y', margin.top + availableHeight1 / 2) + .text(function(d) { return d }); + + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup Scales + + x = lines.xScale(); + y = lines.yScale(); + x2 = lines2.xScale(); + y2 = lines2.yScale(); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-lineWithFocusChart').data([data]); + var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-lineWithFocusChart').append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-legendWrap'); + + var focusEnter = gEnter.append('g').attr('class', 'nv-focus'); + focusEnter.append('g').attr('class', 'nv-x nv-axis'); + focusEnter.append('g').attr('class', 'nv-y nv-axis'); + focusEnter.append('g').attr('class', 'nv-linesWrap'); + + var contextEnter = gEnter.append('g').attr('class', 'nv-context'); + contextEnter.append('g').attr('class', 'nv-x nv-axis'); + contextEnter.append('g').attr('class', 'nv-y nv-axis'); + contextEnter.append('g').attr('class', 'nv-linesWrap'); + contextEnter.append('g').attr('class', 'nv-brushBackground'); + contextEnter.append('g').attr('class', 'nv-x nv-brush'); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Legend + + if (showLegend) { + legend.width(availableWidth); + + g.select('.nv-legendWrap') + .datum(data) + .call(legend); + + if ( margin.top != legend.height()) { + margin.top = legend.height(); + availableHeight1 = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom - height2; + } + + g.select('.nv-legendWrap') + .attr('transform', 'translate(0,' + (-margin.top) +')') + } + + //------------------------------------------------------------ + + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + + //------------------------------------------------------------ + // Main Chart Component(s) + + lines + .width(availableWidth) + .height(availableHeight1) + .color( + data + .map(function(d,i) { + return d.color || color(d, i); + }) + .filter(function(d,i) { + return !data[i].disabled; + }) + ); + + lines2 + .defined(lines.defined()) + .width(availableWidth) + .height(availableHeight2) + .color( + data + .map(function(d,i) { + return d.color || color(d, i); + }) + .filter(function(d,i) { + return !data[i].disabled; + }) + ); + + g.select('.nv-context') + .attr('transform', 'translate(0,' + ( availableHeight1 + margin.bottom + margin2.top) + ')') + + var contextLinesWrap = g.select('.nv-context .nv-linesWrap') + .datum(data.filter(function(d) { return !d.disabled })) + + d3.transition(contextLinesWrap).call(lines2); + + //------------------------------------------------------------ + + + /* + var focusLinesWrap = g.select('.nv-focus .nv-linesWrap') + .datum(data.filter(function(d) { return !d.disabled })) + + d3.transition(focusLinesWrap).call(lines); + */ + + + //------------------------------------------------------------ + // Setup Main (Focus) Axes + + xAxis + .scale(x) + .ticks( availableWidth / 100 ) + .tickSize(-availableHeight1, 0); + + yAxis + .scale(y) + .ticks( availableHeight1 / 36 ) + .tickSize( -availableWidth, 0); + + g.select('.nv-focus .nv-x.nv-axis') + .attr('transform', 'translate(0,' + availableHeight1 + ')'); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup Brush + + brush + .x(x2) + .on('brush', function() { + //When brushing, turn off transitions because chart needs to change immediately. + var oldTransition = chart.transitionDuration(); + chart.transitionDuration(0); + onBrush(); + chart.transitionDuration(oldTransition); + }); + + if (brushExtent) brush.extent(brushExtent); + + var brushBG = g.select('.nv-brushBackground').selectAll('g') + .data([brushExtent || brush.extent()]) + + var brushBGenter = brushBG.enter() + .append('g'); + + brushBGenter.append('rect') + .attr('class', 'left') + .attr('x', 0) + .attr('y', 0) + .attr('height', availableHeight2); + + brushBGenter.append('rect') + .attr('class', 'right') + .attr('x', 0) + .attr('y', 0) + .attr('height', availableHeight2); + + var gBrush = g.select('.nv-x.nv-brush') + .call(brush); + gBrush.selectAll('rect') + //.attr('y', -5) + .attr('height', availableHeight2); + gBrush.selectAll('.resize').append('path').attr('d', resizePath); + + onBrush(); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup Secondary (Context) Axes + + x2Axis + .scale(x2) + .ticks( availableWidth / 100 ) + .tickSize(-availableHeight2, 0); + + g.select('.nv-context .nv-x.nv-axis') + .attr('transform', 'translate(0,' + y2.range()[0] + ')'); + d3.transition(g.select('.nv-context .nv-x.nv-axis')) + .call(x2Axis); + + + y2Axis + .scale(y2) + .ticks( availableHeight2 / 36 ) + .tickSize( -availableWidth, 0); + + d3.transition(g.select('.nv-context .nv-y.nv-axis')) + .call(y2Axis); + + g.select('.nv-context .nv-x.nv-axis') + .attr('transform', 'translate(0,' + y2.range()[0] + ')'); + + //------------------------------------------------------------ + + + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ + + legend.dispatch.on('stateChange', function(newState) { + chart.update(); + }); + + dispatch.on('tooltipShow', function(e) { + if (tooltips) showTooltip(e, that.parentNode); + }); + + //============================================================ + + + //============================================================ + // Functions + //------------------------------------------------------------ + + // Taken from crossfilter (http://square.github.com/crossfilter/) + function resizePath(d) { + var e = +(d == 'e'), + x = e ? 1 : -1, + y = availableHeight2 / 3; + return 'M' + (.5 * x) + ',' + y + + 'A6,6 0 0 ' + e + ' ' + (6.5 * x) + ',' + (y + 6) + + 'V' + (2 * y - 6) + + 'A6,6 0 0 ' + e + ' ' + (.5 * x) + ',' + (2 * y) + + 'Z' + + 'M' + (2.5 * x) + ',' + (y + 8) + + 'V' + (2 * y - 8) + + 'M' + (4.5 * x) + ',' + (y + 8) + + 'V' + (2 * y - 8); + } + + + function updateBrushBG() { + if (!brush.empty()) brush.extent(brushExtent); + brushBG + .data([brush.empty() ? x2.domain() : brushExtent]) + .each(function(d,i) { + var leftWidth = x2(d[0]) - x.range()[0], + rightWidth = x.range()[1] - x2(d[1]); + d3.select(this).select('.left') + .attr('width', leftWidth < 0 ? 0 : leftWidth); + + d3.select(this).select('.right') + .attr('x', x2(d[1])) + .attr('width', rightWidth < 0 ? 0 : rightWidth); + }); + } + + + function onBrush() { + brushExtent = brush.empty() ? null : brush.extent(); + var extent = brush.empty() ? x2.domain() : brush.extent(); + + //The brush extent cannot be less than one. If it is, don't update the line chart. + if (Math.abs(extent[0] - extent[1]) <= 1) { + return; + } + + dispatch.brush({extent: extent, brush: brush}); + + + updateBrushBG(); + + // Update Main (Focus) + var focusLinesWrap = g.select('.nv-focus .nv-linesWrap') + .datum( + data + .filter(function(d) { return !d.disabled }) + .map(function(d,i) { + return { + key: d.key, + values: d.values.filter(function(d,i) { + return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1]; + }) + } + }) + ); + focusLinesWrap.transition().duration(transitionDuration).call(lines); + + + // Update Main (Focus) Axes + g.select('.nv-focus .nv-x.nv-axis').transition().duration(transitionDuration) + .call(xAxis); + g.select('.nv-focus .nv-y.nv-axis').transition().duration(transitionDuration) + .call(yAxis); + } + + //============================================================ + + + }); + + return chart; + } + + + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ + + lines.dispatch.on('elementMouseover.tooltip', function(e) { + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); + + lines.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + + dispatch.on('tooltipHide', function() { + if (tooltips) nv.tooltip.cleanup(); + }); + + //============================================================ + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + // expose chart's sub-components + chart.dispatch = dispatch; + chart.legend = legend; + chart.lines = lines; + chart.lines2 = lines2; + chart.xAxis = xAxis; + chart.yAxis = yAxis; + chart.x2Axis = x2Axis; + chart.y2Axis = y2Axis; + + d3.rebind(chart, lines, 'defined', 'isArea', 'size', 'xDomain', 'yDomain', 'xRange', 'yRange', 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi', 'id'); + + chart.options = nv.utils.optionsFunc.bind(chart); + + chart.x = function(_) { + if (!arguments.length) return lines.x; + lines.x(_); + lines2.x(_); + return chart; + }; + + chart.y = function(_) { + if (!arguments.length) return lines.y; + lines.y(_); + lines2.y(_); + return chart; + }; + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.margin2 = function(_) { + if (!arguments.length) return margin2; + margin2 = _; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.height2 = function(_) { + if (!arguments.length) return height2; + height2 = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color =nv.utils.getColor(_); + legend.color(color); + return chart; + }; + + chart.showLegend = function(_) { + if (!arguments.length) return showLegend; + showLegend = _; + return chart; + }; + + chart.tooltips = function(_) { + if (!arguments.length) return tooltips; + tooltips = _; + return chart; + }; + + chart.tooltipContent = function(_) { + if (!arguments.length) return tooltip; + tooltip = _; + return chart; + }; + + chart.interpolate = function(_) { + if (!arguments.length) return lines.interpolate(); + lines.interpolate(_); + lines2.interpolate(_); + return chart; + }; + + chart.noData = function(_) { + if (!arguments.length) return noData; + noData = _; + return chart; + }; + + // Chart has multiple similar Axes, to prevent code duplication, probably need to link all axis functions manually like below + chart.xTickFormat = function(_) { + if (!arguments.length) return xAxis.tickFormat(); + xAxis.tickFormat(_); + x2Axis.tickFormat(_); + return chart; + }; + + chart.yTickFormat = function(_) { + if (!arguments.length) return yAxis.tickFormat(); + yAxis.tickFormat(_); + y2Axis.tickFormat(_); + return chart; + }; + + chart.brushExtent = function(_) { + if (!arguments.length) return brushExtent; + brushExtent = _; + return chart; + }; + + chart.transitionDuration = function(_) { + if (!arguments.length) return transitionDuration; + transitionDuration = _; + return chart; + }; + + //============================================================ + + + return chart; +} + +nv.models.linePlusBarWithFocusChart = function() { + "use strict"; + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var lines = nv.models.line() + , lines2 = nv.models.line() + , bars = nv.models.historicalBar() + , bars2 = nv.models.historicalBar() + , xAxis = nv.models.axis() + , x2Axis = nv.models.axis() + , y1Axis = nv.models.axis() + , y2Axis = nv.models.axis() + , y3Axis = nv.models.axis() + , y4Axis = nv.models.axis() + , legend = nv.models.legend() + , brush = d3.svg.brush() + ; + + var margin = {top: 30, right: 30, bottom: 30, left: 60} + , margin2 = {top: 0, right: 30, bottom: 20, left: 60} + , width = null + , height = null + , height2 = 100 + , getX = function(d) { return d.x } + , getY = function(d) { return d.y } + , color = nv.utils.defaultColor() + , showLegend = true + , extent + , brushExtent = null + , tooltips = true + , tooltip = function(key, x, y, e, graph) { + return '<h3>' + key + '</h3>' + + '<p>' + y + ' at ' + x + '</p>'; + } + , x + , x2 + , y1 + , y2 + , y3 + , y4 + , noData = "No Data Available." + , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'brush') + , transitionDuration = 0 + ; + + lines + .clipEdge(true) + ; + lines2 + .interactive(false) + ; + xAxis + .orient('bottom') + .tickPadding(5) + ; + y1Axis + .orient('left') + ; + y2Axis + .orient('right') + ; + x2Axis + .orient('bottom') + .tickPadding(5) + ; + y3Axis + .orient('left') + ; + y4Axis + .orient('right') + ; + + //============================================================ + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var showTooltip = function(e, offsetElement) { + if (extent) { + e.pointIndex += Math.ceil(extent[0]); + } + var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ), + top = e.pos[1] + ( offsetElement.offsetTop || 0), + x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)), + y = (e.series.bar ? y1Axis : y2Axis).tickFormat()(lines.y()(e.point, e.pointIndex)), + content = tooltip(e.series.key, x, y, e, chart); + + nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement); + }; + + //------------------------------------------------------------ + + + + function chart(selection) { + selection.each(function(data) { + var container = d3.select(this), + that = this; + + var availableWidth = (width || parseInt(container.style('width')) || 960) + - margin.left - margin.right, + availableHeight1 = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom - height2, + availableHeight2 = height2 - margin2.top - margin2.bottom; + + chart.update = function() { container.transition().duration(transitionDuration).call(chart); }; + chart.container = this; + + + //------------------------------------------------------------ + // Display No Data message if there's nothing to show. + + if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { + var noDataText = container.selectAll('.nv-noData').data([noData]); + + noDataText.enter().append('text') + .attr('class', 'nvd3 nv-noData') + .attr('dy', '-.7em') + .style('text-anchor', 'middle'); + + noDataText + .attr('x', margin.left + availableWidth / 2) + .attr('y', margin.top + availableHeight1 / 2) + .text(function(d) { return d }); + + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup Scales + + var dataBars = data.filter(function(d) { return !d.disabled && d.bar }); + var dataLines = data.filter(function(d) { return !d.bar }); // removed the !d.disabled clause here to fix Issue #240 + + x = bars.xScale(); + x2 = x2Axis.scale(); + y1 = bars.yScale(); + y2 = lines.yScale(); + y3 = bars2.yScale(); + y4 = lines2.yScale(); + + var series1 = data + .filter(function(d) { return !d.disabled && d.bar }) + .map(function(d) { + return d.values.map(function(d,i) { + return { x: getX(d,i), y: getY(d,i) } + }) + }); + + var series2 = data + .filter(function(d) { return !d.disabled && !d.bar }) + .map(function(d) { + return d.values.map(function(d,i) { + return { x: getX(d,i), y: getY(d,i) } + }) + }); + + x .range([0, availableWidth]); + + x2 .domain(d3.extent(d3.merge(series1.concat(series2)), function(d) { return d.x } )) + .range([0, availableWidth]); + + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-linePlusBar').data([data]); + var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-linePlusBar').append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-legendWrap'); + + var focusEnter = gEnter.append('g').attr('class', 'nv-focus'); + focusEnter.append('g').attr('class', 'nv-x nv-axis'); + focusEnter.append('g').attr('class', 'nv-y1 nv-axis'); + focusEnter.append('g').attr('class', 'nv-y2 nv-axis'); + focusEnter.append('g').attr('class', 'nv-barsWrap'); + focusEnter.append('g').attr('class', 'nv-linesWrap'); + + var contextEnter = gEnter.append('g').attr('class', 'nv-context'); + contextEnter.append('g').attr('class', 'nv-x nv-axis'); + contextEnter.append('g').attr('class', 'nv-y1 nv-axis'); + contextEnter.append('g').attr('class', 'nv-y2 nv-axis'); + contextEnter.append('g').attr('class', 'nv-barsWrap'); + contextEnter.append('g').attr('class', 'nv-linesWrap'); + contextEnter.append('g').attr('class', 'nv-brushBackground'); + contextEnter.append('g').attr('class', 'nv-x nv-brush'); + + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Legend + + if (showLegend) { + legend.width( availableWidth / 2 ); + + g.select('.nv-legendWrap') + .datum(data.map(function(series) { + series.originalKey = series.originalKey === undefined ? series.key : series.originalKey; + series.key = series.originalKey + (series.bar ? ' (left axis)' : ' (right axis)'); + return series; + })) + .call(legend); + + if ( margin.top != legend.height()) { + margin.top = legend.height(); + availableHeight1 = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom - height2; + } + + g.select('.nv-legendWrap') + .attr('transform', 'translate(' + ( availableWidth / 2 ) + ',' + (-margin.top) +')'); + } + + //------------------------------------------------------------ + + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + + //------------------------------------------------------------ + // Context Components + + bars2 + .width(availableWidth) + .height(availableHeight2) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled && data[i].bar })); + + lines2 + .width(availableWidth) + .height(availableHeight2) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled && !data[i].bar })); + + var bars2Wrap = g.select('.nv-context .nv-barsWrap') + .datum(dataBars.length ? dataBars : [{values:[]}]); + + var lines2Wrap = g.select('.nv-context .nv-linesWrap') + .datum(!dataLines[0].disabled ? dataLines : [{values:[]}]); + + g.select('.nv-context') + .attr('transform', 'translate(0,' + ( availableHeight1 + margin.bottom + margin2.top) + ')') + + bars2Wrap.transition().call(bars2); + lines2Wrap.transition().call(lines2); + + //------------------------------------------------------------ + + + + //------------------------------------------------------------ + // Setup Brush + + brush + .x(x2) + .on('brush', onBrush); + + if (brushExtent) brush.extent(brushExtent); + + var brushBG = g.select('.nv-brushBackground').selectAll('g') + .data([brushExtent || brush.extent()]) + + var brushBGenter = brushBG.enter() + .append('g'); + + brushBGenter.append('rect') + .attr('class', 'left') + .attr('x', 0) + .attr('y', 0) + .attr('height', availableHeight2); + + brushBGenter.append('rect') + .attr('class', 'right') + .attr('x', 0) + .attr('y', 0) + .attr('height', availableHeight2); + + var gBrush = g.select('.nv-x.nv-brush') + .call(brush); + gBrush.selectAll('rect') + //.attr('y', -5) + .attr('height', availableHeight2); + gBrush.selectAll('.resize').append('path').attr('d', resizePath); + + //------------------------------------------------------------ + + //------------------------------------------------------------ + // Setup Secondary (Context) Axes + + x2Axis + .ticks( availableWidth / 100 ) + .tickSize(-availableHeight2, 0); + + g.select('.nv-context .nv-x.nv-axis') + .attr('transform', 'translate(0,' + y3.range()[0] + ')'); + g.select('.nv-context .nv-x.nv-axis').transition() + .call(x2Axis); + + + y3Axis + .scale(y3) + .ticks( availableHeight2 / 36 ) + .tickSize( -availableWidth, 0); + + g.select('.nv-context .nv-y1.nv-axis') + .style('opacity', dataBars.length ? 1 : 0) + .attr('transform', 'translate(0,' + x2.range()[0] + ')'); + + g.select('.nv-context .nv-y1.nv-axis').transition() + .call(y3Axis); + + + y4Axis + .scale(y4) + .ticks( availableHeight2 / 36 ) + .tickSize(dataBars.length ? 0 : -availableWidth, 0); // Show the y2 rules only if y1 has none + + g.select('.nv-context .nv-y2.nv-axis') + .style('opacity', dataLines.length ? 1 : 0) + .attr('transform', 'translate(' + x2.range()[1] + ',0)'); + + g.select('.nv-context .nv-y2.nv-axis').transition() + .call(y4Axis); + + //------------------------------------------------------------ + + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ + + legend.dispatch.on('stateChange', function(newState) { + chart.update(); + }); + + dispatch.on('tooltipShow', function(e) { + if (tooltips) showTooltip(e, that.parentNode); + }); + + //============================================================ + + + //============================================================ + // Functions + //------------------------------------------------------------ + + // Taken from crossfilter (http://square.github.com/crossfilter/) + function resizePath(d) { + var e = +(d == 'e'), + x = e ? 1 : -1, + y = availableHeight2 / 3; + return 'M' + (.5 * x) + ',' + y + + 'A6,6 0 0 ' + e + ' ' + (6.5 * x) + ',' + (y + 6) + + 'V' + (2 * y - 6) + + 'A6,6 0 0 ' + e + ' ' + (.5 * x) + ',' + (2 * y) + + 'Z' + + 'M' + (2.5 * x) + ',' + (y + 8) + + 'V' + (2 * y - 8) + + 'M' + (4.5 * x) + ',' + (y + 8) + + 'V' + (2 * y - 8); + } + + + function updateBrushBG() { + if (!brush.empty()) brush.extent(brushExtent); + brushBG + .data([brush.empty() ? x2.domain() : brushExtent]) + .each(function(d,i) { + var leftWidth = x2(d[0]) - x2.range()[0], + rightWidth = x2.range()[1] - x2(d[1]); + d3.select(this).select('.left') + .attr('width', leftWidth < 0 ? 0 : leftWidth); + + d3.select(this).select('.right') + .attr('x', x2(d[1])) + .attr('width', rightWidth < 0 ? 0 : rightWidth); + }); + } + + + function onBrush() { + brushExtent = brush.empty() ? null : brush.extent(); + extent = brush.empty() ? x2.domain() : brush.extent(); + + + dispatch.brush({extent: extent, brush: brush}); + + updateBrushBG(); + + + //------------------------------------------------------------ + // Prepare Main (Focus) Bars and Lines + + bars + .width(availableWidth) + .height(availableHeight1) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled && data[i].bar })); + + + lines + .width(availableWidth) + .height(availableHeight1) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled && !data[i].bar })); + + var focusBarsWrap = g.select('.nv-focus .nv-barsWrap') + .datum(!dataBars.length ? [{values:[]}] : + dataBars + .map(function(d,i) { + return { + key: d.key, + values: d.values.filter(function(d,i) { + return bars.x()(d,i) >= extent[0] && bars.x()(d,i) <= extent[1]; + }) + } + }) + ); + + var focusLinesWrap = g.select('.nv-focus .nv-linesWrap') + .datum(dataLines[0].disabled ? [{values:[]}] : + dataLines + .map(function(d,i) { + return { + key: d.key, + values: d.values.filter(function(d,i) { + return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1]; + }) + } + }) + ); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Update Main (Focus) X Axis + + if (dataBars.length) { + x = bars.xScale(); + } else { + x = lines.xScale(); + } + + xAxis + .scale(x) + .ticks( availableWidth / 100 ) + .tickSize(-availableHeight1, 0); + + xAxis.domain([Math.ceil(extent[0]), Math.floor(extent[1])]); + + g.select('.nv-x.nv-axis').transition().duration(transitionDuration) + .call(xAxis); + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Update Main (Focus) Bars and Lines + + focusBarsWrap.transition().duration(transitionDuration).call(bars); + focusLinesWrap.transition().duration(transitionDuration).call(lines); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup and Update Main (Focus) Y Axes + + g.select('.nv-focus .nv-x.nv-axis') + .attr('transform', 'translate(0,' + y1.range()[0] + ')'); + + + y1Axis + .scale(y1) + .ticks( availableHeight1 / 36 ) + .tickSize(-availableWidth, 0); + + g.select('.nv-focus .nv-y1.nv-axis') + .style('opacity', dataBars.length ? 1 : 0); + + + y2Axis + .scale(y2) + .ticks( availableHeight1 / 36 ) + .tickSize(dataBars.length ? 0 : -availableWidth, 0); // Show the y2 rules only if y1 has none + + g.select('.nv-focus .nv-y2.nv-axis') + .style('opacity', dataLines.length ? 1 : 0) + .attr('transform', 'translate(' + x.range()[1] + ',0)'); + + g.select('.nv-focus .nv-y1.nv-axis').transition().duration(transitionDuration) + .call(y1Axis); + g.select('.nv-focus .nv-y2.nv-axis').transition().duration(transitionDuration) + .call(y2Axis); + } + + //============================================================ + + onBrush(); + + }); + + return chart; + } + + + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ + + lines.dispatch.on('elementMouseover.tooltip', function(e) { + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); + + lines.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + + bars.dispatch.on('elementMouseover.tooltip', function(e) { + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); + + bars.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + + dispatch.on('tooltipHide', function() { + if (tooltips) nv.tooltip.cleanup(); + }); + + //============================================================ + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + // expose chart's sub-components + chart.dispatch = dispatch; + chart.legend = legend; + chart.lines = lines; + chart.lines2 = lines2; + chart.bars = bars; + chart.bars2 = bars2; + chart.xAxis = xAxis; + chart.x2Axis = x2Axis; + chart.y1Axis = y1Axis; + chart.y2Axis = y2Axis; + chart.y3Axis = y3Axis; + chart.y4Axis = y4Axis; + + d3.rebind(chart, lines, 'defined', 'size', 'clipVoronoi', 'interpolate'); + //TODO: consider rebinding x, y and some other stuff, and simply do soemthign lile bars.x(lines.x()), etc. + //d3.rebind(chart, lines, 'x', 'y', 'size', 'xDomain', 'yDomain', 'xRange', 'yRange', 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi', 'id'); + + chart.options = nv.utils.optionsFunc.bind(chart); + + chart.x = function(_) { + if (!arguments.length) return getX; + getX = _; + lines.x(_); + bars.x(_); + return chart; + }; + + chart.y = function(_) { + if (!arguments.length) return getY; + getY = _; + lines.y(_); + bars.y(_); + return chart; + }; + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + legend.color(color); + return chart; + }; + + chart.showLegend = function(_) { + if (!arguments.length) return showLegend; + showLegend = _; + return chart; + }; + + chart.tooltips = function(_) { + if (!arguments.length) return tooltips; + tooltips = _; + return chart; + }; + + chart.tooltipContent = function(_) { + if (!arguments.length) return tooltip; + tooltip = _; + return chart; + }; + + chart.noData = function(_) { + if (!arguments.length) return noData; + noData = _; + return chart; + }; + + chart.brushExtent = function(_) { + if (!arguments.length) return brushExtent; + brushExtent = _; + return chart; + }; + + + //============================================================ + + + return chart; +} + +nv.models.multiBar = function() { + "use strict"; + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var margin = {top: 0, right: 0, bottom: 0, left: 0} + , width = 960 + , height = 500 + , x = d3.scale.ordinal() + , y = d3.scale.linear() + , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one + , getX = function(d) { return d.x } + , getY = function(d) { return d.y } + , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove + , clipEdge = true + , stacked = false + , stackOffset = 'zero' // options include 'silhouette', 'wiggle', 'expand', 'zero', or a custom function + , color = nv.utils.defaultColor() + , hideable = false + , barColor = null // adding the ability to set the color for each rather than the whole group + , disabled // used in conjunction with barColor to communicate from multiBarHorizontalChart what series are disabled + , delay = 1200 + , xDomain + , yDomain + , xRange + , yRange + , groupSpacing = 0.1 + , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout') + ; + + //============================================================ + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var x0, y0 //used to store previous scales + ; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var availableWidth = width - margin.left - margin.right, + availableHeight = height - margin.top - margin.bottom, + container = d3.select(this); + + if(hideable && data.length) hideable = [{ + values: data[0].values.map(function(d) { + return { + x: d.x, + y: 0, + series: d.series, + size: 0.01 + };} + )}]; + + if (stacked) + data = d3.layout.stack() + .offset(stackOffset) + .values(function(d){ return d.values }) + .y(getY) + (!data.length && hideable ? hideable : data); + + + //add series index to each data point for reference + data.forEach(function(series, i) { + series.values.forEach(function(point) { + point.series = i; + }); + }); + + + //------------------------------------------------------------ + // HACK for negative value stacking + if (stacked) + data[0].values.map(function(d,i) { + var posBase = 0, negBase = 0; + data.map(function(d) { + var f = d.values[i] + f.size = Math.abs(f.y); + if (f.y<0) { + f.y1 = negBase; + negBase = negBase - f.size; + } else + { + f.y1 = f.size + posBase; + posBase = posBase + f.size; + } + }); + }); + + //------------------------------------------------------------ + // Setup Scales + + // remap and flatten the data for use in calculating the scales' domains + var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate + data.map(function(d) { + return d.values.map(function(d,i) { + return { x: getX(d,i), y: getY(d,i), y0: d.y0, y1: d.y1 } + }) + }); + + x .domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x })) + .rangeBands(xRange || [0, availableWidth], groupSpacing); + + //y .domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return d.y + (stacked ? d.y1 : 0) }).concat(forceY))) + y .domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return stacked ? (d.y > 0 ? d.y1 : d.y1 + d.y ) : d.y }).concat(forceY))) + .range(yRange || [availableHeight, 0]); + + // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point + if (x.domain()[0] === x.domain()[1]) + x.domain()[0] ? + x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01]) + : x.domain([-1,1]); + + if (y.domain()[0] === y.domain()[1]) + y.domain()[0] ? + y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01]) + : y.domain([-1,1]); + + + x0 = x0 || x; + y0 = y0 || y; + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-multibar').data([data]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multibar'); + var defsEnter = wrapEnter.append('defs'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g') + + gEnter.append('g').attr('class', 'nv-groups'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + //------------------------------------------------------------ + + + + defsEnter.append('clipPath') + .attr('id', 'nv-edge-clip-' + id) + .append('rect'); + wrap.select('#nv-edge-clip-' + id + ' rect') + .attr('width', availableWidth) + .attr('height', availableHeight); + + g .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : ''); + + + + var groups = wrap.select('.nv-groups').selectAll('.nv-group') + .data(function(d) { return d }, function(d,i) { return i }); + groups.enter().append('g') + .style('stroke-opacity', 1e-6) + .style('fill-opacity', 1e-6); + groups.exit() + .transition() + .selectAll('rect.nv-bar') + .delay(function(d,i) { + return i * delay/ data[0].values.length; + }) + .attr('y', function(d) { return stacked ? y0(d.y0) : y0(0) }) + .attr('height', 0) + .remove(); + groups + .attr('class', function(d,i) { return 'nv-group nv-series-' + i }) + .classed('hover', function(d) { return d.hover }) + .style('fill', function(d,i){ return color(d, i) }) + .style('stroke', function(d,i){ return color(d, i) }); + groups + .transition() + .style('stroke-opacity', 1) + .style('fill-opacity', .75); + + + var bars = groups.selectAll('rect.nv-bar') + .data(function(d) { return (hideable && !data.length) ? hideable.values : d.values }); + + bars.exit().remove(); + + + var barsEnter = bars.enter().append('rect') + .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'}) + .attr('x', function(d,i,j) { + return stacked ? 0 : (j * x.rangeBand() / data.length ) + }) + .attr('y', function(d) { return y0(stacked ? d.y0 : 0) }) + .attr('height', 0) + .attr('width', x.rangeBand() / (stacked ? 1 : data.length) ) + .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',0)'; }) + ; + bars + .style('fill', function(d,i,j){ return color(d, j, i); }) + .style('stroke', function(d,i,j){ return color(d, j, i); }) + .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here + d3.select(this).classed('hover', true); + dispatch.elementMouseover({ + value: getY(d,i), + point: d, + series: data[d.series], + pos: [x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length), y(getY(d,i) + (stacked ? d.y0 : 0))], // TODO: Figure out why the value appears to be shifted + pointIndex: i, + seriesIndex: d.series, + e: d3.event + }); + }) + .on('mouseout', function(d,i) { + d3.select(this).classed('hover', false); + dispatch.elementMouseout({ + value: getY(d,i), + point: d, + series: data[d.series], + pointIndex: i, + seriesIndex: d.series, + e: d3.event + }); + }) + .on('click', function(d,i) { + dispatch.elementClick({ + value: getY(d,i), + point: d, + series: data[d.series], + pos: [x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length), y(getY(d,i) + (stacked ? d.y0 : 0))], // TODO: Figure out why the value appears to be shifted + pointIndex: i, + seriesIndex: d.series, + e: d3.event + }); + d3.event.stopPropagation(); + }) + .on('dblclick', function(d,i) { + dispatch.elementDblClick({ + value: getY(d,i), + point: d, + series: data[d.series], + pos: [x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length), y(getY(d,i) + (stacked ? d.y0 : 0))], // TODO: Figure out why the value appears to be shifted + pointIndex: i, + seriesIndex: d.series, + e: d3.event + }); + d3.event.stopPropagation(); + }); + bars + .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'}) + .transition() + .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',0)'; }) + + if (barColor) { + if (!disabled) disabled = data.map(function() { return true }); + bars + .style('fill', function(d,i,j) { return d3.rgb(barColor(d,i)).darker( disabled.map(function(d,i) { return i }).filter(function(d,i){ return !disabled[i] })[j] ).toString(); }) + .style('stroke', function(d,i,j) { return d3.rgb(barColor(d,i)).darker( disabled.map(function(d,i) { return i }).filter(function(d,i){ return !disabled[i] })[j] ).toString(); }); + } + + + if (stacked) + bars.transition() + .delay(function(d,i) { + + return i * delay / data[0].values.length; + }) + .attr('y', function(d,i) { + + return y((stacked ? d.y1 : 0)); + }) + .attr('height', function(d,i) { + return Math.max(Math.abs(y(d.y + (stacked ? d.y0 : 0)) - y((stacked ? d.y0 : 0))),1); + }) + .attr('x', function(d,i) { + return stacked ? 0 : (d.series * x.rangeBand() / data.length ) + }) + .attr('width', x.rangeBand() / (stacked ? 1 : data.length) ); + else + bars.transition() + .delay(function(d,i) { + return i * delay/ data[0].values.length; + }) + .attr('x', function(d,i) { + return d.series * x.rangeBand() / data.length + }) + .attr('width', x.rangeBand() / data.length) + .attr('y', function(d,i) { + return getY(d,i) < 0 ? + y(0) : + y(0) - y(getY(d,i)) < 1 ? + y(0) - 1 : + y(getY(d,i)) || 0; + }) + .attr('height', function(d,i) { + return Math.max(Math.abs(y(getY(d,i)) - y(0)),1) || 0; + }); + + + + //store old scales for use in transitions on update + x0 = x.copy(); + y0 = y.copy(); + + }); + + return chart; + } + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + chart.dispatch = dispatch; + + chart.options = nv.utils.optionsFunc.bind(chart); + + chart.x = function(_) { + if (!arguments.length) return getX; + getX = _; + return chart; + }; + + chart.y = function(_) { + if (!arguments.length) return getY; + getY = _; + return chart; + }; + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.xScale = function(_) { + if (!arguments.length) return x; + x = _; + return chart; + }; + + chart.yScale = function(_) { + if (!arguments.length) return y; + y = _; + return chart; + }; + + chart.xDomain = function(_) { + if (!arguments.length) return xDomain; + xDomain = _; + return chart; + }; + + chart.yDomain = function(_) { + if (!arguments.length) return yDomain; + yDomain = _; + return chart; + }; + + chart.xRange = function(_) { + if (!arguments.length) return xRange; + xRange = _; + return chart; + }; + + chart.yRange = function(_) { + if (!arguments.length) return yRange; + yRange = _; + return chart; + }; + + chart.forceY = function(_) { + if (!arguments.length) return forceY; + forceY = _; + return chart; + }; + + chart.stacked = function(_) { + if (!arguments.length) return stacked; + stacked = _; + return chart; + }; + + chart.stackOffset = function(_) { + if (!arguments.length) return stackOffset; + stackOffset = _; + return chart; + }; + + chart.clipEdge = function(_) { + if (!arguments.length) return clipEdge; + clipEdge = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + return chart; + }; + + chart.barColor = function(_) { + if (!arguments.length) return barColor; + barColor = nv.utils.getColor(_); + return chart; + }; + + chart.disabled = function(_) { + if (!arguments.length) return disabled; + disabled = _; + return chart; + }; + + chart.id = function(_) { + if (!arguments.length) return id; + id = _; + return chart; + }; + + chart.hideable = function(_) { + if (!arguments.length) return hideable; + hideable = _; + return chart; + }; + + chart.delay = function(_) { + if (!arguments.length) return delay; + delay = _; + return chart; + }; + + chart.groupSpacing = function(_) { + if (!arguments.length) return groupSpacing; + groupSpacing = _; + return chart; + }; + + //============================================================ + + + return chart; +} + +nv.models.multiBarChart = function() { + "use strict"; + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var multibar = nv.models.multiBar() + , xAxis = nv.models.axis() + , yAxis = nv.models.axis() + , legend = nv.models.legend() + , controls = nv.models.legend() + ; + + var margin = {top: 30, right: 20, bottom: 50, left: 60} + , width = null + , height = null + , color = nv.utils.defaultColor() + , showControls = true + , showLegend = true + , showXAxis = true + , showYAxis = true + , rightAlignYAxis = false + , reduceXTicks = true // if false a tick will show for every data point + , staggerLabels = false + , rotateLabels = 0 + , tooltips = true + , tooltip = function(key, x, y, e, graph) { + return '<h3>' + key + '</h3>' + + '<p>' + y + ' on ' + x + '</p>' + } + , x //can be accessed via chart.xScale() + , y //can be accessed via chart.yScale() + , state = { stacked: false } + , defaultState = null + , noData = "No Data Available." + , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState') + , controlWidth = function() { return showControls ? 180 : 0 } + , transitionDuration = 250 + ; + + multibar + .stacked(false) + ; + xAxis + .orient('bottom') + .tickPadding(7) + .highlightZero(true) + .showMaxMin(false) + .tickFormat(function(d) { return d }) + ; + yAxis + .orient((rightAlignYAxis) ? 'right' : 'left') + .tickFormat(d3.format(',.1f')) + ; + + controls.updateState(false); + //============================================================ + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var showTooltip = function(e, offsetElement) { + var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ), + top = e.pos[1] + ( offsetElement.offsetTop || 0), + x = xAxis.tickFormat()(multibar.x()(e.point, e.pointIndex)), + y = yAxis.tickFormat()(multibar.y()(e.point, e.pointIndex)), + content = tooltip(e.series.key, x, y, e, chart); + + nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement); + }; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var container = d3.select(this), + that = this; + + var availableWidth = (width || parseInt(container.style('width')) || 960) + - margin.left - margin.right, + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + + chart.update = function() { container.transition().duration(transitionDuration).call(chart) }; + chart.container = this; + + //set state.disabled + state.disabled = data.map(function(d) { return !!d.disabled }); + + if (!defaultState) { + var key; + defaultState = {}; + for (key in state) { + if (state[key] instanceof Array) + defaultState[key] = state[key].slice(0); + else + defaultState[key] = state[key]; + } + } + //------------------------------------------------------------ + // Display noData message if there's nothing to show. + + if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { + var noDataText = container.selectAll('.nv-noData').data([noData]); + + noDataText.enter().append('text') + .attr('class', 'nvd3 nv-noData') + .attr('dy', '-.7em') + .style('text-anchor', 'middle'); + + noDataText + .attr('x', margin.left + availableWidth / 2) + .attr('y', margin.top + availableHeight / 2) + .text(function(d) { return d }); + + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup Scales + + x = multibar.xScale(); + y = multibar.yScale(); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-multiBarWithLegend').data([data]); + var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multiBarWithLegend').append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-x nv-axis'); + gEnter.append('g').attr('class', 'nv-y nv-axis'); + gEnter.append('g').attr('class', 'nv-barsWrap'); + gEnter.append('g').attr('class', 'nv-legendWrap'); + gEnter.append('g').attr('class', 'nv-controlsWrap'); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Legend + + if (showLegend) { + legend.width(availableWidth - controlWidth()); + + if (multibar.barColor()) + data.forEach(function(series,i) { + series.color = d3.rgb('#ccc').darker(i * 1.5).toString(); + }) + + g.select('.nv-legendWrap') + .datum(data) + .call(legend); + + if ( margin.top != legend.height()) { + margin.top = legend.height(); + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + } + + g.select('.nv-legendWrap') + .attr('transform', 'translate(' + controlWidth() + ',' + (-margin.top) +')'); + } + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Controls + + if (showControls) { + var controlsData = [ + { key: 'Grouped', disabled: multibar.stacked() }, + { key: 'Stacked', disabled: !multibar.stacked() } + ]; + + controls.width(controlWidth()).color(['#444', '#444', '#444']); + g.select('.nv-controlsWrap') + .datum(controlsData) + .attr('transform', 'translate(0,' + (-margin.top) +')') + .call(controls); + } + + //------------------------------------------------------------ + + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + if (rightAlignYAxis) { + g.select(".nv-y.nv-axis") + .attr("transform", "translate(" + availableWidth + ",0)"); + } + + //------------------------------------------------------------ + // Main Chart Component(s) + + multibar + .disabled(data.map(function(series) { return series.disabled })) + .width(availableWidth) + .height(availableHeight) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled })) + + + var barsWrap = g.select('.nv-barsWrap') + .datum(data.filter(function(d) { return !d.disabled })) + + barsWrap.transition().call(multibar); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup Axes + + if (showXAxis) { + xAxis + .scale(x) + .ticks( availableWidth / 100 ) + .tickSize(-availableHeight, 0); + + g.select('.nv-x.nv-axis') + .attr('transform', 'translate(0,' + y.range()[0] + ')'); + g.select('.nv-x.nv-axis').transition() + .call(xAxis); + + var xTicks = g.select('.nv-x.nv-axis > g').selectAll('g'); + + xTicks + .selectAll('line, text') + .style('opacity', 1) + + if (staggerLabels) { + var getTranslate = function(x,y) { + return "translate(" + x + "," + y + ")"; + }; + + var staggerUp = 5, staggerDown = 17; //pixels to stagger by + // Issue #140 + xTicks + .selectAll("text") + .attr('transform', function(d,i,j) { + return getTranslate(0, (j % 2 == 0 ? staggerUp : staggerDown)); + }); + + var totalInBetweenTicks = d3.selectAll(".nv-x.nv-axis .nv-wrap g g text")[0].length; + g.selectAll(".nv-x.nv-axis .nv-axisMaxMin text") + .attr("transform", function(d,i) { + return getTranslate(0, (i === 0 || totalInBetweenTicks % 2 !== 0) ? staggerDown : staggerUp); + }); + } + + if (reduceXTicks) + xTicks + .filter(function(d,i) { + return i % Math.ceil(data[0].values.length / (availableWidth / 100)) !== 0; + }) + .selectAll('text, line') + .style('opacity', 0); + + if(rotateLabels) + xTicks + .selectAll('.tick text') + .attr('transform', 'rotate(' + rotateLabels + ' 0,0)') + .style('text-anchor', rotateLabels > 0 ? 'start' : 'end'); + + g.select('.nv-x.nv-axis').selectAll('g.nv-axisMaxMin text') + .style('opacity', 1); + } + + + if (showYAxis) { + yAxis + .scale(y) + .ticks( availableHeight / 36 ) + .tickSize( -availableWidth, 0); + + g.select('.nv-y.nv-axis').transition() + .call(yAxis); + } + + + //------------------------------------------------------------ + + + + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ + + legend.dispatch.on('stateChange', function(newState) { + state = newState; + dispatch.stateChange(state); + chart.update(); + }); + + controls.dispatch.on('legendClick', function(d,i) { + if (!d.disabled) return; + controlsData = controlsData.map(function(s) { + s.disabled = true; + return s; + }); + d.disabled = false; + + switch (d.key) { + case 'Grouped': + multibar.stacked(false); + break; + case 'Stacked': + multibar.stacked(true); + break; + } + + state.stacked = multibar.stacked(); + dispatch.stateChange(state); + + chart.update(); + }); + + dispatch.on('tooltipShow', function(e) { + if (tooltips) showTooltip(e, that.parentNode) + }); + + // Update chart from a state object passed to event handler + dispatch.on('changeState', function(e) { + + if (typeof e.disabled !== 'undefined') { + data.forEach(function(series,i) { + series.disabled = e.disabled[i]; + }); + + state.disabled = e.disabled; + } + + if (typeof e.stacked !== 'undefined') { + multibar.stacked(e.stacked); + state.stacked = e.stacked; + } + + chart.update(); + }); + + //============================================================ + + + }); + + return chart; + } + + + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ + + multibar.dispatch.on('elementMouseover.tooltip', function(e) { + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); + + multibar.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + dispatch.on('tooltipHide', function() { + if (tooltips) nv.tooltip.cleanup(); + }); + + //============================================================ + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + // expose chart's sub-components + chart.dispatch = dispatch; + chart.multibar = multibar; + chart.legend = legend; + chart.xAxis = xAxis; + chart.yAxis = yAxis; + + d3.rebind(chart, multibar, 'x', 'y', 'xDomain', 'yDomain', 'xRange', 'yRange', 'forceX', 'forceY', 'clipEdge', + 'id', 'stacked', 'stackOffset', 'delay', 'barColor','groupSpacing'); + + chart.options = nv.utils.optionsFunc.bind(chart); + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + legend.color(color); + return chart; + }; + + chart.showControls = function(_) { + if (!arguments.length) return showControls; + showControls = _; + return chart; + }; + + chart.showLegend = function(_) { + if (!arguments.length) return showLegend; + showLegend = _; + return chart; + }; + + chart.showXAxis = function(_) { + if (!arguments.length) return showXAxis; + showXAxis = _; + return chart; + }; + + chart.showYAxis = function(_) { + if (!arguments.length) return showYAxis; + showYAxis = _; + return chart; + }; + + chart.rightAlignYAxis = function(_) { + if(!arguments.length) return rightAlignYAxis; + rightAlignYAxis = _; + yAxis.orient( (_) ? 'right' : 'left'); + return chart; + }; + + chart.reduceXTicks= function(_) { + if (!arguments.length) return reduceXTicks; + reduceXTicks = _; + return chart; + }; + + chart.rotateLabels = function(_) { + if (!arguments.length) return rotateLabels; + rotateLabels = _; + return chart; + } + + chart.staggerLabels = function(_) { + if (!arguments.length) return staggerLabels; + staggerLabels = _; + return chart; + }; + + chart.tooltip = function(_) { + if (!arguments.length) return tooltip; + tooltip = _; + return chart; + }; + + chart.tooltips = function(_) { + if (!arguments.length) return tooltips; + tooltips = _; + return chart; + }; + + chart.tooltipContent = function(_) { + if (!arguments.length) return tooltip; + tooltip = _; + return chart; + }; + + chart.state = function(_) { + if (!arguments.length) return state; + state = _; + return chart; + }; + + chart.defaultState = function(_) { + if (!arguments.length) return defaultState; + defaultState = _; + return chart; + }; + + chart.noData = function(_) { + if (!arguments.length) return noData; + noData = _; + return chart; + }; + + chart.transitionDuration = function(_) { + if (!arguments.length) return transitionDuration; + transitionDuration = _; + return chart; + }; + + //============================================================ + + + return chart; +} + +nv.models.multiBarHorizontal = function() { + "use strict"; + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var margin = {top: 0, right: 0, bottom: 0, left: 0} + , width = 960 + , height = 500 + , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one + , x = d3.scale.ordinal() + , y = d3.scale.linear() + , getX = function(d) { return d.x } + , getY = function(d) { return d.y } + , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove + , color = nv.utils.defaultColor() + , barColor = null // adding the ability to set the color for each rather than the whole group + , disabled // used in conjunction with barColor to communicate from multiBarHorizontalChart what series are disabled + , stacked = false + , showValues = false + , valuePadding = 60 + , valueFormat = d3.format(',.2f') + , delay = 1200 + , xDomain + , yDomain + , xRange + , yRange + , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout') + ; + + //============================================================ + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var x0, y0 //used to store previous scales + ; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var availableWidth = width - margin.left - margin.right, + availableHeight = height - margin.top - margin.bottom, + container = d3.select(this); + + + if (stacked) + data = d3.layout.stack() + .offset('zero') + .values(function(d){ return d.values }) + .y(getY) + (data); + + + //add series index to each data point for reference + data.forEach(function(series, i) { + series.values.forEach(function(point) { + point.series = i; + }); + }); + + + + //------------------------------------------------------------ + // HACK for negative value stacking + if (stacked) + data[0].values.map(function(d,i) { + var posBase = 0, negBase = 0; + data.map(function(d) { + var f = d.values[i] + f.size = Math.abs(f.y); + if (f.y<0) { + f.y1 = negBase - f.size; + negBase = negBase - f.size; + } else + { + f.y1 = posBase; + posBase = posBase + f.size; + } + }); + }); + + + + //------------------------------------------------------------ + // Setup Scales + + // remap and flatten the data for use in calculating the scales' domains + var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate + data.map(function(d) { + return d.values.map(function(d,i) { + return { x: getX(d,i), y: getY(d,i), y0: d.y0, y1: d.y1 } + }) + }); + + x .domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x })) + .rangeBands(xRange || [0, availableHeight], .1); + + //y .domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return d.y + (stacked ? d.y0 : 0) }).concat(forceY))) + y .domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return stacked ? (d.y > 0 ? d.y1 + d.y : d.y1 ) : d.y }).concat(forceY))) + + if (showValues && !stacked) + y.range(yRange || [(y.domain()[0] < 0 ? valuePadding : 0), availableWidth - (y.domain()[1] > 0 ? valuePadding : 0) ]); + else + y.range(yRange || [0, availableWidth]); + + x0 = x0 || x; + y0 = y0 || d3.scale.linear().domain(y.domain()).range([y(0),y(0)]); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = d3.select(this).selectAll('g.nv-wrap.nv-multibarHorizontal').data([data]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multibarHorizontal'); + var defsEnter = wrapEnter.append('defs'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-groups'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + //------------------------------------------------------------ + + + + var groups = wrap.select('.nv-groups').selectAll('.nv-group') + .data(function(d) { return d }, function(d,i) { return i }); + groups.enter().append('g') + .style('stroke-opacity', 1e-6) + .style('fill-opacity', 1e-6); + groups.exit().transition() + .style('stroke-opacity', 1e-6) + .style('fill-opacity', 1e-6) + .remove(); + groups + .attr('class', function(d,i) { return 'nv-group nv-series-' + i }) + .classed('hover', function(d) { return d.hover }) + .style('fill', function(d,i){ return color(d, i) }) + .style('stroke', function(d,i){ return color(d, i) }); + groups.transition() + .style('stroke-opacity', 1) + .style('fill-opacity', .75); + + + var bars = groups.selectAll('g.nv-bar') + .data(function(d) { return d.values }); + + bars.exit().remove(); + + + var barsEnter = bars.enter().append('g') + .attr('transform', function(d,i,j) { + return 'translate(' + y0(stacked ? d.y0 : 0) + ',' + (stacked ? 0 : (j * x.rangeBand() / data.length ) + x(getX(d,i))) + ')' + }); + + barsEnter.append('rect') + .attr('width', 0) + .attr('height', x.rangeBand() / (stacked ? 1 : data.length) ) + + bars + .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here + d3.select(this).classed('hover', true); + dispatch.elementMouseover({ + value: getY(d,i), + point: d, + series: data[d.series], + pos: [ y(getY(d,i) + (stacked ? d.y0 : 0)), x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length) ], + pointIndex: i, + seriesIndex: d.series, + e: d3.event + }); + }) + .on('mouseout', function(d,i) { + d3.select(this).classed('hover', false); + dispatch.elementMouseout({ + value: getY(d,i), + point: d, + series: data[d.series], + pointIndex: i, + seriesIndex: d.series, + e: d3.event + }); + }) + .on('click', function(d,i) { + dispatch.elementClick({ + value: getY(d,i), + point: d, + series: data[d.series], + pos: [x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length), y(getY(d,i) + (stacked ? d.y0 : 0))], // TODO: Figure out why the value appears to be shifted + pointIndex: i, + seriesIndex: d.series, + e: d3.event + }); + d3.event.stopPropagation(); + }) + .on('dblclick', function(d,i) { + dispatch.elementDblClick({ + value: getY(d,i), + point: d, + series: data[d.series], + pos: [x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length), y(getY(d,i) + (stacked ? d.y0 : 0))], // TODO: Figure out why the value appears to be shifted + pointIndex: i, + seriesIndex: d.series, + e: d3.event + }); + d3.event.stopPropagation(); + }); + + + barsEnter.append('text'); + + if (showValues && !stacked) { + bars.select('text') + .attr('text-anchor', function(d,i) { return getY(d,i) < 0 ? 'end' : 'start' }) + .attr('y', x.rangeBand() / (data.length * 2)) + .attr('dy', '.32em') + .text(function(d,i) { return valueFormat(getY(d,i)) }) + bars.transition() + .select('text') + .attr('x', function(d,i) { return getY(d,i) < 0 ? -4 : y(getY(d,i)) - y(0) + 4 }) + } else { + bars.selectAll('text').text(''); + } + + bars + .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'}) + + if (barColor) { + if (!disabled) disabled = data.map(function() { return true }); + bars + .style('fill', function(d,i,j) { return d3.rgb(barColor(d,i)).darker( disabled.map(function(d,i) { return i }).filter(function(d,i){ return !disabled[i] })[j] ).toString(); }) + .style('stroke', function(d,i,j) { return d3.rgb(barColor(d,i)).darker( disabled.map(function(d,i) { return i }).filter(function(d,i){ return !disabled[i] })[j] ).toString(); }); + } + + if (stacked) + bars.transition() + .attr('transform', function(d,i) { + return 'translate(' + y(d.y1) + ',' + x(getX(d,i)) + ')' + }) + .select('rect') + .attr('width', function(d,i) { + return Math.abs(y(getY(d,i) + d.y0) - y(d.y0)) + }) + .attr('height', x.rangeBand() ); + else + bars.transition() + .attr('transform', function(d,i) { + //TODO: stacked must be all positive or all negative, not both? + return 'translate(' + + (getY(d,i) < 0 ? y(getY(d,i)) : y(0)) + + ',' + + (d.series * x.rangeBand() / data.length + + + x(getX(d,i)) ) + + ')' + }) + .select('rect') + .attr('height', x.rangeBand() / data.length ) + .attr('width', function(d,i) { + return Math.max(Math.abs(y(getY(d,i)) - y(0)),1) + }); + + + //store old scales for use in transitions on update + x0 = x.copy(); + y0 = y.copy(); + + }); + + return chart; + } + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + chart.dispatch = dispatch; + + chart.options = nv.utils.optionsFunc.bind(chart); + + chart.x = function(_) { + if (!arguments.length) return getX; + getX = _; + return chart; + }; + + chart.y = function(_) { + if (!arguments.length) return getY; + getY = _; + return chart; + }; + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.xScale = function(_) { + if (!arguments.length) return x; + x = _; + return chart; + }; + + chart.yScale = function(_) { + if (!arguments.length) return y; + y = _; + return chart; + }; + + chart.xDomain = function(_) { + if (!arguments.length) return xDomain; + xDomain = _; + return chart; + }; + + chart.yDomain = function(_) { + if (!arguments.length) return yDomain; + yDomain = _; + return chart; + }; + + chart.xRange = function(_) { + if (!arguments.length) return xRange; + xRange = _; + return chart; + }; + + chart.yRange = function(_) { + if (!arguments.length) return yRange; + yRange = _; + return chart; + }; + + chart.forceY = function(_) { + if (!arguments.length) return forceY; + forceY = _; + return chart; + }; + + chart.stacked = function(_) { + if (!arguments.length) return stacked; + stacked = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + return chart; + }; + + chart.barColor = function(_) { + if (!arguments.length) return barColor; + barColor = nv.utils.getColor(_); + return chart; + }; + + chart.disabled = function(_) { + if (!arguments.length) return disabled; + disabled = _; + return chart; + }; + + chart.id = function(_) { + if (!arguments.length) return id; + id = _; + return chart; + }; + + chart.delay = function(_) { + if (!arguments.length) return delay; + delay = _; + return chart; + }; + + chart.showValues = function(_) { + if (!arguments.length) return showValues; + showValues = _; + return chart; + }; + + chart.valueFormat= function(_) { + if (!arguments.length) return valueFormat; + valueFormat = _; + return chart; + }; + + chart.valuePadding = function(_) { + if (!arguments.length) return valuePadding; + valuePadding = _; + return chart; + }; + + //============================================================ + + + return chart; +} + +nv.models.multiBarHorizontalChart = function() { + "use strict"; + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var multibar = nv.models.multiBarHorizontal() + , xAxis = nv.models.axis() + , yAxis = nv.models.axis() + , legend = nv.models.legend().height(30) + , controls = nv.models.legend().height(30) + ; + + var margin = {top: 30, right: 20, bottom: 50, left: 60} + , width = null + , height = null + , color = nv.utils.defaultColor() + , showControls = true + , showLegend = true + , stacked = false + , tooltips = true + , tooltip = function(key, x, y, e, graph) { + return '<h3>' + key + ' - ' + x + '</h3>' + + '<p>' + y + '</p>' + } + , x //can be accessed via chart.xScale() + , y //can be accessed via chart.yScale() + , state = { stacked: stacked } + , defaultState = null + , noData = 'No Data Available.' + , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState') + , controlWidth = function() { return showControls ? 180 : 0 } + , transitionDuration = 250 + ; + + multibar + .stacked(stacked) + ; + xAxis + .orient('left') + .tickPadding(5) + .highlightZero(false) + .showMaxMin(false) + .tickFormat(function(d) { return d }) + ; + yAxis + .orient('bottom') + .tickFormat(d3.format(',.1f')) + ; + + controls.updateState(false); + //============================================================ + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var showTooltip = function(e, offsetElement) { + var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ), + top = e.pos[1] + ( offsetElement.offsetTop || 0), + x = xAxis.tickFormat()(multibar.x()(e.point, e.pointIndex)), + y = yAxis.tickFormat()(multibar.y()(e.point, e.pointIndex)), + content = tooltip(e.series.key, x, y, e, chart); + + nv.tooltip.show([left, top], content, e.value < 0 ? 'e' : 'w', null, offsetElement); + }; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var container = d3.select(this), + that = this; + + var availableWidth = (width || parseInt(container.style('width')) || 960) + - margin.left - margin.right, + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + + chart.update = function() { container.transition().duration(transitionDuration).call(chart) }; + chart.container = this; + + //set state.disabled + state.disabled = data.map(function(d) { return !!d.disabled }); + + if (!defaultState) { + var key; + defaultState = {}; + for (key in state) { + if (state[key] instanceof Array) + defaultState[key] = state[key].slice(0); + else + defaultState[key] = state[key]; + } + } + + //------------------------------------------------------------ + // Display No Data message if there's nothing to show. + + if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { + var noDataText = container.selectAll('.nv-noData').data([noData]); + + noDataText.enter().append('text') + .attr('class', 'nvd3 nv-noData') + .attr('dy', '-.7em') + .style('text-anchor', 'middle'); + + noDataText + .attr('x', margin.left + availableWidth / 2) + .attr('y', margin.top + availableHeight / 2) + .text(function(d) { return d }); + + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup Scales + + x = multibar.xScale(); + y = multibar.yScale(); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-multiBarHorizontalChart').data([data]); + var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multiBarHorizontalChart').append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-x nv-axis'); + gEnter.append('g').attr('class', 'nv-y nv-axis'); + gEnter.append('g').attr('class', 'nv-barsWrap'); + gEnter.append('g').attr('class', 'nv-legendWrap'); + gEnter.append('g').attr('class', 'nv-controlsWrap'); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Legend + + if (showLegend) { + legend.width(availableWidth - controlWidth()); + + if (multibar.barColor()) + data.forEach(function(series,i) { + series.color = d3.rgb('#ccc').darker(i * 1.5).toString(); + }) + + g.select('.nv-legendWrap') + .datum(data) + .call(legend); + + if ( margin.top != legend.height()) { + margin.top = legend.height(); + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + } + + g.select('.nv-legendWrap') + .attr('transform', 'translate(' + controlWidth() + ',' + (-margin.top) +')'); + } + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Controls + + if (showControls) { + var controlsData = [ + { key: 'Grouped', disabled: multibar.stacked() }, + { key: 'Stacked', disabled: !multibar.stacked() } + ]; + + controls.width(controlWidth()).color(['#444', '#444', '#444']); + g.select('.nv-controlsWrap') + .datum(controlsData) + .attr('transform', 'translate(0,' + (-margin.top) +')') + .call(controls); + } + + //------------------------------------------------------------ + + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + + //------------------------------------------------------------ + // Main Chart Component(s) + + multibar + .disabled(data.map(function(series) { return series.disabled })) + .width(availableWidth) + .height(availableHeight) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled })) + + + var barsWrap = g.select('.nv-barsWrap') + .datum(data.filter(function(d) { return !d.disabled })) + + barsWrap.transition().call(multibar); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup Axes + + xAxis + .scale(x) + .ticks( availableHeight / 24 ) + .tickSize(-availableWidth, 0); + + g.select('.nv-x.nv-axis').transition() + .call(xAxis); + + var xTicks = g.select('.nv-x.nv-axis').selectAll('g'); + + xTicks + .selectAll('line, text') + .style('opacity', 1) + + + yAxis + .scale(y) + .ticks( availableWidth / 100 ) + .tickSize( -availableHeight, 0); + + g.select('.nv-y.nv-axis') + .attr('transform', 'translate(0,' + availableHeight + ')'); + g.select('.nv-y.nv-axis').transition() + .call(yAxis); + + //------------------------------------------------------------ + + + + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ + + legend.dispatch.on('stateChange', function(newState) { + state = newState; + dispatch.stateChange(state); + chart.update(); + }); + + controls.dispatch.on('legendClick', function(d,i) { + if (!d.disabled) return; + controlsData = controlsData.map(function(s) { + s.disabled = true; + return s; + }); + d.disabled = false; + + switch (d.key) { + case 'Grouped': + multibar.stacked(false); + break; + case 'Stacked': + multibar.stacked(true); + break; + } + + state.stacked = multibar.stacked(); + dispatch.stateChange(state); + + chart.update(); + }); + + dispatch.on('tooltipShow', function(e) { + if (tooltips) showTooltip(e, that.parentNode); + }); + + // Update chart from a state object passed to event handler + dispatch.on('changeState', function(e) { + + if (typeof e.disabled !== 'undefined') { + data.forEach(function(series,i) { + series.disabled = e.disabled[i]; + }); + + state.disabled = e.disabled; + } + + if (typeof e.stacked !== 'undefined') { + multibar.stacked(e.stacked); + state.stacked = e.stacked; + } + + selection.call(chart); + }); + //============================================================ + + + }); + + return chart; + } + + + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ + + multibar.dispatch.on('elementMouseover.tooltip', function(e) { + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); + + multibar.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + dispatch.on('tooltipHide', function() { + if (tooltips) nv.tooltip.cleanup(); + }); + + //============================================================ + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + // expose chart's sub-components + chart.dispatch = dispatch; + chart.multibar = multibar; + chart.legend = legend; + chart.xAxis = xAxis; + chart.yAxis = yAxis; + + d3.rebind(chart, multibar, 'x', 'y', 'xDomain', 'yDomain', 'xRange', 'yRange', 'forceX', 'forceY', 'clipEdge', 'id', 'delay', 'showValues', 'valueFormat', 'stacked', 'barColor'); + + chart.options = nv.utils.optionsFunc.bind(chart); + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + legend.color(color); + return chart; + }; + + chart.showControls = function(_) { + if (!arguments.length) return showControls; + showControls = _; + return chart; + }; + + chart.showLegend = function(_) { + if (!arguments.length) return showLegend; + showLegend = _; + return chart; + }; + + chart.tooltip = function(_) { + if (!arguments.length) return tooltip; + tooltip = _; + return chart; + }; + + chart.tooltips = function(_) { + if (!arguments.length) return tooltips; + tooltips = _; + return chart; + }; + + chart.tooltipContent = function(_) { + if (!arguments.length) return tooltip; + tooltip = _; + return chart; + }; + + chart.state = function(_) { + if (!arguments.length) return state; + state = _; + return chart; + }; + + chart.defaultState = function(_) { + if (!arguments.length) return defaultState; + defaultState = _; + return chart; + }; + + chart.noData = function(_) { + if (!arguments.length) return noData; + noData = _; + return chart; + }; + + chart.transitionDuration = function(_) { + if (!arguments.length) return transitionDuration; + transitionDuration = _; + return chart; + }; + //============================================================ + + + return chart; +} +nv.models.multiChart = function() { + "use strict"; + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var margin = {top: 30, right: 20, bottom: 50, left: 60}, + color = d3.scale.category20().range(), + width = null, + height = null, + showLegend = true, + tooltips = true, + tooltip = function(key, x, y, e, graph) { + return '<h3>' + key + '</h3>' + + '<p>' + y + ' at ' + x + '</p>' + }, + x, + y, + yDomain1, + yDomain2 + ; //can be accessed via chart.lines.[x/y]Scale() + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var x = d3.scale.linear(), + yScale1 = d3.scale.linear(), + yScale2 = d3.scale.linear(), + + lines1 = nv.models.line().yScale(yScale1), + lines2 = nv.models.line().yScale(yScale2), + + bars1 = nv.models.multiBar().stacked(false).yScale(yScale1), + bars2 = nv.models.multiBar().stacked(false).yScale(yScale2), + + stack1 = nv.models.stackedArea().yScale(yScale1), + stack2 = nv.models.stackedArea().yScale(yScale2), + + xAxis = nv.models.axis().scale(x).orient('bottom').tickPadding(5), + yAxis1 = nv.models.axis().scale(yScale1).orient('left'), + yAxis2 = nv.models.axis().scale(yScale2).orient('right'), + + legend = nv.models.legend().height(30), + dispatch = d3.dispatch('tooltipShow', 'tooltipHide'); + + var showTooltip = function(e, offsetElement) { + var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ), + top = e.pos[1] + ( offsetElement.offsetTop || 0), + x = xAxis.tickFormat()(lines1.x()(e.point, e.pointIndex)), + y = ((e.series.yAxis == 2) ? yAxis2 : yAxis1).tickFormat()(lines1.y()(e.point, e.pointIndex)), + content = tooltip(e.series.key, x, y, e, chart); + + nv.tooltip.show([left, top], content, undefined, undefined, offsetElement.offsetParent); + }; + + function chart(selection) { + selection.each(function(data) { + var container = d3.select(this), + that = this; + + chart.update = function() { container.transition().call(chart); }; + chart.container = this; + + var availableWidth = (width || parseInt(container.style('width')) || 960) + - margin.left - margin.right, + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + + var dataLines1 = data.filter(function(d) {return !d.disabled && d.type == 'line' && d.yAxis == 1}) + var dataLines2 = data.filter(function(d) {return !d.disabled && d.type == 'line' && d.yAxis == 2}) + var dataBars1 = data.filter(function(d) {return !d.disabled && d.type == 'bar' && d.yAxis == 1}) + var dataBars2 = data.filter(function(d) {return !d.disabled && d.type == 'bar' && d.yAxis == 2}) + var dataStack1 = data.filter(function(d) {return !d.disabled && d.type == 'area' && d.yAxis == 1}) + var dataStack2 = data.filter(function(d) {return !d.disabled && d.type == 'area' && d.yAxis == 2}) + + var series1 = data.filter(function(d) {return !d.disabled && d.yAxis == 1}) + .map(function(d) { + return d.values.map(function(d,i) { + return { x: d.x, y: d.y } + }) + }) + + var series2 = data.filter(function(d) {return !d.disabled && d.yAxis == 2}) + .map(function(d) { + return d.values.map(function(d,i) { + return { x: d.x, y: d.y } + }) + }) + + x .domain(d3.extent(d3.merge(series1.concat(series2)), function(d) { return d.x } )) + .range([0, availableWidth]); + + var wrap = container.selectAll('g.wrap.multiChart').data([data]); + var gEnter = wrap.enter().append('g').attr('class', 'wrap nvd3 multiChart').append('g'); + + gEnter.append('g').attr('class', 'x axis'); + gEnter.append('g').attr('class', 'y1 axis'); + gEnter.append('g').attr('class', 'y2 axis'); + gEnter.append('g').attr('class', 'lines1Wrap'); + gEnter.append('g').attr('class', 'lines2Wrap'); + gEnter.append('g').attr('class', 'bars1Wrap'); + gEnter.append('g').attr('class', 'bars2Wrap'); + gEnter.append('g').attr('class', 'stack1Wrap'); + gEnter.append('g').attr('class', 'stack2Wrap'); + gEnter.append('g').attr('class', 'legendWrap'); + + var g = wrap.select('g'); + + if (showLegend) { + legend.width( availableWidth / 2 ); + + g.select('.legendWrap') + .datum(data.map(function(series) { + series.originalKey = series.originalKey === undefined ? series.key : series.originalKey; + series.key = series.originalKey + (series.yAxis == 1 ? '' : ' (right axis)'); + return series; + })) + .call(legend); + + if ( margin.top != legend.height()) { + margin.top = legend.height(); + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + } + + g.select('.legendWrap') + .attr('transform', 'translate(' + ( availableWidth / 2 ) + ',' + (-margin.top) +')'); + } + + + lines1 + .width(availableWidth) + .height(availableHeight) + .interpolate("monotone") + .color(data.map(function(d,i) { + return d.color || color[i % color.length]; + }).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'line'})); + + lines2 + .width(availableWidth) + .height(availableHeight) + .interpolate("monotone") + .color(data.map(function(d,i) { + return d.color || color[i % color.length]; + }).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'line'})); + + bars1 + .width(availableWidth) + .height(availableHeight) + .color(data.map(function(d,i) { + return d.color || color[i % color.length]; + }).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'bar'})); + + bars2 + .width(availableWidth) + .height(availableHeight) + .color(data.map(function(d,i) { + return d.color || color[i % color.length]; + }).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'bar'})); + + stack1 + .width(availableWidth) + .height(availableHeight) + .color(data.map(function(d,i) { + return d.color || color[i % color.length]; + }).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'area'})); + + stack2 + .width(availableWidth) + .height(availableHeight) + .color(data.map(function(d,i) { + return d.color || color[i % color.length]; + }).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'area'})); + + g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + + var lines1Wrap = g.select('.lines1Wrap') + .datum(dataLines1) + var bars1Wrap = g.select('.bars1Wrap') + .datum(dataBars1) + var stack1Wrap = g.select('.stack1Wrap') + .datum(dataStack1) + + var lines2Wrap = g.select('.lines2Wrap') + .datum(dataLines2) + var bars2Wrap = g.select('.bars2Wrap') + .datum(dataBars2) + var stack2Wrap = g.select('.stack2Wrap') + .datum(dataStack2) + + var extraValue1 = dataStack1.length ? dataStack1.map(function(a){return a.values}).reduce(function(a,b){ + return a.map(function(aVal,i){return {x: aVal.x, y: aVal.y + b[i].y}}) + }).concat([{x:0, y:0}]) : [] + var extraValue2 = dataStack2.length ? dataStack2.map(function(a){return a.values}).reduce(function(a,b){ + return a.map(function(aVal,i){return {x: aVal.x, y: aVal.y + b[i].y}}) + }).concat([{x:0, y:0}]) : [] + + yScale1 .domain(yDomain1 || d3.extent(d3.merge(series1).concat(extraValue1), function(d) { return d.y } )) + .range([0, availableHeight]) + + yScale2 .domain(yDomain2 || d3.extent(d3.merge(series2).concat(extraValue2), function(d) { return d.y } )) + .range([0, availableHeight]) + + lines1.yDomain(yScale1.domain()) + bars1.yDomain(yScale1.domain()) + stack1.yDomain(yScale1.domain()) + + lines2.yDomain(yScale2.domain()) + bars2.yDomain(yScale2.domain()) + stack2.yDomain(yScale2.domain()) + + if(dataStack1.length){d3.transition(stack1Wrap).call(stack1);} + if(dataStack2.length){d3.transition(stack2Wrap).call(stack2);} + + if(dataBars1.length){d3.transition(bars1Wrap).call(bars1);} + if(dataBars2.length){d3.transition(bars2Wrap).call(bars2);} + + if(dataLines1.length){d3.transition(lines1Wrap).call(lines1);} + if(dataLines2.length){d3.transition(lines2Wrap).call(lines2);} + + + + xAxis + .ticks( availableWidth / 100 ) + .tickSize(-availableHeight, 0); + + g.select('.x.axis') + .attr('transform', 'translate(0,' + availableHeight + ')'); + d3.transition(g.select('.x.axis')) + .call(xAxis); + + yAxis1 + .ticks( availableHeight / 36 ) + .tickSize( -availableWidth, 0); + + + d3.transition(g.select('.y1.axis')) + .call(yAxis1); + + yAxis2 + .ticks( availableHeight / 36 ) + .tickSize( -availableWidth, 0); + + d3.transition(g.select('.y2.axis')) + .call(yAxis2); + + g.select('.y2.axis') + .style('opacity', series2.length ? 1 : 0) + .attr('transform', 'translate(' + x.range()[1] + ',0)'); + + legend.dispatch.on('stateChange', function(newState) { + chart.update(); + }); + + dispatch.on('tooltipShow', function(e) { + if (tooltips) showTooltip(e, that.parentNode); + }); + + }); + + return chart; + } + + + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ + + lines1.dispatch.on('elementMouseover.tooltip', function(e) { + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); + + lines1.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + + lines2.dispatch.on('elementMouseover.tooltip', function(e) { + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); + + lines2.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + + bars1.dispatch.on('elementMouseover.tooltip', function(e) { + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); + + bars1.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + + bars2.dispatch.on('elementMouseover.tooltip', function(e) { + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); + + bars2.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + + stack1.dispatch.on('tooltipShow', function(e) { + //disable tooltips when value ~= 0 + //// TODO: consider removing points from voronoi that have 0 value instead of this hack + if (!Math.round(stack1.y()(e.point) * 100)) { // 100 will not be good for very small numbers... will have to think about making this valu dynamic, based on data range + setTimeout(function() { d3.selectAll('.point.hover').classed('hover', false) }, 0); + return false; + } + + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top], + dispatch.tooltipShow(e); + }); + + stack1.dispatch.on('tooltipHide', function(e) { + dispatch.tooltipHide(e); + }); + + stack2.dispatch.on('tooltipShow', function(e) { + //disable tooltips when value ~= 0 + //// TODO: consider removing points from voronoi that have 0 value instead of this hack + if (!Math.round(stack2.y()(e.point) * 100)) { // 100 will not be good for very small numbers... will have to think about making this valu dynamic, based on data range + setTimeout(function() { d3.selectAll('.point.hover').classed('hover', false) }, 0); + return false; + } + + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top], + dispatch.tooltipShow(e); + }); + + stack2.dispatch.on('tooltipHide', function(e) { + dispatch.tooltipHide(e); + }); + + lines1.dispatch.on('elementMouseover.tooltip', function(e) { + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); + + lines1.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + + lines2.dispatch.on('elementMouseover.tooltip', function(e) { + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); + + lines2.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + + dispatch.on('tooltipHide', function() { + if (tooltips) nv.tooltip.cleanup(); + }); + + + + //============================================================ + // Global getters and setters + //------------------------------------------------------------ + + chart.dispatch = dispatch; + chart.lines1 = lines1; + chart.lines2 = lines2; + chart.bars1 = bars1; + chart.bars2 = bars2; + chart.stack1 = stack1; + chart.stack2 = stack2; + chart.xAxis = xAxis; + chart.yAxis1 = yAxis1; + chart.yAxis2 = yAxis2; + chart.options = nv.utils.optionsFunc.bind(chart); + + chart.x = function(_) { + if (!arguments.length) return getX; + getX = _; + lines1.x(_); + bars1.x(_); + return chart; + }; + + chart.y = function(_) { + if (!arguments.length) return getY; + getY = _; + lines1.y(_); + bars1.y(_); + return chart; + }; + + chart.yDomain1 = function(_) { + if (!arguments.length) return yDomain1; + yDomain1 = _; + return chart; + }; + + chart.yDomain2 = function(_) { + if (!arguments.length) return yDomain2; + yDomain2 = _; + return chart; + }; + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin = _; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = _; + legend.color(_); + return chart; + }; + + chart.showLegend = function(_) { + if (!arguments.length) return showLegend; + showLegend = _; + return chart; + }; + + chart.tooltips = function(_) { + if (!arguments.length) return tooltips; + tooltips = _; + return chart; + }; + + chart.tooltipContent = function(_) { + if (!arguments.length) return tooltip; + tooltip = _; + return chart; + }; + + return chart; +} + + +nv.models.ohlcBar = function() { + "use strict"; + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var margin = {top: 0, right: 0, bottom: 0, left: 0} + , width = 960 + , height = 500 + , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one + , x = d3.scale.linear() + , y = d3.scale.linear() + , getX = function(d) { return d.x } + , getY = function(d) { return d.y } + , getOpen = function(d) { return d.open } + , getClose = function(d) { return d.close } + , getHigh = function(d) { return d.high } + , getLow = function(d) { return d.low } + , forceX = [] + , forceY = [] + , padData = false // If true, adds half a data points width to front and back, for lining up a line chart with a bar chart + , clipEdge = true + , color = nv.utils.defaultColor() + , xDomain + , yDomain + , xRange + , yRange + , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout') + ; + + //============================================================ + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + //TODO: store old scales for transitions + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var availableWidth = width - margin.left - margin.right, + availableHeight = height - margin.top - margin.bottom, + container = d3.select(this); + + + //------------------------------------------------------------ + // Setup Scales + + x .domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) )); + + if (padData) + x.range(xRange || [availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]); + else + x.range(xRange || [0, availableWidth]); + + y .domain(yDomain || [ + d3.min(data[0].values.map(getLow).concat(forceY)), + d3.max(data[0].values.map(getHigh).concat(forceY)) + ]) + .range(yRange || [availableHeight, 0]); + + // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point + if (x.domain()[0] === x.domain()[1]) + x.domain()[0] ? + x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01]) + : x.domain([-1,1]); + + if (y.domain()[0] === y.domain()[1]) + y.domain()[0] ? + y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01]) + : y.domain([-1,1]); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = d3.select(this).selectAll('g.nv-wrap.nv-ohlcBar').data([data[0].values]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-ohlcBar'); + var defsEnter = wrapEnter.append('defs'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-ticks'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + //------------------------------------------------------------ + + + container + .on('click', function(d,i) { + dispatch.chartClick({ + data: d, + index: i, + pos: d3.event, + id: id + }); + }); + + + defsEnter.append('clipPath') + .attr('id', 'nv-chart-clip-path-' + id) + .append('rect'); + + wrap.select('#nv-chart-clip-path-' + id + ' rect') + .attr('width', availableWidth) + .attr('height', availableHeight); + + g .attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : ''); + + + + var ticks = wrap.select('.nv-ticks').selectAll('.nv-tick') + .data(function(d) { return d }); + + ticks.exit().remove(); + + + var ticksEnter = ticks.enter().append('path') + .attr('class', function(d,i,j) { return (getOpen(d,i) > getClose(d,i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i }) + .attr('d', function(d,i) { + var w = (availableWidth / data[0].values.length) * .9; + return 'm0,0l0,' + + (y(getOpen(d,i)) + - y(getHigh(d,i))) + + 'l' + + (-w/2) + + ',0l' + + (w/2) + + ',0l0,' + + (y(getLow(d,i)) - y(getOpen(d,i))) + + 'l0,' + + (y(getClose(d,i)) + - y(getLow(d,i))) + + 'l' + + (w/2) + + ',0l' + + (-w/2) + + ',0z'; + }) + .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',' + y(getHigh(d,i)) + ')'; }) + //.attr('fill', function(d,i) { return color[0]; }) + //.attr('stroke', function(d,i) { return color[0]; }) + //.attr('x', 0 ) + //.attr('y', function(d,i) { return y(Math.max(0, getY(d,i))) }) + //.attr('height', function(d,i) { return Math.abs(y(getY(d,i)) - y(0)) }) + .on('mouseover', function(d,i) { + d3.select(this).classed('hover', true); + dispatch.elementMouseover({ + point: d, + series: data[0], + pos: [x(getX(d,i)), y(getY(d,i))], // TODO: Figure out why the value appears to be shifted + pointIndex: i, + seriesIndex: 0, + e: d3.event + }); + + }) + .on('mouseout', function(d,i) { + d3.select(this).classed('hover', false); + dispatch.elementMouseout({ + point: d, + series: data[0], + pointIndex: i, + seriesIndex: 0, + e: d3.event + }); + }) + .on('click', function(d,i) { + dispatch.elementClick({ + //label: d[label], + value: getY(d,i), + data: d, + index: i, + pos: [x(getX(d,i)), y(getY(d,i))], + e: d3.event, + id: id + }); + d3.event.stopPropagation(); + }) + .on('dblclick', function(d,i) { + dispatch.elementDblClick({ + //label: d[label], + value: getY(d,i), + data: d, + index: i, + pos: [x(getX(d,i)), y(getY(d,i))], + e: d3.event, + id: id + }); + d3.event.stopPropagation(); + }); + + ticks + .attr('class', function(d,i,j) { return (getOpen(d,i) > getClose(d,i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i }) + d3.transition(ticks) + .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',' + y(getHigh(d,i)) + ')'; }) + .attr('d', function(d,i) { + var w = (availableWidth / data[0].values.length) * .9; + return 'm0,0l0,' + + (y(getOpen(d,i)) + - y(getHigh(d,i))) + + 'l' + + (-w/2) + + ',0l' + + (w/2) + + ',0l0,' + + (y(getLow(d,i)) + - y(getOpen(d,i))) + + 'l0,' + + (y(getClose(d,i)) + - y(getLow(d,i))) + + 'l' + + (w/2) + + ',0l' + + (-w/2) + + ',0z'; + }) + //.attr('width', (availableWidth / data[0].values.length) * .9 ) + + + //d3.transition(ticks) + //.attr('y', function(d,i) { return y(Math.max(0, getY(d,i))) }) + //.attr('height', function(d,i) { return Math.abs(y(getY(d,i)) - y(0)) }); + //.order(); // not sure if this makes any sense for this model + + }); + + return chart; + } + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + chart.dispatch = dispatch; + + chart.options = nv.utils.optionsFunc.bind(chart); + + chart.x = function(_) { + if (!arguments.length) return getX; + getX = _; + return chart; + }; + + chart.y = function(_) { + if (!arguments.length) return getY; + getY = _; + return chart; + }; + + chart.open = function(_) { + if (!arguments.length) return getOpen; + getOpen = _; + return chart; + }; + + chart.close = function(_) { + if (!arguments.length) return getClose; + getClose = _; + return chart; + }; + + chart.high = function(_) { + if (!arguments.length) return getHigh; + getHigh = _; + return chart; + }; + + chart.low = function(_) { + if (!arguments.length) return getLow; + getLow = _; + return chart; + }; + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.xScale = function(_) { + if (!arguments.length) return x; + x = _; + return chart; + }; + + chart.yScale = function(_) { + if (!arguments.length) return y; + y = _; + return chart; + }; + + chart.xDomain = function(_) { + if (!arguments.length) return xDomain; + xDomain = _; + return chart; + }; + + chart.yDomain = function(_) { + if (!arguments.length) return yDomain; + yDomain = _; + return chart; + }; + + chart.xRange = function(_) { + if (!arguments.length) return xRange; + xRange = _; + return chart; + }; + + chart.yRange = function(_) { + if (!arguments.length) return yRange; + yRange = _; + return chart; + }; + + chart.forceX = function(_) { + if (!arguments.length) return forceX; + forceX = _; + return chart; + }; + + chart.forceY = function(_) { + if (!arguments.length) return forceY; + forceY = _; + return chart; + }; + + chart.padData = function(_) { + if (!arguments.length) return padData; + padData = _; + return chart; + }; + + chart.clipEdge = function(_) { + if (!arguments.length) return clipEdge; + clipEdge = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + return chart; + }; + + chart.id = function(_) { + if (!arguments.length) return id; + id = _; + return chart; + }; + + //============================================================ + + + return chart; +} +nv.models.pie = function() { + "use strict"; + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var margin = {top: 0, right: 0, bottom: 0, left: 0} + , width = 500 + , height = 500 + , getX = function(d) { return d.x } + , getY = function(d) { return d.y } + , getDescription = function(d) { return d.description } + , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one + , color = nv.utils.defaultColor() + , valueFormat = d3.format(',.2f') + , showLabels = true + , pieLabelsOutside = true + , donutLabelsOutside = false + , labelType = "key" + , labelThreshold = .02 //if slice percentage is under this, don't show label + , donut = false + , labelSunbeamLayout = false + , startAngle = false + , endAngle = false + , donutRatio = 0.5 + , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout') + ; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var availableWidth = width - margin.left - margin.right, + availableHeight = height - margin.top - margin.bottom, + radius = Math.min(availableWidth, availableHeight) / 2, + arcRadius = radius-(radius / 5), + container = d3.select(this); + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + //var wrap = container.selectAll('.nv-wrap.nv-pie').data([data]); + var wrap = container.selectAll('.nv-wrap.nv-pie').data(data); + var wrapEnter = wrap.enter().append('g').attr('class','nvd3 nv-wrap nv-pie nv-chart-' + id); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-pie'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + g.select('.nv-pie').attr('transform', 'translate(' + availableWidth / 2 + ',' + availableHeight / 2 + ')'); + + //------------------------------------------------------------ + + + container + .on('click', function(d,i) { + dispatch.chartClick({ + data: d, + index: i, + pos: d3.event, + id: id + }); + }); + + + var arc = d3.svg.arc() + .outerRadius(arcRadius); + + if (startAngle) arc.startAngle(startAngle) + if (endAngle) arc.endAngle(endAngle); + if (donut) arc.innerRadius(radius * donutRatio); + + // Setup the Pie chart and choose the data element + var pie = d3.layout.pie() + .sort(null) + .value(function(d) { return d.disabled ? 0 : getY(d) }); + + var slices = wrap.select('.nv-pie').selectAll('.nv-slice') + .data(pie); + + slices.exit().remove(); + + var ae = slices.enter().append('g') + .attr('class', 'nv-slice') + .on('mouseover', function(d,i){ + d3.select(this).classed('hover', true); + dispatch.elementMouseover({ + label: getX(d.data), + value: getY(d.data), + point: d.data, + pointIndex: i, + pos: [d3.event.pageX, d3.event.pageY], + id: id + }); + }) + .on('mouseout', function(d,i){ + d3.select(this).classed('hover', false); + dispatch.elementMouseout({ + label: getX(d.data), + value: getY(d.data), + point: d.data, + index: i, + id: id + }); + }) + .on('click', function(d,i) { + dispatch.elementClick({ + label: getX(d.data), + value: getY(d.data), + point: d.data, + index: i, + pos: d3.event, + id: id + }); + d3.event.stopPropagation(); + }) + .on('dblclick', function(d,i) { + dispatch.elementDblClick({ + label: getX(d.data), + value: getY(d.data), + point: d.data, + index: i, + pos: d3.event, + id: id + }); + d3.event.stopPropagation(); + }); + + slices + .attr('fill', function(d,i) { return color(d, i); }) + .attr('stroke', function(d,i) { return color(d, i); }); + + var paths = ae.append('path') + .each(function(d) { this._current = d; }); + //.attr('d', arc); + + d3.transition(slices.select('path')) + .attr('d', arc) + .attrTween('d', arcTween); + + if (showLabels) { + // This does the normal label + var labelsArc = d3.svg.arc().innerRadius(0); + + if (pieLabelsOutside){ labelsArc = arc; } + + if (donutLabelsOutside) { labelsArc = d3.svg.arc().outerRadius(arc.outerRadius()); } + + ae.append("g").classed("nv-label", true) + .each(function(d, i) { + var group = d3.select(this); + + group + .attr('transform', function(d) { + if (labelSunbeamLayout) { + d.outerRadius = arcRadius + 10; // Set Outer Coordinate + d.innerRadius = arcRadius + 15; // Set Inner Coordinate + var rotateAngle = (d.startAngle + d.endAngle) / 2 * (180 / Math.PI); + if ((d.startAngle+d.endAngle)/2 < Math.PI) { + rotateAngle -= 90; + } else { + rotateAngle += 90; + } + return 'translate(' + labelsArc.centroid(d) + ') rotate(' + rotateAngle + ')'; + } else { + d.outerRadius = radius + 10; // Set Outer Coordinate + d.innerRadius = radius + 15; // Set Inner Coordinate + return 'translate(' + labelsArc.centroid(d) + ')' + } + }); + + group.append('rect') + .style('stroke', '#fff') + .style('fill', '#fff') + .attr("rx", 3) + .attr("ry", 3); + + group.append('text') + .style('text-anchor', labelSunbeamLayout ? ((d.startAngle + d.endAngle) / 2 < Math.PI ? 'start' : 'end') : 'middle') //center the text on it's origin or begin/end if orthogonal aligned + .style('fill', '#000') + + + }); + + slices.select(".nv-label").transition() + .attr('transform', function(d) { + if (labelSunbeamLayout) { + d.outerRadius = arcRadius + 10; // Set Outer Coordinate + d.innerRadius = arcRadius + 15; // Set Inner Coordinate + var rotateAngle = (d.startAngle + d.endAngle) / 2 * (180 / Math.PI); + if ((d.startAngle+d.endAngle)/2 < Math.PI) { + rotateAngle -= 90; + } else { + rotateAngle += 90; + } + return 'translate(' + labelsArc.centroid(d) + ') rotate(' + rotateAngle + ')'; + } else { + d.outerRadius = radius + 10; // Set Outer Coordinate + d.innerRadius = radius + 15; // Set Inner Coordinate + return 'translate(' + labelsArc.centroid(d) + ')' + } + }); + + slices.each(function(d, i) { + var slice = d3.select(this); + + slice + .select(".nv-label text") + .style('text-anchor', labelSunbeamLayout ? ((d.startAngle + d.endAngle) / 2 < Math.PI ? 'start' : 'end') : 'middle') //center the text on it's origin or begin/end if orthogonal aligned + .text(function(d, i) { + var percent = (d.endAngle - d.startAngle) / (2 * Math.PI); + var labelTypes = { + "key" : getX(d.data), + "value": getY(d.data), + "percent": d3.format('%')(percent) + }; + return (d.value && percent > labelThreshold) ? labelTypes[labelType] : ''; + }); + + var textBox = slice.select('text').node().getBBox(); + slice.select(".nv-label rect") + .attr("width", textBox.width + 10) + .attr("height", textBox.height + 10) + .attr("transform", function() { + return "translate(" + [textBox.x - 5, textBox.y - 5] + ")"; + }); + }); + } + + + // Computes the angle of an arc, converting from radians to degrees. + function angle(d) { + var a = (d.startAngle + d.endAngle) * 90 / Math.PI - 90; + return a > 90 ? a - 180 : a; + } + + function arcTween(a) { + a.endAngle = isNaN(a.endAngle) ? 0 : a.endAngle; + a.startAngle = isNaN(a.startAngle) ? 0 : a.startAngle; + if (!donut) a.innerRadius = 0; + var i = d3.interpolate(this._current, a); + this._current = i(0); + return function(t) { + return arc(i(t)); + }; + } + + function tweenPie(b) { + b.innerRadius = 0; + var i = d3.interpolate({startAngle: 0, endAngle: 0}, b); + return function(t) { + return arc(i(t)); + }; + } + + }); + + return chart; + } + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + chart.dispatch = dispatch; + chart.options = nv.utils.optionsFunc.bind(chart); + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.values = function(_) { + nv.log("pie.values() is no longer supported."); + return chart; + }; + + chart.x = function(_) { + if (!arguments.length) return getX; + getX = _; + return chart; + }; + + chart.y = function(_) { + if (!arguments.length) return getY; + getY = d3.functor(_); + return chart; + }; + + chart.description = function(_) { + if (!arguments.length) return getDescription; + getDescription = _; + return chart; + }; + + chart.showLabels = function(_) { + if (!arguments.length) return showLabels; + showLabels = _; + return chart; + }; + + chart.labelSunbeamLayout = function(_) { + if (!arguments.length) return labelSunbeamLayout; + labelSunbeamLayout = _; + return chart; + }; + + chart.donutLabelsOutside = function(_) { + if (!arguments.length) return donutLabelsOutside; + donutLabelsOutside = _; + return chart; + }; + + chart.pieLabelsOutside = function(_) { + if (!arguments.length) return pieLabelsOutside; + pieLabelsOutside = _; + return chart; + }; + + chart.labelType = function(_) { + if (!arguments.length) return labelType; + labelType = _; + labelType = labelType || "key"; + return chart; + }; + + chart.donut = function(_) { + if (!arguments.length) return donut; + donut = _; + return chart; + }; + + chart.donutRatio = function(_) { + if (!arguments.length) return donutRatio; + donutRatio = _; + return chart; + }; + + chart.startAngle = function(_) { + if (!arguments.length) return startAngle; + startAngle = _; + return chart; + }; + + chart.endAngle = function(_) { + if (!arguments.length) return endAngle; + endAngle = _; + return chart; + }; + + chart.id = function(_) { + if (!arguments.length) return id; + id = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + return chart; + }; + + chart.valueFormat = function(_) { + if (!arguments.length) return valueFormat; + valueFormat = _; + return chart; + }; + + chart.labelThreshold = function(_) { + if (!arguments.length) return labelThreshold; + labelThreshold = _; + return chart; + }; + //============================================================ + + + return chart; +} +nv.models.pieChart = function() { + "use strict"; + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var pie = nv.models.pie() + , legend = nv.models.legend() + ; + + var margin = {top: 30, right: 20, bottom: 20, left: 20} + , width = null + , height = null + , showLegend = true + , color = nv.utils.defaultColor() + , tooltips = true + , tooltip = function(key, y, e, graph) { + return '<h3>' + key + '</h3>' + + '<p>' + y + '</p>' + } + , state = {} + , defaultState = null + , noData = "No Data Available." + , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState') + ; + + //============================================================ + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var showTooltip = function(e, offsetElement) { + var tooltipLabel = pie.description()(e.point) || pie.x()(e.point) + var left = e.pos[0] + ( (offsetElement && offsetElement.offsetLeft) || 0 ), + top = e.pos[1] + ( (offsetElement && offsetElement.offsetTop) || 0), + y = pie.valueFormat()(pie.y()(e.point)), + content = tooltip(tooltipLabel, y, e, chart); + + nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement); + }; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var container = d3.select(this), + that = this; + + var availableWidth = (width || parseInt(container.style('width')) || 960) + - margin.left - margin.right, + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + + chart.update = function() { container.transition().call(chart); }; + chart.container = this; + + //set state.disabled + state.disabled = data.map(function(d) { return !!d.disabled }); + + if (!defaultState) { + var key; + defaultState = {}; + for (key in state) { + if (state[key] instanceof Array) + defaultState[key] = state[key].slice(0); + else + defaultState[key] = state[key]; + } + } + + //------------------------------------------------------------ + // Display No Data message if there's nothing to show. + + if (!data || !data.length) { + var noDataText = container.selectAll('.nv-noData').data([noData]); + + noDataText.enter().append('text') + .attr('class', 'nvd3 nv-noData') + .attr('dy', '-.7em') + .style('text-anchor', 'middle'); + + noDataText + .attr('x', margin.left + availableWidth / 2) + .attr('y', margin.top + availableHeight / 2) + .text(function(d) { return d }); + + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-pieChart').data([data]); + var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-pieChart').append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-pieWrap'); + gEnter.append('g').attr('class', 'nv-legendWrap'); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Legend + + if (showLegend) { + legend + .width( availableWidth ) + .key(pie.x()); + + wrap.select('.nv-legendWrap') + .datum(data) + .call(legend); + + if ( margin.top != legend.height()) { + margin.top = legend.height(); + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + } + + wrap.select('.nv-legendWrap') + .attr('transform', 'translate(0,' + (-margin.top) +')'); + } + + //------------------------------------------------------------ + + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + + //------------------------------------------------------------ + // Main Chart Component(s) + + pie + .width(availableWidth) + .height(availableHeight); + + + var pieWrap = g.select('.nv-pieWrap') + .datum([data]); + + d3.transition(pieWrap).call(pie); + + //------------------------------------------------------------ + + + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ + + legend.dispatch.on('stateChange', function(newState) { + state = newState; + dispatch.stateChange(state); + chart.update(); + }); + + pie.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + + // Update chart from a state object passed to event handler + dispatch.on('changeState', function(e) { + + if (typeof e.disabled !== 'undefined') { + data.forEach(function(series,i) { + series.disabled = e.disabled[i]; + }); + + state.disabled = e.disabled; + } + + chart.update(); + }); + + //============================================================ + + + }); + + return chart; + } + + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ + + pie.dispatch.on('elementMouseover.tooltip', function(e) { + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); + + dispatch.on('tooltipShow', function(e) { + if (tooltips) showTooltip(e); + }); + + dispatch.on('tooltipHide', function() { + if (tooltips) nv.tooltip.cleanup(); + }); + + //============================================================ + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + // expose chart's sub-components + chart.legend = legend; + chart.dispatch = dispatch; + chart.pie = pie; + + d3.rebind(chart, pie, 'valueFormat', 'values', 'x', 'y', 'description', 'id', 'showLabels', 'donutLabelsOutside', 'pieLabelsOutside', 'labelType', 'donut', 'donutRatio', 'labelThreshold'); + chart.options = nv.utils.optionsFunc.bind(chart); + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + legend.color(color); + pie.color(color); + return chart; + }; + + chart.showLegend = function(_) { + if (!arguments.length) return showLegend; + showLegend = _; + return chart; + }; + + chart.tooltips = function(_) { + if (!arguments.length) return tooltips; + tooltips = _; + return chart; + }; + + chart.tooltipContent = function(_) { + if (!arguments.length) return tooltip; + tooltip = _; + return chart; + }; + + chart.state = function(_) { + if (!arguments.length) return state; + state = _; + return chart; + }; + + chart.defaultState = function(_) { + if (!arguments.length) return defaultState; + defaultState = _; + return chart; + }; + + chart.noData = function(_) { + if (!arguments.length) return noData; + noData = _; + return chart; + }; + + //============================================================ + + + return chart; +} + +nv.models.scatter = function() { + "use strict"; + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var margin = {top: 0, right: 0, bottom: 0, left: 0} + , width = 960 + , height = 500 + , color = nv.utils.defaultColor() // chooses color + , id = Math.floor(Math.random() * 100000) //Create semi-unique ID incase user doesn't select one + , x = d3.scale.linear() + , y = d3.scale.linear() + , z = d3.scale.linear() //linear because d3.svg.shape.size is treated as area + , getX = function(d) { return d.x } // accessor to get the x value + , getY = function(d) { return d.y } // accessor to get the y value + , getSize = function(d) { return d.size || 1} // accessor to get the point size + , getShape = function(d) { return d.shape || 'circle' } // accessor to get point shape + , onlyCircles = true // Set to false to use shapes + , forceX = [] // List of numbers to Force into the X scale (ie. 0, or a max / min, etc.) + , forceY = [] // List of numbers to Force into the Y scale + , forceSize = [] // List of numbers to Force into the Size scale + , interactive = true // If true, plots a voronoi overlay for advanced point intersection + , pointKey = null + , pointActive = function(d) { return !d.notActive } // any points that return false will be filtered out + , padData = false // If true, adds half a data points width to front and back, for lining up a line chart with a bar chart + , padDataOuter = .1 //outerPadding to imitate ordinal scale outer padding + , clipEdge = false // if true, masks points within x and y scale + , clipVoronoi = true // if true, masks each point with a circle... can turn off to slightly increase performance + , clipRadius = function() { return 25 } // function to get the radius for voronoi point clips + , xDomain = null // Override x domain (skips the calculation from data) + , yDomain = null // Override y domain + , xRange = null // Override x range + , yRange = null // Override y range + , sizeDomain = null // Override point size domain + , sizeRange = null + , singlePoint = false + , dispatch = d3.dispatch('elementClick', 'elementMouseover', 'elementMouseout') + , useVoronoi = true + ; + + //============================================================ + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var x0, y0, z0 // used to store previous scales + , timeoutID + , needsUpdate = false // Flag for when the points are visually updating, but the interactive layer is behind, to disable tooltips + ; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var availableWidth = width - margin.left - margin.right, + availableHeight = height - margin.top - margin.bottom, + container = d3.select(this); + + //add series index to each data point for reference + data.forEach(function(series, i) { + series.values.forEach(function(point) { + point.series = i; + }); + }); + + //------------------------------------------------------------ + // Setup Scales + + // remap and flatten the data for use in calculating the scales' domains + var seriesData = (xDomain && yDomain && sizeDomain) ? [] : // if we know xDomain and yDomain and sizeDomain, no need to calculate.... if Size is constant remember to set sizeDomain to speed up performance + d3.merge( + data.map(function(d) { + return d.values.map(function(d,i) { + return { x: getX(d,i), y: getY(d,i), size: getSize(d,i) } + }) + }) + ); + + x .domain(xDomain || d3.extent(seriesData.map(function(d) { return d.x; }).concat(forceX))) + + if (padData && data[0]) + x.range(xRange || [(availableWidth * padDataOuter + availableWidth) / (2 *data[0].values.length), availableWidth - availableWidth * (1 + padDataOuter) / (2 * data[0].values.length) ]); + //x.range([availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]); + else + x.range(xRange || [0, availableWidth]); + + y .domain(yDomain || d3.extent(seriesData.map(function(d) { return d.y }).concat(forceY))) + .range(yRange || [availableHeight, 0]); + + z .domain(sizeDomain || d3.extent(seriesData.map(function(d) { return d.size }).concat(forceSize))) + .range(sizeRange || [16, 256]); + + // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point + if (x.domain()[0] === x.domain()[1] || y.domain()[0] === y.domain()[1]) singlePoint = true; + if (x.domain()[0] === x.domain()[1]) + x.domain()[0] ? + x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01]) + : x.domain([-1,1]); + + if (y.domain()[0] === y.domain()[1]) + y.domain()[0] ? + y.domain([y.domain()[0] - y.domain()[0] * 0.01, y.domain()[1] + y.domain()[1] * 0.01]) + : y.domain([-1,1]); + + if ( isNaN(x.domain()[0])) { + x.domain([-1,1]); + } + + if ( isNaN(y.domain()[0])) { + y.domain([-1,1]); + } + + + x0 = x0 || x; + y0 = y0 || y; + z0 = z0 || z; + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-scatter').data([data]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-scatter nv-chart-' + id + (singlePoint ? ' nv-single-point' : '')); + var defsEnter = wrapEnter.append('defs'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-groups'); + gEnter.append('g').attr('class', 'nv-point-paths'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + //------------------------------------------------------------ + + + defsEnter.append('clipPath') + .attr('id', 'nv-edge-clip-' + id) + .append('rect'); + + wrap.select('#nv-edge-clip-' + id + ' rect') + .attr('width', availableWidth) + .attr('height', availableHeight); + + g .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : ''); + + + function updateInteractiveLayer() { + + if (!interactive) return false; + + var eventElements; + + var vertices = d3.merge(data.map(function(group, groupIndex) { + return group.values + .map(function(point, pointIndex) { + // *Adding noise to make duplicates very unlikely + // *Injecting series and point index for reference + /* *Adding a 'jitter' to the points, because there's an issue in d3.geom.voronoi. + */ + var pX = getX(point,pointIndex); + var pY = getY(point,pointIndex); + + return [x(pX)+ Math.random() * 1e-7, + y(pY)+ Math.random() * 1e-7, + groupIndex, + pointIndex, point]; //temp hack to add noise untill I think of a better way so there are no duplicates + }) + .filter(function(pointArray, pointIndex) { + return pointActive(pointArray[4], pointIndex); // Issue #237.. move filter to after map, so pointIndex is correct! + }) + }) + ); + + + + //inject series and point index for reference into voronoi + if (useVoronoi === true) { + + if (clipVoronoi) { + var pointClipsEnter = wrap.select('defs').selectAll('.nv-point-clips') + .data([id]) + .enter(); + + pointClipsEnter.append('clipPath') + .attr('class', 'nv-point-clips') + .attr('id', 'nv-points-clip-' + id); + + var pointClips = wrap.select('#nv-points-clip-' + id).selectAll('circle') + .data(vertices); + pointClips.enter().append('circle') + .attr('r', clipRadius); + pointClips.exit().remove(); + pointClips + .attr('cx', function(d) { return d[0] }) + .attr('cy', function(d) { return d[1] }); + + wrap.select('.nv-point-paths') + .attr('clip-path', 'url(#nv-points-clip-' + id + ')'); + } + + + if(vertices.length) { + // Issue #283 - Adding 2 dummy points to the voronoi b/c voronoi requires min 3 points to work + vertices.push([x.range()[0] - 20, y.range()[0] - 20, null, null]); + vertices.push([x.range()[1] + 20, y.range()[1] + 20, null, null]); + vertices.push([x.range()[0] - 20, y.range()[0] + 20, null, null]); + vertices.push([x.range()[1] + 20, y.range()[1] - 20, null, null]); + } + + var bounds = d3.geom.polygon([ + [-10,-10], + [-10,height + 10], + [width + 10,height + 10], + [width + 10,-10] + ]); + + var voronoi = d3.geom.voronoi(vertices).map(function(d, i) { + return { + 'data': bounds.clip(d), + 'series': vertices[i][2], + 'point': vertices[i][3] + } + }); + + + var pointPaths = wrap.select('.nv-point-paths').selectAll('path') + .data(voronoi); + pointPaths.enter().append('path') + .attr('class', function(d,i) { return 'nv-path-'+i; }); + pointPaths.exit().remove(); + pointPaths + .attr('d', function(d) { + if (d.data.length === 0) + return 'M 0 0' + else + return 'M' + d.data.join('L') + 'Z'; + }); + + var mouseEventCallback = function(d,mDispatch) { + if (needsUpdate) return 0; + var series = data[d.series]; + if (typeof series === 'undefined') return; + + var point = series.values[d.point]; + + mDispatch({ + point: point, + series: series, + pos: [x(getX(point, d.point)) + margin.left, y(getY(point, d.point)) + margin.top], + seriesIndex: d.series, + pointIndex: d.point + }); + }; + + pointPaths + .on('click', function(d) { + mouseEventCallback(d, dispatch.elementClick); + }) + .on('mouseover', function(d) { + mouseEventCallback(d, dispatch.elementMouseover); + }) + .on('mouseout', function(d, i) { + mouseEventCallback(d, dispatch.elementMouseout); + }); + + + } else { + /* + // bring data in form needed for click handlers + var dataWithPoints = vertices.map(function(d, i) { + return { + 'data': d, + 'series': vertices[i][2], + 'point': vertices[i][3] + } + }); + */ + + // add event handlers to points instead voronoi paths + wrap.select('.nv-groups').selectAll('.nv-group') + .selectAll('.nv-point') + //.data(dataWithPoints) + //.style('pointer-events', 'auto') // recativate events, disabled by css + .on('click', function(d,i) { + //nv.log('test', d, i); + if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point + var series = data[d.series], + point = series.values[i]; + + dispatch.elementClick({ + point: point, + series: series, + pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top], + seriesIndex: d.series, + pointIndex: i + }); + }) + .on('mouseover', function(d,i) { + if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point + var series = data[d.series], + point = series.values[i]; + + dispatch.elementMouseover({ + point: point, + series: series, + pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top], + seriesIndex: d.series, + pointIndex: i + }); + }) + .on('mouseout', function(d,i) { + if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point + var series = data[d.series], + point = series.values[i]; + + dispatch.elementMouseout({ + point: point, + series: series, + seriesIndex: d.series, + pointIndex: i + }); + }); + } + + needsUpdate = false; + } + + needsUpdate = true; + + var groups = wrap.select('.nv-groups').selectAll('.nv-group') + .data(function(d) { return d }, function(d) { return d.key }); + groups.enter().append('g') + .style('stroke-opacity', 1e-6) + .style('fill-opacity', 1e-6); + groups.exit() + .remove(); + groups + .attr('class', function(d,i) { return 'nv-group nv-series-' + i }) + .classed('hover', function(d) { return d.hover }); + groups + .transition() + .style('fill', function(d,i) { return color(d, i) }) + .style('stroke', function(d,i) { return color(d, i) }) + .style('stroke-opacity', 1) + .style('fill-opacity', .5); + + + if (onlyCircles) { + + var points = groups.selectAll('circle.nv-point') + .data(function(d) { return d.values }, pointKey); + points.enter().append('circle') + .style('fill', function (d,i) { return d.color }) + .style('stroke', function (d,i) { return d.color }) + .attr('cx', function(d,i) { return nv.utils.NaNtoZero(x0(getX(d,i))) }) + .attr('cy', function(d,i) { return nv.utils.NaNtoZero(y0(getY(d,i))) }) + .attr('r', function(d,i) { return Math.sqrt(z(getSize(d,i))/Math.PI) }); + points.exit().remove(); + groups.exit().selectAll('path.nv-point').transition() + .attr('cx', function(d,i) { return nv.utils.NaNtoZero(x(getX(d,i))) }) + .attr('cy', function(d,i) { return nv.utils.NaNtoZero(y(getY(d,i))) }) + .remove(); + points.each(function(d,i) { + d3.select(this) + .classed('nv-point', true) + .classed('nv-point-' + i, true) + .classed('hover',false) + ; + }); + points.transition() + .attr('cx', function(d,i) { return nv.utils.NaNtoZero(x(getX(d,i))) }) + .attr('cy', function(d,i) { return nv.utils.NaNtoZero(y(getY(d,i))) }) + .attr('r', function(d,i) { return Math.sqrt(z(getSize(d,i))/Math.PI) }); + + } else { + + var points = groups.selectAll('path.nv-point') + .data(function(d) { return d.values }); + points.enter().append('path') + .style('fill', function (d,i) { return d.color }) + .style('stroke', function (d,i) { return d.color }) + .attr('transform', function(d,i) { + return 'translate(' + x0(getX(d,i)) + ',' + y0(getY(d,i)) + ')' + }) + .attr('d', + d3.svg.symbol() + .type(getShape) + .size(function(d,i) { return z(getSize(d,i)) }) + ); + points.exit().remove(); + groups.exit().selectAll('path.nv-point') + .transition() + .attr('transform', function(d,i) { + return 'translate(' + x(getX(d,i)) + ',' + y(getY(d,i)) + ')' + }) + .remove(); + points.each(function(d,i) { + d3.select(this) + .classed('nv-point', true) + .classed('nv-point-' + i, true) + .classed('hover',false) + ; + }); + points.transition() + .attr('transform', function(d,i) { + //nv.log(d,i,getX(d,i), x(getX(d,i))); + return 'translate(' + x(getX(d,i)) + ',' + y(getY(d,i)) + ')' + }) + .attr('d', + d3.svg.symbol() + .type(getShape) + .size(function(d,i) { return z(getSize(d,i)) }) + ); + } + + + // Delay updating the invisible interactive layer for smoother animation + clearTimeout(timeoutID); // stop repeat calls to updateInteractiveLayer + timeoutID = setTimeout(updateInteractiveLayer, 300); + //updateInteractiveLayer(); + + //store old scales for use in transitions on update + x0 = x.copy(); + y0 = y.copy(); + z0 = z.copy(); + + }); + + return chart; + } + + + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ + chart.clearHighlights = function() { + //Remove the 'hover' class from all highlighted points. + d3.selectAll(".nv-chart-" + id + " .nv-point.hover").classed("hover",false); + }; + + chart.highlightPoint = function(seriesIndex,pointIndex,isHoverOver) { + d3.select(".nv-chart-" + id + " .nv-series-" + seriesIndex + " .nv-point-" + pointIndex) + .classed("hover",isHoverOver); + }; + + + dispatch.on('elementMouseover.point', function(d) { + if (interactive) chart.highlightPoint(d.seriesIndex,d.pointIndex,true); + }); + + dispatch.on('elementMouseout.point', function(d) { + if (interactive) chart.highlightPoint(d.seriesIndex,d.pointIndex,false); + }); + + //============================================================ + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + chart.dispatch = dispatch; + chart.options = nv.utils.optionsFunc.bind(chart); + + chart.x = function(_) { + if (!arguments.length) return getX; + getX = d3.functor(_); + return chart; + }; + + chart.y = function(_) { + if (!arguments.length) return getY; + getY = d3.functor(_); + return chart; + }; + + chart.size = function(_) { + if (!arguments.length) return getSize; + getSize = d3.functor(_); + return chart; + }; + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.xScale = function(_) { + if (!arguments.length) return x; + x = _; + return chart; + }; + + chart.yScale = function(_) { + if (!arguments.length) return y; + y = _; + return chart; + }; + + chart.zScale = function(_) { + if (!arguments.length) return z; + z = _; + return chart; + }; + + chart.xDomain = function(_) { + if (!arguments.length) return xDomain; + xDomain = _; + return chart; + }; + + chart.yDomain = function(_) { + if (!arguments.length) return yDomain; + yDomain = _; + return chart; + }; + + chart.sizeDomain = function(_) { + if (!arguments.length) return sizeDomain; + sizeDomain = _; + return chart; + }; + + chart.xRange = function(_) { + if (!arguments.length) return xRange; + xRange = _; + return chart; + }; + + chart.yRange = function(_) { + if (!arguments.length) return yRange; + yRange = _; + return chart; + }; + + chart.sizeRange = function(_) { + if (!arguments.length) return sizeRange; + sizeRange = _; + return chart; + }; + + chart.forceX = function(_) { + if (!arguments.length) return forceX; + forceX = _; + return chart; + }; + + chart.forceY = function(_) { + if (!arguments.length) return forceY; + forceY = _; + return chart; + }; + + chart.forceSize = function(_) { + if (!arguments.length) return forceSize; + forceSize = _; + return chart; + }; + + chart.interactive = function(_) { + if (!arguments.length) return interactive; + interactive = _; + return chart; + }; + + chart.pointKey = function(_) { + if (!arguments.length) return pointKey; + pointKey = _; + return chart; + }; + + chart.pointActive = function(_) { + if (!arguments.length) return pointActive; + pointActive = _; + return chart; + }; + + chart.padData = function(_) { + if (!arguments.length) return padData; + padData = _; + return chart; + }; + + chart.padDataOuter = function(_) { + if (!arguments.length) return padDataOuter; + padDataOuter = _; + return chart; + }; + + chart.clipEdge = function(_) { + if (!arguments.length) return clipEdge; + clipEdge = _; + return chart; + }; + + chart.clipVoronoi= function(_) { + if (!arguments.length) return clipVoronoi; + clipVoronoi = _; + return chart; + }; + + chart.useVoronoi= function(_) { + if (!arguments.length) return useVoronoi; + useVoronoi = _; + if (useVoronoi === false) { + clipVoronoi = false; + } + return chart; + }; + + chart.clipRadius = function(_) { + if (!arguments.length) return clipRadius; + clipRadius = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + return chart; + }; + + chart.shape = function(_) { + if (!arguments.length) return getShape; + getShape = _; + return chart; + }; + + chart.onlyCircles = function(_) { + if (!arguments.length) return onlyCircles; + onlyCircles = _; + return chart; + }; + + chart.id = function(_) { + if (!arguments.length) return id; + id = _; + return chart; + }; + + chart.singlePoint = function(_) { + if (!arguments.length) return singlePoint; + singlePoint = _; + return chart; + }; + + //============================================================ + + + return chart; +} +nv.models.scatterChart = function() { + "use strict"; + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var scatter = nv.models.scatter() + , xAxis = nv.models.axis() + , yAxis = nv.models.axis() + , legend = nv.models.legend() + , controls = nv.models.legend() + , distX = nv.models.distribution() + , distY = nv.models.distribution() + ; + + var margin = {top: 30, right: 20, bottom: 50, left: 75} + , width = null + , height = null + , color = nv.utils.defaultColor() + , x = d3.fisheye ? d3.fisheye.scale(d3.scale.linear).distortion(0) : scatter.xScale() + , y = d3.fisheye ? d3.fisheye.scale(d3.scale.linear).distortion(0) : scatter.yScale() + , xPadding = 0 + , yPadding = 0 + , showDistX = false + , showDistY = false + , showLegend = true + , showXAxis = true + , showYAxis = true + , rightAlignYAxis = false + , showControls = !!d3.fisheye + , fisheye = 0 + , pauseFisheye = false + , tooltips = true + , tooltipX = function(key, x, y) { return '<strong>' + x + '</strong>' } + , tooltipY = function(key, x, y) { return '<strong>' + y + '</strong>' } + , tooltip = null + , state = {} + , defaultState = null + , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState') + , noData = "No Data Available." + , transitionDuration = 250 + ; + + scatter + .xScale(x) + .yScale(y) + ; + xAxis + .orient('bottom') + .tickPadding(10) + ; + yAxis + .orient((rightAlignYAxis) ? 'right' : 'left') + .tickPadding(10) + ; + distX + .axis('x') + ; + distY + .axis('y') + ; + + controls.updateState(false); + + //============================================================ + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var x0, y0; + + var showTooltip = function(e, offsetElement) { + //TODO: make tooltip style an option between single or dual on axes (maybe on all charts with axes?) + + var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ), + top = e.pos[1] + ( offsetElement.offsetTop || 0), + leftX = e.pos[0] + ( offsetElement.offsetLeft || 0 ), + topX = y.range()[0] + margin.top + ( offsetElement.offsetTop || 0), + leftY = x.range()[0] + margin.left + ( offsetElement.offsetLeft || 0 ), + topY = e.pos[1] + ( offsetElement.offsetTop || 0), + xVal = xAxis.tickFormat()(scatter.x()(e.point, e.pointIndex)), + yVal = yAxis.tickFormat()(scatter.y()(e.point, e.pointIndex)); + + if( tooltipX != null ) + nv.tooltip.show([leftX, topX], tooltipX(e.series.key, xVal, yVal, e, chart), 'n', 1, offsetElement, 'x-nvtooltip'); + if( tooltipY != null ) + nv.tooltip.show([leftY, topY], tooltipY(e.series.key, xVal, yVal, e, chart), 'e', 1, offsetElement, 'y-nvtooltip'); + if( tooltip != null ) + nv.tooltip.show([left, top], tooltip(e.series.key, xVal, yVal, e, chart), e.value < 0 ? 'n' : 's', null, offsetElement); + }; + + var controlsData = [ + { key: 'Magnify', disabled: true } + ]; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var container = d3.select(this), + that = this; + + var availableWidth = (width || parseInt(container.style('width')) || 960) + - margin.left - margin.right, + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + + chart.update = function() { container.transition().duration(transitionDuration).call(chart); }; + chart.container = this; + + //set state.disabled + state.disabled = data.map(function(d) { return !!d.disabled }); + + if (!defaultState) { + var key; + defaultState = {}; + for (key in state) { + if (state[key] instanceof Array) + defaultState[key] = state[key].slice(0); + else + defaultState[key] = state[key]; + } + } + + //------------------------------------------------------------ + // Display noData message if there's nothing to show. + + if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { + var noDataText = container.selectAll('.nv-noData').data([noData]); + + noDataText.enter().append('text') + .attr('class', 'nvd3 nv-noData') + .attr('dy', '-.7em') + .style('text-anchor', 'middle'); + + noDataText + .attr('x', margin.left + availableWidth / 2) + .attr('y', margin.top + availableHeight / 2) + .text(function(d) { return d }); + + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup Scales + + x0 = x0 || x; + y0 = y0 || y; + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-scatterChart').data([data]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-scatterChart nv-chart-' + scatter.id()); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); + + // background for pointer events + gEnter.append('rect').attr('class', 'nvd3 nv-background'); + + gEnter.append('g').attr('class', 'nv-x nv-axis'); + gEnter.append('g').attr('class', 'nv-y nv-axis'); + gEnter.append('g').attr('class', 'nv-scatterWrap'); + gEnter.append('g').attr('class', 'nv-distWrap'); + gEnter.append('g').attr('class', 'nv-legendWrap'); + gEnter.append('g').attr('class', 'nv-controlsWrap'); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Legend + + if (showLegend) { + var legendWidth = (showControls) ? availableWidth / 2 : availableWidth; + legend.width(legendWidth); + + wrap.select('.nv-legendWrap') + .datum(data) + .call(legend); + + if ( margin.top != legend.height()) { + margin.top = legend.height(); + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + } + + wrap.select('.nv-legendWrap') + .attr('transform', 'translate(' + (availableWidth - legendWidth) + ',' + (-margin.top) +')'); + } + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Controls + + if (showControls) { + controls.width(180).color(['#444']); + g.select('.nv-controlsWrap') + .datum(controlsData) + .attr('transform', 'translate(0,' + (-margin.top) +')') + .call(controls); + } + + //------------------------------------------------------------ + + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + if (rightAlignYAxis) { + g.select(".nv-y.nv-axis") + .attr("transform", "translate(" + availableWidth + ",0)"); + } + + //------------------------------------------------------------ + // Main Chart Component(s) + + scatter + .width(availableWidth) + .height(availableHeight) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled })); + + if (xPadding !== 0) + scatter.xDomain(null); + + if (yPadding !== 0) + scatter.yDomain(null); + + wrap.select('.nv-scatterWrap') + .datum(data.filter(function(d) { return !d.disabled })) + .call(scatter); + + //Adjust for x and y padding + if (xPadding !== 0) { + var xRange = x.domain()[1] - x.domain()[0]; + scatter.xDomain([x.domain()[0] - (xPadding * xRange), x.domain()[1] + (xPadding * xRange)]); + } + + if (yPadding !== 0) { + var yRange = y.domain()[1] - y.domain()[0]; + scatter.yDomain([y.domain()[0] - (yPadding * yRange), y.domain()[1] + (yPadding * yRange)]); + } + + //Only need to update the scatter again if x/yPadding changed the domain. + if (yPadding !== 0 || xPadding !== 0) { + wrap.select('.nv-scatterWrap') + .datum(data.filter(function(d) { return !d.disabled })) + .call(scatter); + } + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup Axes + if (showXAxis) { + xAxis + .scale(x) + .ticks( xAxis.ticks() && xAxis.ticks().length ? xAxis.ticks() : availableWidth / 100 ) + .tickSize( -availableHeight , 0); + + g.select('.nv-x.nv-axis') + .attr('transform', 'translate(0,' + y.range()[0] + ')') + .call(xAxis); + + } + + if (showYAxis) { + yAxis + .scale(y) + .ticks( yAxis.ticks() && yAxis.ticks().length ? yAxis.ticks() : availableHeight / 36 ) + .tickSize( -availableWidth, 0); + + g.select('.nv-y.nv-axis') + .call(yAxis); + } + + + if (showDistX) { + distX + .getData(scatter.x()) + .scale(x) + .width(availableWidth) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled })); + gEnter.select('.nv-distWrap').append('g') + .attr('class', 'nv-distributionX'); + g.select('.nv-distributionX') + .attr('transform', 'translate(0,' + y.range()[0] + ')') + .datum(data.filter(function(d) { return !d.disabled })) + .call(distX); + } + + if (showDistY) { + distY + .getData(scatter.y()) + .scale(y) + .width(availableHeight) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled })); + gEnter.select('.nv-distWrap').append('g') + .attr('class', 'nv-distributionY'); + g.select('.nv-distributionY') + .attr('transform', + 'translate(' + (rightAlignYAxis ? availableWidth : -distY.size() ) + ',0)') + .datum(data.filter(function(d) { return !d.disabled })) + .call(distY); + } + + //------------------------------------------------------------ + + + + + if (d3.fisheye) { + g.select('.nv-background') + .attr('width', availableWidth) + .attr('height', availableHeight); + + g.select('.nv-background').on('mousemove', updateFisheye); + g.select('.nv-background').on('click', function() { pauseFisheye = !pauseFisheye;}); + scatter.dispatch.on('elementClick.freezeFisheye', function() { + pauseFisheye = !pauseFisheye; + }); + } + + + function updateFisheye() { + if (pauseFisheye) { + g.select('.nv-point-paths').style('pointer-events', 'all'); + return false; + } + + g.select('.nv-point-paths').style('pointer-events', 'none' ); + + var mouse = d3.mouse(this); + x.distortion(fisheye).focus(mouse[0]); + y.distortion(fisheye).focus(mouse[1]); + + g.select('.nv-scatterWrap') + .call(scatter); + + if (showXAxis) + g.select('.nv-x.nv-axis').call(xAxis); + + if (showYAxis) + g.select('.nv-y.nv-axis').call(yAxis); + + g.select('.nv-distributionX') + .datum(data.filter(function(d) { return !d.disabled })) + .call(distX); + g.select('.nv-distributionY') + .datum(data.filter(function(d) { return !d.disabled })) + .call(distY); + } + + + + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ + + controls.dispatch.on('legendClick', function(d,i) { + d.disabled = !d.disabled; + + fisheye = d.disabled ? 0 : 2.5; + g.select('.nv-background') .style('pointer-events', d.disabled ? 'none' : 'all'); + g.select('.nv-point-paths').style('pointer-events', d.disabled ? 'all' : 'none' ); + + if (d.disabled) { + x.distortion(fisheye).focus(0); + y.distortion(fisheye).focus(0); + + g.select('.nv-scatterWrap').call(scatter); + g.select('.nv-x.nv-axis').call(xAxis); + g.select('.nv-y.nv-axis').call(yAxis); + } else { + pauseFisheye = false; + } + + chart.update(); + }); + + legend.dispatch.on('stateChange', function(newState) { + state.disabled = newState.disabled; + dispatch.stateChange(state); + chart.update(); + }); + + scatter.dispatch.on('elementMouseover.tooltip', function(e) { + d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-distx-' + e.pointIndex) + .attr('y1', function(d,i) { return e.pos[1] - availableHeight;}); + d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-disty-' + e.pointIndex) + .attr('x2', e.pos[0] + distX.size()); + + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); + + dispatch.on('tooltipShow', function(e) { + if (tooltips) showTooltip(e, that.parentNode); + }); + + // Update chart from a state object passed to event handler + dispatch.on('changeState', function(e) { + + if (typeof e.disabled !== 'undefined') { + data.forEach(function(series,i) { + series.disabled = e.disabled[i]; + }); + + state.disabled = e.disabled; + } + + chart.update(); + }); + + //============================================================ + + + //store old scales for use in transitions on update + x0 = x.copy(); + y0 = y.copy(); + + + }); + + return chart; + } + + + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ + + scatter.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + + d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-distx-' + e.pointIndex) + .attr('y1', 0); + d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-disty-' + e.pointIndex) + .attr('x2', distY.size()); + }); + dispatch.on('tooltipHide', function() { + if (tooltips) nv.tooltip.cleanup(); + }); + + //============================================================ + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + // expose chart's sub-components + chart.dispatch = dispatch; + chart.scatter = scatter; + chart.legend = legend; + chart.controls = controls; + chart.xAxis = xAxis; + chart.yAxis = yAxis; + chart.distX = distX; + chart.distY = distY; + + d3.rebind(chart, scatter, 'id', 'interactive', 'pointActive', 'x', 'y', 'shape', 'size', 'xScale', 'yScale', 'zScale', 'xDomain', 'yDomain', 'xRange', 'yRange', 'sizeDomain', 'sizeRange', 'forceX', 'forceY', 'forceSize', 'clipVoronoi', 'clipRadius', 'useVoronoi'); + chart.options = nv.utils.optionsFunc.bind(chart); + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + legend.color(color); + distX.color(color); + distY.color(color); + return chart; + }; + + chart.showDistX = function(_) { + if (!arguments.length) return showDistX; + showDistX = _; + return chart; + }; + + chart.showDistY = function(_) { + if (!arguments.length) return showDistY; + showDistY = _; + return chart; + }; + + chart.showControls = function(_) { + if (!arguments.length) return showControls; + showControls = _; + return chart; + }; + + chart.showLegend = function(_) { + if (!arguments.length) return showLegend; + showLegend = _; + return chart; + }; + + chart.showXAxis = function(_) { + if (!arguments.length) return showXAxis; + showXAxis = _; + return chart; + }; + + chart.showYAxis = function(_) { + if (!arguments.length) return showYAxis; + showYAxis = _; + return chart; + }; + + chart.rightAlignYAxis = function(_) { + if(!arguments.length) return rightAlignYAxis; + rightAlignYAxis = _; + yAxis.orient( (_) ? 'right' : 'left'); + return chart; + }; + + + chart.fisheye = function(_) { + if (!arguments.length) return fisheye; + fisheye = _; + return chart; + }; + + chart.xPadding = function(_) { + if (!arguments.length) return xPadding; + xPadding = _; + return chart; + }; + + chart.yPadding = function(_) { + if (!arguments.length) return yPadding; + yPadding = _; + return chart; + }; + + chart.tooltips = function(_) { + if (!arguments.length) return tooltips; + tooltips = _; + return chart; + }; + + chart.tooltipContent = function(_) { + if (!arguments.length) return tooltip; + tooltip = _; + return chart; + }; + + chart.tooltipXContent = function(_) { + if (!arguments.length) return tooltipX; + tooltipX = _; + return chart; + }; + + chart.tooltipYContent = function(_) { + if (!arguments.length) return tooltipY; + tooltipY = _; + return chart; + }; + + chart.state = function(_) { + if (!arguments.length) return state; + state = _; + return chart; + }; + + chart.defaultState = function(_) { + if (!arguments.length) return defaultState; + defaultState = _; + return chart; + }; + + chart.noData = function(_) { + if (!arguments.length) return noData; + noData = _; + return chart; + }; + + chart.transitionDuration = function(_) { + if (!arguments.length) return transitionDuration; + transitionDuration = _; + return chart; + }; + + //============================================================ + + + return chart; +} + +nv.models.scatterPlusLineChart = function() { + "use strict"; + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var scatter = nv.models.scatter() + , xAxis = nv.models.axis() + , yAxis = nv.models.axis() + , legend = nv.models.legend() + , controls = nv.models.legend() + , distX = nv.models.distribution() + , distY = nv.models.distribution() + ; + + var margin = {top: 30, right: 20, bottom: 50, left: 75} + , width = null + , height = null + , color = nv.utils.defaultColor() + , x = d3.fisheye ? d3.fisheye.scale(d3.scale.linear).distortion(0) : scatter.xScale() + , y = d3.fisheye ? d3.fisheye.scale(d3.scale.linear).distortion(0) : scatter.yScale() + , showDistX = false + , showDistY = false + , showLegend = true + , showXAxis = true + , showYAxis = true + , rightAlignYAxis = false + , showControls = !!d3.fisheye + , fisheye = 0 + , pauseFisheye = false + , tooltips = true + , tooltipX = function(key, x, y) { return '<strong>' + x + '</strong>' } + , tooltipY = function(key, x, y) { return '<strong>' + y + '</strong>' } + , tooltip = function(key, x, y, date) { return '<h3>' + key + '</h3>' + + '<p>' + date + '</p>' } + , state = {} + , defaultState = null + , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState') + , noData = "No Data Available." + , transitionDuration = 250 + ; + + scatter + .xScale(x) + .yScale(y) + ; + xAxis + .orient('bottom') + .tickPadding(10) + ; + yAxis + .orient((rightAlignYAxis) ? 'right' : 'left') + .tickPadding(10) + ; + distX + .axis('x') + ; + distY + .axis('y') + ; + + controls.updateState(false); + //============================================================ + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var x0, y0; + + var showTooltip = function(e, offsetElement) { + //TODO: make tooltip style an option between single or dual on axes (maybe on all charts with axes?) + + var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ), + top = e.pos[1] + ( offsetElement.offsetTop || 0), + leftX = e.pos[0] + ( offsetElement.offsetLeft || 0 ), + topX = y.range()[0] + margin.top + ( offsetElement.offsetTop || 0), + leftY = x.range()[0] + margin.left + ( offsetElement.offsetLeft || 0 ), + topY = e.pos[1] + ( offsetElement.offsetTop || 0), + xVal = xAxis.tickFormat()(scatter.x()(e.point, e.pointIndex)), + yVal = yAxis.tickFormat()(scatter.y()(e.point, e.pointIndex)); + + if( tooltipX != null ) + nv.tooltip.show([leftX, topX], tooltipX(e.series.key, xVal, yVal, e, chart), 'n', 1, offsetElement, 'x-nvtooltip'); + if( tooltipY != null ) + nv.tooltip.show([leftY, topY], tooltipY(e.series.key, xVal, yVal, e, chart), 'e', 1, offsetElement, 'y-nvtooltip'); + if( tooltip != null ) + nv.tooltip.show([left, top], tooltip(e.series.key, xVal, yVal, e.point.tooltip, e, chart), e.value < 0 ? 'n' : 's', null, offsetElement); + }; + + var controlsData = [ + { key: 'Magnify', disabled: true } + ]; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var container = d3.select(this), + that = this; + + var availableWidth = (width || parseInt(container.style('width')) || 960) + - margin.left - margin.right, + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + + chart.update = function() { container.transition().duration(transitionDuration).call(chart); }; + chart.container = this; + + //set state.disabled + state.disabled = data.map(function(d) { return !!d.disabled }); + + if (!defaultState) { + var key; + defaultState = {}; + for (key in state) { + if (state[key] instanceof Array) + defaultState[key] = state[key].slice(0); + else + defaultState[key] = state[key]; + } + } + + //------------------------------------------------------------ + // Display noData message if there's nothing to show. + + if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { + var noDataText = container.selectAll('.nv-noData').data([noData]); + + noDataText.enter().append('text') + .attr('class', 'nvd3 nv-noData') + .attr('dy', '-.7em') + .style('text-anchor', 'middle'); + + noDataText + .attr('x', margin.left + availableWidth / 2) + .attr('y', margin.top + availableHeight / 2) + .text(function(d) { return d }); + + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup Scales + + x = scatter.xScale(); + y = scatter.yScale(); + + x0 = x0 || x; + y0 = y0 || y; + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-scatterChart').data([data]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-scatterChart nv-chart-' + scatter.id()); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g') + + // background for pointer events + gEnter.append('rect').attr('class', 'nvd3 nv-background').style("pointer-events","none"); + + gEnter.append('g').attr('class', 'nv-x nv-axis'); + gEnter.append('g').attr('class', 'nv-y nv-axis'); + gEnter.append('g').attr('class', 'nv-scatterWrap'); + gEnter.append('g').attr('class', 'nv-regressionLinesWrap'); + gEnter.append('g').attr('class', 'nv-distWrap'); + gEnter.append('g').attr('class', 'nv-legendWrap'); + gEnter.append('g').attr('class', 'nv-controlsWrap'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + if (rightAlignYAxis) { + g.select(".nv-y.nv-axis") + .attr("transform", "translate(" + availableWidth + ",0)"); + } + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Legend + + if (showLegend) { + legend.width( availableWidth / 2 ); + + wrap.select('.nv-legendWrap') + .datum(data) + .call(legend); + + if ( margin.top != legend.height()) { + margin.top = legend.height(); + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + } + + wrap.select('.nv-legendWrap') + .attr('transform', 'translate(' + (availableWidth / 2) + ',' + (-margin.top) +')'); + } + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Controls + + if (showControls) { + controls.width(180).color(['#444']); + g.select('.nv-controlsWrap') + .datum(controlsData) + .attr('transform', 'translate(0,' + (-margin.top) +')') + .call(controls); + } + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Main Chart Component(s) + + scatter + .width(availableWidth) + .height(availableHeight) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled })) + + wrap.select('.nv-scatterWrap') + .datum(data.filter(function(d) { return !d.disabled })) + .call(scatter); + + wrap.select('.nv-regressionLinesWrap') + .attr('clip-path', 'url(#nv-edge-clip-' + scatter.id() + ')'); + + var regWrap = wrap.select('.nv-regressionLinesWrap').selectAll('.nv-regLines') + .data(function(d) {return d }); + + regWrap.enter().append('g').attr('class', 'nv-regLines'); + + var regLine = regWrap.selectAll('.nv-regLine').data(function(d){return [d]}); + var regLineEnter = regLine.enter() + .append('line').attr('class', 'nv-regLine') + .style('stroke-opacity', 0); + + regLine + .transition() + .attr('x1', x.range()[0]) + .attr('x2', x.range()[1]) + .attr('y1', function(d,i) {return y(x.domain()[0] * d.slope + d.intercept) }) + .attr('y2', function(d,i) { return y(x.domain()[1] * d.slope + d.intercept) }) + .style('stroke', function(d,i,j) { return color(d,j) }) + .style('stroke-opacity', function(d,i) { + return (d.disabled || typeof d.slope === 'undefined' || typeof d.intercept === 'undefined') ? 0 : 1 + }); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup Axes + + if (showXAxis) { + xAxis + .scale(x) + .ticks( xAxis.ticks() ? xAxis.ticks() : availableWidth / 100 ) + .tickSize( -availableHeight , 0); + + g.select('.nv-x.nv-axis') + .attr('transform', 'translate(0,' + y.range()[0] + ')') + .call(xAxis); + } + + if (showYAxis) { + yAxis + .scale(y) + .ticks( yAxis.ticks() ? yAxis.ticks() : availableHeight / 36 ) + .tickSize( -availableWidth, 0); + + g.select('.nv-y.nv-axis') + .call(yAxis); + } + + + if (showDistX) { + distX + .getData(scatter.x()) + .scale(x) + .width(availableWidth) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled })); + gEnter.select('.nv-distWrap').append('g') + .attr('class', 'nv-distributionX'); + g.select('.nv-distributionX') + .attr('transform', 'translate(0,' + y.range()[0] + ')') + .datum(data.filter(function(d) { return !d.disabled })) + .call(distX); + } + + if (showDistY) { + distY + .getData(scatter.y()) + .scale(y) + .width(availableHeight) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled })); + gEnter.select('.nv-distWrap').append('g') + .attr('class', 'nv-distributionY'); + g.select('.nv-distributionY') + .attr('transform', 'translate(' + (rightAlignYAxis ? availableWidth : -distY.size() ) + ',0)') + .datum(data.filter(function(d) { return !d.disabled })) + .call(distY); + } + + //------------------------------------------------------------ + + + + + if (d3.fisheye) { + g.select('.nv-background') + .attr('width', availableWidth) + .attr('height', availableHeight) + ; + + g.select('.nv-background').on('mousemove', updateFisheye); + g.select('.nv-background').on('click', function() { pauseFisheye = !pauseFisheye;}); + scatter.dispatch.on('elementClick.freezeFisheye', function() { + pauseFisheye = !pauseFisheye; + }); + } + + + function updateFisheye() { + if (pauseFisheye) { + g.select('.nv-point-paths').style('pointer-events', 'all'); + return false; + } + + g.select('.nv-point-paths').style('pointer-events', 'none' ); + + var mouse = d3.mouse(this); + x.distortion(fisheye).focus(mouse[0]); + y.distortion(fisheye).focus(mouse[1]); + + g.select('.nv-scatterWrap') + .datum(data.filter(function(d) { return !d.disabled })) + .call(scatter); + + if (showXAxis) + g.select('.nv-x.nv-axis').call(xAxis); + + if (showYAxis) + g.select('.nv-y.nv-axis').call(yAxis); + + g.select('.nv-distributionX') + .datum(data.filter(function(d) { return !d.disabled })) + .call(distX); + g.select('.nv-distributionY') + .datum(data.filter(function(d) { return !d.disabled })) + .call(distY); + } + + + + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ + + controls.dispatch.on('legendClick', function(d,i) { + d.disabled = !d.disabled; + + fisheye = d.disabled ? 0 : 2.5; + g.select('.nv-background') .style('pointer-events', d.disabled ? 'none' : 'all'); + g.select('.nv-point-paths').style('pointer-events', d.disabled ? 'all' : 'none' ); + + if (d.disabled) { + x.distortion(fisheye).focus(0); + y.distortion(fisheye).focus(0); + + g.select('.nv-scatterWrap').call(scatter); + g.select('.nv-x.nv-axis').call(xAxis); + g.select('.nv-y.nv-axis').call(yAxis); + } else { + pauseFisheye = false; + } + + chart.update(); + }); + + legend.dispatch.on('stateChange', function(newState) { + state = newState; + dispatch.stateChange(state); + chart.update(); + }); + + + scatter.dispatch.on('elementMouseover.tooltip', function(e) { + d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-distx-' + e.pointIndex) + .attr('y1', e.pos[1] - availableHeight); + d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-disty-' + e.pointIndex) + .attr('x2', e.pos[0] + distX.size()); + + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top]; + dispatch.tooltipShow(e); + }); + + dispatch.on('tooltipShow', function(e) { + if (tooltips) showTooltip(e, that.parentNode); + }); + + // Update chart from a state object passed to event handler + dispatch.on('changeState', function(e) { + + if (typeof e.disabled !== 'undefined') { + data.forEach(function(series,i) { + series.disabled = e.disabled[i]; + }); + + state.disabled = e.disabled; + } + + chart.update(); + }); + + //============================================================ + + + //store old scales for use in transitions on update + x0 = x.copy(); + y0 = y.copy(); + + + }); + + return chart; + } + + + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ + + scatter.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + + d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-distx-' + e.pointIndex) + .attr('y1', 0); + d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-disty-' + e.pointIndex) + .attr('x2', distY.size()); + }); + dispatch.on('tooltipHide', function() { + if (tooltips) nv.tooltip.cleanup(); + }); + + //============================================================ + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + // expose chart's sub-components + chart.dispatch = dispatch; + chart.scatter = scatter; + chart.legend = legend; + chart.controls = controls; + chart.xAxis = xAxis; + chart.yAxis = yAxis; + chart.distX = distX; + chart.distY = distY; + + d3.rebind(chart, scatter, 'id', 'interactive', 'pointActive', 'x', 'y', 'shape', 'size', 'xScale', 'yScale', 'zScale', 'xDomain', 'yDomain', 'xRange', 'yRange', 'sizeDomain', 'sizeRange', 'forceX', 'forceY', 'forceSize', 'clipVoronoi', 'clipRadius', 'useVoronoi'); + + chart.options = nv.utils.optionsFunc.bind(chart); + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + legend.color(color); + distX.color(color); + distY.color(color); + return chart; + }; + + chart.showDistX = function(_) { + if (!arguments.length) return showDistX; + showDistX = _; + return chart; + }; + + chart.showDistY = function(_) { + if (!arguments.length) return showDistY; + showDistY = _; + return chart; + }; + + chart.showControls = function(_) { + if (!arguments.length) return showControls; + showControls = _; + return chart; + }; + + chart.showLegend = function(_) { + if (!arguments.length) return showLegend; + showLegend = _; + return chart; + }; + + chart.showXAxis = function(_) { + if (!arguments.length) return showXAxis; + showXAxis = _; + return chart; + }; + + chart.showYAxis = function(_) { + if (!arguments.length) return showYAxis; + showYAxis = _; + return chart; + }; + + chart.rightAlignYAxis = function(_) { + if(!arguments.length) return rightAlignYAxis; + rightAlignYAxis = _; + yAxis.orient( (_) ? 'right' : 'left'); + return chart; + }; + + chart.fisheye = function(_) { + if (!arguments.length) return fisheye; + fisheye = _; + return chart; + }; + + chart.tooltips = function(_) { + if (!arguments.length) return tooltips; + tooltips = _; + return chart; + }; + + chart.tooltipContent = function(_) { + if (!arguments.length) return tooltip; + tooltip = _; + return chart; + }; + + chart.tooltipXContent = function(_) { + if (!arguments.length) return tooltipX; + tooltipX = _; + return chart; + }; + + chart.tooltipYContent = function(_) { + if (!arguments.length) return tooltipY; + tooltipY = _; + return chart; + }; + + chart.state = function(_) { + if (!arguments.length) return state; + state = _; + return chart; + }; + + chart.defaultState = function(_) { + if (!arguments.length) return defaultState; + defaultState = _; + return chart; + }; + + chart.noData = function(_) { + if (!arguments.length) return noData; + noData = _; + return chart; + }; + + chart.transitionDuration = function(_) { + if (!arguments.length) return transitionDuration; + transitionDuration = _; + return chart; + }; + + //============================================================ + + + return chart; +} + +nv.models.sparkline = function() { + "use strict"; + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var margin = {top: 2, right: 0, bottom: 2, left: 0} + , width = 400 + , height = 32 + , animate = true + , x = d3.scale.linear() + , y = d3.scale.linear() + , getX = function(d) { return d.x } + , getY = function(d) { return d.y } + , color = nv.utils.getColor(['#000']) + , xDomain + , yDomain + , xRange + , yRange + ; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var availableWidth = width - margin.left - margin.right, + availableHeight = height - margin.top - margin.bottom, + container = d3.select(this); + + + //------------------------------------------------------------ + // Setup Scales + + x .domain(xDomain || d3.extent(data, getX )) + .range(xRange || [0, availableWidth]); + + y .domain(yDomain || d3.extent(data, getY )) + .range(yRange || [availableHeight, 0]); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-sparkline').data([data]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sparkline'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')') + + //------------------------------------------------------------ + + + var paths = wrap.selectAll('path') + .data(function(d) { return [d] }); + paths.enter().append('path'); + paths.exit().remove(); + paths + .style('stroke', function(d,i) { return d.color || color(d, i) }) + .attr('d', d3.svg.line() + .x(function(d,i) { return x(getX(d,i)) }) + .y(function(d,i) { return y(getY(d,i)) }) + ); + + + // TODO: Add CURRENT data point (Need Min, Mac, Current / Most recent) + var points = wrap.selectAll('circle.nv-point') + .data(function(data) { + var yValues = data.map(function(d, i) { return getY(d,i); }); + function pointIndex(index) { + if (index != -1) { + var result = data[index]; + result.pointIndex = index; + return result; + } else { + return null; + } + } + var maxPoint = pointIndex(yValues.lastIndexOf(y.domain()[1])), + minPoint = pointIndex(yValues.indexOf(y.domain()[0])), + currentPoint = pointIndex(yValues.length - 1); + return [minPoint, maxPoint, currentPoint].filter(function (d) {return d != null;}); + }); + points.enter().append('circle'); + points.exit().remove(); + points + .attr('cx', function(d,i) { return x(getX(d,d.pointIndex)) }) + .attr('cy', function(d,i) { return y(getY(d,d.pointIndex)) }) + .attr('r', 2) + .attr('class', function(d,i) { + return getX(d, d.pointIndex) == x.domain()[1] ? 'nv-point nv-currentValue' : + getY(d, d.pointIndex) == y.domain()[0] ? 'nv-point nv-minValue' : 'nv-point nv-maxValue' + }); + }); + + return chart; + } + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + chart.options = nv.utils.optionsFunc.bind(chart); + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.x = function(_) { + if (!arguments.length) return getX; + getX = d3.functor(_); + return chart; + }; + + chart.y = function(_) { + if (!arguments.length) return getY; + getY = d3.functor(_); + return chart; + }; + + chart.xScale = function(_) { + if (!arguments.length) return x; + x = _; + return chart; + }; + + chart.yScale = function(_) { + if (!arguments.length) return y; + y = _; + return chart; + }; + + chart.xDomain = function(_) { + if (!arguments.length) return xDomain; + xDomain = _; + return chart; + }; + + chart.yDomain = function(_) { + if (!arguments.length) return yDomain; + yDomain = _; + return chart; + }; + + chart.xRange = function(_) { + if (!arguments.length) return xRange; + xRange = _; + return chart; + }; + + chart.yRange = function(_) { + if (!arguments.length) return yRange; + yRange = _; + return chart; + }; + + chart.animate = function(_) { + if (!arguments.length) return animate; + animate = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + return chart; + }; + + //============================================================ + + + return chart; +} + +nv.models.sparklinePlus = function() { + "use strict"; + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var sparkline = nv.models.sparkline(); + + var margin = {top: 15, right: 100, bottom: 10, left: 50} + , width = null + , height = null + , x + , y + , index = [] + , paused = false + , xTickFormat = d3.format(',r') + , yTickFormat = d3.format(',.2f') + , showValue = true + , alignValue = true + , rightAlignValue = false + , noData = "No Data Available." + ; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var container = d3.select(this); + + var availableWidth = (width || parseInt(container.style('width')) || 960) + - margin.left - margin.right, + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + + + + chart.update = function() { chart(selection) }; + chart.container = this; + + + //------------------------------------------------------------ + // Display No Data message if there's nothing to show. + + if (!data || !data.length) { + var noDataText = container.selectAll('.nv-noData').data([noData]); + + noDataText.enter().append('text') + .attr('class', 'nvd3 nv-noData') + .attr('dy', '-.7em') + .style('text-anchor', 'middle'); + + noDataText + .attr('x', margin.left + availableWidth / 2) + .attr('y', margin.top + availableHeight / 2) + .text(function(d) { return d }); + + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } + + var currentValue = sparkline.y()(data[data.length-1], data.length-1); + + //------------------------------------------------------------ + + + + //------------------------------------------------------------ + // Setup Scales + + x = sparkline.xScale(); + y = sparkline.yScale(); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-sparklineplus').data([data]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sparklineplus'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-sparklineWrap'); + gEnter.append('g').attr('class', 'nv-valueWrap'); + gEnter.append('g').attr('class', 'nv-hoverArea'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Main Chart Component(s) + + var sparklineWrap = g.select('.nv-sparklineWrap'); + + sparkline + .width(availableWidth) + .height(availableHeight); + + sparklineWrap + .call(sparkline); + + //------------------------------------------------------------ + + + var valueWrap = g.select('.nv-valueWrap'); + + var value = valueWrap.selectAll('.nv-currentValue') + .data([currentValue]); + + value.enter().append('text').attr('class', 'nv-currentValue') + .attr('dx', rightAlignValue ? -8 : 8) + .attr('dy', '.9em') + .style('text-anchor', rightAlignValue ? 'end' : 'start'); + + value + .attr('x', availableWidth + (rightAlignValue ? margin.right : 0)) + .attr('y', alignValue ? function(d) { return y(d) } : 0) + .style('fill', sparkline.color()(data[data.length-1], data.length-1)) + .text(yTickFormat(currentValue)); + + + + gEnter.select('.nv-hoverArea').append('rect') + .on('mousemove', sparklineHover) + .on('click', function() { paused = !paused }) + .on('mouseout', function() { index = []; updateValueLine(); }); + //.on('mouseout', function() { index = null; updateValueLine(); }); + + g.select('.nv-hoverArea rect') + .attr('transform', function(d) { return 'translate(' + -margin.left + ',' + -margin.top + ')' }) + .attr('width', availableWidth + margin.left + margin.right) + .attr('height', availableHeight + margin.top); + + + + function updateValueLine() { //index is currently global (within the chart), may or may not keep it that way + if (paused) return; + + var hoverValue = g.selectAll('.nv-hoverValue').data(index) + + var hoverEnter = hoverValue.enter() + .append('g').attr('class', 'nv-hoverValue') + .style('stroke-opacity', 0) + .style('fill-opacity', 0); + + hoverValue.exit() + .transition().duration(250) + .style('stroke-opacity', 0) + .style('fill-opacity', 0) + .remove(); + + hoverValue + .attr('transform', function(d) { return 'translate(' + x(sparkline.x()(data[d],d)) + ',0)' }) + .transition().duration(250) + .style('stroke-opacity', 1) + .style('fill-opacity', 1); + + if (!index.length) return; + + hoverEnter.append('line') + .attr('x1', 0) + .attr('y1', -margin.top) + .attr('x2', 0) + .attr('y2', availableHeight); + + + hoverEnter.append('text').attr('class', 'nv-xValue') + .attr('x', -6) + .attr('y', -margin.top) + .attr('text-anchor', 'end') + .attr('dy', '.9em') + + + g.select('.nv-hoverValue .nv-xValue') + .text(xTickFormat(sparkline.x()(data[index[0]], index[0]))); + + hoverEnter.append('text').attr('class', 'nv-yValue') + .attr('x', 6) + .attr('y', -margin.top) + .attr('text-anchor', 'start') + .attr('dy', '.9em') + + g.select('.nv-hoverValue .nv-yValue') + .text(yTickFormat(sparkline.y()(data[index[0]], index[0]))); + + } + + + function sparklineHover() { + if (paused) return; + + var pos = d3.mouse(this)[0] - margin.left; + + function getClosestIndex(data, x) { + var distance = Math.abs(sparkline.x()(data[0], 0) - x); + var closestIndex = 0; + for (var i = 0; i < data.length; i++){ + if (Math.abs(sparkline.x()(data[i], i) - x) < distance) { + distance = Math.abs(sparkline.x()(data[i], i) - x); + closestIndex = i; + } + } + return closestIndex; + } + + index = [getClosestIndex(data, Math.round(x.invert(pos)))]; + + updateValueLine(); + } + + }); + + return chart; + } + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + // expose chart's sub-components + chart.sparkline = sparkline; + + d3.rebind(chart, sparkline, 'x', 'y', 'xScale', 'yScale', 'color'); + + chart.options = nv.utils.optionsFunc.bind(chart); + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.xTickFormat = function(_) { + if (!arguments.length) return xTickFormat; + xTickFormat = _; + return chart; + }; + + chart.yTickFormat = function(_) { + if (!arguments.length) return yTickFormat; + yTickFormat = _; + return chart; + }; + + chart.showValue = function(_) { + if (!arguments.length) return showValue; + showValue = _; + return chart; + }; + + chart.alignValue = function(_) { + if (!arguments.length) return alignValue; + alignValue = _; + return chart; + }; + + chart.rightAlignValue = function(_) { + if (!arguments.length) return rightAlignValue; + rightAlignValue = _; + return chart; + }; + + chart.noData = function(_) { + if (!arguments.length) return noData; + noData = _; + return chart; + }; + + //============================================================ + + + return chart; +} + +nv.models.stackedArea = function() { + "use strict"; + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var margin = {top: 0, right: 0, bottom: 0, left: 0} + , width = 960 + , height = 500 + , color = nv.utils.defaultColor() // a function that computes the color + , id = Math.floor(Math.random() * 100000) //Create semi-unique ID incase user doesn't selet one + , getX = function(d) { return d.x } // accessor to get the x value from a data point + , getY = function(d) { return d.y } // accessor to get the y value from a data point + , style = 'stack' + , offset = 'zero' + , order = 'default' + , interpolate = 'linear' // controls the line interpolation + , clipEdge = false // if true, masks lines within x and y scale + , x //can be accessed via chart.xScale() + , y //can be accessed via chart.yScale() + , scatter = nv.models.scatter() + , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'areaClick', 'areaMouseover', 'areaMouseout') + ; + + scatter + .size(2.2) // default size + .sizeDomain([2.2,2.2]) // all the same size by default + ; + + /************************************ + * offset: + * 'wiggle' (stream) + * 'zero' (stacked) + * 'expand' (normalize to 100%) + * 'silhouette' (simple centered) + * + * order: + * 'inside-out' (stream) + * 'default' (input order) + ************************************/ + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var availableWidth = width - margin.left - margin.right, + availableHeight = height - margin.top - margin.bottom, + container = d3.select(this); + + //------------------------------------------------------------ + // Setup Scales + + x = scatter.xScale(); + y = scatter.yScale(); + + //------------------------------------------------------------ + + var dataRaw = data; + // Injecting point index into each point because d3.layout.stack().out does not give index + data.forEach(function(aseries, i) { + aseries.seriesIndex = i; + aseries.values = aseries.values.map(function(d, j) { + d.index = j; + d.seriesIndex = i; + return d; + }); + }); + + var dataFiltered = data.filter(function(series) { + return !series.disabled; + }); + + data = d3.layout.stack() + .order(order) + .offset(offset) + .values(function(d) { return d.values }) //TODO: make values customizeable in EVERY model in this fashion + .x(getX) + .y(getY) + .out(function(d, y0, y) { + var yHeight = (getY(d) === 0) ? 0 : y; + d.display = { + y: yHeight, + y0: y0 + }; + }) + (dataFiltered); + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-stackedarea').data([data]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-stackedarea'); + var defsEnter = wrapEnter.append('defs'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-areaWrap'); + gEnter.append('g').attr('class', 'nv-scatterWrap'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + //------------------------------------------------------------ + + + scatter + .width(availableWidth) + .height(availableHeight) + .x(getX) + .y(function(d) { return d.display.y + d.display.y0 }) + .forceY([0]) + .color(data.map(function(d,i) { + return d.color || color(d, d.seriesIndex); + })); + + + var scatterWrap = g.select('.nv-scatterWrap') + .datum(data); + + scatterWrap.call(scatter); + + defsEnter.append('clipPath') + .attr('id', 'nv-edge-clip-' + id) + .append('rect'); + + wrap.select('#nv-edge-clip-' + id + ' rect') + .attr('width', availableWidth) + .attr('height', availableHeight); + + g .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : ''); + + var area = d3.svg.area() + .x(function(d,i) { return x(getX(d,i)) }) + .y0(function(d) { + return y(d.display.y0) + }) + .y1(function(d) { + return y(d.display.y + d.display.y0) + }) + .interpolate(interpolate); + + var zeroArea = d3.svg.area() + .x(function(d,i) { return x(getX(d,i)) }) + .y0(function(d) { return y(d.display.y0) }) + .y1(function(d) { return y(d.display.y0) }); + + + var path = g.select('.nv-areaWrap').selectAll('path.nv-area') + .data(function(d) { return d }); + + path.enter().append('path').attr('class', function(d,i) { return 'nv-area nv-area-' + i }) + .attr('d', function(d,i){ + return zeroArea(d.values, d.seriesIndex); + }) + .on('mouseover', function(d,i) { + d3.select(this).classed('hover', true); + dispatch.areaMouseover({ + point: d, + series: d.key, + pos: [d3.event.pageX, d3.event.pageY], + seriesIndex: i + }); + }) + .on('mouseout', function(d,i) { + d3.select(this).classed('hover', false); + dispatch.areaMouseout({ + point: d, + series: d.key, + pos: [d3.event.pageX, d3.event.pageY], + seriesIndex: i + }); + }) + .on('click', function(d,i) { + d3.select(this).classed('hover', false); + dispatch.areaClick({ + point: d, + series: d.key, + pos: [d3.event.pageX, d3.event.pageY], + seriesIndex: i + }); + }) + path.exit().transition() + .attr('d', function(d,i) { return zeroArea(d.values,i) }) + .remove(); + path + .style('fill', function(d,i){ + return d.color || color(d, d.seriesIndex) + }) + .style('stroke', function(d,i){ return d.color || color(d, d.seriesIndex) }); + path.transition() + .attr('d', function(d,i) { + return area(d.values,i) + }); + + + + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ + + scatter.dispatch.on('elementMouseover.area', function(e) { + g.select('.nv-chart-' + id + ' .nv-area-' + e.seriesIndex).classed('hover', true); + }); + scatter.dispatch.on('elementMouseout.area', function(e) { + g.select('.nv-chart-' + id + ' .nv-area-' + e.seriesIndex).classed('hover', false); + }); + + //============================================================ + //Special offset functions + chart.d3_stackedOffset_stackPercent = function(stackData) { + var n = stackData.length, //How many series + m = stackData[0].length, //how many points per series + k = 1 / n, + i, + j, + o, + y0 = []; + + for (j = 0; j < m; ++j) { //Looping through all points + for (i = 0, o = 0; i < dataRaw.length; i++) //looping through series' + o += getY(dataRaw[i].values[j]) //total value of all points at a certian point in time. + + if (o) for (i = 0; i < n; i++) + stackData[i][j][1] /= o; + else + for (i = 0; i < n; i++) + stackData[i][j][1] = k; + } + for (j = 0; j < m; ++j) y0[j] = 0; + return y0; + }; + + }); + + + return chart; + } + + + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ + + scatter.dispatch.on('elementClick.area', function(e) { + dispatch.areaClick(e); + }) + scatter.dispatch.on('elementMouseover.tooltip', function(e) { + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top], + dispatch.tooltipShow(e); + }); + scatter.dispatch.on('elementMouseout.tooltip', function(e) { + dispatch.tooltipHide(e); + }); + + //============================================================ + + //============================================================ + // Global getters and setters + //------------------------------------------------------------ + + chart.dispatch = dispatch; + chart.scatter = scatter; + + d3.rebind(chart, scatter, 'interactive', 'size', 'xScale', 'yScale', 'zScale', 'xDomain', 'yDomain', 'xRange', 'yRange', + 'sizeDomain', 'forceX', 'forceY', 'forceSize', 'clipVoronoi', 'useVoronoi','clipRadius','highlightPoint','clearHighlights'); + + chart.options = nv.utils.optionsFunc.bind(chart); + + chart.x = function(_) { + if (!arguments.length) return getX; + getX = d3.functor(_); + return chart; + }; + + chart.y = function(_) { + if (!arguments.length) return getY; + getY = d3.functor(_); + return chart; + } + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.clipEdge = function(_) { + if (!arguments.length) return clipEdge; + clipEdge = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + return chart; + }; + + chart.offset = function(_) { + if (!arguments.length) return offset; + offset = _; + return chart; + }; + + chart.order = function(_) { + if (!arguments.length) return order; + order = _; + return chart; + }; + + //shortcut for offset + order + chart.style = function(_) { + if (!arguments.length) return style; + style = _; + + switch (style) { + case 'stack': + chart.offset('zero'); + chart.order('default'); + break; + case 'stream': + chart.offset('wiggle'); + chart.order('inside-out'); + break; + case 'stream-center': + chart.offset('silhouette'); + chart.order('inside-out'); + break; + case 'expand': + chart.offset('expand'); + chart.order('default'); + break; + case 'stack_percent': + chart.offset(chart.d3_stackedOffset_stackPercent); + chart.order('default'); + break; + } + + return chart; + }; + + chart.interpolate = function(_) { + if (!arguments.length) return interpolate; + interpolate = _; + return chart; + }; + //============================================================ + + + return chart; +} + +nv.models.stackedAreaChart = function() { + "use strict"; + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var stacked = nv.models.stackedArea() + , xAxis = nv.models.axis() + , yAxis = nv.models.axis() + , legend = nv.models.legend() + , controls = nv.models.legend() + , interactiveLayer = nv.interactiveGuideline() + ; + + var margin = {top: 30, right: 25, bottom: 50, left: 60} + , width = null + , height = null + , color = nv.utils.defaultColor() // a function that takes in d, i and returns color + , showControls = true + , showLegend = true + , showXAxis = true + , showYAxis = true + , rightAlignYAxis = false + , useInteractiveGuideline = false + , tooltips = true + , tooltip = function(key, x, y, e, graph) { + return '<h3>' + key + '</h3>' + + '<p>' + y + ' on ' + x + '</p>' + } + , x //can be accessed via chart.xScale() + , y //can be accessed via chart.yScale() + , yAxisTickFormat = d3.format(',.2f') + , state = { style: stacked.style() } + , defaultState = null + , noData = 'No Data Available.' + , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState') + , controlWidth = 250 + , cData = ['Stacked','Stream','Expanded'] + , controlLabels = {} + , transitionDuration = 250 + ; + + xAxis + .orient('bottom') + .tickPadding(7) + ; + yAxis + .orient((rightAlignYAxis) ? 'right' : 'left') + ; + + controls.updateState(false); + //============================================================ + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var showTooltip = function(e, offsetElement) { + var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ), + top = e.pos[1] + ( offsetElement.offsetTop || 0), + x = xAxis.tickFormat()(stacked.x()(e.point, e.pointIndex)), + y = yAxis.tickFormat()(stacked.y()(e.point, e.pointIndex)), + content = tooltip(e.series.key, x, y, e, chart); + + nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement); + }; + + //============================================================ + + + function chart(selection) { + selection.each(function(data) { + var container = d3.select(this), + that = this; + + var availableWidth = (width || parseInt(container.style('width')) || 960) + - margin.left - margin.right, + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + + chart.update = function() { container.transition().duration(transitionDuration).call(chart); }; + chart.container = this; + + //set state.disabled + state.disabled = data.map(function(d) { return !!d.disabled }); + + if (!defaultState) { + var key; + defaultState = {}; + for (key in state) { + if (state[key] instanceof Array) + defaultState[key] = state[key].slice(0); + else + defaultState[key] = state[key]; + } + } + + //------------------------------------------------------------ + // Display No Data message if there's nothing to show. + + if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { + var noDataText = container.selectAll('.nv-noData').data([noData]); + + noDataText.enter().append('text') + .attr('class', 'nvd3 nv-noData') + .attr('dy', '-.7em') + .style('text-anchor', 'middle'); + + noDataText + .attr('x', margin.left + availableWidth / 2) + .attr('y', margin.top + availableHeight / 2) + .text(function(d) { return d }); + + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup Scales + + x = stacked.xScale(); + y = stacked.yScale(); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-wrap.nv-stackedAreaChart').data([data]); + var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-stackedAreaChart').append('g'); + var g = wrap.select('g'); + + gEnter.append("rect").style("opacity",0); + gEnter.append('g').attr('class', 'nv-x nv-axis'); + gEnter.append('g').attr('class', 'nv-y nv-axis'); + gEnter.append('g').attr('class', 'nv-stackedWrap'); + gEnter.append('g').attr('class', 'nv-legendWrap'); + gEnter.append('g').attr('class', 'nv-controlsWrap'); + gEnter.append('g').attr('class', 'nv-interactive'); + + g.select("rect").attr("width",availableWidth).attr("height",availableHeight); + //------------------------------------------------------------ + // Legend + + if (showLegend) { + var legendWidth = (showControls) ? availableWidth - controlWidth : availableWidth; + legend + .width(legendWidth); + + g.select('.nv-legendWrap') + .datum(data) + .call(legend); + + if ( margin.top != legend.height()) { + margin.top = legend.height(); + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + } + + g.select('.nv-legendWrap') + .attr('transform', 'translate(' + (availableWidth-legendWidth) + ',' + (-margin.top) +')'); + } + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Controls + + if (showControls) { + var controlsData = [ + { + key: controlLabels.stacked || 'Stacked', + metaKey: 'Stacked', + disabled: stacked.style() != 'stack', + style: 'stack' + }, + { + key: controlLabels.stream || 'Stream', + metaKey: 'Stream', + disabled: stacked.style() != 'stream', + style: 'stream' + }, + { + key: controlLabels.expanded || 'Expanded', + metaKey: 'Expanded', + disabled: stacked.style() != 'expand', + style: 'expand' + }, + { + key: controlLabels.stack_percent || 'Stack %', + metaKey: 'Stack_Percent', + disabled: stacked.style() != 'stack_percent', + style: 'stack_percent' + } + ]; + + controlWidth = (cData.length/3) * 260; + + controlsData = controlsData.filter(function(d) { + return cData.indexOf(d.metaKey) !== -1; + }) + + controls + .width( controlWidth ) + .color(['#444', '#444', '#444']); + + g.select('.nv-controlsWrap') + .datum(controlsData) + .call(controls); + + + if ( margin.top != Math.max(controls.height(), legend.height()) ) { + margin.top = Math.max(controls.height(), legend.height()); + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + } + + + g.select('.nv-controlsWrap') + .attr('transform', 'translate(0,' + (-margin.top) +')'); + } + + //------------------------------------------------------------ + + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + if (rightAlignYAxis) { + g.select(".nv-y.nv-axis") + .attr("transform", "translate(" + availableWidth + ",0)"); + } + + //------------------------------------------------------------ + // Main Chart Component(s) + + //------------------------------------------------------------ + //Set up interactive layer + if (useInteractiveGuideline) { + interactiveLayer + .width(availableWidth) + .height(availableHeight) + .margin({left: margin.left, top: margin.top}) + .svgContainer(container) + .xScale(x); + wrap.select(".nv-interactive").call(interactiveLayer); + } + + stacked + .width(availableWidth) + .height(availableHeight) + + var stackedWrap = g.select('.nv-stackedWrap') + .datum(data); + + stackedWrap.transition().call(stacked); + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup Axes + + if (showXAxis) { + xAxis + .scale(x) + .ticks( availableWidth / 100 ) + .tickSize( -availableHeight, 0); + + g.select('.nv-x.nv-axis') + .attr('transform', 'translate(0,' + availableHeight + ')'); + + g.select('.nv-x.nv-axis') + .transition().duration(0) + .call(xAxis); + } + + if (showYAxis) { + yAxis + .scale(y) + .ticks(stacked.offset() == 'wiggle' ? 0 : availableHeight / 36) + .tickSize(-availableWidth, 0) + .setTickFormat( (stacked.style() == 'expand' || stacked.style() == 'stack_percent') + ? d3.format('%') : yAxisTickFormat); + + g.select('.nv-y.nv-axis') + .transition().duration(0) + .call(yAxis); + } + + //------------------------------------------------------------ + + + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ + + stacked.dispatch.on('areaClick.toggle', function(e) { + if (data.filter(function(d) { return !d.disabled }).length === 1) + data.forEach(function(d) { + d.disabled = false; + }); + else + data.forEach(function(d,i) { + d.disabled = (i != e.seriesIndex); + }); + + state.disabled = data.map(function(d) { return !!d.disabled }); + dispatch.stateChange(state); + + chart.update(); + }); + + legend.dispatch.on('stateChange', function(newState) { + state.disabled = newState.disabled; + dispatch.stateChange(state); + chart.update(); + }); + + controls.dispatch.on('legendClick', function(d,i) { + if (!d.disabled) return; + + controlsData = controlsData.map(function(s) { + s.disabled = true; + return s; + }); + d.disabled = false; + + stacked.style(d.style); + + + state.style = stacked.style(); + dispatch.stateChange(state); + + chart.update(); + }); + + + interactiveLayer.dispatch.on('elementMousemove', function(e) { + stacked.clearHighlights(); + var singlePoint, pointIndex, pointXLocation, allData = []; + data + .filter(function(series, i) { + series.seriesIndex = i; + return !series.disabled; + }) + .forEach(function(series,i) { + pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x()); + stacked.highlightPoint(i, pointIndex, true); + var point = series.values[pointIndex]; + if (typeof point === 'undefined') return; + if (typeof singlePoint === 'undefined') singlePoint = point; + if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex)); + + //If we are in 'expand' mode, use the stacked percent value instead of raw value. + var tooltipValue = (stacked.style() == 'expand') ? point.display.y : chart.y()(point,pointIndex); + allData.push({ + key: series.key, + value: tooltipValue, + color: color(series,series.seriesIndex), + stackedValue: point.display + }); + }); + + allData.reverse(); + + //Highlight the tooltip entry based on which stack the mouse is closest to. + if (allData.length > 2) { + var yValue = chart.yScale().invert(e.mouseY); + var yDistMax = Infinity, indexToHighlight = null; + allData.forEach(function(series,i) { + if ( yValue >= series.stackedValue.y0 && yValue <= (series.stackedValue.y0 + series.stackedValue.y)) + { + indexToHighlight = i; + return; + } + }); + if (indexToHighlight != null) + allData[indexToHighlight].highlight = true; + } + + var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex)); + + //If we are in 'expand' mode, force the format to be a percentage. + var valueFormatter = (stacked.style() == 'expand') ? + function(d,i) {return d3.format(".1%")(d);} : + function(d,i) {return yAxis.tickFormat()(d); }; + interactiveLayer.tooltip + .position({left: pointXLocation + margin.left, top: e.mouseY + margin.top}) + .chartContainer(that.parentNode) + .enabled(tooltips) + .valueFormatter(valueFormatter) + .data( + { + value: xValue, + series: allData + } + )(); + + interactiveLayer.renderGuideLine(pointXLocation); + + }); + + interactiveLayer.dispatch.on("elementMouseout",function(e) { + dispatch.tooltipHide(); + stacked.clearHighlights(); + }); + + + dispatch.on('tooltipShow', function(e) { + if (tooltips) showTooltip(e, that.parentNode); + }); + + // Update chart from a state object passed to event handler + dispatch.on('changeState', function(e) { + + if (typeof e.disabled !== 'undefined') { + data.forEach(function(series,i) { + series.disabled = e.disabled[i]; + }); + + state.disabled = e.disabled; + } + + if (typeof e.style !== 'undefined') { + stacked.style(e.style); + } + + chart.update(); + }); + + }); + + + return chart; + } + + + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ + + stacked.dispatch.on('tooltipShow', function(e) { + //disable tooltips when value ~= 0 + //// TODO: consider removing points from voronoi that have 0 value instead of this hack + /* + if (!Math.round(stacked.y()(e.point) * 100)) { // 100 will not be good for very small numbers... will have to think about making this valu dynamic, based on data range + setTimeout(function() { d3.selectAll('.point.hover').classed('hover', false) }, 0); + return false; + } + */ + + e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top], + dispatch.tooltipShow(e); + }); + + stacked.dispatch.on('tooltipHide', function(e) { + dispatch.tooltipHide(e); + }); + + dispatch.on('tooltipHide', function() { + if (tooltips) nv.tooltip.cleanup(); + }); + + //============================================================ + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + // expose chart's sub-components + chart.dispatch = dispatch; + chart.stacked = stacked; + chart.legend = legend; + chart.controls = controls; + chart.xAxis = xAxis; + chart.yAxis = yAxis; + chart.interactiveLayer = interactiveLayer; + + d3.rebind(chart, stacked, 'x', 'y', 'size', 'xScale', 'yScale', 'xDomain', 'yDomain', 'xRange', 'yRange', 'sizeDomain', 'interactive', 'useVoronoi', 'offset', 'order', 'style', 'clipEdge', 'forceX', 'forceY', 'forceSize', 'interpolate'); + + chart.options = nv.utils.optionsFunc.bind(chart); + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.height = function(_) { + if (!arguments.length) return height; + height = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + legend.color(color); + stacked.color(color); + return chart; + }; + + chart.showControls = function(_) { + if (!arguments.length) return showControls; + showControls = _; + return chart; + }; + + chart.showLegend = function(_) { + if (!arguments.length) return showLegend; + showLegend = _; + return chart; + }; + + chart.showXAxis = function(_) { + if (!arguments.length) return showXAxis; + showXAxis = _; + return chart; + }; + + chart.showYAxis = function(_) { + if (!arguments.length) return showYAxis; + showYAxis = _; + return chart; + }; + + chart.rightAlignYAxis = function(_) { + if(!arguments.length) return rightAlignYAxis; + rightAlignYAxis = _; + yAxis.orient( (_) ? 'right' : 'left'); + return chart; + }; + + chart.useInteractiveGuideline = function(_) { + if(!arguments.length) return useInteractiveGuideline; + useInteractiveGuideline = _; + if (_ === true) { + chart.interactive(false); + chart.useVoronoi(false); + } + return chart; + }; + + chart.tooltip = function(_) { + if (!arguments.length) return tooltip; + tooltip = _; + return chart; + }; + + chart.tooltips = function(_) { + if (!arguments.length) return tooltips; + tooltips = _; + return chart; + }; + + chart.tooltipContent = function(_) { + if (!arguments.length) return tooltip; + tooltip = _; + return chart; + }; + + chart.state = function(_) { + if (!arguments.length) return state; + state = _; + return chart; + }; + + chart.defaultState = function(_) { + if (!arguments.length) return defaultState; + defaultState = _; + return chart; + }; + + chart.noData = function(_) { + if (!arguments.length) return noData; + noData = _; + return chart; + }; + + chart.transitionDuration = function(_) { + if (!arguments.length) return transitionDuration; + transitionDuration = _; + return chart; + }; + + chart.controlsData = function(_) { + if (!arguments.length) return cData; + cData = _; + return chart; + }; + + chart.controlLabels = function(_) { + if (!arguments.length) return controlLabels; + if (typeof _ !== 'object') return controlLabels; + controlLabels = _; + return chart; + }; + + yAxis.setTickFormat = yAxis.tickFormat; + + yAxis.tickFormat = function(_) { + if (!arguments.length) return yAxisTickFormat; + yAxisTickFormat = _; + return yAxis; + }; + + + //============================================================ + + return chart; +} +})();
\ No newline at end of file diff --git a/addons/web_graph/static/src/css/flotr2.css b/addons/web_graph/static/src/css/flotr2.css deleted file mode 100644 index fae7b033811..00000000000 --- a/addons/web_graph/static/src/css/flotr2.css +++ /dev/null @@ -1,69 +0,0 @@ -/* Flotr Styles */ - -.flotr-datagrid-container { - border: 1px solid #999; - border-bottom: none; - background: #fff; -} -.flotr-datagrid { - border-collapse: collapse; - border-spacing: 0; -} -.flotr-datagrid td, .flotr-datagrid th { - border: 1px solid #ccc; - padding: 1px 3px; - min-width: 2em; -} -.flotr-datagrid tr:hover, .flotr-datagrid col.hover { - background: #f3f3f3; -} -.flotr-datagrid tr:hover th, .flotr-datagrid th.hover { - background: #999; - color: #fff; -} -.flotr-datagrid th { - text-align: left; - background: #e3e3e3; - border: 2px outset #fff; -} -.flotr-datagrid-toolbar { - padding: 1px; - border-bottom: 1px solid #ccc; - background: #f9f9f9; -} -.flotr-datagrid td:hover { - background: #ccc; -} -.flotr-datagrid .first-row th { - text-align: center; -} -.flotr-canvas { - margin-bottom: -3px; - padding-bottom: 1px; -} -.flotr-tabs-group { - border-top: 1px solid #999; -} -.flotr-tab { - border: 1px solid #666; - border-top: none; - margin: 0 3px; - padding: 1px 4px; - cursor: pointer; - -moz-border-radius: 0 0 4px 4px; - -webkit-border-bottom-left-radius: 4px; - -webkit-border-bottom-right-radius: 4px; - border-radius: 0 0 4px 4px; - opacity: 0.5; - -moz-opacity: 0.5; -} -.flotr-tab.selected { - background: #ddd; - opacity: 1; - -moz-opacity: 1; -} -.flotr-tab:hover { - background: #ccc; -} - - diff --git a/addons/web_graph/static/src/css/graph.css b/addons/web_graph/static/src/css/graph.css index 2fd4b5953cf..2213f655a6b 100644 --- a/addons/web_graph/static/src/css/graph.css +++ b/addons/web_graph/static/src/css/graph.css @@ -1,80 +1,118 @@ -.openerp a.dropdown-menu-icon { - z-index: 1; - position: absolute; - color: #4c4c4c; - right: 8px; +.graph_main_content { + position: relative; } -.openerp a.dropdown-menu-icon:hover { - text-decoration: none; + +.graph_main_content td { + font-size: 12px; + margin: 45px; + border-collapse: collapse; + vertical-align: middle; + border-bottom: 1px solid #ccc; + padding: 6px 8px; + text-align: right; + color: black; } -.editor-render { - position: relative; + +.graph_main_content td:first-child { + background-color: rgb(240, 238, 238); } -#editor-render-body { - position: relative; - width: 650px; - height: 350px; -} -.openerp .graph-dropdown { - display: none; - position: absolute; - top: 32px; - right: 8px; - padding: 8px; - border: 1px solid #afafb6; - background: white; - z-index: 1; - min-width: 160px; - overflow-x: hidden; - -moz-border-radius: 3px; - -webkit-border-radius: 3px; - border-radius: 3px; - -moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); - -webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); - -box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); -} -.openerp .graph-dropdown h3 { - margin: 8px 4px 4px 0; - color: #8786b7; + +.graph_main_content table { + margin-top: 10px; +} + +.graph_measure_selection .oe_selected { + padding-left: 0; +} + +.graph_measure_selection .oe_selected:before { + content: "\f00c"; + width: 20px; + display: inline-block; + font-family: FontAwesome; +} + +.graph_measure_selection li { + margin-left: 5px; +} + +.graph_main_content th { font-size: 13px; + font-weight: normal; + padding: 10px 8px; + vertical-align: top; + color: black; } -.openerp .graph-menu > li > a { - position: relative; - display: block; - padding: 2px 4px 2px 20px; - line-height: 12px; - color: #4c4c4c; - text-decoration: none; -} -.openerp .graph-menu > li > a:hover { - text-decoration: none; - background: #f0f0fa; - background: -moz-linear-gradient(#f0f0fa, #eeeef6); - background: -webkit-gradient(linear, left top, left bottom, from(#f0f0fa), to(#eeeef6)); - background: -webkit-linear-gradient(#f0f0fa, #eeeef6); - -moz-box-shadow: none; - -webkit-box-shadow: none; - -box-shadow: none; -} -.openerp .open .graph-dropdown { - display: block; -} - -.openerp .graph-menu { - padding: 0; - margin: 0; -} -.openerp .graph-menu .active:before { - content: "W"; - font-family: "entypoRegular" !important; - font-size: 24px; - font-weight: 300 !important; - color: #a3a3a3; - position: absolute; - left: 4px; - top: -2px; -} -.openerp .graph-menu li { - padding: 0; - list-style-type: none; + +.graph_main_content tbody td{ + border: 1px solid #b8b8b8; +} + +.graph_main_content tbody { + border-top: 1px solid black; +} + +.graph_main_content thead { + background-color: rgb(240, 238, 238); + border: 1px solid #b8b8b8; +} + +.graph_main_content thead th { + border-left: 1px solid #b8b8b8; +} + +div.graph_widget { + padding:10px; +} + +.oe_dashboard div.graph_widget { + padding:0; +} + +td.graph_border { + text-align: left; + white-space: nowrap; +} + +.graph_main_content table:not(.heatmap) tr:hover td:not(:first-child) +{ + background-color: rgb(245, 245, 245); +} + +span.field-selection { + display: none; + padding: 0; +} + +span.web_graph_indent { + padding-left: 30px; +} + +.web_graph_click:hover { + cursor: pointer; +} + +.graph_widget .dropdown-menu { + font-size: 13px; + padding: 4px 0; + background: #e8e8e8 !important; + border: 1px solid rgba(0, 0, 0, 0.15); + font-color: !important black; + text-shadow: none; + background-color: #e8e8e8; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + border-radius: 3px; +} + +.graph_widget .dropdown-menu li a, .graph_widget .dropdown-menu li a:hover, .graph_widget .dropdown-menu li a:focus { + color: black; + text-decoration: none; +} + +th.measure_row { + color: black; + font-size: 12px; + padding:0px 5px 0px 5px ; + margin:0; } diff --git a/addons/web_graph/static/src/css/nv.d3.css b/addons/web_graph/static/src/css/nv.d3.css new file mode 100644 index 00000000000..cae834827cc --- /dev/null +++ b/addons/web_graph/static/src/css/nv.d3.css @@ -0,0 +1,769 @@ + +/******************** + * HTML CSS + */ + + +.chartWrap { + margin: 0; + padding: 0; + overflow: hidden; +} + +/******************** + Box shadow and border radius styling +*/ +.nvtooltip.with-3d-shadow, .with-3d-shadow .nvtooltip { + -moz-box-shadow: 0 5px 10px rgba(0,0,0,.2); + -webkit-box-shadow: 0 5px 10px rgba(0,0,0,.2); + box-shadow: 0 5px 10px rgba(0,0,0,.2); + + -webkit-border-radius: 6px; + -moz-border-radius: 6px; + border-radius: 6px; +} + +/******************** + * TOOLTIP CSS + */ + +.nvtooltip { + position: absolute; + background-color: rgba(255,255,255,1.0); + padding: 1px; + border: 1px solid rgba(0,0,0,.2); + z-index: 10000; + + font-family: Arial; + font-size: 13px; + text-align: left; + pointer-events: none; + + white-space: nowrap; + + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +/*Give tooltips that old fade in transition by + putting a "with-transitions" class on the container div. +*/ +.nvtooltip.with-transitions, .with-transitions .nvtooltip { + transition: opacity 250ms linear; + -moz-transition: opacity 250ms linear; + -webkit-transition: opacity 250ms linear; + + transition-delay: 250ms; + -moz-transition-delay: 250ms; + -webkit-transition-delay: 250ms; +} + +.nvtooltip.x-nvtooltip, +.nvtooltip.y-nvtooltip { + padding: 8px; +} + +.nvtooltip h3 { + margin: 0; + padding: 4px 14px; + line-height: 18px; + font-weight: normal; + background-color: rgba(247,247,247,0.75); + text-align: center; + + border-bottom: 1px solid #ebebeb; + + -webkit-border-radius: 5px 5px 0 0; + -moz-border-radius: 5px 5px 0 0; + border-radius: 5px 5px 0 0; +} + +.nvtooltip p { + margin: 0; + padding: 5px 14px; + text-align: center; +} + +.nvtooltip span { + display: inline-block; + margin: 2px 0; +} + +.nvtooltip table { + margin: 6px; + border-spacing:0; +} + + +.nvtooltip table td { + padding: 2px 9px 2px 0; + vertical-align: middle; +} + +.nvtooltip table td.key { + font-weight:normal; +} +.nvtooltip table td.value { + text-align: right; + font-weight: bold; +} + +.nvtooltip table tr.highlight td { + padding: 1px 9px 1px 0; + border-bottom-style: solid; + border-bottom-width: 1px; + border-top-style: solid; + border-top-width: 1px; +} + +.nvtooltip table td.legend-color-guide div { + width: 8px; + height: 8px; + vertical-align: middle; +} + +.nvtooltip .footer { + padding: 3px; + text-align: center; +} + + +.nvtooltip-pending-removal { + position: absolute; + pointer-events: none; +} + + +/******************** + * SVG CSS + */ + + +svg { + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + /* Trying to get SVG to act like a greedy block in all browsers */ + display: block; + width:100%; + height:100%; +} + + +svg text { + font: normal 12px Arial; +} + +svg .title { + font: bold 14px Arial; +} + +.nvd3 .nv-background { + fill: white; + fill-opacity: 0; + /* + pointer-events: none; + */ +} + +.nvd3.nv-noData { + font-size: 18px; + font-weight: bold; +} + + +/********** +* Brush +*/ + +.nv-brush .extent { + fill-opacity: .125; + shape-rendering: crispEdges; +} + + + +/********** +* Legend +*/ + +.nvd3 .nv-legend .nv-series { + cursor: pointer; +} + +.nvd3 .nv-legend .disabled circle { + fill-opacity: 0; +} + + + +/********** +* Axes +*/ +.nvd3 .nv-axis { + pointer-events:none; +} + +.nvd3 .nv-axis path { + fill: none; + stroke: #000; + stroke-opacity: .75; + shape-rendering: crispEdges; +} + +.nvd3 .nv-axis path.domain { + stroke-opacity: .75; +} + +.nvd3 .nv-axis.nv-x path.domain { + stroke-opacity: 0; +} + +.nvd3 .nv-axis line { + fill: none; + stroke: #e5e5e5; + shape-rendering: crispEdges; +} + +.nvd3 .nv-axis .zero line, +/*this selector may not be necessary*/ .nvd3 .nv-axis line.zero { + stroke-opacity: .75; +} + +.nvd3 .nv-axis .nv-axisMaxMin text { + font-weight: bold; +} + +.nvd3 .x .nv-axis .nv-axisMaxMin text, +.nvd3 .x2 .nv-axis .nv-axisMaxMin text, +.nvd3 .x3 .nv-axis .nv-axisMaxMin text { + text-anchor: middle +} + + + +/********** +* Brush +*/ + +.nv-brush .resize path { + fill: #eee; + stroke: #666; +} + + + +/********** +* Bars +*/ + +.nvd3 .nv-bars .negative rect { + zfill: brown; +} + +.nvd3 .nv-bars rect { + zfill: steelblue; + fill-opacity: .75; + + transition: fill-opacity 250ms linear; + -moz-transition: fill-opacity 250ms linear; + -webkit-transition: fill-opacity 250ms linear; +} + +.nvd3 .nv-bars rect.hover { + fill-opacity: 1; +} + +.nvd3 .nv-bars .hover rect { + fill: lightblue; +} + +.nvd3 .nv-bars text { + fill: rgba(0,0,0,0); +} + +.nvd3 .nv-bars .hover text { + fill: rgba(0,0,0,1); +} + + +/********** +* Bars +*/ + +.nvd3 .nv-multibar .nv-groups rect, +.nvd3 .nv-multibarHorizontal .nv-groups rect, +.nvd3 .nv-discretebar .nv-groups rect { + stroke-opacity: 0; + + transition: fill-opacity 250ms linear; + -moz-transition: fill-opacity 250ms linear; + -webkit-transition: fill-opacity 250ms linear; +} + +.nvd3 .nv-multibar .nv-groups rect:hover, +.nvd3 .nv-multibarHorizontal .nv-groups rect:hover, +.nvd3 .nv-discretebar .nv-groups rect:hover { + fill-opacity: 1; +} + +.nvd3 .nv-discretebar .nv-groups text, +.nvd3 .nv-multibarHorizontal .nv-groups text { + font-weight: bold; + fill: rgba(0,0,0,1); + stroke: rgba(0,0,0,0); +} + +/*********** +* Pie Chart +*/ + +.nvd3.nv-pie path { + stroke-opacity: 0; + transition: fill-opacity 250ms linear, stroke-width 250ms linear, stroke-opacity 250ms linear; + -moz-transition: fill-opacity 250ms linear, stroke-width 250ms linear, stroke-opacity 250ms linear; + -webkit-transition: fill-opacity 250ms linear, stroke-width 250ms linear, stroke-opacity 250ms linear; + +} + +.nvd3.nv-pie .nv-slice text { + stroke: #000; + stroke-width: 0; +} + +.nvd3.nv-pie path { + stroke: #fff; + stroke-width: 1px; + stroke-opacity: 1; +} + +.nvd3.nv-pie .hover path { + fill-opacity: .7; +} +.nvd3.nv-pie .nv-label { + pointer-events: none; +} +.nvd3.nv-pie .nv-label rect { + fill-opacity: 0; + stroke-opacity: 0; +} + +/********** +* Lines +*/ + +.nvd3 .nv-groups path.nv-line { + fill: none; + stroke-width: 1.5px; + /* + stroke-linecap: round; + shape-rendering: geometricPrecision; + + transition: stroke-width 250ms linear; + -moz-transition: stroke-width 250ms linear; + -webkit-transition: stroke-width 250ms linear; + + transition-delay: 250ms + -moz-transition-delay: 250ms; + -webkit-transition-delay: 250ms; + */ +} + +.nvd3 .nv-groups path.nv-line.nv-thin-line { + stroke-width: 1px; +} + + +.nvd3 .nv-groups path.nv-area { + stroke: none; + /* + stroke-linecap: round; + shape-rendering: geometricPrecision; + + stroke-width: 2.5px; + transition: stroke-width 250ms linear; + -moz-transition: stroke-width 250ms linear; + -webkit-transition: stroke-width 250ms linear; + + transition-delay: 250ms + -moz-transition-delay: 250ms; + -webkit-transition-delay: 250ms; + */ +} + +.nvd3 .nv-line.hover path { + stroke-width: 6px; +} + +/* +.nvd3.scatter .groups .point { + fill-opacity: 0.1; + stroke-opacity: 0.1; +} + */ + +.nvd3.nv-line .nvd3.nv-scatter .nv-groups .nv-point { + fill-opacity: 0; + stroke-opacity: 0; +} + +.nvd3.nv-scatter.nv-single-point .nv-groups .nv-point { + fill-opacity: .5 !important; + stroke-opacity: .5 !important; +} + + +.with-transitions .nvd3 .nv-groups .nv-point { + transition: stroke-width 250ms linear, stroke-opacity 250ms linear; + -moz-transition: stroke-width 250ms linear, stroke-opacity 250ms linear; + -webkit-transition: stroke-width 250ms linear, stroke-opacity 250ms linear; + +} + +.nvd3.nv-scatter .nv-groups .nv-point.hover, +.nvd3 .nv-groups .nv-point.hover { + stroke-width: 7px; + fill-opacity: .95 !important; + stroke-opacity: .95 !important; +} + + +.nvd3 .nv-point-paths path { + stroke: #aaa; + stroke-opacity: 0; + fill: #eee; + fill-opacity: 0; +} + + + +.nvd3 .nv-indexLine { + cursor: ew-resize; +} + + +/********** +* Distribution +*/ + +.nvd3 .nv-distribution { + pointer-events: none; +} + + + +/********** +* Scatter +*/ + +/* **Attempting to remove this for useVoronoi(false), need to see if it's required anywhere +.nvd3 .nv-groups .nv-point { + pointer-events: none; +} +*/ + +.nvd3 .nv-groups .nv-point.hover { + stroke-width: 20px; + stroke-opacity: .5; +} + +.nvd3 .nv-scatter .nv-point.hover { + fill-opacity: 1; +} + +/* +.nv-group.hover .nv-point { + fill-opacity: 1; +} +*/ + + +/********** +* Stacked Area +*/ + +.nvd3.nv-stackedarea path.nv-area { + fill-opacity: .7; + /* + stroke-opacity: .65; + fill-opacity: 1; + */ + stroke-opacity: 0; + + transition: fill-opacity 250ms linear, stroke-opacity 250ms linear; + -moz-transition: fill-opacity 250ms linear, stroke-opacity 250ms linear; + -webkit-transition: fill-opacity 250ms linear, stroke-opacity 250ms linear; + + /* + transition-delay: 500ms; + -moz-transition-delay: 500ms; + -webkit-transition-delay: 500ms; + */ + +} + +.nvd3.nv-stackedarea path.nv-area.hover { + fill-opacity: .9; + /* + stroke-opacity: .85; + */ +} +/* +.d3stackedarea .groups path { + stroke-opacity: 0; +} + */ + + + +.nvd3.nv-stackedarea .nv-groups .nv-point { + stroke-opacity: 0; + fill-opacity: 0; +} + +/* +.nvd3.nv-stackedarea .nv-groups .nv-point.hover { + stroke-width: 20px; + stroke-opacity: .75; + fill-opacity: 1; +}*/ + + + +/********** +* Line Plus Bar +*/ + +.nvd3.nv-linePlusBar .nv-bar rect { + fill-opacity: .75; +} + +.nvd3.nv-linePlusBar .nv-bar rect:hover { + fill-opacity: 1; +} + + +/********** +* Bullet +*/ + +.nvd3.nv-bullet { font: 10px sans-serif; } +.nvd3.nv-bullet .nv-measure { fill-opacity: .8; } +.nvd3.nv-bullet .nv-measure:hover { fill-opacity: 1; } +.nvd3.nv-bullet .nv-marker { stroke: #000; stroke-width: 2px; } +.nvd3.nv-bullet .nv-markerTriangle { stroke: #000; fill: #fff; stroke-width: 1.5px; } +.nvd3.nv-bullet .nv-tick line { stroke: #666; stroke-width: .5px; } +.nvd3.nv-bullet .nv-range.nv-s0 { fill: #eee; } +.nvd3.nv-bullet .nv-range.nv-s1 { fill: #ddd; } +.nvd3.nv-bullet .nv-range.nv-s2 { fill: #ccc; } +.nvd3.nv-bullet .nv-title { font-size: 14px; font-weight: bold; } +.nvd3.nv-bullet .nv-subtitle { fill: #999; } + + +.nvd3.nv-bullet .nv-range { + fill: #bababa; + fill-opacity: .4; +} +.nvd3.nv-bullet .nv-range:hover { + fill-opacity: .7; +} + + + +/********** +* Sparkline +*/ + +.nvd3.nv-sparkline path { + fill: none; +} + +.nvd3.nv-sparklineplus g.nv-hoverValue { + pointer-events: none; +} + +.nvd3.nv-sparklineplus .nv-hoverValue line { + stroke: #333; + stroke-width: 1.5px; + } + +.nvd3.nv-sparklineplus, +.nvd3.nv-sparklineplus g { + pointer-events: all; +} + +.nvd3 .nv-hoverArea { + fill-opacity: 0; + stroke-opacity: 0; +} + +.nvd3.nv-sparklineplus .nv-xValue, +.nvd3.nv-sparklineplus .nv-yValue { + /* + stroke: #666; + */ + stroke-width: 0; + font-size: .9em; + font-weight: normal; +} + +.nvd3.nv-sparklineplus .nv-yValue { + stroke: #f66; +} + +.nvd3.nv-sparklineplus .nv-maxValue { + stroke: #2ca02c; + fill: #2ca02c; +} + +.nvd3.nv-sparklineplus .nv-minValue { + stroke: #d62728; + fill: #d62728; +} + +.nvd3.nv-sparklineplus .nv-currentValue { + /* + stroke: #444; + fill: #000; + */ + font-weight: bold; + font-size: 1.1em; +} + +/********** +* historical stock +*/ + +.nvd3.nv-ohlcBar .nv-ticks .nv-tick { + stroke-width: 2px; +} + +.nvd3.nv-ohlcBar .nv-ticks .nv-tick.hover { + stroke-width: 4px; +} + +.nvd3.nv-ohlcBar .nv-ticks .nv-tick.positive { + stroke: #2ca02c; +} + +.nvd3.nv-ohlcBar .nv-ticks .nv-tick.negative { + stroke: #d62728; +} + +.nvd3.nv-historicalStockChart .nv-axis .nv-axislabel { + font-weight: bold; +} + +.nvd3.nv-historicalStockChart .nv-dragTarget { + fill-opacity: 0; + stroke: none; + cursor: move; +} + +.nvd3 .nv-brush .extent { + /* + cursor: ew-resize !important; + */ + fill-opacity: 0 !important; +} + +.nvd3 .nv-brushBackground rect { + stroke: #000; + stroke-width: .4; + fill: #fff; + fill-opacity: .7; +} + + + +/********** +* Indented Tree +*/ + + +/** + * TODO: the following 3 selectors are based on classes used in the example. I should either make them standard and leave them here, or move to a CSS file not included in the library + */ +.nvd3.nv-indentedtree .name { + margin-left: 5px; +} + +.nvd3.nv-indentedtree .clickable { + color: #08C; + cursor: pointer; +} + +.nvd3.nv-indentedtree span.clickable:hover { + color: #005580; + text-decoration: underline; +} + + +.nvd3.nv-indentedtree .nv-childrenCount { + display: inline-block; + margin-left: 5px; +} + +.nvd3.nv-indentedtree .nv-treeicon { + cursor: pointer; + /* + cursor: n-resize; + */ +} + +.nvd3.nv-indentedtree .nv-treeicon.nv-folded { + cursor: pointer; + /* + cursor: s-resize; + */ +} + +/********** +* Parallel Coordinates +*/ + +.nvd3 .background path { + fill: none; + stroke: #ccc; + stroke-opacity: .4; + shape-rendering: crispEdges; +} + +.nvd3 .foreground path { + fill: none; + stroke: steelblue; + stroke-opacity: .7; +} + +.nvd3 .brush .extent { + fill-opacity: .3; + stroke: #fff; + shape-rendering: crispEdges; +} + +.nvd3 .axis line, .axis path { + fill: none; + stroke: #000; + shape-rendering: crispEdges; +} + +.nvd3 .axis text { + text-shadow: 0 1px 0 #fff; +} + +/**** +Interactive Layer +*/ +.nvd3 .nv-interactiveGuideLine { + pointer-events:none; +} +.nvd3 line.nv-guideline { + stroke: #ccc; +}
\ No newline at end of file diff --git a/addons/web_graph/static/src/js/graph.js b/addons/web_graph/static/src/js/graph.js deleted file mode 100644 index a84bb53fc85..00000000000 --- a/addons/web_graph/static/src/js/graph.js +++ /dev/null @@ -1,434 +0,0 @@ -/*--------------------------------------------------------- - * OpenERP web_graph - *---------------------------------------------------------*/ - -openerp.web_graph = function (instance) { - -var _lt = instance.web._lt; -var _t = instance.web._t; - -// removed ``undefined`` values -var filter_values = function (o) { - var out = {}; - for (var k in o) { - if (!o.hasOwnProperty(k) || o[k] === undefined) { continue; } - out[k] = o[k]; - } - return out; -}; - -instance.web.views.add('graph', 'instance.web_graph.GraphView'); -instance.web_graph.GraphView = instance.web.View.extend({ - template: "GraphView", - display_name: _lt('Graph'), - view_type: "graph", - - init: function(parent, dataset, view_id, options) { - var self = this; - this._super(parent); - this.set_default_options(options); - this.dataset = dataset; - this.view_id = view_id; - - this.mode = "bar"; // line, bar, area, pie, radar - this.orientation = false; // true: horizontal, false: vertical - this.stacked = true; - - this.spreadsheet = false; // Display data grid, allows copy to CSV - this.forcehtml = false; - this.legend = "top"; // top, inside, no - - this.domain = []; - this.context = {}; - this.group_by = []; - - this.graph = null; - }, - view_loading: function(r) { - return this.load_graph(r); - }, - destroy: function () { - if (this.graph) { - this.graph.destroy(); - } - this._super(); - }, - - load_graph: function(fields_view_get) { - // TODO: move to load_view and document - var self = this; - this.fields_view = fields_view_get; - this.$el.addClass(this.fields_view.arch.attrs['class']); - - this.mode = this.fields_view.arch.attrs.type || 'bar'; - this.orientation = this.fields_view.arch.attrs.orientation == 'horizontal'; - - var width = this.$el.parent().width(); - this.$el.css("width", width); - this.container = this.$el.find("#editor-render-body").css({ - width: width, - height: Math.min(500, width * 0.8) - })[0]; - - var graph_render = this.proxy('graph_render'); - this.$el.on('click', '.oe_graph_options a', function (evt) { - var $el = $(evt.target); - - self.graph_render({data: filter_values({ - mode: $el.data('mode'), - legend: $el.data('legend'), - orientation: $el.data('orientation'), - stacked: $el.data('stacked') - })}); - }); - - this.$el.find("#graph_show_data").click(function () { - self.spreadsheet = ! self.spreadsheet; - self.graph_render(); - }); - this.$el.find("#graph_switch").click(function () { - if (self.mode != 'radar') { - self.orientation = ! self.orientation; - } - self.graph_render(); - }); - - this.$el.find("#graph_download").click(function () { - if (self.legend == "top") { self.legend = "inside"; } - self.forcehtml = true; - - self.graph_get_data().done(function (result) { - self.graph_render_all(result).download.saveImage('png'); - }).always(function () { - self.forcehtml = false; - }); - }); - this.trigger('graph_view_loaded', fields_view_get); - }, - - get_format: function (options) { - options = options || {}; - var legend = { - show: this.legend != 'no', - }; - - switch (this.legend) { - case 'top': - legend.noColumns = 4; - legend.container = this.$el.find("div.graph_header_legend")[0]; - break; - case 'inside': - legend.position = 'nw'; - legend.backgroundColor = '#D2E8FF'; - break; - } - - return _.extend({ - legend: legend, - mouse: { - track: true, - relative: true - }, - spreadsheet : { - show: this.spreadsheet, - initialTab: "data" - }, - HtmlText : (options.xaxis && options.xaxis.labelsAngle) ? false : !this.forcehtml, - }, options); - }, - - make_graph: function (mode, container, data) { - if (mode === 'area') { mode = 'line'; } - var format = this.get_format(this['options_' + mode](data)); - return Flotr.draw(container, data.data, format); - }, - - options_bar: function (data) { - var min = _(data.data).chain() - .map(function (record) { - if (record.data.length > 0){ - return _.min(record.data, function (item) { - return item[1]; - })[1]; - } - }).min().value(); - return { - bars : { - show : true, - stacked : this.stacked, - horizontal : this.orientation, - barWidth : 0.7, - lineWidth : 1 - }, - grid : { - verticalLines : this.orientation, - horizontalLines : !this.orientation, - outline : "sw", - }, - yaxis : { - ticks: this.orientation ? data.ticks : false, - min: !this.orientation ? (min < 0 ? min : 0) : null - }, - xaxis : { - labelsAngle: 45, - ticks: this.orientation ? false : data.ticks, - min: this.orientation ? (min < 0 ? min : 0) : null - } - }; - }, - - options_pie: function (data) { - return { - pie : { - show: true - }, - grid : { - verticalLines : false, - horizontalLines : false, - outline : "", - }, - xaxis : {showLabels: false}, - yaxis : {showLabels: false}, - }; - }, - - options_radar: function (data) { - return { - radar : { - show : true, - stacked : this.stacked - }, - grid : { - circular : true, - minorHorizontalLines : true - }, - xaxis : { - ticks: data.ticks - }, - }; - }, - - options_line: function (data) { - return { - lines : { - show : true, - }, - points: { - show: true, - }, - grid : { - verticalLines : this.orientation, - horizontalLines : !this.orientation, - outline : "sw", - }, - yaxis : { - ticks: this.orientation ? data.ticks : false - }, - xaxis : { - labelsAngle: 45, - ticks: this.orientation ? false : data.ticks - } - }; - }, - - graph_get_data: function () { - var model = this.dataset.model, - domain = new instance.web.CompoundDomain(this.domain || []), - context = new instance.web.CompoundContext(this.context || {}), - group_by = this.group_by || [], - view_id = this.view_id || false, - mode = this.mode || 'bar', - orientation = this.orientation || false, - stacked = this.stacked || false; - - var obj = new instance.web.Model(model); - var view_get; - var fields; - var result = []; - var ticks = {}; - - return this.alive(obj.call("fields_view_get", [view_id, 'graph', context]).then(function(tmp) { - view_get = tmp; - fields = view_get['fields']; - var toload = _.select(group_by, function(x) { return fields[x] === undefined }); - if (toload.length >= 1) - return obj.call("fields_get", [toload, context]); - else - return $.when([]); - }).then(function (fields_to_add) { - _.extend(fields, fields_to_add); - - var tree = $($.parseXML(view_get['arch'])); - - var pos = 0; - var xaxis = _.clone(group_by || []); - var yaxis = []; - tree.find("field").each(function() { - var field = $(this); - if (! field.attr("name")) - return; - if ((group_by.length == 0) && ((! pos) || instance.web.py_eval(field.attr('group') || "false"))) { - xaxis.push(field.attr('name')); - } - if (pos && ! instance.web.py_eval(field.attr('group') || "false")) { - yaxis.push(field.attr('name')); - } - pos += 1; - }); - - if (xaxis.length === 0) - throw new Error("No field for the X axis!"); - if (yaxis.length === 0) - throw new Error("No field for the Y axis!"); - - // Convert a field's data into a displayable string - - function _convert_key(field, data) { - if (fields[field]['type'] === 'many2one') - data = data && data[0]; - return data; - } - - function _convert(field, data, tick) { - tick = tick === undefined ? true : false; - try { - data = instance.web.format_value(data, fields[field]); - } catch(e) { - data = "" + data; - } - if (tick) { - if (ticks[data] === undefined) - ticks[data] = _.size(ticks); - return ticks[data]; - } - return data || 0; - } - - function _orientation(x, y) { - if (! orientation) - return [x, y] - return [y, x] - } - - if (mode === "pie") { - return obj.call("read_group", [domain, yaxis.concat([xaxis[0]]), [xaxis[0]]], {context: context}).then(function(res) { - _.each(res, function(record) { - result.push({ - 'data': [[_convert(xaxis[0], record[xaxis[0]]), record[yaxis[0]]]], - 'label': _convert(xaxis[0], record[xaxis[0]], false) - }); - }); - }); - } else if ((! stacked) || (xaxis.length < 2)) { - var defs = []; - _.each(xaxis, function(x) { - defs.push(obj.call("read_group", [domain, yaxis.concat([x]), [x]], {context: context}).then(function(res) { - return [x, res]; - })); - }); - return $.when.apply($, defs).then(function() { - _.each(_.toArray(arguments), function(res) { - var x = res[0]; - res = res[1]; - result.push({ - 'data': _.map(res, function(record) { - return _orientation(_convert(x, record[x]), record[yaxis[0]] || 0); - }), - 'label': fields[x]['string'] - }); - }); - }); - } else { - xaxis.reverse(); - return obj.call("read_group", [domain, yaxis.concat(xaxis.slice(0, 1)), xaxis.slice(0, 1)], {context: context}).then(function(axis) { - var defs = []; - _.each(axis, function(x) { - var key = x[xaxis[0]] - defs.push(obj.call("read_group", [x['__domain'], yaxis.concat(xaxis.slice(1, 2)), xaxis.slice(1, 2)], {context: context}).then(function(res) { - return [x, key, res]; - })); - }); - return $.when.apply($, defs).then(function() { - _.each(_.toArray(arguments), function(res) { - var x = res[0]; - var key = res[1]; - res = res[2]; - result.push({ - 'data': _.map(res, function(record) { - return _orientation(_convert(xaxis[1], record[xaxis[1]]), record[yaxis[0]] || 0); - }), - 'label': _convert(xaxis[0], key, false) - }) - }); - }); - }); - } - }).then(function() { - var res = { - 'data': result, - 'ticks': _.map(ticks, function(el, key) { return [el, key || _t("Undefined")] }) - }; - return res; - })); - }, - - // Render the graph and update menu styles - graph_render: function (options) { - options = options || {}; - _.extend(this, options.data); - - return this.graph_get_data() - .done(this.proxy('graph_render_all')); - }, - - graph_render_all: function (data) { - var i; - if (this.mode=='area') { - for (i=0; i<data.data.length; i++) { - data.data[i].lines = {fill: true} - } - } - if (this.graph) { - this.graph.destroy(); - } - - // Render the graph - this.$el.find(".graph_header_legend").children().remove(); - this.graph = this.make_graph(this.mode, this.container, data); - - // Update styles of menus - - this.$el.find("a").removeClass("active"); - - var $active = this.$el.find('a[data-mode=' + this.mode + ']'); - if ($active.length > 1) { - $active = $active.filter('[data-stacked=' + this.stacked + ']'); - } - $active = $active.add( - this.$el.find('a:not([data-mode])[data-legend=' + this.legend + ']')); - - $active.addClass('active'); - - if (this.spreadsheet) { - this.$el.find("#graph_show_data").addClass("active"); - } - return this.graph; - }, - - // render the graph using the domain, context and group_by - // calls the 'graph_data_get' python controller to process all data - // TODO: check is group_by should better be in the context - do_search: function(domain, context, group_by) { - this.domain = domain; - this.context = context; - this.group_by = group_by; - - this.graph_render(); - }, - - do_show: function() { - this.do_push_state({}); - return this._super(); - }, -}); -}; diff --git a/addons/web_graph/static/src/js/graph_view.js b/addons/web_graph/static/src/js/graph_view.js new file mode 100644 index 00000000000..fa39fc581db --- /dev/null +++ b/addons/web_graph/static/src/js/graph_view.js @@ -0,0 +1,222 @@ +/*--------------------------------------------------------- + * OpenERP web_graph + *---------------------------------------------------------*/ + +/* jshint undef: false */ + +openerp.web_graph = function (instance) { +'use strict'; + +var _lt = instance.web._lt; +var _t = instance.web._t; + +instance.web.views.add('graph', 'instance.web_graph.GraphView'); + +instance.web_graph.GraphView = instance.web.View.extend({ + display_name: _lt('Graph'), + view_type: 'graph', + + // ---------------------------------------------------------------------- + // Init stuff + // ---------------------------------------------------------------------- + init: function(parent, dataset, view_id, options) { + var self = this; + this._super(parent, dataset, view_id, options); + this.dataset = dataset; + this.model = new instance.web.Model(dataset.model, {group_by_no_leaf: true}); + this.search_view = parent.searchview; + this.col_search_field = { + get_context: function() { return { col_group_by: self.graph_widget.get_col_groupbys()};}, + get_domain: function () {}, + get_groupby: function () {}, + }; + }, + + view_loading: function (fields_view_get) { + var self = this, + arch = fields_view_get.arch; + + this.widget_config = { + title: arch.attrs.string, + stacked : (arch.attrs.stacked === 'True'), + mode: arch.attrs.type || 'bar', + measures: [], + row_groupby: [], + col_groupby: [], + graph_view: this, + }; + + _.each(arch.children, function (field) { + var field_name = field.attrs.name; + if (_.has(field.attrs, 'interval')) { + field_name = field.attrs.name + ':' + field.attrs.interval; + } + if (_.has(field.attrs, 'type')) { + switch (field.attrs.type) { + case 'row': + self.widget_config.row_groupby.push(field_name); + break; + case 'col': + self.widget_config.col_groupby.push(field_name); + break; + case 'measure': + self.widget_config.measures.push(field_name); + break; + } + } else { // old style, kept for backward compatibility + if ('operator' in field.attrs) { + self.widget_config.measures.push(field_name); + } else { + self.widget_config.row_groupby.push(field_name); + } + } + }); + if (self.widget_config.measures.length === 0) { + self.widget_config.measures.push('__count'); + } + }, + + do_search: function (domain, context, group_by) { + if (this.ignore_do_search) { + this.ignore_do_search = false; + return; + } + var self = this, + groupbys = this.get_groupbys_from_searchview(), + col_group_by = groupbys.col_group_by; + + if (!this.graph_widget) { + if (group_by.length) { + this.widget_config.row_groupby = group_by; + } + if (col_group_by.length) { + this.widget_config.col_groupby = col_group_by; + } + this.graph_widget = new openerp.web_graph.Graph(this, this.model, domain, this.widget_config); + this.graph_widget.appendTo(this.$el); + this.ViewManager.on('switch_mode', this, function (e) { + if (e === 'graph') { + var group_bys = self.get_groupbys_from_searchview(); + this.graph_widget.set(domain, group_bys.group_by, group_bys.col_group_by); + } + }); + return; + } + + this.graph_widget.set(domain, group_by, col_group_by); + }, + + get_groupbys_from_searchview: function () { + var result = { group_by: [], col_group_by: []}, + searchdata = this.search_view.build_search_data(); + + _.each(searchdata.groupbys, function (data) { + data = (_.isString(data)) ? py.eval(data) : data; + result.group_by = result.group_by.concat(data.group_by); + if (data.col_group_by) { + result.col_group_by = result.col_group_by.concat(data.col_group_by); + } + }); + + if (result.col_group_by.length) { + return result; + } + _.each(searchdata.contexts, function (context) { + if (context.col_group_by) { + result.col_group_by = result.col_group_by.concat(context.col_group_by); + } + }); + return result; + }, + + do_show: function () { + this.do_push_state({}); + return this._super(); + }, + + // ---------------------------------------------------------------------- + // Search view integration + // ---------------------------------------------------------------------- + + // add groupby to the search view + register_groupby: function(row_groupby, col_groupby) { + var query = this.search_view.query; + + if (!_.has(this.search_view, '_s_groupby')) { return; } + + if (row_groupby.length && col_groupby.length) { + // when two changes to the search view will be done, the method do_search + // will be called twice, once with the correct groupby and incorrect col_groupby, + // and once with correct informations. This flag is necessary to prevent the + // incorrect informations to propagate and trigger useless queries + this.ignore_do_search = true; + } + + // add row groupbys + var row_facet = this.make_row_groupby_facets(row_groupby), + row_search_facet = query.findWhere({category:'GroupBy'}); + + if (row_search_facet) { + row_search_facet.values.reset(row_facet.values); + } else { + if (row_groupby.length) { + query.add(row_facet); + } + } + // add col groupbys + var col_facet = this.make_col_groupby_facets(col_groupby), + col_search_facet = query.findWhere({category:'ColGroupBy'}); + + if (col_search_facet) { + col_search_facet.values.reset(col_facet.values); + } else { + if (col_groupby.length) { + query.add(col_facet); + } + } + }, + + make_row_groupby_facets: function(groupbys) { + return { + category:'GroupBy', + values: this.make_groupby_values(groupbys, 'group_by'), + icon:'w', + field: this.search_view._s_groupby + }; + }, + + make_col_groupby_facets: function(groupbys) { + return { + category:'ColGroupBy', + values: this.make_groupby_values(groupbys, 'col_group_by'), + icon:'f', + field: this.col_search_field + }; + }, + + make_groupby_values: function (groupbys, category) { + return _.map(groupbys, function (groupby) { + var context = {}; + context[category] = groupby.field; + var value; + if (category === 'group_by' && groupby.type !== 'date' && groupby.type !== 'datetime') { + value = groupby.filter || {attrs: {domain: [], context: context}}; + } else { + value = {attrs: {domain: [], context: context}}; + } + return { + label: groupby.string, + value: value + }; + }); + }, +}); +}; + + + + + + + + diff --git a/addons/web_graph/static/src/js/graph_widget.js b/addons/web_graph/static/src/js/graph_widget.js new file mode 100644 index 00000000000..b4f06aee3f1 --- /dev/null +++ b/addons/web_graph/static/src/js/graph_widget.js @@ -0,0 +1,761 @@ + +/* jshint undef: false */ + +(function () { +'use strict'; +var QWeb = openerp.web.qweb; +var _lt = openerp.web._lt; +var _t = openerp.web._t; + +nv.dev = false; // sets nvd3 library in production mode + +openerp.web_graph.Graph = openerp.web.Widget.extend({ + template: 'GraphWidget', + + // ---------------------------------------------------------------------- + // Init stuff + // ---------------------------------------------------------------------- + init: function(parent, model, domain, options) { + this._super(parent); + this.model = model; + this.domain = domain; + this.mode = options.mode || 'pivot'; // pivot, bar, pie, line + this.heatmap_mode = options.heatmap_mode || 'none'; + this.visible_ui = options.visible_ui || true; + this.bar_ui = options.bar_ui || 'group'; + this.graph_view = options.graph_view || null; + this.pivot_options = options; + this.title = options.title || 'Data'; + }, + + start: function() { + var self = this; + this.table = $('<table>'); + this.$('.graph_main_content').append(this.table); + + var indexes = {'pivot': 0, 'bar': 1, 'line': 2, 'chart': 3}; + this.$('.graph_mode_selection label').eq(indexes[this.mode]).addClass('active'); + + if (this.mode !== 'pivot') { + this.$('.graph_heatmap label').addClass('disabled'); + } + + openerp.session.rpc('/web_graph/check_xlwt').then(function (result) { + self.$('.graph_options_selection label').toggle(result); + }); + + return this.model.call('fields_get', []).then(function (f) { + self.fields = f; + self.fields.__count = {field:'__count', type: 'integer', string:_t('Quantity')}; + self.important_fields = self.get_search_fields(); + self.measure_list = self.get_measures(); + self.add_measures_to_options(); + self.pivot_options.row_groupby = self.create_field_values(self.pivot_options.row_groupby || []); + self.pivot_options.col_groupby = self.create_field_values(self.pivot_options.col_groupby || []); + self.pivot_options.measures = self.create_field_values(self.pivot_options.measures || [{field:'__count', type: 'integer', string:'Quantity'}]); + self.pivot = new openerp.web_graph.PivotTable(self.model, self.domain, self.fields, self.pivot_options); + self.pivot.update_data().then(function () { + self.display_data(); + if (self.graph_view) { + self.graph_view.register_groupby(self.pivot.rows.groupby, self.pivot.cols.groupby); + } + }); + openerp.web.bus.on('click', self, function () { + if (self.dropdown) { + self.dropdown.remove(); + self.dropdown = null; + } + }); + self.put_measure_checkmarks(); + }); + }, + + // this method gets the fields that appear in the search view, under the + // 'Groupby' heading + get_search_fields: function () { + var self = this, + parent = this.getParent(); + + while (!(parent instanceof openerp.web.ViewManager)) { + parent = parent.getParent(); + } + + var search_view = parent.searchview; + + var groupbygroups = _(search_view.inputs).select(function (g) { + return g instanceof openerp.web.search.GroupbyGroup; + }); + + var filters = _.flatten(_.pluck(groupbygroups, 'filters'), true), + groupbys = _.flatten(_.map(filters, function (filter) { + var groupby = py.eval(filter.attrs.context).group_by; + if (!(groupby instanceof Array)) { groupby = [groupby]; } + return _.map(groupby, function(g) { + return {field: g, filter: filter}; + }); + })); + + return _.uniq(_.map(groupbys, function (groupby) { + var field = groupby.field, + filter = groupby.filter, + raw_field = field.split(':')[0], + string = (field === raw_field) ? filter.attrs.string : self.fields[raw_field].string; + + filter = (field === raw_field) ? filter : undefined; + + return { field: raw_field, string: string, filter: filter }; + }), false, function (filter) {return filter.field;}); + }, + + // Extracts the integer/float fields which are not 'id' + get_measures: function() { + return _.compact(_.map(this.fields, function (f, id) { + if (((f.type === 'integer') || (f.type === 'float')) && (id !== 'id')) { + return {field:id, type: f.type, string: f.string}; + } + })); + }, + + add_measures_to_options: function() { + this.$('.graph_measure_selection').append( + _.map(this.measure_list, function (measure) { + return $('<li>').append($('<a>').attr('data-choice', measure.field) + .attr('href', '#') + .text(measure.string)); + })); + }, + + // ---------------------------------------------------------------------- + // Configuration methods + // ---------------------------------------------------------------------- + set: function (domain, row_groupby, col_groupby) { + if (!this.pivot) { + this.pivot_options.domain = domain; + this.pivot_options.row_groupby = row_groupby; + this.pivot_options.col_groupby = col_groupby; + return; + } + var row_gbs = this.create_field_values(row_groupby), + col_gbs = this.create_field_values(col_groupby), + dom_changed = !_.isEqual(this.pivot.domain, domain), + row_gb_changed = !_.isEqual(row_gbs, this.pivot.rows.groupby), + col_gb_changed = !_.isEqual(col_gbs, this.pivot.cols.groupby), + row_reduced = is_strict_beginning_of(row_gbs, this.pivot.rows.groupby), + col_reduced = is_strict_beginning_of(col_gbs, this.pivot.cols.groupby); + + if (!dom_changed && row_reduced && !col_gb_changed) { + this.pivot.fold_with_depth(this.pivot.rows, row_gbs.length); + this.display_data(); + return; + } + if (!dom_changed && col_reduced && !row_gb_changed) { + this.pivot.fold_with_depth(this.pivot.cols, col_gbs.length); + this.display_data(); + return; + } + + if (!dom_changed && col_reduced && row_reduced) { + this.pivot.fold_with_depth(this.pivot.rows, row_gbs.length); + this.pivot.fold_with_depth(this.pivot.cols, col_gbs.length); + this.display_data(); + return; + } + + if (dom_changed || row_gb_changed || col_gb_changed) { + this.pivot.set(domain, row_gbs, col_gbs).then(this.proxy('display_data')); + } + }, + + set_mode: function (mode) { + this.mode = mode; + + if (mode === 'pivot') { + this.$('.graph_heatmap label').removeClass('disabled'); + this.$('.graph_main_content').css('position', 'inherit') + } else { + this.$('.graph_heatmap label').addClass('disabled'); + this.$('.graph_main_content').css('position', 'relative') + } + this.display_data(); + }, + + set_heatmap_mode: function (mode) { // none, row, col, all + this.heatmap_mode = mode; + if (mode === 'none') { + this.$('.graph_heatmap label').removeClass('disabled'); + this.$('.graph_heatmap label').removeClass('active'); + } + this.display_data(); + }, + + create_field_value: function (f) { + var field = (_.contains(f, ':')) ? f.split(':')[0] : f, + groupby_field = _.findWhere(this.important_fields, {field:field}), + string = groupby_field ? groupby_field.string : this.fields[field].string, + result = {field: f, string: string, type: this.fields[field].type }; + + if (groupby_field) { + result.filter = groupby_field.filter; + } + + return result; + }, + + create_field_values: function (field_ids) { + return _.map(field_ids, this.proxy('create_field_value')); + }, + + + get_col_groupbys: function () { + return _.pluck(this.pivot.cols.groupby, 'field'); + }, + + // ---------------------------------------------------------------------- + // UI code + // ---------------------------------------------------------------------- + events: { + 'click .graph_mode_selection label' : 'mode_selection', + 'click .graph_measure_selection li' : 'measure_selection', + 'click .graph_options_selection label' : 'option_selection', + 'click .graph_heatmap label' : 'heatmap_mode_selection', + 'click .web_graph_click' : 'header_cell_clicked', + 'click a.field-selection' : 'field_selection', + }, + + mode_selection: function (event) { + event.preventDefault(); + var mode = event.currentTarget.getAttribute('data-mode'); + this.set_mode(mode); + }, + + measure_selection: function (event) { + event.preventDefault(); + event.stopPropagation(); + var measure_field = event.target.getAttribute('data-choice'); + var measure = { + field: measure_field, + type: this.fields[measure_field].type, + string: this.fields[measure_field].string + }; + + this.pivot.toggle_measure(measure).then(this.proxy('display_data')); + this.put_measure_checkmarks(); + }, + + put_measure_checkmarks: function () { + var self = this, + measures_li = this.$('.graph_measure_selection a'); + measures_li.removeClass('oe_selected'); + _.each(this.measure_list, function (measure, index) { + if (_.findWhere(self.pivot.measures, measure)) { + measures_li.eq(index).addClass('oe_selected'); + } + }); + + }, + + option_selection: function (event) { + event.preventDefault(); + switch (event.currentTarget.getAttribute('data-choice')) { + case 'bar_grouped': + this.bar_ui = 'group'; + if (this.mode === 'bar') { + this.display_data(); + } + break; + case 'bar_stacked': + this.bar_ui = 'stack'; + if (this.mode === 'bar') { + this.display_data(); + } + break; + case 'swap_axis': + this.swap_axis(); + break; + case 'update_values': + this.pivot.update_data().then(this.proxy('display_data')); + break; + case 'export_data': + this.export_xls(); + break; + } + }, + + heatmap_mode_selection: function (event) { + event.preventDefault(); + var mode = event.currentTarget.getAttribute('data-mode'); + if (this.heatmap_mode === mode) { + event.stopPropagation(); + this.set_heatmap_mode('none'); + } else { + this.set_heatmap_mode(mode); + } + }, + + header_cell_clicked: function (event) { + event.preventDefault(); + event.stopPropagation(); + var id = event.target.getAttribute('data-id'), + header = this.pivot.get_header(id), + self = this; + + if (header.expanded) { + this.fold(header); + return; + } + if (header.path.length < header.root.groupby.length) { + this.expand(id); + return; + } + if (!this.important_fields.length) { + return; + } + + var fields = _.map(this.important_fields, function (field) { + return {id: field.field, value: field.string, type:self.fields[field.field.split(':')[0]].type}; + }); + if (this.dropdown) { + this.dropdown.remove(); + } + this.dropdown = $(QWeb.render('field_selection', {fields:fields, header_id:id})); + $(event.target).after(this.dropdown); + this.dropdown.css({position:'absolute', + left:event.pageX, + top:event.pageY}); + this.$('.field-selection').next('.dropdown-menu').toggle(); + + + }, + + field_selection: function (event) { + var id = event.target.getAttribute('data-id'), + field_id = event.target.getAttribute('data-field-id'), + interval, + groupby = this.create_field_value(field_id); + event.preventDefault(); + if (this.fields[field_id].type === 'date' || this.fields[field_id].type === 'datetime') { + interval = event.target.getAttribute('data-interval'); + groupby.field = groupby.field + ':' + interval; + } + this.expand(id, groupby); + }, + + // ---------------------------------------------------------------------- + // Pivot Table integration + // ---------------------------------------------------------------------- + expand: function (header_id, groupby) { + var self = this, + header = this.pivot.get_header(header_id), + update_groupby = !!groupby; + + groupby = groupby || header.root.groupby[header.path.length]; + + this.pivot.expand(header_id, groupby).then(function () { + if (update_groupby && self.graph_view) { + self.graph_view.register_groupby(self.pivot.rows.groupby, self.pivot.cols.groupby); + } + self.display_data(); + }); + + }, + + fold: function (header) { + var update_groupby = this.pivot.fold(header); + + this.display_data(); + if (update_groupby && this.graph_view) { + this.graph_view.register_groupby(this.pivot.rows.groupby, this.pivot.cols.groupby); + } + }, + + swap_axis: function () { + this.pivot.swap_axis(); + this.display_data(); + this.graph_view.register_groupby(this.pivot.rows.groupby, this.pivot.cols.groupby); + }, + + // ---------------------------------------------------------------------- + // Convert Pivot data structure into table structure : + // compute rows, cols, colors, cell width, cell height, ... + // ---------------------------------------------------------------------- + build_table: function() { + return { + headers: this.build_headers(), + measure_row: this.build_measure_row(), + rows: this.build_rows(), + nbr_measures: this.pivot.measures.length, + title: this.title, + }; + }, + + build_headers: function () { + var pivot = this.pivot, + nbr_measures = pivot.measures.length, + height = _.max(_.map(pivot.cols.headers, function(g) {return g.path.length;})), + rows = []; + + _.each(pivot.cols.headers, function (col) { + if (col.path.length === 0) { return;} + var cell_width = nbr_measures * (col.expanded ? pivot.get_ancestor_leaves(col).length : 1), + cell_height = col.expanded ? 1 : height - col.path.length + 1, + cell = {width: cell_width, height: cell_height, title: col.title, id: col.id, expanded: col.expanded}; + if (rows[col.path.length - 1]) { + rows[col.path.length - 1].push(cell); + } else { + rows[col.path.length - 1] = [cell]; + } + }); + + if (pivot.get_cols_leaves().length > 1) { + rows[0].push({width: nbr_measures, height: height, title: _t('Total'), id: pivot.main_col().id }); + } + if (pivot.cols.headers.length === 1) { + rows = [[{width: nbr_measures, height: 1, title: _t('Total'), id: pivot.main_col().id, expanded: false}]]; + } + return rows; + }, + + build_measure_row: function () { + var nbr_leaves = this.pivot.get_cols_leaves().length, + nbr_cols = nbr_leaves + ((nbr_leaves > 1) ? 1 : 0), + result = [], + add_total = this.pivot.get_cols_leaves().length > 1, + i, m; + for (i = 0; i < nbr_cols; i++) { + for (m = 0; m < this.pivot.measures.length; m++) { + result.push({ + text:this.pivot.measures[m].string, + is_bold: add_total && (i === nbr_cols - 1) + }); + } + } + return result; + }, + + make_cell: function (row, col, value, index) { + var formatted_value = openerp.web.format_value(value, {type:this.pivot.measures[index].type}), + cell = {value:formatted_value}; + + if (this.heatmap_mode === 'none') { return cell; } + var total = (this.heatmap_mode === 'both') ? this.pivot.get_total()[index] + : (this.heatmap_mode === 'row') ? this.pivot.get_total(row)[index] + : this.pivot.get_total(col)[index]; + var color = Math.floor(90 + 165*(total - Math.abs(value))/total); + if (color < 255) { + cell.color = color; + } + return cell; + }, + + build_rows: function () { + var self = this, + pivot = this.pivot, + m, cell; + + return _.map(pivot.rows.headers, function (row) { + var cells = []; + _.each(pivot.get_cols_leaves(), function (col) { + var values = pivot.get_values(row.id,col.id); + for (m = 0; m < pivot.measures.length; m++) { + cells.push(self.make_cell(row,col,values[m], m)); + } + }); + if (pivot.get_cols_leaves().length > 1) { + var totals = pivot.get_total(row); + for (m = 0; m < pivot.measures.length; m++) { + cell = self.make_cell(row, pivot.main_col(), totals[m], m); + cell.is_bold = 'true'; + cells.push(cell); + } + } + return { + id: row.id, + indent: row.path.length, + title: row.title, + expanded: row.expanded, + cells: cells, + }; + }); + }, + + // ---------------------------------------------------------------------- + // Main display method + // ---------------------------------------------------------------------- + display_data: function () { + this.$('.graph_main_content svg').remove(); + this.$('.graph_main_content div').remove(); + this.table.empty(); + this.table.toggleClass('heatmap', this.heatmap_mode !== 'none'); + this.width = this.$el.width(); + this.height = Math.min(Math.max(document.documentElement.clientHeight - 116 - 60, 250), Math.round(0.8*this.$el.width())); + + this.$('.graph_header').toggle(this.visible_ui); + if (this.pivot.no_data) { + this.$('.graph_main_content').append($(QWeb.render('graph_no_data'))); + } else { + if (this.mode === 'pivot') { + this.draw_table(); + } else { + this.$('.graph_main_content').append($('<div><svg>')); + this.svg = this.$('.graph_main_content svg')[0]; + this[this.mode](); + } + } + }, + + // ---------------------------------------------------------------------- + // Drawing the table + // ---------------------------------------------------------------------- + draw_table: function () { + var table = this.build_table(); + this.draw_headers(table.headers); + this.draw_measure_row(table.measure_row); + this.draw_rows(table.rows); + }, + + make_header_cell: function (header) { + var cell = (_.has(header, 'cells') ? $('<td>') : $('<th>')) + .addClass('graph_border') + .attr('rowspan', header.height) + .attr('colspan', header.width); + var content = $('<span>').addClass('web_graph_click') + .attr('href','#') + .text(' ' + (header.title || _t('Undefined'))) + .attr('data-id', header.id); + if (_.has(header, 'expanded')) { + content.addClass(header.expanded ? 'fa fa-minus-square' : 'fa fa-plus-square'); + } else { + content.css('font-weight', 'bold'); + } + if (_.has(header, 'indent')) { + for (var i = 0; i < header.indent; i++) { cell.prepend($('<span>', {class:'web_graph_indent'})); } + } + return cell.append(content); + }, + + draw_headers: function (headers) { + var make_cell = this.make_header_cell, + empty_cell = $('<th>').attr('rowspan', headers.length), + thead = $('<thead>'); + + _.each(headers, function (row) { + var html_row = $('<tr>'); + _.each(row, function (header) { + html_row.append(make_cell(header)); + }); + thead.append(html_row); + }); + thead.children(':first').prepend(empty_cell); + this.table.append(thead); + }, + + draw_measure_row: function (measure_row) { + if (this.pivot.measures.length === 1) { return; } + var html_row = $('<tr>').append('<th>'); + _.each(measure_row, function (cell) { + var measure_cell = $('<th>').addClass('measure_row').text(cell.text); + if (cell.is_bold) {measure_cell.css('font-weight', 'bold');} + html_row.append(measure_cell); + }); + this.$('thead').append(html_row); + }, + + draw_rows: function (rows) { + var table = this.table, + make_cell = this.make_header_cell; + + _.each(rows, function (row) { + var html_row = $('<tr>').append(make_cell(row)); + _.each(row.cells, function (cell) { + var html_cell = $('<td>').text(cell.value); + if (_.has(cell, 'color')) { + html_cell.css('background-color', $.Color(255, cell.color, cell.color)); + } + if (cell.is_bold) { html_cell.css('font-weight', 'bold'); } + html_row.append(html_cell); + }); + table.append(html_row); + }); + }, + + // ---------------------------------------------------------------------- + // Drawing charts code + // ---------------------------------------------------------------------- + bar: function () { + var self = this, + dim_x = this.pivot.rows.groupby.length, + dim_y = this.pivot.cols.groupby.length, + show_controls = (this.width > 400 && this.height > 300 && dim_x + dim_y >=2), + data; + + // No groupby + if ((dim_x === 0) && (dim_y === 0)) { + data = [{key: _t('Total'), values:[{ + x: _t('Total'), + y: this.pivot.get_total(), + }]}]; + // Only column groupbys + } else if ((dim_x === 0) && (dim_y >= 1)){ + data = _.map(this.pivot.get_cols_with_depth(1), function (header) { + return { + key: header.title, + values: [{x:header.title, y: self.pivot.get_total(header)[0]}] + }; + }); + // Just 1 row groupby + } else if ((dim_x === 1) && (dim_y === 0)) { + data = _.map(this.pivot.main_row().children, function (pt) { + var value = self.pivot.get_total(pt), + title = (pt.title !== undefined) ? pt.title : _t('Undefined'); + return {x: title, y: value}; + }); + data = [{key: self.pivot.measures[0].string, values:data}]; + // 1 row groupby and some col groupbys + } else if ((dim_x === 1) && (dim_y >= 1)) { + data = _.map(this.pivot.get_cols_with_depth(1), function (colhdr) { + var values = _.map(self.pivot.get_rows_with_depth(1), function (header) { + return { + x: header.title || _t('Undefined'), + y: self.pivot.get_values(header.id, colhdr.id)[0] || 0 + }; + }); + return {key: colhdr.title || _t('Undefined'), values: values}; + }); + // At least two row groupby + } else { + var keys = _.uniq(_.map(this.pivot.get_rows_with_depth(2), function (hdr) { + return hdr.title || _t('Undefined'); + })); + data = _.map(keys, function (key) { + var values = _.map(self.pivot.get_rows_with_depth(1), function (hdr) { + var subhdr = _.find(hdr.children, function (child) { + return ((child.title === key) || ((child.title === undefined) && (key === _t('Undefined')))); + }); + return { + x: hdr.title || _t('Undefined'), + y: (subhdr) ? self.pivot.get_total(subhdr)[0] : 0 + }; + }); + return {key:key, values: values}; + }); + } + + nv.addGraph(function () { + var chart = nv.models.multiBarChart() + .width(self.width) + .height(self.height) + .reduceXTicks(false) + .stacked(self.bar_ui === 'stack') + .showControls(show_controls); + + if (self.width / data[0].values.length < 80) { + chart.rotateLabels(-15); + chart.reduceXTicks(true); + chart.margin({bottom:40}); + } + + d3.select(self.svg) + .datum(data) + .attr('width', self.width) + .attr('height', self.height) + .call(chart); + + nv.utils.windowResize(chart.update); + return chart; + }); + + }, + + line: function () { + var self = this, + dim_x = this.pivot.rows.groupby.length, + dim_y = this.pivot.cols.groupby.length; + + var data = _.map(this.pivot.get_cols_leaves(), function (col) { + var values = _.map(self.pivot.get_rows_with_depth(dim_x), function (row) { + return {x: row.title, y: self.pivot.get_values(row.id,col.id)[0] || 0}; + }); + var title = _.map(col.path, function (p) { + return p || _t('Undefined'); + }).join('/'); + if (dim_y === 0) { + title = self.pivot.measures[0].string; + } + return {values: values, key: title}; + }); + + nv.addGraph(function () { + var chart = nv.models.lineChart() + .x(function (d,u) { return u; }) + .width(self.width) + .height(self.height) + .margin({top: 30, right: 20, bottom: 20, left: 60}); + + d3.select(self.svg) + .attr('width', self.width) + .attr('height', self.height) + .datum(data) + .call(chart); + + return chart; + }); + }, + + pie: function () { + var self = this, + dim_x = this.pivot.rows.groupby.length; + var data = _.map(this.pivot.get_rows_leaves(), function (row) { + var title = _.map(row.path, function (p) { + return p || _t('Undefined'); + }).join('/'); + if (dim_x === 0) { + title = self.measure_label; + } + return {x: title, y: self.pivot.get_total(row)}; + }); + + nv.addGraph(function () { + var chart = nv.models.pieChart() + .color(d3.scale.category10().range()) + .width(self.width) + .height(self.height); + + d3.select(self.svg) + .datum(data) + .transition().duration(1200) + .attr('width', self.width) + .attr('height', self.height) + .call(chart); + + nv.utils.windowResize(chart.update); + return chart; + }); + }, + + // ---------------------------------------------------------------------- + // Controller stuff... + // ---------------------------------------------------------------------- + export_xls: function() { + var c = openerp.webclient.crashmanager; + openerp.web.blockUI(); + this.session.get_file({ + url: '/web_graph/export_xls', + data: {data: JSON.stringify(this.build_table())}, + complete: openerp.web.unblockUI, + error: c.rpc_error.bind(c) + }); + }, + +}); + +// Utility function: returns true if the beginning of array2 is array1 and +// if array1 is not array2 +function is_strict_beginning_of (array1, array2) { + if (array1.length >= array2.length) { return false; } + var result = true; + for (var i = 0; i < array1.length; i++) { + if (!_.isEqual(array1[i], array2[i])) { return false;} + } + return result; +} +})(); diff --git a/addons/web_graph/static/src/js/pivot_table.js b/addons/web_graph/static/src/js/pivot_table.js new file mode 100644 index 00000000000..f77d49f9a1a --- /dev/null +++ b/addons/web_graph/static/src/js/pivot_table.js @@ -0,0 +1,469 @@ + +/* jshint undef: false */ + +(function () { +'use strict'; + +var _lt = openerp.web._lt; +var _t = openerp.web._t; + +// PivotTable requires a call to update_data after initialization +openerp.web_graph.PivotTable = openerp.web.Class.extend({ + + init: function (model, domain, fields, options) { + this.cells = []; + this.domain = domain; + this.no_data = true; + this.model = model; + this.fields = fields; + this.fields.__count = {type: 'integer', string:_t('Quantity')}; + this.measures = options.measures || []; + this.rows = { groupby: options.row_groupby, headers: null }; + this.cols = { groupby: options.col_groupby, headers: null }; + }, + + // ---------------------------------------------------------------------- + // Configuration methods + // ---------------------------------------------------------------------- + // this.measures: list of measure [measure], measure = {field: _, string: _, type: _} + // this.rows.groupby, this.cols.groupby : list of groupbys used for describing rows (...), + // a groupby is also {field:_, string:_, type:_} + // If its type is date/datetime, field can have the corresponding interval in its description, + // for example 'create_date:week'. + set_measures: function (measures) { + this.measures = measures; + return this.update_data(); + }, + + toggle_measure: function (measure) { + var current_measure = _.findWhere(this.measures, measure); + if (current_measure) { // remove current_measure + var index = this.measures.indexOf(current_measure); + this.measures = _.without(this.measures, current_measure); + if (this.measures.length === 0) { + this.no_data = true; + } else { + _.each(this.cells, function (cell) { + cell.values.splice(index, 1); + }); + } + return $.Deferred().resolve(); + } else { // add a new measure + this.measures.push(measure); + return this.update_data(); + } + }, + + set: function (domain, row_groupby, col_groupby) { + var row_gb_changed = !_.isEqual(row_groupby, this.rows.groupby), + col_gb_changed = !_.isEqual(col_groupby, this.cols.groupby); + + this.domain = domain; + this.rows.groupby = row_groupby; + this.cols.groupby = col_groupby; + + if (row_gb_changed) { this.rows.headers = null; } + if (col_gb_changed) { this.cols.headers = null; } + + return this.update_data(); + }, + + // ---------------------------------------------------------------------- + // Cells manipulation methods + // ---------------------------------------------------------------------- + // cells are objects {x:_, y:_, values:_} where x < y and values is an array + // of values (one for each measure). The condition x < y might look + // unnecessary, but it makes the rest of the code simpler: headers + // don't krow if they are rows or cols, they just know their id, so + // it is useful that a call get_values(id1, id2) is the same as get_values(id2, id1) + add_cell : function (id1, id2, values) { + this.cells.push({x: Math.min(id1, id2), y: Math.max(id1, id2), values: values}); + }, + + get_values: function (id1, id2, default_values) { + var cell = _.findWhere(this.cells, {x: Math.min(id1, id2), y: Math.max(id1, id2)}); + return (cell !== undefined) ? cell.values : (default_values || new Array(this.measures.length)); + }, + + // ---------------------------------------------------------------------- + // Headers/Rows/Cols manipulation methods + // ---------------------------------------------------------------------- + // this.rows.headers, this.cols.headers = [header] describe the tree structure + // of rows/cols. Headers are objects + // { + // id:_, (unique id obviously) + // path: [...], (array of all parents title, with its own title at the end) + // title:_, (name of the row/col) + // children:[_], (subrows or sub cols of this row/col) + // domain:_, (domain of data in this row/col) + // root:_ (ref to this.rows or this.cols corresponding to the header) + // expanded:_ (boolean, true if it has been expanded) + // } + is_row: function (id) { + return !!_.findWhere(this.rows.headers, {id:id}); + }, + + is_col: function (id) { + return !!_.findWhere(this.cols.headers, {id:id}); + }, + + get_header: function (id) { + return _.findWhere(this.rows.headers.concat(this.cols.headers), {id:id}); + }, + + _get_headers_with_depth: function (headers, depth) { + return _.filter(headers, function (header) { + return header.path.length === depth; + }); + }, + + // return all columns with a path length of 'depth' + get_cols_with_depth: function (depth) { + return this._get_headers_with_depth(this.cols.headers, depth); + }, + + // return all rows with a path length of 'depth' + get_rows_with_depth: function (depth) { + return this._get_headers_with_depth(this.rows.headers, depth); + }, + + get_ancestor_leaves: function (header) { + return _.where(this.get_ancestors_and_self(header), {expanded:false}); + }, + + // return all non expanded rows + get_rows_leaves: function () { + return _.where(this.rows.headers, {expanded:false}); + }, + + // return all non expanded cols + get_cols_leaves: function () { + return _.where(this.cols.headers, {expanded:false}); + }, + + get_ancestors: function (header) { + var self = this; + if (!header.children) return []; + return [].concat.apply([], _.map(header.children, function (c) {return self.get_ancestors_and_self(c); })); + }, + + get_ancestors_and_self: function (header) { + var self = this; + return [].concat.apply([header], _.map(header.children, function (c) { return self.get_ancestors_and_self(c); })); + }, + + get_total: function (header) { + return (header) ? this.get_values(header.id, this.get_other_root(header).headers[0].id) + : this.get_values(this.rows.headers[0].id, this.cols.headers[0].id); + }, + + get_other_root: function (header) { + return (header.root === this.rows) ? this.cols : this.rows; + }, + + main_row: function () { return this.rows.headers[0]; }, + + main_col: function () { return this.cols.headers[0]; }, + + // ---------------------------------------------------------------------- + // Table manipulation methods : fold/expand/swap + // ---------------------------------------------------------------------- + // return true if the folding changed the groupbys, false if not + fold: function (header) { + var ancestors = this.get_ancestors(header), + removed_ids = _.pluck(ancestors, 'id'); + + header.root.headers = _.difference(header.root.headers, ancestors); + header.children = []; + header.expanded = false; + + this.cells = _.reject(this.cells, function (cell) { + return (_.contains(removed_ids, cell.x) || _.contains(removed_ids, cell.y)); + }); + + var new_groupby_length = _.max(_.pluck(_.pluck(header.root.headers, 'path'), 'length')); + if (new_groupby_length < header.root.groupby.length) { + header.root.groupby.splice(new_groupby_length); + return true; + } + return false; + }, + + fold_with_depth: function (root, depth) { + var self = this; + _.each(this._get_headers_with_depth(root.headers, depth), function (header) { + self.fold(header); + }); + }, + + expand_all: function () { + this.rows.headers = null; + this.cols.headers = null; + return this.update_data(); + }, + + expand: function (header_id, groupby) { + var self = this, + header = this.get_header(header_id), + otherRoot = this.get_other_root(header), + fields = otherRoot.groupby.concat(this.measures); + + if (header.path.length === header.root.groupby.length) { + header.root.groupby.push(groupby); + } + groupby = [groupby].concat(otherRoot.groupby); + + return this.get_groups(groupby, fields, header.domain).then(function (groups) { + _.each(groups.reverse(), function (group) { + // make header + var child = self.make_header(group, header); + child.expanded = false; + header.children.splice(0,0, child); + header.root.headers.splice(header.root.headers.indexOf(header) + 1, 0, child); + // make cells + _.each(self.get_ancestors_and_self(group), function (data) { + var values = _.map(self.measures, function (m) { + return data.attributes.aggregates[m.field]; + }); + var other = _.find(otherRoot.headers, function (h) { + if (header.root === self.cols) { + return _.isEqual(data.path.slice(1), h.path); + } else { + return _.isEqual(_.rest(data.path), h.path); + } + }); + if (other) { + self.add_cell(child.id, other.id, values); + } + }); + }); + header.expanded = true; + }); + }, + + make_header: function (group, parent) { + var title = parent ? group.attributes.value : _t('Total'); + return { + id: _.uniqueId(), + path: parent ? parent.path.concat(title) : [], + title: title, + children: [], + domain: parent ? group.model._domain : this.domain, + root: parent ? parent.root : undefined, + }; + }, + + swap_axis: function () { + var temp = this.rows; + this.rows = this.cols; + this.cols = temp; + }, + + // ---------------------------------------------------------------------- + // Data updating methods + // ---------------------------------------------------------------------- + // Load the data from the db, using the method this.load_data + // update_data will try to preserve the expand/not expanded status of each + // column/row. If you want to expand all, then set this.cols.headers/this.rows.headers + // to null before calling update_data. + update_data: function () { + var self = this; + + return this.load_data().then (function (result) { + if (result) { + self.no_data = false; + self[self.cols.headers ? 'update_headers' : 'expand_headers'](self.cols, result.col_headers); + self[self.rows.headers ? 'update_headers' : 'expand_headers'](self.rows, result.row_headers); + } else { + self.no_data = true; + } + }); + }, + + expand_headers: function (root, new_headers) { + root.headers = new_headers; + _.each(root.headers, function (header) { + header.root = root; + header.expanded = (header.children.length > 0); + }); + }, + + update_headers: function (root, new_headers) { + _.each(root.headers, function (header) { + var corresponding_header = _.find(new_headers, function (h) { + return _.isEqual(h.path, header.path); + }); + if (corresponding_header && header.expanded) { + corresponding_header.expanded = true; + _.each(corresponding_header.children, function (c) { + c.expanded = false; + }); + } + if (corresponding_header && (!header.expanded)) { + corresponding_header.expanded = false; + } + }); + var updated_headers = _.filter(new_headers, function (header) { + return (header.expanded !== undefined); + }); + _.each(updated_headers, function (header) { + if (!header.expanded) { + header.children = []; + } + header.root = root; + }); + root.headers = updated_headers; + }, + + // ---------------------------------------------------------------------- + // Data loading methods + // ---------------------------------------------------------------------- + + // To obtain all the values required to draw the full table, we have to do + // at least 2 + min(row.groupby.length, col.groupby.length) + // calls to readgroup. To simplify the code, we will always do + // 2 + row.groupby.length calls. For example, if row.groupby = [r1, r2, r3] + // and col.groupby = [c1, c2], then we will make the call with the following + // groupbys: [r1,r2,r3], [c1,r1,r2,r3], [c1,c2,r1,r2,r3], []. + load_data: function () { + var self = this, + cols = this.cols.groupby, + rows = this.rows.groupby, + visible_fields = rows.concat(cols, self.measures); + + if (this.measures.length === 0) { + return $.Deferred.resolve().promise(); + } + + var groupbys = _.map(_.range(cols.length + 1), function (i) { + return cols.slice(0, i).concat(rows); + }); + groupbys.push([]); + + var get_data_requests = _.map(groupbys, function (groupby) { + return self.get_groups(groupby, visible_fields, self.domain); + }); + + return $.when.apply(null, get_data_requests).then(function () { + var data = Array.prototype.slice.call(arguments), + row_data = data[0], + col_data = (cols.length !== 0) ? data[data.length - 2] : [], + has_data = data[data.length - 1][0]; + + return has_data && self.format_data(col_data, row_data, data); + }); + }, + + get_groups: function (groupbys, fields, domain, path) { + var self = this, + groupby = (groupbys.length) ? groupbys[0] : []; + path = path || []; + + return this._query_db(groupby, fields, domain, path).then(function (groups) { + if (groupbys.length > 1) { + var get_subgroups = $.when.apply(null, _.map(groups, function (group) { + return self.get_groups(_.rest(groupbys), fields, group.model._domain, path.concat(group.attributes.value)).then(function (subgroups) { + group.children = subgroups; + }); + })); + return get_subgroups.then(function () { + return groups; + }); + } else { + return groups; + } + }); + + }, + + _query_db: function (groupby, fields, domain, path) { + var self = this, + field_ids = _.without(_.pluck(fields, 'field'), '__count'), + fields = _.map(field_ids, function(f) { return self.raw_field(f); }); + + return this.model.query(field_ids) + .filter(domain) + .group_by(groupby.field) + .then(function (results) { + var groups = _.filter(results, function (group) { + return group.attributes.length > 0; + }); + return _.map(groups, function (g) { return self.format_group(g, path); }); + }); + }, + + // if field is a fieldname, returns field, if field is field_id:interval, retuns field_id + raw_field: function (field) { + return field.split(':')[0]; + }, + + // add the path to the group and sanitize the value... + format_group: function (group, current_path) { + var attrs = group.attributes, + value = attrs.value, + grouped_on = attrs.grouped_on ? this.raw_field(attrs.grouped_on) : false; + + if (value === false) { + group.attributes.value = _t('Undefined'); + } else if (grouped_on && this.fields[grouped_on].type === 'selection') { + var selection = this.fields[grouped_on].selection, + value_lookup = _.where(selection, {0:value}); + group.attributes.value = value_lookup ? value_lookup[0][1] : _t('Undefined'); + } else if (value instanceof Array) { + group.attributes.value = value[1]; + } + + group.path = (value !== undefined) ? (current_path || []).concat(group.attributes.value) : []; + group.attributes.aggregates.__count = group.attributes.length; + + return group; + }, + + format_data: function (col_data, row_data, cell_data) { + var self = this, + dim_row = this.rows.groupby.length, + dim_col = this.cols.groupby.length, + col_headers = this.get_ancestors_and_self(this.make_headers(col_data, dim_col)), + row_headers = this.get_ancestors_and_self(this.make_headers(row_data, dim_row)); + + this.cells = []; + _.each(cell_data, function (data, index) { + self.make_cells(data, index, [], row_headers, col_headers); + }); // not pretty. make it more functional? + + return {col_headers: col_headers, row_headers: row_headers}; + }, + + make_headers: function (data, depth, parent) { + var self = this, + main = this.make_header(data, parent); + + if (main.path.length < depth) { + main.children = _.map(data.children || data, function (data_pt) { + return self.make_headers (data_pt, depth, main); + }); + } + return main; + }, + + make_cells: function (data, index, current_path, rows, cols) { + var self = this; + _.each(data, function (group) { + var attr = group.attributes, + path = attr.grouped_on ? current_path.concat(attr.value) : current_path, + values = _.map(self.measures, function (measure) { return attr.aggregates[measure.field]; }), + row = _.find(rows, function (header) { return _.isEqual(header.path, path.slice(index)); }), + col = _.find(cols, function (header) { return _.isEqual(header.path, path.slice(0, index)); }); + + self.add_cell(row.id, col.id, values); + if (group.children) { + self.make_cells (group.children, index, path, rows, cols); + } + }); + }, + +}); + +})(); + + diff --git a/addons/web_graph/static/src/xml/web_graph.xml b/addons/web_graph/static/src/xml/web_graph.xml index e2e316b22d9..4522fd2504f 100644 --- a/addons/web_graph/static/src/xml/web_graph.xml +++ b/addons/web_graph/static/src/xml/web_graph.xml @@ -1,41 +1,109 @@ -<template> - <div t-name="GraphView" id="element-chart" class="oe_semantic_html_override editor-render" style="position:relative; width: 300px;"> - <a href="#" class="oe_e dropdown-menu-icon" data-toggle="dropdown" title="Graph Options">&iacute;</a> - <div class="graph_header_legend"> +<templates> + <div t-name="GraphWidget" class="graph_widget"> + <div class="graph_header"> + + <div class="btn-toolbar hidden-print" role="toolbar"> + + <div class="btn-group graph_mode_selection" data-toggle="buttons"> + <label class="btn btn-default" data-mode="pivot" title="Table Mode"> + <input type="radio" name="modes"/><span class="fa fa-table"></span> + </label> + <label class="btn btn-default" data-mode="bar" title="Bar Chart"> + <input type="radio" name="modes"/><span class="fa fa-bar-chart-o"></span> + </label> + <label class="btn btn-default" data-mode="line" title="Line Chart"> + <input type="radio" name="modes"/><span class="fa fa-check-square"></span> + </label> + <label class="btn btn-default" data-mode="pie" title="Pie Chart"> + <input type="radio" name="modes"/><span class="fa fa-adjust"></span> + </label> + </div> + + <div class="btn-group graph_heatmap" data-toggle="buttons" > + <label class="btn btn-default" data-mode="both" title="Heat Map"> + <input type="radio" name="heatmap"/><span class="fa fa-th"></span> + </label> + <label class="btn btn-default" data-mode="row" title="Heat Map (rows)"> + <input type="radio" name="heatmap"/><span class="fa fa-bars"></span> + </label> + <label class="btn btn-default" data-mode="col" title="Heat Map (columns)"> + <input type="radio" name="heatmap"/><span class="fa fa-bars fa-rotate-90"></span> + </label> </div> - <div class="graph-dropdown"> - <h3 class="menu-section"> - Graph Mode - </h3> - <ul class="graph-menu oe_graph_options"> - <li><a href="#" data-mode="pie">Pie</a></li> - <li><a href="#" data-mode="bar" data-legend="top" data-stacked="true">Bars</a> - </li> - <li><a href="#" data-mode="line">Lines</a></li> - <li><a href="#" data-mode="area" data-legend="top" data-stacked="true">Areas</a> - </li> - <li><a href="#" data-mode="radar" data-legend="inside" - data-orientation="0">Radar</a></li> - </ul> - <h3 class="menu-section"> - Legend - </h3> - <ul class="graph-menu oe_graph_options"> - <li><a href="#" data-legend="no">Hidden</a></li> - <li><a href="#" data-legend="inside">Inside</a></li> - <li><a href="#" data-legend="top">Top</a></li> - </ul> - <h3 class="menu-section"> - Actions - </h3> - <ul class="graph-menu"> - <li><a href="#" id="graph_switch">Switch Axis</a></li> - <li><a href="#" id="graph_show_data">Show Data</a></li> - <li><a href="#" id="graph_download">Download as PNG</a></li> - </ul> + + <div class="btn-group graph_options_selection"> + <label class="btn btn-default" data-choice="swap_axis" title="Swap Axis"> + <span class="fa fa-expand"></span> + </label> + <label class="btn btn-default" data-choice="update_values" title="Reload Data"> + <span class="fa fa-refresh"></span> + </label> + <label class="btn btn-default" data-choice="export_data" title="Export Data" style="display:none"> + <span class="fa fa-download"></span> + </label> </div> - <div id="editor-render-body"> + <div class="btn-group"> + <label class="btn btn-default dropdown-toggle" data-toggle="dropdown"> + Measures <span class="caret"></span> + </label> + <ul class="dropdown-menu graph_measure_selection" role="menu"> + </ul> </div> </div> + </div> + <div class="graph_main_content"></div> + </div> -</template> + <div t-name="field_selection" class="btn-group"> + <span class="btn btn-default field-selection dropdown-toggle" data-toggle="dropdown"></span> + <ul class="dropdown-menu" role="menu"> + <t t-foreach="fields" t-as="field"> + <t t-if="(field.type === 'date') || (field.type === 'datetime')"> + <li> + <a class="field-selection" t-att-data-field-id="field.id" t-att-data-interval="'day'" t-att-data-id="header_id" href="#"> + <t t-esc="field.value + ' (day)'"/> + </a> + </li> + <li> + <a class="field-selection" t-att-data-field-id="field.id" t-att-data-interval="'week'" t-att-data-id="header_id" href="#"> + <t t-esc="field.value + ' (week)'"/> + </a> + </li> + <li> + <a class="field-selection" t-att-data-field-id="field.id" t-att-data-interval="'month'" t-att-data-id="header_id" href="#"> + <t t-esc="field.value + ' (month)'"/> + </a> + </li> + <li> + <a class="field-selection" t-att-data-field-id="field.id" t-att-data-interval="'quarter'" t-att-data-id="header_id" href="#"> + <t t-esc="field.value + ' (quarter)'"/> + </a> + </li> + <li> + <a class="field-selection" t-att-data-field-id="field.id" t-att-data-interval="'year'" t-att-data-id="header_id" href="#"> + <t t-esc="field.value + ' (year)'"/> + </a> + </li> + </t> + + <t t-if="(field.type !== 'date') && (field.type !== 'datetime')"> + <li> + <a class="field-selection" t-att-data-field-id="field.id" t-att-data-id="header_id" href="#"> + <t t-esc="field.value"/> + </a> + </li> + </t> + </t> + </ul> + </div> + + <div t-name="graph_no_data" class="oe_view_nocontent" > + <div class="oe_empty_custom_dashboard"> + <p><b>No data to display.</b></p> + <p> + No data available for this graph. Try to add some records, or make sure + that there is at least one measure and no active filter in the search bar. + </p> + </div> + </div> +</templates> |
