1const TrendLines = { 2 xAxisAttribute: null, // { name, min, max, isTemporal, timeUnit } 3 yAxisAttribute: null, // { name, min, max, isTemporal, timeUnit } 4 splittingAttributes: null, // { encodingName: { encodingName, name, numberOfBins, isQuantitativeBin, isTemporal, timeUnit, min, max, binSize, allGroups } } 5 nonSplittingAttributes: null, // { encodingName: { encodingName, name } } 6 7 dataAfterFiltering: null, 8 allGroups: null, // [ { splittingAttributes: { encodingName: { name, value } ... }, objects, regressionParam, pointsOnTrendLine }] 9 categoryToColourDict: {}, 10 stopGeneratingSpec: null, 11 12 applyConstraints: function() { 13 const self = this; 14 15 self.applyNumberOfRecordsConstraints([ 'row', 'column', 'shape' ]); 16 self.applyBinConstraints([ 'row', 'column', 'shape' ]); 17 self.applyYearConstraints([ 'xAxis', 'yAxis', 'row', 'column', 'size', 'colour', 'shape', 'tooltip' ]); 18 }, 19 applyNumberOfRecordsConstraints: function(shelfNames) { 20 const self = this; 21 let stopGeneratingSpec = false; 22 23 for (let i = 0; i < shelfNames.length; i++) { 24 let currentShelfName = shelfNames[i]; 25 let currentShelfData = Shelf[currentShelfName].getCapsuleData(); 26 let isCurrentShelfEmpty = Shelf[currentShelfName].isEmpty(); 27 28 if (!isCurrentShelfEmpty) { 29 let isCurrentShelfNumberOfRecords = 30 currentShelfData.type == 'quantitative' && 31 currentShelfData.attributeName == 'Number of Records' && 32 currentShelfData.isAutoGenerated; 33 34 if (isCurrentShelfNumberOfRecords) { 35 Shelves.restoreState(); 36 Shelf[currentShelfName].blink(); 37 VisualizationPane.stopUpdating(); 38 stopGeneratingSpec = true; 39 break; 40 } 41 } 42 } 43 44 self.stopGeneratingSpec = stopGeneratingSpec; 45 }, 46 applyBinConstraints: function(shelfNames) { 47 const self = this; 48 49 if (self.stopGeneratingSpec) 50 return; 51 52 for (let i = 0; i < shelfNames.length; i++) { 53 let currentShelfName = shelfNames[i]; 54 let currentShelfData = Shelf[currentShelfName].getCapsuleData(); 55 let isCurrentShelfEmpty = Shelf[currentShelfName].isEmpty(); 56 57 if (!isCurrentShelfEmpty) { 58 let isCurrentShelfQuantitative = currentShelfData.type == 'quantitative'; 59 let isCurrentShelfNumberOfRecords = 60 currentShelfData.type == 'quantitative' && 61 currentShelfData.attributeName == 'Number of Records' && 62 currentShelfData.isAutoGenerated; 63 64 if (isCurrentShelfQuantitative && !isCurrentShelfNumberOfRecords) { 65 currentShelfData.aggregate = 'bin'; 66 Shelves.refreshCapsules(); 67 } 68 } 69 } 70 }, 71 applyYearConstraints: function(shelfNames) { 72 const self = this; 73 74 if (self.stopGeneratingSpec) 75 return; 76 77 for (let i = 0; i < shelfNames.length; i++) { 78 let currentShelfName = shelfNames[i]; 79 let currentShelfData = Shelf[currentShelfName].getCapsuleData(); 80 let isCurrentShelfEmpty = Shelf[currentShelfName].isEmpty(); 81 82 if (!isCurrentShelfEmpty) { 83 let isCurrentShelfTemporal = currentShelfData.type == 'temporal'; 84 let isCurrentShelfTimeUnitUnset = currentShelfData.timeUnit == 'none'; 85 86 if (isCurrentShelfTemporal && isCurrentShelfTimeUnitUnset) { 87 currentShelfData.timeUnit = 'year'; 88 Shelves.refreshCapsules(); 89 } 90 } 91 } 92 }, 93 init: function() { 94 const self = this; 95 let xAxisShelfData = Shelf.xAxis.getCapsuleData(); 96 let yAxisShelfData = Shelf.yAxis.getCapsuleData(); 97 let colourShelfData = Shelf.colour.getCapsuleData(); 98 let rowShelfData = Shelf.row.getCapsuleData(); 99 let columnShelfData = Shelf.column.getCapsuleData(); 100 let sizeShelfData = Shelf.size.getCapsuleData(); 101 let shapeShelfData = Shelf.shape.getCapsuleData(); 102 103 let isColourShelfEmpty = Shelf.colour.isEmpty() 104 let isRowShelfEmpty = Shelf.row.isEmpty(); 105 let isColumnShelfEmpty = Shelf.column.isEmpty(); 106 let isSizeShelfEmpty = Shelf.size.isEmpty(); 107 let isShapeShelfEmpty = Shelf.shape.isEmpty(); 108 109 let splitAttrOnColorShelf = isColourShelfEmpty ? false : self.isSplittingAttribute(colourShelfData); 110 let splitAttrOnRowShelf = isRowShelfEmpty ? false : self.isSplittingAttribute(rowShelfData); 111 let splitAttrOnColumnShelf = isColumnShelfEmpty ? false : self.isSplittingAttribute(columnShelfData); 112 let splitAttrOnSizeShelf = isSizeShelfEmpty ? false : self.isSplittingAttribute(sizeShelfData); 113 let splitAttrOnShapeShelf = isShapeShelfEmpty ? false : self.isSplittingAttribute(shapeShelfData); 114 115 let xAxisAttribute = self.createAxisAttribute(xAxisShelfData); 116 let yAxisAttribute = self.createAxisAttribute(yAxisShelfData); 117 let splittingAttributes = null; 118 let nonSplittingAttributes = null; 119 120 if ((!isColourShelfEmpty && splitAttrOnColorShelf) || 121 (!isRowShelfEmpty && splitAttrOnRowShelf) || 122 (!isColumnShelfEmpty && splitAttrOnColumnShelf) || 123 (!isSizeShelfEmpty && splitAttrOnSizeShelf) || 124 (!isShapeShelfEmpty && splitAttrOnShapeShelf)) splittingAttributes = {}; 125 if ((!isColourShelfEmpty && !splitAttrOnColorShelf) || 126 (!isRowShelfEmpty && !splitAttrOnRowShelf) || 127 (!isColumnShelfEmpty && !splitAttrOnColumnShelf) || 128 (!isSizeShelfEmpty && !splitAttrOnSizeShelf) || 129 (!isShapeShelfEmpty && !splitAttrOnShapeShelf)) nonSplittingAttributes = {}; 130 131 if (!isColourShelfEmpty && splitAttrOnColorShelf) splittingAttributes.color = self.createSplittingAttribute(colourShelfData, 'color'); 132 if (!isRowShelfEmpty && splitAttrOnRowShelf) splittingAttributes.row = self.createSplittingAttribute(rowShelfData, 'row'); 133 if (!isColumnShelfEmpty && splitAttrOnColumnShelf) splittingAttributes.column = self.createSplittingAttribute(columnShelfData, 'column'); 134 if (!isSizeShelfEmpty && splitAttrOnSizeShelf) splittingAttributes.size = self.createSplittingAttribute(sizeShelfData, 'size'); 135 if (!isShapeShelfEmpty && splitAttrOnShapeShelf) splittingAttributes.shape = self.createSplittingAttribute(shapeShelfData, 'shape'); 136 137 if (!isColourShelfEmpty && !splitAttrOnColorShelf) nonSplittingAttributes.color = self.createNonSplittingAttribute(colourShelfData, 'color'); 138 if (!isRowShelfEmpty && !splitAttrOnRowShelf) nonSplittingAttributes.row = self.createNonSplittingAttribute(rowShelfData, 'row'); 139 if (!isColumnShelfEmpty && !splitAttrOnColumnShelf) nonSplittingAttributes.column = self.createNonSplittingAttribute(columnShelfData, 'column'); 140 if (!isSizeShelfEmpty && !splitAttrOnSizeShelf) nonSplittingAttributes.size = self.createNonSplittingAttribute(sizeShelfData, 'size'); 141 if (!isShapeShelfEmpty && !splitAttrOnShapeShelf) nonSplittingAttributes.shape = self.createNonSplittingAttribute(shapeShelfData, 'shape'); 142 143 self.xAxisAttribute = xAxisAttribute; 144 self.yAxisAttribute = yAxisAttribute; 145 self.splittingAttributes = splittingAttributes; 146 self.nonSplittingAttributes = nonSplittingAttributes; 147 }, 148 isSplittingAttribute: function(shelfData) { 149 return (shelfData.type == 'ordinal' || shelfData.type == 'nominal') || 150 (shelfData.type == 'quantitative' && shelfData.aggregate == 'bin') || 151 (shelfData.type == 'temporal' && shelfData.timeUnit == 'year') || 152 (shelfData.type == 'temporal' && shelfData.timeUnit == 'month') || 153 (shelfData.type == 'temporal' && shelfData.timeUnit == 'day'); 154 }, 155 createAxisAttribute: function(shelfData) { 156 let axisAttribute = {}; 157 158 axisAttribute.name = null; 159 axisAttribute.min = null; 160 axisAttribute.max = null; 161 axisAttribute.isTemporal = null; 162 axisAttribute.timeUnit = null; 163 164 if (shelfData.type == 'temporal') { 165 axisAttribute.isTemporal = true; 166 axisAttribute.timeUnit = (shelfData.timeUnit == 'none') ? 'year' : shelfData.timeUnit; 167 axisAttribute.name = shelfData.attributeName + ' (' + axisAttribute.timeUnit + ')'; 168 } 169 170 if (shelfData.type != 'temporal') { 171 axisAttribute.isTemporal = false;
172 axisAttribute.timeUnit = null; 173 axisAttribute.name = shelfData.attributeName; 174 } 175 176 return axisAttribute; 177 }, 178 createSplittingAttribute: function(shelfData, encodingName) { 179 let splittingAttribute = {}; 180 181 splittingAttribute.name = null; 182 splittingAttribute.encodingName = encodingName; 183 splittingAttribute.isQuantitativeBin = (shelfData.type == 'quantitative' && shelfData.aggregate == 'bin'); 184 splittingAttribute.numberOfBins = null; 185 splittingAttribute.min = null; 186 splittingAttribute.max = null; 187 splittingAttribute.binSize = null; 188 splittingAttribute.isTemporal = shelfData.type == 'temporal'; 189 splittingAttribute.timeUnit = (shelfData.type == 'temporal') ? shelfData.timeUnit : null; 190 splittingAttribute.allGroups = null; 191 192 // name 193 if (splittingAttribute.isTemporal) 194 splittingAttribute.name = shelfData.attributeName + ' (' + splittingAttribute.timeUnit + ')'; 195 if (!splittingAttribute.isTemporal) 196 splittingAttribute.name = shelfData.attributeName; 197 198 // number of bins 199 if (splittingAttribute.isQuantitativeBin && shelfData.maxbins == 'none') 200 splittingAttribute.numberOfBins = 5; // default 201 if (splittingAttribute.isQuantitativeBin && shelfData.maxbins != 'none') 202 splittingAttribute.numberOfBins = shelfData.maxbins; 203 204 return splittingAttribute; 205 }, 206 createNonSplittingAttribute: function(shelfData, encodingName) { 207 let nonSplittingAttribute = {}; 208 209 nonSplittingAttribute.name = shelfData.attributeName; 210 nonSplittingAttribute.encodingName = encodingName; 211 212 return nonSplittingAttribute; 213 }, 214 215 // get data 216 217 filterData: function() { 218 const self = this; 219 let xAxisAttribute = self.xAxisAttribute; 220 let yAxisAttribute = self.yAxisAttribute; 221 let splittingAttributes = self.splittingAttributes; 222 let dataAfterApplyingFilters = Filters.getFilteredData(); 223 let dataAfterRemovingMissingValues = []; 224 225 for (let i = 0; i < dataAfterApplyingFilters.length; i++) { 226 let currentRow = dataAfterApplyingFilters[i]; 227 let currentxAxisAttrValue = currentRow[xAxisAttribute.name]; 228 let currentyAxisAttrValue = currentRow[yAxisAttribute.name]; 229 let isAtLeastOneAxisValueMissing = (currentxAxisAttrValue === null || currentyAxisAttrValue === null); 230 let isAtLeastOneSplittingAttrValueMissing = false; 231 232 // find isAtLeastOneSplittingAttrValueMissing 233 for (let encodingName in splittingAttributes) { 234 let currentSplittingAttribute = splittingAttributes[encodingName]; 235 let currentSplittingAttrValue = currentRow[currentSplittingAttribute.name]; 236 237 if (currentSplittingAttrValue === null) { 238 isAtLeastOneSplittingAttrValueMissing = true; 239 break; 240 } 241 } 242 243 // save if none is missing 244 if (!(isAtLeastOneAxisValueMissing || isAtLeastOneSplittingAttrValueMissing)) 245 dataAfterRemovingMissingValues.push(currentRow); 246 } 247 248 self.dataAfterFiltering = dataAfterRemovingMissingValues; 249 }, 250 findMinMax: function() { 251 const self = this; 252 let xAxisAttribute = self.xAxisAttribute; 253 let yAxisAttribute = self.yAxisAttribute; 254 let splittingAttributes = self.splittingAttributes; 255 let quantBinSplittingAttributes = []; 256 257 // init 258 xAxisAttribute.min = Infinity; 259 xAxisAttribute.max = -Infinity; 260 yAxisAttribute.min = Infinity; 261 yAxisAttribute.max = -Infinity; 262 263 for (let encodingName in splittingAttributes) { 264 let currentSplittingAttribute = splittingAttributes[encodingName]; 265 266 if (currentSplittingAttribute.isQuantitativeBin) { 267 currentSplittingAttribute.min = Infinity; 268 currentSplittingAttribute.max = -Infinity; 269 quantBinSplittingAttributes.push(currentSplittingAttribute); 270 } 271 } 272 273 // find min max 274 for (let i = 0; i < self.dataAfterFiltering.length; i++) { 275 let currentRow = self.dataAfterFiltering[i]; 276 let currentXAxisValue = +currentRow[xAxisAttribute.name]; 277 let currentYAxisValue = +currentRow[yAxisAttribute.name]; 278 279 // find for x axis attribute 280 if (currentXAxisValue < xAxisAttribute.min) xAxisAttribute.min = currentXAxisValue; 281 if (currentXAxisValue > xAxisAttribute.max) xAxisAttribute.max = currentXAxisValue; 282 283 // find for y axis attribute 284 if (currentYAxisValue < yAxisAttribute.min) yAxisAttribute.min = currentYAxisValue; 285 if (currentYAxisValue > yAxisAttribute.max) yAxisAttribute.max = currentYAxisValue; 286 287 // find for splitting attributes 288 for (let j = 0; j < quantBinSplittingAttributes.length; j++) { 289 let currentSplittingAttribute = quantBinSplittingAttributes[j]; 290 let currentSplittingAttrValue = +currentRow[currentSplittingAttribute.name]; 291 292 if (currentSplittingAttrValue < currentSplittingAttribute.min) currentSplittingAttribute.min = currentSplittingAttrValue; 293 if (currentSplittingAttrValue > currentSplittingAttribute.max) currentSplittingAttribute.max = currentSplittingAttrValue; 294 } 295 } 296 297 // find bin size for quantitative bin splitting attributes 298 for (let i = 0; i < quantBinSplittingAttributes.length; i++) { 299 let currentSplittingAttribute = quantBinSplittingAttributes[i]; 300 currentSplittingAttribute.binSize = (currentSplittingAttribute.max - currentSplittingAttribute.min) / currentSplittingAttribute.numberOfBins; 301 } 302 }, 303 304 // group combinations 305 306 generateAllGroupCombinations: function() { 307 const self = this; 308 let splittingAttributes = self.splittingAttributes; 309 310 if (splittingAttributes !== null) { 311 self.findAllGroupsForEachSplittingAttribute(); 312 self.findAllGroupCombinations(); 313 self.createCategoryToColourDict(); 314 self.retrieveObjectsForEachGroup(); 315 } 316 317 if (splittingAttributes === null) 318 self.allGroups = [{ 319 splittingAttributes: null, 320 objects: self.dataAfterFiltering, 321 regressionParam: {}, 322 pointsOnTrendLine: [] 323 }]; 324 }, 325 findAllGroupsForEachSplittingAttribute: function() { 326 const self = this; 327 let splittingAttributes = self.splittingAttributes; 328 329 for (let encodingName in splittingAttributes) { 330 let currentSplittingAttribute = splittingAttributes[encodingName]; 331 332 if (currentSplittingAttribute.isQuantitativeBin) { 333 currentSplittingAttribute.allGroups = []; 334 335 for (let i = 0; i < currentSplittingAttribute.numberOfBins; i++) 336 currentSplittingAttribute.allGroups.push(i); 337 } 338 339 if (currentSplittingAttribute.isTemporal) { 340 let getYear = currentSplittingAttribute.timeUnit == "year"; 341 let getMonth = currentSplittingAttribute.timeUnit == "month"; 342 let getWeek = currentSplittingAttribute.timeUnit == "day"; 343 let timeNameArray = []; 344 let timeObject = {}; 345 346 for (let i = 0; i < self.dataAfterFiltering.length; i++) {
347 let currentRow = self.dataAfterFiltering[i]; 348 let currentTime = currentRow[currentSplittingAttribute.name]; 349 let currentTimeString = null; 350 351 if (getYear) currentTimeString = currentTime.toString(); 352 if (getMonth) currentTimeString = Helpers.getMonthName(currentTime); 353 if (getWeek) currentTimeString = Helpers.getWeekdayName(currentTime); 354 timeObject[currentTimeString] = null; 355 } 356 357 timeNameArray = Object.keys(timeObject); 358 if (getYear) timeNameArray.sort(); 359 if (getMonth) timeNameArray = Helpers.sortMonthNames(timeNameArray); 360 if (getWeek) timeNameArray = Helpers.sortWeekdayNames(timeNameArray); 361 currentSplittingAttribute.allGroups = timeNameArray; 362 } 363 364 if (!currentSplittingAttribute.isQuantitativeBin && !currentSplittingAttribute.isTemporal) { 365 let categoryObject = {}; 366 367 for (let i = 0; i < self.dataAfterFiltering.length; i++) { 368 let currentRow = self.dataAfterFiltering[i]; 369 let currentCategory = currentRow[currentSplittingAttribute.name]; 370 371 categoryObject[currentCategory] = null; 372 } 373 374 currentSplittingAttribute.allGroups = Object.keys(categoryObject); 375 currentSplittingAttribute.allGroups.sort(); 376 } 377 } 378 }, 379 findAllGroupCombinations: function() { 380 const self = this; 381 let splittingAttributes = self.splittingAttributes; 382 let splittingAttributeArray = []; 383 let numberOfGroupCombinations = 1; 384 let allGroupCombinations = []; 385 386 // initialization 387 for (let encodingName in splittingAttributes) 388 splittingAttributeArray.push(splittingAttributes[encodingName]); 389 390 for (let i = 0; i < splittingAttributeArray.length; i++) { 391 let currentSplittingAttribute = splittingAttributeArray[i]; 392 let currentMagicNumber = 1; 393 394 if (i > 0) 395 for (let j = 0; j < i; j++) 396 currentMagicNumber *= splittingAttributeArray[j].allGroups.length; 397 398 currentSplittingAttribute.magicNumber = currentMagicNumber 399 numberOfGroupCombinations *= currentSplittingAttribute.allGroups.length; 400 } 401 402 for (let i = 0; i < numberOfGroupCombinations; i++) 403 allGroupCombinations.push({ 404 splittingAttributes: {}, 405 objects: [], 406 regressionParam: {}, 407 pointsOnTrendLine: [] 408 }); 409 410 // create allGroupCombinations 411 for (let i = 0; i < splittingAttributeArray.length; i++) { 412 let currentSplittingAttribute = splittingAttributeArray[i]; 413 let currentSplittingAttrGroupIndex = -1; 414 let currentSplittingAttrGroup = null; 415 let currentMagicNumber = currentSplittingAttribute.magicNumber; 416 417 for (let j = 0; j < numberOfGroupCombinations; j++) { 418 let attributeObject = {}; 419 420 if (j % currentMagicNumber == 0) { // get currentSplittingAttrGroup 421 currentSplittingAttrGroupIndex++; 422 currentSplittingAttrGroupIndex = currentSplittingAttrGroupIndex % currentSplittingAttribute.allGroups.length; 423 currentSplittingAttrGroup = currentSplittingAttribute.allGroups[currentSplittingAttrGroupIndex]; 424 } 425 426 attributeObject.name = currentSplittingAttribute.name; 427 attributeObject.value = currentSplittingAttrGroup; 428 allGroupCombinations[j].splittingAttributes[currentSplittingAttribute.encodingName] = attributeObject; 429 } 430 } 431 432 self.allGroups = allGroupCombinations; 433 }, 434 createCategoryToColourDict: function() { 435 const self = this; 436 let splittingAttributes = self.splittingAttributes; 437 let colourShelfHasSplitAttr = splittingAttributes !== null && 'color' in splittingAttributes; 438 let categoryToColourDict = {}; 439 440 if (colourShelfHasSplitAttr) 441 for (let i = 0; i < splittingAttributes.color.allGroups.length; i++) { 442 let currentColourIndex = i; 443 let currentColourCode = Helpers.getTableau10Colour(currentColourIndex); 444 let currentGroup = splittingAttributes.color.allGroups[i]; 445 446 categoryToColourDict[currentGroup] = currentColourCode; 447 } 448 449 self.categoryToColourDict = categoryToColourDict; 450 }, 451 retrieveObjectsForEachGroup: function() { 452 const self = this; 453 let splittingAttributes = self.splittingAttributes; 454 455 for (let i = 0; i < self.dataAfterFiltering.length; i++) { 456 let currentRow = self.dataAfterFiltering[i]; 457 458 for (let j = 0; j < self.allGroups.length; j++) { 459 let currentGroup = self.allGroups[j]; 460 let satisfyGroupDefinition = true; 461 462 for (let encodingName in currentGroup.splittingAttributes) {
463 let currentSplittingAttribute = splittingAttributes[encodingName]; 464 let currentGroupValue = currentGroup.splittingAttributes[encodingName].value; 465 let currentRowValue = currentRow[currentSplittingAttribute.name]; 466 let currentRowCategory = null; 467 468 if (currentSplittingAttribute.isQuantitativeBin) { 469 currentRowValue = +currentRowValue; 470 currentRowCategory = Math.floor((currentRowValue - currentSplittingAttribute.min) / currentSplittingAttribute.binSize); 471 472 if (currentRowCategory >= currentSplittingAttribute.numberOfBins) 473 currentRowCategory = currentSplittingAttribute.numberOfBins - 1; 474 } 475 476 if (currentSplittingAttribute.isTemporal) { 477 let getYear = currentSplittingAttribute.timeUnit == "year"; 478 let getMonth = currentSplittingAttribute.timeUnit == "month"; 479 let getWeek = currentSplittingAttribute.timeUnit == "day"; 480 481 if (getYear) currentRowCategory = currentRowValue.toString(); 482 if (getMonth) currentRowCategory = Helpers.getMonthName(currentRowValue); 483 if (getWeek) currentRowCategory = Helpers.getWeekdayName(currentRowValue); 484 } 485 486 if (!currentSplittingAttribute.isQuantitativeBin && 487 !currentSplittingAttribute.isTemporal) 488 currentRowCategory = currentRowValue; 489 490 if (currentRowCategory !== currentGroupValue) { 491 satisfyGroupDefinition = false; 492 break; 493 } 494 } 495 496 if (satisfyGroupDefinition) { 497 currentGroup.objects.push(currentRow); 498 break; 499 } 500 } 501 } 502 }, 503 504 // trend lines 505 506 computeRegressionParamForEachGroup: function() { 507 const self = this; 508 let xAxisAttribute = self.xAxisAttribute; 509 let yAxisAttribute = self.yAxisAttribute; 510 511 for (let i = 0; i < self.allGroups.length; i++) { 512 let currentGroup = self.allGroups[i]; 513 let pointsForCurrentGroup = []; 514 515 // create data point array 516 for (let j = 0; j < currentGroup.objects.length; j++) { 517 let currentRow = currentGroup.objects[j]; 518 let currentXValue = +currentRow[xAxisAttribute.name]; 519 let currentYValue = +currentRow[yAxisAttribute.name]; 520 521 pointsForCurrentGroup.push([ currentXValue, currentYValue ]); 522 } 523 524 // return parameters 525 if (pointsForCurrentGroup.length < 2) 526 currentGroup.regressionParam = null; 527 528 if (pointsForCurrentGroup.length >= 2) { 529 let parameters = Regression.linear(pointsForCurrentGroup); 530 531 currentGroup.regressionParam = { 532 slope: parameters.equation[0], 533 yIntercept: parameters.equation[1] 534 }; 535 } 536 } 537 }, 538 generatePointsOnTrendLineForEachGroup: function() { 539 const self = this; 540 let isRowShelfEmpty = Shelf.row.isEmpty(); 541 let isColumnShelfEmpty = Shelf.column.isEmpty(); 542 let hasRowOrColumn = !isRowShelfEmpty || !isColumnShelfEmpty; 543 let numberOfPointsOnTrendLine = hasRowOrColumn ? 30 : 100; 544 545 let xAxisAttribute = self.xAxisAttribute; 546 let yAxisAttribute = self.yAxisAttribute; 547 548 let groupsWithRegressionParam = []; 549 let endPointsOnTrendLine = []; 550 let shortVectorForEachTrendLine = []; 551 552 // init 553 for (let i = 0; i < self.allGroups.length; i++) { 554 let currentGroup = self.allGroups[i]; 555 556 if (currentGroup.regressionParam !== null) 557 groupsWithRegressionParam.push(currentGroup); 558 } 559 560 // finding end points 561 for (let i = 0; i < groupsWithRegressionParam.length; i++) { 562 let currentGroup = groupsWithRegressionParam[i]; 563 let currentSlope = currentGroup.regressionParam.slope; 564 let currentYIntercept = currentGroup.regressionParam.yIntercept; 565 let currentEndPoints = []; 566 567 let point1 = { x: xAxisAttribute.min, y: currentSlope * xAxisAttribute.min + currentYIntercept }; 568 let point2 = { x: xAxisAttribute.max, y: currentSlope * xAxisAttribute.max + currentYIntercept }; 569 let point3 = { x: (yAxisAttribute.min - currentYIntercept) / currentSlope, y: yAxisAttribute.min }; 570 let point4 = { x: (yAxisAttribute.max - currentYIntercept) / currentSlope, y: yAxisAttribute.max }; 571 let possibleEndPoints = [ point1, point2, point3, point4 ]; 572 573 for (let j = 0; j < possibleEndPoints.length; j++) 574 if (possibleEndPoints[j].x >= xAxisAttribute.min && possibleEndPoints[j].x <= xAxisAttribute.max && 575 possibleEndPoints[j].y >= yAxisAttribute.min && possibleEndPoints[j].y <= yAxisAttribute.max) 576 currentEndPoints.push(possibleEndPoints[j]); 577 578 endPointsOnTrendLine.push(currentEndPoints); // should have only two points 579 } 580 581 // find short vector 582 for (let i = 0; i < groupsWithRegressionParam.length; i++) { 583 let currentStartingPoint = endPointsOnTrendLine[i][0]; 584 let currentEndingPoint = endPointsOnTrendLine[i][1]; 585 586 let differentVector = { x: currentEndingPoint.x - currentStartingPoint.x, y: currentEndingPoint.y - currentStartingPoint.y }; 587 let magitudeOfDiffVector = Math.sqrt(differentVector.x * differentVector.x + differentVector.y * differentVector.y); 588 let requiredMagitude = magitudeOfDiffVector / numberOfPointsOnTrendLine; 589 let unitVector = { x: differentVector.x / magitudeOfDiffVector, y: differentVector.y / magitudeOfDiffVector }; 590 let shortVector = { x: unitVector.x * requiredMagitude, y: unitVector.y * requiredMagitude }; 591 592 shortVectorForEachTrendLine.push(shortVector); 593 } 594 595 // generate trend line points 596 for (let i = 0; i < groupsWithRegressionParam.length; i++) { 597 let currentGroup = groupsWithRegressionParam[i]; 598 let currentStartingPoint = endPointsOnTrendLine[i][0]; 599 let currentShortVector = shortVectorForEachTrendLine[i]; 600 let pointsOnCurrentTrendLine = []; 601 602 for (let j = 0; j < numberOfPointsOnTrendLine; j++) 603 pointsOnCurrentTrendLine.push({ 604 x: currentStartingPoint.x + currentShortVector.x * j, 605 y: currentStartingPoint.y + currentShortVector.y * j 606 }); 607 608 currentGroup.pointsOnTrendLine = pointsOnCurrentTrendLine; 609 } 610 }, 611 612 // specification 613 614 modifyVegaLiteSpecification: function() { 615 const self = this; 616 let isRowShelfEmpty = Shelf.row.isEmpty(); 617 let isColumnShelfEmpty = Shelf.column.isEmpty(); 618 let hasRowOrColumn = !isRowShelfEmpty || !isColumnShelfEmpty; 619 620 if (!hasRowOrColumn) { 621 TrendLinesForUnfacetedCharts.initSpecification(); 622 TrendLinesForUnfacetedCharts.initScatterplotSpecification(); 623 TrendLinesForUnfacetedCharts.addScatterplotData(); 624 TrendLinesForUnfacetedCharts.addScatterplotSpecification(); 625 TrendLinesForUnfacetedCharts.initTrendLineSpecifications(); 626 TrendLinesForUnfacetedCharts.addTrendLineData(); 627 TrendLinesForUnfacetedCharts.addTrendLineSpecifications(); 628 TrendLinesForUnfacetedCharts.modifySpecificationWidthAndHeight(); 629 VegaliteGenerator.specification = TrendLinesForUnfacetedCharts.newSpecification; 630 } 631 632 if (hasRowOrColumn) { 633 TrendLinesForFacetedCharts.initSpecification(); 634 TrendLinesForFacetedCharts.addData(); 635 TrendLinesForFacetedCharts.findAllLayers(); 636 TrendLinesForFacetedCharts.initScatterplotSpecification(); 637 TrendLinesForFacetedCharts.initTrendLineSpecifications(); 638 TrendLinesForFacetedCharts.modifySpecificationWidthAndHeight(); 639 VegaliteGenerator.specification = TrendLinesForFacetedCharts.newSpecification; 640 } 641 }, 642 getSortedRanges: function(splittingAttribute) { 643 let sortedRanges = []; 644 645 for (let i = 0; i < splittingAttribute.allGroups.length; i++) { 646 let currentBinIndex = i; 647 let currentLowerBound = splittingAttribute.min + currentBinIndex * splittingAttribute.binSize; 648 let currentUpperBound = currentLowerBound + splittingAttribute.binSize; 649 650 if (currentLowerBound.countDecimals() > 2) 651 currentLowerBound = currentLowerBound.toFixed(2); 652 if (currentUpperBound.countDecimals() > 2) 653 currentUpperBound = currentUpperBound.toFixed(2); 654 655 sortedRanges.push(currentLowerBound + ' - ' + currentUpperBound); 656 } 657 658 return sortedRanges; 659 } 660}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.