grapht 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,89 @@
1
+ function(data) {
2
+ var margin = { top: 50, right: 20, bottom: 30, left: 40 },
3
+ width = 400,
4
+ height = 400,
5
+ svgWidth = width + margin.right + margin.left,
6
+ svgHeight = height + margin.top + margin.bottom;
7
+
8
+ var svg = d3.select("body").append("svg")
9
+ .style("fill", "none")
10
+ .attr("width", svgWidth)
11
+ .attr("height", svgHeight)
12
+ .attr('viewBox', "0 -10 " +svgWidth+ " " +svgHeight+ "")
13
+ .append('g')
14
+ .attr('transform', 'translate(' +margin.left+ ',' +margin.top+ ')')
15
+ .style("fill", "none");
16
+
17
+ // Axis Scales
18
+ var x = d3.scale
19
+ .ordinal()
20
+ .domain(data.map(function(d) { return d.name; }))
21
+ .rangeRoundBands([0, width], 0.2);
22
+
23
+ var y = d3.scale
24
+ .linear()
25
+ .domain([0, d3.max(data, function(d) { return d.value; })])
26
+ .range([height, 0])
27
+ .nice();
28
+
29
+ // Axes
30
+ var xAxis = d3.svg
31
+ .axis()
32
+ .scale(x)
33
+ .orient('bottom')
34
+ .ticks(data.length);
35
+
36
+ var yAxis = d3.svg
37
+ .axis()
38
+ .scale(y)
39
+ .orient('left')
40
+ .tickSize(width);
41
+
42
+ svg.selectAll('.bar')
43
+ .data(data)
44
+ .enter().append('rect')
45
+ .attr('class', 'bar')
46
+ .style('fill', '#428bca')
47
+ .attr('opacity', '0.3')
48
+ .attr('y', function(d) { return y(d.value); })
49
+ .attr('x', function(d) { return x(d.name); })
50
+ .attr('height', function(d) { return height - y(d.value); })
51
+ .attr('width', x.rangeBand());
52
+
53
+ svg.selectAll('.text')
54
+ .attr('font-family', 'Arial')
55
+ .selectAll('label')
56
+ .attr('fill-opacity', '0.3')
57
+
58
+ var xAxisElement = svg.append('g')
59
+ .attr('class', 'x axis')
60
+ .attr('transform', "translate(0," +height+ ")")
61
+ .call(xAxis);
62
+
63
+ var yAxisElement = svg.append('g')
64
+ .attr('class', 'y axis')
65
+ .attr("transform", "translate(" +width+ ",0)")
66
+ .call(yAxis);
67
+
68
+ xAxisElement.selectAll('line')
69
+ .style('fill', 'none')
70
+ .attr('stroke', 'black')
71
+ .attr('stroke-width', '1px')
72
+ .attr('stroke-opacity', 0.1)
73
+ .attr('shape-rendering', 'geometricPrecision');
74
+
75
+ xAxisElement.selectAll('text')
76
+ .style('fill', '#333333')
77
+ .attr('font-family', 'Arial');
78
+
79
+ yAxisElement.selectAll('line')
80
+ .style('fill', 'none')
81
+ .attr('stroke', 'black')
82
+ .attr('stroke-width', '1px')
83
+ .attr('stroke-opacity', 0.1)
84
+ .attr('shape-rendering', 'geometricPrecision');
85
+
86
+ yAxisElement.selectAll('text')
87
+ .style('fill', '#333333')
88
+ .attr('font-family', 'Arial');
89
+ }
@@ -0,0 +1,47 @@
1
+ function(data) {
2
+ var margin = { top: 20, right: 20, bottom: 10, left: 10 },
3
+ width = 400,
4
+ height = 400,
5
+ svgWidth = width + margin.right + margin.left,
6
+ svgHeight = height + margin.top + margin.bottom,
7
+ radius = Math.min(width, height) / 2;
8
+
9
+ var svg = d3.select("body").append("svg")
10
+ .style("fill", "none")
11
+ .attr("width", svgWidth)
12
+ .attr("height", svgHeight)
13
+ .attr('viewBox', "0 -10 " +svgWidth+ " " +svgHeight+ "")
14
+ .append('g')
15
+ .attr('transform', 'translate(' +(width/2)+ ',' +(height/2)+ ')')
16
+ .style("fill", "none");
17
+
18
+ var color = d3.scale
19
+ .ordinal()
20
+ .range(["#428bca", "#5cb85c", "#5bc0de", "#f0ad4e", "#d9534f"]);
21
+
22
+ var arc = d3.svg.arc()
23
+ .outerRadius(radius - 10)
24
+ .innerRadius(radius - 70);
25
+
26
+ var pie = d3.layout.pie()
27
+ .sort(null)
28
+ .value(function(d) { return d.value; });
29
+
30
+ var arcElement = svg.selectAll(".arc")
31
+ .data(pie(data))
32
+ .enter().append("g")
33
+ .attr("class", "arc");
34
+
35
+ arcElement.append("path")
36
+ .attr("d", arc)
37
+ .style("fill", function(d) { return color(d.data.name); });
38
+
39
+ arcElement.append("text")
40
+ .attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")"; })
41
+ .attr("dy", ".35em")
42
+ .style('fill', '#FFFFFF')
43
+ .attr('font-family', 'Arial')
44
+ .attr('font-weight', 'bold')
45
+ .style("text-anchor", "middle")
46
+ .text(function(d) { return d.data.name; });
47
+ }
@@ -0,0 +1,111 @@
1
+ function(data) {
2
+ var fns = {
3
+ extentOf: function(data, axis) {
4
+ var values = d3.values(data),
5
+ flatVals = Array.prototype.concat.apply([], values),
6
+ axisVals = flatVals.map(function(o) { return o[axis]; });
7
+
8
+ return d3.extent(axisVals);
9
+ }
10
+ };
11
+
12
+ var margin = { top: 40, right: 80, bottom: 40, left: 40 },
13
+ width = 400,
14
+ height = 400,
15
+ svgWidth = width + margin.right + margin.left,
16
+ svgHeight = height + margin.top + margin.bottom;
17
+
18
+ var svg = d3.select("body").append("svg")
19
+ .style("fill", "none")
20
+ .attr("width", svgWidth)
21
+ .attr("height", svgHeight)
22
+ .attr('viewBox', "0 -10 " +svgWidth+ " " +svgHeight+ "")
23
+ .append('g')
24
+ .attr('transform', 'translate(' +margin.left+ ',' +margin.top+ ')')
25
+ .style("fill", "none");
26
+
27
+
28
+ var x = d3.scale
29
+ .linear()
30
+ .range([0, width])
31
+ .domain(fns.extentOf(data, 'x')),
32
+ y = d3.scale
33
+ .linear()
34
+ .range([height, 0])
35
+ .domain(fns.extentOf(data, 'y')),
36
+ color = d3.scale
37
+ .ordinal()
38
+ .range(["#428bca", "#5cb85c", "#5bc0de", "#f0ad4e", "#d9534f"]);
39
+
40
+ var xAxis = d3.svg
41
+ .axis()
42
+ .scale(x)
43
+ .orient('bottom')
44
+ .tickSize(-height),
45
+ yAxis = d3.svg
46
+ .axis()
47
+ .scale(y)
48
+ .orient('left')
49
+ .tickSize(width),
50
+ line = d3.svg
51
+ .line()
52
+ .interpolate('basis')
53
+ .x(function(d) { return x(d.x); })
54
+ .y(function(d) { return y(d.y); });
55
+
56
+ var xAxisElement = svg.append('g')
57
+ .attr('class', 'x axis')
58
+ .attr('transform', "translate(0," +height+ ")")
59
+ .call(xAxis);
60
+
61
+ var yAxisElement = svg.append('g')
62
+ .attr('class', 'y axis')
63
+ .attr("transform", "translate(" +width+ ",0)")
64
+ .call(yAxis);
65
+
66
+ var lineElements = svg.selectAll('.line')
67
+ .data(d3.entries(data))
68
+ .enter().append('g')
69
+ .attr('class', 'line');
70
+
71
+ lineElements.append('path')
72
+ .attr('class', 'line')
73
+ .attr('stroke-width', '2px')
74
+ .attr('opacity', '0.3')
75
+ .attr('d', function(d) { return line(d.value) })
76
+ .attr('stroke', function(d) { return color(d.key); });
77
+
78
+ lineElements.append('text')
79
+ .datum(function(d) { return { name: d.key, value: d.value[d.value.length - 1] }; })
80
+ .attr('transform', function(d) { return "translate(" +x(d.value.x)+ "," +y(d.value.y)+ ")"; })
81
+ .attr('x', 3)
82
+ .attr('dy', '.35em')
83
+ .style('fill', '#333333')
84
+ .attr('font-family', 'Arial')
85
+ .text(function(d) { return d.name; });
86
+
87
+ // axis styling
88
+ xAxisElement.selectAll('line')
89
+ .style('fill', 'none')
90
+ .attr('stroke', 'black')
91
+ .attr('stroke-width', '1px')
92
+ .attr('stroke-opacity', 0.1)
93
+ .attr('shape-rendering', 'geometricPrecision');
94
+
95
+ xAxisElement.selectAll('text')
96
+ .style('fill', '#333333')
97
+ .attr('font-family', 'Arial')
98
+ .attr('dy', '20px');
99
+
100
+ yAxisElement.selectAll('line')
101
+ .style('fill', 'none')
102
+ .attr('stroke', 'black')
103
+ .attr('stroke-width', '1px')
104
+ .attr('stroke-opacity', 0.1)
105
+ .attr('shape-rendering', 'geometricPrecision');
106
+
107
+ yAxisElement.selectAll('text')
108
+ .style('fill', '#333333')
109
+ .attr('font-family', 'Arial')
110
+ .attr('dx', '-10px');
111
+ }
@@ -0,0 +1,6 @@
1
+ module Grapht
2
+ ROOT = File.expand_path('../..', __FILE__)
3
+
4
+ require_relative 'grapht/shell'
5
+ require_relative 'grapht/type'
6
+ end
@@ -0,0 +1,18 @@
1
+ require 'open3'
2
+
3
+ module Grapht
4
+ module Shell
5
+ class Error < StandardError; end
6
+
7
+ CMD = File.join(Grapht::ROOT, 'bin/grapht')
8
+ ALLOWED_OPTIONS = %w(-f)
9
+
10
+ def self.exec(type, json_data, options={})
11
+ options = *options.select { |k,v| ALLOWED_OPTIONS.include? k }.flatten
12
+
13
+ out, err, status = Open3.capture3 CMD, type, *options, stdin_data: json_data
14
+ raise Grapht::Shell::Error, err unless status.success?
15
+ out
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,5 @@
1
+ module Grapht
2
+ module Type
3
+ BAR_HORIZONTAL = 'bar-horizontal'
4
+ end
5
+ end
@@ -0,0 +1,3 @@
1
+ module Grapht
2
+ VERSION = "0.1.5"
3
+ end
@@ -0,0 +1,107 @@
1
+ page = require('webpage').create()
2
+ system = require('system')
3
+ fs = require('fs')
4
+ thisFile = system.args[0]
5
+ graphType = system.args[1]
6
+ graphFormat = false
7
+ scriptPath = fs.absolute(thisFile)
8
+ .replace(/[\w\s\-\.]+?\.(js|coffee)$/i, '')
9
+ vendorPath = "#{scriptPath}../vendor/"
10
+ defsPath = "#{scriptPath}../lib/graph-definitions/"
11
+ userDefsPath = system.env['EXT_GRAPHT_DEFINITIONS_HOME']
12
+ dependencies = ['d3.min.js', 'json2.js']
13
+ niceDirPathRX = /\/$|$/
14
+
15
+ # -----------------------------------------------------------------------------
16
+ # Helper Functions
17
+ # -----------------------------------------------------------------------------
18
+
19
+ fns =
20
+ # Logs the supplied message, and optional trace to STDERR and exits the process
21
+ # with an exit code of 1.
22
+ logError: (message, trace) ->
23
+ fs.write '/dev/stderr', "<ERROR> #{message}\n"
24
+
25
+ if trace
26
+ traceString = trace.map (t) -> "\t#{t.file}: on line: #{t.line}"
27
+ fs.write '/dev/stderr', traceString.join('\n')
28
+
29
+ phantom.exit(1)
30
+
31
+ # Searchs for a valid graph definition in the supplied definition paths. If
32
+ # no definition is found, we log an error to STDERR and exit with an exitcode
33
+ # of 1.
34
+ findDef: (type, defPaths...) ->
35
+ for dir in defPaths when dir?
36
+ dir = dir.replace(niceDirPathRX, '/')
37
+ path = "#{dir}#{type}.js"
38
+ return path if fs.exists(path)
39
+
40
+ @logError "No graph definition could be found for '#{type}'"
41
+
42
+ loadDef: (def) -> fs.read(def)
43
+
44
+ # Wraps the supplied graph definition in a function that executes the definition
45
+ # and returns the resulting content of the document body. This function is intended
46
+ # to minimize boiler-plate in graph definitions, and reduce the likelihood of user
47
+ # error.
48
+ wrapDef: (def) ->
49
+ "function() {
50
+ (#{def}).apply(this, arguments);
51
+ return document.body.innerHTML;
52
+ }"
53
+
54
+ getOptions: ->
55
+ optionsIn = system.args[2..]
56
+ optionsOut = {}
57
+ slice = 2
58
+
59
+ for i in [0...optionsIn.length] by slice
60
+ [key, value] = optionsIn[i...i+slice]
61
+ optionsOut[key] = value
62
+
63
+ optionsOut
64
+
65
+ getFormat: ->
66
+ options = @getOptions()
67
+ options['-f'] || options['--format']
68
+
69
+ readDataIn: ->
70
+ try
71
+ if fs.size('/dev/stdin') == 0
72
+ fns.logError('No graph data was received!')
73
+ else
74
+ fs.read('/dev/stdin')
75
+
76
+ catch err
77
+ @logError err
78
+
79
+
80
+ # -----------------------------------------------------------------------------
81
+ # Core Graph Generation Logic
82
+ # -----------------------------------------------------------------------------
83
+
84
+ # Configure the page context.
85
+ page.libraryPath = vendorPath
86
+ page.onError = fns.logError
87
+ dependencies.forEach (dp) -> page.injectJs(dp) || Helper.logError "could not load #{dp}!"
88
+
89
+ # load and evaluate the graph definition within the context of the JSON, supplied
90
+ # via STDIN.
91
+ graphData = fns.readDataIn()
92
+ graphDef = fns.wrapDef fns.loadDef fns.findDef(graphType, userDefsPath, defsPath)
93
+ graphFormat = fns.getFormat()
94
+ parsedData = try
95
+ JSON.parse(graphData)
96
+ catch err
97
+ fns.logError(err)
98
+
99
+ page.content = content = page.evaluate(graphDef, parsedData)
100
+
101
+ # Write resulting content to STDOUT and exit.
102
+ if graphFormat
103
+ page.render '/dev/stdout', { format: graphFormat, quality: 100 }
104
+ else
105
+ fs.write '/dev/stdout', "#{content}\n"
106
+
107
+ phantom.exit()
@@ -0,0 +1,79 @@
1
+ require 'spec_helper'
2
+
3
+ describe Grapht::Shell do
4
+ describe '.exec' do
5
+ let(:type) { Grapht::Type::BAR_HORIZONTAL }
6
+ let(:format) { "" }
7
+ let(:json_data) {
8
+ <<-json
9
+ [
10
+ { "name": "foo", "value": 20 },
11
+ { "name": "bar", "value": 40 },
12
+ { "name": "baz", "value": 35 }
13
+ ]
14
+ json
15
+ }
16
+
17
+ subject { Grapht::Shell.exec type, json_data, '-f' => format }
18
+
19
+ context 'when given a known type' do
20
+ it { should match(/^<svg/) }
21
+ end
22
+
23
+ context 'when given an unknown type' do
24
+ let(:type) { 'some-invalid-graph-type' }
25
+
26
+ it "should raise a Grapht::Shell:Error" do
27
+ expect { subject }.to raise_error(Grapht::Shell::Error, /No graph definition could be found/)
28
+ end
29
+ end
30
+
31
+ context 'when well-formed data is provided' do
32
+ it { should match(/^<svg/) }
33
+ end
34
+
35
+ context 'when malformed data is provided' do
36
+ let(:json_data) { "{} <this is not JSON>" }
37
+
38
+ it "should raise a Grapht::Shell::Error" do
39
+ expect { subject }.to raise_error(Grapht::Shell::Error, /Unable to parse JSON string/)
40
+ end
41
+ end
42
+
43
+ context 'when no data is provided' do
44
+ let(:json_data) { "" }
45
+
46
+ it "should raise a Grapht::Shell::Error" do
47
+ expect { subject }.to raise_error(Grapht::Shell::Error, /No graph data was received/)
48
+ end
49
+ end
50
+
51
+ context 'when no format is provided' do
52
+ it { should match(/^<svg/) }
53
+ end
54
+
55
+ context 'when an unknown format is provided' do
56
+ let(:format) { 'some-crazy-invalid-format' }
57
+ it { should be_empty }
58
+ end
59
+
60
+ context "when a binary format is provided" do
61
+ before { subject.force_encoding('binary') }
62
+
63
+ context "when a format of 'png' is provided" do
64
+ let(:format) { 'png' }
65
+ it { should start_with("\x89\x50\x4E\x47\x0D\x0A\x1A\x0A".force_encoding('binary')) }
66
+ end
67
+
68
+ context "when a format of 'jpg' is provided" do
69
+ let(:format) { 'jpg' }
70
+ it { should start_with("\xFF\xD8\xFF\xE0\x00\x10JFIF\x00".force_encoding('binary')) }
71
+ end
72
+
73
+ context "when a format of 'gif' is provided" do
74
+ let(:format) { 'gif' }
75
+ it { should start_with("\x47\x49\x46\x38\x37\x61".force_encoding('binary')) }
76
+ end
77
+ end
78
+ end
79
+ end