svg-graph 2.1.1 → 2.2.1

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.
@@ -1,6 +1,8 @@
1
1
  # Allows to customize datapoint shapes
2
2
  class DataPoint
3
- OVERLAY = "OVERLAY" unless defined?(OVERLAY)
3
+ # magic string that defines if a shape is intented to be overlayed to a default.
4
+ # this allowes to have strike through of a circle etc.
5
+ OVERLAY = "OVERLAY"
4
6
  DEFAULT_SHAPE = lambda{|x,y,line| ["circle", {
5
7
  "cx" => x,
6
8
  "cy" => y,
@@ -9,27 +11,33 @@ class DataPoint
9
11
  }]
10
12
  } unless defined? DEFAULT_SHAPE
11
13
  CRITERIA = [] unless defined? CRITERIA
12
-
14
+
13
15
  # matchers are class scope. Once configured, each DataPoint instance will have
14
16
  # access to the same matchers
15
- # @param matchers [Array] multiple arrays of the following form:
16
- # [ regex ,
17
+ # @param matchers [Array] multiple arrays of the following form 2 or 3 elements:
18
+ # [ regex ,
17
19
  # lambda taking three arguments (x,y, line_number for css)
18
- # -> return value of the lambda must be an array: [svg tag name, Hash with attributes for the svg tag, e.g. "points" and "class"]
20
+ # -> return value of the lambda must be an array: [svg tag name,
21
+ # Hash with attributes for the svg tag, e.g. "points" and "class",
22
+ # make sure to check source code of you graph type for valid css class.],
23
+ # "OVERLAY" (magic string, if specified, puts the shape on top of existing datapoint)
19
24
  # ]
20
25
  # @example
21
26
  # DataPoint.configure_shape_criteria(
22
- # [/.*/, lambda{|x,y,line| ['polygon', {
23
- # "points" => "#{x-1.5},#{y+2.5} #{x+1.5},#{y+2.5} #{x+1.5},#{y-2.5} #{x-1.5},#{y-2.5}",
24
- # "class" => "dataPoint#{line}"
25
- # }]
27
+ # [/.*/, lambda{|x,y,line|
28
+ # [ 'polygon',
29
+ # {
30
+ # "points" => "#{x-1.5},#{y+2.5} #{x+1.5},#{y+2.5} #{x+1.5},#{y-2.5} #{x-1.5},#{y-2.5}",
31
+ # "class" => "dataPoint#{line}"
32
+ # }
33
+ # ]
26
34
  # }]
27
35
  # )
28
36
  def DataPoint.configure_shape_criteria(*matchers)
29
37
  CRITERIA.push(*matchers)
30
38
  end
31
-
32
- #
39
+
40
+ #
33
41
  def DataPoint.reset_shape_criteria
34
42
  CRITERIA.clear
35
43
  end
@@ -43,7 +51,11 @@ class DataPoint
43
51
  @y = y
44
52
  @line = line
45
53
  end
46
-
54
+
55
+ # Returns different shapes depending on datapoint descriptions, if shape criteria have been configured.
56
+ # The definded criteria are evaluated in two stages, first the ones, which are note defined as overlay.
57
+ # then the "OVERLAY"
58
+ # @param datapoint_description [String] description or label of the current datapoint
47
59
  # @return [Array<Array>] see example
48
60
  # @example Return value
49
61
  # # two dimensional array, the splatted (*) inner array can be used as argument to REXML::add_element
@@ -53,12 +65,12 @@ class DataPoint
53
65
  # # for each svg we insert it to the graph
54
66
  # dp.each {|s| @graph.add_element( *s )}
55
67
  #
56
- def shape(description=nil)
57
- # select all criteria with size 2, and collect rendered lambdas in an array
68
+ def shape(datapoint_description=nil)
69
+ # select all criteria with size 2, and collect rendered lambdas in an array
58
70
  shapes = CRITERIA.select {|criteria|
59
71
  criteria.size == 2
60
72
  }.collect {|regexp, proc|
61
- proc.call(@x, @y, @line) if description =~ regexp
73
+ proc.call(@x, @y, @line) if datapoint_description =~ regexp
62
74
  }.compact
