vectorgraphics / asymptote

2D & 3D TeX-Aware Vector Graphics Language
https://asymptote.sourceforge.io/
GNU General Public License v3.0
556 stars 93 forks source link

Cannot fill noncyclic contours #263

Closed justonlyasy closed 2 years ago

justonlyasy commented 3 years ago

I want to reproduce this image in Asymptote but ... qymGO Here is my code,

import graph;
import palette;
import contour;

size(350);

pair a=(-6,-6);
pair b=(6,6);

real f(real x, real y) {return (x/5)^2+(y/3)^2;}

int N=100;
int Divs=15;

bounds range=bounds(0,5.1);

real[] Cvals=uniform(range.min,range.max,Divs);
guide[][] g=contour(f,a,b,Cvals,N);
pen[] Palette=quantize(BWRainbow(),Divs);

pen[][] interior=interior(g,extend(Palette,grey,black));
fill(g,interior);
draw(g);
limits(a-(0.3,0.3),b+(0.3,0.3));
xaxis("$x$",BottomTop,LeftTicks(Size=4,Ticks=new real[]{-6,-4,-2,0,2,4,6}));
yaxis("$y$",LeftRight,RightTicks(Size=4,Ticks=new real[]{-6,-4,-2,0,2,4,6}));
shipout(bbox(mm,invisible));

Capture

Can Asymptote fill for uncyclic contours like my image?

yangxing844 commented 3 years ago

maybe you can try to make all the cycle and fill them first and then use clip command to clip the part you want?

import graph;
import palette;
import contour;

size(350);

pair a=(-6,-6);
pair b=(6,6);

real f(real x, real y) {return (x/5)^2+(y/3)^2;}

int N=100;
int Divs=15;

bounds range=bounds(0,1.1);

real[] Cvals=uniform(range.min,range.max,Divs);
guide[][] g=contour(f,a,b,Cvals,N);
pen[] Palette=quantize(BWRainbow(),Divs);

pen[][] interior=interior(g,extend(Palette,grey,black));
fill(g,interior);
draw(g);

clip(box((-2.6,-2.6),(2.6,2.6)));
xaxis("$x$",BottomTop,LeftTicks(Size=4,Ticks=new real[]{-6,-4,-2,0,2,4,6}));
yaxis("$y$",LeftRight,RightTicks(Size=4,Ticks=new real[]{-6,-4,-2,0,2,4,6}));

this is the output learn

johncbowman commented 2 years ago

The suggested solution works in this instance but we still need a general solution for cases that cannot be extended to closed contours (for example, horizontal contour lines).

This is actually a very old issue that got postponed when we transitioned from sourceforge to github: https://sourceforge.net/p/asymptote/bugs/84/

johncbowman commented 2 years ago

Good news: 13 years after the original bug report was submitted, I have finally found a much simpler solution: simply quantize a color density image (see imagecontour.asy). If you draw the contour lines wide enough (or draw the image at high-enough resolution), pixelization artifacts will be hidden and what results gives the illusion of vector graphics without the complexity of trying to fill noncyclic (unclosed) curves. The quantized image is less accurate but more appealing than the unquantized one (compare the attached images), especially for pedagogical purposes.

I will commit an update tomorrow that supports this functionality and then close this issue. I may replace fillcontour.asy (which has caused confusion, due to this unresolved issue) with the quantized example, even though the name fillcontour is perhaps a bit misleading.

imagecontourstep

imagecontour

justonlyasy commented 2 years ago

You can review fillcontour.asy to remove duplicate code.

justonlyasy commented 2 years ago

I cannot get a desired output, can you give me a suggestion? The contour lines is not same with...

import graph;
import palette;
import contour;

size(350);

pair a=(-6,-6);
pair b=(6,6);

real f(real x, real y) {return (x/5)^2+(y/3)^2;}

int N=200;
int Divs=15;
int divs=1;
int n=Divs*divs;

defaultpen(1bp);
pen Tickpen=black;
pen tickpen=gray+0.5*linewidth(currentpen);
pen[] Palette=quantize(BWRainbow(),n);

bounds range=image(f,Range(0,5.1),a,b,600,Palette,n);

real[] Cvals=uniform(range.min,range.max,Divs);

draw(contour(f,a,b,Cvals,N),Tickpen+squarecap+beveljoin);

limits(a-(0.3,0.3),b+(0.3,0.3));
xaxis("$x$",BottomTop,LeftTicks(Size=4,Ticks=new real[]{-6,-4,-2,0,2,4,6}));
yaxis("$y$",LeftRight,RightTicks(Size=4,Ticks=new real[]{-6,-4,-2,0,2,4,6}));

image

johncbowman commented 2 years ago

Your color density plot was not produced on the full data Range. Start with the example https://asymptote.sourceforge.io/gallery/2Dgraphs/fillcontour.asy and adapt it to your function (keeping only the major contours):

test

import graph;
import palette;
import contour;

size(350,350);

pair a=(-6,-6);
pair b=(6,6);

real f(real x, real y) {return (x/5)^2+(y/3)^2;}

int N=200;
int Divs=15;

defaultpen(1bp);
pen Tickpen=black;
pen[] Palette=quantize(BWRainbow(),Divs);

bounds range=image(f,a,b,3N,Palette,Divs);

real[] Cvals=uniform(range.min,range.max,Divs);
draw(contour(f,a,b,Cvals,N,operator --),Tickpen+squarecap+beveljoin);

xaxis("$x$",BottomTop,LeftTicks(Size=4,Ticks=new real[]{-6,-4,-2,0,2,4,6}));
yaxis("$y$",LeftRight,RightTicks(Size=4,Ticks=new real[]{-6,-4,-2,0,2,4,6}));
johncbowman commented 2 years ago

The contour line caps in this example could be improved by changing squarecap to extendcap and slightly cropping. You can also force the ticks to be drawn above the color density plot:

test
import graph;
import palette;
import contour;

size(350,350);

pair a=(-6,-6);
pair b=(6,6);

real f(real x, real y) {return (x/5)^2+(y/3)^2;}

int N=200;
int Divs=15;

defaultpen(1bp);
pen Tickpen=black;
pen[] Palette=quantize(BWRainbow(),Divs);

bounds range=image(f,a,b,3N,Palette,Divs);

real[] Cvals=uniform(range.min,range.max,Divs);
draw(contour(f,a,b,Cvals,N,operator --),Tickpen+extendcap+beveljoin);

pair eps=(1,1)*0.02;
limits(a+eps,b-eps);
crop();

xaxis("$x$",BottomTop,LeftTicks(Size=4,Ticks=new real[]{-6,-4,-2,0,2,4,6}),above=true);
yaxis("$y$",LeftRight,RightTicks(Size=4,Ticks=new real[]{-6,-4,-2,0,2,4,6}),above=true);
justonlyasy commented 2 years ago

Excuse me, the last curiosity.

This code runs approximately 98% in my desired picture

// settings.render=8;
import graph;
import palette;
import contour;

size(350,350);

pair a=(-6,-6);
pair b=(6,6);

real f(real x, real y) {return (x/5)^2+(y/3)^2;}

int N=200;
int Divs=16;

defaultpen(1bp);
pen Tickpen=black;
pen[] Palette=quantize(BWRainbow(),Divs);

bounds range=image(f,a,b,3N,Palette,Divs);

real[] Cvals=uniform(range.min,range.max,Divs);
draw(contour(f,a,b,Cvals,N),Tickpen+squarecap+beveljoin);

crop(); // before the limits command.
limits(a-(0.3,0.3),b+(0.3,0.3));

xaxis("$x$",BottomTop,LeftTicks(Size=4,Ticks=new real[]{-6,-4,-2,0,2,4,6}));
yaxis("$y$",LeftRight,RightTicks(Size=4,Ticks=new real[]{-6,-4,-2,0,2,4,6}));

except for

workspace (1)

Does this work normally? Can we delete them?

johncbowman commented 2 years ago

