Merge key entries in gnuplot - 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

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:

How to display mouse coordinates in multiplots?

In interactive terminals, e.g. like wxt or qt, gnuplot can show the coordinates of the mouse cursor on the status line of the terminal window. This is helpful in many cases and works fine for a single plot but does not work for a multiplot.
gnuplot documentation explicitely mentions (check help mouse)
Mousing is not available inside multiplot mode. When multiplot is completed using unset multiplot, then the mouse will be turned on again but acts only on the most recent plot within the multiplot (like replot does).
This is a pity, because if it would be very convenient to quickly read out some data values from different graphs in a multiplot environment.
Question: is there maybe nevertheless a way to show the coordinates in a multiplot environment?
Since gnuplot 5.4.0 there is the "experimental" option set mouse mouseformat function string_valued_function(x, y). Check help mouseformat.
With this, you can create some special functions. What you need to do is:
memorize the GPVAL_... graph parameters for each plot by adding them to a string variable
calculate the coordinates for the corresponding plot starting from x,y of the last plot
If you have overlapping plots, the script will show the coordinates of the later added plot (which is "on top").
So, hopefully this will be also useful to others. No guarantee that the following script is free of bugs. Any simplifications and better ideas would be very welcome.
Ideally, this multiplot mouse coordinates would be implemented in gnuplot itself.
I noted that the conversion of the coordinates doesn't seem to be "perfect", although I guess the formulae are correct, e.g. converting x,y mouse coordinates into terminal pixel coordinates I got a range from (-1 to 639) and (0 to 379) in a wxt 640,480 terminal. Any explanations are welcome.
Script: (requires gnuplot>=5.4.0)
### display mouse cursor coordinates in multiplot
reset session
set term wxt size 640,480
set samples 11
f(x) = x
g(x) = exp(x)
GraphParams = ''
addGraphParams(logAxisX,logAxisY) = GraphParams.\
sprintf(' "%g %g %g %g %g %g %g %g %g %g %g %g"', \
GPVAL_X_MIN, GPVAL_X_MAX, GPVAL_TERM_XMIN, GPVAL_TERM_XMAX, \
GPVAL_X_LOG, logAxisX, \
GPVAL_Y_MIN, GPVAL_Y_MAX, GPVAL_TERM_YMIN, GPVAL_TERM_YMAX, \
GPVAL_Y_LOG, logAxisY)
getGraphParam(graphNo,paramNo) = real(word(word(GraphParams,graphNo),paramNo))
getXmin(n) = getGraphParam(n,1)
getXmax(n) = getGraphParam(n,2)
getTXmin(n) = getGraphParam(n,3)
getTXmax(n) = getGraphParam(n,4)
getLogX(n) = getGraphParam(n,5)
getLogAxisX(n) = int(getGraphParam(n,6))
getYmin(n) = getGraphParam(n,7)
getYmax(n) = getGraphParam(n,8)
getTYmin(n) = getGraphParam(n,9)
getTYmax(n) = getGraphParam(n,10)
getLogY(n) = getGraphParam(n,11)
getLogAxisY(n) = int(getGraphParam(n,12))
getPlotIdx(x,y) = (plotIdx=NaN, sum [_i=1:words(GraphParams)] ( \
TermX(x)>=getTXmin(_i) && TermX(x)<=getTXmax(_i) && \
TermY(y)>=getTYmin(_i) && TermY(y)<=getTYmax(_i) ? plotIdx=_i : 0), plotIdx)
GraphX(n,x,y) = (n==n ? real(TermX(x)-getTXmin(n))/(getTXmax(n)-getTXmin(n)) : NaN)
GraphY(n,x,y) = (n==n ? real(TermY(y)-getTYmin(n))/(getTYmax(n)-getTYmin(n)) : NaN)
FirstX(x,y) = (_n=getPlotIdx(x,y), _n==_n ? getLogAxisX(_n) ? \
getLogX(_n)**(GraphX(_n,x,y)*(log(getXmax(_n))/log(getLogX(_n)) - \
log(getXmin(_n))/log(getLogX(_n))) + log(getXmin(_n))/log(getLogX(_n))) : \
getXmin(_n) + GraphX(_n,x,y)*(getXmax(_n)-getXmin(_n)) : NaN)
FirstY(x,y) = (_n=getPlotIdx(x,y), _n==_n ? getLogAxisY(_n) ? \
getLogY(_n)**(GraphY(_n,x,y)*(log(getYmax(_n))/log(getLogY(_n)) - \
log(getYmin(_n))/log(getLogY(_n))) + log(getYmin(_n))/log(getLogY(_n))) : \
getYmin(_n) + GraphY(_n,x,y)*(getYmax(_n)-getYmin(_n)) : NaN)
# take GPVAL_... from last plot
TermX(x) = GPVAL_TERM_XMIN + (x-GPVAL_X_MIN) * \
(GPVAL_TERM_XMAX-GPVAL_TERM_XMIN)/(GPVAL_X_MAX-GPVAL_X_MIN)
TermY(y) = GPVAL_TERM_YMIN + (y-GPVAL_Y_MIN) * \
(GPVAL_TERM_YMAX-GPVAL_TERM_YMIN)/(GPVAL_Y_MAX-GPVAL_Y_MIN)
mouse(x,y) = sprintf("%g, %g",FirstX(x,y),FirstY(x,y))
set mouse mouseformat function mouse(x,y)
set multiplot
set size 0.5,1.0
set origin 0.0,0.0
set grid x,y
set key bottom right samplen 2
plot [0:10] '+' u 1:(g($1)) w lp pt 7 lw 2 lc rgb "red" ti " x^2"
GraphParams = addGraphParams(0,0)
set size 0.37,0.45
set origin 0.07, 0.5
set logscale y
set obj 1 rect from graph 0,0 to graph 1,1 fc "white" behind
plot [0:10] '+' u 1:(g($1)) w lp pt 7 lw 2 lc rgb "red" ti " x^2"
unset obj 1
GraphParams = addGraphParams(0,1)
set size 0.5,0.5
set origin 0.5,0.5
set logscale x 2
set logscale y
plot [0.5:100] '+' u (g($1)):1 w lp pt 7 lw 2 lc rgb "web-green" ti "x"
GraphParams = addGraphParams(1,1)
set size 0.5,0.5
set origin 0.5,0.0
unset logscale x
unset logscale y
plot [1:10] '+' u 1:(f($1)) w lp pt 7 lw 2 lc rgb "web-green" ti "x"
GraphParams = addGraphParams(0,0)
unset multiplot
### end of code
Result: (Screen capture from wxt terminal)
Note the moving mouse cursor and the coordinates in the status line and that the coordinates are (NaN,NaN) between the individual graphs.