63
75
  # if above did not render anything use the defalt shape
64
76
  shapes = [DEFAULT_SHAPE.call(@x, @y, @line)] if shapes.empty?
@@ -66,7 +78,7 @@ class DataPoint
66
78
  overlays = CRITERIA.select { |criteria|
67
79
  criteria.last == OVERLAY
68
80
  }.collect { |regexp, proc|
69
- proc.call(@x, @y, @line) if description =~ regexp
81
+ proc.call(@x, @y, @line) if datapoint_description =~ regexp
70
82
  }.compact
71
83
 
72
84
  return shapes + overlays
@@ -103,6 +103,7 @@ module SVG
103
103
  # [number_format] '%.2f'
104
104
  def initialize( config )
105
105
  @config = config
106
+ # array of Hash
106
107
  @data = []
107
108
  #self.top_align = self.top_font = 0
108
109
  #self.right_align = self.right_font = 0
@@ -144,22 +145,29 @@ module SVG
144
145
  :show_graph_subtitle => false,
145
146
  :graph_subtitle => 'Graph Sub Title',
146
147
  :key => true,
148
+ :key_width => nil,
147
149
  :key_position => :right, # bottom or right
148
150
 
149
- :font_size =>12,
150
- :title_font_size =>16,
151
- :subtitle_font_size =>14,
152
- :x_label_font_size =>12,
153
- :y_label_font_size =>12,
154
- :x_title_font_size =>14,
155
- :y_title_font_size =>14,
156
- :key_font_size =>10,
157
-
158
- :no_css =>false,
159
- :add_popups =>false,
160
- :number_format => '%.2f'
151
+ :font_size => 12,
152
+ :title_font_size => 16,
153
+ :subtitle_font_size => 14,
154
+ :x_label_font_size => 12,
155
+ :y_label_font_size => 12,
156
+ :x_title_font_size => 14,
157
+ :y_title_font_size => 14,
158
+ :key_font_size => 10,
159
+ :key_box_size => 12,
160
+ :key_spacing => 5,
161
+
162
+ :no_css => false,
163
+ :add_popups => false,
164
+ :popup_radius => 10,
165
+ :number_format => '%.2f',
166
+ :style_sheet => '',
167
+ :inline_style_sheet => ''
161
168
  })
162
169
  set_defaults if self.respond_to? :set_defaults
170
+ # override default values with user supplied values
163
171
  init_with config
164
172
  end
165
173
 
@@ -173,16 +181,38 @@ module SVG
173
181
  # :data => data_sales_02,
174
182
  # :title => 'Sales 2002'
175
183
  # })
176
- def add_data conf
177
- @data = [] unless (defined? @data and !@data.nil?)
184
+ # @param conf [Hash] with the following keys:
185
+ # :data [Array] mandatory
186
+ # :title [String] mandatory name of data series for legend of graph
187
+ # :description [Array<String>] (optional) if given, description for each datapoint (shown in popups)
188
+ # :shape [Array<String>] (optional) if given, DataPoint shape is chosen based on this string instead of description
189
+ # :url [Array<String>] (optional) if given, link will be added to each datapoint
190
+ def add_data(conf)
191
+ @data ||= []
192
+ raise "No data provided by #{conf.inspect}" unless conf[:data].is_a?(Array)
193
+
194
+ add_data_init_or_check_optional_keys(conf, conf[:data].size)
195
+ @data << conf
196
+ end
178
197
 
179
- if conf[:data] and conf[:data].kind_of? Array
180
- @data << conf
181
- else
182
- raise "No data provided by #{conf.inspect}"
198
+ # Checks all optional keys of the add_data method
199
+ def add_data_init_or_check_optional_keys(conf, datasize)
200
+ conf[:description] ||= Array.new(datasize)
201
+ conf[:shape] ||= Array.new(datasize)
202
+ conf[:url] ||= Array.new(datasize)
203
+
204
+ if conf[:description].size != datasize
205
+ raise "Description for popups does not have same size as provided data: #{conf[:description].size} vs #{conf[:data].size/2}"
183
206
  end
