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.
- 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;
|