Giter Site home page Giter Site logo

ljubobratovicrelja / ggplotd Goto Github PK

View Code? Open in Web Editor NEW

This project forked from blackedder/ggplotd

0.0 2.0 0.0 624 KB

Plotting library for the D programming library. The design is inspired by ggplot2 for R.

License: Boost Software License 1.0

Shell 0.54% D 99.46%

ggplotd's Introduction

GGPlotD Build Status

GGPlotD is a plotting library for the D programming language. The design is heavily inspired by ggplot2 for R, which is based on a general Grammar of Graphics described by Leland Wilkinson. The library depends on cairo(D) for the actual drawing. The library is designed to make it easy to build complex plots from simple building blocks.

Install

Easiest is to use dub and add the library to your dub configuration file, which will automatically download the D dependencies. The library also depends on cairo so you will need to have that installed. On ubuntu/debian you can install cairo with:

sudo apt-get install libcairo2-dev 

GTK Support

The library also includes support for plotting to a GTK window. You can build this in using dub with:

dub -c ggplotd-gtk

If you want to add this to link to this version from your own D program the easiest way is with subConfigurations:

    "dependencies": {
        "ggplotd": ">=0.4.5"
	},
    "subConfigurations": {
		"ggplotd": "ggplotd-gtk"
	}

Documentation

This README contains a couple of examples and basic documentation on how to extend GGPlotD. API documentation is automatically generated and put online (including examples) under http://blackedder.github.io/ggplotd/index.html. For example for the available geom* functions see: http://blackedder.github.io/ggplotd/ggplotd/geom.html

Examples

A noisy figure

import ggplotd.ggplotd; 
import ggplotd.aes; 
import ggplotd.geom;

void main()
{
    import std.array : array;
    import std.math : sqrt;
    import std.algorithm : map;
    import std.range : repeat, iota;
    import std.random : uniform;
    // Generate some noisy data with reducing width
    auto f = (double x) { return x/(1+x); };
    auto width = (double x) { return sqrt(0.1/(1+x)); };
    auto xs = iota( 0, 10, 0.1 ).array;

    auto ysfit = xs.map!((x) => f(x)).array;
    auto ysnoise = xs.map!((x) => f(x) + uniform(-width(x),width(x))).array;

    auto aes = Aes!(typeof(xs), "x",
        typeof(ysnoise), "y", string[], "colour" )( xs, ysnoise,
        ("a").repeat(xs.length).array ); 
        
    auto gg = GGPlotD().put( geomPoint( aes)); 
    gg.put(geomLine( Aes!(typeof(xs), "x", typeof(ysfit), "y" )( xs, ysfit)));

    //  
    auto ys2fit = xs.map!((x) => 1-f(x)).array;
    auto ys2noise = xs.map!((x) => 1-f(x) + uniform(-width(x),width(x))).array;

    gg.put( geomLine( Aes!(typeof(xs), "x", typeof(ys2fit), "y" )( xs,
        ys2fit)));

    gg.put( geomPoint( Aes!(typeof(xs), "x", typeof(ys2noise), "y", string[],
        "colour" )( xs, ys2noise, ("b").repeat(xs.length).array) ));

    gg.save( "noise.png" );
}

Histogram

A histogram

import ggplotd.ggplotd; 
import ggplotd.aes; 
import ggplotd.geom;

void main()
{
    import std.array : array;
    import std.algorithm : map;
    import std.range : repeat, iota;
    import std.random : uniform;
    auto xs = iota(0,25,1).map!((x) => uniform(0.0,5)+uniform(0.0,5)).array;
    auto aes = Aes!(typeof(xs), "x")( xs );

    auto gg = GGPlotD().put( geomHist( aes ) );

    auto ys = (0.0).repeat( xs.length ).array;
    auto aesPs = aes.merge( Aes!(double[], "y", double[], "colour" )
        ( ys, ys ) );

    gg.put( geomPoint( aesPs ) );

    gg.save( "hist.png" );
}

A 2D version of the histogram is also implemented in geomHist2D. See the documentation for the code used to create this figure. Note that we use another colour gradient here than the default.

Histogram 2D

Combined histograms

Two combined data sources

import ggplotd.ggplotd; 
import ggplotd.aes; 
import ggplotd.geom;

