Skip to content

Instantly share code, notes, and snippets.

@holyjak holyjak/incanter.clj
Last active Nov 4, 2018

Embed
What would you like to do?
Comparison of gnuplot, Incanter, oz/vega-lite for plotting usage data - Incanter
(ns clj-charting.incanter
(:require
[incanter.core :refer :all]
[incanter.charts :refer :all]
[clj-charting.usage-chart-preparation :refer [read-usage-data moving-window-means]])
(:import
[org.jfree.chart JFreeChart]
[org.jfree.chart.plot XYPlot]
(org.jfree.chart.axis ValueAxis NumberAxis NumberTickUnit TickUnitSource TickUnit)
(java.text NumberFormat DecimalFormat FieldPosition)))
(defn merge-y-axis
"Merge the Y axis of two line / time series charts. The former chart will have
the left Y axis, and the latter will have the right. Incanter does not support 2 Y
axes out of the box.
Source: https://matthewdowney.github.io/clojure-incanter-plot-multiple-y-axis.html"
[^JFreeChart chart ^JFreeChart chart-to-merge]
(let [^XYPlot plot (.getPlot chart-to-merge)]
(doto ^XYPlot (.getPlot chart)
(.setRangeAxis 1 (.getRangeAxis plot))
(.setDataset 1 (.getDataset plot))
(.mapDatasetToRangeAxis 1 1)
(.setRenderer 1 (.getRenderer plot)))
(-> (.getPlot chart)
(.getLegendItems)
(.addAll (.getLegendItems plot)))
chart))
(defn byte-scale
"For the given number [in bytes] return [scale, scale suffix] so that we can divide it
by the scale and display with the corresponding suffix.
Example: 2333 -> [1024 \"kB\"]"
[num]
(let [k 1024
m (int (Math/pow 1024 2))
g (int (Math/pow 1024 3))]
(condp <= num
g [g "GB"]
m [m "MB"]
k [k "kB"]
[1 ""])))
(defn format-bytes
"For the given number [in bytes] return [the number scaled down, the scale suffix such as \"kB\"].
Example: 2333 -> [2.278 \"kB\"]"
[num]
(let [[scale unit] (byte-scale num)]
[(/ num scale) unit]))
;; Instance of NumberFormat that displays a byte number scaled down and with the scale suffix
;; Example: 2333 -> \"2.3kB\"]"
(def byteFmt (let [dec-fmt (java.text.DecimalFormat. "#.#")]
(proxy [java.text.NumberFormat] []
(format [^double number, ^StringBuffer toAppendTo, ^FieldPosition pos]
(let [[n u] (format-bytes number)]
(.append
(.format dec-fmt n toAppendTo pos)
u))))))
(defn nearest-byte-tick
"For the given byte number, find out what tick to show on the axis;
e.g. we would rather see a tick such as '800MB' than '783.5MB' on it."
([^double size tick-fn]
(let [[scale] (byte-scale size)]
(NumberTickUnit.
(* scale
;; FIXME if size = 1000 upgrade to 1024
(.getSize
(tick-fn
(NumberTickUnit. (/ size scale)))))
byteFmt))))
(def byte-tick-source
"TickUnitSource suitable for byte values spanning multiple of kB - MB - GB"
;; TODO Instead of reusing IntegerTickUnits, reimplement it to support powers of 2
(let [int-tick-units (NumberAxis/createIntegerTickUnits)]
(reify
TickUnitSource
(^TickUnit getLargerTickUnit [_ ^TickUnit unit]
(nearest-byte-tick
(.getSize unit)
#(.getLargerTickUnit int-tick-units %)))
(^TickUnit getCeilingTickUnit [me ^TickUnit unit]
(.getCeilingTickUnit me (.getSize unit)))
(^TickUnit getCeilingTickUnit [_ ^double size]
(nearest-byte-tick
size
#(.getCeilingTickUnit int-tick-units %))))))
(defn set-bytes-tick-unit [^JFreeChart chart]
(let [^XYPlot plot (.getPlot chart)
^NumberAxis axis (.getRangeAxis plot)]
(.setStandardTickUnits axis byte-tick-source)
chart))
(defn plot-usage [file]
(let [data (read-usage-data file)
time (sel data :cols 0)
mem (sel data :cols 1)
cpu (sel data :cols 2)]
(->
(time-series-plot time cpu :title file :y-label "cpu [%]" :legend true)
(add-lines time (moving-window-means 60 cpu) :series-label "cpu (mean)")
(merge-y-axis
(set-bytes-tick-unit
(time-series-plot time mem :series-label "Memory")))
(view))))
(plot-usage "siege-c10-all-urls-async-node11.dat")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.