Grapht
Grapht is a server-side graphing library built on PhantomJS and utilizing D3.js. Grapht provides a CLI for simple Bash scripting. It also profides a light-weight Ruby API to make service-level integration simple.
Why was Grapht built on PhantomJS?
- PhantomJS allows us to leverage D3.js, a best-of-breed data visualization library authored by the formidable data visualization expert, Mike Bostock. D3.js is a battlefield tested library.
- Using PhantomJS allows us to reuse existing data visualization logic, originally authored for our client-side application. This means we get consistent visualizations across the various layers of our stack, and we minimize developer effort.
How well does Grapht perform?
While PhantomJS is able to run Javascript extremely fast, it has a slow startup time. When measured on a 2.4GHz Intel Core i7 laptop, with 16GB of DDR3 RAM, the benchmarks† are as follows (averaged over 10 runs):
Bytes-In | Average, Real Time ( Seconds ) |
---|---|
1,323 | 1.559 |
13,527 | 1.675 |
135,252 | 1.715 |
time (sec.) / bytes
Note that even when incrementing the amount of data-in by an order of magnitude, time increases minimally (roughly √n). From this, we can infer a start-up time of ~1.5 seconds, for PhantomJS, on the aforementioned hardware.
† All measurements were collected using the following command: time -p bin/grapht bar-horizontal < data/bar_data.json > /dev/null
CLI
Overview
Grapht provides a CLI, accessed using the bin/grapht
command. The basic invocation
requires one argument specifying the desired graph type, and a JSON string provided
via STDIN
. For example, if we want a horizontal bar graph:
bin/grapht bar-horizontal < ~/my-data.json
The result will be a string of svg markup:
<svg width="200" height="200">
<rect class="bar" x="0" y="13" width="0" height="50"></rect>
<rect class="bar" x="0" y="75" width="200" height="50"></rect>
<rect class="bar" x="0" y="137" width="150" height="50"></rect>
<g class="x axis" transform="translate(0,200)">
<g class="tick" style="opacity: 1; " transform="translate(0,0)">
<line y2="-200" x2="0"></line>
<text y="3" x="0" dy=".71em" style="text-anchor: middle; ">20</text>
</g>
<g class="tick" style="opacity: 1; " transform="translate(20,0)">
<line y2="-200" x2="0"></line>
<text y="3" x="0" dy=".71em" style="text-anchor: middle; ">22</text>
</g>
<g class="tick" style="opacity: 1; " transform="translate(40,0)">
<line y2="-200" x2="0"></line>
<text y="3" x="0" dy=".71em" style="text-anchor: middle; ">24</text>
</g>
<g class="tick" style="opacity: 1; " transform="translate(60.00000000000001,0)">
<line y2="-200" x2="0"></line>
<text y="3" x="0" dy=".71em" style="text-anchor: middle; ">26</text>
</g>
<g class="tick" style="opacity: 1; " transform="translate(80,0)">
<line y2="-200" x2="0"></line>
<text y="3" x="0" dy=".71em" style="text-anchor: middle; ">28</text>
</g>
<g class="tick" style="opacity: 1; " transform="translate(100,0)">
<line y2="-200" x2="0"></line>
<text y="3" x="0" dy=".71em" style="text-anchor: middle; ">30</text>
</g>
<g class="tick" style="opacity: 1; " transform="translate(120.00000000000001,0)">
<line y2="-200" x2="0"></line>
<text y="3" x="0" dy=".71em" style="text-anchor: middle; ">32</text>
</g>
<g class="tick" style="opacity: 1; " transform="translate(140,0)">
<line y2="-200" x2="0"></line>
<text y="3" x="0" dy=".71em" style="text-anchor: middle; ">34</text>
</g>
<g class="tick" style="opacity: 1; " transform="translate(160,0)">
<line y2="-200" x2="0"></line>
<text y="3" x="0" dy=".71em" style="text-anchor: middle; ">36</text>
</g>
<g class="tick" style="opacity: 1; " transform="translate(180,0)">
<line y2="-200" x2="0"></line>
<text y="3" x="0" dy=".71em" style="text-anchor: middle; ">38</text>
</g>
<g class="tick" style="opacity: 1; " transform="translate(200,0)">
<line y2="-200" x2="0"></line>
<text y="3" x="0" dy=".71em" style="text-anchor: middle; ">40</text>
</g>
<path class="domain" d="M0,-200V0H200V-200"></path>
</g>
<g class="y axis">
<g class="tick" style="opacity: 1; " transform="translate(0,38)">
<line x2="6" y2="0"></line>
<text x="9" y="0" dy=".32em" style="text-anchor: start; ">foo</text>
</g>
<g class="tick" style="opacity: 1; " transform="translate(0,100)">
<line x2="6" y2="0"></line>
<text x="9" y="0" dy=".32em" style="text-anchor: start; ">bar</text>
</g>
<g class="tick" style="opacity: 1; " transform="translate(0,162)">
<line x2="6" y2="0"></line>
<text x="9" y="0" dy=".32em" style="text-anchor: start; ">baz</text>
</g>
<path class="domain" d="M6,0H0V200H6"></path>
</g>
</svg>
Usage
bin/grapht GRAPH_TYPE [options] < JSON_INPUT
Options
Currently supported flags:
Flag | Description | Allowable Values |
---|---|---|
-f |
Specifies an output format for the generated graph. | png|jpg|gif|pdf |
Ruby API
To generate the same horizontal bar graph generated in the CLI section--using the Ruby API--we can do the following:
json = "[{ \"name\": \"foo\", \"value\": 20 },{ \"name\": \"bar\", \"value\": 40 },{ \"name\": \"baz\", \"value\": 35 }]"
type = Grapht::Type::BAR_HORIZONTAL
graph = Grapht::Shell.exec type, json
Supported Graphs
Grapht is extensible, allowing users to create new graph definitions using Javascript and D3.js. However, Grapht provides a handful of graph definitions out of the box:
- Horizontal Bar Graph
- Vertical Bar Graph
- Line Graph
- Pie Graph
User Defined Graph Definitions
Users may create their own graph definitions with Grapht. To do this, we first
have to register a location where the new graph definitions will reside. This
is done simply by setting the EXT_GRAPHT_DEFINITIONS_HOME
environment variable.
For example, if we have our custom definitions stored in
~/Development/my_project/my_graph_defs
, we set our environment variable to this
path:
export EXT_GRAPHT_DEFINITIONS_HOME=~/Development/my_project/my_graph_defs
bin/grapht my-scatterplot < ~/my-data.json
In the example above, we supply the name of our new graph definition
(my-scatterplot
) to the Grapht CLI. Grapht will first look in
EXT_GRAPHT_DEFINITIONS_HOME
for the file my-scatterplot.js
. If it's not
found, it will check its own internal set of graph definitions. If it is found,
Grapht will load the new definition.
Creating Your First Graph Definition
All graph definitions must comply with a short set of rules:
- They must be wrapped within a function that takes a single data argument.
- They must generate one, and only one root DOM node.
Here's an example of a valid graph definition:
function(data) { // <- our wrapper fuction
var width = 200,
height = 200;
// our single root node added to document.body
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
// a bunch of D3 operations on svg...
}
Error Handling
Grapht will raise an error in the following scenarios:
- When any of its dependencies cannot be found. Currently, Grapht
depends upon the D3.js and JSON2 javascript libraries. These dependencies
can be found in Grapht's
vendor
directory. - When an unknown graph type is supplied to Grapht
- When malformed JSON is supplied to Grapht
- When PhantomJS raises an internal error
When using Grapht's CLI, all errors are printed to STDERR
and Grapht exits
with an exit-code of 1
.
When accessing Grapht's features through the Ruby API, all errors are presented
as instances of Grapht::Shell::Error
. The error messages, in this case, are
consistent with the errors messages raised from the CLI.
Rails Integration
While Rails integration is not scripted by the Grapht library, it is quite simple to integrate. The following steps are all that is required for integration:
- Add the following to your
Gemfile
:ruby gem 'grapht'
- In the Rails
app
directory, create a directory namedgraph-definitions
- Create the file
config/initializers/grapht.rb
. In the file add the following:ruby ENV['EXT_GRAPHT_DEFINITIONS_HOME'] = Rails.root.join('app/graph-definitions')
- Place all user-defined graph definitions in the
app/graph-definitions
Contributing
- Fork it (http://github.com/trade-informatics/grapht/fork)
- Create your feature branch
git checkout -b my-new-feature
- Commit your changes
git commit -am 'Add some feature'
- Push to the branch
git push origin my-new-feature
- Create a new pull request
License
Copyright (c) 2014 Trade Informatics, Inc