184
- end
185
207
 
208
+ if conf[:shape].size != datasize
209
+ raise "Shapes for points do not have same size as provided data: #{conf[:shape].size} vs #{conf[:data].size/2}"
210
+ end
211
+
212
+ if conf[:url].size != datasize
213
+ raise "URLs for points do not have same size as provided data: #{conf[:url].size} vs #{conf[:data].size/2}"
214
+ end
215
+ end
186
216
 
187
217
  # This method removes all data from the object so that you can
188
218
  # reuse it to create a new graph but with the same config options.
@@ -247,6 +277,14 @@ module SVG
247
277
  return out
248
278
  end
249
279
 
280
+ # Burns the graph to an SVG string and returns it with a text/html mime type to be
281
+ # displayed in IRuby.
282
+ #
283
+ # @return [Array] A 2-dimension array containing the SVg string and a mime-type. This is the format expected by IRuby.
284
+ def to_iruby
285
+ ["text/html", burn_svg_only]
286
+ end
287
+
250
288
 
251
289
  # Set the height of the graph box, this is the total height
252
290
  # of the SVG box created - not the graph it self which auto
@@ -256,13 +294,23 @@ module SVG
256
294
  # of the SVG box created - not the graph it self which auto
257
295
  # scales to fix the space.
258
296
  attr_accessor :width
259
- # Set the path to an external stylesheet, set to '' if
297
+ # Set the path/url to an external stylesheet, set to '' if
260
298
  # you want to revert back to using the defaut internal version.
261
299
  #
262
300
  # To create an external stylesheet create a graph using the
263
301
  # default internal version and copy the stylesheet section to
264
302
  # an external file and edit from there.
265
303
  attr_accessor :style_sheet
304
+ # Define as String the stylesheet contents to be inlined, set to '' to disable.
305
+ # This can be used, when referring to a url via :style_sheet is not suitable.
306
+ # E.g. in situations where there will be no internet access or the graph must
307
+ # consist of only one file.
308
+ #
309
+ # If not empty, the :style_sheet parameter (url) above will be ignored and is
310
+ # not written to the file
311
+ # see also https://github.com/erullmann/svg-graph2/commit/55eb6e983f6fcc69cc5a110d0ee6e05f906f639a
312
+ # Default: ''
313
+ attr_accessor :inline_style_sheet
266
314
  # (Bool) Show the value of each element of data on the graph
267
315
  attr_accessor :show_data_values
268
316
  # By default (nil/undefined) the x-axis is at the bottom of the graph.
@@ -292,11 +340,13 @@ module SVG
292
340
  # are long field names they will not overlap so easily.
293
341
  # Default is false, to turn on set to true.
294
342
  attr_accessor :stagger_y_labels
295
- # This turns the X axis labels by 90 degrees.
343
+ # This turns the X axis labels by 90 degrees when true or by a custom
344
+ # amount when a numeric value is given.
296
345
  # Default is false, to turn on set to true.
297
346
  attr_accessor :rotate_x_labels
298
- # This turns the Y axis labels by 90 degrees.
299
- # Default is true, to turn on set to false.
347
+ # This turns the Y axis labels by 90 degrees when true or by a custom
348
+ # amount when a numeric value is given.
349
+ # Default is false, to turn on set to true or numeric value.
300
350
  attr_accessor :rotate_y_labels
301
351
  # How many "steps" to use between displayed X axis labels,
302
352
  # a step of one means display every label, a step of two results
@@ -358,6 +408,13 @@ module SVG
358
408
  # Where the key should be positioned, defaults to
359
409
  # :right, set to :bottom if you want to move it.
360
410
  attr_accessor :key_position
411
+
412
+ attr_accessor :key_box_size
413
+
414
+ attr_accessor :key_spacing
415
+
416
+ attr_accessor :key_width
417
+
361
418
  # Set the font size (in points) of the data point labels.
362
419
  # Defaults to 12.
363
420
  attr_accessor :font_size
@@ -402,24 +459,20 @@ module SVG
402
459
 
403
460
  protected
