Chris@144: module yetilab.plot.chart; Chris@108: Chris@128: import org.jzy3d.plot3d.text.drawable: DrawableTextBillboard, DrawableTextBitmap; Chris@119: import org.jzy3d.maths: Range, Coord3d; Chris@126: import org.jzy3d.plot3d.primitives: Shape, HistogramBar, FlatLine2d, Polygon, Quad, Point; Chris@138: import org.jzy3d.plot3d.primitives.axes.layout.providers: StaticTickProvider, RegularTickProvider; Chris@140: import org.jzy3d.plot3d.primitives.axes.layout.renderers: ITickRenderer, TickLabelMap, IntegerTickRenderer; Chris@108: import org.jzy3d.chart: Chart, ChartLauncher; Chris@108: import org.jzy3d.plot3d.builder: Builder; Chris@144: import org.jzy3d.colors: Color; Chris@108: import org.jzy3d.plot3d.rendering.canvas: Quality; Chris@119: import org.jzy3d.plot3d.rendering.view.modes: ViewPositionMode; Chris@108: Chris@137: import javax.imageio: ImageIO; Chris@137: Chris@137: import java.io: File; Chris@137: Chris@132: chartColours = array [ Chris@132: { r = 82, g = 126, b = 154 }, // dark steel blue Chris@132: { r = 161, g = 54, b = 2 }, // red Chris@132: { r = 207, g = 228, b = 148 }, // grey-green Chris@132: { r = 21, g = 183, b = 197 }, // light blue Chris@132: { r = 251, g = 116, b = 43 }, // light red Chris@132: { r = 200, g = 125, b = 234 }, // light purple Chris@132: { r = 126, g = 33, b = 28 }, // dried blood! Chris@132: { r = 188, g = 13, b = 207 }, // mid purple Chris@131: ]; Chris@131: Chris@132: chartColour n = Chris@132: if n < 0 Chris@132: then chartColour (-n) Chris@132: else Chris@132: rgb = chartColours[n % (length chartColours)]; Chris@132: new Color(rgb.r / 255.0, rgb.g / 255.0, rgb.b / 255.0); Chris@132: fi; Chris@132: Chris@133: newPercentTickRenderer () = Chris@133: (f v = " \(int (v * 100))%"; Chris@133: class PercentageTickRenderer extends ITickRenderer Chris@133: String format(double value) f value, Chris@133: String format(float value) f value Chris@133: end; Chris@133: new PercentageTickRenderer()); Chris@133: Chris@140: newPaddedIntTickRenderer () = Chris@140: (f v = " \(int (v + 0.5))"; Chris@140: class PaddedIntTickRenderer extends ITickRenderer Chris@140: String format(double value) f value, Chris@140: String format(float value) f value Chris@140: end; Chris@140: new PaddedIntTickRenderer()); Chris@140: Chris@132: parseOptions options defaultKeys defaultXKeys = Chris@132: (parsed = { Chris@133: var keys = array (sort defaultKeys), Chris@132: var labels = [:], Chris@132: var animated = false, Chris@132: var normalised = false, Chris@132: var unit = "", Chris@137: var xkeys = array (sort defaultXKeys), Chris@137: var saveTo = "", Chris@137: var display = true, Chris@132: }; Chris@132: for options Chris@132: \case of Chris@133: Keys kk: parsed.keys := array kk; Chris@133: XKeys xk: parsed.xkeys := array xk; Chris@132: Animated a: parsed.animated := a; Chris@132: Normalised n: parsed.normalised := n; Chris@132: Unit u: parsed.unit := u; Chris@132: Labels ll: parsed.labels := ll; Chris@137: SaveTo file: parsed.saveTo := file; Chris@137: Display d: parsed.display := d; Chris@132: esac; Chris@132: if empty? parsed.labels then Chris@132: parsed.labels := mapIntoHash id id parsed.keys Chris@132: fi; Chris@132: parsed); Chris@132: Chris@137: newChart opts = Chris@137: (quality = Quality#Fastest; Chris@137: quality#setAnimated(opts.animated); Chris@137: // if opts.display then Chris@137: new Chart(quality); Chris@137: // else Chris@137: // new Chart(quality, "offscreen,640,640"); Chris@137: // fi); Chris@137: ); Chris@137: Chris@137: showChart opts chart is 'a -> ~Chart -> () = Chris@141: (if opts.display then Chris@137: \() ChartLauncher#openChart(chart); Chris@141: else Chris@141: \() ChartLauncher#openStaticChart(chart); Chris@141: fi; Chris@137: if opts.saveTo != "" then Chris@137: \() chart#screenshot(opts.saveTo); Chris@137: fi); Chris@137: Chris@132: plotBarChart options values = Chris@132: (opts = parseOptions options (keys values) []; Chris@137: chart = newChart opts; Chris@132: var n = length opts.keys; Chris@119: scene = chart#getScene(); Chris@139: ticks = new double[n]; Chris@119: tickLabels = new TickLabelMap(); Chris@132: var i = 0; Chris@132: var x = n - i - 1; Chris@134: total = sum (map do k: if k in values then values[k] else 0 fi done opts.keys); Chris@132: for opts.keys do k: Chris@136: bar = new HistogramBar(); Chris@134: v = if k in values then values[k] else 0 fi; Chris@133: v = if opts.normalised and total > 0 then v / total else v fi; Chris@136: bar#setData(new Coord3d(x, 0, 0), v, 0.45, chartColour i); Chris@136: bar#setWireframeDisplayed(false); Chris@136: scene#add(bar); Chris@132: ticks[i] := i; Chris@132: tickLabels#register(x, opts.labels[k]); Chris@132: i := i + 1; Chris@132: x := x - 1; Chris@119: done; Chris@136: chart#getView()#setViewPoint(new Coord3d(pi/2, 0, 0)); Chris@136: axes = chart#getAxeLayout(); Chris@136: axes#setXAxeLabelDisplayed(false); Chris@136: axes#setYAxeLabelDisplayed(false); Chris@136: axes#setZAxeLabelDisplayed(true); Chris@136: if opts.normalised then Chris@136: axes#setZAxeLabel(""); Chris@136: axes#setZTickRenderer(newPercentTickRenderer ()); Chris@135: else Chris@136: axes#setZAxeLabel(opts.unit); Chris@135: fi; Chris@119: axes#setXTickProvider(new StaticTickProvider(ticks)); Chris@119: axes#setXTickRenderer(tickLabels); Chris@136: axes#setYTickLabelDisplayed(false); Chris@137: showChart opts chart); Chris@119: Chris@132: plotLines options values = Chris@132: (opts = parseOptions options (keys values) (keys values[head (keys values)]); Chris@137: chart = newChart opts; Chris@124: scene = chart#getScene(); Chris@132: n = length opts.xkeys; Chris@124: var z = 0; Chris@132: for opts.keys do k: Chris@124: v = values[k]; Chris@124: x = new float[n]; Chris@124: y = new float[n]; Chris@124: var i = 0; Chris@132: for opts.xkeys do xk: Chris@124: x[i] := i; Chris@124: y[i] := if xk in v then v[xk] else 0 fi; Chris@124: i := i + 1; Chris@124: done; Chris@124: line = new FlatLine2d(x, y, z); Chris@124: line#setWireframeDisplayed(true); Chris@132: line#setWireframeColor(chartColour z); Chris@124: line#setWireframeWidth(2); Chris@124: line#setFaceDisplayed(false); Chris@124: scene#add(line); Chris@124: z := z + 1; Chris@124: done; Chris@124: chart#getView()#setViewPoint(new Coord3d(0, 0, 0)); Chris@124: axes = chart#getAxeLayout(); Chris@124: axes#setXAxeLabelDisplayed(false); Chris@124: axes#setYAxeLabelDisplayed(false); Chris@124: axes#setZAxeLabelDisplayed(true); Chris@132: axes#setZAxeLabel(opts.unit); Chris@124: axes#setYTickLabelDisplayed(false); Chris@137: showChart opts chart); Chris@124: Chris@129: stack keys xkeys values normalised = Chris@127: (stacked = mapIntoHash id \(mapIntoHash id \{ y0 = 0, y1 = 0 } xkeys) keys; Chris@125: prev = mapIntoHash id \0 xkeys; Chris@140: valueOf k xk = Chris@140: if k in values and xk in values[k] Chris@140: then values[k][xk] else 0 Chris@140: fi; Chris@125: for xkeys do xk: Chris@129: total = sum (map do k: valueOf k xk done keys); Chris@125: for keys do k: Chris@129: value = Chris@129: if normalised and total > 0 Chris@129: then (valueOf k xk) / total Chris@129: else (valueOf k xk) Chris@129: fi; Chris@125: height = prev[xk] + value; Chris@127: stacked[k][xk] := { y0 = prev[xk], y1 = height }; Chris@125: prev[xk] := height; Chris@125: done; Chris@125: done; Chris@125: stacked); Chris@125: Chris@136: newRect x y0 y1 z colour is number -> number -> number -> number -> ~Color -> 'a = Chris@136: (poly = new Quad(); Chris@136: poly#add(new Point(new Coord3d(x + 0.5, z, y0))); Chris@136: poly#add(new Point(new Coord3d(x + 0.5, z, y1))); Chris@136: poly#add(new Point(new Coord3d(x - 0.5, z, y1))); Chris@136: poly#add(new Point(new Coord3d(x - 0.5, z, y0))); Chris@136: poly#setWireframeDisplayed(true); Chris@136: poly#setWireframeColor(colour); Chris@136: poly#setFaceDisplayed(true); Chris@136: poly#setColor(colour); Chris@136: poly); Chris@136: Chris@132: plotStacked options values = Chris@132: (opts = parseOptions options (keys values) (keys values[head (keys values)]); Chris@137: chart = newChart opts; Chris@125: scene = chart#getScene(); Chris@132: stacked = stack opts.keys opts.xkeys values opts.normalised; Chris@125: var z = 0; Chris@130: var ty = 0; Chris@138: nxk = length opts.xkeys; Chris@139: xticks = new double[nxk]; Chris@133: xtickLabels = new TickLabelMap(); Chris@138: for [0..nxk - 1] do x: Chris@139: xticks[x] := x; Chris@139: k = opts.xkeys[x]; Chris@139: xtickLabels#register(x, if k in opts.labels then opts.labels[k] else k fi); Chris@133: done; Chris@132: for opts.keys do k: Chris@127: ranges = stacked[k]; Chris@132: c = chartColour z; Chris@138: for [0..nxk - 1] do x: Chris@133: xk = opts.xkeys[x]; Chris@127: rect = newRect x ranges[xk].y0 ranges[xk].y1 z c; Chris@127: scene#add(rect); Chris@125: done; Chris@138: text = new DrawableTextBitmap(opts.labels[k], new Coord3d(-(nxk/5 + 0.5), z, ty), c); Chris@128: scene#add(text); Chris@126: z := z - 1; Chris@130: ty := ty + 0.1; Chris@125: done; Chris@129: chart#getView()#setViewPoint(new Coord3d(-pi/2, 0, 0)); Chris@125: axes = chart#getAxeLayout(); Chris@125: axes#setXAxeLabelDisplayed(false); Chris@139: if nxk < 10 then Chris@139: axes#setXTickProvider(new StaticTickProvider(xticks)); Chris@139: fi; Chris@133: axes#setXTickRenderer(xtickLabels); Chris@125: axes#setYAxeLabelDisplayed(false); Chris@125: axes#setZAxeLabelDisplayed(true); Chris@133: if opts.normalised then Chris@133: axes#setZAxeLabel(""); Chris@133: axes#setZTickRenderer(newPercentTickRenderer ()); Chris@133: else Chris@133: axes#setZAxeLabel(opts.unit); Chris@140: axes#setZTickRenderer(newPaddedIntTickRenderer ()); Chris@133: fi; Chris@125: axes#setYTickLabelDisplayed(false); Chris@137: showChart opts chart); Chris@124: Chris@115: { Chris@124: plotBarChart, Chris@124: plotLines, Chris@125: stack, Chris@125: plotStacked, Chris@115: } Chris@110: