Commit d54a40c2 authored by olau@iola.dk's avatar olau@iola.dk

Move selection support to a plugin (based on patch from andershol),

move color code to separate jQuery plugin inlined in the Flot code
(and hosted here for the time being), fixup some various other little
things in preparation for the 0.6 release


git-svn-id: https://flot.googlecode.com/svn/trunk@220 1e0a6537-2640-0410-bfb7-f154510ff394
parent bc2bcd1b
......@@ -665,39 +665,6 @@ can set "hoverable" and "clickable" to false in the options for that
series, like this { data: [...], label: "Foo", clickable: false }.
Customizing the selection
=========================
selection: {
mode: null or "x" or "y" or "xy",
color: color
}
You enable selection support by setting the mode to one of "x", "y" or
"xy". In "x" mode, the user will only be able to specify the x range,
similarly for "y" mode. For "xy", the selection becomes a rectangle
where both ranges can be specified. "color" is color of the selection.
When selection support is enabled, a "plotselected" event will be emitted
on the DOM element you passed into the plot function. The event
handler gets one extra parameter with the ranges selected on the axes,
like this:
placeholder.bind("plotselected", function(event, ranges) {
alert("You selected " + ranges.xaxis.from + " to " + ranges.xaxis.to)
// similar for yaxis, secondary axes are in x2axis
// and y2axis if present
});
The "plotselected" event is only fired when the user has finished
making the selection. A "plotselecting" event is fired during the
process with the same parameters as the "plotselected" event, in case
you want to know what's happening while it's happening,
A "plotunselected" event with no arguments is emitted when the user
clicks the mouse to remove the selection.
Specifying gradients
====================
......@@ -739,34 +706,6 @@ Plot Methods
The Plot object returned from the plot function has some methods you
can call:
- 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
mode is "x", you should put in either an xaxis (or x2axis) object,
if the mode is "y" you need to put in an yaxis (or y2axis) object
and both xaxis/x2axis and yaxis/y2axis if the selection mode is
"xy", like this:
setSelection({ xaxis: { from: 0, to: 10 }, yaxis: { from: 40, to: 60 } });
setSelection will trigger the "plotselected" event when called. If
you don't want that to happen, e.g. if you're inside a
"plotselected" handler, pass true as the second parameter.
- clearSelection(preventEvent)
Clear the selection rectangle. Pass in true to avoid getting a
"plotunselected" event.
- getSelection()
Returns the current selection in the same format as the
"plotselected" event. If there's currently no selection, it
returns null.
- highlight(series, datapoint)
Highlight a specific datapoint in the data series. You can either
......@@ -815,7 +754,7 @@ can call:
- triggerRedrawOverlay()
Schedules an update of an overlay canvas used for drawing
interactive things like the selection and point highlights. This
interactive things like a selection and point highlights. This
is mostly useful for writing plugins. The redraw doesn't happen
immediately, instead a timer is set to catch multiple successive
redraws (e.g. from a mousemove).
......
Flot 0.x
Flot 0.6
--------
API changes:
1. In the global options specified in the $.plot command,
1. Selection support has been moved to a plugin. Thus if you're
passing selection: { mode: something }, you MUST include the file
jquery.flot.selection.js after jquery.flot.js. This reduces the size
of base Flot and makes it easier to customize the selection as well as
improving code clarity. The change is based on patch from andershol.
2. In the global options specified in the $.plot command,
"lines", "points", "bars" and "shadowSize" have been moved to a
sub-object called "series", i.e.
$.plot(placeholder, data, { lines: { show: true }})
becomes
should be changed to
$.plot(placeholder, data, { series: { lines: { show: true }}})
All future series-specific options will go into this sub-object to
simplify plugin writing. Backward-compatibility hooks are in place,
so old code should not break.
simplify plugin writing. Backward-compatibility code is in place, so
old code should not break.
2. "plothover" no longer provides the original data point, but instead
3. "plothover" no longer provides the original data point, but instead
a normalized one, since there may be no corresponding original point.
3. Due to a bug in previous versions of jQuery, you now need at least
4. Due to a bug in previous versions of jQuery, you now need at least
jQuery 1.2.6. But if you can, try jQuery 1.3.2 as it got some
improvements in event handling speed.
......@@ -85,9 +91,9 @@ Changes:
offset within the placeholder.
- Plugin system: register an init method in the $.flot.plugins array
to get started, see PLUGINS.txt for details on how to write plugins.
There are also some extra methods to enable access to internal
state.
to get started, see PLUGINS.txt for details on how to write plugins
(it's easy). There are also some extra methods to enable access to
internal state.
- Hooks: you can register functions that are called while Flot is
crunching the data and doing the plot. This can be used to modify
......@@ -116,6 +122,11 @@ Changes:
- Support for twelve-hour date formatting (patch by Forrest Aldridge).
- The color parsing code in Flot has been cleaned up and split out so
it's now available as a separate jQuery plugin. It's included inline
in the Flot source to make dependency managing easier. This also
makes it really easy to use the color helpers in Flot plugins.
Bug fixes:
- Fixed two corner-case bugs when drawing filled curves (report and
......@@ -130,7 +141,7 @@ Bug fixes:
problem reported by Sergio Nunes).
- Updated mousemove position expression to the latest from jQuery (bug
reported by meyuchas).
- Use borders instead of background in legend (fix printing issue 25
- Use CSS borders instead of background in legend (fix printing issue 25
and 45).
- Explicitly convert axis min/max to numbers.
- Fixed a bug with drawing marking lines with different colors
......
......@@ -21,6 +21,10 @@
<script id="source" language="javascript" type="text/javascript">
$(function () {
var c = 0;
function plot() {
++c;
var d1 = [];
for (var i = 0; i < 14; i += 0.5)
d1.push([i, Math.sin(i)]);
......@@ -31,6 +35,14 @@ $(function () {
var d3 = [[0, 12], [7, 12], null, [7, 2.5], [12, 2.5]];
$.plot($("#placeholder"), [ d1, d2, d3 ]);
$("p").text("" +c);
if (c < 1000)
setTimeout(plot, 10);
}
plot();
});
</script>
......
......@@ -28,7 +28,8 @@ $(function () {
$.plot($("#placeholder"),
[ { data: oilprices, label: "Oil price ($)" },
{ data: exchangerates, label: "USD/EUR exchange rate", yaxis: 2 }],
{ xaxis: { mode: 'time' },
{
xaxis: { mode: 'time' },
yaxis: { min: 0 },
y2axis: { tickFormatter: function (v, axis) { return v.toFixed(axis.tickDecimals) +"€" }},
legend: { position: 'sw' } });
......
......@@ -11,7 +11,7 @@
<body>
<h1>Flot Examples</h1>
<p>Here are some examples for <a href="http://code.google.com/p/flot/">Flot</a>:</p>
<p>Here are some examples for <a href="http://code.google.com/p/flot/">Flot</a>, the Javascript charting library for jQuery:</p>
<ul>
<li><a href="basic.html">Basic example</a></li>
......@@ -24,20 +24,20 @@
<ul>
<li><a href="turning-series.html">Turning series on/off</a></li>
<li><a href="selection.html">Selection support and zooming</a> and <a href="zooming.html">zooming with overview</a></li>
<li><a href="selection.html">Rectangular selection support and zooming</a> and <a href="zooming.html">zooming with overview</a></li> (both with selection plugin)
<li><a href="interacting.html">Interacting with the data points</a></li>
<li><a href="navigate.html">Panning and zooming</a> (with plugin)</li>
<li><a href="navigate.html">Panning and zooming</a> (with navigation plugin)</li>
</ul>
<p>Some more esoteric features:</p>
<ul>
<li><a href="time.html">Plotting time series</a> and <a href="visitors.html">visitors per day with zooming and weekends</a></li>
<li><a href="time.html">Plotting time series</a> and <a href="visitors.html">visitors per day with zooming and weekends</a> (with selection plugin)</li>
<li><a href="dual-axis.html">Dual axis support</a></li>
<li><a href="thresholding.html">Thresholding the data</a> (with plugin)</li>
<li><a href="stacking.html">Stacked charts</a> (with plugin)</li>
<li><a href="tracking.html">Tracking curves with crosshair</a> (with plugin)</li>
<li><a href="image.html">Plotting prerendered images</a> (with plugin)</li>
<li><a href="thresholding.html">Thresholding the data</a> (with threshold plugin)</li>
<li><a href="stacking.html">Stacked charts</a> (with stacking plugin)</li>
<li><a href="tracking.html">Tracking curves with crosshair</a> (with crosshair plugin)</li>
<li><a href="image.html">Plotting prerendered images</a> (with image plugin)</li>
</ul>
</body>
</html>
......@@ -38,7 +38,6 @@ $(function () {
lines: { show: true },
points: { show: true }
},
selection: { mode: "xy" },
grid: { hoverable: true, clickable: true },
yaxis: { min: -1.2, max: 1.2 }
});
......
......@@ -7,6 +7,7 @@
<!--[if IE]><script language="javascript" type="text/javascript" src="../excanvas.min.js"></script><![endif]-->
<script language="javascript" type="text/javascript" src="../jquery.js"></script>
<script language="javascript" type="text/javascript" src="../jquery.flot.js"></script>
<script language="javascript" type="text/javascript" src="../jquery.flot.selection.js"></script>
</head>
<body>
<h1>Flot Examples</h1>
......@@ -15,23 +16,23 @@
<p>1000 kg. CO<sub>2</sub> emissions per year per capita for various countries (source: <a href="http://en.wikipedia.org/wiki/List_of_countries_by_carbon_dioxide_emissions_per_capita">Wikipedia</a>).</p>
<p>Flot supports selections. You can enable
rectangular selection
<p>Flot supports selections through the selection plugin.
You can enable rectangular selection
or one-dimensional selection if the user should only be able to
select on one axis. Try left-clicking and drag on the plot above
select on one axis. Try left-click and drag on the plot above
where selection on the x axis is enabled.</p>
<p>You selected: <span id="selection"></span></p>
<p>The plot command returns a Plot object you can use to control
the selection. Try clicking the buttons below.</p>
<p>The plot command returns a plot object you can use to control
the selection. Click the buttons below.</p>
<p><input id="clearSelection" type="button" value="Clear selection" />
<input id="setSelection" type="button" value="Select year 1994" /></p>
<p>Selections are really useful for zooming. Just replot the
chart with min and max values for the axes set to the values
in the "plotselected" event triggered. Try enabling the checkbox
in the "plotselected" event triggered. Enable the checkbox
below and select a region again.</p>
<p><input id="zoom" type="checkbox">Zoom to selection.</input></p>
......
......@@ -7,6 +7,7 @@
<!--[if IE]><script language="javascript" type="text/javascript" src="../excanvas.min.js"></script><![endif]-->
<script language="javascript" type="text/javascript" src="../jquery.js"></script>
<script language="javascript" type="text/javascript" src="../jquery.flot.js"></script>
<script language="javascript" type="text/javascript" src="../jquery.flot.selection.js"></script>
</head>
<body>
<h1>Flot Examples</h1>
......
......@@ -7,6 +7,7 @@
<!--[if IE]><script language="javascript" type="text/javascript" src="../excanvas.min.js"></script><![endif]-->
<script language="javascript" type="text/javascript" src="../jquery.js"></script>
<script language="javascript" type="text/javascript" src="../jquery.flot.js"></script>
<script language="javascript" type="text/javascript" src="../jquery.flot.selection.js"></script>
</head>
<body>
<h1>Flot Examples</h1>
......
......@@ -102,16 +102,18 @@ The plugin also adds four public methods:
});
eventHolder.mousemove(function (e) {
if (!plot.getSelection()) {
if (!crosshair.locked) {
if (plot.getSelection && plot.getSelection()) {
crosshair.x = -1; // hide the crosshair while selecting
return;
}
if (crosshair.locked)
return;
var offset = plot.offset();
crosshair.x = Math.max(0, Math.min(e.pageX - offset.left, plot.width()));
crosshair.y = Math.max(0, Math.min(e.pageY - offset.top, plot.height()));
plot.triggerRedrawOverlay();
}
}
else
crosshair.x = -1; // hide the crosshair while selecting
});
});
......
/* Javascript plotting library for jQuery, v. 0.5.
/* Javascript plotting library for jQuery, v. 0.6.
*
* Released under the MIT license by IOLA, December 2007.
*
*/
// first an inline dependency, jquery.colorhelpers.js, we inline it here
// for convenience
/* Plugin for jQuery for working with colors.
*
* Version 1.0.
*
* Inspiration from jQuery color animation plugin by John Resig.
*
* Released under the MIT license by Ole Laursen, October 2009.
*
* Examples:
*
* $.color.parse("#fff").scale('rgb', 0.25).add('a', -0.5).toString()
* var c = $.color.extract($("#mydiv"), 'background-color');
* console.log(c.r, c.g, c.b, c.a);
* $.color.make(100, 50, 25, 0.4).toString() // returns "rgba(100,50,25,0.4)"
*
* Note that .scale() and .add() work in-place instead of returning
* new objects.
*/
(function(){jQuery.color={};jQuery.color.make=function(E,D,B,C){var F={};F.r=E||0;F.g=D||0;F.b=B||0;F.a=C!=null?C:1;F.add=function(I,H){for(var G=0;G<I.length;++G){F[I.charAt(G)]+=H}return F.normalize()};F.scale=function(I,H){for(var G=0;G<I.length;++G){F[I.charAt(G)]*=H}return F.normalize()};F.toString=function(){if(F.a>=1){return"rgb("+[F.r,F.g,F.b].join(",")+")"}else{return"rgba("+[F.r,F.g,F.b,F.a].join(",")+")"}};F.normalize=function(){function G(I,J,H){return J<I?I:(J>H?H:J)}F.r=G(0,parseInt(F.r),255);F.g=G(0,parseInt(F.g),255);F.b=G(0,parseInt(F.b),255);F.a=G(0,F.a,1);return F};F.clone=function(){return jQuery.color.make(F.r,F.b,F.g,F.a)};return F.normalize()};jQuery.color.extract=function(C,B){var D;do{D=C.css(B).toLowerCase();if(D!=""&&D!="transparent"){break}C=C.parent()}while(!jQuery.nodeName(C.get(0),"body"));if(D=="rgba(0, 0, 0, 0)"){D="transparent"}return jQuery.color.parse(D)};jQuery.color.parse=function(E){var D,B=jQuery.color.make;if(D=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(E)){return B(parseInt(D[1],10),parseInt(D[2],10),parseInt(D[3],10))}if(D=/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(E)){return B(parseInt(D[1],10),parseInt(D[2],10),parseInt(D[3],10),parseFloat(D[4]))}if(D=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(E)){return B(parseFloat(D[1])*2.55,parseFloat(D[2])*2.55,parseFloat(D[3])*2.55)}if(D=/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(E)){return B(parseFloat(D[1])*2.55,parseFloat(D[2])*2.55,parseFloat(D[3])*2.55,parseFloat(D[4]))}if(D=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(E)){return B(parseInt(D[1],16),parseInt(D[2],16),parseInt(D[3],16))}if(D=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(E)){return B(parseInt(D[1]+D[1],16),parseInt(D[2]+D[2],16),parseInt(D[3]+D[3],16))}var C=jQuery.trim(E).toLowerCase();if(C=="transparent"){return B(255,255,255,0)}else{D=A[C];return B(D[0],D[1],D[2])}};var A={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]}})();
// the actual Flot code
(function($) {
function Plot(placeholder, data_, options_, plugins) {
// data is on the form:
......@@ -100,10 +124,6 @@
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"
},
hooks: {}
},
canvas = null, // the canvas for the plot itself
......@@ -122,17 +142,12 @@
bindEvents: [],
drawOverlay: []
},
plot = this,
// dedicated to storing data for buggy standard compliance cases
workarounds = {};
plot = this;
// public functions
plot.setData = setData;
plot.setupGrid = setupGrid;
plot.draw = draw;
plot.clearSelection = clearSelection;
plot.setSelection = setSelection;
plot.getSelection = getSelection;
plot.getPlaceholder = function() { return placeholder; };
plot.getCanvas = function() { return canvas; };
plot.getPlotOffset = function() { return plotOffset; };
......@@ -263,7 +278,7 @@
if (typeof sc == "number")
assignedColors.push(sc);
else
usedColors.push(parseColor(series[i].color));
usedColors.push($.color.parse(series[i].color));
}
}
......@@ -279,14 +294,13 @@
while (colors.length < neededColors) {
var c;
if (options.colors.length == i) // check degenerate case
c = new Color(100, 100, 100);
c = $.color.make(100, 100, 100);
else
c = parseColor(options.colors[i]);
c = $.color.parse(options.colors[i]);
// vary color if needed
var sign = variation % 2 == 1 ? -1 : 1;
var factor = 1 + sign * Math.ceil(variation / 2) * 0.2;
c.scale(factor, factor, factor);
c.scale('rgb', 1 + sign * Math.ceil(variation / 2) * 0.2)
// FIXME: if we're getting to close to something else,
// we should probably skip this one
......@@ -555,13 +569,9 @@
eventHolder = $([overlay, canvas]);
// bind events
if (options.selection.mode != null
|| options.grid.hoverable)
if (options.grid.hoverable)
eventHolder.mousemove(onMouseMove);
if (options.selection.mode != null)
eventHolder.mousedown(onMouseDown);
if (options.grid.clickable)
eventHolder.click(onClick);
......@@ -1697,7 +1707,7 @@
if (filloptions.fillColor)
return getColorOrGradient(filloptions.fillColor, bottom, top, seriesColor);
var c = parseColor(seriesColor);
var c = $.color.parse(seriesColor);
c.a = typeof fill == "number" ? fill : 0.4;
c.normalize();
return c.toString();
......@@ -1761,12 +1771,13 @@
// label boxes
var c = options.legend.backgroundColor;
if (c == null) {
var tmp;
if (options.grid.backgroundColor && typeof options.grid.backgroundColor == "string")
tmp = options.grid.backgroundColor;
c = options.grid.backgroundColor;
if (c && typeof c == "string")
c = $.color.parse(c);
else
tmp = extractColor(legend);
c = parseColor(tmp).adjust(null, null, null, 1).toString();
c = $.color.extract(legend, 'background-color');
c.a = 1;
c = c.toString();
}
var div = legend.children();
$('<div style="position:absolute;width:' + div.width() + 'px;height:' + div.height() + 'px;' + pos +'background-color:' + c + ';"> </div>').prependTo(legend).css('opacity', options.legend.backgroundOpacity);
......@@ -1777,16 +1788,8 @@
// interactive features
var lastMousePos = { pageX: null, pageY: null },
selection = {
first: { x: -1, y: -1}, second: { x: -1, y: -1},
show: false,
active: false
},
highlights = [],
clickIsMouseUp = false,
redrawTimeout = null,
hoverTimeout = null;
var highlights = [],
redrawTimeout = null;
// returns the data item the mouse is over, or null if none is found
function findNearbyItem(mouseX, mouseY, seriesFilter) {
......@@ -1870,50 +1873,12 @@
}
function onMouseMove(e) {
lastMousePos.pageX = e.pageX;
lastMousePos.pageY = e.pageY;
if (options.grid.hoverable)
triggerClickHoverEvent("plothover", lastMousePos,
triggerClickHoverEvent("plothover", e,
function (s) { return s["hoverable"] != false; });
if (selection.active) {
placeholder.trigger("plotselecting", [ getSelection() ]);
updateSelection(lastMousePos);
}
}
function onMouseDown(e) {
if (e.which != 1) // only accept left-click
return;
// cancel out any text selections
document.body.focus();
// prevent text selection and drag in old-school browsers
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(selection.first, e);
lastMousePos.pageX = null;
selection.active = true;
$(document).one("mouseup", onSelectionMouseUp);
}
function onClick(e) {
if (clickIsMouseUp) {
clickIsMouseUp = false;
return;
}
triggerClickHoverEvent("plotclick", e,
function (s) { return s["clickable"] != false; });
}
......@@ -1981,22 +1946,6 @@
else
drawPointHighlight(hi.series, hi.point);
}
// draw selection
if (selection.show && selectionIsSane()) {
octx.strokeStyle = parseColor(options.selection.color).scale(null, null, null, 0.8).toString();
octx.lineWidth = 1;
ctx.lineJoin = "round";
octx.fillStyle = parseColor(options.selection.color).scale(null, null, null, 0.4).toString();
var x = Math.min(selection.first.x, selection.second.x),
y = Math.min(selection.first.y, selection.second.y),
w = Math.abs(selection.second.x - selection.first.x),
h = Math.abs(selection.second.y - selection.first.y);
octx.fillRect(x, y, w, h);
octx.strokeRect(x, y, w, h);
}
octx.restore();
executeHooks(hooks.drawOverlay, [octx]);
......@@ -2058,7 +2007,7 @@
var pointRadius = series.points.radius + series.points.lineWidth / 2;
octx.lineWidth = pointRadius;
octx.strokeStyle = parseColor(series.color).scale(1, 1, 1, 0.5).toString();
octx.strokeStyle = $.color.parse(series.color).scale('a', 0.5).toString();
var radius = 1.5 * pointRadius;
octx.beginPath();
octx.arc(axisx.p2c(x), axisy.p2c(y), radius, 0, 2 * Math.PI, false);
......@@ -2067,147 +2016,13 @@
function drawBarHighlight(series, point) {
octx.lineWidth = series.bars.lineWidth;
octx.strokeStyle = parseColor(series.color).scale(1, 1, 1, 0.5).toString();
var fillStyle = parseColor(series.color).scale(1, 1, 1, 0.5).toString();
octx.strokeStyle = $.color.parse(series.color).scale('a', 0.5).toString();
var fillStyle = $.color.parse(series.color).scale('a', 0.5).toString();
var barLeft = series.bars.align == "left" ? 0 : -series.bars.barWidth/2;
drawBar(point[0], point[1], point[2] || 0, barLeft, barLeft + series.bars.barWidth,
0, function () { return fillStyle; }, series.xaxis, series.yaxis, octx, series.bars.horizontal);
}
function getSelection() {
if (!selectionIsSane())
return null;
var x1 = Math.min(selection.first.x, selection.second.x),
x2 = Math.max(selection.first.x, selection.second.x),
y1 = Math.max(selection.first.y, selection.second.y),
y2 = Math.min(selection.first.y, selection.second.y);
var r = {};
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.y2axis = { from: axes.y2axis.c2p(y1), to: axes.y2axis.c2p(y2) };
return r;
}
function triggerSelectedEvent() {
var r = getSelection();
placeholder.trigger("plotselected", [ r ]);
// backwards-compat stuff, to be removed in future
if (axes.xaxis.used && axes.yaxis.used)
placeholder.trigger("selected", [ { x1: r.xaxis.from, y1: r.yaxis.from, x2: r.xaxis.to, y2: r.yaxis.to } ]);
}
function onSelectionMouseUp(e) {
// revert drag stuff for old-school browsers
if (document.onselectstart !== undefined)
document.onselectstart = workarounds.onselectstart;
if (document.ondrag !== undefined)
document.ondrag = workarounds.ondrag;
// no more draggy-dee-drag
selection.active = false;
updateSelection(e);
if (selectionIsSane()) {
triggerSelectedEvent();
clickIsMouseUp = true;
}
else {
// this counts as a clear
placeholder.trigger("plotunselected", [ ]);
placeholder.trigger("plotselecting", [ null ]);
}
return false;
}
function setSelectionPos(pos, e) {
var offset = eventHolder.offset();
pos.x = clamp(0, e.pageX - offset.left - plotOffset.left, plotWidth);
pos.y = clamp(0, e.pageY - offset.top - plotOffset.top, plotHeight);
if (options.selection.mode == "y") {
if (pos == selection.first)
pos.x = 0;
else
pos.x = plotWidth;
}
if (options.selection.mode == "x") {
if (pos == selection.first)
pos.y = 0;
else
pos.y = plotHeight;
}
}
function updateSelection(pos) {
if (pos.pageX == null)
return;
setSelectionPos(selection.second, pos);
if (selectionIsSane()) {
selection.show = true;
triggerRedrawOverlay();
}
else
clearSelection(true);
}
function clearSelection(preventEvent) {
if (selection.show) {
selection.show = false;
triggerRedrawOverlay();
if (!preventEvent)
placeholder.trigger("plotunselected", [ ]);
}
}
function setSelection(ranges, preventEvent) {
var range;
if (options.selection.mode == "y") {
selection.first.x = 0;
selection.second.x = plotWidth;
}
else {
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") {
selection.first.y = 0;
selection.second.y = plotHeight;
}
else {
range = extractRange(ranges, "y");
selection.first.y = range.axis.p2c(range.from);
selection.second.y = range.axis.p2c(range.to);
}
selection.show = true;
triggerRedrawOverlay();
if (!preventEvent)
triggerSelectedEvent();
}
function selectionIsSane() {
var minSize = 5;
return Math.abs(selection.second.x - selection.first.x) >= minSize &&
Math.abs(selection.second.y - selection.first.y) >= minSize;
}
function getColorOrGradient(spec, bottom, top, defaultColor) {
if (typeof spec == "string")
return spec;
......@@ -2219,7 +2034,12 @@
for (var i = 0, l = spec.colors.length; i < l; ++i) {
var c = spec.colors[i];
gradient.addColorStop(i / (l - 1), typeof c == "string" ? c : parseColor(defaultColor).scale(c.brightness, c.brightness, c.brightness, c.opacity));
if (typeof c != "string") {
c = $.color.parse(defaultColor).scale('rgb', c.brightness);
c.a *= c.opacity;
c = c.toString();
}
gradient.addColorStop(i / (l - 1), c);
}
return gradient;
......@@ -2296,167 +2116,4 @@
return base * Math.floor(n / base);
}
function clamp(min, value, max) {
if (value < min)
return min;
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) {
var rgba = ['r','g','b','a'];
var x = 4; //rgba.length
while (-1<--x) {
this[rgba[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(rf, gf, bf, af) {
x = 4; //rgba.length
while (-1<--x) {
if (arguments[x] != null)
this[rgba[x]] *= arguments[x];
}
return this.normalize();
};
this.adjust = function(rd, gd, bd, ad) {
x = 4; //rgba.length
while (-1<--x) {
if (arguments[x] != null)
this[rgba[x]] += arguments[x];
}
return this.normalize();
};
this.clone = function() {
return new Color(this.r, this.b, this.g, this.a);
};
this.normalize = function() {
this.r = clamp(0, parseInt(this.r), 255);
this.g = clamp(0, parseInt(this.g), 255);
this.b = clamp(0, parseInt(this.b), 255);
this.a = clamp(0, this.a, 1);
return this;
};
this.normalize();
}
var lookupColors = {
aqua:[0,255,255],
azure:[240,255,255],
beige:[245,245,220],
black:[0,0,0],
blue:[0,0,255],
brown:[165,42,42],
cyan:[0,255,255],
darkblue:[0,0,139],
darkcyan:[0,139,139],
darkgrey:[169,169,169],
darkgreen:[0,100,0],
darkkhaki:[189,183,107],
darkmagenta:[139,0,139],
darkolivegreen:[85,107,47],
darkorange:[255,140,0],
darkorchid:[153,50,204],
darkred:[139,0,0],
darksalmon:[233,150,122],
darkviolet:[148,0,211],
fuchsia:[255,0,255],
gold:[255,215,0],
green:[0,128,0],
indigo:[75,0,130],
khaki:[240,230,140],
lightblue:[173,216,230],
lightcyan:[224,255,255],
lightgreen:[144,238,144],
lightgrey:[211,211,211],
lightpink:[255,182,193],
lightyellow:[255,255,224],
lime:[0,255,0],
magenta:[255,0,255],
maroon:[128,0,0],
navy:[0,0,128],
olive:[128,128,0],
orange:[255,165,0],
pink:[255,192,203],
purple:[128,0,128],
violet:[128,0,128],
red:[255,0,0],
silver:[192,192,192],
white:[255,255,255],
yellow:[255,255,0]
};
function extractColor(element) {
var color, elem = element;
do {
color = elem.css("background-color").toLowerCase();
// keep going until we find an element that has color, or
// we hit the body
if (color != '' && color != 'transparent')
break;
elem = elem.parent();
} while (!$.nodeName(elem.get(0), "body"));
// catch Safari's way of signalling transparent
if (color == "rgba(0, 0, 0, 0)")
return "transparent";
return color;
}
// parse string, returns Color
function parseColor(str) {
var result;
// Look for rgb(num,num,num)
if (result = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(str))
return new Color(parseInt(result[1], 10), parseInt(result[2], 10), parseInt(result[3], 10));
// Look for rgba(num,num,num,num)
if (result = /rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))
return new Color(parseInt(result[1], 10), parseInt(result[2], 10), parseInt(result[3], 10), parseFloat(result[4]));
// Look for rgb(num%,num%,num%)
if (result = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(str))
return new Color(parseFloat(result[1])*2.55, parseFloat(result[2])*2.55, parseFloat(result[3])*2.55);
// Look for rgba(num%,num%,num%,num)
if (result = /rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))
return new Color(parseFloat(result[1])*2.55, parseFloat(result[2])*2.55, parseFloat(result[3])*2.55, parseFloat(result[4]));
// Look for #a0b1c2
if (result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(str))
return new Color(parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16));
// Look for #fff
if (result = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(str))
return new Color(parseInt(result[1]+result[1], 16), parseInt(result[2]+result[2], 16), parseInt(result[3]+result[3], 16));
// Otherwise, we're most likely dealing with a named color
var name = $.trim(str).toLowerCase();
if (name == "transparent")
return new Color(255, 255, 255, 0);
else {
result = lookupColors[name];
return new Color(result[0], result[1], result[2]);
}
}
})(jQuery);
/*
Flot plugin for selecting regions.
The plugin defines the following options:
selection: {
mode: null or "x" or "y" or "xy",
color: color
}
You enable selection support by setting the mode to one of "x", "y" or
"xy". In "x" mode, the user will only be able to specify the x range,
similarly for "y" mode. For "xy", the selection becomes a rectangle
where both ranges can be specified. "color" is color of the selection.
When selection support is enabled, a "plotselected" event will be emitted
on the DOM element you passed into the plot function. The event
handler gets one extra parameter with the ranges selected on the axes,
like this:
placeholder.bind("plotselected", function(event, ranges) {
alert("You selected " + ranges.xaxis.from + " to " + ranges.xaxis.to)
// similar for yaxis, secondary axes are in x2axis
// and y2axis if present
});
The "plotselected" event is only fired when the user has finished
making the selection. A "plotselecting" event is fired during the
process with the same parameters as the "plotselected" event, in case
you want to know what's happening while it's happening,
A "plotunselected" event with no arguments is emitted when the user
clicks the mouse to remove the selection.
The plugin allso adds the following methods to the plot object:
- 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
mode is "x", you should put in either an xaxis (or x2axis) object,
if the mode is "y" you need to put in an yaxis (or y2axis) object
and both xaxis/x2axis and yaxis/y2axis if the selection mode is
"xy", like this:
setSelection({ xaxis: { from: 0, to: 10 }, yaxis: { from: 40, to: 60 } });
setSelection will trigger the "plotselected" event when called. If
you don't want that to happen, e.g. if you're inside a
"plotselected" handler, pass true as the second parameter.
- clearSelection(preventEvent)
Clear the selection rectangle. Pass in true to avoid getting a
"plotunselected" event.
- getSelection()
Returns the current selection in the same format as the
"plotselected" event. If there's currently no selection, the
function returns null.
*/
(function ($) {
function init(plot) {
var selection = {
first: { x: -1, y: -1}, second: { x: -1, y: -1},
show: false,
active: false
};
// FIXME: The drag handling implemented here should be
// abstracted out, there's some similar code from a library in
// the navigation plugin, this should be massaged a bit to fit
// the Flot cases here better and reused. Doing this would
// make this plugin much slimmer.
var savedhandlers = {};
function onMouseMove(e) {
if (selection.active) {
plot.getPlaceholder().trigger("plotselecting", [ getSelection() ]);
updateSelection(e);
}
}
function onMouseDown(e) {
if (e.which != 1) // only accept left-click
return;
// cancel out any text selections
document.body.focus();
// prevent text selection and drag in old-school browsers
if (document.onselectstart !== undefined && savedhandlers.onselectstart == null) {
savedhandlers.onselectstart = document.onselectstart;
document.onselectstart = function () { return false; };
}
if (document.ondrag !== undefined && savedhandlers.ondrag == null) {
savedhandlers.ondrag = document.ondrag;
document.ondrag = function () { return false; };
}
setSelectionPos(selection.first, e);
selection.active = true;
$(document).one("mouseup", onMouseUp);
}
function onMouseUp(e) {
// revert drag stuff for old-school browsers
if (document.onselectstart !== undefined)
document.onselectstart = savedhandlers.onselectstart;
if (document.ondrag !== undefined)
document.ondrag = savedhandlers.ondrag;
// no more draggy-dee-drag
selection.active = false;
updateSelection(e);
if (selectionIsSane())
triggerSelectedEvent();
else {
// this counts as a clear
plot.getPlaceholder().trigger("plotunselected", [ ]);
plot.getPlaceholder().trigger("plotselecting", [ null ]);
}
return false;
}
function getSelection() {
if (!selectionIsSane())
return null;
var x1 = Math.min(selection.first.x, selection.second.x),
x2 = Math.max(selection.first.x, selection.second.x),
y1 = Math.max(selection.first.y, selection.second.y),
y2 = Math.min(selection.first.y, selection.second.y);
var r = {};
var axes = plot.getAxes();
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.y2axis = { from: axes.y2axis.c2p(y1), to: axes.y2axis.c2p(y2) };
return r;
}
function triggerSelectedEvent() {
var r = getSelection();
plot.getPlaceholder().trigger("plotselected", [ r ]);
// backwards-compat stuff, to be removed in future
var axes = plot.getAxes();
if (axes.xaxis.used && axes.yaxis.used)
plot.getPlaceholder().trigger("selected", [ { x1: r.xaxis.from, y1: r.yaxis.from, x2: r.xaxis.to, y2: r.yaxis.to } ]);
}
function clamp(min, value, max) {
return value < min? min: (value > max? max: value);
}
function setSelectionPos(pos, e) {
var o = plot.getOptions();
var offset = plot.getPlaceholder().offset();
var plotOffset = plot.getPlotOffset();
pos.x = clamp(0, e.pageX - offset.left - plotOffset.left, plot.width());
pos.y = clamp(0, e.pageY - offset.top - plotOffset.top, plot.height());
if (o.selection.mode == "y")
pos.x = pos == selection.first? 0: plot.width();
if (o.selection.mode == "x")
pos.y = pos == selection.first? 0: plot.height();
}
function updateSelection(pos) {
if (pos.pageX == null)
return;
setSelectionPos(selection.second, pos);
if (selectionIsSane()) {
selection.show = true;
plot.triggerRedrawOverlay();
}
else
clearSelection(true);
}
function clearSelection(preventEvent) {
if (selection.show) {
selection.show = false;
plot.triggerRedrawOverlay();
if (!preventEvent)
plot.getPlaceholder().trigger("plotunselected", [ ]);
}
}
function setSelection(ranges, preventEvent) {
var axis, range, axes = plot.getAxes();
var o = plot.getOptions();
if (o.selection.mode == "y") {
selection.first.x = 0;
selection.second.x = plot.width();
}
else {
axis = ranges["xaxis"]? axes["xaxis"]: (ranges["x2axis"]? axes["x2axis"]: axes["xaxis"]);
range = ranges["xaxis"] || ranges["x2axis"] || { from:ranges["x1"], to:ranges["x2"] }
selection.first.x = axis.p2c(Math.min(range.from, range.to));
selection.second.x = axis.p2c(Math.max(range.from, range.to));
}
if (o.selection.mode == "x") {
selection.first.y = 0;
selection.second.y = plot.height();
}
else {
axis = ranges["yaxis"]? axes["yaxis"]: (ranges["y2axis"]? axes["y2axis"]: axes["yaxis"]);
range = ranges["yaxis"] || ranges["y2axis"] || { from:ranges["y1"], to:ranges["y2"] }
selection.first.y = axis.p2c(Math.min(range.from, range.to));
selection.second.y = axis.p2c(Math.max(range.from, range.to));
}
selection.show = true;
plot.triggerRedrawOverlay();
if (!preventEvent)
triggerSelectedEvent();
}
function selectionIsSane() {
var minSize = 5;
return Math.abs(selection.second.x - selection.first.x) >= minSize &&
Math.abs(selection.second.y - selection.first.y) >= minSize;
}
plot.clearSelection = clearSelection;
plot.setSelection = setSelection;
plot.getSelection = getSelection;
plot.hooks.bindEvents.push(function(plot, eventHolder) {
var o = plot.getOptions();
if (o.selection.mode != null)
eventHolder.mousemove(onMouseMove);
if (o.selection.mode != null)
eventHolder.mousedown(onMouseDown);
});
plot.hooks.drawOverlay.push(function (plot, ctx) {
// draw selection
if (selection.show && selectionIsSane()) {
var plotOffset = plot.getPlotOffset();
var o = plot.getOptions();
ctx.save();
ctx.translate(plotOffset.left, plotOffset.top);
var c = $.color.parse(o.selection.color);
ctx.strokeStyle = c.scale('a', 0.8).toString();
ctx.lineWidth = 1;
ctx.lineJoin = "round";
ctx.fillStyle = c.scale('a', 0.4).toString();
var x = Math.min(selection.first.x, selection.second.x),
y = Math.min(selection.first.y, selection.second.y),
w = Math.abs(selection.second.x - selection.first.x),
h = Math.abs(selection.second.y - selection.first.y);
ctx.fillRect(x, y, w, h);
ctx.strokeRect(x, y, w, h);
ctx.restore();
}
});
}
$.plot.plugins.push({
init: init,
options: {
selection: {
mode: null, // one of null, "x", "y" or "xy"
color: "#e8cfac"
}
},
name: 'selection',
version: '1.0'
});
})(jQuery);
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment