diff --git a/README.fastline.md b/README.fastline.md new file mode 100644 index 0000000..32ec876 --- /dev/null +++ b/README.fastline.md @@ -0,0 +1,27 @@ +## Fast line plot plugin for Flot ## +This plugin makes a very basic line plot. It is intended to efficiently render large and fast-updating data sets. I created this plugin when I needed to display lines of some thousands of points updating several times a second. While the original Flot implementation worked fine with Webkit (Safari and Chrome), the result on Firefox was catastrophic. This plugin gives the same performance on all three browsers. + +## Basic usage ## +When creating a Flot chart, simply add the *fastline* tag to the series definition, like this: +``` +var my_plot = $.plot($("#plot_container"), [data], series:{fastline:{ active:true, show:true }}); +``` + +## Data format ## +The pcolor plugin expects that the data is composed of 2 arrays, one for the horizontal coordinates and one for the vertical coordinates: +``` +data = [x coordinates <1D array>, y coordinates <1D array>] +``` +It is sorted differently than normal Flot lines to allow this sort of operations: ```Math.min.apply(null,vector)```. + +## Data range ## +The displayed range can be adjusted in this way: +``` +my_plot.getAxes().xaxis.options.min = new_x_min; +my_plot.getAxes().xaxis.options.max = new_x_max; +my_plot.getAxes().yaxis.options.min = new_y_min; +my_plot.getAxes().yaxis.options.max = new_y_max; +``` + +## Example ## +See included file example.fastline.html diff --git a/README.pcolor.md b/README.pcolor.md new file mode 100644 index 0000000..b538c69 --- /dev/null +++ b/README.pcolor.md @@ -0,0 +1,72 @@ +## 2D pseudo-color plot plugin for Flot ## +This plugin takes a 2D array and maps the values to a defined color map. +## Basic usage ## +When creating a Flot chart, simply add the *pcolor* tag to the series definition, like this: +``` +var my_plot = $.plot($("#plot_container"), [data], series:{pcolor:{ active:true, show:true, colormap: my_colormap, scalebar: scalebar_options }}); +``` + +## Data format ## +The pcolor plugin expects that the data is composed of 3 arrays, one for the horizontal coordinates, one for the vertical coordinates, and one for the values: +``` +data = [x coordinates <1D or 2D array>, y coordinates <1D or 2D array>, values <2D array>] +``` +The coordinate arrays can be either 1D or 2D. + +## Data range ## +The minima and maxima in x and y directions are accessible in this way: +``` +var x_min = my_plot.getAxes().xaxis.datamin +var x_max = my_plot.getAxes().xaxis.datamax +var y_min = my_plot.getAxes().yaxis.datamin +var y_max = my_plot.getAxes().yaxis.datamax +``` +The displayed range can be adjusted in this way: +``` +my_plot.getAxes().xaxis.options.min = new_x_min; +my_plot.getAxes().xaxis.options.max = new_x_max; +my_plot.getAxes().yaxis.options.min = new_y_min; +my_plot.getAxes().yaxis.options.max = new_y_max; +``` + +## Color map ## +The colormap is an array defining a linear gradient. Each element of the array must be [color position, color code]. +For example, a blue/white/red gradient would be defined as: +``` +var colormap_bwr = [[0,"#0000ff"],[0.5,"#ffffff"],[1,"#ff0000"]]; +``` +Another popular color map is the *jet* color map: +``` +var colormap_jet = [ [0,"#00007f"], [0.125,"#0000ff"], [0.25,"#007fff"], [0.375,"#00ffff"], +[0.5,"#7fff7f"], [0.625,"#ffff00"], [0.75,"#ff7f00"], [0.875,"#ff0000"], [1.0,"#7f0000"] ]; +``` + +## Scale bar ## +By default, a scale bar showing the extent of the represented colours appears in the top right corner of the plot. The scale bar options are: +``` +scalebar_options = { + location:"top right" // combination of top, left, right and bottom + orientation:"vertical" // horizontal or vertical + width:100, + height:15, + fontsize:"9px", + fontfamily:"times", + labels:3, // number of labels, >1 to display any + labelformat:"1f", + labelformatter: function(value,precision){ return "text"; }, + textalign:"right" +} +``` +The label format number represents the truncation precision and the letter the JavaScript function used to format the numbers: *f* = toFixed, *p* = toPrecision, *e* = toExponential, *c* = custom function provided in the *labelformatter* option. +The label alignment, set by the ```textalign``` property, can be *left*, *center*, *right*, or *spread*. The last option, when the bar is horizontally oriented, spreads the labels across the whole scale bar width. +The scale bar is deactivated by settings ```scalebar_options = null```. + +## Grid ## +The grid can be displayed by setting ```grid: {aboveData:true}``` in Flot options. Note that it will display above the color bar. +Another option exists, which requires to expose the *drawGrid* function of Flot. If you did so, you can add ```grid:true``` to *pcolor* options. The grid will be drawn above the plot but below the scale bar. + +## Performances and compatibility ## +The pcolor plugin has been tested to work on Safari, Chrome and Firefox under Mac OS X. The behaviour in terms of performance was similar. + +## Example ## +See included file example.pcolor.html diff --git a/example.fastline.html b/example.fastline.html new file mode 100644 index 0000000..d5819bb --- /dev/null +++ b/example.fastline.html @@ -0,0 +1,37 @@ + + + + + + + + + +
+ + + diff --git a/example.pcolor.html b/example.pcolor.html new file mode 100644 index 0000000..a182e6f --- /dev/null +++ b/example.pcolor.html @@ -0,0 +1,62 @@ + + + + + + + + + +
+ + + + diff --git a/plugins/jquery.flot.fastline.js b/plugins/jquery.flot.fastline.js new file mode 100644 index 0000000..79a25be --- /dev/null +++ b/plugins/jquery.flot.fastline.js @@ -0,0 +1,83 @@ +(function ($) { + "use strict"; + var pluginName = "fastline", pluginVersion = "0.1"; + var options = { + series: { + fastline: { + active: false, + show: false, + } + } + }; + var defaultOptions = { + }; + function init(plot) { + plot.hooks.processOptions.push(processOptions); + function processOptions(plot,options){ + if(options.series.fastline.active){ + plot.hooks.drawSeries.push(drawSeries); + } + } + function drawSeries(plot, ctx, serie){ + if (serie.fastline.show) { + var offset = plot.getPlotOffset(); + if (serie.xaxis.min!=undefined) {var x_min = serie.xaxis.min;} + else {var x_min = Math.min.apply(null, serie.data[0]);} + + if (serie.xaxis.max!=undefined) {var x_max = serie.xaxis.max;} + else {var x_max = Math.max.apply(null, serie.data[0]);} + + if (serie.yaxis.min!=undefined) {var y_min = serie.yaxis.min;} + else {var y_min = Math.min.apply(null, serie.data[1]);} + + if (serie.yaxis.max!=undefined) {var y_max = serie.yaxis.max;} + else {var y_max = Math.max.apply(null, serie.data[1]);} + + var dx = x_max - x_min; + var dy = y_max - y_min; + var np = serie.data[0].length; + var w = plot.width(); + var h = plot.height(); + + // builds line + ctx.save(); + ctx.beginPath(); + ctx.lineWidth=1; + ctx.strokeStyle=serie.color; + var ox = offset.left; + var oh = h+offset.top; + var oy = offset.top; + var ow = w+offset.left; + var px = ox, py=oy; + var xscale = w/dx; + var yscale = h/dy; + var first = true; + var pnx = ox, pny = oh; + for (var i = 0; i < np; i++) { + px = ox + parseInt((serie.data[0][i]-x_min)*xscale); + py = oh - parseInt((serie.data[1][i]-y_min)*yscale); + if (pnx!=px || pny!=py) { + if (px>=ox && py>=oy && px<=ow && py<=oh) { + if (first) { + ctx.moveTo(px,py); + first = false; + } else { + ctx.lineTo(px,py); + } + pnx = px; + pny = py; + } + } + } + ctx.stroke(); + ctx.restore(); + } + } + } + $.plot.plugins.push({ + init: init, + options: options, + name: pluginName, + version: pluginVersion + }); +})(jQuery); \ No newline at end of file diff --git a/plugins/jquery.flot.pcolor.js b/plugins/jquery.flot.pcolor.js new file mode 100644 index 0000000..b49d535 --- /dev/null +++ b/plugins/jquery.flot.pcolor.js @@ -0,0 +1,412 @@ +(function ($) { + "use strict"; + var pluginName = "pcolor", pluginVersion = "0.2.1"; + var options = { + series: { + pcolor: { + active: false, + show: false, + scalebar: + { + location: "top right", // can be a combination of top, right, left, bottom + orientation: "vertical", // horizontal or vertical + width:15, // pixel width + height:100, // pixel height + fontsize:"10px", // font size + fontfamily:"times", // font family + labels:3, // number of labels (>1 or not displayed) + labelformat:"1f", // label format: number = truncation accuracy + // letter = "f":toFixed, "p":toPrecision, "e":toExponential, "c":custom + labelformatter: function(value,precision){return ""}, + textalign:"right" // alignment of labels: left (default), center, right or spread + } + } + } + }; + function init(plot) { + var offset = null,opt = null,series = null; + plot.hooks.processOptions.push(processOptions); + function processOptions(plot,options){ + // parses options for pcolor plot + if(options.series.pcolor.active){ + // hook for pcolor rendering + plot.hooks.drawSeries.push(drawSeries); + + // creates pcolor objects + plot.pcolor = {scalebar:{}} + if (options.series.pcolor.scalebar) { + // parses position string + var tags = options.series.pcolor.scalebar.location.split(" "); + var left = (tags.indexOf("left")>-1); + var right = (tags.indexOf("right")>-1); + var top = (tags.indexOf("top")>-1); + var bottom = (tags.indexOf("bottom")>-1); + plot.pcolor.scalebar.position = [top && left, + top && right, + bottom && right, + bottom && left].indexOf(true); + // determines if the scale bar is rendered + plot.pcolor.scalebar.show = (tags.length>0) && (tags.indexOf("none")==-1); + // orientation: horizontal=true, vertical=false (default) + plot.pcolor.scalebar.orientation = (options.series.pcolor.scalebar.orientation.indexOf("horizontal")>-1); + // alignment of labels + var align_left = (options.series.pcolor.scalebar.textalign=="left"); + var align_center = (options.series.pcolor.scalebar.textalign=="center"); + var align_right = (options.series.pcolor.scalebar.textalign=="right"); + var align_spread = (options.series.pcolor.scalebar.textalign=="spread"); + plot.pcolor.scalebar.textalign = [align_left,align_center,align_right,align_spread].indexOf(true); + plot.pcolor.scalebar.textalign = (plot.pcolor.scalebar.textalign>-1)*plot.pcolor.scalebar.textalign; + // spread not allowed for vertical configuration + if (!plot.pcolor.scalebar.orientation && plot.pcolor.scalebar.textalign==3) { + plot.pcolor.scalebar.textalign = 0; + } + // creates canvas with scale bar gradient + plot.pcolor.scalebar.grdcnv = document.createElement("canvas"); + var sctx = plot.pcolor.scalebar.grdcnv.getContext("2d"); + plot.pcolor.scalebar.grdcnv.width = options.series.pcolor.scalebar.width; + plot.pcolor.scalebar.grdcnv.height = options.series.pcolor.scalebar.height; + if (plot.pcolor.scalebar.orientation) { + var cgrd = sctx.createLinearGradient(0,0,plot.pcolor.scalebar.grdcnv.width,0); + } else { + var cgrd = sctx.createLinearGradient(0,plot.pcolor.scalebar.grdcnv.height,0,0); + } + } else { + plot.pcolor.scalebar.show = false; + } + // creates color map + // most convenient way found so far: draw a gradient on an off-screen canvas + // and store it as a bitmap + var ocnv = document.createElement("canvas"); + ocnv.width = 16385; + ocnv.height = 1; + var octx = ocnv.getContext("2d"); + var grd = octx.createLinearGradient(0,0,16385,0); + if (options.series.pcolor.colormap==undefined) { + var colormap = [[0,"#0000ff"],[0.5,"#ffffff"],[1,"#ff0000"]]; + } else { + var colormap = options.series.pcolor.colormap; + } + for (var n=0; n0) { + offset = plot.getPlotOffset(); + var w = serie.data[2][0].length, h = serie.data[2].length; + var pw = plot.width()+offset.left+offset.right, ph = plot.height()+offset.top+offset.bottom; + var cw = ctx.canvas.width, ch = ctx.canvas.height; + var ocnv = plot.pcolor.ocnv; + var octx = plot.pcolor.octx; + ocnv.width = w; + ocnv.height = h; + var img = octx.createImageData(w, h); + + if (typeof serie.data[0][0] == "number") { // 1D map for x coordinates => makes 2D map + var new_map_x = new Array(h); + var x_min = Math.min.apply(null, serie.data[0]); + var x_max = Math.max.apply(null, serie.data[0]); + for (var i=0; i=x_min && serie.data[0][i][j]<=x_max && + serie.data[1][i][j]>=y_min && serie.data[1][i][j]<=y_max) { + var px = (w-1)*(serie.data[0][i][j]-x_min)/dx; + var py = (h-1)*(serie.data[1][i][j]-y_min)/dy; + var pxm = Math.floor(px); + var pym = Math.floor(py); + var pxp = Math.ceil(px); + var pyp = Math.ceil(py); + var idxmm = (pym*w+pxm)*4; + var idxpm = (pyp*w+pxm)*4; + var idxmp = (pym*w+pxp)*4; + var idxpp = (pyp*w+pxp)*4; + var v = 4*parseInt(((serie.data[2][i][j]-c_min)/dc)*16384); + var r = plot.pcolor.colormap.data[v]; + var g = plot.pcolor.colormap.data[v+1]; + var b = plot.pcolor.colormap.data[v+2]; + img.data[idxmm] = r; + img.data[idxmm+1] = g; + img.data[idxmm+2] = b; + img.data[idxmm+3] = 255; + if (idxmp!=idxmm) { + img.data[idxmp] = r; + img.data[idxmp+1] = g; + img.data[idxmp+2] = b; + img.data[idxmp+3] = 255; + } + if (idxpm!=idxmm) { + img.data[idxpm] = r; + img.data[idxpm+1] = g; + img.data[idxpm+2] = b; + img.data[idxpm+3] = 255; + } + if (idxpp!=idxmm) { + img.data[idxpp] = r; + img.data[idxpp+1] = g; + img.data[idxpp+2] = b; + img.data[idxpp+3] = 255; + } + } + } + } + octx.putImageData(img,0,0); + ctx.save(); + ctx.beginPath(); + ctx.rect(offset.left,offset.top,pw-offset.left-offset.right,ph-offset.top-offset.bottom); + ctx.clip(); + ctx.setTransform(sw,0,0,sh,0,0); + ctx.drawImage(ocnv,offset.left/sw*cw/pw,offset.top/sh*ch/ph); + ctx.restore(); + // adds scalebar if necessary + if (plot.pcolor.scalebar.show) { + ctx.save(); + // prepares labels + if (serie.pcolor.scalebar.labels>1) { + ctx.font = serie.pcolor.scalebar.fontsize + " " + serie.pcolor.scalebar.fontfamily; + // formats labels and computes their width + var labels = new Array(serie.pcolor.scalebar.labels); + var lwidths = new Array(serie.pcolor.scalebar.labels); + var dt = dc/(serie.pcolor.scalebar.labels-1); + // couldn't find a standard way to obtain the font height + // using the full block character trick instead. + var tmh = ctx.measureText('\u2588').width; + for (var t=0; t1, as otherwise it makes no sense + if (serie.pcolor.scalebar.labels>1) { + // positions the background for the labels and the labels inside + ctx.fillStyle="rgba(255,255,255,0.66)"; + if (plot.pcolor.scalebar.orientation) { + // for horizontal orientation + ctx.fillRect(cpx+ocpx, cpy+ocpy, plot.pcolor.scalebar.grdcnv.width, tmh+4); + ctx.fillStyle="#000000"; + var cdw = (plot.pcolor.scalebar.grdcnv.width-tmw-4)/(serie.pcolor.scalebar.labels-1); + var labx = cpx+ocpx+otx+2; // horizontal offset + var laby = cpy+ocpy+oty; // vertical position + for (t=0; t