draw.scm 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835
  1. ;; guile-charting
  2. ;; Copyright (C) 2007, 2012, 2014, 2019 Andy Wingo <wingo at pobox dot com>
  3. ;; This library is free software; you can redistribute it and/or
  4. ;; modify it under the terms of the GNU Lesser General Public
  5. ;; License as published by the Free Software Foundation; either
  6. ;; version 3 of the License, or (at your option) any later version.
  7. ;;
  8. ;; This library is distributed in the hope that it will be useful,
  9. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  11. ;; Lesser General Public License for more details.
  12. ;;
  13. ;; You should have received a copy of the GNU Lesser General Public
  14. ;; License along with this library; if not, see
  15. ;; <http://www.gnu.org/licenses/>.
  16. ;;; Commentary:
  17. ;;
  18. ;;
  19. ;;
  20. ;;; Code:
  21. (define-module (charting draw)
  22. #:use-module (ice-9 format)
  23. #:use-module (ice-9 match)
  24. #:use-module (cairo)
  25. #:use-module (charting util)
  26. #:export (draw-legend
  27. draw-bar
  28. draw-axis-label
  29. draw-bar-group
  30. draw-perf-test
  31. draw-annotations
  32. draw-perf-series
  33. draw-page-map
  34. draw-bar-legend
  35. draw-point
  36. draw-decorator
  37. draw-ticks
  38. draw-tick-labels
  39. draw-grid
  40. draw-title
  41. draw-chart-area
  42. draw-background
  43. reset-colors!
  44. ))
  45. (define *pi* 3.141592653589793)
  46. (define-syntax-rule (with-cairo cr body ...)
  47. (begin
  48. (cairo-save cr)
  49. (call-with-values (lambda () body ...)
  50. (lambda vals
  51. (cairo-restore cr)
  52. (apply values vals)))))
  53. (define-syntax-rule (with-rgb cr r g b body ...)
  54. (with-cairo cr
  55. (cairo-set-source-rgb cr r g b)
  56. body ...))
  57. (define-syntax-rule (with-rgba cr r g b a body ...)
  58. (with-cairo cr
  59. (cairo-set-source-rgba cr r g b a)
  60. body ...))
  61. (define-syntax-rule (with-line-width cr w body ...)
  62. (with-cairo cr
  63. (cairo-set-line-width cr w)
  64. body ...))
  65. (define-syntax-rule (with-font-size cr s body ...)
  66. (with-cairo cr
  67. (cairo-set-font-size cr s)
  68. body ...))
  69. (define-syntax-rule (with-y-inverted cr body ...)
  70. (with-cairo cr
  71. (cairo-scale cr 1 -1)
  72. body ...))
  73. (define-syntax-rule (with-rotate cr degrees body ...)
  74. (with-cairo cr
  75. (cairo-rotate cr (* 2 *pi* (/ degrees 360)))
  76. body ...))
  77. (define-syntax-rule (with-color cr color body ...)
  78. (with-cairo cr
  79. (apply cairo-set-source-rgb cr
  80. (map
  81. (lambda (x) (/ x 255.0))
  82. (let ((c color))
  83. (assoc-ref *palette* c))))
  84. body ...))
  85. (define-syntax-rule (with-color-for-series cr series body ...)
  86. (with-color cr (color-for-series series)
  87. body ...))
  88. (define-syntax-rule (with-current-point cr x y body ...)
  89. (call-with-values (lambda () (cairo-get-current-point cr))
  90. (lambda (x y) body ...)))
  91. ;; the 2014 material design palette, mixed up a bit
  92. (define *palette*
  93. '(("Light Blue 500" 3 169 244)
  94. ("Teal 500" 0 150 136)
  95. ("Orange 500" 255 152 0)
  96. ("Yellow 500" 255 235 59)
  97. ("Light Green 500" 139 195 74)
  98. ("Amber 500" 255 193 7)
  99. ("Indigo 500" 63 81 181)
  100. ("Red 500" 244 67 54)
  101. ("Cyan 500" 0 188 212)
  102. ("Deep Orange 500" 255 87 34)
  103. ("Lime 500" 205 220 57)
  104. ("Brown 500" 121 85 72)
  105. ("Blue 500" 33 150 243)
  106. ("Blue Gray 500" 96 125 139)
  107. ("Purple 500" 156 39 176)
  108. ("Deep purple 500" 103 58 183)
  109. ("Pink 500" 233 30 99)
  110. ("Green 500" 76 175 80)
  111. ("Gray 100" #xf5 #xf5 #xf5)
  112. ("Gray 200" #xee #xee #xee)
  113. ("Gray 300" #xe0 #xe0 #xe0)
  114. ("Gray 800" #x42 #x42 #x42)))
  115. (define *series-colors* '())
  116. (define *available-colors* *palette*)
  117. (define (reset-colors!)
  118. (set! *series-colors* '())
  119. (set! *available-colors* *palette*))
  120. (define (color-for-series series)
  121. (cond
  122. ((assoc-ref *series-colors* series))
  123. ((pair? *available-colors*)
  124. (set! *series-colors*
  125. (acons series (caar *available-colors*)
  126. *series-colors*))
  127. (set! *available-colors*
  128. (cdr *available-colors*))
  129. (pk series (cdar *series-colors*)))
  130. (else
  131. (let ((color-name (string-append "tmp-" series))
  132. (color (list (random 256) (random 256) (random 256))))
  133. (set! *series-colors* (acons series color-name *series-colors*))
  134. (set! *palette* (acons color-name color *palette*))
  135. color-name))))
  136. (define (rel-rectangle cr width height)
  137. (cairo-rel-line-to cr width 0)
  138. (cairo-rel-line-to cr 0 height)
  139. (cairo-rel-line-to cr (- width) 0)
  140. (cairo-rel-line-to cr 0 (- height)))
  141. (define (filled-rectangle cr width height stroke fill)
  142. (rel-rectangle cr width height)
  143. (with-color cr fill
  144. (cairo-fill-preserve cr))
  145. (with-color cr stroke
  146. (cairo-stroke cr)))
  147. (define (filled-rectangle/1 cr width height stroke fill)
  148. (let ((line-width (cairo-get-line-width cr)))
  149. (cairo-rel-move-to cr (/ line-width 2) (/ line-width 2))
  150. (filled-rectangle cr (- width line-width)
  151. (- height line-width)
  152. stroke fill)))
  153. (define (show-text cr text justification)
  154. (with-cairo cr
  155. (let ((width (cairo-text-extents:width
  156. (cairo-text-extents cr text))))
  157. (cairo-rel-move-to
  158. cr
  159. (case justification
  160. ((left) 0)
  161. ((right) (- width))
  162. ((center) (- (/ width 2)))
  163. (else
  164. (error "unknown justification" justification)))
  165. 0)
  166. (cairo-scale cr 1 -1)
  167. (cairo-show-text cr text))))
  168. (define-with-kwargs (draw-legend cr expand-right? expand-down?
  169. measure-only? (text-height 12)
  170. (draw-outlines? #t)
  171. (draw-background? #t)
  172. (text-measurer
  173. (lambda (size)
  174. (lambda (text)
  175. (with-cairo cr
  176. (cairo-set-font-size
  177. cr (or size text-height))
  178. (cairo-text-width cr text)))))
  179. (series-list '()))
  180. "Draw a legend.
  181. @var{series-list} is expected to be a list of series names. The
  182. @var{cr} is expected to be positioned at one of the corners of
  183. the legend; @var{expand-right?} and @var{expand-down?} control which way
  184. the legend will be rendered."
  185. (let* ((box-width text-height)
  186. (padding 5)
  187. (spacing (/ text-height 2))
  188. (text-width (apply max (map (text-measurer text-height)
  189. series-list)))
  190. (total-width (+ (* 2 padding) box-width spacing text-width))
  191. (num-series (length series-list))
  192. (total-height (+ (* 2 padding) (* (- num-series 1) spacing)
  193. (* num-series text-height))))
  194. (cond
  195. (measure-only?
  196. (values total-width total-height))
  197. (else
  198. (with-current-point
  199. cr basex basey
  200. (cairo-rel-move-to cr (if expand-right? 0 (- total-width))
  201. (if expand-down? (- total-height) 0))
  202. (when draw-background?
  203. (filled-rectangle/1 cr total-width total-height
  204. "Gray 800" "Gray 100"))
  205. (let lp ((x (+ basex (if expand-right?
  206. padding
  207. (- (- total-width padding)))))
  208. (y (+ basey (if expand-down?
  209. (- padding)
  210. (- total-height padding))))
  211. (series-list series-list))
  212. (cond
  213. ((not (null? series-list))
  214. (cairo-move-to cr x (- y box-width))
  215. (if draw-outlines?
  216. (filled-rectangle/1 cr box-width box-width
  217. "Gray 800"
  218. (color-for-series (car series-list)))
  219. (begin
  220. (rel-rectangle cr box-width box-width)
  221. (with-color cr (color-for-series (car series-list))
  222. (cairo-fill cr))))
  223. (cairo-move-to cr (+ x box-width spacing) (- y text-height))
  224. (with-font-size cr text-height
  225. (show-text cr (car series-list) 'left))
  226. (lp x (- y text-height spacing) (cdr series-list))))))))))
  227. (define-with-kwargs (draw-decorator cr scale label y+-bracket y--bracket
  228. y-bracket)
  229. "Draw a decorator.
  230. A decorator is something drawn around a point, such as error
  231. bars. This function currently supports drawing error bars in the
  232. Y direction, which are specified individually as @var{y+-bracket}
  233. and @var{y--bracket}."
  234. (define (make-bracket x y mx my len)
  235. (cairo-move-to cr x y)
  236. (cairo-rel-line-to cr (* mx len) (* my len))
  237. (cairo-rel-line-to cr (* my -5) (* mx 0))
  238. (cairo-rel-line-to cr (* my 10) (* mx 0))
  239. (with-color cr "Gray 800" (cairo-stroke cr)))
  240. (with-current-point
  241. cr x y
  242. (if (or y+-bracket y-bracket)
  243. (make-bracket x y 0 1 (* (or y+-bracket y-bracket) scale)))
  244. (if label
  245. (begin
  246. (cairo-move-to cr x (+ y 4))
  247. (show-text cr label 'center)))
  248. (if (or y--bracket y-bracket)
  249. (make-bracket x y 0 -1 (* (or y--bracket y-bracket) scale)))))
  250. (define-with-kwargs (draw-bar cr height scale bar-width bar-value-formatter
  251. series (decorator '()))
  252. "Draw a single bar.
  253. @var{cr} is expected to have been placed at the lower left corner
  254. of where the bar should be. @var{decorator} is a property list
  255. that can be passed to @ref{charting draw draw-decorator}."
  256. (with-current-point
  257. cr x y
  258. (filled-rectangle/1 cr bar-width (* height scale)
  259. "Gray 800"
  260. (color-for-series series))
  261. (cairo-move-to cr (+ x (/ bar-width 2)) (+ y (* height scale)))
  262. (apply draw-decorator cr scale
  263. #:label (and bar-value-formatter (bar-value-formatter height))
  264. decorator)))
  265. (define-with-kwargs (draw-bar-group cr data bar-width scale bar-value-formatter)
  266. "Draw a group of bars.
  267. @var{data} is a property list suitable for passing to
  268. @ref{charting draw draw-bar}. @var{cr} is expected to have been positioned
  269. along the x axis in the center of where the bar group should be
  270. displayed."
  271. (cairo-rel-move-to cr (- (/ (* (length data) bar-width) 2)) 0)
  272. (with-current-point
  273. cr basex basey
  274. (let lp ((data data) (n 0))
  275. (cond
  276. ((not (null? data))
  277. (cairo-move-to cr (+ basex (* n bar-width)) basey)
  278. (apply draw-bar cr (caar data) scale bar-width bar-value-formatter
  279. (cdar data))
  280. (lp (cdr data) (1+ n)))))))
  281. (define-with-kwargs (draw-bar-legend cr data width
  282. (text-height 10)
  283. (font-family "Bitstream Vera Sans")
  284. (horizontal-spacing 10)
  285. (vertical-spacing 2))
  286. "Draw a \"bar legend\".
  287. A bar legend is meant to show what categories exist, as well as
  288. indicating their contribution to a graph. Use a bar legend if it would
  289. be confusing to label some other chart in which the pixel count of a
  290. category is proportional to its magnitude, but you want to make sure to
  291. label all categories, even those with small magnitudes.
  292. @var{data} is as in @ref{charting draw draw-page-map}. The legend will
  293. be written below the current position of @var{cr}."
  294. (with-current-point
  295. cr x0 y0
  296. (with-cairo
  297. cr
  298. (cairo-select-font-face cr font-family 'normal 'normal)
  299. (cairo-set-font-size cr text-height)
  300. (match data
  301. (((section-names . (starts . sizes)) ...)
  302. (let ((max-size (apply max sizes))
  303. (labels-width (apply max (map (lambda (name)
  304. (cairo-text-extents:width
  305. (cairo-text-extents cr name)))
  306. section-names))))
  307. (let lp ((y0 y0)
  308. (labels section-names)
  309. (sizes sizes))
  310. (match (cons labels sizes)
  311. ((() . ()) #t)
  312. (((label . labels) . (size . sizes))
  313. (let ((y0 (- y0 text-height)))
  314. (cairo-move-to cr (+ x0 labels-width) y0)
  315. (show-text cr label 'right)
  316. (cairo-move-to cr (+ labels-width horizontal-spacing) y0)
  317. (with-color-for-series
  318. cr label
  319. (rel-rectangle
  320. cr
  321. (* (- width labels-width horizontal-spacing)
  322. (/ size max-size))
  323. text-height)
  324. (cairo-fill cr))
  325. (cairo-move-to cr width y0)
  326. (show-text cr (format #f "~a" size) 'right)
  327. (lp (- y0 vertical-spacing) labels sizes)))))))))))
  328. (define (draw-box-background cr width color x bottom top)
  329. (cairo-move-to cr x bottom)
  330. (rel-rectangle cr width top)
  331. (with-color cr color (cairo-fill cr)))
  332. (define (draw-box-tick cr width color line-width x y)
  333. (cairo-move-to cr x y)
  334. (with-line-width cr (* (cairo-get-line-width cr) line-width)
  335. (cairo-rel-line-to cr width 0)
  336. (with-color cr color
  337. (cairo-stroke cr))))
  338. (define (draw-box-range cr width color x min max)
  339. (cairo-move-to cr (+ x (/ width 2)) min)
  340. (cairo-rel-line-to cr 0 (- max min))
  341. (with-color cr color (cairo-stroke cr)))
  342. (define-with-kwargs (draw-box-plot cr data translate-y
  343. box-width box-value-formatter)
  344. "Draw a single box plot.
  345. data := (series point ...)
  346. @var{cr} is expected to have been placed at the lower left corner of
  347. where the box-plot should be."
  348. (let* ((series (car data))
  349. (color (color-for-series (car data)))
  350. (ordered (list->vector (sort (cdr data) <)))
  351. (len (vector-length ordered)))
  352. (define (percentile p)
  353. (vector-ref ordered (min (floor/ (* p len) 100) (1- len))))
  354. (unless (zero? len)
  355. (with-current-point
  356. cr x y
  357. (let ((median (if (even? len)
  358. (/ (+ (vector-ref ordered (/ len 2))
  359. (vector-ref ordered (1- (/ len 2))))
  360. 2)
  361. (vector-ref ordered (/ (1- len) 2)))))
  362. (draw-box-background cr box-width color x
  363. (translate-y (percentile 25))
  364. (translate-y (percentile 75)))
  365. (draw-box-range cr box-width color x
  366. (translate-y (percentile 0))
  367. (translate-y (percentile 100)))
  368. (for-each
  369. (lambda (tick)
  370. (match tick
  371. ((p width thickness)
  372. (draw-box-tick cr (* box-width width) color thickness
  373. (+ x
  374. (/ (* (- 1.0 width) box-width) 2))
  375. (translate-y (percentile p))))))
  376. '((5 0.5 1)
  377. (95 0.5 1)))
  378. (draw-box-tick cr (/ box-width 2) "Gray 800" 2
  379. (+ x (/ box-width 4)) (translate-y median))
  380. (when box-value-formatter
  381. (cairo-move-to cr (+ x (/ box-width 2))
  382. (+ y 4 (translate-y (vector-ref ordered (1- len)))))
  383. (with-font-size cr 10
  384. (show-text cr (box-value-formatter median) 'center))))))))
  385. (define (compute-histogram ordered translate-y)
  386. (let* ((len (vector-length ordered))
  387. (base (floor (translate-y (vector-ref ordered 0)))))
  388. (define (bucket-for n)
  389. (inexact->exact (floor (- (translate-y (vector-ref ordered n)) base))))
  390. (let ((ret (make-vector (1+ (bucket-for (1- len))) 0)))
  391. (let lp ((n 0))
  392. (when (< n len)
  393. (let ((bucket (bucket-for n)))
  394. (vector-set! ret bucket (1+ (vector-ref ret bucket)))
  395. (lp (1+ n)))))
  396. (values base ret))))
  397. (define (draw-histogram cr x base buckets len box-width color)
  398. (let ((x (+ x (/ box-width 2)))
  399. (x-scale (/ (* box-width 1.5) len)))
  400. (cairo-move-to cr x base)
  401. (let lp ((n 0) (prev 0))
  402. (when (< n (vector-length buckets))
  403. (let ((next (* (vector-ref buckets n) x-scale)))
  404. (cairo-rel-line-to cr (- next prev) 0)
  405. (cairo-rel-line-to cr 0 1)
  406. (lp (1+ n) next))))
  407. (cairo-line-to cr x (+ base (vector-length buckets)))
  408. (let lp ((n (1- (vector-length buckets))) (prev 0))
  409. (when (>= n 0)
  410. (let ((next (* (vector-ref buckets n) x-scale)))
  411. (cairo-rel-line-to cr (- prev next) 0)
  412. (cairo-rel-line-to cr 0 -1)
  413. (lp (1- n) next))))
  414. (cairo-close-path cr)
  415. (with-color cr color
  416. (cairo-fill cr))))
  417. (define (draw-histogram* cr x base buckets len width color)
  418. (let ((x-scale (/ (* width 3) len)))
  419. (cairo-move-to cr x base)
  420. (let lp ((n 0) (prev 0))
  421. (when (< n (vector-length buckets))
  422. (let ((next (* (vector-ref buckets n) x-scale)))
  423. (cairo-rel-line-to cr (- next prev) 0)
  424. (cairo-rel-line-to cr 0 1)
  425. (lp (1+ n) next))))
  426. (cairo-line-to cr x (+ base (vector-length buckets)))
  427. (let lp ((n (1- (vector-length buckets))) (prev 0))
  428. (when (>= n 0)
  429. (let ((next (* (vector-ref buckets n) x-scale)))
  430. (cairo-rel-line-to cr (- prev next) 0)
  431. (cairo-rel-line-to cr 0 -1)
  432. (lp (1- n) next))))
  433. (cairo-close-path cr)
  434. (with-color cr color
  435. (cairo-fill cr))))
  436. (define-with-kwargs (draw-histo-plot cr series points box-width label-offset
  437. translate-y box-value-formatter
  438. label-height vertical-label? baseline)
  439. "Draw a histogram of @var{points} as part of a larger chart of data.
  440. @var{cr} is expected to have been placed at the lower left corner of
  441. where the histo-plot should be."
  442. (let* ((color (color-for-series series))
  443. (ordered (list->vector (sort points <)))
  444. (len (vector-length ordered))
  445. (median (and (> len 0)
  446. (if (even? len)
  447. (/ (+ (vector-ref ordered (/ len 2))
  448. (vector-ref ordered (1- (/ len 2))))
  449. 2)
  450. (vector-ref ordered (/ (1- len) 2))))))
  451. (define (percentile p)
  452. (vector-ref ordered (min (floor/ (* p len) 100) (1- len))))
  453. (unless (zero? len)
  454. (with-current-point
  455. cr x y
  456. (when baseline
  457. (cairo-rel-move-to cr 0 (translate-y baseline)))
  458. (rel-rectangle cr box-width (- (translate-y median)
  459. (if baseline
  460. (translate-y baseline)
  461. 0)))
  462. (with-color cr color (cairo-fill cr))
  463. (call-with-values (lambda () (compute-histogram ordered translate-y))
  464. (lambda (base buckets)
  465. (draw-histogram cr x base buckets len box-width "Gray 800")))
  466. (with-line-width cr (/ (cairo-get-line-width cr) 2)
  467. (draw-box-range cr box-width "Gray 800" x
  468. (translate-y (percentile 0))
  469. (translate-y (percentile 100))))
  470. (when box-value-formatter
  471. (call-with-values (lambda ()
  472. (cond
  473. ((not baseline)
  474. (values median 1.0))
  475. ((< median baseline)
  476. (values baseline 1.0))
  477. (else
  478. (values baseline -1.0))))
  479. (lambda (val direction)
  480. (cairo-move-to cr (+ x (/ box-width 2))
  481. (+ y (translate-y val)
  482. (* direction label-offset)))
  483. (with-font-size
  484. cr label-height
  485. (let ((label (box-value-formatter median)))
  486. (if vertical-label?
  487. (begin
  488. (cairo-rel-move-to cr (* label-height 0.5) 0)
  489. (with-rotate
  490. cr 90
  491. (show-text cr label
  492. (if (negative? direction) 'right 'left))))
  493. (show-text cr label 'center)))))))))))
  494. (define-with-kwargs (draw-perf-test cr data box-width box-spacing
  495. translate-y box-value-formatter
  496. box-label-height vertical-box-labels?
  497. baseline)
  498. "Draw a group of boxes corresponding to runs of one benchmark in
  499. different scenarios.
  500. Each scenario corresponds to a series. The format of @var{data} is
  501. @code{((@var{series} @var{point} ...) ...)}, where the series is a
  502. string, and the points are numbers. @var{cr} is expected to have been
  503. positioned along the x axis in the center of where the data for the test
  504. should be displayed."
  505. (define (avg l)
  506. (if (null? l)
  507. 0.0
  508. (/ (apply + l) 1.0 (length l))))
  509. (define (variance l)
  510. (let* ((mean (avg l)))
  511. (avg (map (lambda (x) (expt (- x mean) 2)) l))))
  512. (define (translated-variance l)
  513. (variance (map translate-y l)))
  514. (let* ((sqrt-avg-translated-variance
  515. (sqrt (avg (map translated-variance (map cdr data)))))
  516. (label-offset (if baseline
  517. box-label-height
  518. (+ 6 (* sqrt-avg-translated-variance 2)))))
  519. (cairo-rel-move-to cr
  520. (- (/ (* (length data) (+ box-width box-spacing)) 2))
  521. 0)
  522. (with-current-point
  523. cr basex basey
  524. (let lp ((data data) (n 0))
  525. (match data
  526. (() #t)
  527. (((series . points) . data)
  528. (cairo-move-to cr
  529. (+ basex (* n (+ box-width box-spacing))
  530. (/ box-spacing 2)
  531. 0.5)
  532. basey)
  533. (unless (null? points)
  534. (draw-histo-plot cr series points box-width label-offset
  535. translate-y box-value-formatter
  536. box-label-height vertical-box-labels?
  537. baseline))
  538. (lp data (1+ n))))))))
  539. (define (draw-background-shape cr ordered xticks box-width box-spacing
  540. translate-y range color)
  541. (define (percentile ordered p)
  542. (vector-ref ordered (min (floor/ (* p (vector-length ordered)) 100) (1- (vector-length ordered)))))
  543. (cairo-move-to cr (car xticks) (translate-y (percentile (car ordered) (+ 50 range))))
  544. (for-each (lambda (x ordered)
  545. (cairo-line-to cr x (translate-y (percentile ordered (+ 50 range)))))
  546. xticks ordered)
  547. (for-each (lambda (x ordered)
  548. (cairo-line-to cr x (translate-y (percentile ordered (- 50 range)))))
  549. (reverse xticks) (reverse ordered))
  550. (cairo-close-path cr)
  551. (with-color cr color
  552. (cairo-fill cr)))
  553. (define-with-kwargs (draw-histo-plots cr ordered xticks box-width box-spacing
  554. translate-y)
  555. (for-each
  556. (lambda (x ordered)
  557. (define (percentile p)
  558. (vector-ref ordered (min (floor/ (* p (vector-length ordered)) 100) (1- (vector-length ordered)))))
  559. (call-with-values (lambda () (compute-histogram ordered translate-y))
  560. (lambda (base buckets)
  561. (draw-histogram* cr x base buckets (vector-length ordered) (* (+ box-width box-spacing) 3) "Gray 800")))
  562. (with-line-width cr (/ (cairo-get-line-width cr) 2)
  563. (draw-box-range cr 0 "Gray 800" x
  564. (translate-y (percentile 0))
  565. (translate-y (percentile 100))))
  566. #;
  567. (when box-value-formatter
  568. (cairo-move-to cr (+ x (/ box-width 2))
  569. (+ y 25 (translate-y median)))
  570. (with-font-size cr 10
  571. (show-text cr (box-value-formatter median) 'center))))
  572. xticks ordered))
  573. (define (draw-median-labels cr median xticks translate-y box-value-formatter)
  574. (for-each (lambda (x y)
  575. (cairo-move-to cr (+ x 5) (- (translate-y y) 5))
  576. (with-font-size cr 10
  577. (show-text cr (box-value-formatter y) 'left)))
  578. xticks median))
  579. (define-with-kwargs (draw-annotations cr annotations xticks width height)
  580. (define (translate-x x)
  581. (let lp ((ticks xticks))
  582. (match ticks
  583. (((before-from . before-to) (after-from . after-to) . _)
  584. (if (<= before-from x after-from)
  585. (+ before-to
  586. (* (- x before-from)
  587. (/ (- after-to before-to) (- after-from before-from))))
  588. (lp (cdr ticks)))))))
  589. (for-each (lambda (x)
  590. (cairo-move-to cr x 0)
  591. (cairo-line-to cr x height)
  592. (with-color cr "Gray 800" (cairo-stroke cr)))
  593. (map translate-x annotations)))
  594. (define-with-kwargs (draw-perf-series cr data xticks box-width box-spacing
  595. translate-y box-value-formatter
  596. vertical-labels?)
  597. "Draw a group of boxes corresponding to runs of one benchmark in
  598. different scenarios.
  599. @var{data} := @code{(@var{x} @var{point} ...) ...)}
  600. , where the series is a
  601. string, and the points are numbers. @var{cr} is expected to have been
  602. positioned along the x axis in the center of where the data for the test
  603. should be displayed."
  604. (let* ((ordered (map (lambda (data) (list->vector (sort (cdr data) <)))
  605. data))
  606. (median (map (lambda (ordered)
  607. (let ((len (vector-length ordered)))
  608. (and (> len 0)
  609. (if (even? len)
  610. (/ (+ (vector-ref ordered (/ len 2))
  611. (vector-ref ordered (1- (/ len 2))))
  612. 2)
  613. (vector-ref ordered (/ (1- len) 2))))))
  614. ordered)))
  615. (with-cairo
  616. cr
  617. (draw-background-shape cr ordered xticks box-width box-spacing translate-y
  618. 45 "Orange 1")
  619. (draw-background-shape cr ordered xticks box-width box-spacing translate-y
  620. 25 "Orange 2")
  621. (draw-background-shape cr ordered xticks box-width box-spacing translate-y
  622. 15 "Scarlet Red 1")
  623. (draw-background-shape cr ordered xticks box-width box-spacing translate-y
  624. 5 "Scarlet Red 2")
  625. (draw-histo-plots cr ordered xticks box-width box-spacing translate-y)
  626. (draw-median-labels cr median xticks translate-y box-value-formatter))))
  627. (define-with-kwargs (draw-point cr x y label)
  628. "Draw a point at the current position."
  629. ; (cairo-rel-move-to cr 2 0)
  630. (with-color-for-series
  631. cr label
  632. (cairo-new-path cr)
  633. (cairo-arc cr x y 2 0 (* 2 *pi*))
  634. (cairo-close-path cr)
  635. (cairo-stroke cr)))
  636. (define-with-kwargs (draw-page-map cr data chart-width chart-height
  637. (page-size 4096)
  638. (page-height 2)
  639. (page-spacing 1))
  640. "Draw a page map for the given data set.
  641. @var{data} := @code{(@var{label} . (@var{start} . @var{size})) ...)}
  642. @var{label} is a string, and @var{start} and @var{size} are numbers.
  643. @var{cr} is expected to have been positioned at the lower-left corner of the chart area."
  644. (for-each
  645. (match-lambda
  646. ((label . (start . size))
  647. (let lp ((start start)
  648. (size size))
  649. (unless (zero? size)
  650. (call-with-values (lambda () (floor/ start page-size))
  651. (lambda (page offset)
  652. (let* ((offset-fraction (/ offset page-size))
  653. (inc (min (- page-size offset) size))
  654. (inc-fraction (/ inc page-size)))
  655. (cairo-move-to cr (+ (* offset-fraction chart-width))
  656. (- chart-height
  657. (* page (+ page-height page-spacing))
  658. page-height))
  659. (with-color-for-series
  660. cr label
  661. (rel-rectangle cr (* inc-fraction chart-width) page-height)
  662. (cairo-fill cr))
  663. (lp (+ start inc) (- size inc)))))))))
  664. data))
  665. (define-with-kwargs (draw-ticks cr ticks tick-size vertical?)
  666. "Draw ticks on an axis.
  667. @var{ticks} is a list of positions in the current cairo
  668. coordinate system."
  669. (for-each
  670. (lambda (pos)
  671. (cond
  672. (vertical?
  673. (cairo-move-to cr 0 pos)
  674. (cairo-rel-line-to cr (- (* 2/3 tick-size)) 0))
  675. (else
  676. (cairo-move-to cr pos 0)
  677. (cairo-rel-line-to cr 0 (- (* 2/3 tick-size))))))
  678. ticks)
  679. (with-color cr "Gray 200"
  680. (cairo-stroke cr)))
  681. (define-with-kwargs (draw-tick-labels cr tick-labels tick-size
  682. vertical-axis?
  683. vertical-text?
  684. text-height)
  685. "Draw tick labels on an axis.
  686. @var{tick-labels} is an alist of label-position pairs, where
  687. position is in the current cairo coordinate system, along one
  688. axis."
  689. (for-each
  690. (lambda (pair)
  691. (let ((text (if (string? (car pair))
  692. (car pair)
  693. (object->string (car pair)))))
  694. (cond
  695. (vertical-axis?
  696. (cairo-move-to cr (- tick-size) (- (cdr pair) (/ text-height 2)))
  697. (with-font-size
  698. cr text-height
  699. (show-text cr text 'right)))
  700. (vertical-text?
  701. (cairo-move-to cr (+ (cdr pair) (/ text-height 2)) (- tick-size))
  702. (with-rotate
  703. cr 90
  704. (with-font-size
  705. cr text-height
  706. (show-text cr text 'right))))
  707. (else
  708. (cairo-move-to cr (cdr pair) (- (+ text-height tick-size)))
  709. (with-font-size
  710. cr text-height
  711. (show-text cr text 'center))))))
  712. tick-labels))
  713. (define-with-kwargs (draw-grid cr ticks width vertical?)
  714. "Draw grid lines.
  715. @var{ticks} is a list of positions in the current cairo coordinate
  716. system. @var{width} is the that the grid lines should be: the chart
  717. width of @var{vertical?}, and the height otherwise."
  718. (for-each
  719. (lambda (pos)
  720. (cond
  721. (vertical?
  722. (cairo-move-to cr 0 pos)
  723. (cairo-rel-line-to cr width 0))
  724. (else
  725. (cairo-move-to cr pos 0)
  726. (cairo-rel-line-to cr 0 width))))
  727. ticks)
  728. (with-rgb cr 1 1 1
  729. (cairo-stroke cr)))
  730. (define-with-kwargs (draw-axis-label cr text text-height axis-length vertical?)
  731. "Draw an axis label.
  732. The label will be drawn such that the current position of
  733. @var{cr} is the closest corner of the label's bounding box."
  734. (cond
  735. (vertical?
  736. (with-rotate
  737. cr 90
  738. (with-font-size
  739. cr text-height
  740. (cairo-rel-move-to cr (/ axis-length 2) 0)
  741. (show-text cr text 'center))))
  742. (else
  743. (with-font-size
  744. cr text-height
  745. (cairo-rel-move-to cr (/ axis-length 2) (- text-height))
  746. (show-text cr text 'center)))))
  747. (define-with-kwargs (draw-title cr text font-size)
  748. "Draw a title.
  749. @var{cr} is expected to have been positioned at the lower
  750. boundary of where the title should be written, in the center."
  751. (with-font-size
  752. cr font-size
  753. (with-color cr "Gray 800" (show-text cr text 'center))))
  754. (define-with-kwargs (draw-chart-area cr width height)
  755. "Draw the actual box for the chart background.
  756. @var{cr} is expected to have been positioned at the origin."
  757. (with-line-width cr (* 2 (cairo-get-line-width cr))
  758. (rel-rectangle cr width height)
  759. (with-color cr "Gray 300"
  760. (cairo-fill cr))))
  761. (define-with-kwargs (draw-background cr)
  762. "Draw the background."
  763. (with-rgb cr 1 1 1
  764. (cairo-paint cr)))