Changeset 12995
- Timestamp:
- 11/13/08 23:33:30 (1 year ago)
- Files:
-
- plugins/sfStatsPlugin/branches/1.0/web/js/flot/API.txt (modified) (22 diffs)
- plugins/sfStatsPlugin/branches/1.0/web/js/flot/NEWS.txt (modified) (1 diff)
- plugins/sfStatsPlugin/branches/1.0/web/js/flot/README.txt (modified) (3 diffs)
- plugins/sfStatsPlugin/branches/1.0/web/js/flot/TODO (modified) (2 diffs)
- plugins/sfStatsPlugin/branches/1.0/web/js/flot/jquery.flot.js (modified) (45 diffs)
- plugins/sfStatsPlugin/branches/1.0/web/js/flot/jquery.flot.pack.js (modified) (1 diff)
- plugins/sfStatsPlugin/branches/1.1/web/js/flot/API.txt (modified) (22 diffs)
- plugins/sfStatsPlugin/branches/1.1/web/js/flot/NEWS.txt (modified) (1 diff)
- plugins/sfStatsPlugin/branches/1.1/web/js/flot/README.txt (modified) (3 diffs)
- plugins/sfStatsPlugin/branches/1.1/web/js/flot/TODO (modified) (2 diffs)
- plugins/sfStatsPlugin/branches/1.1/web/js/flot/jquery.flot.js (modified) (45 diffs)
- plugins/sfStatsPlugin/branches/1.1/web/js/flot/jquery.flot.pack.js (modified) (1 diff)
- plugins/sfStatsPlugin/branches/1.2/web/js/flot/API.txt (modified) (22 diffs)
- plugins/sfStatsPlugin/branches/1.2/web/js/flot/NEWS.txt (modified) (1 diff)
- plugins/sfStatsPlugin/branches/1.2/web/js/flot/README.txt (modified) (3 diffs)
- plugins/sfStatsPlugin/branches/1.2/web/js/flot/TODO (modified) (2 diffs)
- plugins/sfStatsPlugin/branches/1.2/web/js/flot/jquery.flot.js (modified) (45 diffs)
- plugins/sfStatsPlugin/branches/1.2/web/js/flot/jquery.flot.pack.js (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
plugins/sfStatsPlugin/branches/1.0/web/js/flot/API.txt
r9680 r12995 8 8 The placeholder is a jQuery object that the plot will be put into. 9 9 This placeholder needs to have its width and height set as explained 10 in the README . The plot will modify some properties of the placeholder11 so it's recommended you simply pass in a div that you don't use for 12 anything else.10 in the README (go read that now if you haven't, it's short). The plot 11 will modify some properties of the placeholder so it's recommended you 12 simply pass in a div that you don't use for anything else. 13 13 14 14 The format of the data is documented below, as is the available 15 options. The "plot" object returned has some me mbers you can call.15 options. The "plot" object returned has some methods you can call. 16 16 These are documented separately below. 17 17 … … 39 39 Note that to simplify the internal logic in Flot both the x and y 40 40 values 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 withoutnoticing the wrong type.41 how to do this). This is a common problem because you might retrieve 42 data from the database and serialize them directly to JSON without 43 noticing the wrong type. 44 44 45 45 If 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. As47 a special case, a null value for lines is interpreted as a line 48 segment end, i.e. the two pointsbefore and after the null value are46 or couldn't be converted to a number, the point is ignored when 47 drawing. As a special case, a null value for lines is interpreted as a 48 line segment end, i.e. the point before and after the null value are 49 49 not connected. 50 50 … … 58 58 bars: specific bars options, 59 59 points: specific points options, 60 xaxis: 1 or 2, 61 yaxis: 1 or 2, 60 62 shadowSize: number 61 63 } … … 81 83 in which case you can hard-code the color index to prevent the colors 82 84 from jumping around between the series. 85 86 The "xaxis" and "yaxis" options specify which axis to use, specify 2 87 to get the secondary axis (x axis at top or y axis to the right). 88 E.g., you can use this to make a dual axis plot by specifying 89 { yaxis: 2 } for one data series. 83 90 84 91 The rest of the options are all documented below as they are the same … … 141 148 If you want the legend to appear somewhere else in the DOM, you can 142 149 specify "container" as a jQuery object to put the legend table into. 143 The "position" and "margin" etc. options will then be ignored. 150 The "position" and "margin" etc. options will then be ignored. Note 151 that it will overwrite the contents of the container. 144 152 145 153 … … 148 156 ==================== 149 157 150 xaxis, yaxis : {158 xaxis, yaxis, x2axis, y2axis: { 151 159 mode: null or "time" 152 160 min: null or number 153 161 max: null or number 154 162 autoscaleMargin: null or number 163 labelWidth: null or number 164 labelHeight: null or number 165 155 166 ticks: null or number or ticks array or (fn: range -> ticks array) 156 167 tickSize: number or array … … 160 171 } 161 172 162 The twoaxes have the same kind of options. The "mode" option173 The axes have the same kind of options. The "mode" option 163 174 determines how the data is interpreted, the default of null means as 164 175 decimal numbers. Use "time" for time series data, see the next section. … … 175 186 nearest whole tick. The default value is "null" for the x axis and 176 187 0.02 for the y axis which seems appropriate for most cases. 188 189 "labelWidth" and "labelHeight" specifies the maximum size of the tick 190 labels in pixels. They're useful in case you need to align several 191 plots. 177 192 178 193 The rest of the options deal with the ticks. … … 226 241 number of decimals to display (default is auto-detected). 227 242 228 Alternatively, for ultimate control you can provide a function to229 "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:243 Alternatively, for ultimate control over how ticks look like you can 244 provide a function to "tickFormatter". The function is passed two 245 parameters, the tick value and an "axis" object with information, and 246 should return a string. The default formatter looks like this: 232 247 233 248 function formatter(val, axis) { … … 254 269 ================ 255 270 271 Time series are a bit more difficult than scalar data because 272 calendars don't follow a simple base 10 system. For many cases, Flot 273 abstracts most of this away, but it can still be a bit difficult to 274 get the data into Flot. So we'll first discuss the data format. 275 256 276 The time series support in Flot is based on Javascript timestamps, 257 277 i.e. everywhere a time value is expected or handed over, a Javascript 258 timestamp number is used. This is not the same asa Date object. A278 timestamp number is used. This is a number, not a Date object. A 259 279 Javascript timestamp is the number of milliseconds since January 1, 260 1970 00:00:00 . This is almost the same as Unix timestamps, except it's280 1970 00:00:00 UTC. This is almost the same as Unix timestamps, except it's 261 281 in milliseconds, so remember to multiply by 1000! 262 282 263 You can see a timestamp by outputting 264 265 (new Date()).getTime() 283 You can see a timestamp like this 284 285 alert((new Date()).getTime()) 286 287 Normally you want the timestamps to be displayed according to a 288 certain time zone, usually the time zone in which the data has been 289 produced. However, Flot always displays timestamps according to UTC. 290 It has to as the only alternative with core Javascript is to interpret 291 the timestamps according to the time zone that the visitor is in, 292 which means that the ticks will shift unpredictably with the time zone 293 and daylight savings of each visitor. 294 295 So given that there's no good support for custom time zones in 296 Javascript, you'll have to take care of this server-side. 297 298 The easiest way to think about it is to pretend that the data 299 production time zone is UTC, even if it isn't. So if you have a 300 datapoint at 2002-02-20 08:00, you can generate a timestamp for eight 301 o'clock UTC even if it really happened eight o'clock UTC+0200. 266 302 267 303 In PHP you can get an appropriate timestamp with 268 'strtotime("2002-02-20 ") * 1000', in Python with269 ' time.mktime(datetime_object.timetuple()) * 1000', in .NET with304 'strtotime("2002-02-20 UTC") * 1000', in Python with 305 'calendar.timegm(datetime_object.timetuple()) * 1000', in .NET with 270 306 something like: 271 307 … … 274 310 System.TimeSpan span = new System.TimeSpan(System.DateTime.Parse("1/1/1970").Ticks); 275 311 System.DateTime time = input.Subtract(span); 276 return (int)(time.Ticks / 10000); 277 } 312 return (long)(time.Ticks / 10000); 313 } 314 315 Javascript also has some support for parsing date strings, so it is 316 possible to generate the timestamps manually client-side. 317 318 If you've already got the real UTC timestamp, it's too late to use the 319 pretend trick described above. But you can fix up the timestamps by 320 adding the time zone offset, e.g. for UTC+0200 you would add 2 hours 321 to the UTC timestamp you got. Then it'll look right on the plot. Most 322 programming environments have some means of getting the timezone 323 offset for a specific date. 278 324 279 325 Once you've got the timestamps into the data and specified "time" as 280 326 the 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: 327 format them. As always, you can tweak the ticks via the "ticks" option 328 - just remember that the values should be timestamps (numbers), not 329 Date objects. 330 331 Tick generation and formatting can also be controlled separately 332 through the following axis options: 286 333 287 334 xaxis, yaxis: { … … 322 369 tickFormatter: function (val, axis) { 323 370 var d = new Date(val); 324 return d.get Date() + "/" + (d.getMonth() + 1);371 return d.getUTCDate() + "/" + (d.getUTCMonth() + 1); 325 372 } 326 373 … … 342 389 343 390 lines, points, bars: { 344 show: boolean ,345 lineWidth: number ,346 fill: boolean ,347 fillColor: color or null391 show: boolean 392 lineWidth: number 393 fill: boolean or number 394 fillColor: color 348 395 } 349 396 … … 354 401 bars: { 355 402 barWidth: number 403 align: "left" or "center" 356 404 } 357 405 … … 370 418 }; 371 419 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 423 area graphs. You can use "fillColor" to specify the color of the fill. 424 If "fillColor" evaluates to false (default for everything except 425 points), the fill color is auto-set to the color of the data series. 426 You can adjust the opacity of the fill by setting fill to a number 427 between 0 (fully transparent) and 1 (fully opaque). 428 429 "barWidth" is the width of the bars in units of the x axis, contrary 430 to most other measures that are specified in pixels. For instance, for 431 time series the unit is milliseconds so 24 * 60 * 60 * 1000 produces 432 bars with the width of a day. "align" specifies whether a bar should 433 be left-aligned (default) or centered on top of the value it 434 represents. 380 435 381 436 The "colors" array specifies a default color theme to get colors for … … 400 455 tickColor: color 401 456 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) 404 458 borderWidth: number 405 459 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 465 The grid is the thing with the axes and a number of ticks. "color" 409 466 is the color of the grid itself whereas "backgroundColor" specifies 410 467 the background color inside the grid area. The default value of null 411 468 means that the background is transparent. You should only need to set 412 backgroundColor if want the grid area to be a different color from the469 backgroundColor if you want the grid area to be a different color from the 413 470 page color. Otherwise you might as well just set the background color 414 471 of the page with CSS. … … 420 477 to disable the border. 421 478 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 480 background of the plot. You can either specify an array of ranges on 481 the form { xaxis: { from, to }, yaxis: { from, to } } (secondary axis 482 coordinates with x2axis/y2axis) or with a function that returns such 483 an array given the axes for the plot in an object as the first 484 parameter. 485 486 You can set the color of markings by specifying "color" in the ranges 487 object. Here's an example array: 488 489 markings: [ { xaxis: { from: 0, to: 2 }, yaxis: { from: 10, to: 10 }, color: "#bb0000" }, ... ] 432 490 433 491 If 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. 492 border of the plot. So for example if you only specify { xaxis: { 493 from: 0, to: 2 } } it means an area that extends from the top to the 494 bottom of the plot in the x range 0-2. 495 496 A line is drawn if from and to are the same, e.g. 497 498 markings: [ { yaxis: { from: 1, to: 1 } }, ... ] 499 500 would draw a line parallel to the x axis at y = 1. You can control the 501 line width with "lineWidth" in the ranges objects. 436 502 437 503 An example function might look like this: 438 504 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; 444 510 } 445 511 … … 447 513 If you set "clickable" to true, the plot will listen for click events 448 514 on 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: 515 a position and a nearby data item object as parameters. The coordinates 516 are available both in the unit of the axes (not in pixels) and in 517 global screen coordinates. 518 519 Likewise, if you set "hoverable" to true, the plot will listen for 520 mouse move events on the plot area and fire a "plothover" event with 521 the same parameters as the "plotclick" event. If "autoHighlight" is 522 true (the default), nearby data items are highlighted automatically. 523 If needed, you can disable highlighting and control it yourself with 524 the highlight/unhighlight plot methods described elsewhere. 525 526 You can use "plotclick" and "plothover" events like this: 452 527 453 528 $.plot($("#placeholder"), [ d ], { grid: { clickable: true } }); 454 529 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 } 457 539 }); 458 540 459 Support for hover indications or for associating the clicks with any 460 specific data is still forthcoming. 541 The 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 551 For instance, if you have specified the data like this 552 553 $.plot($("#placeholder"), [ { label: "Foo", data: [[0, 10], [7, 3]] } ], ...); 554 555 and the mouse is near the point (7, 3), "datapoint" is the [7, 3] we 556 specified, "dataIndex" will be 1, "series" is a normalized series 557 object with among other things the "Foo" label in series.label and the 558 color in series.color, and "seriesIndex" is 0. 559 560 If you use the above events to update some other information and want 561 to clear out that info in case the mouse goes away, you'll probably 562 also need to listen to "mouseout" events on the placeholder div. 563 564 "mouseActiveRadius" specifies how far the mouse can be from an item 565 and still activate it. If there are two or more points within this 566 radius, Flot chooses the closest item. For bars, the top-most bar 567 (from the latest specified data series) is chosen. 461 568 462 569 … … 474 581 where both ranges can be specified. "color" is color of the selection. 475 582 476 When selection support is enabled, a " selected" event will be emitted583 When selection support is enabled, a "plotselected" event will be emitted 477 584 on 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 585 handler gets one extra parameter with the ranges selected on the axes, 586 like 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 482 592 }); 483 593 484 594 485 Plot Me mbers595 Plot Methods 486 596 ------------ 487 597 488 The Plot object returned from the plot function has the following489 members:598 The Plot object returned from the plot function has some methods you 599 can call: 490 600 491 601 - clearSelection() … … 493 603 Clear the selection rectangle. 494 604 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 512 629 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 519 634 520 635 - setData(data) … … 524 639 that). You'll probably want to call draw() afterwards. 525 640 526 You can use this function to speed up redrawing a plot if you now527 that the ax is won't change. Put in the new data with641 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 528 643 setData(newdata) and call draw() afterwards, and you're good to 529 644 go. 645 530 646 531 532 - getData()533 534 Returns the data currently used. The data series returned are535 normalized with missing settings filled in. So for instance to536 find out what color Flot has assigned to the data series, you537 could do this:538 539 var series = plot.getData();540 for (var i = 0; i < series.length; ++i)541 alert([i].color);542 543 647 - setupGrid() 544 648 … … 554 658 555 659 Redraws the canvas. 660 661 662 There are also some members that let you peek inside the internal 663 workings of Flot which in some cases is useful. Note that if you change 664 something in the objects returned, you're changing the objects used by 665 Flot 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 1 Flot 0.5 2 -------- 3 4 Backwards API change summary: Timestamps are now in UTC. Also 5 "selected" event -> becomes "plotselected" with new data, the 6 parameters for setSelection are now different (but backwards 7 compatibility hooks are in place), coloredAreas becomes markings with 8 a new interface (but backwards compatibility hooks are in place). 9 10 11 Interactivity: 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 16 Highlighting: you can now highlight points and datapoints are 17 autohighlighted when you hover over them (if hovering is turned on). 18 19 Support for dual axis has been added (based on patch by someone who's 20 annoyed and /david). For each data series you can specify which axes 21 it belongs to, and there are two more axes, x2axis and y2axis, to 22 customize. This affects the "selected" event which has been renamed to 23 "plotselected" and spews out { xaxis: { from: -10, to: 20 } ... }, 24 setSelection in which the parameters are on a new form (backwards 25 compatible hooks are in place so old code shouldn't break) and 26 markings (formerly coloredAreas). 27 28 Timestamps in time mode are now displayed according to 29 UTC instead of the time zone of the visitor. This affects the way the 30 timestamps should be input; you'll probably have to offset the 31 timestamps according to your local time zone. It also affects any 32 custom date handling code (which basically now should use the 33 equivalent UTC date mehods, e.g. .setUTCMonth() instead of 34 .setMonth(). 35 36 Added support for specifying the size of tick labels (axis.labelWidth, 37 axis.labelHeight). Useful for specifying a max label size to keep 38 multiple plots aligned. 39 40 Markings, previously coloredAreas, are now specified as ranges on the 41 axes, like { xaxis: { from: 0, to: 10 }}. Furthermore with markings 42 you can now draw horizontal/vertical lines by setting from and to to 43 the same coordinate (idea from line support patch by by Ryan Funduk). 44 45 The "fill" option can now be a number that specifies the opacity of 46 the fill. 47 48 You can now specify a coordinate as null (like [2, null]) and Flot 49 will take the other coordinate into account when scaling the axes 50 (based on patch by joebno). 51 52 New option for bars "align". Set it to "center" to center the bars on 53 the value they represent. 54 55 setSelection now takes a second parameter which you can use to prevent 56 the method from firing the "plotselected" handler. 57 58 Using the "container" option in legend now overwrites the container 59 element instead of just appending to it (fixes infinite legend bug, 60 reported by several people, fix by Brad Dewey). 61 62 Fixed a bug in calculating spacing around the plot (reported by 63 timothytoe). Fixed a bug in finding max values for all-negative data 64 sets. Prevent the possibility of eternal looping in tick calculations. 65 Fixed a bug when borderWidth is set to 0 (reported by 66 Rob/sanchothefat). Fixed a bug with drawing bars extending below 0 67 (reported by James Hewitt, patch by Ryan Funduk). Fixed a 68 bug with line widths of bars (reported by MikeM). Fixed a bug with 69 'nw' and 'sw' legend positions. Improved the handling of axis 70 auto-scaling with bars. Fixed a bug with multi-line x-axis tick 71 labels (reported by Luca Ciano). IE-fix help by Savage Zhang. 72 73 1 74 Flot 0.4 2 75 -------- plugins/sfStatsPlugin/branches/1.0/web/js/flot/README.txt
r9680 r12995 22 22 include the excanvas script like this: 23 23 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]--> 25 25 26 26 If it's not working on your development IE 6.0, check that it has … … 45 45 <div id="placeholder" style="width:600px;height:300px"></div> 46 46 47 You can also do it with an external stylesheet. 47 You can also do it with an external stylesheet. Make sure that the 48 placeholder isn't within something with a display:none CSS property - 49 in that case, Flot has trouble measuring label dimensions which 50 results in garbled looks and might have trouble measuring the 51 placeholder dimensions which is fatal (it'll throw an exception). 48 52 49 Then on document ready, run the plot function: 53 Then when the div is ready in the DOM, which is usually on document 54 ready, run the plot function: 50 55 51 56 $.plot($("#placeholder"), data, options); … … 54 59 settings if you want to customize the plot. Take a look at the 55 60 examples for some ideas of what to put in or look at the reference 56 in the file "API.txt". 61 in 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 } }); 57 65 58 66 The plot function immediately draws the chart and then returns a Plot plugins/sfStatsPlugin/branches/1.0/web/js/flot/TODO
r9680 r12995 12 12 selection 13 13 - user should be able to cancel selection with escape 14 - select points15 14 16 15 interactive zooming … … 19 18 - auto-margins 20 19 21 support for highlighting stuff22 - lines23 - points24 25 20 legend 26 21 - interactive auto-highlight of graph? 22 - ability to specify noRows instead of just noColumns 27 23 28 24 labels 29 25 - 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 36 27 37 28 error margin indicators 38 29 - for scientific/statistical purposes 30 31 hi-low bars 39 32 40 33 non-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. 2 2 * 3 * Released under the MIT license by iola, December 2007.3 * Released under the MIT license by IOLA, December 2007. 4 4 * 5 5 */ … … 12 12 // or { data: [ [x1, y1], [x2, y2], ... ], label: "some label" } 13 13 14 var series = [] ;15 varoptions = {14 var series = [], 15 options = { 16 16 // the color theme used for graphs 17 17 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 28 96 }, 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 = {}; 94 108 95 109 this.setData = setData; … … 101 115 this.getPlotOffset = function() { return plotOffset; }; 102 116 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; 104 120 105 121 // initialize … … 144 160 if (options.yaxis.noTicks && options.yaxis.ticks == null) 145 161 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; 146 166 } 147 167 … … 150 170 151 171 // collect what we already got of colors 152 var neededColors = series.length ;153 var usedColors = [];154 varassignedColors = [];172 var neededColors = series.length, 173 usedColors = [], 174 assignedColors = []; 155 175 for (i = 0; i < series.length; ++i) { 156 176 var sc = series[i].color; … … 171 191 172 192 // produce colors as needed 173 var colors = []; 174 var variation = 0; 193 var colors = [], variation = 0; 175 194 i = 0; 176 195 while (colors.length < neededColors) { … … 216 235 if (s.shadowSize == null) 217 236 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; 218 245 } 219 246 } 220 247 221 248 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 225 259 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; 227 272 for (var j = 0; j < data.length; ++j) { 228 273 if (data[j] == null) … … 232 277 233 278 // 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)) 235 294 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 } 258 304 } 259 305 … … 283 329 eventHolder = $([overlay, canvas]); 284 330 285 286 331 // bind events 287 if (options.selection.mode != null) { 288 eventHolder.mousedown(onMouseDown); 289 332 if (options.selection.mode != null || options.grid.hoverable) { 290 333 // FIXME: temp. work-around until jQuery bug 1871 is fixed 291 334 eventHolder.each(function () { 292 335 this.onmousemove = onMouseMove; 293 336 }); 337 338 if (options.selection.mode != null) 339 eventHolder.mousedown(onMouseDown); 294 340 } 295 341 … … 299 345 300 346 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]); 311 366 312 367 setSpacing(); … … 357 412 if (typeof axisOptions.ticks == "number" && axisOptions.ticks > 0) 358 413 noTicks = axisOptions.ticks; 359 else if (axis == xaxis)414 else if (axis == axes.xaxis || axis == axes.x2axis) 360 415 noTicks = canvasWidth / 100; 361 416 else … … 383 438 if (escape) { 384 439 switch (c) { 385 case 'h': c = "" + d.get Hours(); break;386 case 'H': c = leftPad(d.get Hours()); break;387 case 'M': c = leftPad(d.get Minutes()); break;388 case 'S': c = leftPad(d.get Seconds()); break;389 case 'd': c = "" + d.get Date(); break;390 case 'm': c = "" + (d.get Month() + 1); break;391 case 'y': c = "" + d.get FullYear(); break;392 case 'b': c = "" + monthNames[d.get Month()]; 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; 393 448 } 394 449 r.push(c); … … 477 532 478 533 if (unit == "second") 479 d.set Seconds(floorInBase(d.getSeconds(), tickSize));534 d.setUTCSeconds(floorInBase(d.getUTCSeconds(), tickSize)); 480 535 if (unit == "minute") 481 d.set Minutes(floorInBase(d.getMinutes(), tickSize));536 d.setUTCMinutes(floorInBase(d.getUTCMinutes(), tickSize)); 482 537 if (unit == "hour") 483 d.set Hours(floorInBase(d.getHours(), tickSize));538 d.setUTCHours(floorInBase(d.getUTCHours(), tickSize)); 484 539 if (unit == "month") 485 d.set Month(floorInBase(d.getMonth(), tickSize));540 d.setUTCMonth(floorInBase(d.getUTCMonth(), tickSize)); 486 541 if (unit == "year") 487 d.set FullYear(floorInBase(d.getFullYear(), tickSize));542 d.setUTCFullYear(floorInBase(d.getUTCFullYear(), tickSize)); 488 543 489 544 // reset smaller components 490 d.set Milliseconds(0);545 d.setUTCMilliseconds(0); 491 546 if (step >= timeUnitSize.minute) 492 d.set Seconds(0);547 d.setUTCSeconds(0); 493 548 if (step >= timeUnitSize.hour) 494 d.set Minutes(0);549 d.setUTCMinutes(0); 495 550 if (step >= timeUnitSize.day) 496 d.set Hours(0);551 d.setUTCHours(0); 497 552 if (step >= timeUnitSize.day * 4) 498 d.set Date(1);553 d.setUTCDate(1); 499 554 if (step >= timeUnitSize.year) 500 d.set Month(0);501 502 503 var carry = 0, v ;555 d.setUTCMonth(0); 556 557 558 var carry = 0, v = Number.NaN, prev; 504 559 do { 560 prev = v; 505 561 v = d.getTime(); 506 562 ticks.push({ v: v, label: axis.tickFormatter(v, axis) }); … … 510 566 // up but we need to take care of fractions 511 567 // so we don't end up in the middle of a day 512 d.set Date(1);568 d.setUTCDate(1); 513 569 var start = d.getTime(); 514 d.set Month(d.getMonth() + 1);570 d.setUTCMonth(d.getUTCMonth() + 1); 515 571 var end = d.getTime(); 516 572 d.setTime(v + carry * timeUnitSize.hour + (end - start) * tickSize); 517 carry = d.get Hours();518 d.set Hours(0);573 carry = d.getUTCHours(); 574 d.setUTCHours(0); 519 575 } 520 576 else 521 d.set Month(d.getMonth() + tickSize);577 d.setUTCMonth(d.getUTCMonth() + tickSize); 522 578 } 523 579 else if (unit == "year") { 524 d.set FullYear(d.getFullYear() + tickSize);580 d.setUTCFullYear(d.getUTCFullYear() + tickSize); 525 581 } 526 582 else 527 583 d.setTime(v + step); 528 } while (v < axis.max );584 } while (v < axis.max && v != prev); 529 585 530 586 return ticks; … … 600 656 generator = function (axis) { 601 657 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; 605 662 do { 663 prev = v; 606 664 v = start + i * axis.tickSize; 607 665 ticks.push({ v: v, label: axis.tickFormatter(v, axis) }); 608 666 ++i; 609 } while (v < axis.max );667 } while (v < axis.max && v != prev); 610 668 return ticks; 611 669 }; … … 622 680 else 623 681 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 638 688 function setTicks(axis, axisOptions) { 639 689 axis.ticks = []; 690 691 if (!axis.used) 692 return; 640 693 641 694 if (axisOptions.ticks == null) … … 680 733 681 734 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)); 705 799 706 800 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 709 813 plotWidth = canvasWidth - plotOffset.left - plotOffset.right; 710 711 // set width for labels; to avoid measuring the widths of712 // the labels, we construct fixed-size boxes and put the713 // labels inside them, the fixed-size boxes are easy to714 // mid-align715 xLabelBoxWidth = plotWidth / 6;716 717 // measure x label heights718 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;734 814 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); 737 821 } 738 822 … … 744 828 } 745 829 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 754 859 function drawGrid() { 755 860 var i; … … 760 865 761 866 // draw background, if any 762 if (options.grid.backgroundColor != null) {867 if (options.grid.backgroundColor) { 763 868 ctx.fillStyle = options.grid.backgroundColor; 764 869 ctx.fillRect(0, 0, plotWidth, plotHeight); 765 870 } 766 871 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; 775 893 776 894 // 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) 800 897 continue; 801 898 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 } 805 929 } 806 930 } … … 810 934 ctx.strokeStyle = options.grid.tickColor; 811 935 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) 816 940 continue; // skip those lying on the axes 817 941 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) 825 950 continue; 826 951 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 830 976 ctx.stroke(); 831 977 … … 836 982 ctx.lineJoin = "round"; 837 983 ctx.strokeRect(0, 0, plotWidth, plotHeight); 838 ctx.restore(); 839 } 984 } 985 986 ctx.restore(); 840 987 } 841 988 … … 843 990 target.find(".tickLabels").remove(); 844 991 845 var i, tick;846 992 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 }); 863 1019 864 1020 html += '</div>'; … … 877 1033 878 1034 function drawSeriesLines(series) { 879 function plotLine(data, offset ) {1035 function plotLine(data, offset, axisx, axisy) { 880 1036 var prev, cur = null, drawx = null, drawy = null; 881 1037 … … 892 1048 893 1049 // 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) 896 1052 continue; // line segment is outside 897 1053 // 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) 903 1059 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; 906 1062 } 907 1063 908 1064 // 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) 911 1067 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) 917 1073 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; 920 1076 } 921 1077 922 1078 // 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) 925 1081 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) 931 1087 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; 934 1090 } 935 1091 936 1092 // 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) 939 1095 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) 945 1101 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; 955 1111 ctx.lineTo(drawx, drawy); 956 1112 } … … 958 1114 } 959 1115 960 function plotLineArea(data ) {1116 function plotLineArea(data, axisx, axisy) { 961 1117 var prev, cur = null; 962 1118 963 var bottom = Math.min(Math.max(0, yaxis.min), yaxis.max);1119 var bottom = Math.min(Math.max(0, axisy.min), axisy.max); 964 1120 var top, lastX = 0; 965 1121 … … 972 1128 if (areaOpen && prev != null && cur == null) { 973 1129 // close area 974 ctx.lineTo( tHoz(lastX), tVert(bottom));1130 ctx.lineTo(axisx.p2c(lastX), axisy.p2c(bottom)); 975 1131 ctx.fill(); 976 1132 areaOpen = false; … … 987 1143 988 1144 // 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) 991 1147 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) 997 1153 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; 1000 1156 } 1001 1157 1002 1158 // 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) 1005 1161 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) 1011 1167 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; 1014 1170 } 1015 1171 … … 1017 1173 // open area 1018 1174 ctx.beginPath(); 1019 ctx.moveTo( tHoz(x1), tVert(bottom));1175 ctx.moveTo(axisx.p2c(x1), axisy.p2c(bottom)); 1020 1176 areaOpen = true; 1021 1177 } 1022 1178 1023 1179 // 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)); 1027 1183 continue; 1028 1184 } 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)); 1032 1188 continue; 1033 1189 } … … 1041 1197 1042 1198 // 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; 1050 1206 } 1051 1207 1052 1208 // 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; 1060 1216 } 1061 1217 … … 1064 1220 // to fill 1065 1221 if (x1 != x1old) { 1066 if (y1 <= yaxis.min)1067 top = yaxis.min;1222 if (y1 <= axisy.min) 1223 top = axisy.min; 1068 1224 else 1069 top = yaxis.max;1225 top = axisy.max; 1070 1226 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)); 1073 1229 } 1074 1230 1075 1231 // 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)); 1078 1234 1079 1235 // fill the other rectangle if it's there 1080 1236 if (x2 != x2old) { 1081 if (y2 <= yaxis.min)1082 top = yaxis.min;1237 if (y2 <= axisy.min) 1238 top = axisy.min; 1083 1239 else 1084 top = yaxis.max;1240 top = axisy.max; 1085 1241 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)); 1088 1244 } 1089 1245 … … 1092 1248 1093 1249 if (areaOpen) { 1094 ctx.lineTo( tHoz(lastX), tVert(bottom));1250 ctx.lineTo(axisx.p2c(lastX), axisy.p2c(bottom)); 1095 1251 ctx.fill(); 1096 1252 } … … 1108 1264 ctx.lineWidth = sw / 2; 1109 1265 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); 1111 1267 1112 1268 ctx.lineWidth = sw / 2; 1113 1269 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); 1115 1271 } 1116 1272 1117 1273 ctx.lineWidth = lw; 1118 1274 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); 1125 1279 ctx.restore(); 1126 1280 } 1127 1281 1128 1282 function drawSeriesPoints(series) { 1129 function plotPoints(data, radius, fill ) {1283 function plotPoints(data, radius, fill, axisx, axisy) { 1130 1284 for (var i = 0; i < data.length; ++i) { 1131 1285 if (data[i] == null) … … 1133 1287 1134 1288 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) 1136 1290 continue; 1137 1291 1138 1292 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); 1140 1294 if (fill) 1141 1295 ctx.fill(); … … 1144 1298 } 1145 1299 1146 function plotPointShadows(data, offset, radius ) {1300 function plotPointShadows(data, offset, radius, axisx, axisy) { 1147 1301 for (var i = 0; i < data.length; ++i) { 1148 1302 if (data[i] == null) … … 1150 1304 1151 1305 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) 1153 1307 continue; 1154 1308 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); 1156 1310 ctx.stroke(); 1157 1311 } … … 1167 1321 ctx.lineWidth = sw / 2; 1168 1322 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); 1170 1325 1171 1326 ctx.lineWidth = sw / 2; 1172 1327 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); 1174 1330 } 1175 1331 1176 1332 ctx.lineWidth = series.points.lineWidth; 1177 1333 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); 1180 1337 ctx.restore(); 1181 1338 } 1182 1339 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 1183 1418 function drawSeriesBars(series) { 1184 function plotBars(data, bar Width, offset, fill) {1419 function plotBars(data, barLeft, barRight, offset, fill, axisx, axisy) { 1185 1420 for (var i = 0; i < data.length; i++) { 1186 1421 if (data[i] == null) 1187 1422 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); 1244 1424 } 1245 1425 } … … 1249 1429 ctx.lineJoin = "round"; 1250 1430 1251 var bw = series.bars.barWidth;1252 var lw = Math.min(series.bars.lineWidth, bw);1253 1431 // FIXME: figure out a way to add shadows 1254 1432 /* 1433 var bw = series.bars.barWidth; 1434 var lw = series.bars.lineWidth; 1255 1435 var sw = series.shadowSize; 1256 1436 if (sw > 0) { … … 1265 1445 }*/ 1266 1446 1267 ctx.lineWidth = lw;1447 ctx.lineWidth = series.bars.lineWidth; 1268 1448 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); 1274 1452 ctx.restore(); 1275 1453 } 1276 1454 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 1277 1470 function insertLegend() { 1278 1471 target.find(".legend").remove(); … … 1305 1498 fragments.push('</tr>'); 1306 1499 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); 1335 1610 } 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 1350 1618 function onMouseMove(ev) { 1351 1619 // FIXME: temp. work-around until jQuery bug 1871 is fixed … … 1360 1628 lastMousePos.pageY = e.pageY; 1361 1629 } 1630 1631 if (options.grid.hoverable && !hoverTimeout) 1632 hoverTimeout = setTimeout(onHover, 100); 1633 1634 if (selection.active) 1635 updateSelection(lastMousePos); 1362 1636 } 1363 1637 … … 1381 1655 setSelectionPos(selection.first, e); 1382 1656 1383 if (selectionInterval != null)1384 clearInterval(selectionInterval);1385 1657 lastMousePos.pageX = null; 1386 selection Interval = setInterval(updateSelectionOnMouseMove, 200);1658 selection.active = true; 1387 1659 $(document).one("mouseup", onSelectionMouseUp); 1388 1660 } 1389 1661 1390 1662 function onClick(e) { 1391 if ( ignoreClick) {1392 ignoreClick= false;1663 if (clickIsMouseUp) { 1664 clickIsMouseUp = false; 1393 1665 return; 1394 1666 } 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); 1404 1825 } 1405 1826 1406 1827 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 } ]); 1433 1848 } 1434 1849 1435 1850 function onSelectionMouseUp(e) { 1851 // revert drag stuff for old-school browsers 1436 1852 if (document.onselectstart !== undefined) 1437 1853 document.onselectstart = workarounds.onselectstart; … … 1439 1855 document.ondrag = workarounds.ondrag; 1440 1856 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 1455 1866 return false; 1456 1867 } 1457 1868 1458 1869 function setSelectionPos(pos, e) { 1459 var offset = $(overlay).offset();1870 var offset = eventHolder.offset(); 1460 1871 if (options.selection.mode == "y") { 1461 1872 if (pos == selection.first) … … 1481 1892 } 1482 1893 1483 function updateSelection OnMouseMove() {1484 if ( lastMousePos.pageX == null)1894 function updateSelection(pos) { 1895 if (pos.pageX == null) 1485 1896 return; 1486 1897 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(); 1491 1905 } 1492 1906 1493 1907 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 } 1512 1927 1513 1928 if (options.selection.mode == "x") { … … 1516 1931 } 1517 1932 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 1561 1945 function selectionIsSane() { 1562 1946 var minSize = 5; … … 1581 1965 function floorInBase(n, base) { 1582 1966 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; 1583 1976 } 1584 1977 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 8 8 The placeholder is a jQuery object that the plot will be put into. 9 9 This placeholder needs to have its width and height set as explained 10 in the README . The plot will modify some properties of the placeholder11 so it's recommended you simply pass in a div that you don't use for 12 anything else.10 in the README (go read that now if you haven't, it's short). The plot 11 will modify some properties of the placeholder so it's recommended you 12 simply pass in a div that you don't use for anything else. 13 13 14 14 The format of the data is documented below, as is the available 15 options. The "plot" object returned has some me mbers you can call.15 options. The "plot" object returned has some methods you can call. 16 16 These are documented separately below. 17 17 … … 39 39 Note that to simplify the internal logic in Flot both the x and y 40 40 values 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 withoutnoticing the wrong type.41 how to do this). This is a common problem because you might retrieve 42 data from the database and serialize them directly to JSON without 43 noticing the wrong type. 44 44 45 45 If 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. As47 a special case, a null value for lines is interpreted as a line 48 segment end, i.e. the two pointsbefore and after the null value are46 or couldn't be converted to a number, the point is ignored when 47 drawing. As a special case, a null value for lines is interpreted as a 48 line segment end, i.e. the point before and after the null value are 49 49 not connected. 50 50 … … 58 58 bars: specific bars options, 59 59 points: specific points options, 60 xaxis: 1 or 2, 61 yaxis: 1 or 2, 60 62 shadowSize: number 61 63 } … … 81 83 in which case you can hard-code the color index to prevent the colors 82 84 from jumping around between the series. 85 86 The "xaxis" and "yaxis" options specify which axis to use, specify 2 87 to get the secondary axis (x axis at top or y axis to the right). 88 E.g., you can use this to make a dual axis plot by specifying 89 { yaxis: 2 } for one data series. 83 90 84 91 The rest of the options are all documented below as they are the same … … 141 148 If you want the legend to appear somewhere else in the DOM, you can 142 149 specify "container" as a jQuery object to put the legend table into. 143 The "position" and "margin" etc. options will then be ignored. 150 The "position" and "margin" etc. options will then be ignored. Note 151 that it will overwrite the contents of the container. 144 152 145 153 … … 148 156 ==================== 149 157 150 xaxis, yaxis : {158 xaxis, yaxis, x2axis, y2axis: { 151 159 mode: null or "time" 152 160 min: null or number 153 161 max: null or number 154 162 autoscaleMargin: null or number 163 labelWidth: null or number 164 labelHeight: null or number 165 155 166 ticks: null or number or ticks array or (fn: range -> ticks array) 156 167 tickSize: number or array … … 160 171 } 161 172 162 The twoaxes have the same kind of options. The "mode" option173 The axes have the same kind of options. The "mode" option 163 174 determines how the data is interpreted, the default of null means as 164 175 decimal numbers. Use "time" for time series data, see the next section. … … 175 186 nearest whole tick. The default value is "null" for the x axis and 176 187 0.02 for the y axis which seems appropriate for most cases. 188 189 "labelWidth" and "labelHeight" specifies the maximum size of the tick 190 labels in pixels. They're useful in case you need to align several 191 plots. 177 192 178 193 The rest of the options deal with the ticks. … … 226 241 number of decimals to display (default is auto-detected). 227 242 228 Alternatively, for ultimate control you can provide a function to229 "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:243 Alternatively, for ultimate control over how ticks look like you can 244 provide a function to "tickFormatter". The function is passed two 245 parameters, the tick value and an "axis" object with information, and 246 should return a string. The default formatter looks like this: 232 247 233 248 function formatter(val, axis) { … … 254 269 ================ 255 270 271 Time series are a bit more difficult than scalar data because 272 calendars don't follow a simple base 10 system. For many cases, Flot 273 abstracts most of this away, but it can still be a bit difficult to 274 get the data into Flot. So we'll first discuss the data format. 275 256 276 The time series support in Flot is based on Javascript timestamps, 257 277 i.e. everywhere a time value is expected or handed over, a Javascript 258 timestamp number is used. This is not the same asa Date object. A278 timestamp number is used. This is a number, not a Date object. A 259 279 Javascript timestamp is the number of milliseconds since January 1, 260 1970 00:00:00 . This is almost the same as Unix timestamps, except it's280 1970 00:00:00 UTC. This is almost the same as Unix timestamps, except it's 261 281 in milliseconds, so remember to multiply by 1000! 262 282 263 You can see a timestamp by outputting 264 265 (new Date()).getTime() 283 You can see a timestamp like this 284 285 alert((new Date()).getTime()) 286 287 Normally you want the timestamps to be displayed according to a 288 certain time zone, usually the time zone in which the data has been 289 produced. However, Flot always displays timestamps according to UTC. 290 It has to as the only alternative with core Javascript is to interpret 291 the timestamps according to the time zone that the visitor is in, 292 which means that the ticks will shift unpredictably with the time zone 293 and daylight savings of each visitor. 294 295 So given that there's no good support for custom time zones in 296 Javascript, you'll have to take care of this server-side. 297 298 The easiest way to think about it is to pretend that the data 299 production time zone is UTC, even if it isn't. So if you have a 300 datapoint at 2002-02-20 08:00, you can generate a timestamp for eight 301 o'clock UTC even if it really happened eight o'clock UTC+0200. 266 302 267 303 In PHP you can get an appropriate timestamp with 268 'strtotime("2002-02-20 ") * 1000', in Python with269 ' time.mktime(datetime_object.timetuple()) * 1000', in .NET with304 'strtotime("2002-02-20 UTC") * 1000', in Python with 305 'calendar.timegm(datetime_object.timetuple()) * 1000', in .NET with 270 306 something like: 271 307 … … 274 310 System.TimeSpan span = new System.TimeSpan(System.DateTime.Parse("1/1/1970").Ticks); 275 311 System.DateTime time = input.Subtract(span); 276 return (int)(time.Ticks / 10000); 277 } 312 return (long)(time.Ticks / 10000); 313 } 314 315 Javascript also has some support for parsing date strings, so it is 316 possible to generate the timestamps manually client-side. 317 318 If you've already got the real UTC timestamp, it's too late to use the 319 pretend trick described above. But you can fix up the timestamps by 320 adding the time zone offset, e.g. for UTC+0200 you would add 2 hours 321 to the UTC timestamp you got. Then it'll look right on the plot. Most 322 programming environments have some means of getting the timezone 323 offset for a specific date. 278 324 279 325 Once you've got the timestamps into the data and specified "time" as 280 326 the 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: 327 format them. As always, you can tweak the ticks via the "ticks" option 328 - just remember that the values should be timestamps (numbers), not 329 Date objects. 330 331 Tick generation and formatting can also be controlled separately 332 through the following axis options: 286 333 287 334 xaxis, yaxis: { … … 322 369 tickFormatter: function (val, axis) { 323 370 var d = new Date(val); 324 return d.get Date() + "/" + (d.getMonth() + 1);371 return d.getUTCDate() + "/" + (d.getUTCMonth() + 1); 325 372 } 326 373 … … 342 389 343 390 lines, points, bars: { 344 show: boolean ,345 lineWidth: number ,346 fill: boolean ,347 fillColor: color or null391 show: boolean 392 lineWidth: number 393 fill: boolean or number 394 fillColor: color 348 395 } 349 396 … … 354 401 bars: { 355 402 barWidth: number 403 align: "left" or "center" 356 404 } 357 405 … … 370 418 }; 371 419 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 423 area graphs. You can use "fillColor" to specify the color of the fill. 424 If "fillColor" evaluates to false (default for everything except 425 points), the fill color is auto-set to the color of the data series. 426 You can adjust the opacity of the fill by setting fill to a number 427 between 0 (fully transparent) and 1 (fully opaque). 428 429 "barWidth" is the width of the bars in units of the x axis, contrary 430 to most other measures that are specified in pixels. For instance, for 431 time series the unit is milliseconds so 24 * 60 * 60 * 1000 produces 432 bars with the width of a day. "align" specifies whether a bar should 433 be left-aligned (default) or centered on top of the value it 434 represents. 380 435 381 436 The "colors" array specifies a default color theme to get colors for … … 400 455 tickColor: color 401 456 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) 404 458 borderWidth: number 405 459 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 465 The grid is the thing with the axes and a number of ticks. "color" 409 466 is the color of the grid itself whereas "backgroundColor" specifies 410 467 the background color inside the grid area. The default value of null 411 468 means that the background is transparent. You should only need to set 412 backgroundColor if want the grid area to be a different color from the469 backgroundColor if you want the grid area to be a different color from the 413 470 page color. Otherwise you might as well just set the background color 414 471 of the page with CSS. … … 420 477 to disable the border. 421 478 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 480 background of the plot. You can either specify an array of ranges on 481 the form { xaxis: { from, to }, yaxis: { from, to } } (secondary axis 482 coordinates with x2axis/y2axis) or with a function that returns such 483 an array given the axes for the plot in an object as the first 484 parameter. 485 486 You can set the color of markings by specifying "color" in the ranges 487 object. Here's an example array: 488 489 markings: [ { xaxis: { from: 0, to: 2 }, yaxis: { from: 10, to: 10 }, color: "#bb0000" }, ... ] 432 490 433 491 If 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. 492 border of the plot. So for example if you only specify { xaxis: { 493 from: 0, to: 2 } } it means an area that extends from the top to the 494 bottom of the plot in the x range 0-2. 495 496 A line is drawn if from and to are the same, e.g. 497 498 markings: [ { yaxis: { from: 1, to: 1 } }, ... ] 499 500 would draw a line parallel to the x axis at y = 1. You can control the 501 line width with "lineWidth" in the ranges objects. 436 502 437 503 An example function might look like this: 438 504 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; 444 510 } 445 511 … … 447 513 If you set "clickable" to true, the plot will listen for click events 448 514 on 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: 515 a position and a nearby data item object as parameters. The coordinates 516 are available both in the unit of the axes (not in pixels) and in 517 global screen coordinates. 518 519 Likewise, if you set "hoverable" to true, the plot will listen for 520 mouse move events on the plot area and fire a "plothover" event with 521 the same parameters as the "plotclick" event. If "autoHighlight" is 522 true (the default), nearby data items are highlighted automatically. 523 If needed, you can disable highlighting and control it yourself with 524 the highlight/unhighlight plot methods described elsewhere. 525 526 You can use "plotclick" and "plothover" events like this: 452 527 453 528 $.plot($("#placeholder"), [ d ], { grid: { clickable: true } }); 454 529 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 } 457 539 }); 458 540 459 Support for hover indications or for associating the clicks with any 460 specific data is still forthcoming. 541 The 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 551 For instance, if you have specified the data like this 552 553 $.plot($("#placeholder"), [ { label: "Foo", data: [[0, 10], [7, 3]] } ], ...); 554 555 and the mouse is near the point (7, 3), "datapoint" is the [7, 3] we 556 specified, "dataIndex" will be 1, "series" is a normalized series 557 object with among other things the "Foo" label in series.label and the 558 color in series.color, and "seriesIndex" is 0. 559 560 If you use the above events to update some other information and want 561 to clear out that info in case the mouse goes away, you'll probably 562 also need to listen to "mouseout" events on the placeholder div. 563 564 "mouseActiveRadius" specifies how far the mouse can be from an item 565 and still activate it. If there are two or more points within this 566 radius, Flot chooses the closest item. For bars, the top-most bar 567 (from the latest specified data series) is chosen. 461 568 462 569 … … 474 581 where both ranges can be specified. "color" is color of the selection. 475 582 476 When selection support is enabled, a " selected" event will be emitted583 When selection support is enabled, a "plotselected" event will be emitted 477 584 on 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 585 handler gets one extra parameter with the ranges selected on the axes, 586 like 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 482 592 }); 483 593 484 594 485 Plot Me mbers595 Plot Methods 486 596 ------------ 487 597 488 The Plot object returned from the plot function has the following489 members:598 The Plot object returned from the plot function has some methods you 599 can call: 490 600 491 601 - clearSelection() … … 493 603 Clear the selection rectangle. 494 604 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 } });