Is there a way to plot an arc with given start, centre, end point in gnuplot? - gnuplot

The final goal is to plot a contour path in Cartesian space existing of simple element types (only lines + arcs/circles). The parameter of the circular element is presented by a start, centre and end point. I have no influence, whether the arc is fully closed (=ellipsis or circle) or only a quarter, half a circle or something in between. It's given by a file.
I found already the circle command, but there is only a begin/end angle provided.
I know, that's possible to calculate the angles out of the 3 points, but
i'm looking for a smart command like CAD-applications do with start, centre, end.
Thanks for you thoughts.

Two ways come to my mind for plotting arcs between 3 given points where one of them is the center point.
You explicitely asked for ellipses, so the second approach might be the one for you. Nevertheless, because of its simplicity I will show the simple rotating vector approach as well.
Both approaches work easiest if you have your data in a datablock (check this: gnuplot: load datafile 1:1 into datablock) in order to extract the point coordinates from a certain data line.
1. Rotating & scaling vectors:
a simple way which will always work (but will not necessarily give ellipses): rotate the first vector towards the second vector in the given rotation direction and linearly scale its length. This will result in circle arcs when the two vectors have identical length. Depending on the angles and lengths of the two vectors, it might result in ellipses or spirals.
2. Calculate suitable ellipses:
a bit more complicated is plotting ellipses. I'm not sure, but I guess with the above 3 given points you can draw an infinite number of ellipses through these points. The restriction in the example below is that the longer vector will be taken as the larger elliptic axis. The ellipse will be scaled and rotated such that the endpoint of the smaller vector will be on the ellipse. If the angle difference of the vectors is 0 degrees it will be interpreted as 360 degrees. However, it will be impossible to find an ellipse if the two vectors have an angle difference of 0, 180 or 360 degrees and at the same time different lengths. The code below will nevertheless draw some arcs.
I hope there is no bug in the code and maybe it can be simplified. Suggestions are welcome.
Data: SO57408781_Arcs.dat
File without headerline. Columns: x0 y0 x1 y1 x2 y2 rotation_direction
-7 7 -5 7 -5 7 +1
0 7 2 7 0 9 +1
7 7 9 7 5 7 +1
-4 0 0 4 -2 -2 +1
5 0 8 0 5 5 +1
9 -2 0 -5 7 -7 +1
-7 -7 -5 -7 -4 -9 +1
Code:
### draw ars/ellipses with center point and startpoint to endpoint
reset session
set size square
set angle degrees
FileToDatablock(f,d) = GPVAL_SYSNAME[1:7] eq "Windows" ? \
sprintf('< echo %s ^<^<EOD & type "%s"',d,f) : \
sprintf('< echo "\%s <<EOD" & cat "%s"',d,f) # Linux/MacOS
FILE = 'SO57408781_Arcs.dat'
# file structure
# x0 y0 x1 y1 x2 y2 rotation_direction
load FileToDatablock(FILE,'$Arcs')
# value extraction from datablock
x(i,n) = word($Arcs[i],2*n+1)
y(i,n) = word($Arcs[i],2*n+2)
Direction(i) = word($Arcs[i],7) # rotation direction +1=CCW, -1=CW
# length and angles
L(dx,dy) = sqrt(dx**2 + dy**2)
Angle(x0,y0,x1,y1) = (_dx=x1-x0, _dy=y1-y0, _L=sqrt(_dx**2 + _dy**2), _L==0 ? NaN : \
(_dy>=0 ? acos(_dx/_L) : 360-acos(_dx/_L) ))
# get x,y vector lengths, angles and rotation direction
getXYVAD(n) = (x0=x(n,0), y0=y(n,0), x1=x(n,1), y1=y(n,1), x2=x(n,2), y2=y(n,2), \
v1=L(x1-x0,y1-y0), v2=L(x2-x0,y2-y0), \
a1=Angle(x0,y0,x1,y1), a2=Angle(x0,y0,x2,y2), rd=Direction(n) )
# calculate parameters for the arc
getParamsA(i) = (ca=a2>a1, cd=rd>0, a12=(360*(cd && ca ? 0 : 1) + (a2-a1)*(cd?1:-1))*(cd?1:-1) )
xPosA(i,t) = x0 + (v1*(1-t) + v2*t)*cos(a1+a12*t)
yPosA(i,t) = y0 + (v1*(1-t) + v2*t)*sin(a1+a12*t)
# calculate parameters for the ellipse
getParamsE(n) = (v1>=v2 ? (va=v1, vb=v2, aa=a1, ab=a2) : (va=v2, vb=v1, aa=a2, ab=a1), \
ca = a2>=a1, cd = rd>0, cv = v1>=v2, \
a12 = 360*(a1-a2!=0 && ca==cd ? 0 : cd==cv?1:-1) + (cv?1:-1)*(a2-a1), \
a3 = asin(sqrt(va**2 - (vb*cos(a12))**2) / va), \
a = (abs(a12)<90 ? a3 : abs(a12)<180 ? 180-a3 : abs(a12)<270 ? 180+a3 : 360-a3)*sgn(a12), \
Ra=va, Rb = (abs(a12)==180 || abs(a12)==360) ? Ra : va*vb*abs(sin(a12))/sqrt(va**2 - (vb*cos(a12))**2))
xPosE(i,t) = x0+Ra*cos(a*t)*cos(aa)-Rb*sin(a*t)*sin(aa)
yPosE(i,t) = y0+Ra*cos(a*t)*sin(aa)+Rb*sin(a*t)*cos(aa)
set xrange[-11:11]
set yrange[-11:11]
set key out center top noautotitle
set multiplot layout 1,2
set key title 'Rotating \& scaling vectors'
plot for [i=1:|$Arcs|] [t=0:1] '+' u (getXYVAD(i), getParamsA(i), xPosA(i,t)):(yPosA(i,t)) w l lc "web-green",\
$Arcs u 1:2:($3-$1):($4-$2) w vec lc "blue", \
'' u 1:2:($5-$1):($6-$2) w vec lc "red", \
'' u 1:2 w p pt 7 lc "black" ti "Center point", \
'' u 3:4 w p pt 7 lc "blue" ti "Start point", \
'' u 5:6 w p pt 7 lc "red" ti "End point", \
'' u 1:2:($0+1) w labels offset -0.7,-0.7
set key title 'Drawing ellipses'
plot for [i=1:|$Arcs|] [t=0:1] '+' u (getXYVAD(i), getParamsE(i), xPosE(i,t)):(yPosE(i,t)) w l lc "web-green",\
$Arcs u 1:2:($3-$1):($4-$2) w vec lc "blue", \
'' u 1:2:($5-$1):($6-$2) w vec lc "red", \
'' u 1:2 w p pt 7 lc "black" ti "Center point", \
'' u 3:4 w p pt 7 lc "blue" ti "Start point", \
'' u 5:6 w p pt 7 lc "red" ti "End point", \
'' u 1:2:($0+1) w labels offset -0.7,-0.7
unset multiplot
### end of code
Result:

Related

Gnuplot link all points on edges and fill the area

