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