svg-graph 2.2.0 → 2.2.2
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 +4 -4
- data/History.txt +15 -0
- data/README.md +18 -5
- data/Rakefile +16 -6
- data/lib/SVG/Graph/DataPoint.rb +28 -16
- data/lib/SVG/Graph/Graph.rb +132 -71
- data/lib/SVG/Graph/Line.rb +16 -10
- data/lib/SVG/Graph/Pie.rb +13 -2
- data/lib/SVG/Graph/Plot.rb +69 -31
- data/lib/SVG/Graph/Schedule.rb +1 -1
- data/lib/SVG/Graph/TimeSeries.rb +29 -29
- data/lib/svggraph.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9d2a4756bdc712df308bb715f9076bac952fbf64dd8d8b2900d9c424b30026b7
|
|
4
|
+
data.tar.gz: 5c3b12638b3eb65936f32a84e50f222113b2c7c4c0e6f69bbbd9c594c99bd6eb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fd35db671d05e9f8c2559508a4ab7bf7bfd7d4582f6433fe4b93c2cd4f182c0692100f806cf3d72704ff4ec74a267ec2b319100df7704972c2ad46530ddc1978
|
|
7
|
+
data.tar.gz: 5216c72900a99522c7f49ac48166f85167bfecaf087c2d2e3891abfeaad44adbe378e1230cc87569d7e99ce9f1f4bd318de708eeff615218c23449c48f345dfd
|
data/History.txt
CHANGED
|
@@ -2,6 +2,21 @@ TODO / Backlog
|
|
|
2
2
|
* refactor various hardcoded constant pixel offsets in Graph class to use named constants or variables
|
|
3
3
|
* Fix bug in Plot where min/max_x/y_value are not respected, TODO
|
|
4
4
|
|
|
5
|
+
=== 2.2.3 /
|
|
6
|
+
|
|
7
|
+
=== 2.2.2 / 2023-04-30
|
|
8
|
+
* fix line9 color typo in Line.rb [thanks, akostadinov #42]
|
|
9
|
+
* anchor right y labels [thanks, akostadinov #44]
|
|
10
|
+
* start line from first point, not 0 0 [thanks, akostadinov #43]
|
|
11
|
+
|
|
12
|
+
=== 2.2.1 / 2020-12-25
|
|
13
|
+
* Remove inline styling for data point labels and popups [thanks marnen, PR #23]
|
|
14
|
+
* fix #29 text background not aligned close to axis due to missing anchors
|
|
15
|
+
* add & document :shape and :url hash keys for `add_data` [thanks t12nslookup, PR #32]
|
|
16
|
+
* add custom datapoint support to Line graphs
|
|
17
|
+
* Add white text behind popup text so that the black text is more readable [thanks t12nslookup, PR #30]
|
|
18
|
+
* fix #19 rotated lables should no longer be cropped or overlapping
|
|
19
|
+
|
|
5
20
|
=== 2.2.0 / 2019-11-26
|
|
6
21
|
* on top of 2.2.0.beta adds the following
|
|
7
22
|
* Fixed Divizion by zero when data is 0,0 in Pie [thanks chrismedrdz, PR #16]
|
data/README.md
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
SVG::Graph
|
|
2
2
|
============
|
|
3
3
|
|
|
4
|
+
[](https://travis-ci.com/lumean/svg-graph2)
|
|
5
|
+
[](https://codeclimate.com/github/lumean/svg-graph2/maintainability)
|
|
6
|
+
[](https://codeclimate.com/github/lumean/svg-graph2/test_coverage)
|
|
7
|
+
[](https://percy.io/a5e00e98/svg-graph2)
|
|
8
|
+
|
|
4
9
|
Description
|
|
5
10
|
-----------
|
|
6
11
|
This repo is the continuation of the original [SVG::Graph library](http://www.germane-software.com/software/SVG/SVG::Graph/) by Sean Russell. I'd like to thank Claudio Bustos for giving me permissions to continue publishing the gem under it's original name: [svg-graph](https://rubygems.org/gems/svg-graph)
|
|
@@ -106,14 +111,22 @@ Source: [C3js.rb](../master/examples/c3js.rb)
|
|
|
106
111
|
|
|
107
112
|
[Link to Preview](https://cdn.rawgit.com/lumean/svg-graph2/master/examples/c3js.html)
|
|
108
113
|
|
|
109
|
-
<iframe src="https://cdn.rawgit.com/lumean/svg-graph2/master/examples/c3js.html" width="600px"> </iframe>
|
|
110
|
-
|
|
111
|
-
Also have a look at the original [SVG::Graph web page](http://www.germane-software.com/software/SVG/SVG::Graph/), but note that this repository has already added some additional functionality, not available with the original.
|
|
112
114
|
|
|
113
115
|
Build
|
|
114
116
|
-----
|
|
117
|
+
* Test
|
|
118
|
+
|
|
119
|
+
`bundle exec rake`
|
|
115
120
|
|
|
116
121
|
* Build gem:
|
|
117
|
-
|
|
122
|
+
|
|
123
|
+
`gem build svg-graph.gemspec`
|
|
124
|
+
|
|
118
125
|
* Install:
|
|
119
|
-
|
|
126
|
+
|
|
127
|
+
`gem install svg-graph-\<version>.gem`
|
|
128
|
+
|
|
129
|
+
Percy.io integration
|
|
130
|
+
---
|
|
131
|
+
https://docs.percy.io/docs/travis-ci
|
|
132
|
+
https://docs.percy.io/docs/snapshot-cli-command
|
data/Rakefile
CHANGED
|
@@ -21,12 +21,22 @@
|
|
|
21
21
|
# self.remote_rdoc_dir = 'svg-graph'
|
|
22
22
|
#end
|
|
23
23
|
|
|
24
|
-
# run all unit tests with 'rake test'
|
|
25
|
-
task default:
|
|
24
|
+
# by default run all unit tests with 'rake test'
|
|
25
|
+
task default: [:test]
|
|
26
26
|
|
|
27
27
|
task :test do
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
[
|
|
29
|
+
"test/test_data_point.rb",
|
|
30
|
+
"test/test_plot.rb",
|
|
31
|
+
"test/test_svg_graph.rb",
|
|
32
|
+
"test/test_graph.rb",
|
|
33
|
+
"test/run_examples_and_percy.io.rb"
|
|
34
|
+
].each do |file|
|
|
35
|
+
# exec all above scripts (with simplecov if env is set)
|
|
36
|
+
args = file
|
|
37
|
+
if ENV['COVERAGE']
|
|
38
|
+
args = '-r ./test/simplecov ' + file
|
|
39
|
+
end
|
|
40
|
+
ruby args
|
|
41
|
+
end
|
|
32
42
|
end
|
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
|
|
@@ -180,16 +181,38 @@ module SVG
|
|
|
180
181
|
# :data => data_sales_02,
|
|
181
182
|
# :title => 'Sales 2002'
|
|
182
183
|
# })
|
|
183
|
-
|
|
184
|
-
|
|
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
|
|
185
197
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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}"
|
|
190
206
|
end
|
|
191
|
-
end
|
|
192
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
|
|
193
216
|
|
|
194
217
|
# This method removes all data from the object so that you can
|
|
195
218
|
# reuse it to create a new graph but with the same config options.
|
|
@@ -323,7 +346,7 @@ module SVG
|
|
|
323
346
|
attr_accessor :rotate_x_labels
|
|
324
347
|
# This turns the Y axis labels by 90 degrees when true or by a custom
|
|
325
348
|
# amount when a numeric value is given.
|
|
326
|
-
# Default is
|
|
349
|
+
# Default is false, to turn on set to true or numeric value.
|
|
327
350
|
attr_accessor :rotate_y_labels
|
|
328
351
|
# How many "steps" to use between displayed X axis labels,
|
|
329
352
|
# a step of one means display every label, a step of two results
|
|
@@ -436,17 +459,17 @@ module SVG
|
|
|
436
459
|
|
|
437
460
|
protected
|
|
438
461
|
|
|
439
|
-
# implementation of
|
|
440
|
-
# used for Schedule and Plot
|
|
462
|
+
# implementation of a multiple array sort used for Schedule and Plot
|
|
441
463
|
def sort( *arrys )
|
|
442
|
-
|
|
464
|
+
new_arrys = arrys.transpose.sort_by(&:first).transpose
|
|
465
|
+
new_arrys.each_index { |k| arrys[k].replace(new_arrys[k]) }
|
|
443
466
|
end
|
|
444
467
|
|
|
445
468
|
# Overwrite configuration options with supplied options. Used
|
|
446
469
|
# by subclasses.
|
|
447
470
|
def init_with config
|
|
448
471
|
config.each { |key, value|
|
|
449
|
-
|
|
472
|
+
self.send( key.to_s+"=", value ) if self.respond_to? key
|
|
450
473
|
}
|
|
451
474
|
end
|
|
452
475
|
|
|
@@ -468,12 +491,20 @@ module SVG
|
|
|
468
491
|
# are not shown
|
|
469
492
|
def max_y_label_width_px
|
|
470
493
|
return 0 if !show_y_labels
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
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
|
|
475
507
|
end
|
|
476
|
-
max_width += 5 + y_label_font_size if stagger_y_labels
|
|
477
508
|
return max_width
|
|
478
509
|
end
|
|
479
510
|
|
|
@@ -520,37 +551,74 @@ module SVG
|
|
|
520
551
|
end
|
|
521
552
|
|
|
522
553
|
# Adds pop-up point information to a graph only if the config option is set.
|
|
523
|
-
def add_popup( x, y, label, style="" )
|
|
554
|
+
def add_popup( x, y, label, style="", url="" )
|
|
524
555
|
if add_popups
|
|
525
556
|
if( numeric?(label) )
|
|
526
557
|
label = @number_format % label
|
|
527
558
|
end
|
|
528
559
|
txt_width = label.length * font_size * 0.6 + 10
|
|
529
560
|
tx = (x+txt_width > @graph_width ? x-5 : x+5)
|
|
530
|
-
|
|
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", {
|
|
531
567
|
"x" => tx.to_s,
|
|
532
568
|
"y" => (y - font_size).to_s,
|
|
533
|
-
"class" => "
|
|
534
|
-
"visibility" => "hidden",
|
|
569
|
+
"class" => "dataPointPopupMask"
|
|
535
570
|
})
|
|
536
|
-
t.attributes["style"] =
|
|
571
|
+
t.attributes["style"] = style +
|
|
537
572
|
(x+txt_width > @graph_width ? "text-anchor: end;" : "text-anchor: start;")
|
|
538
573
|
t.text = label.to_s
|
|
539
|
-
|
|
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 )
|
|
540
586
|
|
|
541
587
|
# add a circle to catch the mouseover
|
|
542
|
-
|
|
588
|
+
mouseover = Element.new( "circle" )
|
|
589
|
+
mouseover.add_attributes({
|
|
543
590
|
"cx" => x.to_s,
|
|
544
591
|
"cy" => y.to_s,
|
|
545
592
|
"r" => "#{popup_radius}",
|
|
546
593
|
"style" => "opacity: 0",
|
|
547
594
|
"onmouseover" =>
|
|
548
|
-
"document.getElementById(#{
|
|
595
|
+
"document.getElementById(#{g.object_id.to_s}).style.visibility ='visible'",
|
|
549
596
|
"onmouseout" =>
|
|
550
|
-
"document.getElementById(#{
|
|
597
|
+
"document.getElementById(#{g.object_id.to_s}).style.visibility = 'hidden'",
|
|
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",
|
|
551
615
|
})
|
|
616
|
+
href = Element.new("a")
|
|
617
|
+
href.add_attribute("xlink:href", url)
|
|
618
|
+
href.add_element(mouseover)
|
|
619
|
+
@foreground.add_element(href)
|
|
552
620
|
end # if add_popups
|
|
553
|
-
end # add_popup
|
|
621
|
+
end # def add_popup
|
|
554
622
|
|
|
555
623
|
# returns the longest label from an array of labels as string
|
|
556
624
|
# each object in the array must support .to_s
|
|
@@ -712,13 +780,14 @@ module SVG
|
|
|
712
780
|
elsif x > @graph_width - textStr.length/2 * font_size
|
|
713
781
|
style << "text-anchor: end;"
|
|
714
782
|
end
|
|
715
|
-
#
|
|
716
|
-
@foreground.add_element( "text", {
|
|
783
|
+
# background for better readability
|
|
784
|
+
text = @foreground.add_element( "text", {
|
|
717
785
|
"x" => x.to_s,
|
|
718
786
|
"y" => y.to_s,
|
|
719
|
-
"class" => "
|
|
720
|
-
|
|
721
|
-
|
|
787
|
+
"class" => "dataPointLabelBackground",
|
|
788
|
+
})
|
|
789
|
+
text.text = textStr
|
|
790
|
+
text.attributes["style"] = style if style.length > 0
|
|
722
791
|
# actual label
|
|
723
792
|
text = @foreground.add_element( "text", {
|
|
724
793
|
"x" => x.to_s,
|
|
@@ -831,18 +900,23 @@ module SVG
|
|
|
831
900
|
def draw_y_labels
|
|
832
901
|
stagger = y_label_font_size + 5
|
|
833
902
|
label_height = field_height
|
|
903
|
+
label_width = max_y_label_width_px
|
|
834
904
|
count = 0
|
|
835
905
|
y_offset = @graph_height + y_label_offset( label_height )
|
|
836
|
-
y_offset += font_size/
|
|
906
|
+
y_offset += font_size/3.0
|
|
837
907
|
for label in get_y_labels
|
|
838
908
|
if show_y_labels
|
|
909
|
+
# x = 0, y = 0 is top left right next to graph area
|
|
839
910
|
y = y_offset - (label_height * count)
|
|
840
|
-
|
|
911
|
+
# instead of calculating the middle anchor position, simply use
|
|
912
|
+
# static offset and anchor end to right-align the labels. See line :936 below.
|
|
913
|
+
#x = -label_width/2.0 + y_label_font_size/2.0
|
|
914
|
+
x = 3
|
|
841
915
|
|
|
842
916
|
if stagger_y_labels and count % 2 == 1
|
|
843
917
|
x -= stagger
|
|
844
918
|
@graph.add_element( "path", {
|
|
845
|
-
"d" => "
|
|
919
|
+
"d" => "M0 #{y} h#{-stagger}",
|
|
846
920
|
"class" => "staggerGuideLine"
|
|
847
921
|
})
|
|
848
922
|
end
|
|
@@ -857,18 +931,14 @@ module SVG
|
|
|
857
931
|
textStr = @number_format % label
|
|
858
932
|
end
|
|
859
933
|
text.text = textStr
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
text.attributes["transform"] = "translate( -#{font_size} 0 ) "+
|
|
934
|
+
# note text-anchor is at bottom of textfield
|
|
935
|
+
#text.attributes["style"] = "text-anchor: middle"
|
|
936
|
+
text.attributes["style"] = "text-anchor: end"
|
|
937
|
+
degrees = rotate_y_labels
|
|
938
|
+
text.attributes["transform"] = "translate( -#{font_size} 0 ) " +
|
|
866
939
|
"rotate( #{degrees} #{x} #{y} ) "
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
text.attributes["y"] = (y - (y_label_font_size/2)).to_s
|
|
870
|
-
text.attributes["style"] = "text-anchor: end"
|
|
871
|
-
end
|
|
940
|
+
# text.attributes["y"] = (y - (y_label_font_size/2)).to_s
|
|
941
|
+
|
|
872
942
|
end # if show_y_labels
|
|
873
943
|
draw_y_guidelines( label_height, count ) if show_y_guidelines
|
|
874
944
|
count += 1
|
|
@@ -1008,30 +1078,6 @@ module SVG
|
|
|
1008
1078
|
|
|
1009
1079
|
private
|
|
1010
1080
|
|
|
1011
|
-
def sort_multiple( arrys, lo=0, hi=arrys[0].length-1 )
|
|
1012
|
-
if lo < hi
|
|
1013
|
-
p = partition(arrys,lo,hi)
|
|
1014
|
-
sort_multiple(arrys, lo, p-1)
|
|
1015
|
-
sort_multiple(arrys, p+1, hi)
|
|
1016
|
-
end
|
|
1017
|
-
arrys
|
|
1018
|
-
end
|
|
1019
|
-
|
|
1020
|
-
def partition( arrys, lo, hi )
|
|
1021
|
-
p = arrys[0][lo]
|
|
1022
|
-
l = lo
|
|
1023
|
-
z = lo+1
|
|
1024
|
-
while z <= hi
|
|
1025
|
-
if arrys[0][z] < p
|
|
1026
|
-
l += 1
|
|
1027
|
-
arrys.each { |arry| arry[z], arry[l] = arry[l], arry[z] }
|
|
1028
|
-
end
|
|
1029
|
-
z += 1
|
|
1030
|
-
end
|
|
1031
|
-
arrys.each { |arry| arry[lo], arry[l] = arry[l], arry[lo] }
|
|
1032
|
-
l
|
|
1033
|
-
end
|
|
1034
|
-
|
|
1035
1081
|
def style
|
|
1036
1082
|
if no_css
|
|
1037
1083
|
styles = parse_css
|
|
@@ -1212,7 +1258,7 @@ module SVG
|
|
|
1212
1258
|
font-weight: normal;
|
|
1213
1259
|
}
|
|
1214
1260
|
|
|
1215
|
-
.dataPointLabel{
|
|
1261
|
+
.dataPointLabel, .dataPointLabelBackground, .dataPointPopup, .dataPointPopupMask{
|
|
1216
1262
|
fill: #000000;
|
|
1217
1263
|
text-anchor:middle;
|
|
1218
1264
|
font-size: 10px;
|
|
@@ -1220,6 +1266,21 @@ module SVG
|
|
|
1220
1266
|
font-weight: normal;
|
|
1221
1267
|
}
|
|
1222
1268
|
|
|
1269
|
+
.dataPointLabelBackground{
|
|
1270
|
+
stroke: #ffffff;
|
|
1271
|
+
stroke-width: 2;
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
.dataPointPopupMask{
|
|
1275
|
+
stroke: white;
|
|
1276
|
+
stroke-width: 7;
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
.dataPointPopup{
|
|
1280
|
+
fill: black;
|
|
1281
|
+
stroke-width: 2;
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1223
1284
|
.staggerGuideLine{
|
|
1224
1285
|
fill: none;
|
|
1225
1286
|
stroke: #000000;
|
data/lib/SVG/Graph/Line.rb
CHANGED
|
@@ -42,7 +42,7 @@ module SVG
|
|
|
42
42
|
#
|
|
43
43
|
# = Examples
|
|
44
44
|
#
|
|
45
|
-
#
|
|
45
|
+
# https://github.com/lumean/svg-graph2/blob/master/examples/line.rb
|
|
46
46
|
#
|
|
47
47
|
# = Notes
|
|
48
48
|
# Only number of fileds datapoints will be drawn, additional data values
|
|
@@ -238,8 +238,9 @@ module SVG
|
|
|
238
238
|
})
|
|
239
239
|
end
|
|
240
240
|
|
|
241
|
+
matcher = /^(\S+ \S+) (.*)/.match lpath
|
|
241
242
|
@graph.add_element("path", {
|
|
242
|
-
"d" => "
|
|
243
|
+
"d" => "M#{matcher[1]} L#{matcher[2]}",
|
|
243
244
|
"class" => "line#{line}"
|
|
244
245
|
})
|
|
245
246
|
|
|
@@ -249,17 +250,22 @@ module SVG
|
|
|
249
250
|
next if cum_sum[i].nil?
|
|
250
251
|
c = calc_coords(i, cum_sum[i], fieldwidth, fieldheight)
|
|
251
252
|
if show_data_points
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
253
|
+
shape_selection_string = data[:description][i].to_s
|
|
254
|
+
if !data[:shape][i].nil?
|
|
255
|
+
shape_selection_string = data[:shape][i].to_s
|
|
256
|
+
end
|
|
257
|
+
DataPoint.new(c[:x], c[:y], line).shape(shape_selection_string).each{|s|
|
|
258
|
+
@graph.add_element( *s )
|
|
259
|
+
}
|
|
258
260
|
end
|
|
259
261
|
|
|
260
262
|
make_datapoint_text( c[:x], c[:y] - font_size/2, cum_sum[i] + minvalue)
|
|
261
263
|
# number format shall not apply to popup (use .to_s conversion)
|
|
262
|
-
|
|
264
|
+
descr = ""
|
|
265
|
+
if !data[:description][i].to_s.empty?
|
|
266
|
+
descr = ", #{data[:description][i].to_s}"
|
|
267
|
+
end
|
|
268
|
+
add_popup(c[:x], c[:y], (cum_sum[i] + minvalue).to_s + descr, "", data[:url][i].to_s)
|
|
263
269
|
end
|
|
264
270
|
end
|
|
265
271
|
|
|
@@ -314,7 +320,7 @@ module SVG
|
|
|
314
320
|
}
|
|
315
321
|
.line9{
|
|
316
322
|
fill: none;
|
|
317
|
-
stroke: #
|
|
323
|
+
stroke: #cc6666;
|
|
318
324
|
stroke-width: 1px;
|
|
319
325
|
}
|
|
320
326
|
.line10{
|
data/lib/SVG/Graph/Pie.rb
CHANGED
|
@@ -36,7 +36,7 @@ module SVG
|
|
|
36
36
|
#
|
|
37
37
|
# = Examples
|
|
38
38
|
#
|
|
39
|
-
#
|
|
39
|
+
# https://github.com/lumean/svg-graph2/blob/master/examples/pie.rb
|
|
40
40
|
#
|
|
41
41
|
# == See also
|
|
42
42
|
#
|
|
@@ -343,7 +343,7 @@ module SVG
|
|
|
343
343
|
|
|
344
344
|
def get_css
|
|
345
345
|
return <<EOL
|
|
346
|
-
.dataPointLabel{
|
|
346
|
+
.dataPointLabel, .dataPointLabelBackground, .dataPointPopup{
|
|
347
347
|
fill: #000000;
|
|
348
348
|
text-anchor:middle;
|
|
349
349
|
font-size: #{datapoint_font_size}px;
|
|
@@ -351,6 +351,17 @@ module SVG
|
|
|
351
351
|
font-weight: normal;
|
|
352
352
|
}
|
|
353
353
|
|
|
354
|
+
.dataPointLabelBackground{
|
|
355
|
+
stroke: #ffffff;
|
|
356
|
+
stroke-width: 2;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
.dataPointPopup{
|
|
360
|
+
fill: #000000;
|
|
361
|
+
visibility: hidden;
|
|
362
|
+
stroke-width: 2;
|
|
363
|
+
}
|
|
364
|
+
|
|
354
365
|
/* key - MUST match fill styles */
|
|
355
366
|
.key1,.fill1{
|
|
356
367
|
fill: #ff0000;
|
data/lib/SVG/Graph/Plot.rb
CHANGED
|
@@ -55,14 +55,14 @@ module SVG
|
|
|
55
55
|
#
|
|
56
56
|
# = Examples
|
|
57
57
|
#
|
|
58
|
-
#
|
|
58
|
+
# https://github.com/lumean/svg-graph2/blob/master/examples/plot.rb
|
|
59
59
|
#
|
|
60
60
|
# = Notes
|
|
61
61
|
#
|
|
62
|
-
# The default stylesheet handles upto
|
|
62
|
+
# The default stylesheet handles upto 12 data sets, if you
|
|
63
63
|
# use more you must create your own stylesheet and add the
|
|
64
64
|
# additional settings for the extra data sets. You will know
|
|
65
|
-
# if you go over
|
|
65
|
+
# if you go over 12 data sets as they will have no style and
|
|
66
66
|
# be in black.
|
|
67
67
|
#
|
|
68
68
|
# Unlike the other types of charts, data sets must contain x,y pairs:
|
|
@@ -164,10 +164,15 @@ module SVG
|
|
|
164
164
|
# :data => data_set2,
|
|
165
165
|
# :title => 'two points'
|
|
166
166
|
# })
|
|
167
|
+
# @param conf [Hash] with keys
|
|
168
|
+
# :data [Array] of x,y pairs, one pair for each point
|
|
169
|
+
# :title [String] mandatory name of data series for legend of graph
|
|
170
|
+
# :description [Array<String>] (optional) if given, description for each datapoint (shown in popups)
|
|
171
|
+
# :shape [Array<String>] (optional) if given, DataPoint shape is chosen based on this string instead of description
|
|
172
|
+
# :url [Array<String>] (optional) if given, link will be added to each datapoint
|
|
167
173
|
def add_data(conf)
|
|
168
|
-
|
|
169
|
-
raise "No data provided by #{conf.inspect}" unless conf[:data]
|
|
170
|
-
conf[:data].kind_of? Array
|
|
174
|
+
@data ||= []
|
|
175
|
+
raise "No data provided by #{conf.inspect}" unless conf[:data].is_a?(Array)
|
|
171
176
|
# support array of arrays and flatten it
|
|
172
177
|
conf[:data] = conf[:data].flatten
|
|
173
178
|
# check that we have pairs of values
|
|
@@ -175,23 +180,23 @@ module SVG
|
|
|
175
180
|
"The data provided contained an odd set of "+
|
|
176
181
|
"data points" unless conf[:data].length % 2 == 0
|
|
177
182
|
|
|
183
|
+
# clear the min/max x/y range caches
|
|
184
|
+
clear_cache
|
|
185
|
+
|
|
178
186
|
# remove nil values
|
|
179
187
|
conf[:data] = conf[:data].compact
|
|
180
188
|
|
|
181
|
-
return if conf[:data].length
|
|
189
|
+
return if conf[:data].length.zero?
|
|
182
190
|
|
|
183
|
-
conf
|
|
184
|
-
if conf[:description].size != conf[:data].size/2
|
|
185
|
-
raise "Description for popups does not have same size as provided data: #{conf[:description].size} vs #{conf[:data].size/2}"
|
|
186
|
-
end
|
|
191
|
+
add_data_init_or_check_optional_keys(conf, conf[:data].size / 2)
|
|
187
192
|
|
|
188
193
|
x = []
|
|
189
194
|
y = []
|
|
190
195
|
conf[:data].each_index {|i|
|
|
191
196
|
(i%2 == 0 ? x : y) << conf[:data][i]
|
|
192
197
|
}
|
|
193
|
-
sort(
|
|
194
|
-
conf[:data] = [x,y]
|
|
198
|
+
sort(x, y, conf[:description], conf[:shape], conf[:url])
|
|
199
|
+
conf[:data] = [x, y]
|
|
195
200
|
# at the end data looks like:
|
|
196
201
|
# [
|
|
197
202
|
# [all x values],
|
|
@@ -218,22 +223,40 @@ module SVG
|
|
|
218
223
|
@border_right = label_right if label_right > @border_right
|
|
219
224
|
end
|
|
220
225
|
|
|
221
|
-
|
|
222
226
|
X = 0
|
|
223
227
|
Y = 1
|
|
224
228
|
|
|
229
|
+
# procedure to clear all the cached variables used in working out the
|
|
230
|
+
# max and min ranges for the chart
|
|
231
|
+
def clear_cache
|
|
232
|
+
@max_x_cache = @min_x_cache = @max_y_cache = @min_y_cache = nil
|
|
233
|
+
end
|
|
234
|
+
|
|
225
235
|
def max_x_range
|
|
236
|
+
return @max_x_cache unless @max_x_cache.nil?
|
|
237
|
+
|
|
238
|
+
# needs to be computed fresh when called, to cover the use-case:
|
|
239
|
+
# add_data -> burn -> add_data -> burn
|
|
240
|
+
# when values would be cached, the graph is not updated for second burning
|
|
226
241
|
max_value = @data.collect{|x| x[:data][X][-1] }.max
|
|
227
242
|
max_value = max_value > max_x_value ? max_value : max_x_value if max_x_value
|
|
228
|
-
max_value
|
|
243
|
+
@max_x_cache = max_value
|
|
244
|
+
@max_x_cache
|
|
229
245
|
end
|
|
230
246
|
|
|
231
247
|
def min_x_range
|
|
248
|
+
return @min_x_cache unless @min_x_cache.nil?
|
|
249
|
+
|
|
250
|
+
# needs to be computed fresh when called, to cover the use-case:
|
|
251
|
+
# add_data -> burn -> add_data -> burn
|
|
252
|
+
# when values would be cached, the graph is not updated for second burning
|
|
232
253
|
min_value = @data.collect{|x| x[:data][X][0] }.min
|
|
233
254
|
min_value = min_value < min_x_value ? min_value : min_x_value if min_x_value
|
|
234
|
-
min_value
|
|
255
|
+
@min_x_cache = min_value
|
|
256
|
+
@min_x_cache
|
|
235
257
|
end
|
|
236
258
|
|
|
259
|
+
# calculate the min and max x value as well as the scale division used for the x-axis
|
|
237
260
|
def x_label_range
|
|
238
261
|
max_value = max_x_range
|
|
239
262
|
min_value = min_x_range
|
|
@@ -246,11 +269,12 @@ module SVG
|
|
|
246
269
|
end
|
|
247
270
|
scale_range = max_value - min_value
|
|
248
271
|
|
|
249
|
-
|
|
272
|
+
# either use the given step size or by default do 9 divisions.
|
|
273
|
+
scale_division = scale_x_divisions || (scale_range / 9.0)
|
|
250
274
|
@x_offset = 0
|
|
251
275
|
|
|
252
276
|
if scale_x_integers
|
|
253
|
-
scale_division = scale_division < 1 ? 1 : scale_division.
|
|
277
|
+
scale_division = scale_division < 1 ? 1 : scale_division.ceil
|
|
254
278
|
@x_offset = min_value.to_f - min_value.floor
|
|
255
279
|
min_value = min_value.floor
|
|
256
280
|
end
|
|
@@ -258,10 +282,13 @@ module SVG
|
|
|
258
282
|
[min_value, max_value, scale_division]
|
|
259
283
|
end
|
|
260
284
|
|
|
285
|
+
# get array of values for the x axis divisions, assuming left-most value starts
|
|
286
|
+
# exactly where the graph starts.
|
|
261
287
|
def get_x_values
|
|
262
288
|
min_value, max_value, @x_scale_division = x_label_range
|
|
289
|
+
x_times = ((max_value-min_value)/@x_scale_division).round + 1
|
|
263
290
|
rv = []
|
|
264
|
-
|
|
291
|
+
x_times.times{|v| rv << (min_value + (v * @x_scale_division))}
|
|
265
292
|
return rv
|
|
266
293
|
end
|
|
267
294
|
alias :get_x_labels :get_x_values
|
|
@@ -273,17 +300,25 @@ module SVG
|
|
|
273
300
|
# otherwise there is always 1 division unused
|
|
274
301
|
end
|
|
275
302
|
|
|
276
|
-
|
|
277
303
|
def max_y_range
|
|
304
|
+
return @max_y_cache unless @max_y_cache.nil?
|
|
305
|
+
|
|
278
306
|
max_value = @data.collect{|x| x[:data][Y].max }.max
|
|
279
307
|
max_value = max_value > max_y_value ? max_value : max_y_value if max_y_value
|
|
280
|
-
max_value
|
|
308
|
+
@max_y_cache = max_value
|
|
309
|
+
@max_y_cache
|
|
281
310
|
end
|
|
282
311
|
|
|
283
312
|
def min_y_range
|
|
313
|
+
return @min_y_cache unless @min_y_cache.nil?
|
|
314
|
+
|
|
315
|
+
# needs to be computed fresh when called, to cover the use-case:
|
|
316
|
+
# add_data -> burn -> add_data -> burn
|
|
317
|
+
# when values would be cached, the graph is not updated for second burning
|
|
284
318
|
min_value = @data.collect{|x| x[:data][Y].min }.min
|
|
285
319
|
min_value = min_value < min_y_value ? min_value : min_y_value if min_y_value
|
|
286
|
-
min_value
|
|
320
|
+
@min_y_cache = min_value
|
|
321
|
+
@min_y_cache
|
|
287
322
|
end
|
|
288
323
|
|
|
289
324
|
def y_label_range
|
|
@@ -298,11 +333,11 @@ module SVG
|
|
|
298
333
|
end
|
|
299
334
|
scale_range = max_value - min_value
|
|
300
335
|
|
|
301
|
-
scale_division = scale_y_divisions || (scale_range /
|
|
336
|
+
scale_division = scale_y_divisions || (scale_range / 9.0)
|
|
302
337
|
@y_offset = 0
|
|
303
338
|
|
|
304
339
|
if scale_y_integers
|
|
305
|
-
scale_division = scale_division < 1 ? 1 : scale_division.
|
|
340
|
+
scale_division = scale_division < 1 ? 1 : scale_division.ceil
|
|
306
341
|
@y_offset = (min_value.to_f - min_value.floor).to_f
|
|
307
342
|
min_value = min_value.floor
|
|
308
343
|
end
|
|
@@ -314,7 +349,7 @@ module SVG
|
|
|
314
349
|
min_value, max_value, @y_scale_division = y_label_range
|
|
315
350
|
if max_value != min_value
|
|
316
351
|
while (max_value - min_value) < @y_scale_division
|
|
317
|
-
@y_scale_division /=
|
|
352
|
+
@y_scale_division /= 9.0
|
|
318
353
|
end
|
|
319
354
|
end
|
|
320
355
|
rv = []
|
|
@@ -333,9 +368,10 @@ module SVG
|
|
|
333
368
|
else
|
|
334
369
|
dx = (max - values[-1]).to_f / (values[-1] - values[-2])
|
|
335
370
|
end
|
|
336
|
-
@graph_height.to_f / values.length
|
|
371
|
+
@graph_height.to_f / (values.length - 1)
|
|
337
372
|
end
|
|
338
373
|
|
|
374
|
+
# calculates the x,y coordinates of a datapoint in the plot area
|
|
339
375
|
def calc_coords(x, y)
|
|
340
376
|
coords = {:x => 0, :y => 0}
|
|
341
377
|
# scale the coordinates, use float division / multiplication
|
|
@@ -349,9 +385,7 @@ module SVG
|
|
|
349
385
|
line = 1
|
|
350
386
|
|
|
351
387
|
x_min = min_x_range
|
|
352
|
-
x_max = max_x_range
|
|
353
388
|
y_min = min_y_range
|
|
354
|
-
y_max = max_y_range
|
|
355
389
|
|
|
356
390
|
for data in @data
|
|
357
391
|
x_points = data[:data][X]
|
|
@@ -384,12 +418,16 @@ module SVG
|
|
|
384
418
|
x_points.each_index { |idx|
|
|
385
419
|
c = calc_coords(x_points[idx] - x_min, y_points[idx] - y_min)
|
|
386
420
|
if show_data_points
|
|
387
|
-
|
|
421
|
+
shape_selection_string = data[:description][idx].to_s
|
|
422
|
+
if !data[:shape][idx].nil?
|
|
423
|
+
shape_selection_string = data[:shape][idx].to_s
|
|
424
|
+
end
|
|
425
|
+
DataPoint.new(c[:x], c[:y], line).shape(shape_selection_string).each{|s|
|
|
388
426
|
@graph.add_element( *s )
|
|
389
427
|
}
|
|
390
428
|
end
|
|
391
429
|
make_datapoint_text( c[:x], c[:y]-6, y_points[idx] )
|
|
392
|
-
add_popup(c[:x], c[:y], format( x_points[idx], y_points[idx], data[:description][idx]))
|
|
430
|
+
add_popup(c[:x], c[:y], format( x_points[idx], y_points[idx], data[:description][idx].to_s), "", data[:url][idx].to_s)
|
|
393
431
|
}
|
|
394
432
|
end
|
|
395
433
|
line += 1
|
|
@@ -401,7 +439,7 @@ module SVG
|
|
|
401
439
|
info = []
|
|
402
440
|
info << (round_popups ? x.round : @number_format % x )
|
|
403
441
|
info << (round_popups ? y.round : @number_format % y )
|
|
404
|
-
info << desc
|
|
442
|
+
info << desc if !desc.empty?
|
|
405
443
|
"(#{info.compact.join(', ')})"
|
|
406
444
|
end
|
|
407
445
|
|
data/lib/SVG/Graph/Schedule.rb
CHANGED
data/lib/SVG/Graph/TimeSeries.rb
CHANGED
|
@@ -4,20 +4,20 @@ require_relative 'Plot'
|
|
|
4
4
|
module SVG
|
|
5
5
|
module Graph
|
|
6
6
|
# === For creating SVG plots of scalar temporal data
|
|
7
|
-
#
|
|
7
|
+
#
|
|
8
8
|
# = Synopsis
|
|
9
|
-
#
|
|
9
|
+
#
|
|
10
10
|
# require 'SVG/Graph/TimeSeries'
|
|
11
|
-
#
|
|
11
|
+
#
|
|
12
12
|
# # Data sets are x,y pairs
|
|
13
13
|
# projection = ["6/17/72", 11, "1/11/72", 7, "4/13/04", 11,
|
|
14
14
|
# "9/11/01", 9, "9/1/85", 2, "9/1/88", 1, "1/15/95", 13]
|
|
15
15
|
# actual = ["8/1/73", 18, "3/1/77", 15, "10/1/98", 4,
|
|
16
16
|
# "5/1/02", 14, "3/1/95", 6, "8/1/91", 12, "12/1/87", 6,
|
|
17
17
|
# "5/1/84", 17, "10/1/80", 12]
|
|
18
|
-
#
|
|
18
|
+
#
|
|
19
19
|
# title = "Ice Cream Cone Consumption"
|
|
20
|
-
#
|
|
20
|
+
#
|
|
21
21
|
# graph = SVG::Graph::TimeSeries.new( {
|
|
22
22
|
# :width => 640,
|
|
23
23
|
# :height => 480,
|
|
@@ -39,31 +39,31 @@ module SVG
|
|
|
39
39
|
# :stagger_x_labels => true,
|
|
40
40
|
# :x_label_format => "%m/%d/%y",
|
|
41
41
|
# })
|
|
42
|
-
#
|
|
42
|
+
#
|
|
43
43
|
# graph.add_data({
|
|
44
44
|
# :data => projection,
|
|
45
45
|
# :title => 'Projected',
|
|
46
46
|
# :template => '%d/%m/%y'
|
|
47
47
|
# })
|
|
48
|
-
#
|
|
48
|
+
#
|
|
49
49
|
# graph.add_data({
|
|
50
50
|
# :data => actual,
|
|
51
51
|
# :title => 'Actual',
|
|
52
52
|
# :template => '%d/%m/%y'
|
|
53
53
|
# })
|
|
54
|
-
#
|
|
54
|
+
#
|
|
55
55
|
# print graph.burn()
|
|
56
56
|
#
|
|
57
57
|
# = Description
|
|
58
|
-
#
|
|
58
|
+
#
|
|
59
59
|
# Produces a graph of temporal scalar data.
|
|
60
|
-
#
|
|
60
|
+
#
|
|
61
61
|
# = Examples
|
|
62
62
|
#
|
|
63
|
-
#
|
|
64
|
-
#
|
|
63
|
+
# https://github.com/lumean/svg-graph2/blob/master/examples/timeseries.rb
|
|
64
|
+
#
|
|
65
65
|
# = Notes
|
|
66
|
-
#
|
|
66
|
+
#
|
|
67
67
|
# The default stylesheet handles upto 10 data sets, if you
|
|
68
68
|
# use more you must create your own stylesheet and add the
|
|
69
69
|
# additional settings for the extra data sets. You will know
|
|
@@ -73,18 +73,18 @@ module SVG
|
|
|
73
73
|
# Unlike the other types of charts, data sets must contain x,y pairs:
|
|
74
74
|
#
|
|
75
75
|
# [ "12:30", 2 ] # A data set with 1 point: ("12:30",2)
|
|
76
|
-
# [ "01:00",2, "14:20",6] # A data set with 2 points: ("01:00",2) and
|
|
77
|
-
# # ("14:20",6)
|
|
76
|
+
# [ "01:00",2, "14:20",6] # A data set with 2 points: ("01:00",2) and
|
|
77
|
+
# # ("14:20",6)
|
|
78
78
|
#
|
|
79
|
-
# Note that multiple data sets within the same chart can differ in length,
|
|
79
|
+
# Note that multiple data sets within the same chart can differ in length,
|
|
80
80
|
# and that the data in the datasets needn't be in order; they will be ordered
|
|
81
81
|
# by the plot along the X-axis.
|
|
82
|
-
#
|
|
82
|
+
#
|
|
83
83
|
# The dates must be parseable by DateTime#parse or DateTime#strptime, but otherwise can be
|
|
84
84
|
# any order of magnitude (seconds within the hour, or years)
|
|
85
|
-
#
|
|
85
|
+
#
|
|
86
86
|
# = See also
|
|
87
|
-
#
|
|
87
|
+
#
|
|
88
88
|
# * SVG::Graph::Graph
|
|
89
89
|
# * SVG::Graph::BarHorizontal
|
|
90
90
|
# * SVG::Graph::Bar
|
|
@@ -117,9 +117,9 @@ module SVG
|
|
|
117
117
|
# See Time::strformat, default: '%Y-%m-%d %H:%M:%S'
|
|
118
118
|
attr_accessor :x_label_format
|
|
119
119
|
# Use this to set the spacing between dates on the axis. The value
|
|
120
|
-
# must be of the form
|
|
120
|
+
# must be of the form
|
|
121
121
|
# "\d+ ?(days|weeks|months|years|hours|minutes|seconds)?"
|
|
122
|
-
#
|
|
122
|
+
#
|
|
123
123
|
# EG:
|
|
124
124
|
#
|
|
125
125
|
# graph.timescale_divisions = "2 weeks"
|
|
@@ -133,9 +133,9 @@ module SVG
|
|
|
133
133
|
# Add data to the plot.
|
|
134
134
|
#
|
|
135
135
|
# d1 = [ "12:30", 2 ] # A data set with 1 point: ("12:30",2)
|
|
136
|
-
# d2 = [ "01:00",2, "14:20",6] # A data set with 2 points: ("01:00",2) and
|
|
137
|
-
# # ("14:20",6)
|
|
138
|
-
# graph.add_data(
|
|
136
|
+
# d2 = [ "01:00",2, "14:20",6] # A data set with 2 points: ("01:00",2) and
|
|
137
|
+
# # ("14:20",6)
|
|
138
|
+
# graph.add_data(
|
|
139
139
|
# :data => d1,
|
|
140
140
|
# :title => 'One',
|
|
141
141
|
# :template => '%H:%M' #template is optional
|
|
@@ -182,10 +182,10 @@ module SVG
|
|
|
182
182
|
def get_x_labels
|
|
183
183
|
get_x_values.collect { |v| Time.at(v).strftime( x_label_format ) }
|
|
184
184
|
end
|
|
185
|
-
|
|
185
|
+
|
|
186
186
|
private
|
|
187
187
|
|
|
188
|
-
# Accepts date time as a string, number of seconds since the epoch, or Time
|
|
188
|
+
# Accepts date time as a string, number of seconds since the epoch, or Time
|
|
189
189
|
# object and returns a Time object. Raises an error if not a valid date time
|
|
190
190
|
# representation.
|
|
191
191
|
def parse_time(time, template)
|
|
@@ -249,15 +249,15 @@ module SVG
|
|
|
249
249
|
step = amount
|
|
250
250
|
end
|
|
251
251
|
# only do this if division_units is not year or month. Those are done already above in the cases.
|
|
252
|
-
min.step( max, step ) {|v| rv << v} if step
|
|
252
|
+
min.step( max + (step/10), step ) {|v| rv << v} if step
|
|
253
253
|
@x_scale_division = step if step
|
|
254
254
|
return rv
|
|
255
255
|
end
|
|
256
256
|
end
|
|
257
|
-
min.step( max, @x_scale_division ) {|v| rv << v}
|
|
257
|
+
min.step( max + (@x_scale_division/10), @x_scale_division ) {|v| rv << v}
|
|
258
258
|
return rv
|
|
259
259
|
end # get_x_values
|
|
260
|
-
|
|
260
|
+
|
|
261
261
|
end # class TimeSeries
|
|
262
262
|
end # module Graph
|
|
263
263
|
end # module SVG
|
data/lib/svggraph.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: svg-graph
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.2.
|
|
4
|
+
version: 2.2.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sean Russell
|
|
@@ -12,7 +12,7 @@ authors:
|
|
|
12
12
|
autorequire:
|
|
13
13
|
bindir: bin
|
|
14
14
|
cert_chain: []
|
|
15
|
-
date:
|
|
15
|
+
date: 2023-04-30 00:00:00.000000000 Z
|
|
16
16
|
dependencies: []
|
|
17
17
|
description: "Gem version of SVG:::Graph. SVG:::Graph is a pure Ruby library for generating
|
|
18
18
|
charts,\nwhich are a type of graph where the values of one axis are not scalar.
|
|
@@ -72,7 +72,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
72
72
|
- !ruby/object:Gem::Version
|
|
73
73
|
version: '0'
|
|
74
74
|
requirements: []
|
|
75
|
-
rubygems_version: 3.
|
|
75
|
+
rubygems_version: 3.2.3
|
|
76
76
|
signing_key:
|
|
77
77
|
specification_version: 4
|
|
78
78
|
summary: SVG:::Graph is a pure Ruby library for generating charts, which are a type
|