404
461
 
405
- # implementation of quicksort
406
- # used for Schedule and Plot
462
+ # implementation of a multiple array sort used for Schedule and Plot
407
463
  def sort( *arrys )
408
- sort_multiple( arrys )
464
+ new_arrys = arrys.transpose.sort_by(&:first).transpose
465
+ new_arrys.each_index { |k| arrys[k].replace(new_arrys[k]) }
409
466
  end
410
467
 
411
468
  # Overwrite configuration options with supplied options. Used
412
469
  # by subclasses.
413
470
  def init_with config
414
471
  config.each { |key, value|
415
- self.send( key.to_s+"=", value ) if self.respond_to? key
472
+ self.send( key.to_s+"=", value ) if self.respond_to? key
416
473
  }
417
- @popup_radius ||= 10
418
474
  end
419
475
 
420
- # size of the square box in the legend which indicates the colors
421
- KEY_BOX_SIZE = 12
422
-
423
476
  # Override this (and call super) to change the margin to the left
424
477
  # of the plot area. Results in @border_left being set.
425
478
  #
@@ -438,12 +491,20 @@ module SVG
438
491
  # are not shown
439
492
  def max_y_label_width_px
440
493
  return 0 if !show_y_labels
441
- if !rotate_y_labels
442
- max_width = get_longest_label(get_y_labels).to_s.length * y_label_font_size * 0.6
443
- else
444
- max_width = y_label_font_size + 3
494
+ base_width = y_label_font_size + 3
495
+ if rotate_y_labels == true
496
+ self.rotate_y_labels = 90
497
+ end
498
+ if rotate_y_labels == false
499
+ self.rotate_y_labels = 0
500
+ end
501
+ # don't change rotate_y_label, if neither true nor false
502
+ label_width = get_longest_label(get_y_labels).to_s.length * y_label_font_size * 0.5
503
+ rotated_width = label_width * Math.cos( rotate_y_labels * Math::PI / 180).abs()
504
+ max_width = base_width + rotated_width
505
+ if stagger_y_labels
506
+ max_width += 5 + y_label_font_size
445
507
  end
446
- max_width += 5 + y_label_font_size if stagger_y_labels
447
508
  return max_width
448
509
  end
449
510
 
@@ -458,8 +519,14 @@ module SVG
458
519
  if key and key_position == :right
459
520
  val = keys.max { |a,b| a.length <=> b.length }
460
521
  @border_right += val.length * key_font_size * 0.6
461
- @border_right += KEY_BOX_SIZE
522
+ @border_right += key_box_size
462
523
  @border_right += 10 # Some padding around the box
524
+
525
+ if key_width.nil?
526
+ @border_right
527
+ else
528
+ @border_right = [key_width, @border_right].min
529
+ end
463
530
  end
464
531
  if (x_title_location == :end)
465
532
  @border_right = [@border_right, x_title.length * x_title_font_size * 0.6].max
@@ -484,37 +551,74 @@ module SVG
484
551
  end
485
552
 
486
553
  # Adds pop-up point information to a graph only if the config option is set.
487
- def add_popup( x, y, label, style="" )
554
+ def add_popup( x, y, label, style="", url="" )
488
555
  if add_popups
489
556
  if( numeric?(label) )
490
557
  label = @number_format % label
491
558
  end
492
559
  txt_width = label.length * font_size * 0.6 + 10
493
560
  tx = (x+txt_width > @graph_width ? x-5 : x+5)
