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