[REF] work in progress. reset graph view to minimal state in addon web_graph

bzr revid: ged@openerp.com-20131108113210-aptz2pne05iwca82
This commit is contained in:
Gery Debongnie 2013-11-08 12:32:10 +01:00
parent 32b20f5ed6
commit 69257d5d6f
61 changed files with 5 additions and 26056 deletions

View File

@ -15,37 +15,6 @@ Graph Views for Web Client.
'version': '3.0',
'depends': ['web'],
'js': [
'static/lib/dropdown.js',
'static/lib/flotr2/lib/bean.js',
'static/lib/flotr2/js/Flotr.js',
'static/lib/flotr2/js/DefaultOptions.js',
'static/lib/flotr2/js/Color.js',
'static/lib/flotr2/js/Date.js',
'static/lib/flotr2/js/DOM.js',
'static/lib/flotr2/js/EventAdapter.js',
'static/lib/flotr2/js/Text.js',
'static/lib/flotr2/js/Graph.js',
'static/lib/flotr2/js/Axis.js',
'static/lib/flotr2/js/Series.js',
'static/lib/flotr2/js/types/lines.js',
'static/lib/flotr2/js/types/bars.js',
'static/lib/flotr2/js/types/bubbles.js',
'static/lib/flotr2/js/types/candles.js',
'static/lib/flotr2/js/types/gantt.js',
'static/lib/flotr2/js/types/markers.js',
'static/lib/flotr2/js/types/pie.js',
'static/lib/flotr2/js/types/points.js',
'static/lib/flotr2/js/types/radar.js',
'static/lib/flotr2/js/types/timeline.js',
'static/lib/flotr2/js/plugins/crosshair.js',
'static/lib/flotr2/js/plugins/download.js',
'static/lib/flotr2/js/plugins/grid.js',
'static/lib/flotr2/js/plugins/hit.js',
'static/lib/flotr2/js/plugins/selection.js',
'static/lib/flotr2/js/plugins/labels.js',
'static/lib/flotr2/js/plugins/legend.js',
'static/lib/flotr2/js/plugins/spreadsheet.js',
'static/lib/flotr2/js/plugins/titles.js',
'static/src/js/graph.js'
],
'css': [

View File

@ -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 );

View File

@ -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.

View File

@ -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

View File

@ -1,89 +0,0 @@
Flotr2
======
The Canvas graphing library.
![Google Groups](http://groups.google.com/intl/en/images/logos/groups_logo_sm.gif)
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.

View File

@ -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

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -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;
})();

View File

@ -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;
})();

View File

@ -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 };
}
};
})();

View File

@ -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"]
};

View File

@ -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
}
};

View File

@ -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
};
}
}
};
})();

View File

@ -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;
})();

View File

@ -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;
})();

View File

@ -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;
})();

View File

@ -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;
})();

View File

@ -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
);
}
}
});
})();

View File

@ -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;
}
});
})();

View File

@ -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;
}
}
});
})();

View File

@ -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;
}
})();

View File

@ -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);
}
});
})();

View File

@ -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));
}
}
}
}
}
});
})();

View File

@ -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);
}
}
}
}
}
});
})();

View File

@ -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;
}
});
})();

View File

@ -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>&nbsp;</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);
}
});
})();

View File

@ -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);
}
}
});
})();

View File

@ -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;
}
});

View File

@ -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)
});

View File

@ -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);
}
}
});

View File

@ -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);
}
}
});

View File

@ -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);
}
};
}
}
});

View File

@ -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);
}
})();

View File

@ -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];
}
});
})();

View File

@ -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();
}
}
});

View File

@ -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);
}
});

View File

@ -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;
}
});

View File

@ -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;
})();

File diff suppressed because one or more lines are too long

View File

@ -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
});

View File

@ -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;
}
};
})();

View File

@ -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;
}

File diff suppressed because it is too large Load Diff

View File

@ -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;
});

View File

@ -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.

View File

@ -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;
};

View File

@ -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;
}

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 700 B

File diff suppressed because it is too large Load Diff

View File

@ -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}})();

View File

@ -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;
};
})();

View File

@ -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)

View File

@ -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;
}

View File

@ -1,80 +0,0 @@
.openerp a.dropdown-menu-icon {
z-index: 1;
position: absolute;
color: #4c4c4c;
right: 8px;
}
.openerp a.dropdown-menu-icon:hover {
text-decoration: none;
}
.editor-render {
position: relative;
}
#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;
font-size: 13px;
}
.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;
}

View File

@ -7,15 +7,6 @@ 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({
@ -24,411 +15,16 @@ instance.web_graph.GraphView = instance.web.View.extend({
view_type: "graph",
init: function(parent, dataset, view_id, options) {
var self = this;
this._super(parent);
this._super(parent, dataset, view_id, options);
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() {
do_show: function () {
this.do_push_state({});
return this._super();
},
});
};

View File

@ -1,41 +1,4 @@
<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">&amp;iacute;</a>
<div class="graph_header_legend">
</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>
<div id="editor-render-body">
</div>
<div t-name="GraphView">
</div>
</template>