void main()
{
    import std.array : array;
    import std.algorithm : map;
    import std.range : repeat, iota, chain;
    import std.random : uniform;
    auto xs = iota(0,50,1).map!((x) => uniform(0.0,5)+uniform(0.0,5)).array;
    auto cols = "a".repeat(25).chain("b".repeat(25));
    auto aes = Aes!(typeof(xs), "x", typeof(cols), "colour", 
        double[], "fill" )( 
            xs, cols, 0.45.repeat(xs.length).array);
    auto gg = GGPlotD().put( geomHist( aes ) );
    gg.save( "filled_hist.svg" );
}

Box plot

Box plot

import ggplotd.ggplotd; 
import ggplotd.aes; 
import ggplotd.geom;

void main() {
    import std.array : array;
    import std.algorithm : map;
    import std.range : repeat, iota, chain;
    import std.random : uniform;
    auto xs = iota(0,50,1).map!((x) => uniform(0.0,5)+uniform(0.0,5)).array;
    auto cols = "a".repeat(25).chain("b".repeat(25)).array;
    auto aes = Aes!(typeof(xs), "x", typeof(cols), "colour", 
        double[], "fill", typeof(cols), "label" )( 
            xs, cols, 0.45.repeat(xs.length).array, cols);
    auto gg = GGPlotD().put( geomBox( aes ) );
    gg.save( "boxplot.svg" );
}

Custom axes, margins, image size and title

Manipulating axes

import ggplotd.ggplotd;
import ggplotd.geom;
import ggplotd.aes;
import ggplotd.axes;

void main()
{
    import std.array : array;
    import std.math : sqrt;
    import std.algorithm : map;
    import std.range : iota;
    // Generate some noisy data with reducing width
    auto f = (double x) { return x/(1+x); };
    auto width = (double x) { return sqrt(0.1/(1+x)); };
    auto xs = iota( 0, 10, 0.1 ).array;

    auto ysfit = xs.map!((x) => f(x)).array;

    auto gg = GGPlotD().put( geomLine( Aes!(typeof(xs), "x", typeof(ysfit),
        "y")( xs, ysfit ) ) );

    // Setting range and label for xaxis
    gg.put( xaxisRange( 0, 8 ) )
        .put( xaxisLabel( "My xlabel" ) );

    // Setting range and label for yaxis
    gg.put( yaxisRange( 0, 2.0 ) ).put( yaxisLabel( "My ylabel" ) );

    // change offset
    gg.put( xaxisOffset( 0.25 ) ).put( yaxisOffset( 0.5 ) );

    // Change margins
    gg.put( Margins( 60, 60, 40, 30 ) );

    // Set a title 
    gg.put( title( "And now for something completely different" ) );

    // Saving as 500x300 pixel svg file
    gg.save( "axes.svg", 500, 300 );
}

Data

The examples above all use the Aes struct to hold all the data and pass it to geom* functions. It is also straightforward to use your own data range as long as each element provides access to the needed data at compile time, i.e. for geomPoint the element needs to have a x and y field. See below for a simple example:

import ggplotd.ggplotd; 
import ggplotd.aes; 
import ggplotd.geom;

struct Point { 
    double x; 
    double y; 
}

void main()
{
    /// http://blackedder.github.io/ggplotd/images/data.png
    import std.array : array;
    import std.math : sqrt;
    import std.algorithm : map;
    import std.range : iota;
    import std.random : uniform;
    // Generate some noisy data with reducing width
    auto f = (double x) { return x/(1+x); };
    auto width = (double x) { return sqrt(0.1/(1+x)); };
    auto xs = iota( 0, 10, 0.1 ).array;

    auto points = xs.map!((x) => Point(x,
        f(x) + uniform(-width(x),width(x))));

    auto gg = GGPlotD().put( geomPoint( points ) );

    gg.save( "data.png" );
}

Extending GGplotD

Due to GGplotD’s design it is relatively straightforward to extend GGplotD to support new types of plots. This is especially true if your function depends on the already implemented base types geomType, geomLine, geomEllipse, geomRectangle and geomPolygon. The main reason for not having added more functions yet is lack of time. If you decide to implement your own function then please open a pull request or at least copy your code into an issue. That way we can all benefit from your work :) Even if you think the code is not up to scratch it will be easier for the maintainer(s) to take your code and adapt it than to start from scrap.

stat*

The stat* functions are meant to calculate different statistics from data. The results should be an Aes that can be passed to a geom* function and plotted. There are a variety of existing functions (statHist, statDensity, statFunction etc.). Of course if you have written your own then you are encouraged to open a issue/pull request on github to submit them for inclusion, so that others can benefit from your good work. See below for an example of a plot created with the statFunction, which makes it straightforward to draw different functions.

