PageSourceSearch

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

js terrancelaw.github.io collected 2026-10-03 10:05:48 UTC 15,486 bytes, 388 lines download raw bytes

1const TrendLinesForFacetedCharts = {
2	allLayers: [], // [ { encodingName: { name, value } ... } ] (array of layer objects)
3	newSpecification: {},
4
5	initSpecification: function() {
6		const self = this;
7		let newSpecification = {};
8
9		newSpecification.data = {};
10		newSpecification.data.values = [];
11		newSpecification.facet = {};
12		newSpecification.spec = {};
13		newSpecification.spec.layer = [];
14
15		self.initFacetSpecification(newSpecification);
16		self.newSpecification = newSpecification;
17	},
18	initFacetSpecification: function(newSpecification) {
19		let splittingAttributes = TrendLines.splittingAttributes;
20		let rowIsOccupied = 'row' in splittingAttributes;
21		let columnIsOccupied = 'column' in splittingAttributes;
22
23		if (rowIsOccupied && !splittingAttributes.row.isQuantitativeBin) 
24			newSpecification.facet.row = {
25				field: 'row',
26				type: 'nominal',
27				axis: { title: splittingAttributes.row.name }
28			};
29
30		if (rowIsOccupied && splittingAttributes.row.isQuantitativeBin) 
31			newSpecification.facet.row = {
32				field: 'row',
33				type: 'nominal',
34				axis: { title: splittingAttributes.row.name },
35				sort: TrendLines.getSortedRanges(splittingAttributes.row)
36			};
37
38		if (columnIsOccupied && !splittingAttributes.column.isQuantitativeBin)
39			newSpecification.facet.column = {
40				field: 'column',
41				type: 'nominal',
42				axis: { title: splittingAttributes.column.name }
43			};
44
45		if (columnIsOccupied && splittingAttributes.column.isQuantitativeBin)
46			newSpecification.facet.column = {
47				field: 'column',
48				type: 'nominal',
49				axis: { title: splittingAttributes.column.name },
50				sort: TrendLines.getSortedRanges(splittingAttributes.column)
51			};
52	},
53	addData: function() {
54		const self = this;
55		let data = [];
56
57		self.addScatterplotData(data);
58		self.addTrendLineData(data);
59		self.newSpecification.data.values = data;
60	},
61	addScatterplotData: function(data) {
62		let xAxisAttribute = TrendLines.xAxisAttribute;
63		let yAxisAttribute = TrendLines.yAxisAttribute;
64		let splittingAttributes = TrendLines.splittingAttributes;
65		let nonSplittingAttributes = TrendLines.nonSplittingAttributes;
66
67		let isTooltipShelfEmpty = Shelf.tooltip.isEmpty();
68		let tooltipShelfData = Shelf.tooltip.getCapsuleData();
69
70		for (let i = 0; i < TrendLines.allGroups.length; i++) {
71			let currentGroup = TrendLines.allGroups[i];
72
73			for (let j = 0; j < currentGroup.objects.length; j++) {
74				let currentRow = currentGroup.objects[j];
75				let currentPointObject = {};
76
77				// splitting
78				for (let encodingName in currentGroup.splittingAttributes) {
79					let currentSplittingAttribute = splittingAttributes[encodingName];
80					let currentGroupValue = currentGroup.splittingAttributes[encodingName].value;
81
82					if (currentSplittingAttribute.isQuantitativeBin) {
83						let currentBinIndex = currentGroupValue;
84						let currentLowerBound = currentSplittingAttribute.min + currentBinIndex * currentSplittingAttribute.binSize;
85						let currentUpperBound = currentLowerBound + currentSplittingAttribute.binSize;
86
87						if (currentLowerBound.countDecimals() > 2) currentLowerBound = currentLowerBound.toFixed(2);
88						if (currentUpperBound.countDecimals() > 2) currentUpperBound = currentUpperBound.toFixed(2);
89						currentPointObject[encodingName] = currentLowerBound + ' - ' + currentUpperBound;
90					}
91
92					if (!currentSplittingAttribute.isQuantitativeBin)
93						currentPointObject[encodingName] = currentGroupValue;
94				}
95
96				// non splitting
97				for (let encodingName in nonSplittingAttributes)
98					currentPointObject[nonSplittingAttributes[encodingName].name] = currentRow[nonSplittingAttributes[encodingName].name];
99
100				// others
101				if (!isTooltipShelfEmpty)
102					currentPointObject[tooltipShelfData.attributeName] = currentRow[tooltipShelfData.attributeName];
103
104				currentPointObject.x = currentRow[xAxisAttribute.name];
105				currentPointObject.y = currentRow[yAxisAttribute.name];
106				currentPointObject.objectType = 'point';
107				data.push(currentPointObject);
108			}
109		}
110	},
111	addTrendLineData: function(data) {
112		const self = this;
113		let splittingAttributes = TrendLines.splittingAttributes;
114
115		for (let i = 0; i < TrendLines.allGroups.length; i++) {
116			let currentGroup = TrendLines.allGroups[i];
117			let currentPointsOnTrendLine = currentGroup.pointsOnTrendLine;
118
119			for (let j = 0; j < currentPointsOnTrendLine.length; j++) {
120				for (let encodingName in currentGroup.splittingAttributes) {
121					let currentSplittingAttribute = splittingAttributes[encodingName];
122					let currentGroupValue = currentGroup.splittingAttributes[encodingName].value;
123
124					if (currentSplittingAttribute.isQuantitativeBin) {
125						let currentBinIndex = currentGroupValue;
126						let currentLowerBound = currentSplittingAttribute.min + currentBinIndex * currentSplittingAttribute.binSize;
127						let currentUpperBound = currentLowerBound + currentSplittingAttribute.binSize;
128
129						if (currentLowerBound.countDecimals() > 2) currentLowerBound = currentLowerBound.toFixed(2);
130						if (currentUpperBound.countDecimals() > 2) currentUpperBound = currentUpperBound.toFixed(2);
131						currentPointsOnTrendLine[j][encodingName] = currentLowerBound + ' - ' + currentUpperBound;
132						currentPointsOnTrendLine[j].objectType = 'line';
133					}
134
135					if (!currentSplittingAttribute.isQuantitativeBin) {
136						currentPointsOnTrendLine[j][encodingName] = currentGroupValue;
137						currentPointsOnTrendLine[j].objectType = 'line';
138					}
139				}
140
141				data.push(currentPointsOnTrendLine[j]);
142			}
143		}
144	},
145	findAllLayers: function() {
146		const self = this;
147		let splittingAttributes = TrendLines.splittingAttributes;
148		let splittingAttributeArray = [];
149		let numberOfLayers = 1;
150		let allLayers = [];
151
152		// init
153		for (let encodingName in splittingAttributes)
154			if (encodingName != 'row' && encodingName != 'column')
155				splittingAttributeArray.push(splittingAttributes[encodingName]);
156
157		for (let i = 0; i < splittingAttributeArray.length; i++) {
158			let currentSplittingAttribute = splittingAttributeArray[i];
159			let currentMagicNumber = 1;
160
161			if (i > 0) 
162				for (let j = 0; j < i; j++) 
163					currentMagicNumber *= splittingAttributeArray[j].allGroups.length;
164
165			currentSplittingAttribute.magicNumber = currentMagicNumber;
166			numberOfLayers *= currentSplittingAttribute.allGroups.length;
167		}
168
169		for (let i = 0; i < numberOfLayers; i++)
170			allLayers.push({});
171
172		// create allLayer
173		for (let i = 0; i < splittingAttributeArray.length; i++) {
174			let currentSplittingAttribute = splittingAttributeArray[i];
175			let currentSplittingAttrGroupIndex = -1;
176			let currentSplittingAttrGroup = null;
177			let currentMagicNumber = currentSplittingAttribute.magicNumber;
178
179			for (let j = 0; j < numberOfLayers; j++) {
180				let attributeObject = {};
181
182				if (j % currentMagicNumber == 0) { // get currentSplittingAttrGroup
183					currentSplittingAttrGroupIndex++;
184					currentSplittingAttrGroupIndex = currentSplittingAttrGroupIndex % currentSplittingAttribute.allGroups.length;
185					currentSplittingAttrGroup = currentSplittingAttribute.allGroups[currentSplittingAttrGroupIndex];
186				}
187
188				attributeObject.name = currentSplittingAttribute.name;
189				attributeObject.value = currentSplittingAttrGroup;
190				allLayers[j][currentSplittingAttribute.encodingName] = attributeObject;
191			}
192		}
193
194		self.allLayers = allLayers;
195	},
196	initScatterplotSpecification: function() {
197		const self = this;
198		let scatterplotSpecification = {};
199
200		scatterplotSpecification.transform = [ { filter: { field: "objectType", equal: "point" } } ];
201		scatterplotSpecification.mark = "point";
202		scatterplotSpecification.encoding = {};
203
204		self.initScatterplotXYSpec(scatterplotSpecification);
205		self.initScatterplotSplittingAttrSpec(scatterplotSpecification);
206		self.initScatterplotNonSplittingAttrSpec(scatterplotSpecification);
207		self.initScatterplotTooltipSpec(scatterplotSpecification);
208		self.newSpecification.spec.layer.push(scatterplotSpecification);
209	},
210	initScatterplotXYSpec: function(scatterplotSpecification) {
211		let xAxisAttribute = TrendLines.xAxisAttribute;
212		let yAxisAttribute = TrendLines.yAxisAttribute;
213
214		scatterplotSpecification.encoding.x = { field: 'x', type: 'quantitative', axis: { title: xAxisAttribute.name } };
215		scatterplotSpecification.encoding.y = { field: 'y', type: 'quantitative', axis: { title: yAxisAttribute.name } };
216
217		if (xAxisAttribute.isTemporal) {
218			scatterplotSpecification.encoding.x.axis.format = 'd';
219			scatterplotSpecification.encoding.x.scale = {};
220			scatterplotSpecification.encoding.x.scale.domain = [ xAxisAttribute.min, xAxisAttribute.max ];
221		}
222
223		if (yAxisAttribute.isTemporal) {
224			scatterplotSpecification.encoding.y.axis.format = 'd';
225			scatterplotSpecification.encoding.y.scale = {};
226			scatterplotSpecification.encoding.y.scale.domain = [ yAxisAttribute.min, yAxisAttribute.max ];
227		}
228	},
229	initScatterplotSplittingAttrSpec: function(scatterplotSpecification) {
230		const self = this;
231		let splittingAttributes = TrendLines.splittingAttributes;
232
233		for (let encodingName in splittingAttributes)
234			if (encodingName != 'row' && encodingName != 'column') {
235				let currentSplittingAttr = splittingAttributes[encodingName];
236				let attributeName = currentSplittingAttr.name;
237				let categoryList = currentSplittingAttr.allGroups;
238				let colorScale = (encodingName == 'color') ? self.generateColourScale(attributeName, categoryList) : null;
239				let title = currentSplittingAttr.name;
240
241				if (!currentSplittingAttr.isQuantitativeBin && colorScale === null)
242					scatterplotSpecification.encoding[encodingName] = {
243						field: encodingName,
244						type: 'nominal',
245						axis: { title: title }
246					};
247
248				if (!currentSplittingAttr.isQuantitativeBin && colorScale !== null)
249					scatterplotSpecification.encoding[encodingName] = {
250						field: encodingName,
251						type: 'nominal',
252						axis: { title: title },
253						scale: colorScale
254					};
255				
256				if (currentSplittingAttr.isQuantitativeBin)
257					scatterplotSpecification.encoding[encodingName] = {
258						field: encodingName,
259						type: 'nominal',
260						axis: { title: title },
261						sort: TrendLines.getSortedRanges(currentSplittingAttr)
262					};
263			}
264	},
265	initScatterplotNonSplittingAttrSpec: function(scatterplotSpecification) {
266		let nonSplittingAttributes = TrendLines.nonSplittingAttributes;
267
268		for (let encodingName in nonSplittingAttributes) {
269			let shelfName = (encodingName == 'color') ? 'colour' : encodingName;
270			VegaliteGenerator.setOneEncoding(shelfName, scatterplotSpecification);
271		}
272	},
273	initScatterplotTooltipSpec: function(scatterplotSpecification) {
274		let isTooltipShelfEmpty = Shelf.tooltip.isEmpty();
275
276		if (!isTooltipShelfEmpty) {
277			VegaliteGenerator.setOneEncoding('tooltip', scatterplotSpecification);
278			VegaliteGenerator.adjustTooltipEncoding(scatterplotSpecification);
279		}
280	},
281	initTrendLineSpecifications: function(scatterplotSpecification) {
282		const self = this;
283		let splittingAttributes = TrendLines.splittingAttributes;
284
285		for (let i = 0; i < self.allLayers.length; i++) {
286			let currentLayerConfig = self.allLayers[i];
287			let currentTrendLineSpecification = {};
288
289			currentTrendLineSpecification.transform = null;
290			currentTrendLineSpecification.mark = "line";
291			currentTrendLineSpecification.encoding = {};
292
293			self.initTrendLineFilterSpec(currentTrendLineSpecification, currentLayerConfig);
294			self.initTrendLineXYSpec(currentTrendLineSpecification);
295			self.initTrendLineMarkSpec(currentTrendLineSpecification, currentLayerConfig);
296			self.initTrendLineTooltipSpec(currentTrendLineSpecification);
297			self.newSpecification.spec.layer.push(currentTrendLineSpecification);
298		}
299	},
300	initTrendLineFilterSpec: function(trendLineSpecification, layerConfig) {
301		let splittingAttributes = TrendLines.splittingAttributes;
302
303		trendLineSpecification.transform = [ { filter: { field: "objectType", equal: "line" } } ];
304
305		for (let encodingName in layerConfig) {
306			let currentGroupValue = layerConfig[encodingName].value;
307			let currentFilterObject = { filter: { field: encodingName, equal: currentGroupValue } };
308			let currentSplittingAttribute = splittingAttributes[encodingName];
309
310			if (currentSplittingAttribute.isQuantitativeBin) {
311				let currentBinIndex = currentGroupValue;
312				let currentLowerBound = currentSplittingAttribute.min + currentBinIndex * currentSplittingAttribute.binSize;
313				let currentUpperBound = currentLowerBound + currentSplittingAttribute.binSize;
314
315				if (currentLowerBound.countDecimals() > 2) currentLowerBound = currentLowerBound.toFixed(2);
316				if (currentUpperBound.countDecimals() > 2) currentUpperBound = currentUpperBound.toFixed(2);
317				currentFilterObject.filter.equal = currentLowerBound + ' - ' + currentUpperBound;
318			}
319
320			trendLineSpecification.transform.push(currentFilterObject);
321		}	
322	},
323	initTrendLineXYSpec: function(trendLineSpecification) {
324		trendLineSpecification.encoding.x = { field: 'x', type: 'quantitative' };
325		trendLineSpecification.encoding.y = { field: 'y', type: 'quantitative' };
326	},
327	initTrendLineMarkSpec: function(trendLineSpecification, layerConfig) {
328		let splittingAttributes = TrendLines.splittingAttributes;
329		let colorShelfHasSplitAttr = splittingAttributes !== null && 'color' in splittingAttributes;
330		let currentMarkSpecification = { type: 'line' };
331
332		if (colorShelfHasSplitAttr) {
333			let colourShelfAttribute = layerConfig.color.name;
334			let currentGroupColourValue = layerConfig.color.value;
335			let isAttrOriginallyNominal = colourShelfAttribute in Database.categoryToColourDictForEachNominalAttr;
336			let currentGroupColourCode = null;
337
338			if (isAttrOriginallyNominal)
339				currentGroupColourCode = Database.categoryToColourDictForEachNominalAttr[colourShelfAttribute][currentGroupColourValue];
340			if (!isAttrOriginallyNominal)
341				currentGroupColourCode = TrendLines.categoryToColourDict[currentGroupColourValue];
342
343			currentMarkSpecification.stroke = currentGroupColourCode;
344		}
345
346		trendLineSpecification.mark = currentMarkSpecification;
347	},
348	initTrendLineTooltipSpec: function(trendLineSpecification) {
349		let splittingAttributes = TrendLines.splittingAttributes;
350
351		if (splittingAttributes === null)
352			trendLineSpecification.encoding.tooltip = { value: false };
353		if (splittingAttributes !== null)
354			trendLineSpecification.encoding.tooltip = [];
355
356		for (let encodingName in splittingAttributes) {
357			let currentSplittingAttribute = splittingAttributes[encodingName];
358			let newEncodingSpecification = { field: encodingName, type: 'nominal', axis: { title: currentSplittingAttribute.name } };
359			trendLineSpecification.encoding.tooltip.push(newEncodingSpecification);
360		}
361	},
362	modifySpecificationWidthAndHeight: function() {
363		const self = this;
364		let width = VegaliteGenerator.specification.width;
365		let height = VegaliteGenerator.specification.height;
366
367		self.newSpecification.spec.layer[0].width = width;
368		self.newSpecification.spec.layer[0].height = height;
369	},
370	generateColourScale: function(attributeName, categoryList) { // assuming no missing values in categoryList
371		let isOriginallyNominal = (attributeName in Database.categoryToColourDictForEachNominalAttr);
372		let categoryToColourDict = isOriginallyNominal ? Database.categoryToColourDictForEachNominalAttr[attributeName] : null;
373		let colourCodeList = [];
374
375		if (!isOriginallyNominal)
376			return null;
377
378		for (let i = 0; i < categoryList.length; i++) {
379			let currentCategory = categoryList[i];
380			let currentColourCode = categoryToColourDict[currentCategory];
381			colourCodeList.push(currentColourCode);
382		}
383		return {
384			domain: categoryList,
385			range: colourCodeList
386		};
387	}
388}

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.