real legendlinelength=50;
real legendhskip=1.2;
real legendvskip=legendhskip;
real legendmargin=10;
real legendmaxrelativewidth=1;
// Return a unit polygon with n sides.
path polygon(int n)
{
guide g;
for(int i=0; i < n; ++i) g=g--expi(2pi*(i+0.5)/n-0.5*pi);
return g--cycle;
}
// Return a unit n-point cyclic cross, with optional inner radius r and
// end rounding.
path cross(int n, bool round=true, real r=0)
{
assert(n > 1);
real r=min(r,1);
real theta=pi/n;
real s=sin(theta);
real c=cos(theta);
pair z=(c,s);
transform mirror=reflect(0,z);
pair p1=(r,0);
path elementary;
if(round) {
pair e1=p1+z*max(1-r*(s+c),0);
elementary=p1--e1..(c,s)..mirror*e1--mirror*p1;
} else {
pair p2=p1+z*(max(sqrt(1-(r*s)^2)-r*c),0);
elementary=p1--p2--mirror*p2--mirror*p1;
}
guide g;
real step=360/n;
for(int i=0; i < n; ++i)
g=g--rotate(i*step-90)*elementary;
return g--cycle;
}
path plus=rotate(45)*cross(4);
path diamond=rotate(45)*polygon(4);
using markroutine=void(picture pic=currentpicture, frame f, path g);
// On picture pic, add frame f about every node of path g.
void marknodes(picture pic=currentpicture, frame f, path g) {
for(int i=0; i < size(g); ++i)
add(pic,f,point(g,i));
}
// On picture pic, add n copies of frame f to path g, evenly spaced in
// arclength.
// If rotated=true, the frame will be rotated by the angle of the tangent
// to the path at the points where the frame will be added.
// If centered is true, center the frames within n evenly spaced arclength
// intervals.
markroutine markuniform(bool centered=false, int n, bool rotated=false) {
return new void(picture pic=currentpicture, frame f, path g) {
if(n <= 0) return;
void add(real x) {
real t=reltime(g,x);
add(pic,rotated ? rotate(degrees(dir(g,t)))*f : f,point(g,t));
}
if(centered) {
real width=1/n;
for(int i=0; i < n; ++i) add((i+0.5)*width);
} else {
if(n == 1) add(0.5);
else {
real width=1/(n-1);
for(int i=0; i < n; ++i)
add(i*width);
}
}
};
}
// On picture pic, add frame f at points z(t) for n evenly spaced values of
// t in [a,b].
markroutine markuniform(pair z(real t), real a, real b, int n)
{
return new void(picture pic=currentpicture, frame f, path) {
real width=b-a;
for(int i=0; i <= n; ++i) {
add(pic,f,z(a+i/n*width));
}
};
}
struct marker {
frame f;
bool above=true;
markroutine markroutine=marknodes;
void mark(picture pic=currentpicture, path g) {
markroutine(pic,f,g);
};
}
marker marker(frame f=newframe, markroutine markroutine=marknodes,
bool above=true)
{
marker m=new marker;
m.f=f;
m.above=above;
m.markroutine=markroutine;
return m;
}
marker marker(path[] g, markroutine markroutine=marknodes, pen p=currentpen,
filltype filltype=NoFill, bool above=true)
{
frame f;
filltype.fill(f,g,p);
return marker(f,markroutine,above);
}
// On picture pic, add path g with opacity thinning about every node.
marker markthin(path g, pen p=currentpen,
real thin(real fraction)=new real(real x) {return x^2;},
filltype filltype=NoFill) {
marker M=new marker;
M.above=true;
filltype.fill(M.f,g,p);
real factor=1/abs(size(M.f));
M.markroutine=new void(picture pic=currentpicture, frame, path G) {
transform t=pic.calculateTransform();
int n=size(G);
for(int i=0; i < n; ++i) {
pair z=point(G,i);
frame f;
real fraction=1;
if(i > 0) fraction=min(fraction,abs(t*(z-point(G,i-1)))*factor);
if(i < n-1) fraction=min(fraction,abs(t*(point(G,i+1)-z))*factor);
filltype.fill(f,g,p+opacity(thin(fraction)));
add(pic,f,point(G,i));
}
};
return M;
}
marker nomarker;
real circlescale=0.85;
path[] MarkPath={scale(circlescale)*unitcircle,
polygon(3),polygon(4),polygon(5),invert*polygon(3),
cross(4),cross(6),diamond,plus};
int[] MarkFillable={0,1,2,3,4,7};
marker[] Mark=sequence(new marker(int i) {return marker(MarkPath[i]);},
MarkPath.length);
marker[] MarkFill=sequence(new marker(int i) {
return marker(MarkPath[MarkFillable[i]],Fill);
},MarkFillable.length);
marker Mark(int n)
{
n=n % (Mark.length+MarkFill.length);
if(n < Mark.length) return Mark[n];
else return MarkFill[n-Mark.length];
}
picture legenditem(Legend legenditem, real linelength)
{
picture pic;
pair z1=(0,0);
pair z2=z1+(linelength,0);
if(!legenditem.above && !empty(legenditem.mark))
marknodes(pic,legenditem.mark,interp(z1,z2,0.5));
if(linelength > 0)
Draw(pic,z1--z2,legenditem.p);
if(legenditem.above && !empty(legenditem.mark))
marknodes(pic,legenditem.mark,interp(z1,z2,0.5));
if(legenditem.plabel != invisible)
label(pic,legenditem.label,z2,E,legenditem.plabel);
else
label(pic,legenditem.label,z2,E,currentpen);
return pic;
}
picture legend(Legend[] Legend, int perline=1, real linelength,
real hskip, real vskip, real maxwidth=0, real maxheight=0,
bool hstretch=false, bool vstretch=false)
{
if(maxwidth <= 0) hstretch=false;
if(maxheight <= 0) vstretch=false;
if(Legend.length <= 1) vstretch=hstretch=false;
picture inset;
size(inset,0,0,IgnoreAspect);
if(Legend.length == 0)
return inset;
// Check for legend entries with lines:
bool bLineEntriesAvailable=false;
for(int i=0; i < Legend.length; ++i) {
if(Legend[i].p != invisible) {
bLineEntriesAvailable=true;
break;
}
}
real markersize=0;
for(int i=0; i < Legend.length; ++i)
markersize=max(markersize,size(Legend[i].mark).x);
// If no legend has a line, set the line length to zero
if(!bLineEntriesAvailable)
linelength=0;
linelength=max(linelength,markersize*(linelength == 0 ? 1 : 2));
// Get the maximum dimensions per legend entry;
// calculate line length for a one-line legend
real heightPerEntry=0;
real widthPerEntry=0;
real totalwidth=0;
for(int i=0; i < Legend.length; ++i) {
picture pic=legenditem(Legend[i],linelength);
pair lambda=size(pic);
heightPerEntry=max(heightPerEntry,lambda.y);
widthPerEntry=max(widthPerEntry,lambda.x);
if(Legend[i].p != invisible)
totalwidth += lambda.x;
else {
// Legend entries without leading line need less space in one-line legends
picture pic=legenditem(Legend[i],0);
totalwidth += size(pic).x;
}
}
// Does everything fit into one line?
if(((perline < 1) || (perline >= Legend.length)) &&
(maxwidth >= totalwidth+(totalwidth/Legend.length)*
(Legend.length-1)*(hskip-1))) {
// One-line legend
real currPosX=0;
real itemDistance;
if(hstretch)
itemDistance=(maxwidth-totalwidth)/(Legend.length-1);
else
itemDistance=(totalwidth/Legend.length)*(hskip-1);
for(int i=0; i < Legend.length; ++i) {
picture pic=legenditem(Legend[i],
Legend[i].p == invisible ? 0 : linelength);
add(inset,pic,(currPosX,0));
currPosX += size(pic).x+itemDistance;
}
} else {
// multiline legend
if(maxwidth > 0) {
int maxperline=floor(maxwidth/(widthPerEntry*hskip));
if((perline < 1) || (perline > maxperline))
perline=maxperline;
}
if(perline < 1) // This means: maxwidth < widthPerEntry
perline=1;
if(perline <= 1) hstretch=false;
if(hstretch) hskip=(maxwidth/widthPerEntry-perline)/(perline-1)+1;
if(vstretch) {
int rows=ceil(Legend.length/perline);
vskip=(maxheight/heightPerEntry-rows)/(rows-1)+1;
}
if(hstretch && (perline == 1)) {
Draw(inset,(0,0)--(maxwidth,0),invisible());
for(int i=0; i < Legend.length; ++i)
add(inset,legenditem(Legend[i],linelength),
(0.5*(maxwidth-widthPerEntry),
-quotient(i,perline)*heightPerEntry*vskip));
} else
for(int i=0; i < Legend.length; ++i)
add(inset,legenditem(Legend[i],linelength),
((i%perline)*widthPerEntry*hskip,
-quotient(i,perline)*heightPerEntry*vskip));
}
return inset;
}
frame legend(picture pic=currentpicture, int perline=1,
real xmargin=legendmargin, real ymargin=xmargin,
real linelength=legendlinelength,
real hskip=legendhskip, real vskip=legendvskip,
real maxwidth=perline == 0 ?
legendmaxrelativewidth*size(pic).x : 0, real maxheight=0,
bool hstretch=false, bool vstretch=false, pen p=currentpen)
{
frame F;
if(pic.legend.length == 0) return F;
F=legend(pic.legend,perline,linelength,hskip,vskip,
max(maxwidth-2xmargin,0),
max(maxheight-2ymargin,0),
hstretch,vstretch).fit();
box(F,xmargin,ymargin,p);
return F;
}
pair[] pairs(real[] x, real[] y)
{
if(x.length != y.length) abort("arrays have different lengths");
return sequence(new pair(int i) {return (x[i],y[i]);},x.length);
}
filltype dotfilltype=Fill;
void dot(frame f, pair z, pen p=currentpen, filltype filltype=dotfilltype)
{
if(filltype == Fill)
draw(f,z,dotsize(p)+p);
else {
real s=0.5*(dotsize(p)-linewidth(p));
if(s <= 0) return;
path g=shift(z)*scale(s)*unitcircle;
begingroup(f);
filltype.fill(f,g,p);
draw(f,g,p);
endgroup(f);
}
}
void dot(picture pic=currentpicture, pair z, pen p=currentpen,
filltype filltype=dotfilltype)
{
pic.add(new void(frame f, transform t) {
dot(f,t*z,p,filltype);
},true);
pic.addPoint(z,dotsize(p)+p);
}
void dot(picture pic=currentpicture, Label L, pair z, align align=NoAlign,
string format=defaultformat, pen p=currentpen, filltype filltype=dotfilltype)
{
Label L=L.copy();
L.position(z);
if(L.s == "") {
if(format == "") format=defaultformat;
L.s="("+format(format,z.x)+","+format(format,z.y)+")";
}
L.align(align,E);
L.p(p);
dot(pic,z,p,filltype);
add(pic,L);
}
void dot(picture pic=currentpicture, Label[] L=new Label[], pair[] z,
align align=NoAlign, string format=defaultformat, pen p=currentpen,
filltype filltype=dotfilltype)
{
int stop=min(L.length,z.length);
for(int i=0; i < stop; ++i)
dot(pic,L[i],z[i],align,format,p,filltype);
for(int i=stop; i < z.length; ++i)
dot(pic,z[i],p,filltype);
}
void dot(picture pic=currentpicture, Label[] L=new Label[],
explicit path g, align align=RightSide, string format=defaultformat,
pen p=currentpen, filltype filltype=dotfilltype)
{
int n=size(g);
int stop=min(L.length,n);
for(int i=0; i < stop; ++i)
dot(pic,L[i],point(g,i),-sgn(align.dir.x)*I*dir(g,i),format,p,filltype);
for(int i=stop; i < n; ++i)
dot(pic,point(g,i),p,filltype);
}
void dot(picture pic=currentpicture, path[] g, pen p=currentpen,
filltype filltype=dotfilltype)
{
for(int i=0; i < g.length; ++i)
dot(pic,g[i],p,filltype);
}
void dot(picture pic=currentpicture, Label L, pen p=currentpen,
filltype filltype=dotfilltype)
{
dot(pic,L,L.position,p,filltype);
}
// A dot in a frame.
frame dotframe(pen p=currentpen, filltype filltype=dotfilltype)
{
frame f;
dot(f,(0,0),p,filltype);
return f;
}
frame dotframe=dotframe();
marker dot(pen p=currentpen, filltype filltype=dotfilltype)
{
return marker(dotframe(p,filltype));
}
marker dot=dot();