Search code examples
gnuplot

How to dash a line using different pointtypes in gnuplot?


I'm trying to plot a datafile with dashed lines. Here are some of the values:

2001 dic 21 5,9 9,2 3,8 0,2
2001 dic 22 6,8 8,7 4,8 4,2
2001 dic 23 6,3 8,1 6,5 6,8
2001 dic 24 3,1 4,1 3
2001 dic 25 -1, 3,5 -5
2001 dic 26 4,5 8 0,8 14,8
2001 dic 27 4 7 2,7 0,2
2001 dic 28 0,1 3,4 -3,5
2001 dic 29 7 10,7 2,9 6,2
2001 dic 30 11,2 12,9 8 1,4
2001 dic 31 5,2 6,3 5 3,4
2002 gen 1 2,9 6,9 0,8 
2002 gen 2 -0,8 5,4 -5,4 
2002 gen 3 2,0 8,0 -1,8 
2002 gen 4 2,0 5,2 0,1 
2002 gen 5 -0,8 5,5 -5,2 
2002 gen 6 0,3 6,7 -4,3 
2002 gen 7 0,5 5,6 -3,4 
2002 gen 8 1,2 7,9 -3,3 
2002 gen 9 1,9 8,4 -2,8 
2002 gen 10 1,8 8,1 -2,7 
2002 gen 11 3,6 7,7 -0,7 
2002 gen 12 6,0 10,2 3,5 

For avoiding confusion with other graphs, I should plot not exactly dashed lines but beads ('°'). Gnuplot permits only ".-_" for user-defined dashed lines. Plotting with user-defined linespoints could be the solution but there you have lines and beads, not just 'beads', something like this (look at the plot in the middle): 3 graph plot

To plot with just user-defined points leaves a lot of empty spaces between the beads. So I'm trying to create a new datafile with many more values obtained by linear interpolation of the primitive datafile values, to fill those empty spaces (the first three columns are timestamp data of the kind '2022 feb 28' and the like):

set table $Temp
     plot "datafile.csv" u 1:4:5:6:7 skip 15 w table 
unset table

set print $Temp2  #this file is like $Temp with lots of values added by interpolation   
  do for [i = 1:|$Temp|-1] { 
  print $Temp[i:($1)],$Temp[i:($4)]
              do for [j = 1,10] { print $Temp[i:($1)]+($Temp[i+1:($1)]-$Temp[i:($1)])*j/11), $Temp[i:($4)]+($Temp[i+1:($4)]-$Temp[i:($4)])*j/11) }
              } 
  print $Temp[|$Temp|:($1)],$Temp[|$Temp|:($4)]  
    set print

Obviously this does not work since I'm not handling the columns of the $Temp file correctly. Looking up "arrays" in gnuplot manual does not provide any hint. At the end, the resulting file should be plotted with points pt "°".


Solution

  • Edit: Since I changed the question title to "...different pointtypes...", I should show how to do it with multiple columns and different pointtypes. The data of multiple columns is interpolated along the path and appended to the datablock $DashMultipleCols as (sub-)blocks which can later be addressed via index. I leave it up to you to decide whether solid/dash/dotted or plus/circle/triangle is easier to distinguish.

    The following example creates a datablock with equidistant points along a given path.

    • for illustration some random test data is created with 1 x-column and 3 y-columns

    • for comparison the first graph is with solid, dashed and dotted lines. To my opinion the lines can be well distinguished, but you asked for "dashing" with a symbol.

    • for the second plot the data of columns 2,3,4 is "dashed" with the pointtypes 1,6,8, respectively.

    • the distance between the symbols is set by a variable, here: Dist = 8

    • in order to get a visually equal distribution, coordinate conversions from x,y to pixel coordinates and reverse are used. For this, the gnuplot variables GPVAL... are used which are available after plotting. That's why it is necessary to plot twice. The command pause -1 can be removed.

    • this works for linear axes (would need adjustment for logarithmic axes)

    • Your data is timedata. The code needs to be adjusted accordingly.

    Code: (tested with wxt terminal)

    ### "dashing" with symbols equidistantly along a path
    reset session
    
    # create some random test data
    set print $Data
        x0 = 0
        y0 = y1 = y2 = 100
        do for [i=1:10] {
            print sprintf("%g %g %g %g", x0=x0+int(rand(0)*10)+5, y0=y0+int(rand(0)*10)-5, y1=y1+int(rand(0)*10)-5, y2=y2+int(rand(0)*10)-5)
        }
    set print
    
    set key top left Left reverse
    # plot to get the GPVAL_ ... values
    plot $Data u 1:2 w l dt 1 lc "black" ti "Column 2, solid", \
            '' u 1:3 w l dt 2 lc "black" ti "Column 3, dashed", \
            '' u 1:4 w l dt 3 lc "black" ti "Column 4, dotted"
    
    # store GPVAL parameters after plot in variables
    txmin = GPVAL_TERM_XMIN
    txmax = GPVAL_TERM_XMAX
    tymin = GPVAL_TERM_YMIN
    tymax = GPVAL_TERM_YMAX
    xmin  = GPVAL_X_MIN
    xmax  = GPVAL_X_MAX
    ymin  = GPVAL_Y_MIN
    ymax  = GPVAL_Y_MAX
    
    # x,y to pix and pix to x,y coordinate conversion
    XtoPix(x)    = txmin + real(x-xmin)    *(txmax-txmin)/( xmax- xmin)
    YtoPix(y)    = tymin + real(y-ymin)    *(tymax-tymin)/( ymax- ymin)
    PixToX(scrx) =  xmin + real(scrx-txmin)*( xmax- xmin)/(txmax-txmin)
    PixToY(scry) =  ymin + real(scry-tymin)*( ymax- ymin)/(tymax-tymin)
    
    # get lengths and angles in pixel coordinates
    set angle degrees
    Length(x0,y0,x1,y1) = sqrt((x1-x0)**2 + (y1-y0)**2)
    Angle(x0,y0,x1,y1)  = (_dx=x1-x0, _dy=y1-y0, _L=sqrt(_dx**2 + _dy**2), _L==0 ? NaN : \
                          (_dy>=0 ? acos(_dx/_L) : 360-acos(_dx/_L) ))
    
    colX  = 1
    colsY = "2 3 4"         # multiple y-columns
    do for [colY in colsY] {
        set table $LaA      # table for length and angles
            Total = 0
            plot x1=y1=NaN $Data u (x0=x1,x1=XtoPix(column(colX)),x0):\
                                   (y0=y1,y1=YtoPix(column(int(colY))),y0):\
                    (L=Length(x0,y0,x1,y1)):(L==L ? Total=Total+L : 0, Angle(x0,y0,x1,y1)) w table
        unset table
        X0(n)        = real(word($LaA[n],1))
        Y0(n)        = real(word($LaA[n],2))
        SegLength(n) = real(word($LaA[n],3))
        SegAngle(n)  = real(word($LaA[n],4))
    
        # create equidistant datapoints along path
        set print $DashSingleCol
            Dist = 8                    # Distance between symbols
            N = floor(Total/Dist)
            idx = 2
            L0 = 0
            L = SegLength(idx)
            do for [i=0:N] {
                R = i*Dist
                while (L-R<0) {
                    L0 = L
                    idx = idx + 1
                    L = L + SegLength(idx)
                }
                print sprintf("%g %g", PixToX(X0(idx)+(R-L0)*cos(SegAngle(idx))), \
                                       PixToY(Y0(idx)+(R-L0)*sin(SegAngle(idx))))
            }
        set print
        set print $DashMultipleCols append
            print $DashSingleCol
            print "\n\n"
        set print
    }
    
    pause -1 
    
    mySymbol(n) = int(word("1 6 8",n))
    
    plot for [i=1:words(colsY)] $DashMultipleCols u 1:2:(mySymbol(i)) index i-1 \
              w p pt mySymbol(i) ps 0.6 lc "black" ti sprintf("Column %s, Symbol %d",word(colsY,i),mySymbol(i))
    ### end of code
    

    Result:

    enter image description here

    enter image description here

    Addition: Code adapted for time format

    The time in gnuplot is nothing else than seconds passed since Jan 1st, 1970 00:00:00. If you enter in the gnuplot console print time(0) (which is the current time) you will get something like 1646757721, so about 1.6 billion seconds have passed since then until today.

    The main differences and things to keep in mind compared to the above code:

    • define your specific time format, e.g. myTimeFmt = "%Y %b %d"
    • with this time format and the spaces inbetween, your data will be in columns 4,5, and 6.
    • for plotting the data the first time you use, e.g. plot $Data u (timecolumn(1,myTimeFmt)):4 w l
    • for the length and angle table ($LaA) you also have to use columns 4,5,6 and timecolumn() as well, i.e. plot x1=y1=NaN $Data u (x0=x1,x1=XtoPix(timecolumn(colX,myTimeFmt)),x0)
    • for creating the datablock $DashSingleCol you have to force the format for the time to be "%.0f" i.e. print sprintf("%.0f %g", PixToX(X0(idx)+(R-L0)*cos(SegAngle(idx))). Otherwise, with "%g" gnuplot would write a floating point number with exponent but only 6 digits, e.g. 1.64676e+09 which would be unwanted truncation or rounding.
    • for plotting $DashMultipleCols you can simply use u 1:2 because the time is already in seconds and does not have to be changes via timecolumn().

    I hope this additional code and the explanations will help you to "dash" your data with different symbols.

    Code:

    ### "dashing" with symbols equidistantly along a path (with timedata)
    reset session
    
    myTimeFmt = "%Y %b %d"
    
    # create some random test data
    set print $Data
        t0 = time(0)
        y0 = y1 = y2 = 10
        SecsPerDay =  24*3600   # seconds per day
        do for [i=1:10] {
            t0=t0+int(rand(0)*10*SecsPerDay)+5*SecsPerDay
            print sprintf("%s %g %g %g", strftime(myTimeFmt,t0), \
                    y0=y0+int(rand(0)*10)-5, y1=y1+int(rand(0)*10)-5, y2=y2+int(rand(0)*10)-5)
        }
    set print
    
    set key top left Left reverse
    # plot to get the GPVAL_ ... values
    set format x "%b %01d\n%Y" timedate
    plot $Data u (timecolumn(1,myTimeFmt)):4 w l dt 1 lc "black" ti "Column 4, solid", \
            '' u (timecolumn(1,myTimeFmt)):5 w l dt 2 lc "black" ti "Column 5, dashed", \
            '' u (timecolumn(1,myTimeFmt)):6 w l dt 3 lc "black" ti "Column 6, dotted"
    
    # store GPVAL parameters after plot in variables
    txmin = GPVAL_TERM_XMIN
    txmax = GPVAL_TERM_XMAX
    tymin = GPVAL_TERM_YMIN
    tymax = GPVAL_TERM_YMAX
    xmin  = GPVAL_X_MIN
    xmax  = GPVAL_X_MAX
    ymin  = GPVAL_Y_MIN
    ymax  = GPVAL_Y_MAX
    
    # x,y to pix and pix to x,y coordinate conversion
    XtoPix(x)    = txmin + real(x-xmin)    *(txmax-txmin)/( xmax- xmin)
    YtoPix(y)    = tymin + real(y-ymin)    *(tymax-tymin)/( ymax- ymin)
    PixToX(scrx) =  xmin + real(scrx-txmin)*( xmax- xmin)/(txmax-txmin)
    PixToY(scry) =  ymin + real(scry-tymin)*( ymax- ymin)/(tymax-tymin)
    
    # get lengths and angles in pixel coordinates
    set angle degrees
    Length(x0,y0,x1,y1) = sqrt((x1-x0)**2 + (y1-y0)**2)
    Angle(x0,y0,x1,y1)  = (_dx=x1-x0, _dy=y1-y0, _L=sqrt(_dx**2 + _dy**2), _L==0 ? NaN : \
                          (_dy>=0 ? acos(_dx/_L) : 360-acos(_dx/_L) ))
    
    colX  = 1
    colsY = "4 5 6"         # multiple y-columns
    do for [colY in colsY] {
        set table $LaA      # table for length and angles
            Total = 0
            plot x1=y1=NaN $Data u (x0=x1,x1=XtoPix(timecolumn(colX,myTimeFmt)),x0):\
                                   (y0=y1,y1=YtoPix(column(int(colY))),y0):\
                    (L=Length(x0,y0,x1,y1)):(L==L ? Total=Total+L : 0, Angle(x0,y0,x1,y1)) w table
        unset table
        X0(n)        = real(word($LaA[n],1))
        Y0(n)        = real(word($LaA[n],2))
        SegLength(n) = real(word($LaA[n],3))
        SegAngle(n)  = real(word($LaA[n],4))
    
        # create equidistant datapoints along path
        set print $DashSingleCol
            Dist = 8                    # Distance between symbols
            N = floor(Total/Dist)
            idx = 2
            L0 = 0
            L = SegLength(idx)
            do for [i=0:N] {
                R = i*Dist
                while (L-R<0) {
                    L0 = L
                    idx = idx + 1
                    L = L + SegLength(idx)
                }
                print sprintf("%.0f %g", PixToX(X0(idx)+(R-L0)*cos(SegAngle(idx))), \
                                       PixToY(Y0(idx)+(R-L0)*sin(SegAngle(idx))))
            }
        set print
        set print $DashMultipleCols append
            print $DashSingleCol
            print "\n\n"
        set print
    }
    
    pause -1 
    
    mySymbol(n) = int(word("1 6 8",n))
    
    plot for [i=1:words(colsY)] $DashMultipleCols u 1:2:(mySymbol(i)) index i-1 \
              w p pt mySymbol(i) ps 0.6 lc "black" ti sprintf("Column %s, pointtype %d",word(colsY,i),mySymbol(i))
    ### end of code
    

    Result:

    enter image description here

    enter image description here