view yetilab/plot/chart.yeti @ 178:032c4986b6b0

Implement and test matrix concat
author Chris Cannam
date Thu, 02 May 2013 21:58:58 +0100
parents 4065178f776b
children 4bce024b3cc6
line wrap: on
line source
module yetilab.plot.chart;

import org.jzy3d.plot3d.text.drawable: DrawableTextBillboard, DrawableTextBitmap;
import org.jzy3d.maths: Range, Coord3d;
import org.jzy3d.plot3d.primitives: Shape, HistogramBar, FlatLine2d, Polygon, Quad, Point;
import org.jzy3d.plot3d.primitives.axes.layout.providers: StaticTickProvider, RegularTickProvider;
import org.jzy3d.plot3d.primitives.axes.layout.renderers: ITickRenderer, TickLabelMap, IntegerTickRenderer;
import org.jzy3d.chart: Chart, ChartLauncher;
import org.jzy3d.plot3d.builder: Builder;
import org.jzy3d.colors: Color;
import org.jzy3d.plot3d.rendering.canvas: Quality;
import org.jzy3d.plot3d.rendering.view.modes: ViewPositionMode;

import javax.imageio: ImageIO;

import java.io: File;

chartColours = array [
    { r = 82,  g = 126, b = 154 }, // dark steel blue
    { r = 161, g = 54,  b = 2   }, // red
    { r = 207, g = 228, b = 148 }, // grey-green
    { r = 21,  g = 183, b = 197 }, // light blue
    { r = 251, g = 116, b = 43  }, // light red
    { r = 200, g = 125, b = 234 }, // light purple
    { r = 126, g = 33,  b = 28  }, // dried blood!
    { r = 188, g = 13,  b = 207 }, // mid purple
];    

chartColour n =
    if n < 0
    then chartColour (-n)
    else
        rgb = chartColours[n % (length chartColours)];
        new Color(rgb.r / 255.0, rgb.g / 255.0, rgb.b / 255.0);
    fi;

newPercentTickRenderer () =
   (f v = " \(int (v * 100))%";
    class PercentageTickRenderer extends ITickRenderer
        String format(double value) f value,
        String format(float value) f value
    end;
    new PercentageTickRenderer());

newPaddedIntTickRenderer () =
   (f v = "    \(int (v + 0.5))";
    class PaddedIntTickRenderer extends ITickRenderer
        String format(double value) f value,
        String format(float value) f value
    end;
    new PaddedIntTickRenderer());

parseOptions options defaultKeys defaultXKeys =
   (parsed = {
        var keys = array (sort defaultKeys),
        var labels = [:],
        var animated = false,
        var normalised = false,
        var unit = "",
        var xkeys = array (sort defaultXKeys),
        var saveTo = "",
        var display = true,
    };
    for options
       \case of
        Keys kk: parsed.keys := array kk;
        XKeys xk: parsed.xkeys := array xk;
        Animated a: parsed.animated := a;
        Normalised n: parsed.normalised := n;
        Unit u: parsed.unit := u;
        Labels ll: parsed.labels := ll;
        SaveTo file: parsed.saveTo := file;
        Display d: parsed.display := d;
        esac;
    if empty? parsed.labels then
        parsed.labels := mapIntoHash id id parsed.keys
    fi;
    parsed);

newChart opts =
   (quality = Quality#Fastest;
    quality#setAnimated(opts.animated);
//    if opts.display then
        new Chart(quality);
//    else
//        new Chart(quality, "offscreen,640,640");
//    fi);
    );

showChart opts chart is 'a -> ~Chart -> () =
   (if opts.display then
        \() ChartLauncher#openChart(chart);
    else
        \() ChartLauncher#openStaticChart(chart);
    fi;
    if opts.saveTo != "" then
        \() chart#screenshot(opts.saveTo);
    fi);
    
plotBarChart options values =
   (opts = parseOptions options (keys values) [];
    chart = newChart opts;
    var n = length opts.keys;
    scene = chart#getScene();
    ticks = new double[n];
    tickLabels = new TickLabelMap();
    var i = 0;
    var x = n - i - 1;
    total = sum (map do k: if k in values then values[k] else 0 fi done opts.keys);
    for opts.keys do k:
        bar = new HistogramBar();
        v = if k in values then values[k] else 0 fi;
        v = if opts.normalised and total > 0 then v / total else v fi;
        bar#setData(new Coord3d(x, 0, 0), v, 0.45, chartColour i);
        bar#setWireframeDisplayed(false);
        scene#add(bar);
        ticks[i] := i;
        tickLabels#register(x, opts.labels[k]);
        i := i + 1;
        x := x - 1;
    done;
    chart#getView()#setViewPoint(new Coord3d(pi/2, 0, 0));
    axes = chart#getAxeLayout();
    axes#setXAxeLabelDisplayed(false);
    axes#setYAxeLabelDisplayed(false);
    axes#setZAxeLabelDisplayed(true);
    if opts.normalised then
        axes#setZAxeLabel("");
        axes#setZTickRenderer(newPercentTickRenderer ());
    else
        axes#setZAxeLabel(opts.unit);
    fi;
    axes#setXTickProvider(new StaticTickProvider(ticks));
    axes#setXTickRenderer(tickLabels);
    axes#setYTickLabelDisplayed(false);
    showChart opts chart);