I have x-y data like this:
133.322 1073
399.966 1073
799.932 1073
1333.22 1073
133.322 1078
399.966 1078
799.932 1078
1333.22 1078
133.322 1083
399.966 1083
799.932 1083
1333.22 1083
133.322 1088
399.966 1088
799.932 1088
1333.22 1088
133.322 1093
399.966 1093
799.932 1093
1333.22 1093
133.322 1098
399.966 1098
799.932 1098
1333.22 1098
133.322 1103
399.966 1103
799.932 1103
1333.22 1103
1333.22 1108
What I what to get is like this:
or
How can I do it with Gnuplot? I need to plot many other data in this way. So a common solution would be great. Thank you very much.
The following is an attempt to draw an outline around random points.
Mathematicians probably might have ideas for a better procedure.
The procedure:
determine the bounding box for the datapoints, i.e. the extremal points for left,top,right,bottom which typically define a tetragon, but could also be a trigon or 2-gon
for each "quadrant", i.e. left/top, top/right, right/bottom, bottom/left, check whether the other points are outside of this polygon via determining the curvature (or orientation) between the two extreme points and a given point.
sort the outside points with ascending or descending x depending on the quadrant, basically sort the outline path "clockwise". Problem: gnuplot has no sort function. You could use an external program, but I generally prefer a gnuplot-only solution which makes it a bit longer.
remove all points which lead to a concave outline, this might need several iterations.
I would be happy to see simpler solutions. Suggestions for improvements are welcome.
Code:
### find and fill outline of random distributed points
reset session
# create some random test data
set samples 50
set table $Data
plot '+' u (invnorm(rand(0))*100):(invnorm(rand(0))*100) w table
unset table
# if you have a file, skip the above test data section
# and uncomment the following 3 lines and put your filename
#set table $Data
# plot "myData.dat" u 1:2 w table
#unset table
# get the coordinates of the leftmost, topmost, rightmost, and bottommost points
# in case of multiple points with leftmost x-value take the one with the topmost y-value
# similar of the other extreme points
#
# function to initialize extreme point cordinates
Init(x,y) = (Lx=column(x),Ly=column(y), \
Tx=column(x),Ty=column(y), \
Rx=column(x),Ry=column(y), \
Bx=column(x),By=column(y) )
# find extreme point coordinates
GetExtremes(x,y) = (Lx>column(x) ? (Lx=column(x),Ly=column(y)) : \
(Lx==column(x) && Ly<column(y) ? Ly=column(y) : NaN), \
Ty<column(y) ? (Tx=column(x),Ty=column(y)) : \
(Ty==column(y) && Tx<column(x) ? Tx=column(x) : NaN), \
Rx<column(x) ? (Rx=column(x),Ry=column(y)) : \
(Rx==column(x) && Ry>column(y) ? Ry=column(y) : NaN), \
By>column(y) ? (Bx=column(x),By=column(y)) : \
(By==column(y) && Bx>column(x) ? Bx=column(x) : NaN))
set table $Dummy
plot n=0 $Data u (n==0?(Init(1,2),n=n+1):GetExtremes(1,2)) w table
unset table
# put extremal points into array 1=left,2=top,3=right,4=bottom
# and 5=left again for closed line of the extremal polygon
array EPx[5] = [Lx,Tx,Rx,Bx,Lx]
array EPy[5] = [Ly,Ty,Ry,By,Ly]
# (re-)define sgn() function such that sgn(NaN) gives NaN (gnuplot would give 0)
mysgn(x) = x==x ? sgn(x) : NaN
# function to determine orientation of the curves through 3 points A,B,C
# output: -1=clockwise; +1=counterclockwise (mathematical positive); NaN if one point is NaN
Orientation(xa,ya,xb,yb,xc,yc) = mysgn((xb-xa)*(yc-ya)-(xc-xa)*(yb-ya))
# determine outside points for all 4 "quadrants"
# a point is outside
set datafile missing NaN # important for removing points from outline
array SinglePoint[1] # dummy array for plotting a single point
array EmptyLines[2] # dummy array for plotting two emtpy lines
array OutsidePoints[4] # number of outside points for each "quadrant"
set table $OutsidePoints
do for [n=1:4] {
i=0
plot SinglePoint u (EPx[n]):(EPy[n]) w table
plot $Data u 1:(Orientation(EPx[n],EPy[n],$1,$2,EPx[n%4+1],EPy[n%4+1]) < 0 ? (i=i+1,$2):NaN) w table
plot SinglePoint u (EPx[n%4+1]):(EPy[n%4+1]) w table
plot EmptyLines u ("") w table
OutsidePoints[n] = i+2
}
unset table
# sort the outside points of the 4 "quadrants" by in- or decreasing x
# since gnuplot has no built-in sort function it's a bit lengthy
AllOutsidePoints = (sum [i=1:4] OutsidePoints[i])-3 # reduce by 3 double extremal points
array Xall[AllOutsidePoints]
array Yall[AllOutsidePoints]
idx = 0
do for [n=1:4] {
array X[OutsidePoints[n]] # initialize array
array Y[OutsidePoints[n]] # initialize array
set table $Dummy
plot $OutsidePoints u (X[$0+1]=$1, Y[$0+1]=$2) index n-1 w table
unset table
# Bubblesort, inefficient but simple
SortDirection = n<3 ? +1 : -1 # n=1,2: +1=ascending, n=3,4: -1=descending
do for [j=OutsidePoints[n]:2:-1] {
do for [i=1:j-1] {
if ( X[i]*SortDirection > X[i+1]*SortDirection) {
tmp=X[i]; X[i]=X[i+1]; X[i+1]=tmp;
tmp=Y[i]; Y[i]=Y[i+1]; Y[i+1]=tmp;
}
}
}
# append array to datablock
set print $Outline append
do for [i=1:|X|-(n==4?0:1)] { print sprintf("%g %g",X[i],Y[i]) }
set print
# create an array for all sorted outside datapoints
last = |X|-(n==4?0:1)
do for [i=1:last] {
Xall[idx+i]=X[i]
Yall[idx+i]=Y[i]
}
idx=idx+last
}
# function checks convexity: result: >0: concave, 1=convex
# Orientation: -1=clockwise, 0=straight, +1=counterclockwise, NaN=undefined point
# function actually doesn't depend on n, just to shorten the function call later
CheckConvexity(n) = (Convex=1,sum [i=2:|Xall|-1] ( idx0=i-1, idx1=i, idx2=i+1, Convex=Convex && \
(Orientation(Xall[idx0],Yall[idx0],Xall[idx1],Yall[idx1],Xall[idx2],Yall[idx2])<0)),Convex)
# put array to datablock
set table $Convex
plot Xall u (Xall[$0+1]):(Yall[$0+1]) w table
unset table
# remove concave points (could take several iterations)
Count=0
while (!CheckConvexity(0)) {
Count = Count+1
print sprintf("Iteration %d ...",Count)
# set concave points to NaN
do for [i=2:|Xall|-1] {
idx0=i-1; idx1=i; idx2=i+1
if (Orientation(Xall[idx0],Yall[idx0],Xall[idx1],Yall[idx1],Xall[idx2],Yall[idx2])>=0) {
Yall[idx1] = NaN
}
}
# remove NaN points by plotting it to datablock
set table $Convex
plot Xall u (Xall[$0+1]):(Yall[$0+1]) w table
unset table
# create new X,Y array with reduced size
array Xall[|$Convex|]
array Yall[|$Convex|]
# put datablock into array again for next iteration if necessary
set table $Dummy
plot $Convex u (Xall[$0+1]=$1):(Yall[$0+1]=$2) w table
unset table
}
print sprintf("Convex after %d iterations.",Count)
set object 1 rect from Lx,By to Rx,Ty dt 3
set offsets graph 0.01, graph 0.01, graph 0.01, graph 0.01
set key out top center horizontal
plot \
keyentry w l ls 0 ti "Bounding box", \
$Outline u 1:2 w filledcurves closed lc rgb "green" fs solid 0.1 not noautoscale, \
EPx u (EPx[$0+1]):(EPy[$0+1]) w l lc rgb "black" ti "Extremal polygon", \
$Data u 1:2 w p pt 7 lc rgb "blue" ti "Points inside polygon", \
$OutsidePoints u 1:2 w p pt 7 lc rgb "red" ti "Points outside polygon", \
Xall u (Xall[$0+1]):(Yall[$0+1]) w l lw 1 lc rgb "green" ti "Concave outline", \
$Convex u 1:2 w lp pt 7 lw 2 lc rgb "orange" ti "Convex outline", \
### end of code
Result:
Addition: (second procedure)
I agree with #Christoph that gnuplot is probably not the optimal tool for doing this but nevertheless you can do it :-). Here is a shorter procedure.
The procedure:
search for the bottommost point and if there are several points with bottommost y-value take the one with the rightmost x-coordinate.
from this point calculate the angles a to all other points. Find the point with the smallest difference to a target angle, which at the beginning is aTar=0. If there are several points with the same smallest difference angle take the point with the largest distance dMax. Add this point to the outline.
set the new target angle to the angle of the line between the previous and the new found point.
go again to 2 until you will end up with the first point BxStart,ByStart.
I hope my description is somehow understandable. Although the code is shorter than the first version it might be less efficient because it hast to go multiple times (i.e. number of outline points) through all points. Worst case would be points which are already aligned on a circle.
Code:
### fill outline of random points
reset session
# create some test data
set samples 50
set table $Data
plot '+' u (invnorm(rand(0))*100):(invnorm(rand(0))*100) w table
unset table
### if you have a file, uncomment the following lines and put your filename
#set table $Data
# plot "myData.dat" u 1:2 w table
#unset table
# Angle by dx,dy (range: -90°<= angle < 270°), NaN if dx==dy==0
set angle degrees
Angle(dx,dy) = dx==dx && dy==dy ? dx==0 ? dy==0 ? NaN : sgn(dy)*90 : dx<0 ? 180+atan(dy/dx) : atan(dy/dx) : NaN
# Angle by dx,dy (range: 0°<= angle < 360°), NaN if dx==dy==0
Angle360(dx,dy) = (a_tmp=Angle(dx,dy), a_tmp==a_tmp) ? a_tmp-360*floor(a_tmp/360.) : NaN
# get the coordinates of the bottommost point
# in case of multiple points with bottommost y-coordinate take the one with the rightmost x-coordinate
Init(x,y) = (Bx=column(x),By=column(y))
GetExtremes(x,y) = (By>column(y) ? (Bx=column(x),By=column(y)) : \
(By==column(y) && Bx>column(x) ? Bx=column(x) : NaN))
set table $Dummy
plot $Data u ($0==0?Init(1,2):GetExtremes(1,2)) w table
unset table
print sprintf("Bottommost point: %g,%g:", Bx,By)
# Distance
Dist(x0,y0,x1,y1) = sqrt((x1-x0)**2 + (y1-y0)**2)
# function for getting the next outline point
GetNext(x,y) = (a=Angle360(column(x)-Bx,column(y)-By), aDiff=(a<aTar?a+360-aTar:a-aTar),\
d=Dist(Bx,column(x),By,column(y)), \
aMin>aDiff ? (aNext=a, aMin=aDiff,dMax=d,xNext=column(x),yNext=column(y)) : \
aMin==aDiff ? dMax<d ? (dMax=d, xNext=column(x),yNext=column(y)) : NaN : NaN)
BxStart=Bx; ByStart=By
set print $Outline append
print sprintf("% 9g % 9g",BxStart,ByStart) # add starting point to outline
aTar=0
while 1 { # endless loop
aMin=360; dMax=0
set table $Dummy
plot $Data u (GetNext(1,2)) w table
unset table
print sprintf("% 9g % 9g",xNext,yNext)
Bx=xNext; By=yNext; aTar=aNext
if (xNext==BxStart && yNext==ByStart) { break } # exit loop
}
set print
plot $Data u 1:2 w p pt 7 ti "Datapoints",\
$Outline u 1:2 w l lc rgb "red" ti "Convex Outline"
### end of code
Result:

gnuplot contour plot hatched lines

I'm using gnuplot for contour plot of a several function. This is for optimization problem.
I have 3 functions:
f(x,y)
g1(x,y)
g2(x,y)
both g1(x,y) and g2(x,y) are constraints and would like to plot on top of the contour plot of f(x,y).
Here is the textbook example:
Here is my attempt to replicate it in gnuplot, thanks to #theozh.
### contour lines with labels
reset session
f(x,y)=(x**2+y-11)**2+(x+y**2-7)**2
g1(x,y)=(x-5)**2+y**2
g2(x,y) = 4*x+y
set xrange [0:6]
set yrange [0:6]
set isosample 250, 250
set key outside
set contour base
set cntrparam levels disc 10,30,75,150,300,500,850,1500
unset surface
set table $Contourf
splot f(x,y)
unset table
set contour base
set cntrparam levels disc 26
unset surface
set table $Contourg1
splot g1(x,y)
unset table
set contour base
set cntrparam levels disc 20
unset surface
set table $Contourg2
splot g2(x,y)
unset table
set style textbox opaque noborder
set datafile commentschar " "
plot for [i=1:8] $Contourf u 1:2:(i) skip 5 index i-1 w l lw 1.5 lc var title columnheader(5)
replot $Contourg1 u 1:2:(1) skip 5 index 0 w l lw 4 lc 0 title columnheader(5)
replot $Contourg2 u 1:2:(1) skip 5 index 0 w l lw 4 lc 0 title columnheader(5)
I would like to replicate the textbook picture in the gnuplot example. How to do a hatch mark on the functions g1 and g2, the thick black line in plot above.
#theozh provided an excellent solution below. However, the method doesnot work for steep curves. As an example
reset session
unset key
set size square
g(x,y) = -0.8-1/x**3+y
set xrange [0:4]
set yrange [0:4]
set isosample 250, 250
set key off
set contour base
unset surface
set cntrparam levels disc 0
set table $Contourg
splot g(x,y)
unset table
set angle degree
set datafile commentschar " "
plot $Contourg u 1:2 skip 5 index 0 w l lw 2 lc 0 title columnheader(5)
set style fill transparent pattern 4
replot $Contourg u 1:2:($2+0.2) skip 5 index 0 w filledcurves lc 0 notitle
yields the following figure. Is there a way to use different offsets, for example offset x values for x < 1.3 and for x > 1.3 offset y values. This would yield a much better filled curve. A matlab implementations of what I was looking for can be found here: https://www.mathworks.com/matlabcentral/fileexchange/29121-hatched-lines-and-contours.
In replcating #Ethans program, I get the following, the dashtype is relatively thick compared to #Ethan not sure why, I'm using gnuplot v5.2 and wxt terminal.
When I replicate #theozh code, it works very well except for closed contours, not sure why? see below for example:
f(x,y)=x*exp(-x**2-y**2)+(x**2+y**2)/20
g1(x,y)= x*y/2+(x+2)**2+(y-2)**2/2-2
set xrange [-7:7]
set yrange [-7:7]
set isosample 250, 250
set key outside
set contour base
unset surface
set cntrparam levels disc 4,3.5,3,2.5,2,1.5,1,0.5,0
set table $Contourf
splot f(x,y)
unset table
set cntrparam levels disc 0
set table $Contourg1
splot g1(x,y)
unset table
# create some extra offset contour lines
# macro for setting contour lines
ContourCreate = '\
set cntrparam levels disc Level; \
set table #Output; \
splot #Input; \
unset table'
Level = 0.45
Input = 'g1(x,y)'
Output = '$Contourg1_ext'
#ContourCreate
# Macro for ordering the datapoints of the contour lines which might be split
ContourOrder = '\
stats #DataIn skip 6 nooutput; \
N = STATS_blank-1; \
set table #DataOut; \
do for [i=N:0:-1] { plot #DataIn u 1:2 skip 5 index 0 every :::i::i with table }; \
unset table'
DataIn = '$Contourg1'
DataOut = '$Contourg1_ord'
#ContourOrder
DataIn = '$Contourg1_ext'
DataOut = '$Contourg1_extord'
#ContourOrder
# Macro for reversing a datablock
ContourReverse = '\
set print #DataOut; \
do for [i=|#DataIn|:1:-1] { print #DataIn[i]}; \
set print'
DataIn = '$Contourg1_extord'
DataOut = '$Contourg1_extordrev'
#ContourReverse
# Macro for adding datablocks
ContourAdd = '\
set print #DataOut; \
do for [i=|#DataIn1|:1:-1] { print #DataIn1[i]}; \
do for [i=|#DataIn2|:1:-1] { print #DataIn2[i]}; \
set print'
DataIn1 = '$Contourg1_ord'
DataIn2 = '$Contourg1_extordrev'
DataOut = '$Contourg1_add'
#ContourAdd
set style fill noborder
set datafile commentschar " "
plot \
for [i=1:8] $Contourf u 1:2:(i) skip 5 index i-1 w l lw 1.5 lc var title columnheader(5), \
$Contourg1 u 1:2 skip 5 index 0 w l lw 2 lc 0 title columnheader(5), \
$Contourg1_add u 1:2 w filledcurves fs transparent pattern 5 lc rgb "black" notitle
Another possibility is to use a custom dash pattern, as shown below:
By the way, it is almost never correct to use "replot" to compose a single figure.
# Additional contour levels displaced by 0.2 from the original
set contour base
set cntrparam levels disc 20.2
unset surface
set table $Contourg2d
splot g2(x,y)
unset table
set contour base
set contour base
set cntrparam levels disc 26.2
unset surface
set table $Contourg1d
splot g1(x,y)
unset table
set linetype 101 lc "black" linewidth 5 dashtype (0.5,5)
plot for [i=1:8] $Contourf u 1:2:(i) skip 5 index i-1 w l lw 1.5 lc var title columnheader(5), \
$Contourg1 u 1:2:(1) skip 5 index 0 w l lw 1 lc "black" title columnheader(5), \
$Contourg2 u 1:2:(1) skip 5 index 0 w l lw 1 lc "black" title columnheader(5), \
$Contourg1d u 1:2:(1) skip 5 index 0 w l linetype 101 notitle, \
$Contourg2d u 1:2:(1) skip 5 index 0 w l linetype 101 notitle
Amended to show use of contours offset so that the dashes are only on one side of the line.
Here is the solution you (and I) were hoping for.
You just enter the hatch parameters into a datablock: TiltAngle in degrees (>0°: left side, <0° right side in direction of path), HatchLength and HatchGap in pixels. The procedure has become a bit lengthy but it does what you want. I have tested it with gnuplot 5.2.8 and 5.4.1 and wxt and qt terminal.
What the procedure basically does:
determines the angle between two consecutive points of the data input curve
interpolates datapoints along the curve according to HatchSeparation
Scales everything such that is independent of graph scale and terminal size (this, however, requires a dummy plot of the curves without hatch lines for getting the gnuplot variables GPVAL_X_MAX, GPVAL_X_MIN, GPVAL_TERM_XMAX, GPVAL_TERM_XMIN, GPVAL_Y_MAX, GPVAL_Y_MIN, GPVAL_TERM_YMAX, GPVAL_TERM_YMIN.
Limitations:
does not work (yet) with logarithmic axes
several independent paths need to be separated by two empty lines
if you are using it together with your contour lines you have to make sure that the contour line datapoints are in the right order (see comment in my first answer). Furthermore, gnuplot might generate contour lines which are separated with a single empty line. I can address this if I find some time or/and somebody requests it.
Edit: (completely revised version)
The previous script (to my opinion) was pretty messy and difficult to follow (although nobody complained ;-). I removed the calls to subprocedures and hence the prefixes for variables in the subprocedures and put all in one script, except the test data generation.
Have fun with hatching your lines! Comments and improvements are welcome!
Test data generation: SO57118566_createTestData.gp
### Create some circle test data
FILE = "SO57118566.dat"
set angle degrees
# create some test data
# x y r a0 a1 N
$myCircleParams <<EOD
1.0 0.3 0.6 0 360 120
2.4 0.3 0.6 0 360 120
3.8 0.3 0.6 0 360 120
1.7 -0.3 0.6 0 360 120
3.1 -0.3 0.6 0 360 120
EOD
X(n) = real(word($myCircleParams[n],1)) # center x
Y(n) = real(word($myCircleParams[n],2)) # center y
R(n) = real(word($myCircleParams[n],3)) # radius
A0(n) = real(word($myCircleParams[n],4)) # start angle
A1(n) = real(word($myCircleParams[n],5)) # end angle
N(n) = int(word($myCircleParams[n],6)) # number of samples
set table FILE
do for [i=1:|$myCircleParams|] {
set samples N(i)
plot [A0(i):A1(i)] '+' u (X(i)+R(i)*cos($1)):(Y(i)+R(i)*sin($1))
}
unset table
set size ratio -1
plot FILE u 1:2:-2 w l lc var
### end of script
Strange enough, the previous version worked for gnuplot5.2.0 to 5.2.7 but not for gnuplot>=5.2.8. With this current script it is vice versa, but I haven't yet found out why.
Update:
Finally found why it wasn't working with <=5.2.7. Apparently something with the scaling which has changed between 5.2.7 and 5.2.8. Other terminals than wxt or qt might have different scaling factors.
You need to add/change the lines (already added in the script below):
Factor = GPVAL_VERSION==5.2 && int(GPVAL_PATCHLEVEL)<=7 ? \
GPVAL_TERM eq "wxt" ? 20 : GPVAL_TERM eq "qt" ? 10 : 1 : 1
Rxaupu = (GPVAL_X_MAX-xmin)/(GPVAL_TERM_XMAX-xtmin)*Factor # x ratio axes units to pixel units
Ryaupu = (GPVAL_Y_MAX-ymin)/(GPVAL_TERM_YMAX-ytmin)*Factor # y
Script: (tested with gnuplot 5.2.0, 5.2.7, 5.2.8, 5.4.1)
### Add hatch pattern to a curve
reset session
FILE = "SO57118566.dat"
set size ratio -1 # set same x,y scaling
set angle degree
unset key
# plot path without hatch lines to get the proper gnuplot variables: GPVAL_...
plot FILE u 1:2:-2 w l lc var
# Hatch parameters:
# TiltAngle >0°: left side, <0° right side in direction of path
# HatchLength hatch line length in pixels
# HatchGap separation of hatch lines in pixels
# TA HL HG Color
$myHatchParams <<EOD
-90 10 5 0x0080ff
-30 15 10 0x000000
90 5 3 0xff0000
45 25 12 0xffff00
-60 10 7 0x00c000
EOD
# extract hatch parameters
TA(n) = real(word($myHatchParams[n],1)) # TiltAngle
HL(n) = real(word($myHatchParams[n],2)) # HatchLength
Gpx(n) = real(word($myHatchParams[n],3)) # HatchGap in pixels
Color(n) = int(word($myHatchParams[n],4)) # Color
# terminal constants
xmin = GPVAL_X_MIN
ymin = GPVAL_Y_MIN
xtmin = GPVAL_TERM_XMIN
ytmin = GPVAL_TERM_YMIN
Factor = GPVAL_VERSION==5.2 && int(GPVAL_PATCHLEVEL)<=7 ? \
GPVAL_TERM eq "wxt" ? 20 : GPVAL_TERM eq "qt" ? 10 : 1 : 1
Rxaupu = (GPVAL_X_MAX-xmin)/(GPVAL_TERM_XMAX-xtmin)*Factor # x ratio axes units to pixel units
Ryaupu = (GPVAL_Y_MAX-ymin)/(GPVAL_TERM_YMAX-ytmin)*Factor # y
Angle(dx,dy) = dx==0 && dy==0 ? NaN : atan2(dy,dx) # -180° to +180°, NaN if dx,dy==0
LP(dx,dy) = sqrt(dx**2 + dy**2) # length of path segment
ax2px(x) = (x-xmin)/Rxaupu + xtmin # x axes coordinates to pixel coordinates
ay2py(y) = (y-ymin)/Rxaupu + ytmin # y
px2ax(x) = (x-xtmin)*Rxaupu + xmin # x pixel coordinates to axes coordinates
py2ay(y) = (y-ytmin)*Rxaupu + ymin # y
# create datablock $Path with pixel coordinates and cumulated path length
stats FILE u 0 nooutput # get number of blocks of input file
N = STATS_blocks
set table $Path
do for [i=0:N-1] {
x1 = y1 = NaN
Length = 0
plot FILE u (x0=x1, x1=ax2px($1)):(y0=y1, y1=ay2py($2)): \
(dx=x1-x0, dy=y1-y0, ($0>0?Length=Length+LP(dx,dy):Length)) index i w table
plot '+' u ('') every ::0::1 w table # two empty lines
}
unset table
# create hatch lines table
# resample data in equidistant steps along the length of the path
$Temp <<EOD # datablock $Temp definition required for function definition below
EOD
x0(n) = real(word($Temp[n],1)) # x coordinate
y0(n) = real(word($Temp[n],2)) # y coordinate
r0(n) = real(word($Temp[n],3)) # cumulated path length
ap(n) = Angle(x0(n+1)-x0(n),y0(n+1)-y0(n)) # path angle
ah(n,i) = ap(n)+TA(i+1) # hatch line angle
Frac(n) = (ri-r0(n))/(r0(n+1)-r0(n)) # interpolation along
hsx(n) = (x0(n) + Frac(n)*(x0(n+1)-x0(n))) # x hatch line start point
hsy(n) = (y0(n) + Frac(n)*(y0(n+1)-y0(n))) # y
hex(n,i) = (hsx(n) + HL(i+1)*cos(ah(n,i))) # x hatch line end point
hey(n,i) = (hsy(n) + HL(i+1)*sin(ah(n,i))) # y
# create datblock with hatchlines x,y,dx,dy
set print $HatchLines
do for [i=0:N-1] {
set table $Temp
splot $Path u 1:2:3 index i
unset table
ri = -Gpx(i+1)
do for [j=1:|$Temp|-2] {
if (strlen($Temp[j])==0 || $Temp[j][1:1] eq '#') {print $Temp[j]}
else {
while (ri<r0(j)) {
ri = ri + Gpx(i+1)
print sprintf("%g %g %g %g", \
xs=px2ax(hsx(j)), ys=py2ay(hsy(j)), \
px2ax(hex(j,i))-xs, py2ay(hey(j,i))-ys)
}
}
}
print ""; print "" # two empty lines
}
set print
plot $Path u (px2ax($1)):(py2ay($2)):(Color(column(-2)+1)) w l lc rgb var, \
$HatchLines u 1:2:3:4:(Color(column(-2)+1)) w vec nohead lc rgb var
### end of script
Result:
If you really want to have good hatch marks, you can draw a whole lot of arrows with no heads.
The example below computes the locations and slopes of each hatch mark in the loop making them nearly perpendicular to the drawn line (to numerical accuracy). It also spaces them along the line (again to rudimentary numerical accuracy but for a plot it is more than good enough.
reset
set grid
set sample 1000
set xrange [0:6]
set yrange [0:6]
# First, plot the actual curve
plot 1/log(x)
# Choose a length for your hatch marks, this will
# depend on your axis scale.
Hlength = 0.2
# Choose a distance along the curve for the hatch marks. Again
# will depend on you axis scale.
Hspace = 0.5
# Identify one end of the curve on the plot, set x location for
# first hatch mark.
# For this case, it is when 1/log(x) = 4
x1point = exp(0.25)
y1point = 1/log(x1point)
# Its just easier to guess how many hatch marks you need instead
# of trying to compute the length of the line.
do for [loop=1:14] {
# Next, find the slope of the function at this point.
# If you have the exact derivative, use that.
# This example assumes you perhaps have a user defined funtion
# that is likely too difficult to get a derivative so it
# increments x by a small amount to numerically compute it
slope = (1/log(x1point+0.001)-y1point)/(0.001)
#slopeAng = atan2(slope)
slopeAng = atan2((1/log(x1point+.001)-y1point),0.001)
# Also find the perpendicular to this slope
perp = 1/slope
# Get angle of perp from horizontal
perpAng = atan(perp)
# Draw a small hatch mark at this point
x2point = x1point + Hlength*cos(perpAng)
y2point = y1point - Hlength*sin(perpAng)
# The hatch mark is just an arrow with no heads
set arrow from x1point,y1point to x2point,y2point nohead
# Move along the curve approximately a distance of Hspace
x1point = x1point + Hspace*cos(slopeAng)
y1point = 1/log(x1point)
# loop around to do next hatch mark
}
replot
You will get something like this
Note that you can easily adjust the hatch mark length and the spacing between them. Also, if your x and y axis have significantly different scales, it would not be too hard to scale the x or y length of the arrow so they 'look' like equal lengths.
Edit:
You have the added complication of doing a contour plot. I've completed what you need to do. I resolved your g1 and g2 functions at the contour level you wanted the constraints and named two new functions g1_26 and g2_20 and solved for y for each.
I also discoverd that the hatch marks change sides with the simple program above when the sign of the slope changes so I added the sgn(slope) when calculating the x2 and y2 points of the hatch mark and also added a flip variable so you can easily control which side of the line the hatch marks are drawn. Here is the code:
### contour lines with labels
reset session
f(x,y)=(x**2+y-11)**2+(x+y**2-7)**2
g1(x,y)=(x-5)**2+y**2
g2(x,y) = 4*x+y
set xrange [0:6]
set yrange [0:6]
set isosample 250, 250
set key outside
set contour base
set cntrparam levels disc 10,30,75,150,300,500,850,1500
unset surface
set table $Contourf
splot f(x,y)
unset table
set contour base
set cntrparam levels disc 26
unset surface
set table $Contourg1
splot g1(x,y)
unset table
set contour base
set cntrparam levels disc 20
unset surface
set table $Contourg2
splot g2(x,y)
unset table
set style textbox opaque noborder
set datafile commentschar " "
plot for [i=1:8] $Contourf u 1:2:(i) skip 5 index i-1 w l lw 1.5 lc var title columnheader(5)
replot $Contourg1 u 1:2:(1) skip 5 index 0 w l lw 4 lc 0 title columnheader(5)
replot $Contourg2 u 1:2:(1) skip 5 index 0 w l lw 4 lc 0 title columnheader(5)
###############################
# Flip should be -1 or 1 depending on which side you want hatched.
flip = -1
# put hatches on g1
# Since your g1 constraint is at g1(x,y) = 26, lets
# get new formula for this specific line.
#g1(x,y)=(x-5)**2+y**2 = 26
g1_26(x) = sqrt( -(x-5)**2 + 26)
# Choose a length for your hatch marks, this will
# depend on your axis scale.
Hlength = 0.15
# Choose a distance along the curve for the hatch marks. Again
# will depend on you axis scale.
Hspace = 0.2
# Identify one end of the curve on the plot, set x location for
# first hatch mark.
x1point = 0
y1point = g1_26(x1point)
# Its just easier to guess how many hatch marks you need instead
# of trying to compute the length of the line.
do for [loop=1:41] {
# Next, find the slope of the function at this point.
# If you have the exact derivative, use that.
# This example assumes you perhaps have a user defined funtion
# that is likely too difficult to get a derivative so it
# increments x by a small amount to numerically compute it
slope = (g1_26(x1point+0.001)-y1point)/(0.001)
#slopeAng = atan2(slope)
slopeAng = atan2((g1_26(x1point+.001)-y1point),0.001)
# Also find the perpendicular to this slope
perp = 1/slope
# Get angle of perp from horizontal
perpAng = atan(perp)
# Draw a small hatch mark at this point
x2point = x1point + flip*sgn(slope)*Hlength*cos(perpAng)
y2point = y1point - flip*sgn(slope)*Hlength*sin(perpAng)
# The hatch mark is just an arrow with no heads
set arrow from x1point,y1point to x2point,y2point nohead lw 2
# Move along the curve approximately a distance of Hspace
x1point = x1point + Hspace*cos(slopeAng)
y1point = g1_26(x1point)
# loop around to do next hatch mark
}
###############################
# Flip should be -1 or 1 depending on which side you want hatched.
flip = -1
# put hatches on g2
# Since your g2 constraint is at g2(x,y) = 20, lets
# get new formula for this specific line.
#g2(x,y) = 4*x+y = 20
g2_20(x) = 20 - 4*x
# Choose a length for your hatch marks, this will
# depend on your axis scale.
Hlength = 0.15
# Choose a distance along the curve for the hatch marks. Again
# will depend on you axis scale.
Hspace = 0.2
# Identify one end of the curve on the plot, set x location for
# first hatch mark.
x1point =3.5
y1point = g2_20(x1point)
# Its just easier to guess how many hatch marks you need instead
# of trying to compute the length of the line.
do for [loop=1:32] {
# Next, find the slope of the function at this point.
# If you have the exact derivative, use that.
# This example assumes you perhaps have a user defined funtion
# that is likely too difficult to get a derivative so it
# increments x by a small amount to numerically compute it
slope = (g2_20(x1point+0.001)-y1point)/(0.001)
slopeAng = atan2((g2_20(x1point+.001)-y1point),0.001)
# Also find the perpendicular to this slope
perp = 1/slope
# Get angle of perp from horizontal
perpAng = atan(perp)
# Draw a small hatch mark at this point
x2point = x1point + flip*sgn(slope)*Hlength*cos(perpAng)
y2point = y1point - flip*sgn(slope)*Hlength*sin(perpAng)
# The hatch mark is just an arrow with no heads
set arrow from x1point,y1point to x2point,y2point nohead lw 2
# Move along the curve approximately a distance of Hspace
x1point = x1point + Hspace*cos(slopeAng)
y1point = g2_20(x1point)
# loop around to do next hatch mark
}
replot
Here is the result:
Comment: forget about these early cumbersome attempts. Nevertheless, I will leave it here. Please check my other answer.
I'm not aware of a feature in gnuplot which would generate such hatched lines.
One workaround could be the following: shift your curves slightly by some value and fill it with filledcurves and a hatch pattern. However, this works only well if the curve is a straight line or not too much bent.
Unfortunately, there is also only a very limited number of hatch patterns in gnuplot (see Hatch patterns in gnuplot) and they are not customizable.
You need to play with the shift value and the hatched fill pattern.
Code:
### contour lines with hatched side
reset session
f(x,y)=(x**2+y-11)**2+(x+y**2-7)**2
g1(x,y)=(x-5)**2+y**2
g2(x,y) = 4*x+y
set xrange [0:6]
set yrange [0:6]
set isosample 250, 250
set key outside
set contour base
unset surface
set cntrparam levels disc 10,30,75,150,300,500,850,1500
set table $Contourf
splot f(x,y)
unset table
set cntrparam levels disc 26
set table $Contourg1
splot g1(x,y)
unset table
set cntrparam levels disc 20
set table $Contourg2
splot g2(x,y)
unset table
set angle degree
set datafile commentschar " "
plot for [i=1:8] $Contourf u 1:2:(i) skip 5 index i-1 w l lw 1.5 lc var title columnheader(5)
replot $Contourg1 u 1:2 skip 5 index 0 w l lw 4 lc 0 title columnheader(5)
replot $Contourg2 u 1:2 skip 5 index 0 w l lw 4 lc 0 title columnheader(5)
set style fill transparent pattern 5
replot $Contourg1 u 1:2:($2+0.2) skip 5 index 0 w filledcurves lc 0 notitle
set style fill transparent pattern 4
replot $Contourg2 u 1:2:($2+0.5) skip 5 index 0 w filledcurves lc 0 notitle
### end of code
Result:
Addition:
With gnuplot you will probably find a workaround most of the times. It's just a matter how complicated or ugly you allow it to become.
For such steep functions use the following "trick". The basic idea is simple: take the original curve and the shifted one and combine these two curves and plot them as filled. But you have to reverse one of the curves (similar to what I already described earlier: https://stackoverflow.com/a/53769446/7295599).
However, here, a new "problem" arises. For whatever reason, the contour line data consist out of several blocks separated by an empty line and it's not a continous sequence in x. I don't know why but that's the contour lines gnuplot creates. To get the order right, plot the data into a new datablock $ContourgOnePiece starting from the last block (every :::N::N)to the first block (every :::0::0). Determine the number of these "blocks" by stats $Contourg and STATS_blank. Do the same thing for the shifted contour line into $ContourgShiftedOnePiece.
Then combine the two datablocks by printing them line by line to a new datablock $ClosedCurveHatchArea, where you actually reverse one of them.
This procedure will work OK for strictly monotonous curves, but I guess you will get problems with oscillating or closed curves. But I guess there might be also some other weird workarounds.
I admit, this is not a "clean" and "robust" solution, but it somehow works.
Code:
### lines with one hatched side
reset session
set size square
g(x,y) = -0.8-1/x**3+y
set xrange [0:4]
set yrange [0:4]
set isosample 250, 250
set key off
set contour base
unset surface
set cntrparam levels disc 0
set table $Contourg
splot g(x,y)
unset table
set angle degree
set datafile commentschar " "
# determine how many pieces $Contourg has
stats $Contourg skip 6 nooutput # skip 6 lines
N = STATS_blank-1 # number of empty lines
set table $ContourgOnePiece
do for [i=N:0:-1] {
plot $Contourg u 1:2 skip 5 index 0 every :::i::i with table
}
unset table
# do the same thing with the shifted $Contourg
set table $ContourgShiftedOnePiece
do for [i=N:0:-1] {
plot $Contourg u ($1+0.1):($2+0.1):2 skip 5 index 0 every :::i::i with table
}
unset table
# add the two curves but reverse the second of them
set print $ClosedCurveHatchArea append
do for [i=1:|$ContourgOnePiece|:1] {
print $ContourgOnePiece[i]
}
do for [i=|$ContourgShiftedOnePiece|:1:-1] {
print $ContourgShiftedOnePiece[i]
}
set print
plot $Contourg u 1:2 skip 5 index 0 w l lw 2 lc 0 title columnheader(5)
set style fill transparent pattern 5 noborder
replot $ClosedCurveHatchArea u 1:2 w filledcurves lc 0
### end of code
Result:
Addition 2:
Actually, I like #Ethan's approach of creating an extra level contour line. This works well as long as the gradient is not too large. Otherwise you might get noticeable deformations of the second contour line (see red curve below). However, in the above examples with g1 and g2 you won't notice a difference. Another advantages is that the hatch lines are perpendicular to the curve. A disadvantage is that you might get some interruptions of the regular pattern.
The solution with a small shift of the original curve in x and/or y and filling areas doesn't work with oscillating or closed lines.
Below, the black hatched curves are a mix of these approaches.
Procedure:
create a single contour line
create an extended (ext) or shifted (shf) contourline (either by a new contour value or by shifting an existing one)
order the contour line (ord)
reverse the contour lin (rev)
add the ordered (ord) and the extended,ordered,reversed (extordrev)
plot the added contour line (add) with filledcuves
NB: if you want to shift a contour line by x,y you have to order first and then shift it, otherwise the macro #ContourOrder cannot order it anymore.
You see, it can get complicated. In summary, so far there are three approaches:
(a) extra level contour line and thick dashed line (#Ethan)
pro: short, works for oscillating and closed curves;
con: bad if large gradient
(b) x,y shifted contour line and hatched filledcurves (#theozh)
pro: few parameters, clear picture;
con: lengthy, only 4 hatch patterns)
(c) derivative of data point (#Dan Sp.)
pro: possibly flexibility for tilted hatch patterns;
con: need of derivative (numerical if no function but datapoints), pattern depends on scale
The black curves are actually a mix of (a) and (b).
The blue curve is (b). Neither (a) nor (b) will work nicely on the red curve. Maybe (c)?
You could think of further mixing the approaches... but this probably gets also lengthy.
Code:
### contour lines with hashed side
set term wxt butt
reset session
f(x,y)=(x**2+y-11)**2+(x+y**2-7)**2
g1(x,y)=(x-5)**2+y**2
g2(x,y) = 4*x+y
g3(x,y) = -0.8-1/x**3+y
set xrange [0:6]
set yrange [0:6]
set isosample 250, 250
set key outside
set contour base
unset surface
set cntrparam levels disc 10,30,75,150,300,500,850,1500
set table $Contourf
splot f(x,y)
unset table
set cntrparam levels disc 26
set table $Contourg1
splot g1(x,y)
unset table
set cntrparam levels disc 20
set table $Contourg2
splot g2(x,y)
unset table
set cntrparam levels disc 0
set table $Contourg3
splot g3(x,y)
unset table
# create some extra offset contour lines
# macro for setting contour lines
ContourCreate = '\
set cntrparam levels disc Level; \
set table #Output; \
splot #Input; \
unset table'
Level = 27.5
Input = 'g1(x,y)'
Output = '$Contourg1_ext'
#ContourCreate
Level = 20.5
Input = 'g2(x,y)'
Output = '$Contourg2_ext'
#ContourCreate
Level = 10
Input = 'f(x,y)'
Output = '$Contourf0'
#ContourCreate
Level = 13
Input = 'f(x,y)'
Output = '$Contourf0_ext'
#ContourCreate
# Macro for ordering the datapoints of the contour lines which might be split
ContourOrder = '\
stats #DataIn skip 6 nooutput; \
N = STATS_blank-1; \
set table #DataOut; \
do for [i=N:0:-1] { plot #DataIn u 1:2 skip 5 index 0 every :::i::i with table }; \
unset table'
DataIn = '$Contourg1'
DataOut = '$Contourg1_ord'
#ContourOrder
DataIn = '$Contourg1_ext'
DataOut = '$Contourg1_extord'
#ContourOrder
DataIn = '$Contourg2'
DataOut = '$Contourg2_ord'
#ContourOrder
DataIn = '$Contourg2_ext'
DataOut = '$Contourg2_extord'
#ContourOrder
DataIn = '$Contourg3'
DataOut = '$Contourg3_ord'
#ContourOrder
set table $Contourg3_ordshf
plot $Contourg3_ord u ($1+0.15):($2+0.15) w table # shift the curve
unset table
DataIn = '$Contourf0'
DataOut = '$Contourf0_ord'
#ContourOrder
DataIn = '$Contourf0_ext'
DataOut = '$Contourf0_extord'
#ContourOrder
# Macro for reversing a datablock
ContourReverse = '\
set print #DataOut; \
do for [i=|#DataIn|:1:-1] { print #DataIn[i]}; \
set print'
DataIn = '$Contourg1_extord'
DataOut = '$Contourg1_extordrev'
#ContourReverse
DataIn = '$Contourg2_extord'
DataOut = '$Contourg2_extordrev'
#ContourReverse
DataIn = '$Contourg3_ordshf'
DataOut = '$Contourg3_ordshfrev'
#ContourReverse
DataIn = '$Contourf0_extord'
DataOut = '$Contourf0_extordrev'
#ContourReverse
# Macro for adding datablocks
ContourAdd = '\
set print #DataOut; \
do for [i=|#DataIn1|:1:-1] { print #DataIn1[i]}; \
do for [i=|#DataIn2|:1:-1] { print #DataIn2[i]}; \
set print'
DataIn1 = '$Contourg1_ord'
DataIn2 = '$Contourg1_extordrev'
DataOut = '$Contourg1_add'
#ContourAdd
DataIn1 = '$Contourg2_ord'
DataIn2 = '$Contourg2_extordrev'
DataOut = '$Contourg2_add'
#ContourAdd
DataIn1 = '$Contourg3_ord'
DataIn2 = '$Contourg3_ordshfrev'
DataOut = '$Contourg3_add'
#ContourAdd
DataIn1 = '$Contourf0_ord'
DataIn2 = '$Contourf0_extordrev'
DataOut = '$Contourf0_add'
#ContourAdd
set style fill noborder
set datafile commentschar " "
plot \
for [i=1:8] $Contourf u 1:2:(i) skip 5 index i-1 w l lw 1.5 lc var title columnheader(5), \
$Contourg1 u 1:2 skip 5 index 0 w l lw 3 lc 0 title columnheader(5), \
$Contourg2 u 1:2 skip 5 index 0 w l lw 3 lc 0 title columnheader(5), \
$Contourg3 u 1:2 skip 5 index 0 w l lw 3 lc 0 title columnheader(5), \
$Contourg1_add u 1:2 w filledcurves fs transparent pattern 4 lc rgb "black" notitle, \
$Contourg2_add u 1:2 w filledcurves fs transparent pattern 5 lc rgb "black" notitle, \
$Contourg3_add u 1:2 w filledcurves fs transparent pattern 5 lc rgb "blue" notitle, \
$Contourf0_add u 1:2 w filledcurves fs transparent pattern 6 lc rgb "red" notitle, \
### end of code
Result:
Addition 3:
If you plot a line with filledcurves, I guess gnuplot will connect the first and last point with a straight line and fills the enclosed area.
In your circle/ellipse example the outer curve is cut at the top border of the graph. I guess that's why the script does not work in this case. You have to identify these points where the outer curve starts and ends and arrange your connected curve such that these points will be the start and end point.
You see it's getting complicated...
The following should illustrate how it should work: make one curve where you start e.g. with the inner curve from point 1 to 100, then add point 1 of inner curve again, continue with point 1 of outer curve (which has opposite direction) to point 100 and add point 1 of outer curve again. Then gnuplot will close the curve by connecting point 1 of outer curve with point 1 of inner curve. Then plot it as filled with hatch pattern.
By the way, if you change your function g1(x,y) to g1(x,y)= x*y/2+(x+2)**2+(y-1.5)**2/2-2
(note the difference y-1.5 instead of y-2) everything works fine. See below.
Code:
### Hatching on a closed line
reset session
f(x,y)=x*exp(-x**2-y**2)+(x**2+y**2)/20
g1(x,y)= x*y/2+(x+2)**2+(y-1.5)**2/2-2
set xrange [-7:7]
set yrange [-7:7]
set isosample 250, 250
set key outside
set contour base
unset surface
set cntrparam levels disc 4,3.5,3,2.5,2,1.5,1,0.5,0
set table $Contourf
splot f(x,y)
unset table
set cntrparam levels disc 0
set table $Contourg1
splot g1(x,y)
unset table
# create some extra offset contour lines
# macro for setting contour lines
ContourCreate = '\
set cntrparam levels disc Level; \
set table #Output; \
splot #Input; \
unset table'
Level = 1
Input = 'g1(x,y)'
Output = '$Contourg1_ext'
#ContourCreate
# Macro for ordering the datapoints of the contour lines which might be split
ContourOrder = '\
stats #DataIn skip 6 nooutput; \
N = STATS_blank-1; \
set table #DataOut; \
do for [i=N:0:-1] { plot #DataIn u 1:2 skip 5 index 0 every :::i::i with table }; \
unset table'
DataIn = '$Contourg1'
DataOut = '$Contourg1_ord'
#ContourOrder
DataIn = '$Contourg1_ext'
DataOut = '$Contourg1_extord'
#ContourOrder
# Macro for reversing a datablock
ContourReverse = '\
set print #DataOut; \
do for [i=|#DataIn|:1:-1] { print #DataIn[i]}; \
set print'
DataIn = '$Contourg1_extord'
DataOut = '$Contourg1_extordrev'
#ContourReverse
# Macro for adding datablocks
ContourAdd = '\
set print #DataOut; \
do for [i=|#DataIn1|:1:-1] { print #DataIn1[i]}; \
do for [i=|#DataIn2|:1:-1] { print #DataIn2[i]}; \
set print'
DataIn2 = '$Contourg1_ord'
DataIn1 = '$Contourg1_extordrev'
DataOut = '$Contourg1_add'
#ContourAdd
set style fill noborder
set datafile commentschar " "
plot \
for [i=1:8] $Contourf u 1:2:(i) skip 5 index i-1 w l lw 1.5 lc var title columnheader(5), \
$Contourg1 u 1:2 skip 5 index 0 w l lw 2 lc 0 title columnheader(5), \
$Contourg1_add u 1:2 w filledcurves fs transparent pattern 5 lc rgb "black" notitle
### end of code
Result:
Late to the party, but this seems to still be of interest to some.
I'm the author of the Matlab implementation referenced by the OP.
https://www.mathworks.com/matlabcentral/fileexchange/29121-hatched-lines-and-contours
As it turns out, that version was not the first one I did (just the first to be shared online). The OG version is one I wrote for Java using Graphics2D:
https://github.com/ramcdona/HatchedStroke
I technically have a 3D version implemented in Java3D. If anyone needs it, let me know.
Most recently, I also implemented a version in Python in Matplotlib -- available since 3.4.0.
https://matplotlib.org/stable/gallery/images_contours_and_fields/contours_in_optimization_demo.html
It seems I need this every time I use another plotting tool. Python's Plotly is a likely next target. Maybe someone else will beat me to it.

Merge key entries in gnuplot

I would like to plot data with lines and points with different colors. It seems to exist different solutions:
https://stackoverflow.com/a/31887351/4373898 , https://stackoverflow.com/a/31562632/4373898
https://gnuplot-tricks.blogspot.fr/2009/12/defining-some-new-plot-styles.html
However, none of them handle the key properly, showing only one entry with both the line and a point with different colors...
Is there another way to achieve it?
This is minimal (non-)working example.
set key bottom right
plot '+' using 1:1 title "same data with different lines and points color" with lines lc 'blue', '' using 1:1 every 3 notitle with points ps 1.2 pt 7 lc 'red';
Kind regards,
Alexis.
If your plot is not too complex, you could perhaps achieve this by playing with the spacing parameter of the legend. Setting spacing to -1 achieves that the labels/symbols overlap:
set terminal pngcairo enhanced
set output 'fig.png'
set xr [-pi/2:pi/2]
set yr [0:1]
set key at graph 0.95,graph 0.9 spacing -1
plot \
cos(x) w l lc rgb 'dark-red' lw 2, \
'-' w p lc rgb 'royalblue' pt 7 ps 2 t ' '
-1 0.540302
0 1
1 0.540302
e
which gives
However, the disadvantage is that the setting is in a sense global - should the plot contain more functions/data files, everything would overlap. In order to use this "method" in this particular case, it would be necessary to invoke multiplot and create the keys separately.
Having 3 different colors for a linespoints plot is certainly "non-standard" and as you noticed a challenge especially for the proper key.
Here is a suggestion without using multiplot. Personally, I would use multiplot only if absolutely necessary. Otherwise you will lose the benefits of automargin and autoscale and you have to deal with margins and ranges, etc. yourself as you did in your solution.
Since the automatic creation of the key is the problem, then "do-it-yourself".
Update: simplified plot command and legend at graph coordinates.
This suggestion draws the legend via labels and arrows. You give the position and distances of the legend in graph coordinates.
The mix of points and linespoints within a for loop is taken from here.
Script: (works with gnuplot>=5.0.0, Jan. 2015)
### legend for plot with linespoints and different colors for line, fill and border
reset session
# no. lw pt colorLine colorFill colorBorder label
mySettings = '\
1 2 5 0x00ff00 0xffff00 0xff0000 "x + 1" \
2 2 7 0x0000ff 0xff00ff 0x000000 "x^2 - 8" \
' # end of settings
myLw(n) = real(word(mySettings,n*7-5)) # linewidth
myPt(n) = int(word(mySettings,n*7-4)) # pointtype
cL(n) = int(word(mySettings,n*7-3)) # color line
cF(n) = int(word(mySettings,n*7-2)) # color fill
cB(n) = int(word(mySettings,n*7-1)) # color border
myLabel(n) = word(mySettings,n*7) # label
myLt(n) = n==1 ? 1 : -2
myPtL(v,i) = i==1 ? 0 : i==2 ? myPt(v) : myPt(v)-1
myColor(v,i) = i==1 ? cL(v) : i==2 ? cF(v) : cB(v)
myPs = 3 # fixed pointsize
# Legend position graph units
xPos = 0.05 # x-position
yPos = 0.95 # y-position
dy = 0.07 # y distance
dx = 0.025 # x length
set for [i=1:words(mySettings)/7] arrow from graph xPos-dx,yPos-(i-1)*dy to graph xPos+dx,yPos-(i-1)*dy \
lw myLw(i) lc rgb myColor(i,1) nohead back
set for [i=1:2] for [j=2:3] label myLabel(i) at graph xPos,yPos-(i-1)*dy left offset 3,0 \
point pt myPtL(i,j) ps myPs lc rgb myColor(i,j) lw myLw(i) front
set samples 11 # samples for functions
set key noautotitle
set grid x
set grid y
f1(x) = x + 1
f2(x) = x**2 - 8
plot for [i=1:3] f1(x) w lp lt myLt(i) pt myPtL(1,i) ps (i>>1)*myPs lc rgb myColor(1,i) lw myLw(1), \
for [i=1:3] [-5:5] f2(x) w lp lt myLt(i) pt myPtL(2,i) ps (i>>1)*myPs lc rgb myColor(2,i) lw myLw(2)
### end of script
Result:
Thanks ewcz for your answer, it is a first step toward the expected result. However, as you stated it, this is a little bit trickier to adapt it if you have multiple functions/data to display on the same plot.
Below is a minimal working example with two functions (and thus, two key entries) with a line, points, and points outline of different colors.
# These parameters are used to compute the spacing between entries of the key
pointSize = 1;
yticsScale = 1;
# We use the default spacing (1.25)
keySpacing = pointSize*yticsScale*1.25;
# Initial coordinate of the key
keyY = 4; # In character system
keyX = 0.87; # In graph system
# Just to generate data
set samples 20;
set xrange [-pi:pi];
set term pngcairo;
set output 'graph.png';
set xlabel "x"
set ylabel "y"
# Set the alignment (and thus the coordinate point) of the key
# Set the spacing to -1 to stack different (thanks to ewcz for the idea)
set key bottom right spacing -1
# Start a multiplot
set multiplot
# Make plots as big as possible
set origin 0,0
set size 1,1
# Set the key position
set key at graph keyX, character keyY
# Plot multiple times the same function with different styles.
# Make sure that all functions have a title (empty if necessary).
plot cos(x+pi) w l lc "light-red", \
cos(x+pi) w p pt 5 ps 1.8 lc "dark-red" t ' ', \
cos(x+pi) w p pt 5 ps 1.2 lc "red" t ' '
# Update key coordinates for the next plot
keyY = keyY + keySpacing
# Draw the key of the next plot at the new coordinates
set key at graph keyX, character keyY
plot cos(x) w l lc "light-blue", \
cos(x) w p pt 7 ps 1.8 lc "dark-blue" t ' ', \
cos(x) w p pt 7 ps 1.2 lc "blue" t ' ';
# That's all
unset multiplot
set output;
The resulting plot:
Hope that will help others.
Kind regards.
Alexis
Edit:
The previous code works if both functions/data have the same ranges (on x and y) allowing autoscale to work properly.
In the case of data where you do not know the ranges, you must compute it before plotting.
# Just to generate data
set samples 20;
# First data will be defined on [-pi:pi] with values between -1 and 1.
set table '1.dat'
plot [-pi:pi] cos(x)
unset table
# Second data will be defined on [-pi/2,pi/2] with values between 0 and -2
set table '2.dat'
plot [-pi/2:pi/2] 2*cos(x+pi)
unset table
# These parameters are used to compute the spacing between entries of the key
pointSize = 1;
yticsScale = 1;
keySpacingScale = 1.25; # Gnuplot default spacing
keySpacing = pointSize * yticsScale * keySpacingScale; # Spacing computation
set pointsize pointSize;
set ytics scale yticsScale;
set key spacing -1; # Make key entries overlapping (thanks ewcz for the idea)
# Initial coordinate of the key
keyY = 4.5; # In character system
keyX = 0.98; # In graph system
set term pngcairo;
set output 'graph.png';
# Remove redundant objects
# Borders, labels, tics will be drawn for each plot, this is not necessary as all plots will be stacked. So remove then.
set border 0
set tics textcolor "white" # Dirty tricks to keep plots aligned but to not show the tics
set xlabel " " # The same
set ylabel " " # The same
# Compute the ranges
min(v1, v2) = (v1 < v2) ? v1 : v2;
max(v1, v2) = (v1 > v2) ? v1 : v2;
# Get min and max for the data
stats [*:*] [*:*] '1.dat' name 'f1' nooutput;
stats [*:*] [*:*] '2.dat' name 'f2' nooutput;
# Get the range limits
xmin = min(f1_min_x, f2_min_x)
xmax = max(f1_max_x, f2_max_x)
ymin = min(f1_min_y, f2_min_y)
ymax = max(f1_max_y, f2_max_y)
# Autoscale the range to match all the data
set xrange [* < xmin:xmax < *] writeback
set yrange [* < ymin:ymax < *] writeback
# Start a multiplot
set multiplot
# Make plots as big as possible
set origin 0,0
set size 1,1
# Set the key
set key bottom right at graph keyX, character keyY
# Plot multiple times the same function with different styles.
# Make sure that all functions have a title (empty if necessary).
plot '1.dat' w l lc "light-red" t "cos(x)", \
'' w p pt 5 ps 1.8 lc "dark-red" t ' ', \
'' w p pt 5 ps 1.2 lc "red" t ' '
# Update key coordinates for the next plot
keyY = keyY + keySpacing
# Draw the key of the next plot at the new coordinates
set key at graph keyX, character keyY
# Display at least once the labels
set border
set tics textcolor "black"
set xlabel "x"
set ylabel "y"
# Disable ranges autoscaling
set xrange restore
set yrange restore
plot '2.dat' w l lc "light-blue" t "2cos(x+pi)", \
'' w p pt 5 ps 1.8 lc "dark-blue" t ' ', \
'' w p pt 5 ps 1.2 lc "blue" t ' '
# That's all
unset multiplot
set output;
One more time the resulting plots:
Kind regards,
Alexis

How to draw a filledcurve graph with GNUPlot by using 2 csv files

If I have 2 csv files ("CSV1.csv" dataname_1 and "CSV2.csv" dataname_2), how can I draw filled curve graph from the data of 2 csv files. The formats of these CSV files are identical, where 2 is timestamps and 5 is the value thus the using 2:5
I am trying this:
plot dataname_2 using 2:5 title "Above" with filledcurves above lc rgb 'blue',\
dataname_1 using 2:5 title "Below" with filledcurves below lc rgb 'red',\
dataname_2 using 2:5 title "Engine Starts" with lines lc rgb "#1E90FF",\
dataname_1 using 2:5 title "Engine Hours" with lines lc rgb "#FF1493"
I need to modify the code above so that the output is:
A solution which will probably always work is to prepare the data with whatever external tool in such a way that gnuplot can handle and plot it. I'm aware that the philosophy of gnuplot is to concentrate on plotting and not necessarily on data preparation for plotting. However, it is always good to have a minimum set of features to do some basic data preparation.
In your case you have several problems, well, let's call it challenges ;-)
with filledcurves requires data within the same file or datablock
however, gnuplot cannot easily merge datafiles line by line (it can with some workaround, see: https://stackoverflow.com/a/61559658/7295599). Simply appending would be no problem with gnuplot.
the latter doesn't help you since with filledcurves needs identical x for upper and lower curve, i.e. x,y1,y2 and your data has x1,y1 and x2,y2
however, gnuplot cannot easily resample data (it can with some workaround, see: Resampling data with gnuplot)
with filledcurves cannot directly fill curves with non-monotonic increasing x (not the case with your data. Here just for illustration purposes) (it can with some workaround see: https://stackoverflow.com/a/53769446/7295599 or https://stackoverflow.com/a/56176717/7295599)
So a workaround for all this could be the following (works with gnuplot 5.2, maybe can be tweaked to work with earlier versions):
Assumptions:
Data x1,y1 and x2,y2 in two files or datablocks
Data has not necessarily identical x, i.e. x1,y1 and x2,y2
Data may contain non-monotonic x
the two curves have only one intersection (well, the workaround below will just take the first one)
Procedure:
if not already, get the data into a datablock.
Find the intersection of the curves.
Create new datablocks: Filled1 using Data1 from the beginning to the intersection point and using Data2 backwards from the intersection point to the beginning. Filled2 using Data1 from the end backwards to the intersection point and using Data2 from the intersection point to the end.
Plot $Data1 and $Data2 with lines and $Filled1 and $Filled2 with filledcurves
Steps 2 and 3, probably will not be much shorter in another programming language unless there are dedicated functions.
Get files to datablock: (see also here gnuplot: load datafile 1:1 into datablock)
# get files to datablocks
set table $Data1
plot 'myFile1.dat' u 1:2 w table
set table $Data2
plot 'myFile2.dat' u 1:2 w table
unset table`
Code: (copy&paste for gnuplot >=5.2)
### fill intersecting curves from two files not having identical x
reset session
$Data1 <<EOD
1 1
2 0
4 1
3 3
5 5
6 6
8 8
9 9
EOD
$Data2 <<EOD
1 3
3.5 5
7.5 1
9 7
EOD
# orientation of 3 points a,b,c: -1=clockwise, +1=counterclockwise
Orientation(a,b,c) = sgn((word(b,1)-word(a,1))*(word(c,2)-word(a,2)) - \
(word(c,1)-word(a,1))*(word(b,2)-word(a,2)))
# check for intersection of segment a-b with segment c-d,
# 0=no intersection, 1=intersection
IntersectionCheck(a,b,c,d) = \
Orientation(a,c,b) == Orientation(a,d,b) || \
Orientation(c,a,d) == Orientation(c,b,d) ? 0 : 1
# coordinate of intersection
M(a,b) = real(word(a,1)*word(b,2) - word(a,2)*word(b,1))
N(a,b,c,d) = (word(a,1)-word(b,1))*(word(c,2)-word(d,2)) - \
(word(a,2)-word(b,2))*(word(c,1)-word(d,1))
Px(a,b,c,d) = (M(a,b)*(word(c,1)-word(d,1)) - (word(a,1)-word(b,1))*M(c,d))/N(a,b,c,d)
Py(a,b,c,d) = (M(a,b)*(word(c,2)-word(d,2)) - (word(a,2)-word(b,2))*M(c,d))/N(a,b,c,d)
Intersection(a,b,c,d) = sprintf("%g %g", Px(a,b,c,d), Py(a,b,c,d))
stop = 0
do for [i=1:|$Data1|-1] {
a = $Data1[i]
b = $Data1[i+1]
do for [j=1:|$Data2|-1] {
c = $Data2[j]
d = $Data2[j+1]
if (IntersectionCheck(a,b,c,d)) {
i0 = i; j0 = j
stop=1; break }
}
if (stop) { break }
}
# create the datablocks for the outline to be filled
set print $Filled1
do for [k=1:i0] { print $Data1[k] }
print Intersection(a,b,c,d)
do for [k=j0:1:-1] { print $Data2[k] }
set print $Filled2
do for [k=|$Data1|:i0+1:-1] { print $Data1[k] }
print Intersection(a,b,c,d)
do for [k=j0+1:|$Data2|] { print $Data2[k] }
set print
set key top left
plot $Filled1 u 1:2 w filledcurves lc rgb 0x3f48cc, \
$Filled2 u 1:2 w filledcurves lc rgb 0xed1c24, \
$Data1 u 1:2 w lp pt 7 lw 5 lc rgb 0x99d9ea, \
$Data2 u 1:2 w lp pt 7 lw 5 lc rgb 0xff80c0
### end of code
Result:

GNUPLOT: Show a x value given a y value

i'm having some problems with gnuplot
I have to draw a cdf function and i'm interested in the values of variable x when F(x) is equal to 0.1 and 0.9
How can I tell Gnuplot to show me on the x axis the value corresponding to a given value on the y value (in my example those values are 0.1 and 0.9)
thanks
You're basically asking gnuplot to solve an equation. In your particular case, actually two equations: F(x)=0.1 and F(x)=0.9. As far as I know this cannot be done, but I might be wrong. What you can do if you simply want a graphical solution, is make a conditional plot, and ask that when F(x) is very close to 0.1 0.9, gnuplot plots something other than the function.
For example, assume f(x)=x^2 and you want to know "graphically" for which x f(x)=0.1. Then you can request the value abs(f(x) - 0.1) be small, for example < 0.01. Then tell gnuplot to go to zero (just an example!) if this is the case, otherwise plot f(x)=x^2:
f(x)=x**2
set xrange [-2:2]
set samples 1000
plot abs(f(x)-1) < 0.01 ? 0 : f(x)
Which yields:
The two peaks that go to zero mark graphically on the x axis the solution to the equation f(x)=0.1. Of course, you need gnuplot to sample this point in order to see a peak. Thus you need to play with set samples and set xrange.
From your question it is not clear whether you have a function F(x) as expression or just a x,y-data file. I assume that your function is monotonic increasing in x and y.
Two solutions come to my mind:
via simple linear interpolation
via curve fitting
Let's create some test data. For this, let's assume your function is known (as expression) and something like this (check help norm): F(x) = a*norm(b*x + c)
Let's take a = 1; b = 0.8; c = -4. In the example below, sampling will be only 8, just for illustration purpose.
You can easily set samples 200 and you will get the same results as for the curve fitting method below. From gnuplot 5.0 on, you could write the data into a datablock instead of a file on disk.
Data: SO22276755.dat
0 3.16712e-05
1.42857 0.002137
2.85714 0.043238
4.28571 0.283855
5.71429 0.716145
7.14286 0.956762
8.57143 0.997863
10 0.999968
Script 1: (basically works for gnuplot 4.6.0, March 2012)
### interpolate x-values
reset
FILE = "SO22276755.dat"
yis = '0.10 0.90'
yi(n) = real(word(yis,n))
xis = ''
xi(n) = real(word(xis,n))
Interpolate(yi) = (x1-x0)/(y1-y0)*(yi-y0) + x0
getXis(xis) = xis.(n=words(xis), n<words(yis) ? yi=real(word(yis,n+1)) : 0, \
y0<=yi && y1>=yi ? sprintf(" %g",Interpolate(yi)) : '')
set key left top noautotitle
set grid x,y
plot x1=y1=NaN FILE u (x0=x1,x1=$1):(y0=y1,y1=$2,xis=getXis(xis),y1) \
w l lc rgb "blue" ti "data", \
'+' u (xi=xi(int($0+1))):(yi=yi(int($0+1))):\
(sprintf("(%.4g|%.4g)",xi,yi)) every ::0::1 \
w labels point pt 7 lc rgb "red" right offset -1,0 ti "interpolated"
### end of script
Result:
Script 2: (basically works for gnuplot>=4.6.0, March 2012)
With this approach you are fitting your known function F(x) to constant lines, i.e. your desired values 0.1 and 0.9. For this, a file will be created (could be a datablock for gnuplot>=5.0) and it will basically look like this SO22276755.fit:
0 0.1
1 0.1
0 0.9
1 0.9
### interpolate x-values
reset
F(x) = a*norm(b*x+c) # function
a = 1
b = 0.8
c = -4
yis = '0.10 0.90'
yi(n) = real(word(yis,n))
xis = ''
xi(n) = real(word(xis,n))
set key left top noautotitle
set grid x,y
# create fit levels file
LEVELS = "SO22276755.fit"
set table LEVELS
set samples 2
plot for [i=1:words(yis)] '+' u (yi(i))
unset table
xmin = 0
xmax = 10
set xrange[xmin:xmax]
set samples 100
xis = ''
do for [i=1:words(yis)] {
xi = (xmin+xmax)*0.5 # set start value
fit F(xi) LEVELS u 1:2 index i-1 via xi
xis = xis.sprintf(" %g",xi)
}
plot F(x) w l lc rgb "web-green" ti "F(x)", \
'+' u (xi=xi(int($0+1))):(yi=yi(int($0+1))):(sprintf("(%.4g|%.4g)",xi,yi)) \
every ::0::1 w labels point pt 7 lc rgb "red" righ offset -1,0 ti "fitted"
### end of script
Result:

Resources