1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
|
# Allows to customize datapoint shapes
class DataPoint
# magic string that defines if a shape is intented to be overlayed to a default.
# this allowes to have strike through of a circle etc.
OVERLAY = "OVERLAY"
DEFAULT_SHAPE = lambda{|x,y,line| ["circle", {
"cx" => x,
"cy" => y,
"r" => "2.5",
"class" => "dataPoint#{line}"
}]
} unless defined? DEFAULT_SHAPE
CRITERIA = [] unless defined? CRITERIA
# matchers are class scope. Once configured, each DataPoint instance will have
# access to the same matchers
# @param matchers [Array] multiple arrays of the following form 2 or 3 elements:
# [ regex ,
# lambda taking three arguments (x,y, line_number for css)
# -> return value of the lambda must be an array: [svg tag name,
# Hash with attributes for the svg tag, e.g. "points" and "class",
# make sure to check source code of you graph type for valid css class.],
# "OVERLAY" (magic string, if specified, puts the shape on top of existing datapoint)
# ]
# @example
# DataPoint.configure_shape_criteria(
# [/.*/, lambda{|x,y,line|
# [ 'polygon',
# {
# "points" => "#{x-1.5},#{y+2.5} #{x+1.5},#{y+2.5} #{x+1.5},#{y-2.5} #{x-1.5},#{y-2.5}",
# "class" => "dataPoint#{line}"
# }
# ]
# }]
# )
def DataPoint.configure_shape_criteria(*matchers)
CRITERIA.push(*matchers)
end
#
def DataPoint.reset_shape_criteria
CRITERIA.clear
end
# creates a new DataPoint
# @param x [Numeric] x coordinates of the point
# @param y [Numeric] y coordinates of the point
# @param line [Fixnum] line index of the current dataset (e.g. when multiple times Graph.add_data()), can be used to reference to the correct css class
def initialize(x, y, line)
@x = x
@y = y
@line = line
end
# Returns different shapes depending on datapoint descriptions, if shape criteria have been configured.
# The definded criteria are evaluated in two stages, first the ones, which are note defined as overlay.
# then the "OVERLAY"
# @param datapoint_description [String] description or label of the current datapoint
# @return [Array<Array>] see example
# @example Return value
# # two dimensional array, the splatted (*) inner array can be used as argument to REXML::add_element
# [["svgtag", {"points" => "", "class"=> "dataPoint#{line}" } ], ["svgtag", {"points"=>"", "class"=> ""}], ...]
# @exmple Usage
# dp = DataPoint.new(x, y, line).shape(data[:description])
# # for each svg we insert it to the graph
# dp.each {|s| @graph.add_element( *s )}
#
def shape(datapoint_description=nil)
# select all criteria with size 2, and collect rendered lambdas in an array
shapes = CRITERIA.select {|criteria|
criteria.size == 2
}.collect {|regexp, proc|
proc.call(@x, @y, @line) if datapoint_description =~ regexp
}.compact
# if above did not render anything use the defalt shape
shapes = [DEFAULT_SHAPE.call(@x, @y, @line)] if shapes.empty?
overlays = CRITERIA.select { |criteria|
criteria.last == OVERLAY
}.collect { |regexp, proc|
proc.call(@x, @y, @line) if datapoint_description =~ regexp
}.compact
return shapes + overlays
end
end
|