PageRenderTime 26ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/timApp/modules/cs/static/chartjs/timonedata.js

https://gitlab.com/tim-jyu/tim
JavaScript | 408 lines | 328 code | 31 blank | 49 comment | 96 complexity | 079c4257c77cbe8d3afa739ac849350e MD5 | raw file
  1. /*!
  2. * chartjs-plugin-trendline.js
  3. * Version: 0.1.1
  4. *
  5. * Copyright 2017 Marcus Alsterfjord
  6. * Released under the MIT license
  7. * https://github.com/Makanz/chartjs-plugin-trendline/blob/master/README.md
  8. *
  9. * Mod by: vesal: accept also xy-data so works with scatter
  10. */
  11. var pluginTrendlineLinear = {
  12. beforeDraw: function(chartInstance) {
  13. let yScale;
  14. let xScale;
  15. for (let axis in chartInstance.scales) {
  16. if ( axis[0] == 'x')
  17. xScale = chartInstance.scales[axis];
  18. else
  19. yScale = chartInstance.scales[axis];
  20. if ( xScale && yScale ) break;
  21. }
  22. let ctx = chartInstance.ctx;
  23. if ( !ctx && chartInstance.chart)
  24. ctx = chartInstance.chart.ctx;
  25. if ( !ctx ) return;
  26. chartInstance.data.datasets.forEach(function(dataset, index) {
  27. if (dataset.trendlineLinear && chartInstance.isDatasetVisible(index)) {
  28. let datasetMeta = chartInstance.getDatasetMeta(index);
  29. addFitter(datasetMeta, ctx, dataset, xScale, yScale);
  30. }
  31. });
  32. ctx.setLineDash([]);
  33. }
  34. };
  35. function addFitter(datasetMeta, ctx, dataset, xScale, yScale) {
  36. let style = dataset.trendlineLinear.style || dataset.borderColor;
  37. let lineWidth = dataset.trendlineLinear.width || dataset.borderWidth;
  38. let lineStyle = dataset.trendlineLinear.lineStyle || "solid";
  39. style = (style !== undefined) ? style : "rgba(169,169,169, .6)";
  40. lineWidth = (lineWidth !== undefined) ? lineWidth : 3;
  41. let fitter = new LineFitter();
  42. let lastIndex = dataset.data.length - 1;
  43. let startPos = 0;
  44. let endPos = 0;
  45. if ( lastIndex > 0) {
  46. startPos = datasetMeta.data[0]._model.x;
  47. endPos = datasetMeta.data[lastIndex]._model.x;
  48. }
  49. let xy = false;
  50. if ( dataset.data && typeof dataset.data[0] === 'object') xy = true;
  51. dataset.data.forEach(function(data, index) {
  52. if ( xy ) fitter.add(data.x, data.y);
  53. else fitter.add(index, data);
  54. });
  55. let x1 = xScale.getPixelForValue(fitter.minx);
  56. let x2 = xScale.getPixelForValue(fitter.maxx);
  57. let y1 = yScale.getPixelForValue(fitter.f(fitter.minx));
  58. let y2 = yScale.getPixelForValue(fitter.f(fitter.maxx));
  59. if ( !xy ) { x1 = startPos; x2 = endPos; }
  60. ctx.lineWidth = lineWidth;
  61. if (lineStyle === "dotted") { ctx.setLineDash([2, 3]); }
  62. ctx.beginPath();
  63. ctx.moveTo(x1, y1);
  64. ctx.lineTo(x2, y2);
  65. ctx.strokeStyle = style;
  66. ctx.stroke();
  67. }
  68. Chart.plugins.register(pluginTrendlineLinear);
  69. function LineFitter() {
  70. this.count = 0;
  71. this.sumX = 0;
  72. this.sumX2 = 0;
  73. this.sumXY = 0;
  74. this.sumY = 0;
  75. this.minx = 1e100;
  76. this.maxx = -1e100;
  77. }
  78. LineFitter.prototype = {
  79. 'add': function (x, y) {
  80. this.count++;
  81. this.sumX += x;
  82. this.sumX2 += x * x;
  83. this.sumXY += x * y;
  84. this.sumY += y;
  85. if ( x < this.minx ) this.minx = x;
  86. if ( x > this.maxx ) this.maxx = x;
  87. },
  88. 'f': function (x) {
  89. let det = this.count * this.sumX2 - this.sumX * this.sumX;
  90. let offset = (this.sumX2 * this.sumY - this.sumX * this.sumXY) / det;
  91. let scale = (this.count * this.sumXY - this.sumX * this.sumY) / det;
  92. return offset + x * scale;
  93. }
  94. };
  95. /**
  96. * Simple is object check.
  97. * @param item
  98. * @returns {boolean}
  99. */
  100. function isObject(item) {
  101. return (item && typeof item === 'object' && !Array.isArray(item));
  102. }
  103. /**
  104. * Deep merge two objects.
  105. * @param target object where to merge
  106. * @param source object where from merge
  107. * @forcechar char for starting the attribute name when no deepcopy is done, instead object reference
  108. */
  109. function mergeDeep(target, source, forcechar) {
  110. if (isObject(target) && isObject(source)) {
  111. if ( !forcechar ) forcechar = '!';
  112. for (let key in source) {
  113. if ( key.startsWith('_') ) { continue; }
  114. if ( key.startsWith(forcechar) ) {
  115. target[key.substring(1)] = source[key];
  116. continue;
  117. }
  118. if (isObject(source[key])) {
  119. // if (!target[key]) Object.assign(target, { [key]: {} });
  120. if (!target[key]) target[key] = {};
  121. mergeDeep(target[key], source[key], forcechar);
  122. } else {
  123. // Object.assign(target, { [key]: source[key] });
  124. target[key] = source[key];
  125. }
  126. }
  127. }
  128. return target;
  129. }
  130. // TIM jsframe function for ChartJS
  131. var TIMJS = {};
  132. TIMJS.basicoptions = {
  133. 'type': 'bar',
  134. 'data': {
  135. 'labels': ['Chart','has','no','data'],
  136. 'datasets': [
  137. {
  138. 'label': '',
  139. 'lineTension': 0,
  140. 'fill': false,
  141. 'backgroundColor': 'rgba(0,0,255,0.5)',
  142. 'borderColor': '#0000ff',
  143. 'borderWidth': 1,
  144. 'data': [0, 1, 2, 3],
  145. },
  146. ]
  147. },
  148. 'options': {
  149. 'responsive': true,
  150. 'legend': { 'display': false, 'position': 'right', },
  151. 'title': { 'display': true, 'text': '' },
  152. 'scales': {
  153. 'xAxes': [{ 'position': 'bottom', 'scaleLabel': {'labelString': '', 'display': true}}],
  154. 'yAxes': [{'type': 'linear', 'position': 'left', 'scaleLabel': {'labelString': '', 'display': true}, 'display': true, 'ticks': { 'min': 0, }}],
  155. }
  156. }
  157. };
  158. TIMJS.COLORS = [
  159. '#0000ff',
  160. '#FF0000',
  161. '#4dc9f6',
  162. '#f67019',
  163. '#f53794',
  164. '#537bc4',
  165. '#acc236',
  166. '#166a8f',
  167. '#00a950',
  168. '#58595b',
  169. '#8549ba'
  170. ];
  171. TIMJS.color = Chart.helpers.color;
  172. function pros(od) {
  173. // return od;
  174. let max = Math.max(...od);
  175. let a = [];
  176. for (let i=0; i<od.length; i++) {
  177. a[i] = Math.round(od[i]*1000/max)/10;
  178. }
  179. // console.log(max, a);
  180. return a;
  181. }
  182. /**
  183. * Add dtata to datasets from datas-object
  184. * @param datasets where to add
  185. * @param datas where from copy
  186. * @param keys keys to take from datas
  187. * @param dopros add prosentual value instead totaö value
  188. */
  189. function addData(datasets, datas, keys, dopros) {
  190. let ci = 0;
  191. for (const v of keys) {
  192. let d = {
  193. lineTension: 0,
  194. label: ''+v+ (dopros ? ' %' : ''),
  195. fill: false,
  196. // borderDash: [3,10],
  197. borderWidth: 1,
  198. };
  199. let od = datas[v];
  200. if ( !od || od.length === 0) continue;
  201. d.data = dopros ? pros(od) : od;
  202. let wasIn = false;
  203. for (const di in datasets ) {
  204. if (datasets[di].label === d.label) {
  205. datasets[di] = d;
  206. ci = di;
  207. wasIn = true;
  208. break;
  209. }
  210. }
  211. if (!wasIn) {
  212. datasets.push(d);
  213. ci = datasets.length-1;
  214. }
  215. ci = ci % TIMJS.COLORS.length;
  216. d.backgroundColor = TIMJS.color(TIMJS.COLORS[ci]).alpha(0.5).rgbString();
  217. d.borderColor = TIMJS.COLORS[ci];
  218. }
  219. }
  220. TIMJS.globaldata = {};
  221. TIMJS.chart = null;
  222. TIMJS.originalData = {};
  223. TIMJS.initData = {};
  224. TIMJS.options = TIMJS.basicoptions;
  225. window.onload = function() {
  226. if ( TIMJS.initData ) TIMJS.globaldata = TIMJS.initData; // mergeDeep(globaldata, window.initData);
  227. TIMJS.setData(TIMJS, TIMJS.globaldata);
  228. };
  229. /**
  230. * Be sure that there is n items in datasets, if not clone previous item
  231. * @param datasets data sets array for data
  232. * @param n number of items needed at least
  233. */
  234. function ensureDataSets(datasets, n) {
  235. let diff = n - datasets.length;
  236. if ( diff <= 0 ) return;
  237. for (let i = diff; i < n; i++ ) {
  238. datasets[i] = {};
  239. mergeDeep(datasets[i], datasets[i-1]);
  240. }
  241. }
  242. /**
  243. * Set chart data from data, needs global variables globaldata, chart, originaldata
  244. * @param P global data needed to work
  245. * @param data data to be added or merged to chart
  246. */
  247. TIMJS.setData = function(P, data) {
  248. try {
  249. // we expect that data is in format {data, labels, fielddata}
  250. // and current jsframe gives it in format {c: {data, labels}, fielddata}
  251. if ( data && data.c ) { // so we must tune it a bit
  252. let tdata = data.c;
  253. tdata.fielddata = data.fielddata;
  254. data = tdata;
  255. }
  256. P.globaldata = data;
  257. // noinspection JSUnresolvedVariable
  258. if ( P.originalData ) {
  259. let newData = {};
  260. if ( P.chart ) P.originalData.datas = null; // prevent another add
  261. mergeDeep(newData, P.originalData, '#'); // do not loose possible !
  262. mergeDeep(newData, data);
  263. data = newData;
  264. }
  265. if ( !P.chart ) {
  266. let ar = data.aspectRatio || data.options && data.options.aspectRatio;
  267. if ( ar ) P.options.options.aspectRatio = ar;
  268. let ctx = document.getElementById('canvas').getContext('2d');
  269. P.chart = new Chart(ctx,P.options);
  270. }
  271. let datasets = P.chart.config.data.datasets;
  272. let coptions = P.chart.config.options;
  273. let dopros = data.dopros || false;
  274. let fieldindex = data.fieldindex || 0;
  275. if ( data.type ) {
  276. P.chart.config.type = data.type;
  277. if ( data.type === "scatter" || data.linearx) {
  278. coptions.scales.xAxes = [{ type: 'linear', position: "bottom", scaleLabel: {labelString: "", display: true}}];
  279. coptions.scales.yAxes = [{ type: 'linear', position: "left", scaleLabel: {labelString: "", display: true}}];
  280. }
  281. if ( data.type === "scatter" ) {
  282. datasets[0].showLine = false; // for version 2.8.0
  283. }
  284. }
  285. if ( data.labels ) P.chart.data.labels = data.labels;
  286. if ( data.data ) {
  287. fieldindex++;
  288. datasets[0].data = dopros ? pros(data.data) : data.data;
  289. }
  290. if ( data.data2 || data.label2 || data.dataopt2 ) {
  291. ensureDataSets(datasets, 2);
  292. datasets[1].backgroundColor = 'rgba(0,127,0,0.5)';
  293. datasets[1].borderColor = '#080';
  294. }
  295. if ( data.data2 ) {
  296. if ( fieldindex === 1 ) fieldindex = 2;
  297. datasets[1].data = dopros ? pros(data.data2) : data.data2;
  298. }
  299. if ( data.label ) datasets[0].label = data.label;
  300. if ( data.title) P.chart.options.title.text = data.title; // Tämä pitää olla näin
  301. if ( data.xlabel ) {
  302. if ( Array.isArray(coptions.scales.xAxes) ) { // in 2.8.0 is an array
  303. coptions.scales.xAxes[0].scaleLabel.labelString = data.xlabel;
  304. } else {
  305. coptions.scales.xAxes.scaleLabel.labelString = data.xlabel;
  306. }
  307. }
  308. if ( data.ylabel) {
  309. if ( Array.isArray(coptions.scales.yAxes) ) {
  310. coptions.scales.yAxes[0].scaleLabel.labelString = data.ylabel;
  311. } else {
  312. coptions.scales.yAxes.scaleLabel.labelString = data.ylabel;
  313. }
  314. }
  315. if ( data.label && datasets.length > 1 ) coptions.legend.display = true;
  316. if ( data.label2 ) {
  317. coptions.legend.display = true;
  318. datasets[1].label = data.label2;
  319. }
  320. if (typeof data.legend != "undefined") {
  321. // noinspection EqualityComparisonWithCoercionJS
  322. if ( data.legend == false ) {
  323. coptions.legend.display = false;
  324. // noinspection EqualityComparisonWithCoercionJS
  325. } else if ( data.legend == true ) {
  326. coptions.legend.display = true;
  327. } else {
  328. coptions.legend.display = true;
  329. coptions.legend.position = data.legend;
  330. }
  331. }
  332. let fdata = data.fielddata || P.fieldData;
  333. if ( fdata ) {
  334. ensureDataSets(datasets, fieldindex+1);
  335. if (fieldindex === 0) {
  336. if (!P.data) {
  337. P.data = {};
  338. }
  339. P.data.labels = fdata.graphdata.labels;
  340. }
  341. datasets[fieldindex].data = fdata.graphdata.data;
  342. datasets[fieldindex].backgroundColor = 'rgba(255,0,0,0.5)';
  343. datasets[fieldindex].borderColor = '#F00';
  344. }
  345. if ( data.trend ) { // TODO: for every data
  346. let w = data.trend;
  347. if ( w === true ) w = 2;
  348. datasets[0].trendlineLinear = {
  349. 'style': "rgba(255,105,180, .8)",
  350. 'lineStyle': "dotted|solid",
  351. 'width': w
  352. };
  353. }
  354. if ( data.datas ) {
  355. let keys = data.datakeys || Object.keys(data.datas);
  356. addData(datasets, data.datas, keys, dopros);
  357. }
  358. if ( data.label && datasets.length > 1 ) coptions.legend.display = true;
  359. if ( data.options ) { mergeDeep(P.chart.options, data.options); }
  360. if ( data.dataopt ) { mergeDeep(datasets[0], data.dataopt); }
  361. if ( data.dataopt2 ) { mergeDeep(datasets[1], data.dataopt2); }
  362. P.chart.update();
  363. } catch(err) {
  364. console.error(err);
  365. let cont = document.getElementById('container');
  366. let p = document.createElement("p");
  367. let textnode = document.createTextNode(err.message);
  368. p.appendChild(textnode);
  369. cont.insertBefore(p, cont.firstChild);
  370. }
  371. };
  372. function setData(data) {
  373. TIMJS.setData(TIMJS, data);
  374. }
  375. function getData() {
  376. return TIMJS.globaldata;
  377. }