module Rubyvis # Constructs a new mark with default properties. Marks, with the exception of # the root panel, are not typically constructed directly; instead, they are # added to a panel or an existing mark via Mark#add # # Represents a data-driven graphical mark. The +Mark+ class is # the base class for all graphical marks in Protovis; it does not provide any # specific rendering functionality, but together with Panel establishes # the core framework. # # Concrete mark types include familiar visual elements such as bars, lines # and labels. Although a bar mark may be used to construct a bar chart, marks # know nothing about charts; it is only through their specification and # composition that charts are produced. These building blocks permit many # combinatorial possibilities. # # Marks are associated with data: a mark is generated once per # associated datum, mapping the datum to visual properties such as # position and color. Thus, a single mark specification represents a set of # visual elements that share the same data and visual encoding. The type of # mark defines the names of properties and their meaning. A property may be # static, ignoring the associated datum and returning a constant; or, it may be # dynamic, derived from the associated datum or index. Such dynamic encodings # can be specified succinctly using anonymous functions. Special properties # called event handlers can be registered to add interactivity. # # Protovis uses inheritance to simplify the specification of related # marks: a new mark can be derived from an existing mark, inheriting its # properties. The new mark can then override properties to specify new # behavior, potentially in terms of the old behavior. In this way, the old mark # serves as the prototype for the new mark. Most mark types share the # same basic properties for consistency and to facilitate inheritance. # # The prioritization of redundant properties is as follows:
dot.shape_radius(lambda { 10 / self.scale})# # Note that the stroke width and font size are defined irrespective of scale # (i.e., in screen space) already. Also note that when a transform is applied # to a panel, the scale affects only the child marks, not the panel itself. # @attr [Float] # @see Panel#transform attr_accessor :scale # Array with stores properties values attr_reader :_properties # OpenStruct which store values for scenes attr_accessor :binds # Defines a setter-getter for the specified property. # # If a cast function has been assigned to the specified property name, the # property function is wrapped by the cast function, or, if a constant is # specified, the constant is immediately cast. Note, however, that if the # property value is null, the cast function is not invoked. # # Parameters: # * @param [String] name the property name. # * @param [Boolean] +def+ whether is a property or a def. # * @param [Proc] +cast+ the cast function for this property. # * @param [Class] +klass+ the klass on which property will be added def self.property_method(name, _def, func=nil, klass=nil) return if klass.method_defined? name klass.send(:define_method, name) do |*arguments| v,dummy = arguments if _def and self.scene if arguments.size>0 defs[name]=OpenStruct.new({:id=>(v.nil?) ? 0 : Rubyvis.id, :value=> v}) return self end return defs[name] end if arguments.size>0 v=v.to_proc if v.respond_to? :to_proc type=(!_def).to_i<<1 | (v.is_a? Proc).to_i property_value(name,(type & 1 !=0) ? lambda {|*args| x=v.js_apply(self, args) (func and x) ? func.call(x) : x } : (func and v) ? func.call(v) : v)._type=type #@_properties_types[name]=type return self end i=instance() if i.nil? raise "No instance for #{self} on #{name}" else # puts "index:#{self.index}, name:#{name}, val:#{i.send(name)}" i.send(name) end end camel=name.to_s.gsub(/(_.)/) {|v| v[1,1].upcase} if camel!=name klass.send(:alias_method, camel, name) end end # Creates a dinamic property using property_method(). def self.attr_accessor_dsl(*attr) attr.each do |sym| if sym.is_a? Array name,func=sym else name=sym func=nil end @properties[name]=true self.property_method(name,false, func, Rubyvis::Mark) define_method(name.to_s+"=") {|v| self.send(name,v) } end end # Delete index # @private def delete_index @index=nil @index_defined=false end # The mark index. The value of this field depends on which instance (i.e., # which element of the data array) is currently being evaluated. During the # build phase, the index is incremented over each datum; when handling events, # the index is set to the instance that triggered the event. # @return [Integer] def index @index end # Returns true if index attribute is set and not deleted def index_defined? @index_defined end # Set index attribute. def index=(v) @index_defined=true @index=v v end # Sets the value of the property name to v def property_value(name,v) prop=Property.new({:name=>name, :id=>Rubyvis.id, :value=>v}) @_properties.delete_if{|v1| v1.name==name} @_properties.push(prop) #@_properties_values[name]=v prop end # Alias for setting the left, right, top and bottom properties simultaneously. # # @see Mark#left # @see Mark#right # @see Mark#top # @see Mark#bottom # @return [Mark] self def margin(n) self.left(n).right(n).top(n).bottom(n) end # @private Returns the current instance of this mark in the scene graph. # This is typically equivalent to this.scene[this.index], however if the scene or index is unset, the default instance of the mark is returned. If no default is set, the default is the last instance. # Similarly, if the scene or index of the parent panel is unset, the default instance of this mark in the last instance of the enclosing panel is returned, and so on. # # @return a node in the scene graph. def instance(default_index=nil) scene=self.scene scene||=self.parent.instance(-1).children[self.child_index] index = index_defined? ? self.index : default_index # "defined?: #{index_defined?} : type: #{type}, self.index: #{self.index}, index:#{index}" scene[index<0 ? scene.size-1: index] end ## # :section: Marks properties ## ## # :attr: data # The data property; an array of objects. The size of the array determines the number of marks that will be instantiated; each element in the array will be passed to property functions to compute the property values. Typically, the data property is specified as a constant array, such as # m.data([1, 2, 3, 4, 5]) # However, it is perfectly acceptable to define the data property as a # proc. This function might compute the data dynamically, allowing # different data to be used per enclosing panel. For instance, in the stacked # area graph example (see Mark#scene), the data function on the area mark # dereferences each series. ## # :attr: visible # The visible property; a boolean determining whether or not the mark instance # is visible. If a mark instance is not visible, its other properties will not # be evaluated. Similarly, for panels no child marks will be rendered. ## # :attr: left # The left margin; the distance, in pixels, between the left edge of the # enclosing panel and the left edge of this mark. Note that in some cases this # property may be redundant with the right property, or with the conjunction of # right and width. ## # :attr: right # The right margin; the distance, in pixels, between the right edge of the # enclosing panel and the right edge of this mark. Note that in some cases this # property may be redundant with the left property, or with the conjunction of # left and width. ## # :attr: top # The top margin; the distance, in pixels, between the top edge of the # enclosing panel and the top edge of this mark. Note that in some cases this # property may be redundant with the bottom property, or with the conjunction # of bottom and height. ## # :attr: bottom # The bottom margin; the distance, in pixels, between the bottom edge of the # enclosing panel and the bottom edge of this mark. Note that in some cases # this property may be redundant with the top property, or with the conjunction # of top and height. ## # :attr: cursor # The cursor property; corresponds to the CSS cursor property. This is # typically used in conjunction with event handlers to indicate interactivity. # See {CSS 2 cursor}[http://www.w3.org/TR/CSS2/ui.html#propdef-cursor] ## # :attr: title # The title property; corresponds to the HTML/SVG title property, allowing the # general of simple plain text tooltips. ## # :attr: events # The events property; corresponds to the SVG pointer-events property, # specifying how the mark should participate in mouse events. The default value # is "painted". Supported values are: # #
"painted": The given mark may receive events when the mouse is over a # "painted" area. The painted areas are the interior (i.e., fill) of the mark # if a 'fillStyle' is specified, and the perimeter (i.e., stroke) of the mark # if a 'strokeStyle' is specified. # #
"all": The given mark may receive events when the mouse is over either the # interior (i.e., fill) or the perimeter (i.e., stroke) of the mark, regardless # of the specified fillStyle and strokeStyle. # #
"none": The given mark may not receive events. ## # :attr: reverse # The reverse property; a boolean determining whether marks are ordered from # front-to-back or back-to-front. SVG does not support explicit z-ordering; # shapes are rendered in the order they appear. Thus, by default, marks are # rendered in data order. Setting the reverse property to false reverses the # order in which they are rendered; however, the properties are still evaluated # (i.e., built) in forward order. ## # :attr: id # The instance identifier, for correspondence across animated transitions. If # no identifier is specified, correspondence is determined using the mark # index. Identifiers are not global, but local to a given mark. # attr_accessor_dsl :data, :visible, :left, :right, :top, :bottom, :cursor, :title, :reverse, :antialias, :events, :id @scene=nil @stack=[] @index=nil # @private Records which properties are defined on this mark type. def self.properties @properties end # Common index for all marks def Mark.index @index end # Set common index for all marks def Mark.index=(v) @index=v end # Get common scene for all marks def self.scene @scene end # Set common scene for all marks def self.scene=(v) @scene=v end # Return properties for current mark def properties (self.class).properties end # Get common stack for all marks def Mark.stack @stack end # Set common stack for all marks def Mark.stack=(v) @stack=v end # Create a new Mark def initialize(opts=Hash.new) @_properties=[] opts.each {|k,v| self.send("#{k}=",v) if self.respond_to? k } @defs={} @child_index=-1 @index=-1 @index_defined = true @scale=1 @scene=nil end # The mark type; a lower name. The type name controls rendering # behavior, and unless the rendering engine is extended, must be one of the # built-in concrete mark types: area, bar, dot, image, label, line, panel, # rule, or wedge. def type "mark" end # Default properties for all mark types. By default, the data array is the # parent data as a single-element array; if the data property is not # specified, this causes each mark to be instantiated as a singleton # with the parents datum. The visible property is true by default, # and the reverse property is false. def self.defaults Mark.new({:data=>lambda {|d| [d]}, :visible=>true, :antialias=>true, :events=>'painted'}) end # Sets the prototype of this mark to the specified mark. Any properties not # defined on this mark may be inherited from the specified prototype mark, # or its prototype, and so on. The prototype mark need not be the same # type of mark as this mark. (Note that for inheritance to be useful, # properties with the same name on different mark types should # have equivalent meaning.) def extend(proto) @proto=proto @target=proto.target self end # Find the instances of this mark that match source. # @see Anchor def instances(source) mark = self _index = [] scene=nil while (!(scene = mark.scene)) do source = source.parent; _index.push(OpenStruct.new({:index=>source.index, :child_index=>mark.child_index})) mark = mark.parent end while (_index.size>0) do i = _index.pop() scene = scene[i.index].children[i.child_index] end # # When the anchor target is also an ancestor, as in the case of adding # to a panel anchor, only generate one instance per panel. Also, set # the margins to zero, since they are offset by the enclosing panel. # / if (index_defined?) s = scene[self.index].dup s.right = s.top = s.left = s.bottom = 0; return [s]; end scene end #Returns the previous instance of this mark in the scene graph, or # null if this is the first instance. # # @return a node in the scene graph, or null. def sibling (self.index==0) ? nil: self.scene[self.index-1] end # Returns the current instance in the scene graph of this mark, # in the previous instance of the enclosing parent panel. # May return null if this instance could not be found. # # @return a node in the scene graph, or null. def cousin par=self.parent s= par ? par.sibling : nil (s and s.children) ? s.children[self.child_index][self.index] : nil end def add(type) parent.add(type).extend(self) end # Returns an anchor with the specified name. All marks support the five # standard anchor names: # * top # * left # * center # * bottom # * right # # In addition to positioning properties (left, right, top bottom), the # anchors support text rendering properties (text-align, text-baseline). # Text is rendered to appear inside the mark by default. # # To facilitate stacking, anchors are defined in terms of their opposite # edge. For example, the top anchor defines the bottom property, # such that the mark extends upwards; the bottom anchor instead defines # the top property, such that the mark extends downwards. See also Layout::Stack # # While anchor names are typically constants, the anchor name is a true # property, which means you can specify a function to compute the anchor name # dynamically. See the Anchor#name property for details. # # @param [String] name the anchor name; either a string or a property function. # @return [Anchor] the new anchor. def anchor(name='center') mark_anchor(name) end # Implementation of mark anchor def mark_anchor(name="center") # :nodoc: anchor=Rubyvis::Anchor. new(self). name(name). data(lambda { pp self.scene.target if $DEBUG a=self.scene.target.map {|s| puts "s:#{s.data}" if $DEBUG; s.data} p a if $DEBUG a }). visible(lambda { self.scene.target[self.index].visible }). id(lambda {self.scene.target[self.index].id}). left(lambda { s = self.scene.target[self.index] w = s.width w||=0 if ['bottom','top','center'].include?(self.name) s.left + w / 2.0 elsif self.name=='left' nil else s.left + w end }). top(lambda { s = self.scene.target[self.index] h = s.height h||= 0 if ['left','right','center'].include? self.name s.top+h/2.0 elsif self.name=='top' nil else s.top + h end }).right(lambda { s = self.scene.target[self.index] self.name() == "left" ? s.right + (s.width ? s.width : 0) : nil; }).bottom(lambda { s = self.scene.target[self.index]; self.name() == "top" ? s.bottom + (s.height ? s.height : 0) : nil; }).text_align(lambda { if ['bottom','top','center'].include? self.name 'center' elsif self.name=='right' 'right' else 'left' end }).text_baseline(lambda { if ['right','left','center'].include? self.name 'middle' elsif self.name=='top' 'top' else 'bottom' end }) return anchor end # Computes the implied properties for this mark for the specified # instance s in the scene graph. Implied properties are those with # dependencies on multiple other properties; for example, the width property # may be implied if the left and right properties are set. This method can be # overridden by concrete mark types to define new implied properties, if # necessary. # # @param s a node in the scene graph; the instance of the mark to build. def build_implied(s) # :nodoc: mark_build_implied(s) end def mark_build_implied(s) # :nodoc: l=s.left r=s.right t=s.top b=s.bottom prop=self.properties #p self w = (prop[:width]) ? s.width : 0 h = (prop[:height]) ? s.height : 0 width=self.parent ? self.parent.width() : (w+(l.nil? ? 0 : l)+(r.nil? ? 0 : r)) #puts (self.parent)? "parent width: #{self.parent.width}" : "no parent" if $DEBUG #p prop.sort if $DEBUG puts "build implied #{type}: l:#{l},r:#{r},t:#{t},b:#{b}, w:#{prop[:width]} #{w},h: #{prop[:height]} #{h}, width:#{width}" if $DEBUG if w.nil? r||=0 l||=0 w=width-r-l elsif r.nil? if l.nil? r=(width-w) / (2.0) l=r else r=width-w-l end elsif l.nil? l=width-w-r end height=self.parent ? self.parent.height(): (h+(t.nil? ? 0 : t )+(b.nil? ? 0 : b)) if h.nil? t||=0 b||=0 h=height-t-b elsif b.nil? if t.nil? t=(height-h) / 2.0 b=t else b=height-h-t end elsif t.nil? t=height-h-b end s.left=l s.right=r s.top=t s.bottom=b puts "Post->left: #{l},right:#{r},top:#{t},bottom:#{b}, width:#{w}, height:#{h}" if $DEBUG s.width=w if prop[:width] #puts "width:#{s.width}" if $DEBUG s.height=h if prop[:height] s.text_style=Rubyvis::Color.transparent if prop[:text_style] and !s.text_style s.fill_style=Rubyvis::Color.transparent if prop[:fill_style] and !s.fill_style s.stroke_style=Rubyvis::Color.transparent if prop[:stroke_style] and !s.stroke_style end # Renders this mark, including recursively rendering all child marks if this is # a panel. This method finds all instances of this mark and renders them. This # method descends recursively to the level of the mark to be rendered, finding # all visible instances of the mark. After the marks are rendered, the scene # and index attributes are removed from the mark to restore them to a clean # state. # #
If an enclosing panel has an index property set (as is the case inside in # an event handler), then only instances of this mark inside the given instance # of the panel will be rendered; otherwise, all visible instances of the mark # will be rendered. def render parent=self.parent @stack=Mark.stack if parent and !self.root.scene root.render() return end @indexes=[] mark=self until mark.parent.nil? @indexes.unshift(mark.child_index) end bind while(parent and !parent.respond_to? :index) do parent=parent.parent end self.context( parent ? parent.scene : nil, parent ? parent.index : -1, lambda {render_render(self.root, 0,1)}) end def render_render(mark,depth,scale) # :nodoc: mark.scale=scale if (depth < @indexes.size) @stack.unshift(nil) if (mark.index_defined?) render_instance(mark, depth, scale); else mark.scene.size.times {|i| mark.index = i; render_instance(mark, depth, scale); } mark.delete_index end stack.shift else mark.build Rubyvis.Scene.scale = scale; Rubyvis.Scene.update_all(mark.scene); end mark.scale=nil end def render_instance(mark,depth,scale) # :nodoc: s=mark.scene[mark.index] if s.visible child_index=@indexes[depth] child=mark.children[child_index] child_index.times {|i| mark.children[i].scene=s.children[i] } Mark.stack[0]=s.data if (child.scene) render_render(child,depth+1,scale*s.transform.k) else child.scene=s.children[child_index] render_render(child, depth+1,scale*s.transform.k) child.scene=nil end child_index.times {|i| mark.children[i].scene=nil } end end def bind_bind(mark) begin mark._properties.each {|v| #p v.name k=v.name if !@seen.has_key?(k) @seen[k]=v case k when :data @_data=v when :visible @_required.push(v) when :id @_required.push(v) else @types[v._type].push(v) end end } end while(mark = mark.proto) end private :bind_bind, :render_render, :render_instance # @private In the bind phase, inherited property definitions are cached so they # do not need to be queried during build. def bind mark_bind end def mark_bind() # :nodoc: @seen={} @types={1=>[],2=>[],3=>[]} @_data=nil @_required=[] bind_bind(self) bind_bind((self.class).defaults) @types[1].reverse! @types[3].reverse! mark=self begin properties.each {|name,v| if !@seen[name] @seen[name]=Property.new(:name=>name, :_type=>2, :value=>nil) @types[2].push(@seen[name]) end } end while(mark=mark.proto) @binds=OpenStruct.new({:properties=>@seen, :data=>@_data, :required=>@_required, :optional=>@types[1]+@types[2]+@types[3] }) end def context_apply(scene,index) Mark.scene=scene Mark.index=index return if(!scene) that=scene.mark mark=that ancestors=[] begin ancestors.push(mark) Mark.stack.push(scene[index].data) mark.index=index mark.scene=scene index=scene.parent_index scene=scene.parent end while(mark=mark.parent) k=1 ancestors.size.times {|ic| i=ancestors.size-ic-1 mark=ancestors[i] mark.scale=k k=k*mark.scene[mark.index].transform.k } if (that.children) n=that.children.size n.times {|i| mark=that.children[i] mark.scene=that.scene[that.index].children[i] mark.scale=k } end end def context_clear(scene,index) return if !scene that=scene.mark mark=nil if(that.children) that.children.size.times {|i| mark=that.children[i] mark.scene=nil mark.scale=nil } end mark=that begin Mark.stack.pop if(mark.parent) mark.scene=nil mark.scale=nil end mark.index=nil end while(mark=mark.parent) end def context(scene,index,f) proto=Mark stack=Mark.stack oscene=Mark.scene oindex=Mark.index context_clear(oscene,oindex) context_apply(scene,index) begin f.js_apply(self, stack) ensure context_clear(scene,index) context_apply(oscene,oindex) end end # Evaluates properties and computes implied properties. Properties are # stored in the Mark.scene array for each instance of this mark. # # As marks are built recursively, the Mark.index property is updated to # match the current index into the data array for each mark. Note that the # index property is only set for the mark currently being built and its # enclosing parent panels. The index property for other marks is unset, but is # inherited from the global +Mark+ class prototype. This allows mark # properties to refer to properties on other marks in the same panel # conveniently; however, in general it is better to reference mark instances # specifically through the scene graph rather than depending on the magical # behavior of Mark#index. # # The root scene array has a special property, data, which stores # the current data stack. The first element in this stack is the current datum, # followed by the datum of the enclosing parent panel, and so on. The data # stack should not be accessed directly; instead, property functions are passed # the current data stack as arguments. # #
The evaluation of the data and visible properties is # special. The data property is evaluated first; unlike the other # properties, the data stack is from the parent panel, rather than the current # mark, since the data is not defined until the data property is evaluated. # The visible property is subsequently evaluated for each instance; # only if true will the {@link #buildInstance} method be called, evaluating # other properties and recursively building the scene graph. # #
If this mark is being re-built, any old instances of this mark that no # longer exist (because the new data array contains fewer elements) will be # cleared using {@link #clearInstance}. # # @param parent the instance of the parent panel from the scene graph. def build scene=self.scene stack=Mark.stack if(!scene) self.scene=SceneElement.new scene=self.scene scene.mark=self scene.type=self.type scene.child_index=self.child_index if(self.parent) scene.parent=self.parent.scene scene.parent_index=self.parent.index end end # Resolve anchor target #puts "Resolve target" if(self.target) scene.target=self.target.instances(scene) end #pp self.binds data=self.binds.data #puts "stack:#{stack}" #puts "data_value:#{data.value}" data=(data._type & 1)>0 ? data.value.js_apply(self, stack) : data.value #puts "data:#{data}" stack.unshift(nil) scene.size=data.size data.each_with_index {|d, i| Mark.index=i self.index=i s=scene[i] if !s scene[i]=s=SceneElement.new end stack[0]=data[i] s.data=data[i] build_instance(s) } Mark.index=-1 delete_index stack.shift() self end # @private def build_instance(s) # :nodoc: mark_build_instance(s) end # @private def mark_build_instance(s1) # :nodoc: build_properties(s1, self.binds.required) if s1.visible build_properties(s1, self.binds.optional) build_implied(s1) end end # Evaluates the specified array of properties for the specified # instance s in the scene graph. # # @param s a node in the scene graph; the instance of the mark to build. # @param properties an array of properties. def build_properties(ss, props) #p props props.each do |prop| v=prop.value # p "#{prop.name}=#{v}" if prop._type==3 v=v.js_apply(self, Mark.stack) end ss.send((prop.name.to_s+"=").to_sym, v) end end # @todo implement def event(type,handler) #@_handlers[type]=handler self end end end require 'rubyvis/mark/anchor' require 'rubyvis/mark/bar' require 'rubyvis/mark/panel' require 'rubyvis/mark/area' require 'rubyvis/mark/line' require 'rubyvis/mark/rule' require 'rubyvis/mark/label' require 'rubyvis/mark/dot' require 'rubyvis/mark/wedge'