From 1fd4de1b25398954e9ab43e54d0d0fdf1b5cfce0 Mon Sep 17 00:00:00 2001 From: Frank Wuerthwein Date: Sat, 14 Mar 2009 20:01:55 -0400 Subject: flot-0.6pre@147 --- API.txt | 241 ++- DISTRIBUTION.txt | 4 + FAQ.txt | 58 + NEWS.txt | 102 ++ TODO | 2 - examples/basic.html | 2 +- examples/dual-axis.html | 2 +- examples/graph-types.html | 12 +- examples/index.html | 6 +- examples/interacting.html | 2 +- examples/selection.html | 6 +- examples/setting-options.html | 4 +- examples/thresholding.html | 52 + examples/time.html | 2 +- examples/tracking.html | 90 ++ examples/turning-series.html | 2 +- examples/visitors.html | 4 +- examples/zooming.html | 8 +- excanvas.js | 649 ++++---- excanvas.min.js | 1 + excanvas.pack.js | 1 - jquery.flot.js | 1043 ++++++++----- jquery.flot.pack.js | 1 - jquery.js | 3415 +---------------------------------------- 24 files changed, 1570 insertions(+), 4139 deletions(-) create mode 100644 DISTRIBUTION.txt create mode 100644 FAQ.txt create mode 100644 examples/thresholding.html create mode 100644 examples/tracking.html create mode 100644 excanvas.min.js delete mode 100644 excanvas.pack.js delete mode 100644 jquery.flot.pack.js diff --git a/API.txt b/API.txt index f2f59df..3b31050 100644 --- a/API.txt +++ b/API.txt @@ -5,11 +5,14 @@ Consider a call to the plot function: var plot = $.plot(placeholder, data, options) -The placeholder is a jQuery object that the plot will be put into. -This placeholder needs to have its width and height set as explained -in the README (go read that now if you haven't, it's short). The plot -will modify some properties of the placeholder so it's recommended you -simply pass in a div that you don't use for anything else. +The placeholder is a jQuery object or DOM element or jQuery expression +that the plot will be put into. This placeholder needs to have its +width and height set as explained in the README (go read that now if +you haven't, it's short). The plot will modify some properties of the +placeholder so it's recommended you simply pass in a div that you +don't use for anything else. Make sure you check any fancy styling +you apply to the div, e.g. background images have been reported to be a +problem on IE 7. The format of the data is documented below, as is the available options. The "plot" object returned has some methods you can call. @@ -40,7 +43,8 @@ Note that to simplify the internal logic in Flot both the x and y values must be numbers, even if specifying time series (see below for how to do this). This is a common problem because you might retrieve data from the database and serialize them directly to JSON without -noticing the wrong type. +noticing the wrong type. If you're getting mysterious errors, double +check that you're inputting numbers and not strings. If a null is specified as a point or if one of the coordinates is null or couldn't be converted to a number, the point is ignored when @@ -48,17 +52,23 @@ drawing. As a special case, a null value for lines is interpreted as a line segment end, i.e. the point before and after the null value are not connected. +Lines and points take two coordinates. For bars, you can specify a +third coordinate which is the bottom of the bar (defaults to 0). + The format of a single series object is as follows: { - color: color or number, - data: rawdata, - label: string, - lines: specific lines options, - bars: specific bars options, - points: specific points options, - xaxis: 1 or 2, - yaxis: 1 or 2, + color: color or number + data: rawdata + label: string + lines: specific lines options + bars: specific bars options + points: specific points options + threshold: specific threshold options + xaxis: 1 or 2 + yaxis: 1 or 2 + clickable: boolean + hoverable: boolean shadowSize: number } @@ -88,6 +98,10 @@ to get the secondary axis (x axis at top or y axis to the right). E.g., you can use this to make a dual axis plot by specifying { yaxis: 2 } for one data series. +"clickable" and "hoverable" can be set to false to disable +interactivity for specific series if interactivity is turned on in +plot, see below. + The rest of the options are all documented below as they are the same as the default options passed in via the options parameter in the plot commmand. When you specify them for a specific data series, they will @@ -118,14 +132,14 @@ Customizing the legend legend: { show: boolean - labelFormatter: null or (fn: string -> string) + labelFormatter: null or (fn: string, series object -> string) labelBoxBorderColor: color noColumns: number position: "ne" or "nw" or "se" or "sw" - margin: number of pixels + margin: number of pixels or [x margin, y margin] backgroundColor: null or color - backgroundOpacity: number in 0.0 - 1.0 - container: null or jQuery object + backgroundOpacity: number between 0 and 1 + container: null or jQuery object/DOM element/jQuery expression } The legend is generated as a table with the data series labels and @@ -134,21 +148,24 @@ the labels in some way, e.g. make them to links, you can pass in a function for "labelFormatter". Here's an example that makes them clickable: - labelFormatter: function(label) { - return '' + label + ''; + labelFormatter: function(label, series) { + // series is the series object for the label + return '' + label + ''; } "noColumns" is the number of columns to divide the legend table into. "position" specifies the overall placement of the legend within the plot (top-right, top-left, etc.) and margin the distance to the plot -edge. "backgroundColor" and "backgroundOpacity" specifies the +edge (this can be either a number or an array of two numbers like [x, +y]). "backgroundColor" and "backgroundOpacity" specifies the background. The default is a partly transparent auto-detected background. If you want the legend to appear somewhere else in the DOM, you can -specify "container" as a jQuery object to put the legend table into. -The "position" and "margin" etc. options will then be ignored. Note -that it will overwrite the contents of the container. +specify "container" as a jQuery object/expression to put the legend +table into. The "position" and "margin" etc. options will then be +ignored. Note that Flot will overwrite the contents of the container. +Most of the above settings do not apply @@ -170,7 +187,7 @@ Customizing the axes tickDecimals: null or number } -The axes have the same kind of options. The "mode" option +All axes have the same kind of options. The "mode" option determines how the data is interpreted, the default of null means as decimal numbers. Use "time" for time series data, see the next section. @@ -200,8 +217,8 @@ round tick interval size. Then it generates the ticks. You can specify how many ticks the algorithm aims for by setting "ticks" to a number. The algorithm always tries to generate reasonably round tick values so even if you ask for three ticks, you might get -five if that fits better with the rounding. If you don't want ticks, -set "ticks" to 0 or an empty array. +five if that fits better with the rounding. If you don't want any +ticks at all, set "ticks" to 0 or an empty array. Another option is to skip the rounding part and directly set the tick interval size with "tickSize". If you set it to 2, you'll get ticks at @@ -320,10 +337,11 @@ pretend trick described above. But you can fix up the timestamps by adding the time zone offset, e.g. for UTC+0200 you would add 2 hours to the UTC timestamp you got. Then it'll look right on the plot. Most programming environments have some means of getting the timezone -offset for a specific date. +offset for a specific date (note that you need to get the offset for +each individual timestamp to account for daylight savings). -Once you've got the timestamps into the data and specified "time" as -the axis mode, Flot will automatically generate relevant ticks and +Once you've gotten the timestamps into the data and specified "time" +as the axis mode, Flot will automatically generate relevant ticks and format them. As always, you can tweak the ticks via the "ticks" option - just remember that the values should be timestamps (numbers), not Date objects. @@ -331,17 +349,15 @@ Date objects. Tick generation and formatting can also be controlled separately through the following axis options: - xaxis, yaxis: { - minTickSize - timeformat: null or format string - monthNames: null or array of size 12 of strings - } + minTickSize: array + timeformat: null or format string + monthNames: null or array of size 12 of strings Here "timeformat" is a format string to use. You might use it like this: xaxis: { - mode: "time", + mode: "time" timeformat: "%y/%m/%d" } @@ -360,7 +376,14 @@ specifiers are supported You can customize the month names with the "monthNames" option. For instance, for Danish you might specify: - monthName: ["jan", "feb", "mar", "apr", "maj", "jun", "jul", "aug", "sep", "okt", "nov", "dec"] + monthNames: ["jan", "feb", "mar", "apr", "maj", "jun", "jul", "aug", "sep", "okt", "nov", "dec"] + +The format string and month names are used by a very simple built-in +format function that takes a date object, a format string (and +optionally an array of month names) and returns the formatted string. +If needed, you can access it as $.plot.formatDate(date, formatstring, +monthNames) or even replace it with another more advanced function +from a date library if you're feeling adventurous. If everything else fails, you can control the formatting by specifying a custom tick formatter function as usual. Here's a simple example @@ -391,7 +414,7 @@ Customizing the data series show: boolean lineWidth: number fill: boolean or number - fillColor: color + fillColor: null or color/gradient } points: { @@ -401,23 +424,38 @@ Customizing the data series bars: { barWidth: number align: "left" or "center" + horizontal: boolean + } + + lines: { + steps: boolean } colors: [ color1, color2, ... ] shadowSize: number + threshold: { + below: number + color: color + } + The most important options are "lines", "points" and "bars" that specifies whether and how lines, points and bars should be shown for -each data series. You can specify them independently of each other, -and Flot will happily draw each of them in turn, e.g. +each data series. In case you don't specify anything at all, Flot will +default to showing lines (you can turn this off with +lines: { show: false}). You can specify the various types +independently of each other, and Flot will happily draw each of them +in turn, e.g. var options = { lines: { show: true, fill: true, fillColor: "rgba(255, 255, 255, 0.8)" }, points: { show: true, fill: false } }; -"lineWidth" is the thickness of the line or outline in pixels. +"lineWidth" is the thickness of the line or outline in pixels. You can +set it to 0 to prevent a line or outline from being drawn; this will +also hide the shadow. "fill" is whether the shape should be filled. For lines, this produces area graphs. You can use "fillColor" to specify the color of the fill. @@ -426,12 +464,21 @@ points), the fill color is auto-set to the color of the data series. You can adjust the opacity of the fill by setting fill to a number between 0 (fully transparent) and 1 (fully opaque). -"barWidth" is the width of the bars in units of the x axis, contrary -to most other measures that are specified in pixels. For instance, for -time series the unit is milliseconds so 24 * 60 * 60 * 1000 produces -bars with the width of a day. "align" specifies whether a bar should -be left-aligned (default) or centered on top of the value it -represents. +For bars, fillColor can be a gradient, see the gradient documentation +below. "barWidth" is the width of the bars in units of the x axis, +contrary to most other measures that are specified in pixels. For +instance, for time series the unit is milliseconds so 24 * 60 * 60 * +1000 produces bars with the width of a day. "align" specifies whether +a bar should be left-aligned (default) or centered on top of the value +it represents. When "horizontal" is on, the bars are drawn +horizontally, i.e. from the y axis instead of the x axis; note that +the bar end points are still defined in the same way so you'll +probably want to swap the coordinates if you've been plotting vertical +bars first. + +For lines, "steps" specifies whether two adjacent data points are +connected with a straight (possibly diagonal) line or with first a +horizontal and then a vertical line. The "colors" array specifies a default color theme to get colors for the data series from. You can specify as many colors as you like, like @@ -445,36 +492,40 @@ extra colors by lightening and darkening colors in the theme. "shadowSize" is the default size of shadows in pixels. Set it to 0 to remove shadows. +"threshold" specifies that the data points below "below" should be +drawn with the specified color. This makes it easy to mark points +below 0, e.g. for budget data. + Customizing the grid ==================== grid: { color: color - backgroundColor: color or null + backgroundColor: color/gradient or null tickColor: color labelMargin: number markings: array of markings or (fn: axes -> array of markings) borderWidth: number + borderColor: color or null clickable: boolean hoverable: boolean autoHighlight: boolean mouseActiveRadius: number } -The grid is the thing with the axes and a number of ticks. "color" -is the color of the grid itself whereas "backgroundColor" specifies -the background color inside the grid area. The default value of null -means that the background is transparent. You should only need to set -backgroundColor if you want the grid area to be a different color from the -page color. Otherwise you might as well just set the background color -of the page with CSS. +The grid is the thing with the axes and a number of ticks. "color" is +the color of the grid itself whereas "backgroundColor" specifies the +background color inside the grid area. The default value of null means +that the background is transparent. You can also set a gradient, see +the gradient documentation below. "tickColor" is the color of the ticks and "labelMargin" is the spacing between tick labels and the grid. Note that you can style the tick labels with CSS, e.g. to change the color. They have class "tickLabel". "borderWidth" is the width of the border around the plot. Set it to 0 -to disable the border. +to disable the border. You can also set "borderColor" if you want the +border to have a different color than the grid lines. "markings" is used to draw simple lines and rectangular areas in the background of the plot. You can either specify an array of ranges on @@ -566,6 +617,10 @@ and still activate it. If there are two or more points within this radius, Flot chooses the closest item. For bars, the top-most bar (from the latest specified data series) is chosen. +If you want to disable interactivity for a specific data series, you +can set "hoverable" and "clickable" to false in the options for that +series, like this { data: [...], label: "Foo", clickable: false }. + Customizing the selection ========================= @@ -591,6 +646,55 @@ like this: // and y2axis if present }); +The "plotselected" event is only fired when the user has finished +making the selection. A "plotselecting" event is fired during the +process with the same parameters as the "plotselected" event, in case +you want to know what's happening while it's happening, + +A "plotunselected" event with no arguments is emitted when the user +clicks the mouse to remove the selection. + + +Customizing the crosshair +========================= + + crosshair: { + mode: null or "x" or "y" or "xy" + color: color + } + +You can enable crosshairs, thin lines, that follow the mouse by +setting the mode to one of "x", "y" or "xy". The "x" mode enables a +vertical crosshair that lets you trace the values on the x axis, "y" +enables a horizontal crosshair and "xy" enables them both. + + +Specifying gradients +==================== + +A gradient is specified like this: + + { colors: [ color1, color2, ... ] } + +For instance, you might specify a background on the grid going from +black to gray like this: + + grid: { + backgroundColor: { colors: ["#000", "#999"] } + } + +For the series you can specify the gradient as an object that +specifies the scaling of the brightness and the opacity of the series +color, e.g. + + { colors: [{ opacity: 0.8 }, { brightness: 0.6, opacity: 0.8 } ] + +where the first color simply has its alpha scaled, whereas the second +is also darkened. + +Flot currently only supports vertical gradients drawn from top to +bottom because that's what works with IE. + Plot Methods ------------ @@ -598,11 +702,6 @@ Plot Methods The Plot object returned from the plot function has some methods you can call: - - clearSelection() - - Clear the selection rectangle. - - - setSelection(ranges, preventEvent) Set the selection rectangle. The passed in ranges is on the same @@ -617,7 +716,27 @@ can call: setSelection will trigger the "plotselected" event when called. If you don't want that to happen, e.g. if you're inside a "plotselected" handler, pass true as the second parameter. + + - clearSelection(preventEvent) + + Clear the selection rectangle. Pass in true to avoid getting a + "plotunselected" event. + + + - setCrosshair(pos) + + Set the position of the crosshair. Note that this is cleared if + the user moves the mouse. "pos" should be on the form { x: xpos, + y: ypos } (or x2 and y2 if you're using the secondary axes), which + is coincidentally the same format as what you get from a "plothover" + event. If "pos" is null, the crosshair is cleared. + + + - clearCrosshair() + + Clear the crosshair. + - highlight(series, datapoint) diff --git a/DISTRIBUTION.txt b/DISTRIBUTION.txt new file mode 100644 index 0000000..65d9ffb --- /dev/null +++ b/DISTRIBUTION.txt @@ -0,0 +1,4 @@ +This tree contains +jQuery 1.3.2 http://jquery.com/ +flot revision revision 147 http://code.google.com/p/flot/ + diff --git a/FAQ.txt b/FAQ.txt new file mode 100644 index 0000000..d70fcc6 --- /dev/null +++ b/FAQ.txt @@ -0,0 +1,58 @@ +Frequently asked questions +-------------------------- + +Q: Flot isn't working when I'm using JSON data as source! + +A: Actually, Flot loves JSON data, you just got the format wrong. +Double check that you're not inputting strings instead of numbers, +like [["0", "-2.13"], ["5", "4.3"]]. This is most common mistake, and +the error might not show up immediately because Javascript can do some +conversion automatically. + + +Q: Can I export the graph? + +A: This is a limitation of the canvas technology. There's a hook in +the canvas object for getting an image out, but you won't get the tick +labels. And it's not likely to be supported by IE. At this point, your +best bet is probably taking a screenshot, e.g. with PrtScn. + + +Q: The bars are all tiny in time mode? + +A: It's not really possible to determine the bar width +automatically. So you have to set the width with the barWidth option +which is NOT in pixels, but in the units of the x axis. For time +mode that's milliseconds so the default value of 1 makes the bars 1 +millisecond wide. + + +Q: Can I use Flot with libraries like Mootools or Prototype? + +A: Yes, Flot supports it out of the box and it's easy! Just use jQuery +instead of $, e.g. call jQuery.plot instead of $.plot and use +jQuery(something) instead of $(something). As a convenience, you can +put in a DOM element for the graph placeholder where the examples and +the API documentation are using jQuery objects. + +Depending on how you include jQuery, you may have to add one line of +code to prevent jQuery from overwriting functions from the other +libraries, see the documentation in jQuery ("Using jQuery with other +libraries") for details. + + +Q: Flot doesn't work with [widget framework xyz]! + +A: The problem is most likely within the framework, or your use of the +framework. + +The only non-standard thing used by Flot is the canvas tag; otherwise +it is simply a series of absolute positioned divs within the +placeholder tag you put in. If this is not working, it's probably +because the framework you're using is doing something weird with the +DOM. As a last resort, you might try replotting and see if it helps. + +If you find there's a specific thing we can do to Flot to help, feel +free to submit a bug report. Otherwise, you're welcome to ask for help +on the mailing list, but please don't submit a bug report to Flot - +try the framework instead. diff --git a/NEWS.txt b/NEWS.txt index 49bf82b..a03e649 100644 --- a/NEWS.txt +++ b/NEWS.txt @@ -1,3 +1,105 @@ +Flot 0.x +-------- + +Changes: + +- Added support for disabling interactivity for specific data series + (request from Ronald Schouten and Steve Upton). + +- Flot now calls $() on the placeholder and optional legend container + passed in so you can specify DOM elements or CSS expressions to make + it easier to use Flot with libraries like Prototype or Mootools. + +- A new "plotselecting" event is now emitted while the user is making + selection. + +- Added a new crosshairs feature for tracing the mouse position on the + axes, enable with crosshair { mode: "x"} (see the new tracking + example for a use). + +- The "plothover" event is now emitted immediately instead of at most + 10 times per second, you'll have to put in a setTimeout yourself if + you're doing something really expensive on this event. + +- The built-in date formatter can now be accessed as + $.plot.formatDate(...) (suggestion by Matt Manela) and even + replaced. + +- Added "borderColor" option to the grid (patch from Amaury Chamayou + and patch from Mike R. Williamson). + +- Added support for gradient backgrounds for the grid, take a look at + the "setting options" example (based on patch from Amaury Chamayou, + issue 90). + +- Gradient bars (suggestion by stefpet). + +- Added a "plotunselected" event which is triggered when the selection + is removed, see "selection" example (suggestion by Meda Ugo); + +- The option legend.margin can now specify horizontal and vertical + margins independently (suggestion by someone who's annoyed). + +- Data passed into Flot is now copied to a new canonical format to + enable further processing before it hits the drawing routines. As a + side-effect, this should make Flot more robust in the face of bad + data (and fixes issue 112). + +- Step-wise charting: line charts have a new option "steps" that when + set to true connects the points with horizontal/vertical steps + instead of diagonal lines. + +- Thresholding: you can set a threshold and a color, and the data + points below that threshold will then get the color. Useful for + marking data below 0, for instance. + +- The legend labelFormatter now passes the series in addition to just + the label (suggestion by Vincent Lemeltier). + +- Horizontal bars (based on patch by Jason LeBrun). + +- Support for partial bars by specifying a third coordinate, i.e. they + don't have to start from the axis. This can be used to make stacked + bars. + +Bug fixes: + +- Fixed two corner-case bugs when drawing filled curves (report and + analysis by Joshua Varner). +- Fix auto-adjustment code when setting min to 0 for an axis where the + dataset is completely flat on that axis (report by chovy). +- Fixed a bug with passing in data from getData to setData when the + secondary axes are used (issue 65, reported by nperelman). +- Fixed so that it is possible to turn lines off when no other chart + type is shown (based on problem reported by Glenn Vanderburg), and + fixed so that setting lineWidth to 0 also hides the shadow (based on + problem reported by Sergio Nunes). +- Updated mousemove position expression to the latest from jQuery (bug + reported by meyuchas). +- Use borders instead of background in legend (fix printing issue 25 + and 45). +- Explicitly convert axis min/max to numbers. +- Fixed a bug with drawing marking lines with different colors + (reported by Khurram). +- Fixed a bug with returning y2 values in the selection event (fix + by exists, issue 75). +- Only set position relative on placeholder if it hasn't already a + position different from static (reported by kyberneticist, issue 95). +- Don't round markings to prevent sub-pixel problems (reported by Dan + Lipsitt). +- Make the grid border act similarly to a regular CSS border, i.e. + prevent it from overlapping the plot itself. This also fixes a + problem with anti-aliasing when the width is 1 pixel (reported by + Anthony Ettinger). +- Imported SVN version of excanvas and fixed an issue with the newer + version. Hopefully, this will make Flot work with IE8 (nudge by + Fabien Menager). +- Changed the shadow code for lines to hopefully look a bit better + with vertical lines. +- Round tick positions to avoid possible problems with fractions + (suggestion by Fred, issue 130). + + Flot 0.5 -------- diff --git a/TODO b/TODO index 3f7be1c..1e28b7f 100644 --- a/TODO +++ b/TODO @@ -28,8 +28,6 @@ labels error margin indicators - for scientific/statistical purposes -hi-low bars - non-xy based graph types - figure out how to integrate them with the rest of the plugin - pie charts diff --git a/examples/basic.html b/examples/basic.html index afd7ac1..fde8def 100644 --- a/examples/basic.html +++ b/examples/basic.html @@ -4,7 +4,7 @@ Flot Examples - + diff --git a/examples/dual-axis.html b/examples/dual-axis.html index d97fa8a..03a94e6 100644 --- a/examples/dual-axis.html +++ b/examples/dual-axis.html @@ -4,7 +4,7 @@ Flot Examples - + diff --git a/examples/graph-types.html b/examples/graph-types.html index 64aa0e9..b3c3818 100644 --- a/examples/graph-types.html +++ b/examples/graph-types.html @@ -4,7 +4,7 @@ Flot Examples - + @@ -36,7 +36,11 @@ $(function () { var d5 = []; for (var i = 0; i < 14; i += 0.5) d5.push([i, Math.sqrt(i)]); - + + var d6 = []; + for (var i = 0; i < 14; i += 0.5 + Math.random()) + d6.push([i, Math.sqrt(2*i + Math.sin(i) + 5)]); + $.plot($("#placeholder"), [ { data: d1, @@ -58,6 +62,10 @@ $(function () { data: d5, lines: { show: true }, points: { show: true } + }, + { + data: d6, + lines: { show: true, steps: true } } ]); }); diff --git a/examples/index.html b/examples/index.html index 36ae0a1..3f116ed 100644 --- a/examples/index.html +++ b/examples/index.html @@ -4,7 +4,7 @@ Flot Examples - + @@ -16,11 +16,11 @@ diff --git a/examples/interacting.html b/examples/interacting.html index b5c8003..5cb59d0 100644 --- a/examples/interacting.html +++ b/examples/interacting.html @@ -4,7 +4,7 @@ Flot Examples - + diff --git a/examples/selection.html b/examples/selection.html index 4a745d7..56cb3db 100644 --- a/examples/selection.html +++ b/examples/selection.html @@ -4,7 +4,7 @@ Flot Examples - + @@ -90,6 +90,10 @@ $(function () { xaxis: { min: ranges.xaxis.from, max: ranges.xaxis.to } })); }); + + placeholder.bind("plotunselected", function (event) { + $("#selection").text(""); + }); var plot = $.plot(placeholder, data, options); diff --git a/examples/setting-options.html b/examples/setting-options.html index 31dd798..b0570b2 100644 --- a/examples/setting-options.html +++ b/examples/setting-options.html @@ -4,7 +4,7 @@ Flot Examples - + @@ -53,7 +53,7 @@ $(function () { max: 2 }, grid: { - backgroundColor: "#fffaff" + backgroundColor: { colors: ["#fff", "#eee"] } } }); }); diff --git a/examples/thresholding.html b/examples/thresholding.html new file mode 100644 index 0000000..7d29294 --- /dev/null +++ b/examples/thresholding.html @@ -0,0 +1,52 @@ + + + + + Flot Examples + + + + + + +

Flot Examples

+ +
+ +

You can apply a specific color to the part of a data series + below a threshold. This is can be useful for highlighting negative + values, e.g. when displaying net results or what's in stock.

+ +

+ + + +

+ + + + + diff --git a/examples/time.html b/examples/time.html index f810a28..d1dd4e3 100644 --- a/examples/time.html +++ b/examples/time.html @@ -4,7 +4,7 @@ Flot Examples - + diff --git a/examples/tracking.html b/examples/tracking.html new file mode 100644 index 0000000..722f960 --- /dev/null +++ b/examples/tracking.html @@ -0,0 +1,90 @@ + + + + + Flot Examples + + + + + + +

Flot Examples

+ +
+ +

You can add crosshairs that'll track the mouse position, either + on both axes or as here on only one.

+ +

If you combine it with listening on hover events, you can use + it to track the intersection on the curves by interpolating + the data points.

+ +

+ + + + + diff --git a/examples/turning-series.html b/examples/turning-series.html index 83fb134..7b25479 100644 --- a/examples/turning-series.html +++ b/examples/turning-series.html @@ -4,7 +4,7 @@ Flot Examples - + diff --git a/examples/visitors.html b/examples/visitors.html index 919f903..13440fa 100644 --- a/examples/visitors.html +++ b/examples/visitors.html @@ -4,7 +4,7 @@ Flot Examples - + @@ -60,7 +60,7 @@ $(function () { lines: { show: true, lineWidth: 1 }, shadowSize: 0, xaxis: { ticks: [], mode: "time" }, - yaxis: { ticks: [], min: 0, max: 4000 }, + yaxis: { ticks: [], min: 0, autoscaleMargin: 0.1 }, selection: { mode: "x" } }); diff --git a/examples/zooming.html b/examples/zooming.html index 0f3284b..d74f065 100644 --- a/examples/zooming.html +++ b/examples/zooming.html @@ -4,7 +4,7 @@ Flot Examples - + @@ -31,8 +31,10 @@ $(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))]); + for (var i = 0; i <= 100; ++i) { + var x = x1 + i * (x2 - x1) / 100; + d.push([x, Math.sin(x * Math.sin(x))]); + } return [ { label: "sin(x sin(x))", data: d } diff --git a/excanvas.js b/excanvas.js index 7914cb9..73963d8 100644 --- a/excanvas.js +++ b/excanvas.js @@ -28,80 +28,101 @@ // (http://www.whatwg.org/specs/web-apps/current-work/#the-doctype) // or use Box Sizing Behavior from WebFX // (http://webfx.eae.net/dhtml/boxsizing/boxsizing.html) +// * Non uniform scaling does not correctly scale strokes. // * Optimize. There is always room for speed improvements. -// only add this code if we do not already have a canvas implementation -if (!window.CanvasRenderingContext2D) { +// Only add this code if we do not already have a canvas implementation +if (!document.createElement('canvas').getContext) { -(function () { +(function() { // alias some functions to make (compiled) code shorter var m = Math; var mr = m.round; var ms = m.sin; var mc = m.cos; + var abs = m.abs; + var sqrt = m.sqrt; // this is used for sub pixel precision var Z = 10; var Z2 = Z / 2; + /** + * This funtion is assigned to the elements as element.getContext(). + * @this {HTMLElement} + * @return {CanvasRenderingContext2D_} + */ + function getContext() { + return this.context_ || + (this.context_ = new CanvasRenderingContext2D_(this)); + } + + var slice = Array.prototype.slice; + + /** + * Binds a function to an object. The returned function will always use the + * passed in {@code obj} as {@code this}. + * + * Example: + * + * g = bind(f, obj, a, b) + * g(c, d) // will do f.call(obj, a, b, c, d) + * + * @param {Function} f The function to bind the object to + * @param {Object} obj The object that should act as this when the function + * is called + * @param {*} var_args Rest arguments that will be used as the initial + * arguments when the function is called + * @return {Function} A new function that has bound this + */ + function bind(f, obj, var_args) { + var a = slice.call(arguments, 2); + return function() { + return f.apply(obj, a.concat(slice.call(arguments))); + }; + } + var G_vmlCanvasManager_ = { - init: function (opt_doc) { - var doc = opt_doc || document; + init: function(opt_doc) { if (/MSIE/.test(navigator.userAgent) && !window.opera) { - var self = this; - doc.attachEvent("onreadystatechange", function () { - self.init_(doc); - }); + var doc = opt_doc || document; + // Create a dummy element so that IE will allow canvas elements to be + // recognized. + doc.createElement('canvas'); + doc.attachEvent('onreadystatechange', bind(this.init_, this, doc)); } }, - init_: function (doc) { - if (doc.readyState == "complete") { - // create xmlns - if (!doc.namespaces["g_vml_"]) { - doc.namespaces.add("g_vml_", "urn:schemas-microsoft-com:vml"); - } + init_: function(doc) { + // create xmlns + if (!doc.namespaces['g_vml_']) { + doc.namespaces.add('g_vml_', 'urn:schemas-microsoft-com:vml', + '#default#VML'); + + } + if (!doc.namespaces['g_o_']) { + doc.namespaces.add('g_o_', 'urn:schemas-microsoft-com:office:office', + '#default#VML'); + } - // setup default css + // Setup default CSS. Only add one style sheet per document + if (!doc.styleSheets['ex_canvas_']) { var ss = doc.createStyleSheet(); - ss.cssText = "canvas{display:inline-block;overflow:hidden;" + + ss.owningElement.id = 'ex_canvas_'; + ss.cssText = 'canvas{display:inline-block;overflow:hidden;' + // default size is 300x150 in Gecko and Opera - "text-align:left;width:300px;height:150px}" + - "g_vml_\\:*{behavior:url(#default#VML)}"; - - // 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]); - } - } + 'text-align:left;width:300px;height:150px}' + + 'g_vml_\\:*{behavior:url(#default#VML)}' + + 'g_o_\\:*{behavior:url(#default#VML)}'; + } - }, - 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(); - } + // find all canvas elements + var els = doc.getElementsByTagName('canvas'); + for (var i = 0; i < els.length; i++) { + this.initElement(els[i]); } - el.parentNode.replaceChild(newEl, el); - return newEl; }, /** @@ -112,35 +133,37 @@ if (!window.CanvasRenderingContext2D) { * @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_; + initElement: function(el) { + if (!el.getContext) { + + el.getContext = getContext; + + // Remove fallback content. There is no way to hide text nodes so we + // just remove all childNodes. We could hide all elements and remove + // text nodes but who really cares about the fallback content. + el.innerHTML = ''; + + // do not use inline function because that will leak memory + el.attachEvent('onpropertychange', onPropertyChange); + el.attachEvent('onresize', onResize); + + var attrs = el.attributes; + if (attrs.width && attrs.width.specified) { + // TODO: use runtimeStyle and coordsize + // el.getContext().setWidth_(attrs.width.nodeValue); + el.style.width = attrs.width.nodeValue + 'px'; + } else { + el.width = el.clientWidth; } - 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; + 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_() } - //el.getContext().setCoordsize_() return el; } }; @@ -150,11 +173,11 @@ if (!window.CanvasRenderingContext2D) { switch (e.propertyName) { case 'width': - el.style.width = el.attributes.width.nodeValue + "px"; + el.style.width = el.attributes.width.nodeValue + 'px'; el.getContext().clearRect(); break; case 'height': - el.style.height = el.attributes.height.nodeValue + "px"; + el.style.height = el.attributes.height.nodeValue + 'px'; el.getContext().clearRect(); break; } @@ -214,43 +237,45 @@ if (!window.CanvasRenderingContext2D) { o2.shadowOffsetX = o1.shadowOffsetX; o2.shadowOffsetY = o1.shadowOffsetY; o2.strokeStyle = o1.strokeStyle; + o2.globalAlpha = o1.globalAlpha; o2.arcScaleX_ = o1.arcScaleX_; o2.arcScaleY_ = o1.arcScaleY_; + o2.lineScale_ = o1.lineScale_; } function processStyle(styleString) { var str, alpha = 1; styleString = String(styleString); - if (styleString.substring(0, 3) == "rgb") { - var start = styleString.indexOf("(", 3); - var end = styleString.indexOf(")", start + 1); - var guts = styleString.substring(start + 1, end).split(","); + 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 = "#"; + str = '#'; for (var i = 0; i < 3; i++) { str += dec2hex[Number(guts[i])]; } - if ((guts.length == 4) && (styleString.substr(3, 1) == "a")) { + if (guts.length == 4 && styleString.substr(3, 1) == 'a') { alpha = guts[3]; } } else { str = styleString; } - return [str, alpha]; + return {color: str, alpha: alpha}; } function processLineCap(lineCap) { switch (lineCap) { - case "butt": - return "flat"; - case "round": - return "round"; - case "square": + case 'butt': + return 'flat'; + case 'round': + return 'round'; + case 'square': default: - return "square"; + return 'square'; } } @@ -260,7 +285,7 @@ if (!window.CanvasRenderingContext2D) { * @param {HTMLElement} surfaceElement The element that the 2D context should * be associated with */ - function CanvasRenderingContext2D_(surfaceElement) { + function CanvasRenderingContext2D_(surfaceElement) { this.m_ = createMatrixIdentity(); this.mStack_ = []; @@ -268,12 +293,12 @@ if (!window.CanvasRenderingContext2D) { this.currentPath_ = []; // Canvas context properties - this.strokeStyle = "#000"; - this.fillStyle = "#000"; + this.strokeStyle = '#000'; + this.fillStyle = '#000'; this.lineWidth = 1; - this.lineJoin = "miter"; - this.lineCap = "butt"; + this.lineJoin = 'miter'; + this.lineCap = 'butt'; this.miterLimit = Z * 1; this.globalAlpha = 1; this.canvas = surfaceElement; @@ -288,67 +313,88 @@ if (!window.CanvasRenderingContext2D) { this.element_ = el; this.arcScaleX_ = 1; this.arcScaleY_ = 1; + this.lineScale_ = 1; } var contextPrototype = CanvasRenderingContext2D_.prototype; contextPrototype.clearRect = function() { - this.element_.innerHTML = ""; - this.currentPath_ = []; + this.element_.innerHTML = ''; }; contextPrototype.beginPath = function() { // TODO: Branch current matrix so that save/restore has no effect // as per safari docs. - this.currentPath_ = []; }; contextPrototype.moveTo = function(aX, aY) { - this.currentPath_.push({type: "moveTo", x: aX, y: aY}); - this.currentX_ = aX; - this.currentY_ = aY; + var p = this.getCoords_(aX, aY); + this.currentPath_.push({type: 'moveTo', x: p.x, y: p.y}); + this.currentX_ = p.x; + this.currentY_ = p.y; }; contextPrototype.lineTo = function(aX, aY) { - this.currentPath_.push({type: "lineTo", x: aX, y: aY}); - this.currentX_ = aX; - this.currentY_ = aY; + var p = this.getCoords_(aX, aY); + this.currentPath_.push({type: 'lineTo', x: p.x, y: p.y}); + + this.currentX_ = p.x; + this.currentY_ = p.y; }; contextPrototype.bezierCurveTo = function(aCP1x, aCP1y, aCP2x, aCP2y, aX, aY) { - this.currentPath_.push({type: "bezierCurveTo", - cp1x: aCP1x, - cp1y: aCP1y, - cp2x: aCP2x, - cp2y: aCP2y, - x: aX, - y: aY}); - this.currentX_ = aX; - this.currentY_ = aY; + var p = this.getCoords_(aX, aY); + var cp1 = this.getCoords_(aCP1x, aCP1y); + var cp2 = this.getCoords_(aCP2x, aCP2y); + bezierCurveTo(this, cp1, cp2, p); }; + // Helper function that takes the already fixed cordinates. + function bezierCurveTo(self, cp1, cp2, p) { + self.currentPath_.push({ + type: 'bezierCurveTo', + cp1x: cp1.x, + cp1y: cp1.y, + cp2x: cp2.x, + cp2y: cp2.y, + x: p.x, + y: p.y + }); + self.currentX_ = p.x; + self.currentY_ = p.y; + } + contextPrototype.quadraticCurveTo = function(aCPx, aCPy, aX, aY) { // the following is lifted almost directly from // http://developer.mozilla.org/en/docs/Canvas_tutorial:Drawing_shapes - var 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); + + var cp = this.getCoords_(aCPx, aCPy); + var p = this.getCoords_(aX, aY); + + var cp1 = { + x: this.currentX_ + 2.0 / 3.0 * (cp.x - this.currentX_), + y: this.currentY_ + 2.0 / 3.0 * (cp.y - this.currentY_) + }; + var cp2 = { + x: cp1.x + (p.x - this.currentX_) / 3.0, + y: cp1.y + (p.y - this.currentY_) / 3.0 + }; + + bezierCurveTo(this, cp1, cp2, p); }; contextPrototype.arc = function(aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise) { aRadius *= Z; - var arcType = aClockwise ? "at" : "wa"; + var arcType = aClockwise ? 'at' : 'wa'; - var xStart = aX + (mc(aStartAngle) * aRadius) - Z2; - var yStart = aY + (ms(aStartAngle) * aRadius) - Z2; + 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; + 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) { @@ -356,14 +402,18 @@ if (!window.CanvasRenderingContext2D) { // that can be represented in binary } + var p = this.getCoords_(aX, aY); + var pStart = this.getCoords_(xStart, yStart); + var pEnd = this.getCoords_(xEnd, yEnd); + this.currentPath_.push({type: arcType, - x: aX, - y: aY, + x: p.x, + y: p.y, radius: aRadius, - xStart: xStart, - yStart: yStart, - xEnd: xEnd, - yEnd: yEnd}); + xStart: pStart.x, + yStart: pStart.y, + xEnd: pEnd.x, + yEnd: pEnd.y}); }; @@ -376,44 +426,55 @@ if (!window.CanvasRenderingContext2D) { }; contextPrototype.strokeRect = function(aX, aY, aWidth, aHeight) { - // Will destroy any existing path (same as FF behaviour) + var oldPath = this.currentPath_; this.beginPath(); + this.moveTo(aX, aY); this.lineTo(aX + aWidth, aY); this.lineTo(aX + aWidth, aY + aHeight); this.lineTo(aX, aY + aHeight); this.closePath(); this.stroke(); + + this.currentPath_ = oldPath; }; contextPrototype.fillRect = function(aX, aY, aWidth, aHeight) { - // Will destroy any existing path (same as FF behaviour) + var oldPath = this.currentPath_; this.beginPath(); + this.moveTo(aX, aY); this.lineTo(aX + aWidth, aY); this.lineTo(aX + aWidth, aY + aHeight); this.lineTo(aX, aY + aHeight); this.closePath(); this.fill(); + + this.currentPath_ = oldPath; }; contextPrototype.createLinearGradient = function(aX0, aY0, aX1, aY1) { - var gradient = new CanvasGradient_("gradient"); + var gradient = new CanvasGradient_('gradient'); + gradient.x0_ = aX0; + gradient.y0_ = aY0; + gradient.x1_ = aX1; + gradient.y1_ = aY1; return gradient; }; - contextPrototype.createRadialGradient = function(aX0, aY0, - aR0, aX1, - aY1, aR1) { - var gradient = new CanvasGradient_("gradientradial"); - gradient.radius1_ = aR0; - gradient.radius2_ = aR1; - gradient.focus_.x = aX0; - gradient.focus_.y = aY0; + contextPrototype.createRadialGradient = function(aX0, aY0, aR0, + aX1, aY1, aR1) { + var gradient = new CanvasGradient_('gradientradial'); + gradient.x0_ = aX0; + gradient.y0_ = aY0; + gradient.r0_ = aR0; + gradient.x1_ = aX1; + gradient.y1_ = aY1; + gradient.r1_ = aR1; return gradient; }; - contextPrototype.drawImage = function (image, var_args) { + 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 @@ -454,7 +515,7 @@ if (!window.CanvasRenderingContext2D) { dw = arguments[7]; dh = arguments[8]; } else { - throw "Invalid number of arguments"; + throw Error('Invalid number of arguments'); } var d = this.getCoords_(dx, dy); @@ -471,7 +532,7 @@ if (!window.CanvasRenderingContext2D) { vmlStr.push(' ' , '', ''); - this.element_.insertAdjacentHTML("BeforeEnd", - vmlStr.join("")); + 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 color = a.color; + var opacity = a.alpha * this.globalAlpha; var W = 10; var H = 10; lineStr.push(' max.x) { - max.x = c.x; + if (max.x == null || p.x > max.x) { + max.x = p.x; } - if (min.y == null || c.y < min.y) { - min.y = c.y; + if (min.y == null || p.y < min.y) { + min.y = p.y; } - if (max.y == null || c.y > max.y) { - max.y = c.y; + if (max.y == null || p.y > max.y) { + max.y = p.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; + if (!aFill) { + var lineWidth = this.lineScale_ * this.lineWidth; - focus.x = mr((this.fillStyle.focus_.x / width) * 100 + 50) + "%"; - focus.y = mr((this.fillStyle.focus_.y / height) * 100 + 50) + "%"; - - var colors = []; + // VML cannot correctly render a line if the width is less than 1px. + // In that case, we dilute the color to make the line look thinner. + if (lineWidth < 1) { + opacity *= lineWidth; + } - // inside radius (%) - if (this.fillStyle.type_ == "gradientradial") { - var inside = (this.fillStyle.radius1_ / dimension * 100); + lineStr.push( + '' + ); + } else if (typeof this.fillStyle == 'object') { + var fillStyle = this.fillStyle; + var angle = 0; + var focus = {x: 0, y: 0}; + + // additional offset + var shift = 0; + // scale factor for offset + var expansion = 1; + + if (fillStyle.type_ == 'gradient') { + var x0 = fillStyle.x0_ / this.arcScaleX_; + var y0 = fillStyle.y0_ / this.arcScaleY_; + var x1 = fillStyle.x1_ / this.arcScaleX_; + var y1 = fillStyle.y1_ / this.arcScaleY_; + var p0 = this.getCoords_(x0, y0); + var p1 = this.getCoords_(x1, y1); + var dx = p1.x - p0.x; + var dy = p1.y - p0.y; + angle = Math.atan2(dx, dy) * 180 / Math.PI; + + // The angle should be a non-negative number. + if (angle < 0) { + angle += 360; + } - // percentage that outside radius exceeds inside radius - var expansion = (this.fillStyle.radius2_ / dimension * 100) - inside; + // Very small angles produce an unexpected result because they are + // converted to a scientific notation string. + if (angle < 1e-6) { + angle = 0; + } } else { - var inside = 0; - var expansion = 100; + var p0 = this.getCoords_(fillStyle.x0_, fillStyle.y0_); + var width = max.x - min.x; + var height = max.y - min.y; + focus = { + x: (p0.x - min.x) / width, + y: (p0.y - min.y) / height + }; + + width /= this.arcScaleX_ * Z; + height /= this.arcScaleY_ * Z; + var dimension = m.max(width, height); + shift = 2 * fillStyle.r0_ / dimension; + expansion = 2 * fillStyle.r1_ / dimension - shift; } - 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) { + // We need to sort the color stops in ascending order by offset, + // otherwise IE won't interpret it correctly. + var stops = fillStyle.colors_; + stops.sort(function(cs1, cs2) { return cs1.offset - cs2.offset; }); - for (var i = 0; i < this.fillStyle.colors_.length; i++) { - var fs = this.fillStyle.colors_[i]; - - colors.push( (fs.offset * expansion) + inside, "% ", fs.color, ","); + var length = stops.length; + var color1 = stops[0].color; + var color2 = stops[length - 1].color; + var opacity1 = stops[0].alpha * this.globalAlpha; + var opacity2 = stops[length - 1].alpha * this.globalAlpha; - 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; - } + var colors = []; + for (var i = 0; i < length; i++) { + var stop = stops[i]; + colors.push(stop.offset * expansion + shift + ' ' + stop.color); } - colors.pop(); - - lineStr.push(''); - } else if (aFill) { - lineStr.push(''); + + // When colors attribute is used, the meanings of opacity and o:opacity2 + // are reversed. + lineStr.push(''); } else { - lineStr.push( - '' - ); + lineStr.push(''); } - lineStr.push(""); + lineStr.push(''); - this.element_.insertAdjacentHTML("beforeEnd", lineStr.join("")); - - //this.currentPath_ = []; + this.element_.insertAdjacentHTML('beforeEnd', lineStr.join('')); }; contextPrototype.fill = function() { this.stroke(true); - }; + } contextPrototype.closePath = function() { - this.currentPath_.push({type: "close"}); + this.currentPath_.push({type: 'close'}); }; /** * @private */ contextPrototype.getCoords_ = function(aX, aY) { + var m = this.m_; 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 + x: Z * (aX * m[0][0] + aY * m[1][0] + m[2][0]) - Z2, + y: Z * (aX * m[0][1] + aY * m[1][1] + m[2][1]) - Z2 } }; @@ -742,7 +829,14 @@ if (!window.CanvasRenderingContext2D) { [0, 0, 1] ]; - this.m_ = matrixMultiply(m1, this.m_); + var m = this.m_ = matrixMultiply(m1, this.m_); + + // Get the line scale. + // Determinant of this.m_ means how much the area is enlarged by the + // transformation. So its square root can be used as a scale factor + // for width. + var det = m[0][0] * m[1][1] - m[0][1] * m[1][0]; + this.lineScale_ = sqrt(abs(det)); }; /******** STUBS ********/ @@ -761,15 +855,20 @@ if (!window.CanvasRenderingContext2D) { // Gradient / Pattern Stubs function CanvasGradient_(aType) { this.type_ = aType; - this.radius1_ = 0; - this.radius2_ = 0; + this.x0_ = 0; + this.y0_ = 0; + this.r0_ = 0; + this.x1_ = 0; + this.y1_ = 0; + this.r1_ = 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}); + this.colors_.push({offset: aOffset, + color: aColor.color, + alpha: aColor.alpha}); }; function CanvasPattern_() {} diff --git a/excanvas.min.js b/excanvas.min.js new file mode 100644 index 0000000..832bbd1 --- /dev/null +++ b/excanvas.min.js @@ -0,0 +1 @@ +if(!document.createElement("canvas").getContext){(function(){var R=Math;var S=R.round;var O=R.sin;var a=R.cos;var J=R.abs;var Y=R.sqrt;var A=10;var K=A/2;function G(){return this.context_||(this.context_=new M(this))}var Q=Array.prototype.slice;function b(c,d,e){var Z=Q.call(arguments,2);return function(){return c.apply(d,Z.concat(Q.call(arguments)))}}var H={init:function(Z){if(/MSIE/.test(navigator.userAgent)&&!window.opera){var c=Z||document;c.createElement("canvas");c.attachEvent("onreadystatechange",b(this.init_,this,c))}},init_:function(e){if(!e.namespaces.g_vml_){e.namespaces.add("g_vml_","urn:schemas-microsoft-com:vml","#default#VML")}if(!e.namespaces.g_o_){e.namespaces.add("g_o_","urn:schemas-microsoft-com:office:office","#default#VML")}if(!e.styleSheets.ex_canvas_){var d=e.createStyleSheet();d.owningElement.id="ex_canvas_";d.cssText="canvas{display:inline-block;overflow:hidden;text-align:left;width:300px;height:150px}g_vml_\\:*{behavior:url(#default#VML)}g_o_\\:*{behavior:url(#default#VML)}"}var c=e.getElementsByTagName("canvas");for(var Z=0;Z','","");this.element_.insertAdjacentHTML("BeforeEnd",z.join(""))};I.stroke=function(AE){var j=[];var k=false;var AP=C(AE?this.fillStyle:this.strokeStyle);var AA=AP.color;var AK=AP.alpha*this.globalAlpha;var f=10;var m=10;j.push("w.x){w.x=AI.x}if(AO.y==null||AI.yw.y){w.y=AI.y}}}j.push(' ">');if(!AE){var v=this.lineScale_*this.lineWidth;if(v<1){AK*=v}j.push("')}else{if(typeof this.fillStyle=="object"){var n=this.fillStyle;var t=0;var AH={x:0,y:0};var AB=0;var r=1;if(n.type_=="gradient"){var q=n.x0_/this.arcScaleX_;var d=n.y0_/this.arcScaleY_;var o=n.x1_/this.arcScaleX_;var AQ=n.y1_/this.arcScaleY_;var AM=this.getCoords_(q,d);var AL=this.getCoords_(o,AQ);var h=AL.x-AM.x;var g=AL.y-AM.y;t=Math.atan2(h,g)*180/Math.PI;if(t<0){t+=360}if(t<0.000001){t=0}}else{var AM=this.getCoords_(n.x0_,n.y0_);var Z=w.x-AO.x;var e=w.y-AO.y;AH={x:(AM.x-AO.x)/Z,y:(AM.y-AO.y)/e};Z/=this.arcScaleX_*A;e/=this.arcScaleY_*A;var AG=R.max(Z,e);AB=2*n.r0_/AG;r=2*n.r1_/AG-AB}var z=n.colors_;z.sort(function(i,c){return i.offset-c.offset});var u=z.length;var y=z[0].color;var x=z[u-1].color;var AD=z[0].alpha*this.globalAlpha;var AC=z[u-1].alpha*this.globalAlpha;var AF=[];for(var AJ=0;AJ')}else{j.push('')}}j.push("");this.element_.insertAdjacentHTML("beforeEnd",j.join(""))};I.fill=function(){this.stroke(true)};I.closePath=function(){this.currentPath_.push({type:"close"})};I.getCoords_=function(d,c){var Z=this.m_;return{x:A*(d*Z[0][0]+c*Z[1][0]+Z[2][0])-K,y:A*(d*Z[0][1]+c*Z[1][1]+Z[2][1])-K}};I.save=function(){var Z={};T(this,Z);this.aStack_.push(Z);this.mStack_.push(this.m_);this.m_=D(N(),this.m_)};I.restore=function(){T(this.aStack_.pop(),this);this.m_=this.mStack_.pop()};I.translate=function(d,c){var Z=[[1,0,0],[0,1,0],[d,c,1]];this.m_=D(Z,this.m_)};I.rotate=function(d){var f=a(d);var e=O(d);var Z=[[f,e,0],[-e,f,0],[0,0,1]];this.m_=D(Z,this.m_)};I.scale=function(f,e){this.arcScaleX_*=f;this.arcScaleY_*=e;var c=[[f,0,0],[0,e,0],[0,0,1]];var Z=this.m_=D(c,this.m_);var d=Z[0][0]*Z[1][1]-Z[0][1]*Z[1][0];this.lineScale_=Y(J(d))};I.clip=function(){};I.arcTo=function(){};I.createPattern=function(){return new F};function W(Z){this.type_=Z;this.x0_=0;this.y0_=0;this.r0_=0;this.x1_=0;this.y1_=0;this.r1_=0;this.colors_=[]}W.prototype.addColorStop=function(c,Z){Z=C(Z);this.colors_.push({offset:c,color:Z.color,alpha:Z.alpha})};function F(){}G_vmlCanvasManager=H;CanvasRenderingContext2D=M;CanvasGradient=W;CanvasPattern=F})()}; \ No newline at end of file diff --git a/excanvas.pack.js b/excanvas.pack.js deleted file mode 100644 index 71d6fbd..0000000 --- a/excanvas.pack.js +++ /dev/null @@ -1 +0,0 @@ -if(!window.CanvasRenderingContext2D){(function(){var m=Math;var mr=m.round;var ms=m.sin;var mc=m.cos;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"){if(!doc.namespaces["g_vml_"]){doc.namespaces.add("g_vml_","urn:schemas-microsoft-com:vml")}var ss=doc.createStyleSheet();ss.cssText="canvas{display:inline-block;overflow:hidden;"+"text-align:left;width:300px;height:150px}"+"g_vml_\\:*{behavior:url(#default#VML)}";var els=doc.getElementsByTagName("canvas");for(var i=0;i"){var tagName="/"+el.tagName;var ns;while((ns=el.nextSibling)&&ns.tagName!=tagName){ns.removeNode()}if(ns){ns.removeNode()}}el.parentNode.replaceChild(newEl,el);return newEl},initElement:function(el){el=this.fixElement_(el);el.getContext=function(){if(this.context_){return this.context_}return this.context_=new CanvasRenderingContext2D_(this)};el.attachEvent('onpropertychange',onPropertyChange);el.attachEvent('onresize',onResize);var attrs=el.attributes;if(attrs.width&&attrs.width.specified){el.style.width=attrs.width.nodeValue+"px"}else{el.width=el.clientWidth}if(attrs.height&&attrs.height.specified){el.style.height=attrs.height.nodeValue+"px"}else{el.height=el.clientHeight}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();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"}}function CanvasRenderingContext2D_(surfaceElement){this.m_=createMatrixIdentity();this.mStack_=[];this.aStack_=[];this.currentPath_=[];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(){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){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;if(xStart==xEnd&&!aClockwise){xStart+=0.125}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){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){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;var oldRuntimeWidth=image.runtimeStyle.width;var oldRuntimeHeight=image.runtimeStyle.height;image.runtimeStyle.width='auto';image.runtimeStyle.height='auto';var w=image.width;var h=image.height;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;vmlStr.push(' ','','');this.element_.insertAdjacentHTML("BeforeEnd",vmlStr.join(""))};contextPrototype.stroke=function(aFill){var lineStr=[];var lineOpen=false;var a=processStyle(aFill?this.fillStyle:this.strokeStyle);var color=a[0];var opacity=a[1]*this.globalAlpha;var W=10;var H=10;lineStr.push('max.x){max.x=c.x}if(min.y==null||c.ymax.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=[];if(this.fillStyle.type_=="gradientradial"){var inside=(this.fillStyle.radius1_/dimension*100);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};this.fillStyle.colors_.sort(function(cs1,cs2){return cs1.offset-cs2.offset});for(var i=0;iinsidecolor.offset||insidecolor.offset==null){insidecolor.offset=fs.offset;insidecolor.color=fs.color}if(fs.offset')}else if(aFill){lineStr.push('')}else{lineStr.push('')}lineStr.push("");this.element_.insertAdjacentHTML("beforeEnd",lineStr.join(""))};contextPrototype.fill=function(){this.stroke(true)};contextPrototype.closePath=function(){this.currentPath_.push({type:"close"})};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_)};contextPrototype.clip=function(){};contextPrototype.arcTo=function(){};contextPrototype.createPattern=function(){return new CanvasPattern_};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_(){}G_vmlCanvasManager=G_vmlCanvasManager_;CanvasRenderingContext2D=CanvasRenderingContext2D_;CanvasGradient=CanvasGradient_;CanvasPattern=CanvasPattern_})()} diff --git a/jquery.flot.js b/jquery.flot.js index b1b298e..27358ba 100644 --- a/jquery.flot.js +++ b/jquery.flot.js @@ -13,8 +13,8 @@ var series = [], options = { - // the color theme used for graphs - colors: ["#edc240", "#afd8f8", "#cb4b4b", "#4da74d", "#9440ed"], + // the color theme used for graphs + colors: ["#edc240", "#afd8f8", "#cb4b4b", "#4da74d", "#9440ed"], legend: { show: true, noColumns: 1, // number of colums in legend table @@ -60,10 +60,12 @@ fillColor: "#ffffff" }, lines: { - show: false, + // we don't put in show: false so we can see + // whether lines were actively disabled lineWidth: 2, // in pixels fill: false, - fillColor: null + fillColor: null, + steps: false }, bars: { show: false, @@ -71,14 +73,17 @@ barWidth: 1, // in units of the x axis fill: true, fillColor: null, - align: "left" // or "center" + align: "left", // or "center" + horizontal: false // when horizontal, left is now top }, + threshold: null, // or { below: number, color: color spec} grid: { color: "#545454", // primary color used for outline and labels backgroundColor: null, // null for transparent, else color tickColor: "#dddddd", // color used for the ticks labelMargin: 5, // in pixels - borderWidth: 2, + borderWidth: 2, // in pixels + borderColor: null, // set if different from the grid color markings: null, // array of ranges or fn: axes -> array of ranges markingsColor: "#f4f4f4", markingsLineWidth: 2, @@ -92,13 +97,17 @@ mode: null, // one of null, "x", "y" or "xy" color: "#e8cfac" }, - shadowSize: 4 + crosshair: { + mode: null, // one of null, "x", "y" or "xy", + color: "#aa0000" + }, + shadowSize: 3 }, canvas = null, // the canvas for the plot itself overlay = null, // canvas for interactive stuff on top of plot eventHolder = null, // jQuery object that events should be bound to ctx = null, octx = null, - target = target_, + target = $(target_), axes = { xaxis: {}, yaxis: {}, x2axis: {}, y2axis: {} }, plotOffset = { left: 0, right: 0, top: 0, bottom: 0}, canvasWidth = 0, canvasHeight = 0, @@ -115,6 +124,8 @@ this.getPlotOffset = function() { return plotOffset; }; this.getData = function() { return series; }; this.getAxes = function() { return axes; }; + this.setCrosshair = setCrosshair; + this.clearCrosshair = function () { setCrosshair(null); }; this.highlight = highlight; this.unhighlight = unhighlight; @@ -153,7 +164,8 @@ function parseOptions(o) { $.extend(true, options, o); - + if (options.grid.borderColor == null) + options.grid.borderColor = options.grid.color // backwards compatibility, to be removed in future if (options.xaxis.noTicks && options.xaxis.ticks == null) options.xaxis.ticks = options.xaxis.noTicks; @@ -232,96 +244,249 @@ s.lines = $.extend(true, {}, options.lines, s.lines); s.points = $.extend(true, {}, options.points, s.points); s.bars = $.extend(true, {}, options.bars, s.bars); + + // turn on lines automatically in case nothing is set + if (s.lines.show == null && !s.bars.show && !s.points.show) + s.lines.show = true; if (s.shadowSize == null) s.shadowSize = options.shadowSize; - if (s.xaxis && s.xaxis == 2) - s.xaxis = axes.x2axis; - else + + if (!s.xaxis) s.xaxis = axes.xaxis; - if (s.yaxis && s.yaxis == 2) - s.yaxis = axes.y2axis; - else + + if (s.xaxis == 1) + s.xaxis = axes.xaxis; + else if (s.xaxis == 2) + s.xaxis = axes.x2axis; + + if (!s.yaxis) + s.yaxis = axes.yaxis; + + if (s.yaxis == 1) s.yaxis = axes.yaxis; + else if (s.yaxis == 2) + s.yaxis = axes.y2axis; + + if (!s.threshold) + s.threshold = options.threshold; + s.subseries = null; } } function processData() { var topSentry = Number.POSITIVE_INFINITY, bottomSentry = Number.NEGATIVE_INFINITY, - axis; + axis, i, j, k, m, s; for (axis in axes) { axes[axis].datamin = topSentry; axes[axis].datamax = bottomSentry; + axes[axis].min = options[axis].min; + axes[axis].max = options[axis].max; axes[axis].used = false; } - for (var i = 0; i < series.length; ++i) { - var data = series[i].data, - axisx = series[i].xaxis, - axisy = series[i].yaxis, - mindelta = 0, maxdelta = 0; + for (i = 0; i < series.length; ++i) { + s = series[i]; + s.datapoints = { points: [], incr: 2 }; - // make sure we got room for the bar - if (series[i].bars.show) { - mindelta = series[i].bars.align == "left" ? 0 : -series[i].bars.barWidth/2; - maxdelta = mindelta + series[i].bars.barWidth; + var data = s.data, + points = s.datapoints.points, + axisx = s.xaxis, axisy = s.yaxis, + xmin = topSentry, xmax = bottomSentry, + ymin = topSentry, ymax = bottomSentry, + x, y, p, incr, format = []; + + // determine the increment + if (s.bars.show) { + s.datapoints.incr = 3; + format.push({ d: 0 }); } + + /* + // examine data to find out how to copy + for (j = 0; j < data.length; ++j) { + }*/ + axisx.used = axisy.used = true; - for (var j = 0; j < data.length; ++j) { - if (data[j] == null) - continue; + incr = s.datapoints.incr; + + for (j = k = 0; j < data.length; ++j, k += incr) { + p = data[j]; + x = null; + y = null; + + if (data[j] != null) { + x = p[0]; + y = p[1]; + } - var x = data[j][0], y = data[j][1]; - // convert to number if (x != null && !isNaN(x = +x)) { - if (x + mindelta < axisx.datamin) - axisx.datamin = x + mindelta; - if (x + maxdelta > axisx.datamax) - axisx.datamax = x + maxdelta; + if (x < xmin) + xmin = x; + if (x > xmax) + xmax = x } + else + x = null; if (y != null && !isNaN(y = +y)) { - if (y < axisy.datamin) - axisy.datamin = y; - if (y > axisy.datamax) - axisy.datamax = y; + if (y < ymin) + ymin = y; + if (y > ymax) + ymax = y; } - - if (x == null || y == null || isNaN(x) || isNaN(y)) - data[j] = null; // mark this point as invalid + else + y = null; + + if (x == null || y == null) + x = y = null; // make sure everything is cleared + + for (m = 2; m < incr; ++m) + points[k + m] = p[m] == null ? format[m-2].d : p[m]; + + points[k + 1] = y; + points[k] = x; } - } - for (axis in axes) { - if (axes[axis].datamin == topSentry) - axes[axis].datamin = 0; - if (axes[axis].datamax == bottomSentry) - axes[axis].datamax = 1; + if (s.bars.show) { + // make sure we got room for the bar on the dancing floor + var delta = s.bars.align == "left" ? 0 : -s.bars.barWidth/2; + if(s.bars.horizontal) { + ymin += delta; + ymax += delta + s.bars.barWidth; + } + else { + xmin += delta; + xmax += delta + s.bars.barWidth; + } + } + + axisx.datamin = Math.min(axisx.datamin, xmin); + axisx.datamax = Math.max(axisx.datamax, xmax); + axisy.datamin = Math.min(axisy.datamin, ymin); + axisy.datamax = Math.max(axisy.datamax, ymax); + + + // step charts + if (s.lines.show && s.lines.steps) { + p = []; + // copy, inserting extra points to make steps + for (j = k = 0; j < points.length; j += incr, k += incr) { + x = points[j]; + y = points[j + 1]; + if (j > 0 + && points[j - incr] != null + && x != null + && points[j - incr + 1] != y) { + p[k] = x; + p[k + 1] = points[j - incr + 1]; + k += incr; + } + + p[k] = x; + p[k + 1] = y; + } + s.datapoints.linespoints = p; + } + + // possibly split data points because of threshold + if (s.threshold) { + var orig = $.extend({}, s), thresholded = $.extend({}, s); + orig.datapoints = { points: [], incr: incr }; + thresholded.datapoints = { points: [], incr: incr }; + + thresholded.color = s.threshold.color; + + var below = s.threshold.below, + origpoints = orig.datapoints.points, + threspoints = thresholded.datapoints.points; + + // ordinary points + for (j = 0; j < points.length; j += incr) { + x = points[j]; + y = points[j + 1]; + + if (y < below) + p = threspoints; + else + p = origpoints; + + p.push(x); + p.push(y); + } + + // possibly split lines + if (s.lines.show) { + var lp = s.datapoints.linespoints || points; + + origpoints = []; + threspoints = []; + p = origpoints; + + for (j = 0; j < lp.length; j += incr) { + x = lp[j]; + y = lp[j + 1]; + + var prevp = p; + if (y != null) { + if (y < below) + p = threspoints; + else + p = origpoints; + } + + if (p != prevp && x != null && j > 0 && lp[j - incr] != null) { + // find intersection and add it to both + k = (x - lp[j - incr]) / (y - lp[j - incr + 1]) * (below - y) + x; + prevp.push(k); + prevp.push(below); + p.push(null); // start new segment + p.push(null); + p.push(k); + p.push(below); + } + + p.push(x); + p.push(y); + } + + orig.datapoints.linespoints = origpoints + thresholded.datapoints.linespoints = threspoints; + } + + s.subseries = [orig, thresholded]; + } } } function constructCanvas() { + function makeCanvas(width, height) { + var c = document.createElement('canvas'); + c.width = width; + c.height = height; + if ($.browser.msie) // excanvas hack + c = window.G_vmlCanvasManager.initElement(c); + return c; + } + canvasWidth = target.width(); canvasHeight = target.height(); target.html(""); // clear target - target.css("position", "relative"); // for positioning labels and overlay + if (target.css("position") == 'static') + 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 = $('').appendTo(target).get(0); - if ($.browser.msie) // excanvas hack - canvas = window.G_vmlCanvasManager.initElement(canvas); + canvas = $(makeCanvas(canvasWidth, canvasHeight)).appendTo(target).get(0); ctx = canvas.getContext("2d"); // overlay canvas for interactive features - overlay = $('').appendTo(target).get(0); - if ($.browser.msie) // excanvas hack - overlay = window.G_vmlCanvasManager.initElement(overlay); + overlay = $(makeCanvas(canvasWidth, canvasHeight)).css({ position: 'absolute', left: 0, top: 0 }).appendTo(target).get(0); octx = overlay.getContext("2d"); // we include the canvas in the event holder too, because IE 7 @@ -329,7 +494,8 @@ eventHolder = $([overlay, canvas]); // bind events - if (options.selection.mode != null || options.grid.hoverable) { + if (options.selection.mode != null || options.crosshair.mode != null + || options.grid.hoverable) { // FIXME: temp. work-around until jQuery bug 1871 is fixed eventHolder.each(function () { this.onmousemove = onMouseMove; @@ -339,6 +505,9 @@ eventHolder.mousedown(onMouseDown); } + if (options.crosshair.mode != null) + eventHolder.mouseout(onMouseOut); + if (options.grid.clickable) eventHolder.click(onClick); } @@ -370,19 +539,25 @@ } function setRange(axis, axisOptions) { - var min = axisOptions.min != null ? axisOptions.min : axis.datamin; - var max = axisOptions.max != null ? axisOptions.max : axis.datamax; - + var min = axisOptions.min != null ? +axisOptions.min : axis.datamin, + max = axisOptions.max != null ? +axisOptions.max : axis.datamax; + + // degenerate case + if (min == Number.POSITIVE_INFINITY) + min = 0; + if (max == Number.NEGATIVE_INFINITY) + max = 1; + if (max - min == 0.0) { // degenerate case - var widen; - if (max == 0.0) - widen = 1.0; - else - widen = 0.01; + var widen = max == 0 ? 1 : 0.01; - min -= widen; - max += widen; + if (axisOptions.min == null) + min -= widen; + // alway widen max if we couldn't widen min to ensure we + // don't fall into min == max which doesn't work + if (axisOptions.max == null || axisOptions.min != null) + max += widen; } else { // consider autoscaling @@ -422,44 +597,6 @@ if (axisOptions.mode == "time") { // pretty handling of time - function formatDate(d, fmt, monthNames) { - var leftPad = function(n) { - n = "" + n; - return n.length == 1 ? "0" + n : n; - }; - - var r = []; - var escape = false; - if (monthNames == null) - monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; - for (var i = 0; i < fmt.length; ++i) { - var c = fmt.charAt(i); - - if (escape) { - switch (c) { - case 'h': c = "" + d.getUTCHours(); break; - case 'H': c = leftPad(d.getUTCHours()); break; - case 'M': c = leftPad(d.getUTCMinutes()); break; - case 'S': c = leftPad(d.getUTCSeconds()); break; - case 'd': c = "" + d.getUTCDate(); break; - case 'm': c = "" + (d.getUTCMonth() + 1); break; - case 'y': c = "" + d.getUTCFullYear(); break; - case 'b': c = "" + monthNames[d.getUTCMonth()]; break; - } - r.push(c); - escape = false; - } - else { - if (c == "%") - escape = true; - else - r.push(c); - } - } - return r.join(""); - } - - // map of app. size of time units in milliseconds var timeUnitSize = { "second": 1000, @@ -591,7 +728,7 @@ // first check global format if (axisOptions.timeformat != null) - return formatDate(d, axisOptions.timeformat, axisOptions.monthNames); + return $.plot.formatDate(d, axisOptions.timeformat, axisOptions.monthNames); var t = axis.tickSize[0] * timeUnitSize[axis.tickSize[1]]; var span = axis.max - axis.min; @@ -615,7 +752,7 @@ else fmt = "%y"; - return formatDate(d, fmt, axisOptions.monthNames); + return $.plot.formatDate(d, fmt, axisOptions.monthNames); }; } else { @@ -793,22 +930,24 @@ // get the most space needed around the grid for things // that may stick out - var maxOutset = options.grid.borderWidth / 2; + var maxOutset = options.grid.borderWidth; for (i = 0; i < series.length; ++i) maxOutset = Math.max(maxOutset, 2 * (series[i].points.radius + series[i].points.lineWidth/2)); plotOffset.left = plotOffset.right = plotOffset.top = plotOffset.bottom = maxOutset; + var margin = options.grid.labelMargin + options.grid.borderWidth; + if (axes.xaxis.labelHeight > 0) - plotOffset.bottom = Math.max(maxOutset, axes.xaxis.labelHeight + options.grid.labelMargin); + plotOffset.bottom = Math.max(maxOutset, axes.xaxis.labelHeight + margin); if (axes.yaxis.labelWidth > 0) - plotOffset.left = Math.max(maxOutset, axes.yaxis.labelWidth + options.grid.labelMargin); + plotOffset.left = Math.max(maxOutset, axes.yaxis.labelWidth + margin); if (axes.x2axis.labelHeight > 0) - plotOffset.top = Math.max(maxOutset, axes.x2axis.labelHeight + options.grid.labelMargin); + plotOffset.top = Math.max(maxOutset, axes.x2axis.labelHeight + margin); if (axes.y2axis.labelWidth > 0) - plotOffset.right = Math.max(maxOutset, axes.y2axis.labelWidth + options.grid.labelMargin); + plotOffset.right = Math.max(maxOutset, axes.y2axis.labelWidth + margin); plotWidth = canvasWidth - plotOffset.left - plotOffset.right; plotHeight = canvasHeight - plotOffset.bottom - plotOffset.top; @@ -822,8 +961,13 @@ function draw() { drawGrid(); - for (var i = 0; i < series.length; i++) { - drawSeries(series[i]); + for (var i = 0; i < series.length; ++i) { + var s = series[i]; + if (s.subseries) + for (var j = 0; j < s.subseries.length; ++j) + drawSeries(s.subseries[j]); + else + drawSeries(s); } } @@ -865,13 +1009,13 @@ // draw background, if any if (options.grid.backgroundColor) { - ctx.fillStyle = options.grid.backgroundColor; + ctx.fillStyle = getColorOrGradient(options.grid.backgroundColor, plotHeight, 0, "rgba(255, 255, 255, 0)"); ctx.fillRect(0, 0, plotWidth, plotHeight); } // draw markings - if (options.grid.markings) { - var markings = options.grid.markings; + var markings = options.grid.markings; + if (markings) { if ($.isFunction(markings)) // xmin etc. are backwards-compatible, to be removed in future markings = markings({ xmin: axes.xaxis.min, xmax: axes.xaxis.max, ymin: axes.yaxis.min, ymax: axes.yaxis.max, xaxis: axes.xaxis, yaxis: axes.yaxis, x2axis: axes.x2axis, y2axis: axes.y2axis }); @@ -913,18 +1057,20 @@ if (xrange.from == xrange.to || yrange.from == yrange.to) { // draw line ctx.strokeStyle = m.color || options.grid.markingsColor; + ctx.beginPath(); ctx.lineWidth = m.lineWidth || options.grid.markingsLineWidth; - ctx.moveTo(Math.floor(xrange.from), Math.floor(yrange.from)); - ctx.lineTo(Math.floor(xrange.to), Math.floor(yrange.to)); + //ctx.moveTo(Math.floor(xrange.from), yrange.from); + //ctx.lineTo(Math.floor(xrange.to), yrange.to); + ctx.moveTo(xrange.from, yrange.from); + ctx.lineTo(xrange.to, yrange.to); ctx.stroke(); } else { // fill area ctx.fillStyle = m.color || options.grid.markingsColor; - ctx.fillRect(Math.floor(xrange.from), - Math.floor(yrange.to), - Math.floor(xrange.to - xrange.from), - Math.floor(yrange.from - yrange.to)); + ctx.fillRect(xrange.from, yrange.to, + xrange.to - xrange.from, + yrange.from - yrange.to); } } } @@ -977,10 +1123,10 @@ if (options.grid.borderWidth) { // draw border - ctx.lineWidth = options.grid.borderWidth; - ctx.strokeStyle = options.grid.color; - ctx.lineJoin = "round"; - ctx.strokeRect(0, 0, plotWidth, plotHeight); + var bw = options.grid.borderWidth; + ctx.lineWidth = bw; + ctx.strokeStyle = options.grid.borderColor; + ctx.strokeRect(-bw/2, -bw/2, plotWidth + bw, plotHeight + bw); } ctx.restore(); @@ -989,41 +1135,43 @@ function insertLabels() { target.find(".tickLabels").remove(); - var html = '
'; + var html = ['
']; function addLabels(axis, labelGenerator) { for (var i = 0; i < axis.ticks.length; ++i) { var tick = axis.ticks[i]; if (!tick.label || tick.v < axis.min || tick.v > axis.max) continue; - html += labelGenerator(tick, axis); + html.push(labelGenerator(tick, axis)); } } + + var margin = options.grid.labelMargin + options.grid.borderWidth; addLabels(axes.xaxis, function (tick, axis) { - return '
' + tick.label + "
"; + return '
' + tick.label + "
"; }); addLabels(axes.yaxis, function (tick, axis) { - return '
' + tick.label + "
"; + return '
' + tick.label + "
"; }); addLabels(axes.x2axis, function (tick, axis) { - return '
' + tick.label + "
"; + return '
' + tick.label + "
"; }); addLabels(axes.y2axis, function (tick, axis) { - return '
' + tick.label + "
"; + return '
' + tick.label + "
"; }); - html += '
'; + html.push('
'); - target.append(html); + target.append(html.join("")); } function drawSeries(series) { - if (series.lines.show || (!series.bars.show && !series.points.show)) + if (series.lines.show) drawSeriesLines(series); if (series.bars.show) drawSeriesBars(series); @@ -1032,19 +1180,18 @@ } function drawSeriesLines(series) { - function plotLine(data, offset, axisx, axisy) { - var prev, cur = null, drawx = null, drawy = null; + function plotLine(datapoints, xoffset, yoffset, axisx, axisy) { + var points = datapoints.linespoints || datapoints.points, + incr = datapoints.incr, + prevx = null, prevy = null; ctx.beginPath(); - for (var i = 0; i < data.length; ++i) { - prev = cur; - cur = data[i]; - - if (prev == null || cur == null) - continue; + for (var i = incr; i < points.length; i += incr) { + var x1 = points[i - incr], y1 = points[i - incr + 1], + x2 = points[i], y2 = points[i + 1]; - var x1 = prev[0], y1 = prev[1], - x2 = cur[0], y2 = cur[1]; + if (x1 == null || x2 == null) + continue; // clip with ymin if (y1 <= y2 && y1 < axisy.min) { @@ -1103,29 +1250,27 @@ x2 = axisx.max; } - if (drawx != axisx.p2c(x1) || drawy != axisy.p2c(y1) + offset) - ctx.moveTo(axisx.p2c(x1), axisy.p2c(y1) + offset); + if (x1 != prevx || y1 != prevy) + ctx.moveTo(axisx.p2c(x1) + xoffset, axisy.p2c(y1) + yoffset); - drawx = axisx.p2c(x2); - drawy = axisy.p2c(y2) + offset; - ctx.lineTo(drawx, drawy); + prevx = x2; + prevy = y2; + ctx.lineTo(axisx.p2c(x2) + xoffset, axisy.p2c(y2) + yoffset); } ctx.stroke(); } - function plotLineArea(data, axisx, axisy) { - var prev, cur = null; - - var bottom = Math.min(Math.max(0, axisy.min), axisy.max); - var top, lastX = 0; - - var areaOpen = false; + function plotLineArea(datapoints, axisx, axisy) { + var points = datapoints.linespoints || datapoints.points, + incr = datapoints.incr, + bottom = Math.min(Math.max(0, axisy.min), axisy.max), + top, lastX = 0, areaOpen = false; - for (var i = 0; i < data.length; ++i) { - prev = cur; - cur = data[i]; - - if (areaOpen && prev != null && cur == null) { + for (var i = incr; i < points.length; i += incr) { + var x1 = points[i - incr], y1 = points[i - incr + 1], + x2 = points[i], y2 = points[i + 1]; + + if (areaOpen && x1 != null && x2 == null) { // close area ctx.lineTo(axisx.p2c(lastX), axisy.p2c(bottom)); ctx.fill(); @@ -1133,11 +1278,8 @@ continue; } - if (prev == null || cur == null) + if (x1 == null || x2 == null) continue; - - var x1 = prev[0], y1 = prev[1], - x2 = cur[0], y2 = cur[1]; // clip x values @@ -1180,11 +1322,13 @@ if (y1 >= axisy.max && y2 >= axisy.max) { ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.max)); ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.max)); + lastX = x2; continue; } else if (y1 <= axisy.min && y2 <= axisy.min) { ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.min)); ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.min)); + lastX = x2; continue; } @@ -1239,8 +1383,8 @@ else top = axisy.max; - ctx.lineTo(axisx.p2c(x2old), axisy.p2c(top)); ctx.lineTo(axisx.p2c(x2), axisy.p2c(top)); + ctx.lineTo(axisx.p2c(x2old), axisy.p2c(top)); } lastX = Math.max(x2, x2old); @@ -1256,57 +1400,47 @@ ctx.translate(plotOffset.left, plotOffset.top); ctx.lineJoin = "round"; - var lw = series.lines.lineWidth; - var sw = series.shadowSize; + var lw = series.lines.lineWidth, + 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; + if (lw > 0 && sw > 0) { + // draw shadow as a thick and thin line with transparency + ctx.lineWidth = sw; ctx.strokeStyle = "rgba(0,0,0,0.1)"; - plotLine(series.data, lw/2 + sw/2 + ctx.lineWidth/2, series.xaxis, series.yaxis); - - ctx.lineWidth = sw / 2; - ctx.strokeStyle = "rgba(0,0,0,0.2)"; - plotLine(series.data, lw/2 + ctx.lineWidth/2, series.xaxis, series.yaxis); + var xoffset = 1; + plotLine(series.datapoints, xoffset, Math.sqrt((lw/2 + sw/2)*(lw/2 + sw/2) - xoffset*xoffset), series.xaxis, series.yaxis); + ctx.lineWidth = sw/2; + plotLine(series.datapoints, xoffset, Math.sqrt((lw/2 + sw/4)*(lw/2 + sw/4) - xoffset*xoffset), series.xaxis, series.yaxis); } ctx.lineWidth = lw; ctx.strokeStyle = series.color; - setFillStyle(series.lines, series.color); - if (series.lines.fill) - plotLineArea(series.data, series.xaxis, series.yaxis); - plotLine(series.data, 0, series.xaxis, series.yaxis); + var fillStyle = getFillStyle(series.lines, series.color, 0, plotHeight); + if (fillStyle) { + ctx.fillStyle = fillStyle; + plotLineArea(series.datapoints, series.xaxis, series.yaxis); + } + + if (lw > 0) + plotLine(series.datapoints, 0, 0, series.xaxis, series.yaxis); ctx.restore(); } function drawSeriesPoints(series) { - function plotPoints(data, radius, fill, axisx, axisy) { - for (var i = 0; i < data.length; ++i) { - if (data[i] == null) - continue; - - var x = data[i][0], y = data[i][1]; - if (x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max) + function plotPoints(datapoints, radius, fillStyle, offset, circumference, axisx, axisy) { + var points = datapoints.points, incr = datapoints.incr; + + for (var i = 0; i < points.length; i += incr) { + var x = points[i], y = points[i + 1]; + if (x == null || x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max) continue; ctx.beginPath(); - ctx.arc(axisx.p2c(x), axisy.p2c(y), radius, 0, 2 * Math.PI, true); - if (fill) + ctx.arc(axisx.p2c(x), axisy.p2c(y) + offset, radius, 0, circumference, true); + if (fillStyle) { + ctx.fillStyle = fillStyle; ctx.fill(); - ctx.stroke(); - } - } - - function plotPointShadows(data, offset, radius, axisx, axisy) { - for (var i = 0; i < data.length; ++i) { - if (data[i] == null) - continue; - - var x = data[i][0], y = data[i][1]; - if (x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max) - continue; - ctx.beginPath(); - ctx.arc(axisx.p2c(x), axisy.p2c(y) + offset, radius, 0, Math.PI, false); + } ctx.stroke(); } } @@ -1314,43 +1448,70 @@ ctx.save(); ctx.translate(plotOffset.left, plotOffset.top); - var lw = series.lines.lineWidth; - var sw = series.shadowSize; - if (sw > 0) { + var lw = series.lines.lineWidth, + sw = series.shadowSize, + radius = series.points.radius; + if (lw > 0 && sw > 0) { // draw shadow in two steps - ctx.lineWidth = sw / 2; + var w = sw / 2; + ctx.lineWidth = w; ctx.strokeStyle = "rgba(0,0,0,0.1)"; - plotPointShadows(series.data, sw/2 + ctx.lineWidth/2, - series.points.radius, series.xaxis, series.yaxis); + plotPoints(series.datapoints, radius, null, w + w/2, 2 * Math.PI, + series.xaxis, series.yaxis); - ctx.lineWidth = sw / 2; ctx.strokeStyle = "rgba(0,0,0,0.2)"; - plotPointShadows(series.data, ctx.lineWidth/2, - series.points.radius, series.xaxis, series.yaxis); + plotPoints(series.datapoints, radius, null, w/2, 2 * Math.PI, + series.xaxis, series.yaxis); } - ctx.lineWidth = series.points.lineWidth; + ctx.lineWidth = lw; ctx.strokeStyle = series.color; - setFillStyle(series.points, series.color); - plotPoints(series.data, series.points.radius, series.points.fill, + plotPoints(series.datapoints, radius, + getFillStyle(series.points, series.color), 0, 2 * Math.PI, series.xaxis, series.yaxis); ctx.restore(); } - function drawBar(x, y, barLeft, barRight, offset, fill, axisx, axisy, c) { - var drawLeft = true, drawRight = true, - drawTop = true, drawBottom = false, - left = x + barLeft, right = x + barRight, - bottom = 0, top = y; - - // account for negative bars - if (top < bottom) { - top = 0; - bottom = y; - drawBottom = true; - drawTop = false; + function drawBar(x, y, b, barLeft, barRight, offset, fillStyleCallback, axisx, axisy, c, horizontal) { + var left, right, bottom, top, + drawLeft, drawRight, drawTop, drawBottom, + tmp; + + if (horizontal) { + drawBottom = drawRight = drawTop = true; + drawLeft = false; + left = b; + right = x; + top = y + barLeft; + bottom = y + barRight; + + // account for negative bars + if (right < left) { + tmp = right; + right = left; + left = tmp; + drawLeft = true; + drawRight = false; + } } - + else { + drawLeft = drawRight = drawTop = true; + drawBottom = false; + left = x + barLeft; + right = x + barRight; + bottom = b; + top = y; + + // account for negative bars + if (top < bottom) { + tmp = top; + top = bottom; + bottom = tmp; + drawBottom = true; + drawTop = false; + } + } + // clip if (right < axisx.min || left > axisx.max || top < axisy.min || bottom > axisy.max) @@ -1376,24 +1537,27 @@ drawTop = false; } + left = axisx.p2c(left); + bottom = axisy.p2c(bottom); + right = axisx.p2c(right); + top = axisy.p2c(top); + // fill the bar - if (fill) { + if (fillStyleCallback) { c.beginPath(); - c.moveTo(axisx.p2c(left), axisy.p2c(bottom) + offset); - c.lineTo(axisx.p2c(left), axisy.p2c(top) + offset); - c.lineTo(axisx.p2c(right), axisy.p2c(top) + offset); - c.lineTo(axisx.p2c(right), axisy.p2c(bottom) + offset); + c.moveTo(left, bottom); + c.lineTo(left, top); + c.lineTo(right, top); + c.lineTo(right, bottom); + c.fillStyle = fillStyleCallback(bottom, top); c.fill(); } // draw outline if (drawLeft || drawRight || drawTop || drawBottom) { c.beginPath(); - left = axisx.p2c(left); - bottom = axisy.p2c(bottom); - right = axisx.p2c(right); - top = axisy.p2c(top); - + + // FIXME: inline moveTo is buggy with excanvas c.moveTo(left, bottom + offset); if (drawLeft) c.lineTo(left, top + offset); @@ -1416,55 +1580,40 @@ } function drawSeriesBars(series) { - function plotBars(data, barLeft, barRight, offset, fill, axisx, axisy) { - for (var i = 0; i < data.length; i++) { - if (data[i] == null) + function plotBars(datapoints, barLeft, barRight, offset, fillStyleCallback, axisx, axisy) { + var points = datapoints.points, incr = datapoints.incr; + + for (var i = 0; i < points.length; i += incr) { + if (points[i] == null) continue; - drawBar(data[i][0], data[i][1], barLeft, barRight, offset, fill, axisx, axisy, ctx); + drawBar(points[i], points[i + 1], points[i + 2], barLeft, barRight, offset, fillStyleCallback, axisx, axisy, ctx, series.bars.horizontal); } } ctx.save(); ctx.translate(plotOffset.left, plotOffset.top); - ctx.lineJoin = "round"; - - // FIXME: figure out a way to add shadows - /* - var bw = series.bars.barWidth; - var lw = series.bars.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)"; - 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); - }*/ + // FIXME: figure out a way to add shadows (for instance along the right edge) ctx.lineWidth = series.bars.lineWidth; ctx.strokeStyle = series.color; - setFillStyle(series.bars, series.color); var barLeft = series.bars.align == "left" ? 0 : -series.bars.barWidth/2; - plotBars(series.data, barLeft, barLeft + series.bars.barWidth, 0, series.bars.fill, series.xaxis, series.yaxis); + var fillStyleCallback = series.bars.fill ? function (bottom, top) { return getFillStyle(series.bars, series.color, bottom, top); } : null; + plotBars(series.datapoints, barLeft, barLeft + series.bars.barWidth, 0, fillStyleCallback, series.xaxis, series.yaxis); ctx.restore(); } - function setFillStyle(obj, seriesColor) { - var fill = obj.fill; + function getFillStyle(filloptions, seriesColor, bottom, top) { + var fill = filloptions.fill; if (!fill) - return; + return null; + + if (filloptions.fillColor) + return getColorOrGradient(filloptions.fillColor, bottom, top, seriesColor); - if (obj.fillColor) - ctx.fillStyle = obj.fillColor; - else { - var c = parseColor(seriesColor); - c.a = typeof fill == "number" ? fill : 0.4; - c.normalize(); - ctx.fillStyle = c.toString(); - } + var c = parseColor(seriesColor); + c.a = typeof fill == "number" ? fill : 0.4; + c.normalize(); + return c.toString(); } function insertLegend() { @@ -1473,10 +1622,12 @@ if (!options.legend.show) return; - var fragments = []; - var rowStarted = false; + var fragments = [], rowStarted = false, + lf = options.legend.labelFormatter, s, label; for (i = 0; i < series.length; ++i) { - if (!series[i].label) + s = series[i]; + label = s.label; + if (!label) continue; if (i % options.legend.noColumns == 0) { @@ -1486,12 +1637,11 @@ rowStarted = true; } - var label = series[i].label; - if (options.legend.labelFormatter != null) - label = options.legend.labelFormatter(label); + if (lf) + label = lf(label, s); fragments.push( - '
' + + '
' + '' + label + ''); } if (rowStarted) @@ -1502,18 +1652,21 @@ var table = '' + fragments.join("") + '
'; if (options.legend.container != null) - options.legend.container.html(table); + $(options.legend.container).html(table); else { - var pos = ""; - var p = options.legend.position, m = options.legend.margin; + var pos = "", + p = options.legend.position, + m = options.legend.margin; + if (m[0] == null) + m = [m, m]; if (p.charAt(0) == "n") - pos += 'top:' + (m + plotOffset.top) + 'px;'; + pos += 'top:' + (m[1] + plotOffset.top) + 'px;'; else if (p.charAt(0) == "s") - pos += 'bottom:' + (m + plotOffset.bottom) + 'px;'; + pos += 'bottom:' + (m[1] + plotOffset.bottom) + 'px;'; if (p.charAt(1) == "e") - pos += 'right:' + (m + plotOffset.right) + 'px;'; + pos += 'right:' + (m[0] + plotOffset.right) + 'px;'; else if (p.charAt(1) == "w") - pos += 'left:' + (m + plotOffset.left) + 'px;'; + pos += 'left:' + (m[0] + plotOffset.left) + 'px;'; var legend = $('
' + table.replace('style="', 'style="position:absolute;' + pos +';') + '
').appendTo(target); if (options.legend.backgroundOpacity != 0.0) { // put in the transparent background @@ -1522,7 +1675,7 @@ var c = options.legend.backgroundColor; if (c == null) { var tmp; - if (options.grid.backgroundColor) + if (options.grid.backgroundColor && typeof options.grid.backgroundColor == "string") tmp = options.grid.backgroundColor; else tmp = extractColor(legend); @@ -1530,7 +1683,6 @@ } var div = legend.children(); $('
').prependTo(legend).css('opacity', options.legend.backgroundOpacity); - } } } @@ -1542,77 +1694,87 @@ selection = { first: { x: -1, y: -1}, second: { x: -1, y: -1}, show: false, active: false }, + crosshair = { pos: { x: -1, y: -1 } }, highlights = [], clickIsMouseUp = false, redrawTimeout = null, hoverTimeout = null; // Returns the data item the mouse is over, or null if none is found - function findNearbyItem(mouseX, mouseY) { + function findNearbyItem(mouseX, mouseY, seriesFilter) { var maxDistance = options.grid.mouseActiveRadius, lowestDistance = maxDistance * maxDistance + 1, - item = null, foundPoint = false; + item = null, foundPoint = false, i, j; - function result(i, j) { - return { datapoint: series[i].data[j], - dataIndex: j, - series: series[i], - seriesIndex: i }; - } - for (var i = 0; i < series.length; ++i) { - var data = series[i].data, - axisx = series[i].xaxis, - axisy = series[i].yaxis, + if (!seriesFilter(series[i])) + continue; - // precompute some stuff to make the loop faster - mx = axisx.c2p(mouseX), + var s = series[i], + axisx = s.xaxis, + axisy = s.yaxis, + points = s.datapoints.points, + incr = s.datapoints.incr, + mx = axisx.c2p(mouseX), // precompute some stuff to make the loop faster my = axisy.c2p(mouseY), maxx = maxDistance / axisx.scale, - maxy = maxDistance / axisy.scale, - checkbar = series[i].bars.show, - checkpoint = !(series[i].bars.show && !(series[i].lines.show || series[i].points.show)), - barLeft = series[i].bars.align == "left" ? 0 : -series[i].bars.barWidth/2, - barRight = barLeft + series[i].bars.barWidth; - for (var j = 0; j < data.length; ++j) { - if (data[j] == null) - continue; + maxy = maxDistance / axisy.scale; - var x = data[j][0], y = data[j][1]; - - if (checkbar) { - // For a bar graph, the cursor must be inside the bar - // and no other point can be nearby - if (!foundPoint && mx >= x + barLeft && - mx <= x + barRight && - my >= Math.min(0, y) && my <= Math.max(0, y)) - item = result(i, j); - } - - if (checkpoint) { + if (s.lines.show || s.points.show) { + for (j = 0; j < points.length; j += incr) { + var x = points[j], y = points[j + 1]; + if (x == null) + continue; + // For points and lines, the cursor must be within a // certain distance to the data point - - // check bounding box first - if ((x - mx > maxx || x - mx < -maxx) || - (y - my > maxy || y - my < -maxy)) + if (x - mx > maxx || x - mx < -maxx || + y - my > maxy || y - my < -maxy) continue; // We have to calculate distances in pixels, not in - // data units, because the scale of the axes may be different + // data units, because the scales of the axes may be different var dx = Math.abs(axisx.p2c(x) - mouseX), dy = Math.abs(axisy.p2c(y) - mouseY), - dist = dx * dx + dy * dy; + dist = dx * dx + dy * dy; // no idea in taking sqrt if (dist < lowestDistance) { lowestDistance = dist; - foundPoint = true; - item = result(i, j); + item = [i, j / incr]; } } } + + if (s.bars.show && !item) { // no other point can be nearby + var barLeft = s.bars.align == "left" ? 0 : -s.bars.barWidth/2, + barRight = barLeft + s.bars.barWidth; + + for (j = 0; j < points.length; j += incr) { + var x = points[j], y = points[j + 1], b = points[j + 2]; + if (x == null) + continue; + + // for a bar graph, the cursor must be inside the bar + if (series[i].bars.horizontal ? + (mx <= Math.max(b, x) && mx >= Math.min(b, x) && + my >= y + barLeft && my <= y + barRight) : + (mx >= x + barLeft && mx <= x + barRight && + my >= Math.min(b, y) && my <= Math.max(b, y))) + item = [i, j / incr]; + } + } } - return item; + if (item) { + i = item[0]; + j = item[1]; + + return { datapoint: series[i].data[j], + dataIndex: j, + series: series[i], + seriesIndex: i } + } + + return null; } function onMouseMove(ev) { @@ -1620,19 +1782,32 @@ var e = ev || window.event; 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); + lastMousePos.pageX = e.clientX + (de && de.scrollLeft || b.scrollLeft || 0) - (de.clientLeft || 0); + lastMousePos.pageY = e.clientY + (de && de.scrollTop || b.scrollTop || 0) - (de.clientTop || 0); } else { lastMousePos.pageX = e.pageX; lastMousePos.pageY = e.pageY; } - if (options.grid.hoverable && !hoverTimeout) - hoverTimeout = setTimeout(onHover, 100); + if (options.grid.hoverable) + triggerClickHoverEvent("plothover", lastMousePos, + function (s) { return s["hoverable"] != false; }); + + if (options.crosshair.mode != null) { + if (!selection.active) { + setPositionFromEvent(crosshair.pos, lastMousePos); + triggerRedrawOverlay(); + } + else + crosshair.pos.x = -1; // hide the crosshair while selecting + } + + if (selection.active) { + target.trigger("plotselecting", [ selectionIsSane() ? getSelectionForEvent() : null ]); - if (selection.active) updateSelection(lastMousePos); + } } function onMouseDown(e) { @@ -1659,23 +1834,38 @@ $(document).one("mouseup", onSelectionMouseUp); } + function onMouseOut(ev) { + if (options.crosshair.mode != null && crosshair.pos.x != -1) { + crosshair.pos.x = -1; + triggerRedrawOverlay(); + } + } + function onClick(e) { if (clickIsMouseUp) { clickIsMouseUp = false; return; } - triggerClickHoverEvent("plotclick", e); - } - - function onHover() { - triggerClickHoverEvent("plothover", lastMousePos); - hoverTimeout = null; + triggerClickHoverEvent("plotclick", e, + function (s) { return s["clickable"] != false; }); } + /* + function userPositionInCanvasSpace(pos) { + return { x: parseInt(pos.x != null ? axes.xaxis.p2c(pos.x) : axes.x2axis.p2c(pos.x2)), + y: parseInt(pos.y != null ? axes.yaxis.p2c(pos.y) : axes.y2axis.p2c(pos.y2)) }; + } + + function positionInDivSpace(pos) { + var cpos = userPositionInCanvasSpace(pos); + return { x: cpos.x + plotOffset.left, + y: cpos.y + plotOffset.top }; + }*/ + // trigger click or hover event (they send the same parameters // so we share their code) - function triggerClickHoverEvent(eventname, event) { + function triggerClickHoverEvent(eventname, event, seriesFilter) { var offset = eventHolder.offset(), pos = { pageX: event.pageX, pageY: event.pageY }, canvasX = event.pageX - offset.left - plotOffset.left, @@ -1690,26 +1880,25 @@ if (axes.y2axis.used) pos.y2 = axes.y2axis.c2p(canvasY); - var item = findNearbyItem(canvasX, canvasY); + var item = findNearbyItem(canvasX, canvasY, seriesFilter); if (item) { // fill in mouse pos for any listeners out there item.pageX = parseInt(item.series.xaxis.p2c(item.datapoint[0]) + offset.left + plotOffset.left); item.pageY = parseInt(item.series.yaxis.p2c(item.datapoint[1]) + offset.top + plotOffset.top); - - } if (options.grid.autoHighlight) { + // clear auto-highlights for (var i = 0; i < highlights.length; ++i) { var h = highlights[i]; - if (h.auto && + if (h.auto == eventname && !(item && h.series == item.series && h.point == item.datapoint)) unhighlight(h.series, h.point); } if (item) - highlight(item.series, item.datapoint, true); + highlight(item.series, item.datapoint, eventname); } target.trigger(eventname, [ pos, item ]); @@ -1717,7 +1906,7 @@ function triggerRedrawOverlay() { if (!redrawTimeout) - redrawTimeout = setTimeout(redrawOverlay, 50); + redrawTimeout = setTimeout(redrawOverlay, 30); } function redrawOverlay() { @@ -1728,7 +1917,7 @@ octx.clearRect(0, 0, canvasWidth, canvasHeight); octx.translate(plotOffset.left, plotOffset.top); - var i, hi; + var i, hi; for (i = 0; i < highlights.length; ++i) { hi = highlights[i]; @@ -1737,7 +1926,6 @@ else drawPointHighlight(hi.series, hi.point); } - octx.restore(); // redraw selection if (selection.show && selectionIsSane()) { @@ -1751,9 +1939,30 @@ 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); + octx.fillRect(x, y, w, h); + octx.strokeRect(x, y, w, h); } + + // redraw crosshair + var pos = crosshair.pos, mode = options.crosshair.mode; + if (mode != null && pos.x != -1) { + octx.strokeStyle = parseColor(options.crosshair.color).scale(null, null, null, 0.8).toString(); + octx.lineWidth = 1; + ctx.lineJoin = "round"; + + octx.beginPath(); + if (mode.indexOf("x") != -1) { + octx.moveTo(pos.x, 0); + octx.lineTo(pos.x, plotHeight); + } + if (mode.indexOf("y") != -1) { + octx.moveTo(0, pos.y); + octx.lineTo(plotWidth, pos.y); + } + octx.stroke(); + + } + octx.restore(); } function highlight(s, point, auto) { @@ -1815,16 +2024,31 @@ } function drawBarHighlight(series, point) { - octx.lineJoin = "round"; octx.lineWidth = series.bars.lineWidth; octx.strokeStyle = parseColor(series.color).scale(1, 1, 1, 0.5).toString(); - octx.fillStyle = parseColor(series.color).scale(1, 1, 1, 0.5).toString(); + var fillStyle = parseColor(series.color).scale(1, 1, 1, 0.5).toString(); var barLeft = series.bars.align == "left" ? 0 : -series.bars.barWidth/2; - drawBar(point[0], point[1], barLeft, barLeft + series.bars.barWidth, - 0, true, series.xaxis, series.yaxis, octx); + drawBar(point[0], point[1], point[2] || 0, barLeft, barLeft + series.bars.barWidth, + 0, function () { return fillStyle; }, series.xaxis, series.yaxis, octx, series.bars.horizontal); } - - function triggerSelectedEvent() { + + function setPositionFromEvent(pos, e) { + var offset = eventHolder.offset(); + pos.x = clamp(0, e.pageX - offset.left - plotOffset.left, plotWidth); + pos.y = clamp(0, e.pageY - offset.top - plotOffset.top, plotHeight); + } + + function setCrosshair(pos) { + if (pos == null) + crosshair.pos.x = -1; + else { + crosshair.pos.x = clamp(0, pos.x != null ? axes.xaxis.p2c(pos.x) : axes.x2axis.p2c(pos.x2), plotWidth); + crosshair.pos.y = clamp(0, pos.y != null ? axes.yaxis.p2c(pos.y) : axes.y2axis.p2c(pos.y2), plotHeight); + } + triggerRedrawOverlay(); + } + + function getSelectionForEvent() { var x1 = Math.min(selection.first.x, selection.second.x), x2 = Math.max(selection.first.x, selection.second.x), y1 = Math.max(selection.first.y, selection.second.y), @@ -1838,7 +2062,12 @@ if (axes.yaxis.used) r.yaxis = { from: axes.yaxis.c2p(y1), to: axes.yaxis.c2p(y2) }; if (axes.y2axis.used) - r.yaxis = { from: axes.y2axis.c2p(y1), to: axes.y2axis.c2p(y2) }; + r.y2axis = { from: axes.y2axis.c2p(y1), to: axes.y2axis.c2p(y2) }; + return r; + } + + function triggerSelectedEvent() { + var r = getSelectionForEvent(); target.trigger("plotselected", [ r ]); @@ -1862,22 +2091,24 @@ triggerSelectedEvent(); clickIsMouseUp = true; } + else { + // this counts as a clear + target.trigger("plotunselected", [ ]); + target.trigger("plotselecting", [ null ]); + } return false; } function setSelectionPos(pos, e) { - var offset = eventHolder.offset(); + setPositionFromEvent(pos, e); + 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) @@ -1885,29 +2116,27 @@ else pos.y = plotHeight; } - else { - pos.y = e.pageY - offset.top - plotOffset.top; - pos.y = Math.min(Math.max(0, pos.y), plotHeight); - } } - + function updateSelection(pos) { if (pos.pageX == null) return; - + setSelectionPos(selection.second, pos); if (selectionIsSane()) { selection.show = true; triggerRedrawOverlay(); } else - clearSelection(); + clearSelection(true); } - function clearSelection() { + function clearSelection(preventEvent) { if (selection.show) { selection.show = false; triggerRedrawOverlay(); + if (!preventEvent) + target.trigger("plotunselected", [ ]); } } @@ -1947,6 +2176,24 @@ return Math.abs(selection.second.x - selection.first.x) >= minSize && Math.abs(selection.second.y - selection.first.y) >= minSize; } + + function getColorOrGradient(spec, bottom, top, defaultColor) { + if (typeof spec == "string") + return spec; + else { + // assume this is a gradient spec; IE currently only + // supports a simple vertical gradient properly, so that's + // what we support too + var gradient = ctx.createLinearGradient(0, top, 0, bottom); + + for (var i = 0, l = spec.colors.length; i < l; ++i) { + var c = spec.colors[i]; + gradient.addColorStop(i / (l - 1), typeof c == "string" ? c : parseColor(defaultColor).scale(c.brightness, c.brightness, c.brightness, c.opacity)); + } + + return gradient; + } + } } $.plot = function(target, data, options) { @@ -1960,6 +2207,44 @@ alert(tstr);*/ return plot; }; + + // returns a string with the date d formatted according to fmt + $.plot.formatDate = function(d, fmt, monthNames) { + var leftPad = function(n) { + n = "" + n; + return n.length == 1 ? "0" + n : n; + }; + + var r = []; + var escape = false; + if (monthNames == null) + monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; + for (var i = 0; i < fmt.length; ++i) { + var c = fmt.charAt(i); + + if (escape) { + switch (c) { + case 'h': c = "" + d.getUTCHours(); break; + case 'H': c = leftPad(d.getUTCHours()); break; + case 'M': c = leftPad(d.getUTCMinutes()); break; + case 'S': c = leftPad(d.getUTCSeconds()); break; + case 'd': c = "" + d.getUTCDate(); break; + case 'm': c = "" + (d.getUTCMonth() + 1); break; + case 'y': c = "" + d.getUTCFullYear(); break; + case 'b': c = "" + monthNames[d.getUTCMonth()]; break; + } + r.push(c); + escape = false; + } + else { + if (c == "%") + escape = true; + else + r.push(c); + } + } + return r.join(""); + }; // round to nearby lower multiple of base function floorInBase(n, base) { @@ -1968,7 +2253,7 @@ function clamp(min, value, max) { if (value < min) - return value; + return min; else if (value > max) return max; else @@ -2021,10 +2306,10 @@ }; this.normalize = function() { - this.r = limit(parseInt(this.r), 0, 255); - this.g = limit(parseInt(this.g), 0, 255); - this.b = limit(parseInt(this.b), 0, 255); - this.a = limit(this.a, 0, 1); + this.r = clamp(0, parseInt(this.r), 255); + this.g = clamp(0, parseInt(this.g), 255); + this.b = clamp(0, parseInt(this.b), 255); + this.a = clamp(0, this.a, 1); return this; }; @@ -2089,7 +2374,7 @@ } while (!$.nodeName(elem.get(0), "body")); // catch Safari's way of signalling transparent - if (color == "rgba(0, 0, 0, 0)") + if (color == "rgba(0, 0, 0, 0)") return "transparent"; return color; diff --git a/jquery.flot.pack.js b/jquery.flot.pack.js deleted file mode 100644 index a5714f1..0000000 --- a/jquery.flot.pack.js +++ /dev/null @@ -1 +0,0 @@ -(function(F){function D(AO,e,f){var W=[],o={colors:["#edc240","#afd8f8","#cb4b4b","#4da74d","#9440ed"],legend:{show:true,noColumns:1,labelFormatter:null,labelBoxBorderColor:"#ccc",container:null,position:"ne",margin:5,backgroundColor:null,backgroundOpacity:0.85},xaxis:{mode:null,min:null,max:null,autoscaleMargin:null,ticks:null,tickFormatter:null,labelWidth:null,labelHeight:null,tickDecimals:null,tickSize:null,minTickSize:null,monthNames:null,timeformat:null},yaxis:{autoscaleMargin:0.02},x2axis:{autoscaleMargin:null},y2axis:{autoscaleMargin:0.02},points:{show:false,radius:3,lineWidth:2,fill:true,fillColor:"#ffffff"},lines:{show:false,lineWidth:2,fill:false,fillColor:null},bars:{show:false,lineWidth:2,barWidth:1,fill:true,fillColor:null,align:"left"},grid:{color:"#545454",backgroundColor:null,tickColor:"#dddddd",labelMargin:5,borderWidth:2,markings:null,markingsColor:"#f4f4f4",markingsLineWidth:2,clickable:false,hoverable:false,autoHighlight:true,mouseActiveRadius:10},selection:{mode:null,color:"#e8cfac"},shadowSize:4},X=null,AP=null,AQ=null,g=null,AX=null,K=AO,AA={xaxis:{},yaxis:{},x2axis:{},y2axis:{}},m={left:0,right:0,top:0,bottom:0},AI=0,Z=0,N=0,AB=0,S={};this.setData=n;this.setupGrid=s;this.draw=AU;this.clearSelection=I;this.setSelection=AC;this.getCanvas=function(){return X};this.getPlotOffset=function(){return m};this.getData=function(){return W};this.getAxes=function(){return AA};this.highlight=AS;this.unhighlight=AH;y(f);n(e);j();s();AU();function n(AY){W=U(AY);c();t()}function U(Ac){var Aa=[];for(var AZ=0;AZ=o.colors.length){Ad=0;++Ac}}var Ae=0,Aj;for(Ad=0;AdAj.datamax){Aj.datamax=Ag+Ah}}if(Af!=null&&!isNaN(Af=+Af)){if(AfAi.datamax){Ai.datamax=Af}}if(Ag==null||Af==null||isNaN(Ag)||isNaN(Af)){Ad[Ac]=null}}}for(Ab in AA){if(AA[Ab].datamin==Aa){AA[Ab].datamin=0}if(AA[Ab].datamax==AZ){AA[Ab].datamax=1}}}function j(){AI=K.width();Z=K.height();K.html("");K.css("position","relative");if(AI<=0||Z<=0){throw"Invalid dimensions for plot, width = "+AI+", height = "+Z}X=F('').appendTo(K).get(0);if(F.browser.msie){X=window.G_vmlCanvasManager.initElement(X)}g=X.getContext("2d");AP=F('').appendTo(K).get(0);if(F.browser.msie){AP=window.G_vmlCanvasManager.initElement(AP)}AX=AP.getContext("2d");AQ=F([AP,X]);if(o.selection.mode!=null||o.grid.hoverable){AQ.each(function(){this.onmousemove=J});if(o.selection.mode!=null){AQ.mousedown(AN)}}if(o.grid.clickable){AQ.click(k)}}function s(){function AY(Ab,Aa){Q(Ab,Aa);L(Ab,Aa);w(Ab,Aa);if(Ab==AA.xaxis||Ab==AA.x2axis){Ab.p2c=function(Ac){return(Ac-Ab.min)*Ab.scale};Ab.c2p=function(Ac){return Ab.min+Ac/Ab.scale}}else{Ab.p2c=function(Ac){return(Ab.max-Ac)*Ab.scale};Ab.c2p=function(Ac){return Ab.max-Ac/Ab.scale}}}for(var AZ in AA){AY(AA[AZ],o[AZ])}AW();p();AV()}function Q(Ab,Ad){var Aa=Ad.min!=null?Ad.min:Ab.datamin;var AY=Ad.max!=null?Ad.max:Ab.datamax;if(AY-Aa==0){var AZ;if(AY==0){AZ=1}else{AZ=0.01}Aa-=AZ;AY+=AZ}else{var Ac=Ad.autoscaleMargin;if(Ac!=null){if(Ad.min==null){Aa-=(AY-Aa)*Ac;if(Aa<0&&Ab.datamin>=0){Aa=0}}if(Ad.max==null){AY+=(AY-Aa)*Ac;if(AY>0&&Ab.datamax<=0){AY=0}}}}Ab.min=Aa;Ab.max=AY}function L(Ad,Ag){var Ac;if(typeof Ag.ticks=="number"&&Ag.ticks>0){Ac=Ag.ticks}else{if(Ad==AA.xaxis||Ad==AA.x2axis){Ac=AI/100}else{Ac=Z/60}}var Al=(Ad.max-Ad.min)/Ac;var Ao,Ah,Aj,Ak,Af,Aa,AZ;if(Ag.mode=="time"){function An(Av,Ap,Ar){var Aq=function(Ax){Ax=""+Ax;return Ax.length==1?"0"+Ax:Ax};var Au=[];var At=false;if(Ar==null){Ar=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]}for(var As=0;As=Ab){break}}Ao=Am[Af][0];Aj=Am[Af][1];if(Aj=="year"){Aa=Math.pow(10,Math.floor(Math.log(Al/Ai.year)/Math.LN10));AZ=(Al/Ai.year)/Aa;if(AZ<1.5){Ao=1}else{if(AZ<3){Ao=2}else{if(AZ<7.5){Ao=5}else{Ao=10}}}Ao*=Aa}if(Ag.tickSize){Ao=Ag.tickSize[0];Aj=Ag.tickSize[1]}Ah=function(Ar){var Aw=[],Au=Ar.tickSize[0],Ax=Ar.tickSize[1],Av=new Date(Ar.min);var Aq=Au*Ai[Ax];if(Ax=="second"){Av.setUTCSeconds(C(Av.getUTCSeconds(),Au))}if(Ax=="minute"){Av.setUTCMinutes(C(Av.getUTCMinutes(),Au))}if(Ax=="hour"){Av.setUTCHours(C(Av.getUTCHours(),Au))}if(Ax=="month"){Av.setUTCMonth(C(Av.getUTCMonth(),Au))}if(Ax=="year"){Av.setUTCFullYear(C(Av.getUTCFullYear(),Au))}Av.setUTCMilliseconds(0);if(Aq>=Ai.minute){Av.setUTCSeconds(0)}if(Aq>=Ai.hour){Av.setUTCMinutes(0)}if(Aq>=Ai.day){Av.setUTCHours(0)}if(Aq>=Ai.day*4){Av.setUTCDate(1)}if(Aq>=Ai.year){Av.setUTCMonth(0)}var Az=0,Ay=Number.NaN,As;do{As=Ay;Ay=Av.getTime();Aw.push({v:Ay,label:Ar.tickFormatter(Ay,Ar)});if(Ax=="month"){if(Au<1){Av.setUTCDate(1);var Ap=Av.getTime();Av.setUTCMonth(Av.getUTCMonth()+1);var At=Av.getTime();Av.setTime(Ay+Az*Ai.hour+(At-Ap)*Au);Az=Av.getUTCHours();Av.setUTCHours(0)}else{Av.setUTCMonth(Av.getUTCMonth()+Au)}}else{if(Ax=="year"){Av.setUTCFullYear(Av.getUTCFullYear()+Au)}else{Av.setTime(Ay+Aq)}}}while(AyAY){Ae=AY}Aa=Math.pow(10,-Ae);AZ=Al/Aa;if(AZ<1.5){Ao=1}else{if(AZ<3){Ao=2;if(AZ>2.25&&(AY==null||Ae+1<=AY)){Ao=2.5;++Ae}}else{if(AZ<7.5){Ao=5}else{Ao=10}}}Ao*=Aa;if(Ag.minTickSize!=null&&Ao0){Ac.ticks=Ac.tickGenerator(Ac)}}else{if(Ae.ticks){var Ad=Ae.ticks;if(F.isFunction(Ad)){Ad=Ad({min:Ac.min,max:Ac.max})}var Ab,AY;for(Ab=0;Ab1){AZ=Aa[1]}}else{AY=Aa}if(AZ==null){AZ=Ac.tickFormatter(AY,Ac)}Ac.ticks[Ab]={v:AY,label:AZ}}}}}if(Ae.autoscaleMargin!=null&&Ac.ticks.length>0){if(Ae.min==null){Ac.min=Math.min(Ac.min,Ac.ticks[0].v)}if(Ae.max==null&&Ac.ticks.length>1){Ac.max=Math.min(Ac.max,Ac.ticks[Ac.ticks.length-1].v)}}}function AW(){function AZ(Ac){if(Ac.labelWidth==null){Ac.labelWidth=AI/6}if(Ac.labelHeight==null){labels=[];for(i=0;i'+l+"")}}Ac.labelHeight=0;if(labels.length>0){var Ab=F('
'+labels.join("")+'
').appendTo(K);Ac.labelHeight=Ab.height();Ab.remove()}}}function AY(Ae){if(Ae.labelWidth==null||Ae.labelHeight==null){var Ad,Af=[],Ac;for(Ad=0;Ad'+Ac+"")}}if(Af.length>0){var Ab=F('
'+Af.join("")+"
").appendTo(K);if(Ae.labelWidth==null){Ae.labelWidth=Ab.width()}if(Ae.labelHeight==null){Ae.labelHeight=Ab.find("div").height()}Ab.remove()}if(Ae.labelWidth==null){Ae.labelWidth=0}if(Ae.labelHeight==null){Ae.labelHeight=0}}}AZ(AA.xaxis);AY(AA.yaxis);AZ(AA.x2axis);AY(AA.y2axis);var Aa=o.grid.borderWidth/2;for(i=0;i0){m.bottom=Math.max(Aa,AA.xaxis.labelHeight+o.grid.labelMargin)}if(AA.yaxis.labelWidth>0){m.left=Math.max(Aa,AA.yaxis.labelWidth+o.grid.labelMargin)}if(AA.x2axis.labelHeight>0){m.top=Math.max(Aa,AA.x2axis.labelHeight+o.grid.labelMargin)}if(AA.y2axis.labelWidth>0){m.right=Math.max(Aa,AA.y2axis.labelWidth+o.grid.labelMargin)}N=AI-m.left-m.right;AB=Z-m.bottom-m.top;AA.xaxis.scale=N/(AA.xaxis.max-AA.xaxis.min);AA.yaxis.scale=AB/(AA.yaxis.max-AA.yaxis.min);AA.x2axis.scale=N/(AA.x2axis.max-AA.x2axis.min);AA.y2axis.scale=AB/(AA.y2axis.max-AA.y2axis.min)}function AU(){a();for(var AY=0;AYAd){return{from:Ad,to:Ae,axis:Ab}}return{from:Ae,to:Ad,axis:Ab}}function a(){var Ac;g.save();g.clearRect(0,0,AI,Z);g.translate(m.left,m.top);if(o.grid.backgroundColor){g.fillStyle=o.grid.backgroundColor;g.fillRect(0,0,N,AB)}if(o.grid.markings){var AZ=o.grid.markings;if(F.isFunction(AZ)){AZ=AZ({xmin:AA.xaxis.min,xmax:AA.xaxis.max,ymin:AA.yaxis.min,ymax:AA.yaxis.max,xaxis:AA.xaxis,yaxis:AA.yaxis,x2axis:AA.x2axis,y2axis:AA.y2axis})}for(Ac=0;AcAe.axis.max||Ab.toAb.axis.max){continue}Ae.from=Math.max(Ae.from,Ae.axis.min);Ae.to=Math.min(Ae.to,Ae.axis.max);Ab.from=Math.max(Ab.from,Ab.axis.min);Ab.to=Math.min(Ab.to,Ab.axis.max);if(Ae.from==Ae.to&&Ab.from==Ab.to){continue}Ae.from=Ae.axis.p2c(Ae.from);Ae.to=Ae.axis.p2c(Ae.to);Ab.from=Ab.axis.p2c(Ab.from);Ab.to=Ab.axis.p2c(Ab.to);if(Ae.from==Ae.to||Ab.from==Ab.to){g.strokeStyle=AY.color||o.grid.markingsColor;g.lineWidth=AY.lineWidth||o.grid.markingsLineWidth;g.moveTo(Math.floor(Ae.from),Math.floor(Ab.from));g.lineTo(Math.floor(Ae.to),Math.floor(Ab.to));g.stroke()}else{g.fillStyle=AY.color||o.grid.markingsColor;g.fillRect(Math.floor(Ae.from),Math.floor(Ab.to),Math.floor(Ae.to-Ae.from),Math.floor(Ab.from-Ab.to))}}}g.lineWidth=1;g.strokeStyle=o.grid.tickColor;g.beginPath();var Aa,Ad=AA.xaxis;for(Ac=0;Ac=AA.xaxis.max){continue}g.moveTo(Math.floor(Ad.p2c(Aa))+g.lineWidth/2,0);g.lineTo(Math.floor(Ad.p2c(Aa))+g.lineWidth/2,AB)}Ad=AA.yaxis;for(Ac=0;Ac=Ad.max){continue}g.moveTo(0,Math.floor(Ad.p2c(Aa))+g.lineWidth/2);g.lineTo(N,Math.floor(Ad.p2c(Aa))+g.lineWidth/2)}Ad=AA.x2axis;for(Ac=0;Ac=Ad.max){continue}g.moveTo(Math.floor(Ad.p2c(Aa))+g.lineWidth/2,-5);g.lineTo(Math.floor(Ad.p2c(Aa))+g.lineWidth/2,5)}Ad=AA.y2axis;for(Ac=0;Ac=Ad.max){continue}g.moveTo(N-5,Math.floor(Ad.p2c(Aa))+g.lineWidth/2);g.lineTo(N+5,Math.floor(Ad.p2c(Aa))+g.lineWidth/2)}g.stroke();if(o.grid.borderWidth){g.lineWidth=o.grid.borderWidth;g.strokeStyle=o.grid.color;g.lineJoin="round";g.strokeRect(0,0,N,AB)}g.restore()}function p(){K.find(".tickLabels").remove();var AY='
';function AZ(Ac,Ad){for(var Ab=0;AbAc.max){continue}AY+=Ad(Aa,Ac)}}AZ(AA.xaxis,function(Aa,Ab){return'
'+Aa.label+"
"});AZ(AA.yaxis,function(Aa,Ab){return'
'+Aa.label+"
"});AZ(AA.x2axis,function(Aa,Ab){return'
'+Aa.label+"
"});AZ(AA.y2axis,function(Aa,Ab){return'
'+Aa.label+"
"});AY+="
";K.append(AY)}function AK(AY){if(AY.lines.show||(!AY.bars.show&&!AY.points.show)){h(AY)}if(AY.bars.show){u(AY)}if(AY.points.show){v(AY)}}function h(Aa){function AZ(Aj,Ah,An,Am){var Ag,Ao=null,Ad=null,Ap=null;g.beginPath();for(var Ai=0;Ai=Ak&&Al>Am.max){if(Ak>Am.max){continue}Af=(Am.max-Al)/(Ak-Al)*(Ae-Af)+Af;Al=Am.max}else{if(Ak>=Al&&Ak>Am.max){if(Al>Am.max){continue}Ae=(Am.max-Al)/(Ak-Al)*(Ae-Af)+Af;Ak=Am.max}}if(Af<=Ae&&Af=Ae&&Af>An.max){if(Ae>An.max){continue}Al=(An.max-Af)/(Ae-Af)*(Ak-Al)+Al;Af=An.max}else{if(Ae>=Af&&Ae>An.max){if(Af>An.max){continue}Ak=(An.max-Af)/(Ae-Af)*(Ak-Al)+Al;Ae=An.max}}if(Ad!=An.p2c(Af)||Ap!=Am.p2c(Al)+Ah){g.moveTo(An.p2c(Af),Am.p2c(Al)+Ah)}Ad=An.p2c(Ae);Ap=Am.p2c(Ak)+Ah;g.lineTo(Ad,Ap)}g.stroke()}function Ab(Aj,Aq,Ao){var Ah,Ar=null;var Ad=Math.min(Math.max(0,Ao.min),Ao.max);var Am,Ag=0;var Ap=false;for(var Ai=0;Ai=Ae&&Af>Aq.max){if(Ae>Aq.max){continue}An=(Aq.max-Af)/(Ae-Af)*(Al-An)+An;Af=Aq.max}else{if(Ae>=Af&&Ae>Aq.max){if(Af>Aq.max){continue}Al=(Aq.max-Af)/(Ae-Af)*(Al-An)+An;Ae=Aq.max}}if(!Ap){g.beginPath();g.moveTo(Aq.p2c(Af),Ao.p2c(Ad));Ap=true}if(An>=Ao.max&&Al>=Ao.max){g.lineTo(Aq.p2c(Af),Ao.p2c(Ao.max));g.lineTo(Aq.p2c(Ae),Ao.p2c(Ao.max));continue}else{if(An<=Ao.min&&Al<=Ao.min){g.lineTo(Aq.p2c(Af),Ao.p2c(Ao.min));g.lineTo(Aq.p2c(Ae),Ao.p2c(Ao.min));continue}}var As=Af,Ak=Ae;if(An<=Al&&An=Ao.min){Af=(Ao.min-An)/(Al-An)*(Ae-Af)+Af;An=Ao.min}else{if(Al<=An&&Al=Ao.min){Ae=(Ao.min-An)/(Al-An)*(Ae-Af)+Af;Al=Ao.min}}if(An>=Al&&An>Ao.max&&Al<=Ao.max){Af=(Ao.max-An)/(Al-An)*(Ae-Af)+Af;An=Ao.max}else{if(Al>=An&&Al>Ao.max&&An<=Ao.max){Ae=(Ao.max-An)/(Al-An)*(Ae-Af)+Af;Al=Ao.max}}if(Af!=As){if(An<=Ao.min){Am=Ao.min}else{Am=Ao.max}g.lineTo(Aq.p2c(As),Ao.p2c(Am));g.lineTo(Aq.p2c(Af),Ao.p2c(Am))}g.lineTo(Aq.p2c(Af),Ao.p2c(An));g.lineTo(Aq.p2c(Ae),Ao.p2c(Al));if(Ae!=Ak){if(Al<=Ao.min){Am=Ao.min}else{Am=Ao.max}g.lineTo(Aq.p2c(Ak),Ao.p2c(Am));g.lineTo(Aq.p2c(Ae),Ao.p2c(Am))}Ag=Math.max(Ae,Ak)}if(Ap){g.lineTo(Aq.p2c(Ag),Ao.p2c(Ad));g.fill()}}g.save();g.translate(m.left,m.top);g.lineJoin="round";var Ac=Aa.lines.lineWidth;var AY=Aa.shadowSize;if(AY>0){g.lineWidth=AY/2;g.strokeStyle="rgba(0,0,0,0.1)";AZ(Aa.data,Ac/2+AY/2+g.lineWidth/2,Aa.xaxis,Aa.yaxis);g.lineWidth=AY/2;g.strokeStyle="rgba(0,0,0,0.2)";AZ(Aa.data,Ac/2+g.lineWidth/2,Aa.xaxis,Aa.yaxis)}g.lineWidth=Ac;g.strokeStyle=Aa.color;AD(Aa.lines,Aa.color);if(Aa.lines.fill){Ab(Aa.data,Aa.xaxis,Aa.yaxis)}AZ(Aa.data,0,Aa.xaxis,Aa.yaxis);g.restore()}function v(AZ){function Ac(Ag,Ae,Ah,Ak,Ai){for(var Af=0;AfAk.max||AjAi.max){continue}g.beginPath();g.arc(Ak.p2c(Ad),Ai.p2c(Aj),Ae,0,2*Math.PI,true);if(Ah){g.fill()}g.stroke()}}function Ab(Ag,Ai,Ae,Ak,Ah){for(var Af=0;AfAk.max||AjAh.max){continue}g.beginPath();g.arc(Ak.p2c(Ad),Ah.p2c(Aj)+Ai,Ae,0,Math.PI,false);g.stroke()}}g.save();g.translate(m.left,m.top);var Aa=AZ.lines.lineWidth;var AY=AZ.shadowSize;if(AY>0){g.lineWidth=AY/2;g.strokeStyle="rgba(0,0,0,0.1)";Ab(AZ.data,AY/2+g.lineWidth/2,AZ.points.radius,AZ.xaxis,AZ.yaxis);g.lineWidth=AY/2;g.strokeStyle="rgba(0,0,0,0.2)";Ab(AZ.data,g.lineWidth/2,AZ.points.radius,AZ.xaxis,AZ.yaxis)}g.lineWidth=AZ.points.lineWidth;g.strokeStyle=AZ.color;AD(AZ.points,AZ.color);Ac(AZ.data,AZ.points.radius,AZ.points.fill,AZ.xaxis,AZ.yaxis);g.restore()}function AM(Aj,Ah,Ac,Ai,Aa,Ao,An,Ak,Af){var Am=true,Ae=true,Ab=true,Ad=false,AZ=Aj+Ac,Al=Aj+Ai,AY=0,Ag=Ah;if(AgAn.max||AgAk.max){return }if(AZAn.max){Al=An.max;Ae=false}if(AYAk.max){Ag=Ak.max;Ab=false}if(Ao){Af.beginPath();Af.moveTo(An.p2c(AZ),Ak.p2c(AY)+Aa);Af.lineTo(An.p2c(AZ),Ak.p2c(Ag)+Aa);Af.lineTo(An.p2c(Al),Ak.p2c(Ag)+Aa);Af.lineTo(An.p2c(Al),Ak.p2c(AY)+Aa);Af.fill()}if(Am||Ae||Ab||Ad){Af.beginPath();AZ=An.p2c(AZ);AY=Ak.p2c(AY);Al=An.p2c(Al);Ag=Ak.p2c(Ag);Af.moveTo(AZ,AY+Aa);if(Am){Af.lineTo(AZ,Ag+Aa)}else{Af.moveTo(AZ,Ag+Aa)}if(Ab){Af.lineTo(Al,Ag+Aa)}else{Af.moveTo(Al,Ag+Aa)}if(Ae){Af.lineTo(Al,AY+Aa)}else{Af.moveTo(Al,AY+Aa)}if(Ad){Af.lineTo(AZ,AY+Aa)}else{Af.moveTo(AZ,AY+Aa)}Af.stroke()}}function u(Aa){function AZ(Ae,Ab,Ad,Ah,Af,Ai,Ag){for(var Ac=0;Ac")}Ae.push("");Ac=true}var Ag=W[i].label;if(o.legend.labelFormatter!=null){Ag=o.legend.labelFormatter(Ag)}Ae.push('
'+Ag+"")}if(Ac){Ae.push("")}if(Ae.length==0){return }var Ai=''+Ae.join("")+"
";if(o.legend.container!=null){o.legend.container.html(Ai)}else{var Af="";var AZ=o.legend.position,Aa=o.legend.margin;if(AZ.charAt(0)=="n"){Af+="top:"+(Aa+m.top)+"px;"}else{if(AZ.charAt(0)=="s"){Af+="bottom:"+(Aa+m.bottom)+"px;"}}if(AZ.charAt(1)=="e"){Af+="right:"+(Aa+m.right)+"px;"}else{if(AZ.charAt(1)=="w"){Af+="left:"+(Aa+m.left)+"px;"}}var Ah=F('
'+Ai.replace('style="','style="position:absolute;'+Af+";")+"
").appendTo(K);if(o.legend.backgroundOpacity!=0){var Ad=o.legend.backgroundColor;if(Ad==null){var Ab;if(o.grid.backgroundColor){Ab=o.grid.backgroundColor}else{Ab=A(Ah)}Ad=E(Ab).adjust(null,null,null,1).toString()}var AY=Ah.children();F('
').prependTo(Ah).css("opacity",o.legend.backgroundOpacity)}}}var AG={pageX:null,pageY:null},d={first:{x:-1,y:-1},second:{x:-1,y:-1},show:false,active:false},AF=[],P=false,O=null,z=null;function AT(Ae,Ac){var Al=o.grid.mouseActiveRadius,Ar=Al*Al+1,At=null,An=false;function Ai(Ay,Ax){return{datapoint:W[Ay].data[Ax],dataIndex:Ax,series:W[Ay],seriesIndex:Ay}}for(var Aq=0;Aq=Ag+Aa&&Am<=Ag+As&&Ak>=Math.min(0,Af)&&Ak<=Math.max(0,Af)){At=Ai(Aq,Ap)}}if(Au){if((Ag-Am>AZ||Ag-Am<-AZ)||(Af-Ak>AY||Af-Ak<-AY)){continue}var Aj=Math.abs(Ad.p2c(Ag)-Ae),Ah=Math.abs(Ab.p2c(Af)-Ac),Ao=Aj*Aj+Ah*Ah;if(AoAe.max||AfAd.max){return }var Ac=Ab.points.radius+Ab.points.lineWidth/2;AX.lineWidth=Ac;AX.strokeStyle=E(Ab.color).scale(1,1,1,0.5).toString();var AY=1.5*Ac;AX.beginPath();AX.arc(Ae.p2c(AZ),Ad.p2c(Af),AY,0,2*Math.PI,true);AX.stroke()}function AJ(Aa,AY){AX.lineJoin="round";AX.lineWidth=Aa.bars.lineWidth;AX.strokeStyle=E(Aa.color).scale(1,1,1,0.5).toString();AX.fillStyle=E(Aa.color).scale(1,1,1,0.5).toString();var AZ=Aa.bars.align=="left"?0:-Aa.bars.barWidth/2;AM(AY[0],AY[1],AZ,AZ+Aa.bars.barWidth,0,true,Aa.xaxis,Aa.yaxis,AX)}function r(){var AZ=Math.min(d.first.x,d.second.x),AY=Math.max(d.first.x,d.second.x),Ab=Math.max(d.first.y,d.second.y),Aa=Math.min(d.first.y,d.second.y);var Ac={};if(AA.xaxis.used){Ac.xaxis={from:AA.xaxis.c2p(AZ),to:AA.xaxis.c2p(AY)}}if(AA.x2axis.used){Ac.x2axis={from:AA.x2axis.c2p(AZ),to:AA.x2axis.c2p(AY)}}if(AA.yaxis.used){Ac.yaxis={from:AA.yaxis.c2p(Ab),to:AA.yaxis.c2p(Aa)}}if(AA.y2axis.used){Ac.yaxis={from:AA.y2axis.c2p(Ab),to:AA.y2axis.c2p(Aa)}}K.trigger("plotselected",[Ac]);if(AA.xaxis.used&&AA.yaxis.used){K.trigger("selected",[{x1:Ac.xaxis.from,y1:Ac.yaxis.from,x2:Ac.xaxis.to,y2:Ac.yaxis.to}])}}function Y(AY){if(document.onselectstart!==undefined){document.onselectstart=S.onselectstart}if(document.ondrag!==undefined){document.ondrag=S.ondrag}d.active=false;AL(AY);if(b()){r();P=true}return false}function AR(Aa,AY){var AZ=AQ.offset();if(o.selection.mode=="y"){if(Aa==d.first){Aa.x=0}else{Aa.x=N}}else{Aa.x=AY.pageX-AZ.left-m.left;Aa.x=Math.min(Math.max(0,Aa.x),N)}if(o.selection.mode=="x"){if(Aa==d.first){Aa.y=0}else{Aa.y=AB}}else{Aa.y=AY.pageY-AZ.top-m.top;Aa.y=Math.min(Math.max(0,Aa.y),AB)}}function AL(AY){if(AY.pageX==null){return }AR(d.second,AY);if(b()){d.show=true;x()}else{I()}}function I(){if(d.show){d.show=false;x()}}function AC(AZ,AY){var Aa;if(o.selection.mode=="y"){d.first.x=0;d.second.x=N}else{Aa=V(AZ,"x");d.first.x=Aa.axis.p2c(Aa.from);d.second.x=Aa.axis.p2c(Aa.to)}if(o.selection.mode=="x"){d.first.y=0;d.second.y=AB}else{Aa=V(AZ,"y");d.first.y=Aa.axis.p2c(Aa.from);d.second.y=Aa.axis.p2c(Aa.to)}d.show=true;x();if(!AY){r()}}function b(){var AY=5;return Math.abs(d.second.x-d.first.x)>=AY&&Math.abs(d.second.y-d.first.y)>=AY}}F.plot=function(L,J,I){var K=new D(L,J,I);return K};function C(J,I){return I*Math.floor(J/I)}function H(J,K,I){if(KI){return I}else{return K}}}function G(O,N,J,L){var M=["r","g","b","a"];var I=4;while(-1<--I){this[M[I]]=arguments[I]||((I==3)?1:0)}this.toString=function(){if(this.a>=1){return"rgb("+[this.r,this.g,this.b].join(",")+")"}else{return"rgba("+[this.r,this.g,this.b,this.a].join(",")+")"}};this.scale=function(R,Q,S,P){I=4;while(-1<--I){if(arguments[I]!=null){this[M[I]]*=arguments[I]}}return this.normalize()};this.adjust=function(R,Q,S,P){I=4;while(-1<--I){if(arguments[I]!=null){this[M[I]]+=arguments[I]}}return this.normalize()};this.clone=function(){return new G(this.r,this.b,this.g,this.a)};var K=function(Q,P,R){return Math.max(Math.min(Q,R),P)};this.normalize=function(){this.r=K(parseInt(this.r),0,255);this.g=K(parseInt(this.g),0,255);this.b=K(parseInt(this.b),0,255);this.a=K(this.a,0,1);return this};this.normalize()}var B={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]};function A(J){var I,K=J;do{I=K.css("background-color").toLowerCase();if(I!=""&&I!="transparent"){break}K=K.parent()}while(!F.nodeName(K.get(0),"body"));if(I=="rgba(0, 0, 0, 0)"){return"transparent"}return I}function E(K){var I;if(I=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(K)){return new G(parseInt(I[1],10),parseInt(I[2],10),parseInt(I[3],10))}if(I=/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(K)){return new G(parseInt(I[1],10),parseInt(I[2],10),parseInt(I[3],10),parseFloat(I[4]))}if(I=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(K)){return new G(parseFloat(I[1])*2.55,parseFloat(I[2])*2.55,parseFloat(I[3])*2.55)}if(I=/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(K)){return new G(parseFloat(I[1])*2.55,parseFloat(I[2])*2.55,parseFloat(I[3])*2.55,parseFloat(I[4]))}if(I=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(K)){return new G(parseInt(I[1],16),parseInt(I[2],16),parseInt(I[3],16))}if(I=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(K)){return new G(parseInt(I[1]+I[1],16),parseInt(I[2]+I[2],16),parseInt(I[3]+I[3],16))}var J=F.trim(K).toLowerCase();if(J=="transparent"){return new G(255,255,255,0)}else{I=B[J];return new G(I[0],I[1],I[2])}}})(jQuery); \ No newline at end of file diff --git a/jquery.js b/jquery.js index 2e43a82..b1ae21d 100644 --- a/jquery.js +++ b/jquery.js @@ -1,3408 +1,19 @@ -(function(){ /* - * jQuery 1.2.3 - New Wave Javascript + * jQuery JavaScript Library v1.3.2 + * http://jquery.com/ * - * Copyright (c) 2008 John Resig (jquery.com) - * Dual licensed under the MIT (MIT-LICENSE.txt) - * and GPL (GPL-LICENSE.txt) licenses. + * Copyright (c) 2009 John Resig + * Dual licensed under the MIT and GPL licenses. + * http://docs.jquery.com/License * - * $Date: 2008-02-06 00:21:25 -0500 (Wed, 06 Feb 2008) $ - * $Rev: 4663 $ + * Date: 2009-02-19 17:34:21 -0500 (Thu, 19 Feb 2009) + * Revision: 6246 */ - -// Map over jQuery in case of overwrite -if ( window.jQuery ) - var _jQuery = window.jQuery; - -var jQuery = window.jQuery = function( selector, context ) { - // The jQuery object is actually just the init constructor 'enhanced' - return new jQuery.prototype.init( selector, context ); -}; - -// Map over the $ in case of overwrite -if ( window.$ ) - var _$ = window.$; - -// Map the jQuery namespace to the '$' one -window.$ = jQuery; - -// A simple way to check for HTML strings or ID strings -// (both of which we optimize for) -var quickExpr = /^[^<]*(<(.|\s)+>)[^>]*$|^#(\w+)$/; - -// Is it a simple selector -var isSimple = /^.[^:#\[\.]*$/; - -jQuery.fn = jQuery.prototype = { - init: function( selector, context ) { - // Make sure that a selection was provided - selector = selector || document; - - // Handle $(DOMElement) - if ( selector.nodeType ) { - this[0] = selector; - this.length = 1; - return this; - - // Handle HTML strings - } else if ( typeof selector == "string" ) { - // Are we dealing with HTML string or an ID? - var match = quickExpr.exec( selector ); - - // Verify a match, and that no context was specified for #id - if ( match && (match[1] || !context) ) { - - // HANDLE: $(html) -> $(array) - if ( match[1] ) - selector = jQuery.clean( [ match[1] ], context ); - - // HANDLE: $("#id") - else { - var elem = document.getElementById( match[3] ); - - // Make sure an element was located - if ( elem ) - // Handle the case where IE and Opera return items - // by name instead of ID - if ( elem.id != match[3] ) - return jQuery().find( selector ); - - // Otherwise, we inject the element directly into the jQuery object - else { - this[0] = elem; - this.length = 1; - return this; - } - - else - selector = []; - } - - // HANDLE: $(expr, [context]) - // (which is just equivalent to: $(content).find(expr) - } else - return new jQuery( context ).find( selector ); - - // HANDLE: $(function) - // Shortcut for document ready - } else if ( jQuery.isFunction( selector ) ) - return new jQuery( document )[ jQuery.fn.ready ? "ready" : "load" ]( selector ); - - return this.setArray( - // HANDLE: $(array) - selector.constructor == Array && selector || - - // HANDLE: $(arraylike) - // Watch for when an array-like object, contains DOM nodes, is passed in as the selector - (selector.jquery || selector.length && selector != window && !selector.nodeType && selector[0] != undefined && selector[0].nodeType) && jQuery.makeArray( selector ) || - - // HANDLE: $(*) - [ selector ] ); - }, - - // The current version of jQuery being used - jquery: "1.2.3", - - // The number of elements contained in the matched element set - size: function() { - return this.length; - }, - - // The number of elements contained in the matched element set - length: 0, - - // Get the Nth element in the matched element set OR - // Get the whole matched element set as a clean array - get: function( num ) { - return num == undefined ? - - // Return a 'clean' array - jQuery.makeArray( this ) : - - // Return just the object - this[ num ]; - }, - - // Take an array of elements and push it onto the stack - // (returning the new matched element set) - pushStack: function( elems ) { - // Build a new jQuery matched element set - var ret = jQuery( elems ); - - // Add the old object onto the stack (as a reference) - ret.prevObject = this; - - // Return the newly-formed element set - return ret; - }, - - // Force the current matched set of elements to become - // the specified array of elements (destroying the stack in the process) - // You should use pushStack() in order to do this, but maintain the stack - setArray: function( elems ) { - // Resetting the length to 0, then using the native Array push - // is a super-fast way to populate an object with array-like properties - this.length = 0; - Array.prototype.push.apply( this, elems ); - - return this; - }, - - // Execute a callback for every element in the matched set. - // (You can seed the arguments with an array of args, but this is - // only used internally.) - each: function( callback, args ) { - return jQuery.each( this, callback, args ); - }, - - // Determine the position of an element within - // the matched set of elements - index: function( elem ) { - var ret = -1; - - // Locate the position of the desired element - this.each(function(i){ - if ( this == elem ) - ret = i; - }); - - return ret; - }, - - attr: function( name, value, type ) { - var options = name; - - // Look for the case where we're accessing a style value - if ( name.constructor == String ) - if ( value == undefined ) - return this.length && jQuery[ type || "attr" ]( this[0], name ) || undefined; - - else { - options = {}; - options[ name ] = value; - } - - // Check to see if we're setting style values - return this.each(function(i){ - // Set all the styles - for ( name in options ) - jQuery.attr( - type ? - this.style : - this, - name, jQuery.prop( this, options[ name ], type, i, name ) - ); - }); - }, - - css: function( key, value ) { - // ignore negative width and height values - if ( (key == 'width' || key == 'height') && parseFloat(value) < 0 ) - value = undefined; - return this.attr( key, value, "curCSS" ); - }, - - text: function( text ) { - if ( typeof text != "object" && text != null ) - return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) ); - - var ret = ""; - - jQuery.each( text || this, function(){ - jQuery.each( this.childNodes, function(){ - if ( this.nodeType != 8 ) - ret += this.nodeType != 1 ? - this.nodeValue : - jQuery.fn.text( [ this ] ); - }); - }); - - return ret; - }, - - wrapAll: function( html ) { - if ( this[0] ) - // The elements to wrap the target around - jQuery( html, this[0].ownerDocument ) - .clone() - .insertBefore( this[0] ) - .map(function(){ - var elem = this; - - while ( elem.firstChild ) - elem = elem.firstChild; - - return elem; - }) - .append(this); - - return this; - }, - - wrapInner: function( html ) { - return this.each(function(){ - jQuery( this ).contents().wrapAll( html ); - }); - }, - - wrap: function( html ) { - return this.each(function(){ - jQuery( this ).wrapAll( html ); - }); - }, - - append: function() { - return this.domManip(arguments, true, false, function(elem){ - if (this.nodeType == 1) - this.appendChild( elem ); - }); - }, - - prepend: function() { - return this.domManip(arguments, true, true, function(elem){ - if (this.nodeType == 1) - this.insertBefore( elem, this.firstChild ); - }); - }, - - before: function() { - return this.domManip(arguments, false, false, function(elem){ - this.parentNode.insertBefore( elem, this ); - }); - }, - - after: function() { - return this.domManip(arguments, false, true, function(elem){ - this.parentNode.insertBefore( elem, this.nextSibling ); - }); - }, - - end: function() { - return this.prevObject || jQuery( [] ); - }, - - find: function( selector ) { - var elems = jQuery.map(this, function(elem){ - return jQuery.find( selector, elem ); - }); - - return this.pushStack( /[^+>] [^+>]/.test( selector ) || selector.indexOf("..") > -1 ? - jQuery.unique( elems ) : - elems ); - }, - - clone: function( events ) { - // Do the clone - var ret = this.map(function(){ - if ( jQuery.browser.msie && !jQuery.isXMLDoc(this) ) { - // IE copies events bound via attachEvent when - // using cloneNode. Calling detachEvent on the - // clone will also remove the events from the orignal - // In order to get around this, we use innerHTML. - // Unfortunately, this means some modifications to - // attributes in IE that are actually only stored - // as properties will not be copied (such as the - // the name attribute on an input). - var clone = this.cloneNode(true), - container = document.createElement("div"); - container.appendChild(clone); - return jQuery.clean([container.innerHTML])[0]; - } else - return this.cloneNode(true); - }); - - // Need to set the expando to null on the cloned set if it exists - // removeData doesn't work here, IE removes it from the original as well - // this is primarily for IE but the data expando shouldn't be copied over in any browser - var clone = ret.find("*").andSelf().each(function(){ - if ( this[ expando ] != undefined ) - this[ expando ] = null; - }); - - // Copy the events from the original to the clone - if ( events === true ) - this.find("*").andSelf().each(function(i){ - if (this.nodeType == 3) - return; - var events = jQuery.data( this, "events" ); - - for ( var type in events ) - for ( var handler in events[ type ] ) - jQuery.event.add( clone[ i ], type, events[ type ][ handler ], events[ type ][ handler ].data ); - }); - - // Return the cloned set - return ret; - }, - - filter: function( selector ) { - return this.pushStack( - jQuery.isFunction( selector ) && - jQuery.grep(this, function(elem, i){ - return selector.call( elem, i ); - }) || - - jQuery.multiFilter( selector, this ) ); - }, - - not: function( selector ) { - if ( selector.constructor == String ) - // test special case where just one selector is passed in - if ( isSimple.test( selector ) ) - return this.pushStack( jQuery.multiFilter( selector, this, true ) ); - else - selector = jQuery.multiFilter( selector, this ); - - var isArrayLike = selector.length && selector[selector.length - 1] !== undefined && !selector.nodeType; - return this.filter(function() { - return isArrayLike ? jQuery.inArray( this, selector ) < 0 : this != selector; - }); - }, - - add: function( selector ) { - return !selector ? this : this.pushStack( jQuery.merge( - this.get(), - selector.constructor == String ? - jQuery( selector ).get() : - selector.length != undefined && (!selector.nodeName || jQuery.nodeName(selector, "form")) ? - selector : [selector] ) ); - }, - - is: function( selector ) { - return selector ? - jQuery.multiFilter( selector, this ).length > 0 : - false; - }, - - hasClass: function( selector ) { - return this.is( "." + selector ); - }, - - val: function( value ) { - if ( value == undefined ) { - - if ( this.length ) { - var elem = this[0]; - - // We need to handle select boxes special - if ( jQuery.nodeName( elem, "select" ) ) { - var index = elem.selectedIndex, - values = [], - options = elem.options, - one = elem.type == "select-one"; - - // Nothing was selected - if ( index < 0 ) - return null; - - // Loop through all the selected options - for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) { - var option = options[ i ]; - - if ( option.selected ) { - // Get the specifc value for the option - value = jQuery.browser.msie && !option.attributes.value.specified ? option.text : option.value; - - // We don't need an array for one selects - if ( one ) - return value; - - // Multi-Selects return an array - values.push( value ); - } - } - - return values; - - // Everything else, we just grab the value - } else - return (this[0].value || "").replace(/\r/g, ""); - - } - - return undefined; - } - - return this.each(function(){ - if ( this.nodeType != 1 ) - return; - - if ( value.constructor == Array && /radio|checkbox/.test( this.type ) ) - this.checked = (jQuery.inArray(this.value, value) >= 0 || - jQuery.inArray(this.name, value) >= 0); - - else if ( jQuery.nodeName( this, "select" ) ) { - var values = value.constructor == Array ? - value : - [ value ]; - - jQuery( "option", this ).each(function(){ - this.selected = (jQuery.inArray( this.value, values ) >= 0 || - jQuery.inArray( this.text, values ) >= 0); - }); - - if ( !values.length ) - this.selectedIndex = -1; - - } else - this.value = value; - }); - }, - - html: function( value ) { - return value == undefined ? - (this.length ? - this[0].innerHTML : - null) : - this.empty().append( value ); - }, - - replaceWith: function( value ) { - return this.after( value ).remove(); - }, - - eq: function( i ) { - return this.slice( i, i + 1 ); - }, - - slice: function() { - return this.pushStack( Array.prototype.slice.apply( this, arguments ) ); - }, - - map: function( callback ) { - return this.pushStack( jQuery.map(this, function(elem, i){ - return callback.call( elem, i, elem ); - })); - }, - - andSelf: function() { - return this.add( this.prevObject ); - }, - - data: function( key, value ){ - var parts = key.split("."); - parts[1] = parts[1] ? "." + parts[1] : ""; - - if ( value == null ) { - var data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]); - - if ( data == undefined && this.length ) - data = jQuery.data( this[0], key ); - - return data == null && parts[1] ? - this.data( parts[0] ) : - data; - } else - return this.trigger("setData" + parts[1] + "!", [parts[0], value]).each(function(){ - jQuery.data( this, key, value ); - }); - }, - - removeData: function( key ){ - return this.each(function(){ - jQuery.removeData( this, key ); - }); - }, - - domManip: function( args, table, reverse, callback ) { - var clone = this.length > 1, elems; - - return this.each(function(){ - if ( !elems ) { - elems = jQuery.clean( args, this.ownerDocument ); - - if ( reverse ) - elems.reverse(); - } - - var obj = this; - - if ( table && jQuery.nodeName( this, "table" ) && jQuery.nodeName( elems[0], "tr" ) ) - obj = this.getElementsByTagName("tbody")[0] || this.appendChild( this.ownerDocument.createElement("tbody") ); - - var scripts = jQuery( [] ); - - jQuery.each(elems, function(){ - var elem = clone ? - jQuery( this ).clone( true )[0] : - this; - - // execute all scripts after the elements have been injected - if ( jQuery.nodeName( elem, "script" ) ) { - scripts = scripts.add( elem ); - } else { - // Remove any inner scripts for later evaluation - if ( elem.nodeType == 1 ) - scripts = scripts.add( jQuery( "script", elem ).remove() ); - - // Inject the elements into the document - callback.call( obj, elem ); - } - }); - - scripts.each( evalScript ); - }); - } -}; - -// Give the init function the jQuery prototype for later instantiation -jQuery.prototype.init.prototype = jQuery.prototype; - -function evalScript( i, elem ) { - if ( elem.src ) - jQuery.ajax({ - url: elem.src, - async: false, - dataType: "script" - }); - - else - jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" ); - - if ( elem.parentNode ) - elem.parentNode.removeChild( elem ); -} - -jQuery.extend = jQuery.fn.extend = function() { - // copy reference to target object - var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options; - - // Handle a deep copy situation - if ( target.constructor == Boolean ) { - deep = target; - target = arguments[1] || {}; - // skip the boolean and the target - i = 2; - } - - // Handle case when target is a string or something (possible in deep copy) - if ( typeof target != "object" && typeof target != "function" ) - target = {}; - - // extend jQuery itself if only one argument is passed - if ( length == 1 ) { - target = this; - i = 0; - } - - for ( ; i < length; i++ ) - // Only deal with non-null/undefined values - if ( (options = arguments[ i ]) != null ) - // Extend the base object - for ( var name in options ) { - // Prevent never-ending loop - if ( target === options[ name ] ) - continue; - - // Recurse if we're merging object values - if ( deep && options[ name ] && typeof options[ name ] == "object" && target[ name ] && !options[ name ].nodeType ) - target[ name ] = jQuery.extend( target[ name ], options[ name ] ); - - // Don't bring in undefined values - else if ( options[ name ] != undefined ) - target[ name ] = options[ name ]; - - } - - // Return the modified object - return target; -}; - -var expando = "jQuery" + (new Date()).getTime(), uuid = 0, windowData = {}; - -// exclude the following css properties to add px -var exclude = /z-?index|font-?weight|opacity|zoom|line-?height/i; - -jQuery.extend({ - noConflict: function( deep ) { - window.$ = _$; - - if ( deep ) - window.jQuery = _jQuery; - - return jQuery; - }, - - // See test/unit/core.js for details concerning this function. - isFunction: function( fn ) { - return !!fn && typeof fn != "string" && !fn.nodeName && - fn.constructor != Array && /function/i.test( fn + "" ); - }, - - // check if an element is in a (or is an) XML document - isXMLDoc: function( elem ) { - return elem.documentElement && !elem.body || - elem.tagName && elem.ownerDocument && !elem.ownerDocument.body; - }, - - // Evalulates a script in a global context - globalEval: function( data ) { - data = jQuery.trim( data ); - - if ( data ) { - // Inspired by code by Andrea Giammarchi - // http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html - var head = document.getElementsByTagName("head")[0] || document.documentElement, - script = document.createElement("script"); - - script.type = "text/javascript"; - if ( jQuery.browser.msie ) - script.text = data; - else - script.appendChild( document.createTextNode( data ) ); - - head.appendChild( script ); - head.removeChild( script ); - } - }, - - nodeName: function( elem, name ) { - return elem.nodeName && elem.nodeName.toUpperCase() == name.toUpperCase(); - }, - - cache: {}, - - data: function( elem, name, data ) { - elem = elem == window ? - windowData : - elem; - - var id = elem[ expando ]; - - // Compute a unique ID for the element - if ( !id ) - id = elem[ expando ] = ++uuid; - - // Only generate the data cache if we're - // trying to access or manipulate it - if ( name && !jQuery.cache[ id ] ) - jQuery.cache[ id ] = {}; - - // Prevent overriding the named cache with undefined values - if ( data != undefined ) - jQuery.cache[ id ][ name ] = data; - - // Return the named cache data, or the ID for the element - return name ? - jQuery.cache[ id ][ name ] : - id; - }, - - removeData: function( elem, name ) { - elem = elem == window ? - windowData : - elem; - - var id = elem[ expando ]; - - // If we want to remove a specific section of the element's data - if ( name ) { - if ( jQuery.cache[ id ] ) { - // Remove the section of cache data - delete jQuery.cache[ id ][ name ]; - - // If we've removed all the data, remove the element's cache - name = ""; - - for ( name in jQuery.cache[ id ] ) - break; - - if ( !name ) - jQuery.removeData( elem ); - } - - // Otherwise, we want to remove all of the element's data - } else { - // Clean up the element expando - try { - delete elem[ expando ]; - } catch(e){ - // IE has trouble directly removing the expando - // but it's ok with using removeAttribute - if ( elem.removeAttribute ) - elem.removeAttribute( expando ); - } - - // Completely remove the data cache - delete jQuery.cache[ id ]; - } - }, - - // args is for internal usage only - each: function( object, callback, args ) { - if ( args ) { - if ( object.length == undefined ) { - for ( var name in object ) - if ( callback.apply( object[ name ], args ) === false ) - break; - } else - for ( var i = 0, length = object.length; i < length; i++ ) - if ( callback.apply( object[ i ], args ) === false ) - break; - - // A special, fast, case for the most common use of each - } else { - if ( object.length == undefined ) { - for ( var name in object ) - if ( callback.call( object[ name ], name, object[ name ] ) === false ) - break; - } else - for ( var i = 0, length = object.length, value = object[0]; - i < length && callback.call( value, i, value ) !== false; value = object[++i] ){} - } - - return object; - }, - - prop: function( elem, value, type, i, name ) { - // Handle executable functions - if ( jQuery.isFunction( value ) ) - value = value.call( elem, i ); - - // Handle passing in a number to a CSS property - return value && value.constructor == Number && type == "curCSS" && !exclude.test( name ) ? - value + "px" : - value; - }, - - className: { - // internal only, use addClass("class") - add: function( elem, classNames ) { - jQuery.each((classNames || "").split(/\s+/), function(i, className){ - if ( elem.nodeType == 1 && !jQuery.className.has( elem.className, className ) ) - elem.className += (elem.className ? " " : "") + className; - }); - }, - - // internal only, use removeClass("class") - remove: function( elem, classNames ) { - if (elem.nodeType == 1) - elem.className = classNames != undefined ? - jQuery.grep(elem.className.split(/\s+/), function(className){ - return !jQuery.className.has( classNames, className ); - }).join(" ") : - ""; - }, - - // internal only, use is(".class") - has: function( elem, className ) { - return jQuery.inArray( className, (elem.className || elem).toString().split(/\s+/) ) > -1; - } - }, - - // A method for quickly swapping in/out CSS properties to get correct calculations - swap: function( elem, options, callback ) { - var old = {}; - // Remember the old values, and insert the new ones - for ( var name in options ) { - old[ name ] = elem.style[ name ]; - elem.style[ name ] = options[ name ]; - } - - callback.call( elem ); - - // Revert the old values - for ( var name in options ) - elem.style[ name ] = old[ name ]; - }, - - css: function( elem, name, force ) { - if ( name == "width" || name == "height" ) { - var val, props = { position: "absolute", visibility: "hidden", display:"block" }, which = name == "width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ]; - - function getWH() { - val = name == "width" ? elem.offsetWidth : elem.offsetHeight; - var padding = 0, border = 0; - jQuery.each( which, function() { - padding += parseFloat(jQuery.curCSS( elem, "padding" + this, true)) || 0; - border += parseFloat(jQuery.curCSS( elem, "border" + this + "Width", true)) || 0; - }); - val -= Math.round(padding + border); - } - - if ( jQuery(elem).is(":visible") ) - getWH(); - else - jQuery.swap( elem, props, getWH ); - - return Math.max(0, val); - } - - return jQuery.curCSS( elem, name, force ); - }, - - curCSS: function( elem, name, force ) { - var ret; - - // A helper method for determining if an element's values are broken - function color( elem ) { - if ( !jQuery.browser.safari ) - return false; - - var ret = document.defaultView.getComputedStyle( elem, null ); - return !ret || ret.getPropertyValue("color") == ""; - } - - // We need to handle opacity special in IE - if ( name == "opacity" && jQuery.browser.msie ) { - ret = jQuery.attr( elem.style, "opacity" ); - - return ret == "" ? - "1" : - ret; - } - // Opera sometimes will give the wrong display answer, this fixes it, see #2037 - if ( jQuery.browser.opera && name == "display" ) { - var save = elem.style.outline; - elem.style.outline = "0 solid black"; - elem.style.outline = save; - } - - // Make sure we're using the right name for getting the float value - if ( name.match( /float/i ) ) - name = styleFloat; - - if ( !force && elem.style && elem.style[ name ] ) - ret = elem.style[ name ]; - - else if ( document.defaultView && document.defaultView.getComputedStyle ) { - - // Only "float" is needed here - if ( name.match( /float/i ) ) - name = "float"; - - name = name.replace( /([A-Z])/g, "-$1" ).toLowerCase(); - - var getComputedStyle = document.defaultView.getComputedStyle( elem, null ); - - if ( getComputedStyle && !color( elem ) ) - ret = getComputedStyle.getPropertyValue( name ); - - // If the element isn't reporting its values properly in Safari - // then some display: none elements are involved - else { - var swap = [], stack = []; - - // Locate all of the parent display: none elements - for ( var a = elem; a && color(a); a = a.parentNode ) - stack.unshift(a); - - // Go through and make them visible, but in reverse - // (It would be better if we knew the exact display type that they had) - for ( var i = 0; i < stack.length; i++ ) - if ( color( stack[ i ] ) ) { - swap[ i ] = stack[ i ].style.display; - stack[ i ].style.display = "block"; - } - - // Since we flip the display style, we have to handle that - // one special, otherwise get the value - ret = name == "display" && swap[ stack.length - 1 ] != null ? - "none" : - ( getComputedStyle && getComputedStyle.getPropertyValue( name ) ) || ""; - - // Finally, revert the display styles back - for ( var i = 0; i < swap.length; i++ ) - if ( swap[ i ] != null ) - stack[ i ].style.display = swap[ i ]; - } - - // We should always get a number back from opacity - if ( name == "opacity" && ret == "" ) - ret = "1"; - - } else if ( elem.currentStyle ) { - var camelCase = name.replace(/\-(\w)/g, function(all, letter){ - return letter.toUpperCase(); - }); - - ret = elem.currentStyle[ name ] || elem.currentStyle[ camelCase ]; - - // From the awesome hack by Dean Edwards - // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 - - // If we're not dealing with a regular pixel number - // but a number that has a weird ending, we need to convert it to pixels - if ( !/^\d+(px)?$/i.test( ret ) && /^\d/.test( ret ) ) { - // Remember the original values - var style = elem.style.left, runtimeStyle = elem.runtimeStyle.left; - - // Put in the new values to get a computed value out - elem.runtimeStyle.left = elem.currentStyle.left; - elem.style.left = ret || 0; - ret = elem.style.pixelLeft + "px"; - - // Revert the changed values - elem.style.left = style; - elem.runtimeStyle.left = runtimeStyle; - } - } - - return ret; - }, - - clean: function( elems, context ) { - var ret = []; - context = context || document; - // !context.createElement fails in IE with an error but returns typeof 'object' - if (typeof context.createElement == 'undefined') - context = context.ownerDocument || context[0] && context[0].ownerDocument || document; - - jQuery.each(elems, function(i, elem){ - if ( !elem ) - return; - - if ( elem.constructor == Number ) - elem = elem.toString(); - - // Convert html string into DOM nodes - if ( typeof elem == "string" ) { - // Fix "XHTML"-style tags in all browsers - elem = elem.replace(/(<(\w+)[^>]*?)\/>/g, function(all, front, tag){ - return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i) ? - all : - front + ">"; - }); - - // Trim whitespace, otherwise indexOf won't work as expected - var tags = jQuery.trim( elem ).toLowerCase(), div = context.createElement("div"); - - var wrap = - // option or optgroup - !tags.indexOf("", "" ] || - - !tags.indexOf("", "" ] || - - tags.match(/^<(thead|tbody|tfoot|colg|cap)/) && - [ 1, "", "
" ] || - - !tags.indexOf("", "" ] || - - // matched above - (!tags.indexOf("", "" ] || - - !tags.indexOf("", "" ] || - - // IE can't serialize and