tchart 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/bin/tchart +5 -0
  3. data/lib/tchart.rb +27 -0
  4. data/lib/tchart/lang/tchart_error.rb +9 -0
  5. data/lib/tchart/model/bar.rb +24 -0
  6. data/lib/tchart/model/chart.rb +25 -0
  7. data/lib/tchart/model/command_line_args.rb +18 -0
  8. data/lib/tchart/model/coordinate.rb +29 -0
  9. data/lib/tchart/model/grid_line.rb +25 -0
  10. data/lib/tchart/model/label.rb +43 -0
  11. data/lib/tchart/model/layout.rb +85 -0
  12. data/lib/tchart/model/separator.rb +28 -0
  13. data/lib/tchart/model/settings.rb +68 -0
  14. data/lib/tchart/model/y_item.rb +44 -0
  15. data/lib/tchart/process/chart_builder.rb +63 -0
  16. data/lib/tchart/process/command_line_parser.rb +73 -0
  17. data/lib/tchart/process/data_parser.rb +76 -0
  18. data/lib/tchart/process/data_reader.rb +14 -0
  19. data/lib/tchart/process/items_parser.rb +156 -0
  20. data/lib/tchart/process/layout_builder.rb +106 -0
  21. data/lib/tchart/process/settings_parser.rb +63 -0
  22. data/lib/tchart/process/tex_builder.rb +85 -0
  23. data/lib/tchart/process/tex_writer.rb +13 -0
  24. data/lib/tchart/version.rb +3 -0
  25. data/test/integration_test.rb +82 -0
  26. data/test/tchart/model/bar_test.rb +17 -0
  27. data/test/tchart/model/coordinate_test.rb +10 -0
  28. data/test/tchart/model/grid_line_test.rb +17 -0
  29. data/test/tchart/model/label_test.rb +31 -0
  30. data/test/tchart/model/layout_test.rb +17 -0
  31. data/test/tchart/model/separator_test.rb +19 -0
  32. data/test/tchart/model/settings_test.rb +48 -0
  33. data/test/tchart/model/y_item_test.rb +24 -0
  34. data/test/tchart/process/chart_builder_test.rb +33 -0
  35. data/test/tchart/process/command_line_parser_test.rb +89 -0
  36. data/test/tchart/process/data_parser_test.rb +60 -0
  37. data/test/tchart/process/data_reader_test.rb +23 -0
  38. data/test/tchart/process/items_parser_test.rb +154 -0
  39. data/test/tchart/process/layout_builder_test.rb +189 -0
  40. data/test/tchart/process/settings_parser_test.rb +75 -0
  41. data/test/tchart/process/tex_builder_test.rb +120 -0
  42. data/test/tchart/process/tex_writer_test.rb +38 -0
  43. data/test/tchart_test.rb +47 -0
  44. metadata +154 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b23ad54a382777cdf1e500dbcf5539779a0a3d3b
