skybox 0.2.3
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.
- data/README.md +68 -0
- data/lib/skybox.rb +5 -0
- data/lib/skybox/app.rb +142 -0
- data/lib/skybox/static/css/bootstrap-responsive.css +1109 -0
- data/lib/skybox/static/css/bootstrap.css +6158 -0
- data/lib/skybox/static/css/skybox.css +8 -0
- data/lib/skybox/static/css/skybox.explore.css +40 -0
- data/lib/skybox/static/favicon.ico +0 -0
- data/lib/skybox/static/img/glyphicons-halflings-white.png +0 -0
- data/lib/skybox/static/img/glyphicons-halflings.png +0 -0
- data/lib/skybox/static/img/loading.gif +0 -0
- data/lib/skybox/static/js/bootstrap.js +2268 -0
- data/lib/skybox/static/js/d3.flow.js +320 -0
- data/lib/skybox/static/js/d3.v3.js +7806 -0
- data/lib/skybox/static/js/humanize.js +275 -0
- data/lib/skybox/static/js/jquery-1.9.1.js +9597 -0
- data/lib/skybox/static/js/skybox.explore.js +330 -0
- data/lib/skybox/static/js/skybox.js +289 -0
- data/lib/skybox/version.rb +3 -0
- data/lib/skybox/views/admin/actions/index.erb +22 -0
- data/lib/skybox/views/admin/properties/index.erb +26 -0
- data/lib/skybox/views/explore.erb +9 -0
- data/lib/skybox/views/index.erb +24 -0
- data/lib/skybox/views/layout.erb +62 -0
- metadata +251 -0
@@ -0,0 +1,330 @@
|
|
1
|
+
(function() {
|
2
|
+
var flow = d3.flow();
|
3
|
+
var darkColors = ["#000", "#1f77b4"];
|
4
|
+
var colors = d3.scale.category20();
|
5
|
+
var root = {id:"enter", depth:0};
|
6
|
+
var nodes = [root], links = [];
|
7
|
+
var chart = null, svg = null, g = {};
|
8
|
+
|
9
|
+
var highlightDepth = -1;
|
10
|
+
var easingType = "quad-in";
|
11
|
+
|
12
|
+
// Start with a simple session start query.
|
13
|
+
var query = {
|
14
|
+
selections:[{
|
15
|
+
fields: [{aggregationType:"count"}],
|
16
|
+
groups: [{expression:"action_id"}],
|
17
|
+
conditions: [{type:"on", action:"enter"}]
|
18
|
+
}],
|
19
|
+
sessionIdleTime:7200
|
20
|
+
};
|
21
|
+
|
22
|
+
|
23
|
+
//----------------------------------------------------------------------------
|
24
|
+
//
|
25
|
+
// Initialization
|
26
|
+
//
|
27
|
+
//----------------------------------------------------------------------------
|
28
|
+
|
29
|
+
// Initializes the view.
|
30
|
+
function init() {
|
31
|
+
// Setup the SVG container for the visualization.
|
32
|
+
chart = $("#chart")[0];
|
33
|
+
svg = d3.select(chart).append("svg");
|
34
|
+
g = {
|
35
|
+
link:svg.append("g"),
|
36
|
+
node:svg.append("g")
|
37
|
+
};
|
38
|
+
|
39
|
+
// Add listeners.
|
40
|
+
$(window).resize(window_onResize)
|
41
|
+
$(document).on("click", document_onClick);
|
42
|
+
|
43
|
+
// Update!
|
44
|
+
update();
|
45
|
+
load(root);
|
46
|
+
}
|
47
|
+
|
48
|
+
|
49
|
+
//----------------------------------------------------------------------------
|
50
|
+
//
|
51
|
+
// Methods
|
52
|
+
//
|
53
|
+
//----------------------------------------------------------------------------
|
54
|
+
|
55
|
+
//--------------------------------------
|
56
|
+
// Layout
|
57
|
+
//--------------------------------------
|
58
|
+
|
59
|
+
// Updates the view.
|
60
|
+
function update(options) {
|
61
|
+
// Update the dimensions of the visualization.
|
62
|
+
flow.width($(chart).width());
|
63
|
+
flow.height(window.innerHeight - $(chart).offset().top - 40);
|
64
|
+
|
65
|
+
// Layout data.
|
66
|
+
flow.layout(nodes, links, options);
|
67
|
+
|
68
|
+
// Update SVG container.
|
69
|
+
var margin = flow.margin();
|
70
|
+
svg.attr("width", flow.width()).attr("height", flow.height());
|
71
|
+
g.link.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
|
72
|
+
g.node.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
|
73
|
+
|
74
|
+
// Layout links.
|
75
|
+
g.link.selectAll(".link").data(links, function(d) { return d.key; })
|
76
|
+
.call(function(link) {
|
77
|
+
var enter = link.enter(), exit = link.exit();
|
78
|
+
link.transition().ease(easingType)
|
79
|
+
.call(flow.links.position)
|
80
|
+
.attr("stroke-dashoffset", 0);
|
81
|
+
|
82
|
+
enter.append("path").attr("class", "link")
|
83
|
+
.call(flow.links.position)
|
84
|
+
.each(function(path) {
|
85
|
+
var totalLength = this.getTotalLength();
|
86
|
+
d3.select(this)
|
87
|
+
.attr("stroke-dasharray", totalLength + " " + totalLength)
|
88
|
+
.attr("stroke-dashoffset", totalLength)
|
89
|
+
.transition().ease(easingType)
|
90
|
+
.delay(function(d) { return 250 + (d.target.index*100)})
|
91
|
+
.duration(250)
|
92
|
+
.attr("stroke-dashoffset", 0)
|
93
|
+
.each("end", function(d) { d3.select(this).attr("stroke-dasharray", "none") });
|
94
|
+
});
|
95
|
+
|
96
|
+
exit.remove();
|
97
|
+
});
|
98
|
+
|
99
|
+
// Layout nodes.
|
100
|
+
var node = g.node.selectAll(".node").data(nodes, function(d) { return d.key })
|
101
|
+
.call(function(node) {
|
102
|
+
var enter = node.enter(), exit = node.exit();
|
103
|
+
|
104
|
+
// Update selection.
|
105
|
+
node.selectAll("rect")
|
106
|
+
.transition().ease(easingType)
|
107
|
+
.call(flow.nodes.position)
|
108
|
+
.style("fill", nodeFillColor)
|
109
|
+
.style("fill-opacity", 1)
|
110
|
+
.style("stroke-opacity", 1);
|
111
|
+
node.selectAll(".title")
|
112
|
+
.transition().ease(easingType)
|
113
|
+
.call(flow.nodes.title.position)
|
114
|
+
.attr("display", function(d) { return (d.height > 20 ? "block" : "none"); })
|
115
|
+
.style("fill-opacity", 1);
|
116
|
+
|
117
|
+
// Enter selection.
|
118
|
+
var g = enter.append("g").attr("class", "node")
|
119
|
+
.on("click", node_onClick)
|
120
|
+
.on("mouseover", node_onMouseOver);
|
121
|
+
var rect = g.append("rect");
|
122
|
+
rect.style("fill", nodeFillColor)
|
123
|
+
.style("fill-opacity", function(d) { return (d.depth == 0 ? 1 : 0); })
|
124
|
+
.style("stroke", function(d) { return d3.rgb(nodeFillColor(d)).darker(2); })
|
125
|
+
.style("stroke-opacity", function(d) { return (d.depth == 0 ? 1 : 0); })
|
126
|
+
.call(flow.nodes.position)
|
127
|
+
.transition().ease(easingType).delay(nodeDelay)
|
128
|
+
.style("fill-opacity", 1)
|
129
|
+
.style("stroke-opacity", 1)
|
130
|
+
var title = g.append("text")
|
131
|
+
.attr("class", "title")
|
132
|
+
.attr("dy", "1em")
|
133
|
+
.style("fill", nodeTextColor)
|
134
|
+
.style("fill-opacity", function(d) { return (d.depth == 0 ? 1 : 0); })
|
135
|
+
.attr("display", function(d) { return (d.height > 20 ? "block" : "none"); })
|
136
|
+
.call(flow.nodes.title.position)
|
137
|
+
.text(nodeTitle)
|
138
|
+
.transition().ease(easingType).delay(nodeDelay)
|
139
|
+
.style("fill-opacity", 1)
|
140
|
+
|
141
|
+
// Exit selection.
|
142
|
+
exit.remove();
|
143
|
+
});
|
144
|
+
|
145
|
+
// Update the query text.
|
146
|
+
updateQueryText();
|
147
|
+
}
|
148
|
+
|
149
|
+
/**
|
150
|
+
* Updates the query text.
|
151
|
+
*/
|
152
|
+
function updateQueryText() {
|
153
|
+
var html = "Query: " + skybox.query.html(query);
|
154
|
+
$("#query-text").html(html);
|
155
|
+
|
156
|
+
// Setup the selection popover.
|
157
|
+
$("#query-text .selection").popover({
|
158
|
+
html:true, placement:"bottom",
|
159
|
+
title:"Update Selection",
|
160
|
+
content:
|
161
|
+
'<form>' +
|
162
|
+
' <div class="control-group">' +
|
163
|
+
' <label class="control-label" for="selectionFields">Fields</label>' +
|
164
|
+
' <div class="controls">' +
|
165
|
+
' <span class="selection-fields uneditable-input">count()</span>' +
|
166
|
+
' </div>' +
|
167
|
+
' </div>' +
|
168
|
+
' <div class="control-group">' +
|
169
|
+
' <label class="control-label" for="selectionGroupBy">Group By</label>' +
|
170
|
+
' <div class="controls">' +
|
171
|
+
' <select class="selection-group">' +
|
172
|
+
[{name:"action_id"}].concat(skybox.properties()).map(function(i) {return '<option>' + i.name + '</option>'}).join("") +
|
173
|
+
' </select>' +
|
174
|
+
' </div>' +
|
175
|
+
' </div>' +
|
176
|
+
'</form>'
|
177
|
+
});
|
178
|
+
}
|
179
|
+
|
180
|
+
function nodeDelay(node) {
|
181
|
+
return 500 + (node.index*100);
|
182
|
+
}
|
183
|
+
|
184
|
+
function nodeFillColor(node) {
|
185
|
+
var color;
|
186
|
+
switch(node.id) {
|
187
|
+
case "exit": color = "#000"; break;
|
188
|
+
case "other": color = "lightgray"; break;
|
189
|
+
default: color = colors(node.id);
|
190
|
+
}
|
191
|
+
return color;
|
192
|
+
}
|
193
|
+
|
194
|
+
function nodeTextColor(node) {
|
195
|
+
var fillColor = nodeFillColor(node);
|
196
|
+
return (darkColors.indexOf(fillColor) != -1 ? "#f2f2f2" : "#000");
|
197
|
+
}
|
198
|
+
|
199
|
+
function nodeTitle(node) {
|
200
|
+
var action = skybox.actions.find(node.id);
|
201
|
+
return Humanize.truncate((action ? action.name : ""), 16);
|
202
|
+
}
|
203
|
+
|
204
|
+
|
205
|
+
//--------------------------------------
|
206
|
+
// Data
|
207
|
+
//--------------------------------------
|
208
|
+
|
209
|
+
/**
|
210
|
+
* Runs the current query against the server, sets the returned data and
|
211
|
+
* updates the UI.
|
212
|
+
*
|
213
|
+
* @param {Object} source The node that caused the load to occur.
|
214
|
+
*/
|
215
|
+
function load(source) {
|
216
|
+
$(".loading").show();
|
217
|
+
|
218
|
+
// Execute the query.
|
219
|
+
var xhr = $.post("/query", JSON.stringify({table:skybox.table(), query:query}), function(data) {
|
220
|
+
var level = {nodes:[], links:[]}
|
221
|
+
skybox.data.normalize(data, level.nodes, level.links, {limit:6});
|
222
|
+
|
223
|
+
level.links.forEach(function(link) { link.source = source; });
|
224
|
+
level.nodes.forEach(function(node) { node.depth = source.depth + 1; });
|
225
|
+
if(source.value == undefined) source.value = d3.sum(level.nodes, function(d) { return d.value; })
|
226
|
+
|
227
|
+
nodes = nodes.concat(level.nodes);
|
228
|
+
links = links.concat(level.links);
|
229
|
+
|
230
|
+
update();
|
231
|
+
})
|
232
|
+
// Notify the user if the query fails for some reason.
|
233
|
+
.fail(function() {
|
234
|
+
alert("Unable to load query data.");
|
235
|
+
})
|
236
|
+
.always(function() {
|
237
|
+
$(".loading").hide();
|
238
|
+
});
|
239
|
+
|
240
|
+
return xhr;
|
241
|
+
}
|
242
|
+
|
243
|
+
|
244
|
+
//----------------------------------------------------------------------------
|
245
|
+
//
|
246
|
+
// Events
|
247
|
+
//
|
248
|
+
//----------------------------------------------------------------------------
|
249
|
+
|
250
|
+
//--------------------------------------
|
251
|
+
// Node
|
252
|
+
//--------------------------------------
|
253
|
+
|
254
|
+
/**
|
255
|
+
* Appends an 'After' condition to the query for a node and re-queries.
|
256
|
+
*/
|
257
|
+
function node_onClick(node) {
|
258
|
+
if(node.id == "exit" || node.id == "other") return;
|
259
|
+
|
260
|
+
// Remove nodes higher than current node's depth.
|
261
|
+
nodes = nodes.filter(function(n) { return n.depth <= node.depth; });
|
262
|
+
update({suppressRescaling:true});
|
263
|
+
|
264
|
+
// Clear out conditions after this depth and append new condition.
|
265
|
+
if(node.depth > 0) {
|
266
|
+
query.selections[0].conditions = query.selections[0].conditions.slice(0, node.depth);
|
267
|
+
query.selections[0].conditions.push({type:"after", action:{id:parseInt(node.id)}, within:{quantity:1, unit:"step"}});
|
268
|
+
}
|
269
|
+
else {
|
270
|
+
query.selections[0].conditions = query.selections[0].conditions.slice(0, 1);
|
271
|
+
}
|
272
|
+
load(node)
|
273
|
+
}
|
274
|
+
|
275
|
+
/**
|
276
|
+
* Shows a tooltip on mouse over.
|
277
|
+
*/
|
278
|
+
function node_onMouseOver(node) {
|
279
|
+
var action = skybox.actions.find(node.id);
|
280
|
+
$(this).tooltip({
|
281
|
+
html: true, container:"body",
|
282
|
+
placement: (node.depth == 0 ? "right" : "left"),
|
283
|
+
title:
|
284
|
+
Humanize.truncate(action.name, 30) + "<br/>" +
|
285
|
+
"Count: " + Humanize.intcomma(node.value)
|
286
|
+
});
|
287
|
+
$(this).tooltip("show");
|
288
|
+
}
|
289
|
+
|
290
|
+
|
291
|
+
//--------------------------------------
|
292
|
+
// Window
|
293
|
+
//--------------------------------------
|
294
|
+
|
295
|
+
/**
|
296
|
+
* Updates the view whenever the window is resized.
|
297
|
+
*/
|
298
|
+
function window_onResize() {
|
299
|
+
update();
|
300
|
+
}
|
301
|
+
|
302
|
+
/**
|
303
|
+
* Removes all popovers on click.
|
304
|
+
*/
|
305
|
+
function document_onClick() {
|
306
|
+
if($(event.target).attr("rel") == "popover") return;
|
307
|
+
if($(event.target).parents(".popover").length == 0) {
|
308
|
+
$("*").popover("hide");
|
309
|
+
}
|
310
|
+
}
|
311
|
+
|
312
|
+
|
313
|
+
//----------------------------------------------------------------------------
|
314
|
+
//
|
315
|
+
// Public Interface
|
316
|
+
//
|
317
|
+
//----------------------------------------------------------------------------
|
318
|
+
|
319
|
+
skybox.explore = {
|
320
|
+
init:init,
|
321
|
+
update:update,
|
322
|
+
};
|
323
|
+
|
324
|
+
})();
|
325
|
+
|
326
|
+
|
327
|
+
// Initialize the Explore view once the page has loaded.
|
328
|
+
skybox.ready(function() {
|
329
|
+
skybox.explore.init();
|
330
|
+
});
|
@@ -0,0 +1,289 @@
|
|
1
|
+
(function() {
|
2
|
+
var table = null;
|
3
|
+
var actions = [];
|
4
|
+
var properties = [];
|
5
|
+
|
6
|
+
//----------------------------------------------------------------------------
|
7
|
+
//
|
8
|
+
// Methods
|
9
|
+
//
|
10
|
+
//----------------------------------------------------------------------------
|
11
|
+
|
12
|
+
//--------------------------------------
|
13
|
+
// Core
|
14
|
+
//--------------------------------------
|
15
|
+
|
16
|
+
/**
|
17
|
+
* Attaches a listener for when all required data is loaded.
|
18
|
+
*
|
19
|
+
* @param {Function} callback The function to execute when the view is ready.
|
20
|
+
*/
|
21
|
+
function ready(callback) {
|
22
|
+
// Clears dependencies and checks for completion.
|
23
|
+
var dependencies = ["actions", "properties"];
|
24
|
+
var removedep = function(dependency) {
|
25
|
+
dependencies = dependencies.filter(function(i) { return i != dependency; });
|
26
|
+
if(dependencies.length == 0) {
|
27
|
+
callback();
|
28
|
+
}
|
29
|
+
};
|
30
|
+
|
31
|
+
// Initialize and wait for dependencies.
|
32
|
+
$(document).ready(function() {
|
33
|
+
actions_load().done(function() { removedep("actions"); });
|
34
|
+
properties_load().done(function() { removedep("properties"); });
|
35
|
+
});
|
36
|
+
}
|
37
|
+
|
38
|
+
|
39
|
+
//--------------------------------------
|
40
|
+
// Table
|
41
|
+
//--------------------------------------
|
42
|
+
|
43
|
+
/**
|
44
|
+
* Sets or retrieves the current table.
|
45
|
+
*/
|
46
|
+
function table_get(_) {
|
47
|
+
if (!arguments.length) return table;
|
48
|
+
table = _;
|
49
|
+
}
|
50
|
+
|
51
|
+
|
52
|
+
//--------------------------------------
|
53
|
+
// Actions
|
54
|
+
//--------------------------------------
|
55
|
+
|
56
|
+
/**
|
57
|
+
* Sets or retrieves the actions.
|
58
|
+
*/
|
59
|
+
function actions_get(_) {
|
60
|
+
if (!arguments.length) return actions;
|
61
|
+
actions = _;
|
62
|
+
}
|
63
|
+
|
64
|
+
/**
|
65
|
+
* Retrieves an action object by id.
|
66
|
+
*/
|
67
|
+
function actions_find(id) {
|
68
|
+
for(var i=0; i<actions.length; i++) {
|
69
|
+
if(actions[i].id == id || actions[i].name == id) {
|
70
|
+
return actions[i];
|
71
|
+
}
|
72
|
+
}
|
73
|
+
}
|
74
|
+
|
75
|
+
/**
|
76
|
+
* Loads action data.
|
77
|
+
*
|
78
|
+
* @return {Object} The XHR used to load the action data.
|
79
|
+
*/
|
80
|
+
function actions_load() {
|
81
|
+
var xhr = $.getJSON("/actions", {table:table}, function(data) {
|
82
|
+
actions = [
|
83
|
+
{id:"enter", name:"Session Start"},
|
84
|
+
{id:"exit", name:"Session End"},
|
85
|
+
{id:"other", name:"Other"}
|
86
|
+
].concat(data);
|
87
|
+
})
|
88
|
+
.fail(function() {
|
89
|
+
alert("Unable to load action data");
|
90
|
+
});
|
91
|
+
return xhr;
|
92
|
+
}
|
93
|
+
|
94
|
+
|
95
|
+
//--------------------------------------
|
96
|
+
// Properties
|
97
|
+
//--------------------------------------
|
98
|
+
|
99
|
+
/**
|
100
|
+
* Sets or retrieves the properties.
|
101
|
+
*/
|
102
|
+
function properties_get(_) {
|
103
|
+
if (!arguments.length) return properties;
|
104
|
+
properties = _;
|
105
|
+
}
|
106
|
+
|
107
|
+
/**
|
108
|
+
* Retrieves a property object by id.
|
109
|
+
*/
|
110
|
+
function properties_find(id) {
|
111
|
+
for(var i=0; i<properties.length; i++) {
|
112
|
+
if(properties[i].id == id || properties[i].name == id) {
|
113
|
+
return properties[i];
|
114
|
+
}
|
115
|
+
}
|
116
|
+
}
|
117
|
+
|
118
|
+
/**
|
119
|
+
* Loads property data.
|
120
|
+
*
|
121
|
+
* @return {Object} The XHR used to load the property data.
|
122
|
+
*/
|
123
|
+
function properties_load() {
|
124
|
+
var xhr = $.getJSON("/properties", {table:table}, function(data) {
|
125
|
+
properties = data;
|
126
|
+
})
|
127
|
+
.fail(function() {
|
128
|
+
alert("Unable to load property data");
|
129
|
+
});
|
130
|
+
return xhr;
|
131
|
+
}
|
132
|
+
|
133
|
+
|
134
|
+
//--------------------------------------
|
135
|
+
// Data
|
136
|
+
//--------------------------------------
|
137
|
+
|
138
|
+
/**
|
139
|
+
* Converts the data object returned from a Sky query and converts it into a
|
140
|
+
* collection of nodes and links.
|
141
|
+
*
|
142
|
+
* @param {Object} data The data returned from a Sky query.
|
143
|
+
* @param {Array} nodes An array to append nodes to.
|
144
|
+
* @param {Array} links An array to append links to.
|
145
|
+
*/
|
146
|
+
function data_normalize(data, nodes, links, options) {
|
147
|
+
if(!data) return;
|
148
|
+
if(!options) options = {};
|
149
|
+
|
150
|
+
// Generate nodes from keys.
|
151
|
+
for(var key in data) {
|
152
|
+
nodes.push({id:key, value:data[key].count});
|
153
|
+
}
|
154
|
+
nodes = nodes.sort(function(a,b) { return b.value-a.value;});
|
155
|
+
|
156
|
+
// Limit the number of items if specified.
|
157
|
+
if(!isNaN(options.limit) && nodes.length > options.limit) {
|
158
|
+
var others = nodes.splice(options.limit, nodes.length-options.limit);
|
159
|
+
var other = {id:"other", value:d3.sum(others, function(d) { return d.value; })};
|
160
|
+
nodes.push(other);
|
161
|
+
}
|
162
|
+
|
163
|
+
// Generate links from nodes.
|
164
|
+
for(i=0; i<nodes.length; i++) {
|
165
|
+
var node = nodes[i];
|
166
|
+
links.push({target:node, value:node.value});
|
167
|
+
}
|
168
|
+
}
|
169
|
+
|
170
|
+
|
171
|
+
//--------------------------------------
|
172
|
+
// Query
|
173
|
+
//--------------------------------------
|
174
|
+
|
175
|
+
/**
|
176
|
+
* Converts a query into a human readable HTML string.
|
177
|
+
*
|
178
|
+
* @param {Object} query The query.
|
179
|
+
*
|
180
|
+
* @return {String} The HTML string.
|
181
|
+
*/
|
182
|
+
function query_html(query) {
|
183
|
+
return (query.selections.length > 0 ? query_selection_html(query.selections[0]) : "");
|
184
|
+
}
|
185
|
+
|
186
|
+
/**
|
187
|
+
* Converts a selection into a human readable HTML string.
|
188
|
+
*
|
189
|
+
* @param {Object} selection The selection object.
|
190
|
+
*
|
191
|
+
* @return {String} The HTML string.
|
192
|
+
*/
|
193
|
+
function query_selection_html(selection) {
|
194
|
+
var fields_html = query_selection_fields_html(selection);
|
195
|
+
var conditions_html = query_selection_conditions_html(selection);
|
196
|
+
|
197
|
+
var html = [];
|
198
|
+
if(fields_html) html.push(fields_html);
|
199
|
+
if(conditions_html) html.push(conditions_html);
|
200
|
+
return html.join(" ") + ".";
|
201
|
+
}
|
202
|
+
|
203
|
+
/**
|
204
|
+
* Converts the fields/groups of a selection into a human readable HTML string.
|
205
|
+
*
|
206
|
+
* @param {Object} selection The selection object.
|
207
|
+
*
|
208
|
+
* @return {String} The HTML string.
|
209
|
+
*/
|
210
|
+
function query_selection_fields_html(selection) {
|
211
|
+
// Generate the field/group section.
|
212
|
+
var html = "Find the "
|
213
|
+
html += '<span rel="popover" class="selection">';
|
214
|
+
switch(selection.fields[0].aggregationType) {
|
215
|
+
case "count": html += "number of ";
|
216
|
+
}
|
217
|
+
html += (selection.groups[0].expression == "action_id" ? "actions performed" : selection.groups[0].expression);
|
218
|
+
html += "</span>"
|
219
|
+
|
220
|
+
return html;
|
221
|
+
}
|
222
|
+
|
223
|
+
/**
|
224
|
+
* Converts the conditions of a selection into a human readable HTML string.
|
225
|
+
*
|
226
|
+
* @param {Object} selection The selection object.
|
227
|
+
*
|
228
|
+
* @return {String} The HTML string.
|
229
|
+
*/
|
230
|
+
function query_selection_conditions_html(selection) {
|
231
|
+
if(!selection.conditions || selection.conditions.length == 0) return null;
|
232
|
+
|
233
|
+
var htmls = [];
|
234
|
+
for(i=0; i<selection.conditions.length; i++) {
|
235
|
+
var condition = selection.conditions[i];
|
236
|
+
|
237
|
+
|
238
|
+
var html = '<span class="condition" data-condition-index="' + i + '">';
|
239
|
+
html += condition.type + " ";
|
240
|
+
if(condition.action == "enter") {
|
241
|
+
html += "session start";
|
242
|
+
}
|
243
|
+
else {
|
244
|
+
var action = actions_find(condition.action.id);
|
245
|
+
html += "<em>" + (action ? "'" + action.name + "'" : "<action>") + "</em>";
|
246
|
+
}
|
247
|
+
html += "</span>"
|
248
|
+
htmls.push(html);
|
249
|
+
}
|
250
|
+
switch(selection.fields[0].aggregationType) {
|
251
|
+
case "count": html += "The number of ";
|
252
|
+
}
|
253
|
+
html += (selection.groups[0].expression == "action_id" ? "actions performed" : selection.groups[0].expression);
|
254
|
+
html += "</span>"
|
255
|
+
|
256
|
+
return htmls.join(" and ");
|
257
|
+
}
|
258
|
+
|
259
|
+
|
260
|
+
|
261
|
+
|
262
|
+
//----------------------------------------------------------------------------
|
263
|
+
//
|
264
|
+
// Public Interface
|
265
|
+
//
|
266
|
+
//----------------------------------------------------------------------------
|
267
|
+
|
268
|
+
skybox = {
|
269
|
+
ready:ready,
|
270
|
+
table:table_get,
|
271
|
+
query:{
|
272
|
+
html:query_html
|
273
|
+
},
|
274
|
+
data:{
|
275
|
+
normalize:data_normalize
|
276
|
+
}
|
277
|
+
};
|
278
|
+
|
279
|
+
// Actions namespace.
|
280
|
+
skybox.actions = actions_get,
|
281
|
+
skybox.actions.find = actions_find;
|
282
|
+
skybox.actions.load = actions_load;
|
283
|
+
|
284
|
+
// Properties namespace.
|
285
|
+
skybox.properties = properties_get,
|
286
|
+
skybox.properties.find = properties_find;
|
287
|
+
skybox.properties.load = properties_load;
|
288
|
+
|
289
|
+
})();
|