PageSourceSearch

https://terrancelaw.github.io/iQuery/js/trendLines.js

js terrancelaw.github.io collected 2026-10-03 10:05:47 UTC 26,033 bytes, 660 lines download raw bytes

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.