jquery.flot.errorbars.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. /* Flot plugin for plotting error bars.
  2. Copyright (c) 2007-2013 IOLA and Ole Laursen.
  3. Licensed under the MIT license.
  4. Error bars are used to show standard deviation and other statistical
  5. properties in a plot.
  6. * Created by Rui Pereira - rui (dot) pereira (at) gmail (dot) com
  7. This plugin allows you to plot error-bars over points. Set "errorbars" inside
  8. the points series to the axis name over which there will be error values in
  9. your data array (*even* if you do not intend to plot them later, by setting
  10. "show: null" on xerr/yerr).
  11. The plugin supports these options:
  12. series: {
  13. points: {
  14. errorbars: "x" or "y" or "xy",
  15. xerr: {
  16. show: null/false or true,
  17. asymmetric: null/false or true,
  18. upperCap: null or "-" or function,
  19. lowerCap: null or "-" or function,
  20. color: null or color,
  21. radius: null or number
  22. },
  23. yerr: { same options as xerr }
  24. }
  25. }
  26. Each data point array is expected to be of the type:
  27. "x" [ x, y, xerr ]
  28. "y" [ x, y, yerr ]
  29. "xy" [ x, y, xerr, yerr ]
  30. Where xerr becomes xerr_lower,xerr_upper for the asymmetric error case, and
  31. equivalently for yerr. Eg., a datapoint for the "xy" case with symmetric
  32. error-bars on X and asymmetric on Y would be:
  33. [ x, y, xerr, yerr_lower, yerr_upper ]
  34. By default no end caps are drawn. Setting upperCap and/or lowerCap to "-" will
  35. draw a small cap perpendicular to the error bar. They can also be set to a
  36. user-defined drawing function, with (ctx, x, y, radius) as parameters, as eg.
  37. function drawSemiCircle( ctx, x, y, radius ) {
  38. ctx.beginPath();
  39. ctx.arc( x, y, radius, 0, Math.PI, false );
  40. ctx.moveTo( x - radius, y );
  41. ctx.lineTo( x + radius, y );
  42. ctx.stroke();
  43. }
  44. Color and radius both default to the same ones of the points series if not
  45. set. The independent radius parameter on xerr/yerr is useful for the case when
  46. we may want to add error-bars to a line, without showing the interconnecting
  47. points (with radius: 0), and still showing end caps on the error-bars.
  48. shadowSize and lineWidth are derived as well from the points series.
  49. */
  50. (function ($) {
  51. var options = {
  52. series: {
  53. points: {
  54. errorbars: null, //should be 'x', 'y' or 'xy'
  55. xerr: { err: 'x', show: null, asymmetric: null, upperCap: null, lowerCap: null, color: null, radius: null},
  56. yerr: { err: 'y', show: null, asymmetric: null, upperCap: null, lowerCap: null, color: null, radius: null}
  57. }
  58. }
  59. };
  60. function processRawData(plot, series, data, datapoints){
  61. if (!series.points.errorbars)
  62. return;
  63. // x,y values
  64. var format = [
  65. { x: true, number: true, required: true },
  66. { y: true, number: true, required: true }
  67. ];
  68. var errors = series.points.errorbars;
  69. // error bars - first X then Y
  70. if (errors == 'x' || errors == 'xy') {
  71. // lower / upper error
  72. if (series.points.xerr.asymmetric) {
  73. format.push({ x: true, number: true, required: true });
  74. format.push({ x: true, number: true, required: true });
  75. } else
  76. format.push({ x: true, number: true, required: true });
  77. }
  78. if (errors == 'y' || errors == 'xy') {
  79. // lower / upper error
  80. if (series.points.yerr.asymmetric) {
  81. format.push({ y: true, number: true, required: true });
  82. format.push({ y: true, number: true, required: true });
  83. } else
  84. format.push({ y: true, number: true, required: true });
  85. }
  86. datapoints.format = format;
  87. }
  88. function parseErrors(series, i){
  89. var points = series.datapoints.points;
  90. // read errors from points array
  91. var exl = null,
  92. exu = null,
  93. eyl = null,
  94. eyu = null;
  95. var xerr = series.points.xerr,
  96. yerr = series.points.yerr;
  97. var eb = series.points.errorbars;
  98. // error bars - first X
  99. if (eb == 'x' || eb == 'xy') {
  100. if (xerr.asymmetric) {
  101. exl = points[i + 2];
  102. exu = points[i + 3];
  103. if (eb == 'xy')
  104. if (yerr.asymmetric){
  105. eyl = points[i + 4];
  106. eyu = points[i + 5];
  107. } else eyl = points[i + 4];
  108. } else {
  109. exl = points[i + 2];
  110. if (eb == 'xy')
  111. if (yerr.asymmetric) {
  112. eyl = points[i + 3];
  113. eyu = points[i + 4];
  114. } else eyl = points[i + 3];
  115. }
  116. // only Y
  117. } else if (eb == 'y')
  118. if (yerr.asymmetric) {
  119. eyl = points[i + 2];
  120. eyu = points[i + 3];
  121. } else eyl = points[i + 2];
  122. // symmetric errors?
  123. if (exu == null) exu = exl;
  124. if (eyu == null) eyu = eyl;
  125. var errRanges = [exl, exu, eyl, eyu];
  126. // nullify if not showing
  127. if (!xerr.show){
  128. errRanges[0] = null;
  129. errRanges[1] = null;
  130. }
  131. if (!yerr.show){
  132. errRanges[2] = null;
  133. errRanges[3] = null;
  134. }
  135. return errRanges;
  136. }
  137. function drawSeriesErrors(plot, ctx, s){
  138. var points = s.datapoints.points,
  139. ps = s.datapoints.pointsize,
  140. ax = [s.xaxis, s.yaxis],
  141. radius = s.points.radius,
  142. err = [s.points.xerr, s.points.yerr];
  143. //sanity check, in case some inverted axis hack is applied to flot
  144. var invertX = false;
  145. if (ax[0].p2c(ax[0].max) < ax[0].p2c(ax[0].min)) {
  146. invertX = true;
  147. var tmp = err[0].lowerCap;
  148. err[0].lowerCap = err[0].upperCap;
  149. err[0].upperCap = tmp;
  150. }
  151. var invertY = false;
  152. if (ax[1].p2c(ax[1].min) < ax[1].p2c(ax[1].max)) {
  153. invertY = true;
  154. var tmp = err[1].lowerCap;
  155. err[1].lowerCap = err[1].upperCap;
  156. err[1].upperCap = tmp;
  157. }
  158. for (var i = 0; i < s.datapoints.points.length; i += ps) {
  159. //parse
  160. var errRanges = parseErrors(s, i);
  161. //cycle xerr & yerr
  162. for (var e = 0; e < err.length; e++){
  163. var minmax = [ax[e].min, ax[e].max];
  164. //draw this error?
  165. if (errRanges[e * err.length]){
  166. //data coordinates
  167. var x = points[i],
  168. y = points[i + 1];
  169. //errorbar ranges
  170. var upper = [x, y][e] + errRanges[e * err.length + 1],
  171. lower = [x, y][e] - errRanges[e * err.length];
  172. //points outside of the canvas
  173. if (err[e].err == 'x')
  174. if (y > ax[1].max || y < ax[1].min || upper < ax[0].min || lower > ax[0].max)
  175. continue;
  176. if (err[e].err == 'y')
  177. if (x > ax[0].max || x < ax[0].min || upper < ax[1].min || lower > ax[1].max)
  178. continue;
  179. // prevent errorbars getting out of the canvas
  180. var drawUpper = true,
  181. drawLower = true;
  182. if (upper > minmax[1]) {
  183. drawUpper = false;
  184. upper = minmax[1];
  185. }
  186. if (lower < minmax[0]) {
  187. drawLower = false;
  188. lower = minmax[0];
  189. }
  190. //sanity check, in case some inverted axis hack is applied to flot
  191. if ((err[e].err == 'x' && invertX) || (err[e].err == 'y' && invertY)) {
  192. //swap coordinates
  193. var tmp = lower;
  194. lower = upper;
  195. upper = tmp;
  196. tmp = drawLower;
  197. drawLower = drawUpper;
  198. drawUpper = tmp;
  199. tmp = minmax[0];
  200. minmax[0] = minmax[1];
  201. minmax[1] = tmp;
  202. }
  203. // convert to pixels
  204. x = ax[0].p2c(x),
  205. y = ax[1].p2c(y),
  206. upper = ax[e].p2c(upper);
  207. lower = ax[e].p2c(lower);
  208. minmax[0] = ax[e].p2c(minmax[0]);
  209. minmax[1] = ax[e].p2c(minmax[1]);
  210. //same style as points by default
  211. var lw = err[e].lineWidth ? err[e].lineWidth : s.points.lineWidth,
  212. sw = s.points.shadowSize != null ? s.points.shadowSize : s.shadowSize;
  213. //shadow as for points
  214. if (lw > 0 && sw > 0) {
  215. var w = sw / 2;
  216. ctx.lineWidth = w;
  217. ctx.strokeStyle = "rgba(0,0,0,0.1)";
  218. drawError(ctx, err[e], x, y, upper, lower, drawUpper, drawLower, radius, w + w/2, minmax);
  219. ctx.strokeStyle = "rgba(0,0,0,0.2)";
  220. drawError(ctx, err[e], x, y, upper, lower, drawUpper, drawLower, radius, w/2, minmax);
  221. }
  222. ctx.strokeStyle = err[e].color? err[e].color: s.color;
  223. ctx.lineWidth = lw;
  224. //draw it
  225. drawError(ctx, err[e], x, y, upper, lower, drawUpper, drawLower, radius, 0, minmax);
  226. }
  227. }
  228. }
  229. }
  230. function drawError(ctx,err,x,y,upper,lower,drawUpper,drawLower,radius,offset,minmax){
  231. //shadow offset
  232. y += offset;
  233. upper += offset;
  234. lower += offset;
  235. // error bar - avoid plotting over circles
  236. if (err.err == 'x'){
  237. if (upper > x + radius) drawPath(ctx, [[upper,y],[Math.max(x + radius,minmax[0]),y]]);
  238. else drawUpper = false;
  239. if (lower < x - radius) drawPath(ctx, [[Math.min(x - radius,minmax[1]),y],[lower,y]] );
  240. else drawLower = false;
  241. }
  242. else {
  243. if (upper < y - radius) drawPath(ctx, [[x,upper],[x,Math.min(y - radius,minmax[0])]] );
  244. else drawUpper = false;
  245. if (lower > y + radius) drawPath(ctx, [[x,Math.max(y + radius,minmax[1])],[x,lower]] );
  246. else drawLower = false;
  247. }
  248. //internal radius value in errorbar, allows to plot radius 0 points and still keep proper sized caps
  249. //this is a way to get errorbars on lines without visible connecting dots
  250. radius = err.radius != null? err.radius: radius;
  251. // upper cap
  252. if (drawUpper) {
  253. if (err.upperCap == '-'){
  254. if (err.err=='x') drawPath(ctx, [[upper,y - radius],[upper,y + radius]] );
  255. else drawPath(ctx, [[x - radius,upper],[x + radius,upper]] );
  256. } else if ($.isFunction(err.upperCap)){
  257. if (err.err=='x') err.upperCap(ctx, upper, y, radius);
  258. else err.upperCap(ctx, x, upper, radius);
  259. }
  260. }
  261. // lower cap
  262. if (drawLower) {
  263. if (err.lowerCap == '-'){
  264. if (err.err=='x') drawPath(ctx, [[lower,y - radius],[lower,y + radius]] );
  265. else drawPath(ctx, [[x - radius,lower],[x + radius,lower]] );
  266. } else if ($.isFunction(err.lowerCap)){
  267. if (err.err=='x') err.lowerCap(ctx, lower, y, radius);
  268. else err.lowerCap(ctx, x, lower, radius);
  269. }
  270. }
  271. }
  272. function drawPath(ctx, pts){
  273. ctx.beginPath();
  274. ctx.moveTo(pts[0][0], pts[0][1]);
  275. for (var p=1; p < pts.length; p++)
  276. ctx.lineTo(pts[p][0], pts[p][1]);
  277. ctx.stroke();
  278. }
  279. function draw(plot, ctx){
  280. var plotOffset = plot.getPlotOffset();
  281. ctx.save();
  282. ctx.translate(plotOffset.left, plotOffset.top);
  283. $.each(plot.getData(), function (i, s) {
  284. if (s.points.errorbars && (s.points.xerr.show || s.points.yerr.show))
  285. drawSeriesErrors(plot, ctx, s);
  286. });
  287. ctx.restore();
  288. }
  289. function init(plot) {
  290. plot.hooks.processRawData.push(processRawData);
  291. plot.hooks.draw.push(draw);
  292. }
  293. $.plot.plugins.push({
  294. init: init,
  295. options: options,
  296. name: 'errorbars',
  297. version: '1.0'
  298. });
  299. })(jQuery);