jquery.flot.drawSeries.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656
  1. /**
  2. ## jquery.flot.drawSeries.js
  3. This plugin is used by flot for drawing lines, plots, bars or area.
  4. ### Public methods
  5. */
  6. (function($) {
  7. "use strict";
  8. function DrawSeries() {
  9. function plotLine(datapoints, xoffset, yoffset, axisx, axisy, ctx, steps) {
  10. var points = datapoints.points,
  11. ps = datapoints.pointsize,
  12. prevx = null,
  13. prevy = null;
  14. var x1 = 0.0,
  15. y1 = 0.0,
  16. x2 = 0.0,
  17. y2 = 0.0,
  18. mx = null,
  19. my = null,
  20. i = 0;
  21. ctx.beginPath();
  22. for (i = ps; i < points.length; i += ps) {
  23. x1 = points[i - ps];
  24. y1 = points[i - ps + 1];
  25. x2 = points[i];
  26. y2 = points[i + 1];
  27. if (x1 === null || x2 === null) {
  28. mx = null;
  29. my = null;
  30. continue;
  31. }
  32. if(steps){
  33. if (mx !== null && my !== null) {
  34. // if middle point exists, transfer p2 -> p1 and p1 -> mp
  35. x2 = x1;
  36. y2 = y1;
  37. x1 = mx;
  38. y1 = my;
  39. // 'remove' middle point
  40. mx = null;
  41. my = null;
  42. // subtract pointsize from i to have current point p1 handled again
  43. i -= ps;
  44. } else if (y1 !== y2 && x1 !== x2) {
  45. // create a middle point
  46. y2 = y1;
  47. mx = x2;
  48. my = y1;
  49. }
  50. }
  51. // clip with ymin
  52. if (y1 <= y2 && y1 < axisy.min) {
  53. if (y2 < axisy.min) {
  54. // line segment is outside
  55. continue;
  56. }
  57. // compute new intersection point
  58. x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
  59. y1 = axisy.min;
  60. } else if (y2 <= y1 && y2 < axisy.min) {
  61. if (y1 < axisy.min) {
  62. continue;
  63. }
  64. x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
  65. y2 = axisy.min;
  66. }
  67. // clip with ymax
  68. if (y1 >= y2 && y1 > axisy.max) {
  69. if (y2 > axisy.max) {
  70. continue;
  71. }
  72. x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
  73. y1 = axisy.max;
  74. } else if (y2 >= y1 && y2 > axisy.max) {
  75. if (y1 > axisy.max) {
  76. continue;
  77. }
  78. x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
  79. y2 = axisy.max;
  80. }
  81. // clip with xmin
  82. if (x1 <= x2 && x1 < axisx.min) {
  83. if (x2 < axisx.min) {
  84. continue;
  85. }
  86. y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
  87. x1 = axisx.min;
  88. } else if (x2 <= x1 && x2 < axisx.min) {
  89. if (x1 < axisx.min) {
  90. continue;
  91. }
  92. y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
  93. x2 = axisx.min;
  94. }
  95. // clip with xmax
  96. if (x1 >= x2 && x1 > axisx.max) {
  97. if (x2 > axisx.max) {
  98. continue;
  99. }
  100. y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
  101. x1 = axisx.max;
  102. } else if (x2 >= x1 && x2 > axisx.max) {
  103. if (x1 > axisx.max) {
  104. continue;
  105. }
  106. y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
  107. x2 = axisx.max;
  108. }
  109. if (x1 !== prevx || y1 !== prevy) {
  110. ctx.moveTo(axisx.p2c(x1) + xoffset, axisy.p2c(y1) + yoffset);
  111. }
  112. prevx = x2;
  113. prevy = y2;
  114. ctx.lineTo(axisx.p2c(x2) + xoffset, axisy.p2c(y2) + yoffset);
  115. }
  116. ctx.stroke();
  117. }
  118. function plotLineArea(datapoints, axisx, axisy, fillTowards, ctx, steps) {
  119. var points = datapoints.points,
  120. ps = datapoints.pointsize,
  121. bottom = fillTowards > axisy.min ? Math.min(axisy.max, fillTowards) : axisy.min,
  122. i = 0,
  123. ypos = 1,
  124. areaOpen = false,
  125. segmentStart = 0,
  126. segmentEnd = 0,
  127. mx = null,
  128. my = null;
  129. // we process each segment in two turns, first forward
  130. // direction to sketch out top, then once we hit the
  131. // end we go backwards to sketch the bottom
  132. while (true) {
  133. if (ps > 0 && i > points.length + ps) {
  134. break;
  135. }
  136. i += ps; // ps is negative if going backwards
  137. var x1 = points[i - ps],
  138. y1 = points[i - ps + ypos],
  139. x2 = points[i],
  140. y2 = points[i + ypos];
  141. if (ps === -2) {
  142. /* going backwards and no value for the bottom provided in the series*/
  143. y1 = y2 = bottom;
  144. }
  145. if (areaOpen) {
  146. if (ps > 0 && x1 != null && x2 == null) {
  147. // at turning point
  148. segmentEnd = i;
  149. ps = -ps;
  150. ypos = 2;
  151. continue;
  152. }
  153. if (ps < 0 && i === segmentStart + ps) {
  154. // done with the reverse sweep
  155. ctx.fill();
  156. areaOpen = false;
  157. ps = -ps;
  158. i = segmentStart = segmentEnd + ps;
  159. continue;
  160. }
  161. }
  162. if (x1 == null || x2 == null) {
  163. mx = null;
  164. my = null;
  165. continue;
  166. }
  167. if(steps){
  168. if (mx !== null && my !== null) {
  169. // if middle point exists, transfer p2 -> p1 and p1 -> mp
  170. x2 = x1;
  171. y2 = y1;
  172. x1 = mx;
  173. y1 = my;
  174. // 'remove' middle point
  175. mx = null;
  176. my = null;
  177. // subtract pointsize from i to have current point p1 handled again
  178. i -= ps;
  179. } else if (y1 !== y2 && x1 !== x2) {
  180. // create a middle point
  181. y2 = y1;
  182. mx = x2;
  183. my = y1;
  184. }
  185. }
  186. // clip x values
  187. // clip with xmin
  188. if (x1 <= x2 && x1 < axisx.min) {
  189. if (x2 < axisx.min) {
  190. continue;
  191. }
  192. y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
  193. x1 = axisx.min;
  194. } else if (x2 <= x1 && x2 < axisx.min) {
  195. if (x1 < axisx.min) {
  196. continue;
  197. }
  198. y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
  199. x2 = axisx.min;
  200. }
  201. // clip with xmax
  202. if (x1 >= x2 && x1 > axisx.max) {
  203. if (x2 > axisx.max) {
  204. continue;
  205. }
  206. y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
  207. x1 = axisx.max;
  208. } else if (x2 >= x1 && x2 > axisx.max) {
  209. if (x1 > axisx.max) {
  210. continue;
  211. }
  212. y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
  213. x2 = axisx.max;
  214. }
  215. if (!areaOpen) {
  216. // open area
  217. ctx.beginPath();
  218. ctx.moveTo(axisx.p2c(x1), axisy.p2c(bottom));
  219. areaOpen = true;
  220. }
  221. // now first check the case where both is outside
  222. if (y1 >= axisy.max && y2 >= axisy.max) {
  223. ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.max));
  224. ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.max));
  225. continue;
  226. } else if (y1 <= axisy.min && y2 <= axisy.min) {
  227. ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.min));
  228. ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.min));
  229. continue;
  230. }
  231. // else it's a bit more complicated, there might
  232. // be a flat maxed out rectangle first, then a
  233. // triangular cutout or reverse; to find these
  234. // keep track of the current x values
  235. var x1old = x1,
  236. x2old = x2;
  237. // clip the y values, without shortcutting, we
  238. // go through all cases in turn
  239. // clip with ymin
  240. if (y1 <= y2 && y1 < axisy.min && y2 >= axisy.min) {
  241. x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
  242. y1 = axisy.min;
  243. } else if (y2 <= y1 && y2 < axisy.min && y1 >= axisy.min) {
  244. x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
  245. y2 = axisy.min;
  246. }
  247. // clip with ymax
  248. if (y1 >= y2 && y1 > axisy.max && y2 <= axisy.max) {
  249. x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
  250. y1 = axisy.max;
  251. } else if (y2 >= y1 && y2 > axisy.max && y1 <= axisy.max) {
  252. x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
  253. y2 = axisy.max;
  254. }
  255. // if the x value was changed we got a rectangle
  256. // to fill
  257. if (x1 !== x1old) {
  258. ctx.lineTo(axisx.p2c(x1old), axisy.p2c(y1));
  259. // it goes to (x1, y1), but we fill that below
  260. }
  261. // fill triangular section, this sometimes result
  262. // in redundant points if (x1, y1) hasn't changed
  263. // from previous line to, but we just ignore that
  264. ctx.lineTo(axisx.p2c(x1), axisy.p2c(y1));
  265. ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2));
  266. // fill the other rectangle if it's there
  267. if (x2 !== x2old) {
  268. ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2));
  269. ctx.lineTo(axisx.p2c(x2old), axisy.p2c(y2));
  270. }
  271. }
  272. }
  273. /**
  274. - drawSeriesLines(series, ctx, plotOffset, plotWidth, plotHeight, drawSymbol, getColorOrGradient)
  275. This function is used for drawing lines or area fill. In case the series has line decimation function
  276. attached, before starting to draw, as an optimization the points will first be decimated.
  277. The series parameter contains the series to be drawn on ctx context. The plotOffset, plotWidth and
  278. plotHeight are the corresponding parameters of flot used to determine the drawing surface.
  279. The function getColorOrGradient is used to compute the fill style of lines and area.
  280. */
  281. function drawSeriesLines(series, ctx, plotOffset, plotWidth, plotHeight, drawSymbol, getColorOrGradient) {
  282. ctx.save();
  283. ctx.translate(plotOffset.left, plotOffset.top);
  284. ctx.lineJoin = "round";
  285. if (series.lines.dashes && ctx.setLineDash) {
  286. ctx.setLineDash(series.lines.dashes);
  287. }
  288. var datapoints = {
  289. format: series.datapoints.format,
  290. points: series.datapoints.points,
  291. pointsize: series.datapoints.pointsize
  292. };
  293. if (series.decimate) {
  294. datapoints.points = series.decimate(series, series.xaxis.min, series.xaxis.max, plotWidth, series.yaxis.min, series.yaxis.max, plotHeight);
  295. }
  296. var lw = series.lines.lineWidth;
  297. ctx.lineWidth = lw;
  298. ctx.strokeStyle = series.color;
  299. var fillStyle = getFillStyle(series.lines, series.color, 0, plotHeight, getColorOrGradient);
  300. if (fillStyle) {
  301. ctx.fillStyle = fillStyle;
  302. plotLineArea(datapoints, series.xaxis, series.yaxis, series.lines.fillTowards || 0, ctx, series.lines.steps);
  303. }
  304. if (lw > 0) {
  305. plotLine(datapoints, 0, 0, series.xaxis, series.yaxis, ctx, series.lines.steps);
  306. }
  307. ctx.restore();
  308. }
  309. /**
  310. - drawSeriesPoints(series, ctx, plotOffset, plotWidth, plotHeight, drawSymbol, getColorOrGradient)
  311. This function is used for drawing points using a given symbol. In case the series has points decimation
  312. function attached, before starting to draw, as an optimization the points will first be decimated.
  313. The series parameter contains the series to be drawn on ctx context. The plotOffset, plotWidth and
  314. plotHeight are the corresponding parameters of flot used to determine the drawing surface.
  315. The function drawSymbol is used to compute and draw the symbol chosen for the points.
  316. */
  317. function drawSeriesPoints(series, ctx, plotOffset, plotWidth, plotHeight, drawSymbol, getColorOrGradient) {
  318. function drawCircle(ctx, x, y, radius, shadow, fill) {
  319. ctx.moveTo(x + radius, y);
  320. ctx.arc(x, y, radius, 0, shadow ? Math.PI : Math.PI * 2, false);
  321. }
  322. drawCircle.fill = true;
  323. function plotPoints(datapoints, radius, fill, offset, shadow, axisx, axisy, drawSymbolFn) {
  324. var points = datapoints.points,
  325. ps = datapoints.pointsize;
  326. ctx.beginPath();
  327. for (var i = 0; i < points.length; i += ps) {
  328. var x = points[i],
  329. y = points[i + 1];
  330. if (x == null || x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max) {
  331. continue;
  332. }
  333. x = axisx.p2c(x);
  334. y = axisy.p2c(y) + offset;
  335. drawSymbolFn(ctx, x, y, radius, shadow, fill);
  336. }
  337. if (drawSymbolFn.fill && !shadow) {
  338. ctx.fill();
  339. }
  340. ctx.stroke();
  341. }
  342. ctx.save();
  343. ctx.translate(plotOffset.left, plotOffset.top);
  344. var datapoints = {
  345. format: series.datapoints.format,
  346. points: series.datapoints.points,
  347. pointsize: series.datapoints.pointsize
  348. };
  349. if (series.decimatePoints) {
  350. datapoints.points = series.decimatePoints(series, series.xaxis.min, series.xaxis.max, plotWidth, series.yaxis.min, series.yaxis.max, plotHeight);
  351. }
  352. var lw = series.points.lineWidth,
  353. radius = series.points.radius,
  354. symbol = series.points.symbol,
  355. drawSymbolFn;
  356. if (symbol === 'circle') {
  357. drawSymbolFn = drawCircle;
  358. } else if (typeof symbol === 'string' && drawSymbol && drawSymbol[symbol]) {
  359. drawSymbolFn = drawSymbol[symbol];
  360. } else if (typeof drawSymbol === 'function') {
  361. drawSymbolFn = drawSymbol;
  362. }
  363. // If the user sets the line width to 0, we change it to a very
  364. // small value. A line width of 0 seems to force the default of 1.
  365. if (lw === 0) {
  366. lw = 0.0001;
  367. }
  368. ctx.lineWidth = lw;
  369. ctx.fillStyle = getFillStyle(series.points, series.color, null, null, getColorOrGradient);
  370. ctx.strokeStyle = series.color;
  371. plotPoints(datapoints, radius,
  372. true, 0, false,
  373. series.xaxis, series.yaxis, drawSymbolFn);
  374. ctx.restore();
  375. }
  376. function drawBar(x, y, b, barLeft, barRight, fillStyleCallback, axisx, axisy, c, horizontal, lineWidth) {
  377. var left = x + barLeft,
  378. right = x + barRight,
  379. bottom = b, top = y,
  380. drawLeft, drawRight, drawTop, drawBottom = false,
  381. tmp;
  382. drawLeft = drawRight = drawTop = true;
  383. // in horizontal mode, we start the bar from the left
  384. // instead of from the bottom so it appears to be
  385. // horizontal rather than vertical
  386. if (horizontal) {
  387. drawBottom = drawRight = drawTop = true;
  388. drawLeft = false;
  389. left = b;
  390. right = x;
  391. top = y + barLeft;
  392. bottom = y + barRight;
  393. // account for negative bars
  394. if (right < left) {
  395. tmp = right;
  396. right = left;
  397. left = tmp;
  398. drawLeft = true;
  399. drawRight = false;
  400. }
  401. }
  402. else {
  403. drawLeft = drawRight = drawTop = true;
  404. drawBottom = false;
  405. left = x + barLeft;
  406. right = x + barRight;
  407. bottom = b;
  408. top = y;
  409. // account for negative bars
  410. if (top < bottom) {
  411. tmp = top;
  412. top = bottom;
  413. bottom = tmp;
  414. drawBottom = true;
  415. drawTop = false;
  416. }
  417. }
  418. // clip
  419. if (right < axisx.min || left > axisx.max ||
  420. top < axisy.min || bottom > axisy.max) {
  421. return;
  422. }
  423. if (left < axisx.min) {
  424. left = axisx.min;
  425. drawLeft = false;
  426. }
  427. if (right > axisx.max) {
  428. right = axisx.max;
  429. drawRight = false;
  430. }
  431. if (bottom < axisy.min) {
  432. bottom = axisy.min;
  433. drawBottom = false;
  434. }
  435. if (top > axisy.max) {
  436. top = axisy.max;
  437. drawTop = false;
  438. }
  439. left = axisx.p2c(left);
  440. bottom = axisy.p2c(bottom);
  441. right = axisx.p2c(right);
  442. top = axisy.p2c(top);
  443. // fill the bar
  444. if (fillStyleCallback) {
  445. c.fillStyle = fillStyleCallback(bottom, top);
  446. c.fillRect(left, top, right - left, bottom - top)
  447. }
  448. // draw outline
  449. if (lineWidth > 0 && (drawLeft || drawRight || drawTop || drawBottom)) {
  450. c.beginPath();
  451. // FIXME: inline moveTo is buggy with excanvas
  452. c.moveTo(left, bottom);
  453. if (drawLeft) {
  454. c.lineTo(left, top);
  455. } else {
  456. c.moveTo(left, top);
  457. }
  458. if (drawTop) {
  459. c.lineTo(right, top);
  460. } else {
  461. c.moveTo(right, top);
  462. }
  463. if (drawRight) {
  464. c.lineTo(right, bottom);
  465. } else {
  466. c.moveTo(right, bottom);
  467. }
  468. if (drawBottom) {
  469. c.lineTo(left, bottom);
  470. } else {
  471. c.moveTo(left, bottom);
  472. }
  473. c.stroke();
  474. }
  475. }
  476. /**
  477. - drawSeriesBars(series, ctx, plotOffset, plotWidth, plotHeight, drawSymbol, getColorOrGradient)
  478. This function is used for drawing series represented as bars. In case the series has decimation
  479. function attached, before starting to draw, as an optimization the points will first be decimated.
  480. The series parameter contains the series to be drawn on ctx context. The plotOffset, plotWidth and
  481. plotHeight are the corresponding parameters of flot used to determine the drawing surface.
  482. The function getColorOrGradient is used to compute the fill style of bars.
  483. */
  484. function drawSeriesBars(series, ctx, plotOffset, plotWidth, plotHeight, drawSymbol, getColorOrGradient) {
  485. function plotBars(datapoints, barLeft, barRight, fillStyleCallback, axisx, axisy) {
  486. var points = datapoints.points,
  487. ps = datapoints.pointsize,
  488. fillTowards = series.bars.fillTowards || 0,
  489. calculatedBottom = fillTowards > axisy.min ? Math.min(axisy.max, fillTowards) : axisy.min;
  490. for (var i = 0; i < points.length; i += ps) {
  491. if (points[i] == null) {
  492. continue;
  493. }
  494. // Use third point as bottom if pointsize is 3
  495. var bottom = ps === 3 ? points[i + 2] : calculatedBottom;
  496. drawBar(points[i], points[i + 1], bottom, barLeft, barRight, fillStyleCallback, axisx, axisy, ctx, series.bars.horizontal, series.bars.lineWidth);
  497. }
  498. }
  499. ctx.save();
  500. ctx.translate(plotOffset.left, plotOffset.top);
  501. var datapoints = {
  502. format: series.datapoints.format,
  503. points: series.datapoints.points,
  504. pointsize: series.datapoints.pointsize
  505. };
  506. if (series.decimate) {
  507. datapoints.points = series.decimate(series, series.xaxis.min, series.xaxis.max, plotWidth);
  508. }
  509. ctx.lineWidth = series.bars.lineWidth;
  510. ctx.strokeStyle = series.color;
  511. var barLeft;
  512. var barWidth = series.bars.barWidth[0] || series.bars.barWidth;
  513. switch (series.bars.align) {
  514. case "left":
  515. barLeft = 0;
  516. break;
  517. case "right":
  518. barLeft = -barWidth;
  519. break;
  520. default:
  521. barLeft = -barWidth / 2;
  522. }
  523. var fillStyleCallback = series.bars.fill ? function(bottom, top) {
  524. return getFillStyle(series.bars, series.color, bottom, top, getColorOrGradient);
  525. } : null;
  526. plotBars(datapoints, barLeft, barLeft + barWidth, fillStyleCallback, series.xaxis, series.yaxis);
  527. ctx.restore();
  528. }
  529. function getFillStyle(filloptions, seriesColor, bottom, top, getColorOrGradient) {
  530. var fill = filloptions.fill;
  531. if (!fill) {
  532. return null;
  533. }
  534. if (filloptions.fillColor) {
  535. return getColorOrGradient(filloptions.fillColor, bottom, top, seriesColor);
  536. }
  537. var c = $.color.parse(seriesColor);
  538. c.a = typeof fill === "number" ? fill : 0.4;
  539. c.normalize();
  540. return c.toString();
  541. }
  542. this.drawSeriesLines = drawSeriesLines;
  543. this.drawSeriesPoints = drawSeriesPoints;
  544. this.drawSeriesBars = drawSeriesBars;
  545. this.drawBar = drawBar;
  546. };
  547. $.plot.drawSeries = new DrawSeries();
  548. })(jQuery);