494
- t = @foreground.add_element( "text", {
561
+ g = Element.new( "g" )
562
+ g.attributes["id"] = g.object_id.to_s
563
+ g.attributes["visibility"] = "hidden"
564
+
565
+ # First add the mask
566
+ t = g.add_element( "text", {
495
567
  "x" => tx.to_s,
496
568
  "y" => (y - font_size).to_s,
497
- "class" => "dataPointLabel",
498
- "visibility" => "hidden",
569
+ "class" => "dataPointPopupMask"
499
570
  })
500
- t.attributes["style"] = "stroke-width: 2; fill: #000; #{style}"+
571
+ t.attributes["style"] = style +
501
572
  (x+txt_width > @graph_width ? "text-anchor: end;" : "text-anchor: start;")
502
573
  t.text = label.to_s
503
- t.attributes["id"] = t.object_id.to_s
574
+
575
+ # Then add the text
576
+ t = g.add_element( "text", {
577
+ "x" => tx.to_s,
578
+ "y" => (y - font_size).to_s,
579
+ "class" => "dataPointPopup"
580
+ })
581
+ t.attributes["style"] = style +
582
+ (x+txt_width > @graph_width ? "text-anchor: end;" : "text-anchor: start;")
583
+ t.text = label.to_s
584
+
585
+ @foreground.add_element( g )
504
586
 
505
587
  # add a circle to catch the mouseover
506
- @foreground.add_element( "circle", {
588
+ mouseover = Element.new( "circle" )
589
+ mouseover.add_attributes({
507
590
  "cx" => x.to_s,
508
591
  "cy" => y.to_s,
509
592
  "r" => "#{popup_radius}",
510
593
  "style" => "opacity: 0",
511
594
  "onmouseover" =>
512
- "document.getElementById(#{t.object_id}).setAttribute('visibility', 'visible' )",
595
+ "document.getElementById(#{g.object_id.to_s}).style.visibility ='visible'",
513
596
  "onmouseout" =>
514
- "document.getElementById(#{t.object_id}).setAttribute('visibility', 'hidden' )",
597
+ "document.getElementById(#{g.object_id.to_s}).style.visibility = 'hidden'",
515
598
  })
599
+ if !url.nil?
600
+ href = Element.new("a")
601
+ href.add_attribute("xlink:href", url)
602
+ href.add_element(mouseover)
603
+ @foreground.add_element(href)
604
+ else
605
+ @foreground.add_element(mouseover)
606
+ end
607
+ elsif !url.nil?
608
+ # add a circle to catch the mouseover
609
+ mouseover = Element.new( "circle" )
610
+ mouseover.add_attributes({
611
+ "cx" => x.to_s,
612
+ "cy" => y.to_s,
613
+ "r" => "#{popup_radius}",
614
+ "style" => "opacity: 0",
615
+ })
616
+ href = Element.new("a")
617
+ href.add_attribute("xlink:href", url)
618
+ href.add_element(mouseover)
619
+ @foreground.add_element(href)
516
620
  end # if add_popups
517
- end # add_popup
621
+ end # def add_popup
518
622
 
519
623
  # returns the longest label from an array of labels as string
520
624
  # each object in the array must support .to_s
@@ -676,13 +780,14 @@ module SVG
676
780
  elsif x > @graph_width - textStr.length/2 * font_size
677
781
  style << "text-anchor: end;"
678
782
  end
679
- # white background for better readability
680
- @foreground.add_element( "text", {
783
+ # background for better readability
784
+ text = @foreground.add_element( "text", {
681
785
  "x" => x.to_s,
682
786
  "y" => y.to_s,
683
- "class" => "dataPointLabel",
684
- "style" => "#{style} stroke: #fff; stroke-width: 2;"
685
- }).text = textStr
787
+ "class" => "dataPointLabelBackground",
788
+ })
789
+ text.text = textStr
790
+ text.attributes["style"] = style if style.length > 0
686
791
  # actual label
687
792
  text = @foreground.add_element( "text", {
688
793
  "x" => x.to_s,
@@ -734,8 +839,12 @@ module SVG
734
839
  text.attributes["x"] = x.to_s
735
840
  text.attributes["y"] = y.to_s
736
841
  if rotate_x_labels
842
+ degrees = 90
843
+ if numeric? rotate_x_labels
844
+ degrees = rotate_x_labels
845
+ end
737
846
  text.attributes["transform"] =
738
- "rotate( 90 #{x} #{y-x_label_font_size} )"+
847
+ "rotate( #{degrees} #{x} #{y-x_label_font_size} )"+
739
848
  " translate( 0 -#{x_label_font_size/4} )"
740
849
  text.attributes["style"] = "text-anchor: start"
741
850
  else
@@ -791,18 +900,20 @@ module SVG
791
900
  def draw_y_labels
792
901
  stagger = y_label_font_size + 5
793
902
  label_height = field_height
903
+ label_width = max_y_label_width_px
794
904
  count = 0
795
905
  y_offset = @graph_height + y_label_offset( label_height )
796
- y_offset += font_size/1.2 unless rotate_y_labels
906
+ y_offset += font_size/3.0
797
907
  for label in get_y_labels
798
908
  if show_y_labels
909
+ # x = 0, y = 0 is top left right next to graph area
799
910
  y = y_offset - (label_height * count)
800
- x = rotate_y_labels ? 0 : -3
911
+ x = -label_width/2.0 + y_label_font_size/2.0
801
912
 
802
913
  if stagger_y_labels and count % 2 == 1
803
914
  x -= stagger
804
915
  @graph.add_element( "path", {
805
- "d" => "M#{x} #{y} h#{stagger}",
916
+ "d" => "M0 #{y} h#{-stagger}",
806
917
  "class" => "staggerGuideLine"
807
918
  })
808
919
  end
@@ -817,14 +928,13 @@ module SVG
817
928
  textStr = @number_format % label
818
929
  end
819
930
  text.text = textStr
820
- if rotate_y_labels
821
- text.attributes["transform"] = "translate( -#{font_size} 0 ) "+
822
- "rotate( 90 #{x} #{y} ) "
823
- text.attributes["style"] = "text-anchor: middle"
824
- else
825
- text.attributes["y"] = (y - (y_label_font_size/2)).to_s
826
- text.attributes["style"] = "text-anchor: end"
827
- end
931
+ # note text-anchor is at bottom of textfield
932
+ text.attributes["style"] = "text-anchor: middle"
933
+ degrees = rotate_y_labels
934
+ text.attributes["transform"] = "translate( -#{font_size} 0 ) " +
935
+ "rotate( #{degrees} #{x} #{y} ) "
936
+ # text.attributes["y"] = (y - (y_label_font_size/2)).to_s
937
+
828
938
  end # if show_y_labels
829
939
  draw_y_guidelines( label_height, count ) if show_y_guidelines
830
940
  count += 1
@@ -929,17 +1039,17 @@ module SVG
929
1039
 
930
1040
  key_count = 0
931
1041
  for key_name in keys
932
- y_offset = (KEY_BOX_SIZE * key_count) + (key_count * 5)
1042
+ y_offset = (key_box_size * key_count) + (key_count * key_spacing)
933
1043
  group.add_element( "rect", {
934
1044
  "x" => 0.to_s,
935
1045
  "y" => y_offset.to_s,
936
- "width" => KEY_BOX_SIZE.to_s,
937
- "height" => KEY_BOX_SIZE.to_s,
1046
+ "width" => key_box_size.to_s,
1047
+ "height" => key_box_size.to_s,
938
1048
  "class" => "key#{key_count+1}"
939
1049
  })
940
1050
  group.add_element( "text", {
941
- "x" => (KEY_BOX_SIZE + 5).to_s,
942
- "y" => (y_offset + KEY_BOX_SIZE).to_s,
1051
+ "x" => (key_box_size + key_spacing).to_s,
1052
+ "y" => (y_offset + key_box_size).to_s,
943
1053
  "class" => "keyText"
944
1054
  }).text = key_name.to_s
945
1055
  key_count += 1
@@ -947,15 +1057,15 @@ module SVG
947
1057
 
948
1058
  case key_position
949
1059
  when :right
950
- x_offset = @graph_width + @border_left + 10
951
- y_offset = @border_top + 20
1060
+ x_offset = @graph_width + @border_left + (key_spacing * 2)
1061
+ y_offset = @border_top + (key_spacing * 2)
952
1062
  when :bottom
953
- x_offset = @border_left + 20
954
- y_offset = @border_top + @graph_height + 5
1063
+ x_offset = @border_left + (key_spacing * 2)
1064
+ y_offset = @border_top + @graph_height + key_spacing
955
1065
  if show_x_labels
956
1066
  y_offset += max_x_label_height_px
957
1067
  end
958
- y_offset += x_title_font_size + 5 if show_x_title
1068
+ y_offset += x_title_font_size + key_spacing if show_x_title
959
1069
  end
960
1070
  group.attributes["transform"] = "translate(#{x_offset} #{y_offset})"
961
1071
  end
@@ -964,30 +1074,6 @@ module SVG
964
1074
 
965
1075
  private
966
1076
 
967
- def sort_multiple( arrys, lo=0, hi=arrys[0].length-1 )
968
- if lo < hi
969
- p = partition(arrys,lo,hi)
970
- sort_multiple(arrys, lo, p-1)
971
- sort_multiple(arrys, p+1, hi)
972
- end
973
- arrys
974
- end
975
-
976
- def partition( arrys, lo, hi )
977
- p = arrys[0][lo]
978
- l = lo
979
- z = lo+1
980
- while z <= hi
981
- if arrys[0][z] < p
982
- l += 1
983
- arrys.each { |arry| arry[z], arry[l] = arry[l], arry[z] }
984
- end
985
- z += 1
986
- end
987
- arrys.each { |arry| arry[lo], arry[l] = arry[l], arry[lo] }
988
- l
989
- end
990
-
991
1077
  def style
992
1078
  if no_css
993
1079
  styles = parse_css
@@ -1045,7 +1131,8 @@ module SVG
1045
1131
  @doc << XMLDecl.new
1046
1132
  @doc << DocType.new( %q{svg PUBLIC "-//W3C//DTD SVG 1.0//EN" } +
1047
1133
  %q{"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"} )
1048
- if style_sheet && style_sheet != ''
1134
+ if style_sheet && style_sheet != '' && inline_style_sheet.to_s.empty?
1135
+ # if inline_style_sheet is defined, url style sheet is ignored
1049
1136
  @doc << Instruction.new( "xml-stylesheet",
1050
1137
  %Q{href="#{style_sheet}" type="text/css"} )
1051
1138
  end
@@ -1067,10 +1154,15 @@ module SVG
1067
1154
 
1068
1155
  defs = @root.add_element( "defs" )
1069
1156
  add_defs defs
1070
- if not(style_sheet && style_sheet != '') and !no_css
1071
- @root << Comment.new(" include default stylesheet if none specified ")
1072
- style = defs.add_element( "style", {"type"=>"text/css"} )
1073
- style << CData.new( get_style )
1157
+ if !no_css
1158
+ if inline_style_sheet && inline_style_sheet != ''
1159
+ style = defs.add_element( "style", {"type"=>"text/css"} )
1160
+ style << CData.new( inline_style_sheet )
1161
+ else
1162
+ @root << Comment.new(" include default stylesheet if none specified ")
1163
+ style = defs.add_element( "style", {"type"=>"text/css"} )
1164
+ style << CData.new( get_style )
1165
+ end
1074
1166
  end
1075
1167
 
1076
1168
  @root << Comment.new( "SVG Background" )
@@ -1162,7 +1254,7 @@ module SVG
1162
1254
  font-weight: normal;
1163
1255
  }
1164
1256
 
1165
- .dataPointLabel{
1257
+ .dataPointLabel, .dataPointLabelBackground, .dataPointPopup, .dataPointPopupMask{
1166
1258
  fill: #000000;
1167
1259
  text-anchor:middle;
1168
1260
  font-size: 10px;
@@ -1170,6 +1262,21 @@ module SVG
1170
1262
  font-weight: normal;
1171
1263
  }
1172
1264
 
1265
+ .dataPointLabelBackground{
1266
+ stroke: #ffffff;
1267
+ stroke-width: 2;
1268
+ }
1269
+
1270
+ .dataPointPopupMask{
1271
+ stroke: white;
1272
+ stroke-width: 7;
1273
+ }
1274
+
1275
+ .dataPointPopup{
1276
+ fill: black;
1277
+ stroke-width: 2;
1278
+ }
1279
+
1173
1280
  .staggerGuideLine{
1174
1281
  fill: none;
1175
1282
  stroke: #000000;