-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathData.html
More file actions
637 lines (635 loc) · 92.1 KB
/
Data.html
File metadata and controls
637 lines (635 loc) · 92.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
<html><style>body { overflow:hidden;}
h1 { font-family: Arial, sans-serif; font-size: 24px; color: #44a; }
p { margin:0; padding:0; }
br { margin:0; padding:0; }
.line {
white-space: nowrap;
height:16px;
}
.line>span {
display:inline-block; background-color:#eef; height:100%;
margin: 0 5px 0 0; padding-right: 5px; color:#999;
}
comment {color: #080;} module {color: #804;}
quote {color: #008;} comment>quote {color: #080;}
.listing { margin: 10px; border: 1px solid #ccc;
font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace;
font-size: 14px;
overflow:scroll;
height:90%;
}
code { padding: 5px 0;}</style></html><body>
<h1>Data.ts</h1>
<div class='listing'><code><p id=1 class="line"><span> 1</span><comment>/**</comment></p>
<p id=2 class="line"><span> 2</span><comment> */</comment></p>
<p id=3 class="line"><span> 3</span></p>
<p id=4 class="line"><span> 4</span> <comment>/** */</comment></p>
<p id=5 class="line"><span> 5</span>import { Condition, filter } from <quote>'./DataFilters'</quote>;</p>
<p id=6 class="line"><span> 6</span>import { Log } from <quote>'hsutil'</quote>; const log = new Log(<quote>'Data'</quote>);</p>
<p id=7 class="line"><span> 7</span></p>
<p id=8 class="line"><span> 8</span><comment>/** defines a [min-max] range */</comment></p>
<p id=9 class="line"><span> 9</span>export type NumRange = [number, number];</p>
<p id=10 class="line"><span> 10</span></p>
<p id=11 class="line"><span> 11</span><comment>/** defines a numeric domain that includes all values of a column */</comment></p>
<p id=12 class="line"><span> 12</span>export type NumDomain = [number, number];</p>
<p id=13 class="line"><span> 13</span></p>
<p id=14 class="line"><span> 14</span><comment>/** defines a Date domain that includes all values of a column */</comment></p>
<p id=15 class="line"><span> 15</span>export type DateDomain = [Date, Date];</p>
<p id=16 class="line"><span> 16</span></p>
<p id=17 class="line"><span> 17</span><comment>/** defines a categorical domain that includes all values of a column */</comment></p>
<p id=18 class="line"><span> 18</span>export type NameDomain = string[];</p>
<p id=19 class="line"><span> 19</span></p>
<p id=20 class="line"><span> 20</span><comment>/** defines a generic domain that can be any of the typed domains. */</comment></p>
<p id=21 class="line"><span> 21</span>export type Domain = NumDomain | DateDomain | NameDomain;</p>
<p id=22 class="line"><span> 22</span></p>
<p id=23 class="line"><span> 23</span><comment>/** defines a Column Reference, either as column name or index in the {@link Data.DataRow `DataRow`} array */</comment></p>
<p id=24 class="line"><span> 24</span>export type ColumnReference = number|string;</p>
<p id=25 class="line"><span> 25</span></p>
<p id=26 class="line"><span> 26</span><comment>/** a generic data value type, used in the {@link Data.DataRow `DataRow`} array */</comment></p>
<p id=27 class="line"><span> 27</span>export type DataVal = number|string|Date;</p>
<p id=28 class="line"><span> 28</span></p>
<p id=29 class="line"><span> 29</span><comment>/** a single row of column values */</comment></p>
<p id=30 class="line"><span> 30</span>export type DataRow = DataVal[];</p>
<p id=31 class="line"><span> 31</span></p>
<p id=32 class="line"><span> 32</span><comment>/** a JSON format data set, using arrays of names and rows */</comment></p>
<p id=33 class="line"><span> 33</span>export interface DataSet {</p>
<p id=34 class="line"><span> 34</span> <comment>/** an optional name for the data set */</comment></p>
<p id=35 class="line"><span> 35</span> name?: string;</p>
<p id=36 class="line"><span> 36</span> <comment>/** an array of column names. Each name matches the column with the same index in DataRow */</comment></p>
<p id=37 class="line"><span> 37</span> colNames: string[]; </p>
<p id=38 class="line"><span> 38</span> <comment>/** rows of data */</comment></p>
<p id=39 class="line"><span> 39</span> rows: DataRow[];</p>
<p id=40 class="line"><span> 40</span>}</p>
<p id=41 class="line"><span> 41</span></p>
<p id=42 class="line"><span> 42</span><comment>/** a data set in table format. The first row contains the column names. */</comment></p>
<p id=43 class="line"><span> 43</span>export type DataTable = (string|number)[][];</p>
<p id=44 class="line"><span> 44</span></p>
<p id=45 class="line"><span> 45</span>interface TypeStruct { type: Type; count: number;}</p>
<p id=46 class="line"><span> 46</span></p>
<p id=47 class="line"><span> 47</span>interface MetaStruct {</p>
<p id=48 class="line"><span> 48</span> name: string; <comment>// column name</comment></p>
<p id=49 class="line"><span> 49</span><comment></comment> column: number; <comment>// column index</comment></p>
<p id=50 class="line"><span> 50</span><comment></comment> accessed: boolean; <comment>// has column data been accessed?</comment></p>
<p id=51 class="line"><span> 51</span><comment></comment> cast: boolean; <comment>// has column data been cast </comment></p>
<p id=52 class="line"><span> 52</span><comment></comment> types: TypeStruct[]; <comment>// data types, sorted by likelihood</comment></p>
<p id=53 class="line"><span> 53</span><comment></comment>}</p>
<p id=54 class="line"><span> 54</span></p>
<p id=55 class="line"><span> 55</span></p>
<p id=56 class="line"><span> 56</span>export type sortFn = (x:any, y:any) => number;</p>
<p id=57 class="line"><span> 57</span>export type mapFn = (colVal:DataVal, colIndex:number, rowIndex:number, rows:DataRow[]) => DataVal;</p>
<p id=58 class="line"><span> 58</span>export type mapRowFn = (row:DataRow, rowIndex:number, rows:DataRow[]) => DataRow;</p>
<p id=59 class="line"><span> 59</span>export type initFn = (colVal: DataVal, colIndex:number, row:DataRow) => DataVal;</p>
<p id=60 class="line"><span> 60</span> </p>
<p id=61 class="line"><span> 61</span><comment>/**</comment></p>
<p id=62 class="line"><span> 62</span><comment> * Type definition. Can be one of </comment></p>
<p id=63 class="line"><span> 63</span><comment> * - <quote>'number'</quote> : numeric values</comment></p>
<p id=64 class="line"><span> 64</span><comment> * - <quote>'name'</quote> : nominal values, represented by arbitrary words</comment></p>
<p id=65 class="line"><span> 65</span><comment> * - <quote>'date'</quote> : date values </comment></p>
<p id=66 class="line"><span> 66</span><comment> * - <quote>'currency'</quote> : currency values. Currently supported are values ofg the format <quote>'$dd[,ddd]'</quote></comment></p>
<p id=67 class="line"><span> 67</span><comment> * - <quote>'percent'</quote>; : percent values: <quote>'d%'</quote></comment></p>
<p id=68 class="line"><span> 68</span><comment> */</comment></p>
<p id=69 class="line"><span> 69</span>export type Type = <quote>'number'</quote> | <quote>'name'</quote> | <quote>'date'</quote> | <quote>'currency'</quote> | <quote>'percent'</quote>;</p>
<p id=70 class="line"><span> 70</span></p>
<p id=71 class="line"><span> 71</span>export enum Types {</p>
<p id=72 class="line"><span> 72</span> <comment>/** numeric values */</comment></p>
<p id=73 class="line"><span> 73</span> number = <quote>'number'</quote>,</p>
<p id=74 class="line"><span> 74</span> <comment>/** nominal values, represented by arbitrary words */</comment></p>
<p id=75 class="line"><span> 75</span> name = <quote>'name'</quote>,</p>
<p id=76 class="line"><span> 76</span> <comment>/** date values */</comment></p>
<p id=77 class="line"><span> 77</span> date = <quote>'date'</quote>,</p>
<p id=78 class="line"><span> 78</span> <comment>/** currency values. Currently support6ed are values ofg the format <quote>'$dd[,ddd]'</quote> */</comment></p>
<p id=79 class="line"><span> 79</span> currency = <quote>'currency'</quote>,</p>
<p id=80 class="line"><span> 80</span> <comment>/** percent values: <quote>'d%'</quote> */</comment></p>
<p id=81 class="line"><span> 81</span> percent = <quote>'percent'</quote>,</p>
<p id=82 class="line"><span> 82</span>} </p>
<p id=83 class="line"><span> 83</span></p>
<p id=84 class="line"><span> 84</span><comment>/**</comment></p>
<p id=85 class="line"><span> 85</span><comment> * @param val the string to convert to a date</comment></p>
<p id=86 class="line"><span> 86</span><comment> * @param limitYear the year below which the century is corrected. Defaults to 1970.</comment></p>
<p id=87 class="line"><span> 87</span><comment> * @returns a new Date object parsed from `str`.</comment></p>
<p id=88 class="line"><span> 88</span><comment> * @description returns a new Date object parsed from `str` and corrects for a difference in </comment></p>
<p id=89 class="line"><span> 89</span><comment> * interpreting centuries between webkit and mozilla in converting strings to Dates:</comment></p>
<p id=90 class="line"><span> 90</span><comment> * The string <quote>"15/7/03"</quote> will convert to Jul 15 1903 in Mozilla and July 15 2003 in Webkit.</comment></p>
<p id=91 class="line"><span> 91</span><comment> * If `limitYear` is not specified this method uses 1970 as the decision date: </comment></p>
<p id=92 class="line"><span> 92</span><comment> * years 00-69 will be interpreted as 2000-2069, years 70-99 as 1970-1999.</comment></p>
<p id=93 class="line"><span> 93</span><comment> * Specifying a century in the string leads to a correct parse, e.g. <quote>"15/7/1903"</quote></comment></p>
<p id=94 class="line"><span> 94</span><comment> */</comment></p>
<p id=95 class="line"><span> 95</span>function toDate(val:DataVal, limitYear=1970):Date {</p>
<p id=96 class="line"><span> 96</span> let d:Date;</p>
<p id=97 class="line"><span> 97</span> if (val instanceof Date) { return <Date>val; }</p>
<p id=98 class="line"><span> 98</span> d = new Date(<string>val); </p>
<p id=99 class="line"><span> 99</span> if (!isNaN(d.getTime())) {</p>
<p id=100 class="line"><span> 100</span> const r = new RegExp(`\\d\\d${d.getFullYear() % 100}`, <quote>'g'</quote>);</p>
<p id=101 class="line"><span> 101</span> if (!(<string>val).match(r)) {</p>
<p id=102 class="line"><span> 102</span> const yr = 1900 + d.getFullYear() % 100;</p>
<p id=103 class="line"><span> 103</span> d.setFullYear( (yr < limitYear)? yr+100 : yr); </p>
<p id=104 class="line"><span> 104</span> }</p>
<p id=105 class="line"><span> 105</span> }</p>
<p id=106 class="line"><span> 106</span> return d;</p>
<p id=107 class="line"><span> 107</span>}</p>
<p id=108 class="line"><span> 108</span></p>
<p id=109 class="line"><span> 109</span><comment>/**</comment></p>
<p id=110 class="line"><span> 110</span><comment> * @param type [<quote>'date'</quote> | <quote>'percent'</quote> | <quote>'real'</quote> | _any_] The type to cast into. In case of _any_ - i.e. `type` </comment></p>
<p id=111 class="line"><span> 111</span><comment> * does not match any of the previous keywords, no casting occurs.</comment></p>
<p id=112 class="line"><span> 112</span><comment> * @param sample The value to cast.</comment></p>
<p id=113 class="line"><span> 113</span><comment> * @returns The result of the cast. </comment></p>
<p id=114 class="line"><span> 114</span><comment> * @description Casts the sample to the specified data type.</comment></p>
<p id=115 class="line"><span> 115</span><comment> */</comment></p>
<p id=116 class="line"><span> 116</span>function castVal(type:Type, val:DataVal):DataVal {</p>
<p id=117 class="line"><span> 117</span> let result:DataVal = val;</p>
<p id=118 class="line"><span> 118</span> switch (type) {</p>
<p id=119 class="line"><span> 119</span> case <quote>'date'</quote>: </p>
<p id=120 class="line"><span> 120</span> if (val instanceof Date) { return val; }</p>
<p id=121 class="line"><span> 121</span> result = toDate(val);</p>
<p id=122 class="line"><span> 122</span> if (isNaN(result.getTime())) { result = null; }</p>
<p id=123 class="line"><span> 123</span> return result;</p>
<p id=124 class="line"><span> 124</span> case <quote>'percent'</quote>: </p>
<p id=125 class="line"><span> 125</span> if (typeof val === <quote>'string'</quote>) {</p>
<p id=126 class="line"><span> 126</span> const num = parseFloat(val);</p>
<p id=127 class="line"><span> 127</span> return (<string>val).endsWith(<quote>'%'</quote>)? num/100 : num;</p>
<p id=128 class="line"><span> 128</span> } else {</p>
<p id=129 class="line"><span> 129</span> return val;</p>
<p id=130 class="line"><span> 130</span> }</p>
<p id=131 class="line"><span> 131</span> <comment>// if (isNaN(<number>result)) { result = null; }</comment></p>
<p id=132 class="line"><span> 132</span><comment></comment> <comment>// break;</comment></p>
<p id=133 class="line"><span> 133</span><comment></comment> case <quote>'currency'</quote>:</p>
<p id=134 class="line"><span> 134</span> <comment>// replace all except <quote>'e/E'</quote>, <quote>'.'</quote>, <quote>'+/-'</quote> and digits</comment></p>
<p id=135 class="line"><span> 135</span><comment></comment> result = (typeof val === <quote>'string'</quote>)? val.replace(/[^eE\+\-\.\d]/g, <quote>''</quote>) : val; </p>
<p id=136 class="line"><span> 136</span> <comment>/* falls through */</comment></p>
<p id=137 class="line"><span> 137</span> case <quote>'number'</quote>: </p>
<p id=138 class="line"><span> 138</span> if (typeof result === <quote>'string'</quote>) { result = parseFloat(result); }</p>
<p id=139 class="line"><span> 139</span> if (isNaN(<number>result)) { result = null; }</p>
<p id=140 class="line"><span> 140</span> return result;</p>
<p id=141 class="line"><span> 141</span> default: return <quote>''</quote>+val;</p>
<p id=142 class="line"><span> 142</span> }</p>
<p id=143 class="line"><span> 143</span>} </p>
<p id=144 class="line"><span> 144</span></p>
<p id=145 class="line"><span> 145</span>export function findDomain(data: DataRow[], col:number, type:Type, domain:Domain):Domain {</p>
<p id=146 class="line"><span> 146</span> if (domain===undefined) { domain = <Domain>[]; }</p>
<p id=147 class="line"><span> 147</span> if (col === undefined) { <comment>// use array index as domain</comment></p>
<p id=148 class="line"><span> 148</span><comment></comment> domain[0] = 0;</p>
<p id=149 class="line"><span> 149</span> domain[1] = data.length-1;</p>
<p id=150 class="line"><span> 150</span> } else {</p>
<p id=151 class="line"><span> 151</span> let expand:(r:DataRow) => void;</p>
<p id=152 class="line"><span> 152</span> switch(type) {</p>
<p id=153 class="line"><span> 153</span> case Types.name: </p>
<p id=154 class="line"><span> 154</span> expand = (r:DataRow) => (<string[]>domain).indexOf(<quote>''</quote>+r[col]) < 0? (<string[]>domain).push(<quote>''</quote>+r[col]) : <quote>''</quote>;</p>
<p id=155 class="line"><span> 155</span> break;</p>
<p id=156 class="line"><span> 156</span> case Types.date: </p>
<p id=157 class="line"><span> 157</span> expand = (r:DataRow) => {</p>
<p id=158 class="line"><span> 158</span> let v:Date = toDate(r[col]);</p>
<p id=159 class="line"><span> 159</span> if (domain[0]===undefined) { domain[0] = v; }</p>
<p id=160 class="line"><span> 160</span> if (domain[1]===undefined) { domain[1] = v; }</p>
<p id=161 class="line"><span> 161</span> if (v!==undefined && v!==null) {</p>
<p id=162 class="line"><span> 162</span> if (v<domain[0]) { domain[0] = v; }</p>
<p id=163 class="line"><span> 163</span> else if (v>domain[1]) { domain[1] = v; }</p>
<p id=164 class="line"><span> 164</span> }</p>
<p id=165 class="line"><span> 165</span> };</p>
<p id=166 class="line"><span> 166</span> break;</p>
<p id=167 class="line"><span> 167</span> case Types.number:</p>
<p id=168 class="line"><span> 168</span> case Types.percent:</p>
<p id=169 class="line"><span> 169</span> default: </p>
<p id=170 class="line"><span> 170</span> expand = (r:DataRow) => {</p>
<p id=171 class="line"><span> 171</span> let v:number = parseFloat(r[col].toString());</p>
<p id=172 class="line"><span> 172</span> if (domain[0]===undefined) { domain[0] = v; }</p>
<p id=173 class="line"><span> 173</span> if (domain[1]===undefined) { domain[1] = v; }</p>
<p id=174 class="line"><span> 174</span> if (v!==undefined && v!==null) {</p>
<p id=175 class="line"><span> 175</span> if (v<domain[0]) { domain[0] = v; }</p>
<p id=176 class="line"><span> 176</span> else if (v>domain[1]) { domain[1] = v; }</p>
<p id=177 class="line"><span> 177</span> }</p>
<p id=178 class="line"><span> 178</span> };</p>
<p id=179 class="line"><span> 179</span> }</p>
<p id=180 class="line"><span> 180</span> data.forEach(expand);</p>
<p id=181 class="line"><span> 181</span> }</p>
<p id=182 class="line"><span> 182</span> return domain;</p>
<p id=183 class="line"><span> 183</span>}</p>
<p id=184 class="line"><span> 184</span></p>
<p id=185 class="line"><span> 185</span><comment>/**</comment></p>
<p id=186 class="line"><span> 186</span><comment> * @description determines the data type. Supported types are </comment></p>
<p id=187 class="line"><span> 187</span><comment> * ```</comment></p>
<p id=188 class="line"><span> 188</span><comment> * <quote>'date'</quote>: sample represents a Date, either as a Date object or a String </comment></p>
<p id=189 class="line"><span> 189</span><comment> * <quote>'number'</quote>: sample represents a number</comment></p>
<p id=190 class="line"><span> 190</span><comment> * <quote>'percent'</quote>: sample represents a percentage (special case of a real number)</comment></p>
<p id=191 class="line"><span> 191</span><comment> * <quote>'nominal'</quote>: sample represents a nominal (ordinal or categorical) value</comment></p>
<p id=192 class="line"><span> 192</span><comment> * ```</comment></p>
<p id=193 class="line"><span> 193</span><comment> * @param val the value to bve typed.</comment></p>
<p id=194 class="line"><span> 194</span><comment> * @returns the type (<quote>'number'</quote>, <quote>'date'</quote>, <quote>'percent'</quote>, <quote>'nominal'</quote>, <quote>'currency'</quote>) corresponding to the sample</comment></p>
<p id=195 class="line"><span> 195</span><comment> */</comment></p>
<p id=196 class="line"><span> 196</span>function findType(val:DataVal):Type {</p>
<p id=197 class="line"><span> 197</span> if (!val || val===<quote>''</quote>) { return null; }</p>
<p id=198 class="line"><span> 198</span> if (val instanceof Date) { return <quote>'date'</quote>; } <comment>// if val is already a date</comment></p>
<p id=199 class="line"><span> 199</span><comment></comment> if (typeof val === <quote>'number'</quote>) { return <quote>'number'</quote>; } <comment>// if val is already a number</comment></p>
<p id=200 class="line"><span> 200</span><comment></comment> </p>
<p id=201 class="line"><span> 201</span> <comment>// else: val is a string:</comment></p>
<p id=202 class="line"><span> 202</span><comment></comment> const strVal = <string>val;</p>
<p id=203 class="line"><span> 203</span> if (<quote>''</quote>+parseFloat(strVal) === strVal) { return <quote>'number'</quote>; }</p>
<p id=204 class="line"><span> 204</span> if (strVal.startsWith(<quote>'$'</quote>) && !isNaN(parseFloat(strVal.slice(1)))) { return <quote>'currency'</quote>; }</p>
<p id=205 class="line"><span> 205</span> if (strVal.endsWith(<quote>'%'</quote>) && !isNaN(parseFloat(strVal))) { return <quote>'percent'</quote>; }</p>
<p id=206 class="line"><span> 206</span> if (!isNaN(toDate(strVal).getTime())) { return <quote>'date'</quote>; }</p>
<p id=207 class="line"><span> 207</span></p>
<p id=208 class="line"><span> 208</span> <comment>// // european large number currency representation: <quote>'$dd[,ddd]'</quote></comment></p>
<p id=209 class="line"><span> 209</span><comment></comment> <comment>// if ((/^\$\d{0,2}((,\d\d\d)*)/g).test(val)) { </comment></p>
<p id=210 class="line"><span> 210</span><comment></comment> <comment>// if (!isNaN(parseFloat(val.trim().replace(/[^eE\+\-\.\d]/g, <quote>''</quote>).replace(/,/g, <quote>''</quote>)))) { </comment></p>
<p id=211 class="line"><span> 211</span><comment></comment> <comment>// return Data.type.currency; </comment></p>
<p id=212 class="line"><span> 212</span><comment></comment> <comment>// }</comment></p>
<p id=213 class="line"><span> 213</span><comment></comment> <comment>// }</comment></p>
<p id=214 class="line"><span> 214</span><comment></comment> switch (strVal.toLowerCase()) {</p>
<p id=215 class="line"><span> 215</span> case <quote>"null"</quote>: break;</p>
<p id=216 class="line"><span> 216</span> case <quote>"#ref!"</quote>: break;</p>
<p id=217 class="line"><span> 217</span> default: if (val.length>0) { return <quote>'name'</quote>; }</p>
<p id=218 class="line"><span> 218</span> }</p>
<p id=219 class="line"><span> 219</span> return null;</p>
<p id=220 class="line"><span> 220</span>} </p>
<p id=221 class="line"><span> 221</span></p>
<p id=222 class="line"><span> 222</span><comment>/**</comment></p>
<p id=223 class="line"><span> 223</span><comment> * # Data</comment></p>
<p id=224 class="line"><span> 224</span><comment> * A simple in-memory database that holds data in rows of columns.</comment></p>
<p id=225 class="line"><span> 225</span><comment> * </comment></p>
<p id=226 class="line"><span> 226</span><comment> */</comment></p>
<p id=227 class="line"><span> 227</span>export class Data {</p>
<p id=228 class="line"><span> 228</span> <comment>//----------------------------</comment></p>
<p id=229 class="line"><span> 229</span><comment></comment> <comment>// public part</comment></p>
<p id=230 class="line"><span> 230</span><comment></comment> <comment>//----------------------------</comment></p>
<p id=231 class="line"><span> 231</span><comment></comment> public static type = {</p>
<p id=232 class="line"><span> 232</span> <comment>/** numeric values */</comment></p>
<p id=233 class="line"><span> 233</span> number: <Type><quote>'number'</quote>,</p>
<p id=234 class="line"><span> 234</span> <comment>/** nominal values, represented by arbitrary words */</comment></p>
<p id=235 class="line"><span> 235</span> name: <Type><quote>'name'</quote>,</p>
<p id=236 class="line"><span> 236</span> <comment>/** date values */</comment></p>
<p id=237 class="line"><span> 237</span> date: <Type><quote>'date'</quote>,</p>
<p id=238 class="line"><span> 238</span> <comment>/** currency values. Currently support6ed are values ofg the format <quote>'$dd[,ddd]'</quote> */</comment></p>
<p id=239 class="line"><span> 239</span> currency: <Type><quote>'currency'</quote>,</p>
<p id=240 class="line"><span> 240</span> <comment>/** percent values: <quote>'d%'</quote> */</comment></p>
<p id=241 class="line"><span> 241</span> percent: <Type><quote>'percent'</quote>,</p>
<p id=242 class="line"><span> 242</span><comment>// nominal: <quote>'nominal'</quote></comment></p>
<p id=243 class="line"><span> 243</span><comment></comment> };</p>
<p id=244 class="line"><span> 244</span></p>
<p id=245 class="line"><span> 245</span> constructor(dataset?:DataSet|DataTable) {</p>
<p id=246 class="line"><span> 246</span> if (dataset) { this.importData(dataset); }</p>
<p id=247 class="line"><span> 247</span> }</p>
<p id=248 class="line"><span> 248</span></p>
<p id=249 class="line"><span> 249</span> <comment>/**</comment></p>
<p id=250 class="line"><span> 250</span><comment> * @return the `name` field for this data base, if any</comment></p>
<p id=251 class="line"><span> 251</span><comment> */</comment></p>
<p id=252 class="line"><span> 252</span> public getName():string {</p>
<p id=253 class="line"><span> 253</span> return this.name;</p>
<p id=254 class="line"><span> 254</span> }</p>
<p id=255 class="line"><span> 255</span></p>
<p id=256 class="line"><span> 256</span> <comment>/**</comment></p>
<p id=257 class="line"><span> 257</span><comment> * Imports data from an object literal or an array whos header row contains the column numbers</comment></p>
<p id=258 class="line"><span> 258</span><comment> * @param data the data set to import</comment></p>
<p id=259 class="line"><span> 259</span><comment> */</comment></p>
<p id=260 class="line"><span> 260</span> public importData(dataset:DataSet|DataTable) {</p>
<p id=261 class="line"><span> 261</span> this.name = <quote>'data'</quote>+(Math.round(1000*Math.random()));</p>
<p id=262 class="line"><span> 262</span> if (dataset) {</p>
<p id=263 class="line"><span> 263</span> if (dataset instanceof Array) {</p>
<p id=264 class="line"><span> 264</span> const names = <string[]>dataset[0];</p>
<p id=265 class="line"><span> 265</span> const rows = dataset.slice(1);</p>
<p id=266 class="line"><span> 266</span> this.setData(rows, names);</p>
<p id=267 class="line"><span> 267</span> } else {</p>
<p id=268 class="line"><span> 268</span> if (dataset.name) { this.name = dataset.name; }</p>
<p id=269 class="line"><span> 269</span> this.setData(dataset.rows, dataset.colNames);</p>
<p id=270 class="line"><span> 270</span> }</p>
<p id=271 class="line"><span> 271</span> }</p>
<p id=272 class="line"><span> 272</span> }</p>
<p id=273 class="line"><span> 273</span></p>
<p id=274 class="line"><span> 274</span> <comment>/**</comment></p>
<p id=275 class="line"><span> 275</span><comment> * Exports to an object literal</comment></p>
<p id=276 class="line"><span> 276</span><comment> */</comment></p>
<p id=277 class="line"><span> 277</span> public export():DataSet {</p>
<p id=278 class="line"><span> 278</span> return {</p>
<p id=279 class="line"><span> 279</span> rows: this.getData(),</p>
<p id=280 class="line"><span> 280</span> colNames:this.colNames()</p>
<p id=281 class="line"><span> 281</span> };</p>
<p id=282 class="line"><span> 282</span> }</p>
<p id=283 class="line"><span> 283</span></p>
<p id=284 class="line"><span> 284</span> <comment>/**</comment></p>
<p id=285 class="line"><span> 285</span><comment> * returns the 2D array underlying the data base.</comment></p>
<p id=286 class="line"><span> 286</span><comment> */</comment></p>
<p id=287 class="line"><span> 287</span> public getData():DataRow[] {</p>
<p id=288 class="line"><span> 288</span> return this.data;</p>
<p id=289 class="line"><span> 289</span> }</p>
<p id=290 class="line"><span> 290</span></p>
<p id=291 class="line"><span> 291</span> <comment>/**</comment></p>
<p id=292 class="line"><span> 292</span><comment> * Returns the values in the specified column as a new array.</comment></p>
<p id=293 class="line"><span> 293</span><comment> * @param col the column to return.</comment></p>
<p id=294 class="line"><span> 294</span><comment> */</comment></p>
<p id=295 class="line"><span> 295</span> public getColumn(col:ColumnReference): DataVal[] {</p>
<p id=296 class="line"><span> 296</span> const cn = this.colNumber(col);</p>
<p id=297 class="line"><span> 297</span> return this.data.map((row:DataRow) => row[cn]);</p>
<p id=298 class="line"><span> 298</span> }</p>
<p id=299 class="line"><span> 299</span></p>
<p id=300 class="line"><span> 300</span> <comment>/**</comment></p>
<p id=301 class="line"><span> 301</span><comment> * adds a new column to the data set. if `newCol` already exists, </comment></p>
<p id=302 class="line"><span> 302</span><comment> * the column index is returned without change.</comment></p>
<p id=303 class="line"><span> 303</span><comment> * @param col the name of the new column</comment></p>
<p id=304 class="line"><span> 304</span><comment> * @return the index for the new column</comment></p>
<p id=305 class="line"><span> 305</span><comment> */</comment></p>
<p id=306 class="line"><span> 306</span> public colAdd(col:string):MetaStruct {</p>
<p id=307 class="line"><span> 307</span> let m = this.getMeta(col);</p>
<p id=308 class="line"><span> 308</span> if (m === undefined) { </p>
<p id=309 class="line"><span> 309</span> m = this.meta[col] = <MetaStruct>{};</p>
<p id=310 class="line"><span> 310</span> m.name = col; </p>
<p id=311 class="line"><span> 311</span> m.column = this.meta.length;</p>
<p id=312 class="line"><span> 312</span> this.meta.push(m); <comment>// access name by both column name and index</comment></p>
<p id=313 class="line"><span> 313</span><comment></comment> m.cast = false; <comment>// has not been cast yet</comment></p>
<p id=314 class="line"><span> 314</span><comment></comment> m.accessed = false; <comment>// has not been accessed yet</comment></p>
<p id=315 class="line"><span> 315</span><comment></comment> }</p>
<p id=316 class="line"><span> 316</span> return m;</p>
<p id=317 class="line"><span> 317</span> }</p>
<p id=318 class="line"><span> 318</span></p>
<p id=319 class="line"><span> 319</span> <comment>/**</comment></p>
<p id=320 class="line"><span> 320</span><comment> * initializes the specifed column with values, adding a new column if needed. </comment></p>
<p id=321 class="line"><span> 321</span><comment> * If `val`is a function, it is called as ```</comment></p>
<p id=322 class="line"><span> 322</span><comment> * val(colValue:DataVal, rowIndex:number, row:DataRow)</comment></p>
<p id=323 class="line"><span> 323</span><comment> * ```</comment></p>
<p id=324 class="line"><span> 324</span><comment> * @param col the column to initialize</comment></p>
<p id=325 class="line"><span> 325</span><comment> * @param initializer the value to initialize with, or a function whose return</comment></p>
<p id=326 class="line"><span> 326</span><comment> * value is used to initialize the column</comment></p>
<p id=327 class="line"><span> 327</span><comment> * @return the initialized column number</comment></p>
<p id=328 class="line"><span> 328</span><comment> */</comment></p>
<p id=329 class="line"><span> 329</span> public colInitialize(col:ColumnReference, initializer:initFn|DataVal):number {</p>
<p id=330 class="line"><span> 330</span> const fn = typeof initializer === <quote>'function'</quote>;</p>
<p id=331 class="line"><span> 331</span> let cn:MetaStruct = this.getMeta(col);</p>
<p id=332 class="line"><span> 332</span> if (!cn) {</p>
<p id=333 class="line"><span> 333</span> if (typeof col === <quote>'string'</quote>) { cn = this.colAdd(col); }</p>
<p id=334 class="line"><span> 334</span> else { throw new Error(`column ${col} does not exist in Data`); }</p>
<p id=335 class="line"><span> 335</span> }</p>
<p id=336 class="line"><span> 336</span> this.data.map((row:DataRow, rowIndex:number) =></p>
<p id=337 class="line"><span> 337</span> row[cn.column] = fn? (<initFn>initializer)(row[cn.column], rowIndex, row) : <DataVal>initializer</p>
<p id=338 class="line"><span> 338</span> );</p>
<p id=339 class="line"><span> 339</span> return cn.column;</p>
<p id=340 class="line"><span> 340</span> }</p>
<p id=341 class="line"><span> 341</span></p>
<p id=342 class="line"><span> 342</span> <comment>/**</comment></p>
<p id=343 class="line"><span> 343</span><comment> * returns the column index of the specified column. </comment></p>
<p id=344 class="line"><span> 344</span><comment> * `col` can be either an index or a name.</comment></p>
<p id=345 class="line"><span> 345</span><comment> * @param column the data column, name or index, for which to return the index. </comment></p>
<p id=346 class="line"><span> 346</span><comment> * @return the column number or `undefined`.</comment></p>
<p id=347 class="line"><span> 347</span><comment> */</comment></p>
<p id=348 class="line"><span> 348</span> public colNumber(col:ColumnReference) {</p>
<p id=349 class="line"><span> 349</span> const m = this.getMeta(col);</p>
<p id=350 class="line"><span> 350</span> if (!m) { return undefined; }</p>
<p id=351 class="line"><span> 351</span> else {</p>
<p id=352 class="line"><span> 352</span> m.accessed = true; </p>
<p id=353 class="line"><span> 353</span> return m.column; </p>
<p id=354 class="line"><span> 354</span> }</p>
<p id=355 class="line"><span> 355</span> }</p>
<p id=356 class="line"><span> 356</span> </p>
<p id=357 class="line"><span> 357</span> <comment>/**</comment></p>
<p id=358 class="line"><span> 358</span><comment> * returns the column name for the specified column. </comment></p>
<p id=359 class="line"><span> 359</span><comment> * `col` can be either an index or a name.</comment></p>
<p id=360 class="line"><span> 360</span><comment> * @param column the data column, name or index. </comment></p>
<p id=361 class="line"><span> 361</span><comment> * @return the column name or `undefined`.</comment></p>
<p id=362 class="line"><span> 362</span><comment> */</comment></p>
<p id=363 class="line"><span> 363</span> public colName(col:ColumnReference):string {</p>
<p id=364 class="line"><span> 364</span> var m = this.getMeta(col);</p>
<p id=365 class="line"><span> 365</span> if (!m) { return undefined; }</p>
<p id=366 class="line"><span> 366</span> m.accessed = true; </p>
<p id=367 class="line"><span> 367</span> return m.name; </p>
<p id=368 class="line"><span> 368</span> }</p>
<p id=369 class="line"><span> 369</span></p>
<p id=370 class="line"><span> 370</span> <comment>/**</comment></p>
<p id=371 class="line"><span> 371</span><comment> * returns the names for all columns. </comment></p>
<p id=372 class="line"><span> 372</span><comment> * @return an array of strings with the names.</comment></p>
<p id=373 class="line"><span> 373</span><comment> */</comment></p>
<p id=374 class="line"><span> 374</span> public colNames():string[] {</p>
<p id=375 class="line"><span> 375</span> return this.meta.map((m:MetaStruct) => m.name); </p>
<p id=376 class="line"><span> 376</span> }</p>
<p id=377 class="line"><span> 377</span></p>
<p id=378 class="line"><span> 378</span> <comment>/**</comment></p>
<p id=379 class="line"><span> 379</span><comment> * returns the column type for the specified column. </comment></p>
<p id=380 class="line"><span> 380</span><comment> * `col` can be either an index or a name.</comment></p>
<p id=381 class="line"><span> 381</span><comment> * @param column the data column, name or index. </comment></p>
<p id=382 class="line"><span> 382</span><comment> * @return the column type.</comment></p>
<p id=383 class="line"><span> 383</span><comment> */</comment></p>
<p id=384 class="line"><span> 384</span> public colType(col:ColumnReference):Type { </p>
<p id=385 class="line"><span> 385</span> const meta = this.getMeta(col);</p>
<p id=386 class="line"><span> 386</span> return meta? meta.types[0].type : <quote>'name'</quote>;</p>
<p id=387 class="line"><span> 387</span> }</p>
<p id=388 class="line"><span> 388</span></p>
<p id=389 class="line"><span> 389</span> <comment>/**</comment></p>
<p id=390 class="line"><span> 390</span><comment> * modifies `domain` to include all values in column `col`.</comment></p>
<p id=391 class="line"><span> 391</span><comment> * If no `col` is specified, the range of data indexes is returned.</comment></p>
<p id=392 class="line"><span> 392</span><comment> * @param col optional; the column name or index </comment></p>
<p id=393 class="line"><span> 393</span><comment> * @param domain optional; the Domain range to update</comment></p>
<p id=394 class="line"><span> 394</span><comment> * @return the updated domain</comment></p>
<p id=395 class="line"><span> 395</span><comment> */</comment></p>
<p id=396 class="line"><span> 396</span> public findDomain(col?:ColumnReference, domain?:Domain):Domain {</p>
<p id=397 class="line"><span> 397</span> return findDomain(this.data, this.colNumber(col), this.colType(col), domain);</p>
<p id=398 class="line"><span> 398</span> }</p>
<p id=399 class="line"><span> 399</span></p>
<p id=400 class="line"><span> 400</span> public castData() {</p>
<p id=401 class="line"><span> 401</span> this.meta.forEach((c:MetaStruct) => {</p>
<p id=402 class="line"><span> 402</span> const col = c.column;</p>
<p id=403 class="line"><span> 403</span> if (!c.cast) {</p>
<p id=404 class="line"><span> 404</span> this.data.forEach((row:DataRow) => row[col] = castVal(c.types[0].type, row[col]));</p>
<p id=405 class="line"><span> 405</span> }</p>
<p id=406 class="line"><span> 406</span> c.cast = true;</p>
<p id=407 class="line"><span> 407</span> });</p>
<p id=408 class="line"><span> 408</span> }</p>
<p id=409 class="line"><span> 409</span></p>
<p id=410 class="line"><span> 410</span> <comment>/**</comment></p>
<p id=411 class="line"><span> 411</span><comment> * filters this data set and returns a new data set with a </comment></p>
<p id=412 class="line"><span> 412</span><comment> * shallow copy of rows that pass the `condition`.</comment></p>
<p id=413 class="line"><span> 413</span><comment> * See {@link DataFilters DataFilters} for rules and examples on how to construct conditions.</comment></p>
<p id=414 class="line"><span> 414</span><comment> * @param condition filters </comment></p>
<p id=415 class="line"><span> 415</span><comment> * @return a new Data object with rows that pass the filter</comment></p>
<p id=416 class="line"><span> 416</span><comment> */</comment></p>
<p id=417 class="line"><span> 417</span> public filter(condition:Condition):Data {</p>
<p id=418 class="line"><span> 418</span> return filter(this, condition);</p>
<p id=419 class="line"><span> 419</span> }</p>
<p id=420 class="line"><span> 420</span></p>
<p id=421 class="line"><span> 421</span> <comment>/**</comment></p>
<p id=422 class="line"><span> 422</span><comment> * @description Sorts the rows of values based on the result of the `sortFn`, </comment></p>
<p id=423 class="line"><span> 423</span><comment> * which behaves similarly to the Array.sort method. </comment></p>
<p id=424 class="line"><span> 424</span><comment> * Two modes are supported:</comment></p>
<p id=425 class="line"><span> 425</span><comment> * # Array Mode</comment></p>
<p id=426 class="line"><span> 426</span><comment> * If `col` is omitted, the column arrays are passed as samples into the `sortFn`. </comment></p>
<p id=427 class="line"><span> 427</span><comment> * This allows for complex sorts, combining conditions across multiple columns.</comment></p>
<p id=428 class="line"><span> 428</span><comment> * ```</comment></p>
<p id=429 class="line"><span> 429</span><comment> * data.sort((row1, row2) => row1[5] - row2[5] );</comment></p>
<p id=430 class="line"><span> 430</span><comment> * ```</comment></p>
<p id=431 class="line"><span> 431</span><comment> * # Column mode</comment></p>
<p id=432 class="line"><span> 432</span><comment> * If `col` is specified, either as index or by column name, the respective column value is passed</comment></p>
<p id=433 class="line"><span> 433</span><comment> * into `sortFn`. This allows filtering for simple conditions.</comment></p>
<p id=434 class="line"><span> 434</span><comment> * **The specified column will be automatically cast prior to sorting**<br></comment></p>
<p id=435 class="line"><span> 435</span><comment> * ```</comment></p>
<p id=436 class="line"><span> 436</span><comment> * data.sort(<quote>'ascending'</quote>, <quote>'Date'</quote>);</comment></p>
<p id=437 class="line"><span> 437</span><comment> * data.sort(<quote>'decending'</quote>, <quote>'Date'</quote>);</comment></p>
<p id=438 class="line"><span> 438</span><comment> * data.sort(function(val1, val2) { return val1 - val2; }, <quote>'Date'</quote>);</comment></p>
<p id=439 class="line"><span> 439</span><comment> * ```</comment></p>
<p id=440 class="line"><span> 440</span><comment> * @param col optional; the data column to use for sorting. </comment></p>
<p id=441 class="line"><span> 441</span><comment> * @param sortFn a function to implement the conditions, </comment></p>
<p id=442 class="line"><span> 442</span><comment> * follows the same specifications as the function passed to Array.sort(). </comment></p>
<p id=443 class="line"><span> 443</span><comment> * Some predefined sort function can be invoked by providing a </comment></p>
<p id=444 class="line"><span> 444</span><comment> * respective string instead of a function. The following functions are defined:</comment></p>
<p id=445 class="line"><span> 445</span><comment> ```</comment></p>
<p id=446 class="line"><span> 446</span><comment> ascending sort in ascending order.</comment></p>
<p id=447 class="line"><span> 447</span><comment> descending sort in decending order.</comment></p>
<p id=448 class="line"><span> 448</span><comment> ```</comment></p>
<p id=449 class="line"><span> 449</span><comment> * @return the Data object in order to allow for chaining.</comment></p>
<p id=450 class="line"><span> 450</span><comment> */</comment></p>
<p id=451 class="line"><span> 451</span> public sort(sortFn:string|sortFn, col?:ColumnReference):Data {</p>
<p id=452 class="line"><span> 452</span> let fn = <sortFn>sortFn;</p>
<p id=453 class="line"><span> 453</span> if (!col) {</p>
<p id=454 class="line"><span> 454</span> this.data.sort(fn);</p>
<p id=455 class="line"><span> 455</span> } else {</p>
<p id=456 class="line"><span> 456</span> col = this.colNumber(col);</p>
<p id=457 class="line"><span> 457</span> if (sortFn === <quote>'descending'</quote>) { fn = (a:any, b:any) => (b>a)?1:((b<a)?-1:0); }</p>
<p id=458 class="line"><span> 458</span> if (sortFn === <quote>'ascending'</quote>) { fn = (a:any, b:any) => (b<a)?1:((b>a)?-1:0); }</p>
<p id=459 class="line"><span> 459</span> this.data.sort((r1:any[], r2:any[]) => fn(r1[col], r2[col])); </p>
<p id=460 class="line"><span> 460</span> }</p>
<p id=461 class="line"><span> 461</span> return this;</p>
<p id=462 class="line"><span> 462</span> }</p>
<p id=463 class="line"><span> 463</span></p>
<p id=464 class="line"><span> 464</span> <comment>/** </comment></p>
<p id=465 class="line"><span> 465</span><comment> * Maps one or more columns in each rows of values based </comment></p>
<p id=466 class="line"><span> 466</span><comment> * on the result of the `mapFn`, which behaves similarly to the Array.map() method.</comment></p>
<p id=467 class="line"><span> 467</span><comment> * Two modes are supported:</comment></p>
<p id=468 class="line"><span> 468</span><comment> * </comment></p>
<p id=469 class="line"><span> 469</span><comment> * ## Array Mode</comment></p>
<p id=470 class="line"><span> 470</span><comment> * If `col` is omitted, the `mapFn` is passed the column arrays per row as parameter. </comment></p>
<p id=471 class="line"><span> 471</span><comment> * This allows for complex mapping combining conditions across multiple columns.</comment></p>
<p id=472 class="line"><span> 472</span><comment> * ```</comment></p>
<p id=473 class="line"><span> 473</span><comment> * data.map(function(values){ </comment></p>
<p id=474 class="line"><span> 474</span><comment> * values[1] = values[3] * values[5]; </comment></p>
<p id=475 class="line"><span> 475</span><comment> * return values; </comment></p>
<p id=476 class="line"><span> 476</span><comment> * });</comment></p>
<p id=477 class="line"><span> 477</span><comment> * ```</comment></p>
<p id=478 class="line"><span> 478</span><comment> * Be sure to return the `values` array as a result.</comment></p>
<p id=479 class="line"><span> 479</span><comment> * </comment></p>
<p id=480 class="line"><span> 480</span><comment> * ## Column mode</comment></p>
<p id=481 class="line"><span> 481</span><comment> * If `col` is specified, either as index or by column name, the respective column value is passed</comment></p>
<p id=482 class="line"><span> 482</span><comment> * into `mapFn`, along with the row index and the entire row array. This allows for simple mapping.</comment></p>
<p id=483 class="line"><span> 483</span><comment> * ```</comment></p>
<p id=484 class="line"><span> 484</span><comment> * data.map(<quote>'Price'</quote>, function(value, i, values) { </comment></p>
<p id=485 class="line"><span> 485</span><comment> * return value * 2; </comment></p>
<p id=486 class="line"><span> 486</span><comment> * });</comment></p>
<p id=487 class="line"><span> 487</span><comment> * ```</comment></p>
<p id=488 class="line"><span> 488</span><comment> * @param col the data column, or columns, to apply the mapping to. </comment></p>
<p id=489 class="line"><span> 489</span><comment> * @param mapFn a function to implement the mapping,</comment></p>
<p id=490 class="line"><span> 490</span><comment> * called on each row of the data set in turn as `mapFn(val, i, c, rows)`, where</comment></p>
<p id=491 class="line"><span> 491</span><comment> * - `val`: the column value in the current row</comment></p>
<p id=492 class="line"><span> 492</span><comment> * - `c`: the column index in the current row</comment></p>
<p id=493 class="line"><span> 493</span><comment> * - `i`: the row index </comment></p>
<p id=494 class="line"><span> 494</span><comment> * - `rows`: the rows being iterated over</comment></p>
<p id=495 class="line"><span> 495</span><comment>` * </comment></p>
<p id=496 class="line"><span> 496</span><comment> * follows the same specifications as the function passed to Array.map().<br></comment></p>
<p id=497 class="line"><span> 497</span><comment> * For column mode, some predefined map functions can be invoked by providing a </comment></p>
<p id=498 class="line"><span> 498</span><comment> * respective string instead of a function. The following functions are defined:</comment></p>
<p id=499 class="line"><span> 499</span><comment> * <table></comment></p>
<p id=500 class="line"><span> 500</span><comment> * <tr><td><quote>'noop'</quote></td><td>replace value with itself, performing no operation.</td></tr></comment></p>
<p id=501 class="line"><span> 501</span><comment> * <tr><td><quote>'cumulate'</quote></td><td>replace value with the cumulative sum of values up to the current element.</td></tr></comment></p>
<p id=502 class="line"><span> 502</span><comment> * </table></comment></p>
<p id=503 class="line"><span> 503</span><comment> *</comment></p>
<p id=504 class="line"><span> 504</span><comment> * @return a new Data object containing the mapping.</comment></p>
<p id=505 class="line"><span> 505</span><comment> */</comment></p>
<p id=506 class="line"><span> 506</span> public map(mapFn:string|mapFn|mapRowFn, col?:ColumnReference):Data {</p>
<p id=507 class="line"><span> 507</span> const noop = (val:any) => val;</p>
<p id=508 class="line"><span> 508</span> const cumulate = () => { </p>
<p id=509 class="line"><span> 509</span> let sum=0; </p>
<p id=510 class="line"><span> 510</span> return (val:number, i:number) => { sum += +val; return sum; };</p>
<p id=511 class="line"><span> 511</span> };</p>
<p id=512 class="line"><span> 512</span> function getFn():mapFn|mapRowFn {</p>
<p id=513 class="line"><span> 513</span> let fn; <comment>// define fn inside each col loop to ensure initialization</comment></p>
<p id=514 class="line"><span> 514</span><comment></comment> switch (mapFn) {</p>
<p id=515 class="line"><span> 515</span> case <quote>'cumulate'</quote>: fn = cumulate(); break;</p>
<p id=516 class="line"><span> 516</span> case <quote>'noop'</quote>: fn = noop; break;</p>
<p id=517 class="line"><span> 517</span> default: fn = <mapFn|mapRowFn>mapFn;</p>
<p id=518 class="line"><span> 518</span> }</p>
<p id=519 class="line"><span> 519</span> return fn;</p>
<p id=520 class="line"><span> 520</span> }</p>
<p id=521 class="line"><span> 521</span></p>
<p id=522 class="line"><span> 522</span> let fn = getFn(); <comment>// define fn inside each col loop to ensure initialization</comment></p>
<p id=523 class="line"><span> 523</span><comment></comment> const c = col? this.colNumber(col) : undefined;</p>
<p id=524 class="line"><span> 524</span> let result = new Data({</p>
<p id=525 class="line"><span> 525</span> colNames:this.colNames(), </p>
<p id=526 class="line"><span> 526</span> rows:this.data.map((row:any[], rowIndex:number, rows:any[][]) => { </p>
<p id=527 class="line"><span> 527</span> if (c) {</p>
<p id=528 class="line"><span> 528</span> row[c] = (<mapFn>fn)(row[c], c, rowIndex, rows); </p>
<p id=529 class="line"><span> 529</span> } else {</p>
<p id=530 class="line"><span> 530</span> rows[rowIndex] = (<mapRowFn>fn)(row, rowIndex, rows);</p>
<p id=531 class="line"><span> 531</span> }</p>
<p id=532 class="line"><span> 532</span> return row;</p>
<p id=533 class="line"><span> 533</span> }), </p>
<p id=534 class="line"><span> 534</span> name:this.getName()</p>
<p id=535 class="line"><span> 535</span> });</p>
<p id=536 class="line"><span> 536</span> return result;</p>
<p id=537 class="line"><span> 537</span> }</p>
<p id=538 class="line"><span> 538</span></p>
<p id=539 class="line"><span> 539</span> <comment>//----------------------------</comment></p>
<p id=540 class="line"><span> 540</span><comment></comment> <comment>// private part</comment></p>
<p id=541 class="line"><span> 541</span><comment></comment> <comment>//----------------------------</comment></p>
<p id=542 class="line"><span> 542</span><comment></comment> private data: DataRow[] = [];</p>
<p id=543 class="line"><span> 543</span> private meta: MetaStruct[] = [];</p>
<p id=544 class="line"><span> 544</span> private name: string;</p>
<p id=545 class="line"><span> 545</span></p>
<p id=546 class="line"><span> 546</span> private getMeta(col:ColumnReference):MetaStruct { </p>
<p id=547 class="line"><span> 547</span> <comment>// if (!this.meta) { this.meta = []; }</comment></p>
<p id=548 class="line"><span> 548</span><comment></comment> if (!this.meta[col]) { return undefined; }</p>
<p id=549 class="line"><span> 549</span> this.meta[col].accessed = true;</p>
<p id=550 class="line"><span> 550</span> return this.meta[col]; </p>
<p id=551 class="line"><span> 551</span> }</p>
<p id=552 class="line"><span> 552</span></p>
<p id=553 class="line"><span> 553</span> <comment>/**</comment></p>
<p id=554 class="line"><span> 554</span><comment> * sets `rows` as the data set. A copy of the rows array will be set.</comment></p>
<p id=555 class="line"><span> 555</span><comment> * @param data the data to add</comment></p>
<p id=556 class="line"><span> 556</span><comment> * @param names an array of names that match the columns</comment></p>
<p id=557 class="line"><span> 557</span><comment> * @param autoType unless set to false, the method will attempt to determine the </comment></p>
<p id=558 class="line"><span> 558</span><comment> * type of data and automatically cast data points to their correct value</comment></p>
<p id=559 class="line"><span> 559</span><comment> */</comment></p>
<p id=560 class="line"><span> 560</span> private setData(rows:DataRow[], names:string[], autoType=true):void {</p>
<p id=561 class="line"><span> 561</span> this.meta = [];</p>
<p id=562 class="line"><span> 562</span> this.data = rows.slice();</p>
<p id=563 class="line"><span> 563</span> if (names && names.forEach) {</p>
<p id=564 class="line"><span> 564</span> names.forEach((col:string) => this.colAdd(col));</p>
<p id=565 class="line"><span> 565</span> if (autoType) { </p>
<p id=566 class="line"><span> 566</span> names.forEach((col:string) => this.findTypes(col)); </p>
<p id=567 class="line"><span> 567</span> this.castData();</p>
<p id=568 class="line"><span> 568</span> }</p>
<p id=569 class="line"><span> 569</span> } else {</p>
<p id=570 class="line"><span> 570</span> log.warn(`invalid names setData:\n${log.inspect(names, {depth:5})}`);</p>
<p id=571 class="line"><span> 571</span> }</p>
<p id=572 class="line"><span> 572</span> }</p>
<p id=573 class="line"><span> 573</span></p>
<p id=574 class="line"><span> 574</span> <comment>/**</comment></p>
<p id=575 class="line"><span> 575</span><comment> * Determines the type of data in `col`. An array of counts is created for all</comment></p>
<p id=576 class="line"><span> 576</span><comment> * encountered types, sorted by descending frequency. THe most likely type in position 0</comment></p>
<p id=577 class="line"><span> 577</span><comment> * of the array is returned.</comment></p>
<p id=578 class="line"><span> 578</span><comment> * @param col the index of the column to be typed. </comment></p>
<p id=579 class="line"><span> 579</span><comment> * @return the most likely type of data in `col`.</comment></p>
<p id=580 class="line"><span> 580</span><comment> */</comment></p>
<p id=581 class="line"><span> 581</span> private findTypes(col:ColumnReference):string {</p>
<p id=582 class="line"><span> 582</span> const m = this.getMeta(col);</p>
<p id=583 class="line"><span> 583</span> const types:TypeStruct[] = [];</p>
<p id=584 class="line"><span> 584</span> Object.keys(Data.type).forEach((t:string) => {</p>
<p id=585 class="line"><span> 585</span> const ts = { type: Data.type[t], count: 0 }; </p>
<p id=586 class="line"><span> 586</span> types.push(ts);</p>
<p id=587 class="line"><span> 587</span> types[Data.type[t]] = ts;</p>
<p id=588 class="line"><span> 588</span> });</p>
<p id=589 class="line"><span> 589</span> for (let v of this.allRows(col)) {</p>
<p id=590 class="line"><span> 590</span> const t = findType(v);</p>
<p id=591 class="line"><span> 591</span> if (t !== null) { types[t].count++; }</p>
<p id=592 class="line"><span> 592</span> }</p>
<p id=593 class="line"><span> 593</span> types.sort(function(a:TypeStruct, b:TypeStruct) { </p>
<p id=594 class="line"><span> 594</span> if (a.type===<quote>'currency'</quote>&&a.count>0) { return -1; }</p>
<p id=595 class="line"><span> 595</span> if (b.type===<quote>'currency'</quote>&&b.count>0) { return 1; }</p>
<p id=596 class="line"><span> 596</span> return b.count - a.count;</p>
<p id=597 class="line"><span> 597</span> });</p>
<p id=598 class="line"><span> 598</span> m.types = types;</p>
<p id=599 class="line"><span> 599</span> return types[0].type;</p>
<p id=600 class="line"><span> 600</span> }</p>
<p id=601 class="line"><span> 601</span></p>
<p id=602 class="line"><span> 602</span> <comment>/**</comment></p>
<p id=603 class="line"><span> 603</span><comment> * A generator that provides the specified column value for each row in `Data` in sequence. </comment></p>
<p id=604 class="line"><span> 604</span><comment> * @param column </comment></p>
<p id=605 class="line"><span> 605</span><comment> */</comment></p>
<p id=606 class="line"><span> 606</span> private * allRows(column:ColumnReference):Iterable<DataVal> {</p>
<p id=607 class="line"><span> 607</span> const c = this.colNumber(column);</p>
<p id=608 class="line"><span> 608</span> for (let r=0; r<this.data.length; r++) {</p>
<p id=609 class="line"><span> 609</span> yield this.data[r][c];</p>
<p id=610 class="line"><span> 610</span> }</p>
<p id=611 class="line"><span> 611</span> } </p>
<p id=612 class="line"><span> 612</span>}</p></code></div>
</body>