Function

The goal of each stat* funtion should be to return an Aes that can be drawn with a variety of different geom* functions. Still in many cases the results can only really be drawn in one way. In that case it might make sense to design your function in a way that is drawable by geomType. GeomType makes it easy to define the type of plot you want, i.e. a line, point, rectangle etc.

geom*

A geom* function reads the data, optionally passes the data to a stat* function for transformation and returns a range of Geom structs, which can be drawn by GGPlotD(). In GGPlotD the low level geom* function such as geomType, geomPoint, geomLine, geomEllipse, geomRectangle and geomPolygon draw directly to a cairo.Context. Luckily most higher level geom* functions can just rely on calling those functions. For reference see below for the geomHist drawing implementation. Again if you decide to define your own function then please let us know and send us the code. That way we can add the function to the library and everyone can benefit.

/// Draw histograms based on the x coordinates of the data
auto geomHist(AES)(AES aes, size_t noBins = 0)
{
    import ggplotd.stat : statHist;
    return geomRectangle( statHist( aes, noBins ) );
}

Note that the above highlights the drawing part of the function. Converting the data into bins is done in a separate bin function, which can be found in the code.

Heightmap/surface plots

Currently a couple of heightmap/surface geom* functions are implemented (geomHist2D and geomDensity2D). Both rely on the geomPolygon function to do the actual drawing. The geomPolygon function allows one to draw gradients dependent on height/colour. This function plots any straight/flat polygon, with the colour representing the height of the surface. Note that the function does not check whether the provided surface is flat. Because triangles are by definition straight it might be good to limit your usage to triangles, unless you are completely sure your polygon has no curves.

Polygon

import ggplotd.ggplotd;
import ggplotd.geom;
import ggplotd.aes;

void main()
{
    auto gg = GGPlotD().put( geomPolygon( 
        Aes!(
            double[], "x",
            double[], "y",
            double[], "colour" )(
            [1,0,0], [ 1, 1, 0 ], [1,0.1,0] ) ) );
    gg.save( "polygon.png" );
}

Usage as a library

If you want to use ggplotd to draw the plots, but keep the plot in memory, you can create an image surface and use drawToSurface to draw to it, without saving it to file.

auto width = 470;
auto height = 350;

auto gg = GGPlotD();

// Do what you want, i.e. add lines, add points etc. 
// ...

// Create and draw to the surface. The cairo.Format you want probably 
// depends on what you need to do with it afterwards.
auto surface = new cairo.ImageSurface(cairo.Format.CAIRO_FORMAT_ARGB32,
    width, height); 
surface = gg.drawToSurface( surface, width, height );

// Use the resulting surface in your program

GTK window

If you build the library with GTK support you can show the plot in a window as follows:

void main()
{
    import core.thread;
    import std.array : array;
    import std.algorithm : map;
    import std.range : iota;
    import std.random : uniform;

    import ggplotd.ggplotd;
    import ggplotd.geom;
    import ggplotd.aes;
    import ggplotd.gtk;

    const width = 470;
    const height = 470;

    auto xs = iota(0,100,1).map!((x) => uniform(0.0,5)+uniform(0.0,5)).array;
    auto ys = iota(0,100,1).map!((y) => uniform(0.0,5)+uniform(0.0,5)).array;
    auto aes = Aes!(typeof(xs), "x", typeof(ys), "y")( xs, ys);

    // Start gtk window.
    auto gtkwin = new GTKWindow();

    // gtkwin.run runs the GTK mainloop, so normally blocks, but we can
    // run it in its own thread to get around this
    
    auto tid = new Thread(() { gtkwin.run("plotcli", width, height); }).start(); 
    auto gg = GGPlotD().put( geomHist3D( aes ) ); 
    gtkwin.draw( gg, width, height ); 
    Thread.sleep( dur!("seconds")( 2 ) ); // sleep for 2 seconds

    gg = GGPlotD().put( geomPoint( aes ) );
    gtkwin.clearWindow();
    gtkwin.draw( gg, width, height );

    // Wait for gtk thread to finish (Window closed)
    tid.join();
}

Development

Actual development happens on github, while planning for new features is tracked on trello. Feel free to join the discussion there.

References

Wilkinson, Leland. The Grammar of Graphics. Springer Science & Business Media, 2013.

ggplotd's People

Contributors

blackedder avatar drug007 avatar

Watchers

 avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.