4
+ data.tar.gz: e484d3aed917693c74abd2557e362b08bbc39de9
5
+ SHA512:
6
+ metadata.gz: a4ba8ddc34005fba31ddf15d9a1a32225fb9d60077434a66498d2d76f2686b5dcd47ab2b853d1100bff5c74273c18978ff49c8b5982b29d0788b6158acc03fa3
7
+ data.tar.gz: 2b74530475bc379d20fc6706c750372177ea31f5e8123d7062c50ca0f224da2b04bec43633823df2947ec33099c2abe0df2b44a077cb9ecdd2f8cfae1e286fd7
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'tchart'
4
+
5
+ TChart.run(ARGV)
@@ -0,0 +1,27 @@
1
+ # 'require' all files except ourselves.
2
+ me = File.absolute_path(__FILE__)
3
+ Dir.glob(File.dirname(me) + '/**/*.rb') {|fn| require fn unless fn == me }
4
+
5
+ module TChart
6
+
7
+ #
8
+ # Program entry point. Responsible for running the various steps
9
+ # required to generate TikZ code that renders a chart. Also
10
+ # responsible for reporting errors.
11
+ #
12
+ def self.run(argv)
13
+ args, errors = CommandLineParser.parse(argv) ; abort_if errors
14
+ settings, items, errors = DataReader.read(args.data_filename) ; abort_if errors
15
+ layout, errors = LayoutBuilder.build(settings, items) ; abort_if errors
16
+ chart = ChartBuilder.build(layout, items)
17
+ tex = chart.render
18
+ TeXWriter.write(args.tex_filename, tex)
19
+ end
20
+
21
+ private
22
+
23
+ def self.abort_if errors
24
+ abort(errors.join("\n")) unless errors.empty?
25
+ end
26
+
27
+ end
@@ -0,0 +1,9 @@
1
+ module TChart
2
+
3
+ #
4
+ # Used to distinguish errors raised within the application code
5
+ # from errors raised by Ruby or a third-party library.
6
+ #
7
+ class TChartError < StandardError ; end
8
+
9
+ end
@@ -0,0 +1,24 @@
1
+ module TChart
2
+
3
+ #
4
+ # A bar that represents a date range on the chart. Responsible
5
+ # for generating TikZ code to render the bar.
6
+ #
7
+ class Bar
8
+
9
+ attr_reader :from
10
+ attr_reader :to
11
+ attr_reader :style # TikZ style, must be defined in encompasing TeX document.
12
+
13
+ def initialize(from, to, style)
14
+ @from = from
15
+ @to = to
16
+ @style = style
17
+ end
18
+
19
+ def render(tex)
20
+ tex.bar @from, @to, @style
21
+ end
22
+
23
+ end
24
+ end
@@ -0,0 +1,25 @@
1
+ module TChart
2
+
3
+ #
4
+ # The plots of zero or more date ranges. Includes bars representing the
5
+ # date ranges, x and y axes lines, gridlines, and labels. Has overall
6
+ # responsibility for generating all of the TikZ code to render the chart.
7
+ #
8
+ class Chart
9
+
10
+ attr_reader :elements # Labels, gridlines and bars that make up the chart.
11
+
12
+ def initialize(elements)
13
+ @elements = elements
14
+ end
15
+
16
+ def render # => String
17
+ tex = TeXBuilder.new
18
+ tex.begin_chart
19
+ @elements.each { |element| element.render(tex) }
20
+ tex.end_chart
21
+ tex.to_s
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,18 @@
1
+ module TChart
2
+
3
+ #
4
+ # Responsible for storing all command line arguments for use
5
+ # throughout the lifecycle of the application.
6
+ #
7
+ class CommandLineArgs
8
+
9
+ attr_reader :data_filename # input file
10
+ attr_reader :tex_filename # output file
11
+
12
+ def initialize(data_filename, tex_filename)
13
+ @data_filename = data_filename
14
+ @tex_filename = tex_filename
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,29 @@
1
+ module TChart
2
+
3
+ #
4
+ # An (x,y) location on the chart. x and y can be
5
+ # in any units, e.g. millimeters, pixels, etc.
6
+ #
7
+ class Coordinate
8
+
9
+ attr_reader :x
10
+ attr_reader :y
11
+
12
+ def initialize(x, y)
13
+ @x = x
14
+ @y = y
15
+ end
16
+
17
+ end
18
+ end
19
+
20
+
21
+ module Kernel
22
+
23
+ #
24
+ # Shorthand for TChart::Coordinate.new(x, t).
25
+ #
26
+ def xy(x, y) # => Coordinate
27
+ TChart::Coordinate.new(x, y)
28
+ end
29
+ end
@@ -0,0 +1,25 @@
1
+ module TChart
2
+
3
+ #
4
+ # A horizontal or vertical line on the chart that spans the
5
+ # entire width or height of the chart and serves as a reading
6
+ # aid. The x and y axes, and the top and and right frame of
7
+ # the chart are also grid lines. Responsible for generating
8
+ # TikZ code to render the grid line.
9
+ #
10
+ class GridLine
11
+
12
+ attr_reader :from
13
+ attr_reader :to
14
+
15
+ def initialize(from, to)
16
+ @from = from
17
+ @to = to
18
+ end
19
+
20
+ def render(tex)
21
+ tex.gridline @from, @to, "gridline"
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,43 @@
1
+ module TChart
2
+
3
+ #
4
+ # An x or y axis label on the chart. X axis labels will be years, e.g.
5
+ # "2014", "2015", etc., and y axis labels will be descriptions of the
6
+ # items being plotted, e.g. "Ruby", "C", "C++", etc. Responsible for
7
+ # generating TikZ code to render the label.
8
+ #
9
+ class Label
10
+
11
+ attr_reader :coord # Horizontal and vertical mid-point of label location.
12
+ attr_reader :width # Required for text justification.
13
+ attr_reader :style # TikZ style, must be defined in encompasing TeX document.
14
+ attr_reader :text
15
+
16
+ #
17
+ # The difference between an x axis label and a y axis label is the TikZ style;
18
+ # x axis labels are generated with the style "xlabel", y axis labels use
19
+ # "ylabel". In the TeX document that embeds the chart, the x axis labels will
20
+ # usually be styled with centered text, whereas y axis labels will be left
21
+ # justified.
22
+ #
23
+ def self.build_xlabel(coord, width, text) # => Label
24
+ Label.new(coord, width, "xlabel", text)
25
+ end
26
+
27
+ def self.build_ylabel(coord, width, text) # => Label
28
+ Label.new(coord, width, "ylabel", text)
29
+ end
30
+
31
+ def initialize(coord, width, style, text)
32
+ @coord = coord
33
+ @width = width
34
+ @style = style
35
+ @text = text
36
+ end
37
+
38
+ def render(tex)
39
+ tex.label @coord, @width, @style, @text
40
+ end
41
+
42
+ end
43
+ end
@@ -0,0 +1,85 @@
1
+ module TChart
2
+
3
+ #
4
+ # Responsible for storing various chart metrics such as the length of the
5
+ # axes, the width of the axes labels, the coordinates of the axes labels,
6
+ # and so on. All metrics are unitless, although they should all be in
7
+ # the same unit. Also responsible for converting a date range to its
8
+ # equivalent start and end coordinates on the chart.
9
+ #
10
+ class Layout
11
+
12
+ #
13
+ # The length of the x axis from the leftmost to the rightmost label.
14
+ #
15
+ attr_accessor :x_axis_length
16
+
17
+ #
18
+ # The amount of horizontal space to allocate for each x axis label.
19
+ # Used to determine the width of the margins to the left and right of
20
+ # the x axis, and also passed to TikZ/TeX.
21
+ #
22
+ attr_accessor :x_axis_label_width
23
+
24
+ #
25
+ # The distance of the mid point of the x axis labels from the x axis.
26
+ #
27
+ attr_accessor :x_axis_label_y_coordinate
28
+
29
+ #
30
+ # The x coordinates of the x axis labels and associated vertical
31
+ # grid lines.
32
+ #
33
+ attr_accessor :x_axis_tick_x_coordinates
34
+
35
+ #
36
+ # The dates to be used for the x axis labels.
37
+ #
38
+ attr_accessor :x_axis_tick_dates
39
+
40
+ #
41
+ # The length of the y axis from the topmost to the bottommost item.
42
+ #
43
+ attr_accessor :y_axis_length
44
+
45
+ #
46
+ # The width of the y axis labels. Used to calculate the amount of
47
+ # horizontal space to leave for the labels, and also passed in the
48
+ # generated TikX code.
49
+ #
50
+ attr_accessor :y_axis_label_width
51
+
52
+ #
53
+ # The distance of the mid point of the y axis labels to the left
54
+ # of the y axis.
55
+ #
56
+ attr_accessor :y_axis_label_x_coordinate
57
+
58
+ #
59
+ # The y coordinates of the y axis labels and their associated bars.
60
+ #
61
+ attr_accessor :y_axis_tick_y_coordinates
62
+
63
+ #
64
+ # Convert a date range, e.g. Date.new(2000,1,1)..Date.new(2002,10,3), to
65
+ # its equivalent start and end coordinates on the chart.
66
+ #
67
+ def date_range_to_x_coordinates(date_range) # => [ Numeric, Numeric ]
68
+ x_from = date_to_x_coordinate(date_range.begin)
69
+ x_to = date_to_x_coordinate(date_range.end + 1) # +1 bumps the time to end-of-day of the end date
70
+ [x_from, x_to]
71
+ end
72
+
73
+ private
74
+
75
+ #
76
+ # ratio is: x_coordinate / x_axis_length = ( date - date_range.begin ) / date_range_length
77
+ #
78
+ def date_to_x_coordinate(date) # => Numeric
79
+ date_range_begin, date_range_end = @x_axis_tick_dates.first.jd, @x_axis_tick_dates.last.jd
80
+ date_range_length = date_range_end - date_range_begin
81
+ ( @x_axis_length * ( date.jd - date_range_begin ) * 1.0 ) / date_range_length
82
+ end
83
+
84
+ end
85
+ end
@@ -0,0 +1,28 @@
1
+ module TChart
2
+
3
+ #
4
+ # The input data file specifies settings, data lines, and separators.
5
+ # A separator renders as a horizontal grid line and serves to separate
6
+ # the charted items into sections. Responsible for building the grid
7
+ # line element.
8
+ #
9
+ class Separator
10
+
11
+ #
12
+ # This is part of the charted item interface. Separators
13
+ # have no date ranges so this array will always be empty.
14
+ #
15
+ attr_reader :date_ranges
16
+
17
+ def initialize
18
+ @date_ranges = []
19
+ end
20
+
21
+ def build(layout, y) # => [ GridLine ]
22
+ from = xy(0, y)
23
+ to = xy(layout.x_axis_length, y)
24
+ [ GridLine.new(from, to) ]
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,68 @@
1
+ module TChart
2
+
3
+ #
4
+ # The input data file specifies settings, data lines, and separators.
5
+ # This class stores the settings from the data file. Responsible for
6
+ # providing default values for those settings that are not specified
7
+ # in the input file. Also responsible for answering whether a setting
8
+ # name is known or not, and for providing a list of all setting names.
9
+ #
10
+ # All setting values are in millimeters. The Settings and TeXBuilder
11
+ # are the only two classes that know what units are being used. All
12
+ # other classes are unit agnostic.
13
+ #
14
+ class Settings
15
+
16
+ #
17
+ # The amount of horizontal space available for the chart. It must be
18
+ # large enough to accomodate the width of the y-axis labels, the width
19
+ # of the x axis, and the margins to the left and right of the x axis.
20
+ #
21
+ attr_accessor :chart_width
22
+
23
+ #
24
+ # The amount of vertical space to allocate for each line of the chart.
25
+ # It must be large enough to accomodate the larger of the y axis label
26
+ # height and the bar height.
27
+ #
28
+ attr_accessor :line_height
29
+
30
+ #
31
+ # The amount of horizontal space to allocate for each x axis label.
32
+ # Used to determine the width of the margins to the left and right of
33
+ # the x axis, and also passed to TikZ/TeX.
34
+ #
35
+ attr_accessor :x_axis_label_width
36
+
37
+ #
38
+ # The distance of the mid point of the x axis labels from the x axis.
39
+ #
40
+ attr_accessor :x_axis_label_y_coordinate
41
+
42
+ #
43
+ # The width of the y axis labels. Used to calculate the amount of
44
+ # horizontal space to leave for the labels, and also passed in the
45
+ # generated TikX code.
46
+ #
47
+ attr_accessor :y_axis_label_width
48
+
49
+ def initialize
50
+ @chart_width = 164.99
51
+ @line_height = 4.6
52
+ @x_axis_label_width = 10
53
+ @x_axis_label_y_coordinate = -3
54
+ @y_axis_label_width = 24
55
+ end
56
+
57
+ def has_setting?(setting_name)
58
+ setting_names.include?(setting_name)
59
+ end
60
+
61
+ def setting_names # => [ "chart_width", "line_height", ... ]
62
+ methods # => [ "has_setting?", "chart_width", "chart_width=", ... ]
63
+ .grep(/\w=$/) # => [ "chart_width=", ... ]
64
+ .map {|name| name.to_s.chomp('=')} # => [ "chart_width", ... ]
65
+ end
66
+
67
+ end
68
+ end
@@ -0,0 +1,44 @@
1
+ module TChart
2
+
3
+ #
4
+ # The input data file specifies settings, data lines, and separators. A
5
+ # data line consists of a description, which becomes a y axis label, zero
6
+ # or more date ranges to be plotted on the chart as horizontal bars, and
7
+ # a TikZ style for the bars. YItem is reponsible for capturing all of
8
+ # that information and for building the label and bar elements.
9
+ #
10
+ class YItem
11
+
12
+ attr_reader :description # Used for the y-label.
13
+ attr_reader :bar_style # TikZ style, must be defined in encompasing TeX document.
14
+ attr_reader :date_ranges # Can be >= 0; drawn as bars on the chart.
15
+
16
+ def initialize(description, bar_style, date_ranges)
17
+ @description = description
18
+ @bar_style = bar_style
19
+ @date_ranges = date_ranges
20
+ end
21
+
22
+ def build(layout, y) # => [ Label, Bar, Bar, ... ]
23
+ [ new_y_label(layout, y) ].concat new_bars(layout, y)
24
+ end
25
+
26
+ private
27
+
28
+ def new_y_label(layout, y) # => Label
29
+ Label.build_ylabel(xy(layout.y_axis_label_x_coordinate, y), layout.y_axis_label_width, @description)
30
+ end
31
+
32
+ def new_bars(layout, y) # => [ Bar, Bar, ... ]
33
+ @date_ranges.map do |date_range|
34
+ x_from, x_to = layout.date_range_to_x_coordinates(date_range)
35
+ new_bar(x_from, x_to, y)
36
+ end
37
+ end
38
+
39
+ def new_bar(x_from, x_to, y) # => Bar
40
+ Bar.new(xy(x_from, y), xy(x_to, y), @bar_style)
41
+ end
42
+
43
+ end
44
+ end