gnuplot: How to plot points over the bars of a clustered histogram?

I'm making some speedup graphs using histogram clustered. For each instance, I group the speedup reached using 2,4,8,16 and 32 computers. I also use a line to indicate the "linear speedup". However, for each instance/computer, I would like also to place a point on top of it, to indicate a value I'm comparing my speedup to.
I've tried to use the same strategy for plotting the boxes, but using points instead. However, the points are plotted on the same place, they are not obeying the ''clustered'' spacing.
Here is the code I'm using:
set yrange [0:105]
set grid ytics
set style line 1 lc rgb '#0e1111' lt 1 lw 2 pt -1 ps 1.0
set xlabel "Instance" font "sans, 18"
set ylabel "Normalized Speedup (In %)" font "sans, 18"
set style histogram clustered
plot for [COL=2:2] 'data.dat' using COL:xticlabels(1) title columnheade lc rgbcolor "black" lt -1 fs pattern 3,\
for [COL=3:3] 'data.dat' using COL:xticlabels(1) title columnheade lc rgbcolor "black" lt -1 fs pattern 1,\
"linear.t" t "Lin" with linespoints ls 1
Example of data
0 2 4 8 16 32
ta22 65.67 37.98 38.16 30.91 19.24
ta23 73.69 45.59 48.59 44.20 34.19
That's what I got. The points are what I would like to have.
Is it possible to have such a thing? It would also work like an error bar. However, without line and just a ''max''.
Thank you all!
I couldn't run your code. But, based on your image, I understood you problem.
To plot the points with you want, I created a file called points.dat.
0 2 4 8 16 32
ta22 75.67 47.98 48.16 40.91 29.24
ta23 83.69 55.59 58.59 54.20 44.19
This one nothing else is that your data + 10.
The gnuplot code
reset # Restore the default settings
set encoding utf8 # Selects the character encoding
set terminal pngcairo size 800,500 # Generates output in png
set output 'histogram.png' # The filename
set grid ytics ls -1 lc 'gray' # Grid lines ytics only
set yrange [0:100] # Yrange 0 to 100 (% ?)
set style data histograms # Type of data: histograms
set style histogram clustered gap 1 # Type of histogram: clustered with gap 1
set style fill transparent solid 1 border lt -1 # Style: fillstyle and border
stats 'points.dat' skip 1 matrix nooutput # Statistical summary with skip
# for header and without output
numRows = STATS_size_y # Y size of matrix (rows)
numCols = STATS_size_x # X size of matrix (columns)
array Value[numRows*(numCols-1)] # Create an array based on size of data
position = 0 # Count to position on array
# Loop for populate the array
do for [i=1:numRows]{
do for [j=2:numCols]{
# Statistical summary (with skip for header) at each value and without output
stats 'points.dat' skip 1 u j every ::i-1::i-1 nooutput
position = position + 1 # Increase the count of position
Value[position] = STATS_min # Define the array-value as result of statistical analysis
}
}
# Mapping functions:
# i-cluster/rows (x-values),
# j-column (y-values)
# ignore the cluster name ($1)
posX(i, j) = (i - 1) + 1.0*(j - numCols + 3)/numCols # To X-values
posY(i, j) = i == 1 ? Value[j] : Value[numCols - 1 + j] # To Y-values
# The plot itself as newhistogram and nested loops:
# i-loop to bars and title as columnheade
# j-loop to rows (x-values)
# k-loop to columns (y-values)
plot \
newhistogram ,\
for [i=2:numCols]\
'data.dat' u i:xticlabels(1) ls i-1 title columnheade,\
for [j=1:numRows] \
for [k=1:numCols-1] \
'+' u (posX(j, k)):(posY(j, k)) w p pt 5 ps 0.75 lc 'black' notitle
produces

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.

