split/Tioga/lib/FigMkr.rb in tioga-1.6 vs split/Tioga/lib/FigMkr.rb in tioga-1.7

- old
+ new

@@ -19,34 +19,34 @@ along with Tioga; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA =end require 'Tioga/FigureConstants.rb' +require 'Tioga/Utils.rb' module Tioga class FigureMaker include FigureConstants @@default_figure_maker = nil @@which_pdflatex = nil @@initialized = false # set true by the C code when first make a figure - - # The tag used for cvs export - CVS_TAG = "rel_1_6" # now manually cheating... - - # Version now uses the CVS_TAG to create the version number. CVS_TAG should - # look like 'rel_1_1_0' for the 1.1.0 release. + # 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.7/split/Tioga/lib/FigMkr.rb $' + + TIOGA_VERSION = if SVN_URL =~ /tags\/tioga\/Tioga%20([^\/]+)/ + $1 + else + "SVN version" + end + + def FigureMaker.version - CVS_TAG =~ /\D+(.*?)\s*\$?$/ - version = $1.tr("-_", "..") - if version.length > 0 - return version - else - return "SVN $Revision: 385 $" # Can't do better than that. - end + TIOGA_VERSION end def FigureMaker.default @@default_figure_maker = FigureMaker.new if @@default_figure_maker == nil @@ -56,29 +56,45 @@ def FigureMaker.default=(fm) @@default_figure_maker = fm end def FigureMaker.pdflatex - @@which_pdflatex = 'pdflatex' if @@which_pdflatex == nil - @@which_pdflatex + @@which_pdflatex = 'pdflatex' if @@which_pdflatex == nil + @@which_pdflatex end def FigureMaker.pdflatex=(s) - @@which_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 + + + attr_accessor :title + attr_accessor :xlabel + attr_accessor :ylabel + + attr_accessor :xaxis_locations_for_major_ticks + attr_accessor :xaxis_locations_for_minor_ticks + attr_accessor :xaxis_tick_labels + + attr_accessor :yaxis_locations_for_major_ticks + attr_accessor :yaxis_locations_for_minor_ticks + attr_accessor :yaxis_tick_labels + attr_accessor :style_filename attr_accessor :legend_info + attr_reader :line_type + attr_reader :num_figures attr_reader :figure_names attr_reader :figure_pdfs @@ -118,13 +134,14 @@ # Whether or not do do automatic cleanup of the files attr_accessor :autocleanup # Whether or not do do multithreading for parallel pdflatex calls attr_accessor :multithreads_okay_for_tioga - - - + + # An accessor for @measures_info: + attr_accessor :measures_info + # old preview attributes -- to be removed later attr_accessor :model_number attr_accessor :add_model_number attr_accessor :need_to_reload_data @@ -142,11 +159,10 @@ #attr_accessor :tex_preview_voffset #attr_accessor :tex_preview_figure_width #attr_accessor :tex_preview_figure_height #attr_accessor :tex_preview_minwhitespace #attr_accessor :tex_preview_fullpage - def reset_figures # set the state to default values @figure_commands = [] @num_figures = 0 @create_save_dir = true # creates +save_dir+ by default @@ -188,10 +204,12 @@ @tex_preview_figure_width = '\paperwidth - 2in' @tex_preview_figure_height = '\paperheight - 2in' @num_error_lines = 10 + reset_plot_attrs + @tex_xoffset = 0 @tex_yoffset = 0 @tex_preview_hoffset = '1in' @tex_preview_voffset = '1in' @@ -250,18 +268,63 @@ # Automatic cleanup of by default @autocleanup = true # multithreads by default @multithreads_okay_for_tioga = true - + + + # The values of the sizes measured during the pdflatex run + # we need to keep track of them so we can decide how many times + # we'll run pdflatex. + @measures = {} + + # We *must* initialize the measures_info hash. + @measures_info = {} end + - def initialize + def reset_plot_attrs + @title = nil + @xlabel = nil + @ylabel = nil + @line_type = nil + @xaxis_locations_for_major_ticks = nil + @xaxis_locations_for_minor_ticks = nil + @xaxis_tick_labels = nil + @yaxis_locations_for_major_ticks = nil + @yaxis_locations_for_minor_ticks = nil + @yaxis_tick_labels = nil + private_init_fm_data + end + + + def initialize + @fm_data = Dvector.new(@@fm_data_size) reset_figures end + # Returns informations about the size of the named element. + # Returns a hash: + # * 'width' : the width of the box in figure coordinates + # * 'height': the height of the box in figure coordinates + # * 'just' : the justification used + # * 'align' : the vertical alignment used + # * 'angle' : the angle used + # * 'scale' : the *total* scale used. + # + # If the measurement did not take place yet, the width, height, + # and other attributes will be missing. Look for those. + def get_text_size(name, default = 1.0) + if self.measures_info.key? name + return self.measures_info[name] + else + return {} # Empty hash + end + end + + def reset_state reset_figures end @@ -349,10 +412,14 @@ def line_color=(color) self.stroke_color=(color) end + def line_type=(val) + self.line_type_set(val) + end + def stroke_width self.line_width end def stroke_width=(width) @@ -588,15 +655,16 @@ 'x' => x, 'y' => y, 'scale' => self.legend_scale, 'justification' => self.legend_justification, 'alignment' => self.legend_alignment) end line_width = dict['line_width'] - if line_width >= 0 + 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'] self.line_width = dict['line_width'] self.line_cap = dict['line_cap'] - self.line_type = dict['line_type'] + self.line_type = line_type stroke_line(line_x0, y+line_dy, line_x1, y+line_dy) end # place any marker right in the middle of the line if dict['marker_dict'] != nil marker_dict = dict['marker_dict'] @@ -872,12 +940,42 @@ return { 'top_margin' => top_margin, 'bottom_margin' => bottom_margin } end def context(&cmd) - trace_cmd_no_arg(@enter_context_function, @exit_context_function) { - private_context(cmd) } + trace_cmd_no_arg(@enter_context_function, @exit_context_function) { + + save_title = self.title + save_xlabel = self.xlabel + save_ylabel = self.ylabel + save_line_type = self.line_type + save_xaxis_locations_for_major_ticks = self.xaxis_locations_for_major_ticks + save_xaxis_locations_for_minor_ticks = self.xaxis_locations_for_minor_ticks + save_xaxis_tick_labels = self.xaxis_tick_labels + save_yaxis_locations_for_major_ticks = self.yaxis_locations_for_major_ticks + 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 + ensure + pdf_grestore + self.title = save_title + self.xlabel = save_xlabel + self.ylabel = save_ylabel + self.line_type = save_line_type + self.xaxis_locations_for_major_ticks = save_xaxis_locations_for_major_ticks + self.xaxis_locations_for_minor_ticks = save_xaxis_locations_for_minor_ticks + self.xaxis_tick_labels = save_xaxis_tick_labels + self.yaxis_locations_for_major_ticks = save_yaxis_locations_for_major_ticks + self.yaxis_locations_for_minor_ticks = save_yaxis_locations_for_minor_ticks + self.yaxis_tick_labels = save_yaxis_tick_labels + @fm_data.replace(save_fm_data) + end + + } end def rescale(factor) rescale_text(factor) @@ -1021,35 +1119,47 @@ check_dict(dict, @@keys_for_make_contour, 'make_contour') z_level = dict['z_level'] if z_level == nil z_level = complain_if_missing_numeric_arg(dict, 'z', 'level', 'make_contour') end - dest_xs = get_dvec(dict, 'dest_xs', 'make_contour') - dest_ys = get_dvec(dict, 'dest_ys', 'make_contour') + dest_xs = dict['dest_xs'] + dest_ys = dict['dest_ys'] xs = get_dvec(dict, 'xs', 'make_contour') ys = get_dvec(dict, 'ys', 'make_contour') gaps = dict['gaps'] if (!(gaps.kind_of? Array)) raise "Sorry: 'gaps' for 'make_contour' must be an Array" end zs = alt_names(dict, 'zs', 'data') if (!(zs.kind_of? Dtable)) raise "Sorry: 'zs' for 'make_contour' must be a Dtable" end - dest_xs.clear; dest_ys.clear; gaps.clear + dest_xs.clear unless dest_xs == nil + dest_ys.clear unless dest_ys == nil + gaps.clear legit = dict['legit'] if legit == nil legit = Dtable.new(xs.length,ys.length).set(1.0) elsif (!(legit.kind_of? Dtable)) raise "Sorry: 'legit' for 'make_contour' must be a Dtable -- nonzero means legitimate value in corresponding entry in zs" end method = dict['method'] use_conrec = (method == 'conrec' or method == 'CONREC')? 1 : 0 - private_make_contour(dest_xs, dest_ys, gaps, xs, ys, zs, z_level, legit, use_conrec) + pts_array = private_make_contour(gaps, xs, ys, zs, z_level, legit, use_conrec) + unless dest_xs == nil + dest_xs.resize(pts_array[0].size) + dest_xs.replace(pts_array[0]) + end + unless dest_ys == nil + dest_ys.resize(pts_array[1].size) + dest_ys.replace(pts_array[1]) + end + return pts_array + end @@keys_for_make_steps = FigureMaker.make_name_lookup_hash([ 'xfirst', 'x_first', 'yfirst', 'y_first', 'xlast', 'x_last', 'ylast', 'y_last', 'xs', 'ys', 'dest_xs', 'dest_ys']) @@ -1057,28 +1167,42 @@ check_dict(dict, @@keys_for_make_steps, 'make_steps') xfirst = complain_if_missing_numeric_arg(dict, 'xfirst', 'x_first', 'make_steps') yfirst = complain_if_missing_numeric_arg(dict, 'yfirst', 'y_first', 'make_steps') xlast = complain_if_missing_numeric_arg(dict, 'xlast', 'x_last', 'make_steps') ylast = complain_if_missing_numeric_arg(dict, 'ylast', 'y_last', 'make_steps') - dest_xs = get_dvec(dict, 'dest_xs', 'make_steps') - dest_ys = get_dvec(dict, 'dest_ys', 'make_steps') + dest_xs = dict['dest_xs'] + dest_ys = dict['dest_ys'] xs = get_dvec(dict, 'xs', 'make_steps') ys = get_dvec(dict, 'ys', 'make_steps') - private_make_steps(dest_xs, dest_ys, xs, ys, xfirst, yfirst, xlast, ylast) + pts_array = private_make_steps(xs, ys, xfirst, yfirst, xlast, ylast) + unless dest_xs == nil + dest_xs.resize(pts_array[0].size) + dest_xs.replace(pts_array[0]) + end + unless dest_ys == nil + dest_ys.resize(pts_array[1].size) + dest_ys.replace(pts_array[1]) + end + return pts_array end @@keys_for_make_curves = FigureMaker.make_name_lookup_hash([ 'start_slope', 'end_slope', 'xs', 'ys', 'sample_xs', 'result_ys']) def make_spline_interpolated_points(dict) check_dict(dict, @@keys_for_make_curves, 'make_spline_interpolated_points') start_slope = dict['start_slope'] end_slope = dict['end_slope'] sample_xs = get_dvec(dict, 'sample_xs', 'make_spline_interpolated_points') - result_ys = get_dvec(dict, 'result_ys', 'make_spline_interpolated_points') xs = get_dvec(dict, 'xs', 'make_spline_interpolated_points') ys = get_dvec(dict, 'ys', 'make_spline_interpolated_points') - private_make_spline_interpolated_points(sample_xs, result_ys, xs, ys, start_slope, end_slope) + yvec = private_make_spline_interpolated_points(sample_xs, xs, ys, start_slope, end_slope) + result_ys = dict['result_ys'] + unless result_ys == nil + result_ys.resize(yvec.size) + result_ys.replace(yvec) + end + return yvec end @@keys_for_make_interpolant = FigureMaker.make_name_lookup_hash([ 'start_slope', 'end_slope', 'xs', 'ys']) def make_interpolant(dict) @@ -1112,15 +1236,19 @@ raise "Sorry: Must give both 'x' and 'y' for show_error_bar." end dx = dict['dx'] dx_plus = get_if_given_else_default(dict, 'dx_plus', dx) dx_minus = get_if_given_else_default(dict, 'dx_minus', dx) + dx_plus = 0 if dx_plus == nil + dx_minus = 0 if dx_minus == nil dy = dict['dy'] dy_plus = get_if_given_else_default(dict, 'dy_plus', dy) dy_minus = get_if_given_else_default(dict, 'dy_minus', dy) - if (dx_plus == nil || dx_minus == nil || dy_plus == nil || dy_minus == nil) - raise "Sorry: Must give both 'dx' and 'dy' error ranges for show_error_bar." + dy_plus = 0 if dy_plus == nil + dy_minus = 0 if dy_minus == nil + if (dx_plus == 0 && dy_minus == 0) + raise "Sorry: Must give either or both 'dx' and 'dy' error ranges for show_error_bar." end end_cap = get_if_given_else_default(dict, 'end_cap', 0.15) # end_cap length in default text heights x_end_cap = end_cap * self.default_text_height_dy y_end_cap = end_cap * self.default_text_height_dx line_width = get_if_given_else_default(dict, 'line_width', 1) @@ -1457,11 +1585,11 @@ return private_create_monochrome_image_data(data, first_row, last_row, first_column, last_column, boundary, reverse); end @@keys_for_show_marker = FigureMaker.make_name_lookup_hash([ - 'marker', 'x', 'y', 'at', 'point', 'Xs', 'Ys', 'xs', 'ys', 'mode', 'rendering_mode', + 'marker', 'x', 'y', 'at', 'point', 'Xs', 'Ys', 'xs', 'ys', 'mode', 'rendering_mode', 'legend', 'angle', 'scale', 'font', 'string', 'text', 'color', 'fill_color', 'stroke_color', 'stroke_width', 'horizontal_scale', 'vertical_scale', 'italic_angle', 'ascent_angle', 'alignment', 'justification']) def show_marker(dict) check_dict(dict, @@keys_for_show_marker, 'show_marker') marker = dict['marker'] @@ -1525,14 +1653,34 @@ 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 - # Ruby limits us to 15 args, so pack some small integers together + 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) + h_scale, v_scale, scale, it_angle, ascent_angle, angle, fill_color, stroke_color) + 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 + legend["marker"] = marker if legend["marker"] == nil + legend["marker_scale"] = scale if legend["marker_scale"] == nil + legend["marker_color"] = fill_color if legend["marker_color"] == nil + save_legend_info(legend) + elsif legend_arg.kind_of?String + save_legend_info( + 'line_type' => 'None', + 'marker' => marker, + 'marker_scale' => scale, + 'marker_color' => fill_color, + 'text' => legend_arg) + else + save_legend_info(legend_arg) + end + end end def show_label(dict) at = alt_names(dict, 'at', 'point') if check_pair(at, 'at', 'show_text') @@ -1549,21 +1697,21 @@ show_text(dict) end @@keys_for_show_text = FigureMaker.make_name_lookup_hash([ 'text', 'side', 'loc', 'position', 'pos', 'x', 'y', - 'shift', 'scale', 'color', 'angle', 'alignment', 'justification', 'at', 'point']) + 'shift', 'scale', 'color', 'angle', 'alignment', 'justification', 'at', 'point', 'measure']) def show_text(dict) check_dict(dict, @@keys_for_show_text, 'show_text') text = dict['text'] if text == nil raise "Sorry: Must supply 'text' entry in dictionary for show_text" end scale = get_if_given_else_default(dict, 'scale', 1) color = dict['color'] # color is [r,g,b] array. this adds \textcolor[rgb]{r,g,b}{...} if color != nil - if !color.kind_of?Array + if !color.kind_of? Array raise "Sorry: 'color' must be array of [r,g,b] intensities for show_text (#{color})" end r = color[0]; g = color[1]; b = color[2]; if (!(r.kind_of? Numeric) || !(g.kind_of? Numeric) || !(b.kind_of? Numeric) ) raise "Sorry: 'color' must be array of [r,g,b] intensities for show_text" @@ -1571,10 +1719,11 @@ text = sprintf("\\textcolor[rgb]{%0.2f,%0.2f,%0.2f}{%s}", r, g, b, text) end just = get_if_given_else_default(dict, 'justification', self.justification) align = get_if_given_else_default(dict, 'alignment', self.alignment) angle = get_if_given_else_default(dict, 'angle', 0) + loc = alt_names(dict, 'loc', 'side') if (loc == nil) at = alt_names(dict, 'at', 'point') if check_pair(at, 'at', 'show_text') xloc = at[0] @@ -1584,11 +1733,12 @@ yloc = dict['y'] end if (xloc == nil || yloc == nil) raise "Sorry: Must supply a location for show_text" end - show_rotated_label(text, xloc, yloc, scale, angle, just, align) + show_rotated_label(text, xloc, yloc, scale, angle, just, align, + dict['measure'] || nil) return end position = alt_names(dict, 'position', 'pos') position = 0.5 if position == nil shift = dict['shift'] @@ -1611,14 +1761,16 @@ yloc = shift*self.char_height_dy xloc = convert_frame_to_figure_x(position) else raise "Sorry: 'loc' must be LEFT, RIGHT, TOP, BOTTOM, AT_X_ORIGIN, or AT_Y_ORIGIN for show_text" end - show_rotated_label(text, xloc, yloc, scale, angle, just, align) + show_rotated_label(text, xloc, yloc, scale, angle, just, align, + dict['measure']) return end - show_rotated_text(text, loc, shift, position, scale, angle, just, align) + show_rotated_text(text, loc, shift, position, scale, angle, just, + align, dict['measure']) end def reset_eval_function @eval_command = nil @@ -1794,15 +1946,28 @@ def make_preview_pdf(num) # old name make_pdf(num) 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. + old_measure_keys = @measures.keys num = get_num_for_pdf(num) result = start_making_pdf(num) return unless result - return @figure_pdfs[num] = finish_making_pdf(@figure_names[num]) + begin + @figure_pdfs[num] = finish_making_pdf(@figure_names[num]) + # If the keys have changed, we run that again. + rescue Exception => e + p e, e.backtrace + end + + if @measures.keys != old_measure_keys + make_pdf(num) + end + return @figure_pdfs[num] end def require_pdf(num) num = get_num_for_pdf(num) @@ -1860,13 +2025,14 @@ end return false if num == nil cmd = @figure_commands[num] return false unless cmd.kind_of?(Proc) begin + reset_plot_attrs reset_legend_info - result = private_make(name, cmd) - return result + private_make(name, cmd) + return true rescue Exception => er report_error(er, "ERROR while executing command: #{cmd}") end return false end @@ -1944,34 +2110,66 @@ end end def finish_making_pdf(name) # returns pdfname if succeeds, false if fails. - pdflatex = FigureMaker.pdflatex - quiet = @quiet_mode - run_directory = @run_dir - if (@save_dir == nil) - syscmd = "#{pdflatex} -interaction nonstopmode #{name}.tex > pdflatex.log 2>&1" - else - syscmd = "cd #{@save_dir}; #{pdflatex} -interaction nonstopmode #{name}.tex > pdflatex.log 2>&1" + pdflatex = FigureMaker.pdflatex + quiet = @quiet_mode + run_directory = @run_dir + + if (@save_dir == nil) + logname = "pdflatex.log" + else + logname = "#{@save_dir}/pdflatex.log" + end + + if (@save_dir == nil) + syscmd = "#{pdflatex} -interaction nonstopmode #{name}.tex" + else + syscmd = "cd #{@save_dir}; #{pdflatex} -interaction nonstopmode #{name}.tex" + end + # Now fun begins: + # We use IO::popen for three reasons: + # * first, we want to be able to read back information from + # pdflatex (see \tiogameasure) + # * second, this way of doing should be portable to win, while + # the > trick might not be that much... + # * third, closing the standard input of pdflatex will remove + # painful bugs, when pdflatex gets interrupted but waits + # for an input for which it didn't prompt. + + @measures = {} + IO::popen(syscmd, "r+") do |f| + f.close_write # We don't need that. + log = File.open(logname, "w") + for line in f + log.print line + if line =~ /^(.*)\[(\d)\]=(.+pt)/ + n = $1 + num = $2.to_i + dim = Utils::tex_dimension_to_bp($3) + @measures[n] ||= [] + @measures[n][num] = dim + end end - result = system(syscmd) + end + + # Now passing the saved measures to the C code. + for key, val in @measures + # p @fm_data + private_save_measure(key, *val) + end + + result = $? if !result - if (@save_dir == nil) - logname = "pdflatex.log" - else - logname = "#{@save_dir}/pdflatex.log" - end puts "ERROR: #{pdflatex} failed." file = File.open(logname) if file == nil puts "cannot open #{logname}" else reporting = false; linecount = 0 file.each_line do |line| - firstchar = line[0..0] - comparison = (firstchar <=> '!') - reporting = true if comparison == 0 + reporting = true if line =~ /^!/ if reporting puts line linecount = linecount + 1 break if linecount == @num_error_lines end