grapht 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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