summaryrefslogtreecommitdiffhomepage
path: root/lib/axlsx/drawing/axis.rb
blob: ed3df5f1a99b1da03d6c75086b19231c09b237f0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
# frozen_string_literal: true

module Axlsx
  # the access class defines common properties and values for a chart axis.
  class Axis
    include Axlsx::OptionsParser

    # Creates an Axis object
    # @option options [Axis] cross_axis the perpendicular axis
    # @option options [Symbol] ax_pos
    # @option options [Symbol] crosses
    # @option options [Symbol] tick_lbl_pos
    # @raise [ArgumentError] If axi_id or cross_ax are not unsigned integers
    def initialize(options = {})
      @id = rand(8**8)
      @format_code = "General"
      @delete = @label_rotation = 0
      @scaling = Scaling.new(orientation: :minMax)
      @title = @color = nil
      self.ax_pos = :b
      self.tick_lbl_pos = :nextTo
      self.format_code = "General"
      self.crosses = :autoZero
      self.gridlines = true
      parse_options options
    end

    # the fill color to use in the axis shape properties. This should be a 6 character long hex string
    # e.g. FF0000 for red
    # @return [String]
    attr_reader :color

    # the id of the axis.
    # @return [Integer]
    attr_reader :id
    alias :axID :id

    # The perpendicular axis
    # @return [Integer]
    attr_reader :cross_axis
    alias :crossAx :cross_axis

    # The scaling of the axis
    # @see Scaling
    # @return [Scaling]
    attr_reader :scaling

    # The position of the axis
    # must be one of [:l, :r, :t, :b]
    # @return [Symbol]
    attr_reader :ax_pos
    alias :axPos :ax_pos

    # the position of the tick labels
    # must be one of [:nextTo, :high, :low]
    # @return [Symbol]
    attr_reader :tick_lbl_pos
    alias :tickLblPos :tick_lbl_pos

    # The number format format code for this axis
    # default :General
    # @return [String]
    attr_reader :format_code

    # specifies how the perpendicular axis is crossed
    # must be one of [:autoZero, :min, :max]
    # @return [Symbol]
    attr_reader :crosses

    # specifies how the degree of label rotation
    # @return [Integer]
    attr_reader :label_rotation

    # specifies if gridlines should be shown in the chart
    # @return [Boolean]
    attr_reader :gridlines

    # specifies if gridlines should be shown in the chart
    # @return [Boolean]
    attr_reader :delete

    # the title for the axis. This can be a cell or a fixed string.
    attr_reader :title

    # The color for this axis. This value is used when rendering the axis line in the chart.
    # colors should be in 6 character rbg format
    # @return [String] the rbg color assinged.
    # @see color
    def color=(color_rgb)
      @color = color_rgb
    end

    # The crossing axis for this axis
    # @param [Axis] axis
    def cross_axis=(axis)
      DataTypeValidator.validate "#{self.class}.cross_axis", [Axis], axis
      @cross_axis = axis
    end

    # The position of the axis
    # must be one of [:l, :r, :t, :b]
    def ax_pos=(v) RestrictionValidator.validate "#{self.class}.ax_pos", [:l, :r, :b, :t], v; @ax_pos = v; end
    alias :axPos= :ax_pos=

    # the position of the tick labels
    # must be one of [:nextTo, :high, :low1]
    def tick_lbl_pos=(v) RestrictionValidator.validate "#{self.class}.tick_lbl_pos", [:nextTo, :high, :low, :none], v; @tick_lbl_pos = v; end
    alias :tickLblPos= :tick_lbl_pos=

    # The number format format code for this axis
    # default :General
    def format_code=(v) Axlsx.validate_string(v); @format_code = v; end

    # Specify if gridlines should be shown for this axis
    # default true
    def gridlines=(v) Axlsx.validate_boolean(v); @gridlines = v; end

    # Specify if axis should be removed from the chart
    # default false
    def delete=(v) Axlsx.validate_boolean(v); @delete = v; end

    # specifies how the perpendicular axis is crossed
    # must be one of [:autoZero, :min, :max]
    def crosses=(v) RestrictionValidator.validate "#{self.class}.crosses", [:autoZero, :min, :max], v; @crosses = v; end

    # Specify the degree of label rotation to apply to labels
    # default true
    def label_rotation=(v)
      Axlsx.validate_int(v)
      adjusted = v.to_i * 60000
      Axlsx.validate_angle(adjusted)
      @label_rotation = adjusted
    end

    # The title object for the chart.
    # @param [String, Cell] v
    # @return [Title]
    def title=(v)
      DataTypeValidator.validate "#{self.class}.title", [String, Cell], v
      @title ||= Title.new
      if v.is_a?(String)
        @title.text = v
      elsif v.is_a?(Cell)
        @title.cell = v
      end
    end

    # Serializes the object
    # @param [String] str
    # @return [String]
    def to_xml_string(str = +'')
      str << '<c:axId val="' << @id.to_s << '"/>'
      @scaling.to_xml_string str
      str << '<c:delete val="' << @delete.to_s << '"/>'
      str << '<c:axPos val="' << @ax_pos.to_s << '"/>'
      str << '<c:majorGridlines>'
      # TODO: shape properties need to be extracted into a class
      if gridlines == false
        str << '<c:spPr>'
        str << '<a:ln>'
        str << '<a:noFill/>'
        str << '</a:ln>'
        str << '</c:spPr>'
      end
      str << '</c:majorGridlines>'
      @title.to_xml_string(str) unless @title.nil?
      # Need to set sourceLinked to 0 if we're setting a format code on this row
      # otherwise it will never take, as it will always prefer the 'General' formatting
      # of the cells themselves
      str << '<c:numFmt formatCode="' << @format_code << '" sourceLinked="' << (@format_code.eql?('General') ? '1' : '0') << '"/>'
      str << '<c:majorTickMark val="none"/>'
      str << '<c:minorTickMark val="none"/>'
      str << '<c:tickLblPos val="' << @tick_lbl_pos.to_s << '"/>'
      # TODO: this is also being used for series colors
      # time to extract this into a class spPr - Shape Properties
      if @color
        str << '<c:spPr><a:ln><a:solidFill>'
        str << '<a:srgbClr val="' << @color << '"/>'
        str << '</a:solidFill></a:ln></c:spPr>'
      end
      # some potential value in implementing this in full. Very detailed!
      str << '<c:txPr><a:bodyPr rot="' << @label_rotation.to_s << '"/><a:lstStyle/><a:p><a:pPr><a:defRPr/></a:pPr><a:endParaRPr/></a:p></c:txPr>'
      str << '<c:crossAx val="' << @cross_axis.id.to_s << '"/>'
      str << '<c:crosses val="' << @crosses.to_s << '"/>'
    end
  end
end