Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draw directional arrows on lines and circles #73

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
39 changes: 28 additions & 11 deletions ganja.js
Original file line number Diff line number Diff line change
Expand Up @@ -624,19 +624,20 @@
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.
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(`<SVG onmousedown="if(evt.target==this)this.sel=undefined" viewBox="-2 -${2*(hh/ww||1)} 4 ${4*(hh/ww||1)}" style="width:${ww||512}px; height:${hh||512}px; background-color:#eee; -webkit-user-select:none; -moz-user-select:none; -ms-user-select:none; user-select:none">
// Add a grid (option)
${options.grid?(()=>{
var n = Math.floor(10 / options.scale);
return n>50?'':[...Array(2*n + 1)].map((x,xi)=>`<line x1="-10" y1="${((xi-n)/2-(tot<4?2*options.camera.e02:0))*options.scale}" x2="10" y2="${((xi-n)/2-(tot<4?2*options.camera.e02:0))*options.scale}" stroke-width="0.005" stroke="#CCC"/><line y1="-10" x1="${((xi-n)/2-(tot<4?2*options.camera.e01:0))*options.scale}" y2="10" x2="${((xi-n)/2-(tot<4?2*options.camera.e01:0))*options.scale}" stroke-width="0.005" stroke="#CCC"/>`);
${// Add a grid (option)
options.grid?(()=>{
var n = Math.floor(10 / options.scale);
return n>50?'':[...Array(2*n + 1)].map((x,xi)=>`<line x1="-10" y1="${((xi-n)/2-(tot<4?2*options.camera.e02:0))*options.scale}" x2="10" y2="${((xi-n)/2-(tot<4?2*options.camera.e02:0))*options.scale}" stroke-width="0.005" stroke="#CCC"/><line y1="-10" x1="${((xi-n)/2-(tot<4?2*options.camera.e01:0))*options.scale}" y2="10" x2="${((xi-n)/2-(tot<4?2*options.camera.e01:0))*options.scale}" stroke-width="0.005" stroke="#CCC"/>`).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.
Expand All @@ -658,6 +659,15 @@
if (typeof o =='string') { var res2=(o[0]=='_')?'':`<text x="${lx}" y="${ly}" font-family="Verdana" font-size="${options.fontSize*0.1||0.1}" style="pointer-events:none" fill="${color||'#333'}" transform="rotate(${lr},${lx},${ly})">&nbsp;${o}&nbsp;</text>`; 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 = (color) => {
if (marker_defs[color]) return ''; marker_defs[color]=true;
return `<defs>
<marker id="marker-${color}" orient="auto" markerWidth="10" markerHeight="12" refX="9" refY="6">
<path d="M 1 1 L 9 6 L 1 11" stroke-width="1" stroke="${color||'#888'}" stroke-linecap="round" fill="none"/></marker>
</defs>`;
};

// All other elements are rendered ..
var ni_part = o.Dot(no.Scale(-1)); // O_i + n_o O_oi
var no_part = ni.Scale(-1).Dot(o); // O_o + O_oi n_i
Expand Down Expand Up @@ -686,17 +696,24 @@
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=`<CIRCLE onmousedown="this.parentElement.sel=${oidx}" cx="${lx}" cy="${ly}" r="${pointRadius*0.03}" fill="${color||'green'}"/>`;
lx=sc*(o.e1); ly=sc*(-o.e2); lr=0;
return `<CIRCLE onmousedown="this.parentElement.sel=${oidx}" cx="${lx}" cy="${ly}" r="${pointRadius*0.03}" fill="${color||'green'}"/>`;
} else if (is_flat && !b0 && b1 && !b2) {
// Lines.
var loc=minus_no.LDot(o).Div(o), att=ni.Dot(o);
lx=sc*(-loc.e1); ly=sc*(loc.e2); lr=Math.atan2(-o[14],o[13])/Math.PI*180; return `<LINE style="pointer-events:none" x1=${lx-10} y1=${ly} x2=${lx+10} y2=${ly} stroke-width="${lineWidth*0.005}" stroke="${color||'#888'}" transform="rotate(${lr},${lx},${ly})"/>`;
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')}<path style="pointer-events:none" d="M ${[...Array(21)].map((_, i) => i - 10).map(xoff => `${lx+xoff} ${ly}`).join(' L ')}" stroke-width="${lineWidth*0.005}" stroke="${color||'#888'}" transform="rotate(${lr},${lx},${ly})" marker-mid="url(#marker-${color||'#888'})" fill="none"/>`;
} else if (!is_flat && !b0 && !b1 && b2) {
// Circles
var loc=o.Div(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 `<CIRCLE onmousedown="this.parentElement.sel=${oidx}" cx="${lx}" cy="${ly}" r="${r}" stroke-width="${lineWidth*0.005}" fill="none" stroke="${color||'green'}" stroke-dasharray="${dash_for_r2(r2, r, lineWidth*0.020)}"/>`;
// draw the markers separately, to avoid a chrome rendering bug with <path> (gh-73)
return `<CIRCLE onmousedown="this.parentElement.sel=${oidx}" cx="${lx}" cy="${ly}" r="${r}" stroke-width="${lineWidth*0.005}" fill="none" stroke="${color||'green'}" stroke-dasharray="${dash_for_r2(r2, r, lineWidth*0.020)}"/>
${make_arrow_marker_def(color||'#888')}<path d="
M ${lx - r} ${ly}
a ${r} ${r} 0 0 ${+(direction.e12 < 0)} ${2*r} 0
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=ni,eo=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)));
Expand All @@ -715,7 +732,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?`<POLYGON STYLE="pointer-events:none; fill:${color};opacity:0.7" points="${o.map(o=>((drm[1]==6||drm[1]==14)?-1:1)*options.scale*o[drm[2]]/o[drm[1]]+','+options.scale*o[drm[3]]/o[drm[1]]+' ')}"/>`:`<LINE style="pointer-events:none" x1=${options.scale*((drm[1]==6||drm[1]==14)?-1:1)*o[0][drm[2]]/o[0][drm[1]]} y1=${options.scale*o[0][drm[3]]/o[0][drm[1]]} x2=${options.scale*((drm[1]==6||drm[1]==14)?-1:1)*o[1][drm[2]]/o[1][drm[1]]} y2=${options.scale*o[1][drm[3]]/o[1][drm[1]]} stroke-width="${options.lineWidth*0.005||0.005}" stroke="${color||'#888'}"/>`; }
// svg
Expand All @@ -730,7 +747,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=`<LINE style="pointer-events:none" x1=-10 y1=${ly} x2=10 y2=${ly} stroke-width="${options.lineWidth*0.005||0.005}" stroke="${color||'#888'}" transform="rotate(${lr},0,0)"/>`; 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=`<LINE style="pointer-events:none" x1=${lx} y1=${ly} x2=${lx-o.e02} y2=${ly+o.e01} stroke-width="0.005" stroke="${color||'#888'}"/>`; 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);
};
Expand Down