gerbilcharts 0.2.4 → 0.2.5
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.
- data/History.txt +8 -0
- data/lib/gerbilcharts/charts/area_chart.rb +1 -1
- data/lib/gerbilcharts/charts/chart_base.rb +4 -0
- data/lib/gerbilcharts/models/presets.rb +1 -1
- data/lib/gerbilcharts/models/round_range.rb +15 -5
- data/lib/gerbilcharts/public/brushmetal.css +11 -8
- data/lib/gerbilcharts/public/gerbil.js +334 -327
- data/lib/gerbilcharts/surfaces/area_surface.rb +11 -9
- data/lib/gerbilcharts/surfaces/bar_surface.rb +3 -1
- data/lib/gerbilcharts/surfaces/chart.rb +1 -1
- data/lib/gerbilcharts/surfaces/graph_element.rb +4 -10
- data/lib/gerbilcharts/surfaces/grid.rb +28 -20
- data/lib/gerbilcharts/surfaces/legend.rb +2 -8
- data/lib/gerbilcharts/surfaces/line_surface.rb +7 -2
- data/lib/gerbilcharts/surfaces/pie_surface.rb +1 -0
- data/lib/gerbilcharts/surfaces/stacked_area_surface.rb +17 -7
- data/lib/gerbilcharts/surfaces/surface.rb +1 -1
- data/lib/gerbilcharts/surfaces/tracker.rb +10 -6
- data/lib/gerbilcharts/surfaces/vertical_axis.rb +4 -1
- data/lib/gerbilcharts/svgdc/svgdc.rb +41 -19
- data/lib/gerbilcharts/version.rb +1 -1
- data/test/test_lines.rb +3 -59
- data/test/test_negatives.rb +39 -0
- data/test/test_ranges.rb +21 -0
- metadata +3 -2
@@ -9,41 +9,43 @@ class AreaSurface < Surface
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def int_render(g)
|
12
|
-
|
13
|
-
|
14
12
|
range_options_x = parent.get_global_option(:scaling_x,:auto)
|
15
13
|
range_options_y = parent.get_global_option(:scaling_y,:auto)
|
14
|
+
butterfly = parent.get_global_option(:butterfly,false)
|
15
|
+
|
16
16
|
rx = parent.modelgroup.effective_range_x(range_options_x)
|
17
17
|
ry = parent.modelgroup.effective_range_y(range_options_y)
|
18
18
|
|
19
|
-
p "range y = #{ry}"
|
20
|
-
|
21
19
|
# ajax if used
|
22
20
|
if parent.usesAjax?
|
23
21
|
set_ajaxSurfaceContext(rx.rmax,ry.rmax,"AREA")
|
24
22
|
end
|
25
23
|
|
24
|
+
# butterfly chart
|
25
|
+
ry.update(-ry.rmax) if butterfly
|
26
|
+
|
27
|
+
y_zero = scale_y 0,ry
|
26
28
|
|
27
29
|
parent.modelgroup.each_model_with_index do | mod, i|
|
28
|
-
|
29
30
|
g.begin_polygon
|
30
31
|
firstpoint=true
|
31
32
|
xpos=0
|
32
33
|
ypos=0
|
33
34
|
mod.each_tuple do |x,y|
|
35
|
+
|
36
|
+
y =-y if butterfly and i.odd?
|
37
|
+
|
34
38
|
xpos = scale_x x,rx
|
35
39
|
ypos = scale_y y,ry
|
36
40
|
if firstpoint
|
37
|
-
g.polygon_point xpos
|
41
|
+
g.polygon_point xpos,y_zero
|
38
42
|
firstpoint=false
|
39
43
|
end
|
40
44
|
g.polygon_point xpos,ypos
|
41
45
|
end
|
42
|
-
g.polygon_point xpos
|
46
|
+
g.polygon_point xpos,y_zero
|
43
47
|
g.end_polygon(:id => "item#{i}", "opacity"=>"0.3")
|
44
|
-
g.rectangle(xpos+2*i,ypos,(i+1)*2,@bounds.bottom-ypos,{:id => "item#{i}"})
|
45
48
|
end
|
46
|
-
|
47
49
|
end
|
48
50
|
|
49
51
|
end
|
@@ -51,8 +51,10 @@ class BarSurface < Surface
|
|
51
51
|
if parent.get_global_option(:auto_tooltips,false)
|
52
52
|
opts.merge!(:onmouseover => "OpacityDown(evt)", :onmouseout => "OpacityUp(evt)")
|
53
53
|
opts.store(:gerbiltooltip1, mod.name)
|
54
|
-
|
54
|
+
opts.store(:gerbiltooltip2, "Val = #{mod.latest_val}")
|
55
55
|
end
|
56
|
+
|
57
|
+
opts.merge!(:href => mod.href) if mod.hasHref?
|
56
58
|
|
57
59
|
# draw the bar
|
58
60
|
g.rectangle(xpos, ypos, @element_width, @bounds.bottom - ypos, opts)
|
@@ -31,7 +31,7 @@ class Chart < GraphElement
|
|
31
31
|
# The second parameter says if you want the js inlined in the HTML.
|
32
32
|
#
|
33
33
|
def set_defaults
|
34
|
-
@javascripts = { "
|
34
|
+
@javascripts = { "gerbil.js" => true }
|
35
35
|
end
|
36
36
|
|
37
37
|
def set_modelgroup(g)
|
@@ -79,11 +79,9 @@ class GraphElement
|
|
79
79
|
|
80
80
|
# setup SVG group element and call int_render
|
81
81
|
def render(sdc)
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
int_render(sdc)
|
86
|
-
sdc.setactivewindow
|
82
|
+
sdc.newwin(@group) do |sdc|
|
83
|
+
int_render(sdc)
|
84
|
+
end
|
87
85
|
end
|
88
86
|
|
89
87
|
# render direct - directly render SVG using builder for complex elements
|
@@ -145,11 +143,7 @@ class GraphElement
|
|
145
143
|
|
146
144
|
# query a global option, return the defval if option is not set
|
147
145
|
def get_global_option(optsym, defval)
|
148
|
-
|
149
|
-
return @global_chart_options[optsym]
|
150
|
-
else
|
151
|
-
return defval
|
152
|
-
end
|
146
|
+
@global_chart_options[optsym] || defval
|
153
147
|
end
|
154
148
|
|
155
149
|
# utility methods to derived classes
|
@@ -12,35 +12,43 @@ class Grid < GraphElement
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def int_render(g)
|
15
|
+
ry = grid_range_y
|
16
|
+
rx = grid_range_x
|
15
17
|
|
16
18
|
# horizontal grid lines
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
19
|
+
g.newwin('hgrid', {:class => "gridlineh"} ) do |g|
|
20
|
+
ry.each_label do |val,label|
|
21
|
+
yp = scale_y val,ry
|
22
|
+
g.line(@bounds.left,yp,@bounds.right,yp)
|
23
|
+
end
|
24
|
+
end
|
22
25
|
|
23
26
|
# horiz subticks
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
27
|
+
g.newwin('hgridsub', {:class => "gridlinesub"} ) do |g|
|
28
|
+
ry.each_tick(@tick_count) do |val|
|
29
|
+
yp = scale_y val,ry
|
30
|
+
g.line(@bounds.left,yp,@bounds.right,yp)
|
31
|
+
end
|
32
|
+
end
|
30
33
|
|
31
34
|
|
32
35
|
# vertical subticks
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
36
|
+
g.newwin('vgrid', {:class => "gridlinev"} ) do |g|
|
37
|
+
rx.each_label do |val,label|
|
38
|
+
xp = scale_x val,rx
|
39
|
+
g.line(xp,@bounds.top,xp,@bounds.bottom)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
g.newwin('vgridsub', {:class => "gridlinesub"} ) do |g|
|
45
|
+
rx.each_tick(0) do |val|
|
46
|
+
xp = scale_x val,rx
|
47
|
+
g.line(xp,@bounds.top,xp,@bounds.bottom)
|
48
|
+
end
|
42
49
|
end
|
43
50
|
|
51
|
+
|
44
52
|
end
|
45
53
|
|
46
54
|
protected
|
@@ -17,6 +17,8 @@ class Legend < GraphElement
|
|
17
17
|
# count determines the bounds
|
18
18
|
@bounds.bottom = @bounds.top + 15 * parent.modelgroup.count
|
19
19
|
g.rectangle_r(@bounds, {:class => @class})
|
20
|
+
|
21
|
+
g.rectangle(@bounds.left-5,@bounds.top,5,5)
|
20
22
|
|
21
23
|
rbox = Rect.new
|
22
24
|
rbox.initfrom(@bounds)
|
@@ -29,7 +31,6 @@ class Legend < GraphElement
|
|
29
31
|
|
30
32
|
# Adding tool tips
|
31
33
|
opts = { :class => "legendtext" }
|
32
|
-
parent.get_global_option(:auto_tooltips,false)
|
33
34
|
opts.merge!(:onmouseover => "OpacityDown(evt)", :onmouseout => "OpacityUp(evt)")
|
34
35
|
opts.store(:gerbiltooltip1, mod.name)
|
35
36
|
opts.store(:gerbiltooltip2, "Val = #{mod.latest_val}")
|
@@ -44,21 +45,14 @@ class Legend < GraphElement
|
|
44
45
|
|
45
46
|
end
|
46
47
|
|
47
|
-
|
48
48
|
def align_to_anchor(anc)
|
49
49
|
super
|
50
|
-
|
51
50
|
@bounds.deflate_v(10,10)
|
52
51
|
@bounds.deflate_h(2,4)
|
53
|
-
|
54
52
|
if @width
|
55
53
|
@bounds.left = @bounds.right - @width
|
56
54
|
end
|
57
|
-
|
58
55
|
end
|
59
|
-
|
60
|
-
|
61
56
|
end
|
62
|
-
|
63
57
|
end
|
64
58
|
|
@@ -18,9 +18,13 @@ class LineSurface < Surface
|
|
18
18
|
|
19
19
|
range_options_x = parent.get_global_option(:scaling_x,:auto)
|
20
20
|
range_options_y = parent.get_global_option(:scaling_y,:auto)
|
21
|
+
butterfly = parent.get_global_option(:butterfly,false)
|
22
|
+
|
21
23
|
rx = parent.modelgroup.effective_range_x(range_options_x)
|
22
24
|
ry = parent.modelgroup.effective_range_y(range_options_y)
|
23
|
-
|
25
|
+
|
26
|
+
ry.update(-ry.rmax) if butterfly
|
27
|
+
|
24
28
|
# ajax if used
|
25
29
|
if parent.usesAjax?
|
26
30
|
set_ajaxSurfaceContext(rx.rmax,ry.rmax,"LINE")
|
@@ -30,12 +34,13 @@ class LineSurface < Surface
|
|
30
34
|
|
31
35
|
mod.each_tuple do |x,y|
|
32
36
|
|
37
|
+
y = -y if butterfly and i.odd?
|
38
|
+
|
33
39
|
# the basic line
|
34
40
|
xpos = scale_x x,rx
|
35
41
|
ypos = scale_y y,ry
|
36
42
|
g.lineto xpos,ypos
|
37
43
|
|
38
|
-
|
39
44
|
# datapoint and a tooltip
|
40
45
|
opts = {:id => "item#{i}"}
|
41
46
|
if parent.get_global_option(:auto_tooltips,false)
|
@@ -73,6 +73,7 @@ class PieSurface < Surface
|
|
73
73
|
opts.store(:gerbiltooltip1, mod.name)
|
74
74
|
opts.store(:gerbiltooltip2, "Val = #{mod.latest_val}")
|
75
75
|
end
|
76
|
+
opts.merge!(:href => mod.href) if mod.hasHref?
|
76
77
|
|
77
78
|
# draw the slice
|
78
79
|
g.addshape(GerbilCharts::SVGDC::SVGArc.new(cx,cy,radius,radius,
|
@@ -11,7 +11,7 @@ class StackedAreaSurface < Surface
|
|
11
11
|
def int_render(g)
|
12
12
|
rx = parent.modelgroup.effective_round_range_x
|
13
13
|
ry = parent.modelgroup.cumulative_sweep_round_range_y0
|
14
|
-
|
14
|
+
|
15
15
|
# ajax if used
|
16
16
|
if parent.usesAjax?
|
17
17
|
set_ajaxSurfaceContext(rx.rmax,ry.rmax,"SA")
|
@@ -21,9 +21,11 @@ class StackedAreaSurface < Surface
|
|
21
21
|
sweep_pos= rx.rmin
|
22
22
|
sweep_to = rx.rmax
|
23
23
|
polygons = []
|
24
|
+
modnames = []
|
24
25
|
parent.modelgroup.each_model do | mod|
|
25
26
|
mod.begin_sweep
|
26
27
|
polygons << GerbilCharts::SVGDC::SVGPolygon.new
|
28
|
+
modnames << mod.name
|
27
29
|
end
|
28
30
|
|
29
31
|
# sweep interval
|
@@ -46,14 +48,22 @@ class StackedAreaSurface < Surface
|
|
46
48
|
|
47
49
|
sweep_pos += sweep_interval
|
48
50
|
end
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
51
|
+
|
52
|
+
# layout all polygons in reverse
|
53
|
+
i=polygons.length-1
|
54
|
+
last_x = scale_x sweep_to,rx
|
55
|
+
polygons.reverse_each do |p|
|
53
56
|
p.addpoint(last_x,@bounds.bottom)
|
54
|
-
|
57
|
+
|
58
|
+
opts = {:id => "item#{i}" }
|
59
|
+
if parent.get_global_option(:auto_tooltips,false)
|
60
|
+
opts.merge!(:onmouseover => "OpacityDown(evt)", :onmouseout => "OpacityUp(evt)")
|
61
|
+
opts.store(:gerbiltooltip1, modnames[i])
|
62
|
+
end
|
63
|
+
|
64
|
+
g.addshape(p,opts)
|
55
65
|
i -= 1
|
56
|
-
|
66
|
+
end
|
57
67
|
|
58
68
|
end
|
59
69
|
|
@@ -23,6 +23,11 @@ class Tracker < GraphElement
|
|
23
23
|
# calculate scaling factors
|
24
24
|
rx = parent.modelgroup.effective_round_range_x
|
25
25
|
|
26
|
+
xfrag.g(:id=> 'gtrackerrect', :visibility=>'hidden') {
|
27
|
+
xfrag.rect(:id=>"trackerrect", :class=>"trackerrect",
|
28
|
+
:height=>parent.anchor.bounds.height, :width=>30, :x=>100, :y=>parent.anchor.bounds.top)
|
29
|
+
}
|
30
|
+
|
26
31
|
# output the SVG fragment directly
|
27
32
|
xfrag.g(:id => 'gtrackerpanel') {
|
28
33
|
xfrag.rect(:id=>"trackerpanel", :class=>"trackerpanel",
|
@@ -32,10 +37,6 @@ class Tracker < GraphElement
|
|
32
37
|
:onmouseup=>"TrackerMouseUp(evt)")
|
33
38
|
}
|
34
39
|
|
35
|
-
xfrag.g(:id=> 'gtrackerrect', :visibility=>'hidden') {
|
36
|
-
xfrag.rect(:id=>"trackerrect", :class=>"trackerrect",
|
37
|
-
:height=>parent.anchor.bounds.height, :width=>30, :x=>100, :y=>parent.anchor.bounds.top)
|
38
|
-
}
|
39
40
|
|
40
41
|
ty = parent.anchor.bounds.height/2
|
41
42
|
xfrag.g(:id=> 'gtrackertext', :visibility=>'hidden', :transform => "translate(200 50)") {
|
@@ -49,8 +50,11 @@ class Tracker < GraphElement
|
|
49
50
|
}
|
50
51
|
}
|
51
52
|
|
52
|
-
|
53
|
-
|
53
|
+
raise "Time Tracker expects X-Axis to be a time interval " unless rx.rmin.is_a? Time
|
54
|
+
|
55
|
+
|
56
|
+
xfrag.g(:id=>"gtrackerdata", :visibility=>'hidden',
|
57
|
+
:gerb_fromts=>rx.rmin.tv_sec,
|
54
58
|
:gerb_scale =>(rx.delta)/parent.anchor.bounds.width,
|
55
59
|
:gerb_selts=>"1",
|
56
60
|
:gerb_selsecs=>"1")
|
@@ -13,6 +13,7 @@ class VerticalAxis < Axis
|
|
13
13
|
|
14
14
|
@use_cumulative_y = false
|
15
15
|
@use_cumulative_y = opts[:cumulative] if opts[:cumulative]
|
16
|
+
@butterfly=true
|
16
17
|
end
|
17
18
|
|
18
19
|
def int_render(g)
|
@@ -21,13 +22,15 @@ class VerticalAxis < Axis
|
|
21
22
|
|
22
23
|
return if parent.modelgroup.empty?
|
23
24
|
|
24
|
-
p "axis = #{parent.get_global_option(:scaling_y,false)}"
|
25
25
|
if @use_cumulative_y
|
26
26
|
ry = parent.modelgroup.cumulative_sweep_round_range_y0
|
27
27
|
else
|
28
28
|
ry = parent.modelgroup.effective_range_y(parent.get_global_option(:scaling_y,:auto))
|
29
29
|
end
|
30
30
|
|
31
|
+
ry.update(-ry.rmax) if @butterfly
|
32
|
+
|
33
|
+
|
31
34
|
ry.each_label do |val,label|
|
32
35
|
yp = scale_y val,ry
|
33
36
|
yp_label=yp
|
@@ -24,8 +24,9 @@ class SVGDC
|
|
24
24
|
attr_accessor :height
|
25
25
|
attr_reader :curr_win, :base_win
|
26
26
|
attr_reader :curr_presentation_context
|
27
|
-
attr_reader :svg_styles
|
28
27
|
attr_reader :svg_predefs
|
28
|
+
attr_reader :svg_styles
|
29
|
+
attr_reader :ref_styles
|
29
30
|
attr_reader :svg_javascript
|
30
31
|
attr_reader :ref_javascripts
|
31
32
|
attr_reader :ajaxOptions
|
@@ -46,26 +47,25 @@ class SVGDC
|
|
46
47
|
# Searches for the stylesheet in the current directory or in the gerbilcharts/public directory
|
47
48
|
# If you want to use your own stylesheets put them in the working directory, or in the
|
48
49
|
# public directory of gerbilcharts.
|
49
|
-
|
50
|
-
|
51
|
-
|
50
|
+
# To inline stylesheet use "inline:<stylesheetname>" , eg "inline:brushmetal.css"
|
51
|
+
# Default is to xref a stylesheet
|
52
|
+
def set_stylesheet(cssfile_in)
|
53
|
+
cssfile=cssfile_in.gsub(/^inline:/,'')
|
54
|
+
|
55
|
+
if cssfile.length < cssfile_in.length
|
56
|
+
@svg_styles = get_file_content(cssfile)
|
52
57
|
else
|
53
|
-
|
54
|
-
if not File.exists? try_from_gem
|
55
|
-
p "The stylesheet #{cssfile} was not found. Make sure it exists in the current directory, or in the gem public area"
|
56
|
-
raise "Stylesheet #{cssfile} not found in current directory or in the gerbilcharts/public folder"
|
57
|
-
else
|
58
|
-
@svg_styles=File.read(try_from_gem)
|
59
|
-
end
|
58
|
+
@ref_styles = cssfile
|
60
59
|
end
|
60
|
+
|
61
61
|
end
|
62
62
|
|
63
63
|
def add_javascriptfile(jsfile,inlined)
|
64
64
|
if inlined
|
65
|
-
@svg_javascript
|
66
|
-
@svg_javascript <<
|
65
|
+
@svg_javascript ||= ""
|
66
|
+
@svg_javascript << get_file_content(jsfile)
|
67
67
|
else
|
68
|
-
@ref_javascripts
|
68
|
+
@ref_javascripts ||= []
|
69
69
|
@ref_javascripts << jsfile
|
70
70
|
end
|
71
71
|
end
|
@@ -103,9 +103,16 @@ class SVGDC
|
|
103
103
|
def newwin(name, opts={})
|
104
104
|
w=SVGWin.new(name, opts)
|
105
105
|
@curr_win << w
|
106
|
-
|
106
|
+
@curr_win = w
|
107
|
+
yield self if block_given?
|
108
|
+
reset if block_given?
|
109
|
+
return w if not block_given?
|
107
110
|
end
|
108
111
|
|
112
|
+
def reset
|
113
|
+
@curr_win = @base_win
|
114
|
+
end
|
115
|
+
|
109
116
|
def setactivewindow(w=nil)
|
110
117
|
@curr_win = w ? w : @base_win
|
111
118
|
end
|
@@ -132,9 +139,7 @@ class SVGDC
|
|
132
139
|
doc.svg(svg_attributes) {
|
133
140
|
|
134
141
|
# stylesheet (inlined into SVG)
|
135
|
-
if @svg_styles
|
136
|
-
doc.style(@svg_styles, :type => "text/css")
|
137
|
-
end
|
142
|
+
doc.style(@svg_styles, :type => "text/css") if @svg_styles
|
138
143
|
|
139
144
|
# javascripts (ajax, etc) hrefed
|
140
145
|
if @ref_javascripts
|
@@ -208,8 +213,12 @@ class SVGDC
|
|
208
213
|
doc = Builder::XmlMarkup.new(:target => svg_string, :indent => 2, :standalone => "no")
|
209
214
|
doc.instruct!
|
210
215
|
doc.declare! :DOCTYPE, :svg, :PUBLIC,"-//W3C//DTD SVG 1.1//EN","http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"
|
211
|
-
|
212
216
|
|
217
|
+
# stylesheet instruction if hreferenced
|
218
|
+
if @ref_styles
|
219
|
+
doc.instruct! "xml-stylesheet", :href => @ref_styles, :type => "text/css"
|
220
|
+
end
|
221
|
+
|
213
222
|
render_to_frag(doc,opts)
|
214
223
|
|
215
224
|
# return the svg_string
|
@@ -331,6 +340,19 @@ class SVGDC
|
|
331
340
|
@curr_presentation_context = []
|
332
341
|
end
|
333
342
|
|
343
|
+
private
|
344
|
+
|
345
|
+
# search for the file in the current directory or the gem path
|
346
|
+
def get_file_content(fname)
|
347
|
+
if File.exists? fname
|
348
|
+
return File.read(fname)
|
349
|
+
else
|
350
|
+
try_from_gem = File.dirname(__FILE__) + "/../public/" + fname
|
351
|
+
return File.read(try_from_gem) if File.exists? try_from_gem
|
352
|
+
end
|
353
|
+
raise "Resource #{fname} not found in current directory or in the gerbilcharts/public folder"
|
354
|
+
end
|
355
|
+
|
334
356
|
end
|
335
357
|
|
336
358
|
end
|