
| Current Path : /proc/thread-self/root/usr/share/asymptote/ |
Linux ift1.ift-informatik.de 5.4.0-216-generic #236-Ubuntu SMP Fri Apr 11 19:53:21 UTC 2025 x86_64 |
| Current File : //proc/thread-self/root/usr/share/asymptote/graph.asy |
private import math;
import graph_splinetype;
import graph_settings;
scaleT Linear;
scaleT Log=scaleT(log10,pow10,logarithmic=true);
scaleT Logarithmic=Log;
string baselinetemplate="$10^4$";
// A linear scale, with optional autoscaling of minimum and maximum values,
// scaling factor s and intercept.
scaleT Linear(bool automin=false, bool automax=automin, real s=1,
real intercept=0)
{
real sinv=1/s;
scalefcn T,Tinv;
if(s == 1 && intercept == 0)
T=Tinv=identity;
else {
T=new real(real x) {return (x-intercept)*s;};
Tinv=new real(real x) {return x*sinv+intercept;};
}
return scaleT(T,Tinv,logarithmic=false,automin,automax);
}
// A logarithmic scale, with optional autoscaling of minimum and maximum
// values.
scaleT Log(bool automin=false, bool automax=automin)
{
return scaleT(Log.T,Log.Tinv,logarithmic=true,automin,automax);
}
// A "broken" linear axis omitting the segment [a,b].
scaleT Broken(real a, real b, bool automin=false, bool automax=automin)
{
real skip=b-a;
real T(real x) {
if(x <= a) return x;
if(x <= b) return a;
return x-skip;
}
real Tinv(real x) {
if(x <= a) return x;
return x+skip;
}
return scaleT(T,Tinv,logarithmic=false,automin,automax);
}
// A "broken" logarithmic axis omitting the segment [a,b], where a and b are
// automatically rounded to the nearest integral power of the base.
scaleT BrokenLog(real a, real b, bool automin=false, bool automax=automin)
{
real A=round(Log.T(a));
real B=round(Log.T(b));
a=Log.Tinv(A);
b=Log.Tinv(B);
real skip=B-A;
real T(real x) {
if(x <= a) return Log.T(x);
if(x <= b) return A;
return Log.T(x)-skip;
}
real Tinv(real x) {
real X=Log.Tinv(x);
if(X <= a) return X;
return Log.Tinv(x+skip);
}
return scaleT(T,Tinv,logarithmic=true,automin,automax);
}
Label Break=Label("$\approx$",UnFill(0.2mm));
void scale(picture pic=currentpicture, scaleT x, scaleT y=x, scaleT z=y)
{
pic.scale.x.scale=x;
pic.scale.y.scale=y;
pic.scale.z.scale=z;
pic.scale.x.automin=x.automin;
pic.scale.y.automin=y.automin;
pic.scale.z.automin=z.automin;
pic.scale.x.automax=x.automax;
pic.scale.y.automax=y.automax;
pic.scale.z.automax=z.automax;
}
void scale(picture pic=currentpicture, bool xautoscale=false,
bool yautoscale=xautoscale, bool zautoscale=yautoscale)
{
scale(pic,Linear(xautoscale,xautoscale),Linear(yautoscale,yautoscale),
Linear(zautoscale,zautoscale));
}
struct scientific
{
int sign;
real mantissa;
int exponent;
int ceil() {return sign*ceil(mantissa);}
real scale(real x, real exp) {
static real max=0.1*realMax;
static real limit=-log10(max);
return x*(exp > limit ? 10^-exp : max);
}
real ceil(real x, real exp) {return ceil(sign*scale(abs(x),exp));}
real floor(real x, real exp) {return floor(sign*scale(abs(x),exp));}
}
// Convert x to scientific notation
scientific scientific(real x)
{
scientific s;
s.sign=sgn(x);
x=abs(x);
if(x == 0) {s.mantissa=0; s.exponent=-intMax; return s;}
real logx=log10(x);
s.exponent=floor(logx);
s.mantissa=s.scale(x,s.exponent);
return s;
}
// Autoscale limits and tick divisor.
struct bounds {
real min;
real max;
// Possible tick intervals:
int[] divisor;
void operator init(real min, real max, int[] divisor=new int[]) {
this.min=min;
this.max=max;
this.divisor=divisor;
}
}
// Compute tick divisors.
int[] divisors(int a, int b)
{
int[] dlist;
int n=b-a;
dlist[0]=1;
if(n == 1) {dlist[1]=10; dlist[2]=100; return dlist;}
if(n == 2) {dlist[1]=2; return dlist;}
int sqrtn=floor(sqrt(n));
int i=0;
for(int d=2; d <= sqrtn; ++d)
if(n % d == 0 && (a*b >= 0 || b % (n/d) == 0)) dlist[++i]=d;
for(int d=sqrtn; d >= 1; --d)
if(n % d == 0 && (a*b >= 0 || b % d == 0)) dlist[++i]=quotient(n,d);
return dlist;
}
real upscale(real b, real a)
{
if(b <= 5) b=5;
else if (b > 10 && a >= 0 && b <= 12) b=12;
else if (b > 10 && (a >= 0 || 15 % -a == 0) && b <= 15) b=15;
else b=ceil(b/10)*10;
return b;
}
// Compute autoscale limits and tick divisor.
bounds autoscale(real Min, real Max, scaleT scale=Linear)
{
bounds m;
if(scale.logarithmic) {
m.min=floor(Min);
m.max=ceil(Max);
return m;
}
if(!(finite(Min) && finite(Max)))
abort("autoscale requires finite limits");
Min=scale.Tinv(Min);
Max=scale.Tinv(Max);
m.min=Min;
m.max=Max;
if(Min > Max) {real temp=Min; Min=Max; Max=temp;}
if(Min == Max) {
if(Min == 0) {m.max=1; return m;}
if(Min > 0) {Min=0; Max *= 2;}
else {Min *= 2; Max=0;}
}
int sign;
if(Min < 0 && Max <= 0) {real temp=-Min; Min=-Max; Max=temp; sign=-1;}
else sign=1;
scientific sa=scientific(Min);
scientific sb=scientific(Max);
int exp=max(sa.exponent,sb.exponent);
real a=sa.floor(Min,exp);
real b=sb.ceil(Max,exp);
void zoom() {
--exp;
a=sa.floor(Min,exp);
b=sb.ceil(Max,exp);
}
if(sb.mantissa <= 1.5)
zoom();
while((b-a)*10.0^exp > 10*(Max-Min))
zoom();
real bsave=b;
if(b-a > (a >= 0 ? 8 : 6)) {
b=upscale(b,a);
if(a >= 0) {
if(a <= 5) a=0; else a=floor(a/10)*10;
} else a=-upscale(-a,-1);
}
// Redo b in case the value of a has changed
if(bsave-a > (a >= 0 ? 8 : 6))
b=upscale(bsave,a);
if(sign == -1) {real temp=-a; a=-b; b=temp;}
real Scale=10.0^exp;
m.min=scale.T(a*Scale);
m.max=scale.T(b*Scale);
if(m.min > m.max) {real temp=m.min; m.min=m.max; m.max=temp;}
m.divisor=divisors(round(a),round(b));
return m;
}
typedef string ticklabel(real);
ticklabel Format(string s=defaultformat)
{
return new string(real x) {return format(s,x);};
}
ticklabel OmitFormat(string s=defaultformat ... real[] x)
{
return new string(real v) {
string V=format(s,v);
for(real a : x)
if(format(s,a) == V) return "";
return V;
};
}
string trailingzero="$%#$";
string signedtrailingzero="$%+#$";
ticklabel DefaultFormat=Format();
ticklabel NoZeroFormat=OmitFormat(0);
// Format tick values as integral powers of base; otherwise with DefaultFormat.
ticklabel DefaultLogFormat(int base) {
return new string(real x) {
string exponent=format("%.4f",log(x)/log(base));
return find(exponent,".") == -1 ?
"$"+(string) base+"^{"+exponent+"}$" : format(x);
};
}
// Format all tick values as powers of base.
ticklabel LogFormat(int base) {
return new string(real x) {
return format("$"+(string) base+"^{%g}$",log(x)/log(base));
};
}
ticklabel LogFormat=LogFormat(10);
ticklabel DefaultLogFormat=DefaultLogFormat(10);
// The default direction specifier.
pair zero(real) {return 0;}
struct ticklocate {
real a,b; // Tick values at point(g,0), point(g,length(g)).
autoscaleT S; // Autoscaling transformation.
pair dir(real t); // Absolute 2D tick direction.
triple dir3(real t); // Absolute 3D tick direction.
real time(real v); // Returns the time corresponding to the value v.
ticklocate copy() {
ticklocate T=new ticklocate;
T.a=a;
T.b=b;
T.S=S.copy();
T.dir=dir;
T.dir3=dir3;
T.time=time;
return T;
}
}
autoscaleT defaultS;
typedef real valuetime(real);
valuetime linear(scalefcn S=identity, real Min, real Max)
{
real factor=Max == Min ? 0.0 : 1.0/(Max-Min);
return new real(real v) {return (S(v)-Min)*factor;};
}
ticklocate ticklocate(real a, real b, autoscaleT S=defaultS,
real tickmin=-infinity, real tickmax=infinity,
real time(real)=null, pair dir(real)=zero)
{
if((valuetime) time == null) time=linear(S.T(),a,b);
ticklocate locate;
locate.a=a;
locate.b=b;
locate.S=S.copy();
if(finite(tickmin)) locate.S.tickMin=tickmin;
if(finite(tickmax)) locate.S.tickMax=tickmax;
locate.time=time;
locate.dir=dir;
return locate;
}
private struct locateT {
real t; // tick location time
pair Z; // tick location in frame coordinates
pair pathdir; // path direction in frame coordinates
pair dir; // tick direction in frame coordinates
void dir(transform T, path g, ticklocate locate, real t) {
pathdir=unit(shiftless(T)*dir(g,t));
pair Dir=locate.dir(t);
dir=Dir == 0 ? -I*pathdir : unit(Dir);
}
// Locate the desired position of a tick along a path.
void calc(transform T, path g, ticklocate locate, real val) {
t=locate.time(val);
Z=T*point(g,t);
dir(T,g,locate,t);
}
}
pair ticklabelshift(pair align, pen p=currentpen)
{
return 0.25*unit(align)*labelmargin(p);
}
void drawtick(frame f, transform T, path g, path g2, ticklocate locate,
real val, real Size, int sign, pen p, bool extend)
{
locateT locate1,locate2;
locate1.calc(T,g,locate,val);
if(extend && size(g2) > 0) {
locate2.calc(T,g2,locate,val);
draw(f,locate1.Z--locate2.Z,p);
} else
if(sign == 0)
draw(f,locate1.Z-Size*locate1.dir--locate1.Z+Size*locate1.dir,p);
else
draw(f,locate1.Z--locate1.Z+Size*sign*locate1.dir,p);
}
real zerotickfuzz=10*epsilon;
// Label a tick on a frame.
pair labeltick(frame d, transform T, path g, ticklocate locate, real val,
pair side, int sign, real Size, ticklabel ticklabel,
Label F, real norm=0)
{
locateT locate1;
locate1.calc(T,g,locate,val);
pair align=side*locate1.dir;
pair perp=I*locate1.pathdir;
// Adjust tick label alignment
pair adjust=unit(align+0.75perp*sgn(dot(align,perp)));
// Project align onto adjusted direction.
align=adjust*dot(align,adjust);
pair shift=dot(align,-sign*locate1.dir) <= 0 ? align*Size :
ticklabelshift(align,F.p);
real label;
if(locate.S.scale.logarithmic)
label=locate.S.scale.Tinv(val);
else {
label=val;
if(abs(label) < zerotickfuzz*norm) label=0;
// Fix epsilon errors at +/-1e-4
// default format changes to scientific notation here
if(abs(abs(label)-1e-4) < epsilon) label=sgn(label)*1e-4;
}
string s=ticklabel(label);
if(s != "")
label(d,F.T*baseline(s,baselinetemplate),locate1.Z+shift,align,F.p,
F.filltype);
return locate1.pathdir;
}
// Add axis label L to frame f.
void labelaxis(frame f, transform T, Label L, path g,
ticklocate locate=null, int sign=1, bool ticklabels=false)
{
Label L0=L.copy();
real t=L0.relative(g);
pair z=point(g,t);
pair dir=dir(g,t);
pair perp=I*dir;
if(locate != null) {
locateT locate1;
locate1.dir(T,g,locate,t);
L0.align(L0.align,unit(-sgn(dot(sign*locate1.dir,perp))*perp));
}
pair align=L0.align.dir;
if(L0.align.relative) align *= -perp;
pair alignperp=dot(align,perp)*perp;
pair offset;
if(ticklabels) {
if(piecewisestraight(g)) {
real angle=degrees(dir,warn=false);
transform S=rotate(-angle,z);
frame F=S*f;
pair Align=rotate(-angle)*alignperp;
offset=unit(alignperp-sign*locate.dir(t))*
abs((Align.y >= 0 ? max(F).y : (Align.y < 0 ? min(F).y : 0))-z.y);
}
z += offset;
}
L0.align(align);
L0.position(z);
frame d;
add(d,L0);
pair width=0.5*size(d);
int n=length(g);
real t=L.relative();
pair s=realmult(width,dir(g,t));
if(t <= 0) {
if(L.align.default) s *= -axislabelfactor;
d=shift(s)*d;
} else if(t >= n) {
if(L.align.default) s *= -axislabelfactor;
d=shift(-s)*d;
} else if(offset == 0 && L.align.default) {
pair s=realmult(width,I*dir(g,t));
s=axislabelfactor*s;
d=shift(s)*d;
}
add(f,d);
}
// Check the tick coverage of a linear axis.
bool axiscoverage(int N, transform T, path g, ticklocate locate, real Step,
pair side, int sign, real Size, Label F, ticklabel ticklabel,
real norm, real limit)
{
real coverage=0;
bool loop=cyclic(g);
real a=locate.S.Tinv(locate.a);
real b=locate.S.Tinv(locate.b);
real tickmin=finite(locate.S.tickMin) ? locate.S.Tinv(locate.S.tickMin) : a;
if(Size > 0) {
int count=0;
if(loop) count=N+1;
else {
for(int i=0; i <= N; ++i) {
real val=tickmin+i*Step;
if(val >= a && val <= b)
++count;
}
}
if(count > 0) limit /= count;
for(int i=0; i <= N; ++i) {
real val=tickmin+i*Step;
if(loop || (val >= a && val <= b)) {
frame d;
pair dir=labeltick(d,T,g,locate,val,side,sign,Size,ticklabel,F,norm);
if(abs(dot(size(d),dir)) > limit) return false;
}
}
}
return true;
}
// Check the tick coverage of a logarithmic axis.
bool logaxiscoverage(int N, transform T, path g, ticklocate locate, pair side,
int sign, real Size, Label F, ticklabel ticklabel,
real limit, int first, int last)
{
bool loop=cyclic(g);
real coverage=0;
real a=locate.a;
real b=locate.b;
int count=0;
for(int i=first-1; i <= last+1; i += N) {
if(loop || i >= a && i <= b)
++count;
}
if(count > 0) limit /= count;
for(int i=first-1; i <= last+1; i += N) {
if(loop || i >= a && i <= b) {
frame d;
pair dir=labeltick(d,T,g,locate,i,side,sign,Size,ticklabel,F);
if(abs(dot(size(d),dir)) > limit) return false;
}
}
return true;
}
struct tickvalues {
real[] major;
real[] minor;
int N; // For logarithmic axes: number of decades between tick labels.
}
// Determine a format that distinguishes adjacent pairs of ticks, optionally
// adding trailing zeros.
string autoformat(string format="", real norm ... real[] a)
{
bool trailingzero=(format == trailingzero);
bool signedtrailingzero=(format == signedtrailingzero);
if(!trailingzero && !signedtrailingzero && format != "") return format;
real[] A=sort(a);
real[] a=abs(A);
bool signchange=(A.length > 1 && A[0] < 0 && A[A.length-1] >= 0);
for(int i=0; i < A.length; ++i)
if(a[i] < zerotickfuzz*norm) A[i]=a[i]=0;
int n=0;
bool Fixed=find(a >= 1e4-epsilon | (a > 0 & a <= 1e-4-epsilon)) < 0;
string Format=defaultformat(4,fixed=Fixed);
if(Fixed && n < 4) {
for(int i=0; i < A.length; ++i) {
real a=A[i];
while(format(defaultformat(n,fixed=Fixed),a) != format(Format,a))
++n;
}
}
string trailing=trailingzero ? (signchange ? "# " : "#") :
signedtrailingzero ? "#+" : "";
string format=defaultformat(n,trailing,Fixed);
for(int i=0; i < A.length-1; ++i) {
real a=A[i];
real b=A[i+1];
// Check if an extra digit of precision should be added.
string fixedformat="%#."+string(n+1)+"f";
string A=format(fixedformat,a);
string B=format(fixedformat,b);
if(substr(A,length(A)-1,1) != "0" || substr(B,length(B)-1,1) != "0") {
a *= 0.1;
b *= 0.1;
}
if(a != b) {
while(format(format,a) == format(format,b))
format=defaultformat(++n,trailing,Fixed);
}
}
if(n == 0) return defaultformat;
return format;
}
// Automatic tick generation routine.
tickvalues generateticks(int sign, Label F="", ticklabel ticklabel=null,
int N, int n=0, real Step=0, real step=0,
real Size=0, real size=0,
transform T, pair side, path g, real limit,
pen p, ticklocate locate, int[] divisor,
bool opposite)
{
tickvalues tickvalues;
sign=opposite ? -sign : sign;
if(Size == 0) Size=Ticksize;
if(size == 0) size=ticksize;
F=F.copy();
F.p(p);
if(F.align.dir != 0) side=F.align.dir;
else if(side == 0) side=((sign == 1) ? left : right);
bool ticklabels=false;
path G=T*g;
if(!locate.S.scale.logarithmic) {
real a=locate.S.Tinv(locate.a);
real b=locate.S.Tinv(locate.b);
real norm=max(abs(a),abs(b));
string format=autoformat(F.s,norm,a,b);
if(F.s == "%") F.s="";
if(ticklabel == null) ticklabel=Format(format);
if(a > b) {real temp=a; a=b; b=temp;}
if(b-a < 100.0*epsilon*norm) b=a;
bool autotick=Step == 0 && N == 0;
real tickmin=finite(locate.S.tickMin) && (autotick || locate.S.automin) ?
locate.S.Tinv(locate.S.tickMin) : a;
real tickmax=finite(locate.S.tickMax) && (autotick || locate.S.automax) ?
locate.S.Tinv(locate.S.tickMax) : b;
if(tickmin > tickmax) {real temp=tickmin; tickmin=tickmax; tickmax=temp;}
real inStep=Step;
bool calcStep=true;
real len=tickmax-tickmin;
if(autotick) {
N=1;
if(divisor.length > 0) {
bool autoscale=locate.S.automin && locate.S.automax;
real h=0.5*(b-a);
if(h > 0) {
for(int d=divisor.length-1; d >= 0; --d) {
int N0=divisor[d];
Step=len/N0;
int N1=N0;
int m=2;
while(Step > h) {
N0=m*N1;
Step=len/N0;
m *= 2;
}
if(axiscoverage(N0,T,g,locate,Step,side,sign,Size,F,ticklabel,norm,
limit)) {
N=N0;
if(N0 == 1 && !autoscale && d < divisor.length-1) {
// Try using 2 ticks (otherwise 1);
int div=divisor[d+1];
Step=quotient(div,2)*len/div;
calcStep=false;
if(axiscoverage(2,T,g,locate,Step,side,sign,Size,F,ticklabel,
norm,limit)) N=2;
else Step=len;
}
// Found a good divisor; now compute subtick divisor
if(n == 0) {
if(step != 0) n=ceil(Step/step);
else {
n=quotient(divisor[divisor.length-1],N);
if(N == 1) n=(a*b >= 0) ? 2 : 1;
if(n == 1) n=2;
}
}
break;
}
}
}
}
}
if(inStep != 0 && !locate.S.automin) {
tickmin=floor(tickmin/Step)*Step;
len=tickmax-tickmin;
}
if(calcStep) {
if(N == 1) N=2;
if(N == 0) N=(int) (len/Step);
else Step=len/N;
}
if(n == 0) {
if(step != 0) n=ceil(Step/step);
} else step=Step/n;
b += epsilon*norm;
if(Size > 0) {
for(int i=0; i <= N; ++i) {
real val=tickmin+i*Step;
if(val >= a && val <= b)
tickvalues.major.push(val);
if(size > 0 && step > 0) {
real iStep=i*Step;
real jstop=(len-iStep)/step;
for(int j=1; j < n && j <= jstop; ++j) {
real val=tickmin+iStep+j*step;
if(val >= a && val <= b)
tickvalues.minor.push(val);
}
}
}
}
} else { // Logarithmic
string format=F.s;
if(F.s == "%") F.s="";
int base=round(locate.S.scale.Tinv(1));
if(ticklabel == null)
ticklabel=format == "%" ? Format("") : DefaultLogFormat(base);
real a=locate.S.postscale.Tinv(locate.a);
real b=locate.S.postscale.Tinv(locate.b);
if(a > b) {real temp=a; a=b; b=temp;}
int first=floor(a-epsilon);
int last=ceil(b+epsilon);
if(N == 0) {
N=1;
while(N <= last-first) {
if(logaxiscoverage(N,T,g,locate,side,sign,Size,F,ticklabel,limit,
first,last)) break;
++N;
}
}
if(N <= 2 && n == 0) n=base;
tickvalues.N=N;
if(N > 0) {
for(int i=first-1; i <= last+1; ++i) {
if(i >= a && i <= b)
tickvalues.major.push(locate.S.scale.Tinv(i));
if(n > 0) {
for(int j=2; j < n; ++j) {
real val=(i+1+locate.S.scale.T(j/n));
if(val >= a && val <= b)
tickvalues.minor.push(locate.S.scale.Tinv(val));
}
}
}
}
}
return tickvalues;
}
// Signature of routines that draw labelled paths with ticks and tick labels.
typedef void ticks(frame, transform, Label, pair, path, path, pen,
arrowbar, margin, ticklocate, int[], bool opposite=false);
// Tick construction routine for a user-specified array of tick values.
ticks Ticks(int sign, Label F="", ticklabel ticklabel=null,
bool beginlabel=true, bool endlabel=true,
real[] Ticks=new real[], real[] ticks=new real[], int N=1,
bool begin=true, bool end=true,
real Size=0, real size=0, bool extend=false,
pen pTick=nullpen, pen ptick=nullpen)
{
return new void(frame f, transform t, Label L, pair side, path g, path g2,
pen p, arrowbar arrow, margin margin, ticklocate locate,
int[] divisor, bool opposite) {
// Use local copy of context variables:
int sign=opposite ? -sign : sign;
pen pTick=pTick;
pen ptick=ptick;
ticklabel ticklabel=ticklabel;
real Size=Size;
real size=size;
if(Size == 0) Size=Ticksize;
if(size == 0) size=ticksize;
Label L=L.copy();
Label F=F.copy();
L.p(p);
F.p(p);
if(pTick == nullpen) pTick=p;
if(ptick == nullpen) ptick=pTick;
if(F.align.dir != 0) side=F.align.dir;
else if(side == 0) side=F.T*((sign == 1) ? left : right);
bool ticklabels=false;
path G=t*g;
path G2=t*g2;
scalefcn T;
real a,b;
if(locate.S.scale.logarithmic) {
a=locate.S.postscale.Tinv(locate.a);
b=locate.S.postscale.Tinv(locate.b);
T=locate.S.scale.T;
} else {
a=locate.S.Tinv(locate.a);
b=locate.S.Tinv(locate.b);
T=identity;
}
if(a > b) {real temp=a; a=b; b=temp;}
real norm=max(abs(a),abs(b));
string format=autoformat(F.s,norm...Ticks);
if(F.s == "%") F.s="";
if(ticklabel == null) {
if(locate.S.scale.logarithmic) {
int base=round(locate.S.scale.Tinv(1));
ticklabel=format == "%" ? Format("") : DefaultLogFormat(base);
} else ticklabel=Format(format);
}
begingroup(f);
if(opposite) draw(f,G,p);
else draw(f,margin(G,p).g,p,arrow);
for(int i=(begin ? 0 : 1); i < (end ? Ticks.length : Ticks.length-1); ++i) {
real val=T(Ticks[i]);
if(val >= a && val <= b)
drawtick(f,t,g,g2,locate,val,Size,sign,pTick,extend);
}
for(int i=0; i < ticks.length; ++i) {
real val=T(ticks[i]);
if(val >= a && val <= b)
drawtick(f,t,g,g2,locate,val,size,sign,ptick,extend);
}
endgroup(f);
if(N == 0) N=1;
if(Size > 0 && !opposite) {
for(int i=(beginlabel ? 0 : 1);
i < (endlabel ? Ticks.length : Ticks.length-1); i += N) {
real val=T(Ticks[i]);
if(val >= a && val <= b) {
ticklabels=true;
labeltick(f,t,g,locate,val,side,sign,Size,ticklabel,F,norm);
}
}
}
if(L.s != "" && !opposite)
labelaxis(f,t,L,G,locate,sign,ticklabels);
};
}
// Optional routine to allow modification of auto-generated tick values.
typedef tickvalues tickmodifier(tickvalues);
tickvalues None(tickvalues v) {return v;}
// Tickmodifier that removes all ticks in the intervals [a[i],b[i]].
tickmodifier OmitTickIntervals(real[] a, real[] b) {
return new tickvalues(tickvalues v) {
if(a.length != b.length) abort(differentlengths);
void omit(real[] A) {
if(A.length != 0) {
real norm=max(abs(A));
for(int i=0; i < a.length; ++i) {
int j;
while((j=find(A > a[i]-zerotickfuzz*norm
& A < b[i]+zerotickfuzz*norm)) >= 0) {
A.delete(j);
}
}
}
}
omit(v.major);
omit(v.minor);
return v;
};
}
// Tickmodifier that removes all ticks in the interval [a,b].
tickmodifier OmitTickInterval(real a, real b) {
return OmitTickIntervals(new real[] {a}, new real[] {b});
}
// Tickmodifier that removes the specified ticks.
tickmodifier OmitTick(... real[] x) {
return OmitTickIntervals(x,x);
}
tickmodifier NoZero=OmitTick(0);
tickmodifier Break(real, real)=OmitTickInterval;
// Automatic tick construction routine.
ticks Ticks(int sign, Label F="", ticklabel ticklabel=null,
bool beginlabel=true, bool endlabel=true,
int N, int n=0, real Step=0, real step=0,
bool begin=true, bool end=true, tickmodifier modify=None,
real Size=0, real size=0, bool extend=false,
pen pTick=nullpen, pen ptick=nullpen)
{
return new void(frame f, transform T, Label L, pair side, path g, path g2,
pen p, arrowbar arrow, margin margin, ticklocate locate,
int[] divisor, bool opposite) {
real limit=Step == 0 ? axiscoverage*arclength(T*g) : 0;
tickvalues values=modify(generateticks(sign,F,ticklabel,N,n,Step,step,
Size,size,T,side,g,
limit,p,locate,divisor,opposite));
Ticks(sign,F,ticklabel,beginlabel,endlabel,values.major,values.minor,
values.N,begin,end,Size,size,extend,pTick,ptick)
(f,T,L,side,g,g2,p,arrow,margin,locate,divisor,opposite);
};
}
ticks NoTicks()
{
return new void(frame f, transform T, Label L, pair, path g, path, pen p,
arrowbar arrow, margin margin, ticklocate,
int[], bool opposite) {
path G=T*g;
if(opposite) draw(f,G,p);
else {
draw(f,margin(G,p).g,p,arrow);
if(L.s != "") {
Label L=L.copy();
L.p(p);
labelaxis(f,T,L,G);
}
}
};
}
ticks LeftTicks(Label format="", ticklabel ticklabel=null,
bool beginlabel=true, bool endlabel=true,
int N=0, int n=0, real Step=0, real step=0,
bool begin=true, bool end=true, tickmodifier modify=None,
real Size=0, real size=0, bool extend=false,
pen pTick=nullpen, pen ptick=nullpen)
{
return Ticks(-1,format,ticklabel,beginlabel,endlabel,N,n,Step,step,
begin,end,modify,Size,size,extend,pTick,ptick);
}
ticks RightTicks(Label format="", ticklabel ticklabel=null,
bool beginlabel=true, bool endlabel=true,
int N=0, int n=0, real Step=0, real step=0,
bool begin=true, bool end=true, tickmodifier modify=None,
real Size=0, real size=0, bool extend=false,
pen pTick=nullpen, pen ptick=nullpen)
{
return Ticks(1,format,ticklabel,beginlabel,endlabel,N,n,Step,step,
begin,end,modify,Size,size,extend,pTick,ptick);
}
ticks Ticks(Label format="", ticklabel ticklabel=null,
bool beginlabel=true, bool endlabel=true,
int N=0, int n=0, real Step=0, real step=0,
bool begin=true, bool end=true, tickmodifier modify=None,
real Size=0, real size=0, bool extend=false,
pen pTick=nullpen, pen ptick=nullpen)
{
return Ticks(0,format,ticklabel,beginlabel,endlabel,N,n,Step,step,
begin,end,modify,Size,size,extend,pTick,ptick);
}
ticks LeftTicks(Label format="", ticklabel ticklabel=null,
bool beginlabel=true, bool endlabel=true,
real[] Ticks, real[] ticks=new real[],
real Size=0, real size=0, bool extend=false,
pen pTick=nullpen, pen ptick=nullpen)
{
return Ticks(-1,format,ticklabel,beginlabel,endlabel,
Ticks,ticks,Size,size,extend,pTick,ptick);
}
ticks RightTicks(Label format="", ticklabel ticklabel=null,
bool beginlabel=true, bool endlabel=true,
real[] Ticks, real[] ticks=new real[],
real Size=0, real size=0, bool extend=false,
pen pTick=nullpen, pen ptick=nullpen)
{
return Ticks(1,format,ticklabel,beginlabel,endlabel,
Ticks,ticks,Size,size,extend,pTick,ptick);
}
ticks Ticks(Label format="", ticklabel ticklabel=null,
bool beginlabel=true, bool endlabel=true,
real[] Ticks, real[] ticks=new real[],
real Size=0, real size=0, bool extend=false,
pen pTick=nullpen, pen ptick=nullpen)
{
return Ticks(0,format,ticklabel,beginlabel,endlabel,
Ticks,ticks,Size,size,extend,pTick,ptick);
}
ticks NoTicks=NoTicks(),
LeftTicks=LeftTicks(),
RightTicks=RightTicks(),
Ticks=Ticks();
pair tickMin(picture pic)
{
return minbound(pic.userMin(),(pic.scale.x.tickMin,pic.scale.y.tickMin));
}
pair tickMax(picture pic)
{
return maxbound(pic.userMax(),(pic.scale.x.tickMax,pic.scale.y.tickMax));
}
int Min=-1;
int Value=0;
int Max=1;
int Both=2;
// Structure used to communicate axis and autoscale settings to tick routines.
struct axisT {
int type; // -1 = min, 0 = given value, 1 = max, 2 = min/max
int type2; // for 3D axis
real value;
real value2;
pair side; // 2D tick label direction relative to path (left or right)
real position; // label position along axis
align align; // default axis label alignment and 3D tick label direction
int[] xdivisor;
int[] ydivisor;
int[] zdivisor;
bool extend; // extend axis to graph boundary?
};
axisT axis;
typedef void axis(picture, axisT);
axis Bottom(bool extend=false)
{
return new void(picture pic, axisT axis) {
axis.type=Min;
axis.position=0.5;
axis.side=right;
axis.align=S;
axis.extend=extend;
};
}
axis Top(bool extend=false)
{
return new void(picture pic, axisT axis) {
axis.type=Max;
axis.position=0.5;
axis.side=left;
axis.align=N;
axis.extend=extend;
};
}
axis BottomTop(bool extend=false)
{
return new void(picture pic, axisT axis) {
axis.type=Both;
axis.position=0.5;
axis.side=right;
axis.align=S;
axis.extend=extend;
};
}
axis Left(bool extend=false)
{
return new void(picture pic, axisT axis) {
axis.type=Min;
axis.position=0.5;
axis.side=left;
axis.align=W;
axis.extend=extend;
};
}
axis Right(bool extend=false)
{
return new void(picture pic, axisT axis) {
axis.type=Max;
axis.position=0.5;
axis.side=right;
axis.align=E;
axis.extend=extend;
};
}
axis LeftRight(bool extend=false)
{
return new void(picture pic, axisT axis) {
axis.type=Both;
axis.position=0.5;
axis.side=left;
axis.align=W;
axis.extend=extend;
};
}
axis XEquals(real x, bool extend=true)
{
return new void(picture pic, axisT axis) {
axis.type=Value;
axis.value=pic.scale.x.T(x);
axis.position=1;
axis.side=left;
axis.align=W;
axis.extend=extend;
};
}
axis YEquals(real y, bool extend=true)
{
return new void(picture pic, axisT axis) {
axis.type=Value;
axis.value=pic.scale.y.T(y);
axis.position=1;
axis.side=right;
axis.align=S;
axis.extend=extend;
};
}
axis XZero(bool extend=true)
{
return new void(picture pic, axisT axis) {
axis.type=Value;
axis.value=pic.scale.x.T(pic.scale.x.scale.logarithmic ? 1 : 0);
axis.position=1;
axis.side=left;
axis.align=W;
axis.extend=extend;
};
}
axis YZero(bool extend=true)
{
return new void(picture pic, axisT axis) {
axis.type=Value;
axis.value=pic.scale.y.T(pic.scale.y.scale.logarithmic ? 1 : 0);
axis.position=1;
axis.side=right;
axis.align=S;
axis.extend=extend;
};
}
axis Bottom=Bottom(),
Top=Top(),
BottomTop=BottomTop(),
Left=Left(),
Right=Right(),
LeftRight=LeftRight(),
XZero=XZero(),
YZero=YZero();
// Draw a general axis.
void axis(picture pic=currentpicture, Label L="", path g, path g2=nullpath,
pen p=currentpen, ticks ticks, ticklocate locate,
arrowbar arrow=None, margin margin=NoMargin,
int[] divisor=new int[], bool above=false, bool opposite=false)
{
Label L=L.copy();
real t=reltime(g,0.5);
if(L.defaultposition) L.position(t);
divisor=copy(divisor);
locate=locate.copy();
pic.add(new void (frame f, transform t, transform T, pair lb, pair rt) {
frame d;
ticks(d,t,L,0,g,g2,p,arrow,margin,locate,divisor,opposite);
(above ? add : prepend)(f,t*T*inverse(t)*d);
});
pic.addPath(g,p);
if(L.s != "") {
frame f;
Label L0=L.copy();
L0.position(0);
add(f,L0);
pair pos=point(g,L.relative()*length(g));
pic.addBox(pos,pos,min(f),max(f));
}
}
real xtrans(transform t, real x)
{
return (t*(x,0)).x;
}
real ytrans(transform t, real y)
{
return (t*(0,y)).y;
}
// An internal routine to draw an x axis at a particular y value.
void xaxisAt(picture pic=currentpicture, Label L="", axis axis,
real xmin=-infinity, real xmax=infinity, pen p=currentpen,
ticks ticks=NoTicks, arrowbar arrow=None, margin margin=NoMargin,
bool above=true, bool opposite=false)
{
real y=axis.value;
real y2;
Label L=L.copy();
int[] divisor=copy(axis.xdivisor);
pair side=axis.side;
int type=axis.type;
pic.add(new void (frame f, transform t, transform T, pair lb, pair rt) {
transform tinv=inverse(t);
pair a=xmin == -infinity ? tinv*(lb.x-min(p).x,ytrans(t,y)) : (xmin,y);
pair b=xmax == infinity ? tinv*(rt.x-max(p).x,ytrans(t,y)) : (xmax,y);
pair a2=xmin == -infinity ? tinv*(lb.x-min(p).x,ytrans(t,y2)) : (xmin,y2);
pair b2=xmax == infinity ? tinv*(rt.x-max(p).x,ytrans(t,y2)) : (xmax,y2);
if(xmin == -infinity || xmax == infinity) {
bounds mx=autoscale(a.x,b.x,pic.scale.x.scale);
pic.scale.x.tickMin=mx.min;
pic.scale.x.tickMax=mx.max;
divisor=mx.divisor;
}
real fuzz=epsilon*max(abs(a.x),abs(b.x));
a -= (fuzz,0);
b += (fuzz,0);
frame d;
ticks(d,t,L,side,a--b,finite(y2) ? a2--b2 : nullpath,p,arrow,margin,
ticklocate(a.x,b.x,pic.scale.x),divisor,opposite);
(above ? add : prepend)(f,t*T*tinv*d);
});
void bounds() {
if(type == Both) {
y2=pic.scale.y.automax() ? tickMax(pic).y : pic.userMax().y;
y=opposite ? y2 :
(pic.scale.y.automin() ? tickMin(pic).y : pic.userMin().y);
}
else if(type == Min)
y=pic.scale.y.automin() ? tickMin(pic).y : pic.userMin().y;
else if(type == Max)
y=pic.scale.y.automax() ? tickMax(pic).y : pic.userMax().y;
real Xmin=finite(xmin) ? xmin : pic.userMin().x;
real Xmax=finite(xmax) ? xmax : pic.userMax().x;
pair a=(Xmin,y);
pair b=(Xmax,y);
pair a2=(Xmin,y2);
pair b2=(Xmax,y2);
if(finite(a)) {
pic.addPoint(a,min(p));
pic.addPoint(a,max(p));
}
if(finite(b)) {
pic.addPoint(b,min(p));
pic.addPoint(b,max(p));
}
if(finite(a) && finite(b)) {
frame d;
ticks(d,pic.scaling(warn=false),L,side,
(a.x,0)--(b.x,0),(a2.x,0)--(b2.x,0),p,arrow,margin,
ticklocate(a.x,b.x,pic.scale.x),divisor,opposite);
frame f;
if(L.s != "") {
Label L0=L.copy();
L0.position(0);
add(f,L0);
}
pair pos=a+L.relative()*(b-a);
pic.addBox(pos,pos,(min(f).x,min(d).y),(max(f).x,max(d).y));
}
}
// Process any queued y axis bound calculation requests.
for(int i=0; i < pic.scale.y.bound.length; ++i)
pic.scale.y.bound[i]();
pic.scale.y.bound.delete();
bounds();
// Request another x bounds calculation before final picture scaling.
pic.scale.x.bound.push(bounds);
}
// An internal routine to draw a y axis at a particular x value.
void yaxisAt(picture pic=currentpicture, Label L="", axis axis,
real ymin=-infinity, real ymax=infinity, pen p=currentpen,
ticks ticks=NoTicks, arrowbar arrow=None, margin margin=NoMargin,
bool above=true, bool opposite=false)
{
real x=axis.value;
real x2;
Label L=L.copy();
int[] divisor=copy(axis.ydivisor);
pair side=axis.side;
int type=axis.type;
pic.add(new void (frame f, transform t, transform T, pair lb, pair rt) {
transform tinv=inverse(t);
pair a=ymin == -infinity ? tinv*(xtrans(t,x),lb.y-min(p).y) : (x,ymin);
pair b=ymax == infinity ? tinv*(xtrans(t,x),rt.y-max(p).y) : (x,ymax);
pair a2=ymin == -infinity ? tinv*(xtrans(t,x2),lb.y-min(p).y) : (x2,ymin);
pair b2=ymax == infinity ? tinv*(xtrans(t,x2),rt.y-max(p).y) : (x2,ymax);
if(ymin == -infinity || ymax == infinity) {
bounds my=autoscale(a.y,b.y,pic.scale.y.scale);
pic.scale.y.tickMin=my.min;
pic.scale.y.tickMax=my.max;
divisor=my.divisor;
}
real fuzz=epsilon*max(abs(a.y),abs(b.y));
a -= (0,fuzz);
b += (0,fuzz);
frame d;
ticks(d,t,L,side,a--b,finite(x2) ? a2--b2 : nullpath,p,arrow,margin,
ticklocate(a.y,b.y,pic.scale.y),divisor,opposite);
(above ? add : prepend)(f,t*T*tinv*d);
});
void bounds() {
if(type == Both) {
x2=pic.scale.x.automax() ? tickMax(pic).x : pic.userMax().x;
x=opposite ? x2 :
(pic.scale.x.automin() ? tickMin(pic).x : pic.userMin().x);
} else if(type == Min)
x=pic.scale.x.automin() ? tickMin(pic).x : pic.userMin().x;
else if(type == Max)
x=pic.scale.x.automax() ? tickMax(pic).x : pic.userMax().x;
real Ymin=finite(ymin) ? ymin : pic.userMin().y;
real Ymax=finite(ymax) ? ymax : pic.userMax().y;
pair a=(x,Ymin);
pair b=(x,Ymax);
pair a2=(x2,Ymin);
pair b2=(x2,Ymax);
if(finite(a)) {
pic.addPoint(a,min(p));
pic.addPoint(a,max(p));
}
if(finite(b)) {
pic.addPoint(b,min(p));
pic.addPoint(b,max(p));
}
if(finite(a) && finite(b)) {
frame d;
ticks(d,pic.scaling(warn=false),L,side,
(0,a.y)--(0,b.y),(0,a2.y)--(0,b2.y),p,arrow,margin,
ticklocate(a.y,b.y,pic.scale.y),divisor,opposite);
frame f;
if(L.s != "") {
Label L0=L.copy();
L0.position(0);
add(f,L0);
}
pair pos=a+L.relative()*(b-a);
pic.addBox(pos,pos,(min(d).x,min(f).y),(max(d).x,max(f).y));
}
}
// Process any queued x axis bound calculation requests.
for(int i=0; i < pic.scale.x.bound.length; ++i)
pic.scale.x.bound[i]();
pic.scale.x.bound.delete();
bounds();
// Request another y bounds calculation before final picture scaling.
pic.scale.y.bound.push(bounds);
}
// Set the x limits of a picture.
void xlimits(picture pic=currentpicture, real min=-infinity, real max=infinity,
bool crop=NoCrop)
{
if(min > max) return;
pic.scale.x.automin=min <= -infinity;
pic.scale.x.automax=max >= infinity;
bounds mx;
if(pic.scale.x.automin() || pic.scale.x.automax())
mx=autoscale(pic.userMin().x,pic.userMax().x,pic.scale.x.scale);
if(pic.scale.x.automin) {
if(pic.scale.x.automin()) pic.userMinx(mx.min);
} else pic.userMinx(min(pic.scale.x.T(min),pic.scale.x.T(max)));
if(pic.scale.x.automax) {
if(pic.scale.x.automax()) pic.userMaxx(mx.max);
} else pic.userMaxx(max(pic.scale.x.T(min),pic.scale.x.T(max)));
if(crop) {
pair userMin=pic.userMin();
pair userMax=pic.userMax();
pic.bounds.xclip(userMin.x,userMax.x);
pic.clip(userMin, userMax,
new void (frame f, transform t, transform T, pair, pair) {
frame Tinvf=T == identity() ? f : t*inverse(T)*inverse(t)*f;
clip(f,T*box(((t*userMin).x,(min(Tinvf)).y),
((t*userMax).x,(max(Tinvf)).y)));
});
}
}
// Set the y limits of a picture.
void ylimits(picture pic=currentpicture, real min=-infinity, real max=infinity,
bool crop=NoCrop)
{
if(min > max) return;
pic.scale.y.automin=min <= -infinity;
pic.scale.y.automax=max >= infinity;
bounds my;
if(pic.scale.y.automin() || pic.scale.y.automax())
my=autoscale(pic.userMin().y,pic.userMax().y,pic.scale.y.scale);
if(pic.scale.y.automin) {
if(pic.scale.y.automin()) pic.userMiny(my.min);
} else pic.userMiny(min(pic.scale.y.T(min),pic.scale.y.T(max)));
if(pic.scale.y.automax) {
if(pic.scale.y.automax()) pic.userMaxy(my.max);
} else pic.userMaxy(max(pic.scale.y.T(min),pic.scale.y.T(max)));
if(crop) {
pair userMin=pic.userMin();
pair userMax=pic.userMax();
pic.bounds.yclip(userMin.y,userMax.y);
pic.clip(userMin, userMax,
new void (frame f, transform t, transform T, pair, pair) {
frame Tinvf=T == identity() ? f : t*inverse(T)*inverse(t)*f;
clip(f,T*box(((min(Tinvf)).x,(t*userMin).y),
((max(Tinvf)).x,(t*userMax).y)));
});
}
}
// Crop a picture to the current user-space picture limits.
void crop(picture pic=currentpicture)
{
xlimits(pic,false);
ylimits(pic,false);
if(pic.userSetx() && pic.userSety())
clip(pic,box(pic.userMin(),pic.userMax()));
}
// Restrict the x and y limits to box(min,max).
void limits(picture pic=currentpicture, pair min, pair max, bool crop=NoCrop)
{
xlimits(pic,min.x,max.x);
ylimits(pic,min.y,max.y);
if(crop && pic.userSetx() && pic.userSety())
clip(pic,box(pic.userMin(),pic.userMax()));
}
// Internal routine to autoscale the user limits of a picture.
void autoscale(picture pic=currentpicture, axis axis)
{
if(!pic.scale.set) {
bounds mx,my;
pic.scale.set=true;
if(pic.userSetx()) {
mx=autoscale(pic.userMin().x,pic.userMax().x,pic.scale.x.scale);
if(pic.scale.x.scale.logarithmic &&
floor(pic.userMin().x) == floor(pic.userMax().x)) {
if(pic.scale.x.automin())
pic.userMinx2(floor(pic.userMin().x));
if(pic.scale.x.automax())
pic.userMaxx2(ceil(pic.userMax().x));
}
} else {mx.min=mx.max=0; pic.scale.set=false;}
if(pic.userSety()) {
my=autoscale(pic.userMin().y,pic.userMax().y,pic.scale.y.scale);
if(pic.scale.y.scale.logarithmic &&
floor(pic.userMin().y) == floor(pic.userMax().y)) {
if(pic.scale.y.automin())
pic.userMiny2(floor(pic.userMin().y));
if(pic.scale.y.automax())
pic.userMaxy2(ceil(pic.userMax().y));
}
} else {my.min=my.max=0; pic.scale.set=false;}
pic.scale.x.tickMin=mx.min;
pic.scale.x.tickMax=mx.max;
pic.scale.y.tickMin=my.min;
pic.scale.y.tickMax=my.max;
axis.xdivisor=mx.divisor;
axis.ydivisor=my.divisor;
}
}
// Draw an x axis.
void xaxis(picture pic=currentpicture, Label L="", axis axis=YZero,
real xmin=-infinity, real xmax=infinity, pen p=currentpen,
ticks ticks=NoTicks, arrowbar arrow=None, margin margin=NoMargin,
bool above=false)
{
if(xmin > xmax) return;
if(pic.scale.x.automin && xmin > -infinity) pic.scale.x.automin=false;
if(pic.scale.x.automax && xmax < infinity) pic.scale.x.automax=false;
if(!pic.scale.set) {
axis(pic,axis);
autoscale(pic,axis);
}
Label L=L.copy();
bool newticks=false;
if(xmin != -infinity) {
xmin=pic.scale.x.T(xmin);
newticks=true;
}
if(xmax != infinity) {
xmax=pic.scale.x.T(xmax);
newticks=true;
}
if(newticks && pic.userSetx() && ticks != NoTicks) {
if(xmin == -infinity) xmin=pic.userMin().x;
if(xmax == infinity) xmax=pic.userMax().x;
bounds mx=autoscale(xmin,xmax,pic.scale.x.scale);
pic.scale.x.tickMin=mx.min;
pic.scale.x.tickMax=mx.max;
axis.xdivisor=mx.divisor;
}
axis(pic,axis);
if(xmin == -infinity && !axis.extend) {
if(pic.scale.set)
xmin=pic.scale.x.automin() ? pic.scale.x.tickMin :
max(pic.scale.x.tickMin,pic.userMin().x);
else xmin=pic.userMin().x;
}
if(xmax == infinity && !axis.extend) {
if(pic.scale.set)
xmax=pic.scale.x.automax() ? pic.scale.x.tickMax :
min(pic.scale.x.tickMax,pic.userMax().x);
else xmax=pic.userMax().x;
}
if(L.defaultposition) L.position(axis.position);
L.align(L.align,axis.align);
xaxisAt(pic,L,axis,xmin,xmax,p,ticks,arrow,margin,above);
if(axis.type == Both)
xaxisAt(pic,L,axis,xmin,xmax,p,ticks,arrow,margin,above,true);
}
// Draw a y axis.
void yaxis(picture pic=currentpicture, Label L="", axis axis=XZero,
real ymin=-infinity, real ymax=infinity, pen p=currentpen,
ticks ticks=NoTicks, arrowbar arrow=None, margin margin=NoMargin,
bool above=false, bool autorotate=true)
{
if(ymin > ymax) return;
if(pic.scale.y.automin && ymin > -infinity) pic.scale.y.automin=false;
if(pic.scale.y.automax && ymax < infinity) pic.scale.y.automax=false;
if(!pic.scale.set) {
axis(pic,axis);
autoscale(pic,axis);
}
Label L=L.copy();
bool newticks=false;
if(ymin != -infinity) {
ymin=pic.scale.y.T(ymin);
newticks=true;
}
if(ymax != infinity) {
ymax=pic.scale.y.T(ymax);
newticks=true;
}
if(newticks && pic.userSety() && ticks != NoTicks) {
if(ymin == -infinity) ymin=pic.userMin().y;
if(ymax == infinity) ymax=pic.userMax().y;
bounds my=autoscale(ymin,ymax,pic.scale.y.scale);
pic.scale.y.tickMin=my.min;
pic.scale.y.tickMax=my.max;
axis.ydivisor=my.divisor;
}
axis(pic,axis);
if(ymin == -infinity && !axis.extend) {
if(pic.scale.set)
ymin=pic.scale.y.automin() ? pic.scale.y.tickMin :
max(pic.scale.y.tickMin,pic.userMin().y);
else ymin=pic.userMin().y;
}
if(ymax == infinity && !axis.extend) {
if(pic.scale.set)
ymax=pic.scale.y.automax() ? pic.scale.y.tickMax :
min(pic.scale.y.tickMax,pic.userMax().y);
else ymax=pic.userMax().y;
}
if(L.defaultposition) L.position(axis.position);
L.align(L.align,axis.align);
if(autorotate && L.defaulttransform) {
frame f;
add(f,Label(L.s,(0,0),L.p));
if(length(max(f)-min(f)) > ylabelwidth*fontsize(L.p))
L.transform(rotate(90));
}
yaxisAt(pic,L,axis,ymin,ymax,p,ticks,arrow,margin,above);
if(axis.type == Both)
yaxisAt(pic,L,axis,ymin,ymax,p,ticks,arrow,margin,above,true);
}
// Draw x and y axes.
void axes(picture pic=currentpicture, Label xlabel="", Label ylabel="",
bool extend=true,
pair min=(-infinity,-infinity), pair max=(infinity,infinity),
pen p=currentpen, arrowbar arrow=None, margin margin=NoMargin,
bool above=false)
{
xaxis(pic,xlabel,YZero(extend),min.x,max.x,p,arrow,margin,above);
yaxis(pic,ylabel,XZero(extend),min.y,max.y,p,arrow,margin,above);
}
// Draw a yaxis at x.
void xequals(picture pic=currentpicture, Label L="", real x,
bool extend=false, real ymin=-infinity, real ymax=infinity,
pen p=currentpen, ticks ticks=NoTicks,
arrowbar arrow=None, margin margin=NoMargin, bool above=true)
{
yaxis(pic,L,XEquals(x,extend),ymin,ymax,p,ticks,arrow,margin,above);
}
// Draw an xaxis at y.
void yequals(picture pic=currentpicture, Label L="", real y,
bool extend=false, real xmin=-infinity, real xmax=infinity,
pen p=currentpen, ticks ticks=NoTicks,
arrowbar arrow=None, margin margin=NoMargin, bool above=true)
{
xaxis(pic,L,YEquals(y,extend),xmin,xmax,p,ticks,arrow,margin,above);
}
pair Scale(picture pic=currentpicture, pair z)
{
return (pic.scale.x.T(z.x),pic.scale.y.T(z.y));
}
real ScaleX(picture pic=currentpicture, real x)
{
return pic.scale.x.T(x);
}
real ScaleY(picture pic=currentpicture, real y)
{
return pic.scale.y.T(y);
}
// Draw a tick of length size at pair z in direction dir using pen p.
void tick(picture pic=currentpicture, pair z, pair dir, real size=Ticksize,
pen p=currentpen)
{
pair z=Scale(pic,z);
pic.add(new void (frame f, transform t) {
pair tz=t*z;
draw(f,tz--tz+unit(dir)*size,p);
});
pic.addPoint(z,p);
pic.addPoint(z,unit(dir)*size,p);
}
void xtick(picture pic=currentpicture, explicit pair z, pair dir=N,
real size=Ticksize, pen p=currentpen)
{
tick(pic,z,dir,size,p);
}
void xtick(picture pic=currentpicture, real x, pair dir=N,
real size=Ticksize, pen p=currentpen)
{
tick(pic,(x,pic.scale.y.scale.logarithmic ? 1 : 0),dir,size,p);
}
void ytick(picture pic=currentpicture, explicit pair z, pair dir=E,
real size=Ticksize, pen p=currentpen)
{
tick(pic,z,dir,size,p);
}
void ytick(picture pic=currentpicture, real y, pair dir=E,
real size=Ticksize, pen p=currentpen)
{
tick(pic,(pic.scale.x.scale.logarithmic ? 1 : 0,y),dir,size,p);
}
void tick(picture pic=currentpicture, Label L, real value, explicit pair z,
pair dir, string format="", real size=Ticksize, pen p=currentpen)
{
Label L=L.copy();
L.position(Scale(pic,z));
L.align(L.align,-dir);
if(shift(L.T)*0 == 0)
L.T=shift(dot(dir,L.align.dir) > 0 ? dir*size :
ticklabelshift(L.align.dir,p))*L.T;
L.p(p);
if(L.s == "") L.s=format(format == "" ? defaultformat : format,value);
L.s=baseline(L.s,baselinetemplate);
add(pic,L);
tick(pic,z,dir,size,p);
}
void xtick(picture pic=currentpicture, Label L, explicit pair z, pair dir=N,
string format="", real size=Ticksize, pen p=currentpen)
{
tick(pic,L,z.x,z,dir,format,size,p);
}
void xtick(picture pic=currentpicture, Label L, real x, pair dir=N,
string format="", real size=Ticksize, pen p=currentpen)
{
xtick(pic,L,(x,pic.scale.y.scale.logarithmic ? 1 : 0),dir,size,p);
}
void ytick(picture pic=currentpicture, Label L, explicit pair z, pair dir=E,
string format="", real size=Ticksize, pen p=currentpen)
{
tick(pic,L,z.y,z,dir,format,size,p);
}
void ytick(picture pic=currentpicture, Label L, real y, pair dir=E,
string format="", real size=Ticksize, pen p=currentpen)
{
xtick(pic,L,(pic.scale.x.scale.logarithmic ? 1 : 0,y),dir,format,size,p);
}
private void label(picture pic, Label L, pair z, real x, align align,
string format, pen p)
{
Label L=L.copy();
L.position(z);
L.align(align);
L.p(p);
if(shift(L.T)*0 == 0)
L.T=shift(ticklabelshift(L.align.dir,L.p))*L.T;
if(L.s == "") L.s=format(format == "" ? defaultformat : format,x);
L.s=baseline(L.s,baselinetemplate);
add(pic,L);
}
// Put a label on the x axis.
void labelx(picture pic=currentpicture, Label L="", explicit pair z,
align align=S, string format="", pen p=currentpen)
{
label(pic,L,Scale(pic,z),z.x,align,format,p);
}
void labelx(picture pic=currentpicture, Label L="", real x,
align align=S, string format="", pen p=currentpen)
{
labelx(pic,L,(x,pic.scale.y.scale.logarithmic ? 1 : 0),align,format,p);
}
void labelx(picture pic=currentpicture, Label L,
string format="", explicit pen p=currentpen)
{
labelx(pic,L,L.position.position,format,p);
}
// Put a label on the y axis.
void labely(picture pic=currentpicture, Label L="", explicit pair z,
align align=W, string format="", pen p=currentpen)
{
label(pic,L,Scale(pic,z),z.y,align,format,p);
}
void labely(picture pic=currentpicture, Label L="", real y,
align align=W, string format="", pen p=currentpen)
{
labely(pic,L,(pic.scale.x.scale.logarithmic ? 1 : 0,y),align,format,p);
}
void labely(picture pic=currentpicture, Label L,
string format="", explicit pen p=currentpen)
{
labely(pic,L,L.position.position,format,p);
}
private string noprimary="Primary axis must be drawn before secondary axis";
// Construct a secondary X axis
picture secondaryX(picture primary=currentpicture, void f(picture))
{
if(!primary.scale.set) abort(noprimary);
picture pic;
size(pic,primary);
if(primary.userMax().x == primary.userMin().x) return pic;
f(pic);
if(!pic.userSetx()) return pic;
bounds a=autoscale(pic.userMin().x,pic.userMax().x,pic.scale.x.scale);
real bmin=pic.scale.x.automin() ? a.min : pic.userMin().x;
real bmax=pic.scale.x.automax() ? a.max : pic.userMax().x;
real denom=bmax-bmin;
if(denom != 0) {
pic.erase();
real m=(primary.userMax().x-primary.userMin().x)/denom;
pic.scale.x.postscale=Linear(m,bmin-primary.userMin().x/m);
pic.scale.set=true;
pic.scale.x.tickMin=pic.scale.x.postscale.T(a.min);
pic.scale.x.tickMax=pic.scale.x.postscale.T(a.max);
pic.scale.y.tickMin=primary.userMin().y;
pic.scale.y.tickMax=primary.userMax().y;
axis.xdivisor=a.divisor;
f(pic);
}
pic.userCopy(primary);
return pic;
}
// Construct a secondary Y axis
picture secondaryY(picture primary=currentpicture, void f(picture))
{
if(!primary.scale.set) abort(noprimary);
picture pic;
size(pic,primary);
if(primary.userMax().y == primary.userMin().y) return pic;
f(pic);
if(!pic.userSety()) return pic;
bounds a=autoscale(pic.userMin().y,pic.userMax().y,pic.scale.y.scale);
real bmin=pic.scale.y.automin() ? a.min : pic.userMin().y;
real bmax=pic.scale.y.automax() ? a.max : pic.userMax().y;
real denom=bmax-bmin;
if(denom != 0) {
pic.erase();
real m=(primary.userMax().y-primary.userMin().y)/denom;
pic.scale.y.postscale=Linear(m,bmin-primary.userMin().y/m);
pic.scale.set=true;
pic.scale.x.tickMin=primary.userMin().x;
pic.scale.x.tickMax=primary.userMax().x;
pic.scale.y.tickMin=pic.scale.y.postscale.T(a.min);
pic.scale.y.tickMax=pic.scale.y.postscale.T(a.max);
axis.ydivisor=a.divisor;
f(pic);
}
pic.userCopy(primary);
return pic;
}
typedef guide graph(pair f(real), real, real, int);
typedef guide[] multigraph(pair f(real), real, real, int);
graph graph(interpolate join)
{
return new guide(pair f(real), real a, real b, int n) {
real width=b-a;
return n == 0 ? join(f(a)) :
join(...sequence(new guide(int i) {return f(a+(i/n)*width);},n+1));
};
}
multigraph graph(interpolate join, bool3 cond(real))
{
return new guide[](pair f(real), real a, real b, int n) {
real width=b-a;
if(n == 0) return new guide[] {join(cond(a) ? f(a) : nullpath)};
guide[] G;
guide[] g;
for(int i=0; i < n+1; ++i) {
real t=a+(i/n)*width;
bool3 b=cond(t);
if(b)
g.push(f(t));
else {
if(g.length > 0) {
G.push(join(...g));
g=new guide[] {};
}
if(b == default)
g.push(f(t));
}
}
if(g.length > 0)
G.push(join(...g));
return G;
};
}
guide Straight(... guide[])=operator --;
guide Spline(... guide[])=operator ..;
interpolate Hermite(splinetype splinetype)
{
return new guide(... guide[] a) {
int n=a.length;
if(n == 0) return nullpath;
real[] x,y;
guide G;
for(int i=0; i < n; ++i) {
guide g=a[i];
int m=size(g);
if(m == 0) continue;
pair z=point(g,0);
x.push(z.x);
y.push(z.y);
if(m > 1) {
G=G..hermite(x,y,splinetype) & g;
pair z=point(g,m);
x=new real[] {z.x};
y=new real[] {z.y};
}
}
return G & hermite(x,y,splinetype);
};
}
interpolate Hermite=Hermite(Spline);
guide graph(picture pic=currentpicture, real f(real), real a, real b,
int n=ngraph, real T(real)=identity, interpolate join=operator --)
{
if(T == identity)
return graph(join)(new pair(real x) {
return (x,pic.scale.y.T(f(pic.scale.x.Tinv(x))));},
pic.scale.x.T(a),pic.scale.x.T(b),n);
else
return graph(join)(new pair(real x) {
return Scale(pic,(T(x),f(T(x))));},
a,b,n);
}
guide[] graph(picture pic=currentpicture, real f(real), real a, real b,
int n=ngraph, real T(real)=identity,
bool3 cond(real), interpolate join=operator --)
{
if(T == identity)
return graph(join,cond)(new pair(real x) {
return (x,pic.scale.y.T(f(pic.scale.x.Tinv(x))));},
pic.scale.x.T(a),pic.scale.x.T(b),n);
else
return graph(join,cond)(new pair(real x) {
return Scale(pic,(T(x),f(T(x))));},
a,b,n);
}
guide graph(picture pic=currentpicture, real x(real), real y(real), real a,
real b, int n=ngraph, real T(real)=identity,
interpolate join=operator --)
{
if(T == identity)
return graph(join)(new pair(real t) {return Scale(pic,(x(t),y(t)));},a,b,n);
else
return graph(join)(new pair(real t) {
return Scale(pic,(x(T(t)),y(T(t))));
},a,b,n);
}
guide[] graph(picture pic=currentpicture, real x(real), real y(real), real a,
real b, int n=ngraph, real T(real)=identity, bool3 cond(real),
interpolate join=operator --)
{
if(T == identity)
return graph(join,cond)(new pair(real t) {return Scale(pic,(x(t),y(t)));},
a,b,n);
else
return graph(join,cond)(new pair(real t) {
return Scale(pic,(x(T(t)),y(T(t))));},
a,b,n);
}
guide graph(picture pic=currentpicture, pair z(real), real a, real b,
int n=ngraph, real T(real)=identity, interpolate join=operator --)
{
if(T == identity)
return graph(join)(new pair(real t) {return Scale(pic,z(t));},a,b,n);
else
return graph(join)(new pair(real t) {
return Scale(pic,z(T(t)));
},a,b,n);
}
guide[] graph(picture pic=currentpicture, pair z(real), real a, real b,
int n=ngraph, real T(real)=identity, bool3 cond(real),
interpolate join=operator --)
{
if(T == identity)
return graph(join,cond)(new pair(real t) {return Scale(pic,z(t));},a,b,n);
else
return graph(join,cond)(new pair(real t) {
return Scale(pic,z(T(t)));
},a,b,n);
}
string conditionlength="condition array has different length than data";
void checkconditionlength(int x, int y)
{
checklengths(x,y,conditionlength);
}
guide graph(picture pic=currentpicture, pair[] z, interpolate join=operator --)
{
int i=0;
return graph(join)(new pair(real) {
pair w=Scale(pic,z[i]);
++i;
return w;
},0,0,z.length-1);
}
guide[] graph(picture pic=currentpicture, pair[] z, bool3[] cond,
interpolate join=operator --)
{
int n=z.length;
int i=0;
pair w;
checkconditionlength(cond.length,n);
bool3 condition(real) {
bool3 b=cond[i];
if(b != false) w=Scale(pic,z[i]);
++i;
return b;
}
return graph(join,condition)(new pair(real) {return w;},0,0,n-1);
}
guide graph(picture pic=currentpicture, real[] x, real[] y,
interpolate join=operator --)
{
int n=x.length;
checklengths(n,y.length);
int i=0;
return graph(join)(new pair(real) {
pair w=Scale(pic,(x[i],y[i]));
++i;
return w;
},0,0,n-1);
}
guide[] graph(picture pic=currentpicture, real[] x, real[] y, bool3[] cond,
interpolate join=operator --)
{
int n=x.length;
checklengths(n,y.length);
int i=0;
pair w;
checkconditionlength(cond.length,n);
bool3 condition(real) {
bool3 b=cond[i];
if(b != false) w=Scale(pic,(x[i],y[i]));
++i;
return b;
}
return graph(join,condition)(new pair(real) {return w;},0,0,n-1);
}
// Connect points in z into segments corresponding to consecutive true elements
// of b using interpolation operator join.
path[] segment(pair[] z, bool[] cond, interpolate join=operator --)
{
checkconditionlength(cond.length,z.length);
int[][] segment=segment(cond);
return sequence(new path(int i) {return join(...z[segment[i]]);},
segment.length);
}
pair polar(real r, real theta)
{
return r*expi(theta);
}
guide polargraph(picture pic=currentpicture, real r(real), real a, real b,
int n=ngraph, interpolate join=operator --)
{
return graph(join)(new pair(real theta) {
return Scale(pic,polar(r(theta),theta));
},a,b,n);
}
guide polargraph(picture pic=currentpicture, real[] r, real[] theta,
interpolate join=operator--)
{
int n=r.length;
checklengths(n,theta.length);
int i=0;
return graph(join)(new pair(real) {
pair w=Scale(pic,polar(r[i],theta[i]));
++i;
return w;
},0,0,n-1);
}
void errorbar(picture pic, pair z, pair dp, pair dm, pen p=currentpen,
real size=0)
{
real dmx=-abs(dm.x);
real dmy=-abs(dm.y);
real dpx=abs(dp.x);
real dpy=abs(dp.y);
if(dmx != dpx) draw(pic,Scale(pic,z+(dmx,0))--Scale(pic,z+(dpx,0)),p,
Bars(size));
if(dmy != dpy) draw(pic,Scale(pic,z+(0,dmy))--Scale(pic,z+(0,dpy)),p,
Bars(size));
}
void errorbars(picture pic=currentpicture, pair[] z, pair[] dp, pair[] dm={},
bool[] cond={}, pen p=currentpen, real size=0)
{
if(dm.length == 0) dm=dp;
int n=z.length;
checklengths(n,dm.length);
checklengths(n,dp.length);
bool all=cond.length == 0;
if(!all)
checkconditionlength(cond.length,n);
for(int i=0; i < n; ++i) {
if(all || cond[i])
errorbar(pic,z[i],dp[i],dm[i],p,size);
}
}
void errorbars(picture pic=currentpicture, real[] x, real[] y,
real[] dpx, real[] dpy, real[] dmx={}, real[] dmy={},
bool[] cond={}, pen p=currentpen, real size=0)
{
if(dmx.length == 0) dmx=dpx;
if(dmy.length == 0) dmy=dpy;
int n=x.length;
checklengths(n,y.length);
checklengths(n,dpx.length);
checklengths(n,dpy.length);
checklengths(n,dmx.length);
checklengths(n,dmy.length);
bool all=cond.length == 0;
if(!all)
checkconditionlength(cond.length,n);
for(int i=0; i < n; ++i) {
if(all || cond[i])
errorbar(pic,(x[i],y[i]),(dpx[i],dpy[i]),(dmx[i],dmy[i]),p,size);
}
}
void errorbars(picture pic=currentpicture, real[] x, real[] y,
real[] dpy, bool[] cond={}, pen p=currentpen, real size=0)
{
errorbars(pic,x,y,0*x,dpy,cond,p,size);
}
// Return a vector field on path g, specifying the vector as a function of the
// relative position along path g in [0,1].
picture vectorfield(path vector(real), path g, int n, bool truesize=false,
pen p=currentpen, arrowbar arrow=Arrow,
margin margin=PenMargin)
{
picture pic;
for(int i=0; i < n; ++i) {
real x=(n == 1) ? 0.5 : i/(n-1);
if(truesize)
draw(relpoint(g,x),pic,vector(x),p,arrow);
else
draw(pic,shift(relpoint(g,x))*vector(x),p,arrow,margin);
}
return pic;
}
real maxlength(pair a, pair b, int nx, int ny)
{
return min((b.x-a.x)/nx,(b.y-a.y)/ny);
}
// return a vector field over box(a,b).
picture vectorfield(path vector(pair), pair a, pair b,
int nx=nmesh, int ny=nx, bool truesize=false,
real maxlength=truesize ? 0 : maxlength(a,b,nx,ny),
bool cond(pair z)=null, pen p=currentpen,
arrowbar arrow=Arrow, margin margin=PenMargin)
{
picture pic;
real dx=1/nx;
real dy=1/ny;
bool all=cond == null;
real scale;
if(maxlength > 0) {
real size(pair z) {
path g=vector(z);
return abs(point(g,size(g)-1)-point(g,0));
}
real max=size(a);
for(int i=0; i <= nx; ++i) {
real x=interp(a.x,b.x,i*dx);
for(int j=0; j <= ny; ++j)
max=max(max,size((x,interp(a.y,b.y,j*dy))));
}
scale=max > 0 ? maxlength/max : 1;
} else scale=1;
for(int i=0; i <= nx; ++i) {
real x=interp(a.x,b.x,i*dx);
for(int j=0; j <= ny; ++j) {
real y=interp(a.y,b.y,j*dy);
pair z=(x,y);
if(all || cond(z)) {
path g=scale(scale)*vector(z);
if(truesize)
draw(z,pic,g,p,arrow);
else
draw(pic,shift(z)*g,p,arrow,margin);
}
}
}
return pic;
}
// True arc
path Arc(pair c, real r, real angle1, real angle2, bool direction,
int n=nCircle)
{
angle1=radians(angle1);
angle2=radians(angle2);
if(direction) {
if(angle1 >= angle2) angle1 -= 2pi;
} else if(angle2 >= angle1) angle2 -= 2pi;
return shift(c)*polargraph(new real(real t){return r;},angle1,angle2,n,
operator ..);
}
path Arc(pair c, real r, real angle1, real angle2, int n=nCircle)
{
return Arc(c,r,angle1,angle2,angle2 >= angle1 ? CCW : CW,n);
}
path Arc(pair c, explicit pair z1, explicit pair z2, bool direction=CCW,
int n=nCircle)
{
return Arc(c,abs(z1-c),degrees(z1-c),degrees(z2-c),direction,n);
}
// True circle
path Circle(pair c, real r, int n=nCircle)
{
return Arc(c,r,0,360,n)&cycle;
}