rails-data-explorer 0.0.1
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/.gitignore +10 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +52 -0
- data/Rakefile +18 -0
- data/lib/rails-data-explorer.rb +44 -0
- data/lib/rails-data-explorer/action_view_extension.rb +12 -0
- data/lib/rails-data-explorer/active_record_extension.rb +14 -0
- data/lib/rails-data-explorer/chart.rb +52 -0
- data/lib/rails-data-explorer/chart/box_plot.rb +79 -0
- data/lib/rails-data-explorer/chart/box_plot_group.rb +109 -0
- data/lib/rails-data-explorer/chart/contingency_table.rb +189 -0
- data/lib/rails-data-explorer/chart/descriptive_statistics_table.rb +22 -0
- data/lib/rails-data-explorer/chart/descriptive_statistics_table_group.rb +0 -0
- data/lib/rails-data-explorer/chart/histogram_categorical.rb +73 -0
- data/lib/rails-data-explorer/chart/histogram_quantitative.rb +73 -0
- data/lib/rails-data-explorer/chart/histogram_temporal.rb +78 -0
- data/lib/rails-data-explorer/chart/multi_dimensional_charts.rb +1 -0
- data/lib/rails-data-explorer/chart/parallel_coordinates.rb +89 -0
- data/lib/rails-data-explorer/chart/parallel_set.rb +65 -0
- data/lib/rails-data-explorer/chart/pie_chart.rb +67 -0
- data/lib/rails-data-explorer/chart/scatterplot.rb +120 -0
- data/lib/rails-data-explorer/chart/scatterplot_matrix.rb +1 -0
- data/lib/rails-data-explorer/chart/stacked_bar_chart_categorical_percent.rb +120 -0
- data/lib/rails-data-explorer/data_series.rb +115 -0
- data/lib/rails-data-explorer/data_set.rb +127 -0
- data/lib/rails-data-explorer/data_type.rb +34 -0
- data/lib/rails-data-explorer/data_type/categorical.rb +117 -0
- data/lib/rails-data-explorer/data_type/geo.rb +1 -0
- data/lib/rails-data-explorer/data_type/quantitative.rb +109 -0
- data/lib/rails-data-explorer/data_type/quantitative/decimal.rb +13 -0
- data/lib/rails-data-explorer/data_type/quantitative/integer.rb +13 -0
- data/lib/rails-data-explorer/data_type/quantitative/temporal.rb +62 -0
- data/lib/rails-data-explorer/engine.rb +24 -0
- data/lib/rails-data-explorer/exploration.rb +89 -0
- data/lib/rails-data-explorer/statistics/pearsons_chi_squared_independence_test.rb +75 -0
- data/lib/rails-data-explorer/statistics/rng_category.rb +37 -0
- data/lib/rails-data-explorer/statistics/rng_gaussian.rb +24 -0
- data/lib/rails-data-explorer/statistics/rng_power_law.rb +21 -0
- data/lib/rails-data-explorer/utils/color_scale.rb +33 -0
- data/lib/rails-data-explorer/utils/data_binner.rb +8 -0
- data/lib/rails-data-explorer/utils/data_encoder.rb +2 -0
- data/lib/rails-data-explorer/utils/data_quantizer.rb +2 -0
- data/lib/rails-data-explorer/utils/value_formatter.rb +41 -0
- data/rails-data-explorer.gemspec +30 -0
- data/vendor/assets/javascripts/d3.boxplot.js +302 -0
- data/vendor/assets/javascripts/d3.parcoords.js +585 -0
- data/vendor/assets/javascripts/d3.parsets.js +663 -0
- data/vendor/assets/javascripts/d3.v3.js +9294 -0
- data/vendor/assets/javascripts/nv.d3.js +14369 -0
- data/vendor/assets/javascripts/rails-data-explorer.js +19 -0
- data/vendor/assets/stylesheets/bootstrap-theme.css +346 -0
- data/vendor/assets/stylesheets/bootstrap.css +1727 -0
- data/vendor/assets/stylesheets/d3.boxplot.css +20 -0
- data/vendor/assets/stylesheets/d3.parcoords.css +34 -0
- data/vendor/assets/stylesheets/d3.parsets.css +34 -0
- data/vendor/assets/stylesheets/nv.d3.css +769 -0
- data/vendor/assets/stylesheets/rails-data-explorer.css +21 -0
- data/vendor/assets/stylesheets/rde-default-style.css +42 -0
- metadata +250 -0
@@ -0,0 +1,585 @@
|
|
1
|
+
d3.parcoords = function(config) {
|
2
|
+
var __ = {
|
3
|
+
data: [],
|
4
|
+
dimensions: [],
|
5
|
+
dimensionTitles: {},
|
6
|
+
types: {},
|
7
|
+
brushed: false,
|
8
|
+
mode: "default",
|
9
|
+
rate: 20,
|
10
|
+
width: 600,
|
11
|
+
height: 300,
|
12
|
+
margin: { top: 24, right: 0, bottom: 12, left: 0 },
|
13
|
+
color: "#069",
|
14
|
+
composite: "source-over",
|
15
|
+
alpha: 0.7
|
16
|
+
};
|
17
|
+
|
18
|
+
extend(__, config);
|
19
|
+
var pc = function(selection) {
|
20
|
+
selection = pc.selection = d3.select(selection);
|
21
|
+
|
22
|
+
__.width = selection[0][0].clientWidth;
|
23
|
+
__.height = selection[0][0].clientHeight;
|
24
|
+
|
25
|
+
// canvas data layers
|
26
|
+
["shadows", "marks", "foreground", "highlight"].forEach(function(layer) {
|
27
|
+
canvas[layer] = selection
|
28
|
+
.append("canvas")
|
29
|
+
.attr("class", layer)[0][0];
|
30
|
+
ctx[layer] = canvas[layer].getContext("2d");
|
31
|
+
});
|
32
|
+
|
33
|
+
// svg tick and brush layers
|
34
|
+
pc.svg = selection
|
35
|
+
.append("svg")
|
36
|
+
.attr("style", "width: " + __.width + "px; height: " + __.height + "px")
|
37
|
+
.append("svg:g")
|
38
|
+
.attr("transform", "translate(" + __.margin.left + "," + __.margin.top + ")");
|
39
|
+
|
40
|
+
return pc;
|
41
|
+
};
|
42
|
+
var events = d3.dispatch.apply(this,["render", "resize", "highlight", "brush"].concat(d3.keys(__))),
|
43
|
+
w = function() { return __.width - __.margin.right - __.margin.left; },
|
44
|
+
h = function() { return __.height - __.margin.top - __.margin.bottom },
|
45
|
+
flags = {
|
46
|
+
brushable: false,
|
47
|
+
reorderable: false,
|
48
|
+
axes: false,
|
49
|
+
interactive: false,
|
50
|
+
shadows: false,
|
51
|
+
debug: false
|
52
|
+
},
|
53
|
+
xscale = d3.scale.ordinal(),
|
54
|
+
yscale = {},
|
55
|
+
dragging = {},
|
56
|
+
line = d3.svg.line(),
|
57
|
+
axis = d3.svg.axis().orient("left").ticks(5),
|
58
|
+
g, // groups for axes, brushes
|
59
|
+
ctx = {},
|
60
|
+
canvas = {};
|
61
|
+
|
62
|
+
// side effects for setters
|
63
|
+
var side_effects = d3.dispatch.apply(this,d3.keys(__))
|
64
|
+
.on("composite", function(d) { ctx.foreground.globalCompositeOperation = d.value; })
|
65
|
+
.on("alpha", function(d) { ctx.foreground.globalAlpha = d.value; })
|
66
|
+
.on("width", function(d) { pc.resize(); })
|
67
|
+
.on("height", function(d) { pc.resize(); })
|
68
|
+
.on("margin", function(d) { pc.resize(); })
|
69
|
+
.on("rate", function(d) { rqueue.rate(d.value); })
|
70
|
+
.on("data", function(d) {
|
71
|
+
if (flags.shadows) paths(__.data, ctx.shadows);
|
72
|
+
})
|
73
|
+
.on("dimensions", function(d) {
|
74
|
+
xscale.domain(__.dimensions);
|
75
|
+
if (flags.interactive) pc.render().updateAxes();
|
76
|
+
});
|
77
|
+
|
78
|
+
// expose the state of the chart
|
79
|
+
pc.state = __;
|
80
|
+
pc.flags = flags;
|
81
|
+
|
82
|
+
// create getter/setters
|
83
|
+
getset(pc, __, events);
|
84
|
+
|
85
|
+
// expose events
|
86
|
+
d3.rebind(pc, events, "on");
|
87
|
+
|
88
|
+
// tick formatting
|
89
|
+
d3.rebind(pc, axis, "ticks", "orient", "tickValues", "tickSubdivide", "tickSize", "tickPadding", "tickFormat");
|
90
|
+
|
91
|
+
// getter/setter with event firing
|
92
|
+
function getset(obj,state,events) {
|
93
|
+
d3.keys(state).forEach(function(key) {
|
94
|
+
obj[key] = function(x) {
|
95
|
+
if (!arguments.length) return state[key];
|
96
|
+
var old = state[key];
|
97
|
+
state[key] = x;
|
98
|
+
side_effects[key].call(pc,{"value": x, "previous": old});
|
99
|
+
events[key].call(pc,{"value": x, "previous": old});
|
100
|
+
return obj;
|
101
|
+
};
|
102
|
+
});
|
103
|
+
};
|
104
|
+
|
105
|
+
function extend(target, source) {
|
106
|
+
for (key in source) {
|
107
|
+
target[key] = source[key];
|
108
|
+
}
|
109
|
+
return target;
|
110
|
+
};
|
111
|
+
pc.autoscale = function() {
|
112
|
+
// yscale
|
113
|
+
var defaultScales = {
|
114
|
+
"date": function(k) {
|
115
|
+
return d3.time.scale()
|
116
|
+
.domain(d3.extent(__.data, function(d) {
|
117
|
+
return d[k] ? d[k] : null;
|
118
|
+
}))
|
119
|
+
.nice()
|
120
|
+
.range([h()+1, 1])
|
121
|
+
},
|
122
|
+
"number": function(k) {
|
123
|
+
return d3.scale.linear()
|
124
|
+
.domain(d3.extent(__.data, function(d) { return +d[k]; }))
|
125
|
+
.range([h()+1, 1])
|
126
|
+
},
|
127
|
+
"string": function(k) {
|
128
|
+
return d3.scale.ordinal()
|
129
|
+
.domain(__.data.map(function(p) { return p[k]; }))
|
130
|
+
.rangePoints([h()+1, 1])
|
131
|
+
}
|
132
|
+
};
|
133
|
+
|
134
|
+
__.dimensions.forEach(function(k) {
|
135
|
+
yscale[k] = defaultScales[__.types[k]](k);
|
136
|
+
});
|
137
|
+
|
138
|
+
// hack to remove ordinal dimensions with many values
|
139
|
+
pc.dimensions(pc.dimensions().filter(function(p,i) {
|
140
|
+
var uniques = yscale[p].domain().length;
|
141
|
+
if (__.types[p] == "string" && (uniques > 60 || uniques < 2)) {
|
142
|
+
return false;
|
143
|
+
}
|
144
|
+
return true;
|
145
|
+
}));
|
146
|
+
|
147
|
+
// xscale
|
148
|
+
xscale.rangePoints([0, w()], 1);
|
149
|
+
|
150
|
+
// canvas sizes
|
151
|
+
pc.selection.selectAll("canvas")
|
152
|
+
.style("margin-top", __.margin.top + "px")
|
153
|
+
.style("margin-left", __.margin.left + "px")
|
154
|
+
.attr("width", w()+2)
|
155
|
+
.attr("height", h()+2)
|
156
|
+
|
157
|
+
// default styles, needs to be set when canvas width changes
|
158
|
+
ctx.foreground.strokeStyle = __.color;
|
159
|
+
ctx.foreground.lineWidth = 1.4;
|
160
|
+
ctx.foreground.globalCompositeOperation = __.composite;
|
161
|
+
ctx.foreground.globalAlpha = __.alpha;
|
162
|
+
ctx.highlight.lineWidth = 3;
|
163
|
+
ctx.shadows.strokeStyle = "#dadada";
|
164
|
+
|
165
|
+
return this;
|
166
|
+
};
|
167
|
+
pc.detectDimensions = function() {
|
168
|
+
pc.types(pc.detectDimensionTypes(__.data));
|
169
|
+
pc.dimensions(d3.keys(pc.types()));
|
170
|
+
return this;
|
171
|
+
};
|
172
|
+
|
173
|
+
// a better "typeof" from this post: http://stackoverflow.com/questions/7390426/better-way-to-get-type-of-a-javascript-variable
|
174
|
+
pc.toType = function(v) {
|
175
|
+
return ({}).toString.call(v).match(/\s([a-zA-Z]+)/)[1].toLowerCase()
|
176
|
+
};
|
177
|
+
|
178
|
+
// try to coerce to number before returning type
|
179
|
+
pc.toTypeCoerceNumbers = function(v) {
|
180
|
+
if ((parseFloat(v) == v) && (v != null)) return "number";
|
181
|
+
return pc.toType(v);
|
182
|
+
};
|
183
|
+
|
184
|
+
// attempt to determine types of each dimension based on first row of data
|
185
|
+
pc.detectDimensionTypes = function(data) {
|
186
|
+
var types = {}
|
187
|
+
d3.keys(data[0])
|
188
|
+
.forEach(function(col) {
|
189
|
+
types[col] = pc.toTypeCoerceNumbers(data[0][col]);
|
190
|
+
});
|
191
|
+
return types;
|
192
|
+
};
|
193
|
+
pc.render = function() {
|
194
|
+
// try to autodetect dimensions and create scales
|
195
|
+
if (!__.dimensions.length) pc.detectDimensions();
|
196
|
+
if (!(__.dimensions[0] in yscale)) pc.autoscale();
|
197
|
+
|
198
|
+
pc.render[__.mode]();
|
199
|
+
|
200
|
+
events.render.call(this);
|
201
|
+
return this;
|
202
|
+
};
|
203
|
+
|
204
|
+
pc.render.default = function() {
|
205
|
+
pc.clear('foreground');
|
206
|
+
if (__.brushed) {
|
207
|
+
__.brushed.forEach(path_foreground);
|
208
|
+
} else {
|
209
|
+
__.data.forEach(path_foreground);
|
210
|
+
}
|
211
|
+
};
|
212
|
+
|
213
|
+
var rqueue = d3.renderQueue(path_foreground)
|
214
|
+
.rate(50)
|
215
|
+
.clear(function() { pc.clear('foreground'); });
|
216
|
+
|
217
|
+
pc.render.queue = function() {
|
218
|
+
if (__.brushed) {
|
219
|
+
rqueue(__.brushed);
|
220
|
+
} else {
|
221
|
+
rqueue(__.data);
|
222
|
+
}
|
223
|
+
};
|
224
|
+
pc.shadows = function() {
|
225
|
+
flags.shadows = true;
|
226
|
+
if (__.data.length > 0) paths(__.data, ctx.shadows);
|
227
|
+
return this;
|
228
|
+
};
|
229
|
+
|
230
|
+
// draw little dots on the axis line where data intersects
|
231
|
+
pc.axisDots = function() {
|
232
|
+
var ctx = pc.ctx.marks;
|
233
|
+
ctx.globalAlpha = d3.min([1/Math.pow(data.length, 1/2), 1]);
|
234
|
+
__.data.forEach(function(d) {
|
235
|
+
__.dimensions.map(function(p,i) {
|
236
|
+
ctx.fillRect(position(p)-0.75,yscale[p](d[p])-0.75,1.5,1.5);
|
237
|
+
});
|
238
|
+
});
|
239
|
+
return this;
|
240
|
+
};
|
241
|
+
|
242
|
+
// draw single polyline
|
243
|
+
function color_path(d, ctx) {
|
244
|
+
ctx.strokeStyle = d3.functor(__.color)(d);
|
245
|
+
ctx.beginPath();
|
246
|
+
__.dimensions.map(function(p,i) {
|
247
|
+
if (i == 0) {
|
248
|
+
ctx.moveTo(position(p),yscale[p](d[p]));
|
249
|
+
} else {
|
250
|
+
ctx.lineTo(position(p),yscale[p](d[p]));
|
251
|
+
}
|
252
|
+
});
|
253
|
+
ctx.stroke();
|
254
|
+
};
|
255
|
+
|
256
|
+
// draw many polylines of the same color
|
257
|
+
function paths(data, ctx) {
|
258
|
+
ctx.clearRect(-1,-1,w()+2,h()+2);
|
259
|
+
ctx.beginPath();
|
260
|
+
data.forEach(function(d) {
|
261
|
+
__.dimensions.map(function(p,i) {
|
262
|
+
if (i == 0) {
|
263
|
+
ctx.moveTo(position(p),yscale[p](d[p]));
|
264
|
+
} else {
|
265
|
+
ctx.lineTo(position(p),yscale[p](d[p]));
|
266
|
+
}
|
267
|
+
});
|
268
|
+
});
|
269
|
+
ctx.stroke();
|
270
|
+
};
|
271
|
+
|
272
|
+
function path_foreground(d) {
|
273
|
+
return color_path(d, ctx.foreground);
|
274
|
+
};
|
275
|
+
|
276
|
+
function path_highlight(d) {
|
277
|
+
return color_path(d, ctx.highlight);
|
278
|
+
};
|
279
|
+
pc.clear = function(layer) {
|
280
|
+
ctx[layer].clearRect(0,0,w()+2,h()+2);
|
281
|
+
return this;
|
282
|
+
};
|
283
|
+
pc.createAxes = function() {
|
284
|
+
if (g) pc.removeAxes();
|
285
|
+
|
286
|
+
// Add a group element for each dimension.
|
287
|
+
g = pc.svg.selectAll(".dimension")
|
288
|
+
.data(__.dimensions, function(d) { return d; })
|
289
|
+
.enter().append("svg:g")
|
290
|
+
.attr("class", "dimension")
|
291
|
+
.attr("transform", function(d) { return "translate(" + xscale(d) + ")"; })
|
292
|
+
|
293
|
+
// Add an axis and title.
|
294
|
+
g.append("svg:g")
|
295
|
+
.attr("class", "axis")
|
296
|
+
.attr("transform", "translate(0,0)")
|
297
|
+
.each(function(d) { d3.select(this).call(axis.scale(yscale[d])); })
|
298
|
+
.append("svg:text")
|
299
|
+
.attr({
|
300
|
+
"text-anchor": "middle",
|
301
|
+
"y": 0,
|
302
|
+
"transform": "translate(0,-12)",
|
303
|
+
"x": 0,
|
304
|
+
"class": "label"
|
305
|
+
})
|
306
|
+
.text(function(d) {
|
307
|
+
return d in __.dimensionTitles ? __.dimensionTitles[d] : d; // dimension display names
|
308
|
+
})
|
309
|
+
|
310
|
+
flags.axes= true;
|
311
|
+
return this;
|
312
|
+
};
|
313
|
+
|
314
|
+
pc.removeAxes = function() {
|
315
|
+
g.remove();
|
316
|
+
return this;
|
317
|
+
};
|
318
|
+
|
319
|
+
pc.updateAxes = function() {
|
320
|
+
var g_data = pc.svg.selectAll(".dimension")
|
321
|
+
.data(__.dimensions, function(d) { return d; })
|
322
|
+
|
323
|
+
g_data.enter().append("svg:g")
|
324
|
+
.attr("class", "dimension")
|
325
|
+
.attr("transform", function(p) { return "translate(" + position(p) + ")"; })
|
326
|
+
.style("opacity", 0)
|
327
|
+
.append("svg:g")
|
328
|
+
.attr("class", "axis")
|
329
|
+
.attr("transform", "translate(0,0)")
|
330
|
+
.each(function(d) { d3.select(this).call(axis.scale(yscale[d])); })
|
331
|
+
.append("svg:text")
|
332
|
+
.attr({
|
333
|
+
"text-anchor": "middle",
|
334
|
+
"y": 0,
|
335
|
+
"transform": "translate(0,-12)",
|
336
|
+
"x": 0,
|
337
|
+
"class": "label"
|
338
|
+
})
|
339
|
+
.text(String);
|
340
|
+
|
341
|
+
g_data.exit().remove();
|
342
|
+
|
343
|
+
g = pc.svg.selectAll(".dimension");
|
344
|
+
|
345
|
+
g.transition().duration(1100)
|
346
|
+
.attr("transform", function(p) { return "translate(" + position(p) + ")"; })
|
347
|
+
.style("opacity", 1)
|
348
|
+
if (flags.shadows) paths(__.data, ctx.shadows);
|
349
|
+
return this;
|
350
|
+
};
|
351
|
+
|
352
|
+
pc.brushable = function() {
|
353
|
+
if (!g) pc.createAxes();
|
354
|
+
|
355
|
+
// Add and store a brush for each axis.
|
356
|
+
g.append("svg:g")
|
357
|
+
.attr("class", "brush")
|
358
|
+
.each(function(d) {
|
359
|
+
d3.select(this).call(
|
360
|
+
yscale[d].brush = d3.svg.brush()
|
361
|
+
.y(yscale[d])
|
362
|
+
.on("brushstart", function() {
|
363
|
+
d3.event.sourceEvent.stopPropagation();
|
364
|
+
})
|
365
|
+
.on("brush", pc.brush)
|
366
|
+
);
|
367
|
+
})
|
368
|
+
.selectAll("rect")
|
369
|
+
.style("visibility", null)
|
370
|
+
.attr("x", -15)
|
371
|
+
.attr("width", 30)
|
372
|
+
flags.brushable = true;
|
373
|
+
return this;
|
374
|
+
};
|
375
|
+
|
376
|
+
// Jason Davies, http://bl.ocks.org/1341281
|
377
|
+
pc.reorderable = function() {
|
378
|
+
if (!g) pc.createAxes();
|
379
|
+
|
380
|
+
g.style("cursor", "move")
|
381
|
+
.call(d3.behavior.drag()
|
382
|
+
.on("dragstart", function(d) {
|
383
|
+
dragging[d] = this.__origin__ = xscale(d);
|
384
|
+
})
|
385
|
+
.on("drag", function(d) {
|
386
|
+
dragging[d] = Math.min(w(), Math.max(0, this.__origin__ += d3.event.dx));
|
387
|
+
__.dimensions.sort(function(a, b) { return position(a) - position(b); });
|
388
|
+
xscale.domain(__.dimensions);
|
389
|
+
pc.render();
|
390
|
+
g.attr("transform", function(d) { return "translate(" + position(d) + ")"; })
|
391
|
+
})
|
392
|
+
.on("dragend", function(d) {
|
393
|
+
delete this.__origin__;
|
394
|
+
delete dragging[d];
|
395
|
+
d3.select(this).transition().attr("transform", "translate(" + xscale(d) + ")");
|
396
|
+
pc.render();
|
397
|
+
}));
|
398
|
+
flags.reorderable = true;
|
399
|
+
return this;
|
400
|
+
};
|
401
|
+
|
402
|
+
// pairs of adjacent dimensions
|
403
|
+
pc.adjacent_pairs = function(arr) {
|
404
|
+
var ret = [];
|
405
|
+
for (var i = 0; i < arr.length-1; i++) {
|
406
|
+
ret.push([arr[i],arr[i+1]]);
|
407
|
+
};
|
408
|
+
return ret;
|
409
|
+
};
|
410
|
+
pc.interactive = function() {
|
411
|
+
flags.interactive = true;
|
412
|
+
return this;
|
413
|
+
};
|
414
|
+
|
415
|
+
// Get data within brushes
|
416
|
+
pc.brush = function() {
|
417
|
+
__.brushed = selected();
|
418
|
+
events.brush.call(pc,__.brushed);
|
419
|
+
pc.render();
|
420
|
+
};
|
421
|
+
|
422
|
+
// expose a few objects
|
423
|
+
pc.xscale = xscale;
|
424
|
+
pc.yscale = yscale;
|
425
|
+
pc.ctx = ctx;
|
426
|
+
pc.canvas = canvas;
|
427
|
+
pc.g = function() { return g; };
|
428
|
+
|
429
|
+
pc.brushReset = function(dimension) {
|
430
|
+
if (g) {
|
431
|
+
g.selectAll('.brush')
|
432
|
+
.each(function(d) {
|
433
|
+
d3.select(this).call(
|
434
|
+
yscale[d].brush.clear()
|
435
|
+
);
|
436
|
+
})
|
437
|
+
pc.brush();
|
438
|
+
}
|
439
|
+
return this;
|
440
|
+
};
|
441
|
+
|
442
|
+
// rescale for height, width and margins
|
443
|
+
// TODO currently assumes chart is brushable, and destroys old brushes
|
444
|
+
pc.resize = function() {
|
445
|
+
// selection size
|
446
|
+
pc.selection.select("svg")
|
447
|
+
.attr("width", __.width)
|
448
|
+
.attr("height", __.height)
|
449
|
+
pc.svg.attr("transform", "translate(" + __.margin.left + "," + __.margin.top + ")");
|
450
|
+
|
451
|
+
// scales
|
452
|
+
pc.autoscale();
|
453
|
+
|
454
|
+
// axes, destroys old brushes. the current brush state should pass through in the future
|
455
|
+
if (g) pc.createAxes().brushable();
|
456
|
+
|
457
|
+
events.resize.call(this, {width: __.width, height: __.height, margin: __.margin});
|
458
|
+
return this;
|
459
|
+
};
|
460
|
+
|
461
|
+
// highlight an array of data
|
462
|
+
pc.highlight = function(data) {
|
463
|
+
pc.clear("highlight");
|
464
|
+
d3.select(canvas.foreground).classed("faded", true);
|
465
|
+
data.forEach(path_highlight);
|
466
|
+
events.highlight.call(this,data);
|
467
|
+
return this;
|
468
|
+
};
|
469
|
+
|
470
|
+
// clear highlighting
|
471
|
+
pc.unhighlight = function(data) {
|
472
|
+
pc.clear("highlight");
|
473
|
+
d3.select(canvas.foreground).classed("faded", false);
|
474
|
+
return this;
|
475
|
+
};
|
476
|
+
|
477
|
+
// calculate 2d intersection of line a->b with line c->d
|
478
|
+
// points are objects with x and y properties
|
479
|
+
pc.intersection = function(a, b, c, d) {
|
480
|
+
return {
|
481
|
+
x: ((a.x * b.y - a.y * b.x) * (c.x - d.x) - (a.x - b.x) * (c.x * d.y - c.y * d.x)) / ((a.x - b.x) * (c.y - d.y) - (a.y - b.y) * (c.x - d.x)),
|
482
|
+
y: ((a.x * b.y - a.y * b.x) * (c.y - d.y) - (a.y - b.y) * (c.x * d.y - c.y * d.x)) / ((a.x - b.x) * (c.y - d.y) - (a.y - b.y) * (c.x - d.x))
|
483
|
+
};
|
484
|
+
};
|
485
|
+
|
486
|
+
function is_brushed(p) {
|
487
|
+
return !yscale[p].brush.empty();
|
488
|
+
};
|
489
|
+
|
490
|
+
// data within extents
|
491
|
+
function selected() {
|
492
|
+
var actives = __.dimensions.filter(is_brushed),
|
493
|
+
extents = actives.map(function(p) { return yscale[p].brush.extent(); });
|
494
|
+
|
495
|
+
// test if within range
|
496
|
+
var within = {
|
497
|
+
"date": function(d,p,dimension) {
|
498
|
+
return extents[dimension][0] <= d[p] && d[p] <= extents[dimension][1]
|
499
|
+
},
|
500
|
+
"number": function(d,p,dimension) {
|
501
|
+
return extents[dimension][0] <= d[p] && d[p] <= extents[dimension][1]
|
502
|
+
},
|
503
|
+
"string": function(d,p,dimension) {
|
504
|
+
return extents[dimension][0] <= yscale[p](d[p]) && yscale[p](d[p]) <= extents[dimension][1]
|
505
|
+
}
|
506
|
+
};
|
507
|
+
|
508
|
+
return __.data
|
509
|
+
.filter(function(d) {
|
510
|
+
return actives.every(function(p, dimension) {
|
511
|
+
return within[__.types[p]](d,p,dimension);
|
512
|
+
});
|
513
|
+
});
|
514
|
+
};
|
515
|
+
|
516
|
+
function position(d) {
|
517
|
+
var v = dragging[d];
|
518
|
+
return v == null ? xscale(d) : v;
|
519
|
+
}
|
520
|
+
pc.toString = function() { return "Parallel Coordinates: " + __.dimensions.length + " dimensions (" + d3.keys(__.data[0]).length + " total) , " + __.data.length + " rows"; };
|
521
|
+
|
522
|
+
pc.version = "0.2.2";
|
523
|
+
|
524
|
+
return pc;
|
525
|
+
};
|
526
|
+
|
527
|
+
d3.renderQueue = (function(func) {
|
528
|
+
var _queue = [], // data to be rendered
|
529
|
+
_rate = 10, // number of calls per frame
|
530
|
+
_clear = function() {}, // clearing function
|
531
|
+
_i = 0; // current iteration
|
532
|
+
|
533
|
+
var rq = function(data) {
|
534
|
+
if (data) rq.data(data);
|
535
|
+
rq.invalidate();
|
536
|
+
_clear();
|
537
|
+
rq.render();
|
538
|
+
};
|
539
|
+
|
540
|
+
rq.render = function() {
|
541
|
+
_i = 0;
|
542
|
+
var valid = true;
|
543
|
+
rq.invalidate = function() { valid = false; };
|
544
|
+
|
545
|
+
function doFrame() {
|
546
|
+
if (!valid) return true;
|
547
|
+
if (_i > _queue.length) return true;
|
548
|
+
var chunk = _queue.slice(_i,_i+_rate);
|
549
|
+
_i += _rate;
|
550
|
+
chunk.map(func);
|
551
|
+
}
|
552
|
+
|
553
|
+
d3.timer(doFrame);
|
554
|
+
};
|
555
|
+
|
556
|
+
rq.data = function(data) {
|
557
|
+
rq.invalidate();
|
558
|
+
_queue = data.slice(0);
|
559
|
+
return rq;
|
560
|
+
};
|
561
|
+
|
562
|
+
rq.rate = function(value) {
|
563
|
+
if (!arguments.length) return _rate;
|
564
|
+
_rate = value;
|
565
|
+
return rq;
|
566
|
+
};
|
567
|
+
|
568
|
+
rq.remaining = function() {
|
569
|
+
return _queue.length - _i;
|
570
|
+
};
|
571
|
+
|
572
|
+
// clear the canvas
|
573
|
+
rq.clear = function(func) {
|
574
|
+
if (!arguments.length) {
|
575
|
+
_clear();
|
576
|
+
return rq;
|
577
|
+
}
|
578
|
+
_clear = func;
|
579
|
+
return rq;
|
580
|
+
};
|
581
|
+
|
582
|
+
rq.invalidate = function() {};
|
583
|
+
|
584
|
+
return rq;
|
585
|
+
});
|