split/Tioga/lib/FigMkr.rb in tioga-1.8 vs split/Tioga/lib/FigMkr.rb in tioga-1.9
- old
+ new
@@ -32,11 +32,11 @@
@@which_pdflatex = nil
@@initialized = false # set true by the C code when first make a figure
# This URL will contain tioga-(...) when it is exported from the
# SVN repository. This is where we'll look for version information.
- SVN_URL = '$HeadURL: svn+ssh://rubyforge.org/var/svn/tioga/tags/tioga/Tioga%201.8/split/Tioga/lib/FigMkr.rb $'
+ SVN_URL = '$HeadURL: svn+ssh://rubyforge.org/var/svn/tioga/trunk/tioga/split/Tioga/lib/FigMkr.rb $'
TIOGA_VERSION = if SVN_URL =~ /tags\/tioga\/Tioga%20([^\/]+)/
$1
else
"SVN version"
@@ -64,19 +64,33 @@
def FigureMaker.pdflatex=(s)
@@which_pdflatex = s
end
-
def FigureMaker.make_name_lookup_hash(ary)
dict = Hash.new
ary.each { |name| dict[name] = true }
return dict
end
+
+ def FigureMaker.make_pdf(name='immediate', &block)
+ self.default.make_pdf(name, &block)
+ end
+
+ def FigureMaker.def_enter_page_function(&cmd)
+ self.default.def_enter_page_function(&cmd)
+ end
+
+ def FigureMaker.page_style(&cmd)
+ FigureMaker.def_enter_page_function(&cmd)
+ end
+
+ def FigureMaker.exec(&cmd)
+ cmd.call(self.default)
+ end
+
-
-
attr_accessor :title
attr_accessor :xlabel
attr_accessor :ylabel
attr_accessor :xaxis_locations_for_major_ticks
@@ -126,11 +140,11 @@
attr_accessor :default_frame_top
attr_accessor :default_frame_bottom
attr_accessor :num_error_lines
- # Whether or not to create +save_dir+ if it doesn't exist
+ # Whether or not to create _save_dir_ if it doesn't exist
attr_accessor :create_save_dir
# Whether or not do do automatic cleanup of the files
attr_accessor :autocleanup
@@ -138,10 +152,17 @@
attr_accessor :multithreads_okay_for_tioga
# An accessor for @measures_info:
attr_accessor :measures_info
+
+
+ # If we want to use #legend_bounding_box. It is off by default
+ # as it causes a systematic second run of pdflatex.
+ attr_accessor :measure_legends
+
+
# old preview attributes -- to be removed later
attr_accessor :model_number
attr_accessor :add_model_number
attr_accessor :need_to_reload_data
@@ -227,10 +248,11 @@
'plot_top_margin' => 0.0,
'plot_bottom_margin' => 0.0,
'plot_left_margin' => 0.0,
'plot_right_margin' => 0.18,
'plot_scale' => 1,
+ 'legend_background_function' => false,
'legend_scale' => 1 }
@marker_defaults = {
'fill_color' => Black,
'stroke_color' => Black,
@@ -277,10 +299,16 @@
# we'll run pdflatex.
@measures = {}
# We *must* initialize the measures_info hash.
@measures_info = {}
+
+ # We don't measure legends by default.
+ @measure_legends = false
+
+ # By default, we use Bill's algorithm for major ticks positions
+ self.vincent_or_bill = false
end
def reset_plot_attrs
@title = nil
@@ -609,10 +637,11 @@
'marker_dict' => nil, # use again #prepare_argument_hash ?
'marker_color' => nil,
'marker_scale' => nil,
}
+
def save_legend_info(arg)
if arg.kind_of?String
dict = { 'text' => arg }
else
dict = arg
@@ -623,12 +652,13 @@
dict['marker_color'] = self.line_color if dict['marker_color'] == nil
dict['marker_scale'] = 0.5 if dict['marker_scale'] == nil
end
@legend_info << dict
end
+
- def show_legend
+ def show_legend(legend_background_function=nil)
char_dx = self.default_text_height_dx
char_dy = self.default_text_height_dy
line_ht_x = char_dx * self.legend_scale
line_ht_y = char_dy * self.legend_scale
x = self.legend_text_xstart*line_ht_x
@@ -639,24 +669,52 @@
else
ltw = 7
end
end
xright = x + ltw*line_ht_x
+
y = 1.0 - self.legend_text_ystart*line_ht_y
update_bbox(xright, y)
- dy = -self.legend_text_dy*line_ht_y
line_x0 = self.legend_line_x0*line_ht_x
line_x1 = self.legend_line_x1*line_ht_x
line_dy = self.legend_line_dy*line_ht_y
+
+ # If we are measuring legends, we come up with a more
+ # accurate version of xright:
+ if @measure_legends
+ xright = x + convert_output_to_figure_dx(legend_text_width)
+ xright += line_x0 # To leave a symetric space around
+ # the legend !
+ end
+
self.label_left_margin = self.label_right_margin = self.label_top_margin = self.label_bottom_margin = -1e99
+ unless legend_background_function == nil || legend_background_function == false
+ ybot = y
+ # we need to remove the first element ;-)...
+ @legend_info[1..-1].each do |dict|
+ dy = dict['dy']; dy = 1 if dy == nil
+ ybot -= line_ht_y * dy
+ end
+ # We add half a line below to look good
+ ybot -= line_ht_y * 0.5
+ ytop = y + @legend_info.first['dy'] * line_ht_y * 0.5
+ legend_background_function.call([ 0, xright, ytop, ybot ])
+ end
+ legend_index = 0
@legend_info.each do |dict|
text = dict['text']
if text != nil
- show_text('text' => text,
- 'x' => x, 'y' => y, 'scale' => self.legend_scale,
- 'justification' => self.legend_justification,
- 'alignment' => self.legend_alignment)
+ # We prepare a dictionnary:
+ dct = { 'text' => text,
+ 'x' => x, 'y' => y, 'scale' => self.legend_scale,
+ 'justification' => self.legend_justification,
+ 'alignment' => self.legend_alignment }
+ if @measure_legends
+ dct['measure'] = "legend-#{legend_index}"
+ legend_index += 1
+ end
+ show_text(dct)
end
line_width = dict['line_width']
line_type = dict['line_type']
unless (line_width < 0) || ((line_type.kind_of?String) && (line_type.casecmp('none') == 0))
self.line_color = dict['line_color']
@@ -681,10 +739,24 @@
dy = dict['dy']; dy = 1 if dy == nil
y -= line_ht_y * dy
end
end
+ # Returns the exact length of the text width in output
+ # coordinates
+ def legend_text_width
+ index = 0
+ width = 0.0
+ while h = get_text_size("legend-#{index}") and h.key?('width')
+ if width < h['width']
+ width = h['width']
+ end
+ index += 1
+ end
+ return 10 * width
+ end
+
def legend_height
height = 0.0
@legend_info.each { |dict| height += dict['dy'] }
return height
end
@@ -806,21 +878,21 @@
def trace_cmd_no_arg(entry_function, exit_function, &cmd)
unless entry_function == nil
begin
- entry_function.call
+ entry_function.call(self)
rescue Exception => er
report_error(er, nil)
end
end
- result = cmd.call
+ result = cmd.call(self)
unless exit_function == nil
begin
- exit_function.call
+ exit_function.call(self)
rescue Exception => er
report_error(er, nil)
end
end
@@ -837,11 +909,11 @@
rescue Exception => er
report_error(er, nil)
end
end
- result = cmd.call
+ result = cmd.call(self)
unless exit_function == nil
begin
exit_function.call(arg)
rescue Exception => er
@@ -855,18 +927,28 @@
def show_plot(bounds=nil,&cmd)
trace_cmd_one_arg(@enter_show_plot_function, @exit_show_plot_function, bounds) {
set_bounds(bounds)
- context { clip_to_frame; cmd.call }
+ context { clip_to_frame; cmd.call(self) }
show_plot_box
}
end
+
+ def show_plot_without_clipping(bounds=nil,&cmd)
+ trace_cmd_one_arg(@enter_show_plot_function, @exit_show_plot_function, bounds) {
+ set_bounds(bounds)
+ context { cmd.call(self) }
+ show_plot_box
+ }
+ end
+
+
def subfigure(margins=nil,&cmd)
trace_cmd_one_arg(@enter_subfigure_function, @exit_subfigure_function, margins) {
- context { doing_subfigure; set_subframe(margins); cmd.call }
+ context { doing_subfigure; set_subframe(margins); cmd.call(self) }
}
end
def root_plot
return ! self.in_subplot
@@ -877,11 +959,11 @@
end
def subplot(margins=nil,&cmd)
trace_cmd_one_arg(@enter_subplot_function, @exit_subplot_function, margins) {
reset_legend_info
- context { doing_subplot; set_subframe(margins); cmd.call }
+ context { doing_subplot; set_subframe(margins); cmd.call(self) }
}
end
@@keys_for_column_margins = FigureMaker.make_name_lookup_hash([
@@ -955,11 +1037,11 @@
save_yaxis_locations_for_minor_ticks = self.yaxis_locations_for_minor_ticks
save_yaxis_tick_labels = self.yaxis_tick_labels
save_fm_data = Dvector.new(@@fm_data_size).replace(@fm_data)
pdf_gsave
begin
- cmd.call
+ cmd.call(self)
ensure
pdf_grestore
self.title = save_title
self.xlabel = save_xlabel
self.ylabel = save_ylabel
@@ -1064,14 +1146,96 @@
'justification' => self.ylabel_justification,
'color' => self.ylabel_color)
end
end
+ @@keys_for_show_grid = %w[
+ stroke_color line_type rescale_lines line_width stroke_opacity stroke_transparency
+ ]
+
+ # Draws the gridlines associated with the X and Y axis.
+ #
+ # The following list shows the keys that cen be used in +dict+. In the
+ # list, 'A' stands for either 'x' or 'y', and 'M' stands for 'major' or
+ # 'minor'. Keys can also be specified without the "A_M_" prefix to specify
+ # "grid-global" defaults. The +grid+ key, if true, enables both major and
+ # minor gridlines for both x and y axes.
+ #
+ # * A_M_grid
+ # * A_M_line_type
+ # * A_M_line_width
+ # * A_M_stroke_opacity
+ # * A_M_stroke_transparency
+ # * A_M_stroke_color
+ def show_grid(dict={})
+ dict = {
+ 'line_type' => Line_Type_Dot,
+ 'rescale_lines' => 0.25,
+ 'stroke_color' => Gray,
+ 'major_grid' => true,
+ 'minor_grid' => false,
+ 'x_minor_rescale_lines' => 0.5,
+ 'y_minor_rescale_lines' => 0.5,
+ }.merge(dict)
+
+ # "grid-global" context
+ context do
+ @@keys_for_show_grid.each do |k|
+ next unless val = dict[k]
+ k += '=' if respond_to? "#{k}="
+ next unless respond_to? k
+ send(k, val)
+ end
+
+ if dict['x_major_grid'] || dict['x_minor_grid'] ||
+ dict[ 'major_grid'] || dict[ 'minor_grid'] || dict['grid']
+ top = axis_information(TOP)
+ bottom = axis_information(BOTTOM)
+ y0 = bottom['y0']
+ y1 = top['y1']
+ # Do minor and major grid lines in same context so minor lines will
+ # inherit properties from major lines.
+ context do
+ %w[ major minor ].each do |m|
+ next unless dict["x_#{m}_grid"] || dict["#{m}_grid"] || dict["grid"]
+ @@keys_for_show_grid.each do |k|
+ next unless val = dict["x_#{m}_#{k}"]
+ k += '=' if respond_to? "#{k}="
+ send(k, val)
+ end
+ bottom[m].each {|x| stroke_line(x, y0, x, y1)}
+ end
+ end
+ end
+
+ if dict['y_major_grid'] || dict['y_minor_grid'] ||
+ dict[ 'major_grid'] || dict[ 'minor_grid'] || dict['grid']
+ left = axis_information(LEFT)
+ right = axis_information(RIGHT)
+ x0 = left['x0']
+ x1 = right['x1']
+ # Do minor and major grid lines in same context so minor lines will
+ # inherit properties from major lines.
+ context do
+ %w[ major minor ].each do |m|
+ next unless dict["y_#{m}_grid"] || dict["#{m}_grid"] || dict["grid"]
+ @@keys_for_show_grid.each do |k|
+ next unless val = dict["y_#{m}_#{k}"]
+ k += '=' if respond_to? "#{k}="
+ send(k, val)
+ end
+ left[m].each {|y| stroke_line(x0, y, x1, y)}
+ end
+ end
+ end
+ end
+ end
+
@@keys_for_show_plot_with_legend = FigureMaker.make_name_lookup_hash([
'legend_top_margin', 'legend_bottom_margin', 'legend_left_margin', 'legend_right_margin',
'plot_top_margin', 'plot_bottom_margin', 'plot_left_margin', 'plot_right_margin',
- 'plot_scale', 'legend_scale' ])
+ 'plot_scale', 'legend_scale', 'legend_background_function' ])
def show_plot_with_legend(dict=nil, &cmd)
check_dict(dict, @@keys_for_show_plot_with_legend, 'show_plot_with_legend') if dict != nil
legend_top_margin = get_if_given_else_use_default_dict(dict, 'legend_top_margin', @legend_defaults)
legend_bottom_margin = get_if_given_else_use_default_dict(dict, 'legend_bottom_margin', @legend_defaults)
legend_left_margin = get_if_given_else_use_default_dict(dict, 'legend_left_margin', @legend_defaults)
@@ -1080,17 +1244,22 @@
plot_top_margin = get_if_given_else_use_default_dict(dict, 'plot_top_margin', @legend_defaults)
plot_bottom_margin = get_if_given_else_use_default_dict(dict, 'plot_bottom_margin', @legend_defaults)
plot_left_margin = get_if_given_else_use_default_dict(dict, 'plot_left_margin', @legend_defaults)
plot_right_margin = get_if_given_else_use_default_dict(dict, 'plot_right_margin', @legend_defaults)
plot_scale = get_if_given_else_use_default_dict(dict, 'plot_scale', @legend_defaults)
+ legend_background_function = get_if_given_else_default(dict, 'legend_background_function', nil)
+ @legend_subframe = [legend_left_margin, legend_right_margin,
+ legend_top_margin, legend_bottom_margin]
reset_legend_info
rescale(plot_scale)
- subplot([plot_left_margin, plot_right_margin, plot_top_margin, plot_bottom_margin]) { cmd.call }
- set_subframe([legend_left_margin, legend_right_margin, legend_top_margin, legend_bottom_margin])
+ subplot([plot_left_margin, plot_right_margin, plot_top_margin, plot_bottom_margin]) { cmd.call(self) }
+ # We temporary store the legend information in @legend_subframes
+ set_subframe(@legend_subframe)
rescale(legend_scale) # note that legend_scale is an addition to the plot_scale, not a replacement
@pr_margin = plot_right_margin
- show_legend
+ show_legend(legend_background_function)
+ @legend_subframe = nil
end
def append_points_with_gaps_to_path(xs, ys, gaps, close_subpaths = false)
private_append_points_with_gaps_to_path(xs, ys, gaps, close_subpaths)
end
@@ -1437,10 +1606,21 @@
end_circle[0], end_circle[1], end_circle[2], colormap,
x_hat[0], y_hat[0], x_hat[1], y_hat[1],
extend_start, extend_end)
end
+
+ def string_hls_to_rgb(str)
+ string_hls_to_rgb!(String.new(str))
+ end
+
+
+ def string_rgb_to_hls(str)
+ string_rgb_to_hls!(String.new(str))
+ end
+
+
@@keys_for_show_image = FigureMaker.make_name_lookup_hash([
'll', 'lr', 'ul', 'w', 'width', 'height', 'h',
'opacity_mask', 'stencil_mask',
'jpg', 'JPG', 'jpeg', 'JPEG', 'interpolate', 'data', 'value_mask',
'color_space', 'color_map', 'colormap'])
@@ -1622,45 +1802,26 @@
raise "Sorry: Must give either 'marker' or 'string' for show_marker"
end
if (marker != nil && string != nil)
raise "Sorry: Must give either 'marker' or 'string' for show_marker, but not both"
end
- if (marker == nil)
- glyph = stroke_width = nil
- else
- if !(marker.kind_of?Array) && marker.size >= 2 && marker.size <= 3
- raise "Sorry: 'marker' for show_marker must be array of [font_number, char_code] or [font_number, char_code, rendering_mode]"
- end
- font = marker[0]
- glyph = marker[1]
- if marker.size == 3
- mode = STROKE if mode == nil
- stroke_width = marker[2]
- else
- mode = FILL if mode == nil
- stroke_width = nil
- end
- end
- mode = FILL if mode == nil
- font = Times_Roman if (font == nil && string != nil)
- if (font != nil && !(font.kind_of? Integer))
- raise "Sorry: 'font' for show_marker must be an integer font number (see FigureConstants list)"
- end
+
stroke_width = get_if_given_else_default(dict, 'stroke_width', stroke_width)
angle = get_if_given_else_use_default_dict(dict, 'angle', @marker_defaults)
scale = get_if_given_else_use_default_dict(dict, 'scale', @marker_defaults)
just = get_if_given_else_use_default_dict(dict, 'justification', @marker_defaults)
align = get_if_given_else_use_default_dict(dict, 'alignment', @marker_defaults)
h_scale = get_if_given_else_use_default_dict(dict, 'horizontal_scale', @marker_defaults)
v_scale = get_if_given_else_use_default_dict(dict, 'vertical_scale', @marker_defaults)
it_angle = get_if_given_else_use_default_dict(dict, 'italic_angle', @marker_defaults)
ascent_angle = get_if_given_else_use_default_dict(dict, 'ascent_angle', @marker_defaults)
glyph = 0 if glyph == nil
- int_args = glyph*100000 + font*1000 + mode*100 + align*10 + (just+1) # min value for just is -1
- # Ruby limits us to 15 args, so pack some small integers together
- private_show_marker(int_args, stroke_width, string, x, y, xs, ys,
- h_scale, v_scale, scale, it_angle, ascent_angle, angle, fill_color, stroke_color)
+
+ args = [marker, font, mode, align, just, stroke_width, string, x, y, xs, ys,
+ h_scale, v_scale, scale, it_angle, ascent_angle, angle, fill_color, stroke_color]
+ private_show_marker(args)
+
legend_arg = dict['legend']
if legend_arg != nil
if legend_arg.kind_of?Hash
legend = legend_arg.dup
legend["line_type"] = 'None' if legend["line_type"] == nil
@@ -1782,11 +1943,10 @@
end
@eval_command = cmd
end
-
def def_enter_page_function(&cmd)
if cmd == nil
raise "Sorry: must provide a command block for def_enter_page_function"
end
@enter_page_function = cmd
@@ -1948,11 +2108,12 @@
end
# We wrap the call so that if the keys of @measures
# did change during the call, we call it again.
- def make_pdf(num) # returns pdf name if successful, false if failed.
+ def make_pdf(num,&cmd) # returns pdf name if successful, false if failed.
+ def_figure(num,&cmd) if Kernel.block_given?
old_measure_keys = @measures.keys
num = get_num_for_pdf(num)
result = start_making_pdf(num)
return unless result
begin
@@ -1961,10 +2122,11 @@
rescue Exception => e
p e, e.backtrace
end
if @measures.keys != old_measure_keys
+ # we dont need to pass &cmd since it has now been defined
make_pdf(num)
end
return @figure_pdfs[num]
end
@@ -2257,10 +2419,14 @@
end
if color_space == 'RGB' || color_space == 'rgb'
private_show_rgb_image(ll[0], ll[1], lr[0], lr[1], ul[0], ul[1], interpolate, w, h, data, mask_xo_num)
return self
end
+ if color_space == 'HLS' || color_space == 'hls'
+ private_show_hls_image(ll[0], ll[1], lr[0], lr[1], ul[0], ul[1], interpolate, w, h, data, mask_xo_num)
+ return self
+ end
if color_space == 'CMYK' || color_space == 'cmyk'
private_show_cmyk_image(ll[0], ll[1], lr[0], lr[1], ul[0], ul[1], interpolate, w, h, data, mask_xo_num)
return self
end
if value_mask == nil
@@ -2308,29 +2474,29 @@
def make_page(cmd) # the C implementation uses this to call the page function for the figure
entry_function = @enter_page_function
exit_function = @exit_page_function
unless entry_function == nil
begin
- entry_result = entry_function.call
+ entry_result = entry_function.call(self)
rescue Exception => er
report_error(er, nil)
end
end
result = do_cmd(cmd)
unless result == false or exit_function == nil
begin
- exit_result = exit_function.call
+ exit_result = exit_function.call(self)
rescue Exception => er
report_error(er, nil)
end
end
return result
end
def do_cmd(cmd) # the C implementation uses this to call Ruby commands
begin
- cmd.call
+ cmd.call(self)
return true
rescue Exception => er
report_error(er, nil)
end
return false