]>
Commit | Line | Data |
---|---|---|
31f18b77 FG |
1 | /* Flot plugin for plotting textual data or categories. |
2 | ||
3 | Copyright (c) 2007-2013 IOLA and Ole Laursen. | |
4 | Licensed under the MIT license. | |
5 | ||
6 | Consider a dataset like [["February", 34], ["March", 20], ...]. This plugin | |
7 | allows you to plot such a dataset directly. | |
8 | ||
9 | To enable it, you must specify mode: "categories" on the axis with the textual | |
10 | labels, e.g. | |
11 | ||
12 | $.plot("#placeholder", data, { xaxis: { mode: "categories" } }); | |
13 | ||
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: | |
17 | ||
18 | xaxis: { | |
19 | mode: "categories", | |
20 | categories: ["February", "March", "April"] | |
21 | } | |
22 | ||
23 | If 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 | ||
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). | |
33 | ||
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. | |
40 | ||
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. | |
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); |