1
+ # import " /src/cetz.typ" : draw
2
+ # import " util.typ"
3
+ # import " sample.typ"
4
+
5
+ # let kernel-normal (x , stdev : 1.5 ) = {
6
+ (1 / calc . sqrt (2 * calc . pi * calc . pow (stdev ,2 ))) * calc . exp ( - (x * x )/ (2 * calc . pow (stdev ,2 )))
7
+ }
8
+
9
+ # let _violin-render (self , ctx , violin , filling : true ) = {
10
+ let path = range (self . samples )
11
+ . map ((t )=> violin . min + (violin . max - violin . min ) * (t / self . samples ))
12
+ . map ((u )=> (u , (violin . convolve )(u )))
13
+ . map (((u ,v )) => {
14
+ (violin . x-position + v , u )
15
+ })
16
+
17
+ if self . side == " both" {
18
+ path += path . rev (). map (((x ,y ))=> {(2 * violin . x-position - x ,y )})
19
+ } else if self . side == " left" {
20
+ path = path . map ( ((x ,y ))=> {(2 * violin . x-position - x ,y )})
21
+ }
22
+
23
+ let (x , y ) = (ctx . x , ctx . y )
24
+ let stroke-paths = util . compute-stroke-paths (path , (x . min , y . min ), (x . max , y . max ))
25
+
26
+ for p in stroke-paths {
27
+ let args = arguments (.. p , closed : self . side == " both" )
28
+ if filling {
29
+ args = arguments (.. args , stroke : none )
30
+ } else {
31
+ args = arguments (.. args , fill : none )
32
+ }
33
+ draw . line (.. self . style , .. args )
34
+ }
35
+ }
36
+
37
+ # let _plot-prepare (self , ctx ) = {
38
+ self . violins = self . data . map (entry => {
39
+ let points = entry . at (self . y-key )
40
+ let (min , max ) = (calc . min (.. points ), calc . max (.. points ))
41
+ let range = calc . abs (max - min )
42
+ (
43
+ x-position : entry . at (self . x-key ),
44
+ points : points ,
45
+ length : points . len (),
46
+ min : min - (self . extents * range ),
47
+ max : max + (self . extents * range ),
48
+ convolve : (t ) => {
49
+ points . map ((y )=> (self . kernel )((y - t )/ self . bandwidth )). sum () / (points . len () * self . bandwidth )
50
+ }
51
+ )
52
+ })
53
+ return self
54
+ }
55
+
56
+ # let _plot-stroke (self , ctx ) = {
57
+ for violin in self . violins {
58
+ _violin-render (self , ctx , violin , filling : false )
59
+ }
60
+ }
61
+
62
+ # let _plot-fill (self , ctx ) = {
63
+ for violin in self . violins {
64
+ _violin-render (self , ctx , violin , filling : true )
65
+ }
66
+ }
67
+
68
+ # let _plot-legend-preview (self ) = {
69
+ draw . rect ((0 ,0 ), (1 ,1 ), .. self . style )
70
+ }
71
+
72
+
73
+ // / Add a violin plot
74
+ // /
75
+ // / A violin plot is a chart that can be used to compare the distribution of continuous
76
+ // / data between categories.
77
+ // /
78
+ // / - data (array): Array of data items. An item is an array containing an `x` and one
79
+ // / or more `y` values.
80
+ // / - x-key (int, string): Key to use for retreiving the `x` position of the violin.
81
+ // / - y-key (int, string): Key to use for retreiving values of points within the category.
82
+ // / - side (string): The sides of the violin to be rendered:
83
+ // / / left: Plot only the left side of the violin.
84
+ // / / right: Plot only the right side of the violin.
85
+ // / / both: Plot both sides of the violin.
86
+ // / - kernel (function): The kernel density estimator function, which takes a single
87
+ // / `x` value relative to the center of a distribution (0) and
88
+ // / normalized by the bandwidth
89
+ // / - bandwidth (float): The smoothing parameter of the kernel.
90
+ // / - extents (float): The extension of the domain, expressed as a fraction of spread.
91
+ // / - samples (int): The number of samples of the kernel to render.
92
+ // / - style (dictionary): Style override dictionary.
93
+ // / - mark-style (dictionary): (unused, will eventually be used to render interquartile ranges).
94
+ // / - axes (axes): (unstable, documentation to follow once completed).
95
+ // / - label (none, content): The name of the category to be shown in the legend.
96
+ # let add-violin (
97
+ data ,
98
+ x-key : 0 ,
99
+ y-key : 1 ,
100
+ side : " right" ,
101
+ kernel : kernel-normal . with (stdev : 1.5 ),
102
+ bandwidth : 1 ,
103
+ extents : 0.25 ,
104
+
105
+ samples : 50 ,
106
+ style : (:),
107
+ mark-style : (:),
108
+ axes : (" x" , " y" ),
109
+ label : none ,
110
+ ) = {
111
+
112
+ ((
113
+ type : " violins" ,
114
+
115
+ data : data ,
116
+ x-key : x-key ,
117
+ y-key : y-key ,
118
+ side : side ,
119
+ kernel : kernel ,
120
+ bandwidth : bandwidth ,
121
+ extents : extents ,
122
+
123
+ samples : samples ,
124
+ style : style ,
125
+ mark-style : mark-style ,
126
+ axes : axes ,
127
+ label : label ,
128
+
129
+ plot-prepare : _plot-prepare ,
130
+ plot-stroke : _plot-stroke ,
131
+ plot-fill : _plot-fill ,
132
+ plot-legend-preview : _plot-legend-preview ,
133
+ ),)
134
+
135
+ }
0 commit comments