-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy patheventplot.py
More file actions
123 lines (102 loc) · 4.23 KB
/
Copy patheventplot.py
File metadata and controls
123 lines (102 loc) · 4.23 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
"""Custom artist: eventplot (raster / tick).
A grid of short vertical (or horizontal) tick marks, one per event
time. Used for spike trains, sequence motif hits, log timelines. Each
call adds one *row* of ticks at a given y; call multiple times for
multiple rows.
API: c.add_eventplot(df, aes(x="position_col"), at=0, length=0.6,
orientation="vertical").
- `x=` -> column of event times along the data axis.
- `at=` -> scalar data-coord position of the row baseline
(default 0). The perpendicular row placement, not a
data column — eventplot is intrinsically 1-D.
- `length` -> tick length in data units along the orthogonal axis.
- `orientation -> "vertical" (default; tick is vertical, row stacks on y)
or "horizontal" (tick is horizontal, row stacks on x).
"""
SUMMARY = 'Vertical or horizontal tick marks per row, for spike rasters / event timelines.'
from pathlib import Path
import plotlet as pt
from plotlet.utils import to_list, pack_opts
from plotlet.draw import segment
def eventplot_record(data=None, x=None, orientation=None, at=None,
length=None, linewidth=None, label=None, color=None):
if data is None or x is None:
raise TypeError("eventplot requires data=, x= (positions column).")
return {"type": "eventplot", "pos": to_list(data[x]),
"opts": pack_opts(orientation=orientation, at=at, length=length,
linewidth=linewidth, label=label, color=color)}
def eventplot_xdomain(a):
if a["opts"].get("orientation", "vertical") == "vertical":
return a["pos"]
y = a["opts"].get("at", 0); ln = a["opts"].get("length", 0.6)
return [y - ln / 2, y + ln / 2]
def eventplot_ydomain(a):
if a["opts"].get("orientation", "vertical") == "horizontal":
return a["pos"]
y = a["opts"].get("at", 0); ln = a["opts"].get("length", 0.6)
return [y - ln / 2, y + ln / 2]
def eventplot_draw(a, ctx):
col = ctx.color
lw = a["opts"].get("linewidth", 1.2)
y = a["opts"].get("at", 0)
ln = a["opts"].get("length", 0.6)
orient = a["opts"].get("orientation", "vertical")
out = []
if orient == "vertical":
y1 = ctx.y_scale(y - ln / 2)
y2 = ctx.y_scale(y + ln / 2)
for x in a["pos"]:
px = ctx.x_scale(x)
out.append(segment(px, y1, px, y2, color=col, width=lw))
else: # horizontal — `y` here is read as an x-coord baseline
x1 = ctx.x_scale(y - ln / 2)
x2 = ctx.x_scale(y + ln / 2)
for p in a["pos"]:
py = ctx.y_scale(p)
out.append(segment(x1, py, x2, py, color=col, width=lw))
return "".join(out)
def eventplot_legend_entries(a):
label = a["opts"].get("label")
if not label:
return []
def paint(a, ctx, x0, y_mid):
col = a["_color"]
return (
segment(x0 + 4, y_mid - 5, x0 + 4, y_mid + 5, color=col)
+ segment(x0 + 10, y_mid - 5, x0 + 10, y_mid + 5, color=col)
+ segment(x0 + 14, y_mid - 5, x0 + 14, y_mid + 5, color=col)
+ segment(x0 + 19, y_mid - 5, x0 + 19, y_mid + 5, color=col)
)
return [{"label": label, "color": a.get("_color"), "paint": paint}]
pt.add_artist(pt.ArtistSpec(
name="eventplot",
record=eventplot_record,
xdomain=eventplot_xdomain,
ydomain=eventplot_ydomain,
draw=eventplot_draw,
legend_entries=eventplot_legend_entries,
))
def demo():
"""Build the demonstration chart with synthetic data.
Returns a `pt.Chart` ready for `.save_svg()` or further composition."""
import random
random.seed(4)
n_rows = 8
c = pt.chart(data_height=240)
for i in range(n_rows):
# Each row: ~30 events with rate ~1/sec, total 30s.
t = 0
events = []
while t < 30:
t += random.expovariate(1.0)
if t < 30:
events.append(t)
df = {"t": events}
c.add_eventplot(df, pt.aes(x="t"), at=i, length=0.7,
color="C0", label="trial 0" if i == 0 else None)
c.title("Spike raster").xlabel("time (s)").ylabel("trial")
return c
if __name__ == "__main__":
out = Path(__file__).with_suffix(".svg")
demo().save_svg(out)
print(f"wrote {out}")