emacs-orgmode@gnu.org archives
 help / color / mirror / code / Atom feed
* SOLVED: elisp formulas in column view (without converting to tables)
@ 2009-03-13 23:43 news
  2009-03-14  1:33 ` news
  2009-03-16 20:15 ` news
  0 siblings, 2 replies; 4+ messages in thread
From: news @ 2009-03-13 23:43 UTC (permalink / raw)
  To: emacs-orgmode; +Cc: RLAdams

Hi,
   I have rewritten the org-columns-compute function to allow elisp
   formulas in column view.
   It allows you to specify how to accumulate values from child headers,
   and how to specify the value for the current header, based on other
   columns.
   In the column specification you place elisp code between the braces
   {} after the column name. This elisp should return a list of 2
   values. The first value should be the value for that header, and the second
   value should be the running total of values so far at that level, which will
   eventually be used to set the value for the parent header.

   The following values may be referenced by your elisp code:

               | $name     | the value of the 'name' column for this header (swap 'name' for whatever columns you have) |
               | curval    | the current value for this header                                                          |
               | curtotal  | the running total from headers at the same level as this one                               |
               | prevtotal | the total from headers below this level                                                    |

   Here is the column specification for the example that Eric gave:

     :COLUMNS:  %25ITEM %2d{`(,(or curval 0) 0)} %f{`(,(or curval 0) 0)} %2fd{`(,(+ $f $d) 0)}

   This will ensure that the 'fd' column contains the sum of the 'f' and
   'd' columns. If there are any missing values in the 'f' and 'd'
   columns they will be replaced with 0's. No running total is needed
   for that example so the second element of the return list just
   contains 0.
   I use the ` and , chars to evaluate the elements between the brackets
   before returning the list, but you could also use the (list val1
   val2) form.

   Here is a more complicated example that calculates the geometric mean of child
   headers:

     :COLUMNS:  %25ITEM %5count{`(,(or prevtotal 1) ,(if curtotal (1+ curtotal) 1))} %21val{`(,(if prevtotal (exp (/ prevtotal $count)) (or curval 1)) ,(if curtotal (+ curtotal (log curval)) (or (log curval) 1)))} 

   The 'val' column will contain either raw data, or a geometric mean
   depending on whether the header has children or not. The extra column
   'count' is used to count the number of children (is there an org
   function for this?).

   You could use the calc-eval function in your elisp code to do some
   calculations for you, but this would get a bit messy as it takes a 
   string argument and returns a string, so you would need to make lots
   of use of the number-to-string and string-to-number functions.

   I guess it is not a very simple solution but it does the job for now.

   It anyone can improve on it that would be great... or maybe Dominik
   already has something?

   Here it is:

(defun org-columns-compute (property)
  "Compute the values of property PROPERTY hierarchically, for the entire buffer."
  (interactive)
  (let* ((re (concat "^" outline-regexp))
	 (lmax 30) ; Does anyone use deeper levels???
	 (level 0)
	 (ass (assoc property org-columns-current-fmt-compiled))
	 ;; parse elisp form if there is one
	 (form (nth 3 ass))
	 (uselisp (and (> (length form) 1)
		       (or (equal "(" (substring form 0 1)) 
			   (equal "(" (substring form 1 2)))))
	 (form (if uselisp
		   (replace-regexp-in-string 
		    "\$\\([^()\" 	]+\\)" 
		    "(string-to-number (org-entry-get nil \"\\1\"))" 
		    (nth 3 ass) t)))
	 ;; vector to hold running totals for each level
	 (lsum (make-vector lmax (if uselisp nil 0)))
	 (format (nth 4 ass))
	 (printf (nth 5 ass))
	 (beg org-columns-top-level-marker)
	 last-level val valflag end sumpos sum-alist str str1 useval prevtotal curtotal newvals)
    (save-excursion
      ;; Find the region to compute
      (goto-char beg)
      (setq end (condition-case nil (org-end-of-subtree t) (error (point-max))))
      (goto-char end)
      ;; Walk the tree from the back and do the computations
      (while (re-search-backward re beg t)
	(setq sumpos (match-beginning 0)
	      last-level level
	      level (org-outline-level)
	      ;; total from children, or nil if there were none
	      prevtotal (if (< level last-level) (aref lsum last-level) nil)
	      ;; total at this level
	      curtotal (aref lsum level)
	      ;; current property value as string
	      val (org-entry-get nil property)
	      ;; is it non-empty?
	      valflag (and val (string-match "\\S-" val))
	      ;; current property value as number (or nil if empty)
	      curval (if valflag (org-column-string-to-number val format) nil)
	      ;; get values to replace current value and running total
	      newvals (if uselisp (eval-expression (read form))
			(list (or prevtotal curval)
			      (+ curtotal (or prevtotal curval 0)))))
	(cond
	 ((< level last-level) ; we have moved up to a parent
	  (setq 
	   ;; new value, as string
	   str (if (nth 0 newvals) (org-columns-number-to-string (nth 0 newvals) format printf) nil)
	   ;; add text properties to it
	   useval (org-add-props (copy-sequence str) nil 'org-computed t 'face 'bold)
	   ;; get current text properties
	   sum-alist (get-text-property sumpos 'org-summaries))
	  ;; put new value here as a text property
	  (if (assoc property sum-alist)
	      (setcdr (assoc property sum-alist) useval)
	    (push (cons property useval) sum-alist)
	    (org-unmodified
	     (add-text-properties sumpos (1+ sumpos)
				  (list 'org-summaries sum-alist))))
	  ;; put new org property value 
	  (if (nth 0 newvals) (org-entry-put nil property str))
	  ;; set value for current level total
	  (when (or prevtotal valflag)
	    (aset lsum level (nth 1 newvals)))
	  ;; clear totals for deeper levels
	  (loop for l from (1+ level) to (1- lmax) do
		(aset lsum l (if uselisp nil 0))))
	 ((>= level last-level) ; we have not moved up to a parent
	  ;; set new org property value and add to total for this level
	  (org-entry-put nil property (org-columns-number-to-string (nth 0 newvals) format printf))
	  (aset lsum level (nth 1 newvals)))
	 (t (error "This should not happen")))))))



-- 
aleblanc

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: SOLVED: elisp formulas in column view (without converting to tables)
  2009-03-13 23:43 SOLVED: elisp formulas in column view (without converting to tables) news
@ 2009-03-14  1:33 ` news
  2009-03-16 20:15 ` news
  1 sibling, 0 replies; 4+ messages in thread
From: news @ 2009-03-14  1:33 UTC (permalink / raw)
  To: emacs-orgmode

<news@aleblanc.cotse.net> writes:

> Hi,
>    I have rewritten the org-columns-compute function to allow elisp
>    formulas in column view.
>    It allows you to specify how to accumulate values from child headers,
>    and how to specify the value for the current header, based on other
>    columns.
>    In the column specification you place elisp code between the braces
>    {} after the column name. This elisp should return a list of 2
>    values. The first value should be the value for that header, and the second
>    value should be the running total of values so far at that level, which will
>    eventually be used to set the value for the parent header.
>
>    The following values may be referenced by your elisp code:
>
>                | $name     | the value of the 'name' column for this header (swap 'name' for whatever columns you have) |
>                | curval    | the current value for this header                                                          |
>                | curtotal  | the running total from headers at the same level as this one                               |
>                | prevtotal | the total from headers below this level                                                    |
>

I forgot to mention that curtotal will be nil if the current header is
the first header at the current level, and prevtotal will be nil if the
current header has no children. You can test for these in your elisp functions.

-- 
aleblanc

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: SOLVED: elisp formulas in column view (without converting to tables)
  2009-03-13 23:43 SOLVED: elisp formulas in column view (without converting to tables) news
  2009-03-14  1:33 ` news
@ 2009-03-16 20:15 ` news
  2009-06-09  6:26   ` Carsten Dominik
  1 sibling, 1 reply; 4+ messages in thread
From: news @ 2009-03-16 20:15 UTC (permalink / raw)
  To: emacs-orgmode

<news@aleblanc.cotse.net> writes:

> Hi,
>    I have rewritten the org-columns-compute function to allow elisp
>    formulas in column view.
>    It allows you to specify how to accumulate values from child headers,
>    and how to specify the value for the current header, based on other
>    columns.

Have since discovered that my new version doesn't work with checkbox
formulas. The solution is to use the old version of org-columns-compute
if the formula is not an elisp formula. Rename the old function to
org-columns-compute-orig, and then use this code for
org-columns-compute:

(defun org-columns-compute (property)
  "Sum the values of property PROPERTY hierarchically, for the entire buffer."
  (interactive)
  (let* ((re (concat "^" outline-regexp))
	 (lmax 30) ; Does anyone use deeper levels???
	 (level 0)
	 (ass (assoc property org-columns-current-fmt-compiled))
	 ;; parse elisp form if there is one
	 (form (nth 3 ass))
	 (uselisp (and (> (length form) 1)
		       (or (equal "(" (substring form 0 1)) 
			   (equal "(" (substring form 1 2)))))
	 (form (if uselisp
		   (replace-regexp-in-string 
		    "\$\\([^()\" 	]+\\)" 
		    "(string-to-number (or (org-entry-get nil \"\\1\") \"0\"))" 
		    (nth 3 ass) t)))
	 ;; vector to hold running totals for each level
	 (lsum (make-vector lmax (if uselisp nil 0)))
	 (format (nth 4 ass))
	 (printf (nth 5 ass))
	 (beg org-columns-top-level-marker)
	 last-level val valflag end sumpos sum-alist str str1 useval prevtotal curtotal newvals)
    (if uselisp
	(save-excursion
	  ;; Find the region to compute
	  (goto-char beg)
	  (setq end (condition-case nil (org-end-of-subtree t) (error (point-max))))
	  (goto-char end)
	  ;; Walk the tree from the back and do the computations
	  (while (re-search-backward re beg t)
	    (setq sumpos (match-beginning 0)
		  last-level level
		  level (org-outline-level)
		  ;; total from children, or nil if there were none
		  prevtotal (if (< level last-level) (aref lsum last-level) nil)
		  ;; total at this level
		  curtotal (aref lsum level)
		  ;; current property value as string
		  val (org-entry-get nil property)
		  ;; is it non-empty?
		  valflag (and val (string-match "\\S-" val))
		  ;; current property value as number (or nil if empty)
		  curval (if valflag (org-column-string-to-number val format) nil)
		  ;; get values to replace current value and running total
		  newvals (if uselisp (eval-expression (read form))
			    (list (or prevtotal curval 0)
				  (+ curtotal (or prevtotal curval 0)))))
	    (cond
	     ((< level last-level) ; we have moved up to a parent
	      (setq 
	       ;; new value, as string
	       str (if (nth 0 newvals) (org-columns-number-to-string (nth 0 newvals) format printf) nil)
	       ;; add text properties to it
	       useval (org-add-props (copy-sequence str) nil 'org-computed t 'face 'bold)
	       ;; get current text properties
	       sum-alist (get-text-property sumpos 'org-summaries))
	      ;; put new value here as a text property
	      (if (assoc property sum-alist)
		  (setcdr (assoc property sum-alist) useval)
		(push (cons property useval) sum-alist)
		(org-unmodified
		 (add-text-properties sumpos (1+ sumpos)
				      (list 'org-summaries sum-alist))))
	      ;; put new org property value 
	      (if (nth 0 newvals) (org-entry-put nil property str))
	      ;; set value for current level total
	      (when (or prevtotal valflag)
		(aset lsum level (nth 1 newvals)))
	      ;; clear totals for deeper levels
	      (loop for l from (1+ level) to (1- lmax) do
		    (aset lsum l (if uselisp nil 0))))
	     ((>= level last-level) ; we have not moved up to a parent
	      ;; set new org property value and add to total for this level
	      (org-entry-put nil property (org-columns-number-to-string (nth 0 newvals) format printf))
	      (aset lsum level (nth 1 newvals)))
	     (t (error "This should not happen")))))
      (org-columns-compute-orig property))))



-- 
aleblanc

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: Re: SOLVED: elisp formulas in column view (without converting to tables)
  2009-03-16 20:15 ` news
@ 2009-06-09  6:26   ` Carsten Dominik
  0 siblings, 0 replies; 4+ messages in thread
From: Carsten Dominik @ 2009-06-09  6:26 UTC (permalink / raw)
  To: news@aleblanc.cotse.net> <news@aleblanc.cotse.net; +Cc: emacs-orgmode

Hi,

sorry for replying so late to this proposal, which is a very
nice idea.  However, specifying lisp formulas in a #+COLUMNS
definition might be a bit tedious.

In the mean time, we have installed (in version 6.27) a patch by
Mikael Fornius.  This patch defines new operators `min', `max',
and `mean', and special versions for time computations `min:',
`max:', and `mean:'.  The patch also captures all operator
definitions into a single variable, which does allow for
user-defined Lisp operators.

I have not tried, but I believe it might now be simple to add new
operators by just adding to the variable `org-columns-compile-map'.
I would be interested to hear if someone has amended this variable
successfully.  If yes, maybe we can expose it better by making
is a defcustom, or by introducing a buffer-local add-on to it.

Hope this helps

- Carsten

On Mar 16, 2009, at 9:15 PM, <news@aleblanc.cotse.net> <news@aleblanc.cotse.net 
 > wrote:

> <news@aleblanc.cotse.net> writes:
>
>> Hi,
>>   I have rewritten the org-columns-compute function to allow elisp
>>   formulas in column view.
>>   It allows you to specify how to accumulate values from child  
>> headers,
>>   and how to specify the value for the current header, based on other
>>   columns.
>
> Have since discovered that my new version doesn't work with checkbox
> formulas. The solution is to use the old version of org-columns- 
> compute
> if the formula is not an elisp formula. Rename the old function to
> org-columns-compute-orig, and then use this code for
> org-columns-compute:
>
> (defun org-columns-compute (property)
>  "Sum the values of property PROPERTY hierarchically, for the entire  
> buffer."
>  (interactive)
>  (let* ((re (concat "^" outline-regexp))
> 	 (lmax 30) ; Does anyone use deeper levels???
> 	 (level 0)
> 	 (ass (assoc property org-columns-current-fmt-compiled))
> 	 ;; parse elisp form if there is one
> 	 (form (nth 3 ass))
> 	 (uselisp (and (> (length form) 1)
> 		       (or (equal "(" (substring form 0 1))
> 			   (equal "(" (substring form 1 2)))))
> 	 (form (if uselisp
> 		   (replace-regexp-in-string
> 		    "\$\\([^()\" 	]+\\)"
> 		    "(string-to-number (or (org-entry-get nil \"\\1\") \"0\"))"
> 		    (nth 3 ass) t)))
> 	 ;; vector to hold running totals for each level
> 	 (lsum (make-vector lmax (if uselisp nil 0)))
> 	 (format (nth 4 ass))
> 	 (printf (nth 5 ass))
> 	 (beg org-columns-top-level-marker)
> 	 last-level val valflag end sumpos sum-alist str str1 useval  
> prevtotal curtotal newvals)
>    (if uselisp
> 	(save-excursion
> 	  ;; Find the region to compute
> 	  (goto-char beg)
> 	  (setq end (condition-case nil (org-end-of-subtree t) (error  
> (point-max))))
> 	  (goto-char end)
> 	  ;; Walk the tree from the back and do the computations
> 	  (while (re-search-backward re beg t)
> 	    (setq sumpos (match-beginning 0)
> 		  last-level level
> 		  level (org-outline-level)
> 		  ;; total from children, or nil if there were none
> 		  prevtotal (if (< level last-level) (aref lsum last-level) nil)
> 		  ;; total at this level
> 		  curtotal (aref lsum level)
> 		  ;; current property value as string
> 		  val (org-entry-get nil property)
> 		  ;; is it non-empty?
> 		  valflag (and val (string-match "\\S-" val))
> 		  ;; current property value as number (or nil if empty)
> 		  curval (if valflag (org-column-string-to-number val format) nil)
> 		  ;; get values to replace current value and running total
> 		  newvals (if uselisp (eval-expression (read form))
> 			    (list (or prevtotal curval 0)
> 				  (+ curtotal (or prevtotal curval 0)))))
> 	    (cond
> 	     ((< level last-level) ; we have moved up to a parent
> 	      (setq
> 	       ;; new value, as string
> 	       str (if (nth 0 newvals) (org-columns-number-to-string (nth 0  
> newvals) format printf) nil)
> 	       ;; add text properties to it
> 	       useval (org-add-props (copy-sequence str) nil 'org-computed  
> t 'face 'bold)
> 	       ;; get current text properties
> 	       sum-alist (get-text-property sumpos 'org-summaries))
> 	      ;; put new value here as a text property
> 	      (if (assoc property sum-alist)
> 		  (setcdr (assoc property sum-alist) useval)
> 		(push (cons property useval) sum-alist)
> 		(org-unmodified
> 		 (add-text-properties sumpos (1+ sumpos)
> 				      (list 'org-summaries sum-alist))))
> 	      ;; put new org property value
> 	      (if (nth 0 newvals) (org-entry-put nil property str))
> 	      ;; set value for current level total
> 	      (when (or prevtotal valflag)
> 		(aset lsum level (nth 1 newvals)))
> 	      ;; clear totals for deeper levels
> 	      (loop for l from (1+ level) to (1- lmax) do
> 		    (aset lsum l (if uselisp nil 0))))
> 	     ((>= level last-level) ; we have not moved up to a parent
> 	      ;; set new org property value and add to total for this level
> 	      (org-entry-put nil property (org-columns-number-to-string  
> (nth 0 newvals) format printf))
> 	      (aset lsum level (nth 1 newvals)))
> 	     (t (error "This should not happen")))))
>      (org-columns-compute-orig property))))
>
>
>
> -- 
> aleblanc
>
>
>
> _______________________________________________
> Emacs-orgmode mailing list
> Remember: use `Reply All' to send replies to the list.
> Emacs-orgmode@gnu.org
> http://lists.gnu.org/mailman/listinfo/emacs-orgmode

=======================
PLEASE NOTE NEW ADDRESS
=======================
prof.dr. Carsten Dominik				dominik@uva.nl
Astronomical Institute 'Anton Pannekoek' 	 	www.astro.uva.nl/~dominik
Faculty of Science, University of Amsterdam		phone 	+31-20-5257477/7491
SCIENCE PARK 904, ROOM C4-106			fax   	+31-20-5257484
1098 XH Amsterdam, The Netherlands
mail: PO BOX 94249, 1090GE, Amsterdam

^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2009-06-09 10:10 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2009-03-13 23:43 SOLVED: elisp formulas in column view (without converting to tables) news
2009-03-14  1:33 ` news
2009-03-16 20:15 ` news
2009-06-09  6:26   ` Carsten Dominik

Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs/org-mode.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).