Commit 87390ae7 authored by olau@iola.dk's avatar olau@iola.dk

Added first version

git-svn-id: https://flot.googlecode.com/svn/trunk@2 1e0a6537-2640-0410-bfb7-f154510ff394
parent d68d1600
To be filled in...
About
-----
Flot is a Javascript plotting library for jQuery. Read more at the
website:
http://code.google.com/p/flot/
Take a look at the examples linked from above, they should give a good
impression of what Flot can do.
Installation
------------
Just include the Javascript file after you've included jQuery. Note that you
need to download a version of Excanvas (I currently suggest you take
the one on the Flot homepage as it contains a bugfix for drawing
filled shapes) which is canvas emulation on Internet Explorer. And
don't worry, the emulation is otherwise working fine.
You can include the excanvas script like this:
<!--[if IE]><script language="javascript" type="text/javascript" src="excanvas.js"></script><![endif]-->
Also note that you need at least jQuery 1.2.1.
Basic usage
-----------
Create a placeholder div to put the graph in:
<div id="placeholder"></div>
You need to set the width and height of this div, otherwise the plot
library doesn't know how to scale the graph. You can do it inline or
with an external stylesheet.
Then on document ready, run the plot function:
$.plot($("#placeholder"), data, options);
Here, data is an array of data series and options is an object with
settings if you want to customize the plot. Take a look at the
examples for some ideas of what to put in or look at the reference
in the file "API.txt".
The plot function immediately draws the chart and then returns a Plot
object with a couple of methods.
What's with the name?
---------------------
Well, "Flot" is like "Plot".
And if you look up "flot" in a Danish-to-English dictionary, some up
the words that come up are "good-looking", "attractive", "stylish",
"smart", "impressive", "extravagant". One of the main goals with Flot
is pretty looks.
These are mostly ideas, that they're written down here is no guarantee
that they'll ever be done. If you want something done, feel free to
say why or come up with a patch. :-)
support segmented lines
- put in null value to signify a break in the data set
grid configuration
- how ticks look like
- (handling of 1/2 linewidth hack in grid?)
selection
- user should be able to cancel selection with escape
- select points
interactive zooming
- convenience zoom(x1, y1, x2, y2)? and zoomOut() (via zoom stack)?
- auto-zoom mode?
- auto-margin
handling time data
- dataformat
- axis adjustment
- tick generation
support for highlighting stuff
- lines
- points
legend
- interactive auto-highlight of graph?
interactive hot points
- fire event with value
interactive hover over lines
- fire event with graph id
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Flot Examples</title>
<link href="layout.css" rel="stylesheet" type="text/css"></link>
<!--[if IE]><script language="javascript" type="text/javascript" src="../excanvas.js"></script><![endif]-->
<script language="javascript" type="text/javascript" src="../jquery.js"></script>
<script language="javascript" type="text/javascript" src="../jquery.flot.js"></script>
</head>
<body>
<h1>Flot Examples</h1>
<div id="placeholder" style="width:600px;height:300px;"></div>
<p>Simple example. You don't need to specify much to get an
attractive look. Put in a placeholder, make sure you set its
dimensions (otherwise the plot library will barf) and call the
plot function with the data. The axes are automatically
scaled.</p>
<script id="source" language="javascript" type="text/javascript">
$(function () {
var d1 = [];
for (var i = 0; i < 14; i += 0.5)
d1.push([i, Math.sin(i)]);
var d2 = [[0, 3], [4, 8], [8, 5], [9, 13]];
$.plot($("#placeholder"), [ d1, d2 ]);
});
</script>
</body>
</html>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Flot Examples</title>
<link href="layout.css" rel="stylesheet" type="text/css"></link>
<!--[if IE]><script language="javascript" type="text/javascript" src="../excanvas.js"></script><![endif]-->
<script language="javascript" type="text/javascript" src="../jquery.js"></script>
<script language="javascript" type="text/javascript" src="../jquery.flot.js"></script>
</head>
<body>
<h1>Flot Examples</h1>
<div id="placeholder" style="width:600px;height:300px"></div>
<p>Flot supports lines, points, filled areas, bars and any
combinations of these, in the same plot and even on the same data
series.</p>
<script id="source" language="javascript" type="text/javascript">
$(function () {
var d1 = [];
for (var i = 0; i < 14; i += 0.5)
d1.push([i, Math.sin(i)]);
var d2 = [[0, 3], [4, 8], [8, 5], [9, 13]];
var d3 = [];
for (var i = 0; i < 14; i += 0.5)
d3.push([i, Math.cos(i)]);
var d4 = [];
for (var i = 0; i < 14; i += 0.5)
d4.push([i, Math.sqrt(i * 10)]);
var d5 = [];
for (var i = 0; i < 14; i += 0.5)
d5.push([i, Math.sqrt(i)]);
$.plot($("#placeholder"), [
{
data: d1,
lines: { show: true, fill: true }
},
{
data: d2,
bars: { show: true }
},
{
data: d3,
points: { show: true }
},
{
data: d4,
lines: { show: true }
},
{
data: d5,
lines: { show: true },
points: { show: true }
}
]);
});
</script>
</body>
</html>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Flot Examples</title>
<link href="layout.css" rel="stylesheet" type="text/css"></link>
<!--[if IE]><script language="javascript" type="text/javascript" src="../excanvas.js"></script><![endif]-->
<script language="javascript" type="text/javascript" src="../jquery.js"></script>
<script language="javascript" type="text/javascript" src="../jquery.flot.js"></script>
</head>
<body>
<h1>Flot Examples</h1>
<p>Here are some examples:</p>
<ul>
<li><a href="basic.html">Basic example</a></li>
<li><a href="graph-types.html">Different graph types</a></li>
<li><a href="setting-options.html">Setting various options</a></li>
<li><a href="real-data.html">Real data with a bit of interactivity</a></li>
<li><a href="selection.html">Selection support and zooming</a></li>
<li><a href="zooming.html">Zooming with overview</a></li>
</ul>
</body>
</html>
body {
font-family: sans-serif;
font-size: 16px;
margin: 50px;
}
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Flot Examples</title>
<link href="layout.css" rel="stylesheet" type="text/css"></link>
<!--[if IE]><script language="javascript" type="text/javascript" src="../excanvas.js"></script><![endif]-->
<script language="javascript" type="text/javascript" src="../jquery.js"></script>
<script language="javascript" type="text/javascript" src="../jquery.flot.js"></script>
</head>
<body>
<h1>Flot Examples</h1>
<div id="placeholder" style="width:600px;height:300px;"></div>
<p>Here is an example with real data: military budgets for
various countries in constant (2005) million US dollars (source: <a href="http://www.sipri.org/">SIPRI</a>).</p>
<p>Since all data is available client-side, it's pretty easy to
make the plot interactive. Try turning countries on/off with the
checkboxes below.</p>
<p id="choices">Show:</p>
<script id="source" language="javascript" type="text/javascript">
$(function () {
var datasets = {
"usa": {
label: "USA",
data: [[1988, 483994], [1989, 479060], [1990, 457648], [1991, 401949], [1992, 424705], [1993, 402375], [1994, 377867], [1995, 357382], [1996, 337946], [1997, 336185], [1998, 328611], [1999, 329421], [2000, 342172], [2001, 344932], [2002, 387303], [2003, 440813], [2004, 480451], [2005, 504638], [2006, 528692]]
},
"russia": {
label: "Russia",
data: [[1988, 218000], [1989, 203000], [1990, 171000], [1992, 42500], [1993, 37600], [1994, 36600], [1995, 21700], [1996, 19200], [1997, 21300], [1998, 13600], [1999, 14000], [2000, 19100], [2001, 21300], [2002, 23600], [2003, 25100], [2004, 26100], [2005, 31100], [2006, 34700]]
},
"uk": {
label: "UK",
data: [[1988, 62982], [1989, 62027], [1990, 60696], [1991, 62348], [1992, 58560], [1993, 56393], [1994, 54579], [1995, 50818], [1996, 50554], [1997, 48276], [1998, 47691], [1999, 47529], [2000, 47778], [2001, 48760], [2002, 50949], [2003, 57452], [2004, 60234], [2005, 60076], [2006, 59213]]
},
"germany": {
label: "Germany",
data: [[1988, 55627], [1989, 55475], [1990, 58464], [1991, 55134], [1992, 52436], [1993, 47139], [1994, 43962], [1995, 43238], [1996, 42395], [1997, 40854], [1998, 40993], [1999, 41822], [2000, 41147], [2001, 40474], [2002, 40604], [2003, 40044], [2004, 38816], [2005, 38060], [2006, 36984]]
},
"denmark": {
label: "Denmark",
data: [[1988, 3813], [1989, 3719], [1990, 3722], [1991, 3789], [1992, 3720], [1993, 3730], [1994, 3636], [1995, 3598], [1996, 3610], [1997, 3655], [1998, 3695], [1999, 3673], [2000, 3553], [2001, 3774], [2002, 3728], [2003, 3618], [2004, 3638], [2005, 3467], [2006, 3770]]
},
"sweden": {
label: "Sweden",
data: [[1988, 6402], [1989, 6474], [1990, 6605], [1991, 6209], [1992, 6035], [1993, 6020], [1994, 6000], [1995, 6018], [1996, 3958], [1997, 5780], [1998, 5954], [1999, 6178], [2000, 6411], [2001, 5993], [2002, 5833], [2003, 5791], [2004, 5450], [2005, 5521], [2006, 5271]]
},
"norway": {
label: "Norway",
data: [[1988, 4382], [1989, 4498], [1990, 4535], [1991, 4398], [1992, 4766], [1993, 4441], [1994, 4670], [1995, 4217], [1996, 4275], [1997, 4203], [1998, 4482], [1999, 4506], [2000, 4358], [2001, 4385], [2002, 5269], [2003, 5066], [2004, 5194], [2005, 4887], [2006, 4891]]
}
};
// hard-code color indices to prevent them from shifting as
// countries are turned on/off
var i = 0;
$.each(datasets, function(key, val) {
val.color = i;
++i;
});
// insert checkboxes
var choiceContainer = $("#choices");
$.each(datasets, function(key, val) {
choiceContainer.append('<br/><input type="checkbox" name="' + key +
'" checked="checked" >' + val.label + '</input>');
});
choiceContainer.find("input").click(plotAccordingToChoices);
function plotAccordingToChoices() {
var data = [];
choiceContainer.find("input:checked").each(function () {
var key = $(this).attr("name");
if (key && datasets[key])
data.push(datasets[key]);
});
if (data.length > 0)
$.plot($("#placeholder"), data, {
yaxis: { min: 0 },
xaxis: { tickDecimals: 0 }
});
}
plotAccordingToChoices();
});
</script>
</body>
</html>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Flot Examples</title>
<link href="layout.css" rel="stylesheet" type="text/css"></link>
<!--[if IE]><script language="javascript" type="text/javascript" src="../excanvas.js"></script><![endif]-->
<script language="javascript" type="text/javascript" src="../jquery.js"></script>
<script language="javascript" type="text/javascript" src="../jquery.flot.js"></script>
</head>
<body>
<h1>Flot Examples</h1>
<div id="placeholder" style="width:600px;height:300px"></div>
<p>1000 kg. CO<sub>2</sub> emissions per year per capita for various countries (source: <a href="http://en.wikipedia.org/wiki/List_of_countries_by_carbon_dioxide_emissions_per_capita">Wikipedia</a>).</p>
<p>Flot supports selections. You can enable
rectangular selection
or one-dimensional selection if the user should only be able to
select on one axis. Try left-clicking and drag on the plot above
where selection on the x axis is enabled.</p>
<p>You selected: <span id="selection"></span></p>
<p>The plot command returns a Plot object you can use to control
the selection. Try clicking the buttons below.</p>
<p><input id="clearSelection" type="button" value="Clear selection" />
<input id="setSelection" type="button" value="Select year 1994" /></p>
<p>Selections are really useful for zooming. Just replot the
chart with min and max values for the axes set to the values
in the "selected" event triggered. Try enabling the checkbox
below and select a region again.</p>
<p><input id="zoom" type="checkbox">Zoom to selection.</input></p>
<script id="source" language="javascript" type="text/javascript">
$(function () {
var data = [
{
label: "United States",
data: [[1990, 18.9], [1991, 18.7], [1992, 18.4], [1993, 19.3], [1994, 19.5], [1995, 19.3], [1996, 19.4], [1997, 20.2], [1998, 19.8], [1999, 19.9], [2000, 20.4], [2001, 20.1], [2002, 20.0], [2003, 19.8], [2004, 20.4]]
},
{
label: "Russia",
data: [[1992, 13.4], [1993, 12.2], [1994, 10.6], [1995, 10.2], [1996, 10.1], [1997, 9.7], [1998, 9.5], [1999, 9.7], [2000, 9.9], [2001, 9.9], [2002, 9.9], [2003, 10.3], [2004, 10.5]]
},
{
label: "United Kingdom",
data: [[1990, 10.0], [1991, 11.3], [1992, 9.9], [1993, 9.6], [1994, 9.5], [1995, 9.5], [1996, 9.9], [1997, 9.3], [1998, 9.2], [1999, 9.2], [2000, 9.5], [2001, 9.6], [2002, 9.3], [2003, 9.4], [2004, 9.79]]
},
{
label: "Germany",
data: [[1990, 12.4], [1991, 11.2], [1992, 10.8], [1993, 10.5], [1994, 10.4], [1995, 10.2], [1996, 10.5], [1997, 10.2], [1998, 10.1], [1999, 9.6], [2000, 9.7], [2001, 10.0], [2002, 9.7], [2003, 9.8], [2004, 9.79]]
},
{
label: "Denmark",
data: [[1990, 9.7], [1991, 12.1], [1992, 10.3], [1993, 11.3], [1994, 11.7], [1995, 10.6], [1996, 12.8], [1997, 10.8], [1998, 10.3], [1999, 9.4], [2000, 8.7], [2001, 9.0], [2002, 8.9], [2003, 10.1], [2004, 9.80]]
},
{
label: "Sweden",
data: [[1990, 5.8], [1991, 6.0], [1992, 5.9], [1993, 5.5], [1994, 5.7], [1995, 5.3], [1996, 6.1], [1997, 5.4], [1998, 5.4], [1999, 5.1], [2000, 5.2], [2001, 5.4], [2002, 6.2], [2003, 5.9], [2004, 5.89]]
},
{
label: "Norway",
data: [[1990, 8.3], [1991, 8.3], [1992, 7.8], [1993, 8.3], [1994, 8.4], [1995, 5.9], [1996, 6.4], [1997, 6.7], [1998, 6.9], [1999, 7.6], [2000, 7.4], [2001, 8.1], [2002, 12.5], [2003, 9.9], [2004, 19.0]]
}
];
var options = {
lines: { show: true },
points: { show: true },
legend: { noColumns: 2 },
xaxis: { tickDecimals: 0 },
yaxis: { min: 0 },
selection: { mode: "x" }
};
var placeholder = $("#placeholder");
placeholder.bind("selected", function (event, area) {
$("#selection").text(area.x1.toFixed(1) + " to " + area.x2.toFixed(1));
var zoom = $("#zoom").attr("checked");
if (zoom)
plot = $.plot(placeholder, data,
$.extend(true, {}, options, {
xaxis: { min: area.x1, max: area.x2 }
}));
});
var plot = $.plot(placeholder, data, options);
$("#clearSelection").click(function () {
plot.clearSelection();
});
$("#setSelection").click(function () {
plot.setSelection({ x1: 1994, x2: 1995 });
});
});
</script>
</body>
</html>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Flot Examples</title>
<link href="layout.css" rel="stylesheet" type="text/css"></link>
<!--[if IE]><script language="javascript" type="text/javascript" src="../excanvas.js"></script><![endif]-->
<script language="javascript" type="text/javascript" src="../jquery.js"></script>
<script language="javascript" type="text/javascript" src="../jquery.flot.js"></script>
</head>
<body>
<h1>Flot Examples</h1>
<div id="placeholder" style="width:600px;height:300px"></div>
<p>There are plenty of options you can set to control the precise
looks of your plot. You can control the axes, the legend, the
default graph type, the look of grid, etc.</p>
<p>The idea is that Flot goes to great lengths to provide <b>sensible
defaults</b> which you can then customize as needed for your
particular application. If you've found a use case where the
defaults can be improved, please don't hesitate to give your
feedback.</p>
<script id="source" language="javascript" type="text/javascript">
$(function () {
var d1 = [];
for (var i = 0; i < Math.PI * 2; i += 0.25)
d1.push([i, Math.sin(i)]);
var d2 = [];
for (var i = 0; i < Math.PI * 2; i += 0.25)
d2.push([i, Math.cos(i)]);
var d3 = [];
for (var i = 0; i < Math.PI * 2; i += 0.1)
d3.push([i, Math.tan(i)]);
$.plot($("#placeholder"), [
{ label: "sin(x)", data: d1},
{ label: "cos(x)", data: d2},
{ label: "tan(x)", data: d3}
], {
lines: { show: true },
points: { show: true },
xaxis: {
ticks: [[Math.PI/2, "\u03c0/2"], [Math.PI, "\u03c0"], [Math.PI * 3/2, "3\u03c0/2"], [Math.PI * 2, "2\u03c0"]]
},
yaxis: {
noTicks: 10,
min: -2,
max: 2
},
grid: {
backgroundColor: "#fffaff"
}
});
});
</script>
</body>
</html>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Flot Examples</title>
<link href="layout.css" rel="stylesheet" type="text/css"></link>
<!--[if IE]><script language="javascript" type="text/javascript" src="../excanvas.js"></script><![endif]-->
<script language="javascript" type="text/javascript" src="../jquery.js"></script>
<script language="javascript" type="text/javascript" src="../jquery.flot.js"></script>
</head>
<body>
<h1>Flot Examples</h1>
<div style="float:left">
<div id="placeholder" style="width:500px;height:300px"></div>
</div>
<div id="miniature" style="float:left;margin-left:20px;margin-top:50px">
<div id="overview" style="width:166px;height:100px"></div>
<p id="overviewLegend" style="margin-left:10px"></p>
</div>
<p style="clear:left"> The selection support makes even
pretty advanced zooming schemes possible. With a few lines of code,
the small overview plot to the right has been connected to the large
plot. Try selecting a rectangle on either of them.</p>
<script id="source" language="javascript" type="text/javascript">
$(function () {
// setup plot
function getData(x1, x2) {
var d = [];
for (var i = x1; i < x2; i += (x2 - x1) / 100)
d.push([i, Math.sin(i * Math.sin(i))]);
return [
{ label: "sin(x sin(x))", data: d }
];
}
var options = {
legend: { show: false },
lines: { show: true },
points: { show: true },
yaxis: { noTicks: 10 },
selection: { mode: "xy" }
};
var startData = getData(0, 3 * Math.PI);
var plot = $.plot($("#placeholder"), startData, options);
// setup overview
var overview = $.plot($("#overview"), startData, {
legend: { show: true, container: $("#overviewLegend") },
lines: { show: true, lineWidth: 1 },
shadowSize: 0,
xaxis: { noTicks: 4 },
yaxis: { noTicks: 3, min: -2, max: 2 },
grid: { color: "#999" },
selection: { mode: "xy" }
});
// now connect the two
var internalSelection = false;
$("#placeholder").bind("selected", function (event, area) {
// do the zooming
plot = $.plot($("#placeholder"), getData(area.x1, area.x2),
$.extend(true, {}, options, {
xaxis: { min: area.x1, max: area.x2 },
yaxis: { min: area.y1, max: area.y2 }
}));
if (internalSelection)
return; // prevent eternal loop
internalSelection = true;
overview.setSelection(area);
internalSelection = false;
});
$("#overview").bind("selected", function (event, area) {
if (internalSelection)
return;
internalSelection = true;
plot.setSelection(area);
internalSelection = false;
});
});
</script>
</body>
</html>
// Copyright 2006 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Known Issues:
//
// * Patterns are not implemented.
// * Radial gradient are not implemented. The VML version of these look very
// different from the canvas one.
// * Clipping paths are not implemented.
// * Coordsize. The width and height attribute have higher priority than the
// width and height style values which isn't correct.
// * Painting mode isn't implemented.
// * Canvas width/height should is using content-box by default. IE in
// Quirks mode will draw the canvas using border-box. Either change your
// doctype to HTML5
// (http://www.whatwg.org/specs/web-apps/current-work/#the-doctype)
// or use Box Sizing Behavior from WebFX
// (http://webfx.eae.net/dhtml/boxsizing/boxsizing.html)
// * Optimize. There is always room for speed improvements.
// only add this code if we do not already have a canvas implementation
if (!window.CanvasRenderingContext2D) {
(function () {
// alias some functions to make (compiled) code shorter
var m = Math;
var mr = m.round;
var ms = m.sin;
var mc = m.cos;
// this is used for sub pixel precision
var Z = 10;
var Z2 = Z / 2;
var G_vmlCanvasManager_ = {
init: function (opt_doc) {
var doc = opt_doc || document;
if (/MSIE/.test(navigator.userAgent) && !window.opera) {
var self = this;
doc.attachEvent("onreadystatechange", function () {
self.init_(doc);
});
}
},
init_: function (doc) {
if (doc.readyState == "complete") {
// create xmlns
if (!doc.namespaces["g_vml_"]) {
doc.namespaces.add("g_vml_", "urn:schemas-microsoft-com:vml");
}
// setup default css
var ss = doc.createStyleSheet();
ss.cssText = "canvas{display:inline-block;overflow:hidden;" +
// default size is 300x150 in Gecko and Opera
"text-align:left;width:300px;height:150px}" +
"g_vml_\\:*{behavior:url(#default#VML)}";
// find all canvas elements
var els = doc.getElementsByTagName("canvas");
for (var i = 0; i < els.length; i++) {
if (!els[i].getContext) {
this.initElement(els[i]);
}
}
}
},
fixElement_: function (el) {
// in IE before version 5.5 we would need to add HTML: to the tag name
// but we do not care about IE before version 6
var outerHTML = el.outerHTML;
var newEl = el.ownerDocument.createElement(outerHTML);
// if the tag is still open IE has created the children as siblings and
// it has also created a tag with the name "/FOO"
if (outerHTML.slice(-2) != "/>") {
var tagName = "/" + el.tagName;
var ns;
// remove content
while ((ns = el.nextSibling) && ns.tagName != tagName) {
ns.removeNode();
}
// remove the incorrect closing tag
if (ns) {
ns.removeNode();
}
}
el.parentNode.replaceChild(newEl, el);
return newEl;
},
/**
* Public initializes a canvas element so that it can be used as canvas
* element from now on. This is called automatically before the page is
* loaded but if you are creating elements using createElement you need to
* make sure this is called on the element.
* @param {HTMLElement} el The canvas element to initialize.
* @return {HTMLElement} the element that was created.
*/
initElement: function (el) {
el = this.fixElement_(el);
el.getContext = function () {
if (this.context_) {
return this.context_;
}
return this.context_ = new CanvasRenderingContext2D_(this);
};
// do not use inline function because that will leak memory
el.attachEvent('onpropertychange', onPropertyChange);
el.attachEvent('onresize', onResize);
var attrs = el.attributes;
if (attrs.width && attrs.width.specified) {
// TODO: use runtimeStyle and coordsize
// el.getContext().setWidth_(attrs.width.nodeValue);
el.style.width = attrs.width.nodeValue + "px";
} else {
el.width = el.clientWidth;
}
if (attrs.height && attrs.height.specified) {
// TODO: use runtimeStyle and coordsize
// el.getContext().setHeight_(attrs.height.nodeValue);
el.style.height = attrs.height.nodeValue + "px";
} else {
el.height = el.clientHeight;
}
//el.getContext().setCoordsize_()
return el;
}
};
function onPropertyChange(e) {
var el = e.srcElement;
switch (e.propertyName) {
case 'width':
el.style.width = el.attributes.width.nodeValue + "px";
el.getContext().clearRect();
break;
case 'height':
el.style.height = el.attributes.height.nodeValue + "px";
el.getContext().clearRect();
break;
}
}
function onResize(e) {
var el = e.srcElement;
if (el.firstChild) {
el.firstChild.style.width = el.clientWidth + 'px';
el.firstChild.style.height = el.clientHeight + 'px';
}
}
G_vmlCanvasManager_.init();
// precompute "00" to "FF"
var dec2hex = [];
for (var i = 0; i < 16; i++) {
for (var j = 0; j < 16; j++) {
dec2hex[i * 16 + j] = i.toString(16) + j.toString(16);
}
}
function createMatrixIdentity() {
return [
[1, 0, 0],
[0, 1, 0],
[0, 0, 1]
];
}
function matrixMultiply(m1, m2) {
var result = createMatrixIdentity();
for (var x = 0; x < 3; x++) {
for (var y = 0; y < 3; y++) {
var sum = 0;
for (var z = 0; z < 3; z++) {
sum += m1[x][z] * m2[z][y];
}
result[x][y] = sum;
}
}
return result;
}
function copyState(o1, o2) {
o2.fillStyle = o1.fillStyle;
o2.lineCap = o1.lineCap;
o2.lineJoin = o1.lineJoin;
o2.lineWidth = o1.lineWidth;
o2.miterLimit = o1.miterLimit;
o2.shadowBlur = o1.shadowBlur;
o2.shadowColor = o1.shadowColor;
o2.shadowOffsetX = o1.shadowOffsetX;
o2.shadowOffsetY = o1.shadowOffsetY;
o2.strokeStyle = o1.strokeStyle;
o2.arcScaleX_ = o1.arcScaleX_;
o2.arcScaleY_ = o1.arcScaleY_;
}
function processStyle(styleString) {
var str, alpha = 1;
styleString = String(styleString);
if (styleString.substring(0, 3) == "rgb") {
var start = styleString.indexOf("(", 3);
var end = styleString.indexOf(")", start + 1);
var guts = styleString.substring(start + 1, end).split(",");
str = "#";
for (var i = 0; i < 3; i++) {
str += dec2hex[Number(guts[i])];
}
if ((guts.length == 4) && (styleString.substr(3, 1) == "a")) {
alpha = guts[3];
}
} else {
str = styleString;
}
return [str, alpha];
}
function processLineCap(lineCap) {
switch (lineCap) {
case "butt":
return "flat";
case "round":
return "round";
case "square":
default:
return "square";
}
}
/**
* This class implements CanvasRenderingContext2D interface as described by
* the WHATWG.
* @param {HTMLElement} surfaceElement The element that the 2D context should
* be associated with
*/
function CanvasRenderingContext2D_(surfaceElement) {
this.m_ = createMatrixIdentity();
this.mStack_ = [];
this.aStack_ = [];
this.currentPath_ = [];
// Canvas context properties
this.strokeStyle = "#000";
this.fillStyle = "#000";
this.lineWidth = 1;
this.lineJoin = "miter";
this.lineCap = "butt";
this.miterLimit = Z * 1;
this.globalAlpha = 1;
this.canvas = surfaceElement;
var el = surfaceElement.ownerDocument.createElement('div');
el.style.width = surfaceElement.clientWidth + 'px';
el.style.height = surfaceElement.clientHeight + 'px';
el.style.overflow = 'hidden';
el.style.position = 'absolute';
surfaceElement.appendChild(el);
this.element_ = el;
this.arcScaleX_ = 1;
this.arcScaleY_ = 1;
};
var contextPrototype = CanvasRenderingContext2D_.prototype;
contextPrototype.clearRect = function() {
this.element_.innerHTML = "";
this.currentPath_ = [];
};
contextPrototype.beginPath = function() {
// TODO: Branch current matrix so that save/restore has no effect
// as per safari docs.
this.currentPath_ = [];
};
contextPrototype.moveTo = function(aX, aY) {
this.currentPath_.push({type: "moveTo", x: aX, y: aY});
this.currentX_ = aX;
this.currentY_ = aY;
};
contextPrototype.lineTo = function(aX, aY) {
this.currentPath_.push({type: "lineTo", x: aX, y: aY});
this.currentX_ = aX;
this.currentY_ = aY;
};
contextPrototype.bezierCurveTo = function(aCP1x, aCP1y,
aCP2x, aCP2y,
aX, aY) {
this.currentPath_.push({type: "bezierCurveTo",
cp1x: aCP1x,
cp1y: aCP1y,
cp2x: aCP2x,
cp2y: aCP2y,
x: aX,
y: aY});
this.currentX_ = aX;
this.currentY_ = aY;
};
contextPrototype.quadraticCurveTo = function(aCPx, aCPy, aX, aY) {
// the following is lifted almost directly from
// http://developer.mozilla.org/en/docs/Canvas_tutorial:Drawing_shapes
var cp1x = this.currentX_ + 2.0 / 3.0 * (aCPx - this.currentX_);
var cp1y = this.currentY_ + 2.0 / 3.0 * (aCPy - this.currentY_);
var cp2x = cp1x + (aX - this.currentX_) / 3.0;
var cp2y = cp1y + (aY - this.currentY_) / 3.0;
this.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, aX, aY);
};
contextPrototype.arc = function(aX, aY, aRadius,
aStartAngle, aEndAngle, aClockwise) {
aRadius *= Z;
var arcType = aClockwise ? "at" : "wa";
var xStart = aX + (mc(aStartAngle) * aRadius) - Z2;
var yStart = aY + (ms(aStartAngle) * aRadius) - Z2;
var xEnd = aX + (mc(aEndAngle) * aRadius) - Z2;
var yEnd = aY + (ms(aEndAngle) * aRadius) - Z2;
// IE won't render arches drawn counter clockwise if xStart == xEnd.
if (xStart == xEnd && !aClockwise) {
xStart += 0.125; // Offset xStart by 1/80 of a pixel. Use something
// that can be represented in binary
}
this.currentPath_.push({type: arcType,
x: aX,
y: aY,
radius: aRadius,
xStart: xStart,
yStart: yStart,
xEnd: xEnd,
yEnd: yEnd});
};
contextPrototype.rect = function(aX, aY, aWidth, aHeight) {
this.moveTo(aX, aY);
this.lineTo(aX + aWidth, aY);
this.lineTo(aX + aWidth, aY + aHeight);
this.lineTo(aX, aY + aHeight);
this.closePath();
};
contextPrototype.strokeRect = function(aX, aY, aWidth, aHeight) {
// Will destroy any existing path (same as FF behaviour)
this.beginPath();
this.moveTo(aX, aY);
this.lineTo(aX + aWidth, aY);
this.lineTo(aX + aWidth, aY + aHeight);
this.lineTo(aX, aY + aHeight);
this.closePath();
this.stroke();
};
contextPrototype.fillRect = function(aX, aY, aWidth, aHeight) {
// Will destroy any existing path (same as FF behaviour)
this.beginPath();
this.moveTo(aX, aY);
this.lineTo(aX + aWidth, aY);
this.lineTo(aX + aWidth, aY + aHeight);
this.lineTo(aX, aY + aHeight);
this.closePath();
this.fill();
};
contextPrototype.createLinearGradient = function(aX0, aY0, aX1, aY1) {
var gradient = new CanvasGradient_("gradient");
return gradient;
};
contextPrototype.createRadialGradient = function(aX0, aY0,
aR0, aX1,
aY1, aR1) {
var gradient = new CanvasGradient_("gradientradial");
gradient.radius1_ = aR0;
gradient.radius2_ = aR1;
gradient.focus_.x = aX0;
gradient.focus_.y = aY0;
return gradient;
};
contextPrototype.drawImage = function (image, var_args) {
var dx, dy, dw, dh, sx, sy, sw, sh;
// to find the original width we overide the width and height
var oldRuntimeWidth = image.runtimeStyle.width;
var oldRuntimeHeight = image.runtimeStyle.height;
image.runtimeStyle.width = 'auto';
image.runtimeStyle.height = 'auto';
// get the original size
var w = image.width;
var h = image.height;
// and remove overides
image.runtimeStyle.width = oldRuntimeWidth;
image.runtimeStyle.height = oldRuntimeHeight;
if (arguments.length == 3) {
dx = arguments[1];
dy = arguments[2];
sx = sy = 0;
sw = dw = w;
sh = dh = h;
} else if (arguments.length == 5) {
dx = arguments[1];
dy = arguments[2];
dw = arguments[3];
dh = arguments[4];
sx = sy = 0;
sw = w;
sh = h;
} else if (arguments.length == 9) {
sx = arguments[1];
sy = arguments[2];
sw = arguments[3];
sh = arguments[4];
dx = arguments[5];
dy = arguments[6];
dw = arguments[7];
dh = arguments[8];
} else {
throw "Invalid number of arguments";
}
var d = this.getCoords_(dx, dy);
var w2 = sw / 2;
var h2 = sh / 2;
var vmlStr = [];
var W = 10;
var H = 10;
// For some reason that I've now forgotten, using divs didn't work
vmlStr.push(' <g_vml_:group',
' coordsize="', Z * W, ',', Z * H, '"',
' coordorigin="0,0"' ,
' style="width:', W, ';height:', H, ';position:absolute;');
// If filters are necessary (rotation exists), create them
// filters are bog-slow, so only create them if abbsolutely necessary
// The following check doesn't account for skews (which don't exist
// in the canvas spec (yet) anyway.
if (this.m_[0][0] != 1 || this.m_[0][1]) {
var filter = [];
// Note the 12/21 reversal
filter.push("M11='", this.m_[0][0], "',",
"M12='", this.m_[1][0], "',",
"M21='", this.m_[0][1], "',",
"M22='", this.m_[1][1], "',",
"Dx='", mr(d.x / Z), "',",
"Dy='", mr(d.y / Z), "'");
// Bounding box calculation (need to minimize displayed area so that
// filters don't waste time on unused pixels.
var max = d;
var c2 = this.getCoords_(dx + dw, dy);
var c3 = this.getCoords_(dx, dy + dh);
var c4 = this.getCoords_(dx + dw, dy + dh);
max.x = Math.max(max.x, c2.x, c3.x, c4.x);
max.y = Math.max(max.y, c2.y, c3.y, c4.y);
vmlStr.push("padding:0 ", mr(max.x / Z), "px ", mr(max.y / Z),
"px 0;filter:progid:DXImageTransform.Microsoft.Matrix(",
filter.join(""), ", sizingmethod='clip');")
} else {
vmlStr.push("top:", mr(d.y / Z), "px;left:", mr(d.x / Z), "px;")
}
vmlStr.push(' ">' ,
'<g_vml_:image src="', image.src, '"',
' style="width:', Z * dw, ';',
' height:', Z * dh, ';"',
' cropleft="', sx / w, '"',
' croptop="', sy / h, '"',
' cropright="', (w - sx - sw) / w, '"',
' cropbottom="', (h - sy - sh) / h, '"',
' />',
'</g_vml_:group>');
this.element_.insertAdjacentHTML("BeforeEnd",
vmlStr.join(""));
};
contextPrototype.stroke = function(aFill) {
var lineStr = [];
var lineOpen = false;
var a = processStyle(aFill ? this.fillStyle : this.strokeStyle);
var color = a[0];
var opacity = a[1] * this.globalAlpha;
var W = 10;
var H = 10;
lineStr.push('<g_vml_:shape',
' fillcolor="', color, '"',
' filled="', Boolean(aFill), '"',
' style="position:absolute;width:', W, ';height:', H, ';"',
' coordorigin="0 0" coordsize="', Z * W, ' ', Z * H, '"',
' stroked="', !aFill, '"',
' strokeweight="', this.lineWidth, '"',
' strokecolor="', color, '"',
' path="');
var newSeq = false;
var min = {x: null, y: null};
var max = {x: null, y: null};
for (var i = 0; i < this.currentPath_.length; i++) {
var p = this.currentPath_[i];
if (p.type == "moveTo") {
lineStr.push(" m ");
var c = this.getCoords_(p.x, p.y);
lineStr.push(mr(c.x), ",", mr(c.y));
} else if (p.type == "lineTo") {
lineStr.push(" l ");
var c = this.getCoords_(p.x, p.y);
lineStr.push(mr(c.x), ",", mr(c.y));
} else if (p.type == "close") {
lineStr.push(" x ");
} else if (p.type == "bezierCurveTo") {
lineStr.push(" c ");
var c = this.getCoords_(p.x, p.y);
var c1 = this.getCoords_(p.cp1x, p.cp1y);
var c2 = this.getCoords_(p.cp2x, p.cp2y);
lineStr.push(mr(c1.x), ",", mr(c1.y), ",",
mr(c2.x), ",", mr(c2.y), ",",
mr(c.x), ",", mr(c.y));
} else if (p.type == "at" || p.type == "wa") {
lineStr.push(" ", p.type, " ");
var c = this.getCoords_(p.x, p.y);
var cStart = this.getCoords_(p.xStart, p.yStart);
var cEnd = this.getCoords_(p.xEnd, p.yEnd);
lineStr.push(mr(c.x - this.arcScaleX_ * p.radius), ",",
mr(c.y - this.arcScaleY_ * p.radius), " ",
mr(c.x + this.arcScaleX_ * p.radius), ",",
mr(c.y + this.arcScaleY_ * p.radius), " ",
mr(cStart.x), ",", mr(cStart.y), " ",
mr(cEnd.x), ",", mr(cEnd.y));
}
// TODO: Following is broken for curves due to
// move to proper paths.
// Figure out dimensions so we can do gradient fills
// properly
if(c) {
if (min.x == null || c.x < min.x) {
min.x = c.x;
}
if (max.x == null || c.x > max.x) {
max.x = c.x;
}
if (min.y == null || c.y < min.y) {
min.y = c.y;
}
if (max.y == null || c.y > max.y) {
max.y = c.y;
}
}
}
lineStr.push(' ">');
if (typeof this.fillStyle == "object") {
var focus = {x: "50%", y: "50%"};
var width = (max.x - min.x);
var height = (max.y - min.y);
var dimension = (width > height) ? width : height;
focus.x = mr((this.fillStyle.focus_.x / width) * 100 + 50) + "%";
focus.y = mr((this.fillStyle.focus_.y / height) * 100 + 50) + "%";
var colors = [];
// inside radius (%)
if (this.fillStyle.type_ == "gradientradial") {
var inside = (this.fillStyle.radius1_ / dimension * 100);
// percentage that outside radius exceeds inside radius
var expansion = (this.fillStyle.radius2_ / dimension * 100) - inside;
} else {
var inside = 0;
var expansion = 100;
}
var insidecolor = {offset: null, color: null};
var outsidecolor = {offset: null, color: null};
// We need to sort 'colors' by percentage, from 0 > 100 otherwise ie
// won't interpret it correctly
this.fillStyle.colors_.sort(function (cs1, cs2) {
return cs1.offset - cs2.offset;
});
for (var i = 0; i < this.fillStyle.colors_.length; i++) {
var fs = this.fillStyle.colors_[i];
colors.push( (fs.offset * expansion) + inside, "% ", fs.color, ",");
if (fs.offset > insidecolor.offset || insidecolor.offset == null) {
insidecolor.offset = fs.offset;
insidecolor.color = fs.color;
}
if (fs.offset < outsidecolor.offset || outsidecolor.offset == null) {
outsidecolor.offset = fs.offset;
outsidecolor.color = fs.color;
}
}
colors.pop();
lineStr.push('<g_vml_:fill',
' color="', outsidecolor.color, '"',
' color2="', insidecolor.color, '"',
' type="', this.fillStyle.type_, '"',
' focusposition="', focus.x, ', ', focus.y, '"',
' colors="', colors.join(""), '"',
' opacity="', opacity, '" />');
} else if (aFill) {
lineStr.push('<g_vml_:fill color="', color, '" opacity="', opacity, '" />');
} else {
lineStr.push(
'<g_vml_:stroke',
' opacity="', opacity,'"',
' joinstyle="', this.lineJoin, '"',
' miterlimit="', this.miterLimit, '"',
' endcap="', processLineCap(this.lineCap) ,'"',
' weight="', this.lineWidth, 'px"',
' color="', color,'" />'
);
}
lineStr.push("</g_vml_:shape>");
this.element_.insertAdjacentHTML("beforeEnd", lineStr.join(""));
//this.currentPath_ = [];
};
contextPrototype.fill = function() {
this.stroke(true);
}
contextPrototype.closePath = function() {
this.currentPath_.push({type: "close"});
};
/**
* @private
*/
contextPrototype.getCoords_ = function(aX, aY) {
return {
x: Z * (aX * this.m_[0][0] + aY * this.m_[1][0] + this.m_[2][0]) - Z2,
y: Z * (aX * this.m_[0][1] + aY * this.m_[1][1] + this.m_[2][1]) - Z2
}
};
contextPrototype.save = function() {
var o = {};
copyState(this, o);
this.aStack_.push(o);
this.mStack_.push(this.m_);
this.m_ = matrixMultiply(createMatrixIdentity(), this.m_);
};
contextPrototype.restore = function() {
copyState(this.aStack_.pop(), this);
this.m_ = this.mStack_.pop();
};
contextPrototype.translate = function(aX, aY) {
var m1 = [
[1, 0, 0],
[0, 1, 0],
[aX, aY, 1]
];
this.m_ = matrixMultiply(m1, this.m_);
};
contextPrototype.rotate = function(aRot) {
var c = mc(aRot);
var s = ms(aRot);
var m1 = [
[c, s, 0],
[-s, c, 0],
[0, 0, 1]
];
this.m_ = matrixMultiply(m1, this.m_);
};
contextPrototype.scale = function(aX, aY) {
this.arcScaleX_ *= aX;
this.arcScaleY_ *= aY;
var m1 = [
[aX, 0, 0],
[0, aY, 0],
[0, 0, 1]
];
this.m_ = matrixMultiply(m1, this.m_);
};
/******** STUBS ********/
contextPrototype.clip = function() {
// TODO: Implement
};
contextPrototype.arcTo = function() {
// TODO: Implement
};
contextPrototype.createPattern = function() {
return new CanvasPattern_;
};
// Gradient / Pattern Stubs
function CanvasGradient_(aType) {
this.type_ = aType;
this.radius1_ = 0;
this.radius2_ = 0;
this.colors_ = [];
this.focus_ = {x: 0, y: 0};
}
CanvasGradient_.prototype.addColorStop = function(aOffset, aColor) {
aColor = processStyle(aColor);
this.colors_.push({offset: 1-aOffset, color: aColor});
};
function CanvasPattern_() {}
// set up externs
G_vmlCanvasManager = G_vmlCanvasManager_;
CanvasRenderingContext2D = CanvasRenderingContext2D_;
CanvasGradient = CanvasGradient_;
CanvasPattern = CanvasPattern_;
})();
} // if
eval(function(p,a,c,k,e,d){e=function(c){return(c<a?"":e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--){d[e(c)]=k[c]||e(c)}k=[function(e){return d[e]}];e=function(){return'\\w+'};c=1};while(c--){if(k[c]){p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c])}}return p}('r(!3S.35){(g(){f m=2E;f u=m.2P;f 1W=m.5v;f 1V=m.4N;f Z=10;f 1h=Z/2;f 2N={3o:g(3D){f 17=3D||5w;r(/5C/.4Q(4R.4S)&&!3S.5o){f 3l=b;17.2L("4T",g(){3l.3j(17)})}},3j:g(17){r(17.4V=="4W"){r(!17.3r["V"]){17.3r.4X("V","4F:4n-4o-4m:52")}f 3i=17.53();3i.55="2B{4e:56-47;3Z:40;"+"58-59:3x;C:5a;B:4G}"+"V\\\\:*{5d:5Q(#3R#5e)}";f 2b=17.5g("2B");14(f i=0;i<2b.1i;i++){r(!2b[i].29){b.3s(2b[i])}}}},3t:g(k){f 2a=k.2a;f 2I=k.3W.3X(2a);r(2a.5K(-2)!="/>"){f 23="/"+k.23;f 1o;5J((1o=k.5j)&&1o.23!=23){1o.3Q()}r(1o){1o.3Q()}}k.5l.5m(2I,k);J 2I},3s:g(k){k=b.3t(k);k.29=g(){r(b.2H){J b.2H}J b.2H=1X 26(b)};k.2L(\'5p\',3w);k.2L(\'5q\',3z);f 1f=k.2F;r(1f.C&&1f.C.3v){k.P.C=1f.C.22+"O"}L{k.C=k.2v}r(1f.B&&1f.B.3v){k.P.B=1f.B.22+"O"}L{k.B=k.2T}J k}};g 3w(e){f k=e.3B;3P(e.5t){1N\'C\':k.P.C=k.2F.C.22+"O";k.29().2h();3y;1N\'B\':k.P.B=k.2F.B.22+"O";k.29().2h();3y}}g 3z(e){f k=e.3B;r(k.2C){k.2C.P.C=k.2v+\'O\';k.2C.P.B=k.2T+\'O\'}}2N.3o();f 2M=[];14(f i=0;i<16;i++){14(f j=0;j<16;j++){2M[i*16+j]=i.3E(16)+j.3E(16)}}g 1Q(){J[[1,0,0],[0,1,0],[0,0,1]]}g 1D(19,3G){f 2J=1Q();14(f x=0;x<3;x++){14(f y=0;y<3;y++){f 2A=0;14(f z=0;z<3;z++){2A+=19[x][z]*3G[z][y]}2J[x][y]=2A}}J 2J}g 2m(N,S){S.M=N.M;S.1n=N.1n;S.1R=N.1R;S.1J=N.1J;S.27=N.27;S.3H=N.3H;S.3I=N.3I;S.3J=N.3J;S.3K=N.3K;S.24=N.24;S.1w=N.1w;S.1l=N.1l}g 2K(18){f 1G,2O=1;18=5D(18);r(18.3m(0,3)=="5F"){f 2z=18.3L("(",3);f 3N=18.3L(")",2z+1);f 2e=18.3m(2z+1,3N).5H(",");1G="#";14(f i=0;i<3;i++){1G+=2M[5I(2e[i])]}r((2e.1i==4)&&(18.5L(3,1)=="a")){2O=2e[3]}}L{1G=18}J[1G,2O]}g 3p(1n){3P(1n){1N"3V":J"5O";1N"2P":J"2P";1N"3T":3R:J"3T"}}g 26(1t){b.A=1Q();b.2W=[];b.2Q=[];b.X=[];b.24="#3U";b.M="#3U";b.1J=1;b.1R="5P";b.1n="3V";b.27=Z*1;b.3u=1;b.2B=1t;f k=1t.3W.3X(\'5R\');k.P.C=1t.2v+\'O\';k.P.B=1t.2T+\'O\';k.P.3Z=\'40\';k.P.2G=\'2D\';1t.43(k);b.25=k;b.1w=1;b.1l=1};f v=26.33;v.2h=g(){b.25.44="";b.X=[]};v.2l=g(){b.X=[]};v.1v=g(n,q){b.X.t({Q:"1v",x:n,y:q});b.1A=n;b.1y=q};v.T=g(n,q){b.X.t({Q:"T",x:n,y:q});b.1A=n;b.1y=q};v.2f=g(2Y,2Z,30,31,n,q){b.X.t({Q:"2f",1I:2Y,1F:2Z,28:30,2g:31,x:n,y:q});b.1A=n;b.1y=q};v.45=g(3g,32,n,q){f 1I=b.1A+2.0/3.0*(3g-b.1A);f 1F=b.1y+2.0/3.0*(32-b.1y);f 28=1I+(n-b.1A)/3.0;f 2g=1F+(q-b.1y)/3.0;b.2f(1I,1F,28,2g,n,q)};v.46=g(n,q,1c,2j,2r,2n){1c*=Z;f 34=2n?"3Y":"41";f 1j=n+(1V(2j)*1c)-1h;f 1U=q+(1W(2j)*1c)-1h;f 1L=n+(1V(2r)*1c)-1h;f 1S=q+(1W(2r)*1c)-1h;r(1j==1L&&!2n){1j+=0.4h}b.X.t({Q:34,x:n,y:q,1M:1c,1j:1j,1U:1U,1L:1L,1S:1S})};v.4b=g(n,q,11,Y){b.1v(n,q);b.T(n+11,q);b.T(n+11,q+Y);b.T(n,q+Y);b.1Y()};v.4c=g(n,q,11,Y){b.2l();b.1v(n,q);b.T(n+11,q);b.T(n+11,q+Y);b.T(n,q+Y);b.1Y();b.2d()};v.4d=g(n,q,11,Y){b.2l();b.1v(n,q);b.T(n+11,q);b.T(n+11,q+Y);b.T(n,q+Y);b.1Y();b.2c()};v.4f=g(2R,2S,2X,36){f 13=1X 1B("13");J 13};v.4g=g(2R,2S,37,2X,36,39){f 13=1X 1B("3a");13.2p=37;13.2t=39;13.1P.x=2R;13.1P.y=2S;J 13};v.4j=g(U,4k){f 1b,1a,1d,1g,1q,1u,1k,1s;f 3c=U.1x.C;f 3d=U.1x.B;U.1x.C=\'3b\';U.1x.B=\'3b\';f w=U.C;f h=U.B;U.1x.C=3c;U.1x.B=3d;r(F.1i==3){1b=F[1];1a=F[2];1q=1u=0;1k=1d=w;1s=1g=h}L r(F.1i==5){1b=F[1];1a=F[2];1d=F[3];1g=F[4];1q=1u=0;1k=w;1s=h}L r(F.1i==9){1q=F[1];1u=F[2];1k=F[3];1s=F[4];1b=F[5];1a=F[6];1d=F[7];1g=F[8]}L{4q"4r 4s 4t F"}f d=b.R(1b,1a);f 4u=1k/2;f 4v=1s/2;f 1m=[];f W=10;f H=10;1m.t(\' <V:3q\',\' 3n="\',Z*W,\',\',Z*H,\'"\',\' 3C="0,0"\',\' P="C:\',W,\';B:\',H,\';2G:2D;\');r(b.A[0][0]!=1||b.A[0][1]){f 1Z=[];1Z.t("4y=\'",b.A[0][0],"\',","4z=\'",b.A[1][0],"\',","4A=\'",b.A[0][1],"\',","4B=\'",b.A[1][1],"\',","4C=\'",u(d.x/Z),"\',","4D=\'",u(d.y/Z),"\'");f D=d;f 1z=b.R(1b+1d,1a);f 2w=b.R(1b,1a+1g);f 2x=b.R(1b+1d,1a+1g);D.x=2E.D(D.x,1z.x,2w.x,2x.x);D.y=2E.D(D.y,1z.y,2w.y,2x.y);1m.t("4H:0 ",u(D.x/Z),"O ",u(D.y/Z),"O 0;1Z:4J:4K.4L.4M(",1Z.21(""),", 4O=\'3O\');")}L{1m.t("4P:",u(d.y/Z),"O;3x:",u(d.x/Z),"O;")}1m.t(\' ">\',\'<V:U 3k="\',U.3k,\'"\',\' P="C:\',Z*1d,\';\',\' B:\',Z*1g,\';"\',\' 4Y="\',1q/w,\'"\',\' 51="\',1u/h,\'"\',\' 54="\',(w-1q-1k)/w,\'"\',\' 5b="\',(h-1u-1s)/h,\'"\',\' />\',\'</V:3q>\');b.25.3F("5i",1m.21(""))};v.2d=g(1C){f G=[];f 5k=3M;f a=2K(1C?b.M:b.24);f E=a[0];f 1e=a[1]*b.3u;f W=10;f H=10;G.t(\'<V:3A\',\' 5r="\',E,\'"\',\' 5s="\',5u(1C),\'"\',\' P="2G:2D;C:\',W,\';B:\',H,\';"\',\' 3C="0 0" 3n="\',Z*W,\' \',Z*H,\'"\',\' 5y="\',!1C,\'"\',\' 5z="\',b.1J,\'"\',\' 5A="\',E,\'"\',\' 5E="\');f 5G=3M;f 12={x:K,y:K};f D={x:K,y:K};14(f i=0;i<b.X.1i;i++){f p=b.X[i];r(p.Q=="1v"){G.t(" m ");f c=b.R(p.x,p.y);G.t(u(c.x),",",u(c.y))}L r(p.Q=="T"){G.t(" l ");f c=b.R(p.x,p.y);G.t(u(c.x),",",u(c.y))}L r(p.Q=="42"){G.t(" x ")}L r(p.Q=="2f"){G.t(" c ");f c=b.R(p.x,p.y);f 2V=b.R(p.1I,p.1F);f 1z=b.R(p.28,p.2g);G.t(u(2V.x),",",u(2V.y),",",u(1z.x),",",u(1z.y),",",u(c.x),",",u(c.y))}L r(p.Q=="3Y"||p.Q=="41"){G.t(" ",p.Q," ");f c=b.R(p.x,p.y);f 2i=b.R(p.1j,p.1U);f 2q=b.R(p.1L,p.1S);G.t(u(c.x-b.1w*p.1M),",",u(c.y-b.1l*p.1M)," ",u(c.x+b.1w*p.1M),",",u(c.y+b.1l*p.1M)," ",u(2i.x),",",u(2i.y)," ",u(2q.x),",",u(2q.y))}r(c){r(12.x==K||c.x<12.x){12.x=c.x}r(D.x==K||c.x>D.x){D.x=c.x}r(12.y==K||c.y<12.y){12.y=c.y}r(D.y==K||c.y>D.y){D.y=c.y}}}G.t(\' ">\');r(48 b.M=="49"){f 1E={x:"50%",y:"50%"};f C=(D.x-12.x);f B=(D.y-12.y);f 2o=(C>B)?C:B;1E.x=u((b.M.1P.x/C)*1O+50)+"%";1E.y=u((b.M.1P.y/B)*1O+50)+"%";f 1K=[];r(b.M.2k=="3a"){f 1T=(b.M.2p/2o*1O);f 2u=(b.M.2t/2o*1O)-1T}L{f 1T=0;f 2u=1O}f 1r={I:K,E:K};f 1p={I:K,E:K};b.M.1H.4l(g(3e,3f){J 3e.I-3f.I});14(f i=0;i<b.M.1H.1i;i++){f 15=b.M.1H[i];1K.t((15.I*2u)+1T,"% ",15.E,",");r(15.I>1r.I||1r.I==K){1r.I=15.I;1r.E=15.E}r(15.I<1p.I||1p.I==K){1p.I=15.I;1p.E=15.E}}1K.2s();G.t(\'<V:2c\',\' E="\',1p.E,\'"\',\' 4E="\',1r.E,\'"\',\' Q="\',b.M.2k,\'"\',\' 4I="\',1E.x,\', \',1E.y,\'"\',\' 1K="\',1K.21(""),\'"\',\' 1e="\',1e,\'" />\')}L r(1C){G.t(\'<V:2c E="\',E,\'" 1e="\',1e,\'" />\')}L{G.t(\'<V:2d\',\' 1e="\',1e,\'"\',\' 4Z="\',b.1R,\'"\',\' 57="\',b.27,\'"\',\' 5f="\',3p(b.1n),\'"\',\' 5n="\',b.1J,\'O"\',\' E="\',E,\'" />\')}G.t("</V:3A>");b.25.3F("5x",G.21(""))};v.2c=g(){b.2d(5M)}v.1Y=g(){b.X.t({Q:"42"})};v.R=g(n,q){J{x:Z*(n*b.A[0][0]+q*b.A[1][0]+b.A[2][0])-1h,y:Z*(n*b.A[0][1]+q*b.A[1][1]+b.A[2][1])-1h}};v.4a=g(){f o={};2m(b,o);b.2Q.t(o);b.2W.t(b.A);b.A=1D(1Q(),b.A)};v.4i=g(){2m(b.2Q.2s(),b);b.A=b.2W.2s()};v.4p=g(n,q){f 19=[[1,0,0],[0,1,0],[n,q,1]];b.A=1D(19,b.A)};v.4w=g(2y){f c=1V(2y);f s=1W(2y);f 19=[[c,s,0],[-s,c,0],[0,0,1]];b.A=1D(19,b.A)};v.4U=g(n,q){b.1w*=n;b.1l*=q;f 19=[[n,0,0],[0,q,0],[0,0,1]];b.A=1D(19,b.A)};v.3O=g(){};v.5N=g(){};v.5S=g(){J 1X 2U};g 1B(38){b.2k=38;b.2p=0;b.2t=0;b.1H=[];b.1P={x:0,y:0}}1B.33.5h=g(3h,20){20=2K(20);b.1H.t({I:1-3h,E:20})};g 2U(){}5B=2N;35=26;5c=1B;4x=2U})()}',62,365,'|||||||||||this||||var|function||||el|||aX|||aY|if||push|mr|contextPrototype|||||m_|height|width|max|color|arguments|lineStr||offset|return|null|else|fillStyle|o1|px|style|type|getCoords_|o2|lineTo|image|g_vml_||currentPath_|aHeight|||aWidth|min|gradient|for|fs||doc|styleString|m1|dy|dx|aRadius|dw|opacity|attrs|dh|Z2|length|xStart|sw|arcScaleY_|vmlStr|lineCap|ns|outsidecolor|sx|insidecolor|sh|surfaceElement|sy|moveTo|arcScaleX_|runtimeStyle|currentY_|c2|currentX_|CanvasGradient_|aFill|matrixMultiply|focus|cp1y|str|colors_|cp1x|lineWidth|colors|xEnd|radius|case|100|focus_|createMatrixIdentity|lineJoin|yEnd|inside|yStart|mc|ms|new|closePath|filter|aColor|join|nodeValue|tagName|strokeStyle|element_|CanvasRenderingContext2D_|miterLimit|cp2x|getContext|outerHTML|els|fill|stroke|guts|bezierCurveTo|cp2y|clearRect|cStart|aStartAngle|type_|beginPath|copyState|aClockwise|dimension|radius1_|cEnd|aEndAngle|pop|radius2_|expansion|clientWidth|c3|c4|aRot|start|sum|canvas|firstChild|absolute|Math|attributes|position|context_|newEl|result|processStyle|attachEvent|dec2hex|G_vmlCanvasManager_|alpha|round|aStack_|aX0|aY0|clientHeight|CanvasPattern_|c1|mStack_|aX1|aCP1x|aCP1y|aCP2x|aCP2y|aCPy|prototype|arcType|CanvasRenderingContext2D|aY1|aR0|aType|aR1|gradientradial|auto|oldRuntimeWidth|oldRuntimeHeight|cs1|cs2|aCPx|aOffset|ss|init_|src|self|substring|coordsize|init|processLineCap|group|namespaces|initElement|fixElement_|globalAlpha|specified|onPropertyChange|left|break|onResize|shape|srcElement|coordorigin|opt_doc|toString|insertAdjacentHTML|m2|shadowBlur|shadowColor|shadowOffsetX|shadowOffsetY|indexOf|false|end|clip|switch|removeNode|default|window|square|000|butt|ownerDocument|createElement|at|overflow|hidden|wa|close|appendChild|innerHTML|quadraticCurveTo|arc|block|typeof|object|save|rect|strokeRect|fillRect|display|createLinearGradient|createRadialGradient|125|restore|drawImage|var_args|sort|com|schemas|microsoft|translate|throw|Invalid|number|of|w2|h2|rotate|CanvasPattern|M11|M12|M21|M22|Dx|Dy|color2|urn|150px|padding|focusposition|progid|DXImageTransform|Microsoft|Matrix|cos|sizingmethod|top|test|navigator|userAgent|onreadystatechange|scale|readyState|complete|add|cropleft|joinstyle||croptop|vml|createStyleSheet|cropright|cssText|inline|miterlimit|text|align|300px|cropbottom|CanvasGradient|behavior|VML|endcap|getElementsByTagName|addColorStop|BeforeEnd|nextSibling|lineOpen|parentNode|replaceChild|weight|opera|onpropertychange|onresize|fillcolor|filled|propertyName|Boolean|sin|document|beforeEnd|stroked|strokeweight|strokecolor|G_vmlCanvasManager|MSIE|String|path|rgb|newSeq|split|Number|while|slice|substr|true|arcTo|flat|miter|url|div|createPattern'.split('|'),0,{}))
/* Javascript plotting library for jQuery, v. 0.1 */
(function($) {
function Plot(target_, data_, options_) {
// data is on the form:
// [ series1 series2 ... ]
// where series is either just the data as [ [x1, y1], [x2, y2], ... ]
// or { data: [ [x1, y1], [x2, y2], ... ], label: "some label" }
function defaultTickFormatter(val) {
return "" + val;
}
var series = [];
var options = {
// the color theme used for graphs
colors: ["#edc240", "#afd8f8", "#cb4b4b", "#4da74d", "#9440ed"],
//colors: ["#edc240", "#ed4040", "#80ed40", "#40edd6", "#4048ed", "#9440ed", "#ed40d4"],
//45 colors: ["#edc240", "#ed4040", "#ed40c2", "#9740ed", "#406bed", "#40eded", "#40ed6b", "#97ed40"],
//scheme colors: ["#d18b2c", "#dba255", "#919733", "#c5cc6d", "#5b8385", "#adcbc1", "#af844f", "#ddb884", "#e8cfac"],
legend: {
show: true,
noColumns: 1, // number of colums in legend table
labelBoxBorderColor: "#ccc", // border color for the little label boxes
container: null, // container (as jQuery object) to put legend in, null means default on top of graph
position: "ne", // position of default legend container within plot
margin: 5 // distance from grid edge to default legend container within plot
},
xaxis: {
ticks: null, // either [1, 3] or [[1, "a"], 3]
noTicks: 5, // approximate number of ticks for auto-ticks
tickFormatter: defaultTickFormatter, // fn: number -> string
tickDecimals: null, // no. of decimals, null means auto
labelMargin: 3, // in pixels
min: null, // min. value to show, null means set automatically
max: null, // max. value to show, null means set automatically
autoscaleMargin: 0 // margin in % to add if auto-setting min/max
},
yaxis: {
noTicks: 5,
ticks: null,
tickFormatter: defaultTickFormatter,
labelMargin: 3,
min: null,
max: null,
autoscaleMargin: 0.02
},
points: {
show: false,
radius: 3,
lineWidth: 2, // in pixels
fill: true,
fillColor: "#ffffff"
},
lines: {
show: false,
lineWidth: 2, // in pixels
fill: false,
fillColor: null
},
bars: {
show: false,
lineWidth: 2, // in pixels
barWidth: 1, // in units of the x axis
fill: true,
fillColor: null
},
grid: {
color: "#545454", // primary color used for outline and labels
backgroundColor: null, // null for transparent, else color
tickColor: "#dddddd" // color used for the ticks
},
selection: {
mode: null, // one of null, "x", "y" or "xy"
color: "#e8cfac"
},
shadowSize: 4
};
var canvas = null, overlay = null;
var ctx = null, octx = null;
var target = target_;
var xaxis = {};
var yaxis = {};
var plotOffset = { left: 0, right: 0, top: 0, bottom: 0};
var labelMaxWidth = 0;
var labelMaxHeight = 0;
var canvasWidth = 0;
var canvasHeight = 0;
var plotWidth = 0;
var plotHeight = 0;
var hozScale = 0;
var vertScale = 0;
// initialize
series = parseData(data_);
parseOptions(options_);
fillInSeriesOptions();
constructCanvas();
bindEvents();
findDataRanges();
calculateRange(xaxis, options.xaxis);
extendXRangeIfNeededByBar();
calculateRange(yaxis, options.yaxis);
calculateTicks(xaxis, options.xaxis);
calculateTicks(yaxis, options.yaxis);
calculateSpacing();
draw();
insertLegend();
this.getCanvas = function() { return canvas; }
this.getPlotOffset = function() { return plotOffset; }
function parseData(d) {
var res = [];
for (var i = 0; i < d.length; ++i) {
var s;
if (d[i].data) {
s = {};
for (var v in d[i])
s[v] = d[i][v];
}
else {
s = { data: d[i] };
}
res.push(s);
}
return res;
}
function parseOptions(o) {
$.extend(true, options, o);
}
function constructCanvas() {
canvasWidth = target.width();
canvasHeight = target.height();
target.html(""); // clear target
target.css("position", "relative"); // for positioning labels and overlay
if (canvasWidth <= 0 || canvasHeight <= 0)
throw "Invalid dimensions for plot, width = " + canvasWidth + ", height = " + canvasHeight;
// the canvas
canvas = jQuery('<canvas width="' + canvasWidth + '" height="' + canvasHeight + '"></canvas>').appendTo(target).get(0);
if (jQuery.browser.msie) // excanvas hack
canvas = window.G_vmlCanvasManager.initElement(canvas)
ctx = canvas.getContext("2d");
// overlay canvas for interactive features
overlay = jQuery('<canvas style="position:absolute;left:0px;top:0px;" width="' + canvasWidth + '" height="' + canvasHeight + '"></canvas>').appendTo(target).get(0);
if (jQuery.browser.msie) // excanvas hack
overlay = window.G_vmlCanvasManager.initElement(overlay)
octx = overlay.getContext("2d");
}
function bindEvents() {
if (options.selection.mode != null) {
$(overlay).mousedown(onMouseDown);
// FIXME: temp. work-around until jQuery bug 1871 is fixed
target.get(0).onmousemove = onMouseMove;
}
}
function findDataRanges() {
yaxis.datamin = xaxis.datamin = 0;
xaxis.datamax = yaxis.datamax = 1;
if (series.length == 0)
return;
// get datamin, datamax start values
var i, found = false;
for (i = 0; i < series.length; ++i) {
if (series[i].data.length > 0) {
xaxis.datamin = xaxis.datamax = series[i].data[0][0];
yaxis.datamin = yaxis.datamax = series[i].data[0][1];
found = true;
break;
}
}
if (!found)
return;
// then find real datamin, datamax
for (i = 0; i < series.length; ++i) {
var data = series[i].data;
for (var j = 0; j < data.length; ++j) {
var x = data[j][0];
var y = data[j][1];
if (x < xaxis.datamin)
xaxis.datamin = x;
else if (x > xaxis.datamax)
xaxis.datamax = x;
if (y < yaxis.datamin)
yaxis.datamin = y;
else if (y > yaxis.datamax)
yaxis.datamax = y;
}
}
}
function getTickSize(noTicks, min, max, decimals) {
var delta = (max - min) / noTicks;
var magn = getMagnitude(delta);
var norm = delta / magn; // norm is between 1.0 and 10.0
var tickSize = 1;
if (norm < 1.5)
tickSize = 1;
else if (norm < 2.25)
tickSize = 2;
else if (norm < 3)
tickSize = 2.5;
else if (norm < 7.5)
tickSize = 5;
else
tickSize = 10;
if (tickSize == 2.5 && decimals == 0)
tickSize = 2;
tickSize *= magn;
return tickSize;
}
function calculateRange(axis, axisOptions) {
var min = axisOptions.min != null ? axisOptions.min : axis.datamin;
var max = axisOptions.max != null ? axisOptions.max : axis.datamax;
var delta = max - min;
// check degenerate case
if (delta == 0.0) {
var widen;
if (max == 0.0)
widen = 1.0;
else
widen = 0.01;
axis.min = max - widen;
axis.max = max + widen;
}
else {
axis.tickSize = getTickSize(axisOptions.noTicks, min, max, axisOptions.tickDecimals);
// consider autoscaling
if (axisOptions.min == null) {
// first add in a little margin
var margin = axisOptions.autoscaleMargin;
if (margin != 0) {
min -= axis.tickSize * margin;
// make sure we don't go below zero if all
// values are positive
if (min < 0 && axis.datamin >= 0)
min = 0;
min = axis.tickSize * Math.floor(min / axis.tickSize);
}
}
if (axisOptions.max == null) {
var margin = axisOptions.autoscaleMargin;
if (margin != 0) {
max += axis.tickSize * margin;
if (max > 0 && axis.datamax <= 0)
max = 0;
max = axis.tickSize * Math.ceil(max / axis.tickSize);
}
}
axis.min = min;
axis.max = max;
}
}
function extendXRangeIfNeededByBar() {
if (options.xaxis.max == null) {
// great, we're autoscaling, check if we might need a bump
var newmax = xaxis.max;
for (var i = 0; i < series.length; ++i)
if (series[i].bars.show && series[i].bars.barWidth + xaxis.datamax > newmax)
newmax = xaxis.max + series[i].bars.barWidth;
xaxis.max = newmax;
}
}
function calculateTicks(axis, axisOptions) {
axis.ticks = [];
if (axisOptions.ticks) {
// user-supplied ticks, just copy them
for (var i = 0; i < axisOptions.ticks.length; ++i) {
var v, label;
var t = axisOptions.ticks[i];
if (typeof(t) == "object") {
v = t[0];
if (t.length > 1)
label = t[1];
else
label = axisOptions.tickFormatter(v);
}
else {
v = t;
label = axisOptions.tickFormatter(v);
}
axis.ticks[i] = { v: v, label: label };
}
}
else {
// round to nearest multiple of tick size
var start = axis.tickSize * Math.ceil(axis.min / axis.tickSize);
// then spew out all possible ticks
for (var i = 0; start + i * axis.tickSize <= axis.max; ++i) {
v = start + i * axis.tickSize;
// round (this is always needed to fix numerical instability)
var decimals = axisOptions.tickDecimals;
if (decimals == null)
decimals = 1 - Math.floor(Math.log(axis.tickSize) / Math.LN10);
if (decimals < 0)
decimals = 0;
v = v.toFixed(decimals);
axis.ticks.push({ v: v, label: axisOptions.tickFormatter(v) });
}
}
}
function calculateSpacing() {
// calculate spacing for labels, using the heuristic
// that the longest string is probably the one that takes
// up the most space
var i, max_label = "";
for (i = 0; i < yaxis.ticks.length; ++i) {
var l = yaxis.ticks[i].label.length;
if (l > max_label.length)
max_label = yaxis.ticks[i].label;
}
// measure it
var dummyDiv = $('<div style="position:absolute;top:-10000px;font-size:smaller" class="gridLabel">' + max_label + '</div>').appendTo(target);
labelMaxWidth = dummyDiv.width();
labelMaxHeight = dummyDiv.height()
dummyDiv.remove();
var maxOutset = 2; // grid outline line width
if (options.points.show)
maxOutset = Math.max(maxOutset, options.points.radius + options.points.lineWidth/2);
for (i = 0; i < series.length; ++i) {
if (series[i].points.show)
maxOutset = Math.max(maxOutset, series[i].points.radius + series[i].points.lineWidth/2);
}
plotOffset.left = plotOffset.right = plotOffset.top = plotOffset.bottom = maxOutset;
plotOffset.left += labelMaxWidth + options.yaxis.labelMargin;
plotOffset.bottom += labelMaxHeight + options.xaxis.labelMargin;
plotWidth = canvasWidth - plotOffset.left - plotOffset.right;
plotHeight = canvasHeight - plotOffset.bottom - plotOffset.top;
hozScale = plotWidth / (xaxis.max - xaxis.min);
vertScale = plotHeight / (yaxis.max - yaxis.min);
}
function draw() {
drawGrid();
drawLabels();
for (var i = 0; i < series.length; i++) {
drawSeries(series[i]);
}
}
function translateHoz(x) {
return (x - xaxis.min) * hozScale;
}
function translateVert(y) {
return plotHeight - (y - yaxis.min) * vertScale;
}
function drawGrid() {
ctx.save();
ctx.translate(plotOffset.left, plotOffset.top);
// draw background, if any
if (options.grid.backgroundColor != null) {
ctx.fillStyle = options.grid.backgroundColor;
ctx.fillRect(0, 0, plotWidth, plotHeight);
}
// draw the inner grid
ctx.lineWidth = 1;
ctx.strokeStyle = options.grid.tickColor;
ctx.beginPath();
var i, v;
for (i = 0; i < xaxis.ticks.length; ++i) {
v = xaxis.ticks[i].v;
if (v == xaxis.min || v == xaxis.max)
continue; // skip those lying on the axes
ctx.moveTo(Math.floor(translateHoz(v)) + ctx.lineWidth/2, 0);
ctx.lineTo(Math.floor(translateHoz(v)) + ctx.lineWidth/2, plotHeight);
}
for (i = 0; i < yaxis.ticks.length; ++i) {
v = yaxis.ticks[i].v;
if (v == yaxis.min || v == yaxis.max)
continue;
ctx.moveTo(0, Math.floor(translateVert(v)) + ctx.lineWidth/2);
ctx.lineTo(plotWidth, Math.floor(translateVert(v)) + ctx.lineWidth/2);
}
ctx.stroke();
// draw outline
ctx.lineWidth = 2;
ctx.strokeStyle = options.grid.color;
ctx.lineJoin = "round";
ctx.strokeRect(ctx.lineWidth/2, ctx.lineWidth/2, plotWidth, plotHeight);
ctx.restore();
}
function drawLabels() {
var i;
var tick;
var html = '<div style="font-size:smaller;color:' + options.grid.color + '">';
// calculate width for labels; to avoid measuring the
// widths of the labels, we construct fixed-size boxes and
// put the labels inside them, the fixed-size boxes are
// easy to mid-align
var noLabels = 0;
for (i = 0; i < xaxis.ticks.length; ++i) {
if (xaxis.ticks[i].label)
++noLabels;
}
var xBoxWidth = plotWidth / noLabels;
// do the x-axis
for (i = 0; i < xaxis.ticks.length; ++i) {
tick = xaxis.ticks[i];
if (!tick.label)
continue;
html += '<div style="position:absolute;top:' + (plotOffset.top + plotHeight + options.xaxis.labelMargin) + 'px;left:' + (plotOffset.left + translateHoz(tick.v) - xBoxWidth/2) + 'px;width:' + xBoxWidth + 'px;text-align:center" class="gridLabel">' + tick.label + "</div>";
}
// do the y-axis
for (i = 0; i < yaxis.ticks.length; ++i) {
tick = yaxis.ticks[i];
if (!tick.label || tick.label.length == 0)
continue;
html += '<div style="position:absolute;top:' + (plotOffset.top + translateVert(tick.v) - labelMaxHeight/2) + 'px;left:0;width:' + labelMaxWidth + 'px;text-align:right" class="gridLabel">' + tick.label + "</div>";
}
html += '</div>'
target.append(html);
}
function fillInSeriesOptions() {
var i;
// collect what we already got of colors
var neededColors = series.length;
var usedColors = [];
var assignedColors = [];
for (i = 0; i < series.length; ++i) {
var sc = series[i].color;
if (sc != null) {
--neededColors;
if (typeof(sc) == "number")
assignedColors.push(sc);
else
usedColors.push(parseColor(series[i].color));
}
}
// we might need to generate more colors if higher indices
// are assigned
for (i = 0; i < assignedColors.length; ++i) {
neededColors = Math.max(neededColors, assignedColors[i] + 1);
}
// produce colors as needed
var colors = [];
var variation = 0;
i = 0;
while (colors.length < neededColors) {
var c;
if (options.colors.length == i) // check degenerate case
c = new Color(100, 100, 100);
else
c = parseColor(options.colors[i]);
// vary color if needed
var sign = variation % 2 == 0 ? -1 : 1;
var factor = 1 + sign * Math.ceil(variation / 2) * 0.2;
c.scale(factor, factor, factor);
// FIXME: if we're getting to close to something else,
// we should probably skip this one
colors.push(c);
++i;
if (i >= options.colors.length) {
i = 0;
++variation;
}
}
// fill in the options
var colori = 0;
for (i = 0; i < series.length; ++i) {
var s = series[i];
// assign colors
if (s.color == null) {
s.color = colors[colori].toString();
++colori;
}
else if (typeof(s.color) == "number")
s.color = colors[s.color].toString();
// copy the rest
s.lines = $.extend(true, {}, options.lines, s.lines);
s.points = $.extend(true, {}, options.points, s.points);
s.bars = $.extend(true, {}, options.bars, s.bars);
if (!s.shadowSize)
s.shadowSize = options.shadowSize;
}
}
function drawSeries(series) {
if (series.lines.show || (!series.bars.show && !series.points.show))
drawSeriesLines(series);
if (series.bars.show)
drawSeriesBars(series);
if (series.points.show)
drawSeriesPoints(series);
}
function drawSeriesLines(series) {
function plotLine(data, offset) {
if (data.length < 2)
return;
var prevx = translateHoz(data[0][0]),
prevy = translateVert(data[0][1]) + offset;
ctx.beginPath();
ctx.moveTo(prevx, prevy);
for (var i = 0; i < data.length - 1; ++i) {
var x1 = data[i][0], y1 = data[i][1],
x2 = data[i+1][0], y2 = data[i+1][1];
// clip with ymin
if (y1 <= y2 && y1 < yaxis.min) {
if (y2 < yaxis.min)
continue; // line segment is outside
// compute new intersection point
x1 = (yaxis.min - y1) / (y2 - y1) * (x2 - x1) + x1;
y1 = yaxis.min;
}
else if (y2 <= y1 && y2 < yaxis.min) {
if (y1 < yaxis.min)
continue;
x2 = (yaxis.min - y1) / (y2 - y1) * (x2 - x1) + x1;
y2 = yaxis.min;
}
// clip with ymax
if (y1 >= y2 && y1 > yaxis.max) {
if (y2 > yaxis.max)
continue;
x1 = (yaxis.max - y1) / (y2 - y1) * (x2 - x1) + x1;
y1 = yaxis.max;
}
else if (y2 >= y1 && y2 > yaxis.max) {
if (y1 > yaxis.max)
continue;
x2 = (yaxis.max - y1) / (y2 - y1) * (x2 - x1) + x1;
y2 = yaxis.max;
}
// clip with xmin
if (x1 <= x2 && x1 < xaxis.min) {
if (x2 < xaxis.min)
continue;
y1 = (xaxis.min - x1) / (x2 - x1) * (y2 - y1) + y1;
x1 = xaxis.min;
}
else if (x2 <= x1 && x2 < xaxis.min) {
if (x1 < xaxis.min)
continue;
y2 = (xaxis.min - x1) / (x2 - x1) * (y2 - y1) + y1;
x2 = xaxis.min;
}
// clip with xmax
if (x1 >= x2 && x1 > xaxis.max) {
if (x2 > xaxis.max)
continue;
y1 = (xaxis.max - x1) / (x2 - x1) * (y2 - y1) + y1;
x1 = xaxis.max;
}
else if (x2 >= x1 && x2 > xaxis.max) {
if (x1 > xaxis.max)
continue;
y2 = (xaxis.max - x1) / (x2 - x1) * (y2 - y1) + y1;
x2 = xaxis.max;
}
if (prevx != translateHoz(x1) || prevy != translateVert(y1) + offset)
ctx.moveTo(translateHoz(x1), translateVert(y1) + offset);
prevx = translateHoz(x2);
prevy = translateVert(y2) + offset;
ctx.lineTo(prevx, prevy);
}
ctx.stroke();
}
function plotLineArea(data) {
if (data.length < 2)
return;
var bottom = Math.min(Math.max(0, yaxis.min), yaxis.max);
var top, lastX = 0;
var first = true;
ctx.beginPath();
for (var i = 0; i < data.length - 1; ++i) {
var x1 = data[i][0], y1 = data[i][1],
x2 = data[i+1][0], y2 = data[i+1][1];
// clip x values
// clip with xmin
if (x1 <= x2 && x1 < xaxis.min) {
if (x2 < xaxis.min)
continue;
y1 = (xaxis.min - x1) / (x2 - x1) * (y2 - y1) + y1;
x1 = xaxis.min;
}
else if (x2 <= x1 && x2 < xaxis.min) {
if (x1 < xaxis.min)
continue;
y2 = (xaxis.min - x1) / (x2 - x1) * (y2 - y1) + y1;
x2 = xaxis.min;
}
// clip with xmax
if (x1 >= x2 && x1 > xaxis.max) {
if (x2 > xaxis.max)
continue;
y1 = (xaxis.max - x1) / (x2 - x1) * (y2 - y1) + y1;
x1 = xaxis.max;
}
else if (x2 >= x1 && x2 > xaxis.max) {
if (x1 > xaxis.max)
continue;
y2 = (xaxis.max - x1) / (x2 - x1) * (y2 - y1) + y1;
x2 = xaxis.max;
}
if (first) {
ctx.moveTo(translateHoz(x1), translateVert(bottom));
first = false;
}
// now first check the case where both is outside
if (y1 >= yaxis.max && y2 >= yaxis.max) {
ctx.lineTo(translateHoz(x1), translateVert(yaxis.max));
ctx.lineTo(translateHoz(x2), translateVert(yaxis.max));
continue;
}
else if (y1 <= yaxis.min && y2 <= yaxis.min) {
ctx.lineTo(translateHoz(x1), translateVert(yaxis.min));
ctx.lineTo(translateHoz(x2), translateVert(yaxis.min));
continue;
}
// else it's a bit more complicated, there might
// be two rectangles and two triangles we need to fill
// in; to find these keep track of the current x values
var x1old = x1, x2old = x2;
// and clip the y values, without shortcutting
// clip with ymin
if (y1 <= y2 && y1 < yaxis.min && y2 >= yaxis.min) {
x1 = (yaxis.min - y1) / (y2 - y1) * (x2 - x1) + x1;
y1 = yaxis.min;
}
else if (y2 <= y1 && y2 < yaxis.min && y1 >= yaxis.min) {
x2 = (yaxis.min - y1) / (y2 - y1) * (x2 - x1) + x1;
y2 = yaxis.min;
}
// clip with ymax
if (y1 >= y2 && y1 > yaxis.max && y2 <= yaxis.max) {
x1 = (yaxis.max - y1) / (y2 - y1) * (x2 - x1) + x1;
y1 = yaxis.max;
}
else if (y2 >= y1 && y2 > yaxis.max && y1 <= yaxis.max) {
x2 = (yaxis.max - y1) / (y2 - y1) * (x2 - x1) + x1;
y2 = yaxis.max;
}
// if the x value was changed we got a rectangle
// to fill
if (x1 != x1old) {
if (y1 <= yaxis.min)
top = yaxis.min;
else
top = yaxis.max;
ctx.lineTo(translateHoz(x1old), translateVert(top));
ctx.lineTo(translateHoz(x1), translateVert(top));
}
// fill the triangles
ctx.lineTo(translateHoz(x1), translateVert(y1));
ctx.lineTo(translateHoz(x2), translateVert(y2));
// fill the other rectangle if it's there
if (x2 != x2old) {
if (y2 <= yaxis.min)
top = yaxis.min;
else
top = yaxis.max;
ctx.lineTo(translateHoz(x2old), translateVert(top));
ctx.lineTo(translateHoz(x2), translateVert(top));
}
lastX = Math.max(x2, x2old);
}
/*
ctx.beginPath();
ctx.moveTo(translateHoz(data[0][0]), translateVert(0));
for (var i = 0; i < data.length; i++) {
ctx.lineTo(translateHoz(data[i][0]), translateVert(data[i][1]));
}
ctx.lineTo(translateHoz(data[data.length - 1][0]), translateVert(0));*/
ctx.lineTo(translateHoz(lastX), translateVert(bottom));
ctx.fill();
}
ctx.save();
ctx.translate(plotOffset.left, plotOffset.top);
ctx.lineJoin = "round";
var lw = series.lines.lineWidth;
var sw = series.shadowSize;
// FIXME: consider another form of shadow when filling is turned on
if (sw > 0) {
// draw shadow in two steps
ctx.lineWidth = sw / 2;
ctx.strokeStyle = "rgba(0,0,0,0.1)";
plotLine(series.data, lw/2 + sw/2 + ctx.lineWidth/2);
ctx.lineWidth = sw / 2;
ctx.strokeStyle = "rgba(0,0,0,0.2)";
plotLine(series.data, lw/2 + ctx.lineWidth/2);
}
ctx.lineWidth = lw;
ctx.strokeStyle = series.color;
if (series.lines.fill) {
ctx.fillStyle = series.lines.fillColor != null ? series.lines.fillColor : parseColor(series.color).scale(null, null, null, 0.4).toString();
plotLineArea(series.data, 0);
}
plotLine(series.data, 0);
ctx.restore();
}
function drawSeriesPoints(series) {
function plotPoints(data, radius, fill) {
for (var i = 0; i < data.length; ++i) {
var x = data[i][0], y = data[i][1];
if (x < xaxis.min || x > xaxis.max || y < yaxis.min || y > yaxis.max)
continue;
ctx.beginPath();
ctx.arc(translateHoz(x), translateVert(y), radius, 0, 2 * Math.PI, true);
if (fill)
ctx.fill();
ctx.stroke();
}
}
function plotPointShadows(data, offset, radius) {
for (var i = 0; i < data.length; ++i) {
var x = data[i][0], y = data[i][1];
if (x < xaxis.min || x > xaxis.max || y < yaxis.min || y > yaxis.max)
continue;
ctx.beginPath();
ctx.arc(translateHoz(x), translateVert(y) + offset, radius, 0, Math.PI, false);
ctx.stroke();
}
}
ctx.save();
ctx.translate(plotOffset.left, plotOffset.top);
var lw = series.lines.lineWidth;
var sw = series.shadowSize;
if (sw > 0) {
// draw shadow in two steps
ctx.lineWidth = sw / 2;
ctx.strokeStyle = "rgba(0,0,0,0.1)";
plotPointShadows(series.data, sw/2 + ctx.lineWidth/2, series.points.radius);
ctx.lineWidth = sw / 2;
ctx.strokeStyle = "rgba(0,0,0,0.2)";
plotPointShadows(series.data, ctx.lineWidth/2, series.points.radius);
}
ctx.lineWidth = series.points.lineWidth;
ctx.strokeStyle = series.color;
ctx.fillStyle = series.points.fillColor != null ? series.points.fillColor : series.color;
plotPoints(series.data, series.points.radius, series.points.fill);
ctx.restore();
}
function drawSeriesBars(series) {
function plotBars(data, barWidth, offset, fill) {
if (data.length < 2)
return;
for (var i = 0; i < data.length; i++) {
var x = data[i][0], y = data[i][1];
var drawLeft = true, drawTop = true, drawRight = true;
var left = x, right = x + barWidth, bottom = 0, top = y;
if (right < xaxis.min || left > xaxis.max || top < yaxis.min || bottom > yaxis.max)
continue;
// clip
if (left < xaxis.min) {
left = xaxis.min;
drawLeft = false;
}
if (right > xaxis.max) {
right = xaxis.max;
drawRight = false;
}
if (bottom < yaxis.min)
bottom = yaxis.min;
if (top > yaxis.max) {
top = yaxis.max;
drawTop = false;
}
// fill the bar
if (fill) {
ctx.beginPath();
ctx.moveTo(translateHoz(left), translateVert(bottom) + offset);
ctx.lineTo(translateHoz(left), translateVert(top) + offset);
ctx.lineTo(translateHoz(right), translateVert(top) + offset);
ctx.lineTo(translateHoz(right), translateVert(bottom) + offset);
ctx.fill();
}
// draw outline
if (drawLeft || drawRight || drawTop) {
ctx.beginPath();
ctx.moveTo(translateHoz(left), translateVert(bottom) + offset);
if (drawLeft)
ctx.lineTo(translateHoz(left), translateVert(top) + offset);
else
ctx.moveTo(translateHoz(left), translateVert(top) + offset);
if (drawTop)
ctx.lineTo(translateHoz(right), translateVert(top) + offset);
else
ctx.moveTo(translateHoz(right), translateVert(top) + offset);
if (drawRight)
ctx.lineTo(translateHoz(right), translateVert(bottom) + offset);
else
ctx.moveTo(translateHoz(right), translateVert(bottom) + offset);
ctx.stroke();
}
}
}
ctx.save();
ctx.translate(plotOffset.left, plotOffset.top);
ctx.lineJoin = "round";
var bw = series.bars.barWidth;
var lw = Math.min(series.bars.lineWidth, bw);
// FIXME: figure out a way to add shadows
/*
var sw = series.shadowSize;
if (sw > 0) {
// draw shadow in two steps
ctx.lineWidth = sw / 2;
ctx.strokeStyle = "rgba(0,0,0,0.1)";
plotBars(series.data, bw, lw/2 + sw/2 + ctx.lineWidth/2, false);
ctx.lineWidth = sw / 2;
ctx.strokeStyle = "rgba(0,0,0,0.2)";
plotBars(series.data, bw, lw/2 + ctx.lineWidth/2, false);
}*/
ctx.lineWidth = lw;
ctx.strokeStyle = series.color;
if (series.bars.fill) {
ctx.fillStyle = series.bars.fillColor != null ? series.bars.fillColor : parseColor(series.color).scale(null, null, null, 0.4).toString();
}
plotBars(series.data, bw, 0, series.bars.fill);
ctx.restore();
}
function insertLegend() {
if (!options.legend.show)
return;
var fragments = [];
var rowStarted = false;
for (i = 0; i < series.length; ++i) {
if (!series[i].label)
continue;
if (i % options.legend.noColumns == 0) {
if (rowStarted)
fragments.push('</tr>')
fragments.push('<tr>');
rowStarted = true;
}
fragments.push(
'<td class="legendColorBox"><div style="border:1px solid ' + options.legend.labelBoxBorderColor + ';padding:1px"><div style="width:14px;height:10px;background-color:' + series[i].color + '"></div></div></td>' +
'<td class="legendLabel">' + series[i].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 (options.legend.container != null)
options.legend.container.append(table);
else {
var pos = "";
var p = options.legend.position, m = options.legend.margin;
if (p.charAt(0) == "n")
pos += 'top:' + (m + plotOffset.top) + 'px;';
else if (p.charAt(0) == "s")
pos += 'bottom:' + (m + plotOffset.bottom) + 'px;';
if (p.charAt(1) == "e")
pos += 'right:' + (m + plotOffset.right) + 'px;';
else if (p.charAt(1) == "w")
pos += 'left:' + (m + plotOffset.bottom) + 'px;';
target.append('<div class="legend" style="position:absolute;' + pos +'">' + table + '</div>')
}
}
}
var lastMousePos = { pageX: null, pageY: null };
var selection = { first: { x: -1, y: -1}, second: { x: -1, y: -1} };
var prevSelection = null;
var selectionInterval = null;
function onMouseMove(e) {
// FIXME: temp. work-around until jQuery bug 1871 is fixed
var e = e || window.event;
var de = document.documentElement;
var b = document.body;
if (e.pageX == null && e.clientX != null) {
var de = document.documentElement, b = document.body;
lastMousePos.pageX = e.clientX + (de && de.scrollLeft || b.scrollLeft || 0);
lastMousePos.pageY = e.clientY + (de && de.scrollTop || b.scrollTop || 0);
}
else {
lastMousePos.pageX = e.pageX;
lastMousePos.pageY = e.pageY;
}
}
function onMouseDown(e) {
if (e.which != 1) // only accept left-click
return;
setSelectionPos(selection.first, e);
if (selectionInterval != null)
clearInterval(selectionInterval);
lastMousePos.pageX = null;
selectionInterval = setInterval(updateSelectionOnMouseMove, 200);
$(document).one("mouseup", onSelectionMouseUp);
}
function triggerSelectedEvent() {
var x1, x2, y1, y2;
if (selection.first.x <= selection.second.x) {
x1 = selection.first.x;
x2 = selection.second.x;
}
else {
x1 = selection.second.x;
x2 = selection.first.x;
}
if (selection.first.y >= selection.second.y) {
y1 = selection.first.y;
y2 = selection.second.y;
}
else {
y1 = selection.second.y;
y2 = selection.first.y;
}
x1 = xaxis.min + x1 / hozScale;
x2 = xaxis.min + x2 / hozScale;
y1 = yaxis.max - y1 / vertScale;
y2 = yaxis.max - y2 / vertScale;
target.trigger("selected", [ { x1: x1, y1: y1, x2: x2, y2: y2 } ]);
}
function onSelectionMouseUp(e) {
if (selectionInterval != null) {
clearInterval(selectionInterval);
selectionInterval = null;
}
setSelectionPos(selection.second, e);
clearSelection();
if (!selectionIsSane() || e.which != 1)
return;
drawSelection();
triggerSelectedEvent();
return false;
}
function setSelectionPos(pos, e) {
var offset = $(overlay).offset()
if (options.selection.mode == "y") {
if (pos == selection.first)
pos.x = 0;
else
pos.x = plotWidth;
}
else {
pos.x = e.pageX - offset.left - plotOffset.left;
pos.x = Math.min(Math.max(0, pos.x), plotWidth);
}
if (options.selection.mode == "x") {
if (pos == selection.first)
pos.y = 0;
else
pos.y = plotHeight;
}
else {
pos.y = e.pageY - offset.top - plotOffset.top;
pos.y = Math.min(Math.max(0, pos.y), plotHeight);
}
}
function updateSelectionOnMouseMove() {
if (lastMousePos.pageX == null)
return;
setSelectionPos(selection.second, lastMousePos);
clearSelection();
if (selectionIsSane())
drawSelection();
}
function clearSelection() {
if (prevSelection == null)
return;
var x = Math.min(prevSelection.first.x, prevSelection.second.x),
y = Math.min(prevSelection.first.y, prevSelection.second.y),
w = Math.abs(prevSelection.second.x - prevSelection.first.x),
h = Math.abs(prevSelection.second.y - prevSelection.first.y);
octx.clearRect(x + plotOffset.left - octx.lineWidth,
y + plotOffset.top - octx.lineWidth,
w + octx.lineWidth*2,
h + octx.lineWidth*2);
prevSelection = null;
}
this.clearSelection = clearSelection;
this.setSelection = function(area) {
clearSelection();
if (options.selection.mode == "x") {
selection.first.y = 0;
selection.second.y = plotHeight;
}
else {
selection.first.y = (yaxis.max - area.y1) * vertScale
selection.second.y = (yaxis.max - area.y2) * vertScale
}
if (options.selection.mode == "y") {
selection.first.x = 0;
selection.second.x = plotWidth;
}
else {
selection.first.x = (area.x1 - xaxis.min) * hozScale
selection.second.x = (area.x2 - xaxis.min) * hozScale
}
drawSelection();
triggerSelectedEvent();
}
function drawSelection() {
if (prevSelection != null
&& selection.first.x == prevSelection.first.x
&& selection.first.y == prevSelection.first.y
&& selection.second.x == prevSelection.second.x
&& selection.second.y == prevSelection.second.y)
return;
octx.strokeStyle = parseColor(options.selection.color).scale(null, null, null, 0.8).toString();
octx.lineWidth = 1;
ctx.lineJoin = "round";
octx.fillStyle = parseColor(options.selection.color).scale(null, null, null, 0.4).toString();
prevSelection = { first: { x: selection.first.x,
y: selection.first.y },
second: { x: selection.second.x,
y: selection.second.y } };
var x = Math.min(selection.first.x, selection.second.x),
y = Math.min(selection.first.y, selection.second.y),
w = Math.abs(selection.second.x - selection.first.x),
h = Math.abs(selection.second.y - selection.first.y);
octx.fillRect(x + plotOffset.left, y + plotOffset.top, w, h);
octx.strokeRect(x + plotOffset.left, y + plotOffset.top, w, h);
}
function selectionIsSane() {
var minSize = 5;
return Math.abs(selection.second.x - selection.first.x) >= minSize &&
Math.abs(selection.second.y - selection.first.y) >= minSize;
}
}
$.plot = function(target, data, options) {
var plot = new Plot(target, data, options);
/*var t0 = new Date();
var t1 = new Date();
var tstr = "time used (msecs): " + (t1.getTime() - t0.getTime())
if (window.console)
console.log(tstr);
else
alert(tstr);*/
return plot;
};
/* FIXME: delete
function drawLine(ctx, x1, y1, x2, y2) {
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke();
}
*/
function getMagnitude(x) {
return Math.pow(10, Math.floor(Math.log(x) / Math.LN10));
}
// color helpers, inspiration from the jquery color animation
// plugin by John Resig
function Color (r, g, b, a) {
this.r = r != null ? r: 0;
this.g = g != null ? g: 0;
this.b = b != null ? b: 0;
this.a = a != null ? a: 1.0;
this.toString = function() {
if (this.a >= 1.0)
return "rgb(" + [
this.r, this.g, this.b
].join(",") + ")";
else
return "rgba(" + [
this.r, this.g, this.b, this.a
].join(",") + ")";
}
this.scale = function(rf, gf, bf, af) {
if (rf != null)
this.r *= rf;
if (gf != null)
this.g *= gf;
if (bf != null)
this.b *= bf;
if (af != null)
this.a *= af;
return this.normalize();
}
this.adjust = function(rd, gd, bd, ad) {
if (rd != null)
this.r += rd;
if (gd != null)
this.g += gd;
if (bd != null)
this.b += bd;
if (ad != null)
this.a += ad;
return this.normalize();
}
this.clone = function() {
return new Color(this.r, this.b, this.g, this.a);
}
this.normalize = function() {
this.r = Math.max(Math.min(parseInt(this.r), 255), 0);
this.g = Math.max(Math.min(parseInt(this.g), 255), 0);
this.b = Math.max(Math.min(parseInt(this.b), 255), 0);
this.a = Math.max(Math.min(this.a, 1), 0);
return this;
}
this.normalize();
}
var lookupColors = {
aqua:[0,255,255],
azure:[240,255,255],
beige:[245,245,220],
black:[0,0,0],
blue:[0,0,255],
brown:[165,42,42],
cyan:[0,255,255],
darkblue:[0,0,139],
darkcyan:[0,139,139],
darkgrey:[169,169,169],
darkgreen:[0,100,0],
darkkhaki:[189,183,107],
darkmagenta:[139,0,139],
darkolivegreen:[85,107,47],
darkorange:[255,140,0],
darkorchid:[153,50,204],
darkred:[139,0,0],
darksalmon:[233,150,122],
darkviolet:[148,0,211],
fuchsia:[255,0,255],
gold:[255,215,0],
green:[0,128,0],
indigo:[75,0,130],
khaki:[240,230,140],
lightblue:[173,216,230],
lightcyan:[224,255,255],
lightgreen:[144,238,144],
lightgrey:[211,211,211],
lightpink:[255,182,193],
lightyellow:[255,255,224],
lime:[0,255,0],
magenta:[255,0,255],
maroon:[128,0,0],
navy:[0,0,128],
olive:[128,128,0],
orange:[255,165,0],
pink:[255,192,203],
purple:[128,0,128],
violet:[128,0,128],
red:[255,0,0],
silver:[192,192,192],
white:[255,255,255],
yellow:[255,255,0]
};
// parse string looking for color tuples, returns Color
function parseColor(str) {
// Some named colors to work with
// From Interface by Stefan Petre
// http://interface.eyecon.ro/
var result;
// Look for rgb(num,num,num)
if (result = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(str))
return new Color(parseInt(result[1]), parseInt(result[2]), parseInt(result[3]));
// Look for rgba(num,num,num,num)
if (result = /rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))
return new Color(parseInt(result[1]), parseInt(result[2]), parseInt(result[3]), parseFloat(result[4]));
// Look for rgb(num%,num%,num%)
if (result = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(str))
return new Color(parseFloat(result[1])*2.55, parseFloat(result[2])*2.55, parseFloat(result[3])*2.55);
// Look for rgba(num%,num%,num%,num)
if (result = /rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))
return new Color(parseFloat(result[1])*2.55, parseFloat(result[2])*2.55, parseFloat(result[3])*2.55, parseFloat(result[4]));
// Look for #a0b1c2
if (result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(str))
return new Color(parseInt(result[1],16), parseInt(result[2],16), parseInt(result[3],16));
// Look for #fff
if (result = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(str))
return new Color(parseInt(result[1]+result[1],16), parseInt(result[2]+result[2],16), parseInt(result[3]+result[3],16));
// Otherwise, we're most likely dealing with a named color
var name = jQuery.trim(str).toLowerCase();
if (name == "transparent")
return new Color(0, 0, 0, 0);
else {
result = lookupColors[name];
return new Color(result[0], result[1], result[2]);
}
}
})(jQuery);
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment