Plotting labeled intervals in matplotlib/gnuplot
Updated: Now includes handling the data sample and uses mpl dates functionality.
import matplotlib.pyplot as plt
from matplotlib.dates import DateFormatter, MinuteLocator, SecondLocator
import numpy as np
from StringIO import StringIO
import datetime as dt
### The example data
a=StringIO("""a 10:15:22 10:15:30 OK
b 10:15:23 10:15:28 OK
c 10:16:00 10:17:10 FAILED
b 10:16:30 10:16:50 OK
""")
#Converts str into a datetime object.
conv = lambda s: dt.datetime.strptime(s, '%H:%M:%S')
#Use numpy to read the data in.
data = np.genfromtxt(a, converters={1: conv, 2: conv},
names=['caption', 'start', 'stop', 'state'], dtype=None)
cap, start, stop = data['caption'], data['start'], data['stop']
#Check the status, because we paint all lines with the same color
#together
is_ok = (data['state'] == 'OK')
not_ok = np.logical_not(is_ok)
#Get unique captions and there indices and the inverse mapping
captions, unique_idx, caption_inv = np.unique(cap, 1, 1)
#Build y values from the number of unique captions.
y = (caption_inv + 1) / float(len(captions) + 1)
#Plot function
def timelines(y, xstart, xstop, color='b'):
"""Plot timelines at y from xstart to xstop with given color."""
plt.hlines(y, xstart, xstop, color, lw=4)
plt.vlines(xstart, y+0.03, y-0.03, color, lw=2)
plt.vlines(xstop, y+0.03, y-0.03, color, lw=2)
#Plot ok tl black
timelines(y[is_ok], start[is_ok], stop[is_ok], 'k')
#Plot fail tl red
timelines(y[not_ok], start[not_ok], stop[not_ok], 'r')
#Setup the plot
ax = plt.gca()
ax.xaxis_date()
myFmt = DateFormatter('%H:%M:%S')
ax.xaxis.set_major_formatter(myFmt)
ax.xaxis.set_major_locator(SecondLocator(interval=20)) # used to be SecondLocator(0, interval=20)
#To adjust the xlimits a timedelta is needed.
delta = (stop.max() - start.min())/10
plt.yticks(y[unique_idx], captions)
plt.ylim(0,1)
plt.xlim(start.min()-delta, stop.max()+delta)
plt.xlabel('Time')
plt.show()
gnuplot 5.2 version with creating a unique key list
The main difference to @CiroSantilli's solution is that a list of unique keys is created automatically from column 1 and the index can be accessed via the defined function Lookup()
. The referenced gnuplot demo already uses a list of unique items, however, in the OP's case there are duplicates.
Creating such a list of unique items does not exist in gnuplot right away, so you have to implement it yourself.
The code requires gnuplot >=5.2. It is probably difficult to get a solution which works under gnuplot 4.4 (the time of OP's question) because a few useful features were not implemented at that time: do for
-loops, summation
, datablocks, ... (a version for gnuplot 4.6 might be possible with some workarounds).
Edit: the earlier version used with vectors
and linewidth 20
to plot the bars, however, linewidth 20
also extends in x-direction which is not desired here. Therefore, with boxxyerror
is now used.
Code:
### Time chart
reset session
$Data <<EOD
# category start end status
"event 1" 10:15:22 10:15:30 OK
"event 2" 10:15:23 10:15:28 OK
pause 10:16:00 10:17:10 FAILED
"something else" 10:16:30 10:17:50 OK
unknown 10:17:30 10:18:50 OK
"event 3" 10:18:30 10:19:50 FAILED
pause 10:19:30 10:20:50 OK
"event 1" 10:17:30 10:19:20 FAILED
EOD
# create list of keys
List = ''
set table $Dummy
plot $Data u (List=List.'"'.strcol(1).'" ',NaN) w table
unset table
# create list of unique keys
UniqueList = ''
do for [i=1:words(List)] {
item = word(List,i)
found = 0
do for [j=1:words(UniqueList)] {
if (item eq word(UniqueList,j)) { found=1; break }
}
if (!found) { UniqueList = UniqueList.'"'.item.'" '}
}
print UniqueList
# define functions for lookup and color
Lookup(s) = (Index = NaN, sum [i=1:words(UniqueList)] \
(Index = s eq word(UniqueList,i) ? i : Index,0), Index)
Color(s) = s eq "OK" ? 0x00cc00 : 0xff0000
set xdata time
set timefmt "%H:%M:%S"
set format x "%M'".'%S"'
set yrange [0.5:words(UniqueList)+0.5]
plot $Data u (timecolumn(2)):(Idx=Lookup(strcol(1))): \
(timecolumn(3)):(timecolumn(2)):(Idx-0.3):(Idx+0.3): \
(Color(strcol(4))):ytic(strcol(1)) \
w boxxyerror fill solid 1.0 lc rgb var notitle
### end of code
Result: