jquery.flot.categories.js 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. /* Flot plugin for plotting textual data or categories.
  2. Copyright (c) 2007-2013 IOLA and Ole Laursen.
  3. Licensed under the MIT license.
  4. Consider a dataset like [["February", 34], ["March", 20], ...]. This plugin
  5. allows you to plot such a dataset directly.
  6. To enable it, you must specify mode: "categories" on the axis with the textual
  7. labels, e.g.
  8. $.plot("#placeholder", data, { xaxis: { mode: "categories" } });
  9. By default, the labels are ordered as they are met in the data series. If you
  10. need a different ordering, you can specify "categories" on the axis options
  11. and list the categories there:
  12. xaxis: {
  13. mode: "categories",
  14. categories: ["February", "March", "April"]
  15. }
  16. If you need to customize the distances between the categories, you can specify
  17. "categories" as an object mapping labels to values
  18. xaxis: {
  19. mode: "categories",
  20. categories: { "February": 1, "March": 3, "April": 4 }
  21. }
  22. If you don't specify all categories, the remaining categories will be numbered
  23. from the max value plus 1 (with a spacing of 1 between each).
  24. Internally, the plugin works by transforming the input data through an auto-
  25. generated mapping where the first category becomes 0, the second 1, etc.
  26. Hence, a point like ["February", 34] becomes [0, 34] internally in Flot (this
  27. is visible in hover and click events that return numbers rather than the
  28. category labels). The plugin also overrides the tick generator to spit out the
  29. categories as ticks instead of the values.
  30. If you need to map a value back to its label, the mapping is always accessible
  31. as "categories" on the axis object, e.g. plot.getAxes().xaxis.categories.
  32. */
  33. (function ($) {
  34. var options = {
  35. xaxis: {
  36. categories: null
  37. },
  38. yaxis: {
  39. categories: null
  40. }
  41. };
  42. function processRawData(plot, series, data, datapoints) {
  43. // if categories are enabled, we need to disable
  44. // auto-transformation to numbers so the strings are intact
  45. // for later processing
  46. var xCategories = series.xaxis.options.mode == "categories",
  47. yCategories = series.yaxis.options.mode == "categories";
  48. if (!(xCategories || yCategories))
  49. return;
  50. var format = datapoints.format;
  51. if (!format) {
  52. // FIXME: auto-detection should really not be defined here
  53. var s = series;
  54. format = [];
  55. format.push({ x: true, number: true, required: true });
  56. format.push({ y: true, number: true, required: true });
  57. if (s.bars.show || (s.lines.show && s.lines.fill)) {
  58. var autoscale = !!((s.bars.show && s.bars.zero) || (s.lines.show && s.lines.zero));
  59. format.push({ y: true, number: true, required: false, defaultValue: 0, autoscale: autoscale });
  60. if (s.bars.horizontal) {
  61. delete format[format.length - 1].y;
  62. format[format.length - 1].x = true;
  63. }
  64. }
  65. datapoints.format = format;
  66. }
  67. for (var m = 0; m < format.length; ++m) {
  68. if (format[m].x && xCategories)
  69. format[m].number = false;
  70. if (format[m].y && yCategories)
  71. format[m].number = false;
  72. }
  73. }
  74. function getNextIndex(categories) {
  75. var index = -1;
  76. for (var v in categories)
  77. if (categories[v] > index)
  78. index = categories[v];
  79. return index + 1;
  80. }
  81. function categoriesTickGenerator(axis) {
  82. var res = [];
  83. for (var label in axis.categories) {
  84. var v = axis.categories[label];
  85. if (v >= axis.min && v <= axis.max)
  86. res.push([v, label]);
  87. }
  88. res.sort(function (a, b) { return a[0] - b[0]; });
  89. return res;
  90. }
  91. function setupCategoriesForAxis(series, axis, datapoints) {
  92. if (series[axis].options.mode != "categories")
  93. return;
  94. if (!series[axis].categories) {
  95. // parse options
  96. var c = {}, o = series[axis].options.categories || {};
  97. if ($.isArray(o)) {
  98. for (var i = 0; i < o.length; ++i)
  99. c[o[i]] = i;
  100. }
  101. else {
  102. for (var v in o)
  103. c[v] = o[v];
  104. }
  105. series[axis].categories = c;
  106. }
  107. // fix ticks
  108. if (!series[axis].options.ticks)
  109. series[axis].options.ticks = categoriesTickGenerator;
  110. transformPointsOnAxis(datapoints, axis, series[axis].categories);
  111. }
  112. function transformPointsOnAxis(datapoints, axis, categories) {
  113. // go through the points, transforming them
  114. var points = datapoints.points,
  115. ps = datapoints.pointsize,
  116. format = datapoints.format,
  117. formatColumn = axis.charAt(0),
  118. index = getNextIndex(categories);
  119. for (var i = 0; i < points.length; i += ps) {
  120. if (points[i] == null)
  121. continue;
  122. for (var m = 0; m < ps; ++m) {
  123. var val = points[i + m];
  124. if (val == null || !format[m][formatColumn])
  125. continue;
  126. if (!(val in categories)) {
  127. categories[val] = index;
  128. ++index;
  129. }
  130. points[i + m] = categories[val];
  131. }
  132. }
  133. }
  134. function processDatapoints(plot, series, datapoints) {
  135. setupCategoriesForAxis(series, "xaxis", datapoints);
  136. setupCategoriesForAxis(series, "yaxis", datapoints);
  137. }
  138. function init(plot) {
  139. plot.hooks.processRawData.push(processRawData);
  140. plot.hooks.processDatapoints.push(processDatapoints);
  141. }
  142. $.plot.plugins.push({
  143. init: init,
  144. options: options,
  145. name: 'categories',
  146. version: '1.0'
  147. });
  148. })(jQuery);