HTMLcopy
1
<div id="container"></div>
CSScopy
6
1
html, body, #container {
2
width: 100%;
3
height: 100%;
4
margin: 0;
5
padding: 0;
6
}
JavaScriptcopy
x
1
anychart.onDocumentReady(function () {
2
// Contents of the sample:
3
// 1) Lines 123 to 345: Data description - all data for the entire dashboard is located here.
4
// 2) Lines 347 to 638: Utility functions - some utility functions to make routine operations easy.
5
// 3) Lines 640 to 1945: Functions, that form reports for the dashboard. Each function form its own report and
6
// returns an object consisting of a layer that contains the full report with title and contents positioned and a
7
// anychart.math.rect() describing report bounds.
8
// 4) Lines 1947 to 2070: A bunch of code that forms the report itself, using everything above.
9
10
// We set global fontFamily here to make it default
11
anychart.fontFamily = 'trebuchet, helvetica, arial, sans-serif';
12
13
// region Data description
14
// All the data imitates the manner as if we get it from a database.
15
// System Availability raw data. Columns are: ['System', 'Month', 'Availability']
16
var SARawData = [
17
['Network', Date.UTC(2013, 10), 98.6],
18
['Network', Date.UTC(2013, 11), 98.5],
19
['Network', Date.UTC(2014, 0), 98.5],
20
['Network', Date.UTC(2014, 1), 99.0],
21
['Network', Date.UTC(2014, 2), 99.2],
22
['Network', Date.UTC(2014, 3), 99.0],
23
['Network', Date.UTC(2014, 4), 99.3],
24
['Network', Date.UTC(2014, 5), 99.1],
25
['Network', Date.UTC(2014, 6), 99.0],
26
['Network', Date.UTC(2014, 7), 99.3],
27
['Network', Date.UTC(2014, 8), 99.5],
28
['Network', Date.UTC(2014, 9), 99.7],
29
['ERP', Date.UTC(2013, 10), 98.6],
30
['ERP', Date.UTC(2013, 11), 98.9],
31
['ERP', Date.UTC(2014, 0), 98.8],
32
['ERP', Date.UTC(2014, 1), 98.3],
33
['ERP', Date.UTC(2014, 2), 98.6],
34
['ERP', Date.UTC(2014, 3), 98.7],
35
['ERP', Date.UTC(2014, 4), 98.9],
36
['ERP', Date.UTC(2014, 5), 98.3],
37
['ERP', Date.UTC(2014, 6), 98.1],
38
['ERP', Date.UTC(2014, 7), 99.0],
39
['ERP', Date.UTC(2014, 8), 98.9],
40
['ERP', Date.UTC(2014, 9), 99.3],
41
['Data Warehouse', Date.UTC(2013, 10), 95.3],
42
['Data Warehouse', Date.UTC(2013, 11), 95.9],
43
['Data Warehouse', Date.UTC(2014, 0), 96.7],
44
['Data Warehouse', Date.UTC(2014, 1), 95.6],
45
['Data Warehouse', Date.UTC(2014, 2), 96.8],
46
['Data Warehouse', Date.UTC(2014, 3), 95.8],
47
['Data Warehouse', Date.UTC(2014, 4), 96.3],
48
['Data Warehouse', Date.UTC(2014, 5), 95.6],
49
['Data Warehouse', Date.UTC(2014, 6), 95.4],
50
['Data Warehouse', Date.UTC(2014, 7), 95.5],
51
['Data Warehouse', Date.UTC(2014, 8), 96.7],
52
['Data Warehouse', Date.UTC(2014, 9), 96.9],
53
['Web Site', Date.UTC(2013, 10), 97.9],
54
['Web Site', Date.UTC(2013, 11), 98.4],
55
['Web Site', Date.UTC(2014, 0), 98.5],
56
['Web Site', Date.UTC(2014, 1), 98.8],
57
['Web Site', Date.UTC(2014, 2), 99.0],
58
['Web Site', Date.UTC(2014, 3), 99.3],
59
['Web Site', Date.UTC(2014, 4), 99.2],
60
['Web Site', Date.UTC(2014, 5), 99.4],
61
['Web Site', Date.UTC(2014, 6), 99.4],
62
['Web Site', Date.UTC(2014, 7), 99.5],
63
['Web Site', Date.UTC(2014, 8), 99.6],
64
['Web Site', Date.UTC(2014, 9), 99.7],
65
['Email', Date.UTC(2013, 10), 99.0],
66
['Email', Date.UTC(2013, 11), 98.4],
67
['Email', Date.UTC(2014, 0), 99.1],
68
['Email', Date.UTC(2014, 1), 98.2],
69
['Email', Date.UTC(2014, 2), 98.2],
70
['Email', Date.UTC(2014, 3), 97.9],
71
['Email', Date.UTC(2014, 4), 98.6],
72
['Email', Date.UTC(2014, 5), 99.1],
73
['Email', Date.UTC(2014, 6), 98.4],
74
['Email', Date.UTC(2014, 7), 99.2],
75
['Email', Date.UTC(2014, 8), 99.2],
76
['Email', Date.UTC(2014, 9), 99.3],
77
['HR', Date.UTC(2013, 10), 97.0],
78
['HR', Date.UTC(2013, 11), 97.9],
79
['HR', Date.UTC(2014, 0), 98.2],
80
['HR', Date.UTC(2014, 1), 98.9],
81
['HR', Date.UTC(2014, 2), 98.2],
82
['HR', Date.UTC(2014, 3), 98.7],
83
['HR', Date.UTC(2014, 4), 98.4],
84
['HR', Date.UTC(2014, 5), 98.5],
85
['HR', Date.UTC(2014, 6), 98.6],
86
['HR', Date.UTC(2014, 7), 98.5],
87
['HR', Date.UTC(2014, 8), 98.7],
88
['HR', Date.UTC(2014, 9), 98.8],
89
['Problem Tracking', Date.UTC(2013, 10), 96.1],
90
['Problem Tracking', Date.UTC(2013, 11), 96.1],
91
['Problem Tracking', Date.UTC(2014, 0), 96.0],
92
['Problem Tracking', Date.UTC(2014, 1), 95.9],
93
['Problem Tracking', Date.UTC(2014, 2), 95.7],
94
['Problem Tracking', Date.UTC(2014, 3), 95.5],
95
['Problem Tracking', Date.UTC(2014, 4), 95.0],
96
['Problem Tracking', Date.UTC(2014, 5), 94.9],
97
['Problem Tracking', Date.UTC(2014, 6), 94.8],
98
['Problem Tracking', Date.UTC(2014, 7), 95.0],
99
['Problem Tracking', Date.UTC(2014, 8), 94.8],
100
['Problem Tracking', Date.UTC(2014, 9), 94.4]
101
];
102
// System Availability accepted values. Columns are: ['System', 'Accepted Availability']
103
var SAAcceptedAvailability = [
104
['Network', 99],
105
['ERP', 98],
106
['Data Warehouse', 98],
107
['Web Site', 98],
108
['Email', 98],
109
['HR', 96],
110
['Problem Tracking', 93]
111
];
112
// CPU Capacity % for today. Columns are: ['DateTime', 'Capacity']
113
var HCCPUData = [
114
[Date.UTC(2014, 9, 15, 0), 94.4],
115
[Date.UTC(2014, 9, 15, 1), 92.0],
116
[Date.UTC(2014, 9, 15, 2), 89.6],
117
[Date.UTC(2014, 9, 15, 3), 87.7],
118
[Date.UTC(2014, 9, 15, 4), 89.6],
119
[Date.UTC(2014, 9, 15, 5), 87.0],
120
[Date.UTC(2014, 9, 15, 6), 84.0],
121
[Date.UTC(2014, 9, 15, 7), 73.4],
122
[Date.UTC(2014, 9, 15, 8), 73.2],
123
[Date.UTC(2014, 9, 15, 9), 72.5],
124
[Date.UTC(2014, 9, 15, 10), 74.2],
125
[Date.UTC(2014, 9, 15, 11), 70.8],
126
[Date.UTC(2014, 9, 15, 12), 71.4],
127
[Date.UTC(2014, 9, 15, 13), 74.9],
128
[Date.UTC(2014, 9, 15, 14), 74.5],
129
[Date.UTC(2014, 9, 15, 15), 70.6],
130
[Date.UTC(2014, 9, 15, 16), 68.4],
131
[Date.UTC(2014, 9, 15, 17), 70.3],
132
[Date.UTC(2014, 9, 15, 18), 74.0],
133
[Date.UTC(2014, 9, 15, 19), 75.5],
134
[Date.UTC(2014, 9, 15, 20), 74.7],
135
[Date.UTC(2014, 9, 15, 21), 78.1],
136
[Date.UTC(2014, 9, 15, 22), 78.8],
137
[Date.UTC(2014, 9, 15, 23), 81.6]
138
];
139
// Storage Capacity % for last 12 month. Columns are: ['Month', 'Capacity']
140
var HCStorage = [
141
[Date.UTC(2013, 10), 61.2],
142
[Date.UTC(2013, 11), 64.1],
143
[Date.UTC(2014, 0), 65.8],
144
[Date.UTC(2014, 1), 67.5],
145
[Date.UTC(2014, 2), 69.0],
146
[Date.UTC(2014, 3), 70.3],
147
[Date.UTC(2014, 4), 71.6],
148
[Date.UTC(2014, 5), 71.4],
149
[Date.UTC(2014, 6), 73.0],
150
[Date.UTC(2014, 7), 73.2],
151
[Date.UTC(2014, 8), 73.8],
152
[Date.UTC(2014, 9), 74.6]
153
];
154
// Network Capacity % for last 12 month. Columns are: ['Month', 'Capacity']
155
var HCNetwork = [
156
[Date.UTC(2013, 10), 68.8],
157
[Date.UTC(2013, 11), 72.5],
158
[Date.UTC(2014, 0), 74.1],
159
[Date.UTC(2014, 1), 77.7],
160
[Date.UTC(2014, 2), 85.1],
161
[Date.UTC(2014, 3), 83.0],
162
[Date.UTC(2014, 4), 83.9],
163
[Date.UTC(2014, 5), 79.3],
164
[Date.UTC(2014, 6), 81.7],
165
[Date.UTC(2014, 7), 75.9],
166
[Date.UTC(2014, 8), 79.8],
167
[Date.UTC(2014, 9), 82.8]
168
];
169
// Values to form hardware capacity bullet ranges. They are supposed to be just known for each case.
170
// These values represent range borders:
171
// from 0 to 80 - good case,
172
// from 80 to 90 - excessive case,
173
// from 90 to 100 - critical case.
174
var HCCPURanges = [0, 80, 90, 100];
175
// from 0 to 60 - good case,
176
// from 60 to 80 - excessive case,
177
// from 80 to 100 - critical case.
178
var HCStorageRanges = [0, 60, 80, 100];
179
// from 0 to 60 - good case,
180
// from 60 to 80 - excessive case,
181
// from 80 to 100 - critical case.
182
var HCNetworkRanges = [0, 60, 80, 100];
183
// Daily Network Traffic for different periods of time. Columns are ['Hour', 'Average Traffic'].
184
// These data are supposed to be pre-calculated by a server or something else.
185
// DNT for last six month.
186
var DNT6MonthAvgData = [
187
[0, 171320],
188
[1, 140377],
189
[2, 119245],
190
[3, 58867],
191
[4, 46037],
192
[5, 15094],
193
[6, 25660],
194
[7, 135094],
195
[8, 188679],
196
[9, 186415],
197
[10, 166037],
198
[11, 160754],
199
[12, 135849],
200
[13, 166792],
201
[14, 175849],
202
[15, 175094],
203
[16, 144905],
204
[17, 166037],
205
[18, 129056],
206
[19, 66415],
207
[20, 54339],
208
[21, 35471],
209
[22, 39245],
210
[23, 160754]
211
];
212
// DNT for last week.
213
var DNTWeekAvgData = [
214
[0, 179622],
215
[1, 147924],
216
[2, 125283],
217
[3, 65660],
218
[4, 39245],
219
[5, 3773],
220
[6, 12075],
221
[7, 142641],
222
[8, 193962],
223
[9, 193962],
224
[10, 176603],
225
[11, 156226],
226
[12, 140377],
227
[13, 179622],
228
[14, 169056],
229
[15, 169811],
230
[16, 149433],
231
[17, 178867],
232
[18, 121509],
233
[19, 58113],
234
[20, 44528],
235
[21, 40754],
236
[22, 45283],
237
[23, 170566]
238
];
239
// DNT for yesterday.
240
var DNTYesterdayData = [
241
[0, 193207],
242
[1, 156226],
243
[2, 132075],
244
[3, 42264],
245
[4, 23396],
246
[5, 9056],
247
[6, 15849],
248
[7, 151698],
249
[8, 189433],
250
[9, 190188],
251
[10, 182641],
252
[11, 159245],
253
[12, 152452],
254
[13, 174339],
255
[14, 174339],
256
[15, 180377],
257
[16, 153962],
258
[17, 172830],
259
[18, 107924],
260
[19, 63396],
261
[20, 57358],
262
[21, 66415],
263
[22, 81509],
264
[23, 181886]
265
];
266
// Key Non-System Metrics report data:
267
// Summary expenses YTD. Columns are ['Month', 'Value'].
268
var KNSMExpensesData = [
269
[Date.UTC(2014, 0), 100000],
270
[Date.UTC(2014, 1), 97000],
271
[Date.UTC(2014, 2), 98000],
272
[Date.UTC(2014, 3), 98000],
273
[Date.UTC(2014, 4), 99000],
274
[Date.UTC(2014, 5), 100000],
275
[Date.UTC(2014, 6), 99000],
276
[Date.UTC(2014, 7), 98000],
277
[Date.UTC(2014, 8), 98000],
278
[Date.UTC(2014, 9), 97000]
279
];
280
// Customers satisfaction level YTD. Columns are ['Month', 'Satisfaction level'].
281
var KNSMSatisfactionData = [
282
[Date.UTC(2014, 0), 90],
283
[Date.UTC(2014, 1), 97],
284
[Date.UTC(2014, 2), 98],
285
[Date.UTC(2014, 3), 98],
286
[Date.UTC(2014, 4), 99],
287
[Date.UTC(2014, 5), 100],
288
[Date.UTC(2014, 6), 99],
289
[Date.UTC(2014, 7), 98],
290
[Date.UTC(2014, 8), 98],
291
[Date.UTC(2014, 9), 97]
292
];
293
// Level 1 Problems numbers YTD. Columns are ['Month', 'Number of Problems'].
294
var KNSMProblemsData = [
295
[Date.UTC(2014, 0), 45],
296
[Date.UTC(2014, 1), 97],
297
[Date.UTC(2014, 2), 95],
298
[Date.UTC(2014, 3), 87],
299
[Date.UTC(2014, 4), 99],
300
[Date.UTC(2014, 5), 78],
301
[Date.UTC(2014, 6), 99],
302
[Date.UTC(2014, 7), 86],
303
[Date.UTC(2014, 8), 98],
304
[Date.UTC(2014, 9), 97]
305
];
306
// Values to form KNSM report. They are supposed to be just known for each case.
307
// Target budget value. Reads like "target is to spend no more than 1175000 per year".
308
var KNSMBudgetTarget = 1175000;
309
// Target Level 1 Problems count. Reads like "target is to get no more than 100 level 1 problems per month".
310
var KNSMProblemsTarget = 100;
311
// These values represent range borders in percent of the target budget per month:
312
// from 0 to 90 - good case,
313
// from 90 to 110 - excessive case,
314
// from 110 to 150 - critical case.
315
var KNSMExpensesRanges = [0, 90, 110, 150];
316
// These values represent range borders in customers satisfaction level per month:
317
// from 150 to 100 - good case,
318
// from 100 to 80 - excessive case,
319
// from 80 to 0 - critical case.
320
var KNSMSatisfactionRanges = [150, 100, 80, 0];
321
// These values represent range borders in level 1 problems count per month:
322
// from 0 to 90 - good case,
323
// from 90 to 110 - excessive case,
324
// from 110 to 150 - critical case.
325
var KNSMProblemsRanges = [0, 90, 110, 150];
326
// Some Major Project Milestones for MPM report. Columns are ['Project', 'Milestone', 'Due date']
327
var MPMData = [
328
['ERP Upgrade', 'Full system test', Date.UTC(2014, 9, 24)],
329
['Add services data to DW', 'ETL coding', Date.UTC(2014, 9, 10)],
330
['Upgrade mainframe OS', 'Prepare plan', Date.UTC(2014, 9, 17)],
331
['Disaster recovery site', 'Install hardware', Date.UTC(2014, 9, 20)],
332
['Budgeting system', 'Hire team', Date.UTC(2014, 9, 2)],
333
['Web site face-lift', 'Move into production', Date.UTC(2014, 9, 22)]
334
];
335
// Some Projects for TPQ report. Columns are ['Project', 'Status', 'Funding approved', 'Schedule start']
336
var TPQData = [
337
['Professional service module', 'Pending available staff', true, Date.UTC(2014, 9, 22)],
338
['Upgrade MS Office', 'Cost-benefit analysis', false, Date.UTC(2014, 11, 1)],
339
['Failover for ERP', 'Preparing proposal', false, Date.UTC(2015, 0, 30)],
340
['Upgrade data warehouse HW', 'Evaluating options', true, Date.UTC(2015, 1, 13)],
341
['Executive dashboard', 'Vendor assessment', false, Date.UTC(2015, 4, 2)]
342
];
343
// Dashboard "today" date. It is set here like this to make the dashboard look "up to date" with these static data.
344
var Today = new Date(Date.UTC(2014, 9, 15));
345
// endregion
346
347
// region Utility functions
348
/**
349
* Utility function to setup property to a whole row. Samples of usage:
350
* 1) setupRowProp(table, 0, ['content', 'padding', 'left'], 10);
351
* Sets left padding of cell content in row 0 to 10 if there is a content, where left padding can be set in cells.
352
* So its equivalent to call cell.content().padding().left(10) for all cells in 0 row.
353
* 2) setupRowProp(table, 1, 'padding', [0, 1, 2, 3]);
354
* Sets padding of all cells in the 1 row to (0, 1, 2, 3).
355
* Its equivalent to call cell.padding(0, 1, 2, 3) for all cells in row 1.
356
* This two ways of usage can be combined, for example:
357
* setupRowProp(table, 0, ['content', 'padding'], [1, 2, 3, 4]);
358
* It is equivalent to calling cell.content().padding(1, 2, 3, 4) for all cells in row 0.
359
* This method fails if the property chain is incorrect (for example there is no such property you ask).
360
* @param {anychart.elements.Table} table Table to setup row for.
361
* @param {number} rowIndex Row index.
362
* @param {!Array.<string>|string} propNameOrChain Property name to access, like 'border' or chain of property names
363
* to access, e.g. ['content', 'fontSize']. Note: no checking on valid results is done, so it's up to you to
364
* ensure property existence.
365
* @param {*|Array.<*>} propValueOrArray Value or array of values to set.
366
*/
367
function setupRowProp(table, rowIndex, propNameOrChain, propValueOrArray) {
368
// if passed row index is out of passed table - exit
369
if (rowIndex >= table.rowsCount()) return;
370
// normalize propNameOrChain to an array handle in one way
371
if (typeof propNameOrChain == 'string') propNameOrChain = [propNameOrChain];
372
// cache last chain index
373
var chainLastIndex = propNameOrChain.length - 1;
374
// for all column indexes in the table
375
for (var i = 0; i < table.colsCount(); i++) {
376
// get the cell
377
var prop = table.getCell(rowIndex, i);
378
// pass over the cell to the last but one property
379
for (var j = 0; j < chainLastIndex; j++) {
380
var name = propNameOrChain[j];
381
if (name in prop)
382
prop = prop[name]();
383
else
384
prop = null;
385
if (!prop) break;
386
}
387
var lastName = propNameOrChain[chainLastIndex];
388
// if property getter returns null, or there is no such property to call, we skip the cell
389
if (!prop || !(lastName in prop)) continue;
390
// a way to check if propValueOrArray is an array - if it is, we call apply to pass all array elements to a setter
391
if (propValueOrArray != null && typeof propValueOrArray != 'string' && typeof propValueOrArray.length == 'number')
392
prop[lastName].apply(prop, propValueOrArray);
393
// or just call the setter with one parameter
394
else
395
prop[lastName](propValueOrArray);
396
}
397
}
398
399
/**
400
* Utility function to setup property to a whole column. Samples of usage:
401
* 1) setupColProp(table, 0, ['content', 'padding', 'left'], 10);
402
* Sets left padding of cell content in column 0 to 10 if there is a content, where left padding can be set in cells.
403
* So its equivalent to call cell.content().padding().left(10) for all cells in column 0.
404
* 2) setupColProp(table, 1, 'padding', [0, 1, 2, 3]);
405
* Sets padding of all cells in the 1 row to (0, 1, 2, 3).
406
* Its equivalent to call cell.padding(0, 1, 2, 3) for all cells in column 1.
407
* This two ways of usage can be combined, for example:
408
* setupRowProp(table, 0, ['content', 'padding'], [1, 2, 3, 4]);
409
* It is equivalent to calling cell.content().padding(1, 2, 3, 4) for all cells in column 0.
410
* If the last parameter is set to true, than the first row (that can be a header row) will be skipped.
411
* This method fails if the property chain is incorrect (for example there is no such property you ask).
412
* @param {anychart.elements.Table} table Table to setup row for.
413
* @param {number} colIndex Row index.
414
* @param {!Array.<string>|string} propNameOrChain Property name to access, like 'border' or chain of property names
415
* to access, e.g. ['content', 'fontSize']. Note: no checking on valid results is done, so it's up to you to
416
* ensure property existence.
417
* @param {*|Array.<*>} propValueOrArray Value or array of values to set.
418
* @param {boolean=} opt_skipFirstRow Set true to skip first row.
419
*/
420
function setupColProp(table, colIndex, propNameOrChain, propValueOrArray, opt_skipFirstRow) {
421
// if passed column index is out of passed table - exit
422
if (colIndex >= table.colsCount()) return;
423
// normalize propNameOrChain to an array handle in one way
424
if (typeof propNameOrChain == 'string') propNameOrChain = [propNameOrChain];
425
// cache last chain index
426
var chainLastIndex = propNameOrChain.length - 1;
427
// for all rows in the table (except first, if opt_skipFirstRow is true
428
for (var i = opt_skipFirstRow ? 1 : 0; i < table.rowsCount(); i++) {
429
// get the cell
430
var prop = table.getCell(i, colIndex);
431
// pass over the cell to the last but one property
432
for (var j = 0; j < chainLastIndex; j++) {
433
var name = propNameOrChain[j];
434
if (name in prop)
435
prop = prop[name]();
436
else
437
prop = null;
438
if (!prop) break;
439
}
440
var lastName = propNameOrChain[chainLastIndex];
441
// if property getter returns null, or there is no such property to call, we skip the cell
442
if (!prop || !(lastName in prop)) continue;
443
// a way to check if propValueOrArray is an array - if it is, we call apply to pass all array elements to a setter
444
if (propValueOrArray != null && typeof propValueOrArray != 'string' && typeof propValueOrArray.length == 'number')
445
prop[lastName].apply(prop, propValueOrArray);
446
// or just call the setter with one parameter
447
else
448
prop[lastName](propValueOrArray);
449
}
450
}
451
452
/**
453
* Utility function to calculate the sum of field values over a view.
454
* @param {anychart.data.View} view
455
* @param {string} fieldName
456
* @return {number}
457
*/
458
function calcSum(view, fieldName) {
459
var sum = 0;
460
var count = 0;
461
// we iterate over the view and sum up the field value
462
var iter = view.getIterator();
463
while (iter.advance()) {
464
count++;
465
sum += iter.get(fieldName);
466
}
467
return sum;
468
}
469
470
/**
471
* Utility function to calculate the average value of the field over a view.
472
* @param {anychart.data.View} view
473
* @param {string} fieldName
474
* @return {number}
475
*/
476
function calcAvg(view, fieldName) {
477
// we use calcSum() function to get a sum over the field and then divide by the number of rows in the view.
478
return calcSum(view, fieldName) / view.getIterator().getRowsCount();
479
}
480
481
/**
482
* Utility function to get the value of the field in last row in the view.
483
* @param {anychart.data.View} view
484
* @param {string} fieldName
485
* @return {*}
486
*/
487
function getLastFieldValue(view, fieldName) {
488
var iterator = view.getIterator();
489
if (iterator.select(iterator.getRowsCount() - 1))
490
return iterator.get(fieldName);
491
else
492
return undefined;
493
}
494
495
/**
496
* Creates a small tight date time scale (no gaps at all outside of the range).
497
* @return {anychart.scales.DateTime}
498
*/
499
function createTightDTScale() {
500
// removing gaps and ticks to make the line fill the horizontal space of the cell
501
return anychart.scales.dateTime()
502
.minimumGap(0)
503
.maximumGap(0)
504
.ticks([]);
505
}
506
507
/**
508
* Formats the number to the US currency format.
509
* @param {number} value
510
* @param {number=} opt_decimalDigits
511
* @return {string}
512
*/
513
function formatNumber(value, opt_decimalDigits) {
514
// Short way to add thousand separators to the number.
515
return value.toFixed(opt_decimalDigits || 2).replace(/\d(?=(\d{3})+\.)/g, '$&,');
516
}
517
518
/**
519
* Formats the date in manner we need it to be formatted in the sample.
520
* @param {Date} value
521
* @return {string}
522
*/
523
function formatDate(value) {
524
var m = value.getMonth() + 1;
525
if (m < 10) m = '0' + m;
526
var d = value.getDate();
527
if (d < 10) d = '0' + d;
528
var y = value.getFullYear() - 2000;
529
return m + '/' + d + '/' + y;
530
}
531
532
/**
533
* Returns difference between two dates in days. Result is positive, if date2 is later than date1.
534
* @param {Date} date1
535
* @param {Date} date2
536
* @return {number}
537
*/
538
function getDiffInDays(date1, date2) {
539
/**
540
* Returns if a year is a leap year.
541
* @param {number} year
542
* @return {boolean}
543
*/
544
function isLeapYear(year) {
545
// Leap year logic; the 4-100-400 rule
546
return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
547
}
548
549
/**
550
* Returns the number of days in a month. Also needs year to handle leap years.
551
* @param {number} year
552
* @param {number} month
553
* @return {number}
554
*/
555
function getNumberOfDaysInMonth(year, month) {
556
switch (month) {
557
case 1:
558
return isLeapYear(year) ? 29 : 28;
559
case 3:
560
case 5:
561
case 8:
562
case 10:
563
return 30;
564
}
565
return 31;
566
}
567
568
/**
569
* Returns the number of days in a year.
570
* @param {number} year
571
* @return {number}
572
*/
573
function getNumberOfDaysInYear(year) {
574
return isLeapYear(year) ? 366 : 365;
575
}
576
577
// the sign of the difference
578
var sign = 1;
579
// we swap dates if the difference is "negative" to shorten the calculations and remember the sign
580
if (date1.getTime() > date2.getTime()) {
581
var tmp = date1;
582
date1 = date2;
583
date2 = tmp;
584
sign = -1;
585
}
586
// getting some info from the dates.
587
var y1 = date1.getUTCFullYear();
588
var y2 = date2.getUTCFullYear();
589
var m1 = date1.getUTCMonth();
590
var m2 = date2.getUTCMonth();
591
var d1 = date1.getUTCDate();
592
var d2 = date2.getUTCDate();
593
var result = 0, i;
594
// if the dates are from different years
595
if (y1 < y2) {
596
// than we should add all full years to the result
597
for (i = y1 + 1; i < y2; i++)
598
result += getNumberOfDaysInYear(i);
599
// all full months from the rest of the first year of the range
600
for (i = m1 + 1; i < 12; i++)
601
result += getNumberOfDaysInMonth(y1, i);
602
// and all full month from the beginning of the last year of the range
603
for (i = 0; i < m2; i++)
604
result += getNumberOfDaysInMonth(y2, i);
605
// and all days from the rest of the month of the first date
606
result += getNumberOfDaysInMonth(y1, m1) - d1;
607
// and all days from the beginning of the month of the second date
608
result += d2;
609
} else if (m1 < m2) {
610
// else, if the year is the same but month are different
611
// than we should add all full months in the range between the two dates
612
for (i = m1 + 1; i < m2; i++)
613
result += getNumberOfDaysInMonth(y1, i);
614
// and all days from the rest of the month of the first date
615
result += getNumberOfDaysInMonth(y1, m1) - d1;
616
// and all days from the beginning of the month of the second date
617
result += d2;
618
} else {
619
// else we can just substract one date from another and get the result
620
result += d2 - d1;
621
}
622
// and in the end we should take into account the sign of the difference.
623
return result * sign;
624
}
625
626
/**
627
* Creates filtering functions to filter out rows from a view that are not equal to the fieldValue.
628
* It is used in this sample to filter the SARawData by the system.
629
* @param {string} fieldValue
630
* @return {Function}
631
*/
632
function filterBySystem(fieldValue) {
633
return function (value) {
634
return fieldValue == value;
635
}
636
}
637
638
// endregion
639
640
// region Reports
641
/**
642
* Forms System Availability report using passed data.
643
* @param {Array.<Array>} rawData Raw system availability data. See SARawData variable for sample.
644
* @param {Array.<Array>} acceptedAvailabilities Data to form "acceptation ranges". Also responsible for systems order
645
* in the report. See SAAcceptedAvailability variable for sample.
646
* @return {{report: anychart.graphics.vector.Layer, bounds: anychart.math.Rect}} Report object.
647
*/
648
function formSAReport(rawData, acceptedAvailabilities) {
649
// we are not going to need different mapping on the raw data, so we map it at the same time
650
// we use 'System' and 'Availability' fields in our calculations and 'x' and 'value' fields are used by line charts.
651
var rawView = anychart.data.set(rawData).mapAs({'System': [0], 'Availability': [2], 'x': [1], 'value': [2]});
652
653
// we use common scales for charts in columns to make them comparable
654
var bulletScale = anychart.scales.linear();
655
// settings manual minimum and maximum to show the range we need.
656
bulletScale
657
.minimum(85)
658
.maximum(100);
659
660
// we use common scale for lines also
661
var lineScale = createTightDTScale();
662
663
// we create a markers factory to draw some markers and configure it here
664
var markers = anychart.elements.markersFactory();
665
markers
666
.anchor('center')
667
.position('center')
668
.type('circle')
669
.fill('#c00')
670
.stroke('2 #900')
671
.size(5);
672
// to use markers in a table we don't need call markers.draw() at all - we just need to add a marker obtained by
673
// markers.add(null) call to cell contents, the table will do the rest
674
675
// Preparing report data.
676
// We will build a table using table.contents() method, so we need to build table contents first
677
// Here we make a header row contents. There will be 5 columns in the table
678
var contents = [['Last 12 Month', null, 'System', 'Availability %', null]];
679
// Now we will form row contents for each system listed in acceptedAvailabilities
680
for (var i = 0; i < acceptedAvailabilities.length; i++) {
681
// system name stored in first column
682
var system = acceptedAvailabilities[i][0];
683
// accepted availability value - in second
684
var availability = acceptedAvailabilities[i][1];
685
686
// preparing data for the row
687
// we filter the main data set by the system name and get the view that contain rows only for the current system.
688
var systemData = rawView.filter('System', filterBySystem(system));
689
// and calculate average availability for that filtered view.
690
var avgAvailability = calcAvg(systemData, 'Availability');
691
692
// we will need one line chart per system
693
// we don't need any other chart elements besides the chart line, so we can use line series directly here.
694
var line = anychart.cartesian.series.line(systemData);
695
// we set come line properties to make it look better and also we set the common x scale
696
line
697
.stroke('2 #000')
698
.xScale(lineScale);
699
// we don't want a tooltip on these lines
700
line.tooltip().enabled(false);
701
// we do not call .draw() method here, because the table will do it for us
702
703
// if average availability is less than accepted for the system, we will place a marker in the row
704
var marker;
705
if (avgAvailability < availability)
706
marker = markers.add(null);
707
else
708
marker = null;
709
// we do not call .draw() method here and we won't call markers.draw(), because the table will do it for us
710
711
// we will also need one bullet chart per system to show current situation. We create it and setup.
712
var bullet = anychart.bullet.chart([{'value': avgAvailability, 'type': 'line', 'gap': 0.4}]);
713
bullet
714
.scale(bulletScale)
715
.padding(0)
716
.margin(0);
717
bullet.range(0)
718
.from(availability)
719
.to(100)
720
.fill('#ccc');
721
bullet.title().enabled(false);
722
bullet.axis().enabled(false);
723
bullet.background()
724
.enabled(true)
725
.stroke('#ccc')
726
.fill('#fff');
727
// we do not call .draw() method here, because the table will do it for us
728
729
// and finally we form and push next row to table contents array.
730
contents.push([line, marker, system, bullet, avgAvailability.toFixed(1) + '%']);
731
}
732
733
// we wil also need an axis to show bullet scale, because they are usless without it
734
// so we create and set it up here
735
var axis = anychart.elements.axis();
736
axis
737
.scale(bulletScale)
738
.orientation('bottom')
739
.staggerMode(false)
740
.stroke('#ccc');
741
axis.ticks().stroke('#ccc');
742
axis.minorTicks().enabled(false);
743
axis.title().enabled(false);
744
axis.labels()
745
.fontSize('9px')
746
.textFormatter(function (value) {
747
return value['tickValue'] + '%';
748
});
749
// and form a row that will contain our axis
750
contents.push([null, null, null, axis, null]);
751
752
// now, as we have our table contents prepared, we can form a layer that will contain the report
753
// we create a layer
754
var container = anychart.graphics.layer();
755
// we create some variables to handle report bounds information
756
var titleHeight = 20;
757
var tableHeight = (contents.length - 1) * 24 + 20;
758
var reportBounds = anychart.math.rect(0, 0, 357, tableHeight + titleHeight);
759
760
// we create report title, set it up and draw it to the layer
761
var title = anychart.elements.title();
762
title
763
.container(container)
764
.parentBounds(reportBounds)
765
.fontWeight('normal')
766
.text('<span style="color:#ED8C2B; font-size: 15px;">System Availability</span> <span style="color: #666666; font-size: 10px; font-weight: normal;">(last 30 days)</span>')
767
.orientation('top')
768
.align('left')
769
.vAlign('bottom')
770
.margin(0)
771
.padding(0)
772
.height(titleHeight)
773
.useHtml(true)
774
.draw();
775
776
// we also create a legend for our bullet charts and place it into the title
777
var legendParentBounds = anychart.math.rect(reportBounds.left, reportBounds.top, reportBounds.width, titleHeight);
778
var legend = anychart.elements.legend();
779
// we use custom legend items and custom icon drawers here
780
legend.itemsProvider([
781
{
782
'index': 0,
783
'text': 'Actual',
784
'iconType': function (path, size) {
785
path.clear();
786
var x = Math.round(size / 2);
787
var y = Math.round(size / 2);
788
var height = size * 0.6;
789
path.clear()
790
.moveTo(x, y - height / 2)
791
.lineTo(x, y + height / 2)
792
.lineTo(x + 2, y + height / 2)
793
.lineTo(x + 2, y - height / 2)
794
.close();
795
},
796
'iconStroke': 'none',
797
'iconFill': '#000'
798
},
799
{
800
'index': 1,
801
'text': 'Acceptable',
802
'iconType': function (path, size) {
803
path.clear();
804
var x = Math.round(size / 2);
805
var y = Math.round(size / 2);
806
var height = size * 0.8;
807
path.clear()
808
.moveTo(x - 2, y - height / 2)
809
.lineTo(x - 2, y + height / 2)
810
.lineTo(x + 3, y + height / 2)
811
.lineTo(x + 3, y - height / 2)
812
.close();
813
},
814
'iconStroke': 'none',
815
'iconFill': '#ccc'
816
}
817
]);
818
legend
819
.fontSize('9px')
820
.fontFamily('trebuchet, helvetica, arial, sans-serif')
821
.itemsLayout('horizontal')
822
.iconTextSpacing(0)
823
.container(container)
824
.align('right')
825
.position('bottom')
826
.padding(0)
827
.margin(0)
828
.itemsSpacing(0)
829
.parentBounds(legendParentBounds);
830
legend.title().enabled(false);
831
legend.titleSeparator().enabled(false);
832
legend.paginator().enabled(false);
833
legend.background().enabled(false);
834
legend.draw();
835
836
// we finally create, setup and draw the table to the layer
837
var table = anychart.elements.table();
838
table
839
.container(container)
840
.top(title.getRemainingBounds().getTop())
841
.height(tableHeight)
842
.width(reportBounds.width)
843
.contents(contents)
844
.rowHeight(0, 20)
845
.colWidth(0, 93)
846
.colWidth(1, 20)
847
.colWidth(2, 106)
848
.colWidth(3, 95)
849
.colWidth(4, 43)
850
.cellBorder(null);
851
table.cellTextFactory()
852
.padding(0)
853
.vAlign('center')
854
.hAlign('left');
855
table.getCell(table.rowsCount() - 1, 3).padding(0, 2, 5);
856
table.getCell(0, 3)
857
.colSpan(2)
858
.content()
859
.hAlign('right');
860
// we use our utility method here to setup some properties for entire rows and columns
861
setupRowProp(table, 0, 'bottomBorder', '2 #ccc');
862
setupRowProp(table, 0, ['content', 'fontFamily'], 'verdana, helvetica, arial, sans-serif');
863
setupRowProp(table, 0, ['content', 'vAlign'], 'bottom');
864
setupRowProp(table, 0, ['padding', 'bottom'], 2);
865
setupColProp(table, 3, 'padding', [5, 2, 4], true);
866
setupColProp(table, 4, ['content', 'hAlign'], 'right', true);
867
// and draw the table
868
table.draw();
869
870
// we return an object with both report layer and desired bounds
871
return {
872
'report': container,
873
'bounds': reportBounds
874
};
875
}
876
877
/**
878
* Forms Hardware Capacity report using passed data.
879
* @param {Array.<Array>} CPUData Raw CPU Capacity data.
880
* @param {Array.<Array>} StorageData Raw Storage Capacity data.
881
* @param {Array.<Array>} NetworkData Raw Network Capacity data.
882
* @param {Array} CPURanges Data used to make "acceptation ranges" for CPU capacity levels.
883
* @param {Array} StorageRanges Data used to make "acceptation ranges" for Storage capacity levels.
884
* @param {Array} NetworkRanges Data used to make "acceptation ranges" for Storage capacity levels.
885
* @return {{report: anychart.graphics.vector.Layer, bounds: anychart.math.Rect}} Report object.
886
*/
887
function formHCReport(CPUData, StorageData, NetworkData, CPURanges, StorageRanges, NetworkRanges) {
888
// we declare some variables to collect content elements for the report table
889
var lines = [], bullets = [], averages = [];
890
891
// we use a common bullet scale like in the previous report to make them comparable
892
var bulletScale = anychart.scales.linear();
893
// we setup minimum and maximum because we know what range we want to show
894
bulletScale
895
.minimum(0)
896
.maximum(100);
897
// also we setup ticks count to make our tiny axis that will use this scale look better
898
bulletScale.ticks()
899
.count(3);
900
bulletScale.minorTicks()
901
.count(3);
902
903
// we make almost the same manipulation for all three metrics that we want to show in this report
904
// (CPU, storage, network), so we can make them in a "for", collecting results to arrays declared before
905
for (var i = 0; i < 3; i++) {
906
// we create a data set and map in a default way, because it suites us well in this case (we have an array of
907
// arrays in our incomming data, where in the first column there is 'x' and in the second column there is 'value')
908
var data = anychart.data.set(arguments[i]).mapAs();
909
910
// we calculate average value of capacity to show it in the table
911
var avg = calcAvg(data, 'value');
912
913
// we will need one line chart per hardware system
914
// we don't need any other chart elements besides the chart line, so we can use line series directly here.
915
var line = anychart.cartesian.series.line(data);
916
// we set come line properties to make it look better and also we set the common x scale
917
line
918
.stroke('2 #000')
919
.xScale(createTightDTScale());
920
// we don't want a tooltip on these lines
921
line
922
.tooltip()
923
.enabled(false);
924
// we do not call .draw() method here, because the table will do it for us
925
926
// we cache ranges array corresponding to current hardware system
927
var ranges = arguments[3 + i];
928
// we will also need one bullet chart per system
929
var bullet = anychart.bullet.chart([{'value': avg, 'type': 'bar', gap: 0.6}]);
930
// we create some ranges using passed ranges array to show what levels of used capacity are good and what are not
931
bullet.range(0)
932
.from(ranges[0])
933
.to(ranges[1])
934
.fill('#ccc');
935
bullet.range(1)
936
.from(ranges[1])
937
.to(ranges[2])
938
.fill('#aaa');
939
bullet.range(2)
940
.from(ranges[2])
941
.to(ranges[3])
942
.fill('#666');
943
// we also setup some apparance settings of the bullet chart to make it look better
944
bullet
945
.scale(bulletScale)
946
.padding(0)
947
.margin(0);
948
bullet.title().enabled(false);
949
bullet.axis().enabled(false);
950
bullet.background().enabled(false);
951
// we do not call .draw() method here, because the table will do it for us
952
953
// we push elements we made to proper arrays
954
lines.push(line);
955
bullets.push(bullet);
956
averages.push(avg.toFixed() + '%');
957
}
958
959
// we will need an axis to show scale values for bullet charts, so we create it and set it up
960
var axis = anychart.elements.axis();
961
axis
962
.scale(bulletScale)
963
.orientation('bottom')
964
.stroke('#ccc');
965
axis.title().enabled(false);
966
axis.ticks().stroke('#ccc');
967
axis.minorTicks()
968
.enabled(true)
969
.stroke('#ccc');
970
axis.labels()
971
.fontSize('9px')
972
.textFormatter(function (value) {
973
return value['tickValue'] + '%';
974
});
975
976
// now we have all we need to prepare table contents array - the shortest way to create a table
977
var contents = [
978
['CPU', 'Today', lines[0], 'Overall', bullets[0], averages[0]],
979
['Storage', 'Last 12 Mo.', lines[1], 'Today', bullets[1], averages[1]],
980
['Network', 'Last 12 Mo.', lines[2], 'Today', bullets[2], averages[2]],
981
[null, null, null, null, axis, null]
982
];
983
984
// now we create a report layer and declare some info about report bounds
985
var container = anychart.graphics.layer();
986
var titleHeight = 20;
987
var tableHeight = (contents.length - 1) * 18 + 24;
988
var reportBounds = anychart.math.rect(0, 0, 357, tableHeight + titleHeight);
989
990
// we create, setup and draw report title to the layer
991
var title = anychart.elements.title();
992
title
993
.container(container)
994
.parentBounds(reportBounds)
995
.fontFamily('trebuchet, helvetica, arial, sans-serif')
996
.fontWeight('normal')
997
.fontSize('15px')
998
.fontColor('#ED8C2B')
999
.text('Hardware % of Capacity')
1000
.orientation('top')
1001
.align('left')
1002
.vAlign('bottom')
1003
.margin(0)
1004
.padding(0, 0, 2, 0)
1005
.height(titleHeight)
1006
.useHtml(false)
1007
.draw();
1008
1009
// then we create, setup and draw a custom legend for bullet charts to make them more useful
1010
var legend = anychart.elements.legend();
1011
// we setup legend items first using custom icon drawers
1012
legend.itemsProvider([
1013
{
1014
'index': 0,
1015
'text': 'Actual',
1016
'iconType': function (path, size) {
1017
var x = Math.round(size / 2);
1018
var y = Math.round(size / 2);
1019
var width = size * 0.6;
1020
path
1021
.clear()
1022
.moveTo(x - width / 2, y - 2)
1023
.lineTo(x + width / 2, y - 2)
1024
.lineTo(x + width / 2, y + 1)
1025
.lineTo(x - width / 2, y + 1)
1026
.close();
1027
},
1028
'iconStroke': 'none',
1029
'iconFill': '#000'
1030
},
1031
{
1032
'index': 1,
1033
'text': 'Good',
1034
'iconType': function (path, size) {
1035
var x = Math.round(size / 2);
1036
var y = Math.round(size / 2);
1037
var height = size * 0.8;
1038
path
1039
.clear()
1040
.moveTo(x - 2, y - height / 2)
1041
.lineTo(x - 2, y + height / 2)
1042
.lineTo(x + 3, y + height / 2)
1043
.lineTo(x + 3, y - height / 2)
1044
.close();
1045
},
1046
'iconStroke': 'none',
1047
'iconFill': '#ccc'
1048
},
1049
{
1050
'index': 2,
1051
'text': 'Excessive',
1052
'iconType': function (path, size) {
1053
var x = Math.round(size / 2);
1054
var y = Math.round(size / 2);
1055
var height = size * 0.8;
1056
path
1057
.clear()
1058
.moveTo(x - 2, y - height / 2)
1059
.lineTo(x - 2, y + height / 2)
1060
.lineTo(x + 3, y + height / 2)
1061
.lineTo(x + 3, y - height / 2)
1062
.close();
1063
},
1064
'iconStroke': 'none',
1065
'iconFill': '#aaa'
1066
},
1067
{
1068
'index': 3,
1069
'text': 'Critical',
1070
'iconType': function (path, size) {
1071
var x = Math.round(size / 2);
1072
var y = Math.round(size / 2);
1073
var height = size * 0.8;
1074
path
1075
.clear()
1076
.moveTo(x - 2, y - height / 2)
1077
.lineTo(x - 2, y + height / 2)
1078
.lineTo(x + 3, y + height / 2)
1079
.lineTo(x + 3, y - height / 2)
1080
.close();
1081
},
1082
'iconStroke': 'none',
1083
'iconFill': '#666'
1084
}
1085
]);
1086
// then we setup some legend appearance setting to position it and make it look nice
1087
legend
1088
.fontSize('9px')
1089
.fontFamily('trebuchet, helvetica, arial, sans-serif')
1090
.itemsLayout('horizontal')
1091
.iconTextSpacing(0)
1092
.container(container)
1093
.align('right')
1094
.position('bottom')
1095
.padding(0, 0, 2, 0)
1096
.margin(0)
1097
.itemsSpacing(0)
1098
.parentBounds(anychart.math.rect(reportBounds.left, reportBounds.top, reportBounds.width, titleHeight));
1099
legend.title().enabled(false);
1100
legend.titleSeparator().enabled(false);
1101
legend.paginator().enabled(false);
1102
legend.background().enabled(false);
1103
legend.draw();
1104
1105
// and finally we create, setup and draw the report body - the table
1106
var table = anychart.elements.table();
1107
table
1108
.container(container)
1109
.top(title.getRemainingBounds().top + 2)
1110
.height(tableHeight)
1111
.width(reportBounds.width)
1112
.contents(contents)
1113
.rowHeight(table.rowsCount() - 1, 24)
1114
.colWidth(0, 60)
1115
.colWidth(1, 60)
1116
.colWidth(2, 77)
1117
.colWidth(3, 50)
1118
.colWidth(4, 80)
1119
.colWidth(5, 30)
1120
.cellBorder(null);
1121
table.cellTextFactory()
1122
.padding(0)
1123
.vAlign('center')
1124
.hAlign('left')
1125
.fontSize('10px');
1126
// we use our utility method to setup some properties to entier rows and columns
1127
setupRowProp(table, 0, 'topBorder', '2 #ccc');
1128
setupColProp(table, 0, ['content', 'fontSize'], '12px', false);
1129
setupColProp(table, 2, 'padding', [3, 3], false);
1130
setupColProp(table, 3, 'padding', [0, 0, 0, 8], false);
1131
setupColProp(table, 4, 'padding', [5, 2, 4], false);
1132
setupColProp(table, 5, ['content', 'hAlign'], 'right', false);
1133
setupColProp(table, 5, ['content', 'fontSize'], '11px', false);
1134
table.draw();
1135
1136
// we return an object with both report layer and desired bounds
1137
return {
1138
'report': container,
1139
'bounds': reportBounds
1140
};
1141
}
1142
1143
/**
1144
* Forms Daily Network Traffic report using passed data.
1145
* @param {Array.<Array>} sixMonthsData Average daily traffic for last 6 months.
1146
* @param {Array.<Array>} weekData Average daily traffic for last week.
1147
* @param {Array.<Array>} yesterdayData Average daily traffic for last day.
1148
* @return {{report: anychart.graphics.vector.Layer, bounds: anychart.math.Rect}} Report object.
1149
*/
1150
function formDNTReport(sixMonthsData, weekData, yesterdayData) {
1151
// we create a report layer and declare some info about report bounds
1152
var container = anychart.graphics.layer();
1153
var titleHeight = 20;
1154
var chartHeight = 140;
1155
var reportBounds = anychart.math.rect(0, 0, 357, chartHeight + titleHeight);
1156
1157
// now we create, setup and draw report title
1158
var title = anychart.elements.title();
1159
title
1160
.container(container)
1161
.parentBounds(reportBounds)
1162
.fontFamily('trebuchet, helvetica, arial, sans-serif')
1163
.fontWeight('normal')
1164
.text('<span style="color:#ED8C2B; font-size: 15px;">Daily Network Traffic</span> <span style="color: #666666; font-size: 10px; font-weight: normal;">(kilobytes)</span>')
1165
.orientation('top')
1166
.align('left')
1167
.vAlign('bottom')
1168
.margin(0)
1169
.padding(0, 0, 2, 0)
1170
.height(titleHeight)
1171
.useHtml(true)
1172
.draw();
1173
1174
// we cannot use table borders to draw this grey line, so we draw it using native graphics path
1175
container.path()
1176
.moveTo(0, titleHeight)
1177
.lineTo(reportBounds.width, titleHeight)
1178
.stroke('2 #ccc');
1179
1180
// Now we will create the main object of this report - the chart
1181
// We want to do a rather complex thing here - we want the plotting area where series are drawn to have a top border.
1182
// We can't use background here, because plotting area itself has no background. So we are forced to use top axis,
1183
// but we don't want any ticks or labels on it, so we will turn off all that staff.
1184
// Also we want to show 24 points with 25 labels on the bottom axis - to place each data point between two ticks.
1185
// The only way to do it - to make different scales for data and for the bottom axis.
1186
// Also we want custom label formats for the axis and only 3 lines of the grid (it means 5 ticks on our 25-ticks
1187
// scale, or 6 hours tick interval). Let's see how we can do all that staff.
1188
1189
// let's create our custom scale for bottom axis and x-grid first:
1190
// we make a date-time scale and set fixed minimum and maximum for it
1191
var xAxisScale = anychart.scales.dateTime();
1192
// it doesn't matter what date we set - only the time matters, so let's make it from 01/01/1900 to 01/02/1900
1193
xAxisScale.minimum(Date.UTC(0, 0, 1, 0));
1194
xAxisScale.maximum(Date.UTC(0, 0, 2, 0));
1195
// we set major and minor tick intervals to 6 and 1 hours respectively
1196
xAxisScale.ticks().interval(0, 0, 0, 6);
1197
xAxisScale.minorTicks().interval(0, 0, 0, 1);
1198
1199
// now we create the chart and setup it's bounds.
1200
var chart = anychart.cartesianChart();
1201
chart
1202
.container(container)
1203
.left(0)
1204
.top(titleHeight)
1205
.width(reportBounds.width)
1206
.height(chartHeight);
1207
1208
// first we will create series we need
1209
// we will use palette to setup series colors:
1210
chart.palette(['#aaa', '#666', '#000']);
1211
var line1 = chart.line(sixMonthsData);
1212
line1.name('Daily mean for last 6 month');
1213
line1.markers().enabled(false);
1214
line1.tooltip().enabled(false);
1215
var line2 = chart.line(weekData);
1216
line2.name('Daily mean for last 7 days');
1217
line2.markers().enabled(false);
1218
line2.tooltip().enabled(false);
1219
var line3 = chart.line(yesterdayData);
1220
line3.name('Yesterday');
1221
line3.markers().enabled(false);
1222
line3.tooltip().enabled(false);
1223
1224
// then we will setup the bottom axis:
1225
// we create it, as it doesn't exist yet
1226
var bottomAxis = chart.xAxis(0);
1227
// we setup our custom scale for the axis and make some appearance improvements
1228
bottomAxis
1229
.scale(xAxisScale)
1230
.staggerMode(false)
1231
.overlapMode('allowOverlap')
1232
.stroke('#ccc');
1233
// we switch off axis title and all ticks, as we don't need them
1234
bottomAxis.title().enabled(false);
1235
bottomAxis.ticks().enabled(false);
1236
bottomAxis.minorTicks().enabled(false);
1237
// now we will setup minor axis labels - we want them to show just hours in 12-hours system and nothing more
1238
bottomAxis.minorLabels()
1239
.enabled(true)
1240
.textFormatter(function (value) {
1241
var date = new Date(value['tickValue']);
1242
var h = date.getUTCHours() % 12;
1243
return h || 12;
1244
});
1245
// now we will setup major axis labels - we know, that our custom scale has 5 ticks:
1246
// 12AM, 6AM, 12PM, 6PM and 12AM again
1247
// we want major labels to look like minor, but to show AM and PM strings below on first 12AM and 12PM ticks,
1248
// so we do that using custom textFormatter:
1249
bottomAxis.labels()
1250
.enabled(true)
1251
.textFormatter(function (value) {
1252
var date = new Date(value['tickValue']);
1253
var hour = date.getUTCHours();
1254
var h = (hour % 12) || 12;
1255
if (hour == 0 && date.getUTCDay() == 1)
1256
return h + '\nAM';
1257
else if (hour == 12)
1258
return h + '\nPM';
1259
else
1260
return h;
1261
});
1262
1263
// now we can setup a grid
1264
// we use our custom scale in it to fit axis labels and grid lines
1265
chart.grid()
1266
.scale(xAxisScale)
1267
.oddFill(null)
1268
.evenFill(null)
1269
.stroke('#ccc')
1270
.layout('vertical');
1271
1272
// then we will setup our top "border" - the top axis
1273
// we create it and turn off all it's elements except the axis line
1274
var topAxis = chart.xAxis(1);
1275
topAxis
1276
.stroke('#ccc')
1277
.orientation('top');
1278
topAxis.ticks().enabled(false);
1279
topAxis.minorTicks().enabled(false);
1280
topAxis.title().enabled(false);
1281
topAxis.labels().enabled(false);
1282
topAxis.minorLabels().enabled(false);
1283
1284
// now we will setup Y scale and axis:
1285
chart.yScale()
1286
.maximumGap(0)
1287
.minimumGap(0);
1288
var leftAxis = chart.yAxis();
1289
leftAxis.stroke('#ccc');
1290
leftAxis.title().enabled(false);
1291
leftAxis.ticks().stroke('#ccc');
1292
leftAxis.minorTicks().enabled(false);
1293
// we want custom formatting on Y axis labels, because it needs less space
1294
leftAxis.labels().textFormatter(function (value) {
1295
return (value['tickValue'] / 1000).toFixed(0) + 'K';
1296
});
1297
1298
// the last thing we need to setup for this chart is the legend
1299
var legend = chart.legend();
1300
legend
1301
.enabled(true)
1302
.fontSize('9px')
1303
.fontFamily('trebuchet, helvetica, arial, sans-serif')
1304
.itemsLayout('horizontal')
1305
.iconTextSpacing(3)
1306
.itemsSpacing(4)
1307
.align('right')
1308
.position('top')
1309
.padding(0, 0, 2, 0)
1310
.margin(3, 0);
1311
// we disable other legend elements, as we don't need them
1312
legend.background().enabled(false);
1313
legend.title().enabled(false);
1314
legend.titleSeparator().enabled(false);
1315
legend.paginator().enabled(false);
1316
legend.tooltip().enabled(false);
1317
1318
// finally we draw the chart to the layer
1319
chart.draw();
1320
1321
// we return an object with both report layer and desired bounds
1322
return {
1323
'report': container,
1324
'bounds': reportBounds
1325
};
1326
}
1327
1328
/**
1329
* Forms Key Non-System Metrics report using passed data.
1330
* @param {Array.<Array>} expensesData Raw expenses data.
1331
* @param {Array.<Array>} satisfactionData Raw customers satisfaction data.
1332
* @param {Array.<Array>} problemsData Raw level 1 problems data.
1333
* @param {Array} expensesRanges Data used to make "acceptation ranges" for company expenses levels.
1334
* @param {Array} satisfactionRanges Data used to make "acceptation ranges" for customer satisfaction levels.
1335
* @param {Array} problemsRanges Data used to make "acceptation ranges" for level 1 problems levels.
1336
* @param {number} budget Year budget value.
1337
* @param {number} problemsTarget Problems planned level.
1338
* @return {{report: anychart.graphics.vector.Layer, bounds: anychart.math.Rect}} Report object.
1339
*/
1340
function formKNSMReport(expensesData, satisfactionData, problemsData, expensesRanges, satisfactionRanges,
1341
problemsRanges, budget, problemsTarget) {
1342
// as the first step we should map passed raw data arrays to be able to work with them
1343
var views = [
1344
anychart.data.set(expensesData).mapAs(),
1345
anychart.data.set(satisfactionData).mapAs(),
1346
anychart.data.set(problemsData).mapAs()
1347
];
1348
// then we prepare some data we want to show in the report using our utility methods we declared above
1349
var actualExpenses = calcSum(views[0], 'value');
1350
var actualSatisfaction = getLastFieldValue(views[1], 'value');
1351
var actualProblems = calcAvg(views[2], 'value');
1352
// we will use these values to determing metrics health
1353
var actualValues = [
1354
actualExpenses / (budget / expensesData.length * 12) * 100,
1355
actualSatisfaction,
1356
actualProblems
1357
];
1358
// these texts will be shown in the last column
1359
var actualTexts = [
1360
'$' + formatNumber(actualExpenses / 1000, 1) + 'K',
1361
actualSatisfaction + '/100',
1362
actualProblems.toFixed(0)
1363
];
1364
// these values will be used to setup range markers for line charts in the first column
1365
var rangesForLines = [
1366
[0, budget / 12],
1367
[satisfactionRanges[0], satisfactionRanges[1]],
1368
[problemsRanges[0] / 100 * problemsTarget, problemsRanges[2] / 100 * problemsTarget]
1369
];
1370
// these strings will be shown in the third column
1371
var metrics = [
1372
'Expenses YTD',
1373
'Customer Satisfaction',
1374
'Level 1 Problems'
1375
];
1376
1377
// we use common scales for charts in columns to make them comparable
1378
var bulletScale = anychart.scales.linear();
1379
// we set manual minimum and maximum to show the range we need.
1380
bulletScale
1381
.minimum(0)
1382
.maximum(150);
1383
1384
// we create a markers factory to draw some markers and configure it here
1385
var markers = anychart.elements.markersFactory();
1386
markers
1387
.anchor('center')
1388
.position('center')
1389
.type('circle')
1390
.fill('#c00')
1391
.stroke('2 #900')
1392
.size(5);
1393
1394
// preparing report table contents
1395
var contents = [['Year-to-Date', null, 'Metric', '% of Target', 'Actual']];
1396
// forming a row for each system
1397
for (var i = 0; i < 3; i++) {
1398
// preparing data for the row
1399
var ranges = arguments[i + 3];
1400
1401
// we want to show a range marker for each metric, so we can't use just standalone series - we need charts
1402
// so we create and empty cartesian chart
1403
var chart = anychart.cartesianChart();
1404
// setup X scale
1405
chart.xScale(createTightDTScale());
1406
// create and setup a line on it
1407
var line = chart.line(views[i]);
1408
line.stroke('2 #000');
1409
line.markers().enabled(false);
1410
line.tooltip().enabled(false);
1411
// and create and setup a range marker on it (it has horizontal layout by default)
1412
chart.rangeMarker(0)
1413
.from(rangesForLines[i][0])
1414
.to(rangesForLines[i][1]);
1415
// and also we enable chart background to show chart borders
1416
chart.background()
1417
.enabled(true)
1418
.stroke('#ccc')
1419
.fill('none');
1420
1421
// now we deside whether to show a marker for the row or not using our data ranges
1422
var marker;
1423
if (actualValues[i] > Math.max(ranges[0], ranges[1]) || actualValues[i] < Math.min(ranges[0], ranges[1]))
1424
marker = markers.add(null);
1425
else
1426
marker = null;
1427
1428
// we will also need one bullet chart per system
1429
// so we crete it
1430
var bullet = anychart.bullet.chart([{'value': actualValues[i], 'type': 'bar', 'gap': 0.6}]);
1431
// setup the scale and some appearance settings
1432
bullet
1433
.scale(bulletScale)
1434
.padding(0)
1435
.margin(0);
1436
// create three ranges that we need to show and configure them
1437
bullet.range(0)
1438
.from(ranges[0])
1439
.to(ranges[1])
1440
.fill('#ccc');
1441
bullet.range(1)
1442
.from(ranges[1])
1443
.to(ranges[2])
1444
.fill('#aaa');
1445
bullet.range(2)
1446
.from(ranges[2])
1447
.to(ranges[3])
1448
.fill('#666');
1449
// and disable chart elements we don't need
1450
bullet.title().enabled(false);
1451
bullet.axis().enabled(false);
1452
bullet.background().enabled(false);
1453
1454
// and add the row we just made contents for to an array
1455
contents.push([chart, marker, metrics[i], bullet, actualTexts[i]]);
1456
}
1457
1458
// also we need an axis to show common bullet scale ticks so we create it and set it up here
1459
var axis = anychart.elements.axis();
1460
axis
1461
.scale(bulletScale)
1462
.orientation('bottom')
1463
.staggerMode(false)
1464
.overlapMode('allowOverlap')
1465
.stroke('#ccc');
1466
axis.title().enabled(false);
1467
axis.ticks().stroke('#ccc');
1468
axis.minorTicks().enabled(false);
1469
axis.labels()
1470
.fontSize('9px')
1471
.textFormatter(function (value) {
1472
return value['tickValue'] + '%';
1473
});
1474
1475
// and add it to table contents array
1476
contents.push([null, null, null, axis, null]);
1477
1478
// now we declare some variables to create report layer and store some info about report bounds
1479
var container = anychart.graphics.layer();
1480
var titleHeight = 20;
1481
var tableHeight = (contents.length - 1) * 24 + 20;
1482
var reportBounds = anychart.math.rect(0, 0, 380, tableHeight + titleHeight);
1483
1484
// we will need a title for the report, so we create, setup and draw it to the report layer
1485
var title = anychart.elements.title();
1486
title
1487
.container(container)
1488
.parentBounds(reportBounds)
1489
.fontFamily('trebuchet, helvetica, arial, sans-serif')
1490
.fontWeight('normal')
1491
.fontSize('15px')
1492
.fontColor('#ED8C2B')
1493
.text('Key Non-System Metrics')
1494
.orientation('top')
1495
.align('left')
1496
.vAlign('bottom')
1497
.margin(0)
1498
.padding(0, 0, 2, 0)
1499
.height(titleHeight)
1500
.useHtml(false)
1501
.draw();
1502
1503
// also we will need a legend for bullet charts, so we create, setup and draw it to the report layer
1504
// this legend has custom drawers for legend item icons, as you can see below
1505
var legendParentBounds = anychart.math.rect(reportBounds.left, reportBounds.top, reportBounds.width, titleHeight);
1506
var legend = anychart.elements.legend();
1507
legend
1508
.container(container)
1509
.parentBounds(legendParentBounds)
1510
.itemsLayout('horizontal')
1511
.iconTextSpacing(0)
1512
.itemsSpacing(0)
1513
.align('right')
1514
.position('bottom')
1515
.padding(0, 0, 2, 0)
1516
.margin(0)
1517
.fontSize('9px')
1518
.fontFamily('trebuchet, helvetica, arial, sans-serif')
1519
.itemsProvider([
1520
{
1521
'index': 0,
1522
'text': 'Actual',
1523
'iconType': function (path, size) {
1524
path.clear();
1525
var x = Math.round(size / 2);
1526
var y = Math.round(size / 2);
1527
var width = size * 0.6;
1528
path.clear()
1529
.moveTo(x - width / 2, y - 2)
1530
.lineTo(x + width / 2, y - 2)
1531
.lineTo(x + width / 2, y + 1)
1532
.lineTo(x - width / 2, y + 1)
1533
.close();
1534
},
1535
'iconStroke': 'none',
1536
'iconFill': '#000'
1537
},
1538
{
1539
'index': 1,
1540
'text': 'Good',
1541
'iconType': function (path, size) {
1542
path.clear();
1543
var x = Math.round(size / 2);
1544
var y = Math.round(size / 2);
1545
var height = size * 0.8;
1546
path.clear()
1547
.moveTo(x - 2, y - height / 2)
1548
.lineTo(x - 2, y + height / 2)
1549
.lineTo(x + 3, y + height / 2)
1550
.lineTo(x + 3, y - height / 2)
1551
.close();
1552
},
1553
'iconStroke': 'none',
1554
'iconFill': '#ccc'
1555
},
1556
{
1557
'index': 2,
1558
'text': 'Excessive',
1559
'iconType': function (path, size) {
1560
path.clear();
1561
var x = Math.round(size / 2);
1562
var y = Math.round(size / 2);
1563
var height = size * 0.8;
1564
path.clear()
1565
.moveTo(x - 2, y - height / 2)
1566
.lineTo(x - 2, y + height / 2)
1567
.lineTo(x + 3, y + height / 2)
1568
.lineTo(x + 3, y - height / 2)
1569
.close();
1570
},
1571
'iconStroke': 'none',
1572
'iconFill': '#aaa'
1573
},
1574
{
1575
'index': 3,
1576
'text': 'Critical',
1577
'iconType': function (path, size) {
1578
path.clear();
1579
var x = Math.round(size / 2);
1580
var y = Math.round(size / 2);
1581
var height = size * 0.8;
1582
path.clear()
1583
.moveTo(x - 2, y - height / 2)
1584
.lineTo(x - 2, y + height / 2)
1585
.lineTo(x + 3, y + height / 2)
1586
.lineTo(x + 3, y - height / 2)
1587
.close();
1588
},
1589
'iconStroke': 'none',
1590
'iconFill': '#666'
1591
}
1592
]);
1593
legend.background().enabled(false);
1594
legend.title().enabled(false);
1595
legend.titleSeparator().enabled(false);
1596
legend.paginator().enabled(false);
1597
legend.draw();
1598
1599
// finally we can create the main report object - the table
1600
var table = anychart.elements.table();
1601
// we setup some nessessary properties of the table to tell it how and where to be drawn
1602
table
1603
.container(container)
1604
.top(title.getRemainingBounds().getTop())
1605
.height(tableHeight)
1606
.width(reportBounds.width)
1607
.contents(contents)
1608
.rowHeight(0, 20)
1609
.colWidth(0, 92)
1610
.colWidth(1, 20)
1611
.colWidth(2, 130)
1612
.colWidth(3, 82)
1613
.colWidth(4, 56)
1614
.cellBorder(null);
1615
table.cellTextFactory()
1616
.padding(0)
1617
.vAlign('center')
1618
.hAlign('left');
1619
table.getCell(table.rowsCount() - 1, 3).padding(0, 2, 5);
1620
table.getCell(0, 4).content().hAlign('right');
1621
// we use our utility methods to make setting up whole rows and columns easier
1622
setupRowProp(table, 0, 'bottomBorder', '2 #ccc');
1623
setupRowProp(table, 0, ['content', 'fontFamily'], 'verdana, helvetica, arial, sans-serif');
1624
setupRowProp(table, 0, ['content', 'vAlign'], 'bottom');
1625
setupRowProp(table, 0, ['padding', 'bottom'], 2);
1626
setupColProp(table, 0, 'padding', [2, 0], true);
1627
setupColProp(table, 3, 'padding', [6, 2, 5], true);
1628
setupColProp(table, 4, ['content', 'hAlign'], 'right', true);
1629
// and finally we draw the table to the report layer
1630
table.draw();
1631
1632
// but there is one more thing we want to draw on this report - a thin vertical line over all bullet charts,
1633
// showing the 100% mark
1634
// to do that we should determine exact X position of the 100% mark, using the common bullet scale and axis position
1635
// so we get the upper cell we want to draw the line at to determine both X and top Y coordinates
1636
var cell = table.getCell(1, 3);
1637
// determine its bounds
1638
var bounds = cell.getBounds();
1639
// take cell padding into consideration
1640
var padding = cell.padding();
1641
bounds.left += padding.left();
1642
bounds.width -= padding.left() + padding.right();
1643
// transform the value "100" using the scale of the axis
1644
// the scale.transform() method returns a ratio of the value passed between minimum and maximum of the scale
1645
// this ration is usually between 0 and 1, if the passed value is between minimum and maximum of the scale
1646
// so to get pixel coords we need to use proportions:
1647
var x = bulletScale.transform(100) * bounds.width + bounds.left;
1648
// lets make an offset of two pixels from the top of the cell
1649
var top = bounds.top + 2;
1650
// to determine the bottom line position let's use a handy axis method getRemainingBounds, using which we can
1651
// get axis line Y coordinate in this situation and axis major tick lengths to make the line we want to draw
1652
// look good
1653
var bottom = axis.getRemainingBounds().getBottom() + axis.ticks().length();
1654
// now we have all coordinates we need to draw the line we want
1655
container.path()
1656
.stroke('#000')
1657
.fill('none')
1658
.moveTo(x, top)
1659
.lineTo(x, bottom);
1660
1661
// we return an object with both report layer and desired bounds
1662
return {
1663
'report': container,
1664
'bounds': reportBounds
1665
};
1666
}
1667
1668
/**
1669
* Forms Major Project Milestones report using passed data.
1670
* @param {Array.<Array>} data Raw Major Project Milestones data.
1671
* @param {Date} today Today's date.
1672
* @return {{report: anychart.graphics.vector.Layer, bounds: anychart.math.Rect}} Report object.
1673
*/
1674
function formMPMReport(data, today) {
1675
// for this report we will need a common bullet scale with the range we want to show
1676
var bulletScale = anychart.scales.linear();
1677
bulletScale
1678
.minimum(-20)
1679
.maximum(20);
1680
1681
// and a markers factory to place some markers in rows we want to draw attention to
1682
var markers = anychart.elements.markersFactory();
1683
markers
1684
.anchor('center')
1685
.position('center')
1686
.type('circle')
1687
.fill('#c00')
1688
.stroke('2 #900')
1689
.size(5);
1690
1691
// now we will start forming report table contents
1692
// we place headings to the first row of the table
1693
var contents = [[null, 'Project', 'Milestone', 'Days Until/\nPast Due', 'Due\nDate']];
1694
1695
// than we aggregate passed data to a data set and map it to make iterating over it easier
1696
var view = anychart.data.set(data).mapAs({'Project': [0], 'Milestone': [1], 'Due': [2]});
1697
1698
// now we get an iterator over the mapped data
1699
var iterator = view.getIterator();
1700
// and iterate it
1701
while (iterator.advance()) {
1702
// we determine milestone due using our iterator
1703
var dueDate = new Date(iterator.get('Due'));
1704
// and count the difference in days between today and the due date
1705
var diff = getDiffInDays(today, dueDate);
1706
1707
// we will also need one bullet chart per system
1708
// we fill bullet bars with different color depending if the milestone is past or before due
1709
var bullet = anychart.bullet.chart([{
1710
'value': diff,
1711
'type': 'bar',
1712
'gap': 0.4,
1713
'fill': ((diff >= 0) ? '#999' : '#000')
1714
}]);
1715
// we setup some bullet appearance properties and bullet scale here
1716
bullet
1717
.scale(bulletScale)
1718
.padding(0)
1719
.margin(0);
1720
bullet.title().enabled(false);
1721
bullet.axis().enabled(false);
1722
bullet.background().enabled(false);
1723
1724
// now we determine if we need a marker in this row (if milestone due is more than 10 days before today)
1725
var marker;
1726
if (diff <= -10)
1727
marker = markers.add(null);
1728
else
1729
marker = null;
1730
1731
// and finally add the formed row to contents array using some utility functions and iterator
1732
contents.push([
1733
marker,
1734
iterator.get('Project'),
1735
iterator.get('Milestone'),
1736
bullet,
1737
formatDate(dueDate)
1738
]);
1739
}
1740
1741
// to make bullets more clear and comparable we use common axis for them
1742
// here we create and set it up
1743
var axis = anychart.elements.axis();
1744
axis
1745
.scale(bulletScale)
1746
.orientation('bottom')
1747
.staggerMode(false)
1748
.overlapMode('allowOverlap')
1749
.stroke('#ccc');
1750
axis.title().enabled(false);
1751
axis.labels().fontSize('9px');
1752
axis.ticks().stroke('#ccc');
1753
axis.minorTicks().enabled(false);
1754
1755
// and add the axis to the table contents array
1756
contents.push([null, null, null, axis, null]);
1757
1758
// now we are ready to create report layer and define some variables to store report bounds data
1759
var container = anychart.graphics.layer();
1760
var titleHeight = 20;
1761
var tableTop = titleHeight - 20;
1762
var tableHeight = (contents.length - 1) * 24 + 40;
1763
var reportBounds = anychart.math.rect(0, 0, 380, tableHeight + tableTop);
1764
1765
// we create, setup and draw a title for the report
1766
var title = anychart.elements.title();
1767
title
1768
.container(container)
1769
.parentBounds(reportBounds)
1770
.height(titleHeight)
1771
.fontFamily('trebuchet, helvetica, arial, sans-serif')
1772
.fontWeight('normal')
1773
.text('<span style="color:#ED8C2B; font-size: 15px;">Major Project Milestones</span> <span style="color: #666666; font-size: 10px; font-weight: normal;">(by priority)</span>')
1774
.orientation('top')
1775
.align('left')
1776
.vAlign('bottom')
1777
.margin(0)
1778
.padding(0)
1779
.useHtml(true)
1780
.draw();
1781
1782
// then we create the report main object - the table
1783
var table = anychart.elements.table();
1784
// we set it up and draw it to the report layer
1785
table
1786
.container(container)
1787
.top(tableTop)
1788
.height(tableHeight)
1789
.width(reportBounds.width)
1790
.contents(contents)
1791
.rowHeight(0, 40)
1792
.colWidth(0, 20)
1793
.colWidth(1, 129)
1794
.colWidth(2, 108)
1795
.colWidth(3, 75)
1796
.colWidth(4, 48)
1797
.cellBorder(null);
1798
table.cellTextFactory()
1799
.padding(0)
1800
.vAlign('center')
1801
.hAlign('left')
1802
.fontSize('11px');
1803
// here we use our utility methods described above to make rows and columns setting up easier
1804
setupRowProp(table, 0, 'bottomBorder', '2 #ccc');
1805
setupRowProp(table, 0, ['content', 'vAlign'], 'bottom');
1806
setupRowProp(table, 0, ['content', 'fontSize'], '11px');
1807
setupRowProp(table, 0, ['content', 'fontFamily'], 'verdana, helvetica, arial, sans-serif');
1808
setupRowProp(table, 0, ['padding', 'bottom'], 2);
1809
setupColProp(table, 0, 'padding', [2, 0], true);
1810
setupColProp(table, 3, 'padding', [6, 2, 5], true);
1811
setupColProp(table, 4, ['content', 'hAlign'], 'right', true);
1812
table.getCell(table.rowsCount() - 1, 3).padding(0, 2, 5, 1);
1813
table.getCell(0, 3).content().hAlign('center');
1814
table.getCell(0, 4).content().hAlign('right');
1815
// and finally we can draw the table
1816
table.draw();
1817
1818
// but there is one more thing we want to draw on this report - a thin vertical line over all bullet charts,
1819
// showing the zero mark
1820
// to do that we should determine exact X position of the 0 mark, using the common bullet scale and axis position
1821
// so we get the upper cell we want to draw the line at to determine both X and top Y coordinates
1822
var cell = table.getCell(1, 3);
1823
// determine its bounds
1824
var bounds = cell.getBounds();
1825
// take cell padding into consideration
1826
var padding = cell.padding();
1827
bounds.left += padding.left();
1828
bounds.width -= padding.left() + padding.right();
1829
// transform the value "100" using the scale of the axis
1830
// the scale.transform() method returns a ratio of the value passed between minimum and maximum of the scale
1831
// this ration is usually between 0 and 1, if the passed value is between minimum and maximum of the scale
1832
// so to get pixel coords we need to use proportions:
1833
var x = bulletScale.transform(0) * bounds.width + bounds.left;
1834
// lets make an offset of two pixels from the top of the cell
1835
var top = bounds.top + 2;
1836
// to determine the bottom line position let's use a handy axis method getRemainingBounds, using which we can
1837
// get axis line Y coordinate in this situation and axis major tick lengths to make the line we want to draw
1838
// look good
1839
var bottom = axis.getRemainingBounds().getBottom() + axis.ticks().length();
1840
// now we have all coordinates we need to draw the line we want
1841
container.path()
1842
.stroke('#ccc')
1843
.fill('none')
1844
.moveTo(x, top)
1845
.lineTo(x, bottom);
1846
1847
// we return an object with both report layer and desired bounds
1848
return {
1849
'report': container,
1850
'bounds': reportBounds
1851
};
1852
}
1853
1854
/**
1855
* Forms Top Projects in Queue report using passed data.
1856
* @param {Array.<Array>} data Raw Top Projects in Queue data.
1857
* @return {{report: anychart.graphics.vector.Layer, bounds: anychart.math.Rect}} Report object.
1858
*/
1859
function formTPQReport(data) {
1860
// this tiny report contains just of a table and a title, so all we need to prepare is a contents array for the table
1861
// so we start from making an array with table column headings
1862
var contents = [[null, 'Project', 'Status', 'Funding\nApproved', 'Sched.\nStart']];
1863
1864
// then we aggregate and map the data to make data iterating and retrieving easier
1865
var view = anychart.data.set(data).mapAs({'Project': [0], 'Status': [1], 'Approved': [2], 'Start': [3]});
1866
1867
// then we get data iterator and use it to pass over the data and form the array with content
1868
var iterator = view.getIterator();
1869
for (var i = 0; iterator.advance(); i++) {
1870
// we use iterator.get() method to retrieve data values
1871
contents.push([
1872
++i,
1873
iterator.get('Project'),
1874
iterator.get('Status'),
1875
iterator.get('Approved') ? 'X' : null,
1876
formatDate(new Date(iterator.get('Start')))
1877
]);
1878
}
1879
1880
// now we are ready to create content layer and declare some variables to store info about the report bounds
1881
var container = anychart.graphics.layer();
1882
var titleHeight = 20;
1883
var tableTop = titleHeight - 20;
1884
var tableHeight = (contents.length - 1) * 24 + 40;
1885
var reportBounds = anychart.math.rect(0, 0, 380, tableHeight + tableTop);
1886
1887
// we create, setup and draw the report title
1888
var title = anychart.elements.title();
1889
title
1890
.container(container)
1891
.parentBounds(reportBounds)
1892
.fontFamily('trebuchet, helvetica, arial, sans-serif')
1893
.fontWeight('normal')
1894
.fontSize('15px')
1895
.fontColor('#ED8C2B')
1896
.text('Top Projects in the Queue')
1897
.orientation('top')
1898
.align('left')
1899
.vAlign('bottom')
1900
.margin(0)
1901
.padding(0, 0, 2, 0)
1902
.height(titleHeight)
1903
.useHtml(false)
1904
.draw();
1905
1906
// and then we create, setup and draw the report table
1907
var table = anychart.elements.table();
1908
table
1909
.container(container)
1910
.top(tableTop)
1911
.height(tableHeight)
1912
.width(reportBounds.width)
1913
.contents(contents)
1914
.rowHeight(0, 40)
1915
.colWidth(0, 20)
1916
.colWidth(1, 150)
1917
.colWidth(2, 110)
1918
.colWidth(3, 52)
1919
.colWidth(4, 48)
1920
.cellBorder(null);
1921
table.cellTextFactory()
1922
.padding(0)
1923
.vAlign('center')
1924
.hAlign('left')
1925
.fontSize('11px');
1926
// we use our utility methods to setup common properties for the whole rows and columns
1927
setupRowProp(table, 0, 'bottomBorder', '2 #ccc');
1928
setupRowProp(table, 0, ['content', 'vAlign'], 'bottom');
1929
setupRowProp(table, 0, ['padding', 'bottom'], 2);
1930
setupColProp(table, 0, 'padding', [2, 0], true);
1931
setupColProp(table, 3, ['content', 'hAlign'], 'center', true);
1932
setupColProp(table, 4, ['content', 'hAlign'], 'right', true);
1933
table.getCell(0, 3).content().hAlign('center');
1934
table.getCell(0, 4).content().hAlign('center');
1935
// and then we tell the table to draw to its container
1936
table.draw();
1937
1938
// we return an object with both report layer and desired bounds
1939
return {
1940
'report': container,
1941
'bounds': reportBounds
1942
};
1943
}
1944
1945
// endregion
1946
1947
// region Dashboard assemblage
1948
// to draw the dashboard we need to create a stage first, so we do it, settings stage container id and its size
1949
var stage = anychart.graphics.create('container', 772, 580);
1950
// then we suspend stage redrawing because we know that now we are going to draw a lot on this stage and do want it
1951
// to render all its content only in the end, when everything will be ready
1952
stage.suspend();
1953
1954
// the height of the all dashboard titles
1955
// we use three titles in this dashboard, because they all are positioned differently and contain different info
1956
var titleHeight = 50;
1957
// also we want one of the titles to contain "today's" date, so we format it properly here
1958
var formattedToday = (new Date(Today)).toLocaleDateString('en-US', {year: 'numeric', month: 'long', day: 'numeric'});
1959
1960
// the first title is a disclaimer and is positioned at the top of everything, aligning to the left, with some padding
1961
anychart.elements.title()
1962
.container(stage)
1963
.parentBounds(anychart.math.rect(0, 0, stage.width(), stage.height()))
1964
.fontFamily('trebuchet, helvetica, arial, sans-serif')
1965
.fontWeight('normal')
1966
.fontSize('10px')
1967
.fontColor('#666')
1968
.text('This sample is based on the dashboard sample in "Information Dashboard Design: Displaying Data for At-a-Glance Monitoring" by Stephen Few')
1969
.orientation('top')
1970
.align('left')
1971
.vAlign('bottom')
1972
.margin(0)
1973
.padding(3, 0, 0, 10)
1974
.useHtml(true)
1975
.draw();
1976
1977
// the second title is a "CIO Dashboard" label, also aligned to the left.
1978
// but we also use vAlign and height properties combined to make that positioning you see
1979
anychart.elements.title()
1980
.container(stage)
1981
.parentBounds(anychart.math.rect(0, 0, stage.width(), stage.height()))
1982
.fontFamily('trebuchet, helvetica, arial, sans-serif')
1983
.fontWeight('normal')
1984
.fontSize('20px')
1985
.fontColor('#ED8C2B')
1986
.text('CIO Dashboard')
1987
.orientation('top')
1988
.align('left')
1989
.vAlign('bottom')
1990
.margin(0)
1991
.padding(0, 10, 4, 10)
1992
.height(titleHeight)
1993
.useHtml(true)
1994
.draw();
1995
1996
// the third title is a today's date label aligned to the right.
1997
// it also has vAlign and height properties set in combination to make that positioning you see
1998
anychart.elements.title()
1999
.container(stage)
2000
.parentBounds(anychart.math.rect(0, 0, stage.width(), stage.height()))
2001
.fontFamily('trebuchet, helvetica, arial, sans-serif')
2002
.fontWeight('normal')
2003
.fontSize('12px')
2004
.fontColor('#666')
2005
.text('(As of ' + formattedToday + ')')
2006
.orientation('top')
2007
.align('right')
2008
.vAlign('bottom')
2009
.margin(0)
2010
.padding(0, 10, 4, 10)
2011
.height(titleHeight)
2012
.useHtml(true)
2013
.draw();
2014
2015
// then we draw the thick grey line, separating the titles from the content
2016
stage.path()
2017
.moveTo(10, titleHeight)
2018
.lineTo(stage.width() - 10, titleHeight)
2019
.stroke('4 #ccc');
2020
2021
// then we create to arrays to handle report objects for left and right columns of the dashboard
2022
var leftColumnReports = [
2023
formSAReport(SARawData, SAAcceptedAvailability),
2024
formHCReport(HCCPUData, HCStorage, HCNetwork, HCCPURanges, HCNetworkRanges, HCStorageRanges),
2025
formDNTReport(DNT6MonthAvgData, DNTWeekAvgData, DNTYesterdayData)
2026
];
2027
var rightColumnReports = [
2028
formKNSMReport(KNSMExpensesData, KNSMSatisfactionData, KNSMProblemsData, KNSMExpensesRanges, KNSMSatisfactionRanges,
2029
KNSMProblemsRanges, KNSMBudgetTarget, KNSMProblemsTarget),
2030
formMPMReport(MPMData, Today),
2031
formTPQReport(TPQData)
2032
];
2033
2034
// and now we are ready to start to position the reports from the top with some margins
2035
// down to the bottom, left column first
2036
var top = titleHeight + 15;
2037
var left = 10;
2038
var width = 0;
2039
var layer, bounds, i;
2040
// for all left-column reports
2041
for (i = 0; i < leftColumnReports.length; i++) {
2042
layer = leftColumnReports[i]['report'];
2043
bounds = leftColumnReports[i]['bounds'];
2044
// we add the report layer to the stage
2045
stage.addChild(layer);
2046
// and translate it to spread report among the stage
2047
layer.translate(left, top);
2048
top += bounds.height + 5;
2049
width = Math.max(width, bounds.width);
2050
}
2051
2052
top = titleHeight + 15;
2053
left += width + 15;
2054
width = 0;
2055
// for all left-column reports
2056
for (i = 0; i < rightColumnReports.length; i++) {
2057
layer = rightColumnReports[i]['report'];
2058
bounds = rightColumnReports[i]['bounds'];
2059
// we add the report layer to the stage
2060
stage.addChild(layer);
2061
// and translate it to spread report among the stage
2062
layer.translate(left, top);
2063
top += bounds.height;
2064
width = Math.max(width, bounds.width);
2065
}
2066
2067
// and finally we resume stage redrawing to make it render all the staff we placed on it
2068
stage.resume();
2069
2070
// endregion
2071
});