minor fixes
[sgn.git] / js / flot / categories.js
blobec314bf670afe07dedfcf0c7ff3da37c429b0836
1 /* Flot plugin for plotting textual data or categories.
3 Copyright (c) 2007-2012 IOLA and Ole Laursen.
4 Licensed under the MIT license.
6 Consider a dataset like [["February", 34], ["March", 20], ...]. This plugin
7 allows you to plot such a dataset directly.
9 To enable it, you must specify mode: "categories" on the axis with the textual
10 labels, e.g.
12         $.plot("#placeholder", data, { xaxis: { mode: "categories" } });
14 By default, the labels are ordered as they are met in the data series. If you
15 need a different ordering, you can specify "categories" on the axis options
16 and list the categories there:
18         xaxis: {
19                 mode: "categories",
20                 categories: ["February", "March", "April"]
21         }
23 If you need to customize the distances between the categories, you can specify
24 "categories" as an object mapping labels to values
26         xaxis: {
27                 mode: "categories",
28                 categories: { "February": 1, "March": 3, "April": 4 }
29         }
31 If you don't specify all categories, the remaining categories will be numbered
32 from the max value plus 1 (with a spacing of 1 between each).
34 Internally, the plugin works by transforming the input data through an auto-
35 generated mapping where the first category becomes 0, the second 1, etc.
36 Hence, a point like ["February", 34] becomes [0, 34] internally in Flot (this
37 is visible in hover and click events that return numbers rather than the
38 category labels). The plugin also overrides the tick generator to spit out the
39 categories as ticks instead of the values.
41 If you need to map a value back to its label, the mapping is always accessible
42 as "categories" on the axis object, e.g. plot.getAxes().xaxis.categories.
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
61         var xCategories = series.xaxis.options.mode == "categories",
62             yCategories = series.yaxis.options.mode == "categories";
63         
64         if (!(xCategories || yCategories))
65             return;
67         var format = datapoints.format;
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 });
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         }
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     }
97     function getNextIndex(categories) {
98         var index = -1;
99         
100         for (var v in categories)
101             if (categories[v] > index)
102                 index = categories[v];
104         return index + 1;
105     }
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         }
115         res.sort(function (a, b) { return a[0] - b[0]; });
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         }
139         // fix ticks
140         if (!series[axis].options.ticks)
141             series[axis].options.ticks = categoriesTickGenerator;
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);
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];
161                 if (val == null || !format[m][formatColumn])
162                     continue;
164                 if (!(val in categories)) {
165                     categories[val] = index;
166                     ++index;
167                 }
168                 
169                 points[i + m] = categories[val];
170             }
171         }
172     }
174     function processDatapoints(plot, series, datapoints) {
175         setupCategoriesForAxis(series, "xaxis", datapoints);
176         setupCategoriesForAxis(series, "yaxis", datapoints);
177     }
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);