Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(560)

Side by Side Diff: packages/charted/lib/charts/data_transformers/aggregation.dart

Issue 1521693002: Roll Observatory deps (charted -> ^0.3.0) (Closed) Base URL: https://chromium.googlesource.com/external/github.com/dart-lang/observatory_pub_packages.git@master
Patch Set: Created 5 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 /* 1 //
2 * Copyright 2014 Google Inc. All rights reserved. 2 // Copyright 2014 Google Inc. All rights reserved.
3 * 3 //
4 * Use of this source code is governed by a BSD-style 4 // Use of this source code is governed by a BSD-style
5 * license that can be found in the LICENSE file or at 5 // license that can be found in the LICENSE file or at
6 * https://developers.google.com/open-source/licenses/bsd 6 // https://developers.google.com/open-source/licenses/bsd
7 */ 7 //
8 8
9 part of charted.charts; 9 part of charted.charts;
10 10
11 /** 11 ///Function callback to filter items in the input
12 * Function callback to filter items in the input
13 */
14 typedef bool AggregationFilterFunc(var item); 12 typedef bool AggregationFilterFunc(var item);
15 13
16 typedef dynamic FieldAccessor(dynamic item, dynamic key); 14 typedef dynamic FieldAccessor(dynamic item, dynamic key);
17 15
18 16 /// Given list of items, dimensions and facts, compute
19 // TODO(midoringo, prsd): Consider splitting each aggregation type into its own 17 /// aggregates (COUNT, SUM, MIN, MAX) for facts at each dimension level.
20 // strategy object for readability, maintainability, and scalability.
21 /**
22 * Given list of items, dimensions and facts, compute
23 * aggregates (COUNT, SUM, MIN, MAX) for facts at each dimension level.
24 */
25 class AggregationModel { 18 class AggregationModel {
26
27 // Number of aggregations we collect on each fact 19 // Number of aggregations we collect on each fact
28 int _aggregationTypesCount = 0; 20 int _aggregationTypesCount = 0;
29 21
30 // Currently supported list of aggregations. 22 // Currently supported list of aggregations.
31 static final List<String> supportedAggregationTypes = 23 static final List<String> supportedAggregationTypes = [
32 ['sum', 'min', 'max', 'valid']; 24 'sum',
25 'min',
26 'max',
27 'valid'
28 ];
33 29
34 // Computed aggregation types. 30 // Computed aggregation types.
35 List<String> computedAggregationTypes; 31 List<String> computedAggregationTypes;
36 32
37 // Offsets of aggregations that are computed once per fact per dimension 33 // Offsets of aggregations that are computed once per fact per dimension
38 // If an offset is null, it will not be computed 34 // If an offset is null, it will not be computed
39 int _offsetSum; 35 int _offsetSum;
40 int _offsetMin; 36 int _offsetMin;
41 int _offsetMax; 37 int _offsetMax;
42 int _offsetCnt; 38 int _offsetCnt;
(...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after
96 final Map<String, Function> comparators; 92 final Map<String, Function> comparators;
97 93
98 // Timing operations 94 // Timing operations
99 static final Logger _logger = new Logger('aggregations'); 95 static final Logger _logger = new Logger('aggregations');
100 Stopwatch _timeItWatch; 96 Stopwatch _timeItWatch;
101 String _timeItName; 97 String _timeItName;
102 98
103 FieldAccessor dimensionAccessor; 99 FieldAccessor dimensionAccessor;
104 FieldAccessor factsAccessor; 100 FieldAccessor factsAccessor;
105 101
106 /** 102 /// Create a new [AggregationModel] from a [collection] of items,
107 * Create a new [AggregationModel] from a [collection] of items, 103 /// list of [dimensions] on which the items are grouped and a list of [facts]
108 * list of [dimensions] on which the items are grouped and a list of [facts] 104 /// on which aggregations are computed.
109 * on which aggregations are computed. 105 AggregationModel(List collection, List dimensions, List facts,
110 */ 106 {List<String> aggregationTypes,
111 AggregationModel(List collection, List dimensions, 107 this.walkThroughMap: false,
112 List facts, 108 this.comparators,
113 { List<String> aggregationTypes, 109 this.dimensionAccessor,
114 this.walkThroughMap: false, 110 this.factsAccessor}) {
115 this.comparators,
116 this.dimensionAccessor,
117 this.factsAccessor}) {
118 _init(collection, dimensions, facts, aggregationTypes); 111 _init(collection, dimensions, facts, aggregationTypes);
119 } 112 }
120 113
121 void _timeItStart(String name) { 114 void _timeItStart(String name) {
122 _timeItName = name; 115 _timeItName = name;
123 _timeItWatch = new Stopwatch(); 116 _timeItWatch = new Stopwatch();
124 _timeItWatch.start(); 117 _timeItWatch.start();
125 } 118 }
126 119
127 void _timeItEnd() { 120 void _timeItEnd() {
128 _timeItWatch.stop(); 121 _timeItWatch.stop();
129 _logger.info('[aggregations/$_timeItName] ' 122 _logger.info('[aggregations/$_timeItName] '
130 '${_timeItWatch.elapsed.inMilliseconds}ms/${_rows.length}r'); 123 '${_timeItWatch.elapsed.inMilliseconds}ms/${_rows.length}r');
131 } 124 }
132 125
133 List get factFields => _factFields; 126 List get factFields => _factFields;
134 List get dimensionFields => _dimFields; 127 List get dimensionFields => _dimFields;
135 128
136 /** 129 /// Initialize the view
137 * Initialize the view 130 void _init(List collection, List dimensions, List facts,
138 */ 131 List<String> aggregationTypes) {
139 void _init(List collection, List dimensions,
140 List facts, List<String> aggregationTypes) {
141 if (collection == null) { 132 if (collection == null) {
142 throw new ArgumentError('Data cannot be empty or null'); 133 throw new ArgumentError('Data cannot be empty or null');
143 } 134 }
144 135
145 if (facts == null || facts.isEmpty) { 136 if (facts == null || facts.isEmpty) {
146 throw new ArgumentError('Facts cannot be empty or null'); 137 throw new ArgumentError('Facts cannot be empty or null');
147 } 138 }
148 139
149 if (dimensions == null) { 140 if (dimensions == null) {
150 dimensions = []; 141 dimensions = [];
(...skipping 25 matching lines...) Expand all
176 167
177 _rows = collection; 168 _rows = collection;
178 _dimFields = new List.from(dimensions, growable: false); 169 _dimFields = new List.from(dimensions, growable: false);
179 _factFields = new List.from(facts, growable: false); 170 _factFields = new List.from(facts, growable: false);
180 _entityCache = new Map<String, AggregationItem>(); 171 _entityCache = new Map<String, AggregationItem>();
181 172
182 _createBuffers(); 173 _createBuffers();
183 174
184 _aggregationTypesCount = aggregationTypes.length; 175 _aggregationTypesCount = aggregationTypes.length;
185 for (int i = 0; i < _aggregationTypesCount; i++) { 176 for (int i = 0; i < _aggregationTypesCount; i++) {
186 switch(aggregationTypes[i]) { 177 switch (aggregationTypes[i]) {
187 case 'sum': 178 case 'sum':
188 _offsetSum = i; 179 _offsetSum = i;
189 break; 180 break;
190 case 'min': 181 case 'min':
191 _offsetMin = i; 182 _offsetMin = i;
192 break; 183 break;
193 case 'max': 184 case 'max':
194 _offsetMax = i; 185 _offsetMax = i;
195 break; 186 break;
196 case 'valid': 187 case 'valid':
197 _offsetCnt = i; 188 _offsetCnt = i;
198 break; 189 break;
199 } 190 }
200 } 191 }
201 computedAggregationTypes = new List.from(aggregationTypes, growable: false); 192 computedAggregationTypes = new List.from(aggregationTypes, growable: false);
202 193
203 // Preprocess the data 194 // Preprocess the data
204 _preprocess(); 195 _preprocess();
205 } 196 }
206 197
207 /** 198 /// Re-calculate aggregations based on new dimensions.
208 * Re-calculate aggregations based on new dimensions.
209 */
210 void groupBy(List dimensions, [AggregationFilterFunc filter = null]) { 199 void groupBy(List dimensions, [AggregationFilterFunc filter = null]) {
211 if (dimensions == null) { 200 if (dimensions == null) {
212 dimensions = []; 201 dimensions = [];
213 } 202 }
214 203
215 List savedDimFields = _dimFields; 204 List savedDimFields = _dimFields;
216 _dimFields = new List.from(dimensions, growable: false); 205 _dimFields = new List.from(dimensions, growable: false);
217 206
218 _dimPrefixLength = 0; 207 _dimPrefixLength = 0;
219 while (_dimPrefixLength < _dimFields.length && 208 while (_dimPrefixLength < _dimFields.length &&
220 _dimPrefixLength < savedDimFields.length && 209 _dimPrefixLength < savedDimFields.length &&
221 savedDimFields[_dimPrefixLength] == _dimFields[_dimPrefixLength]) { 210 savedDimFields[_dimPrefixLength] == _dimFields[_dimPrefixLength]) {
222 ++_dimPrefixLength; 211 ++_dimPrefixLength;
223 } 212 }
224 213
225 _createBuffers(); 214 _createBuffers();
226 _preprocess(groupBy:true); 215 _preprocess(groupBy: true);
227 216
228 // For groupBy, compute immediately. 217 // For groupBy, compute immediately.
229 compute(filter); 218 compute(filter);
230 219
231 // Ensure that cache represents updated dimensions 220 // Ensure that cache represents updated dimensions
232 _updateCachedEntities(); 221 _updateCachedEntities();
233 } 222 }
234 223
235 /** 224 /// Create buffers.
236 * Create buffers. 225 ///
237 * This method is called when the object is being created and when 226 /// This method is called when the object is being created and when
238 * a groupBy is called to change the dimensions on which 227 /// a groupBy is called to change the dimensions on which
239 * aggregations are computed. 228 /// aggregations are computed.
240 */
241 void _createBuffers() { 229 void _createBuffers() {
242 // Create both when object is created and groupBy is called 230 // Create both when object is created and groupBy is called
243 _dimEnumCache = new Int32List(_dimFields.length * _rows.length); 231 _dimEnumCache = new Int32List(_dimFields.length * _rows.length);
244 232
245 // Create only when the object is created 233 // Create only when the object is created
246 if (_factsCache == null) { 234 if (_factsCache == null) {
247 _factsCache = new Float64List((_factFields.length + 1) * _rows.length); 235 _factsCache = new Float64List((_factFields.length + 1) * _rows.length);
248 } 236 }
249 237
250 // Create only when the object is created 238 // Create only when the object is created
251 if (_filterResults == null) { 239 if (_filterResults == null) {
252 _filterResults = new List<int>((_rows.length) ~/ SMI_BITS + 1); 240 _filterResults = new List<int>((_rows.length) ~/ SMI_BITS + 1);
253 } 241 }
254 242
255 // Create both when object is created and groupBy is called 243 // Create both when object is created and groupBy is called
256 // Reuse dimension enumerations if possible. 244 // Reuse dimension enumerations if possible.
257 var oldDimToInt = _dimToIntMap; 245 var oldDimToInt = _dimToIntMap;
258 _dimToIntMap = new List<Map<dynamic, int>>.generate(_dimFields.length, 246 _dimToIntMap = new List<Map<dynamic, int>>.generate(_dimFields.length,
259 (i) => i < _dimPrefixLength ? oldDimToInt[i] : new Map<dynamic, int>()); 247 (i) => i < _dimPrefixLength ? oldDimToInt[i] : new Map<dynamic, int>());
260 } 248 }
261 249
262 /** 250 /// Check cache entries
263 * Check cache entries 251 /// When data is grouped by a new dimensions, entities that were
264 * When data is grouped by a new dimensions, entities that were 252 /// created prior to the groupBy should be cleared and removed from cache
265 * created prior to the groupBy should be cleared and removed from cache 253 /// if they aren't valid anymore.
266 * if they aren't valid anymore. 254 /// Update the entities that are valid after the groupBy.
267 * Update the entities that are valid after the groupBy.
268 */
269 void _updateCachedEntities() { 255 void _updateCachedEntities() {
270 List keys = new List.from(_entityCache.keys, growable: false); 256 List keys = new List.from(_entityCache.keys, growable: false);
271 keys.forEach((key) { 257 keys.forEach((key) {
272 _AggregationItemImpl entity = _entityCache[key]; 258 _AggregationItemImpl entity = _entityCache[key];
273 if (entity == null) { 259 if (entity == null) {
274 _entityCache.remove(key); 260 _entityCache.remove(key);
275 } else if (entity != null && entity.isValid) { 261 } else if (entity != null && entity.isValid) {
276 if (key.split(':').length <= _dimPrefixLength) { 262 if (key.split(':').length <= _dimPrefixLength) {
277 entity.update(); 263 entity.update();
278 } else { 264 } else {
279 _entityCache.remove(key); 265 _entityCache.remove(key);
280 entity.clear(); 266 entity.clear();
281 } 267 }
282 } 268 }
283 }); 269 });
284 } 270 }
285 271
286 final Map<String, List> _parsedKeys = {}; 272 final Map<String, List> _parsedKeys = {};
287 /** 273 /// Get value from a map-like object
288 * Get value from a map-like object
289 */
290 dynamic _fetch(var item, String key) { 274 dynamic _fetch(var item, String key) {
291 if (walkThroughMap && key.contains('.')) { 275 if (walkThroughMap && key.contains('.')) {
292 return walk(item, key, _parsedKeys); 276 return walk(item, key, _parsedKeys);
293 } else { 277 } else {
294 return item[key]; 278 return item[key];
295 } 279 }
296 } 280 }
297 281
298 /* 282 /// Preprocess Data
299 * Preprocess Data 283 /// - Enumerate dimension values
300 * - Enumerate dimension values 284 /// - Create sort orders for dimension values
301 * - Create sort orders for dimension values 285 /// - Cache facts in lists
302 * - Cache facts in lists
303 */
304 void _preprocess({bool groupBy: false}) { 286 void _preprocess({bool groupBy: false}) {
305
306 _timeItStart('preprocess'); 287 _timeItStart('preprocess');
307 288
308 // Enumerate dimensions... 289 // Enumerate dimensions...
309 // Cache dimensions and facts. 290 // Cache dimensions and facts.
310 291
311 List<int> dimensionValCount = 292 List<int> dimensionValCount =
312 new List<int>.generate(_dimFields.length, (idx) => 0); 293 new List<int>.generate(_dimFields.length, (idx) => 0);
313 294
314 int dimensionsCount = _dimFields.length; 295 int dimensionsCount = _dimFields.length;
315 int factsCount = _factFields.length; 296 int factsCount = _factFields.length;
316 int rowCount = _rows.length; 297 int rowCount = _rows.length;
317 298
318 for (int ri = 0, factsDataOffset = 0, dimDataOffset = 0; 299 for (int ri = 0, factsDataOffset = 0, dimDataOffset = 0;
319 ri < rowCount; ++ri, factsDataOffset += factsCount, 300 ri < rowCount;
320 dimDataOffset += dimensionsCount) { 301 ++ri, factsDataOffset += factsCount, dimDataOffset += dimensionsCount) {
321 var item = _rows[ri]; 302 var item = _rows[ri];
322 303
323 // Cache the fact values in the big buffer, but only 304 // Cache the fact values in the big buffer, but only
324 // when we are initializing (not when a groupBy was called 305 // when we are initializing (not when a groupBy was called
325 // after initialization) 306 // after initialization)
326 if (!groupBy) { 307 if (!groupBy) {
327 for (int fi = 0; fi < factsCount; fi++) { 308 for (int fi = 0; fi < factsCount; fi++) {
328 var value = factsAccessor(item,_factFields[fi]); 309 var value = factsAccessor(item, _factFields[fi]);
329 _factsCache[factsDataOffset + fi] = 310 _factsCache[factsDataOffset + fi] =
330 (value == null) ? double.NAN : value.toDouble(); 311 (value == null) ? double.NAN : value.toDouble();
331 } 312 }
332 } 313 }
333 314
334 // Enumerate the dimension values and cache enumerated rows 315 // Enumerate the dimension values and cache enumerated rows
335 for (int di = 0; di < dimensionsCount; di++) { 316 for (int di = 0; di < dimensionsCount; di++) {
336 var dimensionVal = dimensionAccessor(item, _dimFields[di]); 317 var dimensionVal = dimensionAccessor(item, _dimFields[di]);
337 int dimensionValEnum = _dimToIntMap[di][dimensionVal]; 318 int dimensionValEnum = _dimToIntMap[di][dimensionVal];
338 if (dimensionValEnum == null) { 319 if (dimensionValEnum == null) {
(...skipping 12 matching lines...) Expand all
351 if (groupBy && i < _dimPrefixLength) { 332 if (groupBy && i < _dimPrefixLength) {
352 return oldSortOrders[i]; 333 return oldSortOrders[i];
353 } 334 }
354 335
355 List dimensionVals = new List.from(_dimToIntMap[i].keys); 336 List dimensionVals = new List.from(_dimToIntMap[i].keys);
356 List retval = new List(_dimToIntMap[i].length); 337 List retval = new List(_dimToIntMap[i].length);
357 338
358 // When a comparator is not specified, our implementation of the 339 // When a comparator is not specified, our implementation of the
359 // comparator tries to gracefully handle null values. 340 // comparator tries to gracefully handle null values.
360 dimensionVals.sort( 341 dimensionVals.sort(
361 comparators != null && comparators.containsKey(_dimFields[i]) ? 342 comparators != null && comparators.containsKey(_dimFields[i])
362 comparators[_dimFields[i]] : _defaultDimComparator); 343 ? comparators[_dimFields[i]]
344 : _defaultDimComparator);
363 345
364 for (int si = 0; si < retval.length; ++si) { 346 for (int si = 0; si < retval.length; ++si) {
365 retval[_dimToIntMap[i][dimensionVals[si]]] = si; 347 retval[_dimToIntMap[i][dimensionVals[si]]] = si;
366 } 348 }
367 return retval; 349 return retval;
368 }, growable: false); 350 }, growable: false);
369 351
370 // Create a list of sorted indices - only if we are not having a full 352 // Create a list of sorted indices - only if we are not having a full
371 // overlap of dimensionFields. 353 // overlap of dimensionFields.
372 if (!groupBy || _dimPrefixLength != _dimFields.length) { 354 if (!groupBy || _dimPrefixLength != _dimFields.length) {
373 _sorted = new List<int>.generate(_rows.length, (i) => i, growable: false); 355 _sorted = new List<int>.generate(_rows.length, (i) => i, growable: false);
374 _sorted.sort(_comparator); 356 _sorted.sort(_comparator);
375 } 357 }
376 358
377 // Pre-compute frequently used values 359 // Pre-compute frequently used values
378 _offsetSortedIndex = factsCount * _aggregationTypesCount; 360 _offsetSortedIndex = factsCount * _aggregationTypesCount;
379 _offsetFilteredCount = factsCount * _aggregationTypesCount + 1; 361 _offsetFilteredCount = factsCount * _aggregationTypesCount + 1;
380 362
381 _timeItEnd(); 363 _timeItEnd();
382 } 364 }
383 365
384 // Ensures that null dimension values don't cause an issue with sorting 366 // Ensures that null dimension values don't cause an issue with sorting
385 _defaultDimComparator(Comparable left, Comparable right) => 367 _defaultDimComparator(Comparable left, Comparable right) =>
386 (left == null && right == null) ? 0 : 368 (left == null && right == null)
387 (left == null) ? -1 : 369 ? 0
388 (right == null) ? 1 : left.compareTo(right); 370 : (left == null) ? -1 : (right == null) ? 1 : left.compareTo(right);
389 371
390 /* 372 /// Given item indices in rows, compare them based
391 * Given item indices in rows, compare them based 373 /// on the sort orders created while pre-processing data.
392 * on the sort orders created while preprocessing data.
393 */
394 _comparator(int one, int two) { 374 _comparator(int one, int two) {
395 if (one == two) { 375 if (one == two) {
396 return 0; 376 return 0;
397 } 377 }
398 378
399 int offsetOne = _dimFields.length * one; 379 int offsetOne = _dimFields.length * one;
400 int offsetTwo = _dimFields.length * two; 380 int offsetTwo = _dimFields.length * two;
401 381
402 for (int i = 0; i < _dimFields.length; ++i) { 382 for (int i = 0; i < _dimFields.length; ++i) {
403 int diff = _dimSortOrders[i][_dimEnumCache[offsetOne + i]] - 383 int diff = _dimSortOrders[i][_dimEnumCache[offsetOne + i]] -
404 _dimSortOrders[i][_dimEnumCache[offsetTwo + i]]; 384 _dimSortOrders[i][_dimEnumCache[offsetTwo + i]];
405 if (diff != 0) { 385 if (diff != 0) {
406 return diff; 386 return diff;
407 } 387 }
408 } 388 }
409 return 0; 389 return 0;
410 } 390 }
411 391
412 /** 392 /// Compute aggregations
413 * Compute aggregations 393 /// If [filter] is not null, it would be used to filter out items that
414 * If [filter] is not null, it would be used to filter out items that 394 /// should not be included in the aggregates.
415 * should not be included in the aggregates.
416 */
417 void compute([AggregationFilterFunc filter = null]) { 395 void compute([AggregationFilterFunc filter = null]) {
418 _timeItStart('compute'); 396 _timeItStart('compute');
419 397
420 _dimToAggrMap = new Map<String, int>(); 398 _dimToAggrMap = new Map<String, int>();
421 _aggregations = new Float64List(AGGREGATIONS_BUFFER_LENGTH); 399 _aggregations = new Float64List(AGGREGATIONS_BUFFER_LENGTH);
422 _filterResults = filter == null ? 400 _filterResults = filter == null
423 null : new List<int>.filled((_rows.length ~/ SMI_BITS) + 1, 0); 401 ? null
402 : new List<int>.filled((_rows.length ~/ SMI_BITS) + 1, 0);
424 403
425 int rowCount = _rows.length; 404 int rowCount = _rows.length;
426 int dimensionCount = _dimFields.length; 405 int dimensionCount = _dimFields.length;
427 int factsCount = _factFields.length; 406 int factsCount = _factFields.length;
428 407
429 // Saves current dimension value to which we are aggregating 408 // Saves current dimension value to which we are aggregating
430 // Values of dimensions are in even indices (starting at 0) and 409 // Values of dimensions are in even indices (starting at 0) and
431 // location of respective dimension in buffer is in odd indices. 410 // location of respective dimension in buffer is in odd indices.
432 List<int> currentDim = new List<int>(dimensionCount * 2); 411 List<int> currentDim = new List<int>(dimensionCount * 2);
433 bool reset = true; 412 bool reset = true;
434 bool isNewDimension = false; 413 bool isNewDimension = false;
435 int aggregationSizePerDim = factsCount * _aggregationTypesCount; 414 int aggregationSizePerDim = factsCount * _aggregationTypesCount;
436 415
437 // Reserve the 0th position for top-level aggregations. 416 // Reserve the 0th position for top-level aggregations.
438 int currentBufferPos = (factsCount * _aggregationTypesCount + 2); 417 int currentBufferPos = (factsCount * _aggregationTypesCount + 2);
439 _dimToAggrMap[''] = 0; 418 _dimToAggrMap[''] = 0;
440 _aggregations[_offsetSortedIndex] = 0.0; 419 _aggregations[_offsetSortedIndex] = 0.0;
441 420
442
443 for (int ri = 0, index = 0, dimensionDataOffset = 0, factsDataOffset = 0; 421 for (int ri = 0, index = 0, dimensionDataOffset = 0, factsDataOffset = 0;
444 ri < rowCount; ++ri, reset = false) { 422 ri < rowCount;
445 423 ++ri, reset = false) {
446 // If filter is not null, check if this row must be included in 424 // If filter is not null, check if this row must be included in
447 // the aggregations and mark it accordingly. 425 // the aggregations and mark it accordingly.
448 index = _sorted[ri]; 426 index = _sorted[ri];
449 if (filter != null) { 427 if (filter != null) {
450 if (!filter(_rows[index])) { 428 if (!filter(_rows[index])) {
451 continue; 429 continue;
452 } else { 430 } else {
453 _filterResults[ri ~/ SMI_BITS] |= (1 << (ri % SMI_BITS)); 431 _filterResults[ri ~/ SMI_BITS] |= (1 << (ri % SMI_BITS));
454 } 432 }
455 } 433 }
456 434
457 dimensionDataOffset = index * dimensionCount; 435 dimensionDataOffset = index * dimensionCount;
458 factsDataOffset = index * factsCount; 436 factsDataOffset = index * factsCount;
459 437
460 // Update top-level aggregations. 438 // Update top-level aggregations.
461 _updateAggregationsAt(0, factsDataOffset, ri == 0 ? true : false); 439 _updateAggregationsAt(0, factsDataOffset, ri == 0 ? true : false);
462 440
463 // See which dimensions get effected by this row. 441 // See which dimensions get effected by this row.
464 // di => the index of the dimension 442 // di => the index of the dimension
465 // ci => index of the cached value in [currentDim] 443 // ci => index of the cached value in [currentDim]
466 for (int di = 0, ci = 0; di < dimensionCount; ++di, ci += 2) { 444 for (int di = 0, ci = 0; di < dimensionCount; ++di, ci += 2) {
467 // If a dimension value changed, then all dimensions that are lower 445 // If a dimension value changed, then all dimensions that are lower
468 // in the hierarchy change too. 446 // in the hierarchy change too.
469 if (reset || 447 if (reset ||
470 currentDim[ci] != _dimEnumCache[dimensionDataOffset + di]) { 448 currentDim[ci] != _dimEnumCache[dimensionDataOffset + di]) {
471 currentDim[ci] = _dimEnumCache[dimensionDataOffset + di]; 449 currentDim[ci] = _dimEnumCache[dimensionDataOffset + di];
472 currentDim[ci + 1] = currentBufferPos; 450 currentDim[ci + 1] = currentBufferPos;
473 451
474 // Save location to aggregations position in the buffer 452 // Save location to aggregations position in the buffer
475 _dimToAggrMap[new List.generate(di + 1, 453 _dimToAggrMap[new List.generate(di + 1, (i) => currentDim[2 * i])
476 (i) => currentDim[2 * i]).join(':')] = currentBufferPos; 454 .join(':')] = currentBufferPos;
477 455
478 // Store items start position 456 // Store items start position
479 _aggregations[currentBufferPos + _offsetSortedIndex] = ri.toDouble(); 457 _aggregations[currentBufferPos + _offsetSortedIndex] = ri.toDouble();
480 458
481 // After the aggregated values, we save the filtered count, 459 // After the aggregated values, we save the filtered count,
482 // index of the first item (in sorted) 460 // index of the first item (in sorted)
483 currentBufferPos += (aggregationSizePerDim + 2); 461 currentBufferPos += (aggregationSizePerDim + 2);
484 reset = true; 462 reset = true;
485 isNewDimension = true; 463 isNewDimension = true;
486 } 464 }
487 465
488 _updateAggregationsAt(currentDim[ci + 1], 466 _updateAggregationsAt(
489 factsDataOffset, isNewDimension); 467 currentDim[ci + 1], factsDataOffset, isNewDimension);
490 isNewDimension = false; 468 isNewDimension = false;
491 } 469 }
492 } 470 }
493 471
494 _timeItEnd(); 472 _timeItEnd();
495 } 473 }
496 474
497 /** 475 /// Helper function that does the actual aggregations.
498 * Helper function that does the actual aggregations. 476 /// This function is called once per row per dimension.
499 * This function is called once per row per dimension. 477 _updateAggregationsAt(
500 */ 478 int aggrDataOffset, int factsDataOffset, bool isNewDimension) {
501 _updateAggregationsAt(int aggrDataOffset,
502 int factsDataOffset, bool isNewDimension) {
503 // Update count. 479 // Update count.
504 _aggregations[aggrDataOffset + _offsetFilteredCount] += 1; 480 _aggregations[aggrDataOffset + _offsetFilteredCount] += 1;
505 481
506 // Update aggregation for each of the facts. 482 // Update aggregation for each of the facts.
507 for (int fi = 0, bufferFactOffset = aggrDataOffset; 483 for (int fi = 0, bufferFactOffset = aggrDataOffset;
508 fi < _factFields.length; 484 fi < _factFields.length;
509 bufferFactOffset += _aggregationTypesCount, ++fi) { 485 bufferFactOffset += _aggregationTypesCount, ++fi) {
510
511 double factValue = _factsCache[factsDataOffset + fi]; 486 double factValue = _factsCache[factsDataOffset + fi];
512 if (factValue.isNaN) { 487 if (factValue.isNaN) {
513 continue; 488 continue;
514 } 489 }
515 490
516 // Sum 491 // Sum
517 if (_offsetSum != null) { 492 if (_offsetSum != null) {
518 _aggregations[bufferFactOffset + _offsetSum] += factValue; 493 _aggregations[bufferFactOffset + _offsetSum] += factValue;
519 } 494 }
520 495
521 // Min 496 // Min
522 if (_offsetMin != null && (isNewDimension || factValue < 497 if (_offsetMin != null &&
523 _aggregations[bufferFactOffset + _offsetMin])) { 498 (isNewDimension ||
499 factValue < _aggregations[bufferFactOffset + _offsetMin])) {
524 _aggregations[bufferFactOffset + _offsetMin] = factValue; 500 _aggregations[bufferFactOffset + _offsetMin] = factValue;
525 } 501 }
526 502
527 // Max 503 // Max
528 if (_offsetMax != null && (isNewDimension || factValue > 504 if (_offsetMax != null &&
529 _aggregations[bufferFactOffset + _offsetMax])) { 505 (isNewDimension ||
506 factValue > _aggregations[bufferFactOffset + _offsetMax])) {
530 _aggregations[bufferFactOffset + _offsetMax] = factValue; 507 _aggregations[bufferFactOffset + _offsetMax] = factValue;
531 } 508 }
532 509
533 // Count 510 // Count
534 if (_offsetCnt != null) { 511 if (_offsetCnt != null) {
535 _aggregations[bufferFactOffset + _offsetCnt]++; 512 _aggregations[bufferFactOffset + _offsetCnt]++;
536 } 513 }
537 } 514 }
538 } 515 }
539 516
540 /* 517 // TODO(prsd):
541 * TODO(prsd): 518 // 1. Implementation of updates and posting updates to entities.
542 * 1. Implementation of updates and posting updates to entities. 519 // patchEntity and addToEntity must add listeners on AggregationItems
543 * patchEntity and addToEntity must add listeners on AggregationItems 520 // and any changes must be propagated to entities.
544 * and any changes must be propagated to entities. 521 // 2. Updates (add/remove/update) should do their best to update the
545 * 2. Updates (add/remove/update) should do their best to update the 522 // aggregations and then maybe do a delayed recomputation (sort etc;)
546 * aggregations and then maybe do a delayed recomputation (sort etc;)
547 */
548 523
549 /** 524 /// Update an item.
550 * Update an item. 525 /// If aggregates were already computed, they are updated to reflect the
551 * If aggregates were already computed, they are updated to reflect the 526 /// new value and any observers are notified.
552 * new value and any observers are notified.
553 */
554 void updateItem(dynamic item, String field) { 527 void updateItem(dynamic item, String field) {
555 throw new UnimplementedError(); 528 throw new UnimplementedError();
556 } 529 }
557 530
558 /** 531 /// Add a new item.
559 * Add a new item. 532 /// If aggregates were already computed, they are updated to reflect
560 * If aggregates were already computed, they are updated to reflect 533 /// values on the new item.
561 * values on the new item.
562 */
563 void addItem(dynamic item) { 534 void addItem(dynamic item) {
564 throw new UnimplementedError(); 535 throw new UnimplementedError();
565 } 536 }
566 537
567 /** 538 /// Remove an existing item.
568 * Remove an existing item. 539 /// If aggregates were already computed, they are updated to reflect
569 * If aggregates were already computed, they are updated to reflect 540 /// facts on the removed item.
570 * facts on the removed item.
571 */
572 void removeItem(dynamic item) { 541 void removeItem(dynamic item) {
573 throw new UnimplementedError(); 542 throw new UnimplementedError();
574 } 543 }
575 544
576 /** 545 /// Return an [AggregationItem] that represents facts for dimension
577 * Return an [AggregationItem] that represents facts for dimension 546 /// represented by [dimension] Only one instance of an entity is created
578 * represented by [dimension] Only one instance of an entity is created 547 /// per dimension (even if this function is called multiple times)
579 * per dimension (even if this function is called multiple times) 548 ///
580 * 549 /// Callers of this method can observe the returned entity for updates to
581 * Callers of this method can observe the returned entity for updates to 550 /// aggregations caused by changes to filter or done through add, remove
582 * aggregations caused by changes to filter or done through add, remove 551 /// or modify of items in the collection.
583 * or modify of items in the collection.
584 */
585 AggregationItem facts(List dimension) { 552 AggregationItem facts(List dimension) {
586 List<int> enumeratedList = new List<int>(); 553 List<int> enumeratedList = new List<int>();
587 for (int i = 0; i < dimension.length; ++i) { 554 for (int i = 0; i < dimension.length; ++i) {
588 enumeratedList.add(_dimToIntMap[i][dimension[i]]); 555 enumeratedList.add(_dimToIntMap[i][dimension[i]]);
589 } 556 }
590 557
591 String key = enumeratedList.join(':'); 558 String key = enumeratedList.join(':');
592 AggregationItem item = _entityCache[key]; 559 AggregationItem item = _entityCache[key];
593 560
594 if (item == null && _dimToAggrMap.containsKey(key)) { 561 if (item == null && _dimToAggrMap.containsKey(key)) {
595 item = new _AggregationItemImpl(this, dimension, key); 562 item = new _AggregationItemImpl(this, dimension, key);
596 _entityCache[key] = item; 563 _entityCache[key] = item;
597 } 564 }
598 565
599 return item; 566 return item;
600 } 567 }
601 568
602 /** 569 /// Return a list of values that are present for a dimension field.
603 * Return a list of values that are present for a dimension field.
604 */
605 List valuesForDimension(dynamic dimensionFieldName) { 570 List valuesForDimension(dynamic dimensionFieldName) {
606 int di = _dimFields.indexOf(dimensionFieldName); 571 int di = _dimFields.indexOf(dimensionFieldName);
607 if (di < 0) { 572 if (di < 0) {
608 return null; 573 return null;
609 } 574 }
610 List values = new List.from(_dimToIntMap[di].keys); 575 List values = new List.from(_dimToIntMap[di].keys);
611 values.sort( 576 values.sort(
612 comparators != null && comparators.containsKey(dimensionFieldName) ? 577 comparators != null && comparators.containsKey(dimensionFieldName)
613 comparators[dimensionFieldName] : _defaultDimComparator); 578 ? comparators[dimensionFieldName]
579 : _defaultDimComparator);
614 return values; 580 return values;
615 } 581 }
616 } 582 }
617 583
618 /** 584 /// Parse a path for nested map-like objects.
619 * Parse a path for nested map-like objects. 585 /// Caches the parsed key in the passed map.
620 * Caches the parsed key in the passed map. 586 ///
621 * 587 /// Takes map keys of the format:
622 * Takes map keys of the format: 588 /// "list(key=val;val=m).another(state=good).state"
623 * "list(key=val;val=m).another(state=good).state" 589 /// and outputs:
624 * and outputs: 590 /// ["list", {"key": "val", "val": "m"},
625 * ["list", {"key": "val", "val": "m"}, 591 /// "another", {"state": "good"}, "state"]
626 * "another", {"state": "good"}, "state"]
627 */
628 List _parseKey(String key, Map parsedKeysCache) { 592 List _parseKey(String key, Map parsedKeysCache) {
629 List parts = parsedKeysCache == null ? null : parsedKeysCache[key]; 593 List parts = parsedKeysCache == null ? null : parsedKeysCache[key];
630 if (parts == null && key != null) { 594 if (parts == null && key != null) {
631 parts = new List(); 595 parts = new List();
632 if (key.contains(').')) { 596 if (key.contains(').')) {
633 int start = 0; 597 int start = 0;
634 int cursor = 0; 598 int cursor = 0;
635 bool inParams = false; 599 bool inParams = false;
636 List matchKeyVals; 600 List matchKeyVals;
637 Map listMatchingMap = {}; 601 Map listMatchingMap = {};
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after
671 parts = key.split('.'); 635 parts = key.split('.');
672 } 636 }
673 if (parsedKeysCache != null) { 637 if (parsedKeysCache != null) {
674 parsedKeysCache[key] = parts; 638 parsedKeysCache[key] = parts;
675 } 639 }
676 } 640 }
677 641
678 return parts; 642 return parts;
679 } 643 }
680 644
681 /** 645 /// Walk a map-like structure that could have list in the path.
682 * Walk a map-like structure that could have list in the path. 646 ///
683 * 647 /// Example:
684 * Example: 648 /// Map testMap = {
685 * Map testMap = { 649 /// "first": "firstval",
686 * "first": "firstval", 650 /// "list": [
687 * "list": [ 651 /// { "val": "m",
688 * { "val": "m", 652 /// "key": "val",
689 * "key": "val", 653 /// "another": [
690 * "another": [ 654 /// { 'state': 'good' },
691 * { 'state': 'good' }, 655 /// { 'state': 'bad' }
692 * { 'state': 'bad' } 656 /// ]
693 * ] 657 /// },
694 * }, 658 /// { "val": "m", "key": "invalid" },
695 * { "val": "m", "key": "invalid" }, 659 /// { "val": "o" }
696 * { "val": "o" } 660 /// ]
697 * ] 661 /// };
698 * }; 662 ///
699 * 663 /// For the above map:
700 * For the above map: 664 /// walk(testMap, "list(key=val;val=m).another(state=good).state");
701 * walk(testMap, "list(key=val;val=m).another(state=good).state"); 665 /// outputs:
702 * outputs: 666 /// good
703 * good
704 */
705 dynamic walk(initial, String key, Map parsedKeyCache) { 667 dynamic walk(initial, String key, Map parsedKeyCache) {
706 List parts = _parseKey(key, parsedKeyCache); 668 List parts = _parseKey(key, parsedKeyCache);
707 return parts.fold(initial, (current, part) { 669 return parts.fold(initial, (current, part) {
708 if (current == null) { 670 if (current == null) {
709 return null; 671 return null;
710 } else if (current is List && part is Map) { 672 } else if (current is List && part is Map) {
711 for (int i = 0; i < current.length; i++) { 673 for (int i = 0; i < current.length; i++) {
712 bool match = true; 674 bool match = true;
713 part.forEach((key, val) { 675 part.forEach((key, val) {
714 if ((key.contains('.') && 676 if ((key.contains('.') &&
715 walk(part, key, parsedKeyCache).toString() != val) || 677 walk(part, key, parsedKeyCache).toString() != val) ||
716 part[key] != val) { 678 part[key] != val) {
717 match = false; 679 match = false;
718 } 680 }
719 }); 681 });
720 if (match) { 682 if (match) {
721 return current[i]; 683 return current[i];
722 } 684 }
723 } 685 }
724 } else { 686 } else {
725 return current[part]; 687 return current[part];
726 } 688 }
727 }); 689 });
728 } 690 }
OLDNEW
« no previous file with comments | « packages/charted/lib/charts/chart_theme.dart ('k') | packages/charted/lib/charts/data_transformers/aggregation_item.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698