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.
- checksums.yaml +5 -5
- data/History.txt +35 -1
- data/{README.markdown → README.md} +27 -6
- data/Rakefile +16 -5
- data/lib/SVG/Graph/Bar.rb +10 -3
- data/lib/SVG/Graph/BarBase.rb +21 -17
- data/lib/SVG/Graph/BarHorizontal.rb +12 -5
- data/lib/SVG/Graph/C3js.rb +274 -0
- data/lib/SVG/Graph/DataPoint.rb +28 -16
- data/lib/SVG/Graph/Graph.rb +211 -104
- data/lib/SVG/Graph/Line.rb +12 -7
- data/lib/SVG/Graph/Pie.rb +52 -41
- data/lib/SVG/Graph/Plot.rb +105 -94
- data/lib/SVG/Graph/TimeSeries.rb +27 -27
- data/lib/svggraph.rb +4 -2
- data/test/test_svg_graph.rb +7 -7
- metadata +14 -14
data/lib/SVG/Graph/DataPoint.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# Allows to customize datapoint shapes
|
2
2
|
class DataPoint
|
3
|
-
|
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,
|
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|
|
23
|
-
#
|
24
|
-
#
|
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(
|
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
|
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
|
81
|
+
proc.call(@x, @y, @line) if datapoint_description =~ regexp
|
70
82
|
}.compact
|
71
83
|
|
72
84
|
return shapes + overlays
|
data/lib/SVG/Graph/Graph.rb
CHANGED
@@ -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
|
-
:
|
159
|
-
|
160
|
-
:
|
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
|
-
|
177
|
-
|
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
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
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
|
-
#
|
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
|
406
|
-
# used for Schedule and Plot
|
462
|
+
# implementation of a multiple array sort used for Schedule and Plot
|
407
463
|
def sort( *arrys )
|
408
|
-
|
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
|
-
|
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
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
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 +=
|
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
|
-
|
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" => "
|
498
|
-
"visibility" => "hidden",
|
569
|
+
"class" => "dataPointPopupMask"
|
499
570
|
})
|
500
|
-
t.attributes["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
|
-
|
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
|
-
|
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(#{
|
595
|
+
"document.getElementById(#{g.object_id.to_s}).style.visibility ='visible'",
|
513
596
|
"onmouseout" =>
|
514
|
-
"document.getElementById(#{
|
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
|
-
#
|
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" => "
|
684
|
-
|
685
|
-
|
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(
|
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/
|
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 =
|
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" => "
|
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
|
-
|
821
|
-
|
822
|
-
|
823
|
-
|
824
|
-
|
825
|
-
|
826
|
-
|
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 = (
|
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" =>
|
937
|
-
"height" =>
|
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" => (
|
942
|
-
"y" => (y_offset +
|
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 +
|
951
|
-
y_offset = @border_top +
|
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 +
|
954
|
-
y_offset = @border_top + @graph_height +
|
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 +
|
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
|
1071
|
-
|
1072
|
-
|
1073
|
-
|
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;
|