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
checksums.yaml
ADDED
@@ -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
|
data/bin/tchart
ADDED
data/lib/tchart.rb
ADDED
@@ -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,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
|