Some time ago I've been experimenting with the processing and display of RSS feeds within Impromptu, and as a result I built a small app that retrieves the news feed from The Guardian online and displays on a canvas. I've had a bit of free time these days, so last night I thought it was time to polish it a little and make it available on this blog (who knows maybe someone else will use it as starting point for another project).
There're a thousand improvements that could be done to it still, but the core of the application is there: I packaged it as a standalone app that you can download here. (use the 'show package contents' Finder command to see the source code).
The application relies on a bunch of XML processing functions that I found within Impromptu 'examples' folder (specifically, it's the example named 35_objc_xml_lib). I pruned that a bit so to fit my purposes and renamed it xml_lib.scm.
By using that, I created a function that extracts title and url info from the guardian feed:
(load "xml_lib.scm") (define feedurl "http://feeds.guardian.co.uk/theguardian/world/rss") ;; ;; loads the feed and extracts title and url ;; (define get-articles-online (lambda () (let* ((out '()) (feed (xml:load-url feedurl)) (titles (objc:nsarray->list (xml:xpath (xml:get-root-node feed) "channel/item/title/text()"))) (urls (objc:nsarray->list (xml:xpath (xml:get-root-node feed) "channel/item/link/text()")))) (for-each (lambda (x y) (let ((xx (objc:nsstring->string x)) (yy (objc:nsstring->string y))) (set! out (append out (list (list xx yy)))))) titles urls) out)))
Some feed titles are a bit longish, so I added a utility function formattext that wraps the titles' text if they exceed a predefined length.
(define formattext (lambda (maxlength txt posx posy) (let ((l (string-length txt))) (if (> l maxlength) (let loop ((i 0) (j maxlength) ;; comparison value: it decreases at each recursion (except the first one) (topvalue maxlength)) ;; komodo value : must be equal to j at the beginning (if (equal? (- topvalue i) j) ;; the first time (loop (+ i 1) j topvalue) (begin ;(print (substring txt (- topvalue i) j)) (if (string=? (substring txt (- topvalue i) j) " ") (string-append (substring txt 0 (- topvalue i)) "n" (substring txt (- topvalue i) (string-length txt))) (if (< i topvalue) ;;avoid negative indexes in substring (loop (+ i 1) (- j 1) topvalue)))))) txt))))
And here's the main loop: it goes through all the feed items at a predefined speed, and displays it on the canvas using a cosine oscillator to vary the colours a bit. At the end of it I'm also updating 3 global variables that are used for the mouse-click-capturing routine.
(define displayloop (lambda (beat feeds) (let* ((dur 5) (posx (random 0 (- *canvas_max_x* 350))) (posy (random 10 (- *canvas_max_y* 150))) (txt (formattext 40 (car (car feeds)) posx posy)) (dim ;(+ (length feeds) 10)) (if (= (length feeds) 29) 60 ;; if it's the first element of the feed list make it bigger (random 25 50))) (fill (if (= (length feeds) 29) '(1 0 (random) 1) ;; if it's the first element of the feed list make it reddish (list (random) 1 (random) 1))) (style (gfx:make-text-style "Arial" dim fill))) (gfx:clear-canvas (*metro* beat) *canvas* (list (cosr .5 .6 .001) 0 (cosr .5 .6 .001) .5 )) (gfx:draw-text (*metro* beat) *canvas* txt style (list posx posy)) (set! *pos_x* posx) (set! *pos_y* posy) (set! *current_url* (cadr (car feeds))) (callback (*metro* (+ beat (* 1/2 dur))) 'displayloop (+ beat dur) (if-cdr-notnull feeds (get-articles-online))))))
In order to capture the clicks on the feed titles I simply create a rectangle path based on the x,y coordinates randomly assigned when displaying the title on the canvas. These coordinates are stored in global variables so that they can be updated constantly.
(io:register-mouse-events *canvas*) (define io:mouse-down (lambda (x y) (print x y) (when (gfx:point-in-path? (gfx:make-rectangle *pos_x* *pos_y* 200 200) x y ) (util:open-url *current_url*))))
Finally, the util:open-url opens up a url string in your browser (I've already talked about it here).
You can see all of this code in action by downloading the app and taking a look its contents (all the files are under Contents/Resources/app).
Some other things it'd be nice to do:
Cite this blog post:
Second biennial conference on Language, Data and Knowledge (LDK 2019), Leipzig, Germany, May 2019.
WHiSe 2017 - 2nd Workshop on Humanities in the Semantic web (colocated with ISWC17), Vienna, Austria, Oct 2017.
blog Let's visualize 'em