I don't know what 98% refers to.

Those circled marks are contour lines. If you don't want them, you can crop them out. The crop command has to go after the limits command and the limits command should reduce, not expand the range in order for this to work. See the example I already posted. Putting crop before the limits command has no effect.

johncbowman commented 2 years ago

If you are simply trying to get (-6,-6) and (6,6) to show up on your axes, here's how (or if for some reason you want a gap around the colour density image, you can use the commented draw commands instead):

test

import graph;
import palette;
import contour;

size(350,350);

pair a=(-6,-6);
pair b=(6,6);

real f(real x, real y) {return (x/5)^2+(y/3)^2;}

int N=200;
int Divs=16;

defaultpen(1bp);
pen Tickpen=black;
pen[] Palette=quantize(BWRainbow(),Divs);

bounds range=image(f,a,b,3N,Palette,Divs);

real[] Cvals=uniform(range.min,range.max,Divs);
draw(contour(f,a,b,Cvals,N),Tickpen+extendcap+beveljoin);

pair eps=(1,1)*0.03;
limits(a+eps,b-eps);
crop();

draw(a,invisible);
draw(b,invisible);

//draw(a*1.05,invisible);
//draw(b*1.05,invisible);

xaxis("$x$",BottomTop,LeftTicks(Size=4),above=true);
yaxis("$y$",LeftRight,RightTicks(Size=4),above=true);
justonlyasy commented 2 years ago

Done, 99.99%... Thanks again.

import graph;
import palette;
import contour;

size(350,350);

pair a=(-6,-6);
pair b=(6,6);

real f(real x, real y) {return (x/5)^2+(y/3)^2;}

int N=200;
int Divs=16;

defaultpen(.9bp);
pen Tickpen=black;
pen[] Palette=quantize(BWRainbow(),Divs);

bounds range=image(f,a,b,3N,Palette,Divs);

real[] Cvals=uniform(range.min,range.max,Divs);

Cvals.pop(); // <--- This command solves "Those circled marks are contour lines".

draw(contour(f,a,b,Cvals,N),Tickpen+extendcap+beveljoin);

crop(); // I don't know the reason "The crop command has to go after the limits command" but this works as expected.
limits(a-(0.3,0.3),b+(0.3,0.3));
draw(box(a,b),cyan+opacity(.5));

xaxis("$x$",BottomTop,LeftTicks(Size=4,size=2));
yaxis("$y$",LeftRight,RightTicks(Size=4,size=2));

image

johncbowman commented 2 years ago

The crop command, used properly, would fix the flaw shown in your image. It belongs after the call to limits. The result below looks much better; you'll need to use Asympote version 2.75.

test

import graph;
import palette;
import contour;

size(350,350);

settings.gsOptions="-r1200x1200";

pair a=(-6,-6);
pair b=(6,6);

real f(real x, real y) {return (x/5)^2+(y/3)^2;}

int N=200;
int Divs=16;

defaultpen(.9bp);
pen Tickpen=black;
pen[] Palette=quantize(BWRainbow(),Divs);

bounds range=image(f,a,b,3N,Palette,Divs);

real[] Cvals=uniform(range.min,range.max,Divs);

Cvals.pop(); // Optionally remove final contour line

draw(contour(f,a,b,Cvals,N),Tickpen+extendcap+beveljoin);

pair eps=(1,1)*0.03;
limits(a+eps,b-eps);
crop();

draw(box(a+eps,b-eps),cyan+opacity(0.5));

pair gap=(1,1)*0.3;
draw(a-gap,invisible);
draw(b+gap,invisible);

xaxis("$x$",BottomTop,LeftTicks(Size=4,size=2),above=true);
yaxis("$y$",LeftRight,RightTicks(Size=4,size=2),above=true);
justonlyasy commented 2 years ago

With your new code, when I comment draw(box(a+eps,b-eps),cyan+opacity(0.5)); and see the svg output on http://asymptote.ualberta.ca/. Is it normal? (For my computer, pdf and png are normal.)