import { Component, ViewChild, ElementRef } from '@angular/core'; import { GHCIService } from '../../../shared/services/ghci.service'; import functionPlot from 'function-plot'; import { Animation, Setting, toJSON, triggerDownload } from './graph2D.helper'; @Component({ moduleId: module.id, selector: 'graph2D-component', templateUrl: './graph2D.component.html', styleUrls: ['./graph2D.component.scss'], host: { '(window:resize)': 'onResize($event)' } }) export class Graph2DComponent { // Ghci Service private ghciServiceSub: any; // Chart Container - DOM Element @ViewChild('graph2DContainer') private graph2DRef: ElementRef; // Chart Instance private instance: any; // Settings settings: Setting = { axis: true, grid: true, tip: true } private funciones= []; private id = 0; private conjunto= []; // Animation state animation: Animation = { data: [], timer: null, currentFrame: 0, speed: 1000, playing: false, init: false, boton: true, zoo: 2000 }; //Nuevo public setZoom = () => { this.animation.zoo = this.animation.zoo ; } public multiGraf = () => { this.animation.boton = !this.animation.boton; } public constructor(private ghciService: GHCIService) { this.ghciServiceSub = ghciService.messages.subscribe( canvas => { // Stop Animation if (this.animation.init) { this.stopAnimation(); } switch(canvas.tipo) { case 'graph': { var jsonCanvas = JSON.parse(canvas.resultado); let fun = eval(this.generarFuncion(jsonCanvas)); var conjs = this.obtenerConjunto(jsonCanvas.funs[0]); var d = conjs + "}"; //Leo var obj = JSON.parse(d); //Para las funciones if (obj.conj.sets.fdom == "function(x)") { var nom = jsonCanvas.funs[0].dom; var elemento1 = this.recursionfuncion(jsonCanvas.funs[0].sets, nom); obj.conj.sets.fdom = function (x) { return eval(elemento1) } } if (obj.conj.sets.fcod == "function(x)") { var nom = jsonCanvas.funs[0].cod; var elemento2 = this.recursionfuncion(jsonCanvas.funs[0].sets, nom); obj.conj.sets.fcod = function (x) { return (eval(elemento2)) } } //para Enumerados if (obj.conj.dom == 'Numer') { var cantElementos = obj.conj.sets.fdom.length; var j = 0; for (var fun of obj.conj.sets.fdom) { //var newstr = nuevo2.replace(fun, j); j = j + 1; } } if (obj.conj.cod == 'Numer') { var cantElementos = obj.conj.sets.fcod.length; var j = 0; for (var fun of obj.conj.sets.fcod) { //var newstr = nuevo2.replace(fun, j); j = j + 1; } } var colores = ['pink', 'red', 'blue', 'orange', 'green'] var num = this.getRandomArbitrary(0, 4); var color = colores[num]; var tipoGraf; if (obj.conj.baseDom != 'R'){ tipoGraf = 'scatter'; }else{ tipoGraf = 'polyline'; } if(this.animation.boton && obj.conj.cod != 'Numer' && obj.conj.dom != 'Numer'){ if(this.conjunto.length == 1){ this.id = 1; this.conjunto.unshift({radio: 2, dom:this.conjunto[0].baseDom, cod:this.conjunto[0].baseCod, baseCod:this.conjunto[0].baseCod, baseDom:this.conjunto[0].baseDom, sets:{fdom:this.conjunto[0].baseDom,fcod:this.conjunto[0].baseCod}}); this.funciones[0].id = this.id; } if (this.conjunto.length != 0){ if (obj.conj.baseDom == 'R'){ this.conjunto[0].baseDom = 'R'; this.conjunto[0].dom = 'R'; this.conjunto[0].sets.fdom = 'R'; } if (obj.conj.baseCod == 'R'){ this.conjunto[0].baseCod = 'R'; this.conjunto[0].cod = 'R'; this.conjunto[0].sets.fcod = 'R'; } } this.conjunto.push(obj.conj); this.funciones.push({ id: this.funciones.length + this.id, sampler: 'builtIn', fn: function(scope) { return fun(scope.x) }, graphType: tipoGraf, color: color }); }else{ this.conjunto = []; this.conjunto.push(obj.conj); this.id = 0; this.funciones = []; this.funciones.push({ id: this.funciones.length, sampler: 'builtIn', fn: function(scope) { return fun(scope.x) }, graphType: tipoGraf, color: color }); } let bounding = this.getBounding(); this.instance = functionPlot({ target: '#graph2D-container', width: bounding.width, height: bounding.height, tip: { color: 'green' }, xAxis: { label: 'x - axis', scale: 'linear', domain: { initial: [-4, 4], type: 'discrete' }, yAxis: { domain: [-4, 4] } }, conj:this.conjunto, data: this.funciones, zoom:this.animation.zoo, plugins: [ functionPlot.plugins.zoomBox() ] }) break; } case 'canvas': { var shapesData = JSON.parse(canvas.resultado); var shapesDataNormalized = this.normalizeShapesData(shapesData); let bounding = this.getBounding(); this.cleanPlot(); this.instance = functionPlot({ target: '#graph2D-container', width: bounding.width, height: bounding.height, grid: true, xAxis: { label: 'x - axis', scale: 'linear', domain: { initial: [-10, 10], type: 'discrete' } }, data: shapesDataNormalized, plugins: [ functionPlot.plugins.zoomBox() ] }) break; } case 'animacion': { this.cleanPlot(); var animationData = canvas.resultado.map(res => JSON.parse(res)); for (var frame of animationData) { this.animation.data.push(this.normalizeShapesData(frame)); } this.runAnimation(); this.animation.init = true; break; } } }, error => { } ) } /** * Angular lifecycle hook. * called after Angular has fully initialized a component's view. */ ngAfterViewInit() { if (!this.instance) { let bounding = this.getBounding(); this.instance = functionPlot({ target: '#graph2D-container', width: bounding.width, height: bounding.height, grid: true, xAxis: { label: 'x - axis', scale: 'linear', domain: { initial: [-10, 10], type: 'discrete' } }, data: [] }) } } /** * Angular lifecycle hook. * called when a directive, pipe, or service is destroyed. */ ngOnDestroy() { if (this.ghciServiceSub) { this.ghciServiceSub.unsubscribe(); } } /** * On Resize Event. */ onResize(event){ let instance = this.instance; let bounding = this.getBounding(); //if (bounding.width > 0) { console.log('yes'); instance.options.width = bounding.width; instance.options.height = bounding.height; instance.build(); //} } onClickMe(event) { console.log('click'); } /** * @name getBounding * @desc get the measures of the container of the graph */ private getBounding = function() { const {width, height} = this.graph2DRef.nativeElement.getBoundingClientRect(); return {width, height} } /** * @name updateFrame * @desc update data for Function Plot and redraw the graph */ public updateFrame = function(d) { if (this.instance) { this.instance.options.data = d; this.instance.draw(); } else { let bounding = this.getBounding(); this.instance = functionPlot({ target: '#graph2D-container', width: bounding.width, height: bounding.height, xAxis: { label: 'x - axis', scale: 'linear', domain: { initial: [-10, 10], type: 'discrete' } }, data: d }) } // Update Frame this.animation.currentFrame = (this.animation.currentFrame + 1) % this.animation.data.length; } /** * @name runAnimation * @desc Run Shapes Animation */ public runAnimation = function() { var $this = this; if ($this.animation.timer !== null) return; $this.updateFrame($this.animation.data[$this.animation.currentFrame]); $this.animation.timer = setInterval(function(){ $this.updateFrame($this.animation.data[$this.animation.currentFrame]); }, $this.animation.speed); $this.animation.playing = true; } /** * @name pauseAnimation * @desc Pause Shapes Animation */ public pauseAnimation = function() { var $this = this; clearInterval($this.animation.timer); $this.animation.timer = null; $this.animation.playing = false; } /** * @name stopAnimation * @desc Stop Shapes Animation */ public stopAnimation = function() { var $this = this; clearInterval($this.animation.timer); $this.animation.data = []; $this.animation.timer = null; $this.animation.currentFrame = 0; $this.animation.speed = 1000; $this.animation.playing = false; $this.animation.init = false; this.instance.removeAllGraphs(); } /** * @name increaseSpeed * @desc Increase Speed Animation */ public increaseSpeed = function() { this.animation.speed *= 1.5; console.log(this.animation.speed); } /** * @name toggleGrid * @desc Show and Hide Grid */ public toggleGrid = function () { //this.instance.toggleGrid(); } /** * @name toggleAxis * @desc Show and Hide Axis */ public toggleAxis = function () { //this.instance.toggleAxis(); } /** * @name toggleTip * @desc Show and Hide Tip */ public toggleTip = function () { //this.instance.toggleTip(); } /** * @name zoomOut * @desc Zoom Out Button Control */ public zoomOut = function () { this.instance.zoomOut(); } /** * @name zoomIn * @desc Zoom In Button Control */ public zoomIn = function () { this.instance.zoomIn(); } /** * @name recenterPlot * @desc center the plot and it returns to the initial state. */ public recenterPlot = function () { this.instance.recenter(); } /** * @name cleanPlot * @desc remove all the graph from the instance. */ public cleanPlot = function () { if (this.animation.playing) { this.stopAnimation(); } else { this.funciones = []; this.conjunto = []; this.id = 0; this.instance.removeAllGraphs(); } } /** * @name exportPlot * @desc Download Plot as an SVG image. */ public exportPlot = function() { // Objects var svg = document.querySelector('svg'); var canvas = document.createElement("canvas"); // Set dimensions of the image var svgSize = svg.getBoundingClientRect(); canvas.width = svgSize.width; canvas.height = svgSize.height; // Convert SVG DOM structure to xml var ctx = canvas.getContext('2d'); var data = new XMLSerializer().serializeToString(svg); // URL Object used to parse, construct, normalise, and encode URLs. var DOMURL = window.URL || (<any>window).webkitURL || window; var img = new Image(); var svgBlob = new Blob([data], { type: 'image/svg+xml;charset=utf-8' }); var url = DOMURL.createObjectURL(svgBlob); img.onload = function () { ctx.drawImage(img, 0, 0); DOMURL.revokeObjectURL(url); var imgURI = canvas .toDataURL('image/png') .replace('image/png', 'image/octet-stream'); triggerDownload(imgURI); }; img.src = url; } /** * @name normalizeRectData * @desc Normalize Rectangle data for Function Plot Library * @param {Object} rectData Data of Rectangle to be normalized * @returns {Object} */ public normalizeRectData = function ($rectData) { var $rectNormalized:any = {}; var $shape:any = {}; $shape.w = $rectData.w; $shape.h = $rectData.h; $shape.x = $rectData.x; $shape.y = $rectData.y; $rectData.color && ($shape.fill = $rectData.color); $rectData.rot !== 'undefined' && ($shape.rotation = $rectData.rot); $rectNormalized.shape = $shape; $rectNormalized.graphType = 'shape'; $rectNormalized.shapeType = 'rect'; return $rectNormalized; } /** * @name normalizeCircleData * @desc Normalize Circle data for Function Plot Library * @param {Object} circleData Data of Circle to be normalized * @returns {Object} */ public normalizeCircleData = function ($circleData) { var $circleNormalized:any = {}; var $shape:any = {}; $shape.r = $circleData.r; $shape.x = $circleData.x; $shape.y = $circleData.y; $circleData.color && ($shape.fill = $circleData.color); $circleData.rot !== 'undefined' && ($shape.rotation = $circleData.rot); $circleNormalized.shape = $shape; $circleNormalized.graphType = 'shape'; $circleNormalized.shapeType = 'circle'; return $circleNormalized; } /** * @name normalizeTextData * @desc Normalize Text data for Function Plot Library * @param {Object} textData Data of Text to be normalized * @returns {Object} */ public normalizeTextData = function ($textData) { var $textNormalized:any = {}; var $shape:any = {}; $shape.text = $textData.text; $shape.size = $textData.size; $shape.x = $textData.x; $shape.y = $textData.y; $textData.color && ($shape.fill = $textData.color); $textData.rot !== 'undefined' && ($shape.rotation = $textData.rot); $textNormalized.shape = $shape; $textNormalized.graphType = 'shape'; $textNormalized.shapeType = 'text'; return $textNormalized; } /** * @name normalizeLineData * @desc Normalize Line data for Function Plot Library * @param {Object} lineData Data of Line to be normalized * @returns {Object} */ // public normalizeLineData = function ($lineData) { // var $lineNormalized:any = {}; // var $points = [] // for (var p of $lineData.pts) { // $points.push([p[0],p[1]]); // } // $lineNormalized.points = $points; // $lineNormalized.color = $lineData.color; // $lineNormalized.rotation = $lineData.rot; // $lineNormalized.fnType = 'points'; // $lineNormalized.polylineType = 'line'; // $lineNormalized.graphType = 'polyline'; // return $lineNormalized; // } /** * @name normalizePolygonData * @desc Normalize Polygon data for Function Plot Library * @param {Object} textData Data of Polygon to be normalized * @returns {Object} */ // public normalizePolygonData = function ($textData) { // var $PoligonNormalized:any = {}; // var $shape:any = {}; // $shape.text = $textData.text; // $shape.size = $textData.size; // $shape.x = $textData.x; // $shape.y = $textData.y; // $textData.color && ($shape.fill = $textData.color); // $textData.rot !== 'undefined' && ($shape.rotation = $textData.rot); // $textNormalized.shape = $shape; // $textNormalized.graphType = 'shape'; // $textNormalized.shapeType = 'text'; // return $textNormalized; // } /** * @name normalizeShapesData * @desc Normalize Shapes data for Function Plot Library * @param {Array} shapesData Data of Shapes to be normalized * @returns {Array} */ public normalizeShapesData = function (shapesData) { var normalized:Array<Object> = []; for (var shape of shapesData) { switch(shape.kind) { case 'rect': { normalized.push(this.normalizeRectData(shape)); break; } case 'circle': { normalized.push(this.normalizeCircleData(shape)); break; } case 'text': { normalized.push(this.normalizeTextData(shape)); break; } // case 'line': { // normalized.push(this.normalizeLineData(shape)); // break; // } // case 'polygon': { // normalized.push(this.normalizePolygonData(shape)); // break; // } } } return normalized; } getRandomArbitrary = function (min, max) { return Math.round(Math.random() * (max - min) + min); } generarFuncion = function (graph) { var funcionString = ''; var grafica; for (var fun of graph.funs) { funcionString = 'var ' + fun.fun + ' = function(' + fun.args.join() + '){\n return ' + this.generarExpresion(fun.bdy) + '}\n' + funcionString; if (fun.fun == graph.graph) { funcionString += 'return ' + fun.fun + '(' + fun.args.join() + ');\n' grafica = fun; } } funcionString = '(' + grafica.args.join() + ')=>{\n' + funcionString + '}'; return funcionString; } generarExpresion = function (exp) { var expresion = ''; if (exp.kind == 'cnd') { expresion = ' (' + this.generarExpresion(exp.cond) + '?' + this.generarExpresion(exp.exp1) + ':' + this.generarExpresion(exp.exp2) + ') '; } else if (exp.kind == 'bop') { if (exp.op == '==') { expresion = ' Math.abs((' + this.generarExpresion(exp.exp1) + ') - (' + this.generarExpresion(exp.exp2) + ')) == 0 '; } else if (exp.op == '/=') { expresion = ' Math.abs((' + this.generarExpresion(exp.exp1) + ') - (' + this.generarExpresion(exp.exp2) + ')) == 0 || Math.abs((' + this.generarExpresion(exp.exp1) + ') - (' + this.generarExpresion(exp.exp2) + ')) == 0 '; } else if (exp.op == '^') { expresion = ' Math.pow(' + this.generarExpresion(exp.exp1) + ',' + this.generarExpresion(exp.exp2) + ') '; } else { expresion = ' (' + this.generarExpresion(exp.exp1) + ')' + exp.op + '(' + this.generarExpresion(exp.exp2) + ') '; } } else if (exp.kind == 'uop') { expresion = ' ' + exp.op + ' ' + this.generarExpresion(exp.exp) + ' '; } else if (exp.kind == 'app') { if (exp.fun == 'cos') { exp.fun = 'Math.cos' } else if (exp.fun == 'sin') { exp.fun = 'Math.sin' } else if (exp.fun == 'round') { exp.fun = 'Math.round' } expresion = ' ' + exp.fun + '(' + exp.args.map(e => this.generarExpresion(e)).join() + ') '; } else if (exp.kind == 'tup') { expresion = ' (' + exp.exps.map(e => this.generarExpresion(e)).join() + ') '; } else if (exp.kind == 'lit') { expresion = ' ' + exp.val + ' '; } else if (exp.kind == 'var') { expresion = ' ' + exp.var + ' '; } else { expresion = ' undefined '; } return expresion; } //Nuevo 20-07-2018 obtenerConjunto = function (grf) { var setf = '\"sets\": {'; var dominio = '{\"conj\": {'; if (grf.dom == 'R') { dominio += "\"radio\": 0.3, \"baseDom\": \"R\", \"dom\": \"R\""; setf += "\"fdom\": \"R\","; } else if (grf.dom == 'Z') { dominio += "\"radio\": 2, \"baseDom\": \"Z\", \"dom\": \"Z\""; setf += "\"fdom\": \"Z\","; }/* else if (grf.dom == 'N') { dominio += "\"radio\":2, \"baseDom\": \"N\", \"dom\": \"N\""; setf += "\"fdom\": \"N\","; }*/ else { var nom = grf.dom; if (Array.isArray(grf.sets[0][nom])) { var arreglo = grf.sets[0][nom]; var arreglo2 = []; for (var item of arreglo) { arreglo2.push("\"" + item + "\""); } dominio += "\"radio\":2, \"baseDom\": \"N\", \"dom\": \"Numer\""; setf += "\"fdom\": [" + arreglo2 + "], "; } else { dominio += this.recursivoDom(grf.sets, nom); setf += "\"fdom\":\"function(x)\","; } } dominio += ", "; if (grf.cod == 'R') { dominio += "\"baseCod\": \"R\", \"cod\": \"R\" ,"; setf += "\"fcod\": \"R\""; } else if (grf.cod == 'Z') { dominio += "\"baseCod\": \"Z\", \"cod\": \"Z\" ,"; setf += "\"fcod\": \"Z\""; } else if (grf.cod == 'N') { dominio += "\"baseCod\": \"N\", \"cod\": \"N\" ,"; setf += "\"fcod\": \"N\""; } else { var nom = grf.cod; if (Array.isArray(grf.sets[0][nom])) { var arreglo = grf.sets[0][nom]; var arreglo2 = []; for (var item of arreglo) { arreglo2.push("\"" + item + "\""); } dominio += "\"baseCod\": \"N\", \"cod\": \"Numer\" ,"; setf += '\"fcod\":[' + arreglo2 + ']'; } else { dominio += this.recursivoCod(grf.sets, nom); setf += "\"fcod\": \"function(x)\""; } } return dominio + setf + "}}"; } recursionfuncion = function (func, nombre) { var fun = func[0][nombre].set; var resul = ""; if (fun == 'R' || fun == 'Z' || fun == 'N') { resul += this.generarF(func[0][nombre].cond); } else { resul += this.generarF(func[0][nombre].cond) + " && " + this.recursionfuncion(func, fun); } return resul; } recursivoDom = function (sets, nom) { var domin = ""; if (sets[0][nom].set == 'R') { domin += "\"radio\": 0.3, \"baseDom\": \"R\", \"dom\": \"Func\""; } else if (sets[0][nom].set == 'Z') { domin += "\"radio\": 2, \"baseDom\": \"Z\", \"dom\": \"Func\""; } else if (sets[0][nom].set == 'N') { domin += "\"radio\": 2, \"baseDom\": \"N\", \"dom\": \"Func\""; } else { var nombre = sets[0][nom].set; domin = this.recursivoDom(sets, nombre); } return domin; } recursivoCod = function (sets, nom) { var coodo = ""; if (sets[0][nom].set == 'R') { coodo += "\"baseCod\": \"R\", \"cod\": \"Func\","; } else if (sets[0][nom].set == 'Z') { coodo += "\"baseCod\": \"Z\", \"cod\": \"Func\","; } else if (sets[0][nom].set == 'N') { coodo += "\"baseCod\": \"N\", \"cod\": \"Func\","; } else { var nombre = sets[0][nom].set; coodo += this.recursivoDom(sets, nombre); } return coodo; } generarF = function (exp) { var expresion = ''; if (exp.kind == 'cond') { expresion = ' (' + this.generarF(exp.cond) + '?' + this.generarF(exp.exp1) + ':' + this.generarF(exp.exp2) + ') '; } else if (exp.kind == 'bop') { if (exp.op == '==') { expresion = ' Math.abs((' + this.generarF(exp.exp1) + ') - (' + this.generarF(exp.exp2) + ')) == 0 '; } else if (exp.op == '/=') { expresion = ' Math.abs((' + this.generarF(exp.exp1) + ') - (' + this.generarF(exp.exp2) + ')) == 0 || Math.abs((' + this.generarF(exp.exp1) + ') - (' + this.generarF(exp.exp2) + ')) == 0 '; } else if (exp.op == '^') { expresion = ' Math.pow(' + this.generarF(exp.exp1) + ',' + this.generarF(exp.exp2) + ') '; } else { expresion = ' (' + this.generarF(exp.exp1) + ')' + exp.op + '(' + this.generarF(exp.exp2) + ') '; } } else if (exp.kind == 'uop') { expresion = ' ' + exp.op + ' ' + this.generarF(exp.exp) + ' '; } else if (exp.kind == 'app') { if (exp.fun == 'cos') { exp.fun = 'Math.cos' } else if (exp.fun == 'sin') { exp.fun = 'Math.sin' } else if (exp.fun == 'round') { exp.fun = 'Math.round' } expresion = ' ' + exp.fun + '(' + exp.args.map(e => this.generarF(e)).join() + ') '; } else if (exp.kind == 'tup') { expresion = ' (' + exp.exps.map(e => this.generarF(e)).join() + ') '; } else if (exp.kind == 'lit') { expresion = ' ' + exp.val + ' '; } else if (exp.kind == 'var') { expresion = ' ' + exp.var + ' '; } else { expresion = ' undefined '; } return expresion; } generarFun = function (graph) { var funcionString = ''; var grafica; for (var fun of graph.funs) { funcionString = 'var ' + fun.fun + ' = function(' + fun.args.join() + '){\n return ' + this.generarF(fun.bdy) + '}\n' + funcionString; if (fun.fun == graph.graph) { funcionString += 'return ' + fun.fun + '(' + fun.args.join() + ');\n' grafica = fun; } } funcionString = '(' + grafica.args.join() + ')=>{\n' + funcionString + '}'; return funcionString; } }