tchart 1.0.2
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.
- checksums.yaml +7 -0
- data/bin/tchart +5 -0
- data/lib/tchart.rb +27 -0
- data/lib/tchart/lang/tchart_error.rb +9 -0
- data/lib/tchart/model/bar.rb +24 -0
- data/lib/tchart/model/chart.rb +25 -0
- data/lib/tchart/model/command_line_args.rb +18 -0
- data/lib/tchart/model/coordinate.rb +29 -0
- data/lib/tchart/model/grid_line.rb +25 -0
- data/lib/tchart/model/label.rb +43 -0
- data/lib/tchart/model/layout.rb +85 -0
- data/lib/tchart/model/separator.rb +28 -0
- data/lib/tchart/model/settings.rb +68 -0
- data/lib/tchart/model/y_item.rb +44 -0
- data/lib/tchart/process/chart_builder.rb +63 -0
- data/lib/tchart/process/command_line_parser.rb +73 -0
- data/lib/tchart/process/data_parser.rb +76 -0
- data/lib/tchart/process/data_reader.rb +14 -0
- data/lib/tchart/process/items_parser.rb +156 -0
- data/lib/tchart/process/layout_builder.rb +106 -0
- data/lib/tchart/process/settings_parser.rb +63 -0
- data/lib/tchart/process/tex_builder.rb +85 -0
- data/lib/tchart/process/tex_writer.rb +13 -0
- data/lib/tchart/version.rb +3 -0
- data/test/integration_test.rb +82 -0
- data/test/tchart/model/bar_test.rb +17 -0
- data/test/tchart/model/coordinate_test.rb +10 -0
- data/test/tchart/model/grid_line_test.rb +17 -0
- data/test/tchart/model/label_test.rb +31 -0
- data/test/tchart/model/layout_test.rb +17 -0
- data/test/tchart/model/separator_test.rb +19 -0
- data/test/tchart/model/settings_test.rb +48 -0
- data/test/tchart/model/y_item_test.rb +24 -0
- data/test/tchart/process/chart_builder_test.rb +33 -0
- data/test/tchart/process/command_line_parser_test.rb +89 -0
- data/test/tchart/process/data_parser_test.rb +60 -0
- data/test/tchart/process/data_reader_test.rb +23 -0
- data/test/tchart/process/items_parser_test.rb +154 -0
- data/test/tchart/process/layout_builder_test.rb +189 -0
- data/test/tchart/process/settings_parser_test.rb +75 -0
- data/test/tchart/process/tex_builder_test.rb +120 -0
- data/test/tchart/process/tex_writer_test.rb +38 -0
- data/test/tchart_test.rb +47 -0
- metadata +154 -0
@@ -0,0 +1,63 @@
|
|
1
|
+
module TChart
|
2
|
+
|
3
|
+
#
|
4
|
+
# Responsible for parsing a line of source data that contains a
|
5
|
+
# setting. Also responsible for accumulating parsed settings.
|
6
|
+
#
|
7
|
+
class SettingsParser
|
8
|
+
|
9
|
+
#
|
10
|
+
# The accumulated settings. All settings start with their default
|
11
|
+
# values and get updated as setting lines are parsed. If a setting
|
12
|
+
# is specified more than once in the source data, the last value
|
13
|
+
# found will be the one used.
|
14
|
+
#
|
15
|
+
attr_reader :settings
|
16
|
+
|
17
|
+
def initialize
|
18
|
+
@settings = Settings.new
|
19
|
+
end
|
20
|
+
|
21
|
+
#
|
22
|
+
# Return true if the passed line is a recognizable settings line
|
23
|
+
# (which may nonetheless have errors, such as unknown setting, etc.),
|
24
|
+
# false otherwise.
|
25
|
+
#
|
26
|
+
def parse?(line)
|
27
|
+
return false if (match = /^([^=]+)=(.+)$/.match(line)).nil?
|
28
|
+
name, value_as_string = match[1].strip, match[2].strip
|
29
|
+
raise_unknown_setting(name) unless known_setting?(name)
|
30
|
+
raise_not_a_recognizable_value(value_as_string) unless recognizable_value?(value_as_string)
|
31
|
+
save_setting(name, value_as_string)
|
32
|
+
true
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def known_setting?(name)
|
38
|
+
@settings.has_setting?(name)
|
39
|
+
end
|
40
|
+
|
41
|
+
#
|
42
|
+
# "chart_width = 42" => true
|
43
|
+
# "gobbledygook = junk" => true
|
44
|
+
# "chart_width 42" => false
|
45
|
+
#
|
46
|
+
def recognizable_value?(value_as_string)
|
47
|
+
value_as_string =~ /^(\+|-)?\d+(\.\d*)?$/
|
48
|
+
end
|
49
|
+
|
50
|
+
def save_setting(name, value_as_string)
|
51
|
+
@settings.send("#{name}=", value_as_string.to_f)
|
52
|
+
end
|
53
|
+
|
54
|
+
def raise_unknown_setting(name)
|
55
|
+
raise TChartError, "unknown setting \"#{name}\"; expecting one of: #{@settings.setting_names.join(', ')}"
|
56
|
+
end
|
57
|
+
|
58
|
+
def raise_not_a_recognizable_value(value_as_string)
|
59
|
+
raise TChartError, "\"#{value_as_string}\" is not a recognizable setting value; expecting e.g. 123 or 123.45"
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
|
3
|
+
module TChart
|
4
|
+
|
5
|
+
#
|
6
|
+
# Responsible for generating TikZ/TeX code for the various chart elements,
|
7
|
+
# including labels, bars, (grid)lines, etc. Also responsible for escaping
|
8
|
+
# TeX special characters, such as {, }, \, etc., and for accumulating the
|
9
|
+
# generated code.
|
10
|
+
#
|
11
|
+
class TeXBuilder
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@output = StringIO.new
|
15
|
+
end
|
16
|
+
|
17
|
+
def begin_chart
|
18
|
+
@output << "\\tikzpicture\n"
|
19
|
+
end
|
20
|
+
|
21
|
+
def end_chart
|
22
|
+
@output << "\\endtikzpicture\n"
|
23
|
+
end
|
24
|
+
|
25
|
+
def comment(text)
|
26
|
+
@output << "% #{escape_tex_special_chars text.to_s}\n"
|
27
|
+
end
|
28
|
+
|
29
|
+
def gridline(from, to, style)
|
30
|
+
@output << "\\draw [#{style}] (#{f from.x}mm, #{f from.y}mm) -- (#{f to.x}mm, #{f to.y}mm);\n"
|
31
|
+
end
|
32
|
+
|
33
|
+
def label(coord, width, style, text)
|
34
|
+
@output << "\\node [#{style}, text width = #{f width}mm] at (#{f coord.x}mm, #{f coord.y}mm) {#{escape_tex_special_chars text.to_s}};\n"
|
35
|
+
end
|
36
|
+
|
37
|
+
def bar(from, to, style)
|
38
|
+
x_mid, width = to_tikz_coords(from.x, to.x)
|
39
|
+
@output << "\\node [#{style}] at (#{f x_mid}mm, #{f from.y}mm) [minimum width = #{f width}mm] {};\n"
|
40
|
+
end
|
41
|
+
|
42
|
+
def to_s # => String
|
43
|
+
@output.string
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
#
|
49
|
+
# f(1.2345) => "1.23"
|
50
|
+
#
|
51
|
+
def f(number)
|
52
|
+
'%.02f' % number
|
53
|
+
end
|
54
|
+
|
55
|
+
#
|
56
|
+
# escape_tex_special_chars('# $ % & _ { } \ ~ ^ |') => '\# \$ \% \& \_ \{ \} \textbackslash{} \~{} \^{} $\vert$'
|
57
|
+
#
|
58
|
+
def escape_tex_special_chars(text)
|
59
|
+
text.gsub(/([#$%&_{}~^\\|])/) do |match|
|
60
|
+
case match
|
61
|
+
when '#', '$', '%', '&', '_', '{', '}'
|
62
|
+
"\\#{match}"
|
63
|
+
when '\\'
|
64
|
+
'$\\backslash$'
|
65
|
+
when '~'
|
66
|
+
'\\~{}'
|
67
|
+
when '^'
|
68
|
+
'\\^{}'
|
69
|
+
when '|'
|
70
|
+
'$\\vert$'
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
#
|
76
|
+
# to_tikx_coords(x_from, x_to) => [ x_mid, width ]
|
77
|
+
#
|
78
|
+
def to_tikz_coords(x_from, x_to)
|
79
|
+
width = x_to - x_from
|
80
|
+
x_mid = x_from + (width / 2.0)
|
81
|
+
[x_mid, width]
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
|
3
|
+
module TChart
|
4
|
+
describe "integration test" do
|
5
|
+
|
6
|
+
before do
|
7
|
+
delete_test_files(data_filename, tex_filename)
|
8
|
+
end
|
9
|
+
|
10
|
+
after do
|
11
|
+
delete_test_files(data_filename, tex_filename)
|
12
|
+
end
|
13
|
+
|
14
|
+
it do
|
15
|
+
create_file(data_filename, data)
|
16
|
+
TChart.run( [ data_filename, tex_filename ] )
|
17
|
+
contents_of(tex_filename).must_equal expected_tex
|
18
|
+
end
|
19
|
+
|
20
|
+
def data_filename
|
21
|
+
"integration_test.txt"
|
22
|
+
end
|
23
|
+
|
24
|
+
def tex_filename
|
25
|
+
"integration_test.tex"
|
26
|
+
end
|
27
|
+
|
28
|
+
def data
|
29
|
+
<<-'EOS'.gsub(/^\s+/, '')
|
30
|
+
chart_width = 100
|
31
|
+
line_height = 5
|
32
|
+
x_axis_label_width = 15
|
33
|
+
x_axis_label_y_coordinate = -5
|
34
|
+
y_axis_label_width = 30
|
35
|
+
|
36
|
+
Objective-C | lang | 2001.5-2002.6
|
37
|
+
-----------------------------------
|
38
|
+
OS X | os | 2001.3-2002.7
|
39
|
+
EOS
|
40
|
+
end
|
41
|
+
|
42
|
+
def expected_tex
|
43
|
+
<<-'EOS'.gsub(/^\s+/, '')
|
44
|
+
\tikzpicture
|
45
|
+
\draw [gridline] (0.00mm, 20.00mm) -- (55.00mm, 20.00mm);
|
46
|
+
\draw [gridline] (0.00mm, 0.00mm) -- (55.00mm, 0.00mm);
|
47
|
+
|
48
|
+
\node [xlabel, text width = 15.00mm] at (0.00mm, -5.00mm) {2001};
|
49
|
+
\draw [gridline] (0.00mm, 0.00mm) -- (0.00mm, 20.00mm);
|
50
|
+
|
51
|
+
\node [xlabel, text width = 15.00mm] at (27.50mm, -5.00mm) {2002};
|
52
|
+
\draw [gridline] (27.50mm, 0.00mm) -- (27.50mm, 20.00mm);
|
53
|
+
|
54
|
+
\node [xlabel, text width = 15.00mm] at (55.00mm, -5.00mm) {2003};
|
55
|
+
\draw [gridline] (55.00mm, 0.00mm) -- (55.00mm, 20.00mm);
|
56
|
+
|
57
|
+
\node [ylabel, text width = 30.00mm] at (-22.50mm, 15.00mm) {Objective-C};
|
58
|
+
|
59
|
+
\node [lang] at (25.09mm, 15.00mm) [minimum width = 32.10mm] {};
|
60
|
+
\draw [gridline] (0.00mm, 10.00mm) -- (55.00mm, 10.00mm);
|
61
|
+
|
62
|
+
\node [ylabel, text width = 30.00mm] at (-22.50mm, 5.00mm) {OS X};
|
63
|
+
|
64
|
+
\node [os] at (23.96mm, 5.00mm) [minimum width = 39.03mm] {};
|
65
|
+
\endtikzpicture
|
66
|
+
EOS
|
67
|
+
end
|
68
|
+
|
69
|
+
def create_file(filename, contents)
|
70
|
+
File.open(filename, "w") { |f| f.puts(contents) }
|
71
|
+
end
|
72
|
+
|
73
|
+
def contents_of(filename)
|
74
|
+
File.open(filename, "r") { |f| f.read }
|
75
|
+
end
|
76
|
+
|
77
|
+
def delete_test_files(*filenames)
|
78
|
+
filenames.each { |fn| File.delete(fn) if File.exists?(fn) }
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require_relative '../../test_helper'
|
2
|
+
|
3
|
+
module TChart
|
4
|
+
describe Bar, "render" do
|
5
|
+
|
6
|
+
before do
|
7
|
+
@tex = TeXBuilder.new
|
8
|
+
@bar = Bar.new(xy(0,30), xy(50, 30), "bar_style")
|
9
|
+
end
|
10
|
+
|
11
|
+
it "generates TeX code to render itself" do
|
12
|
+
@tex.expects(:bar).once
|
13
|
+
@bar.render(@tex)
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
require_relative '../../test_helper'
|
2
|
+
|
3
|
+
describe "xy" do
|
4
|
+
it "returns a newly allocated coordinate with the passed x,y coordinates" do
|
5
|
+
coordinate = xy(4, 2)
|
6
|
+
coordinate.must_be_kind_of TChart::Coordinate
|
7
|
+
coordinate.x.must_equal 4
|
8
|
+
coordinate.y.must_equal 2
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require_relative '../../test_helper'
|
2
|
+
|
3
|
+
module TChart
|
4
|
+
describe GridLine, "render" do
|
5
|
+
|
6
|
+
before do
|
7
|
+
@tex = TeXBuilder.new
|
8
|
+
@gridline = GridLine.new(xy(0,0), xy(10,0))
|
9
|
+
end
|
10
|
+
|
11
|
+
it "generates TeX code to render the grid line" do
|
12
|
+
@tex.expects(:gridline).once
|
13
|
+
@gridline.render(@tex)
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require_relative '../../test_helper'
|
2
|
+
|
3
|
+
module TChart
|
4
|
+
describe Label, "build_xlabel" do
|
5
|
+
it "returns a label with style 'xlabel'" do
|
6
|
+
Label.build_xlabel(xy(4,2), 42, "text").style.must_equal "xlabel"
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
|
11
|
+
describe Label, "build_ylabel" do
|
12
|
+
it "returns a label with style 'ylabel'" do
|
13
|
+
Label.build_ylabel(xy(4,2), 42, "text").style.must_equal "ylabel"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
describe Label, "render" do
|
19
|
+
|
20
|
+
before do
|
21
|
+
@tex = TeXBuilder.new
|
22
|
+
@label = Label.build_ylabel(xy(-10,20), xy(30,20), "description")
|
23
|
+
end
|
24
|
+
|
25
|
+
it "generates TikZ code to render and item" do
|
26
|
+
@tex.expects(:label)
|
27
|
+
@label.render(@tex)
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require_relative '../../test_helper'
|
2
|
+
|
3
|
+
module TChart
|
4
|
+
describe Layout, "date_range_to_x_coordinates" do
|
5
|
+
|
6
|
+
before do
|
7
|
+
@layout = Layout.new
|
8
|
+
@layout.x_axis_tick_dates = [Date.new(2001,1,1), Date.new(2002,1,1)]
|
9
|
+
@layout.x_axis_length = 100
|
10
|
+
end
|
11
|
+
|
12
|
+
it "converts a date range to its equivalent x_coordinate range on the chart" do
|
13
|
+
@layout.date_range_to_x_coordinates(Date.new(2001,1,1)..Date.new(2001,12,31)).must_equal [0.0, 100.0]
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require_relative '../../test_helper'
|
2
|
+
|
3
|
+
module TChart
|
4
|
+
describe Separator, "build" do
|
5
|
+
|
6
|
+
before do
|
7
|
+
@layout = stub( x_axis_length: 100 )
|
8
|
+
@separator = Separator.new
|
9
|
+
@y = 10
|
10
|
+
end
|
11
|
+
|
12
|
+
it "returns an array containing a horizontal gridline" do
|
13
|
+
elements = @separator.build(@layout, @y)
|
14
|
+
elements.length.must_equal 1
|
15
|
+
elements[0].must_equal GridLine.new(xy(0,10), xy(100,10))
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require_relative '../../test_helper'
|
2
|
+
|
3
|
+
module TChart
|
4
|
+
describe Settings, "initialize" do
|
5
|
+
|
6
|
+
before do
|
7
|
+
@settings = Settings.new
|
8
|
+
end
|
9
|
+
|
10
|
+
it "sets its settings to default values" do
|
11
|
+
@settings.setting_names.each do |setting_name|
|
12
|
+
@settings.send(setting_name).wont_be_nil "\"#{setting_name}\" is not being set to a default value in Settings#initialize"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
describe Settings, "has_setting?" do
|
20
|
+
|
21
|
+
before do
|
22
|
+
@settings = Settings.new
|
23
|
+
end
|
24
|
+
|
25
|
+
it "returns true if the setting exists" do
|
26
|
+
@settings.has_setting?('chart_width').must_equal true
|
27
|
+
end
|
28
|
+
it "returns false if the setting does not exist" do
|
29
|
+
@settings.has_setting?('gobbledygook').must_equal false
|
30
|
+
end
|
31
|
+
it "returns false when asked about itself" do
|
32
|
+
@settings.has_setting?('has_setting?').must_equal false
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
describe Settings, "setting_names" do
|
38
|
+
|
39
|
+
before do
|
40
|
+
@settings = Settings.new
|
41
|
+
end
|
42
|
+
|
43
|
+
it "returns the list of setting names" do
|
44
|
+
@settings.setting_names.must_equal ['chart_width', 'line_height', 'x_axis_label_width',
|
45
|
+
'x_axis_label_y_coordinate', 'y_axis_label_width']
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require_relative '../../test_helper'
|
2
|
+
|
3
|
+
module TChart
|
4
|
+
describe YItem, "build" do
|
5
|
+
|
6
|
+
before do
|
7
|
+
@layout = stub
|
8
|
+
@layout.stubs(:x_axis_date_range).returns( Date.new(2001,1,1)..Date.new(2003,1,1) )
|
9
|
+
@layout.stubs(:y_axis_label_x_coordinate).returns(-10)
|
10
|
+
@layout.stubs(:y_axis_label_width).returns(20)
|
11
|
+
@layout.stubs(:date_range_to_x_coordinates).returns [0, 50]
|
12
|
+
@item = YItem.new("description", "bar_style", [ Date.new(2001,1,1)..Date.new(2001,12,31) ])
|
13
|
+
@y = 10
|
14
|
+
end
|
15
|
+
|
16
|
+
it "returns an array containing a label and bars" do
|
17
|
+
elements = @item.build(@layout, @y)
|
18
|
+
elements.length.must_equal 2
|
19
|
+
elements[0].must_be_kind_of Label
|
20
|
+
elements[1].must_be_kind_of Bar
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|