svg-graph 2.1.1 → 2.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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;