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.
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