From a723ef67251e21271c1f4d9cc3fd81a99ac3f77c Mon Sep 17 00:00:00 2001 From: Eric Wieser Date: Thu, 30 Jan 2020 16:03:05 +0000 Subject: [PATCH 1/5] Draw directional arrows on lines and circles This is quite slow, so probably needs some thought --- ganja.js | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/ganja.js b/ganja.js index 8cb18bf..869296a 100644 --- a/ganja.js +++ b/ganja.js @@ -645,6 +645,13 @@ if (typeof o =='string') { var res2=(o[0]=='_')?'':` ${o} `; ly+=0.14; return res2; } // Numbers change the current color. if (typeof o =='number') { color='#'+(o+(1<<25)).toString(16).slice(-6); return ''; }; + + var make_arrow_marker_def = (oidx) => ` + + + + `; + // All other elements are rendered .. var einf_part = o.Dot(cga2d_no.Scale(-1)); // O_i + n_o O_oi var eo_part = cga2d_ni.Scale(-1).Dot(o); // O_o + O_oi n_i @@ -673,17 +680,22 @@ if (!is_flat && b0 && !b1 && !b2) { // Points if (direction.s < 0) { o = Element.Sub(o); } - lx=sc*(o.e1); ly=sc*(-o.e2); lr=0; return res2=``; + lx=sc*(o.e1); ly=sc*(-o.e2); lr=0; + return ``; } else if (is_flat && !b0 && b1 && !b2) { // Lines. var loc=cga2d_nno.LDot(o).Div(o), att=cga2d_ni.Dot(o); - lx=sc*(-loc.e1); ly=sc*(loc.e2); lr=Math.atan2(-o[14],o[13])/Math.PI*180; return ``; + lx=sc*(-loc.e1); ly=sc*(loc.e2); lr=Math.atan2(-o[14],o[13])/Math.PI*180; + return `${make_arrow_marker_def(oidx)}`; } else if (!is_flat && !b0 && !b1 && b2) { // Circles var loc=o.Div(cga2d_ni.LDot(o)); lx=sc*(-loc.e1); ly=sc*(loc.e2); var r2=o.Mul(o.Conjugate).s; var r = Math.sqrt(Math.abs(r2))*sc; - return ``; + return `${make_arrow_marker_def(oidx)}`; } else if (!is_flat && !b0 && b1 && !b2) { // Point Pairs. lr=0; var ei=cga2d_ni,eo=cga2d_no, nix=o.Wedge(ei), sqr=o.LDot(o).s/nix.LDot(nix).s, r=Math.sqrt(Math.abs(sqr)), attitude=((ei.Wedge(eo)).LDot(nix)).Normalized.Mul(Element.Scalar(r)), pos=o.Div(nix); pos=pos.Div( pos.LDot(Element.Sub(ei))); From 7d6b429d0f8ba6ac64f2cd17fc227942c792bff7 Mon Sep 17 00:00:00 2001 From: enkimute Date: Fri, 31 Jan 2020 14:31:34 +0100 Subject: [PATCH 2/5] changed markers from ID to color, as we only need one def per color --- ganja.js | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/ganja.js b/ganja.js index 869296a..e9b0727 100644 --- a/ganja.js +++ b/ganja.js @@ -595,7 +595,7 @@ // An optional second parameter is an options object { width, height, animate, camera, scale, grid, canvas } static graph(f,options) { // Store the original input - if (!f) return; var origf=f; + if (!f) return; var origf=f,marker_defs={}; // generate default options. options=options||{}; options.scale=options.scale||1; options.camera=options.camera||(tot<4?Element.Scalar(1):new Element([0.7071067690849304, 0, 0, 0, 0, 0, 0, 0, 0, 0.7071067690849304, 0, 0, 0, 0, 0, 0])); if (options.conformal && tot==4) var cga2d_ni = options.ni||this.Coeff(4,1,3,1), cga2d_no = options.no||this.Coeff(4,0.5,3,-0.5), cga2d_nno = cga2d_no.Scale(-1); @@ -618,13 +618,13 @@ lx=-2;ly=-1.85;lr=0;color='#444'; // Create the svg element. (master template string till end of function) var svg=new DOMParser().parseFromString(` - // Add a grid (option) - ${options.grid?(()=>{ - var n = Math.floor(10 / options.scale); - return n>50?'':[...Array(2*n + 1)].map((x,xi)=>``); + ${// Add a grid (option) + options.grid?(()=>{ + var n = Math.floor(10 / options.scale); + return n>50?'':[...Array(2*n + 1)].map((x,xi)=>``).join(''); })():''} - // Handle conformal 2D elements. - ${options.conformal?f.map&&f.map((o,oidx)=>{ + ${// Handle conformal 2D elements. + options.conformal?f.map&&f.map((o,oidx)=>{ // Optional animation handling. if((o==Element.graph && or!==false)||(oidx==0&&options.animate&&or!==false)) { anim=true; requestAnimationFrame(()=>{var r=build(origf,(!res)||(document.body.contains(res))).innerHTML; if (res) res.innerHTML=r; }); if (!options.animate) return; } // Resolve expressions passed in. @@ -646,11 +646,12 @@ // Numbers change the current color. if (typeof o =='number') { color='#'+(o+(1<<25)).toString(16).slice(-6); return ''; }; - var make_arrow_marker_def = (oidx) => ` - - - - `; + var make_arrow_marker_def = (color) => { + if (marker_defs[color]) return ''; marker_defs[color]=true; + return ` + + `; + }; // All other elements are rendered .. var einf_part = o.Dot(cga2d_no.Scale(-1)); // O_i + n_o O_oi @@ -686,16 +687,16 @@ // Lines. var loc=cga2d_nno.LDot(o).Div(o), att=cga2d_ni.Dot(o); lx=sc*(-loc.e1); ly=sc*(loc.e2); lr=Math.atan2(-o[14],o[13])/Math.PI*180; - return `${make_arrow_marker_def(oidx)}`; + return `${make_arrow_marker_def(color||'#888')}`; } else if (!is_flat && !b0 && !b1 && b2) { // Circles var loc=o.Div(cga2d_ni.LDot(o)); lx=sc*(-loc.e1); ly=sc*(loc.e2); var r2=o.Mul(o.Conjugate).s; var r = Math.sqrt(Math.abs(r2))*sc; - return `${make_arrow_marker_def(oidx)}`; + a ${r} ${r} 0 0 ${+(direction.e12 < 0)} ${-2*r} 0" marker-mid="url(#marker-${color||'#888'})" marker-end="url(#marker-${color||'#888'})" stroke-width="${lineWidth*0.005}" fill="none" stroke="${color||'green'}" stroke-dasharray="${dash_for_r2(r2, r, lineWidth*0.020)}"/>`; } else if (!is_flat && !b0 && b1 && !b2) { // Point Pairs. lr=0; var ei=cga2d_ni,eo=cga2d_no, nix=o.Wedge(ei), sqr=o.LDot(o).s/nix.LDot(nix).s, r=Math.sqrt(Math.abs(sqr)), attitude=((ei.Wedge(eo)).LDot(nix)).Normalized.Mul(Element.Scalar(r)), pos=o.Div(nix); pos=pos.Div( pos.LDot(Element.Sub(ei))); @@ -714,7 +715,7 @@ return ""; } // Handle projective 2D and 3D elements. - }):f.map&&f.map((o,oidx)=>{ if((o==Element.graph && or!==false)||(oidx==0&&options.animate&&or!==false)) { anim=true; requestAnimationFrame(()=>{var r=build(origf,(!res)||(document.body.contains(res))).innerHTML; if (res) res.innerHTML=r; }); if (!options.animate) return; } while (o instanceof Function) o=o(); o=(o instanceof Array)?o.map(project):project(o); if (o===undefined) return; + }).join(''):f.map&&f.map((o,oidx)=>{ if((o==Element.graph && or!==false)||(oidx==0&&options.animate&&or!==false)) { anim=true; requestAnimationFrame(()=>{var r=build(origf,(!res)||(document.body.contains(res))).innerHTML; if (res) res.innerHTML=r; }); if (!options.animate) return; } while (o instanceof Function) o=o(); o=(o instanceof Array)?o.map(project):project(o); if (o===undefined) return; // line segments and polygons if (o instanceof Array && o.length) { lx=ly=lr=0; o.forEach((o)=>{while (o.call) o=o(); lx+=options.scale*((drm[1]==6||drm[1]==14)?-1:1)*o[drm[2]]/o[drm[1]];ly+=options.scale*o[drm[3]]/o[drm[1]]});lx/=o.length;ly/=o.length; return o.length>2?``:``; } // svg @@ -729,7 +730,7 @@ if (o[to2d[2]]**2+o[to2d[3]]**2>0.0001) { var l=Math.sqrt(o[to2d[2]]**2+o[to2d[3]]**2); o[to2d[2]]/=l; o[to2d[3]]/=l; o[to2d[1]]/=l; lx=0.5; ly=options.scale*((drm[1]==6)?-1:-1)*o[to2d[1]]; lr=-Math.atan2(o[to2d[2]],o[to2d[3]])/Math.PI*180; var res2=``; ly-=0.05; return res2; } // Vectors if (o[to2d[4]]**2+o[to2d[5]]**2>0.0001) { lr=0; ly+=0.05; lx+=0.1; var res2=``; ly=ly+o.e01/4*3-0.05; lx=lx-o.e02/4*3; return res2; } - }).join()}`,'text/html').body; + }).join('')}`,'text/html').body; // return the inside of the created svg element. return svg.removeChild(svg.firstChild); }; From 6866d54156fffbc85b00b54a8142e748665bfa50 Mon Sep 17 00:00:00 2001 From: Eric Wieser Date: Wed, 1 Apr 2020 17:15:47 +0100 Subject: [PATCH 3/5] Draw circles with rather than to avoid rendering glitches --- ganja.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/ganja.js b/ganja.js index e9b0727..aa13d36 100644 --- a/ganja.js +++ b/ganja.js @@ -649,7 +649,7 @@ var make_arrow_marker_def = (color) => { if (marker_defs[color]) return ''; marker_defs[color]=true; return ` - + `; }; @@ -693,10 +693,12 @@ var loc=o.Div(cga2d_ni.LDot(o)); lx=sc*(-loc.e1); ly=sc*(loc.e2); var r2=o.Mul(o.Conjugate).s; var r = Math.sqrt(Math.abs(r2))*sc; - return `${make_arrow_marker_def(color||'#888')} + ${make_arrow_marker_def(color||'#888')}`; + a ${r} ${r} 0 0 ${+(direction.e12 < 0)} ${-2*r} 0" marker-mid="url(#marker-${color||'#888'})" marker-end="url(#marker-${color||'#888'})" fill="none" stroke="none" stroke-width="${lineWidth*0.005}"/>`; } else if (!is_flat && !b0 && b1 && !b2) { // Point Pairs. lr=0; var ei=cga2d_ni,eo=cga2d_no, nix=o.Wedge(ei), sqr=o.LDot(o).s/nix.LDot(nix).s, r=Math.sqrt(Math.abs(sqr)), attitude=((ei.Wedge(eo)).LDot(nix)).Normalized.Mul(Element.Scalar(r)), pos=o.Div(nix); pos=pos.Div( pos.LDot(Element.Sub(ei))); From 00eae879d93b7277f51d7fafe090ca215d26cf4d Mon Sep 17 00:00:00 2001 From: Eric Wieser Date: Wed, 1 Apr 2020 17:33:54 +0100 Subject: [PATCH 4/5] Improve line arrows, and fix marker symmetry --- ganja.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ganja.js b/ganja.js index aa13d36..d605a42 100644 --- a/ganja.js +++ b/ganja.js @@ -649,7 +649,8 @@ var make_arrow_marker_def = (color) => { if (marker_defs[color]) return ''; marker_defs[color]=true; return ` - + + `; }; @@ -687,7 +688,7 @@ // Lines. var loc=cga2d_nno.LDot(o).Div(o), att=cga2d_ni.Dot(o); lx=sc*(-loc.e1); ly=sc*(loc.e2); lr=Math.atan2(-o[14],o[13])/Math.PI*180; - return `${make_arrow_marker_def(color||'#888')}`; + return `${make_arrow_marker_def(color||'#888')}`; } else if (!is_flat && !b0 && !b1 && b2) { // Circles var loc=o.Div(cga2d_ni.LDot(o)); lx=sc*(-loc.e1); ly=sc*(loc.e2); From f0181087f60cbb4e28fa5e8204ba580554dfeee6 Mon Sep 17 00:00:00 2001 From: Eric Wieser Date: Tue, 28 Jul 2020 16:55:12 +0100 Subject: [PATCH 5/5] Fix caching to work for more than just the first frame --- ganja.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ganja.js b/ganja.js index b876760..5849199 100644 --- a/ganja.js +++ b/ganja.js @@ -607,7 +607,7 @@ // An optional second parameter is an options object { width, height, animate, camera, scale, grid, canvas } static graph(f,options) { // Store the original input - if (!f) return; var origf=f,marker_defs={}; + if (!f) return; var origf=f; // generate default options. options=options||{}; options.scale=options.scale||1; options.camera=options.camera||(tot<4?Element.Scalar(1):new Element([0.7071067690849304, 0, 0, 0, 0, 0, 0, 0, 0, 0.7071067690849304, 0, 0, 0, 0, 0, 0])); if (options.conformal && tot==4) var ni = options.ni||this.Coeff(4,1,3,1), no = options.no||this.Coeff(4,0.5,3,-0.5), minus_no = no.Scale(-1); @@ -624,6 +624,7 @@ if (f instanceof Function) f=f(); if (!(f instanceof Array)) f=[].concat.apply([],Object.keys(f).map((k)=>typeof f[k]=='number'?[f[k]]:[f[k],k])); // The build function generates the actual SVG. It will be called everytime the user interacts or the anim flag is set. function build(f,or) { + var marker_defs={}; // Make sure we have an aray. if (or && f && f instanceof Function) f=f(); // Reset position and color for cursor.