From 938928b83dc0dfe70f0858205ea11a9d0a650e69 Mon Sep 17 00:00:00 2001 From: "olau@iola.dk" Date: Mon, 22 Sep 2008 17:52:33 +0000 Subject: [PATCH] Support for preventing event in setSelection and revamped coloredAreas as markings with support for lines git-svn-id: https://flot.googlecode.com/svn/trunk@91 1e0a6537-2640-0410-bfb7-f154510ff394 --- API.txt | 52 ++-- NEWS.txt | 16 +- examples/visitors.html | 32 +-- examples/zooming.html | 12 +- jquery.flot.js | 576 +++++++++++++++++++++-------------------- 5 files changed, 357 insertions(+), 331 deletions(-) diff --git a/API.txt b/API.txt index da84735..f2f59df 100644 --- a/API.txt +++ b/API.txt @@ -454,8 +454,7 @@ Customizing the grid backgroundColor: color or null tickColor: color labelMargin: number - coloredAreas: array of areas or (fn: plot area -> array of areas) - coloredAreasColor: color + markings: array of markings or (fn: axes -> array of markings) borderWidth: number clickable: boolean hoverable: boolean @@ -477,28 +476,37 @@ labels with CSS, e.g. to change the color. They have class "tickLabel". "borderWidth" is the width of the border around the plot. Set it to 0 to disable the border. -"coloredAreas" is an array of areas that will be drawn on top of the -background. You can either specify an array of objects with { x1, y1, -x2, y2 } or a function that returns such an array given the plot area -as { xmin, xmax, ymin, ymax }. The default color of the areas are -"coloredAreasColor". You can override the color of individual areas by -specifying "color" in the area object. +"markings" is used to draw simple lines and rectangular areas in the +background of the plot. You can either specify an array of ranges on +the form { xaxis: { from, to }, yaxis: { from, to } } (secondary axis +coordinates with x2axis/y2axis) or with a function that returns such +an array given the axes for the plot in an object as the first +parameter. -Here's an example array: +You can set the color of markings by specifying "color" in the ranges +object. Here's an example array: - coloredAreas: [ { x1: 0, y1: 10, x2: 2, y2: 15, color: "#bb0000" }, ... ] + markings: [ { xaxis: { from: 0, to: 2 }, yaxis: { from: 10, to: 10 }, color: "#bb0000" }, ... ] If you leave out one of the values, that value is assumed to go to the -border of the plot. So for example { x1: 0, x2: 2 } means an area that -extends from the top to the bottom of the plot in the x range 0-2. +border of the plot. So for example if you only specify { xaxis: { +from: 0, to: 2 } } it means an area that extends from the top to the +bottom of the plot in the x range 0-2. + +A line is drawn if from and to are the same, e.g. + + markings: [ { yaxis: { from: 1, to: 1 } }, ... ] + +would draw a line parallel to the x axis at y = 1. You can control the +line width with "lineWidth" in the ranges objects. An example function might look like this: - coloredAreas: function (plotarea) { - var areas = []; - for (var x = Math.floor(plotarea.xmin); x < plotarea.xmax; x += 2) - areas.push({ x1: x, x2: x + 1 }); - return areas; + markings: function (axes) { + var markings = []; + for (var x = Math.floor(axes.xaxis.min); x < axes.xaxis.max; x += 2) + markings.push({ xaxis: { from: x, to: x + 1 } }); + return markings; } @@ -595,7 +603,7 @@ can call: Clear the selection rectangle. - - setSelection(ranges) + - setSelection(ranges, preventEvent) Set the selection rectangle. The passed in ranges is on the same form as returned in the "plotselected" event. If the selection @@ -606,10 +614,10 @@ can call: setSelection({ xaxis: { from: 0, to: 10 }, yaxis: { from: 40, to: 60 } }); - setSelection will trigger the "plotselected" event when called so you - may have to do a bit of shortcircuiting to prevent an eternal loop - if you invoke setSelection inside a "plotselected" handler. - + setSelection will trigger the "plotselected" event when called. If + you don't want that to happen, e.g. if you're inside a + "plotselected" handler, pass true as the second parameter. + - highlight(series, datapoint) diff --git a/NEWS.txt b/NEWS.txt index 0760da1..065d4a6 100644 --- a/NEWS.txt +++ b/NEWS.txt @@ -4,7 +4,8 @@ Flot x.x Backwards API change summary: Timestamps are now in UTC. Also "selected" event -> becomes "plotselected" with new data, the parameters for setSelection are now different (but backwards -compatibility hooks are in place). +compatibility hooks are in place), coloredAreas becomes markings with +a new interface (but backwards compatibility hooks are in place). Interactivity: added a new "plothover" event and this and the @@ -19,9 +20,10 @@ Support for dual axis has been added (based on patch by someone who's annoyed and /david). For each data series you can specify which axes it belongs to, and there are two more axes, x2axis and y2axis, to customize. This affects the "selected" event which has been renamed to -"plotselected" and spews out { xaxis: { from: -10, to: 20 } ... } and +"plotselected" and spews out { xaxis: { from: -10, to: 20 } ... }, setSelection in which the parameters are on a new form (backwards -compatible hooks are in place so old code shouldn't break). +compatible hooks are in place so old code shouldn't break) and +markings (formerly coloredAreas). Timestamps in time mode are now displayed according to UTC instead of the time zone of the visitor. This affects the way the @@ -35,6 +37,11 @@ Added support for specifying the size of tick labels (axis.labelWidth, axis.labelHeight). Useful for specifying a max label size to keep multiple plots aligned. +Markings, previously coloredAreas, are now specified as ranges on the +axes, like { xaxis: { from: 0, to: 10 }}. Furthermore you can now draw +horizontal/vertical lines by setting from and to to the same +coordinate (idea from line support patch by by Ryan Funduk). + The "fill" option can now be a number that specifies the opacity of the fill. @@ -45,6 +52,9 @@ will take the other coordinate into account when scaling the axes New option for bars "align". Set it to "center" to center the bars on the value they represent. +setSelection now takes a second parameter which you can use to prevent +the method from firing the "plotselected" handler. + Using the "container" option in legend now overwrites the container element instead of just appending to it (fixes infinite legend bug, reported by several people, fix by Brad Dewey). diff --git a/examples/visitors.html b/examples/visitors.html index 7197b68..919f903 100644 --- a/examples/visitors.html +++ b/examples/visitors.html @@ -29,9 +29,9 @@ $(function () { d[i][0] += 60 * 60 * 1000; // helper for returning the weekends in a period - function weekendAreas(plotarea) { - var areas = []; - var d = new Date(plotarea.xmin); + function weekendAreas(axes) { + var markings = []; + var d = new Date(axes.xaxis.min); // go to the first Saturday d.setUTCDate(d.getUTCDate() - ((d.getUTCDay() + 1) % 7)) d.setUTCSeconds(0); @@ -39,19 +39,19 @@ $(function () { d.setUTCHours(0); var i = d.getTime(); do { - // when we don't set y1 and y2 the rectangle - // automatically extends to infinity in those directions - areas.push({ x1: i, x2: i + 2 * 24 * 60 * 60 * 1000 }); + // when we don't set yaxis the rectangle automatically + // extends to infinity upwards and downwards + markings.push({ xaxis: { from: i, to: i + 2 * 24 * 60 * 60 * 1000 } }); i += 7 * 24 * 60 * 60 * 1000; - } while (i < plotarea.xmax); + } while (i < axes.xaxis.max); - return areas; + return markings; } var options = { xaxis: { mode: "time" }, selection: { mode: "x" }, - grid: { coloredAreas: weekendAreas } + grid: { markings: weekendAreas } }; var plot = $.plot($("#placeholder"), [d], options); @@ -65,7 +65,6 @@ $(function () { }); // now connect the two - var internalSelection = false; $("#placeholder").bind("plotselected", function (event, ranges) { // do the zooming @@ -73,20 +72,13 @@ $(function () { $.extend(true, {}, options, { xaxis: { min: ranges.xaxis.from, max: ranges.xaxis.to } })); - - if (internalSelection) - return; // prevent eternal loop - internalSelection = true; - overview.setSelection(ranges); - internalSelection = false; + + // don't fire event on the overview to prevent eternal loop + overview.setSelection(ranges, true); }); $("#overview").bind("plotselected", function (event, ranges) { - if (internalSelection) - return; - internalSelection = true; plot.setSelection(ranges); - internalSelection = false; }); }); diff --git a/examples/zooming.html b/examples/zooming.html index 6040c02..0f3284b 100644 --- a/examples/zooming.html +++ b/examples/zooming.html @@ -63,7 +63,6 @@ $(function () { }); // now connect the two - var internalSelection = false; $("#placeholder").bind("plotselected", function (event, ranges) { // clamp the zooming to prevent eternal zoom @@ -79,18 +78,11 @@ $(function () { yaxis: { min: ranges.yaxis.from, max: ranges.yaxis.to } })); - if (internalSelection) - return; // prevent eternal loop - internalSelection = true; - overview.setSelection(ranges); - internalSelection = false; + // don't fire event on the overview to prevent eternal loop + overview.setSelection(ranges, true); }); $("#overview").bind("plotselected", function (event, ranges) { - if (internalSelection) - return; - internalSelection = true; plot.setSelection(ranges); - internalSelection = false; }); }); diff --git a/jquery.flot.js b/jquery.flot.js index 74cc4d5..4e513ef 100644 --- a/jquery.flot.js +++ b/jquery.flot.js @@ -11,100 +11,100 @@ // where series is either just the data as [ [x1, y1], [x2, y2], ... ] // or { data: [ [x1, y1], [x2, y2], ... ], label: "some label" } - var series = []; - var options = { + var series = [], + options = { // the color theme used for graphs colors: ["#edc240", "#afd8f8", "#cb4b4b", "#4da74d", "#9440ed"], - legend: { - show: true, - noColumns: 1, // number of colums in legend table - labelFormatter: null, // fn: string -> string - labelBoxBorderColor: "#ccc", // border color for the little label boxes - container: null, // container (as jQuery object) to put legend in, null means default on top of graph - position: "ne", // position of default legend container within plot - margin: 5, // distance from grid edge to default legend container within plot - backgroundColor: null, // null means auto-detect - backgroundOpacity: 0.85 // set to 0 to avoid background - }, - xaxis: { - mode: null, // null or "time" - min: null, // min. value to show, null means set automatically - max: null, // max. value to show, null means set automatically - autoscaleMargin: null, // margin in % to add if auto-setting min/max - ticks: null, // either [1, 3] or [[1, "a"], 3] or (fn: axis info -> ticks) or app. number of ticks for auto-ticks - tickFormatter: null, // fn: number -> string - labelWidth: null, // size of tick labels in pixels - labelHeight: null, - - // mode specific options - tickDecimals: null, // no. of decimals, null means auto - tickSize: null, // number or [number, "unit"] - minTickSize: null, // number or [number, "unit"] - monthNames: null, // list of names of months - timeformat: null // format string to use - }, - yaxis: { - autoscaleMargin: 0.02 - }, - x2axis: { - autoscaleMargin: null - }, - y2axis: { - autoscaleMargin: 0.02 - }, - points: { - show: false, - radius: 3, - lineWidth: 2, // in pixels - fill: true, - fillColor: "#ffffff" - }, - lines: { - show: false, - lineWidth: 2, // in pixels - fill: false, - fillColor: null - }, - bars: { - show: false, - lineWidth: 2, // in pixels - barWidth: 1, // in units of the x axis - fill: true, - fillColor: null, - align: "left" // or "center" - }, - grid: { - color: "#545454", // primary color used for outline and labels - backgroundColor: null, // null for transparent, else color - tickColor: "#dddddd", // color used for the ticks - labelMargin: 5, // in pixels - borderWidth: 2, - coloredAreas: null, // array of { x1, y1, x2, y2 } or fn: plot area -> areas - coloredAreasColor: "#f4f4f4", - // interactive stuff - clickable: false, - hoverable: false, - autoHighlight: true, // highlight in case mouse is near - mouseActiveRadius: 10 // how far the mouse can be away to activate an item - }, - selection: { - mode: null, // one of null, "x", "y" or "xy" - color: "#e8cfac" + legend: { + show: true, + noColumns: 1, // number of colums in legend table + labelFormatter: null, // fn: string -> string + labelBoxBorderColor: "#ccc", // border color for the little label boxes + container: null, // container (as jQuery object) to put legend in, null means default on top of graph + position: "ne", // position of default legend container within plot + margin: 5, // distance from grid edge to default legend container within plot + backgroundColor: null, // null means auto-detect + backgroundOpacity: 0.85 // set to 0 to avoid background + }, + xaxis: { + mode: null, // null or "time" + min: null, // min. value to show, null means set automatically + max: null, // max. value to show, null means set automatically + autoscaleMargin: null, // margin in % to add if auto-setting min/max + ticks: null, // either [1, 3] or [[1, "a"], 3] or (fn: axis info -> ticks) or app. number of ticks for auto-ticks + tickFormatter: null, // fn: number -> string + labelWidth: null, // size of tick labels in pixels + labelHeight: null, + + // mode specific options + tickDecimals: null, // no. of decimals, null means auto + tickSize: null, // number or [number, "unit"] + minTickSize: null, // number or [number, "unit"] + monthNames: null, // list of names of months + timeformat: null // format string to use + }, + yaxis: { + autoscaleMargin: 0.02 + }, + x2axis: { + autoscaleMargin: null + }, + y2axis: { + autoscaleMargin: 0.02 + }, + points: { + show: false, + radius: 3, + lineWidth: 2, // in pixels + fill: true, + fillColor: "#ffffff" + }, + lines: { + show: false, + lineWidth: 2, // in pixels + fill: false, + fillColor: null + }, + bars: { + show: false, + lineWidth: 2, // in pixels + barWidth: 1, // in units of the x axis + fill: true, + fillColor: null, + align: "left" // or "center" + }, + grid: { + color: "#545454", // primary color used for outline and labels + backgroundColor: null, // null for transparent, else color + tickColor: "#dddddd", // color used for the ticks + labelMargin: 5, // in pixels + borderWidth: 2, + markings: null, // array of ranges or fn: axes -> array of ranges + markingsColor: "#f4f4f4", + markingsLineWidth: 2, + // interactive stuff + clickable: false, + hoverable: false, + autoHighlight: true, // highlight in case mouse is near + mouseActiveRadius: 10 // how far the mouse can be away to activate an item + }, + selection: { + mode: null, // one of null, "x", "y" or "xy" + color: "#e8cfac" + }, + shadowSize: 4 }, - shadowSize: 4 - }; - var canvas = null, // the canvas for the plot itself - overlay = null, // canvas for interactive stuff on top of plot - eventHolder = null, // jQuery object that events should be bound to - ctx = null, octx = null, - target = target_, - xaxis = {}, yaxis = {}, - x2axis = {}, y2axis = {}, - plotOffset = { left: 0, right: 0, top: 0, bottom: 0}, - canvasWidth = 0, canvasHeight = 0, - plotWidth = 0, plotHeight = 0, - // dedicated to storing data for buggy standard compliance cases - workarounds = {}; + canvas = null, // the canvas for the plot itself + overlay = null, // canvas for interactive stuff on top of plot + eventHolder = null, // jQuery object that events should be bound to + ctx = null, octx = null, + target = target_, + axes = { xaxis: {}, yaxis: {}, x2axis: {}, y2axis: {} }, + plotOffset = { left: 0, right: 0, top: 0, bottom: 0}, + canvasWidth = 0, canvasHeight = 0, + plotWidth = 0, plotHeight = 0, + // dedicated to storing data for buggy standard compliance cases + workarounds = {}; this.setData = setData; this.setupGrid = setupGrid; @@ -114,7 +114,7 @@ this.getCanvas = function() { return canvas; }; this.getPlotOffset = function() { return plotOffset; }; this.getData = function() { return series; }; - this.getAxes = function() { return { xaxis: xaxis, yaxis: yaxis, x2axis: x2axis, y2axis: y2axis }; }; + this.getAxes = function() { return axes; }; this.highlight = highlight; this.unhighlight = unhighlight; @@ -159,6 +159,10 @@ options.xaxis.ticks = options.xaxis.noTicks; if (options.yaxis.noTicks && options.yaxis.ticks == null) options.yaxis.ticks = options.yaxis.noTicks; + if (options.grid.coloredAreas) + options.grid.markings = options.grid.coloredAreas; + if (options.grid.coloredAreasColor) + options.grid.markingsColor = options.grid.coloredAreasColor; } function fillInSeriesOptions() { @@ -231,24 +235,27 @@ if (s.shadowSize == null) s.shadowSize = options.shadowSize; if (s.xaxis && s.xaxis == 2) - s.xaxis = x2axis; + s.xaxis = axes.x2axis; else - s.xaxis = xaxis; + s.xaxis = axes.xaxis; if (s.yaxis && s.yaxis == 2) - s.yaxis = y2axis; + s.yaxis = axes.y2axis; else - s.yaxis = yaxis; + s.yaxis = axes.yaxis; } } function processData() { var topSentry = Number.POSITIVE_INFINITY, - bottomSentry = Number.NEGATIVE_INFINITY; - - xaxis.datamin = yaxis.datamin = x2axis.datamin = y2axis.datamin = topSentry; - xaxis.datamax = yaxis.datamax = x2axis.datamax = y2axis.datamax = bottomSentry; - xaxis.used = yaxis.used = x2axis.used = y2axis.used = false; + bottomSentry = Number.NEGATIVE_INFINITY, + axis; + for (axis in axes) { + axes[axis].datamin = topSentry; + axes[axis].datamax = bottomSentry; + axes[axis].used = false; + } + for (var i = 0; i < series.length; ++i) { var data = series[i].data, axisx = series[i].xaxis, @@ -288,17 +295,12 @@ } } - function setDefaultMinMax(axis) { - if (axis.datamin == topSentry) - axis.datamin = 0; - if (axis.datamax == bottomSentry) - axis.datamax = 1; + for (axis in axes) { + if (axes[axis].datamin == topSentry) + axes[axis].datamin = 0; + if (axes[axis].datamax == bottomSentry) + axes[axis].datamax = 1; } - - setDefaultMinMax(xaxis); - setDefaultMinMax(yaxis); - setDefaultMinMax(x2axis); - setDefaultMinMax(y2axis); } function constructCanvas() { @@ -347,7 +349,7 @@ prepareTickGeneration(axis, options); setTicks(axis, options); // add transformation helpers - if (axis == xaxis || axis == x2axis) { + if (axis == axes.xaxis || axis == axes.x2axis) { // data point to canvas coordinate axis.p2c = function (p) { return (p - axis.min) * axis.scale; }; // canvas coordinate to data point @@ -359,10 +361,8 @@ } } - setupAxis(xaxis, options.xaxis); - setupAxis(yaxis, options.yaxis); - setupAxis(x2axis, options.x2axis); - setupAxis(y2axis, options.y2axis); + for (var axis in axes) + setupAxis(axes[axis], options[axis]); setSpacing(); insertLabels(); @@ -411,7 +411,7 @@ var noTicks; if (typeof axisOptions.ticks == "number" && axisOptions.ticks > 0) noTicks = axisOptions.ticks; - else if (axis == xaxis || axis == x2axis) + else if (axis == axes.xaxis || axis == axes.x2axis) noTicks = canvasWidth / 100; else noTicks = canvasHeight / 60; @@ -786,10 +786,10 @@ } } - measureXLabels(xaxis); - measureYLabels(yaxis); - measureXLabels(x2axis); - measureYLabels(y2axis); + measureXLabels(axes.xaxis); + measureYLabels(axes.yaxis); + measureXLabels(axes.x2axis); + measureYLabels(axes.y2axis); // get the most space needed around the grid for things // that may stick out @@ -799,25 +799,25 @@ plotOffset.left = plotOffset.right = plotOffset.top = plotOffset.bottom = maxOutset; - if (xaxis.labelHeight > 0) - plotOffset.bottom = Math.max(maxOutset, xaxis.labelHeight + options.grid.labelMargin); - if (yaxis.labelWidth > 0) - plotOffset.left = Math.max(maxOutset, yaxis.labelWidth + options.grid.labelMargin); + if (axes.xaxis.labelHeight > 0) + plotOffset.bottom = Math.max(maxOutset, axes.xaxis.labelHeight + options.grid.labelMargin); + if (axes.yaxis.labelWidth > 0) + plotOffset.left = Math.max(maxOutset, axes.yaxis.labelWidth + options.grid.labelMargin); - if (x2axis.labelHeight > 0) - plotOffset.top = Math.max(maxOutset, x2axis.labelHeight + options.grid.labelMargin); + if (axes.x2axis.labelHeight > 0) + plotOffset.top = Math.max(maxOutset, axes.x2axis.labelHeight + options.grid.labelMargin); - if (y2axis.labelWidth > 0) - plotOffset.right = Math.max(maxOutset, y2axis.labelWidth + options.grid.labelMargin); + if (axes.y2axis.labelWidth > 0) + plotOffset.right = Math.max(maxOutset, axes.y2axis.labelWidth + options.grid.labelMargin); plotWidth = canvasWidth - plotOffset.left - plotOffset.right; plotHeight = canvasHeight - plotOffset.bottom - plotOffset.top; // precompute how much the axis is scaling a point in canvas space - xaxis.scale = plotWidth / (xaxis.max - xaxis.min); - yaxis.scale = plotHeight / (yaxis.max - yaxis.min); - x2axis.scale = plotWidth / (x2axis.max - x2axis.min); - y2axis.scale = plotHeight / (y2axis.max - y2axis.min); + axes.xaxis.scale = plotWidth / (axes.xaxis.max - axes.xaxis.min); + axes.yaxis.scale = plotHeight / (axes.yaxis.max - axes.yaxis.min); + axes.x2axis.scale = plotWidth / (axes.x2axis.max - axes.x2axis.min); + axes.y2axis.scale = plotHeight / (axes.y2axis.max - axes.y2axis.min); } function draw() { @@ -827,6 +827,35 @@ } } + function extractRange(ranges, coord) { + var firstAxis = coord + "axis", + secondaryAxis = coord + "2axis", + axis, from, to, reverse; + + if (ranges[firstAxis]) { + axis = axes[firstAxis]; + from = ranges[firstAxis].from; + to = ranges[firstAxis].to; + } + else if (ranges[secondaryAxis]) { + axis = axes[secondaryAxis]; + from = ranges[secondaryAxis].from; + to = ranges[secondaryAxis].to; + } + else { + // backwards-compat stuff - to be removed in future + axis = axes[firstAxis]; + from = ranges[coord + "1"]; + to = ranges[coord + "2"]; + } + + // auto-reverse as an added bonus + if (from != null && to != null && from > to) + return { from: to, to: from, axis: axis }; + + return { from: from, to: to, axis: axis }; + } + function drawGrid() { var i; @@ -840,44 +869,63 @@ ctx.fillRect(0, 0, plotWidth, plotHeight); } - // draw colored areas - if (options.grid.coloredAreas) { - var areas = options.grid.coloredAreas; - if ($.isFunction(areas)) - areas = areas({ xmin: xaxis.min, xmax: xaxis.max, ymin: yaxis.min, ymax: yaxis.max }); - - for (i = 0; i < areas.length; ++i) { - var a = areas[i]; + // draw markings + if (options.grid.markings) { + var markings = options.grid.markings; + if ($.isFunction(markings)) + // xmin etc. are backwards-compatible, to be removed in future + markings = markings({ xmin: axes.xaxis.min, xmax: axes.xaxis.max, ymin: axes.yaxis.min, ymax: axes.yaxis.max, xaxis: axes.xaxis, yaxis: axes.yaxis, x2axis: axes.x2axis, y2axis: axes.y2axis }); + + for (i = 0; i < markings.length; ++i) { + var m = markings[i], + xrange = extractRange(m, "x"), + yrange = extractRange(m, "y"); + + // fill in missing + if (xrange.from == null) + xrange.from = xrange.axis.min; + if (xrange.to == null) + xrange.to = xrange.axis.max; + if (yrange.from == null) + yrange.from = yrange.axis.min; + if (yrange.to == null) + yrange.to = yrange.axis.max; // clip - 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 tmp; - if (a.x1 > a.x2) { - tmp = a.x1; - a.x1 = a.x2; - a.x2 = tmp; - } - if (a.y1 > a.y2) { - tmp = a.y1; - a.y1 = a.y2; - a.y2 = tmp; - } + if (xrange.to < xrange.axis.min || xrange.from > xrange.axis.max || + yrange.to < yrange.axis.min || yrange.from > yrange.axis.max) + continue; + + xrange.from = Math.max(xrange.from, xrange.axis.min); + xrange.to = Math.min(xrange.to, xrange.axis.max); + yrange.from = Math.max(yrange.from, yrange.axis.min); + yrange.to = Math.min(yrange.to, yrange.axis.max); - 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) + if (xrange.from == xrange.to && yrange.from == yrange.to) continue; - ctx.fillStyle = a.color || options.grid.coloredAreasColor; - ctx.fillRect(Math.floor(xaxis.p2c(a.x1)), Math.floor(yaxis.p2c(a.y2)), - Math.floor(xaxis.p2c(a.x2) - xaxis.p2c(a.x1)), Math.floor(yaxis.p2c(a.y1) - yaxis.p2c(a.y2))); + // then draw + xrange.from = xrange.axis.p2c(xrange.from); + xrange.to = xrange.axis.p2c(xrange.to); + yrange.from = yrange.axis.p2c(yrange.from); + yrange.to = yrange.axis.p2c(yrange.to); + + if (xrange.from == xrange.to || yrange.from == yrange.to) { + // draw line + ctx.strokeStyle = m.color || options.grid.markingsColor; + ctx.lineWidth = m.lineWidth || options.grid.markingsLineWidth; + ctx.moveTo(Math.floor(xrange.from), Math.floor(yrange.from)); + ctx.lineTo(Math.floor(xrange.to), Math.floor(yrange.to)); + ctx.stroke(); + } + else { + // fill area + ctx.fillStyle = m.color || options.grid.markingsColor; + ctx.fillRect(Math.floor(xrange.from), + Math.floor(yrange.to), + Math.floor(xrange.to - xrange.from), + Math.floor(yrange.from - yrange.to)); + } } } @@ -885,41 +933,44 @@ ctx.lineWidth = 1; ctx.strokeStyle = options.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) + var v, axis = axes.xaxis; + for (i = 0; i < axis.ticks.length; ++i) { + v = axis.ticks[i].v; + if (v <= axis.min || v >= axes.xaxis.max) continue; // skip those lying on the axes - ctx.moveTo(Math.floor(xaxis.p2c(v)) + ctx.lineWidth/2, 0); - ctx.lineTo(Math.floor(xaxis.p2c(v)) + ctx.lineWidth/2, plotHeight); + ctx.moveTo(Math.floor(axis.p2c(v)) + ctx.lineWidth/2, 0); + ctx.lineTo(Math.floor(axis.p2c(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) + axis = axes.yaxis; + for (i = 0; i < axis.ticks.length; ++i) { + v = axis.ticks[i].v; + if (v <= axis.min || v >= axis.max) continue; - ctx.moveTo(0, Math.floor(yaxis.p2c(v)) + ctx.lineWidth/2); - ctx.lineTo(plotWidth, Math.floor(yaxis.p2c(v)) + ctx.lineWidth/2); + ctx.moveTo(0, Math.floor(axis.p2c(v)) + ctx.lineWidth/2); + ctx.lineTo(plotWidth, Math.floor(axis.p2c(v)) + ctx.lineWidth/2); } - - for (i = 0; i < x2axis.ticks.length; ++i) { - v = x2axis.ticks[i].v; - if (v <= x2axis.min || v >= x2axis.max) + + axis = axes.x2axis + for (i = 0; i < axis.ticks.length; ++i) { + v = axis.ticks[i].v; + if (v <= axis.min || v >= axis.max) continue; - ctx.moveTo(Math.floor(x2axis.p2c(v)) + ctx.lineWidth/2, -5); - ctx.lineTo(Math.floor(x2axis.p2c(v)) + ctx.lineWidth/2, 5); + ctx.moveTo(Math.floor(axis.p2c(v)) + ctx.lineWidth/2, -5); + ctx.lineTo(Math.floor(axis.p2c(v)) + ctx.lineWidth/2, 5); } - for (i = 0; i < y2axis.ticks.length; ++i) { - v = y2axis.ticks[i].v; - if (v <= y2axis.min || v >= y2axis.max) + axis = axes.y2axis; + for (i = 0; i < axis.ticks.length; ++i) { + v = axis.ticks[i].v; + if (v <= axis.min || v >= axis.max) continue; - ctx.moveTo(plotWidth-5, Math.floor(y2axis.p2c(v)) + ctx.lineWidth/2); - ctx.lineTo(plotWidth+5, Math.floor(y2axis.p2c(v)) + ctx.lineWidth/2); + ctx.moveTo(plotWidth-5, Math.floor(axis.p2c(v)) + ctx.lineWidth/2); + ctx.lineTo(plotWidth+5, Math.floor(axis.p2c(v)) + ctx.lineWidth/2); } ctx.stroke(); @@ -938,40 +989,33 @@ function insertLabels() { target.find(".tickLabels").remove(); - var i, tick; var html = '
'; - - // do the x-axis - for (i = 0; i < xaxis.ticks.length; ++i) { - tick = xaxis.ticks[i]; - if (!tick.label || tick.v < xaxis.min || tick.v > xaxis.max) - continue; - html += '
' + tick.label + "
"; + + function addLabels(axis, labelGenerator) { + for (var i = 0; i < axis.ticks.length; ++i) { + var tick = axis.ticks[i]; + if (!tick.label || tick.v < axis.min || tick.v > axis.max) + continue; + html += labelGenerator(tick, axis); + } } - // do the y-axis - for (i = 0; i < yaxis.ticks.length; ++i) { - tick = yaxis.ticks[i]; - if (!tick.label || tick.v < yaxis.min || tick.v > yaxis.max) - continue; - html += '
' + tick.label + "
"; - } + addLabels(axes.xaxis, function (tick, axis) { + return '
' + tick.label + "
"; + }); - // do the x2-axis - for (i = 0; i < x2axis.ticks.length; ++i) { - tick = x2axis.ticks[i]; - if (!tick.label || tick.v < x2axis.min || tick.v > x2axis.max) - continue; - html += '
' + tick.label + "
"; - } - // do the y2-axis - for (i = 0; i < y2axis.ticks.length; ++i) { - tick = y2axis.ticks[i]; - if (!tick.label || tick.v < y2axis.min || tick.v > y2axis.max) - continue; - html += '
' + tick.label + "
"; - } + addLabels(axes.yaxis, function (tick, axis) { + return '
' + tick.label + "
"; + }); + + addLabels(axes.x2axis, function (tick, axis) { + return '
' + tick.label + "
"; + }); + + addLabels(axes.y2axis, function (tick, axis) { + return '
' + tick.label + "
"; + }); html += '
'; @@ -1637,14 +1681,14 @@ canvasX = event.pageX - offset.left - plotOffset.left, canvasY = event.pageY - offset.top - plotOffset.top; - if (xaxis.used) - pos.x = xaxis.c2p(canvasX); - if (yaxis.used) - pos.y = yaxis.c2p(canvasY); - if (x2axis.used) - pos.x2 = x2axis.c2p(canvasX); - if (y2axis.used) - pos.y2 = y2axis.c2p(canvasY); + if (axes.xaxis.used) + pos.x = axes.xaxis.c2p(canvasX); + if (axes.yaxis.used) + pos.y = axes.yaxis.c2p(canvasY); + if (axes.x2axis.used) + pos.x2 = axes.x2axis.c2p(canvasX); + if (axes.y2axis.used) + pos.y2 = axes.y2axis.c2p(canvasY); var item = findNearbyItem(canvasX, canvasY); @@ -1787,19 +1831,19 @@ y2 = Math.min(selection.first.y, selection.second.y); var r = {}; - if (xaxis.used) - r.xaxis = { from: xaxis.c2p(x1), to: xaxis.c2p(x2) }; - if (x2axis.used) - r.x2axis = { from: x2axis.c2p(x1), to: x2axis.c2p(x2) }; - if (yaxis.used) - r.yaxis = { from: yaxis.c2p(y1), to: yaxis.c2p(y2) }; - if (y2axis.used) - r.yaxis = { from: y2axis.c2p(y1), to: y2axis.c2p(y2) }; + if (axes.xaxis.used) + r.xaxis = { from: axes.xaxis.c2p(x1), to: axes.xaxis.c2p(x2) }; + if (axes.x2axis.used) + r.x2axis = { from: axes.x2axis.c2p(x1), to: axes.x2axis.c2p(x2) }; + if (axes.yaxis.used) + r.yaxis = { from: axes.yaxis.c2p(y1), to: axes.yaxis.c2p(y2) }; + if (axes.y2axis.used) + r.yaxis = { from: axes.y2axis.c2p(y1), to: axes.y2axis.c2p(y2) }; target.trigger("plotselected", [ r ]); // backwards-compat stuff, to be removed in future - if (xaxis.used && yaxis.used) + if (axes.xaxis.used && axes.yaxis.used) target.trigger("selected", [ { x1: r.xaxis.from, y1: r.yaxis.from, x2: r.xaxis.to, y2: r.yaxis.to } ]); } @@ -1866,34 +1910,19 @@ triggerRedrawOverlay(); } } - - function setSelection(ranges) { - var axis, from, to; + + function setSelection(ranges, preventEvent) { + var range; if (options.selection.mode == "y") { selection.first.x = 0; selection.second.x = plotWidth; } else { - if (ranges.yaxis) { - axis = xaxis; - from = ranges.xaxis.from; - to = ranges.xaxis.to; - } - else if (ranges.x2axis) { - axis = x2axis; - from = ranges.x2axis.from; - to = ranges.x2axis.to; - } - else { - // backwards-compat stuff - to be removed in future - axis = xaxis; - from = ranges.x1; - to = ranges.x2; - } - - selection.first.x = axis.p2c(from); - selection.second.x = axis.p2c(to); + range = extractRange(ranges, "x"); + + selection.first.x = range.axis.p2c(range.from); + selection.second.x = range.axis.p2c(range.to); } if (options.selection.mode == "x") { @@ -1901,30 +1930,16 @@ selection.second.y = plotHeight; } else { - if (ranges.yaxis) { - axis = yaxis; - from = ranges.yaxis.from; - to = ranges.yaxis.to; - } - else if (ranges.y2axis) { - axis = y2axis; - from = ranges.y2axis.from; - to = ranges.y2axis.to; - } - else { - // backwards-compat stuff - to be removed in future - axis = yaxis; - from = ranges.y1; - to = ranges.y2; - } - - selection.first.y = axis.p2c(from); - selection.second.y = axis.p2c(to); + range = extractRange(ranges, "y"); + + selection.first.y = range.axis.p2c(range.from); + selection.second.y = range.axis.p2c(range.to); } selection.show = true; triggerRedrawOverlay(); - triggerSelectedEvent(); + if (!preventEvent) + triggerSelectedEvent(); } function selectionIsSane() { @@ -1951,6 +1966,15 @@ return base * Math.floor(n / base); } + function clamp(min, value, max) { + if (value < min) + return value; + else if (value > max) + return max; + else + return value; + } + // color helpers, inspiration from the jquery color animation // plugin by John Resig function Color (r, g, b, a) {