Development

Changeset 12995

You must first sign up to be able to contribute.

Changeset 12995

Show
Ignore:
Timestamp:
11/13/08 23:33:30 (1 year ago)
Author:
DennisBenkert
Message:

sfStatsPlugin: updated the js files of flot to version 0.5 (fixes #4530 - patch from [MA]Pascal)

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • plugins/sfStatsPlugin/branches/1.0/web/js/flot/API.txt

    r9680 r12995  
    88The placeholder is a jQuery object that the plot will be put into. 
    99This placeholder needs to have its width and height set as explained 
    10 in the README. The plot will modify some properties of the placeholder 
    11 so it's recommended you simply pass in a div that you don't use for 
    12 anything else. 
     10in the README (go read that now if you haven't, it's short). The plot 
     11will modify some properties of the placeholder so it's recommended you 
     12simply pass in a div that you don't use for anything else. 
    1313 
    1414The format of the data is documented below, as is the available 
    15 options. The "plot" object returned has some members you can call. 
     15options. The "plot" object returned has some methods you can call. 
    1616These are documented separately below. 
    1717 
     
    3939Note that to simplify the internal logic in Flot both the x and y 
    4040values must be numbers, even if specifying time series (see below for 
    41 how to do this). This is a common problem because you might 
    42 accidentally retrieve data from the database and serialize them 
    43 directly to JSON without noticing the wrong type. 
     41how to do this). This is a common problem because you might retrieve 
     42data from the database and serialize them directly to JSON without 
     43noticing the wrong type. 
    4444 
    4545If a null is specified as a point or if one of the coordinates is null 
    46 or NaN or couldn't be converted to a number, the point is ignored. As 
    47 a special case, a null value for lines is interpreted as a line 
    48 segment end, i.e. the two points before and after the null value are 
     46or couldn't be converted to a number, the point is ignored when 
     47drawing. As a special case, a null value for lines is interpreted as a 
     48line segment end, i.e. the point before and after the null value are 
    4949not connected. 
    5050 
     
    5858    bars: specific bars options, 
    5959    points: specific points options, 
     60    xaxis: 1 or 2, 
     61    yaxis: 1 or 2, 
    6062    shadowSize: number 
    6163  } 
     
    8183in which case you can hard-code the color index to prevent the colors 
    8284from jumping around between the series. 
     85 
     86The "xaxis" and "yaxis" options specify which axis to use, specify 2 
     87to get the secondary axis (x axis at top or y axis to the right). 
     88E.g., you can use this to make a dual axis plot by specifying 
     89{ yaxis: 2 } for one data series. 
    8390 
    8491The rest of the options are all documented below as they are the same 
     
    141148If you want the legend to appear somewhere else in the DOM, you can 
    142149specify "container" as a jQuery object to put the legend table into. 
    143 The "position" and "margin" etc. options will then be ignored. 
     150The "position" and "margin" etc. options will then be ignored. Note 
     151that it will overwrite the contents of the container. 
    144152 
    145153 
     
    148156==================== 
    149157 
    150   xaxis, yaxis: { 
     158  xaxis, yaxis, x2axis, y2axis: { 
    151159    mode: null or "time" 
    152160    min: null or number 
    153161    max: null or number 
    154162    autoscaleMargin: null or number 
     163    labelWidth: null or number 
     164    labelHeight: null or number 
     165 
    155166    ticks: null or number or ticks array or (fn: range -> ticks array) 
    156167    tickSize: number or array 
     
    160171  } 
    161172 
    162 The two axes have the same kind of options. The "mode" option 
     173The axes have the same kind of options. The "mode" option 
    163174determines how the data is interpreted, the default of null means as 
    164175decimal numbers. Use "time" for time series data, see the next section. 
     
    175186nearest whole tick. The default value is "null" for the x axis and 
    1761870.02 for the y axis which seems appropriate for most cases. 
     188 
     189"labelWidth" and "labelHeight" specifies the maximum size of the tick 
     190labels in pixels. They're useful in case you need to align several 
     191plots. 
    177192 
    178193The rest of the options deal with the ticks. 
     
    226241number of decimals to display (default is auto-detected). 
    227242 
    228 Alternatively, for ultimate control you can provide a function to 
    229 "tickFormatter". The function is passed two parameters, the tick value 
    230 and an "axis" object with information, and should return a string. The 
    231 default formatter looks like this: 
     243Alternatively, for ultimate control over how ticks look like you can 
     244provide a function to "tickFormatter". The function is passed two 
     245parameters, the tick value and an "axis" object with information, and 
     246should return a string. The default formatter looks like this: 
    232247 
    233248  function formatter(val, axis) { 
     
    254269================ 
    255270 
     271Time series are a bit more difficult than scalar data because 
     272calendars don't follow a simple base 10 system. For many cases, Flot 
     273abstracts most of this away, but it can still be a bit difficult to 
     274get the data into Flot. So we'll first discuss the data format. 
     275 
    256276The time series support in Flot is based on Javascript timestamps, 
    257277i.e. everywhere a time value is expected or handed over, a Javascript 
    258 timestamp number is used. This is not the same as a Date object. A 
     278timestamp number is used. This is a number, not a Date object. A 
    259279Javascript timestamp is the number of milliseconds since January 1, 
    260 1970 00:00:00. This is almost the same as Unix timestamps, except it's 
     2801970 00:00:00 UTC. This is almost the same as Unix timestamps, except it's 
    261281in milliseconds, so remember to multiply by 1000! 
    262282 
    263 You can see a timestamp by outputting 
    264  
    265   (new Date()).getTime() 
     283You can see a timestamp like this 
     284 
     285  alert((new Date()).getTime()) 
     286 
     287Normally you want the timestamps to be displayed according to a 
     288certain time zone, usually the time zone in which the data has been 
     289produced. However, Flot always displays timestamps according to UTC. 
     290It has to as the only alternative with core Javascript is to interpret 
     291the timestamps according to the time zone that the visitor is in, 
     292which means that the ticks will shift unpredictably with the time zone 
     293and daylight savings of each visitor. 
     294 
     295So given that there's no good support for custom time zones in 
     296Javascript, you'll have to take care of this server-side. 
     297 
     298The easiest way to think about it is to pretend that the data 
     299production time zone is UTC, even if it isn't. So if you have a 
     300datapoint at 2002-02-20 08:00, you can generate a timestamp for eight 
     301o'clock UTC even if it really happened eight o'clock UTC+0200. 
    266302 
    267303In PHP you can get an appropriate timestamp with 
    268 'strtotime("2002-02-20") * 1000', in Python with 
    269 'time.mktime(datetime_object.timetuple()) * 1000', in .NET with 
     304'strtotime("2002-02-20 UTC") * 1000', in Python with 
     305'calendar.timegm(datetime_object.timetuple()) * 1000', in .NET with 
    270306something like: 
    271307 
     
    274310    System.TimeSpan span = new System.TimeSpan(System.DateTime.Parse("1/1/1970").Ticks); 
    275311    System.DateTime time = input.Subtract(span); 
    276     return (int)(time.Ticks / 10000); 
    277   } 
     312    return (long)(time.Ticks / 10000); 
     313  } 
     314 
     315Javascript also has some support for parsing date strings, so it is 
     316possible to generate the timestamps manually client-side. 
     317 
     318If you've already got the real UTC timestamp, it's too late to use the 
     319pretend trick described above. But you can fix up the timestamps by 
     320adding the time zone offset, e.g. for UTC+0200 you would add 2 hours 
     321to the UTC timestamp you got. Then it'll look right on the plot. Most 
     322programming environments have some means of getting the timezone 
     323offset for a specific date. 
    278324 
    279325Once you've got the timestamps into the data and specified "time" as 
    280326the axis mode, Flot will automatically generate relevant ticks and 
    281 format them. As always, you can tweak the ticks via the "ticks" 
    282 option. Again the values should be timestamps, not Date objects! 
    283  
    284 Tick generation and formatting is controlled separately through the 
    285 following axis options: 
     327format them. As always, you can tweak the ticks via the "ticks" option 
     328- just remember that the values should be timestamps (numbers), not 
     329Date objects. 
     330 
     331Tick generation and formatting can also be controlled separately 
     332through the following axis options: 
    286333 
    287334  xaxis, yaxis: { 
     
    322369  tickFormatter: function (val, axis) { 
    323370    var d = new Date(val); 
    324     return d.getDate() + "/" + (d.getMonth() + 1); 
     371    return d.getUTCDate() + "/" + (d.getUTCMonth() + 1); 
    325372  } 
    326373 
     
    342389 
    343390  lines, points, bars: { 
    344     show: boolean, 
    345     lineWidth: number, 
    346     fill: boolean, 
    347     fillColor: color or null 
     391    show: boolean 
     392    lineWidth: number 
     393    fill: boolean or number 
     394    fillColor: color 
    348395  } 
    349396 
     
    354401  bars: { 
    355402    barWidth: number 
     403    align: "left" or "center" 
    356404  } 
    357405 
     
    370418  }; 
    371419 
    372 "lineWidth" is the thickness of the line or outline and "fill" is 
    373 whether the shape should be filled. For lines, this produces area 
    374 graphs. If "fillColor" is null (default), the color for the data 
    375 series is used. 
    376  
    377 Note that the options that take numbers works in units of pixels, but 
    378 "barWidth" is the width of the bars in units of the x axis (e.g. for 
    379 time series it's in milliseconds). 
     420"lineWidth" is the thickness of the line or outline in pixels. 
     421 
     422"fill" is whether the shape should be filled. For lines, this produces 
     423area graphs. You can use "fillColor" to specify the color of the fill. 
     424If "fillColor" evaluates to false (default for everything except 
     425points), the fill color is auto-set to the color of the data series. 
     426You can adjust the opacity of the fill by setting fill to a number 
     427between 0 (fully transparent) and 1 (fully opaque). 
     428 
     429"barWidth" is the width of the bars in units of the x axis, contrary 
     430to most other measures that are specified in pixels. For instance, for 
     431time series the unit is milliseconds so 24 * 60 * 60 * 1000 produces 
     432bars with the width of a day. "align" specifies whether a bar should 
     433be left-aligned (default) or centered on top of the value it 
     434represents. 
    380435 
    381436The "colors" array specifies a default color theme to get colors for 
     
    400455    tickColor: color 
    401456    labelMargin: number 
    402     coloredAreas: array of areas or (fn: plot area -> array of areas) 
    403     coloredAreasColor: color 
     457    markings: array of markings or (fn: axes -> array of markings) 
    404458    borderWidth: number 
    405459    clickable: boolean 
    406   } 
    407  
    408 The grid is the thing with the two axes and a number of ticks. "color" 
     460    hoverable: boolean 
     461    autoHighlight: boolean 
     462    mouseActiveRadius: number 
     463  } 
     464 
     465The grid is the thing with the axes and a number of ticks. "color" 
    409466is the color of the grid itself whereas "backgroundColor" specifies 
    410467the background color inside the grid area. The default value of null 
    411468means that the background is transparent. You should only need to set 
    412 backgroundColor if want the grid area to be a different color from the 
     469backgroundColor if you want the grid area to be a different color from the 
    413470page color. Otherwise you might as well just set the background color 
    414471of the page with CSS. 
     
    420477to disable the border. 
    421478 
    422 "coloredAreas" is an array of areas that will be drawn on top of the 
    423 background. You can either specify an array of objects with { x1, y1, 
    424 x2, y2 } or a function that returns such an array given the plot area 
    425 as { xmin, xmax, ymin, ymax }. The default color of the areas are 
    426 "coloredAreasColor". You can override the color of individual areas by 
    427 specifying "color" in the area object. 
    428  
    429 Here's an example array: 
    430  
    431   coloredAreas: [ { x1: 0, y1: 10, x2: 2, y2: 15, color: "#bb0000" }, ... ] 
     479"markings" is used to draw simple lines and rectangular areas in the 
     480background of the plot. You can either specify an array of ranges on 
     481the form { xaxis: { from, to }, yaxis: { from, to } } (secondary axis 
     482coordinates with x2axis/y2axis) or with a function that returns such 
     483an array given the axes for the plot in an object as the first 
     484parameter. 
     485 
     486You can set the color of markings by specifying "color" in the ranges 
     487object. Here's an example array: 
     488 
     489  markings: [ { xaxis: { from: 0, to: 2 }, yaxis: { from: 10, to: 10 }, color: "#bb0000" }, ... ] 
    432490 
    433491If you leave out one of the values, that value is assumed to go to the 
    434 border of the plot. So for example { x1: 0, x2: 2 } means an area that 
    435 extends from the top to the bottom of the plot in the x range 0-2. 
     492border of the plot. So for example if you only specify { xaxis: { 
     493from: 0, to: 2 } } it means an area that extends from the top to the 
     494bottom of the plot in the x range 0-2. 
     495 
     496A line is drawn if from and to are the same, e.g. 
     497 
     498  markings: [ { yaxis: { from: 1, to: 1 } }, ... ] 
     499 
     500would draw a line parallel to the x axis at y = 1. You can control the 
     501line width with "lineWidth" in the ranges objects. 
    436502 
    437503An example function might look like this: 
    438504 
    439   coloredAreas: function (plotarea) { 
    440     var areas = []; 
    441     for (var x = Math.floor(plotarea.xmin); x < plotarea.xmax; x += 2) 
    442       areas.push({ x1: x, x2: x + 1 }); 
    443     return areas; 
     505  markings: function (axes) { 
     506    var markings = []; 
     507    for (var x = Math.floor(axes.xaxis.min); x < axes.xaxis.max; x += 2) 
     508      markings.push({ xaxis: { from: x, to: x + 1 } }); 
     509    return markings; 
    444510  } 
    445511 
     
    447513If you set "clickable" to true, the plot will listen for click events 
    448514on the plot area and fire a "plotclick" event on the placeholder with 
    449 an object { x: number, y: number } as parameter when one occurs. The 
    450 returned coordinates will be in the unit of the plot (not in pixels). 
    451 You can use it like this: 
     515a position and a nearby data item object as parameters. The coordinates 
     516are available both in the unit of the axes (not in pixels) and in 
     517global screen coordinates. 
     518 
     519Likewise, if you set "hoverable" to true, the plot will listen for 
     520mouse move events on the plot area and fire a "plothover" event with 
     521the same parameters as the "plotclick" event. If "autoHighlight" is 
     522true (the default), nearby data items are highlighted automatically. 
     523If needed, you can disable highlighting and control it yourself with 
     524the highlight/unhighlight plot methods described elsewhere. 
     525 
     526You can use "plotclick" and "plothover" events like this: 
    452527 
    453528    $.plot($("#placeholder"), [ d ], { grid: { clickable: true } }); 
    454529 
    455     $("#placeholder").bind("plotclick", function (e, pos) { 
    456         // the values are in pos.x and pos.y 
     530    $("#placeholder").bind("plotclick", function (event, pos, item) { 
     531        alert("You clicked at " + pos.x + ", " + pos.y); 
     532        // secondary axis coordinates if present are in pos.x2, pos.y2, 
     533        // if you need global screen coordinates, they are pos.pageX, pos.pageY 
     534 
     535        if (item) { 
     536          highlight(item.series, item.datapoint); 
     537          alert("You clicked a point!"); 
     538        } 
    457539    }); 
    458540 
    459 Support for hover indications or for associating the clicks with any 
    460 specific data is still forthcoming. 
     541The item object in this example is either null or a nearby object on the form: 
     542 
     543  item: { 
     544      datapoint: the point as you specified it in the data, e.g. [0, 2] 
     545      dataIndex: the index of the point in the data array 
     546      series: the series object 
     547      seriesIndex: the index of the series 
     548      pageX, pageY: the global screen coordinates of the point 
     549  } 
     550 
     551For instance, if you have specified the data like this  
     552 
     553    $.plot($("#placeholder"), [ { label: "Foo", data: [[0, 10], [7, 3]] } ], ...); 
     554 
     555and the mouse is near the point (7, 3), "datapoint" is the [7, 3] we 
     556specified, "dataIndex" will be 1, "series" is a normalized series 
     557object with among other things the "Foo" label in series.label and the 
     558color in series.color, and "seriesIndex" is 0. 
     559 
     560If you use the above events to update some other information and want 
     561to clear out that info in case the mouse goes away, you'll probably 
     562also need to listen to "mouseout" events on the placeholder div. 
     563 
     564"mouseActiveRadius" specifies how far the mouse can be from an item 
     565and still activate it. If there are two or more points within this 
     566radius, Flot chooses the closest item. For bars, the top-most bar 
     567(from the latest specified data series) is chosen. 
    461568 
    462569 
     
    474581where both ranges can be specified. "color" is color of the selection. 
    475582 
    476 When selection support is enabled, a "selected" event will be emitted 
     583When selection support is enabled, a "plotselected" event will be emitted 
    477584on the DOM element you passed into the plot function. The event 
    478 handler gets one extra parameter with the area selected, like this: 
    479  
    480   placeholder.bind("selected", function(event, area) { 
    481     // area selected is area.x1 to area.x2 and area.y1 to area.y2 
     585handler gets one extra parameter with the ranges selected on the axes, 
     586like this: 
     587 
     588  placeholder.bind("plotselected", function(event, ranges) { 
     589    alert("You selected " + ranges.xaxis.from + " to " + ranges.xaxis.to) 
     590    // similar for yaxis, secondary axes are in x2axis 
     591    // and y2axis if present 
    482592  }); 
    483593 
    484594 
    485 Plot Member
     595Plot Method
    486596------------ 
    487597 
    488 The Plot object returned from the plot function has the following 
    489 members
     598The Plot object returned from the plot function has some methods you 
     599can call
    490600 
    491601  - clearSelection() 
     
    493603    Clear the selection rectangle. 
    494604 
    495   - setSelection(area) 
    496  
    497     Set the selection rectangle. The passed in area should have the 
    498     members x1 and x2 if the selection mode is "x" and y1 and y2 if 
    499     the selection mode is "y" and both x1, x2 and y1, y2 if the 
    500     selection mode is "xy", like this: 
    501  
    502       setSelection({ x1: 0, x2: 10, y1: 40, y2: 60}); 
    503  
    504     setSelection will trigger the "selected" event when called so you 
    505     may have to do a bit of shortcircuiting to prevent an eternal loop 
    506     if you invoke the method inside the "selected" handler. 
    507  
    508   - getCanvas() 
    509  
    510     Returns the canvas used for drawing in case you need to hack on it 
    511     yourself. You'll probably need to get the plot offset too. 
     605 
     606  - setSelection(ranges, preventEvent) 
     607 
     608    Set the selection rectangle. The passed in ranges is on the same 
     609    form as returned in the "plotselected" event. If the selection 
     610    mode is "x", you should put in either an xaxis (or x2axis) object, 
     611    if the mode is "y" you need to put in an yaxis (or y2axis) object 
     612    and both xaxis/x2axis and yaxis/y2axis if the selection mode is 
     613    "xy", like this: 
     614 
     615      setSelection({ xaxis: { from: 0, to: 10 }, yaxis: { from: 40, to: 60 } }); 
     616 
     617    setSelection will trigger the "plotselected" event when called. If 
     618    you don't want that to happen, e.g. if you're inside a 
     619    "plotselected" handler, pass true as the second parameter. 
     620     
     621 
     622  - highlight(series, datapoint) 
     623 
     624    Highlight a specific datapoint in the data series. You can either 
     625    specify the actual objects, e.g. if you got them from a 
     626    "plotclick" event, or you can specify the indices, e.g. 
     627    highlight(1, 3) to highlight the fourth point in the second series. 
     628 
    512629   
    513   - getPlotOffset() 
    514  
    515     Gets the offset that the grid has within the canvas as an object 
    516     with the members "left", "right", "top", "bottom". I.e., if you draw a 
    517     circle on the canvas with the center placed at (left, top), its 
    518     center will be at the top-most, left corner of the grid. 
     630  - unhighlight(series, datapoint) 
     631 
     632    Remove the highlighting of the point, same parameters as highlight. 
     633 
    519634 
    520635  - setData(data) 
     
    524639    that). You'll probably want to call draw() afterwards. 
    525640 
    526     You can use this function to speed up redrawing a plot if you now 
    527     that the axis won't change. Put in the new data with 
     641    You can use this function to speed up redrawing a plot if you know 
     642    that the axes won't change. Put in the new data with 
    528643    setData(newdata) and call draw() afterwards, and you're good to 
    529644    go. 
     645 
    530646     
    531  
    532   - getData() 
    533  
    534     Returns the data currently used. The data series returned are 
    535     normalized with missing settings filled in. So for instance to 
    536     find out what color Flot has assigned to the data series, you 
    537     could do this: 
    538  
    539       var series = plot.getData(); 
    540       for (var i = 0; i < series.length; ++i) 
    541         alert([i].color); 
    542  
    543647  - setupGrid() 
    544648 
     
    554658 
    555659    Redraws the canvas. 
     660     
     661 
     662There are also some members that let you peek inside the internal 
     663workings of Flot which in some cases is useful. Note that if you change 
     664something in the objects returned, you're changing the objects used by 
     665Flot to keep track of its state, so be careful. 
     666 
     667  - getData() 
     668 
     669    Returns an array of the data series currently used on normalized 
     670    form with missing settings filled in according to the global 
     671    options. So for instance to find out what color Flot has assigned 
     672    to the data series, you could do this: 
     673 
     674      var series = plot.getData(); 
     675      for (var i = 0; i < series.length; ++i) 
     676        alert(series[i].color); 
     677 
     678 
     679  - getAxes() 
     680 
     681    Gets an object with the axes settings as { xaxis, yaxis, x2axis, 
     682    y2axis }. Various things are stuffed inside an axis object, e.g. 
     683    you could use getAxes().xaxis.ticks to find out what the ticks are 
     684    for the xaxis. 
     685     
     686 
     687  - getCanvas() 
     688 
     689    Returns the canvas used for drawing in case you need to hack on it 
     690    yourself. You'll probably need to get the plot offset too. 
     691 
     692   
     693  - getPlotOffset() 
     694 
     695    Gets the offset that the grid has within the canvas as an object 
     696    with distances from the canvas edges as "left", "right", "top", 
     697    "bottom". I.e., if you draw a circle on the canvas with the center 
     698    placed at (left, top), its center will be at the top-most, left 
     699    corner of the grid. 
     700 
     701 
  • plugins/sfStatsPlugin/branches/1.0/web/js/flot/NEWS.txt

    r9680 r12995  
     1Flot 0.5 
     2-------- 
     3 
     4Backwards API change summary: Timestamps are now in UTC. Also 
     5"selected" event -> becomes "plotselected" with new data, the 
     6parameters for setSelection are now different (but backwards 
     7compatibility hooks are in place), coloredAreas becomes markings with 
     8a new interface (but backwards compatibility hooks are in place). 
     9 
     10 
     11Interactivity: added a new "plothover" event and this and the 
     12"plotclick" event now returns the closest data item (based on patch by 
     13/david, patch by Mark Byers for bar support). See the revamped 
     14"interacting with the data" example for some hints on what you can do. 
     15 
     16Highlighting: you can now highlight points and datapoints are 
     17autohighlighted when you hover over them (if hovering is turned on). 
     18 
     19Support for dual axis has been added (based on patch by someone who's 
     20annoyed and /david). For each data series you can specify which axes 
     21it belongs to, and there are two more axes, x2axis and y2axis, to 
     22customize. This affects the "selected" event which has been renamed to 
     23"plotselected" and spews out { xaxis: { from: -10, to: 20 } ... }, 
     24setSelection in which the parameters are on a new form (backwards 
     25compatible hooks are in place so old code shouldn't break) and 
     26markings (formerly coloredAreas). 
     27 
     28Timestamps in time mode are now displayed according to 
     29UTC instead of the time zone of the visitor. This affects the way the 
     30timestamps should be input; you'll probably have to offset the 
     31timestamps according to your local time zone. It also affects any 
     32custom date handling code (which basically now should use the 
     33equivalent UTC date mehods, e.g. .setUTCMonth() instead of 
     34.setMonth(). 
     35 
     36Added support for specifying the size of tick labels (axis.labelWidth, 
     37axis.labelHeight). Useful for specifying a max label size to keep 
     38multiple plots aligned. 
     39 
     40Markings, previously coloredAreas, are now specified as ranges on the 
     41axes, like { xaxis: { from: 0, to: 10 }}. Furthermore with markings 
     42you can now draw horizontal/vertical lines by setting from and to to 
     43the same coordinate (idea from line support patch by by Ryan Funduk). 
     44 
     45The "fill" option can now be a number that specifies the opacity of 
     46the fill. 
     47 
     48You can now specify a coordinate as null (like [2, null]) and Flot 
     49will take the other coordinate into account when scaling the axes 
     50(based on patch by joebno). 
     51 
     52New option for bars "align". Set it to "center" to center the bars on 
     53the value they represent. 
     54 
     55setSelection now takes a second parameter which you can use to prevent 
     56the method from firing the "plotselected" handler.  
     57 
     58Using the "container" option in legend now overwrites the container 
     59element instead of just appending to it (fixes infinite legend bug, 
     60reported by several people, fix by Brad Dewey). 
     61 
     62Fixed a bug in calculating spacing around the plot (reported by 
     63timothytoe). Fixed a bug in finding max values for all-negative data 
     64sets. Prevent the possibility of eternal looping in tick calculations. 
     65Fixed a bug when borderWidth is set to 0 (reported by 
     66Rob/sanchothefat). Fixed a bug with drawing bars extending below 0 
     67(reported by James Hewitt, patch by Ryan Funduk). Fixed a 
     68bug with line widths of bars (reported by MikeM). Fixed a bug with 
     69'nw' and 'sw' legend positions. Improved the handling of axis 
     70auto-scaling with bars. Fixed a bug with multi-line x-axis tick 
     71labels (reported by Luca Ciano). IE-fix help by Savage Zhang. 
     72 
     73 
    174Flot 0.4 
    275-------- 
  • plugins/sfStatsPlugin/branches/1.0/web/js/flot/README.txt

    r9680 r12995  
    2222include the excanvas script like this: 
    2323 
    24   <!--[if IE]><script language="javascript" type="text/javascript" src="excanvas.js"></script><![endif]--> 
     24  <!--[if IE]><script language="javascript" type="text/javascript" src="excanvas.pack.js"></script><![endif]--> 
    2525 
    2626If it's not working on your development IE 6.0, check that it has 
     
    4545   <div id="placeholder" style="width:600px;height:300px"></div> 
    4646 
    47 You can also do it with an external stylesheet. 
     47You can also do it with an external stylesheet. Make sure that the 
     48placeholder isn't within something with a display:none CSS property - 
     49in that case, Flot has trouble measuring label dimensions which 
     50results in garbled looks and might have trouble measuring the 
     51placeholder dimensions which is fatal (it'll throw an exception). 
    4852 
    49 Then on document ready, run the plot function: 
     53Then when the div is ready in the DOM, which is usually on document 
     54ready, run the plot function: 
    5055 
    5156  $.plot($("#placeholder"), data, options); 
     
    5459settings if you want to customize the plot. Take a look at the 
    5560examples for some ideas of what to put in or look at the reference 
    56 in the file "API.txt". 
     61in the file "API.txt". Here's a quick example that'll draw a line from 
     62(0, 0) to (1, 1): 
     63 
     64  $.plot($("#placeholder"), [ [[0, 0], [1, 1]] ], { yaxis: { max: 1 } }); 
    5765 
    5866The plot function immediately draws the chart and then returns a Plot 
  • plugins/sfStatsPlugin/branches/1.0/web/js/flot/TODO

    r9680 r12995  
    1212selection 
    1313  - user should be able to cancel selection with escape 
    14   - select points 
    1514 
    1615interactive zooming 
     
    1918  - auto-margins 
    2019 
    21 support for highlighting stuff 
    22   - lines 
    23   - points 
    24  
    2520legend 
    2621  - interactive auto-highlight of graph? 
     22  - ability to specify noRows instead of just noColumns 
    2723 
    2824labels 
    2925  - labels on bars, data points 
    30   - plan "all points" option 
    31   - interactive "label this point" command 
    32  
    33 interactive hover over 
    34   - fire event with value for points 
    35   - fire event with graph id for lines 
     26  - interactive "label this point" command/tooltip support 
    3627 
    3728error margin indicators 
    3829  - for scientific/statistical purposes 
     30 
     31hi-low bars 
    3932 
    4033non-xy based graph types 
  • plugins/sfStatsPlugin/branches/1.0/web/js/flot/jquery.flot.js

    r9680 r12995  
    1 /* Javascript plotting library for jQuery, v. 0.4
     1/* Javascript plotting library for jQuery, v. 0.5
    22 * 
    3  * Released under the MIT license by iola, December 2007. 
     3 * Released under the MIT license by IOLA, December 2007. 
    44 * 
    55 */ 
     
    1212        // or { data: [ [x1, y1], [x2, y2], ... ], label: "some label" } 
    1313         
    14         var series = []; 
    15         var options = { 
     14        var series = [], 
     15            options = { 
    1616            // the color theme used for graphs 
    1717            colors: ["#edc240", "#afd8f8", "#cb4b4b", "#4da74d", "#9440ed"], 
    18             legend: { 
    19                 show: true, 
    20                 noColumns: 1, // number of colums in legend table 
    21                 labelFormatter: null, // fn: string -> string 
    22                 labelBoxBorderColor: "#ccc", // border color for the little label boxes 
    23                 container: null, // container (as jQuery object) to put legend in, null means default on top of graph 
    24                 position: "ne", // position of default legend container within plot 
    25                 margin: 5, // distance from grid edge to default legend container within plot 
    26                 backgroundColor: null, // null means auto-detect 
    27                 backgroundOpacity: 0.85 // set to 0 to avoid background 
     18                legend: { 
     19                    show: true, 
     20                    noColumns: 1, // number of colums in legend table 
     21                    labelFormatter: null, // fn: string -> string 
     22                    labelBoxBorderColor: "#ccc", // border color for the little label boxes 
     23                    container: null, // container (as jQuery object) to put legend in, null means default on top of graph 
     24                    position: "ne", // position of default legend container within plot 
     25                    margin: 5, // distance from grid edge to default legend container within plot 
     26                    backgroundColor: null, // null means auto-detect 
     27                    backgroundOpacity: 0.85 // set to 0 to avoid background 
     28                }, 
     29                xaxis: { 
     30                    mode: null, // null or "time" 
     31                    min: null, // min. value to show, null means set automatically 
     32                    max: null, // max. value to show, null means set automatically 
     33                    autoscaleMargin: null, // margin in % to add if auto-setting min/max 
     34                    ticks: null, // either [1, 3] or [[1, "a"], 3] or (fn: axis info -> ticks) or app. number of ticks for auto-ticks 
     35                    tickFormatter: null, // fn: number -> string 
     36                    labelWidth: null, // size of tick labels in pixels 
     37                    labelHeight: null, 
     38                     
     39                    // mode specific options 
     40                    tickDecimals: null, // no. of decimals, null means auto 
     41                    tickSize: null, // number or [number, "unit"] 
     42                    minTickSize: null, // number or [number, "unit"] 
     43                    monthNames: null, // list of names of months 
     44                    timeformat: null // format string to use 
     45                }, 
     46                yaxis: { 
     47                    autoscaleMargin: 0.02 
     48                }, 
     49                x2axis: { 
     50                    autoscaleMargin: null 
     51                }, 
     52                y2axis: { 
     53                    autoscaleMargin: 0.02 
     54                },               
     55                points: { 
     56                    show: false, 
     57                    radius: 3, 
     58                    lineWidth: 2, // in pixels 
     59                    fill: true, 
     60                    fillColor: "#ffffff" 
     61                }, 
     62                lines: { 
     63                    show: false, 
     64                    lineWidth: 2, // in pixels 
     65                    fill: false, 
     66                    fillColor: null 
     67                }, 
     68                bars: { 
     69                    show: false, 
     70                    lineWidth: 2, // in pixels 
     71                    barWidth: 1, // in units of the x axis 
     72                    fill: true, 
     73                    fillColor: null, 
     74                    align: "left" // or "center" 
     75                }, 
     76                grid: { 
     77                    color: "#545454", // primary color used for outline and labels 
     78                    backgroundColor: null, // null for transparent, else color 
     79                    tickColor: "#dddddd", // color used for the ticks 
     80                    labelMargin: 5, // in pixels 
     81                    borderWidth: 2, 
     82                    markings: null, // array of ranges or fn: axes -> array of ranges 
     83                    markingsColor: "#f4f4f4", 
     84                    markingsLineWidth: 2, 
     85                    // interactive stuff 
     86                    clickable: false, 
     87                    hoverable: false, 
     88                    autoHighlight: true, // highlight in case mouse is near 
     89                    mouseActiveRadius: 10 // how far the mouse can be away to activate an item 
     90                }, 
     91                selection: { 
     92                    mode: null, // one of null, "x", "y" or "xy" 
     93                    color: "#e8cfac" 
     94                }, 
     95                shadowSize: 4 
    2896            }, 
    29             xaxis: { 
    30                 mode: null, // null or "time" 
    31                 min: null, // min. value to show, null means set automatically 
    32                 max: null, // max. value to show, null means set automatically 
    33                 autoscaleMargin: null, // margin in % to add if auto-setting min/max 
    34                 ticks: null, // either [1, 3] or [[1, "a"], 3] or (fn: axis info -> ticks) or app. number of ticks for auto-ticks 
    35                 tickFormatter: null, // fn: number -> string 
    36                  
    37                 // mode specific options 
    38                 tickDecimals: null, // no. of decimals, null means auto 
    39                 tickSize: null, // number or [number, "unit"] 
    40                 minTickSize: null, // number or [number, "unit"] 
    41                 monthNames: null, // list of names of months 
    42                 timeformat: null // format string to use 
    43             }, 
    44             yaxis: { 
    45                 autoscaleMargin: 0.02 
    46             }, 
    47             points: { 
    48                 show: false, 
    49                 radius: 3, 
    50                 lineWidth: 2, // in pixels 
    51                 fill: true, 
    52                 fillColor: "#ffffff" 
    53             }, 
    54             lines: { 
    55                 show: false, 
    56                 lineWidth: 2, // in pixels 
    57                 fill: false, 
    58                 fillColor: null 
    59             }, 
    60             bars: { 
    61                 show: false, 
    62                 lineWidth: 2, // in pixels 
    63                 barWidth: 1, // in units of the x axis 
    64                 fill: true, 
    65                 fillColor: null 
    66             }, 
    67             grid: { 
    68                 color: "#545454", // primary color used for outline and labels 
    69                 backgroundColor: null, // null for transparent, else color 
    70                 tickColor: "#dddddd", // color used for the ticks 
    71                 labelMargin: 3, // in pixels 
    72                 borderWidth: 2, 
    73                 clickable: null, 
    74                 coloredAreas: null, // array of { x1, y1, x2, y2 } or fn: plot area -> areas 
    75                 coloredAreasColor: "#f4f4f4" 
    76             }, 
    77             selection: { 
    78                 mode: null, // one of null, "x", "y" or "xy" 
    79                 color: "#e8cfac" 
    80             }, 
    81             shadowSize: 4 
    82         }; 
    83         var canvas = null, overlay = null, eventHolder = null,  
    84             ctx = null, octx = null, 
    85             target = target_, 
    86             xaxis = {}, yaxis = {}, 
    87             plotOffset = { left: 0, right: 0, top: 0, bottom: 0}, 
    88             yLabelMaxWidth = 0, yLabelMaxHeight = 0, xLabelBoxWidth = 0, 
    89             canvasWidth = 0, canvasHeight = 0, 
    90             plotWidth = 0, plotHeight = 0, 
    91             hozScale = 0, vertScale = 0, 
    92             // dedicated to storing data for buggy standard compliance cases 
    93             workarounds = {}; 
     97        canvas = null,      // the canvas for the plot itself 
     98        overlay = null,     // canvas for interactive stuff on top of plot 
     99        eventHolder = null, // jQuery object that events should be bound to 
     100        ctx = null, octx = null, 
     101        target = target_, 
     102        axes = { xaxis: {}, yaxis: {}, x2axis: {}, y2axis: {} }, 
     103        plotOffset = { left: 0, right: 0, top: 0, bottom: 0}, 
     104        canvasWidth = 0, canvasHeight = 0, 
     105        plotWidth = 0, plotHeight = 0, 
     106        // dedicated to storing data for buggy standard compliance cases 
     107        workarounds = {}; 
    94108         
    95109        this.setData = setData; 
     
    101115        this.getPlotOffset = function() { return plotOffset; }; 
    102116        this.getData = function() { return series; }; 
    103         this.getAxes = function() { return { xaxis: xaxis, yaxis: yaxis }; }; 
     117        this.getAxes = function() { return axes; }; 
     118        this.highlight = highlight; 
     119        this.unhighlight = unhighlight; 
    104120         
    105121        // initialize 
     
    144160            if (options.yaxis.noTicks && options.yaxis.ticks == null) 
    145161                options.yaxis.ticks = options.yaxis.noTicks; 
     162            if (options.grid.coloredAreas) 
     163                options.grid.markings = options.grid.coloredAreas; 
     164            if (options.grid.coloredAreasColor) 
     165                options.grid.markingsColor = options.grid.coloredAreasColor; 
    146166        } 
    147167 
     
    150170             
    151171            // collect what we already got of colors 
    152             var neededColors = series.length; 
    153             var usedColors = []; 
    154             var assignedColors = []; 
     172            var neededColors = series.length, 
     173                usedColors = [], 
     174                assignedColors = []; 
    155175            for (i = 0; i < series.length; ++i) { 
    156176                var sc = series[i].color; 
     
    171191 
    172192            // produce colors as needed 
    173             var colors = []; 
    174             var variation = 0; 
     193            var colors = [], variation = 0; 
    175194            i = 0; 
    176195            while (colors.length < neededColors) { 
     
    216235                if (s.shadowSize == null) 
    217236                    s.shadowSize = options.shadowSize; 
     237                if (s.xaxis && s.xaxis == 2) 
     238                    s.xaxis = axes.x2axis; 
     239                else 
     240                    s.xaxis = axes.xaxis; 
     241                if (s.yaxis && s.yaxis == 2) 
     242                    s.yaxis = axes.y2axis; 
     243                else 
     244                    s.yaxis = axes.yaxis; 
    218245            } 
    219246        } 
    220247         
    221248        function processData() { 
    222             xaxis.datamin = yaxis.datamin = Number.MAX_VALUE; 
    223             xaxis.datamax = yaxis.datamax = Number.MIN_VALUE; 
    224  
     249            var topSentry = Number.POSITIVE_INFINITY, 
     250                bottomSentry = Number.NEGATIVE_INFINITY, 
     251                axis; 
     252 
     253            for (axis in axes) { 
     254                axes[axis].datamin = topSentry; 
     255                axes[axis].datamax = bottomSentry; 
     256                axes[axis].used = false; 
     257            } 
     258             
    225259            for (var i = 0; i < series.length; ++i) { 
    226                 var data = series[i].data; 
     260                var data = series[i].data, 
     261                    axisx = series[i].xaxis, 
     262                    axisy = series[i].yaxis, 
     263                    mindelta = 0, maxdelta = 0; 
     264                 
     265                // make sure we got room for the bar 
     266                if (series[i].bars.show) { 
     267                    mindelta = series[i].bars.align == "left" ? 0 : -series[i].bars.barWidth/2; 
     268                    maxdelta = mindelta + series[i].bars.barWidth; 
     269                } 
     270                 
     271                axisx.used = axisy.used = true; 
    227272                for (var j = 0; j < data.length; ++j) { 
    228273                    if (data[j] == null) 
     
    232277 
    233278                    // convert to number 
    234                     if (x == null || y == null || isNaN(x = +x) || isNaN(y = +y)) { 
     279                    if (x != null && !isNaN(x = +x)) { 
     280                        if (x + mindelta < axisx.datamin) 
     281                            axisx.datamin = x + mindelta; 
     282                        if (x + maxdelta > axisx.datamax) 
     283                            axisx.datamax = x + maxdelta; 
     284                    } 
     285                     
     286                    if (y != null && !isNaN(y = +y)) { 
     287                        if (y < axisy.datamin) 
     288                            axisy.datamin = y; 
     289                        if (y > axisy.datamax) 
     290                            axisy.datamax = y; 
     291                    } 
     292                     
     293                    if (x == null || y == null || isNaN(x) || isNaN(y)) 
    235294                        data[j] = null; // mark this point as invalid 
    236                         continue; 
    237                     } 
    238  
    239                     if (x < xaxis.datamin) 
    240                         xaxis.datamin = x; 
    241                     if (x > xaxis.datamax) 
    242                         xaxis.datamax = x; 
    243                     if (y < yaxis.datamin) 
    244                         yaxis.datamin = y; 
    245                     if (y > yaxis.datamax) 
    246                         yaxis.datamax = y; 
    247                 } 
    248             } 
    249              
    250             if (xaxis.datamin == Number.MAX_VALUE) 
    251                 xaxis.datamin = 0; 
    252             if (yaxis.datamin == Number.MAX_VALUE) 
    253                 yaxis.datamin = 0; 
    254             if (xaxis.datamax == Number.MIN_VALUE) 
    255                 xaxis.datamax = 1; 
    256             if (yaxis.datamax == Number.MIN_VALUE) 
    257                 yaxis.datamax = 1; 
     295                } 
     296            } 
     297 
     298            for (axis in axes) { 
     299                if (axes[axis].datamin == topSentry) 
     300                    axes[axis].datamin = 0; 
     301                if (axes[axis].datamax == bottomSentry) 
     302                    axes[axis].datamax = 1; 
     303            } 
    258304        } 
    259305 
     
    283329            eventHolder = $([overlay, canvas]); 
    284330 
    285              
    286331            // bind events 
    287             if (options.selection.mode != null) { 
    288                 eventHolder.mousedown(onMouseDown); 
    289                  
     332            if (options.selection.mode != null || options.grid.hoverable) { 
    290333                // FIXME: temp. work-around until jQuery bug 1871 is fixed 
    291334                eventHolder.each(function () { 
    292335                    this.onmousemove = onMouseMove; 
    293336                }); 
     337 
     338                if (options.selection.mode != null) 
     339                    eventHolder.mousedown(onMouseDown); 
    294340            } 
    295341 
     
    299345 
    300346        function setupGrid() { 
    301             // x axis 
    302             setRange(xaxis, options.xaxis); 
    303             prepareTickGeneration(xaxis, options.xaxis); 
    304             setTicks(xaxis, options.xaxis); 
    305             extendXRangeIfNeededByBar(); 
    306  
    307             // y axis 
    308             setRange(yaxis, options.yaxis); 
    309             prepareTickGeneration(yaxis, options.yaxis); 
    310             setTicks(yaxis, options.yaxis); 
     347            function setupAxis(axis, options) { 
     348                setRange(axis, options); 
     349                prepareTickGeneration(axis, options); 
     350                setTicks(axis, options); 
     351                // add transformation helpers 
     352                if (axis == axes.xaxis || axis == axes.x2axis) { 
     353                    // data point to canvas coordinate 
     354                    axis.p2c = function (p) { return (p - axis.min) * axis.scale; }; 
     355                    // canvas coordinate to data point  
     356                    axis.c2p = function (c) { return axis.min + c / axis.scale; }; 
     357                } 
     358                else { 
     359                    axis.p2c = function (p) { return (axis.max - p) * axis.scale; }; 
     360                    axis.c2p = function (p) { return axis.max - p / axis.scale; }; 
     361                } 
     362            } 
     363 
     364            for (var axis in axes) 
     365                setupAxis(axes[axis], options[axis]); 
    311366 
    312367            setSpacing(); 
     
    357412            if (typeof axisOptions.ticks == "number" && axisOptions.ticks > 0) 
    358413                noTicks = axisOptions.ticks; 
    359             else if (axis == xaxis) 
     414            else if (axis == axes.xaxis || axis == axes.x2axis) 
    360415                noTicks = canvasWidth / 100; 
    361416            else 
     
    383438                        if (escape) { 
    384439                            switch (c) { 
    385                             case 'h': c = "" + d.getHours(); break; 
    386                             case 'H': c = leftPad(d.getHours()); break; 
    387                             case 'M': c = leftPad(d.getMinutes()); break; 
    388                             case 'S': c = leftPad(d.getSeconds()); break; 
    389                             case 'd': c = "" + d.getDate(); break; 
    390                             case 'm': c = "" + (d.getMonth() + 1); break; 
    391                             case 'y': c = "" + d.getFullYear(); break; 
    392                             case 'b': c = "" + monthNames[d.getMonth()]; break; 
     440                            case 'h': c = "" + d.getUTCHours(); break; 
     441                            case 'H': c = leftPad(d.getUTCHours()); break; 
     442                            case 'M': c = leftPad(d.getUTCMinutes()); break; 
     443                            case 'S': c = leftPad(d.getUTCSeconds()); break; 
     444                            case 'd': c = "" + d.getUTCDate(); break; 
     445                            case 'm': c = "" + (d.getUTCMonth() + 1); break; 
     446                            case 'y': c = "" + d.getUTCFullYear(); break; 
     447                            case 'b': c = "" + monthNames[d.getUTCMonth()]; break; 
    393448                            } 
    394449                            r.push(c); 
     
    477532 
    478533                    if (unit == "second") 
    479                         d.setSeconds(floorInBase(d.getSeconds(), tickSize)); 
     534                        d.setUTCSeconds(floorInBase(d.getUTCSeconds(), tickSize)); 
    480535                    if (unit == "minute") 
    481                         d.setMinutes(floorInBase(d.getMinutes(), tickSize)); 
     536                        d.setUTCMinutes(floorInBase(d.getUTCMinutes(), tickSize)); 
    482537                    if (unit == "hour") 
    483                         d.setHours(floorInBase(d.getHours(), tickSize)); 
     538                        d.setUTCHours(floorInBase(d.getUTCHours(), tickSize)); 
    484539                    if (unit == "month") 
    485                         d.setMonth(floorInBase(d.getMonth(), tickSize)); 
     540                        d.setUTCMonth(floorInBase(d.getUTCMonth(), tickSize)); 
    486541                    if (unit == "year") 
    487                         d.setFullYear(floorInBase(d.getFullYear(), tickSize)); 
     542                        d.setUTCFullYear(floorInBase(d.getUTCFullYear(), tickSize)); 
    488543                     
    489544                    // reset smaller components 
    490                     d.setMilliseconds(0); 
     545                    d.setUTCMilliseconds(0); 
    491546                    if (step >= timeUnitSize.minute) 
    492                         d.setSeconds(0); 
     547                        d.setUTCSeconds(0); 
    493548                    if (step >= timeUnitSize.hour) 
    494                         d.setMinutes(0); 
     549                        d.setUTCMinutes(0); 
    495550                    if (step >= timeUnitSize.day) 
    496                         d.setHours(0); 
     551                        d.setUTCHours(0); 
    497552                    if (step >= timeUnitSize.day * 4) 
    498                         d.setDate(1); 
     553                        d.setUTCDate(1); 
    499554                    if (step >= timeUnitSize.year) 
    500                         d.setMonth(0); 
    501  
    502  
    503                     var carry = 0, v
     555                        d.setUTCMonth(0); 
     556 
     557 
     558                    var carry = 0, v = Number.NaN, prev
    504559                    do { 
     560                        prev = v; 
    505561                        v = d.getTime(); 
    506562                        ticks.push({ v: v, label: axis.tickFormatter(v, axis) }); 
     
    510566                                // up but we need to take care of fractions 
    511567                                // so we don't end up in the middle of a day 
    512                                 d.setDate(1); 
     568                                d.setUTCDate(1); 
    513569                                var start = d.getTime(); 
    514                                 d.setMonth(d.getMonth() + 1); 
     570                                d.setUTCMonth(d.getUTCMonth() + 1); 
    515571                                var end = d.getTime(); 
    516572                                d.setTime(v + carry * timeUnitSize.hour + (end - start) * tickSize); 
    517                                 carry = d.getHours(); 
    518                                 d.setHours(0); 
     573                                carry = d.getUTCHours(); 
     574                                d.setUTCHours(0); 
    519575                            } 
    520576                            else 
    521                                 d.setMonth(d.getMonth() + tickSize); 
     577                                d.setUTCMonth(d.getUTCMonth() + tickSize); 
    522578                        } 
    523579                        else if (unit == "year") { 
    524                             d.setFullYear(d.getFullYear() + tickSize); 
     580                            d.setUTCFullYear(d.getUTCFullYear() + tickSize); 
    525581                        } 
    526582                        else 
    527583                            d.setTime(v + step); 
    528                     } while (v < axis.max); 
     584                    } while (v < axis.max && v != prev); 
    529585 
    530586                    return ticks; 
     
    600656                generator = function (axis) { 
    601657                    var ticks = []; 
    602                     var start = floorInBase(axis.min, axis.tickSize); 
    603                     // then spew out all possible ticks 
    604                     var i = 0, v; 
     658 
     659                    // spew out all possible ticks 
     660                    var start = floorInBase(axis.min, axis.tickSize), 
     661                        i = 0, v = Number.NaN, prev; 
    605662                    do { 
     663                        prev = v; 
    606664                        v = start + i * axis.tickSize; 
    607665                        ticks.push({ v: v, label: axis.tickFormatter(v, axis) }); 
    608666                        ++i; 
    609                     } while (v < axis.max); 
     667                    } while (v < axis.max && v != prev); 
    610668                    return ticks; 
    611669                }; 
     
    622680            else 
    623681                axis.tickFormatter = formatter; 
    624         } 
    625          
    626         function extendXRangeIfNeededByBar() { 
    627             if (options.xaxis.max == null) { 
    628                 // great, we're autoscaling, check if we might need a bump 
    629  
    630                 var newmax = xaxis.max; 
    631                 for (var i = 0; i < series.length; ++i) 
    632                     if (series[i].bars.show && series[i].bars.barWidth + xaxis.datamax > newmax) 
    633                         newmax = xaxis.datamax + series[i].bars.barWidth; 
    634                 xaxis.max = newmax; 
    635             } 
    636         } 
    637  
     682            if (axisOptions.labelWidth != null) 
     683                axis.labelWidth = axisOptions.labelWidth; 
     684            if (axisOptions.labelHeight != null) 
     685                axis.labelHeight = axisOptions.labelHeight; 
     686        } 
     687         
    638688        function setTicks(axis, axisOptions) { 
    639689            axis.ticks = []; 
     690 
     691            if (!axis.used) 
     692                return; 
    640693             
    641694            if (axisOptions.ticks == null) 
     
    680733         
    681734        function setSpacing() { 
    682             // calculate y label dimensions 
    683             var i, labels = [], l; 
    684             for (i = 0; i < yaxis.ticks.length; ++i) { 
    685                 l = yaxis.ticks[i].label; 
    686                 if (l) 
    687                     labels.push('<div class="tickLabel">' + l + '</div>'); 
    688             } 
    689  
    690             if (labels.length > 0) { 
    691                 var dummyDiv = $('<div style="position:absolute;top:-10000px;font-size:smaller">' 
    692                                  + labels.join("") + '</div>').appendTo(target); 
    693                 yLabelMaxWidth = dummyDiv.width(); 
    694                 yLabelMaxHeight = dummyDiv.find("div").height(); 
    695                 dummyDiv.remove(); 
    696             } 
    697  
    698             var maxOutset = options.grid.borderWidth; 
    699             if (options.points.show) 
    700                 maxOutset = Math.max(maxOutset, options.points.radius + options.points.lineWidth/2); 
    701             for (i = 0; i < series.length; ++i) { 
    702                 if (series[i].points.show) 
    703                     maxOutset = Math.max(maxOutset, series[i].points.radius + series[i].points.lineWidth/2); 
    704             } 
     735            function measureXLabels(axis) { 
     736                // to avoid measuring the widths of the labels, we 
     737                // construct fixed-size boxes and put the labels inside 
     738                // them, we don't need the exact figures and the 
     739                // fixed-size box content is easy to center 
     740                if (axis.labelWidth == null) 
     741                    axis.labelWidth = canvasWidth / 6; 
     742 
     743                // measure x label heights 
     744                if (axis.labelHeight == null) { 
     745                    labels = []; 
     746                    for (i = 0; i < axis.ticks.length; ++i) { 
     747                        l = axis.ticks[i].label; 
     748                        if (l) 
     749                            labels.push('<div class="tickLabel" style="float:left;width:' + axis.labelWidth + 'px">' + l + '</div>'); 
     750                    } 
     751                     
     752                    axis.labelHeight = 0; 
     753                    if (labels.length > 0) { 
     754                        var dummyDiv = $('<div style="position:absolute;top:-10000px;width:10000px;font-size:smaller">' 
     755                                         + labels.join("") + '<div style="clear:left"></div></div>').appendTo(target); 
     756                        axis.labelHeight = dummyDiv.height(); 
     757                        dummyDiv.remove(); 
     758                    } 
     759                } 
     760            } 
     761             
     762            function measureYLabels(axis) { 
     763                if (axis.labelWidth == null || axis.labelHeight == null) { 
     764                    var i, labels = [], l; 
     765                    // calculate y label dimensions 
     766                    for (i = 0; i < axis.ticks.length; ++i) { 
     767                        l = axis.ticks[i].label; 
     768                        if (l) 
     769                            labels.push('<div class="tickLabel">' + l + '</div>'); 
     770                    } 
     771                     
     772                    if (labels.length > 0) { 
     773                        var dummyDiv = $('<div style="position:absolute;top:-10000px;font-size:smaller">' 
     774                                         + labels.join("") + '</div>').appendTo(target); 
     775                        if (axis.labelWidth == null) 
     776                            axis.labelWidth = dummyDiv.width(); 
     777                        if (axis.labelHeight == null) 
     778                            axis.labelHeight = dummyDiv.find("div").height(); 
     779                        dummyDiv.remove(); 
     780                    } 
     781                     
     782                    if (axis.labelWidth == null) 
     783                        axis.labelWidth = 0; 
     784                    if (axis.labelHeight == null) 
     785                        axis.labelHeight = 0; 
     786                } 
     787            } 
     788             
     789            measureXLabels(axes.xaxis); 
     790            measureYLabels(axes.yaxis); 
     791            measureXLabels(axes.x2axis); 
     792            measureYLabels(axes.y2axis); 
     793 
     794            // get the most space needed around the grid for things 
     795            // that may stick out 
     796            var maxOutset = options.grid.borderWidth / 2; 
     797            for (i = 0; i < series.length; ++i) 
     798                maxOutset = Math.max(maxOutset, 2 * (series[i].points.radius + series[i].points.lineWidth/2)); 
    705799 
    706800            plotOffset.left = plotOffset.right = plotOffset.top = plotOffset.bottom = maxOutset; 
    707              
    708             plotOffset.left += yLabelMaxWidth + options.grid.labelMargin; 
     801 
     802            if (axes.xaxis.labelHeight > 0) 
     803                plotOffset.bottom = Math.max(maxOutset, axes.xaxis.labelHeight + options.grid.labelMargin); 
     804            if (axes.yaxis.labelWidth > 0) 
     805                plotOffset.left = Math.max(maxOutset, axes.yaxis.labelWidth + options.grid.labelMargin); 
     806 
     807            if (axes.x2axis.labelHeight > 0) 
     808                plotOffset.top = Math.max(maxOutset, axes.x2axis.labelHeight + options.grid.labelMargin); 
     809             
     810            if (axes.y2axis.labelWidth > 0) 
     811                plotOffset.right = Math.max(maxOutset, axes.y2axis.labelWidth + options.grid.labelMargin); 
     812 
    709813            plotWidth = canvasWidth - plotOffset.left - plotOffset.right; 
    710  
    711             // set width for labels; to avoid measuring the widths of 
    712             // the labels, we construct fixed-size boxes and put the 
    713             // labels inside them, the fixed-size boxes are easy to 
    714             // mid-align 
    715             xLabelBoxWidth = plotWidth / 6; 
    716              
    717             // measure x label heights 
    718             labels = []; 
    719             for (i = 0; i < xaxis.ticks.length; ++i) { 
    720                 l = xaxis.ticks[i].label; 
    721                 if (l) 
    722                     labels.push('<span class="tickLabel" width="' + xLabelBoxWidth + '">' + l + '</span>'); 
    723             } 
    724  
    725             var xLabelMaxHeight = 0; 
    726             if (labels.length > 0) { 
    727                 var dummyDiv = $('<div style="position:absolute;top:-10000px;font-size:smaller">' 
    728                                  + labels.join("") + '</div>').appendTo(target); 
    729                 xLabelMaxHeight = dummyDiv.height(); 
    730                 dummyDiv.remove(); 
    731             } 
    732  
    733             plotOffset.bottom += xLabelMaxHeight + options.grid.labelMargin; 
    734814            plotHeight = canvasHeight - plotOffset.bottom - plotOffset.top; 
    735             hozScale = plotWidth / (xaxis.max - xaxis.min); 
    736             vertScale = plotHeight / (yaxis.max - yaxis.min); 
     815 
     816            // precompute how much the axis is scaling a point in canvas space 
     817            axes.xaxis.scale = plotWidth / (axes.xaxis.max - axes.xaxis.min); 
     818            axes.yaxis.scale = plotHeight / (axes.yaxis.max - axes.yaxis.min); 
     819            axes.x2axis.scale = plotWidth / (axes.x2axis.max - axes.x2axis.min); 
     820            axes.y2axis.scale = plotHeight / (axes.y2axis.max - axes.y2axis.min); 
    737821        } 
    738822         
     
    744828        } 
    745829 
    746         function tHoz(x) { 
    747             return (x - xaxis.min) * hozScale; 
    748         } 
    749  
    750         function tVert(y) { 
    751             return plotHeight - (y - yaxis.min) * vertScale; 
    752         } 
    753  
     830        function extractRange(ranges, coord) { 
     831            var firstAxis = coord + "axis", 
     832                secondaryAxis = coord + "2axis", 
     833                axis, from, to, reverse; 
     834 
     835            if (ranges[firstAxis]) { 
     836                axis = axes[firstAxis]; 
     837                from = ranges[firstAxis].from; 
     838                to = ranges[firstAxis].to; 
     839            } 
     840            else if (ranges[secondaryAxis]) { 
     841                axis = axes[secondaryAxis]; 
     842                from = ranges[secondaryAxis].from; 
     843                to = ranges[secondaryAxis].to; 
     844            } 
     845            else { 
     846                // backwards-compat stuff - to be removed in future 
     847                axis = axes[firstAxis]; 
     848                from = ranges[coord + "1"]; 
     849                to = ranges[coord + "2"]; 
     850            } 
     851 
     852            // auto-reverse as an added bonus 
     853            if (from != null && to != null && from > to) 
     854                return { from: to, to: from, axis: axis }; 
     855             
     856            return { from: from, to: to, axis: axis }; 
     857        } 
     858         
    754859        function drawGrid() { 
    755860            var i; 
     
    760865 
    761866            // draw background, if any 
    762             if (options.grid.backgroundColor != null) { 
     867            if (options.grid.backgroundColor) { 
    763868                ctx.fillStyle = options.grid.backgroundColor; 
    764869                ctx.fillRect(0, 0, plotWidth, plotHeight); 
    765870            } 
    766871 
    767             // draw colored areas 
    768             if (options.grid.coloredAreas) { 
    769                 var areas = options.grid.coloredAreas; 
    770                 if ($.isFunction(areas)) 
    771                     areas = areas({ xmin: xaxis.min, xmax: xaxis.max, ymin: yaxis.min, ymax: yaxis.max }); 
    772  
    773                 for (i = 0; i < areas.length; ++i) { 
    774                     var a = areas[i]; 
     872            // draw markings 
     873            if (options.grid.markings) { 
     874                var markings = options.grid.markings; 
     875                if ($.isFunction(markings)) 
     876                    // xmin etc. are backwards-compatible, to be removed in future 
     877                    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 }); 
     878 
     879                for (i = 0; i < markings.length; ++i) { 
     880                    var m = markings[i], 
     881                        xrange = extractRange(m, "x"), 
     882                        yrange = extractRange(m, "y"); 
     883 
     884                    // fill in missing 
     885                    if (xrange.from == null) 
     886                        xrange.from = xrange.axis.min; 
     887                    if (xrange.to == null) 
     888                        xrange.to = xrange.axis.max; 
     889                    if (yrange.from == null) 
     890                        yrange.from = yrange.axis.min; 
     891                    if (yrange.to == null) 
     892                        yrange.to = yrange.axis.max; 
    775893 
    776894                    // clip 
    777                     if (a.x1 == null || a.x1 < xaxis.min) 
    778                         a.x1 = xaxis.min; 
    779                     if (a.x2 == null || a.x2 > xaxis.max) 
    780                         a.x2 = xaxis.max; 
    781                     if (a.y1 == null || a.y1 < yaxis.min) 
    782                         a.y1 = yaxis.min; 
    783                     if (a.y2 == null || a.y2 > yaxis.max) 
    784                         a.y2 = yaxis.max; 
    785  
    786                     var tmp; 
    787                     if (a.x1 > a.x2) { 
    788                         tmp = a.x1; 
    789                         a.x1 = a.x2; 
    790                         a.x2 = tmp; 
    791                     } 
    792                     if (a.y1 > a.y2) { 
    793                         tmp = a.y1; 
    794                         a.y1 = a.y2; 
    795                         a.y2 = tmp; 
    796                     } 
    797  
    798                     if (a.x1 >= xaxis.max || a.x2 <= xaxis.min || a.x1 == a.x2 
    799                         || a.y1 >= yaxis.max || a.y2 <= yaxis.min || a.y1 == a.y2) 
     895                    if (xrange.to < xrange.axis.min || xrange.from > xrange.axis.max || 
     896                        yrange.to < yrange.axis.min || yrange.from > yrange.axis.max) 
    800897                        continue; 
    801898 
    802                     ctx.fillStyle = a.color || options.grid.coloredAreasColor; 
    803                     ctx.fillRect(Math.floor(tHoz(a.x1)), Math.floor(tVert(a.y2)), 
    804                                  Math.floor(tHoz(a.x2) - tHoz(a.x1)), Math.floor(tVert(a.y1) - tVert(a.y2))); 
     899                    xrange.from = Math.max(xrange.from, xrange.axis.min); 
     900                    xrange.to = Math.min(xrange.to, xrange.axis.max); 
     901                    yrange.from = Math.max(yrange.from, yrange.axis.min); 
     902                    yrange.to = Math.min(yrange.to, yrange.axis.max); 
     903 
     904                    if (xrange.from == xrange.to && yrange.from == yrange.to) 
     905                        continue; 
     906 
     907                    // then draw 
     908                    xrange.from = xrange.axis.p2c(xrange.from); 
     909                    xrange.to = xrange.axis.p2c(xrange.to); 
     910                    yrange.from = yrange.axis.p2c(yrange.from); 
     911                    yrange.to = yrange.axis.p2c(yrange.to); 
     912                     
     913                    if (xrange.from == xrange.to || yrange.from == yrange.to) { 
     914                        // draw line 
     915                        ctx.strokeStyle = m.color || options.grid.markingsColor; 
     916                        ctx.lineWidth = m.lineWidth || options.grid.markingsLineWidth; 
     917                        ctx.moveTo(Math.floor(xrange.from), Math.floor(yrange.from)); 
     918                        ctx.lineTo(Math.floor(xrange.to), Math.floor(yrange.to)); 
     919                        ctx.stroke(); 
     920                    } 
     921                    else { 
     922                        // fill area 
     923                        ctx.fillStyle = m.color || options.grid.markingsColor; 
     924                        ctx.fillRect(Math.floor(xrange.from), 
     925                                     Math.floor(yrange.to), 
     926                                     Math.floor(xrange.to - xrange.from), 
     927                                     Math.floor(yrange.from - yrange.to)); 
     928                    } 
    805929                } 
    806930            } 
     
    810934            ctx.strokeStyle = options.grid.tickColor; 
    811935            ctx.beginPath(); 
    812             var v
    813             for (i = 0; i < xaxis.ticks.length; ++i) { 
    814                 v = xaxis.ticks[i].v; 
    815                 if (v <= xaxis.min || v >= xaxis.max) 
     936            var v, axis = axes.xaxis
     937            for (i = 0; i < axis.ticks.length; ++i) { 
     938                v = axis.ticks[i].v; 
     939                if (v <= axis.min || v >= axes.xaxis.max) 
    816940                    continue;   // skip those lying on the axes 
    817941 
    818                 ctx.moveTo(Math.floor(tHoz(v)) + ctx.lineWidth/2, 0); 
    819                 ctx.lineTo(Math.floor(tHoz(v)) + ctx.lineWidth/2, plotHeight); 
    820             } 
    821  
    822             for (i = 0; i < yaxis.ticks.length; ++i) { 
    823                 v = yaxis.ticks[i].v; 
    824                 if (v <= yaxis.min || v >= yaxis.max) 
     942                ctx.moveTo(Math.floor(axis.p2c(v)) + ctx.lineWidth/2, 0); 
     943                ctx.lineTo(Math.floor(axis.p2c(v)) + ctx.lineWidth/2, plotHeight); 
     944            } 
     945 
     946            axis = axes.yaxis; 
     947            for (i = 0; i < axis.ticks.length; ++i) { 
     948                v = axis.ticks[i].v; 
     949                if (v <= axis.min || v >= axis.max) 
    825950                    continue; 
    826951 
    827                 ctx.moveTo(0, Math.floor(tVert(v)) + ctx.lineWidth/2); 
    828                 ctx.lineTo(plotWidth, Math.floor(tVert(v)) + ctx.lineWidth/2); 
    829             } 
     952                ctx.moveTo(0, Math.floor(axis.p2c(v)) + ctx.lineWidth/2); 
     953                ctx.lineTo(plotWidth, Math.floor(axis.p2c(v)) + ctx.lineWidth/2); 
     954            } 
     955 
     956            axis = axes.x2axis; 
     957            for (i = 0; i < axis.ticks.length; ++i) { 
     958                v = axis.ticks[i].v; 
     959                if (v <= axis.min || v >= axis.max) 
     960                    continue; 
     961     
     962                ctx.moveTo(Math.floor(axis.p2c(v)) + ctx.lineWidth/2, -5); 
     963                ctx.lineTo(Math.floor(axis.p2c(v)) + ctx.lineWidth/2, 5); 
     964            } 
     965 
     966            axis = axes.y2axis; 
     967            for (i = 0; i < axis.ticks.length; ++i) { 
     968                v = axis.ticks[i].v; 
     969                if (v <= axis.min || v >= axis.max) 
     970                    continue; 
     971 
     972                ctx.moveTo(plotWidth-5, Math.floor(axis.p2c(v)) + ctx.lineWidth/2); 
     973                ctx.lineTo(plotWidth+5, Math.floor(axis.p2c(v)) + ctx.lineWidth/2); 
     974            } 
     975             
    830976            ctx.stroke(); 
    831977             
     
    836982                ctx.lineJoin = "round"; 
    837983                ctx.strokeRect(0, 0, plotWidth, plotHeight); 
    838                 ctx.restore(); 
    839             } 
     984            } 
     985 
     986            ctx.restore(); 
    840987        } 
    841988         
     
    843990            target.find(".tickLabels").remove(); 
    844991             
    845             var i, tick; 
    846992            var html = '<div class="tickLabels" style="font-size:smaller;color:' + options.grid.color + '">'; 
    847              
    848             // do the x-axis 
    849             for (i = 0; i < xaxis.ticks.length; ++i) { 
    850                 tick = xaxis.ticks[i]; 
    851                 if (!tick.label || tick.v < xaxis.min || tick.v > xaxis.max) 
    852                     continue; 
    853                 html += '<div style="position:absolute;top:' + (plotOffset.top + plotHeight + options.grid.labelMargin) + 'px;left:' + (plotOffset.left + tHoz(tick.v) - xLabelBoxWidth/2) + 'px;width:' + xLabelBoxWidth + 'px;text-align:center" class="tickLabel">' + tick.label + "</div>"; 
    854             } 
    855              
    856             // do the y-axis 
    857             for (i = 0; i < yaxis.ticks.length; ++i) { 
    858                 tick = yaxis.ticks[i]; 
    859                 if (!tick.label || tick.v < yaxis.min || tick.v > yaxis.max) 
    860                     continue; 
    861                 html += '<div style="position:absolute;top:' + (plotOffset.top + tVert(tick.v) - yLabelMaxHeight/2) + 'px;left:0;width:' + yLabelMaxWidth + 'px;text-align:right" class="tickLabel">' + tick.label + "</div>"; 
    862             } 
     993 
     994            function addLabels(axis, labelGenerator) { 
     995                for (var i = 0; i < axis.ticks.length; ++i) { 
     996                    var tick = axis.ticks[i]; 
     997                    if (!tick.label || tick.v < axis.min || tick.v > axis.max) 
     998                        continue; 
     999                    html += labelGenerator(tick, axis); 
     1000                } 
     1001            } 
     1002             
     1003            addLabels(axes.xaxis, function (tick, axis) { 
     1004                return '<div style="position:absolute;top:' + (plotOffset.top + plotHeight + options.grid.labelMargin) + 'px;left:' + (plotOffset.left + axis.p2c(tick.v) - axis.labelWidth/2) + 'px;width:' + axis.labelWidth + 'px;text-align:center" class="tickLabel">' + tick.label + "</div>"; 
     1005            }); 
     1006             
     1007             
     1008            addLabels(axes.yaxis, function (tick, axis) { 
     1009                return '<div style="position:absolute;top:' + (plotOffset.top + axis.p2c(tick.v) - axis.labelHeight/2) + 'px;right:' + (plotOffset.right + plotWidth + options.grid.labelMargin) + 'px;width:' + axis.labelWidth + 'px;text-align:right" class="tickLabel">' + tick.label + "</div>"; 
     1010            }); 
     1011             
     1012            addLabels(axes.x2axis, function (tick, axis) { 
     1013                return '<div style="position:absolute;bottom:' + (plotOffset.bottom + plotHeight + options.grid.labelMargin) + 'px;left:' + (plotOffset.left + axis.p2c(tick.v) - axis.labelWidth/2) + 'px;width:' + axis.labelWidth + 'px;text-align:center" class="tickLabel">' + tick.label + "</div>"; 
     1014            }); 
     1015             
     1016            addLabels(axes.y2axis, function (tick, axis) { 
     1017                return '<div style="position:absolute;top:' + (plotOffset.top + axis.p2c(tick.v) - axis.labelHeight/2) + 'px;left:' + (plotOffset.left + plotWidth + options.grid.labelMargin) +'px;width:' + axis.labelWidth + 'px;text-align:left" class="tickLabel">' + tick.label + "</div>"; 
     1018            }); 
    8631019 
    8641020            html += '</div>'; 
     
    8771033         
    8781034        function drawSeriesLines(series) { 
    879             function plotLine(data, offset) { 
     1035            function plotLine(data, offset, axisx, axisy) { 
    8801036                var prev, cur = null, drawx = null, drawy = null; 
    8811037                 
     
    8921048 
    8931049                    // clip with ymin 
    894                     if (y1 <= y2 && y1 < yaxis.min) { 
    895                         if (y2 < yaxis.min) 
     1050                    if (y1 <= y2 && y1 < axisy.min) { 
     1051                        if (y2 < axisy.min) 
    8961052                            continue;   // line segment is outside 
    8971053                        // compute new intersection point 
    898                         x1 = (yaxis.min - y1) / (y2 - y1) * (x2 - x1) + x1; 
    899                         y1 = yaxis.min; 
    900                     } 
    901                     else if (y2 <= y1 && y2 < yaxis.min) { 
    902                         if (y1 < yaxis.min) 
     1054                        x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; 
     1055                        y1 = axisy.min; 
     1056                    } 
     1057                    else if (y2 <= y1 && y2 < axisy.min) { 
     1058                        if (y1 < axisy.min) 
    9031059                            continue; 
    904                         x2 = (yaxis.min - y1) / (y2 - y1) * (x2 - x1) + x1; 
    905                         y2 = yaxis.min; 
     1060                        x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; 
     1061                        y2 = axisy.min; 
    9061062                    } 
    9071063 
    9081064                    // clip with ymax 
    909                     if (y1 >= y2 && y1 > yaxis.max) { 
    910                         if (y2 > yaxis.max) 
     1065                    if (y1 >= y2 && y1 > axisy.max) { 
     1066                        if (y2 > axisy.max) 
    9111067                            continue; 
    912                         x1 = (yaxis.max - y1) / (y2 - y1) * (x2 - x1) + x1; 
    913                         y1 = yaxis.max; 
    914                     } 
    915                     else if (y2 >= y1 && y2 > yaxis.max) { 
    916                         if (y1 > yaxis.max) 
     1068                        x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; 
     1069                        y1 = axisy.max; 
     1070                    } 
     1071                    else if (y2 >= y1 && y2 > axisy.max) { 
     1072                        if (y1 > axisy.max) 
    9171073                            continue; 
    918                         x2 = (yaxis.max - y1) / (y2 - y1) * (x2 - x1) + x1; 
    919                         y2 = yaxis.max; 
     1074                        x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; 
     1075                        y2 = axisy.max; 
    9201076                    } 
    9211077 
    9221078                    // clip with xmin 
    923                     if (x1 <= x2 && x1 < xaxis.min) { 
    924                         if (x2 < xaxis.min) 
     1079                    if (x1 <= x2 && x1 < axisx.min) { 
     1080                        if (x2 < axisx.min) 
    9251081                            continue; 
    926                         y1 = (xaxis.min - x1) / (x2 - x1) * (y2 - y1) + y1; 
    927                         x1 = xaxis.min; 
    928                     } 
    929                     else if (x2 <= x1 && x2 < xaxis.min) { 
    930                         if (x1 < xaxis.min) 
     1082                        y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; 
     1083                        x1 = axisx.min; 
     1084                    } 
     1085                    else if (x2 <= x1 && x2 < axisx.min) { 
     1086                        if (x1 < axisx.min) 
    9311087                            continue; 
    932                         y2 = (xaxis.min - x1) / (x2 - x1) * (y2 - y1) + y1; 
    933                         x2 = xaxis.min; 
     1088                        y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; 
     1089                        x2 = axisx.min; 
    9341090                    } 
    9351091 
    9361092                    // clip with xmax 
    937                     if (x1 >= x2 && x1 > xaxis.max) { 
    938                         if (x2 > xaxis.max) 
     1093                    if (x1 >= x2 && x1 > axisx.max) { 
     1094                        if (x2 > axisx.max) 
    9391095                            continue; 
    940                         y1 = (xaxis.max - x1) / (x2 - x1) * (y2 - y1) + y1; 
    941                         x1 = xaxis.max; 
    942                     } 
    943                     else if (x2 >= x1 && x2 > xaxis.max) { 
    944                         if (x1 > xaxis.max) 
     1096                        y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; 
     1097                        x1 = axisx.max; 
     1098                    } 
     1099                    else if (x2 >= x1 && x2 > axisx.max) { 
     1100                        if (x1 > axisx.max) 
    9451101                            continue; 
    946                         y2 = (xaxis.max - x1) / (x2 - x1) * (y2 - y1) + y1; 
    947                         x2 = xaxis.max; 
    948                     } 
    949  
    950                     if (drawx != tHoz(x1) || drawy != tVert(y1) + offset) 
    951                         ctx.moveTo(tHoz(x1), tVert(y1) + offset); 
    952                      
    953                     drawx = tHoz(x2); 
    954                     drawy = tVert(y2) + offset; 
     1102                        y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; 
     1103                        x2 = axisx.max; 
     1104                    } 
     1105 
     1106                    if (drawx != axisx.p2c(x1) || drawy != axisy.p2c(y1) + offset) 
     1107                        ctx.moveTo(axisx.p2c(x1), axisy.p2c(y1) + offset); 
     1108                     
     1109                    drawx = axisx.p2c(x2); 
     1110                    drawy = axisy.p2c(y2) + offset; 
    9551111                    ctx.lineTo(drawx, drawy); 
    9561112                } 
     
    9581114            } 
    9591115 
    960             function plotLineArea(data) { 
     1116            function plotLineArea(data, axisx, axisy) { 
    9611117                var prev, cur = null; 
    9621118                 
    963                 var bottom = Math.min(Math.max(0, yaxis.min), yaxis.max); 
     1119                var bottom = Math.min(Math.max(0, axisy.min), axisy.max); 
    9641120                var top, lastX = 0; 
    9651121 
     
    9721128                    if (areaOpen && prev != null && cur == null) { 
    9731129                        // close area 
    974                         ctx.lineTo(tHoz(lastX), tVert(bottom)); 
     1130                        ctx.lineTo(axisx.p2c(lastX), axisy.p2c(bottom)); 
    9751131                        ctx.fill(); 
    9761132                        areaOpen = false; 
     
    9871143                     
    9881144                    // clip with xmin 
    989                     if (x1 <= x2 && x1 < xaxis.min) { 
    990                         if (x2 < xaxis.min) 
     1145                    if (x1 <= x2 && x1 < axisx.min) { 
     1146                        if (x2 < axisx.min) 
    9911147                            continue; 
    992                         y1 = (xaxis.min - x1) / (x2 - x1) * (y2 - y1) + y1; 
    993                         x1 = xaxis.min; 
    994                     } 
    995                     else if (x2 <= x1 && x2 < xaxis.min) { 
    996                         if (x1 < xaxis.min) 
     1148                        y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; 
     1149                        x1 = axisx.min; 
     1150                    } 
     1151                    else if (x2 <= x1 && x2 < axisx.min) { 
     1152                        if (x1 < axisx.min) 
    9971153                            continue; 
    998                         y2 = (xaxis.min - x1) / (x2 - x1) * (y2 - y1) + y1; 
    999                         x2 = xaxis.min; 
     1154                        y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; 
     1155                        x2 = axisx.min; 
    10001156                    } 
    10011157 
    10021158                    // clip with xmax 
    1003                     if (x1 >= x2 && x1 > xaxis.max) { 
    1004                         if (x2 > xaxis.max) 
     1159                    if (x1 >= x2 && x1 > axisx.max) { 
     1160                        if (x2 > axisx.max) 
    10051161                            continue; 
    1006                         y1 = (xaxis.max - x1) / (x2 - x1) * (y2 - y1) + y1; 
    1007                         x1 = xaxis.max; 
    1008                     } 
    1009                     else if (x2 >= x1 && x2 > xaxis.max) { 
    1010                         if (x1 > xaxis.max) 
     1162                        y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; 
     1163                        x1 = axisx.max; 
     1164                    } 
     1165                    else if (x2 >= x1 && x2 > axisx.max) { 
     1166                        if (x1 > axisx.max) 
    10111167                            continue; 
    1012                         y2 = (xaxis.max - x1) / (x2 - x1) * (y2 - y1) + y1; 
    1013                         x2 = xaxis.max; 
     1168                        y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; 
     1169                        x2 = axisx.max; 
    10141170                    } 
    10151171 
     
    10171173                        // open area 
    10181174                        ctx.beginPath(); 
    1019                         ctx.moveTo(tHoz(x1), tVert(bottom)); 
     1175                        ctx.moveTo(axisx.p2c(x1), axisy.p2c(bottom)); 
    10201176                        areaOpen = true; 
    10211177                    } 
    10221178                     
    10231179                    // now first check the case where both is outside 
    1024                     if (y1 >= yaxis.max && y2 >= yaxis.max) { 
    1025                         ctx.lineTo(tHoz(x1), tVert(yaxis.max)); 
    1026                         ctx.lineTo(tHoz(x2), tVert(yaxis.max)); 
     1180                    if (y1 >= axisy.max && y2 >= axisy.max) { 
     1181                        ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.max)); 
     1182                        ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.max)); 
    10271183                        continue; 
    10281184                    } 
    1029                     else if (y1 <= yaxis.min && y2 <= yaxis.min) { 
    1030                         ctx.lineTo(tHoz(x1), tVert(yaxis.min)); 
    1031                         ctx.lineTo(tHoz(x2), tVert(yaxis.min)); 
     1185                    else if (y1 <= axisy.min && y2 <= axisy.min) { 
     1186                        ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.min)); 
     1187                        ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.min)); 
    10321188                        continue; 
    10331189                    } 
     
    10411197                     
    10421198                    // clip with ymin 
    1043                     if (y1 <= y2 && y1 < yaxis.min && y2 >= yaxis.min) { 
    1044                         x1 = (yaxis.min - y1) / (y2 - y1) * (x2 - x1) + x1; 
    1045                         y1 = yaxis.min; 
    1046                     } 
    1047                     else if (y2 <= y1 && y2 < yaxis.min && y1 >= yaxis.min) { 
    1048                         x2 = (yaxis.min - y1) / (y2 - y1) * (x2 - x1) + x1; 
    1049                         y2 = yaxis.min; 
     1199                    if (y1 <= y2 && y1 < axisy.min && y2 >= axisy.min) { 
     1200                        x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; 
     1201                        y1 = axisy.min; 
     1202                    } 
     1203                    else if (y2 <= y1 && y2 < axisy.min && y1 >= axisy.min) { 
     1204                        x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; 
     1205                        y2 = axisy.min; 
    10501206                    } 
    10511207 
    10521208                    // clip with ymax 
    1053                     if (y1 >= y2 && y1 > yaxis.max && y2 <= yaxis.max) { 
    1054                         x1 = (yaxis.max - y1) / (y2 - y1) * (x2 - x1) + x1; 
    1055                         y1 = yaxis.max; 
    1056                     } 
    1057                     else if (y2 >= y1 && y2 > yaxis.max && y1 <= yaxis.max) { 
    1058                         x2 = (yaxis.max - y1) / (y2 - y1) * (x2 - x1) + x1; 
    1059                         y2 = yaxis.max; 
     1209                    if (y1 >= y2 && y1 > axisy.max && y2 <= axisy.max) { 
     1210                        x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; 
     1211                        y1 = axisy.max; 
     1212                    } 
     1213                    else if (y2 >= y1 && y2 > axisy.max && y1 <= axisy.max) { 
     1214                        x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; 
     1215                        y2 = axisy.max; 
    10601216                    } 
    10611217 
     
    10641220                    // to fill 
    10651221                    if (x1 != x1old) { 
    1066                         if (y1 <= yaxis.min) 
    1067                             top = yaxis.min; 
     1222                        if (y1 <= axisy.min) 
     1223                            top = axisy.min; 
    10681224                        else 
    1069                             top = yaxis.max; 
     1225                            top = axisy.max; 
    10701226                         
    1071                         ctx.lineTo(tHoz(x1old), tVert(top)); 
    1072                         ctx.lineTo(tHoz(x1), tVert(top)); 
     1227                        ctx.lineTo(axisx.p2c(x1old), axisy.p2c(top)); 
     1228                        ctx.lineTo(axisx.p2c(x1), axisy.p2c(top)); 
    10731229                    } 
    10741230                     
    10751231                    // fill the triangles 
    1076                     ctx.lineTo(tHoz(x1), tVert(y1)); 
    1077                     ctx.lineTo(tHoz(x2), tVert(y2)); 
     1232                    ctx.lineTo(axisx.p2c(x1), axisy.p2c(y1)); 
     1233                    ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2)); 
    10781234 
    10791235                    // fill the other rectangle if it's there 
    10801236                    if (x2 != x2old) { 
    1081                         if (y2 <= yaxis.min) 
    1082                             top = yaxis.min; 
     1237                        if (y2 <= axisy.min) 
     1238                            top = axisy.min; 
    10831239                        else 
    1084                             top = yaxis.max; 
     1240                            top = axisy.max; 
    10851241                         
    1086                         ctx.lineTo(tHoz(x2old), tVert(top)); 
    1087                         ctx.lineTo(tHoz(x2), tVert(top)); 
     1242                        ctx.lineTo(axisx.p2c(x2old), axisy.p2c(top)); 
     1243                        ctx.lineTo(axisx.p2c(x2), axisy.p2c(top)); 
    10881244                    } 
    10891245 
     
    10921248 
    10931249                if (areaOpen) { 
    1094                     ctx.lineTo(tHoz(lastX), tVert(bottom)); 
     1250                    ctx.lineTo(axisx.p2c(lastX), axisy.p2c(bottom)); 
    10951251                    ctx.fill(); 
    10961252                } 
     
    11081264                ctx.lineWidth = sw / 2; 
    11091265                ctx.strokeStyle = "rgba(0,0,0,0.1)"; 
    1110                 plotLine(series.data, lw/2 + sw/2 + ctx.lineWidth/2); 
     1266                plotLine(series.data, lw/2 + sw/2 + ctx.lineWidth/2, series.xaxis, series.yaxis); 
    11111267 
    11121268                ctx.lineWidth = sw / 2; 
    11131269                ctx.strokeStyle = "rgba(0,0,0,0.2)"; 
    1114                 plotLine(series.data, lw/2 + ctx.lineWidth/2); 
     1270                plotLine(series.data, lw/2 + ctx.lineWidth/2, series.xaxis, series.yaxis); 
    11151271            } 
    11161272 
    11171273            ctx.lineWidth = lw; 
    11181274            ctx.strokeStyle = series.color; 
    1119             if (series.lines.fill) { 
    1120                 ctx.fillStyle = series.lines.fillColor != null ? series.lines.fillColor : parseColor(series.color).scale(null, null, null, 0.4).toString(); 
    1121                 plotLineArea(series.data, 0); 
    1122             } 
    1123  
    1124             plotLine(series.data, 0); 
     1275            setFillStyle(series.lines, series.color); 
     1276            if (series.lines.fill) 
     1277                plotLineArea(series.data, series.xaxis, series.yaxis); 
     1278            plotLine(series.data, 0, series.xaxis, series.yaxis); 
    11251279            ctx.restore(); 
    11261280        } 
    11271281 
    11281282        function drawSeriesPoints(series) { 
    1129             function plotPoints(data, radius, fill) { 
     1283            function plotPoints(data, radius, fill, axisx, axisy) { 
    11301284                for (var i = 0; i < data.length; ++i) { 
    11311285                    if (data[i] == null) 
     
    11331287                     
    11341288                    var x = data[i][0], y = data[i][1]; 
    1135                     if (x < xaxis.min || x > xaxis.max || y < yaxis.min || y > yaxis.max) 
     1289                    if (x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max) 
    11361290                        continue; 
    11371291                     
    11381292                    ctx.beginPath(); 
    1139                     ctx.arc(tHoz(x), tVert(y), radius, 0, 2 * Math.PI, true); 
     1293                    ctx.arc(axisx.p2c(x), axisy.p2c(y), radius, 0, 2 * Math.PI, true); 
    11401294                    if (fill) 
    11411295                        ctx.fill(); 
     
    11441298            } 
    11451299 
    1146             function plotPointShadows(data, offset, radius) { 
     1300            function plotPointShadows(data, offset, radius, axisx, axisy) { 
    11471301                for (var i = 0; i < data.length; ++i) { 
    11481302                    if (data[i] == null) 
     
    11501304                     
    11511305                    var x = data[i][0], y = data[i][1]; 
    1152                     if (x < xaxis.min || x > xaxis.max || y < yaxis.min || y > yaxis.max) 
     1306                    if (x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max) 
    11531307                        continue; 
    11541308                    ctx.beginPath(); 
    1155                     ctx.arc(tHoz(x), tVert(y) + offset, radius, 0, Math.PI, false); 
     1309                    ctx.arc(axisx.p2c(x), axisy.p2c(y) + offset, radius, 0, Math.PI, false); 
    11561310                    ctx.stroke(); 
    11571311                } 
     
    11671321                ctx.lineWidth = sw / 2; 
    11681322                ctx.strokeStyle = "rgba(0,0,0,0.1)"; 
    1169                 plotPointShadows(series.data, sw/2 + ctx.lineWidth/2, series.points.radius); 
     1323                plotPointShadows(series.data, sw/2 + ctx.lineWidth/2, 
     1324                                 series.points.radius, series.xaxis, series.yaxis); 
    11701325 
    11711326                ctx.lineWidth = sw / 2; 
    11721327                ctx.strokeStyle = "rgba(0,0,0,0.2)"; 
    1173                 plotPointShadows(series.data, ctx.lineWidth/2, series.points.radius); 
     1328                plotPointShadows(series.data, ctx.lineWidth/2, 
     1329                                 series.points.radius, series.xaxis, series.yaxis); 
    11741330            } 
    11751331 
    11761332            ctx.lineWidth = series.points.lineWidth; 
    11771333            ctx.strokeStyle = series.color; 
    1178             ctx.fillStyle = series.points.fillColor != null ? series.points.fillColor : series.color; 
    1179             plotPoints(series.data, series.points.radius, series.points.fill); 
     1334            setFillStyle(series.points, series.color); 
     1335            plotPoints(series.data, series.points.radius, series.points.fill, 
     1336                       series.xaxis, series.yaxis); 
    11801337            ctx.restore(); 
    11811338        } 
    11821339 
     1340        function drawBar(x, y, barLeft, barRight, offset, fill, axisx, axisy, c) { 
     1341            var drawLeft = true, drawRight = true, 
     1342                drawTop = true, drawBottom = false, 
     1343                left = x + barLeft, right = x + barRight, 
     1344                bottom = 0, top = y; 
     1345 
     1346            // account for negative bars 
     1347            if (top < bottom) { 
     1348                top = 0; 
     1349                bottom = y; 
     1350                drawBottom = true; 
     1351                drawTop = false; 
     1352            } 
     1353             
     1354            // clip 
     1355            if (right < axisx.min || left > axisx.max || 
     1356                top < axisy.min || bottom > axisy.max) 
     1357                return; 
     1358             
     1359            if (left < axisx.min) { 
     1360                left = axisx.min; 
     1361                drawLeft = false; 
     1362            } 
     1363 
     1364            if (right > axisx.max) { 
     1365                right = axisx.max; 
     1366                drawRight = false; 
     1367            } 
     1368 
     1369            if (bottom < axisy.min) { 
     1370                bottom = axisy.min; 
     1371                drawBottom = false; 
     1372            } 
     1373             
     1374            if (top > axisy.max) { 
     1375                top = axisy.max; 
     1376                drawTop = false; 
     1377            } 
     1378 
     1379            // fill the bar 
     1380            if (fill) { 
     1381                c.beginPath(); 
     1382                c.moveTo(axisx.p2c(left), axisy.p2c(bottom) + offset); 
     1383                c.lineTo(axisx.p2c(left), axisy.p2c(top) + offset); 
     1384                c.lineTo(axisx.p2c(right), axisy.p2c(top) + offset); 
     1385                c.lineTo(axisx.p2c(right), axisy.p2c(bottom) + offset); 
     1386                c.fill(); 
     1387            } 
     1388 
     1389            // draw outline 
     1390            if (drawLeft || drawRight || drawTop || drawBottom) { 
     1391                c.beginPath(); 
     1392                left = axisx.p2c(left); 
     1393                bottom = axisy.p2c(bottom); 
     1394                right = axisx.p2c(right); 
     1395                top = axisy.p2c(top); 
     1396                 
     1397                c.moveTo(left, bottom + offset); 
     1398                if (drawLeft) 
     1399                    c.lineTo(left, top + offset); 
     1400                else 
     1401                    c.moveTo(left, top + offset); 
     1402                if (drawTop) 
     1403                    c.lineTo(right, top + offset); 
     1404                else 
     1405                    c.moveTo(right, top + offset); 
     1406                if (drawRight) 
     1407                    c.lineTo(right, bottom + offset); 
     1408                else 
     1409                    c.moveTo(right, bottom + offset); 
     1410                if (drawBottom) 
     1411                    c.lineTo(left, bottom + offset); 
     1412                else 
     1413                    c.moveTo(left, bottom + offset); 
     1414                c.stroke(); 
     1415            } 
     1416        } 
     1417         
    11831418        function drawSeriesBars(series) { 
    1184             function plotBars(data, barWidth, offset, fill) { 
     1419            function plotBars(data, barLeft, barRight, offset, fill, axisx, axisy) { 
    11851420                for (var i = 0; i < data.length; i++) { 
    11861421                    if (data[i] == null) 
    11871422                        continue; 
    1188                      
    1189                     var x = data[i][0], y = data[i][1]; 
    1190                     var drawLeft = true, drawTop = true, drawRight = true; 
    1191                     var left = x, right = x + barWidth, bottom = 0, top = y; 
    1192  
    1193                     if (right < xaxis.min || left > xaxis.max || top < yaxis.min || bottom > yaxis.max) 
    1194                         continue; 
    1195  
    1196                     // clip 
    1197                     if (left < xaxis.min) { 
    1198                         left = xaxis.min; 
    1199                         drawLeft = false; 
    1200                     } 
    1201  
    1202                     if (right > xaxis.max) { 
    1203                         right = xaxis.max; 
    1204                         drawRight = false; 
    1205                     } 
    1206  
    1207                     if (bottom < yaxis.min) 
    1208                         bottom = yaxis.min; 
    1209  
    1210                     if (top > yaxis.max) { 
    1211                         top = yaxis.max; 
    1212                         drawTop = false; 
    1213                     } 
    1214  
    1215                     // fill the bar 
    1216                     if (fill) { 
    1217                         ctx.beginPath(); 
    1218                         ctx.moveTo(tHoz(left), tVert(bottom) + offset); 
    1219                         ctx.lineTo(tHoz(left), tVert(top) + offset); 
    1220                         ctx.lineTo(tHoz(right), tVert(top) + offset); 
    1221                         ctx.lineTo(tHoz(right), tVert(bottom) + offset); 
    1222                         ctx.fill(); 
    1223                     } 
    1224  
    1225                     // draw outline 
    1226                     if (drawLeft || drawRight || drawTop) { 
    1227                         ctx.beginPath(); 
    1228                         ctx.moveTo(tHoz(left), tVert(bottom) + offset); 
    1229                         if (drawLeft) 
    1230                             ctx.lineTo(tHoz(left), tVert(top) + offset); 
    1231                         else 
    1232                             ctx.moveTo(tHoz(left), tVert(top) + offset); 
    1233  
    1234                         if (drawTop) 
    1235                             ctx.lineTo(tHoz(right), tVert(top) + offset); 
    1236                         else 
    1237                             ctx.moveTo(tHoz(right), tVert(top) + offset); 
    1238                         if (drawRight) 
    1239                             ctx.lineTo(tHoz(right), tVert(bottom) + offset); 
    1240                         else 
    1241                             ctx.moveTo(tHoz(right), tVert(bottom) + offset); 
    1242                         ctx.stroke(); 
    1243                     } 
     1423                    drawBar(data[i][0], data[i][1], barLeft, barRight, offset, fill, axisx, axisy, ctx); 
    12441424                } 
    12451425            } 
     
    12491429            ctx.lineJoin = "round"; 
    12501430 
    1251             var bw = series.bars.barWidth; 
    1252             var lw = Math.min(series.bars.lineWidth, bw); 
    12531431            // FIXME: figure out a way to add shadows 
    12541432            /* 
     1433            var bw = series.bars.barWidth; 
     1434            var lw = series.bars.lineWidth; 
    12551435            var sw = series.shadowSize; 
    12561436            if (sw > 0) { 
     
    12651445            }*/ 
    12661446 
    1267             ctx.lineWidth = lw
     1447            ctx.lineWidth = series.bars.lineWidth
    12681448            ctx.strokeStyle = series.color; 
    1269             if (series.bars.fill) { 
    1270                 ctx.fillStyle = series.bars.fillColor != null ? series.bars.fillColor : parseColor(series.color).scale(null, null, null, 0.4).toString(); 
    1271             } 
    1272  
    1273             plotBars(series.data, bw, 0, series.bars.fill); 
     1449            setFillStyle(series.bars, series.color); 
     1450            var barLeft = series.bars.align == "left" ? 0 : -series.bars.barWidth/2; 
     1451            plotBars(series.data, barLeft, barLeft + series.bars.barWidth, 0, series.bars.fill, series.xaxis, series.yaxis); 
    12741452            ctx.restore(); 
    12751453        } 
    12761454 
     1455        function setFillStyle(obj, seriesColor) { 
     1456            var fill = obj.fill; 
     1457            if (!fill) 
     1458                return; 
     1459             
     1460            if (obj.fillColor) 
     1461                ctx.fillStyle = obj.fillColor; 
     1462            else { 
     1463                var c = parseColor(seriesColor); 
     1464                c.a = typeof fill == "number" ? fill : 0.4; 
     1465                c.normalize(); 
     1466                ctx.fillStyle = c.toString(); 
     1467            } 
     1468        } 
     1469         
    12771470        function insertLegend() { 
    12781471            target.find(".legend").remove(); 
     
    13051498                fragments.push('</tr>'); 
    13061499             
    1307             if (fragments.length > 0) { 
    1308                 var table = '<table style="font-size:smaller;color:' + options.grid.color + '">' + fragments.join("") + '</table>'; 
    1309                 if (options.legend.container != null) 
    1310                     options.legend.container.append(table); 
    1311                 else { 
    1312                     var pos = ""; 
    1313                     var p = options.legend.position, m = options.legend.margin; 
    1314                     if (p.charAt(0) == "n") 
    1315                         pos += 'top:' + (m + plotOffset.top) + 'px;'; 
    1316                     else if (p.charAt(0) == "s") 
    1317                         pos += 'bottom:' + (m + plotOffset.bottom) + 'px;'; 
    1318                     if (p.charAt(1) == "e") 
    1319                         pos += 'right:' + (m + plotOffset.right) + 'px;'; 
    1320                     else if (p.charAt(1) == "w") 
    1321                         pos += 'left:' + (m + plotOffset.bottom) + 'px;'; 
    1322                     var legend = $('<div class="legend">' + table.replace('style="', 'style="position:absolute;' + pos +';') + '</div>').appendTo(target); 
    1323                     if (options.legend.backgroundOpacity != 0.0) { 
    1324                         // put in the transparent background 
    1325                         // separately to avoid blended labels and 
    1326                         // label boxes 
    1327                         var c = options.legend.backgroundColor; 
    1328                         if (c == null) { 
    1329                             var tmp; 
    1330                             if (options.grid.backgroundColor != null) 
    1331                                 tmp = options.grid.backgroundColor; 
    1332                             else 
    1333                                 tmp = extractColor(legend); 
    1334                             c = parseColor(tmp).adjust(null, null, null, 1).toString(); 
     1500            if (fragments.length == 0) 
     1501                return; 
     1502 
     1503            var table = '<table style="font-size:smaller;color:' + options.grid.color + '">' + fragments.join("") + '</table>'; 
     1504            if (options.legend.container != null) 
     1505                options.legend.container.html(table); 
     1506            else { 
     1507                var pos = ""; 
     1508                var p = options.legend.position, m = options.legend.margin; 
     1509                if (p.charAt(0) == "n") 
     1510                    pos += 'top:' + (m + plotOffset.top) + 'px;'; 
     1511                else if (p.charAt(0) == "s") 
     1512                    pos += 'bottom:' + (m + plotOffset.bottom) + 'px;'; 
     1513                if (p.charAt(1) == "e") 
     1514                    pos += 'right:' + (m + plotOffset.right) + 'px;'; 
     1515                else if (p.charAt(1) == "w") 
     1516                    pos += 'left:' + (m + plotOffset.left) + 'px;'; 
     1517                var legend = $('<div class="legend">' + table.replace('style="', 'style="position:absolute;' + pos +';') + '</div>').appendTo(target); 
     1518                if (options.legend.backgroundOpacity != 0.0) { 
     1519                    // put in the transparent background 
     1520                    // separately to avoid blended labels and 
     1521                    // label boxes 
     1522                    var c = options.legend.backgroundColor; 
     1523                    if (c == null) { 
     1524                        var tmp; 
     1525                        if (options.grid.backgroundColor) 
     1526                            tmp = options.grid.backgroundColor; 
     1527                        else 
     1528                            tmp = extractColor(legend); 
     1529                        c = parseColor(tmp).adjust(null, null, null, 1).toString(); 
     1530                    } 
     1531                    var div = legend.children(); 
     1532                    $('<div style="position:absolute;width:' + div.width() + 'px;height:' + div.height() + 'px;' + pos +'background-color:' + c + ';"> </div>').prependTo(legend).css('opacity', options.legend.backgroundOpacity); 
     1533                     
     1534                } 
     1535            } 
     1536        } 
     1537 
     1538 
     1539        // interactive features 
     1540         
     1541        var lastMousePos = { pageX: null, pageY: null }, 
     1542            selection = { 
     1543                first: { x: -1, y: -1}, second: { x: -1, y: -1}, 
     1544                show: false, active: false }, 
     1545            highlights = [], 
     1546            clickIsMouseUp = false, 
     1547            redrawTimeout = null, 
     1548            hoverTimeout = null; 
     1549         
     1550        // Returns the data item the mouse is over, or null if none is found 
     1551        function findNearbyItem(mouseX, mouseY) { 
     1552            var maxDistance = options.grid.mouseActiveRadius, 
     1553                lowestDistance = maxDistance * maxDistance + 1, 
     1554                item = null, foundPoint = false; 
     1555 
     1556            function result(i, j) { 
     1557                return { datapoint: series[i].data[j], 
     1558                         dataIndex: j, 
     1559                         series: series[i], 
     1560                         seriesIndex: i }; 
     1561            } 
     1562             
     1563            for (var i = 0; i < series.length; ++i) { 
     1564                var data = series[i].data, 
     1565                    axisx = series[i].xaxis, 
     1566                    axisy = series[i].yaxis, 
     1567                 
     1568                    // precompute some stuff to make the loop faster 
     1569                    mx = axisx.c2p(mouseX), 
     1570                    my = axisy.c2p(mouseY), 
     1571                    maxx = maxDistance / axisx.scale, 
     1572                    maxy = maxDistance / axisy.scale, 
     1573                    checkbar = series[i].bars.show, 
     1574                    checkpoint = !(series[i].bars.show && !(series[i].lines.show || series[i].points.show)), 
     1575                    barLeft = series[i].bars.align == "left" ? 0 : -series[i].bars.barWidth/2, 
     1576                    barRight = barLeft + series[i].bars.barWidth; 
     1577                for (var j = 0; j < data.length; ++j) { 
     1578                    if (data[j] == null) 
     1579                        continue; 
     1580 
     1581                    var x = data[j][0], y = data[j][1]; 
     1582   
     1583                    if (checkbar) { 
     1584                        // For a bar graph, the cursor must be inside the bar 
     1585                        // and no other point can be nearby 
     1586                        if (!foundPoint && mx >= x + barLeft && 
     1587                            mx <= x + barRight && 
     1588                            my >= Math.min(0, y) && my <= Math.max(0, y)) 
     1589                            item = result(i, j); 
     1590                    } 
     1591  
     1592                    if (checkpoint) { 
     1593                        // For points and lines, the cursor must be within a 
     1594                        // certain distance to the data point 
     1595  
     1596                        // check bounding box first 
     1597                        if ((x - mx > maxx || x - mx < -maxx) || 
     1598                            (y - my > maxy || y - my < -maxy)) 
     1599                            continue; 
     1600 
     1601                        // We have to calculate distances in pixels, not in 
     1602                        // data units, because the scale of the axes may be different 
     1603                        var dx = Math.abs(axisx.p2c(x) - mouseX), 
     1604                            dy = Math.abs(axisy.p2c(y) - mouseY), 
     1605                            dist = dx * dx + dy * dy; 
     1606                        if (dist < lowestDistance) { 
     1607                            lowestDistance = dist; 
     1608                            foundPoint = true; 
     1609                            item = result(i, j); 
    13351610                        } 
    1336                         var div = legend.children(); 
    1337                         $('<div style="position:absolute;width:' + div.width() + 'px;height:' + div.height() + 'px;' + pos +'background-color:' + c + ';"> </div>').prependTo(legend).css('opacity', options.legend.backgroundOpacity); 
    1338                          
    1339                     } 
    1340                 } 
    1341             } 
    1342         } 
    1343  
    1344         var lastMousePos = { pageX: null, pageY: null }; 
    1345         var selection = { first: { x: -1, y: -1}, second: { x: -1, y: -1} }; 
    1346         var prevSelection = null; 
    1347         var selectionInterval = null; 
    1348         var ignoreClick = false; 
    1349          
     1611                    } 
     1612                } 
     1613            } 
     1614 
     1615            return item; 
     1616        } 
     1617 
    13501618        function onMouseMove(ev) { 
    13511619            // FIXME: temp. work-around until jQuery bug 1871 is fixed 
     
    13601628                lastMousePos.pageY = e.pageY; 
    13611629            } 
     1630             
     1631            if (options.grid.hoverable && !hoverTimeout) 
     1632                hoverTimeout = setTimeout(onHover, 100); 
     1633 
     1634            if (selection.active) 
     1635                updateSelection(lastMousePos); 
    13621636        } 
    13631637         
     
    13811655            setSelectionPos(selection.first, e); 
    13821656                 
    1383             if (selectionInterval != null) 
    1384                 clearInterval(selectionInterval); 
    13851657            lastMousePos.pageX = null; 
    1386             selectionInterval = setInterval(updateSelectionOnMouseMove, 200)
     1658            selection.active = true
    13871659            $(document).one("mouseup", onSelectionMouseUp); 
    13881660        } 
    13891661 
    13901662        function onClick(e) { 
    1391             if (ignoreClick) { 
    1392                 ignoreClick = false; 
     1663            if (clickIsMouseUp) { 
     1664                clickIsMouseUp = false; 
    13931665                return; 
    13941666            } 
    1395              
    1396             var offset = eventHolder.offset(); 
    1397             var pos = {}; 
    1398             pos.x = e.pageX - offset.left - plotOffset.left; 
    1399             pos.x = xaxis.min + pos.x / hozScale; 
    1400             pos.y = e.pageY - offset.top - plotOffset.top; 
    1401             pos.y = yaxis.max - pos.y / vertScale; 
    1402  
    1403             target.trigger("plotclick", [ pos ]); 
     1667 
     1668            triggerClickHoverEvent("plotclick", e); 
     1669        } 
     1670         
     1671        function onHover() { 
     1672            triggerClickHoverEvent("plothover", lastMousePos); 
     1673            hoverTimeout = null; 
     1674        } 
     1675 
     1676        // trigger click or hover event (they send the same parameters 
     1677        // so we share their code) 
     1678        function triggerClickHoverEvent(eventname, event) { 
     1679            var offset = eventHolder.offset(), 
     1680                pos = { pageX: event.pageX, pageY: event.pageY }, 
     1681                canvasX = event.pageX - offset.left - plotOffset.left, 
     1682                canvasY = event.pageY - offset.top - plotOffset.top; 
     1683 
     1684            if (axes.xaxis.used) 
     1685                pos.x = axes.xaxis.c2p(canvasX); 
     1686            if (axes.yaxis.used) 
     1687                pos.y = axes.yaxis.c2p(canvasY); 
     1688            if (axes.x2axis.used) 
     1689                pos.x2 = axes.x2axis.c2p(canvasX); 
     1690            if (axes.y2axis.used) 
     1691                pos.y2 = axes.y2axis.c2p(canvasY); 
     1692 
     1693            var item = findNearbyItem(canvasX, canvasY); 
     1694 
     1695            if (item) { 
     1696                // fill in mouse pos for any listeners out there 
     1697                item.pageX = parseInt(item.series.xaxis.p2c(item.datapoint[0]) + offset.left + plotOffset.left); 
     1698                item.pageY = parseInt(item.series.yaxis.p2c(item.datapoint[1]) + offset.top + plotOffset.top); 
     1699 
     1700                     
     1701            } 
     1702 
     1703            if (options.grid.autoHighlight) { 
     1704                for (var i = 0; i < highlights.length; ++i) { 
     1705                    var h = highlights[i]; 
     1706                    if (h.auto && 
     1707                        !(item && h.series == item.series && h.point == item.datapoint)) 
     1708                        unhighlight(h.series, h.point); 
     1709                } 
     1710                 
     1711                if (item) 
     1712                    highlight(item.series, item.datapoint, true); 
     1713            } 
     1714             
     1715            target.trigger(eventname, [ pos, item ]); 
     1716        } 
     1717 
     1718        function triggerRedrawOverlay() { 
     1719            if (!redrawTimeout) 
     1720                redrawTimeout = setTimeout(redrawOverlay, 50); 
     1721        } 
     1722 
     1723        function redrawOverlay() { 
     1724            redrawTimeout = null; 
     1725 
     1726            // redraw highlights 
     1727            octx.save(); 
     1728            octx.clearRect(0, 0, canvasWidth, canvasHeight); 
     1729            octx.translate(plotOffset.left, plotOffset.top); 
     1730             
     1731            var i, hi;  
     1732            for (i = 0; i < highlights.length; ++i) { 
     1733                hi = highlights[i]; 
     1734 
     1735                if (hi.series.bars.show) 
     1736                    drawBarHighlight(hi.series, hi.point); 
     1737                else 
     1738                    drawPointHighlight(hi.series, hi.point); 
     1739            } 
     1740            octx.restore(); 
     1741 
     1742            // redraw selection 
     1743            if (selection.show && selectionIsSane()) { 
     1744                octx.strokeStyle = parseColor(options.selection.color).scale(null, null, null, 0.8).toString(); 
     1745                octx.lineWidth = 1; 
     1746                ctx.lineJoin = "round"; 
     1747                octx.fillStyle = parseColor(options.selection.color).scale(null, null, null, 0.4).toString(); 
     1748                 
     1749                var x = Math.min(selection.first.x, selection.second.x), 
     1750                    y = Math.min(selection.first.y, selection.second.y), 
     1751                    w = Math.abs(selection.second.x - selection.first.x), 
     1752                    h = Math.abs(selection.second.y - selection.first.y); 
     1753                 
     1754                octx.fillRect(x + plotOffset.left, y + plotOffset.top, w, h); 
     1755                octx.strokeRect(x + plotOffset.left, y + plotOffset.top, w, h); 
     1756            } 
     1757        } 
     1758         
     1759        function highlight(s, point, auto) { 
     1760            if (typeof s == "number") 
     1761                s = series[s]; 
     1762 
     1763            if (typeof point == "number") 
     1764                point = s.data[point]; 
     1765 
     1766            var i = indexOfHighlight(s, point); 
     1767            if (i == -1) { 
     1768                highlights.push({ series: s, point: point, auto: auto }); 
     1769 
     1770                triggerRedrawOverlay(); 
     1771            } 
     1772            else if (!auto) 
     1773                highlights[i].auto = false; 
     1774        } 
     1775             
     1776        function unhighlight(s, point) { 
     1777            if (typeof s == "number") 
     1778                s = series[s]; 
     1779 
     1780            if (typeof point == "number") 
     1781                point = s.data[point]; 
     1782 
     1783            var i = indexOfHighlight(s, point); 
     1784            if (i != -1) { 
     1785                highlights.splice(i, 1); 
     1786 
     1787                triggerRedrawOverlay(); 
     1788            } 
     1789        } 
     1790         
     1791        function indexOfHighlight(s, p) { 
     1792            for (var i = 0; i < highlights.length; ++i) { 
     1793                var h = highlights[i]; 
     1794                if (h.series == s && h.point[0] == p[0] 
     1795                    && h.point[1] == p[1]) 
     1796                    return i; 
     1797            } 
     1798            return -1; 
     1799        } 
     1800         
     1801        function drawPointHighlight(series, point) { 
     1802            var x = point[0], y = point[1], 
     1803                axisx = series.xaxis, axisy = series.yaxis; 
     1804             
     1805            if (x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max) 
     1806                return; 
     1807             
     1808            var pointRadius = series.points.radius + series.points.lineWidth / 2; 
     1809            octx.lineWidth = pointRadius; 
     1810            octx.strokeStyle = parseColor(series.color).scale(1, 1, 1, 0.5).toString(); 
     1811            var radius = 1.5 * pointRadius; 
     1812            octx.beginPath(); 
     1813            octx.arc(axisx.p2c(x), axisy.p2c(y), radius, 0, 2 * Math.PI, true); 
     1814            octx.stroke(); 
     1815        } 
     1816 
     1817        function drawBarHighlight(series, point) { 
     1818            octx.lineJoin = "round"; 
     1819            octx.lineWidth = series.bars.lineWidth; 
     1820            octx.strokeStyle = parseColor(series.color).scale(1, 1, 1, 0.5).toString(); 
     1821            octx.fillStyle = parseColor(series.color).scale(1, 1, 1, 0.5).toString(); 
     1822            var barLeft = series.bars.align == "left" ? 0 : -series.bars.barWidth/2; 
     1823            drawBar(point[0], point[1], barLeft, barLeft + series.bars.barWidth, 
     1824                    0, true, series.xaxis, series.yaxis, octx); 
    14041825        } 
    14051826         
    14061827        function triggerSelectedEvent() { 
    1407             var x1, x2, y1, y2; 
    1408             if (selection.first.x <= selection.second.x) { 
    1409                 x1 = selection.first.x; 
    1410                 x2 = selection.second.x; 
    1411             } 
    1412             else { 
    1413                 x1 = selection.second.x; 
    1414                 x2 = selection.first.x; 
    1415             } 
    1416  
    1417             if (selection.first.y >= selection.second.y) { 
    1418                 y1 = selection.first.y; 
    1419                 y2 = selection.second.y; 
    1420             } 
    1421             else { 
    1422                 y1 = selection.second.y; 
    1423                 y2 = selection.first.y; 
    1424             } 
    1425              
    1426             x1 = xaxis.min + x1 / hozScale; 
    1427             x2 = xaxis.min + x2 / hozScale; 
    1428  
    1429             y1 = yaxis.max - y1 / vertScale; 
    1430             y2 = yaxis.max - y2 / vertScale; 
    1431  
    1432             target.trigger("selected", [ { x1: x1, y1: y1, x2: x2, y2: y2 } ]); 
     1828            var x1 = Math.min(selection.first.x, selection.second.x), 
     1829                x2 = Math.max(selection.first.x, selection.second.x), 
     1830                y1 = Math.max(selection.first.y, selection.second.y), 
     1831                y2 = Math.min(selection.first.y, selection.second.y); 
     1832 
     1833            var r = {}; 
     1834            if (axes.xaxis.used) 
     1835                r.xaxis = { from: axes.xaxis.c2p(x1), to: axes.xaxis.c2p(x2) }; 
     1836            if (axes.x2axis.used) 
     1837                r.x2axis = { from: axes.x2axis.c2p(x1), to: axes.x2axis.c2p(x2) }; 
     1838            if (axes.yaxis.used) 
     1839                r.yaxis = { from: axes.yaxis.c2p(y1), to: axes.yaxis.c2p(y2) }; 
     1840            if (axes.y2axis.used) 
     1841                r.yaxis = { from: axes.y2axis.c2p(y1), to: axes.y2axis.c2p(y2) }; 
     1842             
     1843            target.trigger("plotselected", [ r ]); 
     1844 
     1845            // backwards-compat stuff, to be removed in future 
     1846            if (axes.xaxis.used && axes.yaxis.used) 
     1847                target.trigger("selected", [ { x1: r.xaxis.from, y1: r.yaxis.from, x2: r.xaxis.to, y2: r.yaxis.to } ]); 
    14331848        } 
    14341849         
    14351850        function onSelectionMouseUp(e) { 
     1851            // revert drag stuff for old-school browsers 
    14361852            if (document.onselectstart !== undefined) 
    14371853                document.onselectstart = workarounds.onselectstart; 
     
    14391855                document.ondrag = workarounds.ondrag; 
    14401856             
    1441             if (selectionInterval != null) { 
    1442                 clearInterval(selectionInterval); 
    1443                 selectionInterval = null; 
    1444             } 
    1445  
    1446             setSelectionPos(selection.second, e); 
    1447             clearSelection(); 
    1448             if (!selectionIsSane() || e.which != 1) 
    1449                 return false; 
    1450              
    1451             drawSelection(); 
    1452             triggerSelectedEvent(); 
    1453             ignoreClick = true; 
    1454  
     1857            // no more draggy-dee-drag 
     1858            selection.active = false; 
     1859            updateSelection(e); 
     1860             
     1861            if (selectionIsSane()) { 
     1862                triggerSelectedEvent(); 
     1863                clickIsMouseUp = true; 
     1864            } 
     1865             
    14551866            return false; 
    14561867        } 
    14571868 
    14581869        function setSelectionPos(pos, e) { 
    1459             var offset = $(overlay).offset(); 
     1870            var offset = eventHolder.offset(); 
    14601871            if (options.selection.mode == "y") { 
    14611872                if (pos == selection.first) 
     
    14811892        } 
    14821893         
    1483         function updateSelectionOnMouseMove() { 
    1484             if (lastMousePos.pageX == null) 
     1894        function updateSelection(pos) { 
     1895            if (pos.pageX == null) 
    14851896                return; 
    14861897             
    1487             setSelectionPos(selection.second, lastMousePos); 
    1488             clearSelection(); 
    1489             if (selectionIsSane()) 
    1490                 drawSelection(); 
     1898            setSelectionPos(selection.second, pos); 
     1899            if (selectionIsSane()) { 
     1900                selection.show = true; 
     1901                triggerRedrawOverlay(); 
     1902            } 
     1903            else 
     1904                clearSelection(); 
    14911905        } 
    14921906 
    14931907        function clearSelection() { 
    1494             if (prevSelection == null) 
    1495                 return; 
    1496  
    1497             var x = Math.min(prevSelection.first.x, prevSelection.second.x), 
    1498                 y = Math.min(prevSelection.first.y, prevSelection.second.y), 
    1499                 w = Math.abs(prevSelection.second.x - prevSelection.first.x), 
    1500                 h = Math.abs(prevSelection.second.y - prevSelection.first.y); 
    1501              
    1502             octx.clearRect(x + plotOffset.left - octx.lineWidth, 
    1503                            y + plotOffset.top - octx.lineWidth, 
    1504                            w + octx.lineWidth*2, 
    1505                            h + octx.lineWidth*2); 
    1506              
    1507             prevSelection = null; 
    1508         } 
    1509          
    1510         function setSelection(area) { 
    1511             clearSelection(); 
     1908            if (selection.show) { 
     1909                selection.show = false; 
     1910                triggerRedrawOverlay(); 
     1911            } 
     1912        } 
     1913 
     1914        function setSelection(ranges, preventEvent) { 
     1915            var range; 
     1916             
     1917            if (options.selection.mode == "y") { 
     1918                selection.first.x = 0; 
     1919                selection.second.x = plotWidth; 
     1920            } 
     1921            else { 
     1922                range = extractRange(ranges, "x"); 
     1923                 
     1924                selection.first.x = range.axis.p2c(range.from); 
     1925                selection.second.x = range.axis.p2c(range.to); 
     1926            } 
    15121927             
    15131928            if (options.selection.mode == "x") { 
     
    15161931            } 
    15171932            else { 
    1518                 selection.first.y = (yaxis.max - area.y1) * vertScale; 
    1519                 selection.second.y = (yaxis.max - area.y2) * vertScale; 
    1520             } 
    1521             if (options.selection.mode == "y") { 
    1522                 selection.first.x = 0; 
    1523                 selection.second.x = plotWidth; 
    1524             } 
    1525             else { 
    1526                 selection.first.x = (area.x1 - xaxis.min) * hozScale; 
    1527                 selection.second.x = (area.x2 - xaxis.min) * hozScale; 
    1528             } 
    1529  
    1530             drawSelection(); 
    1531             triggerSelectedEvent(); 
    1532         } 
    1533          
    1534         function drawSelection() { 
    1535             if (prevSelection != null && 
    1536                 selection.first.x == prevSelection.first.x && 
    1537                 selection.first.y == prevSelection.first.y &&  
    1538                 selection.second.x == prevSelection.second.x && 
    1539                 selection.second.y == prevSelection.second.y) 
    1540                 return; 
    1541              
    1542             octx.strokeStyle = parseColor(options.selection.color).scale(null, null, null, 0.8).toString(); 
    1543             octx.lineWidth = 1; 
    1544             ctx.lineJoin = "round"; 
    1545             octx.fillStyle = parseColor(options.selection.color).scale(null, null, null, 0.4).toString(); 
    1546  
    1547             prevSelection = { first:  { x: selection.first.x, 
    1548                                         y: selection.first.y }, 
    1549                               second: { x: selection.second.x, 
    1550                                         y: selection.second.y } }; 
    1551  
    1552             var x = Math.min(selection.first.x, selection.second.x), 
    1553                 y = Math.min(selection.first.y, selection.second.y), 
    1554                 w = Math.abs(selection.second.x - selection.first.x), 
    1555                 h = Math.abs(selection.second.y - selection.first.y); 
    1556              
    1557             octx.fillRect(x + plotOffset.left, y + plotOffset.top, w, h); 
    1558             octx.strokeRect(x + plotOffset.left, y + plotOffset.top, w, h); 
    1559         } 
    1560  
     1933                range = extractRange(ranges, "y"); 
     1934                 
     1935                selection.first.y = range.axis.p2c(range.from); 
     1936                selection.second.y = range.axis.p2c(range.to); 
     1937            } 
     1938 
     1939            selection.show = true; 
     1940            triggerRedrawOverlay(); 
     1941            if (!preventEvent) 
     1942                triggerSelectedEvent(); 
     1943        } 
     1944         
    15611945        function selectionIsSane() { 
    15621946            var minSize = 5; 
     
    15811965    function floorInBase(n, base) { 
    15821966        return base * Math.floor(n / base); 
     1967    } 
     1968     
     1969    function clamp(min, value, max) { 
     1970        if (value < min) 
     1971            return value; 
     1972        else if (value > max) 
     1973            return max; 
     1974        else 
     1975            return value; 
    15831976    } 
    15841977     
  • plugins/sfStatsPlugin/branches/1.0/web/js/flot/jquery.flot.pack.js

    r9680 r12995  
    1 (function($){function Plot(z,A,B){var C=[];var D={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,tickDecimals:null,tickSize:null,minTickSize:null,monthNames:null,timeformat:null},yaxis:{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},grid:{color:"#545454",backgroundColor:null,tickColor:"#dddddd",labelMargin:3,borderWidth:2,clickable:null,coloredAreas:null,coloredAreasColor:"#f4f4f4"},selection:{mode:null,color:"#e8cfac"},shadowSize:4};var E=null,overlay=null,eventHolder=null,ctx=null,octx=null,target=z,xaxis={},yaxis={},plotOffset={left:0,right:0,top:0,bottom:0},yLabelMaxWidth=0,yLabelMaxHeight=0,xLabelBoxWidth=0,canvasWidth=0,canvasHeight=0,plotWidth=0,plotHeight=0,hozScale=0,vertScale=0,workarounds={};this.setData=setData;this.setupGrid=setupGrid;this.draw=draw;this.clearSelection=clearSelection;this.setSelection=setSelection;this.getCanvas=function(){return E};this.getPlotOffset=function(){return plotOffset};this.getData=function(){return C};this.getAxes=function(){return{xaxis:xaxis,yaxis:yaxis}};parseOptions(B);setData(A);constructCanvas();setupGrid();draw();function setData(d){C=parseData(d);fillInSeriesOptions();processData()}function parseData(d){var a=[];for(var i=0;i<d.length;++i){var s;if(d[i].data){s={};for(var v in d[i])s[v]=d[i][v]}else{s={data:d[i]}}a.push(s)}return a}function parseOptions(o){$.extend(true,D,o);if(D.xaxis.noTicks&&D.xaxis.ticks==null)D.xaxis.ticks=D.xaxis.noTicks;if(D.yaxis.noTicks&&D.yaxis.ticks==null)D.yaxis.ticks=D.yaxis.noTicks}function fillInSeriesOptions(){var i;var a=C.length;var b=[];var d=[];for(i=0;i<C.length;++i){var e=C[i].color;if(e!=null){--a;if(typeof e=="number")d.push(e);else b.push(parseColor(C[i].color))}}for(i=0;i<d.length;++i){a=Math.max(a,d[i]+1)}var f=[];var g=0;i=0;while(f.length<a){var c;if(D.colors.length==i)c=new Color(100,100,100);else c=parseColor(D.colors[i]);var h=g%2==1?-1:1;var j=1+h*Math.ceil(g/2)*0.2;c.scale(j,j,j);f.push(c);++i;if(i>=D.colors.length){i=0;++g}}var k=0,s;for(i=0;i<C.length;++i){s=C[i];if(s.color==null){s.color=f[k].toString();++k}else if(typeof s.color=="number")s.color=f[s.color].toString();s.lines=$.extend(true,{},D.lines,s.lines);s.points=$.extend(true,{},D.points,s.points);s.bars=$.extend(true,{},D.bars,s.bars);if(s.shadowSize==null)s.shadowSize=D.shadowSize}}function processData(){xaxis.datamin=yaxis.datamin=Number.MAX_VALUE;xaxis.datamax=yaxis.datamax=Number.MIN_VALUE;for(var i=0;i<C.length;++i){var a=C[i].data;for(var j=0;j<a.length;++j){if(a[j]==null)continue;var x=a[j][0],y=a[j][1];if(x==null||y==null||isNaN(x=+x)||isNaN(y=+y)){a[j]=null;continue}if(x<xaxis.datamin)xaxis.datamin=x;if(x>xaxis.datamax)xaxis.datamax=x;if(y<yaxis.datamin)yaxis.datamin=y;if(y>yaxis.datamax)yaxis.datamax=y}}if(xaxis.datamin==Number.MAX_VALUE)xaxis.datamin=0;if(yaxis.datamin==Number.MAX_VALUE)yaxis.datamin=0;if(xaxis.datamax==Number.MIN_VALUE)xaxis.datamax=1;if(yaxis.datamax==Number.MIN_VALUE)yaxis.datamax=1}function constructCanvas(){canvasWidth=target.width();canvasHeight=target.height();target.html("");target.css("position","relative");if(canvasWidth<=0||canvasHeight<=0)throw"Invalid dimensions for plot, width = "+canvasWidth+", height = "+canvasHeight;E=$('<canvas width="'+canvasWidth+'" height="'+canvasHeight+'"></canvas>').appendTo(target).get(0);if($.browser.msie)E=window.G_vmlCanvasManager.initElement(E);ctx=E.getContext("2d");overlay=$('<canvas style="position:absolute;left:0px;top:0px;" width="'+canvasWidth+'" height="'+canvasHeight+'"></canvas>').appendTo(target).get(0);if($.browser.msie)overlay=window.G_vmlCanvasManager.initElement(overlay);octx=overlay.getContext("2d");eventHolder=$([overlay,E]);if(D.selection.mode!=null){eventHolder.mousedown(onMouseDown);eventHolder.each(function(){this.onmousemove=onMouseMove})}if(D.grid.clickable)eventHolder.click(onClick)}function setupGrid(){setRange(xaxis,D.xaxis);prepareTickGeneration(xaxis,D.xaxis);setTicks(xaxis,D.xaxis);extendXRangeIfNeededByBar();setRange(yaxis,D.yaxis);prepareTickGeneration(yaxis,D.yaxis);setTicks(yaxis,D.yaxis);setSpacing();insertLabels();insertLegend()}function setRange(a,b){var c=b.min!=null?b.min:a.datamin;var d=b.max!=null?b.max:a.datamax;if(d-c==0.0){var e;if(d==0.0)e=1.0;else e=0.01;c-=e;d+=e}else{var f=b.autoscaleMargin;if(f!=null){if(b.min==null){c-=(d-c)*f;if(c<0&&a.datamin>=0)c=0}if(b.max==null){d+=(d-c)*f;if(d>0&&a.datamax<=0)d=0}}}a.min=c;a.max=d}function prepareTickGeneration(h,j){var k;if(typeof j.ticks=="number"&&j.ticks>0)k=j.ticks;else if(h==xaxis)k=canvasWidth/100;else k=canvasHeight/60;var l=(h.max-h.min)/k;var m,generator,unit,formatter,i,magn,norm;if(j.mode=="time"){function formatDate(d,a,b){var e=function(n){n=""+n;return n.length==1?"0"+n:n};var r=[];var f=false;if(b==null)b=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];for(var i=0;i<a.length;++i){var c=a.charAt(i);if(f){switch(c){case'h':c=""+d.getHours();break;case'H':c=e(d.getHours());break;case'M':c=e(d.getMinutes());break;case'S':c=e(d.getSeconds());break;case'd':c=""+d.getDate();break;case'm':c=""+(d.getMonth()+1);break;case'y':c=""+d.getFullYear();break;case'b':c=""+b[d.getMonth()];break}r.push(c);f=false}else{if(c=="%")f=true;else r.push(c)}}return r.join("")}var o={"second":1000,"minute":60*1000,"hour":60*60*1000,"day":24*60*60*1000,"month":30*24*60*60*1000,"year":365.2425*24*60*60*1000};var p=[[1,"second"],[2,"second"],[5,"second"],[10,"second"],[30,"second"],[1,"minute"],[2,"minute"],[5,"minute"],[10,"minute"],[30,"minute"],[1,"hour"],[2,"hour"],[4,"hour"],[8,"hour"],[12,"hour"],[1,"day"],[2,"day"],[3,"day"],[0.25,"month"],[0.5,"month"],[1,"month"],[2,"month"],[3,"month"],[6,"month"],[1,"year"]];var q=0;if(j.minTickSize!=null){if(typeof j.tickSize=="number")q=j.tickSize;else q=j.minTickSize[0]*o[j.minTickSize[1]]}for(i=0;i<p.length-1;++i)if(l<(p[i][0]*o[p[i][1]]+p[i+1][0]*o[p[i+1][1]])/2&&p[i][0]*o[p[i][1]]>=q)break;m=p[i][0];unit=p[i][1];if(unit=="year"){magn=Math.pow(10,Math.floor(Math.log(l/o.year)/Math.LN10));norm=(l/o.year)/magn;if(norm<1.5)m=1;else if(norm<3)m=2;else if(norm<7.5)m=5;else m=10;m*=magn}if(j.tickSize){m=j.tickSize[0];unit=j.tickSize[1]}generator=function(a){var b=[],tickSize=a.tickSize[0],unit=a.tickSize[1],d=new Date(a.min);var c=tickSize*o[unit];if(unit=="second")d.setSeconds(floorInBase(d.getSeconds(),tickSize));if(unit=="minute")d.setMinutes(floorInBase(d.getMinutes(),tickSize));if(unit=="hour")d.setHours(floorInBase(d.getHours(),tickSize));if(unit=="month")d.setMonth(floorInBase(d.getMonth(),tickSize));if(unit=="year")d.setFullYear(floorInBase(d.getFullYear(),tickSize));d.setMilliseconds(0);if(c>=o.minute)d.setSeconds(0);if(c>=o.hour)d.setMinutes(0);if(c>=o.day)d.setHours(0);if(c>=o.day*4)d.setDate(1);if(c>=o.year)d.setMonth(0);var e=0,v;do{v=d.getTime();b.push({v:v,label:a.tickFormatter(v,a)});if(unit=="month"){if(tickSize<1){d.setDate(1);var f=d.getTime();d.setMonth(d.getMonth()+1);var g=d.getTime();d.setTime(v+e*o.hour+(g-f)*tickSize);e=d.getHours();d.setHours(0)}else d.setMonth(d.getMonth()+tickSize)}else if(unit=="year"){d.setFullYear(d.getFullYear()+tickSize)}else d.setTime(v+c)}while(v<a.max);return b};formatter=function(v,a){var d=new Date(v);if(j.timeformat!=null)return formatDate(d,j.timeformat,j.monthNames);var t=a.tickSize[0]*o[a.tickSize[1]];var b=a.max-a.min;if(t<o.minute)fmt="%h:%M:%S";else if(t<o.day){if(b<2*o.day)fmt="%h:%M";else fmt="%b %d %h:%M"}else if(t<o.month)fmt="%b %d";else if(t<o.year){if(b<o.year)fmt="%b";else fmt="%b %y"}else fmt="%y";return formatDate(d,fmt,j.monthNames)}}else{var s=j.tickDecimals;var u=-Math.floor(Math.log(l)/Math.LN10);if(s!=null&&u>s)u=s;magn=Math.pow(10,-u);norm=l/magn;if(norm<1.5)m=1;else if(norm<3){m=2;if(norm>2.25&&(s==null||u+1<=s)){m=2.5;++u}}else if(norm<7.5)m=5;else m=10;m*=magn;if(j.minTickSize!=null&&m<j.minTickSize)m=j.minTickSize;if(j.tickSize!=null)m=j.tickSize;h.tickDecimals=Math.max(0,(s!=null)?s:u);generator=function(a){var b=[];var c=floorInBase(a.min,a.tickSize);var i=0,v;do{v=c+i*a.tickSize;b.push({v:v,label:a.tickFormatter(v,a)});++i}while(v<a.max);return b};formatter=function(v,a){return v.toFixed(a.tickDecimals)}}h.tickSize=unit?[m,unit]:m;h.tickGenerator=generator;if($.isFunction(j.tickFormatter))h.tickFormatter=function(v,a){return""+j.tickFormatter(v,a)};else h.tickFormatter=formatter}function extendXRangeIfNeededByBar(){if(D.xaxis.max==null){var a=xaxis.max;for(var i=0;i<C.length;++i)if(C[i].bars.show&&C[i].bars.barWidth+xaxis.datamax>a)a=xaxis.datamax+C[i].bars.barWidth;xaxis.max=a}}function setTicks(a,b){a.ticks=[];if(b.ticks==null)a.ticks=a.tickGenerator(a);else if(typeof b.ticks=="number"){if(b.ticks>0)a.ticks=a.tickGenerator(a)}else if(b.ticks){var c=b.ticks;if($.isFunction(c))c=c({min:a.min,max:a.max});var i,v;for(i=0;i<c.length;++i){var d=null;var t=c[i];if(typeof t=="object"){v=t[0];if(t.length>1)d=t[1]}else v=t;if(d==null)d=a.tickFormatter(v,a);a.ticks[i]={v:v,label:d}}}if(b.autoscaleMargin!=null&&a.ticks.length>0){if(b.min==null)a.min=Math.min(a.min,a.ticks[0].v);if(b.max==null&&a.ticks.length>1)a.max=Math.min(a.max,a.ticks[a.ticks.length-1].v)}}function setSpacing(){var i,labels=[],l;for(i=0;i<yaxis.ticks.length;++i){l=yaxis.ticks[i].label;if(l)labels.push('<div class="tickLabel">'+l+'</div>')}if(labels.length>0){var a=$('<div style="position:absolute;top:-10000px;font-size:smaller">'+labels.join("")+'</div>').appendTo(target);yLabelMaxWidth=a.width();yLabelMaxHeight=a.find("div").height();a.remove()}var b=D.grid.borderWidth;if(D.points.show)b=Math.max(b,D.points.radius+D.points.lineWidth/2);for(i=0;i<C.length;++i){if(C[i].points.show)b=Math.max(b,C[i].points.radius+C[i].points.lineWidth/2)}plotOffset.left=plotOffset.right=plotOffset.top=plotOffset.bottom=b;plotOffset.left+=yLabelMaxWidth+D.grid.labelMargin;plotWidth=canvasWidth-plotOffset.left-plotOffset.right;xLabelBoxWidth=plotWidth/6;labels=[];for(i=0;i<xaxis.ticks.length;++i){l=xaxis.ticks[i].label;if(l)labels.push('<span class="tickLabel" width="'+xLabelBoxWidth+'">'+l+'</span>')}var c=0;if(labels.length>0){var a=$('<div style="position:absolute;top:-10000px;font-size:smaller">'+labels.join("")+'</div>').appendTo(target);c=a.height();a.remove()}plotOffset.bottom+=c+D.grid.labelMargin;plotHeight=canvasHeight-plotOffset.bottom-plotOffset.top;hozScale=plotWidth/(xaxis.max-xaxis.min);vertScale=plotHeight/(yaxis.max-yaxis.min)}function draw(){drawGrid();for(var i=0;i<C.length;i++){drawSeries(C[i])}}function tHoz(x){return(x-xaxis.min)*hozScale}function tVert(y){return plotHeight-(y-yaxis.min)*vertScale}function drawGrid(){var i;ctx.save();ctx.clearRect(0,0,canvasWidth,canvasHeight);ctx.translate(plotOffset.left,plotOffset.top);if(D.grid.backgroundColor!=null){ctx.fillStyle=D.grid.backgroundColor;ctx.fillRect(0,0,plotWidth,plotHeight)}if(D.grid.coloredAreas){var b=D.grid.coloredAreas;if($.isFunction(b))b=b({xmin:xaxis.min,xmax:xaxis.max,ymin:yaxis.min,ymax:yaxis.max});for(i=0;i<b.length;++i){var a=b[i];if(a.x1==null||a.x1<xaxis.min)a.x1=xaxis.min;if(a.x2==null||a.x2>xaxis.max)a.x2=xaxis.max;if(a.y1==null||a.y1<yaxis.min)a.y1=yaxis.min;if(a.y2==null||a.y2>yaxis.max)a.y2=yaxis.max;var c;if(a.x1>a.x2){c=a.x1;a.x1=a.x2;a.x2=c}if(a.y1>a.y2){c=a.y1;a.y1=a.y2;a.y2=c}if(a.x1>=xaxis.max||a.x2<=xaxis.min||a.x1==a.x2||a.y1>=yaxis.max||a.y2<=yaxis.min||a.y1==a.y2)continue;ctx.fillStyle=a.color||D.grid.coloredAreasColor;ctx.fillRect(Math.floor(tHoz(a.x1)),Math.floor(tVert(a.y2)),Math.floor(tHoz(a.x2)-tHoz(a.x1)),Math.floor(tVert(a.y1)-tVert(a.y2)))}}ctx.lineWidth=1;ctx.strokeStyle=D.grid.tickColor;ctx.beginPath();var v;for(i=0;i<xaxis.ticks.length;++i){v=xaxis.ticks[i].v;if(v<=xaxis.min||v>=xaxis.max)continue;ctx.moveTo(Math.floor(tHoz(v))+ctx.lineWidth/2,0);ctx.lineTo(Math.floor(tHoz(v))+ctx.lineWidth/2,plotHeight)}for(i=0;i<yaxis.ticks.length;++i){v=yaxis.ticks[i].v;if(v<=yaxis.min||v>=yaxis.max)continue;ctx.moveTo(0,Math.floor(tVert(v))+ctx.lineWidth/2);ctx.lineTo(plotWidth,Math.floor(tVert(v))+ctx.lineWidth/2)}ctx.stroke();if(D.grid.borderWidth){ctx.lineWidth=D.grid.borderWidth;ctx.strokeStyle=D.grid.color;ctx.lineJoin="round";ctx.strokeRect(0,0,plotWidth,plotHeight);ctx.restore()}}function insertLabels(){target.find(".tickLabels").remove();var i,tick;var a='<div class="tickLabels" style="font-size:smaller;color:'+D.grid.color+'">';for(i=0;i<xaxis.ticks.length;++i){tick=xaxis.ticks[i];if(!tick.label||tick.v<xaxis.min||tick.v>xaxis.max)continue;a+='<div style="position:absolute;top:'+(plotOffset.top+plotHeight+D.grid.labelMargin)+'px;left:'+(plotOffset.left+tHoz(tick.v)-xLabelBoxWidth/2)+'px;width:'+xLabelBoxWidth+'px;text-align:center" class="tickLabel">'+tick.label+"</div>"}for(i=0;i<yaxis.ticks.length;++i){tick=yaxis.ticks[i];if(!tick.label||tick.v<yaxis.min||tick.v>yaxis.max)continue;a+='<div style="position:absolute;top:'+(plotOffset.top+tVert(tick.v)-yLabelMaxHeight/2)+'px;left:0;width:'+yLabelMaxWidth+'px;text-align:right" class="tickLabel">'+tick.label+"</div>"}a+='</div>';target.append(a)}function drawSeries(a){if(a.lines.show||(!a.bars.show&&!a.points.show))drawSeriesLines(a);if(a.bars.show)drawSeriesBars(a);if(a.points.show)drawSeriesPoints(a)}function drawSeriesLines(h){function plotLine(a,b){var c,cur=null,drawx=null,drawy=null;ctx.beginPath();for(var i=0;i<a.length;++i){c=cur;cur=a[i];if(c==null||cur==null)continue;var d=c[0],y1=c[1],x2=cur[0],y2=cur[1];if(y1<=y2&&y1<yaxis.min){if(y2<yaxis.min)continue;d=(yaxis.min-y1)/(y2-y1)*(x2-d)+d;y1=yaxis.min}else if(y2<=y1&&y2<yaxis.min){if(y1<yaxis.min)continue;x2=(yaxis.min-y1)/(y2-y1)*(x2-d)+d;y2=yaxis.min}if(y1>=y2&&y1>yaxis.max){if(y2>yaxis.max)continue;d=(yaxis.max-y1)/(y2-y1)*(x2-d)+d;y1=yaxis.max}else if(y2>=y1&&y2>yaxis.max){if(y1>yaxis.max)continue;x2=(yaxis.max-y1)/(y2-y1)*(x2-d)+d;y2=yaxis.max}if(d<=x2&&d<xaxis.min){if(x2<xaxis.min)continue;y1=(xaxis.min-d)/(x2-d)*(y2-y1)+y1;d=xaxis.min}else if(x2<=d&&x2<xaxis.min){if(d<xaxis.min)continue;y2=(xaxis.min-d)/(x2-d)*(y2-y1)+y1;x2=xaxis.min}if(d>=x2&&d>xaxis.max){if(x2>xaxis.max)continue;y1=(xaxis.max-d)/(x2-d)*(y2-y1)+y1;d=xaxis.max}else if(x2>=d&&x2>xaxis.max){if(d>xaxis.max)continue;y2=(xaxis.max-d)/(x2-d)*(y2-y1)+y1;x2=xaxis.max}if(drawx!=tHoz(d)||drawy!=tVert(y1)+b)ctx.moveTo(tHoz(d),tVert(y1)+b);drawx=tHoz(x2);drawy=tVert(y2)+b;ctx.lineTo(drawx,drawy)}ctx.stroke()}function plotLineArea(a){var b,cur=null;var c=Math.min(Math.max(0,yaxis.min),yaxis.max);var d,lastX=0;var e=false;for(var i=0;i<a.length;++i){b=cur;cur=a[i];if(e&&b!=null&&cur==null){ctx.lineTo(tHoz(lastX),tVert(c));ctx.fill();e=false;continue}if(b==null||cur==null)continue;var f=b[0],y1=b[1],x2=cur[0],y2=cur[1];if(f<=x2&&f<xaxis.min){if(x2<xaxis.min)continue;y1=(xaxis.min-f)/(x2-f)*(y2-y1)+y1;f=xaxis.min}else if(x2<=f&&x2<xaxis.min){if(f<xaxis.min)continue;y2=(xaxis.min-f)/(x2-f)*(y2-y1)+y1;x2=xaxis.min}if(f>=x2&&f>xaxis.max){if(x2>xaxis.max)continue;y1=(xaxis.max-f)/(x2-f)*(y2-y1)+y1;f=xaxis.max}else if(x2>=f&&x2>xaxis.max){if(f>xaxis.max)continue;y2=(xaxis.max-f)/(x2-f)*(y2-y1)+y1;x2=xaxis.max}if(!e){ctx.beginPath();ctx.moveTo(tHoz(f),tVert(c));e=true}if(y1>=yaxis.max&&y2>=yaxis.max){ctx.lineTo(tHoz(f),tVert(yaxis.max));ctx.lineTo(tHoz(x2),tVert(yaxis.max));continue}else if(y1<=yaxis.min&&y2<=yaxis.min){ctx.lineTo(tHoz(f),tVert(yaxis.min));ctx.lineTo(tHoz(x2),tVert(yaxis.min));continue}var g=f,x2old=x2;if(y1<=y2&&y1<yaxis.min&&y2>=yaxis.min){f=(yaxis.min-y1)/(y2-y1)*(x2-f)+f;y1=yaxis.min}else if(y2<=y1&&y2<yaxis.min&&y1>=yaxis.min){x2=(yaxis.min-y1)/(y2-y1)*(x2-f)+f;y2=yaxis.min}if(y1>=y2&&y1>yaxis.max&&y2<=yaxis.max){f=(yaxis.max-y1)/(y2-y1)*(x2-f)+f;y1=yaxis.max}else if(y2>=y1&&y2>yaxis.max&&y1<=yaxis.max){x2=(yaxis.max-y1)/(y2-y1)*(x2-f)+f;y2=yaxis.max}if(f!=g){if(y1<=yaxis.min)d=yaxis.min;else d=yaxis.max;ctx.lineTo(tHoz(g),tVert(d));ctx.lineTo(tHoz(f),tVert(d))}ctx.lineTo(tHoz(f),tVert(y1));ctx.lineTo(tHoz(x2),tVert(y2));if(x2!=x2old){if(y2<=yaxis.min)d=yaxis.min;else d=yaxis.max;ctx.lineTo(tHoz(x2old),tVert(d));ctx.lineTo(tHoz(x2),tVert(d))}lastX=Math.max(x2,x2old)}if(e){ctx.lineTo(tHoz(lastX),tVert(c));ctx.fill()}}ctx.save();ctx.translate(plotOffset.left,plotOffset.top);ctx.lineJoin="round";var j=h.lines.lineWidth;var k=h.shadowSize;if(k>0){ctx.lineWidth=k/2;ctx.strokeStyle="rgba(0,0,0,0.1)";plotLine(h.data,j/2+k/2+ctx.lineWidth/2);ctx.lineWidth=k/2;ctx.strokeStyle="rgba(0,0,0,0.2)";plotLine(h.data,j/2+ctx.lineWidth/2)}ctx.lineWidth=j;ctx.strokeStyle=h.color;if(h.lines.fill){ctx.fillStyle=h.lines.fillColor!=null?h.lines.fillColor:parseColor(h.color).scale(null,null,null,0.4).toString();plotLineArea(h.data,0)}plotLine(h.data,0);ctx.restore()}function drawSeriesPoints(d){function plotPoints(a,b,c){for(var i=0;i<a.length;++i){if(a[i]==null)continue;var x=a[i][0],y=a[i][1];if(x<xaxis.min||x>xaxis.max||y<yaxis.min||y>yaxis.max)continue;ctx.beginPath();ctx.arc(tHoz(x),tVert(y),b,0,2*Math.PI,true);if(c)ctx.fill();ctx.stroke()}}function plotPointShadows(a,b,c){for(var i=0;i<a.length;++i){if(a[i]==null)continue;var x=a[i][0],y=a[i][1];if(x<xaxis.min||x>xaxis.max||y<yaxis.min||y>yaxis.max)continue;ctx.beginPath();ctx.arc(tHoz(x),tVert(y)+b,c,0,Math.PI,false);ctx.stroke()}}ctx.save();ctx.translate(plotOffset.left,plotOffset.top);var e=d.lines.lineWidth;var f=d.shadowSize;if(f>0){ctx.lineWidth=f/2;ctx.strokeStyle="rgba(0,0,0,0.1)";plotPointShadows(d.data,f/2+ctx.lineWidth/2,d.points.radius);ctx.lineWidth=f/2;ctx.strokeStyle="rgba(0,0,0,0.2)";plotPointShadows(d.data,ctx.lineWidth/2,d.points.radius)}ctx.lineWidth=d.points.lineWidth;ctx.strokeStyle=d.color;ctx.fillStyle=d.points.fillColor!=null?d.points.fillColor:d.color;plotPoints(d.data,d.points.radius,d.points.fill);ctx.restore()}function drawSeriesBars(g){function plotBars(a,b,c,d){for(var i=0;i<a.length;i++){if(a[i]==null)continue;var x=a[i][0],y=a[i][1];var e=true,drawTop=true,drawRight=true;var f=x,right=x+b,bottom=0,top=y;if(right<xaxis.min||f>xaxis.max||top<yaxis.min||bottom>yaxis.max)continue;if(f<xaxis.min){f=xaxis.min;e=false}if(right>xaxis.max){right=xaxis.max;drawRight=false}if(bottom<yaxis.min)bottom=yaxis.min;if(top>yaxis.max){top=yaxis.max;drawTop=false}if(d){ctx.beginPath();ctx.moveTo(tHoz(f),tVert(bottom)+c);ctx.lineTo(tHoz(f),tVert(top)+c);ctx.lineTo(tHoz(right),tVert(top)+c);ctx.lineTo(tHoz(right),tVert(bottom)+c);ctx.fill()}if(e||drawRight||drawTop){ctx.beginPath();ctx.moveTo(tHoz(f),tVert(bottom)+c);if(e)ctx.lineTo(tHoz(f),tVert(top)+c);else ctx.moveTo(tHoz(f),tVert(top)+c);if(drawTop)ctx.lineTo(tHoz(right),tVert(top)+c);else ctx.moveTo(tHoz(right),tVert(top)+c);if(drawRight)ctx.lineTo(tHoz(right),tVert(bottom)+c);else ctx.moveTo(tHoz(right),tVert(bottom)+c);ctx.stroke()}}}ctx.save();ctx.translate(plotOffset.left,plotOffset.top);ctx.lineJoin="round";var h=g.bars.barWidth;var j=Math.min(g.bars.lineWidth,h);ctx.lineWidth=j;ctx.strokeStyle=g.color;if(g.bars.fill){ctx.fillStyle=g.bars.fillColor!=null?g.bars.fillColor:parseColor(g.color).scale(null,null,null,0.4).toString()}plotBars(g.data,h,0,g.bars.fill);ctx.restore()}function insertLegend(){target.find(".legend").remove();if(!D.legend.show)return;var a=[];var b=false;for(i=0;i<C.length;++i){if(!C[i].label)continue;if(i%D.legend.noColumns==0){if(b)a.push('</tr>');a.push('<tr>');b=true}var d=C[i].label;if(D.legend.labelFormatter!=null)d=D.legend.labelFormatter(d);a.push('<td class="legendColorBox"><div style="border:1px solid '+D.legend.labelBoxBorderColor+';padding:1px"><div style="width:14px;height:10px;background-color:'+C[i].color+';overflow:hidden"></div></div></td>'+'<td class="legendLabel">'+d+'</td>')}if(b)a.push('</tr>');if(a.length>0){var e='<table style="font-size:smaller;color:'+D.grid.color+'">'+a.join("")+'</table>';if(D.legend.container!=null)D.legend.container.append(e);else{var f="";var p=D.legend.position,m=D.legend.margin;if(p.charAt(0)=="n")f+='top:'+(m+plotOffset.top)+'px;';else if(p.charAt(0)=="s")f+='bottom:'+(m+plotOffset.bottom)+'px;';if(p.charAt(1)=="e")f+='right:'+(m+plotOffset.right)+'px;';else if(p.charAt(1)=="w")f+='left:'+(m+plotOffset.bottom)+'px;';var g=$('<div class="legend">'+e.replace('style="','style="position:absolute;'+f+';')+'</div>').appendTo(target);if(D.legend.backgroundOpacity!=0.0){var c=D.legend.backgroundColor;if(c==null){var h;if(D.grid.backgroundColor!=null)h=D.grid.backgroundColor;else h=extractColor(g);c=parseColor(h).adjust(null,null,null,1).toString()}var j=g.children();$('<div style="position:absolute;width:'+j.width()+'px;height:'+j.height()+'px;'+f+'background-color:'+c+';"> </div>').prependTo(g).css('opacity',D.legend.backgroundOpacity)}}}}var F={pageX:null,pageY:null};var G={first:{x:-1,y:-1},second:{x:-1,y:-1}};var H=null;var I=null;var J=false;function onMouseMove(a){var e=a||window.event;if(e.pageX==null&&e.clientX!=null){var c=document.documentElement,b=document.body;F.pageX=e.clientX+(c&&c.scrollLeft||b.scrollLeft||0);F.pageY=e.clientY+(c&&c.scrollTop||b.scrollTop||0)}else{F.pageX=e.pageX;F.pageY=e.pageY}}function onMouseDown(e){if(e.which!=1)return;document.body.focus();if(document.onselectstart!==undefined&&workarounds.onselectstart==null){workarounds.onselectstart=document.onselectstart;document.onselectstart=function(){return false}}if(document.ondrag!==undefined&&workarounds.ondrag==null){workarounds.ondrag=document.ondrag;document.ondrag=function(){return false}}setSelectionPos(G.first,e);if(I!=null)clearInterval(I);F.pageX=null;I=setInterval(updateSelectionOnMouseMove,200);$(document).one("mouseup",onSelectionMouseUp)}function onClick(e){if(J){J=false;return}var a=eventHolder.offset();var b={};b.x=e.pageX-a.left-plotOffset.left;b.x=xaxis.min+b.x/hozScale;b.y=e.pageY-a.top-plotOffset.top;b.y=yaxis.max-b.y/vertScale;target.trigger("plotclick",[b])}function triggerSelectedEvent(){var a,x2,y1,y2;if(G.first.x<=G.second.x){a=G.first.x;x2=G.second.x}else{a=G.second.x;x2=G.first.x}if(G.first.y>=G.second.y){y1=G.first.y;y2=G.second.y}else{y1=G.second.y;y2=G.first.y}a=xaxis.min+a/hozScale;x2=xaxis.min+x2/hozScale;y1=yaxis.max-y1/vertScale;y2=yaxis.max-y2/vertScale;target.trigger("selected",[{x1:a,y1:y1,x2:x2,y2:y2}])}function onSelectionMouseUp(e){if(document.onselectstart!==undefined)document.onselectstart=workarounds.onselectstart;if(document.ondrag!==undefined)document.ondrag=workarounds.ondrag;if(I!=null){clearInterval(I);I=null}setSelectionPos(G.second,e);clearSelection();if(!selectionIsSane()||e.which!=1)return false;drawSelection();triggerSelectedEvent();J=true;return false}function setSelectionPos(a,e){var b=$(overlay).offset();if(D.selection.mode=="y"){if(a==G.first)a.x=0;else a.x=plotWidth}else{a.x=e.pageX-b.left-plotOffset.left;a.x=Math.min(Math.max(0,a.x),plotWidth)}if(D.selection.mode=="x"){if(a==G.first)a.y=0;else a.y=plotHeight}else{a.y=e.pageY-b.top-plotOffset.top;a.y=Math.min(Math.max(0,a.y),plotHeight)}}function updateSelectionOnMouseMove(){if(F.pageX==null)return;setSelectionPos(G.second,F);clearSelection();if(selectionIsSane())drawSelection()}function clearSelection(){if(H==null)return;var x=Math.min(H.first.x,H.second.x),y=Math.min(H.first.y,H.second.y),w=Math.abs(H.second.x-H.first.x),h=Math.abs(H.second.y-H.first.y);octx.clearRect(x+plotOffset.left-octx.lineWidth,y+plotOffset.top-octx.lineWidth,w+octx.lineWidth*2,h+octx.lineWidth*2);H=null}function setSelection(a){clearSelection();if(D.selection.mode=="x"){G.first.y=0;G.second.y=plotHeight}else{G.first.y=(yaxis.max-a.y1)*vertScale;G.second.y=(yaxis.max-a.y2)*vertScale}if(D.selection.mode=="y"){G.first.x=0;G.second.x=plotWidth}else{G.first.x=(a.x1-xaxis.min)*hozScale;G.second.x=(a.x2-xaxis.min)*hozScale}drawSelection();triggerSelectedEvent()}function drawSelection(){if(H!=null&&G.first.x==H.first.x&&G.first.y==H.first.y&&G.second.x==H.second.x&&G.second.y==H.second.y)return;octx.strokeStyle=parseColor(D.selection.color).scale(null,null,null,0.8).toString();octx.lineWidth=1;ctx.lineJoin="round";octx.fillStyle=parseColor(D.selection.color).scale(null,null,null,0.4).toString();H={first:{x:G.first.x,y:G.first.y},second:{x:G.second.x,y:G.second.y}};var x=Math.min(G.first.x,G.second.x),y=Math.min(G.first.y,G.second.y),w=Math.abs(G.second.x-G.first.x),h=Math.abs(G.second.y-G.first.y);octx.fillRect(x+plotOffset.left,y+plotOffset.top,w,h);octx.strokeRect(x+plotOffset.left,y+plotOffset.top,w,h)}function selectionIsSane(){var a=5;return Math.abs(G.second.x-G.first.x)>=a&&Math.abs(G.second.y-G.first.y)>=a}}$.plot=function(a,b,c){var d=new Plot(a,b,c);return d};function floorInBase(n,a){return a*Math.floor(n/a)}function Color(r,g,b,a){var e=['r','g','b','a'];var x=4;while(-1<--x){this[e[x]]=arguments[x]||((x==3)?1.0:0)}this.toString=function(){if(this.a>=1.0){return"rgb("+[this.r,this.g,this.b].join(",")+")"}else{return"rgba("+[this.r,this.g,this.b,this.a].join(",")+")"}};this.scale=function(a,b,c,d){x=4;while(-1<--x){if(arguments[x]!=null)this[e[x]]*=arguments[x]}return this.normalize()};this.adjust=function(a,b,c,d){x=4;while(-1<--x){if(arguments[x]!=null)this[e[x]]+=arguments[x]}return this.normalize()};this.clone=function(){return new Color(this.r,this.b,this.g,this.a)};var f=function(a,b,c){return Math.max(Math.min(a,c),b)};this.normalize=function(){this.r=f(parseInt(this.r),0,255);this.g=f(parseInt(this.g),0,255);this.b=f(parseInt(this.b),0,255);this.a=f(this.a,0,1);return this};this.normalize()}var K={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 extractColor(a){var b,elem=a;do{b=elem.css("background-color").toLowerCase();if(b!=''&&b!='transparent')break;elem=elem.parent()}while(!$.nodeName(elem.get(0),"body"));if(b=="rgba(0, 0, 0, 0)")return"transparent";return b}function parseColor(a){var b;if(b=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(a))return new Color(parseInt(b[1],10),parseInt(b[2],10),parseInt(b[3],10));if(b=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(a))return new Color(parseInt(b[1],10),parseInt(b[2],10),parseInt(b[3],10),parseFloat(b[4]));if(b=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(a))return new Color(parseFloat(b[1])*2.55,parseFloat(b[2])*2.55,parseFloat(b[3])*2.55);if(b=/rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(a))return new Color(parseFloat(b[1])*2.55,parseFloat(b[2])*2.55,parseFloat(b[3])*2.55,parseFloat(b[4]));if(b=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(a))return new Color(parseInt(b[1],16),parseInt(b[2],16),parseInt(b[3],16));if(b=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(a))return new Color(parseInt(b[1]+b[1],16),parseInt(b[2]+b[2],16),parseInt(b[3]+b[3],16));var c=$.trim(a).toLowerCase();if(c=="transparent")return new Color(255,255,255,0);else{b=K[c];return new Color(b[0],b[1],b[2])}}})(jQuery); 
     1(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<Ac.length;++AZ){var Ab;if(Ac[AZ].data){Ab={};for(var AY in Ac[AZ]){Ab[AY]=Ac[AZ][AY]}}else{Ab={data:Ac[AZ]}}Aa.push(Ab)}return Aa}function y(AY){F.extend(true,o,AY);if(o.xaxis.noTicks&&o.xaxis.ticks==null){o.xaxis.ticks=o.xaxis.noTicks}if(o.yaxis.noTicks&&o.yaxis.ticks==null){o.yaxis.ticks=o.yaxis.noTicks}if(o.grid.coloredAreas){o.grid.markings=o.grid.coloredAreas}if(o.grid.coloredAreasColor){o.grid.markingsColor=o.grid.coloredAreasColor}}function c(){var Ad;var Ai=W.length,AY=[],Ab=[];for(Ad=0;Ad<W.length;++Ad){var Ah=W[Ad].color;if(Ah!=null){--Ai;if(typeof Ah=="number"){Ab.push(Ah)}else{AY.push(E(W[Ad].color))}}}for(Ad=0;Ad<Ab.length;++Ad){Ai=Math.max(Ai,Ab[Ad]+1)}var AZ=[],Ac=0;Ad=0;while(AZ.length<Ai){var Ag;if(o.colors.length==Ad){Ag=new G(100,100,100)}else{Ag=E(o.colors[Ad])}var Aa=Ac%2==1?-1:1;var Af=1+Aa*Math.ceil(Ac/2)*0.2;Ag.scale(Af,Af,Af);AZ.push(Ag);++Ad;if(Ad>=o.colors.length){Ad=0;++Ac}}var Ae=0,Aj;for(Ad=0;Ad<W.length;++Ad){Aj=W[Ad];if(Aj.color==null){Aj.color=AZ[Ae].toString();++Ae}else{if(typeof Aj.color=="number"){Aj.color=AZ[Aj.color].toString()}}Aj.lines=F.extend(true,{},o.lines,Aj.lines);Aj.points=F.extend(true,{},o.points,Aj.points);Aj.bars=F.extend(true,{},o.bars,Aj.bars);if(Aj.shadowSize==null){Aj.shadowSize=o.shadowSize}if(Aj.xaxis&&Aj.xaxis==2){Aj.xaxis=AA.x2axis}else{Aj.xaxis=AA.xaxis}if(Aj.yaxis&&Aj.yaxis==2){Aj.yaxis=AA.y2axis}else{Aj.yaxis=AA.yaxis}}}function t(){var Aa=Number.POSITIVE_INFINITY,AZ=Number.NEGATIVE_INFINITY,Ab;for(Ab in AA){AA[Ab].datamin=Aa;AA[Ab].datamax=AZ;AA[Ab].used=false}for(var Ae=0;Ae<W.length;++Ae){var Ad=W[Ae].data,Aj=W[Ae].xaxis,Ai=W[Ae].yaxis,AY=0,Ah=0;if(W[Ae].bars.show){AY=W[Ae].bars.align=="left"?0:-W[Ae].bars.barWidth/2;Ah=AY+W[Ae].bars.barWidth}Aj.used=Ai.used=true;for(var Ac=0;Ac<Ad.length;++Ac){if(Ad[Ac]==null){continue}var Ag=Ad[Ac][0],Af=Ad[Ac][1];if(Ag!=null&&!isNaN(Ag=+Ag)){if(Ag+AY<Aj.datamin){Aj.datamin=Ag+AY}if(Ag+Ah>Aj.datamax){Aj.datamax=Ag+Ah}}if(Af!=null&&!isNaN(Af=+Af)){if(Af<Ai.datamin){Ai.datamin=Af}if(Af>Ai.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('<canvas width="'+AI+'" height="'+Z+'"></canvas>').appendTo(K).get(0);if(F.browser.msie){X=window.G_vmlCanvasManager.initElement(X)}g=X.getContext("2d");AP=F('<canvas style="position:absolute;left:0px;top:0px;" width="'+AI+'" height="'+Z+'"></canvas>').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<Ap.length;++As){var Aw=Ap.charAt(As);if(At){switch(Aw){case"h":Aw=""+Av.getUTCHours();break;case"H":Aw=Aq(Av.getUTCHours());break;case"M":Aw=Aq(Av.getUTCMinutes());break;case"S":Aw=Aq(Av.getUTCSeconds());break;case"d":Aw=""+Av.getUTCDate();break;case"m":Aw=""+(Av.getUTCMonth()+1);break;case"y":Aw=""+Av.getUTCFullYear();break;case"b":Aw=""+Ar[Av.getUTCMonth()];break}Au.push(Aw);At=false}else{if(Aw=="%"){At=true}else{Au.push(Aw)}}}return Au.join("")}var Ai={second:1000,minute:60*1000,hour:60*60*1000,day:24*60*60*1000,month:30*24*60*60*1000,year:365.2425*24*60*60*1000};var Am=[[1,"second"],[2,"second"],[5,"second"],[10,"second"],[30,"second"],[1,"minute"],[2,"minute"],[5,"minute"],[10,"minute"],[30,"minute"],[1,"hour"],[2,"hour"],[4,"hour"],[8,"hour"],[12,"hour"],[1,"day"],[2,"day"],[3,"day"],[0.25,"month"],[0.5,"month"],[1,"month"],[2,"month"],[3,"month"],[6,"month"],[1,"year"]];var Ab=0;if(Ag.minTickSize!=null){if(typeof Ag.tickSize=="number"){Ab=Ag.tickSize}else{Ab=Ag.minTickSize[0]*Ai[Ag.minTickSize[1]]}}for(Af=0;Af<Am.length-1;++Af){if(Al<(Am[Af][0]*Ai[Am[Af][1]]+Am[Af+1][0]*Ai[Am[Af+1][1]])/2&&Am[Af][0]*Ai[Am[Af][1]]>=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(Ay<Ar.max&&Ay!=As);return Aw};Ak=function(Ap,As){var At=new Date(Ap);if(Ag.timeformat!=null){return An(At,Ag.timeformat,Ag.monthNames)}var Aq=As.tickSize[0]*Ai[As.tickSize[1]];var Ar=As.max-As.min;if(Aq<Ai.minute){fmt="%h:%M:%S"}else{if(Aq<Ai.day){if(Ar<2*Ai.day){fmt="%h:%M"}else{fmt="%b %d %h:%M"}}else{if(Aq<Ai.month){fmt="%b %d"}else{if(Aq<Ai.year){if(Ar<Ai.year){fmt="%b"}else{fmt="%b %y"}}else{fmt="%y"}}}}return An(At,fmt,Ag.monthNames)}}else{var AY=Ag.tickDecimals;var Ae=-Math.floor(Math.log(Al)/Math.LN10);if(AY!=null&&Ae>AY){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&&Ao<Ag.minTickSize){Ao=Ag.minTickSize}if(Ag.tickSize!=null){Ao=Ag.tickSize}Ad.tickDecimals=Math.max(0,(AY!=null)?AY:Ae);Ah=function(Ar){var At=[];var Au=C(Ar.min,Ar.tickSize),Aq=0,Ap=Number.NaN,As;do{As=Ap;Ap=Au+Aq*Ar.tickSize;At.push({v:Ap,label:Ar.tickFormatter(Ap,Ar)});++Aq}while(Ap<Ar.max&&Ap!=As);return At};Ak=function(Ap,Aq){return Ap.toFixed(Aq.tickDecimals)}}Ad.tickSize=Aj?[Ao,Aj]:Ao;Ad.tickGenerator=Ah;if(F.isFunction(Ag.tickFormatter)){Ad.tickFormatter=function(Ap,Aq){return""+Ag.tickFormatter(Ap,Aq)}}else{Ad.tickFormatter=Ak}if(Ag.labelWidth!=null){Ad.labelWidth=Ag.labelWidth}if(Ag.labelHeight!=null){Ad.labelHeight=Ag.labelHeight}}function w(Ac,Ae){Ac.ticks=[];if(!Ac.used){return }if(Ae.ticks==null){Ac.ticks=Ac.tickGenerator(Ac)}else{if(typeof Ae.ticks=="number"){if(Ae.ticks>0){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;Ab<Ad.length;++Ab){var AZ=null;var Aa=Ad[Ab];if(typeof Aa=="object"){AY=Aa[0];if(Aa.length>1){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<Ac.ticks.length;++i){l=Ac.ticks[i].label;if(l){labels.push('<div class="tickLabel" style="float:left;width:'+Ac.labelWidth+'px">'+l+"</div>")}}Ac.labelHeight=0;if(labels.length>0){var Ab=F('<div style="position:absolute;top:-10000px;width:10000px;font-size:smaller">'+labels.join("")+'<div style="clear:left"></div></div>').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<Ae.ticks.length;++Ad){Ac=Ae.ticks[Ad].label;if(Ac){Af.push('<div class="tickLabel">'+Ac+"</div>")}}if(Af.length>0){var Ab=F('<div style="position:absolute;top:-10000px;font-size:smaller">'+Af.join("")+"</div>").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;i<W.length;++i){Aa=Math.max(Aa,2*(W[i].points.radius+W[i].points.lineWidth/2))}m.left=m.right=m.top=m.bottom=Aa;if(AA.xaxis.labelHeight>0){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;AY<W.length;AY++){AK(W[AY])}}function V(AZ,Af){var Ac=Af+"axis",AY=Af+"2axis",Ab,Ae,Ad,Aa;if(AZ[Ac]){Ab=AA[Ac];Ae=AZ[Ac].from;Ad=AZ[Ac].to}else{if(AZ[AY]){Ab=AA[AY];Ae=AZ[AY].from;Ad=AZ[AY].to}else{Ab=AA[Ac];Ae=AZ[Af+"1"];Ad=AZ[Af+"2"]}}if(Ae!=null&&Ad!=null&&Ae>Ad){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;Ac<AZ.length;++Ac){var AY=AZ[Ac],Ae=V(AY,"x"),Ab=V(AY,"y");if(Ae.from==null){Ae.from=Ae.axis.min}if(Ae.to==null){Ae.to=Ae.axis.max}if(Ab.from==null){Ab.from=Ab.axis.min}if(Ab.to==null){Ab.to=Ab.axis.max}if(Ae.to<Ae.axis.min||Ae.from>Ae.axis.max||Ab.to<Ab.axis.min||Ab.from>Ab.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<Ad.ticks.length;++Ac){Aa=Ad.ticks[Ac].v;if(Aa<=Ad.min||Aa>=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.ticks.length;++Ac){Aa=Ad.ticks[Ac].v;if(Aa<=Ad.min||Aa>=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.ticks.length;++Ac){Aa=Ad.ticks[Ac].v;if(Aa<=Ad.min||Aa>=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.ticks.length;++Ac){Aa=Ad.ticks[Ac].v;if(Aa<=Ad.min||Aa>=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='<div class="tickLabels" style="font-size:smaller;color:'+o.grid.color+'">';function AZ(Ac,Ad){for(var Ab=0;Ab<Ac.ticks.length;++Ab){var Aa=Ac.ticks[Ab];if(!Aa.label||Aa.v<Ac.min||Aa.v>Ac.max){continue}AY+=Ad(Aa,Ac)}}AZ(AA.xaxis,function(Aa,Ab){return'<div style="position:absolute;top:'+(m.top+AB+o.grid.labelMargin)+"px;left:"+(m.left+Ab.p2c(Aa.v)-Ab.labelWidth/2)+"px;width:"+Ab.labelWidth+'px;text-align:center" class="tickLabel">'+Aa.label+"</div>"});AZ(AA.yaxis,function(Aa,Ab){return'<div style="position:absolute;top:'+(m.top+Ab.p2c(Aa.v)-Ab.labelHeight/2)+"px;right:"+(m.right+N+o.grid.labelMargin)+"px;width:"+Ab.labelWidth+'px;text-align:right" class="tickLabel">'+Aa.label+"</div>"});AZ(AA.x2axis,function(Aa,Ab){return'<div style="position:absolute;bottom:'+(m.bottom+AB+o.grid.labelMargin)+"px;left:"+(m.left+Ab.p2c(Aa.v)-Ab.labelWidth/2)+"px;width:"+Ab.labelWidth+'px;text-align:center" class="tickLabel">'+Aa.label+"</div>"});AZ(AA.y2axis,function(Aa,Ab){return'<div style="position:absolute;top:'+(m.top+Ab.p2c(Aa.v)-Ab.labelHeight/2)+"px;left:"+(m.left+N+o.grid.labelMargin)+"px;width:"+Ab.labelWidth+'px;text-align:left" class="tickLabel">'+Aa.label+"</div>"});AY+="</div>";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<Aj.length;++Ai){Ag=Ao;Ao=Aj[Ai];if(Ag==null||Ao==null){continue}var Af=Ag[0],Al=Ag[1],Ae=Ao[0],Ak=Ao[1];if(Al<=Ak&&Al<Am.min){if(Ak<Am.min){continue}Af=(Am.min-Al)/(Ak-Al)*(Ae-Af)+Af;Al=Am.min}else{if(Ak<=Al&&Ak<Am.min){if(Al<Am.min){continue}Ae=(Am.min-Al)/(Ak-Al)*(Ae-Af)+Af;Ak=Am.min}}if(Al>=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<An.min){if(Ae<An.min){continue}Al=(An.min-Af)/(Ae-Af)*(Ak-Al)+Al;Af=An.min}else{if(Ae<=Af&&Ae<An.min){if(Af<An.min){continue}Ak=(An.min-Af)/(Ae-Af)*(Ak-Al)+Al;Ae=An.min}}if(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<Aj.length;++Ai){Ah=Ar;Ar=Aj[Ai];if(Ap&&Ah!=null&&Ar==null){g.lineTo(Aq.p2c(Ag),Ao.p2c(Ad));g.fill();Ap=false;continue}if(Ah==null||Ar==null){continue}var Af=Ah[0],An=Ah[1],Ae=Ar[0],Al=Ar[1];if(Af<=Ae&&Af<Aq.min){if(Ae<Aq.min){continue}An=(Aq.min-Af)/(Ae-Af)*(Al-An)+An;Af=Aq.min}else{if(Ae<=Af&&Ae<Aq.min){if(Af<Aq.min){continue}Al=(Aq.min-Af)/(Ae-Af)*(Al-An)+An;Ae=Aq.min}}if(Af>=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&&Al>=Ao.min){Af=(Ao.min-An)/(Al-An)*(Ae-Af)+Af;An=Ao.min}else{if(Al<=An&&Al<Ao.min&&An>=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;Af<Ag.length;++Af){if(Ag[Af]==null){continue}var Ad=Ag[Af][0],Aj=Ag[Af][1];if(Ad<Ak.min||Ad>Ak.max||Aj<Ai.min||Aj>Ai.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;Af<Ag.length;++Af){if(Ag[Af]==null){continue}var Ad=Ag[Af][0],Aj=Ag[Af][1];if(Ad<Ak.min||Ad>Ak.max||Aj<Ah.min||Aj>Ah.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(Ag<AY){Ag=0;AY=Ah;Ad=true;Ab=false}if(Al<An.min||AZ>An.max||Ag<Ak.min||AY>Ak.max){return }if(AZ<An.min){AZ=An.min;Am=false}if(Al>An.max){Al=An.max;Ae=false}if(AY<Ak.min){AY=Ak.min;Ad=false}if(Ag>Ak.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.length;Ac++){if(Ae[Ac]==null){continue}AM(Ae[Ac][0],Ae[Ac][1],Ab,Ad,Ah,Af,Ai,Ag,g)}}g.save();g.translate(m.left,m.top);g.lineJoin="round";g.lineWidth=Aa.bars.lineWidth;g.strokeStyle=Aa.color;AD(Aa.bars,Aa.color);var AY=Aa.bars.align=="left"?0:-Aa.bars.barWidth/2;AZ(Aa.data,AY,AY+Aa.bars.barWidth,0,Aa.bars.fill,Aa.xaxis,Aa.yaxis);g.restore()}function AD(Aa,AY){var AZ=Aa.fill;if(!AZ){return }if(Aa.fillColor){g.fillStyle=Aa.fillColor}else{var Ab=E(AY);Ab.a=typeof AZ=="number"?AZ:0.4;Ab.normalize();g.fillStyle=Ab.toString()}}function AV(){K.find(".legend").remove();if(!o.legend.show){return }var Ae=[];var Ac=false;for(i=0;i<W.length;++i){if(!W[i].label){continue}if(i%o.legend.noColumns==0){if(Ac){Ae.push("</tr>")}Ae.push("<tr>");Ac=true}var Ag=W[i].label;if(o.legend.labelFormatter!=null){Ag=o.legend.labelFormatter(Ag)}Ae.push('<td class="legendColorBox"><div style="border:1px solid '+o.legend.labelBoxBorderColor+';padding:1px"><div style="width:14px;height:10px;background-color:'+W[i].color+';overflow:hidden"></div></div></td><td class="legendLabel">'+Ag+"</td>")}if(Ac){Ae.push("</tr>")}if(Ae.length==0){return }var Ai='<table style="font-size:smaller;color:'+o.grid.color+'">'+Ae.join("")+"</table>";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('<div class="legend">'+Ai.replace('style="','style="position:absolute;'+Af+";")+"</div>").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('<div style="position:absolute;width:'+AY.width()+"px;height:"+AY.height()+"px;"+Af+"background-color:"+Ad+';"> </div>').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<W.length;++Aq){var Aw=W[Aq].data,Ad=W[Aq].xaxis,Ab=W[Aq].yaxis,Am=Ad.c2p(Ae),Ak=Ab.c2p(Ac),AZ=Al/Ad.scale,AY=Al/Ab.scale,Av=W[Aq].bars.show,Au=!(W[Aq].bars.show&&!(W[Aq].lines.show||W[Aq].points.show)),Aa=W[Aq].bars.align=="left"?0:-W[Aq].bars.barWidth/2,As=Aa+W[Aq].bars.barWidth;for(var Ap=0;Ap<Aw.length;++Ap){if(Aw[Ap]==null){continue}var Ag=Aw[Ap][0],Af=Aw[Ap][1];if(Av){if(!An&&Am>=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(Ao<Ar){Ar=Ao;An=true;At=Ai(Aq,Ap)}}}}return At}function J(AZ){var Aa=AZ||window.event;if(Aa.pageX==null&&Aa.clientX!=null){var Ab=document.documentElement,AY=document.body;AG.pageX=Aa.clientX+(Ab&&Ab.scrollLeft||AY.scrollLeft||0);AG.pageY=Aa.clientY+(Ab&&Ab.scrollTop||AY.scrollTop||0)}else{AG.pageX=Aa.pageX;AG.pageY=Aa.pageY}if(o.grid.hoverable&&!z){z=setTimeout(R,100)}if(d.active){AL(AG)}}function AN(AY){if(AY.which!=1){return }document.body.focus();if(document.onselectstart!==undefined&&S.onselectstart==null){S.onselectstart=document.onselectstart;document.onselectstart=function(){return false}}if(document.ondrag!==undefined&&S.ondrag==null){S.ondrag=document.ondrag;document.ondrag=function(){return false}}AR(d.first,AY);AG.pageX=null;d.active=true;F(document).one("mouseup",Y)}function k(AY){if(P){P=false;return }M("plotclick",AY)}function R(){M("plothover",AG);z=null}function M(AZ,AY){var Aa=AQ.offset(),Af={pageX:AY.pageX,pageY:AY.pageY},Ad=AY.pageX-Aa.left-m.left,Ab=AY.pageY-Aa.top-m.top;if(AA.xaxis.used){Af.x=AA.xaxis.c2p(Ad)}if(AA.yaxis.used){Af.y=AA.yaxis.c2p(Ab)}if(AA.x2axis.used){Af.x2=AA.x2axis.c2p(Ad)}if(AA.y2axis.used){Af.y2=AA.y2axis.c2p(Ab)}var Ag=AT(Ad,Ab);if(Ag){Ag.pageX=parseInt(Ag.series.xaxis.p2c(Ag.datapoint[0])+Aa.left+m.left);Ag.pageY=parseInt(Ag.series.yaxis.p2c(Ag.datapoint[1])+Aa.top+m.top)}if(o.grid.autoHighlight){for(var Ac=0;Ac<AF.length;++Ac){var Ae=AF[Ac];if(Ae.auto&&!(Ag&&Ae.series==Ag.series&&Ae.point==Ag.datapoint)){AH(Ae.series,Ae.point)}}if(Ag){AS(Ag.series,Ag.datapoint,true)}}K.trigger(AZ,[Af,Ag])}function x(){if(!O){O=setTimeout(T,50)}}function T(){O=null;AX.save();AX.clearRect(0,0,AI,Z);AX.translate(m.left,m.top);var Ab,Aa;for(Ab=0;Ab<AF.length;++Ab){Aa=AF[Ab];if(Aa.series.bars.show){AJ(Aa.series,Aa.point)}else{AE(Aa.series,Aa.point)}}AX.restore();if(d.show&&b()){AX.strokeStyle=E(o.selection.color).scale(null,null,null,0.8).toString();AX.lineWidth=1;g.lineJoin="round";AX.fillStyle=E(o.selection.color).scale(null,null,null,0.4).toString();var AY=Math.min(d.first.x,d.second.x),Ad=Math.min(d.first.y,d.second.y),AZ=Math.abs(d.second.x-d.first.x),Ac=Math.abs(d.second.y-d.first.y);AX.fillRect(AY+m.left,Ad+m.top,AZ,Ac);AX.strokeRect(AY+m.left,Ad+m.top,AZ,Ac)}}function AS(Aa,AY,Ab){if(typeof Aa=="number"){Aa=W[Aa]}if(typeof AY=="number"){AY=Aa.data[AY]}var AZ=q(Aa,AY);if(AZ==-1){AF.push({series:Aa,point:AY,auto:Ab});x()}else{if(!Ab){AF[AZ].auto=false}}}function AH(Aa,AY){if(typeof Aa=="number"){Aa=W[Aa]}if(typeof AY=="number"){AY=Aa.data[AY]}var AZ=q(Aa,AY);if(AZ!=-1){AF.splice(AZ,1);x()}}function q(Aa,Ab){for(var AY=0;AY<AF.length;++AY){var AZ=AF[AY];if(AZ.series==Aa&&AZ.point[0]==Ab[0]&&AZ.point[1]==Ab[1]){return AY}}return -1}function AE(Ab,Aa){var AZ=Aa[0],Af=Aa[1],Ae=Ab.xaxis,Ad=Ab.yaxis;if(AZ<Ae.min||AZ>Ae.max||Af<Ad.min||Af>Ad.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(K<J){return K}else{if(K>I){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); 
  • plugins/sfStatsPlugin/branches/1.1/web/js/flot/API.txt

    r12932 r12995  
    88The placeholder is a jQuery object that the plot will be put into. 
    99This placeholder needs to have its width and height set as explained 
    10 in the README. The plot will modify some properties of the placeholder 
    11 so it's recommended you simply pass in a div that you don't use for 
    12 anything else. 
     10in the README (go read that now if you haven't, it's short). The plot 
     11will modify some properties of the placeholder so it's recommended you 
     12simply pass in a div that you don't use for anything else. 
    1313 
    1414The format of the data is documented below, as is the available 
    15 options. The "plot" object returned has some members you can call. 
     15options. The "plot" object returned has some methods you can call. 
    1616These are documented separately below. 
    1717 
     
    3939Note that to simplify the internal logic in Flot both the x and y 
    4040values must be numbers, even if specifying time series (see below for 
    41 how to do this). This is a common problem because you might 
    42 accidentally retrieve data from the database and serialize them 
    43 directly to JSON without noticing the wrong type. 
     41how to do this). This is a common problem because you might retrieve 
     42data from the database and serialize them directly to JSON without 
     43noticing the wrong type. 
    4444 
    4545If a null is specified as a point or if one of the coordinates is null 
    46 or NaN or couldn't be converted to a number, the point is ignored. As 
    47 a special case, a null value for lines is interpreted as a line 
    48 segment end, i.e. the two points before and after the null value are 
     46or couldn't be converted to a number, the point is ignored when 
     47drawing. As a special case, a null value for lines is interpreted as a 
     48line segment end, i.e. the point before and after the null value are 
    4949not connected. 
    5050 
     
    5858    bars: specific bars options, 
    5959    points: specific points options, 
     60    xaxis: 1 or 2, 
     61    yaxis: 1 or 2, 
    6062    shadowSize: number 
    6163  } 
     
    8183in which case you can hard-code the color index to prevent the colors 
    8284from jumping around between the series. 
     85 
     86The "xaxis" and "yaxis" options specify which axis to use, specify 2 
     87to get the secondary axis (x axis at top or y axis to the right). 
     88E.g., you can use this to make a dual axis plot by specifying 
     89{ yaxis: 2 } for one data series. 
    8390 
    8491The rest of the options are all documented below as they are the same 
     
    141148If you want the legend to appear somewhere else in the DOM, you can 
    142149specify "container" as a jQuery object to put the legend table into. 
    143 The "position" and "margin" etc. options will then be ignored. 
     150The "position" and "margin" etc. options will then be ignored. Note 
     151that it will overwrite the contents of the container. 
    144152 
    145153 
     
    148156==================== 
    149157 
    150   xaxis, yaxis: { 
     158  xaxis, yaxis, x2axis, y2axis: { 
    151159    mode: null or "time" 
    152160    min: null or number 
    153161    max: null or number 
    154162    autoscaleMargin: null or number 
     163    labelWidth: null or number 
     164    labelHeight: null or number 
     165 
    155166    ticks: null or number or ticks array or (fn: range -> ticks array) 
    156167    tickSize: number or array 
     
    160171  } 
    161172 
    162 The two axes have the same kind of options. The "mode" option 
     173The axes have the same kind of options. The "mode" option 
    163174determines how the data is interpreted, the default of null means as 
    164175decimal numbers. Use "time" for time series data, see the next section. 
     
    175186nearest whole tick. The default value is "null" for the x axis and 
    1761870.02 for the y axis which seems appropriate for most cases. 
     188 
     189"labelWidth" and "labelHeight" specifies the maximum size of the tick 
     190labels in pixels. They're useful in case you need to align several 
     191plots. 
    177192 
    178193The rest of the options deal with the ticks. 
     
    226241number of decimals to display (default is auto-detected). 
    227242 
    228 Alternatively, for ultimate control you can provide a function to 
    229 "tickFormatter". The function is passed two parameters, the tick value 
    230 and an "axis" object with information, and should return a string. The 
    231 default formatter looks like this: 
     243Alternatively, for ultimate control over how ticks look like you can 
     244provide a function to "tickFormatter". The function is passed two 
     245parameters, the tick value and an "axis" object with information, and 
     246should return a string. The default formatter looks like this: 
    232247 
    233248  function formatter(val, axis) { 
     
    254269================ 
    255270 
     271Time series are a bit more difficult than scalar data because 
     272calendars don't follow a simple base 10 system. For many cases, Flot 
     273abstracts most of this away, but it can still be a bit difficult to 
     274get the data into Flot. So we'll first discuss the data format. 
     275 
    256276The time series support in Flot is based on Javascript timestamps, 
    257277i.e. everywhere a time value is expected or handed over, a Javascript 
    258 timestamp number is used. This is not the same as a Date object. A 
     278timestamp number is used. This is a number, not a Date object. A 
    259279Javascript timestamp is the number of milliseconds since January 1, 
    260 1970 00:00:00. This is almost the same as Unix timestamps, except it's 
     2801970 00:00:00 UTC. This is almost the same as Unix timestamps, except it's 
    261281in milliseconds, so remember to multiply by 1000! 
    262282 
    263 You can see a timestamp by outputting 
    264  
    265   (new Date()).getTime() 
     283You can see a timestamp like this 
     284 
     285  alert((new Date()).getTime()) 
     286 
     287Normally you want the timestamps to be displayed according to a 
     288certain time zone, usually the time zone in which the data has been 
     289produced. However, Flot always displays timestamps according to UTC. 
     290It has to as the only alternative with core Javascript is to interpret 
     291the timestamps according to the time zone that the visitor is in, 
     292which means that the ticks will shift unpredictably with the time zone 
     293and daylight savings of each visitor. 
     294 
     295So given that there's no good support for custom time zones in 
     296Javascript, you'll have to take care of this server-side. 
     297 
     298The easiest way to think about it is to pretend that the data 
     299production time zone is UTC, even if it isn't. So if you have a 
     300datapoint at 2002-02-20 08:00, you can generate a timestamp for eight 
     301o'clock UTC even if it really happened eight o'clock UTC+0200. 
    266302 
    267303In PHP you can get an appropriate timestamp with 
    268 'strtotime("2002-02-20") * 1000', in Python with 
    269 'time.mktime(datetime_object.timetuple()) * 1000', in .NET with 
     304'strtotime("2002-02-20 UTC") * 1000', in Python with 
     305'calendar.timegm(datetime_object.timetuple()) * 1000', in .NET with 
    270306something like: 
    271307 
     
    274310    System.TimeSpan span = new System.TimeSpan(System.DateTime.Parse("1/1/1970").Ticks); 
    275311    System.DateTime time = input.Subtract(span); 
    276     return (int)(time.Ticks / 10000); 
    277   } 
     312    return (long)(time.Ticks / 10000); 
     313  } 
     314 
     315Javascript also has some support for parsing date strings, so it is 
     316possible to generate the timestamps manually client-side. 
     317 
     318If you've already got the real UTC timestamp, it's too late to use the 
     319pretend trick described above. But you can fix up the timestamps by 
     320adding the time zone offset, e.g. for UTC+0200 you would add 2 hours 
     321to the UTC timestamp you got. Then it'll look right on the plot. Most 
     322programming environments have some means of getting the timezone 
     323offset for a specific date. 
    278324 
    279325Once you've got the timestamps into the data and specified "time" as 
    280326the axis mode, Flot will automatically generate relevant ticks and 
    281 format them. As always, you can tweak the ticks via the "ticks" 
    282 option. Again the values should be timestamps, not Date objects! 
    283  
    284 Tick generation and formatting is controlled separately through the 
    285 following axis options: 
     327format them. As always, you can tweak the ticks via the "ticks" option 
     328- just remember that the values should be timestamps (numbers), not 
     329Date objects. 
     330 
     331Tick generation and formatting can also be controlled separately 
     332through the following axis options: 
    286333 
    287334  xaxis, yaxis: { 
     
    322369  tickFormatter: function (val, axis) { 
    323370    var d = new Date(val); 
    324     return d.getDate() + "/" + (d.getMonth() + 1); 
     371    return d.getUTCDate() + "/" + (d.getUTCMonth() + 1); 
    325372  } 
    326373 
     
    342389 
    343390  lines, points, bars: { 
    344     show: boolean, 
    345     lineWidth: number, 
    346     fill: boolean, 
    347     fillColor: color or null 
     391    show: boolean 
     392    lineWidth: number 
     393    fill: boolean or number 
     394    fillColor: color 
    348395  } 
    349396 
     
    354401  bars: { 
    355402    barWidth: number 
     403    align: "left" or "center" 
    356404  } 
    357405 
     
    370418  }; 
    371419 
    372 "lineWidth" is the thickness of the line or outline and "fill" is 
    373 whether the shape should be filled. For lines, this produces area 
    374 graphs. If "fillColor" is null (default), the color for the data 
    375 series is used. 
    376  
    377 Note that the options that take numbers works in units of pixels, but 
    378 "barWidth" is the width of the bars in units of the x axis (e.g. for 
    379 time series it's in milliseconds). 
     420"lineWidth" is the thickness of the line or outline in pixels. 
     421 
     422"fill" is whether the shape should be filled. For lines, this produces 
     423area graphs. You can use "fillColor" to specify the color of the fill. 
     424If "fillColor" evaluates to false (default for everything except 
     425points), the fill color is auto-set to the color of the data series. 
     426You can adjust the opacity of the fill by setting fill to a number 
     427between 0 (fully transparent) and 1 (fully opaque). 
     428 
     429"barWidth" is the width of the bars in units of the x axis, contrary 
     430to most other measures that are specified in pixels. For instance, for 
     431time series the unit is milliseconds so 24 * 60 * 60 * 1000 produces 
     432bars with the width of a day. "align" specifies whether a bar should 
     433be left-aligned (default) or centered on top of the value it 
     434represents. 
    380435 
    381436The "colors" array specifies a default color theme to get colors for 
     
    400455    tickColor: color 
    401456    labelMargin: number 
    402     coloredAreas: array of areas or (fn: plot area -> array of areas) 
    403     coloredAreasColor: color 
     457    markings: array of markings or (fn: axes -> array of markings) 
    404458    borderWidth: number 
    405459    clickable: boolean 
    406   } 
    407  
    408 The grid is the thing with the two axes and a number of ticks. "color" 
     460    hoverable: boolean 
     461    autoHighlight: boolean 
     462    mouseActiveRadius: number 
     463  } 
     464 
     465The grid is the thing with the axes and a number of ticks. "color" 
    409466is the color of the grid itself whereas "backgroundColor" specifies 
    410467the background color inside the grid area. The default value of null 
    411468means that the background is transparent. You should only need to set 
    412 backgroundColor if want the grid area to be a different color from the 
     469backgroundColor if you want the grid area to be a different color from the 
    413470page color. Otherwise you might as well just set the background color 
    414471of the page with CSS. 
     
    420477to disable the border. 
    421478 
    422 "coloredAreas" is an array of areas that will be drawn on top of the 
    423 background. You can either specify an array of objects with { x1, y1, 
    424 x2, y2 } or a function that returns such an array given the plot area 
    425 as { xmin, xmax, ymin, ymax }. The default color of the areas are 
    426 "coloredAreasColor". You can override the color of individual areas by 
    427 specifying "color" in the area object. 
    428  
    429 Here's an example array: 
    430  
    431   coloredAreas: [ { x1: 0, y1: 10, x2: 2, y2: 15, color: "#bb0000" }, ... ] 
     479"markings" is used to draw simple lines and rectangular areas in the 
     480background of the plot. You can either specify an array of ranges on 
     481the form { xaxis: { from, to }, yaxis: { from, to } } (secondary axis 
     482coordinates with x2axis/y2axis) or with a function that returns such 
     483an array given the axes for the plot in an object as the first 
     484parameter. 
     485 
     486You can set the color of markings by specifying "color" in the ranges 
     487object. Here's an example array: 
     488 
     489  markings: [ { xaxis: { from: 0, to: 2 }, yaxis: { from: 10, to: 10 }, color: "#bb0000" }, ... ] 
    432490 
    433491If you leave out one of the values, that value is assumed to go to the 
    434 border of the plot. So for example { x1: 0, x2: 2 } means an area that 
    435 extends from the top to the bottom of the plot in the x range 0-2. 
     492border of the plot. So for example if you only specify { xaxis: { 
     493from: 0, to: 2 } } it means an area that extends from the top to the 
     494bottom of the plot in the x range 0-2. 
     495 
     496A line is drawn if from and to are the same, e.g. 
     497 
     498  markings: [ { yaxis: { from: 1, to: 1 } }, ... ] 
     499 
     500would draw a line parallel to the x axis at y = 1. You can control the 
     501line width with "lineWidth" in the ranges objects. 
    436502 
    437503An example function might look like this: 
    438504 
    439   coloredAreas: function (plotarea) { 
    440     var areas = []; 
    441     for (var x = Math.floor(plotarea.xmin); x < plotarea.xmax; x += 2) 
    442       areas.push({ x1: x, x2: x + 1 }); 
    443     return areas; 
     505  markings: function (axes) { 
     506    var markings = []; 
     507    for (var x = Math.floor(axes.xaxis.min); x < axes.xaxis.max; x += 2) 
     508      markings.push({ xaxis: { from: x, to: x + 1 } }); 
     509    return markings; 
    444510  } 
    445511 
     
    447513If you set "clickable" to true, the plot will listen for click events 
    448514on the plot area and fire a "plotclick" event on the placeholder with 
    449 an object { x: number, y: number } as parameter when one occurs. The 
    450 returned coordinates will be in the unit of the plot (not in pixels). 
    451 You can use it like this: 
     515a position and a nearby data item object as parameters. The coordinates 
     516are available both in the unit of the axes (not in pixels) and in 
     517global screen coordinates. 
     518 
     519Likewise, if you set "hoverable" to true, the plot will listen for 
     520mouse move events on the plot area and fire a "plothover" event with 
     521the same parameters as the "plotclick" event. If "autoHighlight" is 
     522true (the default), nearby data items are highlighted automatically. 
     523If needed, you can disable highlighting and control it yourself with 
     524the highlight/unhighlight plot methods described elsewhere. 
     525 
     526You can use "plotclick" and "plothover" events like this: 
    452527 
    453528    $.plot($("#placeholder"), [ d ], { grid: { clickable: true } }); 
    454529 
    455     $("#placeholder").bind("plotclick", function (e, pos) { 
    456         // the values are in pos.x and pos.y 
     530    $("#placeholder").bind("plotclick", function (event, pos, item) { 
     531        alert("You clicked at " + pos.x + ", " + pos.y); 
     532        // secondary axis coordinates if present are in pos.x2, pos.y2, 
     533        // if you need global screen coordinates, they are pos.pageX, pos.pageY 
     534 
     535        if (item) { 
     536          highlight(item.series, item.datapoint); 
     537          alert("You clicked a point!"); 
     538        } 
    457539    }); 
    458540 
    459 Support for hover indications or for associating the clicks with any 
    460 specific data is still forthcoming. 
     541The item object in this example is either null or a nearby object on the form: 
     542 
     543  item: { 
     544      datapoint: the point as you specified it in the data, e.g. [0, 2] 
     545      dataIndex: the index of the point in the data array 
     546      series: the series object 
     547      seriesIndex: the index of the series 
     548      pageX, pageY: the global screen coordinates of the point 
     549  } 
     550 
     551For instance, if you have specified the data like this  
     552 
     553    $.plot($("#placeholder"), [ { label: "Foo", data: [[0, 10], [7, 3]] } ], ...); 
     554 
     555and the mouse is near the point (7, 3), "datapoint" is the [7, 3] we 
     556specified, "dataIndex" will be 1, "series" is a normalized series 
     557object with among other things the "Foo" label in series.label and the 
     558color in series.color, and "seriesIndex" is 0. 
     559 
     560If you use the above events to update some other information and want 
     561to clear out that info in case the mouse goes away, you'll probably 
     562also need to listen to "mouseout" events on the placeholder div. 
     563 
     564"mouseActiveRadius" specifies how far the mouse can be from an item 
     565and still activate it. If there are two or more points within this 
     566radius, Flot chooses the closest item. For bars, the top-most bar 
     567(from the latest specified data series) is chosen. 
    461568 
    462569 
     
    474581where both ranges can be specified. "color" is color of the selection. 
    475582 
    476 When selection support is enabled, a "selected" event will be emitted 
     583When selection support is enabled, a "plotselected" event will be emitted 
    477584on the DOM element you passed into the plot function. The event 
    478 handler gets one extra parameter with the area selected, like this: 
    479  
    480   placeholder.bind("selected", function(event, area) { 
    481     // area selected is area.x1 to area.x2 and area.y1 to area.y2 
     585handler gets one extra parameter with the ranges selected on the axes, 
     586like this: 
     587 
     588  placeholder.bind("plotselected", function(event, ranges) { 
     589    alert("You selected " + ranges.xaxis.from + " to " + ranges.xaxis.to) 
     590    // similar for yaxis, secondary axes are in x2axis 
     591    // and y2axis if present 
    482592  }); 
    483593 
    484594 
    485 Plot Member
     595Plot Method
    486596------------ 
    487597 
    488 The Plot object returned from the plot function has the following 
    489 members
     598The Plot object returned from the plot function has some methods you 
     599can call
    490600 
    491601  - clearSelection() 
     
    493603    Clear the selection rectangle. 
    494604 
    495   - setSelection(area) 
    496  
    497     Set the selection rectangle. The passed in area should have the 
    498     members x1 and x2 if the selection mode is "x" and y1 and y2 if 
    499     the selection mode is "y" and both x1, x2 and y1, y2 if the 
    500     selection mode is "xy", like this: 
    501  
    502       setSelection({ x1: 0, x2: 10, y1: 40, y2: 60}); 
    503  
    504     setSelection will trigger the "selected" event when called so you 
    505     may have to do a bit of shortcircuiting to prevent an eternal loop 
    506     if you invoke the method inside the "selected" handler. 
    507  
    508   - getCanvas() 
    509  
    510     Returns the canvas used for drawing in case you need to hack on it 
    511     yourself. You'll probably need to get the plot offset too. 
     605 
     606  - setSelection(ranges, preventEvent) 
     607 
     608    Set the selection rectangle. The passed in ranges is on the same 
     609    form as returned in the "plotselected" event. If the selection 
     610    mode is "x", you should put in either an xaxis (or x2axis) object, 
     611    if the mode is "y" you need to put in an yaxis (or y2axis) object 
     612    and both xaxis/x2axis and yaxis/y2axis if the selection mode is 
     613    "xy", like this: 
     614 
     615      setSelection({ xaxis: { from: 0, to: 10 }, yaxis: { from: 40, to: 60 } });