G. LC
G. LC

Reputation: 822

How to set a variable line width when plotting?

I would like to plot a curve with a variable line width. I normally would do the following if I wanted to use points instead of a line:

gnuplot> plot 'curve.dat' u ($1):($2):($1) ps var

where curve.dat is filled with:

0  0
1  1
2  4
3  9
4  16
5  25

and so on. Now if I try something similar for the line width:

gnuplot> plot 'curve.dat' u ($1):($2):($1) lw var

I get the error message:

undefined variable: var

Or is this something that cannot be done with gnuplot?

Upvotes: 4

Views: 1468

Answers (3)

theozh
theozh

Reputation: 26088

So far, there is no variable linewidth in gnuplot. Furthermore, if your data has N points you will have N-1 lines, although, you give N linewidths in your data. Which linewidth of which data point should be skipped? The first one or the last one? If you draw tapered lines you don't have this problem. You can use filledcurves for this, but as @Ilya Zakharevich mentioned, @Gavin Portwood's solution is actually plotting a line height, not a line width.

The solution below is using filledcurves as well, but plotting real linewidths. For this, you have to go to pixel coordinates, do some calculations and go back to axis coordinates (will not yet work for log scale). gnuplot can easily fill curves between two y-values with identical x-values, which, however, would not help here. But gnuplot can also fill closed curves. Hence, the script is creating a closed curve by creating the "right" outline of the path and the reversed "left" outline of the tapered "line".

Edit: complete revision

  • arrays are used instead of indexing of datablocks (both require gnuplot>=5.2.0)
  • instead of separate functions and variables for x and y values, a combined value x + j*y is used where j = sqrt(-1) is the imaginary unit. This shortens the script a bit.

Script: (works with gnuplot>=5.2.0, Sept. 2017)

### variable linewidth / tapered lines plot 
reset session

$Data <<EOD
 1   10    1.0
 9   20   25.0
 2   30   15.0
 4   20    5.0
 7   40    5.0
 3   70    1.0
 2   50   25.0
 4   40    3.0
 7   70   30.0
 9   40   80.0
EOD

# plot data to get GPVAL_... values and number of rows
plot $Data u (r=$0+1,$1):2

j = {0, 1}  # imaginary unit
array P[r]
array LW[r]
stats $Data u (P[$0+1]=$1+j*$2, LW[$0+1]=$3) nooutput  # put data into arrays

# axes to pixel conversion and vice versa
Factor = GPVAL_VERSION==5.2 && int(GPVAL_PATCHLEVEL)<=7 ? \
         GPVAL_TERM eq "wxt" ? 20 : GPVAL_TERM eq "qt" ? 10 : 1 : 1
aMin   = GPVAL_X_MIN     + j*GPVAL_Y_MIN
pMin   = GPVAL_TERM_XMIN + j*GPVAL_TERM_YMIN
R      = Factor*((GPVAL_X_MAX-real(aMin))/(GPVAL_TERM_XMAX-real(pMin)) + \
            j*(GPVAL_Y_MAX-imag(aMin))/(GPVAL_TERM_YMAX-imag(pMin)))
a2p(a) = (real(a)-real(aMin))/real(R) + real(pMin) + j*((imag(a)-imag(aMin))/imag(R) + imag(pMin))
p2a(p) = (real(p)-real(pMin))*real(R) + real(aMin) + j*((imag(p)-imag(pMin))*imag(R) + imag(aMin))

# various functions in pixel coordinates
dp(i)   = a2p(P[i+1])-a2p(P[i])                     # distance between two points
ap(i)   = atan2(imag(dp(i)),real(dp(i))) + acos(0)  # perpendicular angle (independent of setting degrees or radians)
wp(i,k) = LW[k]*0.5*(cos(ap(i)) + j*sin(ap(i)))     # offset for outline

array OUTL[4*|P|-4]
do for [i=1:2*|P|-2] {
    m0 = (i-1)/2 + 1    # integer division!
    n0 = i/2 + 1        # 
    m1 = |P| - m0 
    n1 = |P| - n0 + 1
    OUTL[i]         = p2a(a2p(P[n0]) - wp(m0,n0))
    OUTL[i+2*|P|-2] = p2a(a2p(P[n1]) + wp(m1,n1))
}

set style fill noborder
set key noautotitle
set grid x,y

plot OUTL u (real(OUTL[$0+1])):(imag(OUTL[$0+1])) w filledcurves lc "web-green"
### end of script

Comments:

  • in qt terminal the result looks different, which is not the intended way, but I guess the reason is the way how an area is filled by the graphics library. The script would need some adaptions.
  • for pngcairo terminal: since you have to plot twice (the first time to get the GPVAL_... variables) you also have to set the filename twice, i.e. something like this:
reset session
...
set term pngcairo
set output "SO52250137.png"
plot $Data u (r=$0+1,$1):2
set output "SO52250137.png"
...
plot OUTL u (real(OUTL[$0+1])):(imag(OUTL[$0+1])) w filledcurves lc "web-green"
set output
### end of script

Result: (from wxt and pngcairo terminal)

enter image description here

(output from qt terminal)

enter image description here

Upvotes: 1

Ilya Zakharevich
Ilya Zakharevich

Reputation: 1419

This addresses a problem with Gavin’s answer: his method creates a certain line height (as opposed to line width). I found a different way, which works with widths.

enter image description here

The defects in my solution: one needs to know the “coordinate space aspect ratio” in advance, and there are artifacts at joins.

Upvotes: 1

Gavin Portwood
Gavin Portwood

Reputation: 1217

You're right that linewidth doesn't accept var like pointsize does. But you can have a similar effect by using filledcurves:

WIDTH_FACTOR=20
plot 'curve.dat' u ($1):($2+$1/WIDTH_FACTOR):($2-$1/WIDTH_FACTOR) w filledcurves

1

Upvotes: 3

Related Questions