node-red-contrib-prib-functions 0.19.2 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/.github/workflows/codeql-analysis.yml +3 -3
  2. package/.github/workflows/npmpublish.yml +6 -6
  3. package/.vs/VSWorkspaceState.json +7 -0
  4. package/.vs/node-red-contrib-prib-functions/v17/.wsuo +0 -0
  5. package/README.md +84 -70
  6. package/dataAnalysis/arrayAllRowsSwap.js +15 -0
  7. package/dataAnalysis/arrayCompareToPrecision.js +34 -0
  8. package/dataAnalysis/arrayDifference.js +14 -0
  9. package/dataAnalysis/arrayDifferenceSeasonal.js +15 -0
  10. package/dataAnalysis/arrayDifferenceSeasonalSecondOrder.js +20 -0
  11. package/dataAnalysis/arrayDifferenceSecondOrder.js +14 -0
  12. package/dataAnalysis/arrayForEachRange.js +38 -0
  13. package/dataAnalysis/arrayOverlay.js +13 -0
  14. package/dataAnalysis/arrayProduct.js +11 -0
  15. package/dataAnalysis/arrayRandom.js +14 -0
  16. package/dataAnalysis/arrayReduceRange.js +11 -0
  17. package/dataAnalysis/arrayScale.js +11 -0
  18. package/dataAnalysis/arraySum.js +11 -0
  19. package/dataAnalysis/arraySumSquared.js +11 -0
  20. package/dataAnalysis/arraySwap.js +11 -0
  21. package/dataAnalysis/dataAnalysis.html +52 -21
  22. package/dataAnalysis/dataAnalysis.js +73 -44
  23. package/dataAnalysis/generateMatrixFunction.js +89 -0
  24. package/dataAnalysis/generateVectorFunction.js +25 -0
  25. package/dataAnalysis/pca.js +472 -325
  26. package/dataAnalysis/svd.js +239 -0
  27. package/documentation/DataAnalysisRealtime.JPG +0 -0
  28. package/documentation/monitorSystem.JPG +0 -0
  29. package/documentation/monitorSystemTest.JPG +0 -0
  30. package/echart/echart.html +68 -0
  31. package/echart/echart.js +85 -0
  32. package/echart/icons/chart-671.png +0 -0
  33. package/echart/lib/echarts.js +95886 -0
  34. package/lib/Chart.js +177 -0
  35. package/lib/Column.js +99 -0
  36. package/lib/GraphDB.js +14 -0
  37. package/lib/Table.js +185 -0
  38. package/lib/objectExtensions.js +361 -0
  39. package/matrix/matrix.js +95 -56
  40. package/matrix/matrixNode.html +88 -55
  41. package/matrix/matrixNode.js +12 -5
  42. package/monitor/BarGauge.js +8 -0
  43. package/monitor/Dataset.js +29 -0
  44. package/monitor/DialGauge.js +109 -0
  45. package/monitor/DialNeedle.js +36 -0
  46. package/monitor/Format.js +74 -0
  47. package/monitor/centerElement.js +14 -0
  48. package/monitor/compareElements.js +95 -0
  49. package/monitor/defs.js +23 -0
  50. package/monitor/extensions.js +906 -0
  51. package/monitor/functions.js +36 -0
  52. package/monitor/json2xml.js +103 -0
  53. package/monitor/monitorSystem.html +199 -0
  54. package/monitor/monitorSystem.js +322 -0
  55. package/monitor/svgHTML.js +179 -0
  56. package/monitor/svgObjects.js +64 -0
  57. package/package.json +20 -6
  58. package/test/00-objectExtensions.js +94 -0
  59. package/test/04-tables.js +33 -0
  60. package/test/data/.config.nodes.json +608 -0
  61. package/test/data/.config.nodes.json.backup +608 -0
  62. package/test/data/.config.runtime.json +4 -0
  63. package/test/data/.config.runtime.json.backup +3 -0
  64. package/test/data/.config.users.json +21 -0
  65. package/test/data/.config.users.json.backup +21 -0
  66. package/test/data/.flow.json.backup +2820 -2003
  67. package/test/data/float32vector10.npy +0 -0
  68. package/test/data/flow.json +2830 -2033
  69. package/test/data/int2matrix2x3.npy +0 -0
  70. package/test/data/package-lock.json +158 -0
  71. package/test/data/package.json +11 -0
  72. package/test/dataAnalysisExtensions.js +471 -0
  73. package/test/dataAnalysisPCA.js +54 -0
  74. package/test/dataAnalysisSVD.js +31 -0
  75. package/test/euclideanDistance.js +2 -2
  76. package/test/transformConfluence.js +1 -1
  77. package/test/transformNumPy.js +132 -0
  78. package/testing/test.html +1 -1
  79. package/testing/test.js +78 -53
  80. package/transform/NumPy.js +303 -0
  81. package/transform/transform.html +12 -0
  82. package/transform/transform.js +34 -2
