datavis-glide 4.0.0-PRE.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +45 -0
- package/README.md +129 -0
- package/datavis.js +101 -0
- package/dist/wcdatavis.css +1957 -0
- package/dist/wcdatavis.min.js +1 -0
- package/global-jquery.js +4 -0
- package/ie-fixes.js +13 -0
- package/index.js +70 -0
- package/meteor.js +1 -0
- package/package.json +102 -0
- package/src/flags.js +6 -0
- package/src/graph.js +1079 -0
- package/src/graph_renderer.js +85 -0
- package/src/grid.js +2777 -0
- package/src/grid_control.js +1957 -0
- package/src/grid_filter.js +1073 -0
- package/src/grid_renderer.js +276 -0
- package/src/group_fun_win.js +121 -0
- package/src/lang/en-US.js +188 -0
- package/src/lang/es-MX.js +188 -0
- package/src/lang/fr-FR.js +188 -0
- package/src/lang/id-ID.js +188 -0
- package/src/lang/nl-NL.js +188 -0
- package/src/lang/pt-BR.js +188 -0
- package/src/lang/ru-RU.js +188 -0
- package/src/lang/th-TH.js +188 -0
- package/src/lang/vi-VN.js +188 -0
- package/src/lang/zh-Hans-CN.js +188 -0
- package/src/operations_palette.js +176 -0
- package/src/prefs_modules.js +132 -0
- package/src/reg/graph_renderer.js +17 -0
- package/src/renderers/graph/chartjs.js +457 -0
- package/src/renderers/graph/google.js +584 -0
- package/src/renderers/graph/jit.js +61 -0
- package/src/renderers/graph/svelte-gantt.js +168 -0
- package/src/renderers/grid/dummy.js +79 -0
- package/src/renderers/grid/handlebars.js +217 -0
- package/src/renderers/grid/squirrelly.js +215 -0
- package/src/renderers/grid/table/group_detail.js +1404 -0
- package/src/renderers/grid/table/group_summary.js +380 -0
- package/src/renderers/grid/table/pivot.js +915 -0
- package/src/renderers/grid/table/plain.js +1592 -0
- package/src/renderers/grid/table.js +2510 -0
- package/src/trans.js +101 -0
- package/src/ui/collapsible.js +234 -0
- package/src/ui/filters/date.js +283 -0
- package/src/ui/grid_filter.js +398 -0
- package/src/ui/popup_menu.js +224 -0
- package/src/ui/popup_window.js +572 -0
- package/src/ui/slider.js +156 -0
- package/src/ui/tabs.js +202 -0
- package/src/ui/templates.js +131 -0
- package/src/ui/toolbar.js +63 -0
- package/src/ui/toolbars/grid.js +873 -0
- package/src/ui/windows/col_config.js +341 -0
- package/src/ui/windows/debug.js +164 -0
- package/src/ui/windows/grid_table_opts.js +139 -0
- package/src/util/handlebars.js +158 -0
- package/src/util/jquery.js +630 -0
- package/src/util/misc.js +1058 -0
- package/src/util/squirrelly.js +155 -0
- package/wcdatavis.css +1601 -0
package/src/graph.js
ADDED
|
@@ -0,0 +1,1079 @@
|
|
|
1
|
+
import _ from 'underscore';
|
|
2
|
+
|
|
3
|
+
import jQuery from 'jquery';
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
deepCopy,
|
|
7
|
+
deepDefaults,
|
|
8
|
+
determineColumns,
|
|
9
|
+
icon,
|
|
10
|
+
gensym,
|
|
11
|
+
getProp,
|
|
12
|
+
getPropDef,
|
|
13
|
+
makeSubclass,
|
|
14
|
+
makeToggleCheckbox,
|
|
15
|
+
mixinLogging,
|
|
16
|
+
presentDownload,
|
|
17
|
+
setProp,
|
|
18
|
+
toInt,
|
|
19
|
+
} from './util/misc.js';
|
|
20
|
+
import { ComputedView, Prefs } from 'datavis-ace';
|
|
21
|
+
import {trans} from './trans.js';
|
|
22
|
+
|
|
23
|
+
import GRAPH_RENDERER_REGISTRY from './reg/graph_renderer.js';
|
|
24
|
+
|
|
25
|
+
// Graph {{{1
|
|
26
|
+
|
|
27
|
+
// JSDoc Types {{{2
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @typedef {object} Graph~Config
|
|
31
|
+
*
|
|
32
|
+
* @property {Graph~Config_When} whenPlain
|
|
33
|
+
* Tells how to configure the graph when the data is plain (has not been grouped or pivotted).
|
|
34
|
+
*
|
|
35
|
+
* @property {Graph~Config_When} whenGroup
|
|
36
|
+
* Tells how to configure the graph when the data is grouped.
|
|
37
|
+
*
|
|
38
|
+
* @property {Graph~Config_When} whenPivot
|
|
39
|
+
* Tells how to configure the graph when the data is pivotted.
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @typedef {object} Graph~Config_When
|
|
44
|
+
* Can either be a function that returns an object, or just an object. If it's a function, it
|
|
45
|
+
* receives the group fields and pivot fields as arguments.
|
|
46
|
+
*
|
|
47
|
+
* @property {string} graphType
|
|
48
|
+
* Name of the type of graph.
|
|
49
|
+
*
|
|
50
|
+
* @property {number} [aggNum]
|
|
51
|
+
* When graphing grouped or pivotted data, the aggregate number that we're graphing for the value.
|
|
52
|
+
*
|
|
53
|
+
* @property {string} [aggType]
|
|
54
|
+
* When graphing pivotted data, the type of aggregate that we're graphing on the value axis.
|
|
55
|
+
*
|
|
56
|
+
* - `cell`: We're graphing separate series for each pivot colval.
|
|
57
|
+
* - `group`: We're graphing the aggregate calculated over all the data in each group.
|
|
58
|
+
* - `pivot`: We're graphing the aggregate calculated over all the data in each pivot.
|
|
59
|
+
*
|
|
60
|
+
* For example, when grouping by "Product" and pivotting by "Country", you can create a "Sum"
|
|
61
|
+
* aggregate on the field "Sales." Let's assume we're doing a column graph.
|
|
62
|
+
*
|
|
63
|
+
* - `cell`: Shows a stacked graph where each bar is a product, and each item in the stack is a country.
|
|
64
|
+
* - `group`: Shows a graph where each bar is a product (showing total sales in all countries).
|
|
65
|
+
* - `pivot`: Shows a graph where each bar is a country (showing total sales for all products).
|
|
66
|
+
*
|
|
67
|
+
* @property {object} [options]
|
|
68
|
+
* These options are passed directly to the graph rendering library (e.g. Google Charts, ChartJS).
|
|
69
|
+
*/
|
|
70
|
+
|
|
71
|
+
// Constructor {{{2
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Creates a new graph.
|
|
75
|
+
*
|
|
76
|
+
* @param {string} id
|
|
77
|
+
*
|
|
78
|
+
* @param {ComputedView} view
|
|
79
|
+
*
|
|
80
|
+
* @param {Graph~Config} opts
|
|
81
|
+
*
|
|
82
|
+
* @class
|
|
83
|
+
*
|
|
84
|
+
* Represents a graph.
|
|
85
|
+
*
|
|
86
|
+
* @property {string} id
|
|
87
|
+
* @property {ComputedView} view
|
|
88
|
+
* @property {object} devConfig
|
|
89
|
+
* @property {object} userConfig
|
|
90
|
+
* @property {object} opts
|
|
91
|
+
* @property {GraphRenderer} renderer
|
|
92
|
+
*/
|
|
93
|
+
|
|
94
|
+
var Graph = makeSubclass('Graph', Object, function (id, view, devConfig, opts) {
|
|
95
|
+
var self = this;
|
|
96
|
+
|
|
97
|
+
self.id = id;
|
|
98
|
+
self.view = view;
|
|
99
|
+
self.devConfig = devConfig || {};
|
|
100
|
+
self.userConfig = {
|
|
101
|
+
plain: {},
|
|
102
|
+
group: {},
|
|
103
|
+
pivot: {}
|
|
104
|
+
};
|
|
105
|
+
self.opts = deepDefaults(opts, {
|
|
106
|
+
title: 'Graph',
|
|
107
|
+
runImmediately: true,
|
|
108
|
+
showToolbar: true,
|
|
109
|
+
showOnDataChange: false,
|
|
110
|
+
});
|
|
111
|
+
self.hasRun = false;
|
|
112
|
+
|
|
113
|
+
if (typeof id !== 'string') {
|
|
114
|
+
throw new Error('Call Error: `id` must be a string');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (!(view instanceof ComputedView)) {
|
|
118
|
+
throw new Error('Call Error: `view` must be an instance of MIE.WC_DataVis.ComputedView');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (self.opts.prefs != null && !(self.opts.prefs instanceof Prefs)) {
|
|
122
|
+
throw new Error('Call Error: `opts.prefs` must be an instance of MIE.WC_DataVis.Prefs');
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (self.opts.prefs != null) {
|
|
126
|
+
self.prefs = self.opts.prefs;
|
|
127
|
+
}
|
|
128
|
+
else if (self.view.prefs != null) {
|
|
129
|
+
self.prefs = self.view.prefs;
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
self.prefs = new Prefs(self.id);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
self.prefs.bind('graph', self);
|
|
136
|
+
|
|
137
|
+
self._makeUserInterface();
|
|
138
|
+
|
|
139
|
+
self.view.addClient(self, 'graph');
|
|
140
|
+
|
|
141
|
+
// Event handlers for keeping the spinner icon updated.
|
|
142
|
+
|
|
143
|
+
self.view.on('fetchDataBegin', function () {
|
|
144
|
+
self._setSpinner('loading');
|
|
145
|
+
self._showSpinner();
|
|
146
|
+
});
|
|
147
|
+
self.view.on('fetchDataEnd', function () {
|
|
148
|
+
self._hideSpinner();
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
self.view.on('workBegin', function () {
|
|
152
|
+
self._setSpinner('working');
|
|
153
|
+
self._showSpinner();
|
|
154
|
+
});
|
|
155
|
+
self.view.on('workEnd', function () {
|
|
156
|
+
self._hideSpinner();
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// Event handler for keeping the UI in sync with the data. We don't let the graph renderer redraw itself because we need to keep the UI in sync with the data. For example, if a pivot is removed, the aggregate will change if we were graphing a pivot-specific aggregate like "Count by [Pivot Field]").
|
|
160
|
+
|
|
161
|
+
self.view.on('workEnd', function (info, ops) {
|
|
162
|
+
self.lastOps = ops;
|
|
163
|
+
// self.drawFromConfig();
|
|
164
|
+
}, {
|
|
165
|
+
who: self
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
self.view.on('dataUpdated', function () {
|
|
169
|
+
if (self.opts.showOnDataChange && !self.isVisible()) {
|
|
170
|
+
self.show({ redraw: false });
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Only need to redraw if there's no renderer. If there is, the renderer's own View
|
|
174
|
+
// (workEnd) handler will take care of it.
|
|
175
|
+
|
|
176
|
+
if (self.renderer == null) {
|
|
177
|
+
self.redraw();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/*
|
|
181
|
+
switch (self.lastDrawnFrom) {
|
|
182
|
+
case 'config':
|
|
183
|
+
self.drawFromConfig();
|
|
184
|
+
break;
|
|
185
|
+
case 'interactive':
|
|
186
|
+
default:
|
|
187
|
+
self.drawInteractive();
|
|
188
|
+
break;
|
|
189
|
+
}
|
|
190
|
+
*/
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
// self.view.on('aggregateSet', function (spec, shouldGraph) {
|
|
194
|
+
// var aggType, aggNum;
|
|
195
|
+
//
|
|
196
|
+
// if (shouldGraph != null) {
|
|
197
|
+
// if (shouldGraph.group && shouldGraph.group.length > 0) {
|
|
198
|
+
// aggType = 'group';
|
|
199
|
+
// aggNum = shouldGraph.group[0].aggNum;
|
|
200
|
+
// }
|
|
201
|
+
// else if (shouldGraph.pivot && shouldGraph.pivot.length > 0) {
|
|
202
|
+
// aggType = 'pivot';
|
|
203
|
+
// aggNum = shouldGraph.pivot[0].aggNum;
|
|
204
|
+
// }
|
|
205
|
+
//
|
|
206
|
+
// if (aggType == null || aggNum == null) {
|
|
207
|
+
// // Couldn't find an aggregate we could graph.
|
|
208
|
+
// return;
|
|
209
|
+
// }
|
|
210
|
+
//
|
|
211
|
+
// // Set the dropdown to match the aggregate we're supposed to graph.
|
|
212
|
+
//
|
|
213
|
+
// var matchingAgg = self.ui.aggDropdown.find('option');
|
|
214
|
+
// matchingAgg = matchingAgg.filter(function (i, elt) {
|
|
215
|
+
// return elt.getAttribute('data-wcdv-agg-type') === aggType;
|
|
216
|
+
// });
|
|
217
|
+
// matchingAgg = matchingAgg.filter(function (i, elt) {
|
|
218
|
+
// return +elt.getAttribute('data-wcdv-agg-num') === aggNum;
|
|
219
|
+
// });
|
|
220
|
+
// if (matchingAgg.length === 1) {
|
|
221
|
+
// self.ui.aggDropdown.val(matchingAgg.attr('value'));
|
|
222
|
+
// // self.ui.aggDropdown.trigger('change');
|
|
223
|
+
// }
|
|
224
|
+
// }
|
|
225
|
+
// });
|
|
226
|
+
|
|
227
|
+
if (self.opts.runImmediately) {
|
|
228
|
+
self.show();
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
self.hasRun = false;
|
|
232
|
+
self.hide();
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/*
|
|
236
|
+
* Store self object so it can be accessed from other JavaScript in the page.
|
|
237
|
+
*/
|
|
238
|
+
|
|
239
|
+
setProp(self, window, 'MIE', 'WC_DataVis', 'graphs', self.id);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
mixinLogging(Graph);
|
|
243
|
+
|
|
244
|
+
// #toString {{{2
|
|
245
|
+
|
|
246
|
+
Graph.prototype.toString = function () {
|
|
247
|
+
return 'Graph(id="' + this.id + '")';
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
// #_makeUserInterface {{{2
|
|
251
|
+
|
|
252
|
+
Graph.prototype._makeUserInterface = function () {
|
|
253
|
+
var self = this;
|
|
254
|
+
|
|
255
|
+
// div.wcdv_graph (ui.root)
|
|
256
|
+
// |
|
|
257
|
+
// +-- div.wcdv_grid_titlebar (ui.titlebar)
|
|
258
|
+
// | |
|
|
259
|
+
// | +-- strong (ui.spinner)
|
|
260
|
+
// | +-- strong [[ the title ]]
|
|
261
|
+
// | `-- button [[ show/hide button ]]
|
|
262
|
+
// |
|
|
263
|
+
// `-- div.wcdv_grid_content (ui.content)
|
|
264
|
+
// |
|
|
265
|
+
// +-- div.wcdv_grid_toolbar (ui.toolbar)
|
|
266
|
+
// +-- div.wcdv_toolbar_section (ui.toolbar_source)
|
|
267
|
+
// +-- div.wcdv_toolbar_section (ui.toolbar_common)
|
|
268
|
+
// +-- div.wcdv_toolbar_section (ui.toolbar_aggregate)
|
|
269
|
+
// `-- div.wcdv_graph_render (ui.graph)
|
|
270
|
+
|
|
271
|
+
self.ui = {};
|
|
272
|
+
self.ui.root = jQuery(document.getElementById(self.id));
|
|
273
|
+
|
|
274
|
+
self.ui.root.addClass('wcdv_graph');
|
|
275
|
+
self.ui.root.children().remove();
|
|
276
|
+
|
|
277
|
+
self.ui.titlebar = jQuery('<div>')
|
|
278
|
+
.addClass('wcdv_grid_titlebar')
|
|
279
|
+
.on('click', function (evt) {
|
|
280
|
+
evt.stopPropagation();
|
|
281
|
+
self.toggle();
|
|
282
|
+
})
|
|
283
|
+
.appendTo(self.ui.root);
|
|
284
|
+
|
|
285
|
+
self._addTitleWidgets(self.ui.titlebar);
|
|
286
|
+
|
|
287
|
+
self.ui.content = jQuery('<div>', {
|
|
288
|
+
'class': 'wcdv_grid_content'
|
|
289
|
+
}).appendTo(self.ui.root);
|
|
290
|
+
|
|
291
|
+
self.ui.toolbar = jQuery('<div>')
|
|
292
|
+
.addClass('wcdv_grid_toolbar')
|
|
293
|
+
.appendTo(self.ui.content)
|
|
294
|
+
;
|
|
295
|
+
|
|
296
|
+
if (!self.opts.showToolbar) {
|
|
297
|
+
self.ui.toolbar.hide();
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// The "pivot" toolbar section lets the user decide if colvals should show up stacked or as
|
|
301
|
+
// separate bars (for bar & column charts).
|
|
302
|
+
|
|
303
|
+
self.ui.toolbar_pivot = jQuery('<div>')
|
|
304
|
+
.addClass('wcdv_toolbar_section')
|
|
305
|
+
.css('visibility', 'hidden')
|
|
306
|
+
.appendTo(self.ui.toolbar);
|
|
307
|
+
self._addPivotButtons(self.ui.toolbar_pivot);
|
|
308
|
+
|
|
309
|
+
// The "aggregates" toolbar section lets the user control what is drawn based on the aggregate
|
|
310
|
+
// functions calculated by the view.
|
|
311
|
+
|
|
312
|
+
self.ui.toolbar_aggregates = jQuery('<div>')
|
|
313
|
+
.addClass('wcdv_toolbar_section pull-right')
|
|
314
|
+
.css('visibility', 'hidden')
|
|
315
|
+
.appendTo(self.ui.toolbar);
|
|
316
|
+
self._addAggregateButtons(self.ui.toolbar_aggregates);
|
|
317
|
+
|
|
318
|
+
self.ui.graph = jQuery('<div>', { 'id': self.id, 'class': 'wcdv_graph_render' });
|
|
319
|
+
|
|
320
|
+
self.ui.root
|
|
321
|
+
.append(self.ui.titlebar)
|
|
322
|
+
.append(self.ui.content
|
|
323
|
+
.append(self.ui.toolbar)
|
|
324
|
+
.append(self.ui.graph))
|
|
325
|
+
;
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
// #_addTitleWidgets {{{2
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Add widgets to the header of the graph.
|
|
332
|
+
*
|
|
333
|
+
* @private
|
|
334
|
+
*
|
|
335
|
+
* @param {jQuery} titlebar
|
|
336
|
+
*/
|
|
337
|
+
|
|
338
|
+
Graph.prototype._addTitleWidgets = function (titlebar) {
|
|
339
|
+
var self = this;
|
|
340
|
+
|
|
341
|
+
self.ui.spinner = jQuery('<span>', {
|
|
342
|
+
'style': 'font-size: 18px',
|
|
343
|
+
'class': 'wcdv_icon_button wcdv_spinner'
|
|
344
|
+
})
|
|
345
|
+
.appendTo(titlebar)
|
|
346
|
+
;
|
|
347
|
+
|
|
348
|
+
self._setSpinner(self.opts.runImmediately ? 'loading' : 'not-loaded');
|
|
349
|
+
|
|
350
|
+
jQuery('<strong>')
|
|
351
|
+
.text(self.opts.title)
|
|
352
|
+
.appendTo(titlebar);
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
// Create container to hold all the controls in the titlebar
|
|
356
|
+
|
|
357
|
+
self.ui.titlebar_controls = jQuery('<div>')
|
|
358
|
+
.addClass('wcdv_titlebar_controls pull-right')
|
|
359
|
+
.appendTo(titlebar);
|
|
360
|
+
|
|
361
|
+
// Create the Export button
|
|
362
|
+
|
|
363
|
+
self.ui.exportBtn = jQuery('<button>', {
|
|
364
|
+
'type': 'button',
|
|
365
|
+
'style': 'font-size: 18px',
|
|
366
|
+
'class': 'wcdv_icon_button wcdv_text-primary'
|
|
367
|
+
})
|
|
368
|
+
.on('click', function (evt) {
|
|
369
|
+
evt.stopPropagation();
|
|
370
|
+
self.export();
|
|
371
|
+
})
|
|
372
|
+
.append(icon('download'))
|
|
373
|
+
.appendTo(self.ui.titlebar_controls)
|
|
374
|
+
;
|
|
375
|
+
|
|
376
|
+
// Create the Refresh button
|
|
377
|
+
|
|
378
|
+
self.ui.refreshBtn = jQuery('<button>', {
|
|
379
|
+
'type': 'button',
|
|
380
|
+
'style': 'font-size: 18px',
|
|
381
|
+
'class': 'wcdv_icon_button wcdv_text-primary'
|
|
382
|
+
})
|
|
383
|
+
.attr('title', 'Refresh')
|
|
384
|
+
.on('click', function (evt) {
|
|
385
|
+
evt.stopPropagation();
|
|
386
|
+
self.refresh();
|
|
387
|
+
})
|
|
388
|
+
.append(icon('refresh-cw'))
|
|
389
|
+
.appendTo(self.ui.titlebar_controls)
|
|
390
|
+
;
|
|
391
|
+
|
|
392
|
+
// This is the "gear" icon that shows/hides the controls below the toolbar. The controls are used
|
|
393
|
+
// to set the group, pivot, aggregate, and filters. Ideally the user only has to utilize these
|
|
394
|
+
// once, and then switches between perspectives to get the same effect.
|
|
395
|
+
|
|
396
|
+
jQuery('<button>', {
|
|
397
|
+
'type': 'button',
|
|
398
|
+
'style': 'font-size: 18px',
|
|
399
|
+
'class': 'wcdv_icon_button wcdv_text-primary'
|
|
400
|
+
})
|
|
401
|
+
.attr('title', trans('GRAPH.TITLEBAR.SHOW_HIDE_CONTROLS'))
|
|
402
|
+
.click(function (evt) {
|
|
403
|
+
evt.stopPropagation();
|
|
404
|
+
self.ui.toolbar.toggle();
|
|
405
|
+
})
|
|
406
|
+
.append(jQuery(icon('settings')))
|
|
407
|
+
.appendTo(self.ui.titlebar_controls)
|
|
408
|
+
;
|
|
409
|
+
|
|
410
|
+
// Create the down-chevron button that shows/hides everything under the titlebar.
|
|
411
|
+
|
|
412
|
+
self.ui.showHideButton = jQuery('<button>', {
|
|
413
|
+
'type': 'button',
|
|
414
|
+
'style': 'font-size: 18px',
|
|
415
|
+
'class': 'wcdv_icon_button wcdv_text-primary showhide'
|
|
416
|
+
})
|
|
417
|
+
.attr('title', trans('GRAPH.TITLEBAR.SHOW_HIDE'))
|
|
418
|
+
.click(function (evt) {
|
|
419
|
+
evt.stopPropagation();
|
|
420
|
+
self.toggle();
|
|
421
|
+
})
|
|
422
|
+
.append(jQuery(icon('chevron-down')))
|
|
423
|
+
.appendTo(self.ui.titlebar_controls)
|
|
424
|
+
;
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
// #_addAggregateButtons {{{2
|
|
428
|
+
|
|
429
|
+
Graph.prototype._addAggregateButtons = function (toolbar) {
|
|
430
|
+
var self = this;
|
|
431
|
+
|
|
432
|
+
var graphTypeDropdownId = gensym();
|
|
433
|
+
jQuery('<label>', { 'for': graphTypeDropdownId }).text('Graph Type: ').appendTo(toolbar);
|
|
434
|
+
self.ui.graphTypeDropdown = jQuery('<select>', { 'id': graphTypeDropdownId })
|
|
435
|
+
.on('change', function () {
|
|
436
|
+
self.drawInteractive();
|
|
437
|
+
})
|
|
438
|
+
.appendTo(toolbar);
|
|
439
|
+
|
|
440
|
+
var aggDropdownId = gensym();
|
|
441
|
+
jQuery('<label>', { 'for': aggDropdownId }).text('Aggregate: ').appendTo(toolbar);
|
|
442
|
+
self.ui.aggDropdown = jQuery('<select>', { 'id': aggDropdownId })
|
|
443
|
+
.on('change', function () {
|
|
444
|
+
self.drawInteractive();
|
|
445
|
+
})
|
|
446
|
+
.appendTo(toolbar);
|
|
447
|
+
|
|
448
|
+
self.ui.zeroAxisCheckbox = makeToggleCheckbox(
|
|
449
|
+
null,
|
|
450
|
+
null,
|
|
451
|
+
false,
|
|
452
|
+
'Y-Axis Starts at Zero',
|
|
453
|
+
toolbar,
|
|
454
|
+
function () {
|
|
455
|
+
self.drawInteractive();
|
|
456
|
+
}
|
|
457
|
+
);
|
|
458
|
+
|
|
459
|
+
self.view.on('workEnd', function () {
|
|
460
|
+
self._updateAggDropdown();
|
|
461
|
+
});
|
|
462
|
+
};
|
|
463
|
+
|
|
464
|
+
// #_setGraphTypeOptions {{{2
|
|
465
|
+
|
|
466
|
+
Graph.prototype._setGraphTypeOptions = function () {
|
|
467
|
+
var self = this;
|
|
468
|
+
|
|
469
|
+
if (self.ui.graphTypeDropdown == null) {
|
|
470
|
+
self.logError(self.makeLogTag('set graph type optoins') + ' Dropdown UI element does not exist');
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
if (self.renderer == null) {
|
|
475
|
+
self.logError(self.makeLogTag('set graph type optoins') + ' Renderer does not exist');
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
self.ui.graphTypeDropdown.children().remove();
|
|
480
|
+
if (self.renderer.graphTypes != null) {
|
|
481
|
+
self.renderer.graphTypes.each(function (gt) {
|
|
482
|
+
self.ui.graphTypeDropdown.append(jQuery('<option>', { 'value': gt.value }).text(gt.name));
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
// #_addPivotButtons {{{2
|
|
488
|
+
|
|
489
|
+
Graph.prototype._addPivotButtons = function (toolbar) {
|
|
490
|
+
var self = this;
|
|
491
|
+
|
|
492
|
+
self.ui.stackCheckbox = makeToggleCheckbox(
|
|
493
|
+
null,
|
|
494
|
+
null,
|
|
495
|
+
true,
|
|
496
|
+
'Stack',
|
|
497
|
+
toolbar,
|
|
498
|
+
function () {
|
|
499
|
+
self.drawInteractive();
|
|
500
|
+
}
|
|
501
|
+
);
|
|
502
|
+
};
|
|
503
|
+
|
|
504
|
+
// #_udpateAggDropdown {{{2
|
|
505
|
+
|
|
506
|
+
Graph.prototype._updateAggDropdown = function () {
|
|
507
|
+
var self = this;
|
|
508
|
+
|
|
509
|
+
// options : [obj]
|
|
510
|
+
// obj : {
|
|
511
|
+
// name : string
|
|
512
|
+
// type : string ('group', 'pivot', 'cell')
|
|
513
|
+
// num : int
|
|
514
|
+
// }
|
|
515
|
+
|
|
516
|
+
var options = [];
|
|
517
|
+
|
|
518
|
+
// addOption : AggregateInfo, string -> ()
|
|
519
|
+
|
|
520
|
+
var addOption = function (aggInfo, appendToName) {
|
|
521
|
+
var name = aggInfo.name || aggInfo.instance.getFullName();
|
|
522
|
+
if (appendToName != null) {
|
|
523
|
+
name += appendToName;
|
|
524
|
+
}
|
|
525
|
+
options.push({
|
|
526
|
+
name: name,
|
|
527
|
+
type: aggInfo.aggType,
|
|
528
|
+
num: aggInfo.aggNum
|
|
529
|
+
});
|
|
530
|
+
};
|
|
531
|
+
|
|
532
|
+
self.view.getData(function (ok, data) {
|
|
533
|
+
self.ui.aggDropdown.children().remove();
|
|
534
|
+
|
|
535
|
+
if (data.isGroup) {
|
|
536
|
+
_.each(getPropDef([], data, 'agg', 'info', 'group'), function (ai) {
|
|
537
|
+
addOption(ai);
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
else if (data.isPivot) {
|
|
541
|
+
_.each(getPropDef([], data, 'agg', 'info', 'group'), function (ai) {
|
|
542
|
+
addOption(ai, ' by ' + data.groupFields.join(', '));
|
|
543
|
+
});
|
|
544
|
+
_.each(getPropDef([], data, 'agg', 'info', 'pivot'), function (ai) {
|
|
545
|
+
addOption(ai, ' by ' + data.pivotFields.join(', '));
|
|
546
|
+
});
|
|
547
|
+
_.each(getPropDef([], data, 'agg', 'info', 'cell'), function (ai) {
|
|
548
|
+
addOption(ai);
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// For pivotted data, there are three different aggregates we could graph. We list them
|
|
553
|
+
// separately in the dropdown, and we want them in the order: cell, group, pivot. It just so
|
|
554
|
+
// happens that this is also alphabetical order, so we just sort by the aggType first before
|
|
555
|
+
// sorting by the aggNum so the dropdown will be in the right order.
|
|
556
|
+
|
|
557
|
+
_.each(_.sortBy(_.sortBy(options, 'type'), 'num'), function (opt) {
|
|
558
|
+
var option = jQuery('<option>', {
|
|
559
|
+
'value': opt.name,
|
|
560
|
+
'data-wcdv-agg-type': opt.type,
|
|
561
|
+
'data-wcdv-agg-num': opt.num,
|
|
562
|
+
}).text(opt.name);
|
|
563
|
+
self.ui.aggDropdown.append(option);
|
|
564
|
+
});
|
|
565
|
+
}, 'Updating graph aggregate dropdown');
|
|
566
|
+
};
|
|
567
|
+
|
|
568
|
+
// #syncDrawnGraphConfigWithUi {{{2
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* Synchronize the configuration actually used to draw a graph with the user interface. This updates
|
|
572
|
+
* things like the graph type and aggregate dropdowns to reflect how the graph was drawn.
|
|
573
|
+
*
|
|
574
|
+
* @param {Graph~Config_When} config
|
|
575
|
+
* The configuration used by the graph that was just drawn.
|
|
576
|
+
*/
|
|
577
|
+
|
|
578
|
+
Graph.prototype.syncDrawnGraphConfigWithUi = function (config) {
|
|
579
|
+
var self = this;
|
|
580
|
+
|
|
581
|
+
if (config == null) {
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
var axis = config.graphType === 'bar' ? 'hAxis' : 'vAxis';
|
|
586
|
+
self.ui.graphTypeDropdown.val(config.graphType);
|
|
587
|
+
|
|
588
|
+
if (self.lastOps != null && self.lastOps.group) {
|
|
589
|
+
self.ui.toolbar_aggregates.css('visibility', 'visible');
|
|
590
|
+
var matchingAgg = self.ui.aggDropdown.find('option');
|
|
591
|
+
if (config.aggType != null) {
|
|
592
|
+
matchingAgg = matchingAgg.filter(function (i, elt) {
|
|
593
|
+
return elt.getAttribute('data-wcdv-agg-type') === config.aggType;
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
if (config.aggNum != null) {
|
|
597
|
+
matchingAgg = matchingAgg.filter(function (i, elt) {
|
|
598
|
+
return +elt.getAttribute('data-wcdv-agg-num') === config.aggNum;
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
if (matchingAgg.length === 1) {
|
|
602
|
+
self.ui.aggDropdown.val(matchingAgg.attr('value'));
|
|
603
|
+
}
|
|
604
|
+
self.ui.zeroAxisCheckbox.prop('checked', getProp(config, 'options', axis, 'minValue') == 0);
|
|
605
|
+
}
|
|
606
|
+
else {
|
|
607
|
+
self.ui.toolbar_aggregates.css('visibility', 'hidden');
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
if (self.lastOps != null && self.lastOps.pivot) {
|
|
611
|
+
self.ui.toolbar_pivot.css('visibility', 'visible');
|
|
612
|
+
self.ui.stackCheckbox.prop('checked', !!getProp(config, 'options', 'isStacked'));
|
|
613
|
+
}
|
|
614
|
+
else {
|
|
615
|
+
self.ui.toolbar_pivot.css('visibility', 'hidden');
|
|
616
|
+
}
|
|
617
|
+
};
|
|
618
|
+
|
|
619
|
+
// #export {{{2
|
|
620
|
+
|
|
621
|
+
Graph.prototype.export = function () {
|
|
622
|
+
var self = this;
|
|
623
|
+
|
|
624
|
+
if (self.exportBlob == null) {
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
var fileName = (self.opts.title || self.id) + '.png';
|
|
629
|
+
presentDownload(self.exportBlob, fileName);
|
|
630
|
+
};
|
|
631
|
+
|
|
632
|
+
// #_setExportBlob {{{2
|
|
633
|
+
|
|
634
|
+
Graph.prototype._setExportBlob = function (blob) {
|
|
635
|
+
var self = this;
|
|
636
|
+
|
|
637
|
+
self.exportBlob = blob;
|
|
638
|
+
self.ui.exportBtn.prop('disabled', blob == null);
|
|
639
|
+
};
|
|
640
|
+
|
|
641
|
+
// #_clearExportBlob {{{2
|
|
642
|
+
|
|
643
|
+
Graph.prototype._clearExportBlob = function () {
|
|
644
|
+
var self = this;
|
|
645
|
+
|
|
646
|
+
self.exportBlob = null;
|
|
647
|
+
self.ui.exportBtn.prop('disabled', true);
|
|
648
|
+
};
|
|
649
|
+
|
|
650
|
+
// #drawFromConfig {{{2
|
|
651
|
+
|
|
652
|
+
Graph.prototype.drawFromConfig = function () {
|
|
653
|
+
var self = this;
|
|
654
|
+
|
|
655
|
+
self.lastDrawnFrom = 'config';
|
|
656
|
+
self.renderer.draw(self.devConfig, self.userConfig);
|
|
657
|
+
};
|
|
658
|
+
|
|
659
|
+
// #drawInteractive {{{2
|
|
660
|
+
|
|
661
|
+
Graph.prototype.drawInteractive = function () {
|
|
662
|
+
var self = this;
|
|
663
|
+
|
|
664
|
+
var graphType = self.ui.graphTypeDropdown.val();
|
|
665
|
+
var minValue = self.ui.zeroAxisCheckbox.prop('checked') ? 0 : null;
|
|
666
|
+
|
|
667
|
+
var config = {
|
|
668
|
+
group: {
|
|
669
|
+
graphs: {},
|
|
670
|
+
current: graphType
|
|
671
|
+
},
|
|
672
|
+
pivot: {
|
|
673
|
+
graphs: {},
|
|
674
|
+
current: graphType
|
|
675
|
+
}
|
|
676
|
+
};
|
|
677
|
+
|
|
678
|
+
// NOTE The `graphType` field here is useless except that it makes the rendering function (e.g.
|
|
679
|
+
// GraphRendererGoogle#draw_plain) more convenient to implement.
|
|
680
|
+
|
|
681
|
+
var selOptIdx = self.ui.aggDropdown.get(0).selectedIndex;
|
|
682
|
+
var selOpt = self.ui.aggDropdown.get(0).options[selOptIdx];
|
|
683
|
+
|
|
684
|
+
config.group.graphs[graphType] = {
|
|
685
|
+
graphType: graphType,
|
|
686
|
+
aggType: selOpt.getAttribute('data-wcdv-agg-type'),
|
|
687
|
+
aggNum: toInt(selOpt.getAttribute('data-wcdv-agg-num')),
|
|
688
|
+
options: {}
|
|
689
|
+
};
|
|
690
|
+
|
|
691
|
+
// At least with Google Charts, you have to swap the horizontal and vertical axis configuration
|
|
692
|
+
// for bar charts (since they're on their side).
|
|
693
|
+
|
|
694
|
+
switch (graphType) {
|
|
695
|
+
case 'bar':
|
|
696
|
+
config.group.graphs[graphType].options = {
|
|
697
|
+
vAxis: {
|
|
698
|
+
minValue: minValue
|
|
699
|
+
}
|
|
700
|
+
};
|
|
701
|
+
break;
|
|
702
|
+
default:
|
|
703
|
+
config.group.graphs[graphType].options = {
|
|
704
|
+
vAxis: {
|
|
705
|
+
minValue: minValue
|
|
706
|
+
}
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
// Copy everything... not strictly necessary AFAIK, but it's safe.
|
|
711
|
+
config.pivot = deepCopy(config.group);
|
|
712
|
+
|
|
713
|
+
// Make sure to add the stack setting for pivot mode.
|
|
714
|
+
config.pivot.graphs[graphType].options.isStacked = self.ui.stackCheckbox.prop('checked');
|
|
715
|
+
|
|
716
|
+
// Store this configuration in the userConfig so that it can be saved with prefs.
|
|
717
|
+
_.extend(self.userConfig, config);
|
|
718
|
+
|
|
719
|
+
if (self.prefs != null) {
|
|
720
|
+
self.prefs.save();
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
self.logDebug(self.makeLogTag() + ' Drawing graph based on interactive config [userConfig = %O]', self.userConfig);
|
|
724
|
+
|
|
725
|
+
self.lastDrawnFrom = 'interactive';
|
|
726
|
+
self.renderer.draw(self.devConfig, self.userConfig);
|
|
727
|
+
};
|
|
728
|
+
|
|
729
|
+
// #checkGraphConfig {{{2
|
|
730
|
+
|
|
731
|
+
Graph.prototype.checkGraphConfig = function () {
|
|
732
|
+
if (self.devConfig == null) {
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
_.each(['whenPlain', 'whenGroup', 'whenPivot'], function (dataFormat) {
|
|
737
|
+
if (self.devConfig[dataFormat] === undefined) {
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
var config = self.devConfig[dataFormat];
|
|
742
|
+
|
|
743
|
+
// Check the "graphType" property.
|
|
744
|
+
|
|
745
|
+
if (config.graphType != null) {
|
|
746
|
+
if (!_.isString(config.graphType)) {
|
|
747
|
+
throw new Error('Graph config error: data format "' + dataFormat + '": `graphType` must be a string');
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
if (['area', 'bar', 'column', 'line', 'pie'].indexOf(config.graphType) === -1) {
|
|
751
|
+
throw new Error('Graph config error: data format "' + dataFormat + '": invalid `graphType`: ' + config.graphType);
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
switch (config.graphType) {
|
|
756
|
+
case 'area':
|
|
757
|
+
case 'bar':
|
|
758
|
+
case 'column':
|
|
759
|
+
case 'line':
|
|
760
|
+
case 'pie':
|
|
761
|
+
if (config.valueField != null && config.valueFields != null) {
|
|
762
|
+
throw new Error('Graph config error: data format "' + dataFormat + '": can\'t define both `valueField` and `valueFields`');
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
// Turn the singular "valueField" into the plural "valueFields."
|
|
766
|
+
|
|
767
|
+
if (config.valueField != null) {
|
|
768
|
+
if (!_.isString(config.valueField)) {
|
|
769
|
+
throw new Error('Graph config error: data format "' + dataFormat + '": `valueField` must be a string');
|
|
770
|
+
}
|
|
771
|
+
config.valueFields = [config.valueField];
|
|
772
|
+
delete config.valueField;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
// Check the "valueFields" property, if it exists.
|
|
776
|
+
|
|
777
|
+
if (config.valueFields != null) {
|
|
778
|
+
if (!_.isArray(config.valueFields)) {
|
|
779
|
+
throw new Error('Graph config error: data format "' + dataFormat + '": `valueFields` must be an array');
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
_.each(config.valueFields, function (f, i) {
|
|
783
|
+
if (!_.isString(f)) {
|
|
784
|
+
throw new Error('Graph config error: data format "' + dataFormat + '": `valueFields[' + i + ']` must be a string');
|
|
785
|
+
}
|
|
786
|
+
});
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
});
|
|
790
|
+
};
|
|
791
|
+
|
|
792
|
+
// #refresh {{{2
|
|
793
|
+
|
|
794
|
+
/**
|
|
795
|
+
* Refreshes the data from the data view in the grid.
|
|
796
|
+
*
|
|
797
|
+
* @method
|
|
798
|
+
* @memberof Grid
|
|
799
|
+
*/
|
|
800
|
+
|
|
801
|
+
Graph.prototype.refresh = function () {
|
|
802
|
+
var self = this;
|
|
803
|
+
|
|
804
|
+
self.view.clearSourceData();
|
|
805
|
+
};
|
|
806
|
+
|
|
807
|
+
// #redraw {{{2
|
|
808
|
+
|
|
809
|
+
Graph.prototype.redraw = function () {
|
|
810
|
+
var self = this;
|
|
811
|
+
|
|
812
|
+
if (self.renderer != null) {
|
|
813
|
+
self.renderer.destroy();
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
self.prefs.prime(function () {
|
|
817
|
+
self.checkGraphConfig();
|
|
818
|
+
var ctor = getProp(self.opts, 'renderer') && GRAPH_RENDERER_REGISTRY.isSet(self.opts.renderer)
|
|
819
|
+
? GRAPH_RENDERER_REGISTRY.get(self.opts.renderer)
|
|
820
|
+
: GRAPH_RENDERER_REGISTRY.get('google');
|
|
821
|
+
self.renderer = new ctor(self, self.ui.graph, self.view, self.opts);
|
|
822
|
+
self.renderer.on('draw', function (config) {
|
|
823
|
+
self.syncDrawnGraphConfigWithUi(config);
|
|
824
|
+
});
|
|
825
|
+
self._setGraphTypeOptions();
|
|
826
|
+
self.drawFromConfig();
|
|
827
|
+
}, {
|
|
828
|
+
who: self
|
|
829
|
+
});
|
|
830
|
+
};
|
|
831
|
+
|
|
832
|
+
// #hide {{{2
|
|
833
|
+
|
|
834
|
+
/**
|
|
835
|
+
* Hide the grid.
|
|
836
|
+
*
|
|
837
|
+
* @method
|
|
838
|
+
* @memberof Grid
|
|
839
|
+
*/
|
|
840
|
+
|
|
841
|
+
Graph.prototype.hide = function () {
|
|
842
|
+
var self = this;
|
|
843
|
+
|
|
844
|
+
self.ui.content.hide({
|
|
845
|
+
duration: 0,
|
|
846
|
+
done: function () {
|
|
847
|
+
if (self.opts.title) {
|
|
848
|
+
self.ui.showHideButton.removeClass('open');
|
|
849
|
+
self.ui.showHideButton.children('svg.wcdv_icon').removeClass('wcdv_icon_rotate_180');
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
});
|
|
853
|
+
};
|
|
854
|
+
|
|
855
|
+
// #show {{{2
|
|
856
|
+
|
|
857
|
+
/**
|
|
858
|
+
* Make the grid visible. If the grid has not been "run" yet, it will be done now.
|
|
859
|
+
*
|
|
860
|
+
* @param {object} [opts]
|
|
861
|
+
*
|
|
862
|
+
* @param {boolean} [opts.redraw=true]
|
|
863
|
+
* If true, automatically redraw the grid after it has been shown. This is almost always what you
|
|
864
|
+
* want, unless you intend to manually call `redraw()` or `refresh()` immediately after showing it.
|
|
865
|
+
*/
|
|
866
|
+
|
|
867
|
+
Graph.prototype.show = function (opts) {
|
|
868
|
+
var self = this;
|
|
869
|
+
|
|
870
|
+
opts = deepDefaults(opts, {
|
|
871
|
+
redraw: true
|
|
872
|
+
});
|
|
873
|
+
|
|
874
|
+
self.ui.content.show({
|
|
875
|
+
duration: 0,
|
|
876
|
+
done: function () {
|
|
877
|
+
if (self.opts.title) {
|
|
878
|
+
self.ui.showHideButton.addClass('open');
|
|
879
|
+
self.ui.showHideButton.children('svg.wcdv_icon').addClass('wcdv_icon_rotate_180');
|
|
880
|
+
}
|
|
881
|
+
if (!self.hasRun && opts.redraw) {
|
|
882
|
+
self.hasRun = true;
|
|
883
|
+
self.redraw();
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
});
|
|
887
|
+
};
|
|
888
|
+
|
|
889
|
+
// #toggle {{{2
|
|
890
|
+
|
|
891
|
+
/**
|
|
892
|
+
* Toggle graph visibility.
|
|
893
|
+
*/
|
|
894
|
+
|
|
895
|
+
Graph.prototype.toggle = function () {
|
|
896
|
+
var self = this;
|
|
897
|
+
|
|
898
|
+
if (self.ui.content.css('display') === 'none') {
|
|
899
|
+
self.show();
|
|
900
|
+
}
|
|
901
|
+
else {
|
|
902
|
+
self.hide();
|
|
903
|
+
}
|
|
904
|
+
};
|
|
905
|
+
|
|
906
|
+
// #isVisible {{{2
|
|
907
|
+
|
|
908
|
+
/**
|
|
909
|
+
* Determine if the graph is currently visible.
|
|
910
|
+
*
|
|
911
|
+
* @returns {boolean}
|
|
912
|
+
* True if the graph is currently visible, false if it is not.
|
|
913
|
+
*/
|
|
914
|
+
|
|
915
|
+
Graph.prototype.isVisible = function () {
|
|
916
|
+
var self = this;
|
|
917
|
+
|
|
918
|
+
return self.ui.content.css('display') !== 'none';
|
|
919
|
+
};
|
|
920
|
+
|
|
921
|
+
// #_setSpinner {{{2
|
|
922
|
+
|
|
923
|
+
/**
|
|
924
|
+
* Set the type of the spinner icon.
|
|
925
|
+
*
|
|
926
|
+
* @param {string} what
|
|
927
|
+
* The kind of spinner icon to show. Must be one of: loading, not-loaded, working.
|
|
928
|
+
*/
|
|
929
|
+
|
|
930
|
+
Graph.prototype._setSpinner = function (what) {
|
|
931
|
+
var self = this;
|
|
932
|
+
|
|
933
|
+
switch (what) {
|
|
934
|
+
case 'loading':
|
|
935
|
+
self.ui.spinner.html(icon('refresh-cw', ['wcdv_icon_spin'], 'Loading...'));
|
|
936
|
+
break;
|
|
937
|
+
case 'not-loaded':
|
|
938
|
+
self.ui.spinner.html(icon('ban', null, 'Not Loaded'));
|
|
939
|
+
break;
|
|
940
|
+
case 'working':
|
|
941
|
+
self.ui.spinner.html(icon('loader-circle', ['wcdv_icon_spin'], 'Working...'));
|
|
942
|
+
break;
|
|
943
|
+
}
|
|
944
|
+
};
|
|
945
|
+
|
|
946
|
+
// #_showSpinner {{{2
|
|
947
|
+
|
|
948
|
+
/**
|
|
949
|
+
* Show the spinner icon.
|
|
950
|
+
*/
|
|
951
|
+
|
|
952
|
+
Graph.prototype._showSpinner = function () {
|
|
953
|
+
var self = this;
|
|
954
|
+
|
|
955
|
+
self.ui.spinner.show();
|
|
956
|
+
};
|
|
957
|
+
|
|
958
|
+
// #_hideSpinner {{{2
|
|
959
|
+
|
|
960
|
+
/**
|
|
961
|
+
* Hide the spinner icon.
|
|
962
|
+
*/
|
|
963
|
+
|
|
964
|
+
Graph.prototype._hideSpinner = function () {
|
|
965
|
+
var self = this;
|
|
966
|
+
|
|
967
|
+
self.ui.spinner.hide();
|
|
968
|
+
};
|
|
969
|
+
|
|
970
|
+
// #setUserConfig {{{2
|
|
971
|
+
|
|
972
|
+
Graph.prototype.setUserConfig = function (config) {
|
|
973
|
+
var self = this;
|
|
974
|
+
|
|
975
|
+
self.userConfig = config;
|
|
976
|
+
|
|
977
|
+
// When the constructor binds to prefs, this method can be called before the renderer is created.
|
|
978
|
+
// That's not a big deal, just don't do anything here if that's the case.
|
|
979
|
+
|
|
980
|
+
if (self.renderer != null) {
|
|
981
|
+
self.renderer.draw(self.devConfig, self.userConfig);
|
|
982
|
+
}
|
|
983
|
+
};
|
|
984
|
+
|
|
985
|
+
// GraphControl {{{1
|
|
986
|
+
|
|
987
|
+
var GraphControl = makeSubclass('GraphControl', Object, function () {
|
|
988
|
+
var self = this;
|
|
989
|
+
|
|
990
|
+
self.ui = {};
|
|
991
|
+
});
|
|
992
|
+
|
|
993
|
+
// #draw {{{2
|
|
994
|
+
|
|
995
|
+
GraphControl.prototype.draw = function () {
|
|
996
|
+
var self = this;
|
|
997
|
+
|
|
998
|
+
self.view.on('getTypeInfo', function (typeInfo) {
|
|
999
|
+
var fields = [];
|
|
1000
|
+
|
|
1001
|
+
_.each(determineColumns(null, null, typeInfo), function (fieldName) {
|
|
1002
|
+
var text = getProp(self.colConfig, fieldName, 'displayText') || fieldName;
|
|
1003
|
+
fields.push({ fieldName: fieldName, displayText: text });
|
|
1004
|
+
});
|
|
1005
|
+
|
|
1006
|
+
// Graph Type Dropdown
|
|
1007
|
+
|
|
1008
|
+
self.ui.graphType = jQuery('<select>');
|
|
1009
|
+
|
|
1010
|
+
if (getProp(self.renderer, 'prototype', 'graphTypes')) {
|
|
1011
|
+
self.renderer.prototype.graphTypes.each(function (gt) {
|
|
1012
|
+
self.ui.graphType.append(jQuery('<option>', { 'value': gt.value }).text(gt.name));
|
|
1013
|
+
});
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
self.ui.root.append(jQuery('<div>').append(self.ui.graphType));
|
|
1017
|
+
|
|
1018
|
+
// Plain Data Configuration
|
|
1019
|
+
|
|
1020
|
+
self.ui.plainCheckbox = jQuery('<input>', { 'type': 'checkbox', 'checked': 'checked' })
|
|
1021
|
+
.on('change', function () {
|
|
1022
|
+
if (self.ui.plainCheckbox.prop('checked')) {
|
|
1023
|
+
self.ui.plainConfig.show();
|
|
1024
|
+
}
|
|
1025
|
+
else {
|
|
1026
|
+
self.ui.plainConfig.hide();
|
|
1027
|
+
}
|
|
1028
|
+
});
|
|
1029
|
+
|
|
1030
|
+
self.ui.root.append(
|
|
1031
|
+
jQuery('<span>', { 'class': 'wcdv_title' })
|
|
1032
|
+
.append(self.ui.plainCheckbox)
|
|
1033
|
+
.append('Plain Data')
|
|
1034
|
+
);
|
|
1035
|
+
|
|
1036
|
+
self.ui.plainCategoryField = jQuery('<select>')
|
|
1037
|
+
.on('change', function () {
|
|
1038
|
+
self.defn.whenPlain.categoryField = self.ui.plainCategoryField.val();
|
|
1039
|
+
});
|
|
1040
|
+
self.ui.plainValueField = jQuery('<select>')
|
|
1041
|
+
.on('change', function () {
|
|
1042
|
+
self.defn.whenPlain.valueField = self.ui.plainValueField.val();
|
|
1043
|
+
});
|
|
1044
|
+
|
|
1045
|
+
_.each(fields, function (f) {
|
|
1046
|
+
self.ui.plainCategoryField.append(
|
|
1047
|
+
jQuery('<option>', { 'value': f.fieldName }).text(f.displayText)
|
|
1048
|
+
);
|
|
1049
|
+
self.ui.plainValueField.append(
|
|
1050
|
+
jQuery('<option>', { 'value': f.fieldName }).text(f.displayText)
|
|
1051
|
+
);
|
|
1052
|
+
});
|
|
1053
|
+
|
|
1054
|
+
self.ui.plainConfig = jQuery('<div>')
|
|
1055
|
+
.append(
|
|
1056
|
+
jQuery('<div>')
|
|
1057
|
+
.append('Category Field: ')
|
|
1058
|
+
.append(self.ui.plainCategoryField)
|
|
1059
|
+
)
|
|
1060
|
+
.append(
|
|
1061
|
+
jQuery('<div>')
|
|
1062
|
+
.append('Value Field: ')
|
|
1063
|
+
.append(self.ui.plainValueField)
|
|
1064
|
+
)
|
|
1065
|
+
.appendTo(self.ui.root);
|
|
1066
|
+
|
|
1067
|
+
// Group Data Configuration
|
|
1068
|
+
|
|
1069
|
+
|
|
1070
|
+
|
|
1071
|
+
// Pivot Data Configuration
|
|
1072
|
+
}, { limit: 1 });
|
|
1073
|
+
};
|
|
1074
|
+
|
|
1075
|
+
// Exports {{{1
|
|
1076
|
+
|
|
1077
|
+
export {
|
|
1078
|
+
Graph,
|
|
1079
|
+
};
|