Gnuplot: Scatter plot and density

I have x- and y-data points representing a star cluster. I want to visualize the density using Gnuplot and its scatter function with overlapping points.
I used the following commands:
set style fill transparent solid 0.04 noborder
set style circle radius 0.01
plot "data.dat" u 1:2 with circles lc rgb "red"
The result:
However I want something like that
Is that possible in Gnuplot? Any ideas?
(edit: revised and simplified)
Probably a much better way than my previous answer is the following:
For each data point check how many other data points are within a radius of R. You need to play with the value or R to get some reasonable graph.
Indexing the datalines requires gnuplot>=5.2.0 and the data in a datablock (without empty lines). You can either first plot your file into a datablock (check help table) or see here:
gnuplot: load datafile 1:1 into datablock
The time for creating this graph will increase with number of points O(N^2) because you have to check each point against all others. I'm not sure if there is a smarter and faster method. The example below with 1200 datapoints will take about 4 seconds on my laptop. You basically can apply the same principle for 3D.
Script: works with gnuplot>=5.2.0
### 2D density color plot
reset session
t1 = time(0.0)
# create some random rest data
set table $Data
set samples 700
plot '+' u (invnorm(rand(0))):(invnorm(rand(0))) w table
set samples 500
plot '+' u (invnorm(rand(0))+2):(invnorm(rand(0))+2) w table
unset table
print sprintf("Time data creation: %.3f s",(t0=t1,t1=time(0.0),t1-t0))
# for each datapoint: how many other datapoints are within radius R
R = 0.5 # Radius to check
Dist(x0,y0,x1,y1) = sqrt((x1-x0)**2 + (y1-y0)**2)
set print $Density
do for [i=1:|$Data|] {
x0 = real(word($Data[i],1))
y0 = real(word($Data[i],2))
c = 0
stats $Data u (Dist(x0,y0,$1,$2)<=R ? c=c+1 : 0) nooutput
d = c / (pi * R**2) # density: points per unit area
print sprintf("%g %g %d", x0, y0, d)
}
set print
print sprintf("Time density check: %.3f sec",(t0=t1,t1=time(0.0),t1-t0))
set size ratio -1 # same screen units for x and y
set palette rgb 33,13,10
plot $Density u 1:2:3 w p pt 7 lc palette z notitle
### end of script
Result:
Would it be an option to postprocess the image with imagemagick?
# convert into a gray scale image
convert source.png -colorspace gray -sigmoidal-contrast 10,50% gray.png
# build the gradient, the heights have to sum up to 256
convert -size 10x1 gradient:white-white white.png
convert -size 10x85 gradient:red-yellow \
gradient:yellow-lightgreen \
gradient:lightgreen-blue \
-append gradient.png
convert gradient.png white.png -append full-gradient.png
# finally convert the picture
convert gray.png full-gradient.png -clut target.png
I have not tried but I am quite sure that gnuplot can plot the gray scale image directly.
Here is the (rotated) gradient image:
This is the result:
Although this question is rather "old" and the problem might have been solved differently...
It's probably more for curiosity and fun than for practical purposes.
The following code implements a coloring according to the density of points using gnuplot only. On my older computer it takes a few minutes to plot 1000 points. I would be interested if this code can be improved especially in terms of speed (without using external tools).
It's a pity that gnuplot does not offer basic functionality like sorting, look-up tables, merging, transposing or other basic functions (I know... it's gnuPLOT... and not an analysis tool).
The code:
### density color plot 2D
reset session
# create some dummy datablock with some distribution
N = 1000
set table $Data
set samples N
plot '+' u (invnorm(rand(0))):(invnorm(rand(0))) w table
unset table
# end creating dummy data
stats $Data u 1:2 nooutput
XMin = STATS_min_x
XMax = STATS_max_x
YMin = STATS_min_y
YMax = STATS_max_y
XRange = XMax-XMin
YRange = YMax-YMin
XBinCount = 20
YBinCount = 20
BinNo(x,y) = floor((y-YMin)/YRange*YBinCount)*XBinCount + floor((x-XMin)/XRange*XBinCount)
# do the binning
set table $Bins
plot $Data u (BinNo($1,$2)):(1) smooth freq # with table
unset table
# prepare final data: BinNo, Sum, XPos, YPos
set print $FinalData
do for [i=0:N-1] {
set table $Data3
plot $Data u (BinNumber = BinNo($1,$2),$1):(XPos = $1,$1):(YPos = $2,$2) every ::i::i with table
plot [BinNumber:BinNumber+0.1] $Bins u (BinNumber == $1 ? (PointsInBin = $2,$2) : NaN) with table
print sprintf("%g\t%g\t%g\t%g", XPos, YPos, BinNumber, PointsInBin)
unset table
}
set print
# plot data
set multiplot layout 2,1
set rmargin at screen 0.85
plot $Data u 1:2 w p pt 7 lc rgb "#BBFF0000" t "Data"
set xrange restore # use same xrange as previous plot
set yrange restore
set palette rgbformulae 33,13,10
set colorbox
# draw the bin borders
do for [i=0:XBinCount] {
XBinPos = i/real(XBinCount)*XRange+XMin
set arrow from XBinPos,YMin to XBinPos,YMax nohead lc rgb "grey" dt 1
}
do for [i=0:YBinCount] {
YBinPos = i/real(YBinCount)*YRange+YMin
set arrow from XMin,YBinPos to XMax,YBinPos nohead lc rgb "grey" dt 1
}
plot $FinalData u 1:2:4 w p pt 7 ps 0.5 lc palette z t "Density plot"
unset multiplot
### end of code
The result:

Resources