@@ -0,0 +1,906 @@
1
+ const path = require('path')
2
+ require("../lib/objectExtensions")
3
+ const Format=require('./Format')
4
+ const Table=require('../lib/Table')
5
+ const { action, children } = require('./defs')
6
+ const { table } = require('console')
7
+
8
+
9
+ const timePrecision=(time,minutes,hours,days)=>{
10
+ if(Math.floor(time/60000)>0) return minutes()
11
+ if(Math.floor(time/60)>0) return hours()
12
+ if(Math.floor(time/24)>0) return days()
13
+ }
14
+ const timePeriodsMS={
15
+ second:1000,
16
+ minute:60*1000,
17
+ hour:60*60*1000,
18
+ day:24*60*60*1000,
19
+ week:7*24*60*60*1000,
20
+ fortnight:2*7*24*60*60*1000,
21
+ month:30*24*60*60*1000,
22
+ year:365*24*60*60*1000,
23
+ }
24
+ const timeUnitRange=(t,second=p=>r,minute=p=>r,hour=p=>r,day=p=>r,week=p=>r,fortnight=p=>r,month=p=>r,year=p=>r)=>{
25
+ let duration=Math.floor(t/1000)
26
+ if(duration==0) return seconds(duration)
27
+ duration=Math.floor(duration/60)
28
+ if(duration==0) return minutes(duration)
29
+ duration=Math.floor(duration/60)
30
+ if(duration==0) return hours(duration)
31
+ duration=Math.floor(duration/60)
32
+ if(duration==0) return days(duration)
33
+ if(Math.floor(duration/7)==0) return weeks(duration)
34
+ if(Math.floor(duration/14)==0) return fortnights(duration)
35
+ if(Math.floor(duration/30)==0) return months(duration)
36
+ return years(Math.floor(duration/365))
37
+ }
38
+ const timeTickPoints=(timeUnit="second",min=0,max=100)=>{
39
+ const period=timePeriodsMS[second]
40
+ const results=[]
41
+ const rangeRatio=Math.floor((max-min)/period);
42
+ for(let j=min; j<max; j++)
43
+ Math.floor(rangeRatio*j)
44
+ }
45
+
46
+
47
+ const circle={
48
+ arcPath:(v)=>" A "+v.rx+" "+v.ry+","+(v.xAxisRotation??0)+","+(v.largeArcFlag??0)+","+(v.sweep-flag??0)+","+v.x+" "+v.y,
49
+ basePoints:(degrees=6,circumference=2*Math.PI)=>{
50
+ const points=[],increment=circle.toRadians(degrees)
51
+ for(let i=0; i<circumference; i+increment)
52
+ points.push([Math.cos(i),Math.sin(i)])
53
+ },
54
+ getPieSlicePath:(to,from=0,radius=100)=>{
55
+ const fromRadians=circle.toRadians(from),toRadians=circle.toRadians(to)
56
+ const flip180=(to-from)>180?",0,0,0":",0,1,1,"
57
+ return " M "+radius*Math.cos(fromRadians+" "+radians*Math.cos(fromRadians) +
58
+ " A "+radius+" "+radius + flip180 + radius*Math.cos(toRadians)+" "+radius*Math.cos(toRadians) +
59
+ " Z"
60
+ },
61
+ getPieSliceRatioPath:(to,from=0,radius=100)=>{
62
+ const fromRadians=from*circle.pi2,toRadians=to*circle.pi2
63
+ const flip180=(to-from)>.5?",0,0,0":",0,1,1,"
64
+ return " M "+radius*Math.cos(fromRadians+" "+radians*Math.cos(fromRadians) +
65
+ " A "+radius+" "+radius + flip180 + radius*Math.cos(toRadians)+" "+radius*Math.cos(toRadians) +
66
+ " Z"
67
+ },
68
+ pi2:2 * Math.PI,
69
+ pi2DegreeFactor:180 / Math.PI,
70
+ point:(degrees=6,radius=1)=>{
71
+ const radians=circle.toRadians(degrees)
72
+ return [radius*Math.cos(radians),radius*Math.sin(radians)]
73
+ },
74
+ points:(degrees=6,radius=1)=>circle.circumferenceBasePoints(degrees).map(c=>[radius*c[0],radius*c[1]]),
75
+ pieSlicesDegrees:(degrees,radius=100)=>{
76
+ const pieces=degrees.reduce((a,c,i)=>{
77
+ a.push({action:"path",id:"pieslice"+i,d:circle.getPieSlicePath(f,t,radius)})
78
+ },[])
79
+ return {action:g,id:"pie",children=pieces}
80
+ },
81
+ toDegrees:(radians)=> radians * circle.pi2DegreeFactor,
82
+ toRadians:(degrees)=> degrees / circle.pi2DegreeFactor
83
+ }
84
+ const stackBlocks=(data, colors=getColors(data[0].length), barWidth, yStart=0, yEnd=this.data[0].length-1 )=>{
85
+ let xPos=0, yPos=0
86
+ const results=[]
87
+ for(let i= 0; i < data.length; i++) {
88
+ const row=data[i]
89
+ xPos=Math.floor((i+0.5)*barWidth);
90
+ yPos=Math.sum(...row)
91
+ row.map((y,i)=>({action:"rect",fill:colors[j],x:xPos,y:yPos,width:barWidth,height:y})
92
+ for(let j=0; j<row.length; j++) {
93
+ if (row[j]==null) continue
94
+ const y=row[j]
95
+ results.push({action:"rect",fill:colors[j],x:xPos,y:yPos,width:barWidth,height:y})
96
+ yPos+=y;
97
+ }
98
+ }
99
+ return results
100
+ }
101
+ const getPushline=(data,j,tickIncrement,barWidth)=>{
102
+ const points = [];
103
+ for(let i= 0; i <data.length; i++) {
104
+ const row=data[i]
105
+ if(row[j]==null) continue;
106
+ xPos=Math.floor(i*tickIncrement)+(j-1)*barWidth;
107
+ points=[xPos,row[j]]
108
+ }
109
+ return {action:"polyline",points:points}
110
+ }
111
+ const getCircleTicks=(radius,degrees=6,strokeWidth=1,size=2)=>{
112
+ const ri=radius-size/2, ro=radius+size/2
113
+ return {action:"path",d:circle.basePoints(degrees).reduce((a,c)=>" M "+ri*c[0]+","+ri*c[1]+" L "+ro*c[0]+","+ro*c[1],"")}
114
+ }
115
+
116
+ function chart() {
117
+ Object.assign(this,{
118
+ bubbleRatio:0.2,
119
+ type:"line",
120
+ width:100,
121
+ height:100,
122
+ colour:"black",
123
+ disableLeftMenu:false,
124
+ grouping:null,
125
+ highlight:'',
126
+ legendDisplay:'show',
127
+ legendPositionX:60,
128
+ legendPositionY:0,
129
+ lineWidth:1,
130
+ outline:'black',
131
+ showOptions:true,
132
+ showPoints:false,
133
+ slices:'row',
134
+ tickIncrement:5
135
+ })
136
+ this.sliceByRow=this.slices=='row'
137
+ this.setAxis("x").setAxis("y").setAxis("z")
138
+ if(this.table == null) throw Error("Cannot render chart as table not specified")
139
+ this.processProperties();
140
+ }
141
+ chart.prototype.setAxis= function(dimension){
142
+ const axis=this.axis[dimension]
143
+ axis={columns:null,position:0,bound:{upper:null,lower:null}}
144
+ axis.columnArray=axis.columns.toLowerCase().split(',')
145
+ if(axis.columnArray.length==0) throw axis+" axis has no valid values for the database version, "+axis+"Axis: "+axis.columnArray;
146
+ if(axis.bound.upper!=null) axis.bound.upper=parseInt(axis.bound.upper,10);
147
+ if(axis.bound.lower!=null) axis.bound.lower=parseInt(axis.bound.lower,10);
148
+ axis.normaliser=1
149
+ return this
150
+ }
151
+ chart.prototype.errorFunction=function(msg){
152
+ throw typeof msg == "string"? Error(msg) : msg
153
+ }
154
+ chart.prototype.getPies=function (sliceByRow=this.sliceByRow,height=this.height) {
155
+ let xPos=0, yPos=0,pies=[]
156
+ const piesCount=(sliceByRow?this.columnIndexDetails.y.size+1:this.data.length)
157
+ const piesPerRow=Math.ceil(Math.sqrt(piesCount))
158
+ for(let i=(sliceByRow?0:0); i < piesCount; i++) {
159
+ pies.push(this.drawPie(xPos,yPos,this.height,i))
160
+ if(i%piesPerRow) continue
161
+ xPos=0;
162
+ yPos+=this.height;
163
+ }
164
+ return pies
165
+ }
166
+
167
+ chart.prototype.getPie=function(radius,i,dataset,sliceByRow=this.sliceByRow,label=isSliceRow)?this.label[i]:dataset[i][this.columnIndex[0]]) {
168
+ const circumference=radius * Math.PI+2
169
+ const total=sliceByRow?dataset.reduce((a,row)=>a+row[i],0):dataset[i].reduce((a,c)=>a+c,0)
170
+ if (total==0) return []
171
+ let angleStart=0, angleEnd=0, slices=[]
172
+ const unitDegreesRatio = 2 * Math.PI/total;
173
+ for(let row= (sliceByRow?0:1); row < arrayLength; row++) {
174
+ const value=(sliceByRow?this.data[row][i]:this.data[i][row])
175
+ if(value==null) continue;
176
+ angleStart=angleEnd;
177
+ angleEnd+=unitDegreesRatio*value;
178
+ const dash=(circumference*ratio)
179
+ const gap=circumference-dash
180
+ slices.push({action:"circle",fill:"transparent",cx:radius,cy:radius,r:radius,stroke:this.colour[row],"stroke-width":radius,
181
+ "stroke-dasharray": dash+","+gap,
182
+ transform:{rotate:{angle:angleStart,x:this.radius,y:this.radius}}}
183
+ )
184
+ slices.push({action:"text",x:x,y:y+10,children:[label]})
185
+ }
186
+ return {action:"g",children:[slices]}
187
+ }
188
+ chart.prototype.checkExists=function(value,errorMessage="value is null",error=this.errorFunction) {
189
+ if(value==null) error(errorMessage)
190
+ }
191
+ chart.prototype.processProperties=function(error=(msg)=>{throw Error(msg)}) {
192
+ if(this.grouping!=null) this.grouping = this.grouping.toUpperCase();
193
+ else if(this.baseTableData.primaryKeys.length>0)
194
+ this.grouping = this.baseTableData.primaryKeys.toString();
195
+ if(!['row',"column"].includes(this.slices)) error("parameter slices can only equal row or column")
196
+ this.sliceByRow=this.slices=="row"
197
+ if(['bubble','bubbleandline'].includes(this.type))
198
+ this.checkExists(this.axis.z.columns,'Missing z axis columns for '+this.type,error);
199
+ if(['bubble','bubbleandline','line','pushline','setline'].includes(this.type))
200
+ this.checkExists(this.axis.x.columns,'Missing x axis columns for '+this.type,error);
201
+ if(['bubble','bubbleandline','line','pushline','setline','stack','bar','pie','events'].includes(this.type))
202
+ this.checkExists(this.axis.y.columns,'Missing y axis columns for '+this.type,error);
203
+ if(['pie'].includes(this.type)) {
204
+ this.grouping=null;
205
+ }
206
+ }
207
+ chart.prototype.setParameter=function(name,value) {
208
+ this[name]=value
209
+ return this
210
+ }
211
+ chart.prototype.getMenuOption=function(label,value,property){
212
+ return '<tr><td>'+label+'</td><td>'
213
+ +'<input type="button" value="'+value+'" onclick="this.value='+this.callBackText+'.setParameter(\\\''+property+'\\\',this.value)"/>'
214
+ +'</td></tr>';
215
+ }
216
+ chart.prototype.setMenuOptions=function(menuArray) {
217
+ if(!this.showOptions) return null;
218
+ if(menuArray==null) menuArray=[];
219
+ if(this.optionsDialog==null) {
220
+ this.optionsDialog = new floatingPanel(this.elementUniqueID + '_optionsDialog', 'RAW', "", this.elementUniqueID + '_optionsDialog_button', false, false);
221
+ this.parentPanel.registerNestedObject(this.elementUniqueID + '_optionsDialog', this.optionsDialog);
222
+ }
223
+ const thisObject = this;
224
+ if(!this.disableLeftMenu)
225
+ if(this.baseTableData.localLeftMenu!=null)
226
+ menuArray=menuArray.concat(this.baseTableData.localLeftMenu);
227
+ options='';
228
+ switch (this.type) {
229
+ case 'pie':
230
+ options+=this.getMenuOption('Slice',(this.slices=='row'?'column':'row'),'slices')
231
+ break;
232
+ case 'bubble':
233
+ case 'bubbleandline':
234
+ case 'line':
235
+ break;
236
+ case 'pushline':
237
+ case 'setline':
238
+ options+=this.getMenuOption('Chart Type',this.type=='setline'?'pushline':'setline'),'type')
239
+ break;
240
+ case 'bar':
241
+ case 'stack':
242
+ options+=this.getMenuOption('Chart Type',(this.type=='bar'?'stack':'bar'),'type')
243
+ break;
244
+ }
245
+ options+=this.getMenuOption('Legend',(this.legendDisplay=="hide"?"show":"hide"),'legendDisplay')
246
+ +'<tr><td>Max. rows</td><td>'
247
+ +' <input type="text" value="'+this.baseTableData.maxResultsToFetch+'" onchange="'+this.callBackText+'.setParameter('+"'baseTableData.maxResultsToFetch'"+',this.value)"/>'
248
+ +'</td></tr>';
249
+ this.optionsDialog.draw();
250
+ this.optionsDialog.setContent('<table>'+options+this.getMetricColumnsOptions()+'</table>','Options', null, null, null)
251
+ }
252
+
253
+ chart.prototype.getBar=function (tickIncrement=this.tickIncrement,barWidth=Math.floor(this.tickIncrement/this.columnIndexDetails.y.size)) {
254
+ const group=[]
255
+ let xPos=0, yPos=0
256
+ for(let i= 0; i < this.data.length; i++) {
257
+ const row=this.data[i]
258
+ for(let j=0; j<this.table.data.length; j++) {
259
+ if (row[j]==null) continue;
260
+ xPos=Math.floor((i+0.5)*tickIncrement)+(j-1)*barWidth;
261
+ yPos=row[j]
262
+ group.push({action:"rect", x:xPos,y:yPos,width:this.barWidth,height:this.height-yPos,fill:this.colour[j],stroke:this.outline})
263
+ }
264
+ }
265
+ return result
266
+ }
267
+ chart.prototype.getBubble=function () {
268
+ const zDetails=this.axis.z;
269
+ const results=[]
270
+ for( let i= 0; i < this.data.length; i++) {
271
+ for( let j=0; j<this.data.length; j++) {
272
+ const row = this.data[i]
273
+ const zData = zDetails.dataStore
274
+ const y=row[j];
275
+ if(y==null || isNaN(y)) continue
276
+ xPos=row[0]
277
+ radius=Math.abs(zData[i][j]/2);
278
+ if(radius==0) continue
279
+ results.push({action:"circle",fill:this.colour[j],cx:xPos,cy:y,r:radius,stroke:this.outline,"stroke-width":1, opacity:0.5})
280
+ }
281
+ }
282
+ return results
283
+ }
284
+ chart.prototype.getBubbleAndLine=function () {
285
+ return [this.getLine(),this.getBubble()]
286
+ }
287
+ chart.prototype.getEvents=function(data) {
288
+ let dataX, plotX, lines=[]
289
+ for(let j=0 ; j<data.length; j++) {
290
+ dataX=data[j][0];
291
+ if (dataX==null || isNaN(dataX) ) continue;
292
+ lines.push({action:"line",x1:dataX,y1:0,x1:dataX,y1:this.height,stroke:"red","stroke-width":this.lineWidth,"stroke-opacity":0.8})
293
+ }
294
+ return lines
295
+ }
296
+ chart.prototype.getLine=function () {
297
+ let errors,points;
298
+ for(let j=0; j<this.data.length; j++)
299
+ try {
300
+ this.plot(j, 1, this.data)
301
+ } catch(e) {
302
+ errors += 'error plotting '+ this.label[i] + ' ' +e +'\n';
303
+ }
304
+ if(errors) this.errorFunction(errors)
305
+ return {action:'polyline',points:"100,100 150,25 150,75 200,0",fill:"none",stroke="red","stroke-width":this.lineWidth,"stroke-opacity":0.8}
306
+ },
307
+ drawChart_pushline: function() {
308
+ const errors=[],result="",yDetails=this.columnIndexDetails.y
309
+ this.barWidth=Math.floor(this.tickIncrement/yDetailssize);
310
+ for(let j=yDetails.start; j<yDetails.end; j++)
311
+ try {
312
+ result+=this.plotSet(j,this.data,this.colour[j], this.barWidth);
313
+ } catch(e) {
314
+ errors += 'error plotting '+ this.label[i] + ' ' +e +'\n';
315
+ }
316
+ if (errors.length) throw errors;
317
+ },
318
+ drawChart_setline: function () {return this.drawChart_pushline();},
319
+ drawChart_stack: function(data,tickIncrement) {
320
+ const y=this.columnIndexDetails.y
321
+ return stackBlock(data, this.colour, tickIncrement)
322
+ },
323
+ chart.prototype.getChart=function () {
324
+ let errors;
325
+ const colourCnt = this.type=='pie'&& this.sliceByRow? ? this.data.length : this.columnIndexDetails.y.size+1
326
+ if(colourCnt>this.colour.length)
327
+ this.color=getColors(colourPallet.length/colourCnt)
328
+ this.ctx.clearRect(0,0,this.width,this.height);
329
+ this.ctx.strokeStyle = "black";
330
+ if(['bar','bubble','bubbleandline','line','pushline','setline','stack'].includes[this.type])
331
+ this.drawLineAxis();
332
+ try { this["drawChart_"+this.type]();} catch(e) {throw "drawing " + this.type + "\n" + e.toString(); };
333
+
334
+ while (this.legend.rows.length> 0) this.legend.deleteRow(0);
335
+ switch (this.type) {
336
+ case 'pie':
337
+ if(this.sliceByRow) {
338
+ for(let i = 1; i < this.data.length; i++)
339
+ this.legend+= "<tr><a style='color:" + this.colour[i] + "' >" + this.data[i][0] + "</a></tr>";
340
+ } else
341
+ for(let i=0; i<this.label.length; i++)
342
+ this.legend+="<tr><a style='color:" + this.colour[i] + "' >" + this.label[i] + "</a></tr>";
343
+ break;
344
+ default:
345
+ this.legend+="x axis: " + this.label[0];
346
+ for(var i=0; i<this.table.data.length; i++) {
347
+ this.legend.+= "<tr><a style='color:" + this.colour[i] + "' >" + this.label[i]
348
+ + (this.zAxis?" z is " +this.axis.z.label[i]:"")
349
+ + "</a></tr>";
350
+ }
351
+ }
352
+ },
353
+ const coords={
354
+ events:(chart,data,xPos,yPos)=>{
355
+ const xPlot=xScaleRange(xPos,chart.xRatio)
356
+ for(let i=0; i<data.length;i++) {
357
+ const v=data[i][0]
358
+ if (v< xPlot.min || v> xPlot.max) continue;
359
+ let details="event:";
360
+ for(let i= 0; i < this.table.data.length; i++)
361
+ details+=" "+this.dataToString(i,v>xPlot.max);
362
+ XYRow.insertCell(-1).innerHTML=details;
363
+ var XYRow=chart.detailXY.insertRow(-1);
364
+ }
365
+ },
366
+ bubble:(chart,data,xPos,yPos)=>{
367
+ const xPlot=xScaleRange(xPos,chart.xRatio)
368
+ const yPlot=yScaleRange(yPos,chart.yRatio)
369
+ const text=[{action:'text',children:["x: "+this.dataToString(0,xPlot.value) + " "+"y: "+this.dataToString(1,yPlot.value)]}]
370
+ const zText=chart.zAxis?
371
+ (chart,row,colum)=>{
372
+ const zDetails=chart.columnIndexDetails.z
373
+ const zData = zDetails.dataStore
374
+ return "z: "+chart.dataToString(i,zData[row][column]);
375
+ }:
376
+ ()=>""
377
+ for(let row= 0; row < this.data.length; row++) {
378
+ const dataRow=data[row]
379
+ const x=dataRow[0]
380
+ const xText=" x: "+this.dataToString(0,x)
381
+ if (v< xPlot.min || v> xPlot.max) continue;
382
+ for(let i= 0; i < this.table.data.length; i++) {
383
+ const y=this.dataToString(i,dataRow[i])
384
+ if(isNaN(y) || y< yPlot.min || y> yPlot.max) continue;
385
+ text.push({action:'text',children:[chart.label[i]+xText+" y: "+y+zText(chart,row,i)]})
386
+ }
387
+ }
388
+ }
389
+ }
390
+ coords.bubbleandline=coords.bubble
391
+ coords.line=coords.bubble
392
+ function getDisplay(chart,xPos,yPos)
393
+ canvasCoordsDetails: function (chart,xPos,yPos) {
394
+ var XYRow=chart.detailXY.insertRow(-1);
395
+ switch (this.type) {
396
+ case 'bubble':
397
+ case 'bubbleandline':
398
+ case 'line':
399
+ case 'pushline':
400
+ case 'setline':
401
+ break;
402
+ case 'bar':
403
+ case 'stack':
404
+ var x=Math.floor((xPos)/this.tickIncrement-0.5);
405
+ XYRow.insertCell(-1).innerHTML="x:";
406
+ switch (this.dataType[0]) {
407
+ case 'timestamp' :
408
+ case 'date' :
409
+ case 'datetime' :
410
+ XYRow.insertCell(-1).innerHTML=this.dataToString(0,(xPos -this.xOffset)/this.xRatio);
411
+ break;
412
+ default:
413
+ XYRow.insertCell(-1).innerHTML=this.data[x][0];
414
+ }
415
+ XYRow=chart.detailXY.insertRow(-1);
416
+ XYRow.insertCell(-1).innerHTML= "y:";
417
+ XYRow.insertCell(-1).innerHTML=this.dataToString(1,(this.yOffset - yPos)/this.yRatio);
418
+ break;
419
+ default:
420
+ break;
421
+ }
422
+ },
423
+ chart.prototype.plotSet=function (y,data,colour,tickIncrement,barWidth) {
424
+ const points=[]
425
+ const group=[]
426
+ for(let i= 0; i < data.length; i++) {
427
+ const dataiy=data[i][y]
428
+ if(dataiy==null) continue;
429
+ const xPos=Math.floor(i*this.tickIncrement)+(y-1)*barWidth
430
+ points.push([xPos,dataiy])
431
+ }
432
+ group.push({action:"polyline",points:points})
433
+ return {action:"g",stroke:color,children:[group,this.getPoints(points)]}
434
+ },
435
+ chart.prototype.getPoints=function(points,call=(x,y)=>({action:"circle",cx:x,cy:y,r:3})) {
436
+ if(!this.showPoints) return ""
437
+ return points.reduce((a,p)=>{
438
+ a.push(call(p.x,py))
439
+ return a
440
+ },[])
441
+ }
442
+ chart.prototype.getPointsRect=function(points,call=(x,y)=>({action:"rect",x:x-3,y:y-3,height:6,width:6})) {
443
+ this.getPoints(points,call)
444
+ }
445
+ plot: function (y, offset, data) {
446
+ if(data.length==0) return;
447
+ const points=[]
448
+ const row0=this.data[0]
449
+ if(data.length==1 || this.highlight=='first') {
450
+ const dataX=row0[0];
451
+ const dataY=row0[y];
452
+ if (dataX==null || dataY==null) return;
453
+ points=[dataX,dataY]
454
+ if(data.length==1) return;
455
+ }
456
+ result=this.showPoints?this.getPoints(points):this.getPointsRect(points)
457
+ const colour=this.colour[y * offset]
458
+ return {action:"g",stroke:colour,children:result}
459
+ var errors;
460
+ let linePointCnt=0;
461
+ const rowPoints=[]
462
+ for(let row=0; row < data.length; row++) {
463
+ try {
464
+ const rowData=this.data[row]
465
+ const dataX=rowData[0];
466
+ const dataY=rowData[y];
467
+ if (dataX==null || dataY==null || isNaN(dataX) || isNaN(dataY)) {
468
+ if(linePointCnt>0) {
469
+ if(linePointCnt==1 && !this.showPoints)
470
+ rowPoints.push([this.data[row-1][0],data[row-1][y]])
471
+ linePointCnt=0;
472
+ }
473
+ continue;
474
+ }
475
+ plotX=dataX
476
+ plotY=dataY
477
+ if(++linePointCnt>1) {
478
+ this.ctx.lineTo(plotX,plotY);
479
+ continue;
480
+ }
481
+ this.getPoints(points)
482
+ } catch (e) {
483
+ errors += " x: " + dataX + " ( " + plotX + " ) " + " y: " + dataY + " ( " + plotY + " ) " + "\n" + e + "\n";
484
+ linePointCnt=0;
485
+ }
486
+ }
487
+ if(linePointCnt>0) {
488
+ if(( linePointCnt==1 || this.highlight=='last' ) && !this.showPoints)
489
+ rowPoints.push([dataX,dataY])
490
+ }
491
+ this.getPointsRect(rowPoints)
492
+ if(this.showPoints) {
493
+ const points=[]
494
+ for(let row= 0; row < data.length; row++) {
495
+ const dataRow=data[row]
496
+ const dataY=dataRow[y]
497
+ if(dataY==null) continue;
498
+ points.push([dataRow[0],dataY])
499
+ }
500
+ this.getPoints(points)
501
+ }
502
+ if (errors!=null) throw errors;
503
+ },
504
+ dataToString: function(i,value) {
505
+ return Format.toString(this.dataType[i],value)
506
+ }
507
+ drawLineAxis: function() {
508
+ x+-0.5
509
+ const ticks=[[0,this.height]]
510
+
511
+ return {action:"g",id:"axis",stroke:"black","stroke-width":this.axis.X.LineWidth,children:result}
512
+
513
+ {action:"line",x1:0,y1:this.height,x2:0,y2:0}
514
+ switch (this.type) {
515
+ case 'bubble':
516
+ case 'bubbleandline':
517
+ case 'line':
518
+ if (this.xTicks.length>1) {
519
+ this.ctx.textAlign='left';
520
+ this.ctx.fillText(Format.toString(this.dataType[0],parseInt(this.xTicks[0])),0,this.height-this.axisOffset/2);
521
+ }
522
+ for(let i = 0; i< this.xTicks.length; i++) {
523
+ const pos=this.xTicks[i]
524
+ this.drawXAxisTick(pos);
525
+ this.ctx.textAlign='center';
526
+ this.ctx.fillText(this.dataToString(0,this.xTicks[i],true), pos,this.height-this.axisOffset/2);
527
+ }
528
+ break;
529
+ case 'bar':
530
+ case 'pushline':
531
+ case 'setline':
532
+ case 'stack':
533
+ if (this.xTicks.length==0) this.tickIncrement=this.xMax;
534
+ else this.tickIncrement=Math.floor(this.xMax/(this.xTicks.length+1));
535
+ var k = Math.ceil((50/this.tickIncrement));
536
+ for(let i = 0; i< this.xTicks.length; i+= k) {
537
+
538
+ var pos=this.axisOffset + this.tickIncrement*(i+1);
539
+ if(this.type == 'setline')
540
+ pos -= this.tickIncrement;
541
+
542
+ this.drawXAxisTick(pos);
543
+ this.ctx.textAlign='center';
544
+ this.ctx.fillText(Format.toString(this.dataType[0],this.data[i][0]), pos,this.height-this.axisOffset/2);
545
+ }
546
+ break;
547
+ default:
548
+ }
549
+ // y axis
550
+ this.ctx.beginPath();
551
+ this.ctx.moveTo(0,0.5);
552
+ this.ctx.lineTo(this.width-( this.type == 'setline' ? this.tickIncrement : 0 ),0.5);
553
+ this.ctx.stroke();
554
+ // y ticks
555
+ for(let i = 0; i< this.yTicks.length; i++) {
556
+ var pos=this.yTicks[i]
557
+ this.drawYAxisTick(pos);
558
+ this.ctx.textAlign='left';
559
+ this.ctx.fillText(this.dataToString(1,this.yTicks[i]),0 , pos+3);
560
+ }
561
+ },
562
+
563
+ calculateTicks: function(yMax,max,min,type,metric) {
564
+ let tickPosition = [];
565
+ if (max==null || yMax==null ) return tickPosition;
566
+ const tickCount = Math.floor(yMax/20);
567
+ if(tickCount < 1) tickCount = 1;
568
+ let i=0,k=0;
569
+ let duration=max-min;
570
+ if(column.isTime()) {
571
+ this.precision[metric]=1;
572
+ const minTS= new Date();
573
+ minTS.setTime(parseInt(min));
574
+ const maxTS= new Date();
575
+ maxTS.setTime(parseInt(max));
576
+ timePrecision(duration,{
577
+ minutes:()=>{
578
+ this.precision[metric]=60;
579
+ minTS.setMilliseconds(0);
580
+ minTS.setSeconds(0);
581
+ },
582
+ hours:()=>{
583
+ this.precision[metric]=360;
584
+ minTS.setMinutes(0);
585
+ },
586
+ days:()=>{
587
+ this.precision[metric]=1440;
588
+ minTS.setHours(0);
589
+ }
590
+ })
591
+ const getTicks=(max,min,step)
592
+ for(let i = minTS.getTime() ; i < max ; i=i+duration )
593
+ if (i>min && i<=max) tickPosition[k++]=i;
594
+ )
595
+ const hour=60*60*1000
596
+ duration=Math.floor(duration/60000); // minutes?
597
+ if (duration > 0) {
598
+ duration=Math.floor(duration/60); // hours?
599
+ if (duration > 0) {
600
+ duration=Math.floor(duration/24); // days?
601
+ if (duration > 0) {
602
+ for(let j=1; j<this.height ; j++)
603
+ if (duration<j*5+1) break;
604
+ duration=j*24*hour;
605
+ } else {
606
+ duration=Math.floor((max-min)/hour);
607
+ duration=(Math.floor(duration/5)+1)*hour;
608
+ }
609
+ } else {
610
+ duration=Math.floor((max-min)/(60*1000)); // minutes
611
+ if (duration<5)
612
+ duration=60*1000; minute==60000
613
+ else if (duration<25)
614
+ duration=5*60*1000;
615
+ else
616
+ duration=10*60*1000;
617
+ }
618
+ for(let i = minTS.getTime() ; i < max ; i=i+duration )
619
+ if (i>min && i<=max) tickPosition[k++]=i;
620
+ break;
621
+ }
622
+ duration=max-min;
623
+ } else {
624
+
625
+ }
626
+ if(column.isMetric()) {
627
+ const min = parseFloat(min)
628
+ const max = parseFloat(max)
629
+ const tickSpan = Math.floor((max-min)/tickCount);
630
+ var tickTotal = Math.min(tickCount+10, Math.floor(Number.MAX_VALUE / tickSpan));
631
+ for(let j=0;j<=tickTotal;j++) {
632
+ tickPosition[j]= min + j*tickSpan;
633
+ if( tickPosition[j] > max) break;
634
+ }
635
+ } else throw 'Unknown type: "'+type+'" for axis tick creation, check types are numeric, label: '+this.label[metric];
636
+ return tickPosition;
637
+ },
638
+ resize: function() {
639
+ switch (this.dataType[0]) {
640
+ case "int":
641
+ case "real":
642
+ case "number":
643
+ case "date":
644
+ case "time":
645
+ case "datetime":
646
+ case "timestamp":
647
+ if(this.columnIndex[0]==null) {
648
+ this.dataMin[0]=0.5;
649
+ this.dataMax[0]=1.5;
650
+ this.xMax = this.width
651
+ break; // xTicks built whilst building data
652
+ }
653
+ const dataMin0=this.dataMin[0]
654
+ const dataMax0=this.dataMax[0]
655
+ this.xMax = this.width-6;
656
+ if(dataMin0==dataMax0) {
657
+ this.dataMin[0]=dataMax0-1;
658
+ this.dataMax[0]=dataMax0+1;
659
+ }
660
+ this.xRatio = this.xMax/(this.dataMax[0]-this.dataMin[0]);
661
+ this.xOffset= this.dataMin[0]
662
+ this.xTicks = this.calculateTicks(this.dataMax[0],this.dataMin[0],this.dataType[0],0);
663
+ break;
664
+ default:
665
+ }
666
+ this.yDataMin = this.yAxisLowerBound;
667
+ this.yDataMax = this.yAxisUpperBound;
668
+
669
+ switch (this.type) {
670
+ case 'stack':
671
+ this.yDataMin= this.yDataMin ?? 0
672
+ this.yDataMax= this.yDataMax ?? 0
673
+ for( var i=0; i < this.table.data.length; i++) {
674
+ if (this.dataMax[i]==null) continue;
675
+ this.yDataMax+=this.dataMax[i];
676
+ }
677
+ if(this.yAxisUpperBound==null)
678
+ this.yDataMax=this.yDataMax*1.01;
679
+ break;
680
+ case 'bar':
681
+ case 'pushline':
682
+ case 'setline':
683
+ this.yDataMin= this.yDataMin??0
684
+ this.yDataMax= this.yDataMax??0;
685
+ for(var i =0; i < this.table.data.length; i++)
686
+ if (this.yDataMax<this.dataMax[i]) this.yDataMax=this.dataMax[i];
687
+ if(this.yAxisUpperBound==null)
688
+ this.yDataMax=this.yDataMax*1.1;
689
+ break;
690
+ default:
691
+ this.yDataMax= this.yDataMax??this.dataMax[1];
692
+ this.yDataMin= this.yDataMin??this.dataMin[1];
693
+ for(var i = 0; i < this.table.data.length; i++) {
694
+ if (this.yDataMax<this.dataMax[i]) this.yDataMax=this.dataMax[i];
695
+ if (this.yDataMin>this.dataMin[i]) this.yDataMin=this.dataMin[i];
696
+ }
697
+ this.yDataMax= this.yDataMax?? 1
698
+ this.yDataMin= this.yDataMin?? 0;
699
+ if(this.yDataMin==this.yDataMax)
700
+ if(this.yDataMax==0)
701
+ this.yDataMax=1;
702
+ if(this.yAxisLowerBound==null)
703
+ this.yDataMin=this.yDataMin*0.99;
704
+ if(this.yAxisUpperBound==null)
705
+ this.yDataMax=this.yDataMax*1.01;
706
+ break;
707
+ }
708
+ if(this.zAxis) {
709
+ var zDetails=this.axis.z;
710
+ this.zDataMax=null;
711
+ this.zDataMin=null;
712
+ this.zRatioColumns=[];
713
+ for (let i=0;i<zDetails.dataMin.length;i++) {
714
+ if(this.zDataMax<zDetails.dataMax[i]) this.zDataMax=zDetails.dataMax[i];
715
+ if(this.zDataMin>zDetails.dataMin[i]) this.zDataMin=zDetails.dataMin[i];
716
+ this.zRatioColumns[i]={};
717
+ var ratio=this.zRatioColumns[i];
718
+ rangeZ=zDetails.dataMax[i]-zDetails.dataMin[i];
719
+ if(rangeZ==0) {
720
+ rangeZ=zDetails.dataMin[i];
721
+ ratio.zRatioOffset=0;
722
+ } else
723
+ ratio.zRatioOffset=zDetails.dataMin[i];
724
+ ratio.zRatio=this.bubbleRatio*(Math.min(this.yMax,this.xMax)/rangeZ );
725
+ if(ratio.zRatio==Infinity) this.zRatio=this.bubbleRatio;
726
+ if (isNaN(ratio.zRatio)) throw "z ratio calculation error, charting width:"+ this.yMax + " max:"+ zDetails.dataMax[i] + " min:"+ zDetails.dataMin[i];
727
+ }
728
+ rangeZ=this.zDataMax-this.zDataMin;
729
+ if(rangeZ==0) {
730
+ rangeZ=this.zDataMin;
731
+ this.zRatioOffset==0;
732
+ } else
733
+ this.zRatioOffset=this.zDataMin;
734
+ this.zRatio=this.bubbleRatio*(Math.min(this.yMax,this.xMax)/rangeZ);
735
+ if(this.zRatio==Infinity) this.zRatio=this.bubbleRatio;
736
+ if (isNaN(this.zRatio)) throw "z ratio calculation error, charting width:"+ this.yMax + " max:"+ Math.max.apply(Math,this.axis.z.dataMax) + " min:"+ Math.min.apply(Math,this.axis.z.dataMin);
737
+ }
738
+ },
739
+ dataConversion: function(i,value) {
740
+ if (value==null) return null;
741
+ if(dataConversionFunc[this.dataType[i]]==null) return value;
742
+ try{
743
+ return dataConversionFunc[this.dataType[i]](value);
744
+ } catch(e) {
745
+ throw "data conversion error data type: " +dataType[i] + ' value: "'+ value +'"';
746
+ }
747
+ },
748
+ buildDataSet: function(dataset) {
749
+ if (dataset.length <1) this.errorFunction('No data to chart')
750
+ delete this.columnIndexDetails;
751
+ this.columnIndex=[];
752
+ this.data=[];
753
+ this.dataType=[];
754
+ this.dataMax=[];
755
+ this.dataMin=[];
756
+ this.group=[];
757
+ this.groupIndex;
758
+ this.groupingValue="";
759
+ this.groupValue=[];
760
+ this.precision=[];
761
+ this.label=[];
762
+ this.xTicks=[];
763
+ this.yTicks=[];
764
+ // this.table.setDelta([])
765
+ this.setColumnDetails(0,this.axis.x.columns);
766
+ let x = -1
767
+ switch (this.baseTableData.columnsInfo.type[this.columnIndex[0]]) {
768
+ case 'timestamp':
769
+ this.xNormliseFactor=this.deltaNormaliser*1000;
770
+ break;
771
+ default:
772
+ this.xNormliseFactor=this.deltaNormaliser;
773
+ }
774
+ this.axis.x.normaliser=1;
775
+ var xCol=this.columnIndex[0];
776
+ const group=table.list.group.names?
777
+ (row)=>this.setData(++x):
778
+ (row)=>{
779
+ if (x==-1) {
780
+ this.setData(++x);
781
+ } else if (dataset[row][xCol] != dataset[row-1][xCol]) {
782
+ this.setData(++x);
783
+ }
784
+ var newGroupingValue='';
785
+ for(let i = 0; i < this.groupIndex.length; i++)
786
+ newGroupingValue+=' '+ dataset[row][this.groupIndex[i]];
787
+ if (this.groupingValue!=newGroupingValue) {
788
+ this.groupingValue=newGroupingValue;
789
+ this.group[0]=this.groupingValue;
790
+ const i=this.groupValue.find(v=>this.groupingValue==v)
791
+ if (i<0) {
792
+ this.groupValue.push(this.groupingValue)
793
+ this.setColumnIndexDetails("y");
794
+ }
795
+ }
796
+
797
+ }
798
+ table.forEachRow(row=>{
799
+ group(row)
800
+ })
801
+
802
+ for(let row = 0; row < dataset.length; row++) {
803
+ if (this.grouping==null) {
804
+ this.setData(++x);
805
+ } else {
806
+ if (x==-1) {
807
+ this.setData(++x);
808
+ } else if (dataset[row][xCol] != dataset[row-1][xCol]) {
809
+ this.setData(++x);
810
+ }
811
+ let newGroupingValue='';
812
+ for( i = 0; i < this.groupIndex.length; i++)
813
+ newGroupingValue+=' '+ dataset[row][this.groupIndex[i]];
814
+ if (this.groupingValue!=newGroupingValue) {
815
+ this.groupingValue=newGroupingValue;
816
+ this.group[0]=this.groupingValue;
817
+ const i=this.groupValue.find(v=>this.groupingValue==v)
818
+ if (i <0) {
819
+ this.groupValue.push(this.groupingValue)
820
+ this.setColumnIndexDetails("y");
821
+ }
822
+ }
823
+ }
824
+ this.xTicks[x]=row;
825
+ for(var i = 0; i < this.columnIndex.length; i++) {
826
+ if (this.group[i]!=this.groupingValue) continue;
827
+ var value = (this.columnIndex[i]==null?x+1:this.dataConversion(i,dataset[row][this.columnIndex[i]]));
828
+ this.data[x][i] = value;
829
+ this.setMaxMin(this, i, value);
830
+ if(this.zAxis) this.setDataAxis('z',dataset[row],x,i);
831
+ }
832
+ }
833
+ },
834
+ setMaxMin: (base,i,value)=>{
835
+ if (base.dataMin[i]==null) {
836
+ base.dataMin[i]=value;
837
+ base.dataMax[i]=value;
838
+ } else if (base.dataMin[i]>value)
839
+ base.dataMin[i]=value;
840
+ else if (base.dataMax[i]<value)
841
+ base.dataMax[i]=value;
842
+ },
843
+ setData: function(x) {
844
+ this.data[x]=[];
845
+ this.deltaData[x]=[];
846
+ },
847
+ setDataAxis: function(axis,row,x,i) {
848
+ var colDetails=this.columnIndexDetails[axis];
849
+ var value = (row==null?null:this.dataConversion(i,row[colDetails.columnIndex[i]]));
850
+ if(colDetails.dataStore[x]==null) {
851
+ colDetails.dataStore[x]=[];
852
+ colDetails.deltaData[x]=[];
853
+ }
854
+ colDetails.dataStore[x][i]=value;
855
+ this.setMaxMin(colDetails, i, value);
856
+ },
857
+ setColumnDetails: function(index,columnName) {
858
+ if(columnName==null) {
859
+ this.columnIndex[index]=null;
860
+ this.dataType[index]='int';
861
+ this.label[index]=this.groupingValue;
862
+ this.group[index]=this.groupingValue;
863
+ return;
864
+ }
865
+ const i = this.findColumnIndex(columnName);
866
+ this.columnIndex[index]=i;
867
+ this.dataType[index]=this.baseTableData.columnsInfo.type[i];
868
+ this.label[index]=this.groupingValue + " " + this.table.getColumn(columnName).title
869
+ this.group[index]=this.groupingValue;
870
+ },
871
+ setColumnDetailsAxis: function(columnDetails,columnName) {
872
+ if(columnName==null) throw "Column name not specified"
873
+ const i = this.findColumnIndex(columnName);
874
+ const index=columnDetails.columnIndex.length;
875
+ columnDetails.columnIndex[index]=i;
876
+ columnDetails.dataType[index]=this.baseTableData.columnsInfo.type[i];
877
+
878
+
879
+
880
+ columnDetails.label[index]=this.groupingValue + " " + this.table.getColumn(columnName).title
881
+ columnDetails.group[index]=this.groupingValue;
882
+ },
883
+ setListProperty: function(select,property,list) {
884
+ this[property]=[];
885
+ const p=this[property]
886
+ for (let i = 0; i < select.options.length; i++)
887
+ if (select.options[i].selected)
888
+ p.push(select.options[i].value)
889
+ this[list]=p.join();
890
+ },
891
+ setMetrics: function(select) {
892
+ this.setListProperty(select,"ySeries","yAxis")
893
+ },
894
+ setGrouping: function(select) {
895
+ this.setListProperty(select,"groupingArray","grouping")
896
+ }
897
+ getMetricColumnsOptions=function() {
898
+ const option='<tr><td>Y Metrics</td><td>'
899
+ +this.table.getSelectHML(this.ySeries,this.callBackText+".setMetrics(this)",(column)=>column.isNumeric())
900
+ +'</td></tr>'
901
+ +'<tr><td>Grouping</td><td>'
902
+ +this.table.getSelectHML(this.grouping,this.callBackText+".setGrouping(this)",(column)=>column.type=="string")
903
+ +'</td></tr>'
904
+ return option
905
+ }
906
+ }