Archive for vecto

an x-axis plus API cleanup

Some more progress on the charting library today:

  • cleaned up the API a little bit, opting for some nicer make-foo function instead of requiring calls to make-instance:
    • make-series label data &key color
    • make-axis label &key control-string draw-gridlines-p label-formatter
      • control-string: a format-compatible control string, and supplying it sets the label-formatter for the axis
      • label-formatter: a function of 1 argument, returns a string used as an axis label
    • make-line-chart width height &key series y-axis x-axis background
  • The lightened portion of the graph is now the average of the background color and pure white
  • The labels on the axes are optional, and the graph adjusts to fill as big an area as possible
  • The distance between x-axis labes is calculated based on actual string-widths, so hopefully they will space themselves out reasonably under most conditions

Some examples:

This demonstrates using a control-string and a custom function for axis labels:

line-chart-with-axis-labels1.png

  (render-chart
   (make-line-chart 400 300
		    :background '(.7 .7 .7)
		    :series (list (make-series "SeriesA"
					       '((-1 -2) (0 4) (1 5) (4 6) (5 -3)))
				  (make-series "SeriesB"
					       '((-1 4) (0 -2) (1 6) (5 -2) (6 5))
					       :color '(.3 .7 .9))
				  (make-series "SeriesC"
					       '((-1 0) (0 3) (1 1) (2 5) (4 -6))))
		    :y-axis (make-axis "widgets"
				       :control-string "~$")
		    :x-axis (make-axis "time"
				       :label-formatter #'months-from-now->mm/yy))
   "line-chart-with-axis-labels.png")

This one changes the background color, removes the axis labels, and disabled grid lines for the x-axis:

line-chart-with-axis-labels2.png

(render-chart
   (make-line-chart 400 300
		    :background '(.7 .5 .7)
		    :series (list (make-series "SeriesA"
					       '((-1 -2) (0 4) (1 5) (4 6) (5 -3)))
				  (make-series "SeriesB"
					       '((-1 4) (0 -2) (1 6) (5 -2) (6 5))
					       :color '(.3 .7 .9))
				  (make-series "SeriesC"
					       '((-1 0) (0 3) (1 1) (2 5) (4 -6))))
		    :y-axis (make-axis nil
				       :control-string "~$")
		    :x-axis (make-axis nil
				       :draw-gridlines-p nil
				       :label-formatter #'months-from-now->mm/yy))
   "line-chart-with-axis-labels.png")

The internals are still messy, but I’ve gone through and was able to simplify some things here and there, here’s a few of the change’s I’ve made to make my code shorter and more readable:

  • replacing (destructuring-bind (x y) (foo) (bar x y) with (apply #’bar (foo))
  • adding a macrolet to concentrate some very similar behavior
  • using loop to count up and down at the same time (for drawing labels up/down the y axis and left/right on the x axis)
  • using (dolist (item lst) …) instead of (mapc #’(lambda(item) …) lst)
  • using loop’s maximizing / minimizing features
  • using multiple-value-list and nth to get specific values from some function results without compiler warnings

I’ve been trying to put my tongue on why exactly I like lisp so much more than other languages, and working through this made it pretty clear: I have much better tools for reducing accidental complexity than in other languages, and can get the code focusing on what I’m trying to do, not how my compiler needs me to do it. The next time I get a free afternoon, I’m going to make an imperative API, similar to vecto’s:

(with-line-chart (400 300 :background '(.7 .7 .7))
    (add-series "SeriesA"
		'((-1 -2) (0 4) (1 5) (4 6) (5 -3)))
    (add-series "SeriesB"
		'((-1 4) (0 -2) (1 6) (5 -2) (6 5))
		:color '(.3 .7 .9))
    (add-series "SeriesC"
		'((-1 0) (0 3) (1 1) (2 5) (4 -6)))
    (set-axis :y "widgets" :control-string "~$")
    (set-axis :x "time" :label-formatter #'months-from-now->mm/yy)
    (save-file "line-chart-with-axis-labels.png")

That’s pretty damn readable.

updated: fixed syntax error in the imperative sample, and got the imperative bit actually working.

Comments

more graphing

Another productive afternoon:

line-chart-with-axis-labels.png

This is the start of axes. The internals are getting very, very messy, but I really like the result. Here’s the code used to create this:

(defun line-chart-with-axis-labels ()
  "draws a simple line chart"
  (let* ((seriesA (make-instance 'series
				 :label "SeriesA"
					;data expressed as a list (x y) pairs
				 :data '((-1 -2) (0 4) (1 5) (4 6) (5 -3))))
	 (seriesB (make-instance 'series
				 :label "SeriesB"
				 :data '((-1 4) (0 -2) (1 6) (5 -2) (6 5))))
	 (y-axis (make-instance 'axis
				:label "widgets"
				:label-formatter #'(lambda (x-val)
						     (format nil "~$" x-val))))
	 (chart (make-instance 'line-chart
			       :width 400
			       :height 300
			       :background '(.7 .7 .7)
			       :series (list seriesA seriesB)
			       :y-axis y-axis)))
    (render-chart chart "line-chart-with-axis-labels.png")))

I predict adding X axes will be very similar to adding the Y axis, and then I’ll be looking more seriously at spending time to make good docs and releasing this back to the community. The API is getting a little dense, too, but that should be pretty easy to simplify, and I have a few ideas on how to do that.

Comments (1)

charting library taking form

It now does line charts and better pie charts, and has had a lot of bugs removed since the last post. We did some code review on it at work, brought some of the spaghetti under control, and included the library in an intranet app.

Here’s a sample line graph:

line-chart-sample.png

The (somewhat verbose) code for this:

(defun line-chart-sample ()
  "draws a simple line chart"
  (let* ((seriesA (make-instance 'series
				 :label "SeriesA"
					;data expressed as a list (x y) pairs
				 :data '((-1 -2) (0 4) (1 5) (4 6) (5 -3))))
	 (seriesB (make-instance 'series
				 :label "SeriesB"
				 :data '((-1 4) (0 -2) (1 6) (5 -2) (6 5))))
	 (chart (make-instance 'line-chart
			       :width 400
			       :background '(.7 .7 .7)
			       :series (list seriesA seriesB))))
    (render-chart chart "line-chart-sample.png")))

I’ll probably make some easier syntax for creating series, similar to the one I made for pie slices, but for now the CLOS approach has been working decently. My data is coming from clsql, so the code ends up more concise than this example.

There are a few spots internally where it gets a little nasty, needing to pass many things around to many functions. I may end up changing the style to use “with-line-chart” functions with some global variables tracking the current chart, but I’m not sure how that would work in a multi-threaded environment. Hell, I’m not sure if my existing code will work in a multi-threaded environment. The load on this intranet app is very low, so I may be lucky enough to get away with it.

Comments (3)

A good day’s hacking

Everyone had the day off, so I decided to work on making a graphing library. I’ve been on a fairly constant quest for easy data visualization, running through several options:

  1. homegrown SVG renderer (javascript)
  2. homegrown png renderer (C#)
  3. Gruff (ruby)
  4. some graphing capability from Dojo (javascript)
  5. cl-plplot

I’ve never been happy with any of these, for a few reasons:

  1. SVG support for IE is a pain
  2. C# requires a million objects to do anything
  3. Gruff had good defaults, but had some annoying bugs, and I ended up jumping through a lot of hoops to get some basic aggregation going.  I looked at monkey-patching gruff, but that seemed like a really bad idea
  4. The dojo charting was slow, and the setup was verbose (this may have changed)
  5. cl-plplot output wasn’t very attractive. Great for scientific usages, but for business apps it didn’t have enough rounded edges.

I want something that’s concise, has good defaults, rendered to PNG, and is easy to use from lisp. So today I tried rolling my own, and I’m pretty happy with the results:

pie-chart-sample.png

The code to generate this graph is pretty concise, too:

(defun pie-chart-sample ()
  "draws a simple pie chart, rending to pie-chart-sample.png"
  (render-chart
   (make-instance 'pie-chart
		  :data-items (make-items '(40
					    (10 "baz")
					    (60 "bar" (.5 .5 .5)))))
   "pie-chart-sample.png"))

This demonstrates a few things:

  1. data can be specified as value, a value/label, or value/label/color
  2. it automatically choses colors if unspecified
  3. the labels are automatically positioned off to the side

There’s still some places to cleanup the API, I don’t like having make-items in there, and a lot of the behind the scenes is messy as all hell.

I started by looking at the source for Paragent, a lisp-based IT management tool. I first saw really attractive graphs coming from lisp there, and it impressed me a lot. It was building on cl-vectors, salza, and zpb-ttf to make png files. There was a lot of weird stuff going on in the paragent code, so I instead opted to port my old javascript code to lisp, using vecto for the rendering.

I ran into a brick wall pretty early when calculating the wedges, as I’ve forgotten just about all the math I ever knew (had to look up the distance formula, for christsakes). The main problem was figuring out how to draw the curved part. I spent a long time staring at Bézier curves before giving up and taking a different approach. I made a circular clip-path (basically a mask) for the chart, and drew big enough wedges to fill the circle, side-stepping my mathematical shortcomings. There’s some goofy code to account for large slices, but it worked for all values I tested.

I did a LOT of learning on this one, working through some package semantics and adding some sparse lisp-unit tests. A few more days worth of effort for other chart types, and I might have something worthy of release.

Comments (1)

Graphing my fuel efficiency with lisp and Vecto

Gonna try to be quick about this, as there’s chicken on the grill. I took a break from Team Fortress 2 to play some more with Vecto, another fine library from Xach. I started another post about last weekend’s lisp playing, but this one got finished first.

I’ve been tracking my fuel efficiency for a few weeks, noting the date, gallons purchased, price per gallon, and miles since last refill. You can’t control what you don’t measure. I had a ruby script keeping up with these as YAML and then giving me some handy stats, but I’m trying to gain some levels in lisp, so I decided to fire up slime and make some graphs.

The graph isn’t pretty, but here’s my miles per gallon:

graph.png

As usual, I pulled up my favorite lisp tutorial and #lisp, and after a few hours, I had a graph. Given my logging fetish, I imagine I’ll be growing the graphing capabilities in the future.

Right now, the implementation is a little weird. I have an class for a fuel entry, and then convert the date to a universal-time using net.telent.date. Then in my graphing function I use the universal time for the x values, and any number of functions for the y values (defaulting to #’mpg-of). In order to get the graph to take up most of the canvas, I then normalize the x and y values, so the minimum is 0 and the maximum is 1, and then scale the number to fit the canvas size before drawing.

I still consider myself a lisp beginner, and there are some bits I’m sure could be more efficient, but it works for now. Those interested in the code can get it here: fuelman.tar.gz, and any feedback in comments is much appreciated.

Update: Played with it a lot more, simplified my normalization crap a lot using loop instead of mapcar, and pulled it into a couple of objects: graph, graph-series, and fuel-graph-series.  Also linked the source above as a proper .tar.gz file, after reading how to do that in wordpress.  The aesthetics are still terrible, but it’s getting there:

graph1.png

Comments