plotLines options values =
   (opts = parseOptions options (keys values) (keys values[head (keys values)]);
    chart = newChart opts;
    scene = chart#getScene();
    n = length opts.xkeys;
    var z = 0;
    for opts.keys do k:
        v = values[k];
        x = new float[n];
        y = new float[n];
        var i = 0;
        for opts.xkeys do xk:
            x[i] := i;
            y[i] := if xk in v then v[xk] else 0 fi;
            i := i + 1;
        done;
        line = new FlatLine2d(x, y, z);
        line#setWireframeDisplayed(true);
        line#setWireframeColor(chartColour z);
        line#setWireframeWidth(2);
        line#setFaceDisplayed(false);
        scene#add(line);
        z := z + 1;
    done;
    chart#getView()#setViewPoint(new Coord3d(0, 0, 0));
    axes = chart#getAxeLayout();
    axes#setXAxeLabelDisplayed(false);
    axes#setYAxeLabelDisplayed(false);
    axes#setZAxeLabelDisplayed(true);
    axes#setZAxeLabel(opts.unit);
    axes#setYTickLabelDisplayed(false);
    showChart opts chart);

stack keys xkeys values normalised =
   (stacked = mapIntoHash id \(mapIntoHash id \{ y0 = 0, y1 = 0 } xkeys) keys;
    prev = mapIntoHash id \0 xkeys;
    valueOf k xk =
        if k in values and xk in values[k] 
        then values[k][xk] else 0 
        fi;
    for xkeys do xk:
        total = sum (map do k: valueOf k xk done keys);
        for keys do k:
            value =
                if normalised and total > 0
                then (valueOf k xk) / total
                else (valueOf k xk)
                fi;
            height = prev[xk] + value;
            stacked[k][xk] := { y0 = prev[xk], y1 = height };
            prev[xk] := height;
        done;
    done;
    stacked);

newRect x y0 y1 z colour is number -> number -> number -> number -> ~Color -> 'a =
   (poly = new Quad();
    poly#add(new Point(new Coord3d(x + 0.5, z, y0)));
    poly#add(new Point(new Coord3d(x + 0.5, z, y1)));
    poly#add(new Point(new Coord3d(x - 0.5, z, y1)));
    poly#add(new Point(new Coord3d(x - 0.5, z, y0)));
    poly#setWireframeDisplayed(true);
    poly#setWireframeColor(colour);
    poly#setFaceDisplayed(true);
    poly#setColor(colour);
    poly);

plotStacked options values =
   (opts = parseOptions options (keys values) (keys values[head (keys values)]);
    chart = newChart opts;
    scene = chart#getScene();
    stacked = stack opts.keys opts.xkeys values opts.normalised;
    var z = 0;
    var ty = 0;
    nxk = length opts.xkeys;
    xticks = new double[nxk];
    xtickLabels = new TickLabelMap();
    for [0..nxk - 1] do x:
        xticks[x] := x;
        k = opts.xkeys[x];
        xtickLabels#register(x, if k in opts.labels then opts.labels[k] else k fi);
    done;
    for opts.keys do k:
        ranges = stacked[k];
        c = chartColour z;
        for [0..nxk - 1] do x:
            xk = opts.xkeys[x];
            rect = newRect x ranges[xk].y0 ranges[xk].y1 z c;
            scene#add(rect);
        done;
        text = new DrawableTextBitmap(opts.labels[k], new Coord3d(-(nxk/5 + 0.5), z, ty), c);
        scene#add(text);
        z := z - 1;
        ty := ty + 0.1;
    done;
    chart#getView()#setViewPoint(new Coord3d(-pi/2, 0, 0));
    axes = chart#getAxeLayout();
    axes#setXAxeLabelDisplayed(false);
    if nxk < 10 then
        axes#setXTickProvider(new StaticTickProvider(xticks));
    fi;
    axes#setXTickRenderer(xtickLabels);
    axes#setYAxeLabelDisplayed(false);
    axes#setZAxeLabelDisplayed(true);
    if opts.normalised then 
        axes#setZAxeLabel("");
        axes#setZTickRenderer(newPercentTickRenderer ());
    else
        axes#setZAxeLabel(opts.unit);
        axes#setZTickRenderer(newPaddedIntTickRenderer ());
    fi;
    axes#setYTickLabelDisplayed(false);
    showChart opts chart);

{
    plotBarChart,
    plotLines,
    stack,
    plotStacked,
}