Gnuplot: gradient coloured arrow - gnuplot

I would like to draw an arrow with not a single colour, but a colour gradient along its length. Does anyone know how to achieve that? Some pseudo-code for creating an arrow that starts red and ends blue:
set palette defined (0 "red", 1 "blue")
set cbr [0:1]
set arrow from 0,0 to 1,1 linecolor palette cb [0:1] # this doesn't work

Besides #Friedrich's solution, I would like to suggest a more general solution (although more complicated).
I assume you want to plot something else besides the arrow.
In case your graph needs to use a palette I guess you're in "trouble", because I'm not sure whether gnuplot supports more than one palette in a plot command
(see Gnuplot 5.2 splot: Multiple pm3d palette in one plot call). So, you have to implement the palette for your arrow by yourself (see e.g. Gnuplot: transparency of data points when using palette). If you want to do bent arrows using Cubic Bézier check (https://stackoverflow.com/a/60389081/7295599).
Code:
### arrow with color gradient (besides other palette in plot)
reset session
array A[4] = [-4,-2,4,2] # arrow coordinates x0,y0,x1,y1
Ax(t) = A[1] + t*(A[3]-A[1])
Ay(t) = A[2] + t*(A[4]-A[2])
AColorStart = 0xff0000 # red
AColorEnd = 0x0000ff # blue
r(c) = (c & 0xff0000)>>16
g(c) = (c & 0x00ff00)>>8
b(c) = (c & 0x0000ff)
AColor(t) = ((int(r(AColorStart)*(1-t)+r(AColorEnd)*t))<<16) + \
((int(g(AColorStart)*(1-t)+g(AColorEnd)*t))<<8) + \
int(b(AColorStart)*(1-t)+b(AColorEnd)*t)
array AHead[1] # dummy array for plotting a single point, here: arrow head
set angle degrees
set style arrow 1 lw 3 lc rgb var size 0.5,15 fixed
set palette grey
plot '++' u 1:2:($1*$2) w image notitle, \
[0:0.99] '+' u (Ax($1)):(Ay($1)):(AColor($1)) w l lw 3 lc rgb var notitle,\
AHead u (Ax(0.99)):(Ay(0.99)):(Ax(1)-Ax(0.99)):(Ay(1)-Ay(0.99)):(AColor($1)) w vec as 1 notitle
### end of code
Result:
Addition:
For what it is worth, here is a variation which allows plotting of multiple arrows each with a different palette. I guess it requires gnuplot 5.2, because of indexing the datablock $PALETTE[i].
Code:
### multiple arrows each with different color gradients (besides other palette in plot)
reset session
# define palettes
set print $myPalettes
test palette # get default palette into datablock $PALETTE
print $PALETTE # add palette to $myPalettes
set palette rgb 33,13,10 # define next palette
test palette # get palette into datablock $PALETTE
print $PALETTE # add palette to $myPalettes
set palette defined (0 "blue", 1 "black", 2 "red") # define next palette
test palette # get palette into datablock $PALETTE
print $PALETTE # add palette to $myPalettes
set print
ColorComp(p,t,c) = int(word($myPalettes[p*257+int(255*t)+1],c+1)*0xff)
AColor(p,t) = (ColorComp(p,t,1)<<16) + (ColorComp(p,t,2)<<8) + ColorComp(p,t,3)
set size ratio -1
set angle degrees
unset key
set style arrow 1 lw 3 lc rgb var size 0.5,15 fixed
array AHead[1] # dummy array for plotting a single point, here: arrow head
set palette grey # yet another palette for the background
# x0 y0 x1 y1 paletteNo
$Arrows <<EOD
-4 -4 4 0 0
-4 -2 4 2 1
-4 0 4 4 2
EOD
Ax(i,t) = word($Arrows[i],1) + t*(word($Arrows[i],3)-word($Arrows[i],1))
Ay(i,t) = word($Arrows[i],2) + t*(word($Arrows[i],4)-word($Arrows[i],2))
Palette(i) = int(word($Arrows[i],5))
plot '++' u 1:2:($1*$2) w image, \
for [i=1:|$Arrows|] [0:0.99:0.01] '+' u (Ax(i,$1)):(Ay(i,$1)):(AColor(Palette(i),$1)) w l lw 4 lc rgb var, \
for [i=1:|$Arrows|] AHead u (Ax(i,0.99)):(Ay(i,0.99)): \
(Ax(i,1)-Ax(i,0.99)):(Ay(i,1)-Ay(i,0.99)):(AColor(Palette(i),$1)) w vec as 1
### end of code
Result:

With line palette one can color-code a line. With a second command one could set the head, via set arrow or with a plot vector command
set palette defined (0 "red", 1 "blue")
set cbr [0:1]
set arrow 1 from 0.9,0.9 to 1,1 lc "blue"
plot sample [t=0:1] "+" us (t):(t):(t) w l palette
Thus two commands are necessary. The head of the arrow has single colour, which you have to specify.

Related

Handling out-of-range values with GNUPlot's splot

I use the splot commadn to produce a heat map of the earth. The x- and y-values represent lattitude and longitude of a specific point on the Earth's surface, while the related z-value is the outcome of an analysis. The zrange is between 0 and 60. However, for some locations on Earth, there is no result available (which is correct) and z is set to 9999 for these cases.
I'm using the following script to produce the heat map:
set terminal png large size 1600,\
1200 font arial 24 crop
set output "map.png"
set palette model RGB defined (0 "dark-green",1 "forest-green",2 "green",3 "light-green",4 "dark-yellow",5 "yellow",6 "red",7 "dark-red")
set xrange[-180.00: 180.00]
set yrange[ -90.00: 90.00]
set zrange[ *: 60]
set grid
set pm3d map
set xlabel "Longitude [deg]"
set ylabel "Latitude [deg]"
unset key
set cblabel "Time [h]"
splot "output\\map.dat" u 5:6:8,\
"input\\world.dat" u 1:2:( .00) w l lw 1 lt -1
It works fine but because of the limitation in zrange, regions with z > 60 are shown in white.
I want to have something like a condition which enables that all 9999 z-values are shown in a specific colour like purple with a declaration like "no result" in the legend.
Any idea how to achieve this?
Thanks in advance,
Florian
Not exactly sure how to modify the style for the selected points, but you can use the ternary operator not to draw them at all. Something like:
splot "output\\map.dat" u 5:6:(($8<=60)?($8):(1/0))
You basically want to have 3 "ranges" of colors:
0 to 60 your defined palette colors
>60 "out of range" color
=9999 "no data" color
Not sure if splot ... w pm3d will allow an easy "independent" setting for z and color.
Furthermore, if you have NxN datapoints you will get (N-1)x(N-1) quadrangles and the color is determined by the z-values of the involved vertices (check help corners2color) and http://gnuplot.sourceforge.net/demo_5.5/pm3d.html (the very last graph). Maybe there is an easy way which I am not aware of.
That's why I would perfer the plotting style with boxxyerror (check help boxxyerror), maybe this is not the intended way, but it is rather flexible. If you are running gnuplt 5.4 you have the function palette() (check help palette).
I would take for missing data (backgroundcolor here:white) and for data out of range "grey", but you can easily change it. You can skip the random data generation part and in the plot command replace $Data with your filename and the corresponding columns. As well, replace 180./N and 90./N with the width (delta longitude) and height (delta latitude) of one data element.
Script: (requires gnuplot>=5.4)
### define separate color for missing values
reset session
set xrange[-180:180]
set yrange[-90:90]
# create some "random" test data
N = 90
set samples N
set isosamples N
set table $Data
c = 0.05
x0 = 70 # (rand(0)*360-180) # or random
y0 = -50 # (rand(0)*180-90) #
size0 = 2
x1 = -150 # (rand(0)*360-180) # or random
y1 = -20 # (rand(0)*180-90) #
size1 = 1
holeP0(x,y) = (1-erf((x-x0)*c/size0)**2) * (1-erf((y-y0)*c/size0)**2)
holeP1(x,y) = (1-erf((x-x1)*c/size1)**2) * (1-erf((y-y1)*c/size1)**2)
f(x,y) = rand(0)<holeP0(x,y) || rand(0)<holeP1(x,y) ? 9999 : (sin(1.3*x*c)*cos(.9*y*c)+cos(.8*x*c)*sin(1.9*y*c)+cos(y*.2*x*c**2))*11.5+33
splot f(x,y)
unset table
set palette model RGB defined (0 "dark-green",1 "forest-green",2 "green",3 "light-green",4 "dark-yellow",5 "yellow",6 "red",7 "dark-red")
myZmin = 0
myZmax = 60
myColorNoData = 0xffffff
myColorOutOfRange = 0x999999
set rmargin screen 0.8
set colorbox user origin screen 0.85,graph 0.2 size graph 0.05,graph 0.8
set cblabel "Amplitude"
set cbrange [myZmin:myZmax]
set tics out
set style fill solid 1.0 border
set key noautotitle at graph 1.27, graph 0.15 reverse Left samplen 2
myColor(col) = (z=column(col), z==9999 ? myColorNoData : z>myZmax ? myColorOutOfRange : palette(z))
plot $Data u 1:2:(180./N):(90./N):(myColor(3)) w boxxy lc rgb var, \
"world.dat" u 1:2:(0) w l lc "black", \
NaN w l lc palette, \
keyentry w boxes lc rgb 0x000000 fill empty ti "no data", \
keyentry w boxes lc rgb myColorOutOfRange ti "\ndata out\nof range"
### end of script
Result:

translate palette defined to rgb variable

With palette it is easy to create color gradients
set view map
set samp 50,50
set palette defined (0 "blue", 1 "green", 2 "red")
spl "++" us 1:2:1 palette pt 5
Now I would like to apply transparency in vertical direction. The option lc rbg variable supports transparency via the alpha channel (see also here):
spl "++" us 1:2:1:(int(($2+5)/10*255)<<24) lc rgb var pt 5
But how can I translate the palette colors into rgb colors?
A second question: why I get only 10 horizontal rows, albeit I specified 50 in samp?
Easy answer first: When there is 2-dimensional sampling, either automatically from splot or explicitly from plot '++', the number of samples in the first dimension is controlled by set sample and the number of samples in the second dimension is controlled by set isosample.
Now the harder one. In gnuplot versions through the current 5.2.8 you cannot add transparency directly to the palette. You can, however, go through a multi-step process of saving the palette into a file or datablock and then reading it back it as an array of RGB colors. Once you have that array you can add an alpha channel value so that it expresses transparency as well. I will show this process using the datablock created by the command test palette. In older versions of gnuplot you may have to instead use the file created by set print "palette.save"; show palette palette 256;.
# save current palette to a datablock as a list of 256 RGB colors, one per line
set palette defined (0 "blue", 1 "green", 2 "red")
test palette
# print one line to show the format (cbval R G B NTSCval)
print $PALETTE[4]
# Create an array of packed RGB values
array RGB[256]
do for [i=1:256] {
Red = int(255. * word($PALETTE[i],2))
Green = int(255. * word($PALETTE[i],3))
Blue = int(255. * word($PALETTE[i],4))
RGB[i] = Red << 16 | Green << 8 | Blue
}
# Sample from '++' are generated to span ranges on the u and v axes
# I choose 1:256 so that the y coordinates match the range of array indices
set sample 50
set isosample 50
set urange [1:256]
set vrange [1:256]
set xrange [*:*] noextend
set yrange [*:*] noextend
# Now you can use colors stored in the array via colorspec `rgb variable`
# which will also accept an alpha channel in the high bits
plot "++" using 1:2:(RGB[int($2)]) with points pt 5 lc rgb variable
# The final step is to add an alpha channel as a function of y
# Here I go from opaque (Alpha = 0) to 50% transparent (Alpha = 127)
# This works because I know y will run from 1-256
ARGB(y) = RGB[int(y)] + (int(y/2)<<24)
plot "++" using 1:2:(ARGB($2)) with points pt 5 lc rgb variable
Output shown below.
The required command sequence, as you can see, is a mess.
It will be much easier in the next gnuplot release (5.4). The new version will provide a function palette(z) that converts from the current palette directly to a packed RGB value. Note that the palette() function isn't in the -rc1 testing version but will be in -rc2. So in version 5.4 all that palette/array/RGB manipulation can be replaced by
plot '++' using 1:2:(palette($2) + (int($2)<<24)) with points pt 5 lc rgb variable
Check also this: Gnuplot: transparency of data points when using palette
First of all, you can check what your defined palette is doing:
set palette defined (0 "blue", 1 "green", 2 "red")
test palette
You will get this:
Each channel (R,G,B) has a function with an input range [0:1] and an output range [0:1]. In this case it is a linear gradient.
So, you have to define such a function and put the channels together with the transparency (alpha) channel using the bit shift (see help operators binary).
The nice thing about a palette is that gnuplot takes care about the range. Here, you have to know minimum and maximum in advance and scale the color accordingly. You could use stats for this.
Code:
### your own palette with transparency
reset session
r(x) = x < 0.5 ? 0 : 2*x -1
g(x) = x < 0.5 ? 2*x : 2-2*x
b(x) = x < 0.5 ? 1-2*x : 0
a(y) = y
myColor(x,y) = (int(a((y-yMin)/(yMax-yMin))*0xff)<<24) + \
(int(r((x-xMin)/(xMax-xMin))*0xff)<<16) + \
(int(g((x-xMin)/(xMax-xMin))*0xff)<<8) + \
int(b((x-xMin)/(xMax-xMin))*0xff)
set samples 50
set isosamples 50
set size square
xMin=-5; xMax=5
yMin=-5; yMax=5
plot '++' u 1:2::(myColor($1,$2)) w p pt 5 ps 0.5 lc rgb var notitle
### end of code
Result:

Gnuplot: transparency of data points when using palette

Is there a way to plot transparent data points when using palette?
I currently have the following code:
set style fill transparent solid 0.2 noborder
set palette rgb 22,13,-22
plot 'mydata.dat' u 1:2:3 ps 0.3 palette
My feeling is that transparency is overwritten by the arguments of the plot command.
Is there a way to plot transparent data points when using palette?
If you check help palette you will not find (or I overlooked) a statement about transparency in the palette. It looks like you can set the palette in different ways for RGB, but not for ARGB (A=alpha channel for transparency). So, I assume it is not possible with palette to have transparency (please correct me if I am wrong).
As workaround you have to set your transparency "manually" by setting the color with some transparency.
You can find the formulae behind the palettes by typing show palette rgbformulae.
The following examples creates a plot with random point positions in xrange[0:1] and yrange[0:1] and random points size (from 2 to 6) and random transparency (from 0x00 to 0xff). The color is determined by x according to your "manual palette". I hope you can adapt this example to your needs.
Code:
### "manual" palette with transparency
reset session
# These are the rgb formulae behind palette 22,13,-22
set angle degrees
r(x) = 3*x-1 < 0 ? 0: (3*x-1 > 1) ? 1 : 3*x-1
g(x) = sin(180*x)
b(x) = 1-(3*x-1) < 0 ? 0: (1-(3*x-1) > 1) ? 1 : 1-(3*x-1)
set xrange [0:1]
set yrange[-0.1:1.1]
RandomSize(n) = rand(0)*4+2 # random size from 2 to 6
RandomTransp(n) = int(rand(0)*0xff)<<24 # random transparency from 0x00 to 0xff
myColor(x) = (int(r(x)*0xff)<<16) + (int(g(x)*0xff)<<8) + int(b(x)*0xff) + RandomTransp(0)
set samples 200
plot '+' u (x=rand(0)):(rand(0)):(RandomSize(0)):(myColor(x)) w p pt 7 ps var lc rgb var not
### end of code
Result:
New answer for Dev version 5.5
The new function set colormap allows to define a transparent palette. First, one defines the fully opaque palette in the usual way, then creates a copy of it and adds transparency to all points:
set palette rgb 22,13,-22
set colormap new MYPALETTE
transparency = 0.5
do for [i=1:|MYPALETTE|] {MYPALETTE[i] = MYPALETTE[i] + (int(transparency*0xff)<<24)}
func(x,y) = x*y
splot func(x,y) w pm3d fillcolor palette MYPALETTE
Of course, this will also work for points, the command in your case will be
plot 'mydata.dat' u 1:2:3 ps 0.3 lc palette MYPALETTE

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

Resources