| 1 |
;;; cl-pdf copyright 2002-2005 Marc Battyani see license.txt for the details |
|---|
| 2 |
;;; You can reach me at marc.battyani@fractalconcept.com or marc@battyani.net |
|---|
| 3 |
;;; The homepage of cl-pdf is here: http://www.fractalconcept.com/asp/html/cl-pdf.html |
|---|
| 4 |
|
|---|
| 5 |
(in-package #:pdf) |
|---|
| 6 |
|
|---|
| 7 |
;;WARNING this part of cl-pdf is an alpha version. Use with care! |
|---|
| 8 |
|
|---|
| 9 |
;; Basic charts: histogram, pie and plot-xy |
|---|
| 10 |
;; (Thanks to Carlos Ungil <Carlos.Ungil@cern.ch> for plot-xy) |
|---|
| 11 |
|
|---|
| 12 |
|
|---|
| 13 |
;; the pre and post draw functions must have this signature: |
|---|
| 14 |
;; (defun pre-draw-chart (object dx dy x-scale y-scale x-min y-min x-max y-max) ...) |
|---|
| 15 |
|
|---|
| 16 |
(defvar *default-chart-font* (get-font)) |
|---|
| 17 |
|
|---|
| 18 |
(defclass chart-item () |
|---|
| 19 |
((x :accessor x :initform 0 :initarg :x) |
|---|
| 20 |
(y :accessor y :initform 0 :initarg :y) |
|---|
| 21 |
(width :accessor width :initform 0 :initarg :width) |
|---|
| 22 |
(height :accessor height :initform 0 :initarg :height) |
|---|
| 23 |
(background-color :accessor background-color :initform '(1 1 1) :initarg :background-color) |
|---|
| 24 |
(title :accessor title :initform "" :initarg :title) |
|---|
| 25 |
(title-font :accessor title-font :initform *default-chart-font* :initarg :title-font) |
|---|
| 26 |
(title-font-size :accessor title-font-size :initform 12 :initarg :title-font-size) |
|---|
| 27 |
(title-color :accessor title-color :initform '(0 0 0) :initarg :title-color) |
|---|
| 28 |
(line-width :accessor line-width :initform 0.5 :initarg :line-width) |
|---|
| 29 |
(line-color :accessor line-color :initform '(0 0 0) :initarg :line-color) |
|---|
| 30 |
(pre-draw-chart-fn :accessor pre-draw-chart-fn :initform nil :initarg :pre-draw-chart-fn) |
|---|
| 31 |
(post-draw-chart-fn :accessor post-draw-chart-fn :initform nil :initarg :post-draw-chart-fn) |
|---|
| 32 |
)) |
|---|
| 33 |
|
|---|
| 34 |
(defclass axis (chart-item) |
|---|
| 35 |
((label-font :accessor label-font :initform *default-chart-font* :initarg :label-font) |
|---|
| 36 |
(label-font-size :accessor label-font-size :initform 10.0 :initarg :label-font-size) |
|---|
| 37 |
(label-position :accessor label-position :initform :center :initarg :label-position) |
|---|
| 38 |
(label-rotation :accessor label-rotation :initform 0 :initarg :label-rotation) |
|---|
| 39 |
(label-color :accessor label-color :initform '(0 0 0) :initarg :label-color) |
|---|
| 40 |
(tick-length :accessor tick-length :initform 6 :initarg :tick-length) |
|---|
| 41 |
(tick-width :accessor tick-width :initform 0.25 :initarg :tick-width) |
|---|
| 42 |
(ticks-positions :accessor ticks-positions) |
|---|
| 43 |
(ticks-separation :accessor ticks-separation) |
|---|
| 44 |
)) |
|---|
| 45 |
|
|---|
| 46 |
(defclass value-axis (axis) |
|---|
| 47 |
((min-value :accessor min-value :initform 0 :initarg :min-value) |
|---|
| 48 |
(max-value :accessor max-value :initform 100 :initarg :max-value) |
|---|
| 49 |
(locked-values :accessor locked-values :initform nil :initarg :locked-values) |
|---|
| 50 |
(subtick-length :accessor subtick-length :initform 2 :initarg :subtick-length) |
|---|
| 51 |
(subtick-width :accessor subtick-width :initform 0.25 :initarg :subtick-width) |
|---|
| 52 |
(integer-tick :accessor integer-tick :initform nil :initarg :integer-tick) |
|---|
| 53 |
(nb-ticks :accessor nb-ticks :initform 10 :initarg :nb-ticks) |
|---|
| 54 |
(tick-value :accessor tick-value) |
|---|
| 55 |
(axis-scale :accessor axis-scale) |
|---|
| 56 |
(axis-min :accessor axis-min) |
|---|
| 57 |
(axis-max :accessor axis-max) |
|---|
| 58 |
(nb-subticks :accessor nb-subticks) |
|---|
| 59 |
(format-string :accessor format-string :initform nil :initarg :format-string) |
|---|
| 60 |
)) |
|---|
| 61 |
|
|---|
| 62 |
(defmethod initialize-instance :after ((axis value-axis) &rest init-options &key &allow-other-keys) |
|---|
| 63 |
(declare (ignore init-options)) |
|---|
| 64 |
(compute-scale axis)) |
|---|
| 65 |
|
|---|
| 66 |
(defclass histo-axis (axis) |
|---|
| 67 |
((label-names :accessor label-names :initform nil :initarg :label-names) |
|---|
| 68 |
)) |
|---|
| 69 |
|
|---|
| 70 |
(defclass vertical-value-axis (value-axis) |
|---|
| 71 |
()) |
|---|
| 72 |
|
|---|
| 73 |
(defclass horizontal-value-axis (value-axis) |
|---|
| 74 |
()) |
|---|
| 75 |
|
|---|
| 76 |
(defclass vertical-histo-axis (histo-axis) |
|---|
| 77 |
()) |
|---|
| 78 |
|
|---|
| 79 |
(defclass horizontal-histo-axis (histo-axis) |
|---|
| 80 |
()) |
|---|
| 81 |
|
|---|
| 82 |
(defmethod axis-size (axis) |
|---|
| 83 |
(width axis)) |
|---|
| 84 |
|
|---|
| 85 |
(defmethod axis-size ((axis vertical-value-axis)) |
|---|
| 86 |
(height axis)) |
|---|
| 87 |
|
|---|
| 88 |
(defmethod axis-size ((axis vertical-histo-axis)) |
|---|
| 89 |
(height axis)) |
|---|
| 90 |
|
|---|
| 91 |
(defmethod draw-object (obj) |
|---|
| 92 |
(declare (ignore obj))) |
|---|
| 93 |
|
|---|
| 94 |
(defun nice-number (n approx integer-p) |
|---|
| 95 |
(let* ((n10 (expt 10 (floor (log n 10)))) |
|---|
| 96 |
(nf (/ n n10)) |
|---|
| 97 |
(value |
|---|
| 98 |
(loop for (max val) in (if approx '((1.5 1)(3 2)(7 5)(15 10)) '((1 1)(2 2)(5 5)(15 10))) |
|---|
| 99 |
when (<= nf max) do (return (* val n10))))) |
|---|
| 100 |
(if integer-p |
|---|
| 101 |
(max 1 (round value)) |
|---|
| 102 |
value))) |
|---|
| 103 |
|
|---|
| 104 |
(defun compute-scale (axis) |
|---|
| 105 |
(let* ((min (min-value axis)) |
|---|
| 106 |
(max (max-value axis)) |
|---|
| 107 |
d nfrac) |
|---|
| 108 |
(when (= min max) |
|---|
| 109 |
(if (zerop min) |
|---|
| 110 |
(setf max 1.0) |
|---|
| 111 |
(if (plusp max) |
|---|
| 112 |
(setf min 0.0 max (* max 1.01)) |
|---|
| 113 |
(setf min (* min 1.01) max 0.0)))) |
|---|
| 114 |
(setf d (nice-number (/ (nice-number (- max min) nil (integer-tick axis)) (nb-ticks axis)) |
|---|
| 115 |
t (integer-tick axis))) |
|---|
| 116 |
(setf nfrac (max (- (floor (log d 10))) 0)) |
|---|
| 117 |
(setf (tick-value axis) d |
|---|
| 118 |
(axis-min axis) (* d (floor min d)) |
|---|
| 119 |
(axis-max axis) (* d (ceiling max d)) |
|---|
| 120 |
(nb-ticks axis) (1+ (ceiling (- (axis-max axis)(axis-min axis)) d)) |
|---|
| 121 |
(axis-scale axis) (/ (axis-size axis)(- (axis-max axis) (axis-min axis))) |
|---|
| 122 |
(ticks-separation axis)(/ (axis-size axis) (1- (nb-ticks axis))) |
|---|
| 123 |
(format-string axis) |
|---|
| 124 |
(or (format-string axis) |
|---|
| 125 |
(if (integer-tick axis) |
|---|
| 126 |
"~d" |
|---|
| 127 |
(format nil "~~,~df" nfrac))) |
|---|
| 128 |
(ticks-positions axis) (make-array (nb-ticks axis))) |
|---|
| 129 |
(loop for tick from 0 below (nb-ticks axis) |
|---|
| 130 |
for pos from 0 by (ticks-separation axis) do |
|---|
| 131 |
(setf (aref (ticks-positions axis) tick) pos)))) |
|---|
| 132 |
|
|---|
| 133 |
(defmethod draw-object ((axis horizontal-histo-axis)) |
|---|
| 134 |
(with-saved-state |
|---|
| 135 |
(set-line-width (line-width axis)) |
|---|
| 136 |
(apply #'set-rgb-stroke (line-color axis)) |
|---|
| 137 |
(translate (x axis) (y axis)) |
|---|
| 138 |
(move-to 0 0) |
|---|
| 139 |
(line-to (width axis) 0) |
|---|
| 140 |
(stroke) |
|---|
| 141 |
(set-line-width (tick-width axis)) |
|---|
| 142 |
(move-to 0 0) |
|---|
| 143 |
(line-to 0 (- (tick-length axis))) |
|---|
| 144 |
(stroke) |
|---|
| 145 |
(apply #'set-rgb-fill (label-color axis)) |
|---|
| 146 |
(loop with nb = (length (label-names axis)) |
|---|
| 147 |
with d = (/ (width axis) nb) |
|---|
| 148 |
with l = (- (tick-length axis)) |
|---|
| 149 |
with font-size = (label-font-size axis) |
|---|
| 150 |
with max-width = (- d (* 0.6 font-size)) |
|---|
| 151 |
with text-y = (* -1.25 font-size) |
|---|
| 152 |
for name in (label-names axis) |
|---|
| 153 |
for tx from d by d |
|---|
| 154 |
for text-x from (* 0.5 d) by d do |
|---|
| 155 |
(move-to tx 0) |
|---|
| 156 |
(line-to tx l) |
|---|
| 157 |
(stroke) |
|---|
| 158 |
(draw-centered-text text-x text-y name (label-font axis) font-size max-width)))) |
|---|
| 159 |
|
|---|
| 160 |
(defmethod draw-object ((axis vertical-value-axis)) |
|---|
| 161 |
(with-saved-state |
|---|
| 162 |
(set-line-width (line-width axis)) |
|---|
| 163 |
(apply #'set-rgb-stroke (line-color axis)) |
|---|
| 164 |
(translate (x axis) (y axis)) |
|---|
| 165 |
(move-to 0 0) |
|---|
| 166 |
(line-to 0 (height axis)) |
|---|
| 167 |
(stroke) |
|---|
| 168 |
(set-line-width (tick-width axis)) |
|---|
| 169 |
(move-to 0 0) |
|---|
| 170 |
(line-to (- (tick-length axis)) 0) |
|---|
| 171 |
(stroke) |
|---|
| 172 |
(apply #'set-rgb-fill (label-color axis)) |
|---|
| 173 |
(loop with nb = (nb-ticks axis) |
|---|
| 174 |
with d = (ticks-separation axis) |
|---|
| 175 |
with l = (- (tick-length axis)) |
|---|
| 176 |
with font-size = (label-font-size axis) |
|---|
| 177 |
with text-x = (* l 1.25) |
|---|
| 178 |
with format = (format-string axis) |
|---|
| 179 |
repeat nb |
|---|
| 180 |
for value from (axis-min axis) by (tick-value axis) |
|---|
| 181 |
for y from 0 by d |
|---|
| 182 |
for text-y from (* -0.35 font-size) by d do |
|---|
| 183 |
(move-to 0 y) |
|---|
| 184 |
(line-to l y) |
|---|
| 185 |
(stroke) |
|---|
| 186 |
(when (integer-tick axis) (setf value (round value))) |
|---|
| 187 |
(draw-left-text text-x text-y (format nil format value) (label-font axis) font-size)))) |
|---|
| 188 |
|
|---|
| 189 |
(defclass legend (chart-item) |
|---|
| 190 |
((label-font :accessor label-font :initform *default-chart-font* :initarg :label-font) |
|---|
| 191 |
(label-font-size :accessor label-font-size :initform 10.0 :initarg :label-font-size) |
|---|
| 192 |
(label-color :accessor label-color :initform '(0 0 0) :initarg :label-color) |
|---|
| 193 |
(labels&colors :accessor labels&colors :initform () :initarg :labels&colors) |
|---|
| 194 |
)) |
|---|
| 195 |
|
|---|
| 196 |
(defmethod draw-object ((obj legend)) |
|---|
| 197 |
(let* ((nb (length (labels&colors obj))) |
|---|
| 198 |
(font-size (label-font-size obj)) |
|---|
| 199 |
(space (* 0.2 font-size)) |
|---|
| 200 |
(line-height (+ space font-size)) |
|---|
| 201 |
(height (+ (* nb line-height) space)) |
|---|
| 202 |
(width (+ (* 3 space) font-size |
|---|
| 203 |
(reduce 'max (labels&colors obj) |
|---|
| 204 |
:key #'(lambda (lc) (text-width (first lc) (label-font obj)font-size)))))) |
|---|
| 205 |
(with-saved-state |
|---|
| 206 |
(set-line-width (line-width obj)) |
|---|
| 207 |
(apply #'set-rgb-stroke (line-color obj)) |
|---|
| 208 |
(apply #'set-rgb-fill (background-color obj)) |
|---|
| 209 |
(translate (x obj) (+ (y obj)(* 0.5 (- (height obj) height)))) |
|---|
| 210 |
(basic-rect 0 0 width height) |
|---|
| 211 |
(fill-and-stroke) |
|---|
| 212 |
(basic-rect 0 0 width height) |
|---|
| 213 |
(clip-path) |
|---|
| 214 |
(end-path-no-op) |
|---|
| 215 |
(loop with dx = font-size |
|---|
| 216 |
with dy = font-size |
|---|
| 217 |
with text-x = (+ dx (* 2 space)) |
|---|
| 218 |
for (name color) in (labels&colors obj) |
|---|
| 219 |
for y downfrom (- height line-height) by line-height do |
|---|
| 220 |
(apply #'set-rgb-fill color) |
|---|
| 221 |
(basic-rect space y dx dy) |
|---|
| 222 |
(fill-and-stroke) |
|---|
| 223 |
(apply #'set-rgb-fill (label-color obj)) |
|---|
| 224 |
(in-text-mode |
|---|
| 225 |
(move-text text-x y) |
|---|
| 226 |
(set-font (label-font obj) font-size) |
|---|
| 227 |
(show-text name)) |
|---|
| 228 |
)))) |
|---|
| 229 |
|
|---|
| 230 |
(defclass histogram (chart-item) |
|---|
| 231 |
((label-names :accessor label-names :initform nil :initarg :label-names) |
|---|
| 232 |
(series :accessor series :initform () :initarg :series) |
|---|
| 233 |
(stacked-series :accessor stacked-series :initform () :initarg :stacked-series) |
|---|
| 234 |
(labels&colors :accessor labels&colors :initform () :initarg :labels&colors) |
|---|
| 235 |
(h-lines-width :accessor h-lines-width :initform 0.2 :initarg :h-lines-width) |
|---|
| 236 |
(h-lines-color :accessor h-lines-color :initform '(0.5 0.5 0.5) :initarg :h-lines-color) |
|---|
| 237 |
(groups-spacing :accessor groups-spacing :initform 0.2 :initarg :groups-spacing) |
|---|
| 238 |
(x-axis :accessor x-axis) |
|---|
| 239 |
(y-axis :accessor y-axis) |
|---|
| 240 |
(legend :accessor legend :initform nil) |
|---|
| 241 |
)) |
|---|
| 242 |
|
|---|
| 243 |
(defmethod initialize-instance :after ((histo histogram) &rest init-options &key |
|---|
| 244 |
x-axis-options y-axis-options legend-options &allow-other-keys) |
|---|
| 245 |
(declare (ignore init-options)) |
|---|
| 246 |
(setf (y-axis histo) |
|---|
| 247 |
(apply #'make-instance 'vertical-value-axis |
|---|
| 248 |
:x (x histo) :y (y histo) :height (height histo) |
|---|
| 249 |
(append y-axis-options |
|---|
| 250 |
(list |
|---|
| 251 |
:min-value (if (stacked-series histo) |
|---|
| 252 |
0.0 |
|---|
| 253 |
(reduce #'min (mapcar #'(lambda (values) |
|---|
| 254 |
(reduce #'min values)) |
|---|
| 255 |
(series histo)))) |
|---|
| 256 |
:max-value (reduce #'max |
|---|
| 257 |
(if (stacked-series histo) |
|---|
| 258 |
(apply 'mapcar #'(lambda (&rest values) |
|---|
| 259 |
(apply '+ values)) |
|---|
| 260 |
(series histo)) |
|---|
| 261 |
(mapcar #'(lambda (values) |
|---|
| 262 |
(reduce #'max values)) |
|---|
| 263 |
(series histo)))))))) |
|---|
| 264 |
(setf (x-axis histo) |
|---|
| 265 |
(apply #'make-instance 'horizontal-histo-axis |
|---|
| 266 |
:x (x histo) :y (y histo) :width (width histo) |
|---|
| 267 |
:label-names (label-names histo) |
|---|
| 268 |
x-axis-options)) |
|---|
| 269 |
(when (> (length (series histo)) 1) |
|---|
| 270 |
(setf (legend histo) |
|---|
| 271 |
(apply #'make-instance 'legend |
|---|
| 272 |
:x (+ (x histo) (width histo) 10) :y (y histo) :width 60 :height (height histo) |
|---|
| 273 |
:labels&colors (labels&colors histo) |
|---|
| 274 |
legend-options)))) |
|---|
| 275 |
|
|---|
| 276 |
(defmethod draw-object ((obj histogram)) |
|---|
| 277 |
(let* ((nb-series (if (stacked-series obj) 1 (length (series obj)))) |
|---|
| 278 |
(nb-values (length (first (series obj)))) |
|---|
| 279 |
(width (width obj)) |
|---|
| 280 |
(group-width (/ width nb-values)) |
|---|
| 281 |
(spacing (* (groups-spacing obj) group-width)) |
|---|
| 282 |
(bar-width (/ (- group-width spacing) nb-series)) |
|---|
| 283 |
(min-value (axis-min (y-axis obj))) |
|---|
| 284 |
(scale (axis-scale (y-axis obj)))) |
|---|
| 285 |
(with-saved-state |
|---|
| 286 |
(translate (x obj)(y obj)) |
|---|
| 287 |
(set-line-width (line-width obj)) |
|---|
| 288 |
(apply #'set-rgb-stroke (line-color obj)) |
|---|
| 289 |
(apply #'set-rgb-fill (background-color obj)) |
|---|
| 290 |
(basic-rect 0 0 width (height obj)) |
|---|
| 291 |
(fill-and-stroke) |
|---|
| 292 |
(set-line-width (h-lines-width obj)) |
|---|
| 293 |
(apply #'set-rgb-stroke (h-lines-color obj)) |
|---|
| 294 |
(loop for tick-y across (ticks-positions (y-axis obj)) do |
|---|
| 295 |
(move-to 0 tick-y) |
|---|
| 296 |
(line-to width tick-y) |
|---|
| 297 |
(stroke)) |
|---|
| 298 |
(set-line-width (line-width obj)) |
|---|
| 299 |
(apply #'set-rgb-stroke (line-color obj)) |
|---|
| 300 |
(if (stacked-series obj) |
|---|
| 301 |
(loop for values in (apply 'mapcar 'list (series obj)) |
|---|
| 302 |
for gx from (* 0.5 spacing) by bar-width |
|---|
| 303 |
for bx from gx by group-width do |
|---|
| 304 |
(loop for y = 0.0 then (+ y dy) |
|---|
| 305 |
for value in values |
|---|
| 306 |
for (nil color) in (labels&colors obj) |
|---|
| 307 |
for dy = (* value scale) do |
|---|
| 308 |
(apply #'set-rgb-fill color) |
|---|
| 309 |
(basic-rect bx y bar-width dy) |
|---|
| 310 |
(fill-and-stroke))) |
|---|
| 311 |
(loop for serie in (series obj) |
|---|
| 312 |
for gx from (* 0.5 spacing) by bar-width |
|---|
| 313 |
for (nil color) in (labels&colors obj) do |
|---|
| 314 |
(apply #'set-rgb-fill color) |
|---|
| 315 |
(loop for value in serie |
|---|
| 316 |
for dy = (* (- value min-value) scale) |
|---|
| 317 |
for bx from gx by group-width do |
|---|
| 318 |
(basic-rect bx 0 bar-width dy) |
|---|
| 319 |
(fill-and-stroke)))))) |
|---|
| 320 |
(draw-object (x-axis obj)) |
|---|
| 321 |
(draw-object (y-axis obj)) |
|---|
| 322 |
(draw-object (legend obj))) |
|---|
| 323 |
|
|---|
| 324 |
|
|---|
| 325 |
(defclass pie-chart (chart-item) |
|---|
| 326 |
((serie :accessor serie :initform () :initarg :serie) |
|---|
| 327 |
(labels&colors :accessor labels&colors :initform () :initarg :labels&colors) |
|---|
| 328 |
(legend :accessor legend :initform nil) |
|---|
| 329 |
)) |
|---|
| 330 |
|
|---|
| 331 |
(defmethod initialize-instance :after ((obj pie-chart) &rest init-options &key no-legend |
|---|
| 332 |
legend-options &allow-other-keys) |
|---|
| 333 |
(declare (ignore init-options)) |
|---|
| 334 |
(unless no-legend |
|---|
| 335 |
(setf (legend obj) |
|---|
| 336 |
(apply #'make-instance 'legend |
|---|
| 337 |
:x (+ (x obj) (width obj) 10) :y (y obj) :width 60 :height (height obj) |
|---|
| 338 |
:labels&colors (labels&colors obj) |
|---|
| 339 |
legend-options)))) |
|---|
| 340 |
|
|---|
| 341 |
(defmethod draw-object ((obj pie-chart)) |
|---|
| 342 |
(let* ((sum (reduce #'+ (serie obj))) |
|---|
| 343 |
(radius (* (width obj) 0.45)) |
|---|
| 344 |
angles) |
|---|
| 345 |
(when (zerop sum) (setf sum 1)) |
|---|
| 346 |
(setf angles (mapcar #'(lambda (v) (/ (* 2 pi v) sum)) (serie obj))) |
|---|
| 347 |
(with-saved-state |
|---|
| 348 |
(translate (+ (x obj)(* 0.5 (width obj)))(+ (y obj)(* 0.5 (height obj)))) |
|---|
| 349 |
(set-line-width (line-width obj)) |
|---|
| 350 |
(apply #'set-rgb-stroke (line-color obj)) |
|---|
| 351 |
(apply #'set-rgb-fill (background-color obj)) |
|---|
| 352 |
(fill-and-stroke) |
|---|
| 353 |
(loop for angle in angles |
|---|
| 354 |
for (nil color) in (labels&colors obj) |
|---|
| 355 |
for start = 0 then end |
|---|
| 356 |
for end = (+ start angle) do |
|---|
| 357 |
(apply #'set-rgb-fill color) |
|---|
| 358 |
(pie 0 0 radius start angle) |
|---|
| 359 |
(fill-and-stroke)))) |
|---|
| 360 |
(draw-object (legend obj))) |
|---|
| 361 |
|
|---|
| 362 |
|
|---|
| 363 |
(defmethod draw-object ((axis horizontal-value-axis)) |
|---|
| 364 |
(with-saved-state |
|---|
| 365 |
(set-line-width (line-width axis)) |
|---|
| 366 |
(apply #'set-rgb-stroke (line-color axis)) |
|---|
| 367 |
(translate (x axis) (y axis)) |
|---|
| 368 |
(move-to 0 0) |
|---|
| 369 |
(line-to (width axis) 0) |
|---|
| 370 |
(stroke) |
|---|
| 371 |
(set-line-width (tick-width axis)) |
|---|
| 372 |
(move-to 0 0) |
|---|
| 373 |
(line-to 0 (- (tick-length axis))) |
|---|
| 374 |
(stroke) |
|---|
| 375 |
(apply #'set-rgb-fill (label-color axis)) |
|---|
| 376 |
(loop with nb = (nb-ticks axis) |
|---|
| 377 |
with d = (ticks-separation axis) |
|---|
| 378 |
with l = (- (tick-length axis)) |
|---|
| 379 |
with font-size = (label-font-size axis) |
|---|
| 380 |
with text-y = (+ (* l 1.25) (* -1 font-size)) |
|---|
| 381 |
with format = (format-string axis) |
|---|
| 382 |
repeat nb |
|---|
| 383 |
for value from (axis-min axis) by (tick-value axis) |
|---|
| 384 |
for x from 0 by d |
|---|
| 385 |
for text-x from (* -0.35 font-size) by d do |
|---|
| 386 |
(move-to x 0) |
|---|
| 387 |
(line-to x l) |
|---|
| 388 |
(stroke) |
|---|
| 389 |
(draw-centered-text text-x text-y (format nil format value) (label-font axis) font-size d)))) |
|---|
| 390 |
|
|---|
| 391 |
(defclass plot-xy (chart-item) |
|---|
| 392 |
((series :accessor series :initform () :initarg :series) |
|---|
| 393 |
(labels&colors :accessor labels&colors :initform () :initarg :labels&colors) |
|---|
| 394 |
(h-lines-width :accessor h-lines-width :initform 0.2 :initarg :h-lines-width) |
|---|
| 395 |
(h-lines-color :accessor h-lines-color :initform '(0.5 0.5 0.5) :initarg :h-lines-color) |
|---|
| 396 |
(point-radius :accessor point-radius :initform 2 :initarg :point-radius) |
|---|
| 397 |
(x-axis :accessor x-axis) |
|---|
| 398 |
(y-axis :accessor y-axis) |
|---|
| 399 |
(legend :accessor legend :initform nil) |
|---|
| 400 |
)) |
|---|
| 401 |
|
|---|
| 402 |
(defmethod initialize-instance :after ((plot plot-xy) &rest init-options &key |
|---|
| 403 |
x-axis-options y-axis-options legend-options &allow-other-keys) |
|---|
| 404 |
(declare (ignore init-options)) |
|---|
| 405 |
(setf (y-axis plot) |
|---|
| 406 |
(apply #'make-instance 'vertical-value-axis |
|---|
| 407 |
:x (x plot) :y (y plot) :height (height plot) |
|---|
| 408 |
(append y-axis-options |
|---|
| 409 |
(list |
|---|
| 410 |
:min-value |
|---|
| 411 |
(reduce #'min |
|---|
| 412 |
(mapcar #'(lambda (values) |
|---|
| 413 |
(reduce #'min |
|---|
| 414 |
(remove nil (mapcar #'second values)))) |
|---|
| 415 |
(series plot))) |
|---|
| 416 |
:max-value |
|---|
| 417 |
(reduce #'max |
|---|
| 418 |
(mapcar #'(lambda (values) |
|---|
| 419 |
(reduce #'max |
|---|
| 420 |
(remove nil (mapcar #'second values)))) |
|---|
| 421 |
(series plot))))))) |
|---|
| 422 |
(setf (x-axis plot) |
|---|
| 423 |
(apply #'make-instance 'horizontal-value-axis |
|---|
| 424 |
:x (x plot) :y (y plot) :width (width plot) |
|---|
| 425 |
(append x-axis-options |
|---|
| 426 |
(list |
|---|
| 427 |
:min-value |
|---|
| 428 |
(reduce #'min |
|---|
| 429 |
(mapcar #'(lambda (values) |
|---|
| 430 |
(reduce #'min |
|---|
| 431 |
(remove nil (mapcar #'first values)))) |
|---|
| 432 |
(series plot))) |
|---|
| 433 |
:max-value |
|---|
| 434 |
(reduce #'max |
|---|
| 435 |
(mapcar #'(lambda (values) |
|---|
| 436 |
(reduce #'max |
|---|
| 437 |
(remove nil (mapcar #'first values)))) |
|---|
| 438 |
(series plot))))))) |
|---|
| 439 |
(when (labels&colors plot) |
|---|
| 440 |
(setf (legend plot) |
|---|
| 441 |
(apply #'make-instance 'legend |
|---|
| 442 |
:x (or (getf legend-options :x) (+ (x plot) (width plot) 10)) |
|---|
| 443 |
:y (or (getf legend-options :y) (y plot)) |
|---|
| 444 |
:width 60 :height (height plot) |
|---|
| 445 |
:labels&colors (labels&colors plot) |
|---|
| 446 |
legend-options)))) |
|---|
| 447 |
|
|---|
| 448 |
|
|---|
| 449 |
(defmethod draw-object ((obj plot-xy)) |
|---|
| 450 |
(let* ((width (width obj)) |
|---|
| 451 |
(height (height obj)) |
|---|
| 452 |
(min-value-y (axis-min (y-axis obj))) |
|---|
| 453 |
(min-value-x (axis-min (x-axis obj))) |
|---|
| 454 |
(max-value-y (axis-max (y-axis obj))) |
|---|
| 455 |
(max-value-x (axis-max (x-axis obj))) |
|---|
| 456 |
(scale-y (axis-scale (y-axis obj))) |
|---|
| 457 |
(scale-x (axis-scale (x-axis obj)))) |
|---|
| 458 |
(with-saved-state |
|---|
| 459 |
(translate (x obj)(y obj)) |
|---|
| 460 |
(set-line-width (line-width obj)) |
|---|
| 461 |
(apply #'set-rgb-stroke (line-color obj)) |
|---|
| 462 |
(apply #'set-rgb-fill (background-color obj)) |
|---|
| 463 |
(basic-rect 0 0 width height) |
|---|
| 464 |
(fill-and-stroke) |
|---|
| 465 |
(when (pre-draw-chart-fn obj) |
|---|
| 466 |
(funcall (pre-draw-chart-fn obj) obj width height scale-x scale-y |
|---|
| 467 |
min-value-x min-value-y max-value-x max-value-y)) |
|---|
| 468 |
(set-line-width (h-lines-width obj)) |
|---|
| 469 |
(apply #'set-rgb-stroke (h-lines-color obj)) |
|---|
| 470 |
(loop for tick-y across (ticks-positions (y-axis obj)) do |
|---|
| 471 |
(move-to 0 tick-y) |
|---|
| 472 |
(line-to width tick-y) |
|---|
| 473 |
(stroke)) |
|---|
| 474 |
(loop for tick-x across (ticks-positions (x-axis obj)) do |
|---|
| 475 |
(move-to tick-x 0) |
|---|
| 476 |
(line-to tick-x height) |
|---|
| 477 |
(stroke)) |
|---|
| 478 |
(set-line-width (line-width obj)) |
|---|
| 479 |
(set-line-join 2) |
|---|
| 480 |
(loop for serie in (series obj) |
|---|
| 481 |
for (nil color) in (labels&colors obj) do |
|---|
| 482 |
(apply #'set-rgb-stroke color) |
|---|
| 483 |
(apply #'set-rgb-fill color) |
|---|
| 484 |
(let ((points '()) |
|---|
| 485 |
(all-points '())) |
|---|
| 486 |
(loop for (xx yy) in serie |
|---|
| 487 |
for x = (when xx (* (- xx min-value-x) scale-x)) |
|---|
| 488 |
for y = (when yy (* (- yy min-value-y) scale-y)) |
|---|
| 489 |
do |
|---|
| 490 |
(if (and x y) |
|---|
| 491 |
(progn |
|---|
| 492 |
(push (list x y) points) |
|---|
| 493 |
(unless (zerop (point-radius obj)) |
|---|
| 494 |
(circle x y (point-radius obj)) |
|---|
| 495 |
(fill-and-stroke))) |
|---|
| 496 |
(when points |
|---|
| 497 |
(push points all-points) |
|---|
| 498 |
(setf points '())))) |
|---|
| 499 |
(when points (push points all-points)) |
|---|
| 500 |
(map nil 'polyline all-points) |
|---|
| 501 |
(stroke))) |
|---|
| 502 |
(when (post-draw-chart-fn obj) |
|---|
| 503 |
(funcall (post-draw-chart-fn obj) obj width height scale-x scale-y |
|---|
| 504 |
min-value-x min-value-y max-value-x max-value-y)))) |
|---|
| 505 |
(draw-object (x-axis obj)) |
|---|
| 506 |
(draw-object (y-axis obj)) |
|---|
| 507 |
(draw-object (legend obj))) |
|---|