jquery.flot.fillbetween.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. /*
  2. Flot plugin for computing bottoms for filled line and bar charts.
  3. The case: you've got two series that you want to fill the area
  4. between. In Flot terms, you need to use one as the fill bottom of the
  5. other. You can specify the bottom of each data point as the third
  6. coordinate manually, or you can use this plugin to compute it for you.
  7. In order to name the other series, you need to give it an id, like this
  8. var dataset = [
  9. { data: [ ... ], id: "foo" } , // use default bottom
  10. { data: [ ... ], fillBetween: "foo" }, // use first dataset as bottom
  11. ];
  12. $.plot($("#placeholder"), dataset, { line: { show: true, fill: true }});
  13. As a convenience, if the id given is a number that doesn't appear as
  14. an id in the series, it is interpreted as the index in the array
  15. instead (so fillBetween: 0 can also mean the first series).
  16. Internally, the plugin modifies the datapoints in each series. For
  17. line series, extra data points might be inserted through
  18. interpolation. Note that at points where the bottom line is not
  19. defined (due to a null point or start/end of line), the current line
  20. will show a gap too. The algorithm comes from the jquery.flot.stack.js
  21. plugin, possibly some code could be shared.
  22. */
  23. (function ($) {
  24. var options = {
  25. series: { fillBetween: null } // or number
  26. };
  27. function init(plot) {
  28. function findBottomSeries(s, allseries) {
  29. var i;
  30. for (i = 0; i < allseries.length; ++i) {
  31. if (allseries[i].id == s.fillBetween)
  32. return allseries[i];
  33. }
  34. if (typeof s.fillBetween == "number") {
  35. i = s.fillBetween;
  36. if (i < 0 || i >= allseries.length)
  37. return null;
  38. return allseries[i];
  39. }
  40. return null;
  41. }
  42. function computeFillBottoms(plot, s, datapoints) {
  43. if (s.fillBetween == null)
  44. return;
  45. var other = findBottomSeries(s, plot.getData());
  46. if (!other)
  47. return;
  48. var ps = datapoints.pointsize,
  49. points = datapoints.points,
  50. otherps = other.datapoints.pointsize,
  51. otherpoints = other.datapoints.points,
  52. newpoints = [],
  53. px, py, intery, qx, qy, bottom,
  54. withlines = s.lines.show,
  55. withbottom = ps > 2 && datapoints.format[2].y,
  56. withsteps = withlines && s.lines.steps,
  57. fromgap = true,
  58. i = 0, j = 0, l;
  59. while (true) {
  60. if (i >= points.length)
  61. break;
  62. l = newpoints.length;
  63. if (points[i] == null) {
  64. // copy gaps
  65. for (m = 0; m < ps; ++m)
  66. newpoints.push(points[i + m]);
  67. i += ps;
  68. }
  69. else if (j >= otherpoints.length) {
  70. // for lines, we can't use the rest of the points
  71. if (!withlines) {
  72. for (m = 0; m < ps; ++m)
  73. newpoints.push(points[i + m]);
  74. }
  75. i += ps;
  76. }
  77. else if (otherpoints[j] == null) {
  78. // oops, got a gap
  79. for (m = 0; m < ps; ++m)
  80. newpoints.push(null);
  81. fromgap = true;
  82. j += otherps;
  83. }
  84. else {
  85. // cases where we actually got two points
  86. px = points[i];
  87. py = points[i + 1];
  88. qx = otherpoints[j];
  89. qy = otherpoints[j + 1];
  90. bottom = 0;
  91. if (px == qx) {
  92. for (m = 0; m < ps; ++m)
  93. newpoints.push(points[i + m]);
  94. //newpoints[l + 1] += qy;
  95. bottom = qy;
  96. i += ps;
  97. j += otherps;
  98. }
  99. else if (px > qx) {
  100. // we got past point below, might need to
  101. // insert interpolated extra point
  102. if (withlines && i > 0 && points[i - ps] != null) {
  103. intery = py + (points[i - ps + 1] - py) * (qx - px) / (points[i - ps] - px);
  104. newpoints.push(qx);
  105. newpoints.push(intery)
  106. for (m = 2; m < ps; ++m)
  107. newpoints.push(points[i + m]);
  108. bottom = qy;
  109. }
  110. j += otherps;
  111. }
  112. else { // px < qx
  113. if (fromgap && withlines) {
  114. // if we come from a gap, we just skip this point
  115. i += ps;
  116. continue;
  117. }
  118. for (m = 0; m < ps; ++m)
  119. newpoints.push(points[i + m]);
  120. // we might be able to interpolate a point below,
  121. // this can give us a better y
  122. if (withlines && j > 0 && otherpoints[j - otherps] != null)
  123. bottom = qy + (otherpoints[j - otherps + 1] - qy) * (px - qx) / (otherpoints[j - otherps] - qx);
  124. //newpoints[l + 1] += bottom;
  125. i += ps;
  126. }
  127. fromgap = false;
  128. if (l != newpoints.length && withbottom)
  129. newpoints[l + 2] = bottom;
  130. }
  131. // maintain the line steps invariant
  132. if (withsteps && l != newpoints.length && l > 0
  133. && newpoints[l] != null
  134. && newpoints[l] != newpoints[l - ps]
  135. && newpoints[l + 1] != newpoints[l - ps + 1]) {
  136. for (m = 0; m < ps; ++m)
  137. newpoints[l + ps + m] = newpoints[l + m];
  138. newpoints[l + 1] = newpoints[l - ps + 1];
  139. }
  140. }
  141. datapoints.points = newpoints;
  142. }
  143. plot.hooks.processDatapoints.push(computeFillBottoms);
  144. }
  145. $.plot.plugins.push({
  146. init: init,
  147. options: options,
  148. name: 'fillbetween',
  149. version: '1.0'
  150. });
  151. })(jQuery);