class GraphKit
A GraphKit
is ‘a complete kit of things needed to make a graph’. By graph, a 1d, 2d, 3d, or 4d (3 + colouring/shading) visualisation is meant. The first thee axes are given the traditional labels :x, :y, :z, and the fourth :f, meaning fill, or function.
Time variation can be can be achieved by an array of GraphKits each at a different time.
A GraphKit
is a hash where some of the keys must be specific things, and it provides a method, check_integrity
, to ensure that the keys are assigned correctly. This method also checks that dimensions and ranks of all the data are consistent, allowing a graph to be plotted.
GraphKit
also allows you access any property, e.g. title, as
graphkit.title graphkit.title =
as well as the hash form:
graphkit[:title] graphkit[:title] =
GraphKits have methods which allow the graphs to be rendered using standard visualisation packages. At present only gnuplot is supported, but others are planned.
GraphKits overload certain operators, for example +
, which mean that they can be combined easily and intuitively. This makes plotting graphs from different sets of results on the same page as easy as adding 2+2!
GraphKits define a minimum set of keys which are guaranteed to be meaningful and work on all platforms. If you stick to using just these properties, you’ll be able to easily plot basic graphs. If you need more control and customisation, you need to look at the documentation both for the visualisation package (e.g. gnuplot) and the module which allows a GraphKit
to interface with that package (e.g. GraphKit::Gnuplot
).
Here is the specification for the standard keys:
-
title (String): the title of the graph
-
xlabel, ylabel, zlabel (String): axis labels
-
xrange, yrange, zrange, frange (Array): ranges of the three dimensions and possibly the function as well.
-
data (Array of GraphKit::DataKits): the lines of data to be plotted.
Methods for writing graphkits to csv (comma separated value) files
Methods for writing graphkits to csv (comma separated value) files
Constants
- AXES
- DEFAULT_COLOURS
- DEFAULT_COLOURS_GNUPLOT
- DEFAULT_COLOURS_MATHEMATICA
- GNUPLOT_DEFAULT_COLOURS
- GNUPLOT_DEFAULT_TERM
- MultiWindow
Public Class Methods
Create a new graphkit with one hash for every datakit (each datakit corresponds to a line or surface on the graph). Each hash should contain specifications for the axes of the graph (see AxisKit#autocreate).
E.g.
kit = GraphKit.autocreate( { x: {data: [1,2,3], title 'x', units: 'm'}, y: {data: [1,4,9], title 'x^2', units: 'm^2'} } )
will create a two dimensional graph that plots x^2 against x.
# File lib/graphkit.rb, line 357 def self.autocreate(*hashes) Log.logf :autocreate new(hashes[0].size).autocreate(*hashes) end
# File lib/graphkit/gnuplot.rb, line 534 def self.latex_multiplot(name, options={}) name = name.sub(/\.eps$/, '') figure_preamble = options[:preamble] || <<EOF \\documentclass[graphicx,reprint,twocolumn]{revtex4} %\documentclass[aip,reprint]{revtex4-1} \\usepackage{graphics,bm,overpic,color} \\usepackage[tight]{subfigure} \\pagestyle{empty} \\begin{document} \\begin{figure} EOF figure_postamble = options[:postamble] || <<EOF \\end{figure} \\end{document} EOF text = <<EOF #{figure_preamble} #{yield} #{figure_postamble} EOF File.open("#{name}.tex", 'w'){|f| f.puts text} raise 'latex failed' unless system "latex #{name}.tex" raise 'dvips failed' unless system "dvips #{name}.dvi" FileUtils.rm "#{name}.eps" if FileTest.exist? "#{name}.eps" raise 'ps2eps failed' unless system "ps2eps #{name}.ps" end
Greate a new graphkit. Rarely used: see GraphKit.autocreate
and GraphKit.quick_create
# File lib/graphkit.rb, line 326 def initialize(naxes=0, hash = {}) logf :initialize super() self[:naxes] = naxes self[:data] = [] hash # @gnuplot_options = GnuplotOptions.new end
Create a new graphkit without providing any labels.
E.g.
kit = GraphKit.quick_create( [ [1,2,3], [1,4,9] ] )
will create a two dimensional graph that plots x^2 against x.
# File lib/graphkit.rb, line 383 def self.quick_create(*datasets) hashes = datasets.map do |data| hash = {} data.each_with_index do |dat, i| hash[AXES[i]] = {data: dat} end hash end autocreate(*hashes) end
Public Instance Methods
Combine with another graph; titles and labels from the first graph will override the second.
# File lib/graphkit.rb, line 455 def +(other) check(['other.naxes', other.naxes, self.naxes]) new = self.class.new(naxes) new.modify(other, [:data]) new.modify(self, [:data]) new.data = self.data + other.data new end
Modify the graphkit according to the options hash
# File lib/graphkit/gnuplot.rb, line 252 def apply_gnuplot_options(options) options = options.dup # No reason to modify the original hash logf :gnuplot processes = %x[ps | grep 'gnuplot'].scan(/^\s*\d+/).map{|match| match.to_i} if options[:outlier_tolerance] raise "Can only get rid of outliers for 1D or 2D data" if naxes > 2 data.each do |datakit| datakit.outlier_tolerance = options[:outlier_tolerance] # datakit.calculate_outliers datakit.exclude_outliers end options.delete(:outlier_tolerance) end self.live = options[:live] options.delete(:live) if live if (self.view =~ /map/ or self.pm3d =~ /map/) and naxes < 4 self.cbrange ||= self.zrange options[:cbrange] ||= options[:zrange] if options[:zrange] end if options[:eval] eval(options[:eval]) options.delete(:eval) end options.each do |k,v| # ep option, val if option == :xrange # ep k, v set(k, v) end end
Check that the graphkit conforms to specification; that the data has dimensions that make sense and that the titles and ranges have the right types.
# File lib/graphkit.rb, line 413 def check_integrity logf :check_integrity check(['data.class', Array], ['title.class', [String, NilClass]], ['has_legend.class', [Hash, NilClass]]) [[:units, [String, NilClass]], [:range, [Array, NilClass]], [:label, [String, NilClass]], [:title, [String, NilClass]]].each do |prop, klass| # next unless self[prop] AXES.each do |key| # p prop, klass # p instance_eval(prop.to_s + "[#{key.inspect}]"), 'ebb' check(["#{key + prop}.class", klass]) # check(["a key from #{prop}", key, AXES + [:f]]) end end data.each do |datakit| check(['class of a member of the data array', datakit.class, DataKit]) check(['datakit.axes.size', datakit.axes.size, naxes]) datakit.check_integrity end return true end
# File lib/graphkit/gnuplot.rb, line 349 def close logf :close begin #require 'sys/proctable' #p Sys::ProcTable.ps.select{ |pe| pe.ppid == pid } Process.kill('TERM', pid) if pid rescue => err puts err, pid end self.pid = nil end
# File lib/graphkit.rb, line 507 def convert(&block) #ep 'Converting graph...' kit = self.dup #p kit kit.data.map! do |dk| dk.convert(&block) end kit end
Convert the rank of the data from from_to to from_to. E.g. convert a line of values of [x, y, z] with rank [1,1,2] to a matrix of values [x, y, z] with rank [1, 1, 2]
convert_rank!([[1,1,1], [1,1,2]])
# File lib/graphkit.rb, line 525 def convert_rank!(from_to, options={}) ep "Converting Rank" case from_to when [[1,1,1], [1,1,2]] dependent_dimension = options[:dd] || options[:dependent_dimension] || 2 other_dims = [0,1,2] - [dependent_dimension] od0 = other_dims[0] od1 = other_dims[1] data.each do |dk| new_data = SparseTensor.new(2) od0_data = dk.axes[AXES[od0]].data.to_a.uniq.sort od1_data = dk.axes[AXES[od1]].data.to_a.uniq.sort for i in 0...dk.axes[:x].data.size od0_index = od0_data.index(dk.axes[AXES[od0]].data[i]) od1_index = od1_data.index(dk.axes[AXES[od1]].data[i]) new_data[[od0_index, od1_index]] = dk.axes[AXES[dependent_dimension]].data[i] end dk.axes[AXES[od0]].data = od0_data dk.axes[AXES[od1]].data = od1_data dk.axes[AXES[dependent_dimension]].data = new_data end else raise "Converting from #{from_to[0].inspect} to #{from_to[1].inspect} is not implemented yet." end eputs self.pretty_inspect end
Duplicate the graphkit.
# File lib/graphkit.rb, line 447 def dup #logf :dup #self.class.new(naxes, self) eval(inspect) end
# File lib/graphkit.rb, line 477 def each_axiskit(*axes, &block) axes = AXES unless axes.size > 0 axes.each do |axis| data.each do |datakit| yield(datakit.axes[axis]) end end end
# File lib/graphkit.rb, line 464 def extend_using(other, mapping = nil) if mapping mapping.each do |mine, others| data[mine].extend_using(other.data[others]) end else raise TypeError.new("A graph can only be extended by a graph with the same number of datasets unless a mapping is provided: this graph has #{data.size} and the other graph has #{other.data.size}") unless data.size == other.data.size data.each_with_index do |dataset, index| dataset.extend_using(other.data[index]) end end end
# File lib/graphkit/gnuplot.rb, line 304 def gnuplot(options={}) apply_gnuplot_options(options) apply_graphkit_standard_options_to_gnuplot check_integrity if options[:io] send_gnuplot_commands(io) else Gnuplot.open(true) do |io| send_gnuplot_commands(io) (STDIN.gets) if live end end end
# File lib/graphkit/gnuplot.rb, line 238 def gnuplot_sets # gnuplot_options included for back. comp self[:gnuplot_sets] ||= @gnuplot_options || GnuplotSetOptions.new self[:gnuplot_sets] end
# File lib/graphkit/gnuplot.rb, line 245 def gnuplot_variables @gnuplot_variables ||= GnuplotVariables.new end
# File lib/graphkit.rb, line 486 def plot_area_size logf :plot_area_size shapes = data.map{|datakit| datakit.plot_area_size}.inject do |old,new| for i in 0...old.size old[i][0] = [old[i][0], new[i][0]].min old[i][1] = [old[i][1], new[i][1]].max end old end shapes end
# File lib/graphkit/gnuplot.rb, line 318 def send_gnuplot_commands(io) self.pid = io.pid gnuplot_sets.apply(io) gnuplot_variables.apply(io) case naxes when 1,2 io << "plot " when 3,4 io << "splot " end imax = data.size - 1 data.each_with_index do |dk,i| next if i>0 and compress_datakits dk.gnuplot_plot_options.with ||= dk.with #b.c. dk.gnuplot_plot_options.title ||= dk.title #b.c. dk.gnuplot_plot_options.apply(io) #p 'imax', imax, i, i == imax next if compress_datakits io << ", " unless i == imax end io << "\n" data.each_with_index do |dk,i| dk.gnuplot(io) unless compress_datakits and i<imax io << "e\n\n" else io << "\n\n" end end end
# File lib/graphkit/csv.rb, line 5 def to_csv(options={}) check_integrity stringio = options[:io] || StringIO.new data.each do |dk| dk.to_csv(options) stringio << "\n\n" end return stringio.string unless options[:io] end
Convert graphkit into a string which can be used by Mathematica
# File lib/graphkit/mm.rb, line 3 def to_mathematica str = "" case data[0].ranks when [1], [1, 1] str = "ListPlot[{#{data.map{|dk| dk.to_mathematica}.join(',')}}" end pas = plot_area_size if (xrange or yrange or zrange) str << ", PlotRange -> " str << "{#{((0...naxes).to_a.map do |nax| ax = AXES[nax] specified_range = (send(ax + :range) or [nil, nil]) "{#{specified_range.zip(pas[nax]).map{|spec, pasr| (spec or pasr).to_s}.join(',')}}" end).join(',')}}" end str << ", PlotStyle -> {#{(data.size.times.map do |i| ((data[i].mtm.plot_style) or "{}") end).join(',')}}" str << "]" end
# File lib/graphkit/vtk_legacy_ruby.rb, line 17 def to_vtk_legacy(options={}) check_integrity ep 'to_vtk_legacy' stringio = options[:io] || StringIO.new npoints = data.map{|dk| dk.vtk_legacy_size}.sum stringio << <<EOF # vtk DataFile Version 3.0 vtk output ASCII DATASET UNSTRUCTURED_GRID POINTS #{npoints} float EOF data.each do |dk| dk.vtk_legacy_points(io: stringio) #stringio << "\n\n" end #ncells = data.map{|dk| dk.vtk_legacy_cells(action: :count)}.sum #ncell_elements = data.map{|dk| dk.vtk_legacy_cells(action: :count_elements)}.sum cellstring = "" pc = 0 data.each do |dk| cellstring += dk.vtk_legacy_cells pointcount: pc pc += dk.vtk_legacy_size #stringio << "\n\n" end ncells = cellstring.count("\n") ncell_elements = cellstring.scan(/\s+/).size stringio << <<EOF CELLS #{ncells} #{ncell_elements} EOF stringio << cellstring stringio << <<EOF CELL_TYPES #{ncells} EOF data.each do |dk| dk.vtk_legacy_cell_types(io: stringio) #stringio << "\n\n" end stringio << <<EOF POINT_DATA #{npoints} SCALARS myvals float LOOKUP_TABLE default EOF data.each do |dk| dk.vtk_legacy_point_data(io: stringio) #stringio << "\n\n" end return stringio.string unless options[:io] end
# File lib/graphkit/vtk_legacy_ruby.rb, line 14 def to_vtk_legacy_fast(options={}) File.open(options[:file_name], 'w'){|file| file.puts to_vtk_legacy} end
# File lib/graphkit.rb, line 499 def transpose! data.each do |datakit| datakit.transpose! end self.xlabel, self.ylabel = ylabel, xlabel self.xrange, self.yrange = xrange, yrange end
Private Instance Methods
# File lib/graphkit/gnuplot.rb, line 283 def apply_graphkit_standard_options_to_gnuplot [:label, :range].each do |property| (AXES - [:f]).each do |axis| option = axis + property val = self.send(option) if val if property == :range val = "[#{val[0]}:#{val[1]}]" end gp.set(option, val) end end end [:title].each do |option| val = send(option) gp.set(option, val) if val end end