gerbilcharts 0.2.4 → 0.2.5
Sign up to get free protection for your applications and to get access to all the features.
- 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
|