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