Mercurial > emacs
annotate lisp/emacs-lisp/chart.el @ 111502:df6573cbdd34
* lisp/emacs-lisp/pcase.el (pcase-let*, pcase-let): Add debug and
indentation specs.
author | Stefan Monnier <monnier@iro.umontreal.ca> |
---|---|
date | Thu, 11 Nov 2010 20:35:06 -0500 |
parents | 05dc9dca8729 |
children | 6e613fbf73d7 |
rev | line source |
---|---|
105237 | 1 ;;; chart.el --- Draw charts (bar charts, etc) |
2 | |
111433
05dc9dca8729
Replace end-of-line, save-excursion etc with point-at-eol, point-at-bol.
Glenn Morris <rgm@gnu.org>
parents:
110909
diff
changeset
|
3 ;; Copyright (C) 1996, 1998, 1999, 2001, 2004, 2005, 2007, 2008, 2009, |
05dc9dca8729
Replace end-of-line, save-excursion etc with point-at-eol, point-at-bol.
Glenn Morris <rgm@gnu.org>
parents:
110909
diff
changeset
|
4 ;; 2010 Free Software Foundation, Inc. |
105237 | 5 |
6 ;; Author: Eric M. Ludlam <zappo@gnu.org> | |
7 ;; Version: 0.2 | |
8 ;; Keywords: OO, chart, graph | |
9 | |
10 ;; This file is part of GNU Emacs. | |
11 | |
12 ;; GNU Emacs is free software: you can redistribute it and/or modify | |
13 ;; it under the terms of the GNU General Public License as published by | |
14 ;; the Free Software Foundation, either version 3 of the License, or | |
15 ;; (at your option) any later version. | |
16 | |
17 ;; GNU Emacs is distributed in the hope that it will be useful, | |
18 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | |
19 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
20 ;; GNU General Public License for more details. | |
21 | |
22 ;; You should have received a copy of the GNU General Public License | |
23 ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. | |
24 | |
25 ;;; Commentary: | |
26 ;; | |
27 ;; This package is an experiment of mine aiding in the debugging of | |
28 ;; eieio, and proved to be neat enough that others may like to use | |
29 ;; it. To quickly see what you can do with chart, run the command | |
30 ;; `chart-test-it-all'. | |
31 ;; | |
32 ;; Chart current can display bar-charts in either of two | |
33 ;; directions. It also supports ranged (integer) axis, and axis | |
34 ;; defined by some set of strings or names. These name can be | |
35 ;; automatically derived from data sequences, which are just lists of | |
36 ;; anything encapsulated in a nice eieio object. | |
37 ;; | |
38 ;; Current example apps for chart can be accessed via these commands: | |
39 ;; `chart-file-count' - count files w/ matching extensions | |
40 ;; `chart-space-usage' - display space used by files/directories | |
41 ;; `chart-emacs-storage' - Emacs storage units used/free (garbage-collect) | |
42 ;; `chart-emacs-lists' - length of Emacs lists | |
43 ;; `chart-rmail-from' - who sends you the most mail (in -summary only) | |
44 ;; | |
45 ;; Customization: | |
46 ;; | |
47 ;; If you find the default colors and pixmaps unpleasant, or too | |
48 ;; short, you can change them. The variable `chart-face-color-list' | |
49 ;; contains a list of colors, and `chart-face-pixmap-list' contains | |
50 ;; all the pixmaps to use. The current pixmaps are those found on | |
51 ;; several systems I found. The two lists should be the same length, | |
52 ;; as the long list will just be truncated. | |
53 ;; | |
54 ;; If you would like to draw your own stipples, simply create some | |
55 ;; xbm's and put them in a directory, then you can add: | |
56 ;; | |
57 ;; (setq x-bitmap-file-path (cons "~/mybitmaps" x-bitmap-file-path)) | |
58 ;; | |
59 ;; to your .emacs (or wherever) and load the `chart-face-pixmap-list' | |
60 ;; with all the bitmaps you want to use. | |
61 | |
62 (require 'eieio) | |
63 | |
64 ;;; Code: | |
110909
cc035ccb9275
Declare and define in one step various mode maps.
Dan Nicolaescu <dann@ics.uci.edu>
parents:
106895
diff
changeset
|
65 (defvar chart-map (make-sparse-keymap) "Keymap used in chart mode.") |
105237 | 66 |
67 (defvar chart-local-object nil | |
68 "Local variable containing the locally displayed chart object.") | |
69 (make-variable-buffer-local 'chart-local-object) | |
70 | |
71 (defvar chart-face-list nil | |
72 "Faces used to colorize charts. | |
73 List is limited currently, which is ok since you really can't display | |
74 too much in text characters anyways.") | |
75 | |
76 (defvar chart-face-color-list '("red" "green" "blue" | |
77 "cyan" "yellow" "purple") | |
78 "Colors to use when generating `chart-face-list'. | |
79 Colors will be the background color.") | |
80 | |
81 (defvar chart-face-pixmap-list | |
82 (if (and (fboundp 'display-graphic-p) | |
83 (display-graphic-p)) | |
84 '("dimple1" "scales" "dot" "cross_weave" "boxes" "dimple3")) | |
85 "If pixmaps are allowed, display these background pixmaps. | |
105474 | 86 Useful if new Emacs is used on B&W display.") |
105237 | 87 |
88 (defcustom chart-face-use-pixmaps nil | |
89 "*Non-nil to use fancy pixmaps in the background of chart face colors." | |
90 :group 'eieio | |
91 :type 'boolean) | |
92 | |
93 (if (and (if (fboundp 'display-color-p) | |
94 (display-color-p) | |
95 window-system) | |
96 (not chart-face-list)) | |
97 (let ((cl chart-face-color-list) | |
98 (pl chart-face-pixmap-list) | |
99 nf) | |
100 (while cl | |
101 (setq nf (make-face (intern (concat "chart-" (car cl) "-" (car pl))))) | |
102 (if (condition-case nil | |
103 (> (x-display-color-cells) 4) | |
104 (error t)) | |
105 (set-face-background nf (car cl)) | |
106 (set-face-background nf "white")) | |
107 (set-face-foreground nf "black") | |
108 (if (and chart-face-use-pixmaps | |
109 pl | |
110 (fboundp 'set-face-background-pixmap)) | |
111 (condition-case nil | |
112 (set-face-background-pixmap nf (car pl)) | |
113 (error (message "Cannot set background pixmap %s" (car pl))))) | |
114 (setq chart-face-list (cons nf chart-face-list)) | |
115 (setq cl (cdr cl) | |
116 pl (cdr pl))))) | |
117 | |
118 (defun chart-mode () | |
119 "Define a mode in Emacs for displaying a chart." | |
120 (kill-all-local-variables) | |
121 (use-local-map chart-map) | |
122 (setq major-mode 'chart-mode | |
123 mode-name "CHART") | |
124 (buffer-disable-undo) | |
125 (set (make-local-variable 'font-lock-global-modes) nil) | |
126 (font-lock-mode -1) | |
127 (run-hooks 'chart-mode-hook) | |
128 ) | |
129 | |
130 (defun chart-new-buffer (obj) | |
131 "Create a new buffer NAME in which the chart OBJ is displayed. | |
105474 | 132 Returns the newly created buffer." |
105813
df4934f25eef
* textmodes/two-column.el (2C-split):
Stefan Monnier <monnier@iro.umontreal.ca>
parents:
105474
diff
changeset
|
133 (with-current-buffer (get-buffer-create (format "*%s*" (oref obj title))) |
105237 | 134 (chart-mode) |
135 (setq chart-local-object obj) | |
136 (current-buffer))) | |
137 | |
138 (defclass chart () | |
139 ((title :initarg :title | |
140 :initform "Emacs Chart") | |
141 (title-face :initarg :title-face | |
142 :initform 'bold-italic) | |
143 (x-axis :initarg :x-axis | |
144 :initform nil ) | |
145 (x-margin :initarg :x-margin | |
146 :initform 5) | |
147 (x-width :initarg :x-width | |
148 ) | |
149 (y-axis :initarg :y-axis | |
150 :initform nil) | |
151 (y-margin :initarg :y-margin | |
152 :initform 5) | |
153 (y-width :initarg :y-width | |
154 ) | |
155 (key-label :initarg :key-label | |
156 :initform "Key") | |
157 (sequences :initarg :sequences | |
158 :initform nil) | |
159 ) | |
105474 | 160 "Superclass for all charts to be displayed in an Emacs buffer.") |
105237 | 161 |
162 (defmethod initialize-instance :AFTER ((obj chart) &rest fields) | |
163 "Initialize the chart OBJ being created with FIELDS. | |
164 Make sure the width/height is correct." | |
165 (oset obj x-width (- (window-width) 10)) | |
166 (oset obj y-width (- (window-height) 12))) | |
167 | |
168 (defclass chart-axis () | |
169 ((name :initarg :name | |
170 :initform "Generic Axis") | |
171 (loweredge :initarg :loweredge | |
172 :initform t) | |
173 (name-face :initarg :name-face | |
174 :initform 'bold) | |
175 (labels-face :initarg :lables-face | |
176 :initform 'italic) | |
177 (chart :initarg :chart | |
178 :initform nil) | |
179 ) | |
180 "Superclass used for display of an axis.") | |
181 | |
182 (defclass chart-axis-range (chart-axis) | |
183 ((bounds :initarg :bounds | |
184 :initform '(0.0 . 50.0)) | |
185 ) | |
105474 | 186 "Class used to display an axis defined by a range of values.") |
105237 | 187 |
188 (defclass chart-axis-names (chart-axis) | |
189 ((items :initarg :items | |
190 :initform nil) | |
191 ) | |
105474 | 192 "Class used to display an axis which represents different named items.") |
105237 | 193 |
194 (defclass chart-sequece () | |
195 ((data :initarg :data | |
196 :initform nil) | |
197 (name :initarg :name | |
198 :initform "Data") | |
199 ) | |
105474 | 200 "Class used for all data in different charts.") |
105237 | 201 |
202 (defclass chart-bar (chart) | |
203 ((direction :initarg :direction | |
204 :initform vertical)) | |
105474 | 205 "Subclass for bar charts (vertical or horizontal).") |
105237 | 206 |
207 (defmethod chart-draw ((c chart) &optional buff) | |
208 "Start drawing a chart object C in optional BUFF. | |
105474 | 209 Erases current contents of buffer." |
105237 | 210 (save-excursion |
211 (if buff (set-buffer buff)) | |
212 (erase-buffer) | |
213 (insert (make-string 100 ?\n)) | |
214 ;; Start by displaying the axis | |
215 (chart-draw-axis c) | |
216 ;; Display title | |
217 (chart-draw-title c) | |
218 ;; Display data | |
219 (message "Rendering chart...") | |
220 (sit-for 0) | |
221 (chart-draw-data c) | |
222 ;; Display key | |
223 ; (chart-draw-key c) | |
224 (message "Rendering chart...done") | |
225 )) | |
226 | |
227 (defmethod chart-draw-title ((c chart)) | |
228 "Draw a title upon the chart. | |
229 Argument C is the chart object." | |
230 (chart-display-label (oref c title) 'horizontal 0 0 (window-width) | |
231 (oref c title-face))) | |
232 | |
233 (defmethod chart-size-in-dir ((c chart) dir) | |
234 "Return the physical size of chart C in direction DIR." | |
235 (if (eq dir 'vertical) | |
236 (oref c y-width) | |
237 (oref c x-width))) | |
238 | |
239 (defmethod chart-draw-axis ((c chart)) | |
240 "Draw axis into the current buffer defined by chart C." | |
241 (let ((ymarg (oref c y-margin)) | |
242 (xmarg (oref c x-margin)) | |
243 (ylen (oref c y-width)) | |
244 (xlen (oref c x-width))) | |
245 (chart-axis-draw (oref c y-axis) 'vertical ymarg | |
246 (if (oref (oref c y-axis) loweredge) nil xlen) | |
247 xmarg (+ xmarg ylen)) | |
248 (chart-axis-draw (oref c x-axis) 'horizontal xmarg | |
249 (if (oref (oref c x-axis) loweredge) nil ylen) | |
250 ymarg (+ ymarg xlen))) | |
251 ) | |
252 | |
253 (defmethod chart-axis-draw ((a chart-axis) &optional dir margin zone start end) | |
105474 | 254 "Draw some axis for A in direction DIR with MARGIN in boundary. |
105237 | 255 ZONE is a zone specification. |
256 START and END represent the boundary." | |
257 (chart-draw-line dir (+ margin (if zone zone 0)) start end) | |
258 (chart-display-label (oref a name) dir (if zone (+ zone margin 3) | |
259 (if (eq dir 'horizontal) | |
260 1 0)) | |
261 start end (oref a name-face))) | |
262 | |
263 (defmethod chart-translate-xpos ((c chart) x) | |
264 "Translate in chart C the coordinate X into a screen column." | |
265 (let ((range (oref (oref c x-axis) bounds))) | |
266 (+ (oref c x-margin) | |
267 (round (* (float (- x (car range))) | |
268 (/ (float (oref c x-width)) | |
269 (float (- (cdr range) (car range)))))))) | |
270 ) | |
271 | |
272 (defmethod chart-translate-ypos ((c chart) y) | |
273 "Translate in chart C the coordinate Y into a screen row." | |
274 (let ((range (oref (oref c y-axis) bounds))) | |
275 (+ (oref c x-margin) | |
276 (- (oref c y-width) | |
277 (round (* (float (- y (car range))) | |
278 (/ (float (oref c y-width)) | |
279 (float (- (cdr range) (car range))))))))) | |
280 ) | |
281 | |
282 (defmethod chart-axis-draw ((a chart-axis-range) &optional dir margin zone start end) | |
283 "Draw axis information based upon a range to be spread along the edge. | |
284 A is the chart to draw. DIR is the direction. | |
285 MARGIN, ZONE, START, and END specify restrictions in chart space." | |
286 (call-next-method) | |
287 ;; We prefer about 5 spaces between each value | |
288 (let* ((i (car (oref a bounds))) | |
289 (e (cdr (oref a bounds))) | |
290 (z (if zone zone 0)) | |
291 (s nil) | |
292 (rng (- e i)) | |
293 ;; want to jump by units of 5 spaces or so | |
294 (j (/ rng (/ (chart-size-in-dir (oref a chart) dir) 4))) | |
295 p1) | |
296 (if (= j 0) (setq j 1)) | |
297 (while (<= i e) | |
298 (setq s | |
299 (cond ((> i 999999) | |
300 (format "%dM" (/ i 1000000))) | |
301 ((> i 999) | |
302 (format "%dK" (/ i 1000))) | |
303 (t | |
304 (format "%d" i)))) | |
305 (if (eq dir 'vertical) | |
306 (let ((x (+ (+ margin z) (if (oref a loweredge) | |
307 (- (length s)) 1)))) | |
308 (if (< x 1) (setq x 1)) | |
309 (chart-goto-xy x (chart-translate-ypos (oref a chart) i))) | |
310 (chart-goto-xy (chart-translate-xpos (oref a chart) i) | |
311 (+ margin z (if (oref a loweredge) -1 1)))) | |
312 (setq p1 (point)) | |
313 (insert s) | |
314 (chart-zap-chars (length s)) | |
315 (put-text-property p1 (point) 'face (oref a labels-face)) | |
316 (setq i (+ i j)))) | |
317 ) | |
318 | |
319 (defmethod chart-translate-namezone ((c chart) n) | |
320 "Return a dot-pair representing a positional range for a name. | |
321 The name in chart C of the Nth name resides. | |
106860
4cf58c2379f4
Mario Lang <mlang@delysid.org>: Remove some duplicated words.
Glenn Morris <rgm@gnu.org>
parents:
106815
diff
changeset
|
322 Automatically compensates for direction." |
105237 | 323 (let* ((dir (oref c direction)) |
324 (w (if (eq dir 'vertical) (oref c x-width) (oref c y-width))) | |
325 (m (if (eq dir 'vertical) (oref c y-margin) (oref c x-margin))) | |
326 (ns (length | |
327 (oref (if (eq dir 'vertical) (oref c x-axis) (oref c y-axis)) | |
328 items))) | |
329 (lpn (/ (+ 1.0 (float w)) (float ns))) | |
330 ) | |
331 (cons (+ m (round (* lpn (float n)))) | |
332 (+ m -1 (round (* lpn (+ 1.0 (float n)))))) | |
333 )) | |
334 | |
335 (defmethod chart-axis-draw ((a chart-axis-names) &optional dir margin zone start end) | |
336 "Draw axis information based upon A range to be spread along the edge. | |
105474 | 337 Optional argument DIR is the direction of the chart. |
338 Optional arguments MARGIN, ZONE, START and END specify boundaries of the drawing." | |
105237 | 339 (call-next-method) |
340 ;; We prefer about 5 spaces between each value | |
341 (let* ((i 0) | |
342 (s (oref a items)) | |
343 (z (if zone zone 0)) | |
344 (r nil) | |
345 (p nil) | |
346 (odd nil) | |
347 p1) | |
348 (while s | |
349 (setq odd (= (% (length s) 2) 1)) | |
350 (setq r (chart-translate-namezone (oref a chart) i)) | |
351 (if (eq dir 'vertical) | |
352 (setq p (/ (+ (car r) (cdr r)) 2)) | |
353 (setq p (- (+ (car r) (/ (- (cdr r) (car r)) 2)) | |
354 (/ (length (car s)) 2)))) | |
355 (if (eq dir 'vertical) | |
356 (let ((x (+ (+ margin z) (if (oref a loweredge) | |
357 (- (length (car s))) | |
358 (length (car s)))))) | |
359 (if (< x 1) (setq x 1)) | |
360 (if (> (length (car s)) (1- margin)) | |
361 (setq x (+ x margin))) | |
362 (chart-goto-xy x p)) | |
363 (chart-goto-xy p (+ (+ margin z) (if (oref a loweredge) | |
364 (if odd -2 -1) | |
365 (if odd 2 1))))) | |
366 (setq p1 (point)) | |
367 (insert (car s)) | |
368 (chart-zap-chars (length (car s))) | |
369 (put-text-property p1 (point) 'face (oref a labels-face)) | |
370 (setq i (+ i 1) | |
371 s (cdr s)))) | |
372 ) | |
373 | |
374 (defmethod chart-draw-data ((c chart-bar)) | |
375 "Display the data available in a bar chart C." | |
376 (let* ((data (oref c sequences)) | |
377 (dir (oref c direction)) | |
378 (odir (if (eq dir 'vertical) 'horizontal 'vertical)) | |
379 ) | |
380 (while data | |
381 (if (stringp (car (oref (car data) data))) | |
382 ;; skip string lists... | |
383 nil | |
384 ;; display number lists... | |
385 (let ((i 0) | |
386 (seq (oref (car data) data))) | |
387 (while seq | |
388 (let* ((rng (chart-translate-namezone c i)) | |
389 (dp (if (eq dir 'vertical) | |
390 (chart-translate-ypos c (car seq)) | |
391 (chart-translate-xpos c (car seq)))) | |
392 (zp (if (eq dir 'vertical) | |
393 (chart-translate-ypos c 0) | |
394 (chart-translate-xpos c 0))) | |
395 (fc (if chart-face-list | |
396 (nth (% i (length chart-face-list)) chart-face-list) | |
397 'default)) | |
398 ) | |
399 (if (< dp zp) | |
400 (progn | |
401 (chart-draw-line dir (car rng) dp zp) | |
402 (chart-draw-line dir (cdr rng) dp zp)) | |
403 (chart-draw-line dir (car rng) zp (1+ dp)) | |
404 (chart-draw-line dir (cdr rng) zp (1+ dp))) | |
405 (if (= (car rng) (cdr rng)) nil | |
406 (chart-draw-line odir dp (1+ (car rng)) (cdr rng)) | |
407 (chart-draw-line odir zp (car rng) (1+ (cdr rng)))) | |
408 (if (< dp zp) | |
409 (chart-deface-rectangle dir rng (cons dp zp) fc) | |
410 (chart-deface-rectangle dir rng (cons zp dp) fc)) | |
411 ) | |
412 ;; find the bounds, and chart it! | |
413 ;; for now, only do one! | |
414 (setq i (1+ i) | |
415 seq (cdr seq))))) | |
416 (setq data (cdr data)))) | |
417 ) | |
418 | |
419 (defmethod chart-add-sequence ((c chart) &optional seq axis-label) | |
420 "Add to chart object C the sequence object SEQ. | |
421 If AXIS-LABEL, then the axis stored in C is updated with the bounds of SEQ, | |
422 or is created with the bounds of SEQ." | |
423 (if axis-label | |
424 (let ((axis (eieio-oref c axis-label))) | |
425 (if (stringp (car (oref seq data))) | |
426 (let ((labels (oref seq data))) | |
427 (if (not axis) | |
428 (setq axis (make-instance chart-axis-names | |
429 :name (oref seq name) | |
430 :items labels | |
431 :chart c)) | |
432 (oset axis items labels))) | |
433 (let ((range (cons 0 1)) | |
434 (l (oref seq data))) | |
435 (if (not axis) | |
436 (setq axis (make-instance chart-axis-range | |
437 :name (oref seq name) | |
438 :chart c))) | |
439 (while l | |
440 (if (< (car l) (car range)) (setcar range (car l))) | |
441 (if (> (car l) (cdr range)) (setcdr range (car l))) | |
442 (setq l (cdr l))) | |
443 (oset axis bounds range))) | |
444 (if (eq axis-label 'x-axis) (oset axis loweredge nil)) | |
445 (eieio-oset c axis-label axis) | |
446 )) | |
447 (oset c sequences (append (oref c sequences) (list seq)))) | |
448 | |
449 ;;; Charting optimizers | |
450 | |
451 (defmethod chart-trim ((c chart) max) | |
452 "Trim all sequences in chart C to be at most MAX elements long." | |
453 (let ((s (oref c sequences))) | |
454 (while s | |
455 (let ((sl (oref (car s) data))) | |
456 (if (> (length sl) max) | |
457 (setcdr (nthcdr (1- max) sl) nil))) | |
458 (setq s (cdr s)))) | |
459 ) | |
460 | |
461 (defmethod chart-sort ((c chart) pred) | |
462 "Sort the data in chart C using predicate PRED. | |
105474 | 463 See `chart-sort-matchlist' for more details." |
105237 | 464 (let* ((sl (oref c sequences)) |
465 (s1 (car sl)) | |
466 (s2 (car (cdr sl))) | |
467 (s nil)) | |
468 (if (stringp (car (oref s1 data))) | |
469 (progn | |
470 (chart-sort-matchlist s1 s2 pred) | |
471 (setq s (oref s1 data))) | |
472 (if (stringp (car (oref s2 data))) | |
473 (progn | |
474 (chart-sort-matchlist s2 s1 pred) | |
475 (setq s (oref s2 data))) | |
476 (error "Sorting of chart %s not supported" (object-name c)))) | |
477 (if (eq (oref c direction) 'horizontal) | |
478 (oset (oref c y-axis) items s) | |
479 (oset (oref c x-axis) items s) | |
480 )) | |
481 ) | |
482 | |
483 (defun chart-sort-matchlist (namelst numlst pred) | |
105474 | 484 "Sort NAMELST and NUMLST (both sequence objects) based on predicate PRED. |
105237 | 485 PRED should be the equivalent of '<, except it must expect two |
105474 | 486 cons cells of the form (NAME . NUM). See `sort' for more details." |
105237 | 487 ;; 1 - create 1 list of cons cells |
488 (let ((newlist nil) | |
489 (alst (oref namelst data)) | |
490 (ulst (oref numlst data))) | |
491 (while alst | |
492 ;; this is reversed, but were are sorting anyway | |
493 (setq newlist (cons (cons (car alst) (car ulst)) newlist)) | |
494 (setq alst (cdr alst) | |
495 ulst (cdr ulst))) | |
496 ;; 2 - Run sort routine on it | |
497 (setq newlist (sort newlist pred) | |
498 alst nil | |
499 ulst nil) | |
500 ;; 3 - Separate the lists | |
501 (while newlist | |
502 (setq alst (cons (car (car newlist)) alst) | |
503 ulst (cons (cdr (car newlist)) ulst)) | |
504 (setq newlist (cdr newlist))) | |
505 ;; 4 - Store them back | |
506 (oset namelst data (reverse alst)) | |
507 (oset numlst data (reverse ulst)))) | |
508 | |
509 ;;; Utilities | |
510 | |
511 (defun chart-goto-xy (x y) | |
512 "Move cursor to position X Y in buffer, and add spaces and CRs if needed." | |
513 (let ((indent-tabs-mode nil) | |
514 (num (progn (goto-char (point-min)) (forward-line y)))) | |
515 (if (and (= 0 num) (/= 0 (current-column))) (newline 1)) | |
516 (if (eobp) (newline num)) | |
517 (if (< x 0) (setq x 0)) | |
518 (if (< y 0) (setq y 0)) | |
519 ;; Now, a quicky column moveto/forceto method. | |
520 (or (= (move-to-column x) x) | |
521 (let ((p (point))) | |
522 (indent-to x) | |
523 (remove-text-properties p (point) '(face)))))) | |
524 | |
525 (defun chart-zap-chars (n) | |
105327 | 526 "Zap up to N chars without deleting EOLs." |
105237 | 527 (if (not (eobp)) |
111433
05dc9dca8729
Replace end-of-line, save-excursion etc with point-at-eol, point-at-bol.
Glenn Morris <rgm@gnu.org>
parents:
110909
diff
changeset
|
528 (if (< n (- (point-at-eol) (point))) |
105237 | 529 (delete-char n) |
111433
05dc9dca8729
Replace end-of-line, save-excursion etc with point-at-eol, point-at-bol.
Glenn Morris <rgm@gnu.org>
parents:
110909
diff
changeset
|
530 (delete-region (point) (point-at-eol))))) |
105237 | 531 |
532 (defun chart-display-label (label dir zone start end &optional face) | |
533 "Display LABEL in direction DIR in column/row ZONE between START and END. | |
534 Optional argument FACE is the property we wish to place on this text." | |
535 (if (eq dir 'horizontal) | |
536 (let (p1) | |
537 (chart-goto-xy (+ start (- (/ (- end start) 2) (/ (length label) 2))) | |
538 zone) | |
539 (setq p1 (point)) | |
540 (insert label) | |
541 (chart-zap-chars (length label)) | |
542 (put-text-property p1 (point) 'face face) | |
543 ) | |
544 (let ((i 0) | |
545 (stz (+ start (- (/ (- end start) 2) (/ (length label) 2))))) | |
546 (while (< i (length label)) | |
547 (chart-goto-xy zone (+ stz i)) | |
548 (insert (aref label i)) | |
549 (chart-zap-chars 1) | |
550 (put-text-property (1- (point)) (point) 'face face) | |
551 (setq i (1+ i)))))) | |
552 | |
553 (defun chart-draw-line (dir zone start end) | |
554 "Draw a line using line-drawing characters in direction DIR. | |
105474 | 555 Use column or row ZONE between START and END." |
105237 | 556 (chart-display-label |
557 (make-string (- end start) (if (eq dir 'vertical) ?| ?\-)) | |
558 dir zone start end)) | |
559 | |
560 (defun chart-deface-rectangle (dir r1 r2 face) | |
561 "Colorize a rectangle in direction DIR across range R1 by range R2. | |
562 R1 and R2 are dotted pairs. Colorize it with FACE." | |
563 (let* ((range1 (if (eq dir 'vertical) r1 r2)) | |
564 (range2 (if (eq dir 'vertical) r2 r1)) | |
565 (y (car range2))) | |
566 (while (<= y (cdr range2)) | |
567 (chart-goto-xy (car range1) y) | |
568 (put-text-property (point) (+ (point) (1+ (- (cdr range1) (car range1)))) | |
569 'face face) | |
570 (setq y (1+ y))))) | |
571 | |
572 ;;; Helpful `I don't want to learn eieio just now' washover functions | |
573 | |
574 (defun chart-bar-quickie (dir title namelst nametitle numlst numtitle | |
575 &optional max sort-pred) | |
105474 | 576 "Wash over the complex EIEIO stuff and create a nice bar chart. |
105327 | 577 Create it going in direction DIR ['horizontal 'vertical] with TITLE |
105237 | 578 using a name sequence NAMELST labeled NAMETITLE with values NUMLST |
579 labeled NUMTITLE. | |
580 Optional arguments: | |
105474 | 581 Set the chart's max element display to MAX, and sort lists with |
105237 | 582 SORT-PRED if desired." |
583 (let ((nc (make-instance chart-bar | |
584 :title title | |
585 :key-label "8-m" ; This is a text key pic | |
586 :direction dir | |
587 )) | |
588 (iv (eq dir 'vertical))) | |
589 (chart-add-sequence nc | |
590 (make-instance chart-sequece | |
591 :data namelst | |
592 :name nametitle) | |
593 (if iv 'x-axis 'y-axis)) | |
594 (chart-add-sequence nc | |
595 (make-instance chart-sequece | |
596 :data numlst | |
597 :name numtitle) | |
598 (if iv 'y-axis 'x-axis)) | |
599 (if sort-pred (chart-sort nc sort-pred)) | |
600 (if (integerp max) (chart-trim nc max)) | |
601 (switch-to-buffer (chart-new-buffer nc)) | |
602 (chart-draw nc))) | |
603 | |
604 ;;; Test code | |
605 | |
606 (defun chart-test-it-all () | |
607 "Test out various charting features." | |
608 (interactive) | |
609 (chart-bar-quickie 'vertical "Test Bar Chart" | |
610 '( "U1" "ME2" "C3" "B4" "QT" "EZ") "Items" | |
611 '( 5 -10 23 20 30 -3) "Values") | |
612 ) | |
613 | |
614 ;;; Sample utility function | |
615 | |
616 (defun chart-file-count (dir) | |
105327 | 617 "Draw a chart displaying the number of different file extensions in DIR." |
105237 | 618 (interactive "DDirectory: ") |
619 (if (not (string-match "/$" dir)) | |
620 (setq dir (concat dir "/"))) | |
621 (message "Collecting statistics...") | |
622 (let ((flst (directory-files dir nil nil t)) | |
623 (extlst (list "<dir>")) | |
624 (cntlst (list 0))) | |
625 (while flst | |
626 (let* ((j (string-match "[^\\.]\\(\\.[a-zA-Z]+\\|~\\|#\\)$" (car flst))) | |
627 (s (if (file-accessible-directory-p (concat dir (car flst))) | |
628 "<dir>" | |
629 (if j | |
630 (substring (car flst) (match-beginning 1) (match-end 1)) | |
631 nil))) | |
632 (m (member s extlst))) | |
633 (if (not s) nil | |
634 (if m | |
635 (let ((cell (nthcdr (- (length extlst) (length m)) cntlst))) | |
636 (setcar cell (1+ (car cell)))) | |
637 (setq extlst (cons s extlst) | |
638 cntlst (cons 1 cntlst))))) | |
639 (setq flst (cdr flst))) | |
640 ;; Lets create the chart! | |
641 (chart-bar-quickie 'vertical "Files Extension Distribution" | |
642 extlst "File Extensions" | |
106895
181539c8b6a4
Fix typos in docstrings, error messages, etc.
Juanma Barranquero <lekktu@gmail.com>
parents:
106860
diff
changeset
|
643 cntlst "# of occurrences" |
105237 | 644 10 |
645 '(lambda (a b) (> (cdr a) (cdr b)))) | |
646 )) | |
647 | |
648 (defun chart-space-usage (d) | |
649 "Display a top usage chart for directory D." | |
650 (interactive "DDirectory: ") | |
651 (message "Collecting statistics...") | |
652 (let ((nmlst nil) | |
653 (cntlst nil) | |
654 (b (get-buffer-create " *du-tmp*"))) | |
655 (set-buffer b) | |
656 (erase-buffer) | |
657 (insert "cd " d ";du -sk * \n") | |
658 (message "Running `cd %s;du -sk *'..." d) | |
659 (call-process-region (point-min) (point-max) shell-file-name t | |
660 (current-buffer) nil) | |
661 (goto-char (point-min)) | |
662 (message "Scanning output ...") | |
663 (while (re-search-forward "^\\([0-9]+\\)[ \t]+\\([^ \n]+\\)$" nil t) | |
664 (let* ((nam (buffer-substring (match-beginning 2) (match-end 2))) | |
665 (num (buffer-substring (match-beginning 1) (match-end 1)))) | |
666 (setq nmlst (cons nam nmlst) | |
667 ;; * 1000 to put it into bytes | |
668 cntlst (cons (* (string-to-number num) 1000) cntlst)))) | |
669 (if (not nmlst) | |
670 (error "No files found!")) | |
671 (chart-bar-quickie 'vertical (format "Largest files in %s" d) | |
672 nmlst "File Name" | |
673 cntlst "File Size" | |
674 10 | |
675 '(lambda (a b) (> (cdr a) (cdr b)))) | |
676 )) | |
677 | |
678 (defun chart-emacs-storage () | |
679 "Chart the current storage requirements of Emacs." | |
680 (interactive) | |
681 (let* ((data (garbage-collect)) | |
682 (names '("strings/2" "vectors" | |
683 "conses" "free cons" | |
684 "syms" "free syms" | |
685 "markers" "free mark" | |
686 ;; "floats" "free flt" | |
687 )) | |
688 (nums (list (/ (nth 3 data) 2) | |
689 (nth 4 data) | |
690 (car (car data)) ; conses | |
691 (cdr (car data)) | |
692 (car (nth 1 data)) ; syms | |
693 (cdr (nth 1 data)) | |
694 (car (nth 2 data)) ; markers | |
695 (cdr (nth 2 data)) | |
696 ;(car (nth 5 data)) ; floats are Emacs only | |
697 ;(cdr (nth 5 data)) | |
698 ))) | |
699 ;; Lets create the chart! | |
700 (chart-bar-quickie 'vertical "Emacs Runtime Storage Usage" | |
701 names "Storage Items" | |
702 nums "Objects"))) | |
703 | |
704 (defun chart-emacs-lists () | |
705 "Chart out the size of various important lists." | |
706 (interactive) | |
707 (let* ((names '("buffers" "frames" "processes" "faces")) | |
708 (nums (list (length (buffer-list)) | |
709 (length (frame-list)) | |
710 (length (process-list)) | |
711 (length (face-list)) | |
712 ))) | |
713 (if (fboundp 'x-display-list) | |
714 (setq names (append names '("x-displays")) | |
715 nums (append nums (list (length (x-display-list)))))) | |
716 ;; Lets create the chart! | |
717 (chart-bar-quickie 'vertical "Emacs List Size Chart" | |
718 names "Various Lists" | |
719 nums "Objects"))) | |
720 | |
721 (defun chart-rmail-from () | |
722 "If we are in an rmail summary buffer, then chart out the froms." | |
723 (interactive) | |
724 (if (not (eq major-mode 'rmail-summary-mode)) | |
725 (error "You must invoke chart-rmail-from in an rmail summary buffer")) | |
726 (let ((nmlst nil) | |
727 (cntlst nil)) | |
728 (save-excursion | |
729 (goto-char (point-min)) | |
730 (while (re-search-forward "\\-[A-Z][a-z][a-z] +\\(\\w+\\)@\\w+" nil t) | |
731 (let* ((nam (buffer-substring (match-beginning 1) (match-end 1))) | |
732 (m (member nam nmlst))) | |
733 (message "Scanned username %s" nam) | |
734 (if m | |
735 (let ((cell (nthcdr (- (length nmlst) (length m)) cntlst))) | |
736 (setcar cell (1+ (car cell)))) | |
737 (setq nmlst (cons nam nmlst) | |
738 cntlst (cons 1 cntlst)))))) | |
106895
181539c8b6a4
Fix typos in docstrings, error messages, etc.
Juanma Barranquero <lekktu@gmail.com>
parents:
106860
diff
changeset
|
739 (chart-bar-quickie 'vertical "Username Occurrence in RMAIL box" |
105237 | 740 nmlst "User Names" |
106895
181539c8b6a4
Fix typos in docstrings, error messages, etc.
Juanma Barranquero <lekktu@gmail.com>
parents:
106860
diff
changeset
|
741 cntlst "# of occurrences" |
105237 | 742 10 |
743 '(lambda (a b) (> (cdr a) (cdr b)))) | |
744 )) | |
745 | |
746 | |
747 (provide 'chart) | |
748 | |
749 ;;; chart.el ends here |