kanaui 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +41 -0
- data/Rakefile +40 -0
- data/app/assets/images/back_disabled.png +0 -0
- data/app/assets/images/back_enabled.png +0 -0
- data/app/assets/images/back_enabled_hover.png +0 -0
- data/app/assets/images/favicon.ico +0 -0
- data/app/assets/images/forward_disabled.png +0 -0
- data/app/assets/images/forward_enabled.png +0 -0
- data/app/assets/images/forward_enabled_hover.png +0 -0
- data/app/assets/images/sort_asc.png +0 -0
- data/app/assets/images/sort_asc_disabled.png +0 -0
- data/app/assets/images/sort_both.png +0 -0
- data/app/assets/images/sort_desc.png +0 -0
- data/app/assets/images/sort_desc_disabled.png +0 -0
- data/app/assets/javascripts/application.js +19 -0
- data/app/assets/javascripts/kanaui/dashboard.js +131 -0
- data/app/assets/javascripts/kanaui/killbill.js +1114 -0
- data/app/assets/javascripts/kanaui/purl.js +271 -0
- data/app/assets/javascripts/kanaui/reports.dataTables.js +31 -0
- data/app/assets/javascripts/kanaui/reports.graphs.js +190 -0
- data/app/assets/javascripts/kanaui/reports.js +201 -0
- data/app/assets/javascripts/kanaui/reports.urls.js +44 -0
- data/app/assets/javascripts/kanaui/tests.js +2 -0
- data/app/assets/stylesheets/application.css +8 -0
- data/app/assets/stylesheets/bootstrap_and_overrides.css +7 -0
- data/app/assets/stylesheets/kanaui/dashboard.css +121 -0
- data/app/assets/stylesheets/kanaui/tests.css +4 -0
- data/app/controllers/kanaui/dashboard_controller.rb +59 -0
- data/app/controllers/kanaui/engine_controller.rb +18 -0
- data/app/helpers/kanaui/application_helper.rb +6 -0
- data/app/helpers/kanaui/dashboard_helper.rb +28 -0
- data/app/models/kanaui/dashboard.rb +5 -0
- data/app/views/kanaui/dashboard/index.html.erb +48 -0
- data/app/views/kanaui/layouts/kanaui_application.html.erb +43 -0
- data/config/locales/en.bootstrap.yml +18 -0
- data/config/routes.rb +10 -0
- data/lib/kanaui/engine.rb +7 -0
- data/lib/kanaui/version.rb +3 -0
- data/lib/kanaui.rb +22 -0
- data/lib/tasks/kanaui_tasks.rake +4 -0
- data/test/dummy/README.rdoc +261 -0
- data/test/dummy/Rakefile +7 -0
- data/test/dummy/app/controllers/application_controller.rb +3 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/config/application.rb +64 -0
- data/test/dummy/config/boot.rb +10 -0
- data/test/dummy/config/database.yml +25 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +39 -0
- data/test/dummy/config/environments/production.rb +67 -0
- data/test/dummy/config/environments/test.rb +37 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/inflections.rb +15 -0
- data/test/dummy/config/initializers/killbill_client.rb +4 -0
- data/test/dummy/config/initializers/mime_types.rb +5 -0
- data/test/dummy/config/initializers/secret_token.rb +7 -0
- data/test/dummy/config/initializers/session_store.rb +8 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +5 -0
- data/test/dummy/config/routes.rb +4 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/db/development.sqlite3 +0 -0
- data/test/dummy/db/production.sqlite3 +0 -0
- data/test/dummy/db/test.sqlite3 +0 -0
- data/test/dummy/log/test.log +6 -0
- data/test/dummy/public/404.html +26 -0
- data/test/dummy/public/422.html +26 -0
- data/test/dummy/public/500.html +25 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/script/rails +6 -0
- data/test/dummy/tmp/cache/assets/C49/370/sprockets%2Fe0912451e460967cb546e2ac00632667 +0 -0
- data/test/dummy/tmp/cache/assets/C59/3D0/sprockets%2F22311d65dd6f27979f0144f4e9603762 +0 -0
- data/test/dummy/tmp/cache/assets/C5C/180/sprockets%2F6b609032785fe05974b318057b808dc9 +0 -0
- data/test/dummy/tmp/cache/assets/C60/440/sprockets%2F9e401779c0877cf93949e930228032ac +0 -0
- data/test/dummy/tmp/cache/assets/C61/8F0/sprockets%2Fe8f725654ee48124c086d488379340e0 +0 -0
- data/test/dummy/tmp/cache/assets/C70/FA0/sprockets%2F43c86018a096a0a65a0905da2b709455 +0 -0
- data/test/dummy/tmp/cache/assets/C7A/180/sprockets%2F19b994259f9c7b6f2879718c3275f359 +0 -0
- data/test/dummy/tmp/cache/assets/C8A/AB0/sprockets%2Fdc2f837cd27313996699c80342b07e04 +0 -0
- data/test/dummy/tmp/cache/assets/C8C/2E0/sprockets%2F8ef8759ba58c509567a8444201c19b24 +0 -0
- data/test/dummy/tmp/cache/assets/C97/750/sprockets%2F34d94639228f869665db78f42ca3118e +0 -0
- data/test/dummy/tmp/cache/assets/CA2/C20/sprockets%2F1d186d79051934c85a0594a610a1ab6b +0 -0
- data/test/dummy/tmp/cache/assets/CB3/D00/sprockets%2F6e18e78b81412e49d335a04a68f16c35 +0 -0
- data/test/dummy/tmp/cache/assets/CBF/560/sprockets%2Fe944ecb8026957a176e29a392a069f46 +0 -0
- data/test/dummy/tmp/cache/assets/CC5/C70/sprockets%2Fc4dacfb400c37f046545be4501065313 +0 -0
- data/test/dummy/tmp/cache/assets/CCA/090/sprockets%2Fc04069b3046b34950b843e32bbdf2a60 +0 -0
- data/test/dummy/tmp/cache/assets/CCF/750/sprockets%2F0512cc5ddae07c2bf0861332668640e4 +0 -0
- data/test/dummy/tmp/cache/assets/CCF/FC0/sprockets%2F09d578e3a00293a0e60b65f01ed551c4 +0 -0
- data/test/dummy/tmp/cache/assets/CD0/E60/sprockets%2F34b252a436ea97340eb6a33353ec7c60 +0 -0
- data/test/dummy/tmp/cache/assets/CD9/960/sprockets%2F9faf733d3c943879081f001120abfc09 +0 -0
- data/test/dummy/tmp/cache/assets/CDA/100/sprockets%2F1421707c6cd2bf4619c218d15c5ed449 +0 -0
- data/test/dummy/tmp/cache/assets/CDD/750/sprockets%2F51c5d9b965a7a23e2703b0d8c74d1358 +0 -0
- data/test/dummy/tmp/cache/assets/CE3/750/sprockets%2Fc54b480c74716d0ff447cc13f9471f25 +0 -0
- data/test/dummy/tmp/cache/assets/CE6/EF0/sprockets%2F358f2a7450053d2e7e60d219787ede7a +0 -0
- data/test/dummy/tmp/cache/assets/CF1/600/sprockets%2F758e0f59cc7d377813b72e9f17109c2d +0 -0
- data/test/dummy/tmp/cache/assets/CF4/4C0/sprockets%2F66ae774f6007684bc3ce544d15a879f9 +0 -0
- data/test/dummy/tmp/cache/assets/CF4/910/sprockets%2F47246555fd8dde5f73d513149bd7d495 +0 -0
- data/test/dummy/tmp/cache/assets/CFB/570/sprockets%2F70a944fdac8557dc70819ec989604e95 +0 -0
- data/test/dummy/tmp/cache/assets/D04/E10/sprockets%2F32fb56262b67a8013f6d1fe3bc45263b +0 -0
- data/test/dummy/tmp/cache/assets/D0A/8E0/sprockets%2Faaccd4041294c01f9d52b59d90e61649 +0 -0
- data/test/dummy/tmp/cache/assets/D0D/B20/sprockets%2F83c8c6d067f200b94289a27ae217cd2d +0 -0
- data/test/dummy/tmp/cache/assets/D16/140/sprockets%2F71765207ccd3c89388ba290d1ead7e73 +0 -0
- data/test/dummy/tmp/cache/assets/D16/610/sprockets%2Fc45f9a1f74ae89810e305307c8c2c98a +0 -0
- data/test/dummy/tmp/cache/assets/D1A/1A0/sprockets%2Fa9e56834936cc1d4e033ed75459f7ac1 +0 -0
- data/test/dummy/tmp/cache/assets/D1D/880/sprockets%2Fe29808c67d12f86b83bb3f545e0685cc +0 -0
- data/test/dummy/tmp/cache/assets/D28/2F0/sprockets%2F0c216cfb363ad008c4dd27e21e286e20 +0 -0
- data/test/dummy/tmp/cache/assets/D29/350/sprockets%2Fa251294b0bc12651ee0dd16fb427ed08 +0 -0
- data/test/dummy/tmp/cache/assets/D2E/DD0/sprockets%2F4aab8120d7fdda3c3101795fc937044a +0 -0
- data/test/dummy/tmp/cache/assets/D33/E20/sprockets%2F01e77b5ab7c8f25d7e007e42d210e60f +0 -0
- data/test/dummy/tmp/cache/assets/D3A/5A0/sprockets%2F3205c58e5fc648bb07247dd6fc1aa058 +0 -0
- data/test/dummy/tmp/cache/assets/D3B/B20/sprockets%2Ff25927a9a796c888c93f9892db4ded57 +0 -0
- data/test/dummy/tmp/cache/assets/D3C/BA0/sprockets%2F499c3460b0df4ea435a25d1fa3776ae7 +0 -0
- data/test/dummy/tmp/cache/assets/D3D/FD0/sprockets%2F6cd2d6e10e5aa7649192213d87ae1dd8 +0 -0
- data/test/dummy/tmp/cache/assets/D40/880/sprockets%2Fa65183bd762da798d82ca16b2384ae3e +0 -0
- data/test/dummy/tmp/cache/assets/D4A/220/sprockets%2Fc3fa86c7916fd5cf3448a46c2701d48f +0 -0
- data/test/dummy/tmp/cache/assets/D4A/710/sprockets%2F19bdf938259f801b6bc26fda54907c3f +0 -0
- data/test/dummy/tmp/cache/assets/D50/880/sprockets%2F6491c413570f9b5ee67b43dae6fb988b +0 -0
- data/test/dummy/tmp/cache/assets/D5C/600/sprockets%2Fdd005cf1e2da44e307bfb30f6e711177 +0 -0
- data/test/dummy/tmp/cache/assets/D62/6B0/sprockets%2Feb40cf55d9fba5bb18801041e5a755b4 +0 -0
- data/test/dummy/tmp/cache/assets/D65/7F0/sprockets%2Fc431bdd5c16d69e6d48b025be24e07b3 +0 -0
- data/test/dummy/tmp/cache/assets/D6D/170/sprockets%2Fe7474ced980c0a511ab3ed94ca66507f +0 -0
- data/test/dummy/tmp/cache/assets/D6F/2F0/sprockets%2Ff83749b55e1a31b4bc71d8f10c9c8ed1 +0 -0
- data/test/dummy/tmp/cache/assets/D71/CA0/sprockets%2F2efce3b73b5a0834d61c30f3e88f943e +0 -0
- data/test/dummy/tmp/cache/assets/D71/D00/sprockets%2F8f431cf56efe254dc4ad2f39f1f05325 +0 -0
- data/test/dummy/tmp/cache/assets/D72/FC0/sprockets%2F0e88a2c64eef7d00ae16ec3d6587446b +0 -0
- data/test/dummy/tmp/cache/assets/D74/AA0/sprockets%2F2a2594863afdfb329cf44429c3f4cdb6 +0 -0
- data/test/dummy/tmp/cache/assets/D78/5F0/sprockets%2Fd779c87f1d25bc5e67e24be981ab005e +0 -0
- data/test/dummy/tmp/cache/assets/D7D/230/sprockets%2F0d9f8069a79511caa7b72d8f7e9ba3e6 +0 -0
- data/test/dummy/tmp/cache/assets/D7E/D40/sprockets%2Fbb1ee49d28b0f4826af433e66b99f68b +0 -0
- data/test/dummy/tmp/cache/assets/D7F/D40/sprockets%2F5df2d7d854a34efce561038973fea75e +0 -0
- data/test/dummy/tmp/cache/assets/D80/C00/sprockets%2F807575a6bca580997ea68b2849eeebab +0 -0
- data/test/dummy/tmp/cache/assets/D8D/640/sprockets%2F3cb906dcb26ce1bb8b148a9ec38151a0 +0 -0
- data/test/dummy/tmp/cache/assets/D8F/740/sprockets%2Fc28d091b6eec75f690312b1babac173e +0 -0
- data/test/dummy/tmp/cache/assets/D99/810/sprockets%2F40a77b8fdb5dbb4a8be79582930bac11 +0 -0
- data/test/dummy/tmp/cache/assets/D9B/590/sprockets%2F415dfd73bc9dcf11a3c443636a7cc85d +0 -0
- data/test/dummy/tmp/cache/assets/D9F/0E0/sprockets%2Fe225dd3abeea78368f1e094da4d6e038 +0 -0
- data/test/dummy/tmp/cache/assets/DA0/D50/sprockets%2Fbe08c987f77ac1cf011f2c295f64ae0e +0 -0
- data/test/dummy/tmp/cache/assets/DA0/DF0/sprockets%2F54a3fbb89812981e0ddd9f293ee0cca0 +0 -0
- data/test/dummy/tmp/cache/assets/DA2/9A0/sprockets%2Fc217dc63d5bb979ab458f9440b8aaeb5 +0 -0
- data/test/dummy/tmp/cache/assets/DAC/420/sprockets%2F8419ce35fe9f11d3ea2c9f8edb48a661 +0 -0
- data/test/dummy/tmp/cache/assets/DB0/FA0/sprockets%2Ff3e0e6737d4f66ef48581ed3f26cc2fc +0 -0
- data/test/dummy/tmp/cache/assets/DB7/B30/sprockets%2Fa35d57efd41de3d595e79db74d99a6a7 +0 -0
- data/test/dummy/tmp/cache/assets/DBD/520/sprockets%2F646b4ef551d2e6dbcafc30ec6a1a1921 +0 -0
- data/test/dummy/tmp/cache/assets/DC3/A40/sprockets%2Fec0ae4ff1ed6a72c0fc4ec564700195b +0 -0
- data/test/dummy/tmp/cache/assets/DCA/970/sprockets%2Fa5b14cb6e98a7b2f9dfdd413420d3be7 +0 -0
- data/test/dummy/tmp/cache/assets/DCD/570/sprockets%2Fdacdabcd4b599417317af696f0bc70f3 +0 -0
- data/test/dummy/tmp/cache/assets/DD0/630/sprockets%2F1a6f238c9a7e8609caef71fff2ad1a16 +0 -0
- data/test/dummy/tmp/cache/assets/DD4/F20/sprockets%2Fe280ef07c4f086afbc447e8a3df6e4e2 +0 -0
- data/test/dummy/tmp/cache/assets/DD5/460/sprockets%2F6670c88dab8f5cb0b36bd51caf58a87f +0 -0
- data/test/dummy/tmp/cache/assets/DD9/7A0/sprockets%2Fa913b7e4c6665cd4d75cd5e4efa46b8c +0 -0
- data/test/dummy/tmp/cache/assets/DE8/770/sprockets%2Fce3edfabfb12520c063ee21bd2ea1858 +0 -0
- data/test/dummy/tmp/cache/assets/DEA/910/sprockets%2F96c4857b4f859eef5c6bf113ecdb8ce9 +0 -0
- data/test/dummy/tmp/cache/assets/DEF/590/sprockets%2Fb8d8adfe3d233e16821faf0cbd03a09d +0 -0
- data/test/dummy/tmp/cache/assets/DF6/EA0/sprockets%2Fda2b70e5c0a5e5e5b2b8e4f3438aaf6f +0 -0
- data/test/dummy/tmp/cache/assets/E01/6A0/sprockets%2Feeec15d595b932dabdbb8f2fd66266b1 +0 -0
- data/test/dummy/tmp/cache/assets/E60/DF0/sprockets%2F7dced5bcfe17976cc5e41ffc1c33dbd2 +0 -0
- data/test/dummy/tmp/cache/assets/E8B/700/sprockets%2F3bd227fedfbfab5ede67a13feec45e14 +0 -0
- data/test/fixtures/kanaui/dashboards.yml +11 -0
- data/test/functional/kanaui/tests_controller_test.rb +9 -0
- data/test/integration/navigation_test.rb +10 -0
- data/test/kanaui_test.rb +7 -0
- data/test/test_helper.rb +15 -0
- data/test/unit/helpers/kanaui/tests_helper_test.rb +6 -0
- metadata +551 -0
@@ -0,0 +1,1114 @@
|
|
1
|
+
/*
|
2
|
+
* 'killbillGraph' is the namespace required to access all the public objects
|
3
|
+
*
|
4
|
+
* PIE
|
5
|
+
*
|
6
|
+
* Input for pie chart should be of the form
|
7
|
+
* dataforPie = {"name":"pie1", "data": [{"label":"one", "value":10}, ]};
|
8
|
+
*
|
9
|
+
* LAYERS and LINES
|
10
|
+
*
|
11
|
+
* Input for layers and line graphs are expected to be of the form:
|
12
|
+
* dataForGraph = [ {"name":"line1", "values":[{"x":"2013-01-01", "y":6}, {"x":"2013-01-02", "y":6}] },
|
13
|
+
* {"name":"line2", "values":[{"x":"2013-01-01", "y":12}, {"x":"2013-01-02", "y":3}] } ];
|
14
|
+
*
|
15
|
+
* There can be up to 20 lines -- limited by the color palette -- per graph; the graph can be either:
|
16
|
+
* - layered graph (KBLayersGraph)
|
17
|
+
* - lines graph (KBLinesGraph)
|
18
|
+
*
|
19
|
+
* Description of the fields:
|
20
|
+
* - name is the 'name of the line-- as shown in the label
|
21
|
+
* - values are the {x,y} coordinates for each point; the x coordinates should be dates and should all be the same for each entries.
|
22
|
+
*
|
23
|
+
*/
|
24
|
+
(function (killbillGraph, $, undefined) {
|
25
|
+
|
26
|
+
|
27
|
+
/**
|
28
|
+
* Input parameters to draw all the graphs
|
29
|
+
*/
|
30
|
+
killbillGraph.KBInputGraphs = function (canvasWidth, canvasHeigth, topMargin, rightMargin, bottomMargin, leftMargin, betweenGraphMargin, graphData) {
|
31
|
+
|
32
|
+
this.topMargin = topMargin;
|
33
|
+
this.rightMargin = rightMargin;
|
34
|
+
this.bottomMargin = bottomMargin;
|
35
|
+
this.leftMargin = leftMargin;
|
36
|
+
|
37
|
+
|
38
|
+
this.betweenGraphMargin = betweenGraphMargin;
|
39
|
+
|
40
|
+
this.canvasWidth = canvasWidth;
|
41
|
+
this.canvasHeigth = canvasHeigth;
|
42
|
+
|
43
|
+
this.data = graphData;
|
44
|
+
|
45
|
+
}
|
46
|
+
|
47
|
+
|
48
|
+
/**
|
49
|
+
* KBHistogram : Histogram chart
|
50
|
+
*/
|
51
|
+
killbillGraph.KBHistogram = function (graphCanvas, title, data, width, heigth, palette) {
|
52
|
+
|
53
|
+
// For non 'bin' histogram interesting blod post : http://www.recursion.org/d3-for-mere-mortals/
|
54
|
+
|
55
|
+
this.graphCanvas = graphCanvas;
|
56
|
+
this.name = name
|
57
|
+
this.data = data;
|
58
|
+
this.width = width;
|
59
|
+
this.heigth = heigth;
|
60
|
+
this.palette = palette;
|
61
|
+
this.title = title;
|
62
|
+
|
63
|
+
this.minValue;
|
64
|
+
this.maxValue;
|
65
|
+
|
66
|
+
|
67
|
+
this.computeMinMax = function () {
|
68
|
+
var min;
|
69
|
+
var max;
|
70
|
+
for (var i = 0; i < this.data.length; i++) {
|
71
|
+
if (min == null || this.data[i] < min) {
|
72
|
+
min = this.data[i];
|
73
|
+
}
|
74
|
+
if (max == null || this.data[i] > max) {
|
75
|
+
max = this.data[i];
|
76
|
+
}
|
77
|
+
}
|
78
|
+
this.minValue = min - 1;
|
79
|
+
this.maxValue = max + 2;
|
80
|
+
}
|
81
|
+
|
82
|
+
|
83
|
+
this.draw = function () {
|
84
|
+
|
85
|
+
this.computeMinMax();
|
86
|
+
|
87
|
+
var formatCount = d3.format(",.0f");
|
88
|
+
|
89
|
+
var x = d3.scale.linear()
|
90
|
+
.domain([this.minValue, this.maxValue])
|
91
|
+
.range([0, this.width]);
|
92
|
+
|
93
|
+
var data = d3.layout.histogram()
|
94
|
+
.bins(x.ticks(10))
|
95
|
+
(this.data);
|
96
|
+
|
97
|
+
var y = d3.scale.linear()
|
98
|
+
.domain([0, d3.max(data, function (d) {
|
99
|
+
return d.y;
|
100
|
+
})])
|
101
|
+
.range([this.heigth, 0]);
|
102
|
+
|
103
|
+
var xAxis = d3.svg.axis()
|
104
|
+
.scale(x)
|
105
|
+
.orient("bottom");
|
106
|
+
|
107
|
+
var svg = this.graphCanvas
|
108
|
+
.append("svg")
|
109
|
+
.attr("width", this.width)
|
110
|
+
.attr("height", this.height)
|
111
|
+
.append("g")
|
112
|
+
.attr("transform", "translate(" + 0 + "," + 0 + ")");
|
113
|
+
|
114
|
+
var bar = svg.selectAll(".bar")
|
115
|
+
.data(data)
|
116
|
+
.enter().append("g")
|
117
|
+
.attr("class", "bar")
|
118
|
+
.attr("transform", function (d) {
|
119
|
+
return "translate(" + x(d.x) + "," + y(d.y) + ")";
|
120
|
+
});
|
121
|
+
|
122
|
+
var myself = this;
|
123
|
+
bar.append("rect")
|
124
|
+
.attr("x", 1)
|
125
|
+
.attr("width", x(data[0].dx) - 1)
|
126
|
+
.attr("height", function (d) {
|
127
|
+
return myself.heigth - y(d.y);
|
128
|
+
});
|
129
|
+
|
130
|
+
bar.append("text")
|
131
|
+
.attr("dy", ".75em")
|
132
|
+
.attr("y", 6)
|
133
|
+
.attr("x", x(data[0].dx) / 2)
|
134
|
+
.attr("text-anchor", "middle")
|
135
|
+
.text(function (d) {
|
136
|
+
return formatCount(d.y);
|
137
|
+
});
|
138
|
+
|
139
|
+
svg.append("g")
|
140
|
+
.attr("class", "x axis")
|
141
|
+
.attr("transform", "translate(" + 0 + "," + myself.heigth + ")")
|
142
|
+
.call(xAxis);
|
143
|
+
|
144
|
+
this.graphCanvas.append("svg:text")
|
145
|
+
.attr("class", "title")
|
146
|
+
.attr("x", (this.width - this.title.length) / 2)
|
147
|
+
.attr("y", -30)
|
148
|
+
.text(this.title);
|
149
|
+
}
|
150
|
+
|
151
|
+
this.addOnMouseHandlers = function () {
|
152
|
+
// Not implemented
|
153
|
+
}
|
154
|
+
|
155
|
+
}
|
156
|
+
|
157
|
+
/**
|
158
|
+
* KBPie : A Pie chart
|
159
|
+
*/
|
160
|
+
killbillGraph.KBPie = function (graphCanvas, title, inputData, width, heigth, palette) {
|
161
|
+
|
162
|
+
// If our value is less than that -- compared to total, we don't display this is too small.
|
163
|
+
this.minDisplayRatio = 0.05;
|
164
|
+
|
165
|
+
this.graphCanvas = graphCanvas;
|
166
|
+
this.name = name
|
167
|
+
this.inputData = inputData;
|
168
|
+
this.width = width;
|
169
|
+
this.heigth = heigth;
|
170
|
+
this.radius = (this.width / 4);
|
171
|
+
this.palette = palette;
|
172
|
+
this.title = title;
|
173
|
+
|
174
|
+
this.totalValue = function () {
|
175
|
+
var result = 0;
|
176
|
+
for (var i = 0; i < this.data.length; i++) {
|
177
|
+
result = result + this.data[i].value;
|
178
|
+
}
|
179
|
+
return result;
|
180
|
+
}
|
181
|
+
|
182
|
+
this.draw = function () {
|
183
|
+
this.addLegend();
|
184
|
+
this.drawPie();
|
185
|
+
this.addOnMouseHandlers();
|
186
|
+
}
|
187
|
+
|
188
|
+
this.drawPie = function () {
|
189
|
+
|
190
|
+
var vis = this.graphCanvas
|
191
|
+
.append("svg:svg")
|
192
|
+
.data([this.data])
|
193
|
+
.attr("width", this.width)
|
194
|
+
.attr("height", this.heigth)
|
195
|
+
.append("svg:g")
|
196
|
+
.attr("transform", "translate(" + (this.width / 2) + "," + this.radius + ")");
|
197
|
+
|
198
|
+
var arc = d3.svg.arc()
|
199
|
+
.outerRadius(this.radius);
|
200
|
+
|
201
|
+
var pie = d3.layout.pie()
|
202
|
+
.value(function (d) {
|
203
|
+
return d.value;
|
204
|
+
});
|
205
|
+
|
206
|
+
var arcs = vis.selectAll("g.slice")
|
207
|
+
.data(pie)
|
208
|
+
.enter()
|
209
|
+
.append("svg:g")
|
210
|
+
.attr("class", "slice");
|
211
|
+
|
212
|
+
var myself = this;
|
213
|
+
arcs.append("svg:path")
|
214
|
+
.style("fill", function (d, i) {
|
215
|
+
return palette(i);
|
216
|
+
})
|
217
|
+
.attr("id", function (d, i) {
|
218
|
+
return "arc-" + myself.data[i]['id'];
|
219
|
+
})
|
220
|
+
.attr("d", arc);
|
221
|
+
this.addValues(arcs, arc);
|
222
|
+
}
|
223
|
+
|
224
|
+
this.getDisplayValue = function (value) {
|
225
|
+
var total = this.totalValue();
|
226
|
+
var minDisplayRatio = this.minDisplayRatio;
|
227
|
+
return (value / total > minDisplayRatio) ? "inline" : "none";
|
228
|
+
}
|
229
|
+
|
230
|
+
this.addValues = function (arcs, arc) {
|
231
|
+
|
232
|
+
var myself = this;
|
233
|
+
arcs.append("svg:text")
|
234
|
+
.attr("transform", function (d) {
|
235
|
+
d.innerRadius = 0;
|
236
|
+
d.outerRadius = this.radius;
|
237
|
+
return "translate(" + arc.centroid(d) + ")";
|
238
|
+
})
|
239
|
+
.attr("id", function (d, i) {
|
240
|
+
return "arc-value-" + myself.data[i]['id'];
|
241
|
+
})
|
242
|
+
.attr("text-anchor", "middle")
|
243
|
+
.text(function (d, i) {
|
244
|
+
return myself.data[i].value;
|
245
|
+
})
|
246
|
+
.attr("display", function (d, i) {
|
247
|
+
return myself.getDisplayValue(myself.data[i].value);
|
248
|
+
});
|
249
|
+
}
|
250
|
+
|
251
|
+
|
252
|
+
this.addLegend = function () {
|
253
|
+
|
254
|
+
this.graphCanvas.append("svg:text")
|
255
|
+
.attr("class", "title")
|
256
|
+
.attr("x", (this.width - this.title.length) / 2)
|
257
|
+
.attr("y", -30)
|
258
|
+
.text(this.title);
|
259
|
+
|
260
|
+
var legend = this.graphCanvas.append("g")
|
261
|
+
.attr("class", "legend")
|
262
|
+
.attr("height", 100)
|
263
|
+
.attr("width", 200)
|
264
|
+
.attr('transform', 'translate(-100,0)')
|
265
|
+
|
266
|
+
|
267
|
+
var myself = this;
|
268
|
+
legend.selectAll('rect')
|
269
|
+
.data(this.data)
|
270
|
+
.enter()
|
271
|
+
.append("rect")
|
272
|
+
.attr("x", this.width - 65)
|
273
|
+
.attr("y", function (d, i) {
|
274
|
+
return i * 20;
|
275
|
+
})
|
276
|
+
.attr("id", function (d, i) {
|
277
|
+
return "pie-legend-" + myself.data[i]['id'];
|
278
|
+
})
|
279
|
+
.attr("width", 11)
|
280
|
+
.attr("height", 11)
|
281
|
+
.attr("rx", 3)
|
282
|
+
.attr("ry", 3)
|
283
|
+
.style("fill", function (d, i) {
|
284
|
+
var color = palette(i);
|
285
|
+
return color;
|
286
|
+
})
|
287
|
+
|
288
|
+
legend.selectAll('text')
|
289
|
+
.data(this.data)
|
290
|
+
.enter()
|
291
|
+
.append("text")
|
292
|
+
.attr("x", this.width - 52)
|
293
|
+
.attr("y", function (d, i) {
|
294
|
+
return i * 20 + 9;
|
295
|
+
})
|
296
|
+
.text(function (d, i) {
|
297
|
+
var text = d.label;
|
298
|
+
return text;
|
299
|
+
});
|
300
|
+
}
|
301
|
+
|
302
|
+
this.addOnMouseHandlers = function () {
|
303
|
+
this.addMouseLegend();
|
304
|
+
}
|
305
|
+
|
306
|
+
this.addMouseLegend = function () {
|
307
|
+
|
308
|
+
var myself = this;
|
309
|
+
$('rect').each(function (i) {
|
310
|
+
|
311
|
+
|
312
|
+
var curLegendId = $(this).attr("id");
|
313
|
+
if (curLegendId === undefined || curLegendId.substring(0, 11) != "pie-legend-") {
|
314
|
+
return;
|
315
|
+
}
|
316
|
+
|
317
|
+
var arcId = $(this).attr("id").replace("pie-legend", "arc");
|
318
|
+
var arcValueId = $(this).attr("id").replace("pie-legend", "arc-value");
|
319
|
+
var arcValue = $("#" + arcValueId);
|
320
|
+
|
321
|
+
var otherArcs = new Array();
|
322
|
+
var otherArcValues = new Array();
|
323
|
+
for (var i = 0; i < myself.data.length; i++) {
|
324
|
+
|
325
|
+
var curArcId = "arc-" + myself.data[i]['id'];
|
326
|
+
var curArcValueId = "arc-value-" + myself.data[i]['id'];
|
327
|
+
if (curArcId != arcId) {
|
328
|
+
var curArc = $("#" + curArcId);
|
329
|
+
otherArcs.push(curArc);
|
330
|
+
var curArcValue = $("#" + curArcValueId);
|
331
|
+
otherArcValues.push(curArcValue);
|
332
|
+
}
|
333
|
+
}
|
334
|
+
|
335
|
+
var myPieLegendRect = $(this);
|
336
|
+
$(this).hover(function () {
|
337
|
+
for (var i = 0; i < otherArcs.length; i++) {
|
338
|
+
otherArcs[i].attr("opacity", 0.1);
|
339
|
+
otherArcValues[i].attr("display", "none");
|
340
|
+
}
|
341
|
+
|
342
|
+
arcValue.attr("display", "inline");
|
343
|
+
myPieLegendRect.attr("width", 15)
|
344
|
+
.attr("height", 15)
|
345
|
+
.attr('transform', 'translate(-3,-3)');
|
346
|
+
}, function () {
|
347
|
+
for (var i = 0; i < otherArcs.length; i++) {
|
348
|
+
otherArcs[i].attr("opacity", 1.0);
|
349
|
+
otherArcValues[i].attr("display", myself.getDisplayValue(parseInt(otherArcValues[i].text())));
|
350
|
+
}
|
351
|
+
arcValue.attr("display", myself.getDisplayValue(parseInt(arcValue.text())));
|
352
|
+
|
353
|
+
myPieLegendRect.attr("width", 11)
|
354
|
+
.attr("height", 11)
|
355
|
+
.attr('transform', 'translate(0,0)');
|
356
|
+
});
|
357
|
+
});
|
358
|
+
}
|
359
|
+
|
360
|
+
this.addDataId = function () {
|
361
|
+
for (var i = 0; i < this.inputData.length; i++) {
|
362
|
+
this.inputData[i]['id'] = (Math.random() + 1).toString(36).substring(7);
|
363
|
+
}
|
364
|
+
return this.inputData;
|
365
|
+
}
|
366
|
+
|
367
|
+
this.data = this.addDataId();
|
368
|
+
|
369
|
+
}
|
370
|
+
|
371
|
+
/**
|
372
|
+
* KBTimeSeriesBase : Base class for both layered and non layered graphs
|
373
|
+
*/
|
374
|
+
killbillGraph.KBTimeSeriesBase = function (graphCanvas, title, inputData, width, heigth, palette) {
|
375
|
+
|
376
|
+
this.graphCanvas = graphCanvas;
|
377
|
+
this.inputData = inputData;
|
378
|
+
|
379
|
+
this.width = width;
|
380
|
+
this.heigth = heigth;
|
381
|
+
this.title = title;
|
382
|
+
|
383
|
+
// the palette function out of which we create color map
|
384
|
+
this.palette = palette;
|
385
|
+
|
386
|
+
|
387
|
+
this.addDataId = function () {
|
388
|
+
for (var i = 0; i < this.inputData.length; i++) {
|
389
|
+
this.inputData[i]['id'] = (Math.random() + 1).toString(36).substring(7);
|
390
|
+
}
|
391
|
+
return this.inputData;
|
392
|
+
}
|
393
|
+
|
394
|
+
/**
|
395
|
+
* Create the 'x' date scale
|
396
|
+
* - dataX is is an ordered array of all the dates
|
397
|
+
*/
|
398
|
+
this.getScaleDate = function () {
|
399
|
+
|
400
|
+
var dataX = this.extractKeyOrValueFromDataLayer(this.data[0], 'x');
|
401
|
+
var minDate = new Date(dataX[0]);
|
402
|
+
var maxDate = new Date(dataX[dataX.length - 1]);
|
403
|
+
return d3.time.scale().domain([minDate, maxDate]).range([0, width]);
|
404
|
+
}
|
405
|
+
|
406
|
+
|
407
|
+
this.formatDate = function (date) {
|
408
|
+
|
409
|
+
// We want to display a UTC date, so before we extract year, month, day info, we add the time difference
|
410
|
+
// between our timezone and UTC
|
411
|
+
date.setHours(date.getHours() + (date.getTimezoneOffset() / 60));
|
412
|
+
var date_part = date.getDate();
|
413
|
+
var month_part = date.getMonth() + 1
|
414
|
+
var year_part = date.getFullYear();
|
415
|
+
|
416
|
+
return moment(date).format('MM[/]DD[/]YYYY')
|
417
|
+
}
|
418
|
+
|
419
|
+
/**
|
420
|
+
* Create the 'Y' axis in a new svg group
|
421
|
+
* - scaleY is the d3 scale built based on height and y point range
|
422
|
+
*/
|
423
|
+
this.createYAxis = function (scaleY) {
|
424
|
+
var yAxisLeft = d3.svg.axis().scale(scaleY).ticks(6).tickSize([-(this.width + 25)]).orient("left");
|
425
|
+
|
426
|
+
this.graphCanvas.append("svg:g")
|
427
|
+
.attr("class", "y axis")
|
428
|
+
.attr("id", "yaxis-" + this['id'])
|
429
|
+
.attr("transform", "translate(-25,0)")
|
430
|
+
.call(yAxisLeft);
|
431
|
+
|
432
|
+
/*
|
433
|
+
$("#yaxis-" + this['id']).children().each(function (i) {
|
434
|
+
if ($(this).attr('class') == 'domain') {
|
435
|
+
//$(this).attr("display", "none");
|
436
|
+
}
|
437
|
+
console.log("Got element " + $(this).attr('class'));
|
438
|
+
});
|
439
|
+
*/
|
440
|
+
}
|
441
|
+
|
442
|
+
/**
|
443
|
+
* Create the 'X' axis in a new svg group
|
444
|
+
* - dataLayer : the data for the layer format
|
445
|
+
* - xAxisGraphGroup the group where this axis will be attached to
|
446
|
+
* - xAxisHeightTick the height of the ticks
|
447
|
+
*/
|
448
|
+
this.createXAxis = function (xAxisGraphGroup, xAxisHeightTick) {
|
449
|
+
|
450
|
+
var scaleX = this.getScaleDate();
|
451
|
+
var xAxis = d3.svg.axis().scale(scaleX).tickSize(-xAxisHeightTick).tickSubdivide(true);
|
452
|
+
xAxisGraphGroup.append("svg:g")
|
453
|
+
.attr("class", "x axis")
|
454
|
+
.call(xAxis);
|
455
|
+
}
|
456
|
+
|
457
|
+
|
458
|
+
/**
|
459
|
+
* Add the cirles for each point in the graph line
|
460
|
+
*
|
461
|
+
* This is used for both stacked and non stacked lines
|
462
|
+
*/
|
463
|
+
this.addCirclesForGraph = function (circleGroup, lineId, dataX, dataY, scaleX, scaleY, lineColor) {
|
464
|
+
|
465
|
+
var nodes = circleGroup.selectAll("g")
|
466
|
+
.data(dataY)
|
467
|
+
.enter();
|
468
|
+
|
469
|
+
nodes.append("svg:circle")
|
470
|
+
.attr("id", function (d, i) {
|
471
|
+
return "circle-" + lineId + "-" + i;
|
472
|
+
})
|
473
|
+
.attr("cx", function (d, i) {
|
474
|
+
return scaleX(new Date(dataX[i]));
|
475
|
+
})
|
476
|
+
.attr("cy", function (d, i) {
|
477
|
+
return scaleY(d);
|
478
|
+
})
|
479
|
+
.attr("r", 3.5)
|
480
|
+
.attr("fill", lineColor)
|
481
|
+
.attr("value", function (d, i) {
|
482
|
+
return d;
|
483
|
+
});
|
484
|
+
}
|
485
|
+
|
486
|
+
this.addOverlayForGraph = function (circleGroup, lineId, dataX, dataY, scaleX, scaleY) {
|
487
|
+
|
488
|
+
var myself = this;
|
489
|
+
|
490
|
+
var nodes = circleGroup.selectAll("g")
|
491
|
+
.data(dataY)
|
492
|
+
.enter()
|
493
|
+
.append("svg:g");
|
494
|
+
|
495
|
+
nodes.append("svg:rect")
|
496
|
+
.attr("id", function (d, i) {
|
497
|
+
return "rect-" + lineId + "-" + i;
|
498
|
+
})
|
499
|
+
.attr("x", function (d, i) {
|
500
|
+
return scaleX(new Date(dataX[i]));
|
501
|
+
})
|
502
|
+
.attr("y", function (d, i) {
|
503
|
+
return scaleY(d);
|
504
|
+
})
|
505
|
+
.attr("width", 140)
|
506
|
+
.attr("height", 50)
|
507
|
+
.attr("rx", 5)
|
508
|
+
.attr("ry", 5)
|
509
|
+
.attr("display", "none")
|
510
|
+
.style("fill", function (d, i) {
|
511
|
+
return "#222";
|
512
|
+
})
|
513
|
+
.attr("transform", 'translate(10,-30)');
|
514
|
+
|
515
|
+
|
516
|
+
nodes.append("svg:text")
|
517
|
+
.attr("id", function (d, i) {
|
518
|
+
return "text-" + lineId + "-" + i + "-1";
|
519
|
+
})
|
520
|
+
.attr("x", function (d, i) {
|
521
|
+
return scaleX(new Date(dataX[i]));
|
522
|
+
})
|
523
|
+
.attr("y", function (d, i) {
|
524
|
+
return scaleY(d);
|
525
|
+
})
|
526
|
+
.attr("fill", "#bbb")
|
527
|
+
.attr("display", "none")
|
528
|
+
.text(function (d, i) {
|
529
|
+
return "Date = " + myself.formatDate(new Date(dataX[i]));
|
530
|
+
})
|
531
|
+
.attr("transform", 'translate(30,-10)');
|
532
|
+
|
533
|
+
nodes.append("svg:text")
|
534
|
+
.attr("id", function (d, i) {
|
535
|
+
return "text-" + lineId + "-" + i + "-2";
|
536
|
+
})
|
537
|
+
.attr("x", function (d, i) {
|
538
|
+
return scaleX(new Date(dataX[i]));
|
539
|
+
})
|
540
|
+
.attr("y", function (d, i) {
|
541
|
+
return scaleY(d);
|
542
|
+
})
|
543
|
+
.attr("fill", "#bbb")
|
544
|
+
.attr("display", "none")
|
545
|
+
.text(function (d, i) {
|
546
|
+
return "Value = " + myself.numberWithCommas(d);
|
547
|
+
})
|
548
|
+
.attr("transform", 'translate(30, 10)');
|
549
|
+
}
|
550
|
+
|
551
|
+
|
552
|
+
this.numberWithCommas = function (x) {
|
553
|
+
var parts = x.toString().split(".");
|
554
|
+
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
555
|
+
return parts.join(".");
|
556
|
+
}
|
557
|
+
|
558
|
+
/**
|
559
|
+
* Extract the 'x' or 'y' from dataLyer format where each entry if of the form:
|
560
|
+
* - attr is either the 'x' or 'y'
|
561
|
+
* - dataLayer : the data for a given layer
|
562
|
+
* E.g:
|
563
|
+
* "name": "crescendo",
|
564
|
+
* "values": [
|
565
|
+
* { "x": "2010-07-08", "y": 0},
|
566
|
+
* ....
|
567
|
+
*/
|
568
|
+
this.extractKeyOrValueFromDataLayer = function (dataLayer, attr) {
|
569
|
+
var result = [];
|
570
|
+
for (var i = 0; i < dataLayer.values.length; i++) {
|
571
|
+
result.push(dataLayer.values[i][attr])
|
572
|
+
}
|
573
|
+
return result;
|
574
|
+
}
|
575
|
+
|
576
|
+
|
577
|
+
/**
|
578
|
+
* Add on the path the name of the line -- not used anymore as we are using external labels
|
579
|
+
*/
|
580
|
+
this.addPathLabel = function (graph, lineId, positionPercent) {
|
581
|
+
graph.append("svg:g")
|
582
|
+
.append("text")
|
583
|
+
.attr("font-size", "15")
|
584
|
+
.append("svg:textPath")
|
585
|
+
.attr("xlink:href", "#path-" + lineId)
|
586
|
+
.attr("startOffset", positionPercent)
|
587
|
+
.text(function (d) {
|
588
|
+
return lineId;
|
589
|
+
});
|
590
|
+
}
|
591
|
+
|
592
|
+
|
593
|
+
/**
|
594
|
+
* Create a new group for the circles-- no translations needed
|
595
|
+
*/
|
596
|
+
this.createCircleGroup = function (lineId) {
|
597
|
+
return this.graphCanvas.append("svg:g")
|
598
|
+
.attr("id", "circles-" + lineId);
|
599
|
+
}
|
600
|
+
|
601
|
+
this.createOverlayGroup = function (lineId) {
|
602
|
+
return this.graphCanvas.append("svg:g")
|
603
|
+
.attr("id", "overlay-" + lineId);
|
604
|
+
}
|
605
|
+
|
606
|
+
/**
|
607
|
+
* Given a colorMap, extract the k-ieme color
|
608
|
+
*
|
609
|
+
* The colormap are standard d3 colormap which switch to new color every 4 colors;
|
610
|
+
* in order to maximize the difference among colors we first get colors that are far apart
|
611
|
+
*
|
612
|
+
*/
|
613
|
+
this.getColor = function (k) {
|
614
|
+
var div = Math.floor(k / 4);
|
615
|
+
var mod = k % 4;
|
616
|
+
var value = div + 4 * mod;
|
617
|
+
return this.colorMap[value];
|
618
|
+
}
|
619
|
+
|
620
|
+
/**
|
621
|
+
* Create the color map from the d3 palette
|
622
|
+
*/
|
623
|
+
this.createColorMap = function () {
|
624
|
+
var colorMap = {}
|
625
|
+
for (var i = 0; i < 20; i++) {
|
626
|
+
colorMap[i] = this.palette(i);
|
627
|
+
}
|
628
|
+
return colorMap;
|
629
|
+
}
|
630
|
+
|
631
|
+
this.addLegend = function () {
|
632
|
+
|
633
|
+
this.graphCanvas.append("svg:text")
|
634
|
+
.attr("class", "title")
|
635
|
+
.attr("x", (this.width - this.title.length ) / 2)
|
636
|
+
.attr("y", -30)
|
637
|
+
.text(this.title);
|
638
|
+
|
639
|
+
var legend = this.graphCanvas.append("g")
|
640
|
+
.attr("class", "legend")
|
641
|
+
.attr("height", 100)
|
642
|
+
.attr("width", 200)
|
643
|
+
.attr('transform', 'translate(+80,+0)')
|
644
|
+
|
645
|
+
|
646
|
+
var myself = this;
|
647
|
+
legend.selectAll('rect')
|
648
|
+
.data(this.data)
|
649
|
+
.enter()
|
650
|
+
.append("rect")
|
651
|
+
.attr("id", function (d, i) {
|
652
|
+
return "ts-legend-" + myself.data[i]['id'];
|
653
|
+
})
|
654
|
+
.attr("x", this.width - 65)
|
655
|
+
.attr("y", function (d, i) {
|
656
|
+
return i * 20;
|
657
|
+
})
|
658
|
+
.attr("width", 11)
|
659
|
+
.attr("height", 11)
|
660
|
+
.attr("rx", 3)
|
661
|
+
.attr("ry", 3)
|
662
|
+
.style("fill", function (d, i) {
|
663
|
+
var color = myself.getColor(i);
|
664
|
+
return color;
|
665
|
+
})
|
666
|
+
|
667
|
+
legend.selectAll('text')
|
668
|
+
.data(this.data)
|
669
|
+
.enter()
|
670
|
+
.append("text")
|
671
|
+
.attr("x", this.width - 52)
|
672
|
+
.attr("y", function (d, i) {
|
673
|
+
return i * 20 + 9;
|
674
|
+
})
|
675
|
+
.text(function (d, i) {
|
676
|
+
var text = d.name;
|
677
|
+
var displayText = text;
|
678
|
+
if (text.length > 25) {
|
679
|
+
displayText = text.substring(0,25) + "...";
|
680
|
+
}
|
681
|
+
return displayText;
|
682
|
+
});
|
683
|
+
}
|
684
|
+
|
685
|
+
this.addOnMouseHandlers = function () {
|
686
|
+
this.addMouseOverCircleForValue();
|
687
|
+
this.addMouseLegend();
|
688
|
+
}
|
689
|
+
|
690
|
+
/**
|
691
|
+
* Attach handlers to all circles so as to display value
|
692
|
+
*
|
693
|
+
* Note that this will attach for all graphs-- not only the one attached to that objec
|
694
|
+
*/
|
695
|
+
this.addMouseOverCircleForValue = function () {
|
696
|
+
|
697
|
+
$('circle').each(function (i) {
|
698
|
+
|
699
|
+
var textId1 = $(this).attr("id").replace("circle", "text") + "-1";
|
700
|
+
var textId2 = $(this).attr("id").replace("circle", "text") + "-2";
|
701
|
+
var rectId = $(this).attr("id").replace("circle", "rect");
|
702
|
+
|
703
|
+
var circleText1 = $('#'.concat(textId1));
|
704
|
+
var circleText2 = $('#'.concat(textId2));
|
705
|
+
var circleRect = $('#'.concat(rectId));
|
706
|
+
|
707
|
+
$(this).hover(function () {
|
708
|
+
circleRect.show();
|
709
|
+
circleText1.show();
|
710
|
+
circleText2.show();
|
711
|
+
}, function () {
|
712
|
+
setTimeout(
|
713
|
+
function () {
|
714
|
+
circleRect.hide();
|
715
|
+
circleText1.hide();
|
716
|
+
circleText2.hide();
|
717
|
+
}, 100);
|
718
|
+
|
719
|
+
});
|
720
|
+
});
|
721
|
+
}
|
722
|
+
|
723
|
+
|
724
|
+
this.performActionOnMouseHoverLegend = function () {
|
725
|
+
}
|
726
|
+
|
727
|
+
/* Build and save colorMap */
|
728
|
+
this.colorMap = this.createColorMap();
|
729
|
+
|
730
|
+
this.data = this.addDataId();
|
731
|
+
this.id = (Math.random() + 1).toString(36).substring(7);
|
732
|
+
|
733
|
+
}
|
734
|
+
|
735
|
+
/**
|
736
|
+
* KBLayersGraph : Inherits KBTimeSeriesBase abd offers specifics for layered graphs
|
737
|
+
*/
|
738
|
+
killbillGraph.KBLayersGraph = function (graphCanvas, title, data, width, heigth, palette) {
|
739
|
+
|
740
|
+
killbillGraph.KBTimeSeriesBase.call(this, graphCanvas, title, data, width, heigth, palette);
|
741
|
+
|
742
|
+
|
743
|
+
/**
|
744
|
+
* Create the area function that defines for each point in the stack graph
|
745
|
+
* its x, y0 (offest from previous stacked graph) and y position
|
746
|
+
*/
|
747
|
+
this.createLayerArea = function (scaleX, scaleY) {
|
748
|
+
var area = d3.svg.area()
|
749
|
+
.x(function (d) {
|
750
|
+
return scaleX(new Date(d.x));
|
751
|
+
})
|
752
|
+
.y0(function (d) {
|
753
|
+
return scaleY(d.y0);
|
754
|
+
})
|
755
|
+
.y1(function (d) {
|
756
|
+
return scaleY(d.y + d.y0);
|
757
|
+
});
|
758
|
+
return area;
|
759
|
+
}
|
760
|
+
|
761
|
+
/**
|
762
|
+
* Create the 'y' scale for the stack graph
|
763
|
+
*
|
764
|
+
* Extract min/max for each x value across all layers
|
765
|
+
*
|
766
|
+
*/
|
767
|
+
this.getLayerScaleValue = function () {
|
768
|
+
|
769
|
+
var tmp = [];
|
770
|
+
for (var i = 0; i < this.data.length; i++) {
|
771
|
+
tmp.push(this.data[i].values)
|
772
|
+
}
|
773
|
+
|
774
|
+
var sumValues = [];
|
775
|
+
for (var i = 0; i < tmp[0].length; i++) {
|
776
|
+
var max = 0;
|
777
|
+
for (var j = 0; j < tmp.length; j++) {
|
778
|
+
max = max + tmp[j][i].y;
|
779
|
+
}
|
780
|
+
sumValues.push(max);
|
781
|
+
}
|
782
|
+
var minValue = 0;
|
783
|
+
var maxValue = 0;
|
784
|
+
for (var i = 0; i < sumValues.length; i++) {
|
785
|
+
if (sumValues[i] < minValue) {
|
786
|
+
minValue = sumValues[i];
|
787
|
+
}
|
788
|
+
if (sumValues[i] > maxValue) {
|
789
|
+
maxValue = sumValues[i];
|
790
|
+
}
|
791
|
+
}
|
792
|
+
if (minValue > 0) {
|
793
|
+
minValue = 0;
|
794
|
+
}
|
795
|
+
return d3.scale.linear().domain([minValue, maxValue]).range([heigth, 0]);
|
796
|
+
}
|
797
|
+
|
798
|
+
|
799
|
+
/**
|
800
|
+
* All all layers on the graph
|
801
|
+
*/
|
802
|
+
this.addLayers = function (stack, area, dataLayers) {
|
803
|
+
|
804
|
+
var dataLayerStack = stack(dataLayers);
|
805
|
+
|
806
|
+
var currentObj = this;
|
807
|
+
|
808
|
+
this.graphCanvas.selectAll("path")
|
809
|
+
.data(dataLayerStack)
|
810
|
+
.enter()
|
811
|
+
.append("path")
|
812
|
+
.style("fill",function (d, i) {
|
813
|
+
return currentObj.getColor(i);
|
814
|
+
}).attr("d", function (d) {
|
815
|
+
return area(d.values);
|
816
|
+
})
|
817
|
+
.attr("id", function (d) {
|
818
|
+
return "path-" + d.id;
|
819
|
+
});
|
820
|
+
}
|
821
|
+
|
822
|
+
this.draw = function () {
|
823
|
+
this.addLegend();
|
824
|
+
this.drawStackLayers();
|
825
|
+
this.addOnMouseHandlers();
|
826
|
+
}
|
827
|
+
|
828
|
+
/**
|
829
|
+
* Draw all layers-- calls previous function addLayers
|
830
|
+
* It will create its Y axis
|
831
|
+
*/
|
832
|
+
this.drawStackLayers = function () {
|
833
|
+
|
834
|
+
var scaleX = this.getScaleDate();
|
835
|
+
var scaleY = this.getLayerScaleValue();
|
836
|
+
|
837
|
+
var stack = d3.layout.stack()
|
838
|
+
.offset("zero")
|
839
|
+
.values(function (d) {
|
840
|
+
return d.values;
|
841
|
+
});
|
842
|
+
|
843
|
+
var area = this.createLayerArea(scaleX, scaleY);
|
844
|
+
|
845
|
+
this.addLayers(stack, area, this.data);
|
846
|
+
|
847
|
+
var dataX = this.extractKeyOrValueFromDataLayer(this.data[0], 'x');
|
848
|
+
var dataY0 = null;
|
849
|
+
for (var i = 0; i < this.data.length; i++) {
|
850
|
+
|
851
|
+
var circleGroup = this.createCircleGroup(this.data[i]['id']);
|
852
|
+
var dataY = this.extractKeyOrValueFromDataLayer(this.data[i], 'y');
|
853
|
+
if (dataY0) {
|
854
|
+
for (var k = 0; k < dataY.length; k++) {
|
855
|
+
dataY[k] = dataY[k] + dataY0[k];
|
856
|
+
}
|
857
|
+
}
|
858
|
+
this.addCirclesForGraph(circleGroup, this.data[i]['id'], dataX, dataY, scaleX, scaleY, this.getColor(i));
|
859
|
+
dataY0 = dataY;
|
860
|
+
}
|
861
|
+
|
862
|
+
this.createYAxis(scaleY);
|
863
|
+
|
864
|
+
dataY0 = null;
|
865
|
+
for (var i = 0; i < this.data.length; i++) {
|
866
|
+
var circleGroup = this.createOverlayGroup(this.data[i]['id']);
|
867
|
+
var dataY = this.extractKeyOrValueFromDataLayer(this.data[i], 'y');
|
868
|
+
if (dataY0) {
|
869
|
+
for (var k = 0; k < dataY.length; k++) {
|
870
|
+
dataY[k] = dataY[k] + dataY0[k];
|
871
|
+
}
|
872
|
+
}
|
873
|
+
this.addOverlayForGraph(circleGroup, this.data[i]['id'], dataX, dataY, scaleX, scaleY);
|
874
|
+
dataY0 = dataY;
|
875
|
+
}
|
876
|
+
|
877
|
+
}
|
878
|
+
|
879
|
+
this.addMouseLegend = function () {
|
880
|
+
|
881
|
+
var myself = this;
|
882
|
+
$('rect').each(function (i) {
|
883
|
+
|
884
|
+
var curLegendId = $(this).attr("id");
|
885
|
+
if (curLegendId === undefined || curLegendId.substring(0, 10) != "ts-legend-") {
|
886
|
+
return;
|
887
|
+
}
|
888
|
+
|
889
|
+
var pathId = $(this).attr("id").replace("ts-legend", "path");
|
890
|
+
var path = $("#" + pathId);
|
891
|
+
|
892
|
+
var otherPaths = new Array();
|
893
|
+
var otherCircles = new Array();
|
894
|
+
for (var i = 0; i < myself.data.length; i++) {
|
895
|
+
if ("path-" + myself.data[i]['id'] != pathId) {
|
896
|
+
var curPath = $("#path-" + myself.data[i]['id']);
|
897
|
+
otherPaths.push(curPath);
|
898
|
+
|
899
|
+
var curCircle = $("#circles-" + myself.data[i]['id']);
|
900
|
+
otherCircles.push(curCircle);
|
901
|
+
}
|
902
|
+
}
|
903
|
+
|
904
|
+
var myLegendRect = $(this);
|
905
|
+
$(this).hover(function () {
|
906
|
+
for (var i = 0; i < otherPaths.length; i++) {
|
907
|
+
otherPaths[i].attr("opacity", 0.1);
|
908
|
+
otherCircles[i].attr("opacity", 0);
|
909
|
+
}
|
910
|
+
|
911
|
+
myLegendRect.attr("width", 15)
|
912
|
+
.attr("height", 15)
|
913
|
+
.attr('transform', 'translate(-3,-3)');
|
914
|
+
}, function () {
|
915
|
+
setTimeout(
|
916
|
+
function () {
|
917
|
+
|
918
|
+
for (var i = 0; i < otherPaths.length; i++) {
|
919
|
+
otherPaths[i].attr("opacity", 1.0);
|
920
|
+
otherCircles[i].attr("opacity", 1.0);
|
921
|
+
}
|
922
|
+
myLegendRect.attr("width", 11)
|
923
|
+
.attr("height", 11)
|
924
|
+
.attr('transform', 'translate(0,0)');
|
925
|
+
}, 100);
|
926
|
+
});
|
927
|
+
});
|
928
|
+
}
|
929
|
+
}
|
930
|
+
killbillGraph.KBLayersGraph.prototype = Object.create(killbillGraph.KBTimeSeriesBase.prototype);
|
931
|
+
|
932
|
+
|
933
|
+
/**
|
934
|
+
* KBLinesGraph : Inherits KBTimeSeriesBase abd offers specifics for layered graphs
|
935
|
+
*/
|
936
|
+
killbillGraph.KBLinesGraph = function (graphCanvas, title, data, width, heigth, palette) {
|
937
|
+
|
938
|
+
killbillGraph.KBTimeSeriesBase.call(this, graphCanvas, title, data, width, heigth, palette);
|
939
|
+
|
940
|
+
/**
|
941
|
+
* Create the 'y' scale for line graphs (non stacked)
|
942
|
+
*/
|
943
|
+
this.getScaleValue = function () {
|
944
|
+
|
945
|
+
var dataYs = [];
|
946
|
+
for (var k = 0; k < this.data.length; k++) {
|
947
|
+
var dataY = this.extractKeyOrValueFromDataLayer(this.data[k], 'y');
|
948
|
+
dataYs.push(dataY);
|
949
|
+
}
|
950
|
+
|
951
|
+
var minValue = 0;
|
952
|
+
var maxValue = 0;
|
953
|
+
for (var i = 0; i < dataYs.length; i++) {
|
954
|
+
for (var j = 0; j < dataYs[i].length; j++) {
|
955
|
+
if (dataYs[i][j] < minValue) {
|
956
|
+
minValue = dataYs[i][j];
|
957
|
+
}
|
958
|
+
if (dataYs[i][j] > maxValue) {
|
959
|
+
maxValue = dataYs[i][j];
|
960
|
+
}
|
961
|
+
}
|
962
|
+
}
|
963
|
+
if (minValue > 0) {
|
964
|
+
minValue = 0;
|
965
|
+
}
|
966
|
+
return d3.scale.linear().domain([minValue, maxValue]).range([this.heigth, 0]);
|
967
|
+
}
|
968
|
+
|
969
|
+
/**
|
970
|
+
* Add the svg line for this data (dataX, dataY)
|
971
|
+
*/
|
972
|
+
this.addLine = function (dataY, scaleX, scaleY, lineColor, lineId) {
|
973
|
+
|
974
|
+
var dataX = this.extractKeyOrValueFromDataLayer(this.data[0], 'x');
|
975
|
+
this.graphCanvas.selectAll("path.line")
|
976
|
+
.data([dataY])
|
977
|
+
.enter()
|
978
|
+
.append("svg:path")
|
979
|
+
.attr("stroke-width", 1.5)
|
980
|
+
.attr("d", d3.svg.line()
|
981
|
+
.x(function (d, i) {
|
982
|
+
return scaleX(new Date(dataX[i]));
|
983
|
+
})
|
984
|
+
.y(function (d) {
|
985
|
+
return scaleY(d);
|
986
|
+
}))
|
987
|
+
.attr("id", "path-" + lineId)
|
988
|
+
.style("stroke", lineColor);
|
989
|
+
}
|
990
|
+
|
991
|
+
this.draw = function () {
|
992
|
+
this.addLegend();
|
993
|
+
this.drawLines();
|
994
|
+
this.addOnMouseHandlers();
|
995
|
+
}
|
996
|
+
|
997
|
+
/**
|
998
|
+
* Draw all lines
|
999
|
+
* It will create its Y axis
|
1000
|
+
*/
|
1001
|
+
this.drawLines = function () {
|
1002
|
+
|
1003
|
+
var scaleX = this.getScaleDate();
|
1004
|
+
var scaleY = this.getScaleValue();
|
1005
|
+
|
1006
|
+
for (var k = 0; k < this.data.length; k++) {
|
1007
|
+
var dataY = this.extractKeyOrValueFromDataLayer(this.data[k], 'y');
|
1008
|
+
this.addLine(dataY, scaleX, scaleY, this.getColor(k), this.data[k]['id']);
|
1009
|
+
}
|
1010
|
+
|
1011
|
+
for (var k = 0; k < this.data.length; k++) {
|
1012
|
+
var dataX = this.extractKeyOrValueFromDataLayer(this.data[0], 'x');
|
1013
|
+
var dataY = this.extractKeyOrValueFromDataLayer(this.data[k], 'y');
|
1014
|
+
var lineId = this.data[k]['id']
|
1015
|
+
var circleGroup = this.createCircleGroup(lineId);
|
1016
|
+
this.addCirclesForGraph(circleGroup, lineId, dataX, dataY, scaleX, scaleY, this.getColor(k));
|
1017
|
+
}
|
1018
|
+
|
1019
|
+
this.createYAxis(scaleY);
|
1020
|
+
|
1021
|
+
for (var k = 0; k < this.data.length; k++) {
|
1022
|
+
var dataX = this.extractKeyOrValueFromDataLayer(this.data[0], 'x');
|
1023
|
+
var dataY = this.extractKeyOrValueFromDataLayer(this.data[k], 'y');
|
1024
|
+
var lineId = this.data[k]['id']
|
1025
|
+
var circleGroup = this.createOverlayGroup(lineId);
|
1026
|
+
this.addOverlayForGraph(circleGroup, lineId, dataX, dataY, scaleX, scaleY);
|
1027
|
+
|
1028
|
+
}
|
1029
|
+
}
|
1030
|
+
|
1031
|
+
this.addMouseLegend = function () {
|
1032
|
+
|
1033
|
+
$('rect').each(function (i) {
|
1034
|
+
|
1035
|
+
var curLegendId = $(this).attr("id");
|
1036
|
+
if (curLegendId === undefined || curLegendId.substring(0, 10) != "ts-legend-") {
|
1037
|
+
return;
|
1038
|
+
}
|
1039
|
+
|
1040
|
+
var pathId = $(this).attr("id").replace("ts-legend", "path");
|
1041
|
+
var path = $("#" + pathId);
|
1042
|
+
|
1043
|
+
var myLegendRect = $(this);
|
1044
|
+
$(this).hover(function () {
|
1045
|
+
path.attr("stroke-width", 3);
|
1046
|
+
myLegendRect.attr("width", 15)
|
1047
|
+
.attr("height", 15)
|
1048
|
+
.attr('transform', 'translate(-3,-3)');
|
1049
|
+
}, function () {
|
1050
|
+
setTimeout(
|
1051
|
+
function () {
|
1052
|
+
path.attr("stroke-width", 1.5);
|
1053
|
+
myLegendRect.attr("width", 11)
|
1054
|
+
.attr("height", 11)
|
1055
|
+
.attr('transform', 'translate(0,0)');
|
1056
|
+
}, 100);
|
1057
|
+
});
|
1058
|
+
});
|
1059
|
+
}
|
1060
|
+
}
|
1061
|
+
|
1062
|
+
killbillGraph.KBLinesGraph.prototype = Object.create(killbillGraph.KBTimeSeriesBase.prototype);
|
1063
|
+
|
1064
|
+
|
1065
|
+
killbillGraph.GraphStructure = function () {
|
1066
|
+
|
1067
|
+
/**
|
1068
|
+
* Setup the main divs for both legend and main charts
|
1069
|
+
*
|
1070
|
+
* It is expected to have a mnain div anchir on the html with id = 'chartAnchor'.
|
1071
|
+
*/
|
1072
|
+
this.setupDomStructure = function () {
|
1073
|
+
|
1074
|
+
var $divChart = $('<div id="charts" class="charts">');
|
1075
|
+
var $spanChart = $('<span id="chartId" class="charts"></span>');
|
1076
|
+
$divChart.prepend($spanChart);
|
1077
|
+
|
1078
|
+
$("#chartAnchor").append($divChart);
|
1079
|
+
}
|
1080
|
+
|
1081
|
+
|
1082
|
+
/**
|
1083
|
+
* Create initial canvas on which to draw all graphs
|
1084
|
+
*/
|
1085
|
+
this.createCanvas = function (m, w, h) {
|
1086
|
+
|
1087
|
+
// See http://tutorials.jenkov.com/svg/svg-viewport-view-box.html
|
1088
|
+
var canvasViewBoxWidth = w + m[1] + m[3];
|
1089
|
+
var canvasViewPortWidth = canvasViewBoxWidth;
|
1090
|
+
var canvasHeight = h + m[0] + m[2];
|
1091
|
+
|
1092
|
+
return d3.select("#chartId")
|
1093
|
+
.append("svg:svg")
|
1094
|
+
.attr("width", canvasViewPortWidth)
|
1095
|
+
.attr("height", canvasHeight)
|
1096
|
+
.attr("viewBox", "0 0 " + canvasViewBoxWidth + " " + canvasHeight)
|
1097
|
+
.attr("preserveAspectRatio", "xMinYMin meet");
|
1098
|
+
}
|
1099
|
+
|
1100
|
+
|
1101
|
+
/**
|
1102
|
+
* Create a new group and make the translation to leave room for margins
|
1103
|
+
*/
|
1104
|
+
this.createCanvasGroup = function (canvas, translateX, translateY) {
|
1105
|
+
return canvas
|
1106
|
+
.append("svg:g")
|
1107
|
+
.attr("transform", "translate(" + translateX + "," + translateY + ")");
|
1108
|
+
|
1109
|
+
}
|
1110
|
+
};
|
1111
|
+
|
1112
|
+
}(window.killbillGraph = window.killbillGraph || {}, jQuery)
|
1113
|
+
)
|
1114
|
+
;
|